Compare commits

..
Author SHA1 Message Date
zOnlyKroks 869aca5e99 [bausystem]: Remove unused key 2026-05-04 19:07:48 +02:00
zOnlyKroks 60c72c3bdc [bausystem]: Fix inability to change replay lock status 2026-05-04 19:04:18 +02:00
Chaoscaot d0535d0c47 Merge pull request '[fightsystem]: Fix broken kit system' (#322) from bugfix/fightsystem-broken-kits into main
Reviewed-on: SteamWar/SteamWar#322
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2026-05-04 18:31:25 +02:00
zOnlyKroks 79fa09e39b Chaos zufrieden stellen 2026-05-04 18:27:28 +02:00
Chaoscaot 4010c2125c Refactor lazy loading of dependents and relations to return lists
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-05-04 13:41:07 +02:00
zOnlyKroks 32de0077de [fightsystem]: Rework blacklist to whitelist 2026-05-03 14:15:59 +02:00
Chaoscaot bd53e016c5 Merge pull request 'feat: Add highligh flag for tracer' (#320) from BauSystem/add-highlight-trace-flag into main
Reviewed-on: SteamWar/SteamWar#320
Reviewed-by: zOnlyKroks <zonlyknox@gmail.com>
2026-05-03 13:04:37 +02:00
zOnlyKroks 5b1ed644d1 [fightsystem]: Fix broken kit system 2026-05-03 12:52:23 +02:00
D4rkr34lm a41787d89d Add flag 2026-05-02 13:36:32 +02:00
Chaoscaot 8e392b56c3 Merge pull request 'fix: windcharge check' (#318) from fix/mssing-string-get into main
Reviewed-on: SteamWar/SteamWar#318
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2026-05-01 12:30:42 +02:00
D4rkr34lm 42feadcd2d Add left out name get 2026-05-01 11:53:57 +02:00
Chaoscaot 15f0344416 Merge pull request 'feat: Add temporary hardcoded windcharge check to autockecker for WGS' (#316) from fix/enable-windcharges-for-wgs into main
Reviewed-on: SteamWar/SteamWar#316
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2026-05-01 11:03:38 +02:00
D4rkr34lm c90a977ab2 use same hacky impl as in fight system 2026-05-01 11:02:33 +02:00
D4rkr34lm b186228f4e Revert "swap out used api"
This reverts commit 5f38474809.
2026-05-01 10:59:23 +02:00
D4rkr34lm 5f38474809 swap out used api 2026-05-01 10:54:53 +02:00
Manuel Frohn 4f27320548 Implement hardcoded windcharge check 2026-04-30 14:44:54 +02:00
D4rkr34lm ba7bd1f1dd Configure V21 impl 2026-04-30 12:52:46 +02:00
YoyoNow fbe70e7ead Improve CheckCommand for WGS 2026-04-24 10:09:56 +02:00
YoyoNow 30a499be1d Hotfix CheckCommand
Remove uneeded stuff in SQLWrapper
2026-04-23 23:33:02 +02:00
Chaoscaot 86e212fe42 Fix Kits
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-04-23 18:23:13 +02:00
YoyoNow 4a646e6be0 Improve WindchargeStopper 2026-04-23 12:09:17 +02:00
YoyoNow bc0dc1925e Merge pull request 'Add WindchargeStopper to handle wind charge entity removal based on fight boundaries' (#182) from windcharges into main
Reviewed-on: SteamWar/SteamWar#182
Reviewed-by: YoyoNow <yoyonow@noreply.localhost>
2026-04-23 12:04:34 +02:00
YoyoNow c3af4dbc68 Merge pull request 'Add CreateKitCommand' (#271) from add-kit-command into main
Reviewed-on: SteamWar/SteamWar#271
2026-04-23 12:01:18 +02:00
YoyoNow 703639537d Fix CreateKitCommand 2026-04-23 11:59:28 +02:00
YoyoNow 9ac3bf6a6c Redesign GameModeConfig and SchematicType 2026-04-23 11:54:50 +02:00
YoyoNow 41ea6c9407 Fix ViewFlag.ADVANCED 2026-04-23 08:27:43 +02:00
YoyoNow 67e9a3544e Fix Tablist.disable removing gm knowledge 2026-04-20 13:45:16 +02:00
YoyoNow f69ae3e77b Fix BauLock 2026-04-20 13:31:45 +02:00
Chaoscaot 25116c3865 Add CreateKitCommand
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-01-03 02:30:36 +01:00
Chaoscaot c0163d813e Add WindchargeStopper to handle wind charge entity removal based on fight boundaries
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-10-28 23:00:53 +01:00
43 changed files with 701 additions and 982 deletions
@@ -0,0 +1,63 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.dev;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import java.io.File;
import java.io.IOException;
@Linked
public class CreateKitCommand extends SWCommand {
public CreateKitCommand() {
super("createkit");
if (!BauSystem.DEV_SERVER) unregister();
}
@Register
public void onCommand(Player player, String name) {
YamlConfiguration yaml = new YamlConfiguration();
yaml.set("Items", player.getInventory().getContents());
yaml.set("Armor", player.getInventory().getArmorContents());
yaml.set("Effects", player.getActivePotionEffects());
yaml.set("LeaderAllowed", true);
yaml.set("MemberAllowed", true);
yaml.set("EnterStage", 0);
yaml.set("TNT", true);
YamlConfiguration kits = new YamlConfiguration();
kits.set("Kits." + name, yaml);
try {
kits.save(new File("new.kits.yaml"));
player.sendMessage("Kit created!");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@@ -135,7 +135,7 @@ public abstract class ViewFlag {
} }
Location secoundLocation; Location secoundLocation;
if (previousVelocity.getX() >= previousVelocity.getZ()) { if (Math.abs(previousVelocity.getX()) >= Math.abs(previousVelocity.getZ())) {
secoundLocation = previous.getLocation().clone().add(delta.getX(), delta.getY(), 0); secoundLocation = previous.getLocation().clone().add(delta.getX(), delta.getY(), 0);
} else { } else {
secoundLocation = previous.getLocation().clone().add(0, delta.getY(), delta.getZ()); secoundLocation = previous.getLocation().clone().add(0, delta.getY(), delta.getZ());
@@ -198,6 +198,16 @@ public abstract class ViewFlag {
} }
}; };
public static ViewFlag HIGHLIGHT = new ViewFlag(true, false, "highlight", "h") {
@Override
public void modify(REntityServer server, List<TraceEntity> entities) {
for (TraceEntity entity : entities) {
entity.setGlowing(true);
}
}
};
/** /**
* Name of the flag * Name of the flag
*/ */
@@ -90,7 +90,7 @@ class EventGroup(id: EntityID<Int>) : IntEntity(id) {
set(value) { set(value) {
groupPointsPerDraw = value groupPointsPerDraw = value
} }
val dependents by lazy { EventRelation.getGroupRelations(this).toList() } val dependents by lazy { EventRelation.getGroupRelations(this) }
val lastFight by lazy { Optional.ofNullable(fights.maxByOrNull { it.startTime }) } val lastFight by lazy { Optional.ofNullable(fights.maxByOrNull { it.startTime }) }
fun getId() = id.value fun getId() = id.value
@@ -51,11 +51,11 @@ class EventRelation(id: EntityID<Int>) : IntEntity(id) {
@JvmStatic @JvmStatic
fun getFightRelations(fight: EventFight) = fun getFightRelations(fight: EventFight) =
useDb { find { (EventRelationTable.fromId eq fight.id.value) and (EventRelationTable.fromType eq FromType.FIGHT) } } useDb { find { (EventRelationTable.fromId eq fight.id.value) and (EventRelationTable.fromType eq FromType.FIGHT) }.toList() }
@JvmStatic @JvmStatic
fun getGroupRelations(group: EventGroup) = fun getGroupRelations(group: EventGroup) =
useDb { find { (EventRelationTable.fromId eq group.id.value) and (EventRelationTable.fromType eq FromType.GROUP) } } useDb { find { (EventRelationTable.fromId eq group.id.value) and (EventRelationTable.fromType eq FromType.GROUP) }.toList() }
@JvmStatic @JvmStatic
fun create(fight: EventFight, fightTeam: FightTeam, fromType: FromType, fromId: Int, fromPlace: Int) = useDb { fun create(fight: EventFight, fightTeam: FightTeam, fromType: FromType, fromId: Int, fromPlace: Int) = useDb {
@@ -52,6 +52,10 @@ public final class GameModeConfig<M, W> {
private static final Map<String, GameModeConfig<?, String>> byGameName; private static final Map<String, GameModeConfig<?, String>> byGameName;
private static final Map<SchematicType, GameModeConfig<?, String>> bySchematicType; private static final Map<SchematicType, GameModeConfig<?, String>> bySchematicType;
public static <M> Collection<GameModeConfig<M, String>> getAll() {
return (Collection) byFileName.values();
}
public static <M> GameModeConfig<M, String> getByFileName(File file) { public static <M> GameModeConfig<M, String> getByFileName(File file) {
return (GameModeConfig<M, String>) byFileName.get(file.getName()); return (GameModeConfig<M, String>) byFileName.get(file.getName());
} }
@@ -87,8 +91,24 @@ public final class GameModeConfig<M, W> {
byFileName = new HashMap<>(); byFileName = new HashMap<>();
byGameName = new HashMap<>(); byGameName = new HashMap<>();
bySchematicType = new HashMap<>(); bySchematicType = new HashMap<>();
SchematicType.values();
DEFAULTS = SQLWrapper.impl.loadGameModeConfig(null); DEFAULTS = SQLWrapper.impl.loadGameModeConfig(null);
init();
}
public static void init() {
byFileName.clear();
byGameName.clear();
bySchematicType.clear();
File folder = SQLWrapper.impl.getSchemTypesFolder();
if (!folder.exists()) return;
if (!folder.isDirectory()) return;
for (File file : Objects.requireNonNull(folder.listFiles())) {
if (!file.getName().endsWith(".yml")) continue;
if (file.getName().endsWith(".kits.yml")) continue;
SQLWrapper.impl.loadGameModeConfig(file);
}
byFileName.values().forEach(gameModeConfig -> { byFileName.values().forEach(gameModeConfig -> {
List<SchematicType> subTypes = Collections.unmodifiableList(gameModeConfig.Schematic.SubTypesStrings.stream() List<SchematicType> subTypes = Collections.unmodifiableList(gameModeConfig.Schematic.SubTypesStrings.stream()
@@ -671,9 +691,9 @@ public final class GameModeConfig<M, W> {
loaded = loader.canLoad(); loaded = loader.canLoad();
Size = new SizeConfig(loader.with("Size")); Size = new SizeConfig(loader.with("Size"));
Inset = new InsetConfig(loader.with("Inset")); Inset = new InsetConfig(loader.with("Inset"));
Type = loader.getSchematicType("Type", "Normal"); Type = null;
SubTypesStrings = loader.getStringList("SubTypes"); SubTypesStrings = loader.getStringList("SubTypes");
SubTypes = loader.getSchematicTypeList("SubTypes"); SubTypes = new ArrayList<>();
Shortcut = loader.getString("Shortcut", ""); Shortcut = loader.getString("Shortcut", "");
Material = loader.getMaterial("Material", "STONE_BUTTON"); Material = loader.getMaterial("Material", "STONE_BUTTON");
ManualCheck = loader.getBoolean("ManualCheck", true); ManualCheck = loader.getBoolean("ManualCheck", true);
@@ -36,8 +36,5 @@ public interface SQLWrapper<M> {
return Collections.emptyList(); return Collections.emptyList();
} }
default void processSchematicType(GameModeConfig<?, String> gameModeConfig) {
}
void additionalExceptionMetadata(StringBuilder builder); void additionalExceptionMetadata(StringBuilder builder);
} }
@@ -19,11 +19,7 @@
package de.steamwar.sql package de.steamwar.sql
import java.io.File
import java.util.* import java.util.*
import java.util.Locale
import java.util.Locale.getDefault
import java.util.stream.Collectors
data class SchematicType( data class SchematicType(
val name: String, val name: String,
@@ -47,58 +43,65 @@ data class SchematicType(
@JvmField @JvmField
val Normal = SchematicType("Normal", "", Type.NORMAL, null, "STONE_BUTTON", false) val Normal = SchematicType("Normal", "", Type.NORMAL, null, "STONE_BUTTON", false)
private val types: List<SchematicType> private val types: MutableList<SchematicType> = mutableListOf()
private val fromDB: Map<String, SchematicType>? private val fromDB: MutableMap<String, SchematicType> = mutableMapOf()
init { init {
val tmpTypes = mutableListOf<SchematicType>() GameModeConfig.init()
val tmpFromDB = mutableMapOf<String, SchematicType>() init()
tmpTypes.add(Normal)
tmpFromDB[Normal.toDB()] = Normal
val folder = SQLWrapper.impl.schemTypesFolder
if (folder.exists()) {
for (configFile in Arrays.stream<File?>(folder.listFiles { _, name ->
name.endsWith(
".yml"
) && !name.endsWith(".kits.yml")
}).sorted().collect(Collectors.toList())) {
val gameModeConfig = SQLWrapper.impl.loadGameModeConfig(configFile)
if (gameModeConfig.Schematic.Type == null) continue
if (tmpFromDB.containsKey(gameModeConfig.Schematic.Type.toDB())) continue
val current = gameModeConfig.Schematic.Type
if (gameModeConfig.CheckQuestions.isNotEmpty()) {
val checkType = current.checkType
tmpTypes.add(checkType!!)
tmpFromDB[checkType.toDB()] = checkType
}
tmpTypes.add(current)
tmpFromDB[current.toDB()] = current
SQLWrapper.impl.processSchematicType(gameModeConfig)
}
}
types = tmpTypes.toList()
fromDB = tmpFromDB.toMap()
} }
@JvmStatic @JvmStatic
fun values() = types fun init() {
types.clear()
fromDB.clear()
types.add(Normal)
fromDB[Normal.toDB()] = Normal
for (gameModeConfig in GameModeConfig.getAll<Any>()) {
val type = gameModeConfig.Schematic.Type
?: continue
if (fromDB.containsKey(type.toDB())) continue
types.add(type)
fromDB[type.toDB()] = type
if (gameModeConfig.CheckQuestions.isNotEmpty() && type.checkType != null) {
types.add(type.checkType)
fromDB[type.checkType.toDB()] = type.checkType
}
}
}
@JvmStatic @JvmStatic
fun fromDB(value: String) = fromDB?.let { it[value.lowercase()] } fun values() =
types
@JvmStatic
fun fromDB(value: String) =
fromDB[value.lowercase()]
} }
fun name() = name fun name() =
fun toDB() = name.lowercase() name
fun check() = type == Type.CHECK_TYPE fun toDB() =
fun fightType() = type == Type.FIGHT_TYPE name.lowercase()
fun writeable() = type == Type.NORMAL
fun checkType() = if (manualCheck) checkType else this fun check() =
fun isAssignable() = type == Type.NORMAL || (type == Type.FIGHT_TYPE && checkType != null) || !manualCheck type == Type.CHECK_TYPE
fun fightType() =
type == Type.FIGHT_TYPE
fun writeable() =
type == Type.NORMAL
fun checkType() =
if (manualCheck) checkType else this
fun isAssignable() =
type == Type.NORMAL || (type == Type.FIGHT_TYPE && checkType != null) || !manualCheck
enum class Type { enum class Type {
NORMAL, NORMAL,
@@ -198,7 +198,7 @@ class SteamwarUser(id: EntityID<Int>): IntEntity(id) {
var discordId by SteamwarUserTable.discordId var discordId by SteamwarUserTable.discordId
val punishments by lazy { Punishment.getPunishmentsOfPlayer(id.value) } private val punishments by lazy { Punishment.getPunishmentsOfPlayer(id.value) }
private val perms by lazy { UserPerm.getPerms(id.value) } private val perms by lazy { UserPerm.getPerms(id.value) }
private val prefix by lazy { perms.firstOrNull { UserPerm.prefixes.containsKey(it) }?.let { UserPerm.prefixes[it]} ?: UserPerm.emptyPrefix } private val prefix by lazy { perms.firstOrNull { UserPerm.prefixes.containsKey(it) }?.let { UserPerm.prefixes[it]} ?: UserPerm.emptyPrefix }
@@ -66,7 +66,6 @@ class Team(id: EntityID<Int>) : IntEntity(id) {
var deleted by TeamTable.deleted var deleted by TeamTable.deleted
private set private set
val members by lazy { useDb { SteamwarUserTable.select(SteamwarUserTable.id).where { SteamwarUserTable.team eq teamId }.map { it[SteamwarUserTable.id].value } } } val members by lazy { useDb { SteamwarUserTable.select(SteamwarUserTable.id).where { SteamwarUserTable.team eq teamId }.map { it[SteamwarUserTable.id].value } } }
val membersUser by lazy { useDb { SteamwarUser.find { SteamwarUserTable.team eq teamId }.toList() } }
fun size() = useDb { SteamwarUser.find { SteamwarUserTable.team eq teamId }.count().toInt() } fun size() = useDb { SteamwarUser.find { SteamwarUserTable.team eq teamId }.count().toInt() }
fun disband(user: SteamwarUser) = useDb { fun disband(user: SteamwarUser) = useDb {
@@ -79,7 +79,7 @@ class TeamTeilnahme(id: EntityID<CompositeID>) : CompositeEntity(id) {
@JvmStatic @JvmStatic
fun getEvents(teamId: Int) = useDb { fun getEvents(teamId: Int) = useDb {
find { TeamTeilnahmeTable.teamId eq teamId }.mapNotNull { Event.byId(it.eventId.value) }.toSet() find { TeamTeilnahmeTable.teamId eq teamId }.map { Event.byId(it.eventId.value) }.toSet()
} }
@JvmStatic @JvmStatic
@@ -42,4 +42,5 @@ dependencies {
compileOnly(libs.fastutil) compileOnly(libs.fastutil)
compileOnly(libs.authlib) compileOnly(libs.authlib)
compileOnly(project(":FightSystem:FightSystem_14"))
} }
@@ -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.listener;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentTask;
import net.minecraft.world.entity.projectile.windcharge.WindCharge;
import org.bukkit.Location;
public class WindchargeStopper21 implements WindchargeStopper.IWindchargeStopper {
public WindchargeStopper21() {
new StateDependentTask(true, FightState.Running, this::run, 1, 1);
}
private static final int middleLine = Config.SpecSpawn.getBlockZ();
private static final Class<?> windChargeClass = WindCharge.class;
private void run() {
Recording.iterateOverEntities(windChargeClass::isInstance, entity -> {
Location location = entity.getLocation();
Location prevLocation = location.clone().subtract(entity.getVelocity());
boolean passedMiddle = location.getBlockZ() > middleLine && prevLocation.getBlockZ() > middleLine ||
location.getBlockZ() < middleLine && prevLocation.getBlockZ() < middleLine;
if(!passedMiddle) {
entity.remove();
}
});
}
}
@@ -0,0 +1,29 @@
/*
* 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.utils;
import org.bukkit.inventory.ItemStack;
public class FlatteningWrapper21 extends FlatteningWrapper14 {
@Override
public boolean hasAttributeModifier(ItemStack stack) {
return stack.hasItemMeta() && stack.getItemMeta() != null && stack.getItemMeta().hasAttributeModifiers();
}
}
@@ -38,6 +38,27 @@ public class ReflectionWrapper21 implements ReflectionWrapper {
FORBIDDEN_TYPES.add(DataComponentTypes.BLOCKS_ATTACKS); FORBIDDEN_TYPES.add(DataComponentTypes.BLOCKS_ATTACKS);
FORBIDDEN_TYPES.add(DataComponentTypes.BUNDLE_CONTENTS); FORBIDDEN_TYPES.add(DataComponentTypes.BUNDLE_CONTENTS);
FORBIDDEN_TYPES.add(DataComponentTypes.CUSTOM_MODEL_DATA); FORBIDDEN_TYPES.add(DataComponentTypes.CUSTOM_MODEL_DATA);
FORBIDDEN_TYPES.add(DataComponentTypes.ATTRIBUTE_MODIFIERS);
FORBIDDEN_TYPES.add(DataComponentTypes.TOOL);
FORBIDDEN_TYPES.add(DataComponentTypes.WEAPON);
FORBIDDEN_TYPES.add(DataComponentTypes.FOOD);
FORBIDDEN_TYPES.add(DataComponentTypes.CONSUMABLE);
FORBIDDEN_TYPES.add(DataComponentTypes.POTION_CONTENTS);
FORBIDDEN_TYPES.add(DataComponentTypes.STORED_ENCHANTMENTS);
FORBIDDEN_TYPES.add(DataComponentTypes.CAN_BREAK);
FORBIDDEN_TYPES.add(DataComponentTypes.CAN_PLACE_ON);
FORBIDDEN_TYPES.add(DataComponentTypes.MAX_DAMAGE);
FORBIDDEN_TYPES.add(DataComponentTypes.USE_REMAINDER);
FORBIDDEN_TYPES.add(DataComponentTypes.USE_COOLDOWN);
FORBIDDEN_TYPES.add(DataComponentTypes.SUSPICIOUS_STEW_EFFECTS);
FORBIDDEN_TYPES.add(DataComponentTypes.CHARGED_PROJECTILES);
FORBIDDEN_TYPES.add(DataComponentTypes.INTANGIBLE_PROJECTILE);
FORBIDDEN_TYPES.add(DataComponentTypes.FIREWORKS);
FORBIDDEN_TYPES.add(DataComponentTypes.FIREWORK_EXPLOSION);
FORBIDDEN_TYPES.add(DataComponentTypes.EQUIPPABLE);
FORBIDDEN_TYPES.add(DataComponentTypes.REPAIR_COST);
FORBIDDEN_TYPES.add(DataComponentTypes.ENCHANTABLE);
} }
@Override @Override
@@ -0,0 +1,23 @@
/*
* 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.listener;
public class WindchargeStopper8 implements WindchargeStopper.IWindchargeStopper {
}
@@ -35,10 +35,7 @@ import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryAction; import org.bukkit.event.inventory.*;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@@ -81,25 +78,8 @@ public class PersonalKitCreator implements Listener {
if(!openKitCreators.containsKey(e.getWhoClicked())) if(!openKitCreators.containsKey(e.getWhoClicked()))
return; return;
Player player = (Player) e.getWhoClicked();
//Deny bad items
if(Kit.isBadItem(e.getCursor())) if(Kit.isBadItem(e.getCursor()))
e.setCancelled(true); e.setCancelled(true);
/* Should the inventory reset? */
if(e.getAction() != InventoryAction.PLACE_ALL)
return;
ItemStack[] items = e.getWhoClicked().getInventory().getContents();
for(int i = 0; i < items.length; i++){
ItemStack stack = items[i];
if(stack != null && i != e.getSlot())
return;
}
FightPlayer fightPlayer = Fight.getFightPlayer(player);
assert fightPlayer != null;
Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), () -> fightPlayer.getKit().loadToPlayer(player), 1);
} }
@EventHandler @EventHandler
@@ -117,7 +97,10 @@ public class PersonalKitCreator implements Listener {
if(backup == null) if(backup == null)
return; return;
backup.close(); InventoryType type = e.getInventory().getType();
if(type != InventoryType.PLAYER && type != InventoryType.CREATIVE) {
backup.close();
}
} }
@EventHandler @EventHandler
@@ -126,7 +109,7 @@ public class PersonalKitCreator implements Listener {
if(backup == null) if(backup == null)
return; return;
backup.close(); Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), backup::close, 1);
} }
@EventHandler @EventHandler
@@ -151,9 +134,11 @@ public class PersonalKitCreator implements Listener {
} }
private void close(){ private void close(){
openKitCreators.remove(player);
Kit kit1 = new Kit(kit.getName(), player); Kit kit1 = new Kit(kit.getName(), player);
kit1.removeBadItems(); kit1.removeBadItems();
openKitCreators.remove(player);
kit1.toPersonalKit(kit); kit1.toPersonalKit(kit);
backup.loadToPlayer(player); backup.loadToPlayer(player);
player.setGameMode(GameMode.SURVIVAL); player.setGameMode(GameMode.SURVIVAL);
@@ -0,0 +1,35 @@
/*
* 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.listener;
import de.steamwar.core.VersionDependent;
import de.steamwar.fightsystem.FightSystem;
import de.steamwar.linkage.Linked;
@Linked
public class WindchargeStopper {
static {
VersionDependent.getVersionImpl(FightSystem.getPlugin());
}
public interface IWindchargeStopper {
}
}
@@ -0,0 +1,37 @@
/*
* 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/>.
*/
plugins {
steamwar.java
}
dependencies {
compileOnly(project(":SpigotCore", "default"))
compileOnly(project(":SchematicSystem:SchematicSystem_Core", "default"))
compileOnly(libs.paperapi21) {
attributes {
// Very Hacky, but it works
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 21)
}
}
compileOnly(libs.nms21)
compileOnly(libs.fawe21)
}
@@ -0,0 +1,137 @@
/*
* 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.schematicsystem.autocheck;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.block.BaseBlock;
import de.steamwar.core.Core;
import de.steamwar.sql.GameModeConfig;
import org.bukkit.Material;
import java.util.*;
import java.util.stream.Collectors;
public class AutoChecker21 implements AutoChecker.IAutoChecker {
public AutoChecker.BlockScanResult scan(Clipboard clipboard, GameModeConfig<Material, String> type) {
AutoChecker.BlockScanResult result = new AutoChecker.BlockScanResult();
BlockVector3 min = clipboard.getMinimumPoint();
BlockVector3 max = clipboard.getMaximumPoint();
for (int x = min.getBlockX(); x <= max.getBlockX(); x++) {
for (int y = min.getBlockY(); y <= max.getBlockY(); y++) {
for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) {
final BaseBlock block = clipboard.getFullBlock(BlockVector3.at(x, y, z));
final Material material = Material.matchMaterial(block.getBlockType().getId());
if (material == null) {
continue;
}
result.getBlockCounts().merge(material, 1, Integer::sum);
if (AutoCheckerItems.impl.getInventoryMaterials().contains(material)) {
checkInventory(result, block, material, new BlockPos(x, y, z), type);
}
if (x == min.getBlockX() || x == max.getBlockX() || y == max.getBlockY() || z == min.getBlockZ() || z == max.getBlockZ()) {
result.getDesignBlocks().computeIfAbsent(material, m -> new ArrayList<>()).add(new BlockPos(x, y, z));
}
}
}
}
return result;
}
private static final Map<Material, Set<Material>> itemsInInv = new EnumMap<>(Material.class);
static {
itemsInInv.put(Material.BUCKET, EnumSet.of(Material.DISPENSER));
itemsInInv.put(Material.TNT, EnumSet.of(Material.CHEST, Material.BARREL, Material.SHULKER_BOX, Material.BLACK_SHULKER_BOX, Material.BLUE_SHULKER_BOX,
Material.BROWN_SHULKER_BOX, Material.CYAN_SHULKER_BOX, Material.GRAY_SHULKER_BOX, Material.GREEN_SHULKER_BOX, Material.LIGHT_BLUE_SHULKER_BOX,
Material.LIGHT_GRAY_SHULKER_BOX, Material.LIME_SHULKER_BOX, Material.MAGENTA_SHULKER_BOX, Material.ORANGE_SHULKER_BOX,
Material.PINK_SHULKER_BOX, Material.PURPLE_SHULKER_BOX, Material.RED_SHULKER_BOX, Material.WHITE_SHULKER_BOX, Material.YELLOW_SHULKER_BOX));
itemsInInv.put(Material.FIRE_CHARGE, EnumSet.of(Material.DISPENSER));
itemsInInv.put(Material.ARROW, EnumSet.of(Material.DISPENSER));
AutoCheckerItems.impl.getAllowedMaterialsInInventory().forEach(material -> itemsInInv.put(material, AutoCheckerItems.impl.getInventoryMaterials()));
}
private void checkInventory(AutoChecker.BlockScanResult result, BaseBlock block, Material material, BlockPos pos, GameModeConfig<Material, String> type) {
CompoundTag nbt = block.getNbtData();
if (nbt == null) {
result.getDefunctNbt().add(pos);
return;
}
if (material == Material.JUKEBOX && nbt.getValue().containsKey("RecordItem")) {
result.getRecords().add(pos);
return;
}
List<CompoundTag> items = nbt.getList("Items", CompoundTag.class);
if (items.isEmpty())
return; // Leeres Inventar
int counter = 0;
int windChargeCount = 0;
for (CompoundTag item : items) {
if (!item.containsKey("id")) {
result.getDefunctNbt().add(pos);
continue;
}
Material itemType = Material.matchMaterial(item.getString("id"));
if (itemType == null) // Leere Slots
continue;
if(type.Schematic.Type.getName().equals("wargearseason26") && material == Material.DISPENSER && itemType == Material.WIND_CHARGE) {
windChargeCount += item.getInt("count");
}
else if (!itemsInInv.getOrDefault(itemType, EnumSet.noneOf(Material.class)).contains(material)) {
result.getForbiddenItems().computeIfAbsent(pos, blockVector3 -> new HashSet<>()).add(itemType);
} else if (material == Material.DISPENSER && (itemType == Material.ARROW || itemType == Material.FIRE_CHARGE)) {
counter += Core.getVersion() >= 21 ? item.getInt("count") : item.getByte("Count");
}
if (item.containsKey("tag")) {
result.getForbiddenNbt().computeIfAbsent(pos, blockVector3 -> new HashSet<>()).add(itemType);
}
}
result.getDispenserItems().put(pos, counter);
result.getWindChargeCount().put(pos, windChargeCount);
}
@Override
public AutoCheckerResult check(Clipboard clipboard, GameModeConfig<Material, String> type) {
return AutoCheckerResult.builder().type(type).height(clipboard.getDimensions().getBlockY()).width(clipboard.getDimensions().getBlockX())
.depth(clipboard.getDimensions().getBlockZ()).blockScanResult(scan(clipboard, type))
.entities(clipboard.getEntities().stream().map(Entity::getLocation)
.map(blockVector3 -> new BlockPos(blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ()))
.collect(Collectors.toList()))
.build();
}
@Override
public AutoCheckerResult sizeCheck(Clipboard clipboard, GameModeConfig<Material, String> type) {
return AutoCheckerResult.builder().type(type).height(clipboard.getDimensions().getBlockY()).width(clipboard.getDimensions().getBlockX())
.depth(clipboard.getDimensions().getBlockZ()).build();
}
}
@@ -0,0 +1,49 @@
/*
* 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.schematicsystem.autocheck;
import org.bukkit.Material;
import java.util.EnumSet;
import java.util.Set;
public class AutoCheckerItems21 implements AutoCheckerItems {
private static final Set<Material> INVENTORY = EnumSet.of(Material.BARREL, Material.BLAST_FURNACE, Material.BREWING_STAND, Material.CAMPFIRE,
Material.CHEST, Material.DISPENSER, Material.DROPPER, Material.FURNACE, Material.HOPPER, Material.JUKEBOX, Material.SHULKER_BOX,
Material.WHITE_SHULKER_BOX, Material.ORANGE_SHULKER_BOX, Material.MAGENTA_SHULKER_BOX, Material.LIGHT_BLUE_SHULKER_BOX, Material.YELLOW_SHULKER_BOX,
Material.LIME_SHULKER_BOX, Material.PINK_SHULKER_BOX, Material.GRAY_SHULKER_BOX, Material.LIGHT_GRAY_SHULKER_BOX, Material.CYAN_SHULKER_BOX,
Material.PURPLE_SHULKER_BOX, Material.BLUE_SHULKER_BOX, Material.BROWN_SHULKER_BOX, Material.GREEN_SHULKER_BOX, Material.RED_SHULKER_BOX,
Material.BLACK_SHULKER_BOX, Material.SMOKER, Material.TRAPPED_CHEST);
private static final Set<Material> FLOWERS = EnumSet.of(Material.CORNFLOWER, Material.POPPY, Material.FERN, Material.DANDELION, Material.BLUE_ORCHID,
Material.ALLIUM, Material.AZURE_BLUET, Material.RED_TULIP, Material.ORANGE_TULIP, Material.WHITE_TULIP, Material.PINK_TULIP, Material.OXEYE_DAISY,
Material.LILY_OF_THE_VALLEY, Material.WITHER_ROSE, Material.SUNFLOWER, Material.DIAMOND_HORSE_ARMOR, Material.IRON_HORSE_ARMOR,
Material.GOLDEN_HORSE_ARMOR, Material.LEATHER_HORSE_ARMOR, Material.HONEY_BOTTLE, Material.LILAC, Material.ROSE_BUSH, Material.PEONY,
Material.TALL_GRASS, Material.LARGE_FERN);
@Override
public Set<Material> getInventoryMaterials() {
return INVENTORY;
}
@Override
public Set<Material> getAllowedMaterialsInInventory() {
return FLOWERS;
}
}
@@ -210,7 +210,6 @@ GUI_INFO_TYPE=§e{0}
GUI_INFO_DOWNLOAD=§eDownload GUI_INFO_DOWNLOAD=§eDownload
GUI_INFO_COLOR=Color translation GUI_INFO_COLOR=Color translation
GUI_INFO_REPLAY=Replay playback GUI_INFO_REPLAY=Replay playback
GUI_INFO_REPLAY_OFF=§7§lTurn off
GUI_INFO_REPLAY_TITLE=Lock playback permanently GUI_INFO_REPLAY_TITLE=Lock playback permanently
GUI_INFO_MEMBER=§eMembers GUI_INFO_MEMBER=§eMembers
GUI_INFO_MOVE=§eMove GUI_INFO_MOVE=§eMove
@@ -262,6 +261,8 @@ AUTO_CHECKER_RESULT_BLOCKS=§7Blocks: §c{0}§7, Max: §e{1}
AUTO_CHECKER_RESULT_UNKNOWN_MATERIAL=§7Unknown block: §c{0} AUTO_CHECKER_RESULT_UNKNOWN_MATERIAL=§7Unknown block: §c{0}
AUTO_CHECKER_RESULT_TOO_MANY_BLOCK=§7{0}: §c{1}§7, Max: §e{2} AUTO_CHECKER_RESULT_TOO_MANY_BLOCK=§7{0}: §c{1}§7, Max: §e{2}
AUTO_CHECKER_RESULT_FORBIDDEN_BLOCK=§7Forbidden block: §c{0} AUTO_CHECKER_RESULT_FORBIDDEN_BLOCK=§7Forbidden block: §c{0}
AUTO_CHECKER_RESULT_WIND_CHARGES=§7Windcharges: §c{0}§7, Max: §e2048
AUTO_CHECKER_RESULT_WIND_CHARGES_DISPENSER=§7Dispenser: §c[{0}, {1}, {2}]§7, Windcharges: §c{3}§7
AUTO_CHECKER_RESULT_FORBIDDEN_ITEM=§7Forbidden Item: [{0}, {1}, {2}] -> §c{3} AUTO_CHECKER_RESULT_FORBIDDEN_ITEM=§7Forbidden Item: [{0}, {1}, {2}] -> §c{3}
AUTO_CHECKER_RESULT_DEFUNCT_NBT=§7Defunct NBT: §7[{0}, {1}, {2}] AUTO_CHECKER_RESULT_DEFUNCT_NBT=§7Defunct NBT: §7[{0}, {1}, {2}]
AUTO_CHECKER_RESULT_DESIGN_BLOCK=§7{0} in Design: [{1}, {2}, {3}] AUTO_CHECKER_RESULT_DESIGN_BLOCK=§7{0} in Design: [{1}, {2}, {3}]
@@ -191,7 +191,6 @@ GUI_INFO_BACK=§eZurück
GUI_INFO_STATUS=§eStatus {0} GUI_INFO_STATUS=§eStatus {0}
GUI_INFO_COLOR=Farbersetzung GUI_INFO_COLOR=Farbersetzung
GUI_INFO_REPLAY=Replay Wiedergabe GUI_INFO_REPLAY=Replay Wiedergabe
GUI_INFO_REPLAY_OFF=§7Zum §lAusschalten
GUI_INFO_REPLAY_TITLE=Wiedergabe dauerhaft sperren GUI_INFO_REPLAY_TITLE=Wiedergabe dauerhaft sperren
GUI_INFO_MEMBER=§eMitglieder GUI_INFO_MEMBER=§eMitglieder
GUI_INFO_MOVE=§eVerschieben GUI_INFO_MOVE=§eVerschieben
@@ -242,6 +241,8 @@ AUTO_CHECKER_RESULT_BLOCKS=§7Blöcke: §c{0}§7, Max: §e{1}
AUTO_CHECKER_RESULT_UNKNOWN_MATERIAL=§7Unbekannter Block: §c{0} AUTO_CHECKER_RESULT_UNKNOWN_MATERIAL=§7Unbekannter Block: §c{0}
AUTO_CHECKER_RESULT_TOO_MANY_BLOCK=§7{0}: §c{1}§7, Max: §e{2} AUTO_CHECKER_RESULT_TOO_MANY_BLOCK=§7{0}: §c{1}§7, Max: §e{2}
AUTO_CHECKER_RESULT_FORBIDDEN_BLOCK=§7Verbotener Block: §c{0} AUTO_CHECKER_RESULT_FORBIDDEN_BLOCK=§7Verbotener Block: §c{0}
AUTO_CHECKER_RESULT_WIND_CHARGES=§7Windcharges: §c{0}§7, Max: §e2048
AUTO_CHECKER_RESULT_WIND_CHARGES_DISPENSER=§7Werfer: §c[{0}, {1}, {2}]§7, Windcharges: §c{3}§7
AUTO_CHECKER_RESULT_FORBIDDEN_ITEM=§7Verbotener gegenstand: [{0}, {1}, {2}] -> §c{3} AUTO_CHECKER_RESULT_FORBIDDEN_ITEM=§7Verbotener gegenstand: [{0}, {1}, {2}] -> §c{3}
AUTO_CHECKER_RESULT_DEFUNCT_NBT=§7Keine NBT-Daten: §c[{0}, {1}, {2}] AUTO_CHECKER_RESULT_DEFUNCT_NBT=§7Keine NBT-Daten: §c[{0}, {1}, {2}]
AUTO_CHECKER_RESULT_DESIGN_BLOCK=§7{0} im Design: [{1}, {2}, {3}] AUTO_CHECKER_RESULT_DESIGN_BLOCK=§7{0} im Design: [{1}, {2}, {3}]
@@ -55,6 +55,7 @@ public class AutoChecker {
private final List<BlockPos> records = new ArrayList<>(); private final List<BlockPos> records = new ArrayList<>();
private final Map<Material, List<BlockPos>> designBlocks = new EnumMap<>(Material.class); private final Map<Material, List<BlockPos>> designBlocks = new EnumMap<>(Material.class);
private final Map<BlockPos, Integer> dispenserItems = new HashMap<>(); private final Map<BlockPos, Integer> dispenserItems = new HashMap<>();
private final Map<BlockPos, Integer> windChargeCount = new HashMap<>();
private final Map<BlockPos, Set<Material>> forbiddenItems = new HashMap<>(); private final Map<BlockPos, Set<Material>> forbiddenItems = new HashMap<>();
private final Map<BlockPos, Set<Material>> forbiddenNbt = new HashMap<>(); private final Map<BlockPos, Set<Material>> forbiddenNbt = new HashMap<>();
} }
@@ -52,6 +52,7 @@ public class AutoCheckerResult {
isBlockCountOk() && isBlockCountOk() &&
isLimitedBlocksOK() && isLimitedBlocksOK() &&
isDispenserItemsOK() && isDispenserItemsOK() &&
isWindchargeCountOK() &&
!type.isAfterDeadline() && !type.isAfterDeadline() &&
entities.isEmpty() && entities.isEmpty() &&
isDesignBlastResistanceOK(); isDesignBlastResistanceOK();
@@ -62,8 +63,18 @@ public class AutoCheckerResult {
!type.isAfterDeadline(); !type.isAfterDeadline();
} }
public boolean isWindchargeCountOK() {
if( type.Schematic.Type.getName().equals("wargearseason26")) {
int windChargesCount = blockScanResult.getWindChargeCount().values().stream().reduce(Integer::sum).orElse(0);
return windChargesCount <= 2048;
}
else {
return true;
}
}
public boolean isDispenserItemsOK() { public boolean isDispenserItemsOK() {
return blockScanResult.getDispenserItems().values().stream().allMatch(i -> i <= type.Schematic.MaxDispenserItems); return blockScanResult.getDispenserItems().values().stream().allMatch(i -> i <= type.Schematic.MaxDispenserItems);
} }
public boolean hasWarnings() { public boolean hasWarnings() {
@@ -127,6 +138,19 @@ public class AutoCheckerResult {
} }
}); });
} }
if(!isWindchargeCountOK()) {
int windChargesCount = blockScanResult.getWindChargeCount().values().stream().reduce(Integer::sum).orElse(0);
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_WIND_CHARGES", p, windChargesCount, 2048);
blockScanResult.getWindChargeCount().entrySet().stream().filter(blockVector3IntegerEntry -> blockVector3IntegerEntry.getValue() > 0).forEach(blockVector3IntegerEntry -> {
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_WIND_CHARGES_DISPENSER", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(blockVector3IntegerEntry.getKey()),
blockVector3IntegerEntry.getKey().getBlockX(),
blockVector3IntegerEntry.getKey().getBlockY(),
blockVector3IntegerEntry.getKey().getBlockZ(),
blockVector3IntegerEntry.getValue());
});
}
blockScanResult.getDispenserItems().entrySet().stream().filter(blockVector3IntegerEntry -> blockVector3IntegerEntry.getValue() > type.Schematic.MaxDispenserItems).forEach(blockVector3IntegerEntry -> { blockScanResult.getDispenserItems().entrySet().stream().filter(blockVector3IntegerEntry -> blockVector3IntegerEntry.getValue() > type.Schematic.MaxDispenserItems).forEach(blockVector3IntegerEntry -> {
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_TOO_MANY_DISPENSER_ITEMS", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(blockVector3IntegerEntry.getKey()), SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_TOO_MANY_DISPENSER_ITEMS", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(blockVector3IntegerEntry.getKey()),
blockVector3IntegerEntry.getKey().getBlockX(), blockVector3IntegerEntry.getKey().getBlockX(),
@@ -135,6 +159,7 @@ public class AutoCheckerResult {
blockVector3IntegerEntry.getValue(), blockVector3IntegerEntry.getValue(),
type.Schematic.MaxDispenserItems); type.Schematic.MaxDispenserItems);
}); });
blockScanResult.getRecords().forEach(blockVector3 -> { blockScanResult.getRecords().forEach(blockVector3 -> {
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_RECORD", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(blockVector3), blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ()); SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_RECORD", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(blockVector3), blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ());
}); });
@@ -151,7 +151,7 @@ public class GUI {
node.setReplaceColor(!node.replaceColor()); node.setReplaceColor(!node.replaceColor());
info(player, node, back); info(player, node, back);
}); });
inv.setItem(13, SWItem.getMaterial(node.allowReplay() ? "EYE_OF_ENDER" : "ENDER_PEARL"), SchematicSystem.MESSAGE.parse("GUI_INFO_REPLAY", player), Arrays.asList(SchematicSystem.MESSAGE.parse("CURRENT", player, SchematicSystem.MESSAGE.parse(node.allowReplay()?"ON":"OFF", player)), SchematicSystem.MESSAGE.parse("GUI_INFO_REPLAY_OFF", player), SchematicSystem.MESSAGE.parse("CLICK", player)), false, clickType -> { inv.setItem(13, SWItem.getMaterial(node.allowReplay() ? "EYE_OF_ENDER" : "ENDER_PEARL"), SchematicSystem.MESSAGE.parse("GUI_INFO_REPLAY", player), Arrays.asList(SchematicSystem.MESSAGE.parse("CURRENT", player, SchematicSystem.MESSAGE.parse(node.allowReplay()?"ON":"OFF", player)), SchematicSystem.MESSAGE.parse("CHANGE", player), SchematicSystem.MESSAGE.parse("CLICK", player)), false, clickType -> {
if(node.allowReplay()) { if(node.allowReplay()) {
SWInventory confInv = new SWInventory(player, 9, SchematicSystem.MESSAGE.parse("GUI_INFO_REPLAY_TITLE", player)); SWInventory confInv = new SWInventory(player, 9, SchematicSystem.MESSAGE.parse("GUI_INFO_REPLAY_TITLE", player));
confInv.setItem(0, SWItem.getDye(10), (byte) 10, SchematicSystem.MESSAGE.parse("CONFIRM", player), type -> { confInv.setItem(0, SWItem.getDye(10), (byte) 10, SchematicSystem.MESSAGE.parse("CONFIRM", player), type -> {
@@ -162,6 +162,9 @@ public class GUI {
info(player, node, back); info(player, node, back);
}); });
confInv.open(); confInv.open();
} else {
node.setAllowReplay(true);
info(player, node, back);
} }
}); });
} }
+1
View File
@@ -32,4 +32,5 @@ dependencies {
implementation(project(":SchematicSystem:SchematicSystem_15")) implementation(project(":SchematicSystem:SchematicSystem_15"))
implementation(project(":SchematicSystem:SchematicSystem_19")) implementation(project(":SchematicSystem:SchematicSystem_19"))
implementation(project(":SchematicSystem:SchematicSystem_20")) implementation(project(":SchematicSystem:SchematicSystem_20"))
implementation(project(":SchematicSystem:SchematicSystem_21"))
} }
@@ -22,7 +22,6 @@ package de.steamwar.sql;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import de.steamwar.velocitycore.VelocityCore; import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.commands.CheckCommand;
import java.io.File; import java.io.File;
@@ -38,15 +37,6 @@ public class SQLWrapperImpl implements SQLWrapper<String> {
return new GameModeConfig<>(file, GameModeConfig.ToString, GameModeConfig.ToString, GameModeConfig.ToInternalName, true); return new GameModeConfig<>(file, GameModeConfig.ToString, GameModeConfig.ToString, GameModeConfig.ToInternalName, true);
} }
@Override
public void processSchematicType(GameModeConfig<?, String> gameModeConfig) {
SchematicType type = gameModeConfig.Schematic.Type;
if (type.checkType() != null) {
CheckCommand.setCheckQuestions(type.checkType(), gameModeConfig.CheckQuestions);
CheckCommand.addFightType(type.checkType(), type);
}
}
@Override @Override
public void additionalExceptionMetadata(StringBuilder builder) { public void additionalExceptionMetadata(StringBuilder builder) {
builder.append("\nServers: "); builder.append("\nServers: ");
@@ -25,7 +25,10 @@ import lombok.Getter;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import java.io.File; import java.io.File;
import java.util.*; import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@UtilityClass @UtilityClass
public class ArenaMode { public class ArenaMode {
@@ -50,12 +53,12 @@ public class ArenaMode {
if(!folder.exists()) if(!folder.exists())
return; return;
for(File file : Arrays.stream(folder.listFiles((file, name) -> name.endsWith(".yml") && !name.endsWith(".kits.yml") && !name.equals("config.yml"))).sorted().toList()) { GameModeConfig.init();
GameModeConfig<String, String> gameModeConfig = new GameModeConfig<>(file, GameModeConfig.ToString, GameModeConfig.ToString, GameModeConfig.ToInternalName, false); SchematicType.init();
for (GameModeConfig<String, String> gameModeConfig : GameModeConfig.<String>getAll()) {
if (!gameModeConfig.Server.loaded) continue; if (!gameModeConfig.Server.loaded) continue;
allModes.add(gameModeConfig); allModes.add(gameModeConfig);
byInternal.put(file.getName().replace(".yml", ""), gameModeConfig); byInternal.put(gameModeConfig.configFile.getName().replace(".yml", ""), gameModeConfig);
for (String name : gameModeConfig.Server.ChatNames) { for (String name : gameModeConfig.Server.ChatNames) {
byChat.put(name.toLowerCase(), gameModeConfig); byChat.put(name.toLowerCase(), gameModeConfig);
} }
@@ -40,6 +40,7 @@ import net.kyori.adventure.text.format.NamedTextColor;
import java.awt.*; import java.awt.*;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.time.Instant; import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -48,20 +49,9 @@ import java.util.logging.Level;
@Linked @Linked
public class CheckCommand extends SWCommand { public class CheckCommand extends SWCommand {
private static final Map<SchematicType, SchematicType> fightTypes = new HashMap<>();
private static final Map<SchematicType, List<String>> checkQuestions = new HashMap<>();
private static final Map<UUID, CheckSession> currentCheckers = new HashMap<>(); private static final Map<UUID, CheckSession> currentCheckers = new HashMap<>();
private static final Map<Integer, CheckSession> currentSchems = new HashMap<>(); private static final Map<Integer, CheckSession> currentSchems = new HashMap<>();
public static void setCheckQuestions(SchematicType checkType, List<String> checkQuestions) {
CheckCommand.checkQuestions.put(checkType, checkQuestions);
}
public static void addFightType(SchematicType checkType, SchematicType fightType) {
fightTypes.put(checkType, fightType);
}
public static boolean isChecking(Player player){ public static boolean isChecking(Player player){
return currentCheckers.containsKey(player.getUniqueId()); return currentCheckers.containsKey(player.getUniqueId());
} }
@@ -147,7 +137,7 @@ public class CheckCommand extends SWCommand {
if(!schem.getSchemtype().check()){ if(!schem.getSchemtype().check()){
VelocityCore.getLogger().log(Level.SEVERE, () -> sender.user().getUserName() + " tried to check an uncheckable schematic!"); VelocityCore.getLogger().log(Level.SEVERE, () -> sender.user().getUserName() + " tried to check an uncheckable schematic!");
return; return;
}else if(schem.getOwner() == sender.user().getId()) { }else if(schem.getOwner() == sender.user().getId() && !sender.user().hasPerm(UserPerm.ADMINISTRATION)) {
sender.system("CHECK_SCHEMATIC_OWN"); sender.system("CHECK_SCHEMATIC_OWN");
return; return;
} }
@@ -248,9 +238,9 @@ public class CheckCommand extends SWCommand {
this.checker = checker; this.checker = checker;
this.schematic = schematic; this.schematic = schematic;
this.startTime = Timestamp.from(Instant.now()); this.startTime = Timestamp.from(Instant.now());
this.checkList = checkQuestions.get(schematic.getSchemtype()).listIterator(); this.checkList = GameModeConfig.getBySchematicType(schematic.getSchemtype()).CheckQuestions.listIterator();
GameModeConfig<String, String> mode = ArenaMode.getBySchemType(fightTypes.get(schematic.getSchemtype())); GameModeConfig<String, String> mode = GameModeConfig.getBySchematicType(schematic.getSchemtype());
new ServerStarter().test(mode, mode.getRandomMap(), checker.getPlayer()).check(schematic.getId()).callback(subserver -> { new ServerStarter().test(mode, mode.getRandomMap(), checker.getPlayer()).check(schematic.getId()).callback(subserver -> {
currentCheckers.put(checker.user().getUUID(), this); currentCheckers.put(checker.user().getUUID(), this);
currentSchems.put(schematic.getId(), this); currentSchems.put(schematic.getId(), this);
@@ -304,7 +294,50 @@ public class CheckCommand extends SWCommand {
} }
private void accept(){ private void accept(){
concludeCheckSession("freigegeben", fightTypes.get(schematic.getSchemtype()), () -> { // TODO: This Code is only for the WGS and because YoyoNow is not available this can be removed or changed after the WGS!
if (schematic.getSchemtype().toDB().equals("cwargearseason26")) {
int userId = schematic.getOwner();
SteamwarUser user = SteamwarUser.byId(userId);
int teamId = user.getTeam();
SchematicNode teamFolder = SchematicNode.getSchematicNodeInNode(172325)
.stream()
.filter(schematicNode -> schematicNode.getName().startsWith(teamId + "_"))
.findFirst()
.orElse(null);
if (teamFolder == null) {
internalAccept();
return;
}
// Copy Schem into team folder of -1 user
String name = DateTimeFormatter.ofPattern("yyyy.MM.dd_HH:mm:ss").format(schematic.getLastUpdate().toLocalDateTime());
NodeData data = NodeData.getLatest(schematic);
SchematicNode node = SchematicNode.createSchematic(-1, name, teamFolder.getNodeId());
NodeData.saveFromStream(node, data.schemData(false), data.getNodeFormat());
// Accept the team folder schematic and set other to Normal
node.setSchemtype(GameModeConfig.getBySchematicType(schematic.getSchemtype()).Schematic.Type);
// Conclude by setting send in schematic to normal and broadcast
concludeCheckSession("freigegeben", SchematicType.Normal, () -> {
Chatter owner = Chatter.of(SteamwarUser.byId(schematic.getOwner()).getUUID());
owner.withPlayerOrOffline(
player -> owner.system("CHECK_ACCEPTED", schematic.getSchemtype().name(), schematic.getName()),
() -> DiscordAlert.send(owner, Color.GREEN, new Message("DC_TITLE_SCHEMINFO"), new Message("DC_SCHEM_ACCEPT", schematic.getName()), true)
);
notifyTeam(new Message("CHECK_ACCEPTED_TEAM", schematic.getName(), owner.user().getUserName()));
return owner.getPlayer() != null;
});
return;
}
internalAccept();
}
private void internalAccept() {
concludeCheckSession("freigegeben", GameModeConfig.getBySchematicType(schematic.getSchemtype()).Schematic.Type, () -> {
Chatter owner = Chatter.of(SteamwarUser.byId(schematic.getOwner()).getUUID()); Chatter owner = Chatter.of(SteamwarUser.byId(schematic.getOwner()).getUUID());
owner.withPlayerOrOffline( owner.withPlayerOrOffline(
player -> owner.system("CHECK_ACCEPTED", schematic.getSchemtype().name(), schematic.getName()), player -> owner.system("CHECK_ACCEPTED", schematic.getSchemtype().name(), schematic.getName()),
@@ -158,7 +158,6 @@ public class Tablist extends ChannelInboundHandlerAdapter {
public void disable() { public void disable() {
sendTabPacket(new ArrayList<>(directTabItems.values()), null); sendTabPacket(new ArrayList<>(directTabItems.values()), null);
directTabItems.clear();
sendTabPacket(current, null); sendTabPacket(current, null);
current.clear(); current.clear();
@@ -55,7 +55,7 @@ public class BauLock {
break; break;
case SUPERVISOR: case SUPERVISOR:
BauweltMember member = BauweltMember.getBauMember(owner.getId(), target.getId()); BauweltMember member = BauweltMember.getBauMember(owner.getId(), target.getId());
locked = !member.isSupervisor(); locked = member == null || !member.isSupervisor();
break; break;
case SERVERTEAM: case SERVERTEAM:
locked = !target.hasPerm(UserPerm.TEAM); locked = !target.hasPerm(UserPerm.TEAM);
+44 -50
View File
@@ -47,67 +47,61 @@ data class UsernamePassword(val name: String, val password: String, val keepLogg
fun Route.configureAuth() { fun Route.configureAuth() {
route("/auth") { route("/auth") {
val client = HttpClient(Java) {
install(ContentNegotiation) {
json()
}
}
post {
val request = call.receive<UsernamePassword>()
SteamwarUser.clear()
val user = SteamwarUser.get(request.name)
val valid = user?.verifyPassword(request.password) ?: false
if (!valid) {
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid username or password", "invalid"))
return@post
}
call.sessions.set(SWUserSession(user.getId()))
call.respond(ResponseUser(user))
}
delete { delete {
call.sessions.clear<SWUserSession>() call.sessions.clear<SWUserSession>()
call.respond(HttpStatusCode.NoContent) call.respond(HttpStatusCode.NoContent)
} }
configureDiscordAuth() route("/discord") {
configurePasswordAuth() post {
} val token = call.receiveText()
}
fun Route.configurePasswordAuth() { val res = client.get("https://discord.com/api/v10/oauth2/@me") {
post { headers {
val request = call.receive<UsernamePassword>() append("Authorization", "Bearer $token")
}
}
val resJson = Json.parseToJsonElement(res.bodyAsText()).jsonObject
val discordId = resJson["user"]?.jsonObject["id"]?.jsonPrimitive?.content
SteamwarUser.clear() if (discordId == null) {
val user = SteamwarUser.get(request.name) call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid Discord token", "invalid"))
val valid = user?.verifyPassword(request.password) ?: false return@post
}
if (!valid) { SteamwarUser.clear()
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid username or password", "invalid")) val user = SteamwarUser.get(discordId.toLong())
return@post
}
call.sessions.set(SWUserSession(user.getId())) if (user == null) {
call.respond(ResponseUser(user)) call.respond(HttpStatusCode.Forbidden, ResponseError("Discord account not linked", "not_linked"))
} return@post
} }
fun Route.configureDiscordAuth() { call.sessions.set(SWUserSession(user.getId()))
val client = HttpClient(Java) { call.respond(ResponseUser(user))
install(ContentNegotiation) {
json()
}
}
post("/discord") {
val token = call.receiveText()
val res = client.get("https://discord.com/api/v10/oauth2/@me") {
headers {
append("Authorization", "Bearer $token")
} }
} }
val resJson = Json.parseToJsonElement(res.bodyAsText()).jsonObject
val discordId = resJson["user"]?.jsonObject["id"]?.jsonPrimitive?.content
if (discordId == null) {
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid Discord token", "invalid"))
return@post
}
SteamwarUser.clear()
val user = SteamwarUser.get(discordId.toLong())
if (user == null) {
call.respond(HttpStatusCode.Forbidden, ResponseError("Discord account not linked", "not_linked"))
return@post
}
call.sessions.set(SWUserSession(user.getId()))
call.respond(ResponseUser(user))
} }
} }
@@ -75,7 +75,7 @@ data class ResponseUser(
@Serializable @Serializable
data class ResponseUserList(val entries: List<ResponseUser>, val rows: Long) data class ResponseUserList(val entries: List<ResponseUser>, val rows: Long)
fun Query.addUserFilter( private fun Query.addUserFilter(
name: String? = null, name: String? = null,
uuid: UUID? = null, uuid: UUID? = null,
team: Set<Int>? = null, team: Set<Int>? = null,
@@ -27,7 +27,6 @@ import io.ktor.server.request.*
import io.ktor.server.response.* 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.jetbrains.exposed.v1.core.ResultRow
import java.sql.Timestamp import java.sql.Timestamp
import java.time.Instant import java.time.Instant
@@ -61,8 +60,6 @@ data class ResponseEventFight(
@Serializable @Serializable
data class ResponseTeam(val id: Int, val name: String, val kuerzel: String, val color: String) { data class ResponseTeam(val id: Int, val name: String, val kuerzel: String, val color: String) {
constructor(team: Team) : this(team.teamId, team.teamName, team.teamKuerzel, team.teamColor) constructor(team: Team) : this(team.teamId, team.teamName, team.teamKuerzel, team.teamColor)
constructor(row: ResultRow) : this(row[TeamTable.id].value, row[TeamTable.name], row[TeamTable.kuerzel], row[TeamTable.color])
} }
@Serializable @Serializable
@@ -19,12 +19,6 @@
package de.steamwar.routes package de.steamwar.routes
import de.steamwar.routes.v2.configureAuthV2
import de.steamwar.routes.v2.configureGameModeRoutes
import de.steamwar.routes.v2.configureSchematicsV2Route
import de.steamwar.routes.v2.configureSteamWarRoute
import de.steamwar.routes.v2.configureTeamRoutes
import de.steamwar.routes.v2.configureUsersRouteV2
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.auth.* import io.ktor.server.auth.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
@@ -40,17 +34,6 @@ fun Application.configureRoutes() {
configureSchematic() configureSchematic()
configureAuth() configureAuth()
configureAuditLog() configureAuditLog()
route("/v2") {
configureAuditLog()
configurePage()
configureEventsRoute()
configureAuthV2()
configureTeamRoutes()
configureSteamWarRoute()
configureUsersRouteV2()
configureSchematicsV2Route()
configureGameModeRoutes()
}
} }
} }
} }
@@ -43,31 +43,8 @@ import java.util.*
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
@Serializable @Serializable
data class ResponseSchematic( data class ResponseSchematic(val name: String, val id: Int, val type: String?, val owner: Int, val item: String, val lastUpdate: Long, val rank: Int, val replaceColor: Boolean, val allowReplay: Boolean) {
val name: String, constructor(node: SchematicNode) : this(node.name, node.getId(), node.schemtype?.name(), node.owner, node.item, node.lastUpdate.time, node.rank, node.replaceColor(), node.allowReplay())
val id: Int,
val type: String?,
val owner: Int,
val item: String,
val lastUpdate: Long,
val replaceColor: Boolean,
val allowReplay: Boolean,
val members: List<ResponseUser>
) {
constructor(node: SchematicNode) : this(
node.name,
node.getId(),
node.schemtype.name(),
node.owner,
node.item,
node.lastUpdate.time,
node.replaceColor(),
node.allowReplay(),
node.members.map {
ResponseUser(
SteamwarUser.byId(it.member)!!
)
})
} }
@Serializable @Serializable
@@ -95,12 +72,9 @@ fun Route.configureSchematic() {
val node = call.receiveSchematic() ?: return@get val node = call.receiveSchematic() ?: return@get
val user = call.principal<SWAuthPrincipal>()?.user val user = call.principal<SWAuthPrincipal>()?.user
if (user != null && !node.accessibleByUser(user)) { if(user != null && !node.accessibleByUser(user)) {
call.respond(HttpStatusCode.Forbidden) call.respond(HttpStatusCode.Forbidden)
SWException.log( SWException.log("User ${user.userName} tried to download schematic ${node.name} without permission", user.id.toString())
"User ${user.userName} tried to download schematic ${node.name} without permission",
user.id.toString()
)
return@get return@get
} }
@@ -109,15 +83,8 @@ fun Route.configureSchematic() {
return@get return@get
} }
call.response.header( call.response.header("Content-Disposition", "attachment; filename=\"${node.name}${data.nodeFormat.fileEnding}\"")
"Content-Disposition", call.respondBytes(data.schemData(false).readAllBytes(), contentType = ContentType.Application.OctetStream, status = HttpStatusCode.OK)
"attachment; filename=\"${node.name}${data.nodeFormat.fileEnding}\""
)
call.respondBytes(
data.schemData(false).readAllBytes(),
contentType = ContentType.Application.OctetStream,
status = HttpStatusCode.OK
)
} }
get("/info") { get("/info") {
val node = call.receiveSchematic() ?: return@get val node = call.receiveSchematic() ?: return@get
@@ -133,22 +100,18 @@ fun Route.configureSchematic() {
val schemName = file.name.substringBeforeLast(".") val schemName = file.name.substringBeforeLast(".")
if (SchematicNode.invalidSchemName(arrayOf(schemName))) { if (SchematicNode.invalidSchemName(arrayOf(schemName))) {
call.respond( call.respond(HttpStatusCode.BadRequest, ResponseError(
HttpStatusCode.BadRequest, ResponseError( error = "INVALID_NAME"
error = "INVALID_NAME" ))
)
)
return@post return@post
} }
val schemType = file.name.substringAfterLast(".") val schemType = file.name.substringAfterLast(".")
if (schemType != "schem" && schemType != "schematic") { if (schemType != "schem" && schemType != "schematic") {
call.respond( call.respond(HttpStatusCode.BadRequest, ResponseError(
HttpStatusCode.BadRequest, ResponseError( error = "INVALID_SUFFIX"
error = "INVALID_SUFFIX" ))
)
)
return@post return@post
} }
@@ -183,27 +146,22 @@ fun Route.configureSchematic() {
.value .value
if (fawe.equals("2.12.3-SNAPSHOT")) { if (fawe.equals("2.12.3-SNAPSHOT")) {
SWException.log( SWException.log("Schematic with Bugged Version Uploaded", """
"Schematic with Bugged Version Uploaded", """
Schematic=$schemName Schematic=$schemName
User=${user.userName} User=${user.userName}
Id=${user.id} Id=${user.id}
""".trimIndent() """.trimIndent())
)
} }
} catch (_: Exception) { } catch (_: Exception) {}
}
} }
NodeData.saveFromStream(node, content.inputStream(), version) NodeData.saveFromStream(node, content.inputStream(), version)
call.respond(ResponseSchematic(node)) call.respond(ResponseSchematic(node))
} catch (e: Exception) { } catch (e: Exception) {
call.respond( call.respond(HttpStatusCode.BadRequest, ResponseError(
HttpStatusCode.BadRequest, ResponseError( error = e.message ?: "GENERIC", code = "UPLOAD_ERROR"
error = e.message ?: "GENERIC", code = "UPLOAD_ERROR" ))
)
)
} }
} }
} }
@@ -220,7 +178,7 @@ suspend fun ApplicationCall.receiveSchematic(fieldName: String = "code", delete:
return null return null
} }
if (dl.timestamp.toInstant().plus(Duration.of(5, ChronoUnit.MINUTES)).isBefore(Instant.now())) { if(dl.timestamp.toInstant().plus(Duration.of(5, ChronoUnit.MINUTES)).isBefore(Instant.now())) {
respond(HttpStatusCode.Gone) respond(HttpStatusCode.Gone)
return null return null
} }
@@ -1,46 +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.routes.v2
import de.steamwar.plugins.SWUserSession
import de.steamwar.routes.configureDiscordAuth
import de.steamwar.routes.configurePasswordAuth
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.call
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.delete
import io.ktor.server.routing.route
import io.ktor.server.sessions.clear
import io.ktor.server.sessions.sessions
fun Route.configureAuthV2() {
route("/auth") {
delete {
call.sessions.clear<SWUserSession>()
call.respond(HttpStatusCode.NoContent)
}
configureDiscordAuth()
route("/legacy") {
configurePasswordAuth()
}
}
}
@@ -1,55 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.routes.v2
import de.steamwar.ResponseError
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.call
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 org.bspfsystems.yamlconfiguration.file.YamlConfiguration
import java.io.File
import kotlin.io.nameWithoutExtension
fun Route.configureGameModeRoutes() {
route("/gamemodes") {
get {
call.respond(
File("/configs/GameModes/").listFiles()!!
.filter { it.name.endsWith(".yml") && !it.name.endsWith(".kits.yml") }
.map { it.nameWithoutExtension })
}
get("/{gamemode}/maps") {
val gamemode = call.parameters["gamemode"]
if (gamemode == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid gamemode"))
return@get
}
val file = File("/configs/GameModes/$gamemode.yml")
if (!file.exists()) {
call.respond(HttpStatusCode.NotFound, ResponseError("Gamemode not found"))
return@get
}
call.respond(YamlConfiguration.loadConfiguration(file).getStringList("Server.Maps"))
}
}
}
@@ -1,210 +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.routes.v2
import com.sun.tools.jdeprscan.Main.call
import de.steamwar.ResponseError
import de.steamwar.plugins.SWAuthPrincipal
import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.routes.ResponseSchematic
import de.steamwar.routes.ResponseSchematicType
import de.steamwar.routes.UploadSchematic
import de.steamwar.routes.nbt
import de.steamwar.routes.receiveSchematic
import de.steamwar.sql.NodeData
import de.steamwar.sql.NodeData.SchematicFormat
import de.steamwar.sql.NodeDownload
import de.steamwar.sql.SWException
import de.steamwar.sql.SchematicNode
import de.steamwar.sql.SchematicType
import dev.dewy.nbt.tags.collection.CompoundTag
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.auth.authentication
import io.ktor.server.auth.principal
import io.ktor.server.request.receive
import io.ktor.server.response.header
import io.ktor.server.response.respond
import io.ktor.server.response.respondBytes
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.route
import java.io.BufferedInputStream
import java.io.DataInputStream
import java.util.Base64
import java.util.zip.GZIPInputStream
data class ListedSchematicNode(val name: String, val id: Int, val type: String)
fun Route.configureSchematicsV2Route() {
route("/schematics") {
get("/types") {
call.respond(SchematicType.values().filter { !it.check() }
.map { ResponseSchematicType(it.name(), it.toDB()) })
}
get("/list/{path...}") {
val path = call.parameters.getAll("path")?.joinToString("/") ?: "/"
val user = call.authentication.principal<SWAuthPrincipal>()
if (user == null) {
call.respond(HttpStatusCode.Unauthorized)
return@get
}
val node = SchematicNode.getNodeFromPath(user.user, path)
call.respond(SchematicNode.list(user.user, node?.id?.value).map { ListedSchematicNode(it.name, it.id.value, it.schemtype.toDB()) })
}
get("/download/{code}") {
val node = call.receiveSchematic() ?: return@get
val user = call.principal<SWAuthPrincipal>()?.user
if(user != null && !node.accessibleByUser(user)) {
call.respond(HttpStatusCode.Forbidden)
SWException.log("User ${user.userName} tried to download schematic ${node.name} without permission", user.id.toString())
return@get
}
val data = NodeData.getLatest(node) ?: run {
call.respond(HttpStatusCode.InternalServerError)
return@get
}
call.response.header("Content-Disposition", "attachment; filename=\"${node.name}${data.nodeFormat.fileEnding}\"")
call.respondBytes(data.schemData(false).readAllBytes(), contentType = ContentType.Application.OctetStream, status = HttpStatusCode.OK)
}
post("/upload") {
val user = call.principal<SWAuthPrincipal>()?.user
if (user == null) {
call.respond(HttpStatusCode.Unauthorized)
return@post
}
val file = call.receive<UploadSchematic>()
val schemName = file.name.substringBeforeLast(".")
if (SchematicNode.invalidSchemName(arrayOf(schemName))) {
call.respond(HttpStatusCode.BadRequest, ResponseError(
error = "INVALID_NAME"
))
return@post
}
val schemType = file.name.substringAfterLast(".")
if (schemType != "schem" && schemType != "schematic") {
call.respond(HttpStatusCode.BadRequest, ResponseError(
error = "INVALID_SUFFIX"
))
return@post
}
var node = SchematicNode.getSchematicNode(user.getId(), schemName, null as Int?)
if (node == null) {
node = SchematicNode.createSchematic(user.getId(), schemName, null)
}
try {
val content = Base64.getDecoder().decode(file.content)
var schem = nbt.fromStream(DataInputStream(BufferedInputStream(GZIPInputStream(content.inputStream()))))
if (schem.size() == 1) schem = schem.first() as CompoundTag
val version = schem.let {
if (it.contains("Materials"))
return@let SchematicFormat.MCEDIT
else if (it.contains("Blocks"))
return@let SchematicFormat.SPONGE_V3
else
return@let SchematicFormat.SPONGE_V2
}
if (version == SchematicFormat.SPONGE_V3) {
try {
val fawe = schem.getCompound("Metadata")
.getCompound("WorldEdit")
.getString("Version")
.value
if (fawe.equals("2.12.3-SNAPSHOT")) {
SWException.log("Schematic with Bugged Version Uploaded", """
Schematic=$schemName
User=${user.userName}
Id=${user.id}
""".trimIndent())
}
} catch (_: Exception) {}
}
NodeData.saveFromStream(node, content.inputStream(), version)
call.respond(ResponseSchematic(node))
} catch (e: Exception) {
call.respond(HttpStatusCode.BadRequest, ResponseError(
error = e.message ?: "GENERIC", code = "UPLOAD_ERROR"
))
}
}
route("/{id}") {
install(SWPermissionCheck) {
mustAuth = true
}
get {
val node = call.receiveSchem() ?: return@get
call.respond(ResponseSchematic(node))
}
post("/download") {
val node = call.receiveSchem() ?: return@post
call.respond(HttpStatusCode.OK, NodeDownload.getLink(node))
}
}
}
}
suspend fun ApplicationCall.receiveSchem(): SchematicNode? {
val schemId = parameters["id"]?.toIntOrNull()
if (schemId == null) {
respond(HttpStatusCode.BadRequest)
return null
}
val schem = SchematicNode.getSchematicNode(schemId)
if (schem == null) {
respond(HttpStatusCode.NotFound)
return null
}
if (!(principal<SWAuthPrincipal>()?.user?.let { schem.accessibleByUser(it) } ?: false)) {
respond(HttpStatusCode.Forbidden)
return null
}
return schem
}
@@ -1,60 +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.routes.v2
import de.steamwar.ResponseError
import de.steamwar.routes.ResponseUser
import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.UserPerm
import de.steamwar.util.fetchData
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.call
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 java.net.InetSocketAddress
fun Route.configureSteamWarRoute() {
route("/steamwar") {
get {
try {
val server = fetchData(InetSocketAddress("steamwar.de", 25565), 100)
call.respond(server)
} catch (e: Exception) {
e.printStackTrace()
call.respond(HttpStatusCode.InternalServerError, ResponseError(e.message ?: "Unknown error"))
return@get
}
}
get("/team") {
call.respond(
listOf(
UserPerm.PREFIX_ADMIN,
UserPerm.PREFIX_DEVELOPER,
UserPerm.PREFIX_MODERATOR,
UserPerm.PREFIX_SUPPORTER,
UserPerm.PREFIX_BUILDER
).associateWith { SteamwarUser.getUsersWithPerm(it) }.mapKeys { UserPerm.prefixes[it.key]!!.chatPrefix }
.mapValues { it.value.map { ResponseUser(it) } })
}
}
}
@@ -1,87 +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.routes.v2
import de.steamwar.routes.ResponseEvent
import de.steamwar.routes.ResponseTeam
import de.steamwar.routes.ResponseUser
import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.Team
import de.steamwar.sql.TeamTable
import de.steamwar.sql.TeamTeilnahme
import de.steamwar.sql.internal.useDb
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.call
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.like
import org.jetbrains.exposed.v1.jdbc.Query
import org.jetbrains.exposed.v1.jdbc.andWhere
import org.jetbrains.exposed.v1.jdbc.selectAll
fun Query.addTeamFilter(teamName: String?, teamKuerzel: String?): Query {
teamName?.let { andWhere { TeamTable.name like "%$it%" } }
teamKuerzel?.let { andWhere { TeamTable.kuerzel like "%$it%" } }
return this
}
fun Route.configureTeamRoutes() {
route("/teams") {
get {
val teamName = call.request.queryParameters["name"]
val teamKuerzel = call.request.queryParameters["kuerzel"]
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 100
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 0
call.respond(useDb {
TeamTable.selectAll().addTeamFilter(teamName, teamKuerzel).limit(limit).offset((page * limit).toLong())
.map { ResponseTeam(it) }
})
}
get("/{team}") {
@Serializable
data class TeamResponse(
val team: ResponseTeam,
val members: List<ResponseUser>,
val leaders: List<ResponseUser>,
val events: List<ResponseEvent>
)
val team = call.parameters["team"]?.toIntOrNull()?.let { Team.byId(it) }
if (team == null) {
call.respond(HttpStatusCode.NotFound)
return@get
}
call.respond(useDb {
TeamResponse(
ResponseTeam(team),
team.membersUser.map { ResponseUser(it) },
team.membersUser.filter { it.leader }.map { ResponseUser(it) },
TeamTeilnahme.getEvents(team.teamId).map { ResponseEvent(it) })
})
}
}
}
@@ -1,274 +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.routes.v2
import de.steamwar.ResponseError
import de.steamwar.data.getCachedSkin
import de.steamwar.plugins.SWAuthPrincipal
import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.routes.ResponseTeam
import de.steamwar.routes.ResponseUser
import de.steamwar.routes.ResponseUserList
import de.steamwar.routes.UserStats
import de.steamwar.routes.addUserFilter
import de.steamwar.routes.catchException
import de.steamwar.sql.Punishment
import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.SteamwarUserTable
import de.steamwar.sql.Team
import de.steamwar.sql.UserPerm
import de.steamwar.sql.internal.useDb
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.auth.principal
import io.ktor.server.request.receive
import io.ktor.server.response.header
import io.ktor.server.response.respond
import io.ktor.server.response.respondFile
import io.ktor.server.routing.Route
import io.ktor.server.routing.delete
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.put
import io.ktor.server.routing.route
import kotlinx.serialization.Serializable
import org.jetbrains.exposed.v1.jdbc.selectAll
import java.sql.Timestamp
import java.time.Instant
import java.util.UUID
@Serializable
data class ResponsePunishment(
val id: Int,
val type: Punishment.PunishmentType,
val reason: String,
val issuer: ResponseUser,
val startTime: Long,
val endTime: Long,
val perma: Boolean,
val active: Boolean
) {
constructor(punishment: Punishment) : this(
punishment.id.value,
punishment.type,
punishment.reason,
ResponseUser(SteamwarUser.byId(punishment.punisher)!!),
punishment.startTime.toInstant().toEpochMilli(),
punishment.endTime.toInstant().toEpochMilli(),
punishment.perma,
punishment.isCurrent()
)
}
@Serializable
data class CreatePunishment(
val type: Punishment.PunishmentType,
val reason: String,
val perma: Boolean,
val endTime: Long
)
fun Route.configureUsersRouteV2() {
get("/users/{user}/skin") {
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
return@get
}
val skin = getCachedSkin(user.uuid.toString())
call.response.header("X-Cache", if (skin.second) "HIT" else "MISS")
call.response.header("Cache-Control", "public, max-age=604800")
call.respondFile(skin.first)
}
get("/users/{user}/stats") {
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
return@get
}
val authUser = call.principal<SWAuthPrincipal>()
if (authUser == null || !(authUser.user.id == user.id || authUser.user.hasPerm(UserPerm.MODERATION))) {
call.respond(HttpStatusCode.Forbidden)
return@get
}
call.respond(UserStats(user))
}
route("/users") {
install(SWPermissionCheck) {
permission = UserPerm.MODERATION
}
get {
val name = call.request.queryParameters["name"]
val uuid = call.request.queryParameters["uuid"]?.let { catchException { UUID.fromString(it) } }
val team = call.request.queryParameters.getAll("team")?.map { it.toInt() }?.toSet()
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 100
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 0
val includePerms = call.request.queryParameters["includePerms"]?.toBoolean() ?: false
val includeId = call.request.queryParameters["includeId"]?.toBoolean() ?: false
call.respond(
useDb {
ResponseUserList(
SteamwarUserTable.selectAll().addUserFilter(name, uuid, team).limit(limit)
.offset((page * limit).toLong())
.map { ResponseUser(it, includeId, includePerms) },
SteamwarUserTable.selectAll().addUserFilter(name, uuid, team).count()
)
}
)
}
route("/{user}") {
get {
@Serializable
data class UserResponse(val user: ResponseUser, val team: ResponseTeam)
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.NotFound)
return@get
}
call.respond(
useDb {
UserResponse(ResponseUser(user), ResponseTeam(Team.byId(user.team)))
}
)
}
put("/prefix/{prefix}") {
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
return@put
}
val prefix = call.parameters["prefix"]?.let { name -> UserPerm.entries.find { it.name == name } }
if (prefix == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid prefix"))
return@put
}
user.perms().filter { it.name.startsWith("PREFIX_") }.forEach {
UserPerm.removePerm(user, it)
}
if (prefix != UserPerm.PREFIX_NONE) {
UserPerm.addPerm(user, prefix)
}
call.respond(HttpStatusCode.Accepted)
}
route("/perms") {
get {
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
return@get
}
call.respond(user.perms())
}
route("/{perm}") {
put {
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
return@put
}
val perm = call.parameters["perm"]?.let { name -> UserPerm.entries.find { it.name == name } }
if (perm == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid perm"))
return@put
}
UserPerm.addPerm(user, perm)
call.respond(HttpStatusCode.Accepted)
}
delete {
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
return@delete
}
val perm = call.parameters["perm"]?.let { name -> UserPerm.entries.find { it.name == name } }
if (perm == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid perm"))
return@delete
}
UserPerm.removePerm(user, perm)
call.respond(HttpStatusCode.Accepted)
}
}
}
}
route("/punishments") {
get {
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
return@get
}
val punishments = user.punishments.toList()
call.respond(punishments.map { ResponsePunishment(it.second) })
}
post {
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
return@post
}
val punishment = call.receive<CreatePunishment>()
val punisher = call.principal<SWAuthPrincipal>()?.user ?: return@post
user.punish(punishment.type, Timestamp.from(Instant.ofEpochMilli(punishment.endTime)), punishment.reason, punisher.getId(), punishment.perma)
call.respond(HttpStatusCode.Accepted)
}
}
}
}
fun ApplicationCall.receiveUser(): SteamwarUser? {
val userString = parameters["user"] ?: return null
if (userString == "me") {
return principal<SWAuthPrincipal>()?.user
}
userString.toIntOrNull()?.let { return SteamwarUser.byId(it) }
userString.let { catchException { UUID.fromString(it) } }?.let { return SteamwarUser.get(it) }
return null
}
+2
View File
@@ -101,6 +101,7 @@ dependencyResolutionManagement {
library("hamcrest", "org.hamcrest:hamcrest:2.2") library("hamcrest", "org.hamcrest:hamcrest:2.2")
library("classindex", "org.atteo.classindex:classindex:3.13") library("classindex", "org.atteo.classindex:classindex:3.13")
library("spigotapi", "org.spigotmc:spigot-api:1.20-R0.1-SNAPSHOT") library("spigotapi", "org.spigotmc:spigot-api:1.20-R0.1-SNAPSHOT")
library("spigotannotations", "org.spigotmc:plugin-annotations:1.2.3-SNAPSHOT") library("spigotannotations", "org.spigotmc:plugin-annotations:1.2.3-SNAPSHOT")
library("paperapi", "io.papermc.paper:paper-api:1.19.2-R0.1-SNAPSHOT") library("paperapi", "io.papermc.paper:paper-api:1.19.2-R0.1-SNAPSHOT")
@@ -222,6 +223,7 @@ include(
"SchematicSystem:SchematicSystem_15", "SchematicSystem:SchematicSystem_15",
"SchematicSystem:SchematicSystem_19", "SchematicSystem:SchematicSystem_19",
"SchematicSystem:SchematicSystem_20", "SchematicSystem:SchematicSystem_20",
"SchematicSystem:SchematicSystem_21",
"SchematicSystem:SchematicSystem_Core" "SchematicSystem:SchematicSystem_Core"
) )