Merge pull request 'Add Unified GameModeConfig' (#176) from UnifiedGameModeConfig into main

Reviewed-on: SteamWar/SteamWar#176
Reviewed-by: D4rkr34lm <dark@steamwar.de>
Reviewed-by: Chaoscaot <max@chaoscaot.de>
This commit is contained in:
2025-10-26 17:47:57 +01:00
105 changed files with 1605 additions and 952 deletions
+2
View File
@@ -23,4 +23,6 @@ plugins {
dependencies {
compileOnly(libs.sqlite)
implementation("org.yaml:snakeyaml:2.2")
}
@@ -0,0 +1,936 @@
/*
* 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 lombok.ToString;
import java.io.File;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@ToString
public final class GameModeConfig<M, W> {
public static final Function<String, String> ToString = Function.identity();
public static final Function<File, String> ToStaticWarGear = __ -> "WarGear";
public static final Function<File, String> ToInternalName = file -> file.getName().replace(".yml", "");
public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm");
private static final Random random = new Random();
static {
SchematicType.values();
}
private static final Map<String, GameModeConfig<?, String>> byFileName = new HashMap<>();
private static final Map<String, GameModeConfig<?, String>> byGameName = new HashMap<>();
private static final Map<SchematicType, GameModeConfig<?, String>> bySchematicType = new HashMap<>();
public static <M> GameModeConfig<M, String> getByFileName(File file) {
return (GameModeConfig<M, String>) byFileName.get(file.getName());
}
public static <M> GameModeConfig<M, String> getByGameName(String gameName) {
return (GameModeConfig<M, String>) byGameName.get(gameName);
}
public static <M> GameModeConfig<M, String> getBySchematicType(SchematicType schematicType) {
return (GameModeConfig<M, String>) bySchematicType.get(schematicType);
}
private static final GameModeConfig<?, String> DEFAULTS = SQLWrapper.impl.loadGameModeConfig(null);
public static <M> GameModeConfig<M, String> getDefaults() {
return (GameModeConfig<M, String>) DEFAULTS;
}
public final boolean loaded;
public final File configFile;
/**
* Bundle for server specific configuration values
*/
public final ServerConfig Server;
/**
* Submission deadline for schematics in 'dd.MM.yyyy HH:mm' format
*
* @implSpec {@code null} by default
*/
public final Date Deadline;
/**
* The questions that have to be answered to accept the schematic
*
* @implSpec Disables check schem type if missing
*/
public final List<String> CheckQuestions;
/**
* Bundle for countdowns during the fight
*/
public final TimesConfig Times;
/**
* Bundle for arena specific configurations. Primarily used by the FightSystem
*/
public final ArenaConfig Arena;
/**
* Bundle for schematic configurations
*/
public final SchematicConfig<M> Schematic;
/**
* The name of the game mode presented to the players
*
* @implSpec {@code YMLWrapper.getDefaultGameName()} by default
*/
public final String GameName;
/**
* The months this game mode should be active and playable<br/>
* The empty List means all of them
*
* @implNote 1 is January - 12 is December
*/
public final List<Integer> ActiveMonths;
/**
* The prefix used for team chats
*
* @implSpec {@code +} by default
*/
public final String TeamChatPrefix;
/**
* Bundle for configurations for the Blue team
*/
public final BlueConfig Blue;
/**
* Bundle for configurations for the Red team
*/
public final RedConfig Red;
/**
* The list of active win conditions
*/
public final List<W> WinConditions;
/**
* Bundle for configurations used by the Winconditions in the FightSystem
*/
public final WinConditionParamsConfig<M> WinConditionParams;
/**
* Bundle for Kit specific configurations
*/
public final KitsConfig<M> Kits;
/**
* A list of integers containing the waiting time of this enter stage in the fight
*/
public final List<Integer> EnterStages;
/**
* Bundle for Techhider configurations
*/
public final TechhiderConfig<M> Techhider;
private static final Field Schematic_TypeField;
static {
try {
Schematic_TypeField = SchematicConfig.class.getDeclaredField("Type");
Schematic_TypeField.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new SecurityException(e.getMessage(), e);
}
}
public GameModeConfig(File file, Function<String, M> materialMapper, Function<String, W> winconditionMapper, Function<File, String> defaultGameName, boolean cacheInstance) {
YMLWrapper<M, W> loader = new YMLWrapper<>(file, materialMapper, winconditionMapper);
configFile = file;
loaded = loader.canLoad();
Server = new ServerConfig(loader.with("Server"));
String deadlineString = loader.getString("Deadline", null);
if (deadlineString != null) {
Date Deadline = null;
try {
Deadline = DATE_FORMAT.parse(deadlineString);
} catch (ParseException e) {
Deadline = null;
}
this.Deadline = Deadline;
} else {
Deadline = null;
}
CheckQuestions = loader.getStringList("CheckQuestions");
Times = new TimesConfig(loader.with("Times"));
// Arena would be here to be in config order but needs Schematic.Size and EnterStages loaded afterwards
Schematic = new SchematicConfig<>(loader.with("Schematic"));
GameName = loader.getString("GameName", defaultGameName.apply(file));
ActiveMonths = loader.getIntList("ActiveMonths");
TeamChatPrefix = loader.getString("TeamChatPrefix", "+");
Blue = new BlueConfig(loader.with("Blue"));
Red = new RedConfig(loader.with("Red"));
WinConditions = Collections.unmodifiableList(loader.getStringList("WinConditions").stream().map(loader.winconditionMapper).collect(Collectors.toList()));
WinConditionParams = new WinConditionParamsConfig<>(loader.with("WinConditionParams"));
Kits = new KitsConfig<>(loader.with("Kits"));
EnterStages = loader.getIntList("EnterStages");
Techhider = new TechhiderConfig<>(loader.with("Techhider"));
Arena = new ArenaConfig(loader.with("Arena"), Schematic.Size, EnterStages);
if (cacheInstance) {
if (configFile != null) {
byFileName.put(configFile.getName(), (GameModeConfig) this);
}
byGameName.put(GameName, (GameModeConfig) this);
}
if (Schematic.Type != null) {
if (cacheInstance) {
bySchematicType.put(Schematic.Type, (GameModeConfig) this);
if (Schematic.Type.checkType() != null) {
bySchematicType.put(Schematic.Type.checkType(), (GameModeConfig) this);
}
}
return;
}
if (!Schematic.loaded) return;
String Schematic_Type = loader.with("Schematic").getString("Type", "Normal");
if (Schematic_Type.equals("Normal")) return;
String Schematic_Shortcut = Schematic.Shortcut;
SchematicType checktype = null;
String material = loader.with("Schematic").getString("Material", "STONE_BUTTON");
if (!CheckQuestions.isEmpty()) {
checktype = new SchematicType("C" + Schematic_Type, "C" + Schematic_Shortcut, SchematicType.Type.CHECK_TYPE, null, material, true);
}
SchematicType current = new SchematicType(Schematic_Type, Schematic_Shortcut, Server.loaded ? SchematicType.Type.FIGHT_TYPE : SchematicType.Type.NORMAL, checktype, material, Deadline, Schematic.ManualCheck);
try {
Schematic_TypeField.set(Schematic, current);
if (cacheInstance) {
bySchematicType.put(Schematic.Type, (GameModeConfig) this);
if (Schematic.Type.checkType() != null) {
bySchematicType.put(Schematic.Type.checkType(), (GameModeConfig) this);
}
}
} catch (IllegalAccessException e) {
throw new SecurityException(e.getMessage(), e);
}
}
@ToString
public static final class ServerConfig {
public final boolean loaded;
/**
* Base server folder
*/
public final String Folder;
/**
* Server java archive
*/
public final String ServerJar;
/**
* Available arenas
*/
public final List<String> Maps;
/**
* Names to address the game mode in the chat interface
*/
public final List<String> ChatNames;
/**
* If the Server is a Spigot server
*
* @implSpec {@code false} by default
*/
public final boolean Spigot;
/**
* If the game mode should be marked as a historic game mode
*
* @implSpec {@code false} by default
*/
public final boolean Historic;
/**
* If ranked matches should be available for the game mode
*
* @implSpec {@code false} by default
*/
public final boolean Ranked;
private ServerConfig(YMLWrapper loader) {
loaded = loader.canLoad();
Folder = loader.getString("Folder", null);
ServerJar = loader.getString("ServerJar", null);
Maps = loader.getStringList("Maps");
ChatNames = loader.getStringList("ChatNames");
Spigot = loader.getBoolean("Spigot", false);
Historic = loader.getBoolean("Historic", false);
Ranked = loader.getBoolean("Ranked", false);
}
}
@ToString
public static final class TimesConfig {
public final boolean loaded;
/**
* Time in seconds the server stops after starting if nobody joins
*
* @implSpec {@code 300} by default
*/
public final int NoPlayersOnlineDuration;
/**
* Time in seconds the team leaders have to choose their schematic
*
* @implSpec {@code 120} by default
*/
public final int PreSchemPasteDuration;
/**
* Time in seconds for preparing
*
* @implSpec {@code 300} by default
*/
public final int SetupDuration;
/**
* Time in seconds the final countdown is long
*
* @implSpec {@code 30} by default
*/
public final int PreFightDuration;
/**
* Time in seconds to spectate the arena after the fight
*
* @implSpec {@code 30} by default
*/
public final int SpectatorDuration;
private TimesConfig(YMLWrapper loader) {
loaded = loader.canLoad();
NoPlayersOnlineDuration = loader.getInt("NoPlayersOnlineDuration", 300);
PreSchemPasteDuration = loader.getInt("PreSchemPasteDuration", 120);
SetupDuration = loader.getInt("SetupDuration", 300);
PreFightDuration = loader.getInt("PreFightDuration", 30);
SpectatorDuration = loader.getInt("SpectatorDuration", 60);
}
}
@ToString
public static final class ArenaConfig {
public final boolean loaded;
/**
* The amount of blocks the schematics should be pasted under the surface
*
* @implSpec {@code 0} by default
*/
public final int WaterDepth;
/**
* The outer border of the arena, measured in blocks around the schematic areas
*/
public final Schem2BorderConfig Schem2Border;
/**
* The offset the teams spawn relative to the center of their area
*/
public final SpawnOffsetConfig SpawnOffset;
/**
* The size of the team areas are expanded around the schematics
*
* @implSpec {@code 12} by default
*/
public final int BorderFromSchematic;
/**
* If ground walkable, teams can walk below the lower arena border during setup
*
* @implSpec {@code true} by default
*/
public final boolean GroundWalkable;
/**
* Disable snow and ice melting
*
* @implSpec {@code false} by default
*/
public final boolean DisableSnowMelt;
/**
* Allow leaving the arena area as spectator
*
* @implSpec {@code false} by default
*/
public final boolean Leaveable;
/**
* Allow missiles to fly to the enemy and not stop at the schem border.
*
* @implSpec {@code !EnterStages.isEmpty()} by default
*/
public final boolean AllowMissiles;
/**
* Denotes that there is no floor for this GameMode
*
* @implSpec {@code false} by default
*/
public final boolean NoFloor;
private ArenaConfig(YMLWrapper loader, SchematicConfig.SizeConfig Size, List<Integer> EnterStages) {
loaded = loader.canLoad();
WaterDepth = loader.getInt("WaterDepth", 0);
Schem2Border = new Schem2BorderConfig(loader.with("Schem2Border"));
SpawnOffset = new SpawnOffsetConfig(loader.with("SpawnOffset"), Size);
BorderFromSchematic = loader.getInt("BorderFromSchematic", 21);
GroundWalkable = loader.getBoolean("GroundWalkable", true);
DisableSnowMelt = loader.getBoolean("DisableSnowMelt", false);
Leaveable = loader.getBoolean("Leaveable", false);
AllowMissiles = loader.getBoolean("AllowMissiles", !EnterStages.isEmpty());
NoFloor = loader.getBoolean("NoFloor", false);
}
@ToString
public static final class Schem2BorderConfig {
public final boolean loaded;
/**
* @implSpec {@code 24} by default
*/
public final int x;
/**
* @implSpec {@code 24} by default
*/
public final int z;
private Schem2BorderConfig(YMLWrapper loader) {
loaded = loader.canLoad();
x = loader.getInt("x", 24);
z = loader.getInt("z", 24);
}
}
@ToString
public static final class SpawnOffsetConfig {
public final boolean loaded;
/**
* @implSpec {@code 0} by default
*/
public final double x;
/**
* @implSpec {@code Schematic.Size.y} by default
*/
public final double y;
/**
* @implSpec {@code 0} by default
*/
public final double z;
private SpawnOffsetConfig(YMLWrapper loader, SchematicConfig.SizeConfig Size) {
loaded = loader.canLoad();
x = loader.getDouble("x", 0);
y = loader.getDouble("y", Size.y);
z = loader.getDouble("z", 0);
}
}
}
@ToString
public static final class SchematicConfig<M> {
public final boolean loaded;
/**
* The size of the schematics
*/
public final SizeConfig Size;
/**
* Used for GameModes with a technic area
*/
public final InsetConfig Inset;
/**
* The schematic type that can be chosen in this arena
*
* @implSpec {@code Normal} by default
*/
public final SchematicType Type;
/**
* The schematic types that are also allowed to be chosen in this arena
*/
public final List<SchematicType> SubTypes;
/**
* Shortcut of the schematic type
*
* @implSpec {@code ""} by default
*/
public final String Shortcut;
/**
* Spigot (1.8) material for GUIs
*
* @implSpec {@code STONE_BUTTON} by default
*/
public final M Material;
/**
* Manual check of schematic necessary
*
* @implSpec {@code true} by default
*/
public final boolean ManualCheck;
/**
* If the schematics should be rotated during pasting
*
* @implSpec {@code true} by default
*/
public final boolean Rotate;
/**
* If the schematics should be pasted aligned to the borders instead of centered
*
* @implSpec {@code false} by default
*/
public final boolean PasteAligned;
/**
* If only public schematics are allowed
*
* @implSpec {@code false} by default
*/
public final boolean OnlyPublicSchematics;
/**
* If the public only force should be completely disabled
*
* @implSpec {@code false} by default
*/
public final boolean IgnorePublicOnly;
/**
* If obsidian and bedrock should be replaced during PRE_RUNNING
*
* @implSpec {@code false} by default
*/
public final boolean ReplaceObsidianBedrock;
/**
* If the replacement should happen with block updates
*
* @implSpec {@code false} by default
*/
public final boolean ReplaceWithBlockupdates;
/**
* If the schematic perparation arena mode is time limited
*
* @implSpec {@code false} by default
*/
public final boolean UnlimitedPrepare;
/**
* Maximal amount of blocks allowed in the schematic
*
* @implSpec {@code 0} by default
*/
public final int MaxBlocks;
/**
* Maximal amount of items per dispenser
*
* @implSpec {@code 128} by default
*/
public final int MaxDispenserItems;
/**
* Maximal blast resistance for the design blocks
*
* @implSpec {@code Double.MAX_VALUE} by default
*/
public final double MaxDesignBlastResistance;
/**
* List of limited material (combinations)<br/>
* List contains tags Amount (integer) and Materials (List of material names in Spigot 1.12 AND Spigot 1.15 format)
*/
public final Map<Set<M>, Integer> Limited;
private SchematicConfig(YMLWrapper<M, ?> loader) {
loaded = loader.canLoad();
Size = new SizeConfig(loader.with("Size"));
Inset = new InsetConfig(loader.with("Inset"));
Type = loader.getSchematicType("Type", "Normal");
SubTypes = loader.getSchematicTypeList("SubTypes");
Shortcut = loader.getString("Shortcut", "");
Material = loader.getMaterial("Material", "STONE_BUTTON");
ManualCheck = loader.getBoolean("ManualCheck", true);
Rotate = loader.getBoolean("Rotate", true);
PasteAligned = loader.getBoolean("PasteAligned", false);
OnlyPublicSchematics = loader.getBoolean("OnlyPublicSchematics", false);
IgnorePublicOnly = loader.getBoolean("IgnorePublicOnly", false);
ReplaceObsidianBedrock = loader.getBoolean("ReplaceObsidianBedrock", false);
ReplaceWithBlockupdates = loader.getBoolean("ReplaceWithBlockupdates", false);
UnlimitedPrepare = loader.getBoolean("UnlimitedPrepare", false);
MaxBlocks = loader.getInt("MaxBlocks", 0);
MaxDispenserItems = loader.getInt("MaxDispenserItems", 128);
MaxDesignBlastResistance = loader.getDouble("MaxDesignBlastResistance", Double.MAX_VALUE);
Map<Set<M>, Integer> Limited = new HashMap<>();
for (Map<?, ?> entry : loader.getMapList("Limited")) {
int amount = (Integer) entry.get("Amount");
Set<String> materials = new HashSet<>((List<String>) entry.get("Materials"));
if (amount == 0) {
materials.forEach(material -> {
Limited.put(Collections.singleton(loader.materialMapper.apply(material.toUpperCase())), 0);
});
} else {
Limited.put(Collections.unmodifiableSet(materials.stream().map(String::toUpperCase).map(loader.materialMapper).collect(Collectors.toSet())), amount);
}
}
this.Limited = Collections.unmodifiableMap(Limited);
}
@ToString
public static final class SizeConfig {
public final boolean loaded;
/**
* @implSpec {@code 0} by default
*/
public final int x;
/**
* @implSpec {@code 0} by default
*/
public final int y;
/**
* @implSpec {@code 0} by default
*/
public final int z;
private SizeConfig(YMLWrapper loader) {
loaded = loader.canLoad();
x = loader.getInt("x", 0);
y = loader.getInt("y", 0);
z = loader.getInt("z", 0);
}
}
@ToString
public static final class InsetConfig {
public final boolean loaded;
/**
* @implSpec {@code 0} by default
*/
public final int x;
/**
* @implSpec {@code 0} by default
*/
public final int z;
/**
* @implSpec {@code 0} by default
*/
public final int top;
/**
* @implSpec {@code 0} by default
*/
public final int bottom;
private InsetConfig(YMLWrapper loader) {
loaded = loader.canLoad();
x = loader.getInt("x", 0);
z = loader.getInt("z", 0);
top = loader.getInt("top", 0);
bottom = loader.getInt("bottom", 0);
}
}
}
@ToString
public static final class BlueConfig {
public final boolean loaded;
/**
* @implSpec {@code Blau} by default
*/
public final String Name;
/**
* @implSpec {@code §3} by default
*/
public final String Prefix;
private BlueConfig(YMLWrapper loader) {
loaded = loader.canLoad();
Name = loader.getString("Name", "Blau");
Prefix = loader.getString("Prefix", "§3");
}
}
@ToString
public static final class RedConfig {
public final boolean loaded;
/**
* @implSpec {@code Rot} by default
*/
public final String Name;
/**
* @implSpec {@code §c} by default
*/
public final String Prefix;
private RedConfig(YMLWrapper loader) {
loaded = loader.canLoad();
Name = loader.getString("Name", "Rot");
Prefix = loader.getString("Prefix", "§c");
}
}
@ToString
public static final class WinConditionParamsConfig<M> {
public final boolean loaded;
/**
* The time of any of the timeout win conditions in seconds
*
* @implSpec {@code 1200} by default
*/
public final int TimeoutTime;
/**
* The percentage when any of the percent win conditions limits or triggers a win
*
* @implSpec {@code 7.0} by default
*/
public final double PercentWin;
/**
* Does the percentage still change after the start of the enter phase
*
* @implSpec {@code true} by default
*/
public final boolean PercentEntern;
/**
* Is Blocks a whitelist (true) or blacklist (false)
*
* @implSpec {@code false} by default
*/
public final boolean BlocksWhitelist;
/**
* Special Blocks (Valid spigot material values) used by the percent win conditions
*/
public final List<M> Blocks;
/**
* Time for being declared TechKo without a shot given.
*
* @implSpec {@code 90} by default
*/
public final int TechKoTime;
private WinConditionParamsConfig(YMLWrapper<M, ?> loader) {
loaded = loader.canLoad();
TimeoutTime = loader.getInt("TimeoutTime", 1200);
PercentWin = loader.getDouble("PercentWin", 7.0);
PercentEntern = loader.getBoolean("PercentEntern", true);
BlocksWhitelist = loader.getBoolean("BlocksWhitelist", false);
Blocks = loader.getMaterialList("Blocks");
TechKoTime = loader.getInt("TechKoTime", 90);
}
}
@ToString
public static final class KitsConfig<M> {
public final boolean loaded;
/**
* The kit file for this configuration
*
* @implSpec {@code kits.yml} by default
*/
public final String File;
/**
* The default kit for team members
*
* @implSpec {@code default} by default
*/
public final String MemberDefault;
/**
* The default kit for team leaders
*
* @implSpec {@code default} by default
*/
public final String LeaderDefault;
/**
* If the personal kit system is active
*
* @implSpec {@code false} by default
*/
public final boolean PersonalKits;
/**
* Items (Valid spigot material values) that are not allowed in the personal kit
*/
public final List<M> ForbiddenItems;
private KitsConfig(YMLWrapper<M, ?> loader) {
loaded = loader.canLoad();
File = loader.getString("File", "kits.yml");
MemberDefault = loader.getString("MemberDefault", "default");
LeaderDefault = loader.getString("LeaderDefault", "default");
PersonalKits = loader.getBoolean("PersonalKits", false);
ForbiddenItems = loader.getMaterialList("ForbiddenItems");
}
}
@ToString
public static final class TechhiderConfig<M> {
public final boolean loaded;
/**
* Activates the tech hider
*
* @implSpec {@code false} by default
*/
public final boolean Active;
/**
* Which block the tech hider replaces to.
*
* @implSpec {@code end_stone} by default
*/
public final M ObfuscateWith;
/**
* A list of all hidden blocks. "water" results in the hiding of all waterlogged blocks as well.
*/
public final Set<M> HiddenBlocks;
/**
* The block entity contents that are hidden (here with minecraft:nametag)
*/
public final Set<String> HiddenBlockEntities;
private TechhiderConfig(YMLWrapper<M, ?> loader) {
loaded = loader.canLoad();
Active = loader.getBoolean("Active", false);
ObfuscateWith = loader.getMaterial("ObfuscateWith", "end_stone");
HiddenBlocks = Collections.unmodifiableSet(new HashSet<>(loader.getMaterialList("HiddenBlocks")));
HiddenBlockEntities = Collections.unmodifiableSet(new HashSet<>(loader.getStringList("HiddenBlockEntities")));
}
}
public boolean isAfterDeadline() {
return Deadline != null && Deadline.before(Date.from(Instant.now()));
}
public String hasMap(String map) {
for (String m : Server.Maps) {
if (m.equalsIgnoreCase(map))
return m;
}
return null;
}
public String getRandomMap() {
return Server.Maps.get(random.nextInt(Server.Maps.size()));
}
public String convertToRealMapName(String map) {
for (String m : Server.Maps) {
if (m.equalsIgnoreCase(map))
return m;
}
return null;
}
public String getChatName() {
return Server.ChatNames.get(0);
}
public boolean isActive() {
if (Server.ChatNames.isEmpty()) return false;
if (ActiveMonths.isEmpty()) return true;
return ActiveMonths.contains(LocalDateTime.now().getMonth().getValue());
}
public String getSchemTypeOrInternalName() {
if (Schematic.loaded) {
return Schematic.Type.toDB();
}
return configFile.getName().replace(".yml", "");
}
}
@@ -21,13 +21,17 @@ package de.steamwar.sql;
import de.steamwar.ImplementationProvider;
import java.util.List;
import java.util.Map;
import java.io.File;
public interface SQLWrapper {
SQLWrapper impl = ImplementationProvider.getImpl("de.steamwar.sql.SQLWrapperImpl");
public interface SQLWrapper<M> {
SQLWrapper<?> impl = ImplementationProvider.getImpl("de.steamwar.sql.SQLWrapperImpl");
void loadSchemTypes(List<SchematicType> tmpTypes, Map<String, SchematicType> tmpFromDB);
File getSchemTypesFolder();
GameModeConfig<M, String> loadGameModeConfig(File file);
default void processSchematicType(GameModeConfig<?, String> gameModeConfig) {
}
void additionalExceptionMetadata(StringBuilder builder);
}
@@ -22,7 +22,9 @@ package de.steamwar.sql;
import de.steamwar.sql.internal.SqlTypeMapper;
import lombok.Getter;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
public class SchematicType {
@@ -38,7 +40,23 @@ public class SchematicType {
tmpTypes.add(Normal);
tmpFromDB.put(Normal.name().toLowerCase(), Normal);
SQLWrapper.impl.loadSchemTypes(tmpTypes, tmpFromDB);
File folder = SQLWrapper.impl.getSchemTypesFolder();
if (folder.exists()) {
for (File configFile : Arrays.stream(folder.listFiles((file, name) -> name.endsWith(".yml") && !name.endsWith(".kits.yml"))).sorted().collect(Collectors.toList())) {
GameModeConfig<?, String> gameModeConfig = SQLWrapper.impl.loadGameModeConfig(configFile);
if (gameModeConfig.Schematic.Type == null) continue;
if (tmpFromDB.containsKey(gameModeConfig.Schematic.Type.toDB())) continue;
SchematicType current = gameModeConfig.Schematic.Type;
if (!gameModeConfig.CheckQuestions.isEmpty()) {
SchematicType checkType = current.checkType;
tmpTypes.add(checkType);
tmpFromDB.put(checkType.toDB(), checkType);
}
tmpTypes.add(current);
tmpFromDB.put(current.toDB(), current);
SQLWrapper.impl.processSchematicType(gameModeConfig);
}
}
fromDB = Collections.unmodifiableMap(tmpFromDB);
types = Collections.unmodifiableList(tmpTypes);
@@ -109,6 +127,7 @@ public class SchematicType {
}
public static SchematicType fromDB(String input) {
if (fromDB == null) return null;
return fromDB.get(input.toLowerCase());
}
@@ -0,0 +1,163 @@
/*
* 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 org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
final class YMLWrapper<M, W> {
final Function<String, M> materialMapper;
final Function<String, W> winconditionMapper;
private final boolean canLoad;
private final Map<String, Object> document;
YMLWrapper(File file, Function<String, M> materialMapper, Function<String, W> winconditionMapper) {
this.materialMapper = materialMapper;
this.winconditionMapper = winconditionMapper;
Yaml yaml = new Yaml();
Map<String, Object> document = Collections.emptyMap();
boolean canLoad = false;
if (file != null && file.exists() && file.isFile()) {
try {
document = yaml.load(new FileReader(file));
canLoad = true;
} catch (IOException e) {
// Ignore
}
}
this.document = document;
this.canLoad = canLoad;
}
private YMLWrapper(boolean canLoad, Map<String, Object> document, Function<String, M> materialMapper, Function<String, W> winconditionMapper) {
this.materialMapper = materialMapper;
this.winconditionMapper = winconditionMapper;
this.canLoad = canLoad;
this.document = document;
}
public boolean canLoad() {
return canLoad;
}
public YMLWrapper<M, W> with(String path) {
if (document.containsKey(path)) {
Object value = document.get(path);
if (value instanceof Map) {
return new YMLWrapper<>(true, (Map) value, materialMapper, winconditionMapper);
}
}
return new YMLWrapper<>(false, Collections.emptyMap(), materialMapper, winconditionMapper);
}
public <T> T get(String path, T defaultValue, Function<Object, T> mapper) {
Object value = this.document.get(path);
if (value == null) return defaultValue;
try {
T mapped = mapper.apply(value);
if (mapped != null) return mapped;
} catch (ClassCastException e) {
// Ignore exception
}
return defaultValue;
}
public String getString(String path, String defaultValue) {
return get(path, defaultValue, Objects::toString);
}
public int getInt(String path, int defaultValue) {
return get(path, defaultValue, Integer.class::cast);
}
public double getDouble(String path, double defaultValue) {
return get(path, defaultValue, Double.class::cast);
}
public boolean getBoolean(String path, boolean defaultValue) {
return get(path, defaultValue, Boolean.class::cast);
}
public SchematicType getSchematicType(String path, String defaultValue) {
String schematicType = getString(path, defaultValue);
return SchematicType.fromDB(schematicType);
}
public M getMaterial(String path, String defaultValue) {
return materialMapper.apply(getString(path, defaultValue).toUpperCase());
}
public <T> List<T> get(String path, Function<Object, List<T>> mapper) {
Object value = this.document.get(path);
if (value == null) return Collections.emptyList();
try {
return Collections.unmodifiableList(mapper.apply(value));
} catch (ClassCastException e) {
return Collections.emptyList();
}
}
public List<String> getStringList(String path) {
return get(path, o -> (List<String>) o);
}
public List<Integer> getIntList(String path) {
return get(path, o -> (List<Integer>) o);
}
public List<SchematicType> getSchematicTypeList(String path) {
List<String> list = getStringList(path);
if (list.isEmpty()) {
return Collections.emptyList();
} else {
return Collections.unmodifiableList(list.stream().map(SchematicType::fromDB).collect(Collectors.toList()));
}
}
public List<M> getMaterialList(String path) {
List<String> list = getStringList(path);
if (list.isEmpty()) {
return Collections.emptyList();
} else {
return Collections.unmodifiableList(list.stream().map(String::toUpperCase).map(materialMapper).collect(Collectors.toList()));
}
}
public List<Map<?, ?>> getMapList(String path) {
Object value = this.document.get(path);
if (value instanceof List) {
return Collections.unmodifiableList((List<Map<?, ?>>) value);
} else {
return Collections.emptyList();
}
}
}