diff --git a/BauSystem_Main/src/BauSystem.properties b/BauSystem_Main/src/BauSystem.properties
index ea4451e7..ba8e3b98 100644
--- a/BauSystem_Main/src/BauSystem.properties
+++ b/BauSystem_Main/src/BauSystem.properties
@@ -171,16 +171,33 @@ LOADER_MESSAGE_CLEAR-HELP = §cDu must im Setup-Modus sein um den Loader zu clea
LOADER_MESSAGE_TNT = §eTNT hinzugefügt {0}
LOADER_MESSAGE_INTERACT = §e{0} hinzugefügt {1}
-LOADER_BUTTON_SWITCH = Hebel
-LOADER_BUTTON_WOOD-Button = Knopf
-LOADER_BUTTON_STONE-Button = Knopf
-LOADER_BUTTON_PRESSURE-PLATE = Druckplatte
-LOADER_BUTTON_WEIGHTED-PRESSURE-PLATE = Druckplatte
-LOADER_BUTTON_TRIPWIRE = Tripwire
-LOADER_BUTTON_NOTEBLOCK = Noteblock
-LOADER_BUTTON_DAYLIGHTSENSOR = Tageslichtsensor
-LOADER_BUTTON_INVALID = Invalider
-
+LOADER_BUTTON_SWITCH=Hebel
+LOADER_BUTTON_WOOD-Button=Knopf
+LOADER_BUTTON_STONE-Button=Knopf
+LOADER_BUTTON_PRESSURE-PLATE=Druckplatte
+LOADER_BUTTON_WEIGHTED-PRESSURE-PLATE=Druckplatte
+LOADER_BUTTON_TRIPWIRE=Tripwire
+LOADER_BUTTON_NOTEBLOCK=Noteblock
+LOADER_BUTTON_DAYLIGHTSENSOR=Tageslichtsensor
+LOADER_BUTTON_INVALID=Invalider
+#Loadtimer
+LOADTIMER_WAITING=§7Platziere ein TNT zum starten...
+LOADTIMER_BOSSBAR=§7Tick: §e{0}§7(§e{1}§7) Zeit: §e{2}s §7Tnt: §e{3} §7Blöcke
+LOADTIMER_ACTIVATED=§7Warte auf Zündung
+LOADTIMER_IGNITION=§7Warte auf Explosion
+LOADTIMER_SUMARY_HEAD=§7---=== (§eLoadtimer-Auswertung§7) ===---
+LOADTIMER_SUMARY_PLAYERTABLE_HEAD=§7Spieler: §eTNT §7(§eTNT/s§7)
+LOADTIMER_SUMARY_PLAYERTABLE_PLAYER=§7{0}: §e{1} §7(§e{2}/s§7)
+LOADTIMER_SUMARY_PLAYERTABLE_ALL=Insgesamt
+LOADTIMER_SUMARY_TIMES_HEAD=§7Zeiten: §eSekunden §7(§eTicks§7)
+LOADTIMER_SUMARY_TIMES_START=§7 || §7Start!
+LOADTIMER_SUMARY_TIMES_ACTIVATION=§7 || Aktivierung: §e{0}s §7(§e{1}t§7)
+LOADTIMER_SUMARY_TIMES_IGNITION=§7 || Zündung: §e{0}s §7(§e{1}t§7)
+LOADTIMER_SUMARY_TIMES_EXPLOSION=§7 || Explosion: §e{0}s §7(§e{1}t§7)
+LOADTIMER_SUMARY_TIMES_LAST=§7\\/
+LOADTIMER_SUMARY_STATS_HEAD=§7Kanonen-Stats§8:
+LOADTIMER_SUMARY_STATS_TNT=§7TNT: §e{0}
+LOADTIMER_SUMARY_STATS_FREQ=§7Belade Frequenz: §e{0}/m§8, §7Schuss Frequenz: §e{1}/m
# Other
-OTHER_ITEMS_TELEPORT_GUI-NAME = Teleportieren
-OTHER_ITEMS_TELEPORT_PLAYER-OFFLINE = §cDer Spieler ist Offline
\ No newline at end of file
+OTHER_ITEMS_TELEPORT_GUI-NAME=Teleportieren
+OTHER_ITEMS_TELEPORT_PLAYER-OFFLINE=§cDer Spieler ist Offline
\ No newline at end of file
diff --git a/BauSystem_Main/src/de/steamwar/bausystem/features/loadtimer/Loadtimer.java b/BauSystem_Main/src/de/steamwar/bausystem/features/loadtimer/Loadtimer.java
new file mode 100644
index 00000000..35353caf
--- /dev/null
+++ b/BauSystem_Main/src/de/steamwar/bausystem/features/loadtimer/Loadtimer.java
@@ -0,0 +1,260 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2021 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.features.loadtimer;
+
+import de.steamwar.bausystem.BauSystem;
+import de.steamwar.bausystem.config.ColorConfig;
+import de.steamwar.bausystem.features.tpslimit.TPSUtils;
+import de.steamwar.bausystem.region.Region;
+import de.steamwar.bausystem.region.utils.RegionExtensionType;
+import de.steamwar.bausystem.region.utils.RegionType;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.boss.BarColor;
+import org.bukkit.boss.BarStyle;
+import org.bukkit.boss.BossBar;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.Action;
+import org.bukkit.event.block.BlockPlaceEvent;
+import org.bukkit.event.entity.EntityExplodeEvent;
+import org.bukkit.event.entity.EntitySpawnEvent;
+import org.bukkit.event.player.PlayerInteractEvent;
+import org.bukkit.scheduler.BukkitTask;
+
+import java.text.DecimalFormat;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class Loadtimer implements Listener {
+
+ private static final Map timers = new HashMap<>();
+ private final Region region;
+ private final Map tntPlaced = new HashMap<>();
+ private final BukkitTask task;
+ private final Map bars = new HashMap();
+ private Stage stage;
+ private boolean finishOnActive = true;
+
+ private long start = -1;
+ private long activate = -1;
+ private long ignite = -1;
+ private long explode = -1;
+
+ public Loadtimer(Region region, boolean finishOnActive) {
+ this.finishOnActive = finishOnActive;
+ this.region = region;
+ this.stage = Stage.WAITING;
+ Bukkit.getPluginManager().registerEvents(this, BauSystem.getInstance());
+ task = Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> {
+ Bukkit.getOnlinePlayers().forEach(player -> {
+ if (!Region.getRegion(player.getLocation()).equals(region) && bars.containsKey(player)) {
+ bars.remove(player).removeAll();
+ }
+ });
+ if (stage == Stage.COUNTING) {
+ long timeSinceStart = TPSUtils.currentTick.get() - start;
+ long timeSinceHalf = timeSinceStart / 2;
+ double timeSec = (timeSinceStart / 20d);
+ String sec = new DecimalFormat("#.#").format(timeSec);
+ AtomicInteger tnt = new AtomicInteger();
+ tntPlaced.forEach((player, integer) -> tnt.addAndGet(integer));
+ Bukkit.getOnlinePlayers().forEach(player -> {
+ if (Region.getRegion(player.getLocation()).equals(region)) {
+ BossBar bar = getOrDefault(player);
+ bar.setTitle(BauSystem.MESSAGE.parse("LOADTIMER_BOSSBAR", player, timeSinceStart, timeSinceHalf, sec, tnt));
+ bar.setProgress((timeSinceStart % 20d) / 20);
+ bar.setColor(BarColor.GREEN);
+ bar.setStyle(BarStyle.SEGMENTED_20);
+ }
+ });
+ } else if (stage == Stage.WAITING) {
+ Bukkit.getOnlinePlayers().forEach(player -> {
+ if (Region.getRegion(player.getLocation()).equals(region)) {
+ BossBar bar = getOrDefault(player);
+ bar.setStyle(BarStyle.SOLID);
+ bar.setTitle(BauSystem.MESSAGE.parse("LOADTIMER_WAITING", player));
+ bar.setColor(BarColor.GREEN);
+ bar.setProgress(1);
+ }
+ });
+ } else if (stage == Stage.ACTIVATED || stage == Stage.IGNITION) {
+ Bukkit.getOnlinePlayers().forEach(player -> {
+ if (Region.getRegion(player.getLocation()).equals(region)) {
+ BossBar bar = getOrDefault(player);
+ bar.setStyle(BarStyle.SOLID);
+ bar.setTitle(BauSystem.MESSAGE.parse(stage == Stage.ACTIVATED ? "LOADTIMER_ACTIVATED" : "LOADTIMER_IGNITION", player));
+ bar.setColor(BarColor.YELLOW);
+ bar.setProgress(1);
+ }
+ });
+ }
+ }, 1, 1);
+ }
+
+ public static boolean hasTimer(Region r) {
+ return timers.containsKey(r);
+ }
+
+ public static Loadtimer getTimer(Region r) {
+ return timers.get(r);
+ }
+
+ public static Loadtimer createLoadtimer(Region r, boolean finishOnActive) {
+ return timers.computeIfAbsent(r, region1 -> new Loadtimer(region1, finishOnActive));
+ }
+
+ private BossBar getOrDefault(Player player) {
+ return bars.computeIfAbsent(player, player1 -> {
+ BossBar bar1 = Bukkit.createBossBar("%PLACEHOLDER%", BarColor.GREEN, BarStyle.SEGMENTED_20);
+ bar1.addPlayer(player1);
+ return bar1;
+ });
+ }
+
+ @EventHandler
+ public void onBlockPlace(BlockPlaceEvent event) {
+ if (Region.getRegion(event.getBlock().getLocation()).equals(region) && event.getBlockPlaced().getType() == Material.TNT) {
+ if (stage == Stage.WAITING) {
+ this.stage = Stage.COUNTING;
+ this.start = TPSUtils.currentTick.get();
+ }
+
+ if (stage == Stage.COUNTING) {
+ tntPlaced.put(event.getPlayer(), tntPlaced.getOrDefault(event.getPlayer(), 0) + 1);
+ }
+ }
+ }
+
+ @EventHandler
+ public void onPlayerInteract(PlayerInteractEvent event) {
+ if (Region.getRegion(event.getPlayer().getLocation()).equals(region)) {
+ if (stage == Stage.COUNTING) {
+ if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
+ Material type = event.getClickedBlock().getType();
+ if (type.name().contains("_BUTTON") || type == Material.LEVER || type.name().contains("_TRAPDOOR") || type == Material.NOTE_BLOCK) {
+ setActivate();
+ }
+ } else if (event.getAction() == Action.PHYSICAL) {
+ setActivate();
+ }
+ }
+ }
+ }
+
+ @EventHandler
+ public void onEntitySpawn(EntitySpawnEvent event) {
+ if (event.getEntityType() == EntityType.PRIMED_TNT && Region.getRegion(event.getLocation()).equals(region) &&
+ (stage == Stage.COUNTING || stage == Stage.ACTIVATED)) {
+ stage = Stage.IGNITION;
+ ignite = TPSUtils.currentTick.get();
+ if (activate == -1)
+ activate = TPSUtils.currentTick.get();
+ if (finishOnActive) {
+ stage = Stage.END;
+ print();
+ delete();
+ }
+ }
+ }
+
+ @EventHandler
+ public void onEntityExplode(EntityExplodeEvent event) {
+ if (event.getEntityType() == EntityType.PRIMED_TNT) {
+ Region r = Region.getRegion(event.getLocation());
+ if (r.equals(region) && r.inRegion(event.getLocation(), RegionType.BUILD, RegionExtensionType.EXTENSION) && stage == Stage.IGNITION) {
+ stage = Stage.END;
+ explode = TPSUtils.currentTick.get();
+ print();
+ delete();
+ }
+ }
+ }
+
+ private void setActivate() {
+ activate = TPSUtils.currentTick.get();
+ stage = Stage.ACTIVATED;
+ if (finishOnActive) {
+ print();
+ delete();
+ }
+ }
+
+ public void print() {
+ long loadTime = activate - start;
+ int allTnt = 0;
+ for (Map.Entry e : tntPlaced.entrySet()) {
+ allTnt += e.getValue();
+ }
+
+ long ignTime = ignite - activate;
+ long explTime = explode - ignTime - activate;
+ if (explTime < 0)
+ explTime = loadTime;
+
+ int finalAllTnt = allTnt;
+ long finalExplTime = explTime;
+ Bukkit.getOnlinePlayers().forEach(player -> {
+ if (Region.getRegion(player.getLocation()).equals(region)) {
+ BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_HEAD", player);
+ BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_PLAYERTABLE_HEAD", player);
+ for (Map.Entry e : tntPlaced.entrySet()) {
+ BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_PLAYERTABLE_PLAYER", player, e.getKey().getName(), e.getValue(), new DecimalFormat("#.#").format(e.getValue() / (loadTime / 20D)));
+ }
+ if (tntPlaced.size() > 1) {
+ BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_PLAYERTABLE_PLAYER", player, BauSystem.MESSAGE.parse("LOADTIMER_SUMARY_PLAYERTABLE_ALL", player), finalAllTnt, new DecimalFormat("#.#").format(finalAllTnt / (loadTime / 20D)));
+ }
+ player.sendMessage(ColorConfig.BASE + "");
+ BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_TIMES_HEAD", player);
+ BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_TIMES_START", player);
+ BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_TIMES_ACTIVATION", player, new DecimalFormat("#.#").format((loadTime / 20D)), loadTime);
+ if (!finishOnActive) {
+ BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_TIMES_IGNITION", player, new DecimalFormat("#.#").format((ignTime / 20D)), ignTime);
+ BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_TIMES_EXPLOSION", player, new DecimalFormat("#.#").format((finalExplTime / 20D)), finalExplTime);
+ }
+ BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_TIMES_LAST", player);
+ player.sendMessage(ColorConfig.BASE + "");
+ BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_STATS_HEAD", player);
+ BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_STATS_TNT", player, finalAllTnt);
+ BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_STATS_FREQ", player, 60D / (loadTime / 20D), 60D / ((finalExplTime + Math.max(ignTime, 0) + loadTime) / 20D));
+ }
+ });
+ }
+
+ public void delete() {
+ HandlerList.unregisterAll(this);
+ timers.remove(region, this);
+ bars.forEach((player, bossBar) -> bossBar.removeAll());
+ bars.clear();
+ task.cancel();
+ }
+
+ private enum Stage {
+ WAITING,
+ COUNTING,
+ ACTIVATED,
+ IGNITION,
+ END
+ }
+}
diff --git a/BauSystem_Main/src/de/steamwar/bausystem/features/loadtimer/LoadtimerCommand.java b/BauSystem_Main/src/de/steamwar/bausystem/features/loadtimer/LoadtimerCommand.java
new file mode 100644
index 00000000..4c3d1b9a
--- /dev/null
+++ b/BauSystem_Main/src/de/steamwar/bausystem/features/loadtimer/LoadtimerCommand.java
@@ -0,0 +1,70 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2021 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.features.loadtimer;
+
+import de.steamwar.bausystem.config.ColorConfig;
+import de.steamwar.bausystem.linkage.LinkageType;
+import de.steamwar.bausystem.linkage.Linked;
+import de.steamwar.bausystem.region.Region;
+import de.steamwar.command.SWCommand;
+import org.bukkit.entity.Player;
+
+@Linked(LinkageType.COMMAND)
+public class LoadtimerCommand extends SWCommand {
+ protected LoadtimerCommand() {
+ super("loadtimer", "lt", "stopuhr");
+ }
+
+ @Register(help = true)
+ public void genericHelp(Player p, String... args) {
+ p.sendMessage(ColorConfig.BASE + "---===( " + ColorConfig.HIGHLIGHT + "Loadtimer" + ColorConfig.BASE + ") ===---");
+ p.sendMessage(ColorConfig.BASE + "Messe dich und deine Freunde beim Beladen einer Kanone und bekomme informationen über die Kanone");
+ p.sendMessage(ColorConfig.OTHER + "/" + ColorConfig.HIGHLIGHT + "loadtimer start" + ColorConfig.OTHER + "-" + ColorConfig.BASE + " Startet den einfachen Loadtimer");
+ p.sendMessage(ColorConfig.OTHER + "/" + ColorConfig.BASE + "loadtimer start " + ColorConfig.OTHER + "[" + ColorConfig.BASE + "full/half" + ColorConfig.OTHER + "] - " + ColorConfig.BASE + "Starte den Timer in einem bestimmten Modus");
+ p.sendMessage(ColorConfig.BASE + "Loadtimer Modis: Full -> Misst vom ersten TNT bis zur Treib-Explosion, kann somit besser die Schuss Frequent berechnen. Half -> Misst nur bis zur Aktivierung");
+ p.sendMessage(ColorConfig.OTHER + "/" + ColorConfig.HIGHLIGHT + "loadtimer stop" + ColorConfig.OTHER + "-" + ColorConfig.BASE + " Stoppe den Aktuellen Loadtimer");
+ }
+
+ @Register("start")
+ public void start(Player p) {
+ start(p, TimerMode.HALF);
+ }
+
+ @Register("start")
+ public void start(Player p, TimerMode mode) {
+ Region r = Region.getRegion(p.getLocation());
+ if (r.isGlobal()) return;
+ if (!Loadtimer.hasTimer(r))
+ Loadtimer.createLoadtimer(r, mode == TimerMode.HALF);
+ }
+
+ @Register("stop")
+ public void stop(Player p) {
+ Region r = Region.getRegion(p.getLocation());
+ if (r.isGlobal()) return;
+ if (Loadtimer.hasTimer(r))
+ Loadtimer.getTimer(r).delete();
+ }
+
+ public enum TimerMode {
+ FULL,
+ HALF
+ }
+}
diff --git a/BauSystem_Main/src/de/steamwar/bausystem/features/loadtimer/LoadtimerGuiItem.java b/BauSystem_Main/src/de/steamwar/bausystem/features/loadtimer/LoadtimerGuiItem.java
new file mode 100644
index 00000000..bd1403f1
--- /dev/null
+++ b/BauSystem_Main/src/de/steamwar/bausystem/features/loadtimer/LoadtimerGuiItem.java
@@ -0,0 +1,77 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2021 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.features.loadtimer;
+
+import de.steamwar.bausystem.Permission;
+import de.steamwar.bausystem.config.ColorConfig;
+import de.steamwar.bausystem.linkage.LinkageType;
+import de.steamwar.bausystem.linkage.Linked;
+import de.steamwar.bausystem.linkage.specific.BauGuiItem;
+import de.steamwar.bausystem.region.Region;
+import de.steamwar.inventory.SWInventory;
+import de.steamwar.inventory.SWItem;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+
+@Linked(LinkageType.BAU_GUI_ITEM)
+public class LoadtimerGuiItem extends BauGuiItem {
+
+ public LoadtimerGuiItem() {
+ super(27);
+ }
+
+ @Override
+ public Permission permission() {
+ return Permission.MEMBER;
+ }
+
+ @Override
+ public ItemStack getItem(Player player) {
+ Region r = Region.getRegion(player.getLocation());
+ if (r.isGlobal())
+ return new SWItem(Material.BOWL, ColorConfig.HIGHLIGHT + "Loadtimer gibt es nicht in der Global Region!").getItemStack();
+ if (Loadtimer.hasTimer(r)) {
+ return new SWItem(Material.BOW, ColorConfig.HIGHLIGHT + "Loadtimer stoppen").getItemStack();
+ } else {
+ return new SWItem(Material.BOW, ColorConfig.HIGHLIGHT + "Loadtimer starten").getItemStack();
+ }
+ }
+
+ @Override
+ public boolean click(ClickType click, Player p) {
+ Region r = Region.getRegion(p.getLocation());
+ if (r.isGlobal()) return false;
+ if (Loadtimer.hasTimer(r)) {
+ p.performCommand("lt stop");
+ } else {
+ SWInventory inv = new SWInventory(p, 9, "Loadtimer Modus");
+ inv.setItem(1, Material.OAK_PLANKS, ColorConfig.HIGHLIGHT + "Full", clickType -> {
+ p.performCommand("lt start full");
+ });
+ inv.setItem(7, Material.OAK_SLAB, ColorConfig.HIGHLIGHT + "Half", clickType -> {
+ p.performCommand("lt start half");
+ });
+ inv.open();
+ }
+ return true;
+ }
+}