diff --git a/TowerRun/build.gradle.kts b/TowerRun/build.gradle.kts
new file mode 100644
index 00000000..fcecdfeb
--- /dev/null
+++ b/TowerRun/build.gradle.kts
@@ -0,0 +1,67 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2024 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 .
+ */
+
+plugins {
+ id("java")
+ id("base")
+
+ id("com.github.johnrengelman.shadow")
+}
+
+group = "de.steamwar"
+version = ""
+
+tasks.compileJava {
+ options.encoding = "UTF-8"
+}
+
+tasks.build {
+ finalizedBy(tasks.shadowJar)
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+}
+
+sourceSets {
+ main {
+ java {
+ srcDirs("src/")
+ }
+ resources {
+ srcDirs("src/")
+ exclude("**/*.java", "**/*.kt")
+ }
+ }
+}
+
+dependencies {
+ compileOnly("org.projectlombok:lombok:1.18.32")
+ annotationProcessor("org.projectlombok:lombok:1.18.32")
+ compileOnly("org.spigotmc:plugin-annotations:1.2.3-SNAPSHOT")
+ annotationProcessor("org.spigotmc:plugin-annotations:1.2.3-SNAPSHOT")
+
+ compileOnly(project(":SpigotCore"))
+
+ compileOnly("de.steamwar:spigot:1.19")
+ compileOnly("de.steamwar:worldedit:1.15")
+
+ compileOnly("org.spigotmc:spigot-api:1.19-R0.1-SNAPSHOT")
+}
diff --git a/TowerRun/src/TowerRun.properties b/TowerRun/src/TowerRun.properties
new file mode 100644
index 00000000..88c2ecd9
--- /dev/null
+++ b/TowerRun/src/TowerRun.properties
@@ -0,0 +1,36 @@
+#
+# This file is a part of the SteamWar software.
+#
+# Copyright (C) 2023 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 .
+#
+
+PREFIX=§eTowerRun§8»§r
+
+PLAYER_DIED=§c{0} §7died§8!
+PLAYER_ESCAPE=§a{0} §7escaped§8!
+GAME_START=§aThe game has started§8!
+GAME_WIN=§a{0} §7has won the game§8!
+GAME_STARTING=§7Starting: §e{0}s§8!
+GAME_WAITING=§7Waiting for players§8...
+SERVER_STOPPING=§cThe server stops in §e{0}s§8!
+SERVER_RESET=§cThe server will be reset in §e{0}s§8!
+KEY_NAME=§eKey
+KEY_FOUND=§a{0} §7found a key§8!
+GAME_TIE=§aThe game ended in a tie§8!
+GAME_TIME=§a{0}:{1}
+CATCH_UP_WARNING=§4!! §cYou should catch up §4!!
+
+COMMAND_START=§aThe game will start soon§8!
\ No newline at end of file
diff --git a/TowerRun/src/TowerRun_de.properties b/TowerRun/src/TowerRun_de.properties
new file mode 100644
index 00000000..2c88c3b6
--- /dev/null
+++ b/TowerRun/src/TowerRun_de.properties
@@ -0,0 +1,35 @@
+#
+# This file is a part of the SteamWar software.
+#
+# Copyright (C) 2023 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 .
+#
+
+PLAYER_DIED=§c{0} §7ist gestorben§8!
+PLAYER_ESCAPE=§a{0} §7ist entkommen§8!
+GAME_START=§aDas Spiel beginnt§8!
+GAME_WIN=§a{0} §7hat das Spiel gewonnen§8!
+GAME_STARTING=§7Das Spiel startet in §e{0}s§8!
+GAME_WAITING=§7Warte auf weitere Spieler§8...
+SERVER_STOPPING=§cDer Server stoppt in §e{0}s§8!
+SERVER_RESET=§cDer Server wird in §e{0}s §czurückgesetzt§8!
+GAME_TIE=§7Keiner hat gewonnen§8!
+
+KEY_NAME=§eSchlüssel
+KEY_FOUND=§a{0} §7hat einen Schlüssel gefunden§8!
+
+CATCH_UP_WARNING=§4!! §cDu solltest aufholen §4!!
+
+COMMAND_START=§7Das Spiel startet bald§8!
\ No newline at end of file
diff --git a/TowerRun/src/de/steamwar/towerrun/TowerRun.java b/TowerRun/src/de/steamwar/towerrun/TowerRun.java
new file mode 100644
index 00000000..c0717b2f
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/TowerRun.java
@@ -0,0 +1,84 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun;
+
+import de.steamwar.message.Message;
+import de.steamwar.towerrun.commands.StartCommand;
+import de.steamwar.towerrun.config.WorldConfig;
+import de.steamwar.towerrun.countdowns.EndCountdown;
+import de.steamwar.towerrun.countdowns.GameCountdown;
+import de.steamwar.towerrun.countdowns.LobbyCountdown;
+import de.steamwar.towerrun.game.TowerRunGame;
+import de.steamwar.towerrun.generator.TowerGenerator;
+import de.steamwar.towerrun.listener.GlobalListener;
+import de.steamwar.towerrun.listener.IngameListener;
+import de.steamwar.towerrun.listener.LobbyListener;
+import de.steamwar.towerrun.listener.NotLobbyListener;
+import lombok.Getter;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.bukkit.plugin.java.annotation.dependency.Dependency;
+import org.bukkit.plugin.java.annotation.plugin.ApiVersion;
+import org.bukkit.plugin.java.annotation.plugin.Description;
+import org.bukkit.plugin.java.annotation.plugin.Plugin;
+import org.bukkit.plugin.java.annotation.plugin.author.Author;
+
+@Plugin(name = "TowerRun", version = "1.0.0")
+@Dependency("SpigotCore")
+@Author("YoyoNow")
+@Author("Chaoscaot")
+@Description("SteamWar TowerRun Plugin")
+@ApiVersion(ApiVersion.Target.v1_19)
+public class TowerRun extends JavaPlugin {
+
+ @Getter
+ private static TowerRun instance;
+
+ @Getter
+ private static Message message;
+
+ @Getter
+ private static TowerGenerator towerGenerator = null;
+
+ @Getter
+ private static GameCountdown gameCountdown;
+
+
+ @Override
+ public void onEnable() {
+ instance = this;
+ message = new Message("TowerRun", getClassLoader());
+
+ if (WorldConfig.TOWER_GENERATOR_CONFIG != null) {
+ towerGenerator = new TowerGenerator(WorldConfig.TOWER_GENERATOR_CONFIG);
+ }
+
+ new LobbyListener();
+ new IngameListener();
+ new GlobalListener();
+ new LobbyListener();
+ new NotLobbyListener();
+ final LobbyCountdown lobbyCountdown = new LobbyCountdown();
+ new EndCountdown(lobbyCountdown);
+ new StartCommand(lobbyCountdown);
+ gameCountdown = new GameCountdown();
+
+ TowerRunGame.reset();
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/commands/StartCommand.java b/TowerRun/src/de/steamwar/towerrun/commands/StartCommand.java
new file mode 100644
index 00000000..ac93fd85
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/commands/StartCommand.java
@@ -0,0 +1,51 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.commands;
+
+import de.steamwar.command.SWCommand;
+import de.steamwar.command.TypeValidator;
+import de.steamwar.sql.SteamwarUser;
+import de.steamwar.sql.UserGroup;
+import de.steamwar.towerrun.TowerRun;
+import de.steamwar.towerrun.countdowns.LobbyCountdown;
+import org.bukkit.entity.Player;
+
+public class StartCommand extends SWCommand {
+ private final LobbyCountdown countdown;
+
+ public StartCommand(LobbyCountdown countdown) {
+ super("start", "skip");
+ this.countdown = countdown;
+ }
+
+ @Register
+ public void command(@Validator Player player) {
+ if (countdown.getTime() > 10) {
+ countdown.setOverride(true);
+ countdown.setTime(10);
+ }
+ TowerRun.getMessage().send("COMMAND_START", player);
+ }
+
+ @ClassValidator(value = Player.class, local = true)
+ public TypeValidator playerValidator() {
+ return (commandSender, player, messageSender) -> SteamwarUser.get(player.getUniqueId()).getUserGroup() != UserGroup.Member;
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/config/Config.java b/TowerRun/src/de/steamwar/towerrun/config/Config.java
new file mode 100644
index 00000000..7890e785
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/config/Config.java
@@ -0,0 +1,58 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.config;
+
+import de.steamwar.towerrun.TowerRun;
+import lombok.experimental.UtilityClass;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@UtilityClass
+public class Config {
+ public static final int MIN_PLAYERS;
+ public static final int LOBBY_TIMER;
+ public static final Set DESTROYABLE_BLOCKS;
+ public static final int GAME_TIMER;
+ public static final int GAME_ESCAPE_TIMER;
+
+ static {
+ File configFile = new File(TowerRun.getInstance().getDataFolder(), "config.yml");
+ if (!configFile.exists()) {
+ TowerRun.getInstance().getLogger().severe("Config not found!");
+ Bukkit.shutdown();
+ }
+
+ ConfigurationSection config = YamlConfiguration.loadConfiguration(configFile);
+
+ MIN_PLAYERS = config.getInt("minPlayers");
+ LOBBY_TIMER = config.getInt("lobbyTimer");
+ GAME_TIMER = config.getInt("gameTimer", 20 * 60);
+ GAME_ESCAPE_TIMER = config.getInt("gameEscapeTimer", 60);
+ DESTROYABLE_BLOCKS = EnumSet.copyOf(config.getStringList("destroyable").stream().map(Material::valueOf).collect(Collectors.toSet()));
+ }
+
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/config/WorldConfig.java b/TowerRun/src/de/steamwar/towerrun/config/WorldConfig.java
new file mode 100644
index 00000000..fafdd453
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/config/WorldConfig.java
@@ -0,0 +1,323 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.config;
+
+import de.steamwar.sql.SchematicType;
+import de.steamwar.towerrun.TowerRun;
+import de.steamwar.towerrun.winconditions.FirstOutsideWincondition;
+import de.steamwar.towerrun.winconditions.LastOutsideWincondition;
+import de.steamwar.towerrun.winconditions.LastRemainingWincondition;
+import de.steamwar.towerrun.winconditions.WinCondition;
+import lombok.Getter;
+import lombok.experimental.UtilityClass;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.util.Vector;
+
+import java.io.File;
+import java.util.*;
+
+@UtilityClass
+public class WorldConfig {
+
+ public static final Region[] REGIONS;
+ public static final Location[] DOORS;
+ public static final Location[] KEYS;
+ public static final int LAVA_Y;
+ public static final int LAVE_SPACE;
+ public static final Location SPAWN;
+ public static final int ESCAPE_HEIGHT;
+ public static final Location MIN_TOWER;
+ public static final Location MAX_TOWER;
+ public static final List ACTIVE_WINCONDITIONS;
+ public static final int MAP_MIN_X;
+ public static final int MAP_MIN_Z;
+ public static final int MAP_MAX_X;
+ public static final int MAP_MAX_Z;
+ public static final Map MELTING_TIMES;
+ public static final TowerGeneratorConfig TOWER_GENERATOR_CONFIG;
+
+ public static final List WINCONDITIONS = new ArrayList<>();
+
+ static {
+ WINCONDITIONS.add(new LastRemainingWincondition());
+ WINCONDITIONS.add(new LastOutsideWincondition());
+ WINCONDITIONS.add(new FirstOutsideWincondition());
+ }
+
+ private static Location parseLocation(ConfigurationSection section) {
+ Location loc = new Location(
+ Bukkit.getWorlds().get(0),
+ section.getDouble("x"),
+ section.getDouble("y"),
+ section.getDouble("z")
+ );
+ if (section.contains("yaw")) {
+ loc.setYaw((float) section.getDouble("yaw"));
+ }
+ if (section.contains("pitch")) {
+ loc.setPitch((float) section.getDouble("pitch"));
+ }
+ return loc;
+ }
+
+ static {
+ File configFile = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "config.yml");
+ if (!configFile.exists()) {
+ TowerRun.getInstance().getLogger().severe("World config not found!");
+ Bukkit.shutdown();
+ }
+
+ ConfigurationSection config = YamlConfiguration.loadConfiguration(configFile);
+ ConfigurationSection tower = config.getConfigurationSection("tower");
+
+ List regions = tower.getConfigurationSection("regions").getKeys(false).stream()
+ .map(tower.getConfigurationSection("regions")::getConfigurationSection)
+ .toList();
+ REGIONS = new Region[regions.size()];
+ for (int i = 0; i < regions.size(); i++) {
+ REGIONS[i] = new Region(regions.get(i));
+ }
+
+ if (REGIONS.length == 1) {
+ MIN_TOWER = REGIONS[0].min;
+ MAX_TOWER = REGIONS[0].max;
+ } else {
+ MIN_TOWER = new Location(
+ Bukkit.getWorlds().get(0),
+ Math.min(REGIONS[0].min.getX(), REGIONS[1].min.getX()),
+ 0,
+ Math.min(REGIONS[0].min.getZ(), REGIONS[1].min.getZ())
+ );
+ MAX_TOWER = new Location(
+ Bukkit.getWorlds().get(0),
+ Math.max(REGIONS[0].max.getX(), REGIONS[1].max.getX()),
+ 255,
+ Math.max(REGIONS[0].max.getZ(), REGIONS[1].max.getZ())
+ );
+ if (REGIONS.length > 2) {
+ for (int i = 2; i < REGIONS.length; i++) {
+ MIN_TOWER.setX(Math.min(MIN_TOWER.getX(), REGIONS[i].min.getX()));
+ MIN_TOWER.setZ(Math.min(MIN_TOWER.getZ(), REGIONS[i].min.getZ()));
+ MAX_TOWER.setX(Math.max(MAX_TOWER.getX(), REGIONS[i].max.getX()));
+ MAX_TOWER.setZ(Math.max(MAX_TOWER.getZ(), REGIONS[i].max.getZ()));
+ }
+ }
+ }
+
+ ESCAPE_HEIGHT = tower.getInt("escapeHeight");
+ SPAWN = parseLocation(tower.getConfigurationSection("spawn"));
+ ConfigurationSection doorSection = tower.getConfigurationSection("doors");
+ if (doorSection != null) {
+ List doors = doorSection.getKeys(false).stream()
+ .map(tower.getConfigurationSection("doors")::getConfigurationSection)
+ .toList();
+ DOORS = new Location[doors.size()];
+ for (int i = 0; i < doors.size(); i++) {
+ DOORS[i] = parseLocation(doors.get(i));
+ }
+ } else {
+ DOORS = new Location[0];
+ }
+
+ ConfigurationSection keysSection = tower.getConfigurationSection("keys");
+ if (keysSection != null) {
+ List keys = keysSection.getKeys(false).stream()
+ .map(tower.getConfigurationSection("keys")::getConfigurationSection)
+ .toList();
+ KEYS = new Location[keys.size()];
+ for (int i = 0; i < keys.size(); i++) {
+ KEYS[i] = parseLocation(keys.get(i));
+ }
+ } else {
+ KEYS = new Location[0];
+ }
+
+ LAVA_Y = tower.getInt("lavaY");
+ LAVE_SPACE = tower.getInt("laveSpace");
+
+ MAP_MIN_X = config.getInt("minX");
+ MAP_MIN_Z = config.getInt("minZ");
+ MAP_MAX_X = config.getInt("maxX");
+ MAP_MAX_Z = config.getInt("maxZ");
+
+ ConfigurationSection meltingBlocksSection = tower.getConfigurationSection("meltingBlocks");
+ if (meltingBlocksSection != null) {
+ Map meltingTimes = new HashMap<>();
+ meltingBlocksSection.getKeys(false).forEach(s -> {
+ meltingTimes.put(Material.valueOf(s), meltingBlocksSection.getInt(s));
+ });
+ MELTING_TIMES = Collections.unmodifiableMap(meltingTimes);
+ } else {
+ MELTING_TIMES = Collections.emptyMap();
+ }
+
+ ACTIVE_WINCONDITIONS = config.getStringList("winconditions");
+ WINCONDITIONS.stream().filter(winCondition -> ACTIVE_WINCONDITIONS.contains(winCondition.getName())).forEach(winCondition -> winCondition.setActive(true));
+
+ ConfigurationSection towerGeneratorSection = config.getConfigurationSection("towerGenerator");
+ if (towerGeneratorSection == null) {
+ TOWER_GENERATOR_CONFIG = null;
+ } else {
+ TOWER_GENERATOR_CONFIG = new TowerGeneratorConfig(towerGeneratorSection);
+ }
+ }
+
+ @Getter
+ public static final class Region {
+ public final Location min;
+ public final Location max;
+
+ public Region(ConfigurationSection section) {
+ min = new Location(
+ Bukkit.getWorlds().get(0),
+ section.getDouble("minX"),
+ 0,
+ section.getDouble("minZ")
+ );
+ max = new Location(
+ Bukkit.getWorlds().get(0),
+ section.getDouble("maxX"),
+ 255,
+ section.getDouble("maxZ")
+ );
+ }
+
+ public boolean contains(Vector vector) {
+ return vector.getX() >= min.getX() && vector.getX() <= max.getX()
+ && vector.getZ() >= min.getZ() && vector.getZ() <= max.getZ();
+ }
+ }
+
+ @Getter
+ public static final class TowerGeneratorConfig {
+ public final int x;
+ public final int y;
+ public final int z;
+ public final int minHeight;
+ public final int maxHeight;
+ public final int minNoBombFloors;
+ public final int maxNoBombFloors;
+ public final double tntChance;
+ public final int minBombs;
+ public final int maxBombs;
+ public final double keyChance;
+ public final int minNoKeyFloors;
+ public final int maxNoKeyFloors;
+ public final SchematicType schematicType;
+ public final TowerGeneratorFillRegion[] fillRegions;
+ public final Region[] tntRegions;
+ public final TowerGeneratorDoorBlock[] doorBlocks;
+
+ public TowerGeneratorConfig(ConfigurationSection section) {
+ x = section.getInt("x");
+ y = section.getInt("y");
+ z = section.getInt("z");
+ minHeight = section.getInt("minHeight");
+ maxHeight = section.getInt("maxHeight");
+ minNoBombFloors = section.getInt("minNoBombFloors");
+ maxNoBombFloors = section.getInt("maxNoBombFloors");
+ minBombs = section.getInt("minBombs");
+ maxBombs = section.getInt("maxBombs");
+ tntChance = section.getDouble("tntChance");
+ keyChance = section.getDouble("keyChance");
+ minNoKeyFloors = section.getInt("minNoKeyFloors");
+ maxNoKeyFloors = section.getInt("maxNoKeyFloors");
+ schematicType = SchematicType.fromDB(section.getString("schematicType"));
+
+ ConfigurationSection fillRegionsSection = section.getConfigurationSection("fillRegions");
+ if (fillRegionsSection != null) {
+ List fillRegions = fillRegionsSection.getKeys(false).stream()
+ .map(fillRegionsSection::getConfigurationSection)
+ .toList();
+
+ this.fillRegions = new TowerGeneratorFillRegion[fillRegions.size()];
+ for (int i = 0; i < fillRegions.size(); i++) {
+ this.fillRegions[i] = new TowerGeneratorFillRegion(fillRegions.get(i));
+ }
+ } else {
+ fillRegions = new TowerGeneratorFillRegion[0];
+ }
+
+ ConfigurationSection tntRegionsSection = section.getConfigurationSection("tntRegions");
+ if (tntRegionsSection != null) {
+ List tntRegions = tntRegionsSection.getKeys(false).stream()
+ .map(tntRegionsSection::getConfigurationSection)
+ .toList();
+
+ this.tntRegions = new Region[tntRegions.size()];
+ for (int i = 0; i < tntRegions.size(); i++) {
+ this.tntRegions[i] = new Region(tntRegions.get(i));
+ }
+ } else {
+ tntRegions = new Region[0];
+ }
+
+ ConfigurationSection doorBlocksSection = section.getConfigurationSection("doorBlocks");
+ if (doorBlocksSection != null) {
+ List doorBlocks = doorBlocksSection.getKeys(false).stream()
+ .map(doorBlocksSection::getConfigurationSection)
+ .toList();
+
+ this.doorBlocks = new TowerGeneratorDoorBlock[doorBlocks.size()];
+ for (int i = 0; i < doorBlocks.size(); i++) {
+ this.doorBlocks[i] = new TowerGeneratorDoorBlock(doorBlocks.get(i));
+ }
+ } else {
+ doorBlocks = new TowerGeneratorDoorBlock[0];
+ }
+ }
+ }
+
+ @Getter
+ public static final class TowerGeneratorFillRegion {
+ private final int minX;
+ private final int minZ;
+ private final int maxX;
+ private final int maxZ;
+ private final double percentage;
+ private final Material material;
+
+ public TowerGeneratorFillRegion(ConfigurationSection section) {
+ minX = section.getInt("minX");
+ minZ = section.getInt("minZ");
+ maxX = section.getInt("maxX");
+ maxZ = section.getInt("maxZ");
+ percentage = section.getDouble("percentage");
+ material = Material.valueOf(section.getString("material"));
+ }
+ }
+
+ @Getter
+ public static final class TowerGeneratorDoorBlock {
+ private final int x;
+ private final int dy;
+ private final int z;
+
+ public TowerGeneratorDoorBlock(ConfigurationSection section) {
+ x = section.getInt("x");
+ dy = section.getInt("dy");
+ z = section.getInt("z");
+ }
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/countdowns/Countdown.java b/TowerRun/src/de/steamwar/towerrun/countdowns/Countdown.java
new file mode 100644
index 00000000..727d19ea
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/countdowns/Countdown.java
@@ -0,0 +1,80 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.countdowns;
+
+import de.steamwar.towerrun.TowerRun;
+import de.steamwar.towerrun.state.GameStateToggleListener;
+import de.steamwar.towerrun.state.GameStates;
+import lombok.Getter;
+import lombok.Setter;
+import org.bukkit.Bukkit;
+import org.bukkit.scheduler.BukkitTask;
+
+import java.util.EnumSet;
+
+public abstract class Countdown extends GameStateToggleListener {
+
+ @Setter
+ @Getter
+ protected int time = defaultTime();
+ protected BukkitTask task;
+
+ protected Countdown(EnumSet enabledStates) {
+ super(enabledStates);
+ }
+
+ int defaultTime() {
+ return 0;
+ }
+ void timerEnd() {}
+ boolean timerShouldCancel() {
+ return true;
+ }
+ void run() {}
+ void timerStart() {}
+ void timerReset() {}
+
+ private void timer() {
+ if (timerShouldCancel()) {
+ time = defaultTime();
+ timerReset();
+ } else {
+ if (time == defaultTime()) {
+ timerStart();
+ }
+ if (time == 0) {
+ timerEnd();
+ return;
+ }
+ run();
+ time--;
+ }
+ }
+
+ @Override
+ public void enable() {
+ task = Bukkit.getScheduler().runTaskTimer(TowerRun.getInstance(), this::timer, 0, 20);
+ }
+
+ @Override
+ public void disable() {
+ task.cancel();
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/countdowns/EndCountdown.java b/TowerRun/src/de/steamwar/towerrun/countdowns/EndCountdown.java
new file mode 100644
index 00000000..95aa3675
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/countdowns/EndCountdown.java
@@ -0,0 +1,67 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.countdowns;
+
+import de.steamwar.towerrun.TowerRun;
+import de.steamwar.towerrun.game.TowerRunGame;
+import de.steamwar.towerrun.state.GameStates;
+import org.bukkit.Bukkit;
+import org.bukkit.Sound;
+
+import java.util.EnumSet;
+import java.util.Objects;
+
+public class EndCountdown extends Countdown {
+
+ private final LobbyCountdown lobbyCountdown;
+
+ private static final boolean RESETS = Objects.requireNonNull(Bukkit.getWorlds().get(0).getWorldFolder().list((dir, name) -> name.equals("backup"))).length > 0;
+
+ public EndCountdown(LobbyCountdown lobbyCountdown) {
+ super(EnumSet.of(GameStates.ENDING));
+ this.lobbyCountdown = lobbyCountdown;
+ }
+
+ @Override
+ int defaultTime() {
+ return 10;
+ }
+
+ @Override
+ void timerEnd() {
+ Bukkit.getOnlinePlayers().forEach(player -> player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1, 1));
+ if (RESETS) {
+ TowerRunGame.reset();
+ lobbyCountdown.setTime(lobbyCountdown.defaultTime());
+ } else {
+ Bukkit.shutdown();
+ }
+ }
+
+ @Override
+ boolean timerShouldCancel() {
+ return false;
+ }
+
+ @Override
+ void run() {
+ TowerRun.getMessage().broadcastActionbar(RESETS ? "SERVER_RESET" : "SERVER_STOPPING", time);
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/countdowns/GameCountdown.java b/TowerRun/src/de/steamwar/towerrun/countdowns/GameCountdown.java
new file mode 100644
index 00000000..b56c1f3b
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/countdowns/GameCountdown.java
@@ -0,0 +1,67 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.countdowns;
+
+import de.steamwar.towerrun.TowerRun;
+import de.steamwar.towerrun.config.Config;
+import de.steamwar.towerrun.game.TowerRunGame;
+import de.steamwar.towerrun.state.GameStates;
+
+import java.util.EnumSet;
+
+public class GameCountdown extends Countdown {
+
+ public GameCountdown() {
+ super(EnumSet.of(GameStates.INGAME));
+ }
+
+ @Override
+ int defaultTime() {
+ return Config.GAME_TIMER;
+ }
+
+ @Override
+ void timerEnd() {
+ if (TowerRunGame.PLAYERS_ESCAPED.isEmpty()) {
+ TowerRunGame.tie();
+ } else {
+ TowerRunGame.win(TowerRunGame.PLAYERS_ESCAPED.get(TowerRunGame.PLAYERS_ESCAPED.size() - 1));
+ }
+ }
+
+ @Override
+ boolean timerShouldCancel() {
+ return false;
+ }
+
+ @Override
+ void run() {
+ int timeMinutes = Math.floorDiv(time, 60);
+ int timeSeconds = time % 60;
+
+ TowerRun.getMessage().broadcastActionbar("GAME_TIME", String.format("%02d", timeMinutes), String.format("%02d", timeSeconds));
+ }
+
+ @Override
+ public void disable() {
+ super.disable();
+ setTime(defaultTime());
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/countdowns/LobbyCountdown.java b/TowerRun/src/de/steamwar/towerrun/countdowns/LobbyCountdown.java
new file mode 100644
index 00000000..40808f1e
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/countdowns/LobbyCountdown.java
@@ -0,0 +1,71 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.countdowns;
+
+import de.steamwar.towerrun.TowerRun;
+import de.steamwar.towerrun.config.Config;
+import de.steamwar.towerrun.game.TowerRunGame;
+import de.steamwar.towerrun.state.GameStates;
+import lombok.Getter;
+import lombok.Setter;
+import org.bukkit.Bukkit;
+import org.bukkit.Sound;
+
+import java.util.EnumSet;
+
+@Getter
+public class LobbyCountdown extends Countdown {
+
+ @Setter
+ private boolean override = false;
+
+ public LobbyCountdown() {
+ super(EnumSet.of(GameStates.LOBBY));
+ }
+
+ @Override
+ int defaultTime() {
+ return Config.LOBBY_TIMER;
+ }
+
+ @Override
+ void timerEnd() {
+ TowerRunGame.prepareTowerOrStart();
+ override = false;
+ }
+
+ @Override
+ boolean timerShouldCancel() {
+ return !override && Bukkit.getOnlinePlayers().size() < Config.MIN_PLAYERS;
+ }
+
+ @Override
+ void run() {
+ TowerRun.getMessage().broadcastActionbar("GAME_STARTING", time);
+ if (time < 10) {
+ Bukkit.getOnlinePlayers().forEach(player -> player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1, 1));
+ }
+ }
+
+ @Override
+ void timerReset() {
+ TowerRun.getMessage().broadcastActionbar("GAME_WAITING");
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/game/TowerRunGame.java b/TowerRun/src/de/steamwar/towerrun/game/TowerRunGame.java
new file mode 100644
index 00000000..2d71ef1d
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/game/TowerRunGame.java
@@ -0,0 +1,184 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.game;
+
+import de.steamwar.core.CraftbukkitWrapper;
+import de.steamwar.towerrun.TowerRun;
+import de.steamwar.towerrun.config.WorldConfig;
+import de.steamwar.towerrun.state.GameState;
+import de.steamwar.towerrun.state.GameStates;
+import lombok.experimental.UtilityClass;
+import net.minecraft.world.level.chunk.Chunk;
+import org.bukkit.*;
+import org.bukkit.craftbukkit.v1_19_R2.CraftWorld;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.bukkit.util.Vector;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.ObjIntConsumer;
+
+@UtilityClass
+public class TowerRunGame {
+ public static final List PLAYERS_ALIVE = new ArrayList<>();
+ public static final List PLAYERS_ESCAPED = new ArrayList<>();
+ private static final World world = Bukkit.getWorlds().get(0);
+
+ public static boolean isAlive(TowerRunPlayer player) {
+ return PLAYERS_ALIVE.contains(player);
+ }
+
+ public static void prepareTowerOrStart() {
+ if (GameState.getCurrentState() == GameStates.LOBBY) {
+ GameState.nextState();
+ if (TowerRun.getTowerGenerator() == null) {
+ start();
+ return;
+ }
+
+ TowerRun.getTowerGenerator().generate();
+ } else {
+ throw new IllegalStateException("Game is already running!");
+ }
+ }
+
+ public static void start() {
+ if (GameState.getCurrentState() == GameStates.GENERATING_TOWER) {
+ PLAYERS_ALIVE.addAll(TowerRunPlayer.getAll());
+ PLAYERS_ALIVE.forEach(p -> {
+ p.reset();
+ p.player().setGameMode(GameMode.SURVIVAL);
+ });
+ GameState.nextState();
+ generateLava();
+ TowerRun.getMessage().broadcast("GAME_START");
+
+ for (Location door : WorldConfig.DOORS) {
+ door.getBlock().setType(Material.AIR);
+ door.clone().add(0, 1, 0).getBlock().setType(Material.AIR);
+ }
+ } else {
+ throw new IllegalStateException("Game is already running!");
+ }
+ }
+
+ private static void generateLava() {
+ for (int x = WorldConfig.MIN_TOWER.getBlockX(); x < WorldConfig.MAX_TOWER.getBlockX(); x += WorldConfig.LAVE_SPACE) {
+ for (int z = WorldConfig.MIN_TOWER.getBlockZ(); z < WorldConfig.MAX_TOWER.getBlockZ(); z += WorldConfig.LAVE_SPACE) {
+ Vector pos = new Vector(x, 0, z);
+ if (Arrays.stream(WorldConfig.REGIONS).anyMatch(region -> region.contains(pos))) {
+ int offset = WorldConfig.LAVA_Y;
+ if (TowerRun.getTowerGenerator() != null) {
+ offset += TowerRun.getTowerGenerator().getHeight();
+ }
+ WorldConfig.MIN_TOWER.getWorld().getBlockAt(x, offset, z).setType(Material.LAVA, true);
+ WorldConfig.MIN_TOWER.getWorld().getBlockAt(x, offset - 1, z).setType(Material.BEDROCK, true);
+ }
+ }
+ }
+ }
+
+ public static void remove(TowerRunPlayer towerRunPlayer) {
+ PLAYERS_ALIVE.remove(towerRunPlayer);
+ PLAYERS_ESCAPED.remove(towerRunPlayer);
+ }
+
+ public static void tie() {
+ Bukkit.getOnlinePlayers().forEach(player -> player.setGameMode(GameMode.SPECTATOR));
+ PLAYERS_ALIVE.clear();
+ Bukkit.getOnlinePlayers().forEach(player -> player.sendTitle(TowerRun.getMessage().parse("GAME_TIE", player), "", 10, 70, 20));
+ GameState.nextState();
+ }
+
+ public static void win(TowerRunPlayer tPlayer) {
+ Bukkit.getOnlinePlayers().forEach(player -> player.setGameMode(GameMode.SPECTATOR));
+ PLAYERS_ALIVE.clear();
+ tPlayer.player().setGameMode(GameMode.SPECTATOR);
+ TowerRun.getMessage().broadcast("GAME_WIN", tPlayer.player().getName());
+ Bukkit.getOnlinePlayers().forEach(player -> {
+ player.sendTitle(TowerRun.getMessage().parse("GAME_WIN", player, tPlayer.player().getName()), "", 10, 70, 20);
+ player.playSound(player.getLocation(), Sound.ENTITY_ENDER_DRAGON_DEATH, 1, 1);
+ });
+ GameState.nextState();
+ }
+
+ public static void reset() {
+ PLAYERS_ALIVE.clear();
+ PLAYERS_ESCAPED.clear();
+ resetWorld();
+ GameState.reset();
+ Bukkit.getOnlinePlayers().forEach(player -> {
+ if (TowerRun.getTowerGenerator() != null) {
+ player.setGameMode(GameMode.SPECTATOR);
+ } else {
+ player.setGameMode(GameMode.SURVIVAL);
+ }
+ player.teleport(WorldConfig.SPAWN);
+ });
+ }
+
+ private static double posToChunk(int pos) {
+ return pos / 16.0;
+ }
+
+ private static int getMinChunkX() {
+ return (int) Math.floor(posToChunk(WorldConfig.MAP_MIN_X));
+ }
+
+ private static int getMaxChunkX() {
+ return (int) Math.ceil(posToChunk(WorldConfig.MAP_MAX_X));
+ }
+
+ private static int getMinChunkZ() {
+ return (int) Math.floor(posToChunk(WorldConfig.MAP_MIN_Z));
+ }
+
+ private static int getMaxChunkZ() {
+ return (int) Math.ceil(posToChunk(WorldConfig.MAP_MAX_Z));
+ }
+
+ private static void forEachChunk(ObjIntConsumer executor) {
+ for (int x = getMinChunkX(); x <= getMaxChunkX(); x++)
+ for (int z = getMinChunkZ(); z <= getMaxChunkZ(); z++)
+ executor.accept(x, z);
+ }
+
+ private static void resetWorld() {
+ world.getEntities().stream().filter(entity -> entity.getType() != EntityType.PLAYER).forEach(Entity::remove);
+
+ World backup = new WorldCreator(world.getName() + "/backup").createWorld();
+ assert backup != null;
+ forEachChunk((x, z) -> resetChunk(backup, x, z));
+ Bukkit.unloadWorld(backup, false);
+ }
+
+ private static void resetChunk(World backup, int x, int z) {
+ Chunk chunk = ((CraftWorld) world).getHandle().d(x, z);
+ Chunk backupChunk = ((CraftWorld) backup).getHandle().d(x, z);
+
+ System.arraycopy(backupChunk.d(), 0, chunk.d(), 0, chunk.d().length);
+
+ for (Player p : Bukkit.getOnlinePlayers())
+ CraftbukkitWrapper.impl.sendChunk(p, x, z);
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/game/TowerRunPlayer.java b/TowerRun/src/de/steamwar/towerrun/game/TowerRunPlayer.java
new file mode 100644
index 00000000..e7b12973
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/game/TowerRunPlayer.java
@@ -0,0 +1,64 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.game;
+
+import de.steamwar.towerrun.TowerRun;
+import de.steamwar.towerrun.config.WorldConfig;
+import org.bukkit.entity.Player;
+import org.bukkit.util.Vector;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public record TowerRunPlayer(Player player) {
+ private static final Map players = new HashMap<>();
+
+ public static TowerRunPlayer get(Player player) {
+ return players.computeIfAbsent(player, TowerRunPlayer::new);
+ }
+
+ public static void remove(Player player) {
+ players.remove(player);
+ }
+
+ public static Collection getAll() {
+ return players.values();
+ }
+
+ public void reset() {
+ player.getInventory().clear();
+ player.updateInventory();
+ player.setHealth(20);
+ if (TowerRun.getTowerGenerator() != null) {
+ player.teleport(TowerRun.getTowerGenerator().getSpawn());
+ } else {
+ player.teleport(WorldConfig.SPAWN);
+ }
+ player.setVelocity(new Vector(0, 0, 0));
+ }
+
+ @Override
+ public String toString() {
+ return "TowerRunPlayer{" +
+ "player=" + player.getName() +
+ '}';
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/generator/TowerGenerator.java b/TowerRun/src/de/steamwar/towerrun/generator/TowerGenerator.java
new file mode 100644
index 00000000..4f379547
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/generator/TowerGenerator.java
@@ -0,0 +1,207 @@
+package de.steamwar.towerrun.generator;
+
+import com.sk89q.worldedit.EditSession;
+import com.sk89q.worldedit.WorldEdit;
+import com.sk89q.worldedit.bukkit.BukkitWorld;
+import com.sk89q.worldedit.extent.clipboard.Clipboard;
+import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats;
+import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader;
+import com.sk89q.worldedit.function.operation.Operations;
+import com.sk89q.worldedit.math.BlockVector3;
+import com.sk89q.worldedit.session.ClipboardHolder;
+import de.steamwar.sql.SchematicData;
+import de.steamwar.sql.SchematicNode;
+import de.steamwar.towerrun.TowerRun;
+import de.steamwar.towerrun.config.WorldConfig;
+import de.steamwar.towerrun.game.TowerRunGame;
+import lombok.Getter;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.block.*;
+import org.bukkit.block.data.Bisected;
+import org.bukkit.block.data.type.Door;
+import org.bukkit.scheduler.BukkitRunnable;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Random;
+
+public class TowerGenerator {
+
+ private static final Random random = new Random();
+ private final WorldConfig.TowerGeneratorConfig config;
+ private final List allSchematics;
+ private final Clipboard roof;
+
+ @Getter
+ private int height;
+
+ @Getter
+ private Location spawn;
+
+ @Getter
+ private List keys = new ArrayList<>();
+
+ public TowerGenerator(WorldConfig.TowerGeneratorConfig config) {
+ File file = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "Roof.schem");
+ if (!file.exists()) {
+ Bukkit.shutdown();
+ throw new SecurityException("TowerRun schematic not found");
+ }
+ roof = loadSchematic(file);
+
+ this.config = config;
+ allSchematics = SchematicNode.getAllSchematicsOfType(config.schematicType);
+ spawn = WorldConfig.SPAWN;
+ }
+
+ private Clipboard loadSchematic(File file) {
+ Clipboard clipboard;
+ try (ClipboardReader reader = Objects.requireNonNull(ClipboardFormats.findByFile(file)).getReader(new FileInputStream(file))) {
+ clipboard = reader.read();
+ } catch (NullPointerException | IOException e) {
+ Bukkit.shutdown();
+ throw new SecurityException("TowerRun schematic not found", e);
+ }
+ return clipboard;
+ }
+
+ public void generate() {
+ new BukkitRunnable() {
+ int height = random.nextInt(config.maxHeight - config.minHeight) + config.minHeight;
+ int y = TowerGenerator.this.config.y;
+ int noBombFloors;
+ int noKeyFloors;
+
+ {
+ TowerGenerator.this.height = 0;
+ noBombFloors = random.nextInt(config.maxNoBombFloors - config.minNoBombFloors) + config.minNoBombFloors;
+ noKeyFloors = random.nextInt(config.maxNoKeyFloors - config.minNoKeyFloors) + config.minNoKeyFloors;
+ keys.clear();
+ }
+
+ @Override
+ public void run() {
+ if (height > 0) {
+ SchematicNode schematicNode = allSchematics.get(random.nextInt(allSchematics.size()));
+ SchematicData schematicData = new SchematicData(schematicNode);
+ int currentY;
+ int width;
+ int depth;
+ try {
+ Clipboard clipboard = schematicData.load();
+ try (EditSession e = WorldEdit.getInstance().getEditSessionFactory().getEditSession(new BukkitWorld(Bukkit.getWorlds().get(0)), -1)) {
+ ClipboardHolder ch = new ClipboardHolder(clipboard);
+ Operations.completeBlindly(ch.createPaste(e).to(BlockVector3.at(config.x, y, config.z)).build());
+ }
+ width = clipboard.getDimensions().getX();
+ depth = clipboard.getDimensions().getZ();
+ currentY = y;
+ y += clipboard.getDimensions().getY();
+ height -= clipboard.getDimensions().getY();
+ TowerGenerator.this.height += clipboard.getDimensions().getY();
+ } catch (IOException e) {
+ allSchematics.remove(schematicNode);
+ return;
+ }
+ spawn = WorldConfig.SPAWN.clone().add(0, y, 0);
+
+ List chestBlocks = new ArrayList<>();
+ for (int x = config.x; x < config.x + width; x++) {
+ for (int z = config.z; z < config.z + depth; z++) {
+ for (int y = currentY; y < this.y; y++) {
+ Block block = Bukkit.getWorlds().get(0).getBlockAt(x, y, z);
+ BlockState blockState = block.getState();
+ if (blockState instanceof Chest) {
+ chestBlocks.add((Container) blockState);
+ }
+ }
+ }
+ }
+
+ noBombFloors--;
+ if (noBombFloors < 0 && random.nextDouble() < config.tntChance) {
+ noBombFloors = random.nextInt(config.maxNoBombFloors - config.minNoBombFloors) + config.minNoBombFloors;
+ int bombCount = random.nextInt(config.maxBombs - config.minBombs) + config.minBombs;
+ for (int i = 0; i < bombCount; i++) {
+ WorldConfig.Region region = config.tntRegions[random.nextInt(config.tntRegions.length)];
+ int x = random.nextInt(region.max.getBlockX() - region.min.getBlockX()) + region.min.getBlockX();
+ int z = random.nextInt(region.max.getBlockZ() - region.min.getBlockZ()) + region.min.getBlockZ();
+ int y = currentY + 1;
+ Bukkit.getWorlds().get(0).getBlockAt(x, y, z).setType(Material.TNT, true);
+ }
+ }
+
+ noKeyFloors--;
+ if (!chestBlocks.isEmpty() && noKeyFloors < 0 && random.nextDouble() < config.keyChance) {
+ noKeyFloors = random.nextInt(config.maxNoKeyFloors - config.minNoKeyFloors) + config.minNoKeyFloors;
+ Container container = chestBlocks.get(random.nextInt(chestBlocks.size()));
+ keys.add(container.getLocation());
+
+ for (WorldConfig.TowerGeneratorDoorBlock doorBlock : config.doorBlocks) {
+ int x = doorBlock.getX();
+ int y = currentY + doorBlock.getDy();
+ int z = doorBlock.getZ();
+ Block block = Bukkit.getWorlds().get(0).getBlockAt(x, y, z);
+ for (int i = 0; i < 5; i++) {
+ if (block.getType().isAir()) {
+ block.setType(Material.IRON_BLOCK, false);
+ block = block.getRelative(0, 1, 0);
+ } else {
+ break;
+ }
+ }
+
+ Bukkit.getScheduler().runTaskLater(TowerRun.getInstance(), () -> {
+ Block door = Bukkit.getWorlds().get(0).getBlockAt(x, y, z);
+ door.setType(Material.IRON_DOOR, false);
+ door = door.getRelative(0, 1, 0);
+ door.setType(Material.IRON_DOOR, false);
+ }, 10);
+ Bukkit.getScheduler().runTaskLater(TowerRun.getInstance(), () -> {
+ Block door = Bukkit.getWorlds().get(0).getBlockAt(x, y, z);
+ Door doorState = (Door) door.getBlockData();
+ doorState.setFacing(BlockFace.EAST);
+ door.setBlockData(doorState, false);
+
+ door = door.getRelative(0, 1, 0);
+ doorState = (Door) door.getBlockData();
+ doorState.setFacing(BlockFace.EAST);
+ doorState.setHalf(Bisected.Half.TOP);
+ door.setBlockData(doorState, false);
+ }, 20);
+ }
+ }
+
+ for (WorldConfig.TowerGeneratorFillRegion fillRegion : config.fillRegions) {
+ for (int x = fillRegion.getMinX(); x < fillRegion.getMaxX(); x++) {
+ for (int z = fillRegion.getMinZ(); z < fillRegion.getMaxZ(); z++) {
+ for (int y = currentY; y < this.y; y++) {
+ Block block = Bukkit.getWorlds().get(0).getBlockAt(x, y, z);
+ if (!block.getType().isAir()) {
+ continue;
+ }
+ if (random.nextDouble() < fillRegion.getPercentage()) {
+ block.setType(fillRegion.getMaterial(), true);
+ }
+ }
+ }
+ }
+ }
+ } else {
+ cancel();
+ try (EditSession e = WorldEdit.getInstance().getEditSessionFactory().getEditSession(new BukkitWorld(Bukkit.getWorlds().get(0)), -1)) {
+ ClipboardHolder ch = new ClipboardHolder(roof);
+ Operations.completeBlindly(ch.createPaste(e).to(BlockVector3.at(config.x, y, config.z)).build());
+ }
+ TowerRunGame.start();
+ }
+ }
+ }.runTaskTimer(TowerRun.getInstance(), 0, 10);
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/listener/GlobalListener.java b/TowerRun/src/de/steamwar/towerrun/listener/GlobalListener.java
new file mode 100644
index 00000000..353adc20
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/listener/GlobalListener.java
@@ -0,0 +1,67 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.listener;
+
+import de.steamwar.towerrun.config.WorldConfig;
+import de.steamwar.towerrun.game.TowerRunGame;
+import de.steamwar.towerrun.game.TowerRunPlayer;
+import de.steamwar.towerrun.state.GameStateBukkitListener;
+import de.steamwar.towerrun.state.GameStates;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerMoveEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+
+import java.util.EnumSet;
+
+public class GlobalListener extends GameStateBukkitListener {
+ public GlobalListener() {
+ super(EnumSet.allOf(GameStates.class));
+ }
+
+ @EventHandler
+ public void onPlayerJoin(PlayerJoinEvent event) {
+ TowerRunPlayer.get(event.getPlayer());
+ }
+
+ @EventHandler
+ public void onPlayerQuit(PlayerQuitEvent event) {
+ if (TowerRunGame.isAlive(TowerRunPlayer.get(event.getPlayer()))) {
+ TowerRunGame.remove(TowerRunPlayer.get(event.getPlayer()));
+ }
+ TowerRunPlayer.remove(event.getPlayer());
+
+ if(Bukkit.getOnlinePlayers().isEmpty() || (Bukkit.getOnlinePlayers().size() == 1 && Bukkit.getOnlinePlayers().stream().allMatch(player -> player == event.getPlayer()))) {
+ Bukkit.shutdown();
+ }
+ }
+
+ @EventHandler
+ public void onPlayerMove(PlayerMoveEvent event) {
+ Location to = event.getTo();
+ if (to.getX() > WorldConfig.MAP_MIN_X && to.getX() < WorldConfig.MAP_MAX_X && to.getZ() > WorldConfig.MAP_MIN_Z && to.getZ() < WorldConfig.MAP_MAX_Z && to.getY() > WorldConfig.ESCAPE_HEIGHT - 20) {
+ return;
+ }
+
+ event.setCancelled(true);
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/listener/IngameListener.java b/TowerRun/src/de/steamwar/towerrun/listener/IngameListener.java
new file mode 100644
index 00000000..ea07eb04
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/listener/IngameListener.java
@@ -0,0 +1,215 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.listener;
+
+import de.steamwar.inventory.SWItem;
+import de.steamwar.towerrun.TowerRun;
+import de.steamwar.towerrun.config.Config;
+import de.steamwar.towerrun.config.WorldConfig;
+import de.steamwar.towerrun.game.TowerRunGame;
+import de.steamwar.towerrun.state.GameStateBukkitListener;
+import de.steamwar.towerrun.state.GameStates;
+import org.bukkit.*;
+import org.bukkit.block.Block;
+import org.bukkit.entity.EntityType;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.block.Action;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.event.block.BlockPhysicsEvent;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+import org.bukkit.event.entity.EntityRegainHealthEvent;
+import org.bukkit.event.entity.ItemSpawnEvent;
+import org.bukkit.event.entity.PlayerDeathEvent;
+import org.bukkit.event.player.PlayerInteractEvent;
+import org.bukkit.scheduler.BukkitRunnable;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+public class IngameListener extends GameStateBukkitListener {
+
+ private int time = 0;
+ private final Map> blocksToMelt = new HashMap<>();
+ private BukkitRunnable blocksToMeltRunnable;
+ private BukkitRunnable antiCampRunnable;
+
+ public IngameListener() {
+ super(EnumSet.of(GameStates.INGAME));
+ }
+
+ @Override
+ public void enable() {
+ super.enable();
+ blocksToMeltRunnable = new BukkitRunnable() {
+ @Override
+ public void run() {
+ List blocks = blocksToMelt.get(time);
+ time++;
+ if (blocks == null) {
+ return;
+ }
+ blocks.forEach(block -> {
+ if (!WorldConfig.MELTING_TIMES.containsKey(block.getType())) return;
+ block.setType(Material.AIR);
+ block.getWorld().playSound(block.getLocation(), Sound.BLOCK_FIRE_EXTINGUISH, 0.1F, 1);
+ });
+ }
+ };
+ blocksToMeltRunnable.runTaskTimer(TowerRun.getInstance(), 0, 1);
+
+ antiCampRunnable = new BukkitRunnable() {
+ @Override
+ public void run() {
+ double minY = TowerRunGame.PLAYERS_ALIVE.stream()
+ .map(p -> p.player().getLocation().getY())
+ .min(Comparator.comparing(Function.identity()))
+ .orElse(0.0);
+
+ TowerRunGame.PLAYERS_ALIVE.forEach(towerRunPlayer -> {
+ if (towerRunPlayer.player().getLocation().getY() - minY > 20) {
+ towerRunPlayer.player().sendTitle("§a", TowerRun.getMessage().parse("CATCH_UP_WARNING", towerRunPlayer.player()), 5, 30, 5);
+ }
+ if (towerRunPlayer.player().getLocation().getY() - minY > 30) {
+ towerRunPlayer.player().damage(1.0);
+ }
+ });
+ }
+ };
+ antiCampRunnable.runTaskTimer(TowerRun.getInstance(), 100, 100);
+ }
+
+ @Override
+ public void disable() {
+ super.disable();
+ blocksToMeltRunnable.cancel();
+ blocksToMeltRunnable = null;
+ blocksToMelt.clear();
+ time = 0;
+
+ antiCampRunnable.cancel();
+ antiCampRunnable = null;
+ }
+
+ @EventHandler
+ public void onPlayerDeath(PlayerDeathEvent event) {
+ event.setDeathMessage(null);
+ Bukkit.getScheduler().runTaskLater(TowerRun.getInstance(), () -> {
+ if (TowerRun.getTowerGenerator() != null) {
+ event.getEntity().teleport(TowerRun.getTowerGenerator().getSpawn());
+ } else {
+ event.getEntity().teleport(WorldConfig.SPAWN);
+ }
+ }, 5
+ );
+ event.getEntity().setGameMode(GameMode.SPECTATOR);
+ Bukkit.getOnlinePlayers().forEach(player -> {
+ player.sendTitle("", TowerRun.getMessage().parse("PLAYER_DIED", player, event.getEntity().getPlayer().getName()), 10, 70, 20);
+ player.playSound(player.getLocation(), Sound.ENTITY_WITHER_DEATH, 1, 1);
+ });
+ }
+
+ @EventHandler
+ public void onBlockBreak(BlockBreakEvent event) {
+ event.setDropItems(false);
+ if (!Config.DESTROYABLE_BLOCKS.contains(event.getBlock().getType())) {
+ event.setCancelled(true);
+ }
+ }
+
+ @EventHandler
+ public void onKeyUse(PlayerInteractEvent event) {
+ if (!event.hasItem()) return;
+ if (event.getItem().getType() != Material.LEVER) return;
+ event.setCancelled(true);
+ if (!event.hasBlock()) return;
+ if (event.getClickedBlock().getType() != Material.IRON_DOOR) return;
+ event.getPlayer().getInventory().setItemInMainHand(null);
+ event.getClickedBlock().breakNaturally();
+ }
+
+ @EventHandler
+ public void onItemSpawn(ItemSpawnEvent event) {
+ event.setCancelled(true);
+ }
+
+ @EventHandler
+ public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
+ if (event.getDamager().getType() == EntityType.PLAYER) {
+ event.setCancelled(true);
+ }
+ }
+
+ @EventHandler
+ public void onPlayerInteract(PlayerInteractEvent event) {
+ if (event.getPlayer().getGameMode() != GameMode.SURVIVAL) {
+ return;
+ }
+
+ if (event.getAction() != Action.RIGHT_CLICK_BLOCK || !event.hasBlock()) {
+ return;
+ }
+
+ if (event.getClickedBlock().getType() != Material.CHEST) {
+ return;
+ }
+
+ Stream locations = TowerRun.getTowerGenerator() == null ? Arrays.stream(WorldConfig.KEYS) : TowerRun.getTowerGenerator().getKeys().stream();
+ if (locations.noneMatch(location -> location.equals(event.getClickedBlock().getLocation()))) {
+ event.getClickedBlock().setType(Material.AIR);
+ return;
+ }
+
+ event.getPlayer().getInventory().addItem(new SWItem(Material.LEVER, TowerRun.getMessage().parse("KEY_NAME", event.getPlayer())).getItemStack());
+
+ event.getClickedBlock().setType(Material.ENDER_CHEST);
+ event.getPlayer().playSound(event.getPlayer().getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 1, 1);
+ TowerRun.getMessage().broadcast("KEY_FOUND", event.getPlayer().getName());
+ }
+
+ @EventHandler
+ public void onBlockPhysics(BlockPhysicsEvent event) {
+ if (event.getSourceBlock().getType() != Material.LAVA) {
+ return;
+ }
+ Block block = event.getSourceBlock();
+ shouldMelt(block.getRelative(0, 1, 0));
+ shouldMelt(block.getRelative(0, -1, 0));
+ shouldMelt(block.getRelative(1, 0, 0));
+ shouldMelt(block.getRelative(-1, 0, 0));
+ shouldMelt(block.getRelative(0, 0, 1));
+ shouldMelt(block.getRelative(0, 0, -1));
+ }
+
+ private void shouldMelt(Block block) {
+ int meltingTime = WorldConfig.MELTING_TIMES.getOrDefault(block.getType(), -1);
+ if (meltingTime == -1) {
+ return;
+ }
+ blocksToMelt.computeIfAbsent(time + meltingTime * 20, integer -> new ArrayList<>()).add(block);
+ }
+
+ @EventHandler
+ public void onEntityRegainHealth(EntityRegainHealthEvent event) {
+ if (event.getEntityType() == EntityType.PLAYER) {
+ event.setCancelled(true);
+ }
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/listener/LobbyListener.java b/TowerRun/src/de/steamwar/towerrun/listener/LobbyListener.java
new file mode 100644
index 00000000..eb2cb157
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/listener/LobbyListener.java
@@ -0,0 +1,81 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.listener;
+
+import de.steamwar.towerrun.TowerRun;
+import de.steamwar.towerrun.config.WorldConfig;
+import de.steamwar.towerrun.state.GameStateBukkitListener;
+import de.steamwar.towerrun.state.GameStates;
+import org.bukkit.GameMode;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.event.entity.EntityDamageEvent;
+import org.bukkit.event.player.PlayerInteractEvent;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerMoveEvent;
+
+import java.util.EnumSet;
+
+public class LobbyListener extends GameStateBukkitListener {
+ public LobbyListener() {
+ super(EnumSet.of(GameStates.LOBBY));
+ }
+
+ @EventHandler
+ public void onPlayerJoin(PlayerJoinEvent event) {
+ Player player = event.getPlayer();
+ if (TowerRun.getTowerGenerator() != null) {
+ player.teleport(TowerRun.getTowerGenerator().getSpawn());
+ player.setGameMode(GameMode.SPECTATOR);
+ } else {
+ player.teleport(WorldConfig.SPAWN);
+ player.setGameMode(GameMode.SURVIVAL);
+ }
+ }
+
+ @EventHandler
+ public void onPlayerMove(PlayerMoveEvent event) {
+ if (TowerRun.getTowerGenerator() != null) {
+ return;
+ }
+ if (event.getTo().getY() < WorldConfig.SPAWN.getY() - 10) {
+ event.getPlayer().teleport(WorldConfig.SPAWN);
+ }
+ }
+
+ @EventHandler
+ public void onPlayerInteract(PlayerInteractEvent event) {
+ event.setCancelled(true);
+ }
+
+ @EventHandler
+ public void onBlockBreak(BlockBreakEvent event) {
+ event.setCancelled(true);
+ }
+
+ @EventHandler
+ public void onEntityDamage(EntityDamageEvent event) {
+ if (event.getEntityType() == EntityType.PLAYER) {
+ event.setCancelled(true);
+ }
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/listener/NotLobbyListener.java b/TowerRun/src/de/steamwar/towerrun/listener/NotLobbyListener.java
new file mode 100644
index 00000000..4d3525e5
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/listener/NotLobbyListener.java
@@ -0,0 +1,46 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.listener;
+
+import de.steamwar.towerrun.TowerRun;
+import de.steamwar.towerrun.config.WorldConfig;
+import de.steamwar.towerrun.state.GameStateBukkitListener;
+import de.steamwar.towerrun.state.GameStates;
+import org.bukkit.GameMode;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.player.PlayerJoinEvent;
+
+import java.util.EnumSet;
+
+public class NotLobbyListener extends GameStateBukkitListener {
+ public NotLobbyListener() {
+ super(EnumSet.complementOf(EnumSet.of(GameStates.LOBBY)));
+ }
+
+ @EventHandler
+ public void onPlayerJoin(PlayerJoinEvent event) {
+ event.getPlayer().setGameMode(GameMode.SPECTATOR);
+ if (TowerRun.getTowerGenerator() != null) {
+ event.getPlayer().teleport(TowerRun.getTowerGenerator().getSpawn());
+ } else {
+ event.getPlayer().teleport(WorldConfig.SPAWN);
+ }
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/state/GameState.java b/TowerRun/src/de/steamwar/towerrun/state/GameState.java
new file mode 100644
index 00000000..3c3a55e0
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/state/GameState.java
@@ -0,0 +1,59 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.state;
+
+import lombok.Getter;
+import lombok.experimental.UtilityClass;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@UtilityClass
+public class GameState {
+ @Getter
+ private static GameStates currentState = GameStates.LOBBY;
+ private static final List gameStateListeners = new ArrayList<>();
+
+ public static void addGameStateListener(GameStateListener gameStateListener) {
+ gameStateListeners.add(gameStateListener);
+ }
+
+ public static void nextState() {
+ final GameStates oldState = currentState;
+ currentState = currentState.getNextState();
+ gameStateChanges(oldState, currentState);
+ }
+
+ static void setState(final GameStates newState) {
+ final GameStates oldState = currentState;
+ currentState = newState;
+ gameStateChanges(oldState, currentState);
+ }
+
+ public static void reset() {
+ final GameStates oldState = currentState;
+ currentState = GameStates.LOBBY;
+ gameStateChanges(oldState, currentState);
+ }
+
+ private static void gameStateChanges(GameStates oldState, GameStates newState) {
+ gameStateListeners.forEach(gameStateListener -> gameStateListener.onGameStateChange(oldState, newState));
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/state/GameStateBukkitListener.java b/TowerRun/src/de/steamwar/towerrun/state/GameStateBukkitListener.java
new file mode 100644
index 00000000..bd259f63
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/state/GameStateBukkitListener.java
@@ -0,0 +1,44 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.state;
+
+import de.steamwar.towerrun.TowerRun;
+import org.bukkit.Bukkit;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+
+import java.util.EnumSet;
+
+public abstract class GameStateBukkitListener extends GameStateToggleListener implements Listener {
+
+ protected GameStateBukkitListener(EnumSet enabledStates) {
+ super(enabledStates);
+ }
+
+ @Override
+ public void enable() {
+ Bukkit.getPluginManager().registerEvents(this, TowerRun.getInstance());
+ }
+
+ @Override
+ public void disable() {
+ HandlerList.unregisterAll(this);
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/state/GameStateListener.java b/TowerRun/src/de/steamwar/towerrun/state/GameStateListener.java
new file mode 100644
index 00000000..d4dcd639
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/state/GameStateListener.java
@@ -0,0 +1,29 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.state;
+
+public abstract class GameStateListener {
+
+ protected GameStateListener() {
+ GameState.addGameStateListener(this);
+ }
+
+ public abstract void onGameStateChange(GameStates oldState, GameStates newState);
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/state/GameStateToggleListener.java b/TowerRun/src/de/steamwar/towerrun/state/GameStateToggleListener.java
new file mode 100644
index 00000000..c8ba5534
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/state/GameStateToggleListener.java
@@ -0,0 +1,48 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.state;
+
+import java.util.EnumSet;
+
+public abstract class GameStateToggleListener extends GameStateListener {
+
+ private final EnumSet enabledStates;
+
+ protected GameStateToggleListener(EnumSet enabledStates) {
+ super();
+ this.enabledStates = enabledStates;
+ if (enabledStates.contains(GameState.getCurrentState())) {
+ enable();
+ }
+ }
+
+ public abstract void enable();
+
+ public abstract void disable();
+
+ @Override
+ public void onGameStateChange(GameStates oldState, GameStates newState) {
+ if (enabledStates.contains(newState) && !enabledStates.contains(oldState)) {
+ enable();
+ } else if (!enabledStates.contains(newState) && enabledStates.contains(oldState)) {
+ disable();
+ }
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/state/GameStates.java b/TowerRun/src/de/steamwar/towerrun/state/GameStates.java
new file mode 100644
index 00000000..347e9814
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/state/GameStates.java
@@ -0,0 +1,34 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.state;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum GameStates {
+ ENDING(null),
+ INGAME(ENDING),
+ GENERATING_TOWER(INGAME),
+ LOBBY(GENERATING_TOWER);
+
+ private final GameStates nextState;
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/winconditions/FirstOutsideWincondition.java b/TowerRun/src/de/steamwar/towerrun/winconditions/FirstOutsideWincondition.java
new file mode 100644
index 00000000..f2d753fa
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/winconditions/FirstOutsideWincondition.java
@@ -0,0 +1,35 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.winconditions;
+
+import de.steamwar.towerrun.game.TowerRunGame;
+import de.steamwar.towerrun.game.TowerRunPlayer;
+import org.bukkit.entity.Player;
+
+public class FirstOutsideWincondition extends OutsideWincondition {
+ public FirstOutsideWincondition() {
+ super("FIRST_OUTSIDE");
+ }
+
+ @Override
+ public void onPlayerOutside(Player player) {
+ TowerRunGame.win(TowerRunPlayer.get(player));
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/winconditions/LastOutsideWincondition.java b/TowerRun/src/de/steamwar/towerrun/winconditions/LastOutsideWincondition.java
new file mode 100644
index 00000000..260ed0fb
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/winconditions/LastOutsideWincondition.java
@@ -0,0 +1,53 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.winconditions;
+
+import de.steamwar.towerrun.game.TowerRunGame;
+import de.steamwar.towerrun.game.TowerRunPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.entity.PlayerDeathEvent;
+
+public class LastOutsideWincondition extends OutsideWincondition {
+ public LastOutsideWincondition() {
+ super("LAST_OUTSIDE");
+ }
+
+ @Override
+ public void onPlayerOutside(Player player) {
+ TowerRunPlayer towerRunPlayer = TowerRunPlayer.get(player);
+ outside(towerRunPlayer);
+ if (TowerRunGame.PLAYERS_ALIVE.isEmpty()) {
+ TowerRunGame.win(towerRunPlayer);
+ }
+ }
+
+ @EventHandler(priority = EventPriority.LOW)
+ public void onPlayerDeath(PlayerDeathEvent event) {
+ if (TowerRunGame.PLAYERS_ALIVE.isEmpty()) {
+ if (TowerRunGame.PLAYERS_ESCAPED.isEmpty()) {
+ TowerRunGame.tie();
+ return;
+ }
+ TowerRunGame.win(TowerRunGame.PLAYERS_ESCAPED.get(TowerRunGame.PLAYERS_ESCAPED.size() - 1));
+ }
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/winconditions/LastRemainingWincondition.java b/TowerRun/src/de/steamwar/towerrun/winconditions/LastRemainingWincondition.java
new file mode 100644
index 00000000..c66818d1
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/winconditions/LastRemainingWincondition.java
@@ -0,0 +1,58 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.winconditions;
+
+import de.steamwar.towerrun.game.TowerRunGame;
+import de.steamwar.towerrun.game.TowerRunPlayer;
+import org.bukkit.GameMode;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.entity.PlayerDeathEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+
+public class LastRemainingWincondition extends WinCondition {
+
+ public LastRemainingWincondition() {
+ super("LAST_REMAINING");
+ }
+
+ @EventHandler
+ public void onPlayerDeath(PlayerDeathEvent event) {
+ TowerRunPlayer tPlayer = TowerRunPlayer.get(event.getEntity());
+ TowerRunGame.PLAYERS_ALIVE.remove(tPlayer);
+ TowerRunGame.PLAYERS_ESCAPED.remove(tPlayer);
+ tPlayer.player().setGameMode(GameMode.SPECTATOR);
+ if (TowerRunGame.PLAYERS_ALIVE.size() == 1 && TowerRunGame.PLAYERS_ESCAPED.isEmpty()) {
+ TowerRunGame.win(TowerRunGame.PLAYERS_ALIVE.get(0));
+ }
+ }
+
+ @EventHandler(priority = EventPriority.LOW)
+ public void onPlayerQuit(PlayerQuitEvent event) {
+ TowerRunPlayer tPlayer = TowerRunPlayer.get(event.getPlayer());
+ if (TowerRunGame.isAlive(tPlayer)) {
+ TowerRunGame.PLAYERS_ALIVE.remove(tPlayer);
+ TowerRunGame.PLAYERS_ESCAPED.remove(tPlayer);
+ if (TowerRunGame.PLAYERS_ALIVE.size() == 1 && TowerRunGame.PLAYERS_ESCAPED.isEmpty()) {
+ TowerRunGame.win(TowerRunGame.PLAYERS_ALIVE.get(0));
+ }
+ }
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/winconditions/OutsideWincondition.java b/TowerRun/src/de/steamwar/towerrun/winconditions/OutsideWincondition.java
new file mode 100644
index 00000000..9419e7ee
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/winconditions/OutsideWincondition.java
@@ -0,0 +1,73 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.winconditions;
+
+import de.steamwar.towerrun.TowerRun;
+import de.steamwar.towerrun.config.Config;
+import de.steamwar.towerrun.config.WorldConfig;
+import de.steamwar.towerrun.game.TowerRunGame;
+import de.steamwar.towerrun.game.TowerRunPlayer;
+import org.bukkit.GameMode;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.player.PlayerMoveEvent;
+
+import java.util.Arrays;
+
+public abstract class OutsideWincondition extends WinCondition {
+
+ protected OutsideWincondition(String name) {
+ super(name);
+ }
+
+ public abstract void onPlayerOutside(Player player);
+
+ @EventHandler
+ public void onPlayerMove(PlayerMoveEvent event) {
+ if (event.getTo().getY() > WorldConfig.ESCAPE_HEIGHT) {
+ return;
+ }
+
+ TowerRunPlayer tPlayer = TowerRunPlayer.get(event.getPlayer());
+ if (!TowerRunGame.isAlive(tPlayer)) {
+ return;
+ }
+
+ if (Arrays.stream(WorldConfig.REGIONS).anyMatch(region -> region.contains(event.getTo().toVector()))) {
+ return;
+ }
+
+ if (event.getTo().getY() - event.getFrom().getY() != 0) {
+ return;
+ }
+
+ onPlayerOutside(event.getPlayer());
+ }
+
+ protected void outside(TowerRunPlayer towerRunPlayer) {
+ TowerRunGame.PLAYERS_ESCAPED.add(towerRunPlayer);
+ TowerRunGame.PLAYERS_ALIVE.remove(towerRunPlayer);
+ towerRunPlayer.player().setGameMode(GameMode.SPECTATOR);
+ TowerRun.getMessage().broadcast("PLAYER_ESCAPE", towerRunPlayer.player().getName());
+ if (TowerRunGame.PLAYERS_ESCAPED.size() == 1) {
+ TowerRun.getGameCountdown().setTime(Config.GAME_ESCAPE_TIMER);
+ }
+ }
+}
diff --git a/TowerRun/src/de/steamwar/towerrun/winconditions/WinCondition.java b/TowerRun/src/de/steamwar/towerrun/winconditions/WinCondition.java
new file mode 100644
index 00000000..4a736dd5
--- /dev/null
+++ b/TowerRun/src/de/steamwar/towerrun/winconditions/WinCondition.java
@@ -0,0 +1,54 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.towerrun.winconditions;
+
+import de.steamwar.towerrun.state.GameStateBukkitListener;
+import de.steamwar.towerrun.state.GameStates;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.*;
+
+@Getter
+public abstract class WinCondition extends GameStateBukkitListener {
+
+ private final String name;
+ @Setter
+ private boolean active = false;
+
+ protected WinCondition(String name) {
+ super(EnumSet.of(GameStates.INGAME));
+ this.name = name;
+ }
+
+ @Override
+ public void enable() {
+ if (active) {
+ super.enable();
+ }
+ }
+
+ @Override
+ public void disable() {
+ if (active) {
+ super.disable();
+ }
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 16df909a..68857a8a 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -27,6 +27,7 @@ include("LobbySystem_2")
include("MissileWars")
include("SpigotCore")
+include("SpigotCore:CRIUDummy")
include("SpigotCore:SpigotCore_8")
include("SpigotCore:SpigotCore_9")
include("SpigotCore:SpigotCore_10")
@@ -38,6 +39,7 @@ include("SpigotCore:SpigotCore_19")
include("SpigotCore:SpigotCore_20")
include("SpigotCore:SpigotCore_Main")
+include("TowerRun")
+
include("VelocityCore")
include("VelocityCore:Persistent")
-include("SpigotCore:CRIUDummy")