diff --git a/BauSystem/BauSystem_Main/build.gradle.kts b/BauSystem/BauSystem_Main/build.gradle.kts index 009c3b36..5138608b 100644 --- a/BauSystem/BauSystem_Main/build.gradle.kts +++ b/BauSystem/BauSystem_Main/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { compileOnly(libs.spigotapi) compileOnly(libs.axiom) compileOnly(libs.authlib) + compileOnly(libs.viaapi) compileOnly(libs.nms20) compileOnly(libs.fawe18) diff --git a/BauSystem/BauSystem_Main/src/BauSystem.properties b/BauSystem/BauSystem_Main/src/BauSystem.properties index 7f44e063..67a16a47 100644 --- a/BauSystem/BauSystem_Main/src/BauSystem.properties +++ b/BauSystem/BauSystem_Main/src/BauSystem.properties @@ -1012,3 +1012,5 @@ COLORREPLACE_HELP=§8//§ecolorreplace §8[§7color§8] §8[§7color§8] §8- § TYPEREPLACE_HELP=§8//§etypereplace §8[§7type§8] §8[§7type§8] §8- §7Replace all blocks of one type with another # Schematic SCHEMATIC_GUI_ITEM=§eSchematics +#VersionAnnouncer +SERVER_VERSION=§7This server runs on Minecraft version §e{0} \ No newline at end of file diff --git a/BauSystem/BauSystem_Main/src/BauSystem_de.properties b/BauSystem/BauSystem_Main/src/BauSystem_de.properties index 46899380..4013f3b2 100644 --- a/BauSystem/BauSystem_Main/src/BauSystem_de.properties +++ b/BauSystem/BauSystem_Main/src/BauSystem_de.properties @@ -953,3 +953,5 @@ COLORREPLACE_HELP=§8//§ecolorreplace §8[§7color§8] §8[§7color§8] §8- § TYPEREPLACE_HELP=§8//§etyreplace §8[§7type§8] §8[§7type§8] §8- §7Ersetzt einen Blockgruppe mit einer anderen # Schematics SCHEMATIC_GUI_ITEM=§eSchematics +#VersionAnnouncer +SERVER_VERSION=§7Dieser Server läuft auf Minecraft-Version §e{0} \ No newline at end of file diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java index e0e4bb13..b37e8e8b 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java @@ -115,9 +115,12 @@ public class BauSystem extends JavaPlugin implements Listener { .map(s -> { try { return Class.forName(s, false, BauSystem.class.getClassLoader()); - } catch (ClassNotFoundException e) { - Bukkit.shutdown(); - throw new SecurityException(e.getMessage(), e); + } catch (ClassNotFoundException | NoClassDefFoundError e) { + if (e.getMessage().equals(s)) { + Bukkit.shutdown(); + throw new SecurityException(e.getMessage(), e); + } + return null; } }) .filter(Objects::nonNull) diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/ResetCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/ResetCommand.java index 680224a7..3e3e4483 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/ResetCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/ResetCommand.java @@ -61,6 +61,9 @@ public class ResetCommand extends SWCommand { PasteBuilder pasteBuilder = new PasteBuilder(new PasteBuilder.FileProvider(region.getResetFile(RegionType.NORMAL))) .color(region.getPlain(Flag.COLOR, ColorMode.class).getColor()); region.reset(pasteBuilder, RegionType.NORMAL, RegionExtensionType.NORMAL); + for (Flag value : Flag.values()) { + region.set(value, value.getDefaultValue()); + } RegionUtils.message(region, "REGION_RESET_RESETED"); } catch (SecurityException e) { BauSystem.MESSAGE.send("REGION_RESET_ERROR", p); diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AFKStopperListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AFKStopperListener.java index 81e2f4dd..479dd394 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AFKStopperListener.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AFKStopperListener.java @@ -35,20 +35,16 @@ import org.bukkit.event.player.PlayerQuitEvent; @Linked public class AFKStopperListener implements Listener { - private int afkTicks = 0; + private long lastMovementTime = System.currentTimeMillis(); public AFKStopperListener() { Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> { - switch (afkTicks) { - case 15: - for (Player p : Bukkit.getOnlinePlayers()) { - p.kickPlayer(BauSystem.MESSAGE.parse("AFK_KICK_MESSAGE", p)); - } - case 14: - BauSystem.MESSAGE.broadcast("AFK_WARNING_MESSAGE"); - default: - afkTicks++; - } + long currentTime = System.currentTimeMillis(); + if(currentTime - lastMovementTime > 10*60000) { // 10 Minutes + for (Player p : Bukkit.getOnlinePlayers()) + p.kickPlayer(BauSystem.MESSAGE.parse("AFK_KICK_MESSAGE", p)); + } else if(currentTime - lastMovementTime > 9*60000) + BauSystem.MESSAGE.broadcast("AFK_WARNING_MESSAGE"); }, 1200, 1200); //every minute } @@ -60,7 +56,7 @@ public class AFKStopperListener implements Listener { Location from = event.getFrom(); if (from.getPitch() != to.getPitch() || from.getYaw() != to.getYaw()) - afkTicks = 0; + lastMovementTime = System.currentTimeMillis(); } @EventHandler(priority = EventPriority.LOWEST) //Potential fix for potential race condition with WE axe spontaneously not working diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauScoreboard.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauScoreboard.java index 74098b1b..66435414 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauScoreboard.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauScoreboard.java @@ -4,13 +4,13 @@ import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.region.GlobalRegion; import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.flags.Flag; -import de.steamwar.bausystem.shared.Pair; import de.steamwar.bausystem.utils.ScoreboardElement; import de.steamwar.linkage.Linked; import de.steamwar.scoreboard.SWScoreboard; import de.steamwar.scoreboard.ScoreboardCallback; -import org.apache.commons.lang3.tuple.MutableTriple; -import org.apache.commons.lang3.tuple.Triple; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -22,8 +22,17 @@ import java.util.*; @Linked public class BauScoreboard implements Listener { + @Setter + @Getter + @AllArgsConstructor + public static class Data { + private ScoreboardElement.ScoreboardGroup group; + private Integer priority; + private String text; + } + private static final Map> ELEMENTS = new HashMap<>(); - private static final Map>> ADDITIONAL_SCOREBOARD_LINES = new HashMap<>(); + private static final Map> ADDITIONAL_SCOREBOARD_LINES = new HashMap<>(); public static void addElement(ScoreboardElement scoreboardElement) { List elements = ELEMENTS.computeIfAbsent(scoreboardElement.getGroup(), scoreboardGroup -> new ArrayList<>()); @@ -32,15 +41,15 @@ public class BauScoreboard implements Listener { } public static void setAdditionalElement(Player player, String key, ScoreboardElement.ScoreboardGroup group, int priority, String value) { - Map> playerElements = ADDITIONAL_SCOREBOARD_LINES.computeIfAbsent(player, player1 -> new HashMap<>()); + Map playerElements = ADDITIONAL_SCOREBOARD_LINES.computeIfAbsent(player, player1 -> new HashMap<>()); if (value == null || value.isBlank()) { playerElements.remove(key); return; } - MutableTriple element = playerElements.computeIfAbsent(key, s -> new MutableTriple<>(null, null, null)); - element.setLeft(group); - element.setMiddle(priority); - element.setRight(value); + Data element = playerElements.computeIfAbsent(key, s -> new Data(null, null, null)); + element.setGroup(group); + element.setPriority(priority); + element.setText(value); } @EventHandler @@ -76,10 +85,10 @@ public class BauScoreboard implements Listener { if (ADDITIONAL_SCOREBOARD_LINES.containsKey(player)) { ADDITIONAL_SCOREBOARD_LINES.get(player).values() .stream() - .filter(triple -> triple.getLeft() == group) - .sorted(Comparator.comparing(MutableTriple::getMiddle)) + .filter(triple -> triple.getGroup() == group) + .sorted(Comparator.comparing(Data::getPriority)) .forEach(triple -> { - groupElements.add(triple.getRight()); + groupElements.add(triple.getText()); }); } groupElements.removeIf(Objects::isNull); diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/VersionAnnouncer.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/VersionAnnouncer.java new file mode 100644 index 00000000..ee9e91be --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/VersionAnnouncer.java @@ -0,0 +1,49 @@ +/* + * 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 . + */ + +package de.steamwar.bausystem.utils; + +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.ViaAPI; +import de.steamwar.bausystem.BauSystem; +import de.steamwar.linkage.Linked; +import net.md_5.bungee.api.ChatMessageType; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +@Linked +public class VersionAnnouncer implements Listener { + + private final String versionString = Bukkit.getBukkitVersion().split("-", 2)[0]; + + @SuppressWarnings("unchecked") + private final ViaAPI via = Via.getAPI(); + + @EventHandler + public void onJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + if(via.getServerVersion().supportedVersions().contains(via.getPlayerVersion(player))) + return; + + BauSystem.MESSAGE.sendPrefixless("SERVER_VERSION", player, ChatMessageType.ACTION_BAR, versionString); + } +} diff --git a/BauSystem/BauSystem_Main/src/plugin.yml b/BauSystem/BauSystem_Main/src/plugin.yml index 71b08d95..53585eaa 100644 --- a/BauSystem/BauSystem_Main/src/plugin.yml +++ b/BauSystem/BauSystem_Main/src/plugin.yml @@ -2,6 +2,8 @@ name: BauSystem authors: [ Lixfel, YoyoNow, Chaoscaot, Zeanon, D4rkr34lm ] version: "2.0" depend: [ WorldEdit, SpigotCore ] +softdepend: + - ViaVersion load: POSTWORLD main: de.steamwar.bausystem.BauSystem api-version: "1.13" diff --git a/CommonCore/SQL/src/de/steamwar/sql/SteamwarUser.java b/CommonCore/SQL/src/de/steamwar/sql/SteamwarUser.java index 31661242..b505ab1f 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/SteamwarUser.java +++ b/CommonCore/SQL/src/de/steamwar/sql/SteamwarUser.java @@ -21,7 +21,6 @@ package de.steamwar.sql; import de.steamwar.sql.internal.*; import lombok.Getter; -import lombok.SneakyThrows; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; @@ -34,6 +33,7 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.logging.Level; import java.util.stream.Collectors; +import java.util.stream.Stream; public class SteamwarUser { private static final SecureRandom random = new SecureRandom(); @@ -61,7 +61,7 @@ public class SteamwarUser { private static final SelectStatement byName = table.selectFields("UserName"); private static final SelectStatement byDiscord = table.selectFields("DiscordId"); private static final SelectStatement byTeam = table.selectFields("Team"); - private static final SelectStatement getServerTeam = new SelectStatement<>(table, "SELECT * FROM UserData WHERE UserGroup != 'Member' AND UserGroup != 'YouTuber'"); + private static final SelectStatement getUsersWithPerm = new SelectStatement<>(table, "SELECT S.* FROM UserData S JOIN UserPerm P ON S.id = P.User WHERE P.Perm = ?"); private static final Statement updateName = table.update(Table.PRIMARY, "UserName"); private static final Statement updatePassword = table.update(Table.PRIMARY, "Password"); @@ -138,10 +138,21 @@ public class SteamwarUser { } } - public static List getServerTeam() { - return getServerTeam.listSelect(); + public static List getUsersWithPerm(UserPerm userPerm) { + return getUsersWithPerm.listSelect(userPerm); } + public static List getServerTeam() { + return Stream.of(getUsersWithPerm(UserPerm.PREFIX_ADMIN), + getUsersWithPerm(UserPerm.PREFIX_DEVELOPER), + getUsersWithPerm(UserPerm.PREFIX_MODERATOR), + getUsersWithPerm(UserPerm.PREFIX_SUPPORTER), + getUsersWithPerm(UserPerm.PREFIX_BUILDER) + ) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + public static List getTeam(int teamId) { return byTeam.listSelect(teamId); } diff --git a/KotlinCore/build.gradle.kts b/KotlinCore/build.gradle.kts index 6c16669c..5d00f0f6 100644 --- a/KotlinCore/build.gradle.kts +++ b/KotlinCore/build.gradle.kts @@ -18,17 +18,8 @@ */ plugins { - id("java") - kotlin("jvm") version "2.0.0" - - id("com.github.johnrengelman.shadow") -} - -group = "de.steamwar" -version = "" - -tasks.compileJava { - options.encoding = "UTF-8" + steamwar.kotlin + alias(libs.plugins.shadow) } tasks.build { @@ -39,29 +30,6 @@ tasks.shadowJar { exclude("org/**") } -java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 -} - -sourceSets { - main { - java { - srcDirs("src/") - } - resources { - srcDirs("src/") - exclude("**/*.java", "**/*.kt") - } - } -} - dependencies { - implementation(kotlin("reflect")) - - compileOnly("io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT") + compileOnly(libs.paperapi21) } - -kotlin { - jvmToolchain(21) -} \ No newline at end of file diff --git a/KotlinCore/src/de/steamwar/core/KotlinCore.kt b/KotlinCore/src/de/steamwar/kotlin/KotlinCore.kt similarity index 93% rename from KotlinCore/src/de/steamwar/core/KotlinCore.kt rename to KotlinCore/src/de/steamwar/kotlin/KotlinCore.kt index dd5a2946..e821ade4 100644 --- a/KotlinCore/src/de/steamwar/core/KotlinCore.kt +++ b/KotlinCore/src/de/steamwar/kotlin/KotlinCore.kt @@ -1,7 +1,7 @@ /* * This file is a part of the SteamWar software. * - * Copyright (C) 2024 SteamWar.de-Serverteam + * 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 @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package de.steamwar.core +package de.steamwar.kotlin import org.bukkit.plugin.java.JavaPlugin diff --git a/KotlinCore/src/plugin.yml b/KotlinCore/src/plugin.yml index 8b050f06..7117121e 100644 --- a/KotlinCore/src/plugin.yml +++ b/KotlinCore/src/plugin.yml @@ -1,5 +1,5 @@ name: KotlinCore version: '2.0.0' -main: de.steamwar.core.KotlinCore +main: de.steamwar.kotlin.KotlinCore load: POSTWORLD api-version: '1.21' diff --git a/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties b/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties index e2797995..4530aaaf 100644 --- a/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties +++ b/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties @@ -4,7 +4,7 @@ DATE=........ COMMAND_HELP_HEAD=§7---=== (§e{0}§7) ===--- # ServerTeamNPC's -NPC_CHAT_1 = §fHello, I''m {0} and I''m a(n) {1}. +NPC_CHAT_1 = §fHello, I''m {0} and I''m a(n) {1}§f. NPC_CHAT_2 = §fWelcome on §eSteam§8War§f, have fun. # Portal Command diff --git a/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties b/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties index d6049037..969270ee 100644 --- a/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties +++ b/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties @@ -4,7 +4,7 @@ DATE=........ COMMAND_HELP_HEAD=§7---=== (§e{0}§7) ===--- # ServerTeamNPC's -NPC_CHAT_1 = §fHallo, ich bin {0} und bin ein {1}. +NPC_CHAT_1 = §fHallo, ich bin {0} und bin ein {1}§f. NPC_CHAT_2 = §fWillkommen auf §eSteam§8War§f, viel Spaß dir. # Portal Command diff --git a/README.md b/README.md new file mode 100644 index 00000000..ad9f4739 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# SteamWar diff --git a/TNTLeague/build.gradle.kts b/TNTLeague/build.gradle.kts new file mode 100644 index 00000000..b9689d85 --- /dev/null +++ b/TNTLeague/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + steamwar.kotlin +} + +dependencies { + compileOnly(libs.paperapi21) + compileOnly(project(":SpigotCore")) + compileOnly(project(":KotlinCore")) +} \ No newline at end of file diff --git a/TNTLeague/src/config.yml b/TNTLeague/src/config.yml new file mode 100644 index 00000000..d2e0a1e6 --- /dev/null +++ b/TNTLeague/src/config.yml @@ -0,0 +1,100 @@ +startDelay: 10 +gameTime: 1200 + +prices: + TNT: + price: 4 + amount: 4 + REDSTONE: + price: 4 + amount: 4 + REPEATER: + price: 4 + amount: 2 + COMPARATOR: + price: 4 + amount: 1 + REDSTONE_BLOCK: + price: 4 + amount: 1 + REDSTONE_TORCH: + price: 4 + amount: 2 + END_STONE: + price: 4 + amount: 8 + ICE: + price: 8 + amount: 1 + LEVER: + price: 4 + amount: 1 + OAK_BUTTON: + price: 4 + amount: 1 + STONE_BUTTON: + price: 4 + amount: 1 + OAK_TRAPDOOR: + price: 4 + amount: 2 + IRON_TRAPDOOR: + price: 4 + amount: 1 + PISTON: + price: 4 + amount: 2 + STICKY_PISTON: + price: 4 + amount: 1 + GLASS: + price: 4 + amount: 2 + OAK_FENCE: + price: 4 + amount: 4 + LADDER: + price: 6 + amount: 2 + WHITE_GLAZED_TERRACOTTA: + price: 4 + amount: 3 + JUKEBOX: + price: 4 + amount: 2 + OBSERVER: + price: 10 + amount: 4 + BREWING_STAND: + price: 4 + amount: 1 + STRING: + price: 4 + amount: 2 + END_STONE_BRICK_SLAB: + price: 4 + amount: 2 + TARGET: + price: 4 + amount: 1 + COPPER_BULB: + price: 4 + amount: 1 + SLIME_BLOCK: + price: 6 + amount: 2 + HONEY_BLOCK: + price: 6 + amount: 2 + STONE_PRESSURE_PLATE: + price: 4 + amount: 1 + NOTE_BLOCK: + price: 3 + amount: 1 + HOPPER: + price: 3 + amount: 1 + GRAVEL: + price: 4 + amount: 3 diff --git a/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt b/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt new file mode 100644 index 00000000..5479e4d7 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt @@ -0,0 +1,48 @@ +package de.steamwar.tntleague + +import de.steamwar.tntleague.command.AcceptCommand +import de.steamwar.tntleague.command.InviteCommand +import de.steamwar.tntleague.command.LeaveCommand +import de.steamwar.tntleague.command.RemoveCommand +import de.steamwar.tntleague.events.GlobalListener +import de.steamwar.tntleague.events.LobbyListener +import net.kyori.adventure.key.Key +import net.kyori.adventure.translation.GlobalTranslator +import net.kyori.adventure.translation.TranslationRegistry +import net.kyori.adventure.util.UTF8ResourceBundleControl +import org.bukkit.plugin.java.JavaPlugin +import java.util.* + +lateinit var plugin: TNTLeague + +class TNTLeague : JavaPlugin() { + init { + plugin = this + } + + override fun onEnable() { + saveResource("config.yml", false) + saveDefaultConfig() + + val registry = TranslationRegistry.create(Key.key("steamwar:tntleague")) + + val bundleDe = ResourceBundle.getBundle("de.steamwar.tntleague.TNTLeague", Locale.GERMAN, UTF8ResourceBundleControl()) + val bundleEn = ResourceBundle.getBundle("de.steamwar.tntleague.TNTLeague", Locale.US, UTF8ResourceBundleControl()) + registry.defaultLocale(Locale.US) + + registry.registerAll(Locale.GERMAN, bundleDe, true) + registry.registerAll(Locale.US, bundleEn, true) + + GlobalTranslator.translator().addSource(registry) + + server.pluginManager.registerEvents(LobbyListener, this) + server.pluginManager.registerEvents(GlobalListener, this) + + logger.info("TNTLeague enabled") + + InviteCommand.register() + AcceptCommand.register() + RemoveCommand.register() + LeaveCommand.register() + } +} diff --git a/TNTLeague/src/de/steamwar/tntleague/TNTLeague_de_DE.properties b/TNTLeague/src/de/steamwar/tntleague/TNTLeague_de_DE.properties new file mode 100644 index 00000000..7200a9e2 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/TNTLeague_de_DE.properties @@ -0,0 +1,18 @@ +join={0} ist dem Spiel beigetreten! +joinTeam={0} ist dem {1} team begetreten! +quit={0} hat das Spiel verlassen! +blue=Blau +red=Rot +shutdown=Der Server fährt in {0} sekunden herunter! +teamWin=Team {0} gewinnt! +notEnoughCoins=Du hast nicht genug Coins um dir das zu kaufen! +gameStarting=Das Spiel beginnt in {0} Sekunden! +gameStart=Start in {0} +gameStarted=Das Spiel beginnt! +gameEnded=Das Spiel ist aus! +dealer=Händler +dealerItem= +dealerPrice=Kosten: {0} Coins +scoreboardTarget=Ziel: {0} +scoreboardTime=Zeit: {0}:{1} +scoreboardTeam= \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/TNTLeague_en_US.properties b/TNTLeague/src/de/steamwar/tntleague/TNTLeague_en_US.properties new file mode 100644 index 00000000..2f7c11e2 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/TNTLeague_en_US.properties @@ -0,0 +1,37 @@ +join={0} joined the game! +joinTeam={0} joined the {1} team! +quit={0} left the game! +quitTeam={0} left the {1} team! +blue=Blue +red=Red +shutdown=The server stops in {0} seconds! +teamWin=Team {0} wins! + +notEnoughCoins=You don't have enough coins to buy this item! + +gameStarting=The game starts in {0} seconds! +gameStart=Starting in {0} +gameStarted=The game has started! + +timeRemaining={0} minutes remaining! + +gameEnded=The game has ended! +draw=The game ended in a draw! +chat={0}» {1} + +dealer=Shopkeeper +dealerItem={0} {1} +dealerPrice=Price: {0} Coins + +scoreboardTarget=Target: {0} +scoreboardTime=Time: {0}:{1} +scoreboardTeam=Team {0}: {1} + +ready=Ready +notReady=Not ready +isReady=Team {0} is ready! +isNotReady=Team {0} is not ready! + +invited={0} invited you to join the {1} team! *Click* +invitedHover=Click to join the {0} team! +invitedPlayer=Invited {0} to join your team! \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/command/AcceptCommand.kt b/TNTLeague/src/de/steamwar/tntleague/command/AcceptCommand.kt new file mode 100644 index 00000000..3606bdbd --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/command/AcceptCommand.kt @@ -0,0 +1,40 @@ +/* + * 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 . + */ + +package de.steamwar.tntleague.command + +import de.steamwar.command.SWCommand +import de.steamwar.tntleague.game.TNTLeagueGame +import org.bukkit.entity.Player + +object AcceptCommand: SWCommand("accept") { + + @Register + fun acceptInvite(sender: Player, @Validator("isLeader") target: Player) { + if (TNTLeagueGame.state != TNTLeagueGame.GameState.LOBBY) return + + val team = TNTLeagueGame.getTeam(target) ?: return + if (team.leader != target) return + if (sender !in team.invites) return + + team.invites.remove(target) + team.opposite.invites.remove(target) + team.join(sender) + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/command/InviteCommand.kt b/TNTLeague/src/de/steamwar/tntleague/command/InviteCommand.kt new file mode 100644 index 00000000..fdfaf359 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/command/InviteCommand.kt @@ -0,0 +1,56 @@ +/* + * 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 . + */ + +package de.steamwar.tntleague.command + +import de.steamwar.command.SWCommand +import de.steamwar.command.TypeValidator +import de.steamwar.tntleague.game.TNTLeagueGame +import de.steamwar.tntleague.util.* +import net.kyori.adventure.text.event.ClickEvent +import net.kyori.adventure.text.event.HoverEvent +import org.bukkit.entity.Player + +object InviteCommand: SWCommand("invite") { + + @Register + fun invitePlayer(@Validator("isLeader") sender: Player, target: Player) { + if (TNTLeagueGame.state != TNTLeagueGame.GameState.LOBBY) return + if (TNTLeagueGame.getTeam(target) != null) return + + val team = TNTLeagueGame.getTeam(sender)!! + team.invites.add(target) + + target.sendMessage(translate("invited", sender.name.yellow(), translate(team.name).colorByTeam(team)).basic().clickEvent( + ClickEvent.callback { + if (target !in team.invites) return@callback + + team.invites.remove(target) + team.opposite.invites.remove(target) + team.join(target) + }) + .hoverEvent(HoverEvent.showText(translate("invitedHover", translate(team.name).colorByTeam(team)).green()))) + sender.sendMessage(translate("invitedPlayer", target.name.yellow()).basic()) + } + + @Validator("isLeader", local = false) + fun isLeader(): TypeValidator { + return TypeValidator { _, player, _ -> TNTLeagueGame.getTeam(player)?.leader == player} + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/command/LeaveCommand.kt b/TNTLeague/src/de/steamwar/tntleague/command/LeaveCommand.kt new file mode 100644 index 00000000..aeb96492 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/command/LeaveCommand.kt @@ -0,0 +1,30 @@ +/* + * 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 . + */ + +package de.steamwar.tntleague.command + +import de.steamwar.command.SWCommand +import de.steamwar.tntleague.game.TNTLeagueGame +import org.bukkit.entity.Player + +object LeaveCommand: SWCommand("leave", "l") { + + @Register + fun leave(player: Player) = TNTLeagueGame.getTeam(player)?.remove(player) +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/command/RemoveCommand.kt b/TNTLeague/src/de/steamwar/tntleague/command/RemoveCommand.kt new file mode 100644 index 00000000..d889f31a --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/command/RemoveCommand.kt @@ -0,0 +1,39 @@ +/* + * 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 . + */ + +package de.steamwar.tntleague.command + +import de.steamwar.command.SWCommand +import de.steamwar.tntleague.game.TNTLeagueGame +import org.bukkit.entity.Player + +object RemoveCommand: SWCommand("remove") { + + @Register + fun removePlayer(@Validator("isLeader") sender: Player, target: Player) { + if (TNTLeagueGame.state != TNTLeagueGame.GameState.LOBBY) return + + if (sender == target) return + val team = TNTLeagueGame.getTeam(sender) ?: return + if (team.leader != sender) return + if (target !in team.members) return + + team.remove(target) + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/config/TNTLeagueConfig.kt b/TNTLeague/src/de/steamwar/tntleague/config/TNTLeagueConfig.kt new file mode 100644 index 00000000..30fb8353 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/config/TNTLeagueConfig.kt @@ -0,0 +1,35 @@ +package de.steamwar.tntleague.config + +import de.steamwar.tntleague.plugin +import org.bukkit.Material +import org.bukkit.configuration.ConfigurationSection +import org.bukkit.configuration.file.FileConfiguration + +data class TNTLeagueConfig( + val startDelay: Int = 10, + val gameTime: Int = 60 * 20, + + val prices: Map +) { + companion object { + val config: TNTLeagueConfig by lazy { loadConfig(plugin.config) } + + private fun loadConfig(config: FileConfiguration): TNTLeagueConfig { + return TNTLeagueConfig(config.getInt("startDelay"), config.getInt("gameTime"), loadPrices(config.getConfigurationSection("prices")!!)) + } + + private fun loadPrices(config: ConfigurationSection): Map { + return config.getKeys(false).associateWith { + Price( + config.getInt("$it.amount"), + config.getInt("$it.price") + ) + }.mapKeys { Material.getMaterial(it.key)!! } + } + } + + data class Price( + val amount: Int, + val price: Int, + ) +} diff --git a/TNTLeague/src/de/steamwar/tntleague/config/TNTLeagueWorldConfig.kt b/TNTLeague/src/de/steamwar/tntleague/config/TNTLeagueWorldConfig.kt new file mode 100644 index 00000000..faaf73aa --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/config/TNTLeagueWorldConfig.kt @@ -0,0 +1,75 @@ +package de.steamwar.tntleague.config + +import de.steamwar.tntleague.plugin +import de.steamwar.tntleague.util.Area +import de.steamwar.tntleague.util.translate +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.configuration.ConfigurationSection +import org.bukkit.configuration.file.YamlConfiguration +import org.bukkit.entity.Villager +import org.bukkit.entity.WanderingTrader +import java.io.File + +val world by lazy { plugin.server.worlds.first()!! } + +private val targetedBlocksRed by lazy { TNTLeagueWorldConfig.redTeam.target.blocks.count { block -> block.type == TNTLeagueWorldConfig.targetMaterial } } + +private val targetedBlocksBlue by lazy { TNTLeagueWorldConfig.blueTeam.target.blocks.count { block -> block.type == TNTLeagueWorldConfig.targetMaterial } } + +private val targetedBlocksAll: Int + get() = if (targetedBlocksBlue == targetedBlocksRed) targetedBlocksBlue else error("Targeted blocks are not equal") + +val targetedBlocks: Int + get() = if (TNTLeagueWorldConfig.target != -1) TNTLeagueWorldConfig.target else targetedBlocksAll + +object TNTLeagueWorldConfig { + private val config: YamlConfiguration by lazy { + YamlConfiguration.loadConfiguration( + File( + plugin.server.worlds.first().worldFolder, + "tntleague.yml" + ) + ) + } + + val blueTeam: TeamConfig = TeamConfig.fromConfig(config.getConfigurationSection("blueTeam")!!) + val redTeam: TeamConfig = TeamConfig.fromConfig(config.getConfigurationSection("redTeam")!!) + val lobby: Location = config.getLocation("lobby", blueTeam.spawnLocation.clone().add(redTeam.spawnLocation).multiply(0.5))!! + val targetMaterial: Material = Material.matchMaterial(config.getString("targetMaterial", "IRON_BLOCK")!!)!! + val minHeight: Int = config.getInt("minHeight", 0) + val target: Int = config.getInt("target", -1) + + @JvmRecord + data class TeamConfig( + val spawnLocation: Location, + val dealerSpawn: Location, + val itemSpawn: Location, + val target: Area + ) { + companion object { + fun fromConfig(config: ConfigurationSection): TeamConfig { + val spawnLocation = config.getLocation("spawn")!! + val dealerSpawn = config.getLocation("dealerSpawn")!! + val itemSpawn = config.getLocation("itemSpawn")!! + val targetPos1 = config.getLocation("targetMin")!! + val targetPos2 = config.getLocation("targetMax")!! + + spawnDealer(dealerSpawn) + + return TeamConfig(spawnLocation, dealerSpawn, itemSpawn, Area(targetPos1, targetPos2)) + } + + private fun spawnDealer(loc: Location) = world.spawn(loc, WanderingTrader::class.java) + .apply { + customName(translate("dealer")) + isCustomNameVisible = false + isInvulnerable = true + isSilent = true + isCollidable = false + isAware = false + setAI(false) + } + } + } +} diff --git a/TNTLeague/src/de/steamwar/tntleague/events/DummyListener.kt b/TNTLeague/src/de/steamwar/tntleague/events/DummyListener.kt new file mode 100644 index 00000000..f9f616de --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/events/DummyListener.kt @@ -0,0 +1,25 @@ +/* + * 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 . + */ + +package de.steamwar.tntleague.events + +import org.bukkit.event.Listener + +object DummyListener: Listener { +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/events/GlobalListener.kt b/TNTLeague/src/de/steamwar/tntleague/events/GlobalListener.kt new file mode 100644 index 00000000..f2930adb --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/events/GlobalListener.kt @@ -0,0 +1,88 @@ +package de.steamwar.tntleague.events + +import de.steamwar.tntleague.config.TNTLeagueWorldConfig +import de.steamwar.tntleague.game.TNTLeagueGame +import de.steamwar.tntleague.game.TNTLeagueTeam +import de.steamwar.tntleague.inventory.SWInventoryHolder +import de.steamwar.tntleague.plugin +import de.steamwar.tntleague.util.* +import io.papermc.paper.event.player.AsyncChatEvent +import org.bukkit.GameMode +import org.bukkit.Material +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.entity.PlayerDeathEvent +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.event.player.PlayerMoveEvent +import org.bukkit.event.player.PlayerQuitEvent +import org.bukkit.event.player.PlayerRespawnEvent + +object GlobalListener: Listener { + + @EventHandler(priority = EventPriority.LOW) + fun onPlayerJoin(e: PlayerJoinEvent) { + e.joinMessage(null) + with(e.player) { + teleport(TNTLeagueWorldConfig.lobby) + inventory.clear() + plugin.server.broadcast(translate("join", name.bold()).basic()) + isOp = false + gameMode = GameMode.SPECTATOR + respawnLocation = TNTLeagueWorldConfig.lobby + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onPlayerQuit(e: PlayerQuitEvent) { + e.quitMessage(null) + plugin.server.broadcast(translate("quit", e.player.name.bold().colorByTeam(TNTLeagueGame.getTeam(e.player))).basic()) + TNTLeagueGame.playerLeave(e.player) + } + + @EventHandler(priority = EventPriority.LOWEST) + fun onPlayerClick(e: InventoryClickEvent) { + val holder = e.inventory.getHolder(false) + if (holder is SWInventoryHolder && e.clickedInventory == holder._inventory) { + e.isCancelled = true + holder.handleInventoryClick(e) + } + } + + @EventHandler(priority = EventPriority.LOWEST) + fun onPlayerMove(e: PlayerMoveEvent) { + if (e.to.blockY < TNTLeagueWorldConfig.minHeight) { + when (val team = TNTLeagueGame.getTeam(e.player)) { + is TNTLeagueTeam -> e.player.teleport(team.config.spawnLocation) + null -> e.player.teleport(TNTLeagueWorldConfig.blueTeam.spawnLocation) + } + } + + e.player.foodLevel = 20 + e.player.saturation = 20f + } + + @EventHandler + fun onPlayerDeath(e: PlayerDeathEvent) { + e.deathMessage(null) + e.drops.clear() + + e.itemsToKeep.removeIf { it.type != Material.DIAMOND_PICKAXE } + } + + @EventHandler + fun onPlayerRespawn(e: PlayerRespawnEvent) { + when (val team = TNTLeagueGame.getTeam(e.player)) { + is TNTLeagueTeam -> e.respawnLocation = team.config.spawnLocation + null -> e.respawnLocation = TNTLeagueWorldConfig.lobby + } + } + + @EventHandler + fun onChat(e: AsyncChatEvent) { + e.renderer { source, sourceDisplayName, message, _ -> + translate("chat", sourceDisplayName.colorByTeam(TNTLeagueGame.getTeam(source)), message).basic() + } + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/events/IngameListener.kt b/TNTLeague/src/de/steamwar/tntleague/events/IngameListener.kt new file mode 100644 index 00000000..bb9b0415 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/events/IngameListener.kt @@ -0,0 +1,77 @@ +package de.steamwar.tntleague.events + +import de.steamwar.scoreboard.SWScoreboard +import de.steamwar.tntleague.config.TNTLeagueWorldConfig +import de.steamwar.tntleague.game.TNTLeagueGame +import de.steamwar.tntleague.game.TNTLeagueTeam +import de.steamwar.tntleague.inventory.DealerInventory +import de.steamwar.tntleague.util.TNTLeagueScoreboard +import org.bukkit.GameMode +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.entity.EntityType +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.entity.EntityExplodeEvent +import org.bukkit.event.entity.PlayerDeathEvent +import org.bukkit.event.player.PlayerDropItemEvent +import org.bukkit.event.player.PlayerInteractEntityEvent +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.event.player.PlayerMoveEvent +import org.bukkit.event.player.PlayerQuitEvent + +object IngameListener: Listener { + + @EventHandler + fun onEntityInteract(e: PlayerInteractEntityEvent) { + if (e.player.gameMode == GameMode.SPECTATOR) return + + if(e.rightClicked.type == EntityType.VILLAGER) { + e.isCancelled = true + e.player.openInventory(DealerInventory(e.player).getInventory()) + } + } + + @EventHandler + fun onExplode(e: EntityExplodeEvent) { + e.blockList().filter { it.type == TNTLeagueWorldConfig.targetMaterial } + .groupBy { getTeamByTargetLocation(it.location) } + .filterKeysNotNull() + .mapValues { it.value.size } + .forEach { it.key.damagedBlocks += it.value } + } + + @EventHandler + fun onJoin(e: PlayerJoinEvent) { + SWScoreboard.createScoreboard(e.player, TNTLeagueScoreboard(e.player)) + } + + @EventHandler + fun onMove(e: PlayerMoveEvent) { + if (TNTLeagueGame.getTeam(e.player) != null) { + if (e.to.blockX >= TNTLeagueWorldConfig.lobby.blockX && e.to.blockX <= TNTLeagueWorldConfig.lobby.blockX + 1 || + e.to.blockZ >= TNTLeagueWorldConfig.lobby.blockZ && e.to.blockZ <= TNTLeagueWorldConfig.lobby.blockZ + 1) { + e.isCancelled = true + } + } + } + + @EventHandler + fun onDropPickaxe(e: PlayerDropItemEvent) { + if (e.itemDrop.itemStack.type == Material.DIAMOND_PICKAXE) { + e.isCancelled = true + } + } + + private fun getTeamByTargetLocation(location: Location): TNTLeagueTeam? = + when (location) { + in TNTLeagueWorldConfig.redTeam.target -> TNTLeagueGame.redTeam + in TNTLeagueWorldConfig.blueTeam.target -> TNTLeagueGame.blueTeam + else -> null + } + + private fun Map.filterKeysNotNull(destination: MutableMap = mutableMapOf()): Map { + this.forEach { (t, u) -> if(t != null) destination[t] = u } + return destination + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/events/LobbyListener.kt b/TNTLeague/src/de/steamwar/tntleague/events/LobbyListener.kt new file mode 100644 index 00000000..e88cfbaa --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/events/LobbyListener.kt @@ -0,0 +1,61 @@ +package de.steamwar.tntleague.events + +import de.steamwar.tntleague.game.TNTLeagueGame +import de.steamwar.tntleague.plugin +import de.steamwar.tntleague.util.basic +import de.steamwar.tntleague.util.colorByTeam +import de.steamwar.tntleague.util.translate +import de.steamwar.tntleague.util.yellow +import io.papermc.paper.util.Tick +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.block.Action +import org.bukkit.event.entity.EntityDamageEvent +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.player.PlayerDropItemEvent +import org.bukkit.event.player.PlayerInteractEvent +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.event.player.PlayerQuitEvent + +object LobbyListener: Listener { + + @EventHandler + fun onPlayerJoin(e: PlayerJoinEvent) { + TNTLeagueGame.getFreeTeam()?.run { + join(e.player) + TNTLeagueGame.checkStart() + } + } + + @EventHandler(priority = EventPriority.LOWEST) + fun onPlayerQuit(e: PlayerQuitEvent) { + val team = TNTLeagueGame.getTeam(e.player) ?: return + team.leave(e.player) + } + + @EventHandler + fun onPlayerDamage(e: EntityDamageEvent) { + e.isCancelled = true + } + + @EventHandler + fun onDropEvent(e: PlayerDropItemEvent) { + e.isCancelled = true + } + + @EventHandler + fun interactEvent(e: PlayerInteractEvent) { + val team = TNTLeagueGame.getTeam(e.player) + if (e.action.isRightClick && team != null && e.item?.isSimilar(team.readyItem()) == true && team.opposite.leader != null) { + team.isReady = !team.isReady + } + } + + @EventHandler + fun inventoryClick(e: InventoryClickEvent) { + if (e.clickedInventory == e.whoClicked.inventory) { + e.isCancelled = true + } + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueGame.kt b/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueGame.kt new file mode 100644 index 00000000..59d4e65c --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueGame.kt @@ -0,0 +1,259 @@ +package de.steamwar.tntleague.game + +import de.steamwar.scoreboard.SWScoreboard +import de.steamwar.sql.Fight +import de.steamwar.sql.FightPlayer +import de.steamwar.sql.SteamwarUser +import de.steamwar.tntleague.config.TNTLeagueConfig +import de.steamwar.tntleague.config.TNTLeagueWorldConfig +import de.steamwar.tntleague.config.world +import de.steamwar.tntleague.events.DummyListener +import de.steamwar.tntleague.events.IngameListener +import de.steamwar.tntleague.events.LobbyListener +import de.steamwar.tntleague.inventory.DealerInventory +import de.steamwar.tntleague.plugin +import de.steamwar.tntleague.util.* +import net.kyori.adventure.bossbar.BossBar +import net.kyori.adventure.sound.Sound +import org.bukkit.GameMode +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.block.data.type.TNT +import org.bukkit.entity.Entity +import org.bukkit.entity.Item +import org.bukkit.entity.Player +import org.bukkit.entity.TNTPrimed +import org.bukkit.entity.Villager +import org.bukkit.event.HandlerList +import org.bukkit.event.Listener +import org.bukkit.inventory.ItemStack +import org.bukkit.scheduler.BukkitTask +import java.sql.Timestamp +import java.time.Instant + +object TNTLeagueGame { + var state: GameState = GameState.LOBBY + set(value) { + if (field.listener != value.listener) { + HandlerList.unregisterAll(field.listener) + plugin.server.pluginManager.registerEvents(value.listener, plugin) + } + field = value + } + + var gameTimeRemaining: Int = TNTLeagueConfig.config.gameTime + + val blueTeam = TNTLeagueTeam(TNTLeagueWorldConfig.blueTeam, TNTLeagueTeam.Team.BLUE) + val redTeam = TNTLeagueTeam(TNTLeagueWorldConfig.redTeam, TNTLeagueTeam.Team.RED) + + private lateinit var start: Timestamp + + private var task: Int? = null + private lateinit var spawnerTask: BukkitTask + private lateinit var timerTask: BukkitTask + + private fun setup() { + assert(state == GameState.STARTING) { "Game is already running" } + + state = GameState.RUNNING + + plugin.server.onlinePlayers.forEach { SWScoreboard.createScoreboard(it, TNTLeagueScoreboard(it)) } + + blueTeam.start() + redTeam.start() + + plugin.server.broadcast(translate("gameStarted").success()) + + val tnt = ItemStack(Material.TNT) + + start = Timestamp.from(Instant.now()) + + spawnerTask = plugin.server.scheduler.runTaskTimer(plugin, bukkit { + if (world.getNearbyEntitiesByType(Item::class.java, TNTLeagueWorldConfig.blueTeam.itemSpawn, 3.0).sumOf { it.itemStack.amount } <= 256) { + spawnItems(TNTLeagueWorldConfig.blueTeam.itemSpawn, tnt) + spawnItems(TNTLeagueWorldConfig.blueTeam.itemSpawn, DealerInventory.coins) + } + if (world.getNearbyEntitiesByType(Item::class.java, TNTLeagueWorldConfig.redTeam.itemSpawn, 3.0).sumOf { it.itemStack.amount } <= 256) { + spawnItems(TNTLeagueWorldConfig.redTeam.itemSpawn, tnt) + spawnItems(TNTLeagueWorldConfig.redTeam.itemSpawn, DealerInventory.coins) + } + }, 5, 10) + + timerTask = plugin.server.scheduler.runTaskTimer(plugin, bukkit { + gameTimeRemaining-- + if (gameTimeRemaining == 0) { + draw(WinReason.TIMEOUT) + return@bukkit + } + + if (gameTimeRemaining % 300 == 0) { + plugin.server.broadcast(translate("timeRemaining", (gameTimeRemaining / 60).toString().yellow()).basic()) + plugin.server.onlinePlayers.forEach { it.playSound(Sound.sound(org.bukkit.Sound.BLOCK_NOTE_BLOCK_PLING.key, Sound.Source.MASTER, 1f, 1f)) } + } + }, 20, 20) + } + + private fun bukkit(f: () -> Unit): () -> Unit = f + + private fun end() { + if(state != GameState.RUNNING) return + state = GameState.END + + plugin.server.onlinePlayers.forEach { + it.gameMode = GameMode.SPECTATOR + SWScoreboard.removeScoreboard(it) + it.playSound(Sound.sound(org.bukkit.Sound.ENTITY_ENDER_DRAGON_DEATH.key, Sound.Source.MASTER, 1f, 1f)) + } + + plugin.server.broadcast(translate("gameEnded").success()) + + spawnerTask.cancel() + + var shutdown = 10 + + plugin.server.scheduler.runTaskTimer(plugin, bukkit { + if (shutdown == 0) { + plugin.server.shutdown() + } + + plugin.server.broadcast(translate("shutdown", shutdown.toString().yellow()).basic()) + + shutdown-- + }, 20, 20) + } + + private fun spawnItems(loc: Location, item: ItemStack) = plugin.server.worlds.first().dropItem(loc, item) + + fun getTeam(player: Player) = if (player in blueTeam.members) blueTeam else if (player in redTeam.members) redTeam else null + + fun getFreeTeam() = if (blueTeam.leader == null) blueTeam else if (redTeam.leader == null) redTeam else null + + fun checkStart() { + if (blueTeam.isReady && redTeam.isReady) { + blueTeam.leader?.inventory?.clear() + redTeam.leader?.inventory?.clear() + state = GameState.STARTING + + var countdown = TNTLeagueConfig.config.startDelay + plugin.server.broadcast(translate("gameStarting", countdown.toString().yellow()).basic()) + val bar = BossBar.bossBar(translate("gameStart", countdown.toString().yellow()).gray(), (TNTLeagueConfig.config.startDelay - countdown) / TNTLeagueConfig.config.startDelay.toFloat(), BossBar.Color.GREEN, BossBar.Overlay.NOTCHED_10) + plugin.server.onlinePlayers.forEach { bar.addViewer(it) } + task = plugin.server.scheduler.scheduleSyncRepeatingTask(plugin, { + plugin.server.onlinePlayers.forEach { it.playSound(Sound.sound(org.bukkit.Sound.ENTITY_EXPERIENCE_ORB_PICKUP.key, Sound.Source.MASTER, 1f, 1f)) } + if (countdown-- == 0) { + plugin.server.onlinePlayers.forEach { it.hideBossBar(bar) } + task = task?.also { plugin.server.scheduler.cancelTask(it) }.let { null } + setup() + } else { + bar.name(translate("gameStart", countdown.toString().yellow()).gray()) + bar.progress((TNTLeagueConfig.config.startDelay - countdown) / TNTLeagueConfig.config.startDelay.toFloat()) + plugin.server.onlinePlayers.filter { !it.activeBossBars().contains(bar) }.forEach { bar.addViewer(it) } + } + }, 20, 20) + + if (task == -1) { + error("Failed to start countdown task") + } + } + } + + fun playerLeave(player: Player) { + blueTeam.invites.remove(player) + redTeam.invites.remove(player) + getTeam(player)?.apply { + members.remove(player) + if (leader == player) { + win(this.opposite, WinReason.LEAVE) + } + } + } + + fun reset() { + assert(state == GameState.LOBBY || state == GameState.STARTING) { "Game is not in lobby or starting state" } + + if (state == GameState.STARTING) { + task = task?.also { plugin.server.scheduler.cancelTask(it) }.let { null } + plugin.server.onlinePlayers.forEach { p -> p.activeBossBars().forEach { it.removeViewer(p) } } + state = GameState.LOBBY + } + } + + fun win(tntLeagueTeam: TNTLeagueTeam, reason: WinReason) { + if (state != GameState.RUNNING) return + end() + plugin.server.broadcast(translate("teamWin", translate(tntLeagueTeam.name).color(tntLeagueTeam.color)).success()) + statistic(tntLeagueTeam, reason) + explode(tntLeagueTeam.opposite) + } + + fun draw(reason: WinReason) { + if (state != GameState.RUNNING) return + end() + plugin.server.broadcast(translate("draw").success()) + statistic(null, reason) + } + + fun explode(team: TNTLeagueTeam) { + Area(team.config.spawnLocation.clone().add(20.0, 30.0, 20.0), team.config.spawnLocation.clone().subtract(20.0, 0.0, 20.0).add(0.0, 30.0, 0.0)) + .locations + .filterIndexed { index, _ -> index % 7 == 0 } + .forEachIndexed { index, location -> + world.spawn(location, TNTPrimed::class.java).apply { + fuseTicks = index + 40 + } + } + } + + private fun statistic(winTeam: TNTLeagueTeam?, reason: WinReason) { + val fightId = Fight.create( + "TNTLeague", + world.name, + start, + TNTLeagueConfig.config.gameTime - gameTimeRemaining, + SteamwarUser.get(blueTeam.leader!!.uniqueId).id, + SteamwarUser.get(redTeam.leader!!.uniqueId).id, + null, + null, + when (winTeam) { + blueTeam -> 1 + redTeam -> 2 + else -> 0 + }, + when (reason) { + WinReason.TIMEOUT -> "TIMEOUT" + WinReason.DESTROYED -> "DESTROYED" + WinReason.LEAVE -> "LEAVE" + } + ) + + addTeamMember(blueTeam, fightId) + addTeamMember(redTeam, fightId) + } + + private fun addTeamMember(team: TNTLeagueTeam, fightId: Int) { + team.members.filter { team.leader != it } + .forEach { + FightPlayer.create( + fightId, + SteamwarUser.get(it.uniqueId).id, + team == blueTeam, + "TNTLeague", + 0, + false + ) + } + } + + enum class GameState(val listener: Listener) { + LOBBY(LobbyListener), + STARTING(LobbyListener), + RUNNING(IngameListener), + END(DummyListener); + } + + enum class WinReason { + TIMEOUT, + DESTROYED, + LEAVE + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueTeam.kt b/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueTeam.kt new file mode 100644 index 00000000..cafa70c2 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueTeam.kt @@ -0,0 +1,146 @@ +package de.steamwar.tntleague.game + +import de.steamwar.tntleague.config.TNTLeagueWorldConfig +import de.steamwar.tntleague.config.targetedBlocks +import de.steamwar.tntleague.plugin +import de.steamwar.tntleague.util.* +import net.kyori.adventure.sound.Sound +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.format.TextColor +import org.bukkit.GameMode +import org.bukkit.Material +import org.bukkit.enchantments.Enchantment +import org.bukkit.entity.Player +import org.bukkit.inventory.ItemStack +import java.awt.Color.green + +data class TNTLeagueTeam(val config: TNTLeagueWorldConfig.TeamConfig, private val team: Team) { + + var leader: Player? = null + set(player) { + field = player + if (player != null) { + with(player.inventory) { + clear() + setItem(4, readyItem()) + } + } + } + + val members = mutableListOf() + val invites = mutableListOf() + + val name: String + get() = team.name.lowercase() + + val color: TextColor + get() = team.color + + var isReady: Boolean = false + set(value) { + field = value + leader?.inventory?.setItem(4, readyItem()) + leader?.playSound(Sound.sound(org.bukkit.Sound.BLOCK_NOTE_BLOCK_PLING.key, Sound.Source.MASTER, 1f, 1f)) + + plugin.server.onlinePlayers.forEach { it.sendActionBar(translate(if (value) "isReady" else "isNotReady", translate(this.name).colorByTeam(this)).let { cmp -> + if (value) { + cmp.green() + } else { + cmp.red() + } + }) } + + if (value && opposite.isReady) { + TNTLeagueGame.checkStart() + } + } + + var damagedBlocks: Int = 0 + set(value) { + field = value + if (value >= targetedBlocks) { + TNTLeagueGame.win(this, TNTLeagueGame.WinReason.DESTROYED) + } + } + + val opposite: TNTLeagueTeam + get() = when (team) { + Team.BLUE -> TNTLeagueGame.redTeam + Team.RED -> TNTLeagueGame.blueTeam + } + + fun join(player: Player): Boolean { + members.add(player) + + with(player) { + teleport(config.spawnLocation) + gameMode = GameMode.ADVENTURE + inventory.clear() + plugin.server.broadcast(translate("joinTeam", name().colorByTeam(this@TNTLeagueTeam), translate(this@TNTLeagueTeam.name).colorByTeam(this@TNTLeagueTeam)).basic()) + } + + if (leader == null) { + leader = player + } + + return true + } + + fun readyItem() = if (isReady) { + ItemStack.of(Material.LIME_DYE).apply { + itemMeta = itemMeta.apply { + displayName(translate("ready").green().translate(leader!!)) + } + } + } else { + ItemStack.of(Material.RED_DYE).apply { + itemMeta = itemMeta.apply { + displayName(translate("notReady").red().translate(leader!!)) + } + } + } + + fun start() = members.forEach { + with(it) { + gameMode = GameMode.SURVIVAL + inventory.addItem(ItemStack.of(Material.DIAMOND_PICKAXE).apply { + itemMeta = itemMeta.apply { + isUnbreakable = true + addEnchant(Enchantment.EFFICIENCY, 1, false) + } + }) + } + } + + fun leave(player: Player) { + if (TNTLeagueGame.state == TNTLeagueGame.GameState.RUNNING) { + TNTLeagueGame.playerLeave(player) + } else { + members.remove(player) + + if (members.isEmpty()) { + plugin.server.onlinePlayers.firstOrNull { it != player && TNTLeagueGame.getTeam(it) == null }?.run { + members.add(this) + } + } + if (leader == player) { + leader = members.firstOrNull() + } + } + } + + fun remove(player: Player) { + leave(player) + with(player) { + teleport(TNTLeagueWorldConfig.lobby) + gameMode = GameMode.SPECTATOR + inventory.clear() + plugin.server.broadcast(translate("quitTeam", name().colorByTeam(this@TNTLeagueTeam), translate(this@TNTLeagueTeam.name).colorByTeam(this@TNTLeagueTeam)).basic()) + } + } + + enum class Team(val color: TextColor) { + BLUE(NamedTextColor.BLUE), + RED(NamedTextColor.RED); + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/inventory/DealerInventory.kt b/TNTLeague/src/de/steamwar/tntleague/inventory/DealerInventory.kt new file mode 100644 index 00000000..04c1c288 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/inventory/DealerInventory.kt @@ -0,0 +1,75 @@ +package de.steamwar.tntleague.inventory + +import de.steamwar.tntleague.config.TNTLeagueConfig +import de.steamwar.tntleague.plugin +import de.steamwar.tntleague.util.* +import net.kyori.adventure.sound.Sound +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.Style +import net.kyori.adventure.text.format.TextDecoration +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.entity.Player +import org.bukkit.inventory.Inventory +import org.bukkit.inventory.ItemStack +import org.bukkit.persistence.PersistentDataType +import java.util.* +import kotlin.math.ceil + +class DealerInventory(player: Player): SWInventoryHolder() { + + init { + items.forEachIndexed { index, item -> + this[index] = item.first to { + val price = item.second.price * if (it.isShiftClick) 5 else 1 + val amount = item.second.amount * if (it.isShiftClick) 5 else 1 + + if (!player.inventory.containsAtLeast(coins, price)) { + player.sendMessage(translate("notEnoughCoins").error()) + player.playSound(Sound.sound(org.bukkit.Sound.ENTITY_VILLAGER_HURT.key, net.kyori.adventure.sound.Sound.Source.MASTER, 1f, 1f)) + return@to + } + + player.inventory.removeItem(coins.asQuantity(price)) + player.inventory.addItem(ItemStack.of(item.first.type, amount)) + } + } + } + + override fun createInventory(): Inventory = plugin.server.createInventory(this, ceil(TNTLeagueConfig.config.prices.size / 9f).toInt() * 9, translate("dealer").reset()) + + companion object { + private val priceKey = NamespacedKey(plugin, "price") + private val amountKey = NamespacedKey(plugin, "amount") + private val coinKey = NamespacedKey(plugin, "coin") + + val coins = ItemStack(Material.RAW_GOLD).apply { + itemMeta = itemMeta.apply { + displayName(Component.text("Coins").bold().gold()) + persistentDataContainer.apply { + set(coinKey, PersistentDataType.BOOLEAN, true) + } + } + } + + val items by lazy { + val prices = TNTLeagueConfig.config.prices + + prices.map { (material, price) -> + ItemStack(material).apply { + itemMeta = itemMeta.apply { + displayName(material.name.lowercase().replace("_", " ") + .replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + .component().gray().appendSpace().append(price.amount.toString().component().yellow())) + amount = price.amount + lore(listOf(price.price.toString().component().yellow().bold().appendSpace().append(Component.text("Coins").yellow()))) + persistentDataContainer.apply { + set(priceKey, PersistentDataType.INTEGER, price.price) + set(amountKey, PersistentDataType.INTEGER, price.amount) + } + } + } to price + } + } + } +} diff --git a/TNTLeague/src/de/steamwar/tntleague/inventory/SWInventoryHolder.kt b/TNTLeague/src/de/steamwar/tntleague/inventory/SWInventoryHolder.kt new file mode 100644 index 00000000..5d89e541 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/inventory/SWInventoryHolder.kt @@ -0,0 +1,41 @@ +package de.steamwar.tntleague.inventory + +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.inventory.InventoryCloseEvent +import org.bukkit.inventory.Inventory +import org.bukkit.inventory.InventoryHolder +import org.bukkit.inventory.ItemStack + +abstract class SWInventoryHolder: InventoryHolder { + + val _inventory: Inventory by lazy { createInventory() } + + private val callbacks = mutableMapOf Unit>() + + override fun getInventory(): Inventory = _inventory + + abstract fun createInventory(): Inventory + + open fun handleInventoryClick(event: InventoryClickEvent) { + callbacks[event.slot]?.invoke(event) + } + + fun addItem(item: ItemStack, slot: Int, callback: (event: InventoryClickEvent) -> Unit) { + _inventory.setItem(slot, item) + addCallback(slot, callback) + } + + fun addCallback(slot: Int, callback: (event: InventoryClickEvent) -> Unit) { + callbacks[slot] = callback + } + + open fun handleClose(event: InventoryCloseEvent) { } + + operator fun set(slot: Int, item: Pair Unit>) { + addItem(item.first, slot, item.second) + } + + operator fun set(slot: Int, item: ItemStack) { + addItem(item, slot) { } + } +} \ No newline at end of file diff --git a/TNTLeague/src/de/steamwar/tntleague/util/Area.kt b/TNTLeague/src/de/steamwar/tntleague/util/Area.kt new file mode 100644 index 00000000..0b27d227 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/util/Area.kt @@ -0,0 +1,46 @@ +package de.steamwar.tntleague.util + +import org.bukkit.Location +import org.bukkit.block.Block + +class Area(loc1: Location, loc2: Location) { + + val min: Location + val max: Location + + init { + require(loc1.world == loc2.world) { "Locations must be in the same world" } + this.min = loc1 min loc2 + this.max = loc1 max loc2 + } + + operator fun contains(loc: Location): Boolean { + return loc.world == min.world && loc.x >= min.x && loc.x <= max.x && loc.y >= min.y && loc.y <= max.y && loc.z >= min.z && loc.z <= max.z + } + + val blocks: Sequence + inline get() = sequence { + for (x in locations) { + yield(x.block) + } + } + + val locations: Sequence + inline get() = sequence { + for (x in min.blockX..max.blockX) { + for (y in min.blockY..max.blockY) { + for (z in min.blockZ..max.blockZ) { + yield(Location(min.world, x.toDouble(), y.toDouble(), z.toDouble())) + } + } + } + } +} + +infix fun Location.max(other: Location): Location { + return Location(world, x.coerceAtLeast(other.x), y.coerceAtLeast(other.y), z.coerceAtLeast(other.z)) +} + +infix fun Location.min(other: Location): Location { + return Location(world, x.coerceAtMost(other.x), y.coerceAtMost(other.y), z.coerceAtMost(other.z)) +} diff --git a/TNTLeague/src/de/steamwar/tntleague/util/Style.kt b/TNTLeague/src/de/steamwar/tntleague/util/Style.kt new file mode 100644 index 00000000..3ef28b9a --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/util/Style.kt @@ -0,0 +1,73 @@ +package de.steamwar.tntleague.util + +import de.steamwar.tntleague.game.TNTLeagueTeam +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.ComponentLike +import net.kyori.adventure.text.TranslatableComponent +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.format.Style +import net.kyori.adventure.text.format.TextDecoration +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer +import net.kyori.adventure.translation.GlobalTranslator +import org.bukkit.entity.Player +import java.util.Locale + +val prefix = Component.text("Steam").yellow() + .append(Component.text("War").darkGray()) + .appendSpace() + +val tntLeaguePrefix = Component.text("TNT").color(NamedTextColor.DARK_RED) + .append(Component.text("League").color(NamedTextColor.GOLD)) + +val tntLeagueChatPrefix: Component = tntLeaguePrefix + .append(Component.text("»").darkGray()) + .appendSpace() + +fun TranslatableComponent.basic(): Component = tntLeagueChatPrefix.append(this.gray()) + +fun TranslatableComponent.error(): Component = tntLeagueChatPrefix.append(this.red()) + +fun TranslatableComponent.success(): Component = tntLeagueChatPrefix.append(this.green()) + +fun String.component(): Component = Component.text(this) + +fun Component.bold(): Component = this.decorate(TextDecoration.BOLD) + +fun String.bold(): Component = this.component().bold() + +fun Component.yellow(): Component = this.color(NamedTextColor.YELLOW) + +fun String.yellow(): Component = this.component().yellow() + +fun Component.red(): Component = this.color(NamedTextColor.RED) + +fun String.red(): Component = this.component().red() + +fun Component.green(): Component = this.color(NamedTextColor.GREEN) + +fun String.green(): Component = this.component().green() + +fun Component.gray(): Component = this.color(NamedTextColor.GRAY) + +fun String.gray(): Component = this.component().gray() + +fun Component.darkGray(): Component = this.color(NamedTextColor.DARK_GRAY) + +fun String.darkGray(): Component = this.component().darkGray() + +fun Component.gold(): Component = this.color(NamedTextColor.GOLD) + +fun translate(key: String, vararg args: ComponentLike): TranslatableComponent = Component.translatable(key, *args).decoration(TextDecoration.ITALIC, false) + +fun Component.reset(): Component = this.style(Style.empty()) + +fun Component.colorByTeam(team: TNTLeagueTeam?) = when (team) { + null -> this.gray() + else -> this.color(team.color) +} + +fun Component.translate(locale: Locale): Component = GlobalTranslator.render(this, locale) + +fun Component.translate(p: Player): Component = this.translate(p.locale()) + +fun Component.toLegacy(): String = LegacyComponentSerializer.legacySection().serialize(this) diff --git a/TNTLeague/src/de/steamwar/tntleague/util/TNTLeagueScoreboard.kt b/TNTLeague/src/de/steamwar/tntleague/util/TNTLeagueScoreboard.kt new file mode 100644 index 00000000..7b8da592 --- /dev/null +++ b/TNTLeague/src/de/steamwar/tntleague/util/TNTLeagueScoreboard.kt @@ -0,0 +1,57 @@ +/* + * 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 . + */ + +package de.steamwar.tntleague.util + +import de.steamwar.scoreboard.ScoreboardCallback +import de.steamwar.tntleague.config.targetedBlocks +import de.steamwar.tntleague.game.TNTLeagueGame +import net.kyori.adventure.text.Component +import org.bukkit.entity.Player +import kotlin.collections.HashMap + +private val scoreboardTitle by lazy { tntLeaguePrefix.toLegacy() } + +data class TNTLeagueScoreboard(val p: Player): ScoreboardCallback { + override fun getData(): HashMap { + val lines = mutableListOf() + + lines.add(Component.space().green()) + + val minutes = TNTLeagueGame.gameTimeRemaining.floorDiv(60) + val seconds = TNTLeagueGame.gameTimeRemaining.rem(60).toString().padStart(2, '0') + lines.add(translate("scoreboardTime", minutes.toString().yellow(), seconds.yellow()).gray()) + + lines.add(Component.space().yellow()) + + with(TNTLeagueGame.blueTeam) { + lines.add(translate("scoreboardTeam", translate(name).colorByTeam(this), (targetedBlocks - damagedBlocks).toString().yellow()).gray()) + } + with(TNTLeagueGame.redTeam) { + lines.add(translate("scoreboardTeam", translate(name).colorByTeam(this), (targetedBlocks - damagedBlocks).toString().yellow()).gray()) + } + + lines.add(Component.space().gray()) + + return lines + .foldIndexed(HashMap()) { index, acc, component -> acc.also { it[component.translate(p).toLegacy()] = index } } + } + + override fun getTitle(): String = scoreboardTitle +} diff --git a/TNTLeague/src/paper-plugin.yml b/TNTLeague/src/paper-plugin.yml new file mode 100644 index 00000000..8687424a --- /dev/null +++ b/TNTLeague/src/paper-plugin.yml @@ -0,0 +1,10 @@ +name: TNTLeague +version: '1.0.0' +main: de.steamwar.tntleague.TNTLeague +load: POSTWORLD +api-version: '1.21' +dependencies: + - name: SpigotCore + required: true + - name: KotlinCore + required: true \ No newline at end of file diff --git a/VelocityCore/src/de/steamwar/messages/BungeeCore.properties b/VelocityCore/src/de/steamwar/messages/BungeeCore.properties index fe118680..6b92436c 100644 --- a/VelocityCore/src/de/steamwar/messages/BungeeCore.properties +++ b/VelocityCore/src/de/steamwar/messages/BungeeCore.properties @@ -130,6 +130,7 @@ USAGE_ALERT=§8/§7alert §8[§emessage§8] USAGE_IGNORE=§8/§7ignore §8[§eplayer§8] #ModListener +CLIENT_DISALLOWED=Attempted use of client {0} MOD_RED_SING=Attempted use of mod {0} MOD_RED_PLUR=Attempted use of mods:\n{0} MOD_YELLOW_SING=§7Deactivate the mod §e{0}§7 to continue playing on §eSteam§8War§7. @@ -292,7 +293,7 @@ FIGHT_UNKNOWN_GAMEMODE=§cUnknown gamemode: {0} FIGHT_UNKNOWN_ARENA=§cThe desired arena does not exist. FIGHT_IN_ARENA=§cYou are already in an arena. FIGHT_BROADCAST=§7Click §ehere§7 to fight §e{0} §7against §e{1}! -FIGHT_BROADCAST_HOVER=§aFight §eagainst §7{1} +FIGHT_BROADCAST_HOVER=§aFight §eagainst §7{0} #CheckCommand CHECK_REMINDER=§7There are §e{0} §7schematics left for review§8! diff --git a/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties b/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties index 2dea174b..fb4067c8 100644 --- a/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties +++ b/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties @@ -115,6 +115,7 @@ USAGE_ALERT=§8/§7alert §8[§eNachricht§8] USAGE_IGNORE=§8/§7ignore §8[§eSpieler§8] #ModListener +CLIENT_DISALLOWED=Versuchte Benutzung des Clients {0} MOD_RED_SING=Versuchte Benutzung des Mods {0} MOD_RED_PLUR=Versuchte Benutzung der Mods:\n{0} MOD_YELLOW_SING=§7Deaktiviere den Mod §e{0}§7, um weiter auf §eSteam§8War §7spielen zu können. @@ -275,7 +276,7 @@ FIGHT_UNKNOWN_GAMEMODE=§cUnbekannter Spielmodus: {0} FIGHT_UNKNOWN_ARENA=§cDie gewünschte Arena gibt es nicht. FIGHT_IN_ARENA=§cDu befindest dich bereits in einer Arena. FIGHT_BROADCAST=§7Klicke §ehier§7, um §e{0} §7gegen §e{1} §7zu §7kämpfen! -FIGHT_BROADCAST_HOVER=§aGegen §7{1} §ekämpfen +FIGHT_BROADCAST_HOVER=§aGegen §7{0} §ekämpfen #CheckCommand CHECK_REMINDER=§7Es sind §e{0} §7Schematics zu prüfen§8! @@ -642,7 +643,6 @@ RANK_PLAYER_FOUND=§eRang §7von §e{0} RANK_HEADER={0} §e{1} {2} RANK_UNPLACED=§7unplatziert RANK_PLACED=§e{0}§8. §7mit §e{1} §7Elo§8. -RANK_EMBLEM=§7Emblem§8: {0} #Fabric Mod Sender MODIFICATION_BAN_MESSAGE=Du hast probiert den FabricModSender zu umgehen / zu modifizieren! diff --git a/VelocityCore/src/de/steamwar/velocitycore/EventStarter.java b/VelocityCore/src/de/steamwar/velocitycore/EventStarter.java index 1be170f3..17d4f537 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/EventStarter.java +++ b/VelocityCore/src/de/steamwar/velocitycore/EventStarter.java @@ -20,6 +20,7 @@ package de.steamwar.velocitycore; import de.steamwar.messages.Chatter; +import de.steamwar.messages.Message; import de.steamwar.persistent.Subserver; import de.steamwar.sql.EventFight; import de.steamwar.sql.Team; @@ -43,7 +44,7 @@ public class EventStarter { public EventStarter() { EventFight.loadAllComingFights(); - VelocityCore.schedule(this::run).delay(10, TimeUnit.SECONDS).schedule(); + VelocityCore.schedule(this::run).repeat(10, TimeUnit.SECONDS).schedule(); } public static Map getEventServer() { @@ -73,7 +74,7 @@ public class EventStarter { } else { command = "/" + spectatePorts.get(next.getSpectatePort()); } - Chatter.broadcast().system("EVENT_FIGHT_BROADCAST", "EVENT_FIGHT_BROADCAST_HOVER", ClickEvent.runCommand(command), blue.getTeamColor(), blue.getTeamName(), red.getTeamColor(), red.getTeamName()); + Chatter.broadcast().system("EVENT_FIGHT_BROADCAST", new Message("EVENT_FIGHT_BROADCAST_HOVER"), ClickEvent.runCommand(command), blue.getTeamColor(), blue.getTeamName(), red.getTeamColor(), red.getTeamName()); } } diff --git a/VelocityCore/src/de/steamwar/velocitycore/commands/BauCommand.java b/VelocityCore/src/de/steamwar/velocitycore/commands/BauCommand.java index 037b4ab6..3e1b5d74 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/commands/BauCommand.java +++ b/VelocityCore/src/de/steamwar/velocitycore/commands/BauCommand.java @@ -126,8 +126,8 @@ public class BauCommand extends SWCommand { }); } - @Register("setbuild") - public void setBuild(Chatter sender, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) { + @Register("setbuilder") + public void setBuilder(Chatter sender, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) { setPerms(sender, user, "setbuild", "BAU_MEMBER_SET_BUILDER", member -> { member.setBuild(true); member.setSupervisor(false); @@ -165,14 +165,17 @@ public class BauCommand extends SWCommand { withMember(owner, user, target -> { target.remove(); + Bauserver bauserver = Bauserver.get(owner.user().getUUID()); Chatter member = Chatter.of(user.getUUID()); - member.system("BAU_DELMEMBER_DELETED_TARGET", owner); member.withPlayer(player -> { - Bauserver bauserver = Bauserver.get(owner.user().getUUID()); if (bauserver != null && bauserver.getRegisteredServer().getPlayersConnected().contains(player)) player.createConnectionRequest(VelocityCore.get().getConfig().lobbyserver()).fireAndForget(); }); + if(bauserver != null) + bauserver.getRegisteredServer().getPlayersConnected().stream().findAny().ifPresent(player -> NetworkSender.send(player, new BaumemberUpdatePacket())); + + member.system("BAU_DELMEMBER_DELETED_TARGET", owner); owner.system("BAU_DELMEMBER_DELETED"); }); } diff --git a/VelocityCore/src/de/steamwar/velocitycore/commands/FightCommand.java b/VelocityCore/src/de/steamwar/velocitycore/commands/FightCommand.java index 1d5283ea..633405e8 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/commands/FightCommand.java +++ b/VelocityCore/src/de/steamwar/velocitycore/commands/FightCommand.java @@ -130,7 +130,7 @@ public class FightCommand extends SWCommand { public void fight(@Validator("arenaPlayer") PlayerChatter sender, @Mapper("nonHistoricArenaMode") @OptionalValue("") @AllowNull ArenaMode arenaMode, @Mapper("arenaMap") @OptionalValue("") @AllowNull String map) { createArena(sender, "/fight ", true, arenaMode, map, false, (p, mode, m) -> new ServerStarter().arena(mode, m).blueLeader(p.getPlayer()).callback( - arena -> Chatter.broadcast().system("FIGHT_BROADCAST", new Message("FIGHT_BROADCAST_HOVER"), ClickEvent.runCommand("/arena " + arena.getServer().getName()), mode.getGameName(), p.getPlayer().getUsername()) + arena -> Chatter.broadcast().system("FIGHT_BROADCAST", new Message("FIGHT_BROADCAST_HOVER", p.getPlayer().getUsername()), ClickEvent.runCommand("/arena " + arena.getServer().getName()), mode.getGameName(), p.getPlayer().getUsername()) ).start() ); } diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/ChannelListener.java b/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/ChannelListener.java index 7665c40f..e1922b48 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/ChannelListener.java +++ b/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/ChannelListener.java @@ -85,7 +85,7 @@ public class ChannelListener extends ListenerAdapter { if(permission != null && !sender.user().perms().contains(permission)) return; - command.execute(sender, args.split(" ")); + command.execute(sender, args.isEmpty() ? new String[0] : args.split(" ")); }); } } diff --git a/VelocityCore/src/de/steamwar/velocitycore/mods/Badlion.java b/VelocityCore/src/de/steamwar/velocitycore/mods/Badlion.java index 74c18eb0..113697ee 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/mods/Badlion.java +++ b/VelocityCore/src/de/steamwar/velocitycore/mods/Badlion.java @@ -22,6 +22,7 @@ package de.steamwar.velocitycore.mods; import com.google.gson.JsonObject; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; +import de.steamwar.messages.Chatter; public class Badlion { // https://github.com/BadlionClient/BadlionClientModAPI @@ -50,6 +51,10 @@ public class Badlion { } public void sendRestrictions(Player player) { + // TODO: Remove when Badlion is fixed, or we fixed the Badlion packet + Chatter sender = Chatter.of(player); + player.disconnect(sender.parse("CLIENT_DISALLOWED", "Badlion")); + player.sendPluginMessage(MinecraftChannelIdentifier.from("badlion:mods"), packet); } } diff --git a/VelocityCore/src/de/steamwar/velocitycore/network/handlers/EloPlayerHandler.java b/VelocityCore/src/de/steamwar/velocitycore/network/handlers/EloPlayerHandler.java index 327adbac..417a0e5e 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/network/handlers/EloPlayerHandler.java +++ b/VelocityCore/src/de/steamwar/velocitycore/network/handlers/EloPlayerHandler.java @@ -20,13 +20,13 @@ package de.steamwar.velocitycore.network.handlers; import com.velocitypowered.api.proxy.Player; -import de.steamwar.velocitycore.ArenaMode; -import de.steamwar.velocitycore.VelocityCore; import de.steamwar.network.packets.PacketHandler; import de.steamwar.network.packets.common.FightEndsPacket; import de.steamwar.sql.SchematicType; import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.UserElo; +import de.steamwar.velocitycore.ArenaMode; +import de.steamwar.velocitycore.VelocityCore; import lombok.RequiredArgsConstructor; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; @@ -52,8 +52,15 @@ public class EloPlayerHandler extends PacketHandler { */ @Handler public void handle(FightEndsPacket fightEndsPacket) { - if (!ArenaMode.getBySchemType(SchematicType.fromDB(fightEndsPacket.getGameMode())).isRanked()) - return; + SchematicType schematicType = SchematicType.fromDB(fightEndsPacket.getGameMode()); + ArenaMode arenaMode; + if (schematicType == null) { + arenaMode = ArenaMode.getByInternal(fightEndsPacket.getGameMode()); + } else { + arenaMode = ArenaMode.getBySchemType(schematicType); + } + if (arenaMode == null) return; + if (!arenaMode.isRanked()) return; if (EloSchemHandler.publicVsPrivate(fightEndsPacket)) return; diff --git a/VelocityCore/src/de/steamwar/velocitycore/network/handlers/EloSchemHandler.java b/VelocityCore/src/de/steamwar/velocitycore/network/handlers/EloSchemHandler.java index 68144738..49da0116 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/network/handlers/EloSchemHandler.java +++ b/VelocityCore/src/de/steamwar/velocitycore/network/handlers/EloSchemHandler.java @@ -19,18 +19,21 @@ package de.steamwar.velocitycore.network.handlers; -import de.steamwar.velocitycore.ArenaMode; import de.steamwar.network.packets.PacketHandler; import de.steamwar.network.packets.common.FightEndsPacket; import de.steamwar.sql.SchemElo; import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SchematicType; +import de.steamwar.velocitycore.ArenaMode; public class EloSchemHandler extends PacketHandler { private static final int K = 20; public static boolean publicVsPrivate(FightEndsPacket packet) { + if (packet.getRedSchem() == -1 && packet.getBlueSchem() == -1) { + return false; + } SchematicNode blueSchem = SchematicNode.getSchematicNode(packet.getBlueSchem()); SchematicNode redSchem = SchematicNode.getSchematicNode(packet.getRedSchem()); return (blueSchem.getOwner() == 0) != (redSchem.getOwner() == 0); @@ -38,9 +41,10 @@ public class EloSchemHandler extends PacketHandler { @Handler public void handle(FightEndsPacket fightEndsPacket) { - if (!ArenaMode.getBySchemType(SchematicType.fromDB(fightEndsPacket.getGameMode())).isRanked()) { - return; - } + SchematicType type = SchematicType.fromDB(fightEndsPacket.getGameMode()); + if (type == null) return; + ArenaMode arenaMode = ArenaMode.getBySchemType(type); + if (!arenaMode.isRanked()) return; if (publicVsPrivate(fightEndsPacket)) return; diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index b457102f..ff9b2587 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -27,4 +27,12 @@ sourceSets { srcDirs("src/") } } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.10") } \ No newline at end of file diff --git a/buildSrc/src/steamwar.kotlin.gradle b/buildSrc/src/steamwar.kotlin.gradle new file mode 100644 index 00000000..03b0a99d --- /dev/null +++ b/buildSrc/src/steamwar.kotlin.gradle @@ -0,0 +1,56 @@ +/* + * 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 "org.jetbrains.kotlin.jvm" +} + +kotlin { + jvmToolchain(21) +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +tasks.compileJava { + options.encoding "UTF-8" +} + +sourceSets { + main { + java { + srcDirs("src/") + } + resources { + srcDirs("src/") + exclude("**/*.java", "**/*.kt") + } + } + test { + java { + srcDirs("testsrc/") + } + resources { + srcDirs("testsrc/") + exclude("**/*.java", "**/*.kt") + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 2fed98a6..ad769fd8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -89,6 +89,7 @@ dependencyResolutionManagement { includeGroup("org.spigotmc") includeGroup("io.papermc.paper") includeGroup("com.velocitypowered") + includeGroup("net.md-5") } } mavenCentral() @@ -113,6 +114,7 @@ dependencyResolutionManagement { library("spigotapi", "org.spigotmc:spigot-api:1.20-R0.1-SNAPSHOT") library("spigotannotations", "org.spigotmc:plugin-annotations:1.2.3-SNAPSHOT") library("paperapi", "io.papermc.paper:paper-api:1.19.2-R0.1-SNAPSHOT") + library("paperapi21", "io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT") library("authlib", "com.mojang:authlib:1.5.25") library("datafixer", "com.mojang:datafixerupper:4.0.26") library("brigadier", "com.mojang:brigadier:1.0.18") @@ -174,7 +176,7 @@ include( "FightSystem:FightSystem_Standalone" ) -// include("KotlinCore") +include("KotlinCore") include("LobbySystem") @@ -214,3 +216,5 @@ include( "VelocityCore", "VelocityCore:Persistent" ) + +include("TNTLeague")