Forráskód Böngészése

Merge branch 'master' into feature/fawe-enhancement

RedstoneFuture 1 éve
szülő
commit
677c3fdf4b
29 módosított fájl, 685 hozzáadás és 320 törlés
  1. 1 1
      missilewars-plugin/pom.xml
  2. 2 2
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/MissileWars.java
  3. 15 2
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/commands/MWCommands.java
  4. 25 0
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/configuration/Config.java
  5. 17 4
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/configuration/Messages.java
  6. 1 1
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/configuration/arena/Arena.java
  7. 86 49
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/Game.java
  8. 5 0
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/GameJoinManager.java
  9. 6 4
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/GameManager.java
  10. 212 0
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/GameResultManager.java
  11. 2 80
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/Team.java
  12. 4 2
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/misc/TeamSpawnProtection.java
  13. 0 1
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/schematics/objects/Missile.java
  14. 7 11
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/signs/MWSign.java
  15. 7 6
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/signs/SignRepository.java
  16. 2 1
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/timer/EndTimer.java
  17. 39 10
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/timer/GameTimer.java
  18. 4 3
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/timer/LobbyTimer.java
  19. 4 21
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/listener/ShieldListener.java
  20. 80 34
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/listener/SignListener.java
  21. 2 3
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/listener/game/GameBoundListener.java
  22. 31 34
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/listener/game/GameListener.java
  23. 79 0
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/ColorUtil.java
  24. 0 48
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/MathUtil.java
  25. 34 0
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/PlayerUtil.java
  26. 1 1
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/serialization/LocationTypeAdapter.java
  27. 0 1
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/stats/PlayerGuiFactory.java
  28. 0 1
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/stats/PreFetcher.java
  29. 19 0
      missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/version/MaterialHelper.java

+ 1 - 1
missilewars-plugin/pom.xml

@@ -26,7 +26,7 @@
         <version>1.0</version>
     </parent>
 
-    <version>4.7.0</version>
+    <version>4.7.1</version>
 
     <modelVersion>4.0.0</modelVersion>
 

+ 2 - 2
missilewars-plugin/src/main/java/de/butzlabben/missilewars/MissileWars.java

@@ -150,7 +150,7 @@ public class MissileWars extends JavaPlugin {
     }
 
     /**
-     * This method registers all events of the missilewars event listener.
+     * This method registers all events of the MissileWars event listener.
      */
     private void registerEvents() {
         playerListener = new PlayerListener();
@@ -161,7 +161,7 @@ public class MissileWars extends JavaPlugin {
     }
 
     /**
-     * This method loads the command manager and registers the missilewars commands.
+     * This method loads the command manager and registers the MissileWars commands.
      */
     private void registerCommands() {
         Logger.BOOT.log("Registering commands");

+ 15 - 2
missilewars-plugin/src/main/java/de/butzlabben/missilewars/commands/MWCommands.java

@@ -83,19 +83,32 @@ public class MWCommands extends BaseCommand {
 
         sender.sendMessage(Messages.getPrefix() + "Current games:");
         
+        sender.sendMessage(" ");
+        
         for (Game game : GameManager.getInstance().getGames().values()) {
             TeamManager teamManager = game.getTeamManager();
             
-            sender.sendMessage("§e " + game.getLobby().getName() + "§7 -- Name: »" + game.getLobby().getDisplayName() + "§7« | Status: " + game.getState());
+            sender.sendMessage("§e " + game.getLobby().getName() 
+                    + "§7 -- Name: »" + game.getLobby().getDisplayName() 
+                    + "§7« | Status: " + game.getState());
+            
             sender.sendMessage("§8 - §f" + "Load with startup: §7" + game.getLobby().isAutoLoad());
-            sender.sendMessage("§8 - §f" + "Current Arena: §7" + game.getArena().getName() + "§7 -- Name: »" + game.getArena().getDisplayName() + "§7«");
+            
+            sender.sendMessage("§8 - §f" + "Current Arena: §7" + ((game.getArena() != null) ? game.getArena().getName() : "?") 
+                    + "§7 -- Name: »" + ((game.getArena() != null) ? game.getArena().getDisplayName() : "?") + "§7«");
+            
             sender.sendMessage("§8 - §f" + "Total players: §7" + game.getTotalGameUserAmount() + "x");
+            
             sender.sendMessage("§8 - §f" + "Team 1: §7" + teamManager.getTeam1().getColor() + teamManager.getTeam1().getName()
                     + " §7with " + teamManager.getTeam1().getMembers().size() + " players");
+            
             sender.sendMessage("§8 - §f" + "Team 2: §7" + teamManager.getTeam2().getColor() + teamManager.getTeam2().getName()
                     + " §7with " + teamManager.getTeam2().getMembers().size() + " players");
+            
             sender.sendMessage("§8 - §f" + "Spectators: §7" + teamManager.getTeamSpec().getColor() + teamManager.getTeamSpec().getName()
                     + " §7with " + teamManager.getTeamSpec().getMembers().size() + " players");
+            
+            sender.sendMessage(" ");
         }
 
     }

+ 25 - 0
missilewars-plugin/src/main/java/de/butzlabben/missilewars/configuration/Config.java

@@ -92,6 +92,8 @@ public class Config {
         cfg.addDefault("replace.material", JUKEBOX.name());
         cfg.addDefault("replace.after_ticks", 2);
         cfg.addDefault("replace.radius", 15);
+        
+        cfg.addDefault("game_result.firework", true);
 
         cfg.addDefault("motd.enable", true);
         cfg.addDefault("motd.lobby", "&6•&e● MissileWars &7| &eLobby");
@@ -131,6 +133,17 @@ public class Config {
                 add("");
                 add("%team2% &7» %team2_color%%team2_amount%");
             }});
+            
+        }
+        
+        cfg.addDefault("actionbar_msg.spectator.delay", 6);
+        
+        if (isNewConfig) {
+            
+            cfg.set("actionbar_msg.spectator.messages", new ArrayList<String>() {{
+                add("&eChoose your team to join: &7/mw teammenu");
+            }});
+            
         }
 
         String gameJoinMenu = "menus.hotbar_menu.game_join_menu";
@@ -315,6 +328,10 @@ public class Config {
     public static int getReplaceRadius() {
         return cfg.getInt("replace.radius");
     }
+    
+    public static boolean isGameResultFirework() {
+        return cfg.getBoolean("game_result.firework");
+    }
 
     public static String motdEnded() {
         return Messages.getConvertedMsg(cfg.getString("motd.ended"));
@@ -404,6 +421,14 @@ public class Config {
         return Messages.getConvertedMsgList(cfg.getStringList("sidebar.entries"));
     }
     
+    public static int getActionbarForSpecDelay() {
+        return cfg.getInt("actionbar_msg.spectator.delay");
+    }
+    
+    public static String[] getActionbarForSpecEntries() {
+        return Messages.getConvertedMsgArray(cfg.getStringList("actionbar_msg.spectator.messages"));
+    }
+    
     public static Map<Integer, Map<Integer, MenuItem>> getGameJoinMenuItems() {
         // Config keys inspired by DeluxeMenus https://wiki.helpch.at/helpchat-plugins/deluxemenus/options-and-configurations/item
         

+ 17 - 4
missilewars-plugin/src/main/java/de/butzlabben/missilewars/configuration/Messages.java

@@ -192,9 +192,10 @@ public class Messages {
         VOTE_ARENA_ALREADY_SELECTED("vote.arena_already_selected", "&cYou have already voted for this arena."),
 
         SIGNEDIT_SIGN_CREATED("signedit.sign_created", "&7Sign was successfully created and connected."),
-        SIGNEDIT_SIGN_REMOVED("signedit.sign_removed", "&7You have successfully removed this missilewars sign."),
-        SIGNEDIT_LOBBY_NOT_FOUND("signedit.lobby_not_found", "&cCould not find lobby %input%."),
-        SIGNEDIT_SIGN_REMOVE_DESC("signedit.sign_remove_desc", "&cYou have to be sneaking in order to remove this sign."),
+        SIGNEDIT_SIGN_REMOVED("signedit.sign_removed", "&7You have successfully removed this MissileWars sign."),
+        SIGNEDIT_EMPTY_LOBBY("signedit.empty_lobby", "&cPlease specify the target lobby name in the second line."),
+        SIGNEDIT_LOBBY_NOT_FOUND("signedit.lobby_not_found", "&cCould not find lobby \"%input%\"."),
+        SIGNEDIT_SIGN_REMOVE_DESC("signedit.sign_remove_desc", "&cThis shield is locked by the MissileWars plugin. Sneak while you are destroying the shield to remove it."),
 
         GAME_STATE_NO_GAME("game_state.no_game", "&cNo Game."),
         GAME_STATE_LOBBY("game_state.lobby", "&aLobby"),
@@ -241,7 +242,7 @@ public class Messages {
      * to the final text message.
      * 
      * @param messageList the target message list
-     * @return (String) the converted message list
+     * @return (List of Strings) the converted message list
      */
     public static List<String> getConvertedMsgList(List<String> messageList) {
         List<String> convertedMsgList = new ArrayList<>();
@@ -251,6 +252,18 @@ public class Messages {
         return convertedMsgList;
     }
     
+    /**
+     * This method returns the desired message array. 
+     * Legacy color-codes with '&' will be converted 
+     * to the final text message.
+     * 
+     * @param messageList the target message list
+     * @return (String[]) the converted message array
+     */
+    public static String[] getConvertedMsgArray(List<String> messageList) {
+        return getConvertedMsgList(messageList).toArray(String[]::new);
+    }
+    
     public static String getPapiMessage(String message, Player player) {
         return ChatColor.translateAlternateColorCodes('&', PlaceholderAPI.setPlaceholders(player, message));
     }

+ 1 - 1
missilewars-plugin/src/main/java/de/butzlabben/missilewars/configuration/arena/Arena.java

@@ -50,7 +50,7 @@ public class Arena implements Cloneable {
     @SerializedName("arrow") private ArrowConfiguration arrowConfiguration = new ArrowConfiguration();
     @SerializedName("save_statistics") private boolean saveStatistics = true;
     @SerializedName("fall_protection") private FallProtectionConfiguration fallProtection = new FallProtectionConfiguration();
-    @SerializedName("money") private MoneyConfiguration money = new MoneyConfiguration();
+    @SerializedName("game_result.money") private MoneyConfiguration money = new MoneyConfiguration();
     @SerializedName("equipment_interval") private EquipmentIntervalConfiguration interval = new EquipmentIntervalConfiguration();
     @SerializedName("missile") private MissileConfiguration missileConfiguration = new MissileConfiguration();
     @SerializedName("shield") private ShieldConfiguration shieldConfiguration = new ShieldConfiguration();

+ 86 - 49
missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/Game.java

@@ -48,6 +48,7 @@ import de.butzlabben.missilewars.listener.game.GameBoundListener;
 import de.butzlabben.missilewars.listener.game.GameListener;
 import de.butzlabben.missilewars.listener.game.LobbyListener;
 import de.butzlabben.missilewars.player.MWPlayer;
+import de.butzlabben.missilewars.util.PlayerUtil;
 import de.butzlabben.missilewars.util.geometry.GameArea;
 import de.butzlabben.missilewars.util.geometry.Geometry;
 import de.butzlabben.missilewars.util.serialization.Serializer;
@@ -81,6 +82,7 @@ public class Game {
     private final MapVoting mapVoting = new MapVoting(this);
     private final Lobby lobby;
     private final Map<UUID, BukkitTask> playerTasks = new HashMap<>();
+    private final List<Location> portalBlocks = new ArrayList<>();
     private GameState state = GameState.LOBBY;
     private TeamManager teamManager;
     private boolean ready = false;
@@ -486,6 +488,7 @@ public class Game {
         }
         
         shield.paste(ball);
+        player.playSound(player.getLocation(), Sound.ENTITY_ENDER_DRAGON_FLAP, 1, 1);
     }
 
     /**
@@ -531,8 +534,43 @@ public class Game {
         }
 
         createInnerGameArea();
+        
+        savePortalPositions();
     }
 
+    /**
+     * This method goes through all blocks within the arena and saves the portal 
+     * block positions so that they can be checked regularly during the game.
+     */
+    private void savePortalPositions() {
+        
+        long startTime = System.currentTimeMillis();
+        
+        int minX = gameArea.getMinX();
+        int minY = gameArea.getMinY();
+        int minZ = gameArea.getMinZ();
+        
+        int maxX = gameArea.getMaxX();
+        int maxY = gameArea.getMaxY();
+        int maxZ = gameArea.getMaxZ();
+        
+        for (int x = minX; x <= maxX; x++) {
+            for (int y = minY; y <= maxY; y++) {
+                for (int z = minZ; z <= maxZ; z++) {
+                    
+                    if (gameWorld.getWorld().getBlockAt(x, y, z).getType() == Material.NETHER_PORTAL) 
+                        portalBlocks.add(new Location(gameWorld.getWorld(), x, y, z));
+                }
+            }
+        }
+        
+        long endTime = System.currentTimeMillis();
+        
+        Logger.DEBUG.log("[Portal Position-Cache] Time reached for Portal-Counting: " + (endTime - startTime) + " ms.");
+        Logger.DEBUG.log("[Portal Position-Cache] Founded " + portalBlocks.size() + " Portal blocks.");
+    
+    }
+    
     private void createInnerGameArea() {
 
         // Depending on the rotation of the (major) Game-Area, the spawn points 
@@ -582,49 +620,11 @@ public class Game {
      * customized message.
      */
     public void sendGameResult() {
-
-        for (Player player : gameWorld.getWorld().getPlayers()) {
-            MWPlayer mwPlayer = getPlayer(player);
-            Team team = mwPlayer.getTeam();
-            
-            if (team.getTeamType() == TeamType.PLAYER) {
-                team.sendMoney(mwPlayer);
-                team.sendGameResultTitle(mwPlayer);
-                team.sendGameResultSound(mwPlayer);
-            } else {
-                sendNeutralGameResultTitle(player);
-            }
-            
-        }
-    }
-
-    /**
-     * This method sends the players the title / subtitle of the
-     * game result there are not in a team (= spectator).
-     */
-    public void sendNeutralGameResultTitle(Player player) {
-        String title;
-        String subTitle;
-
-        if (teamManager.getTeam1().getGameResult() == GameResult.WIN) {
-            title = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_TITLE_WON)
-                    .replace("%team%", teamManager.getTeam1().getName());
-            subTitle = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_SUBTITLE_WON);
-
-        } else if (teamManager.getTeam2().getGameResult() == GameResult.WIN) {
-            title = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_TITLE_WON)
-                    .replace("%team%", teamManager.getTeam2().getName());
-            subTitle = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_SUBTITLE_WON);
-
-        } else {
-            title = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_TITLE_DRAW);
-            subTitle = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_SUBTITLE_DRAW);
-
-        }
-
-        player.sendTitle(title, subTitle);
+        GameResultManager resultManager = new GameResultManager(this);
+        resultManager.executeResult();
+        
     }
-
+    
     /**
      * This method updates the MissileWars signs and the scoreboard.
      */
@@ -721,23 +721,60 @@ public class Game {
     }
 
     public void teleportToFallbackSpawn(Player player) {
-        teleportSafely(player, Config.getFallbackSpawn());
+        PlayerUtil.teleportSafely(player, Config.getFallbackSpawn());
     }
 
     public void teleportToLobbySpawn(Player player) {
-        teleportSafely(player, lobby.getSpawnPoint());
+        PlayerUtil.teleportSafely(player, lobby.getSpawnPoint());
     }
 
     public void teleportToArenaSpectatorSpawn(Player player) {
-        teleportSafely(player, arena.getSpectatorSpawn());
+        PlayerUtil.teleportSafely(player, arena.getSpectatorSpawn());
     }
 
     public void teleportToAfterGameSpawn(Player player) {
-        teleportSafely(player, lobby.getAfterGameSpawn());
+        PlayerUtil.teleportSafely(player, lobby.getAfterGameSpawn());
     }
     
-    public static void teleportSafely(Player player, Location targetLocation) {
-        player.setVelocity(new Vector(0, 0, 0));
-        player.teleport(targetLocation);
+    /**
+     * This method checks all previously saved portal positions to see whether the 
+     * portals are still intact. If not, the game-end is initiated.
+     * 
+     * This could be a more performant version than using the high-frequency 
+     * "BlockPhysicsEvent" event-listener.
+     */
+    public void checkPortals() {
+        
+        for (Location location : portalBlocks) {
+            
+            if (location.getBlock().getType() == Material.NETHER_PORTAL) continue;
+            
+            runWinnerCheck(location);
+            return;
+        }
+    }
+
+    /**
+     * This method determines the winning team based on the position of the destroyed 
+     * portal and brings the game to the game-phase "END".
+     * 
+     * @param location (Location) the (first) detected portal-block location
+     */
+    private void runWinnerCheck(Location location) {
+        
+        Team team1 = getTeamManager().getTeam1();
+        Team team2 = getTeamManager().getTeam2();
+        
+        if (Geometry.isCloser(location, team1.getSpawn(), team2.getSpawn())) {
+            team1.setGameResult(GameResult.LOSE);
+            team2.setGameResult(GameResult.WIN);
+        } else {
+            team1.setGameResult(GameResult.WIN);
+            team2.setGameResult(GameResult.LOSE);
+        }
+
+        sendGameResult();
+        stopGame();
+    
     }
 }

+ 5 - 0
missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/GameJoinManager.java

@@ -177,6 +177,11 @@ public class GameJoinManager {
         }
         
         mwPlayer.getTeam().teleportToTeamSpawn(player);
+        if (isGameJoin) {
+            mwPlayer.getPlayer().playSound(mwPlayer.getPlayer().getLocation(), Sound.ITEM_TRIDENT_THUNDER, 0.6f, 1.4f);
+        } else {
+            mwPlayer.getPlayer().playSound(mwPlayer.getPlayer().getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 100, 3);
+        }
         
         if (mwPlayer.getTeam().getTeamType() == TeamType.PLAYER) {
             // normal team-player join:

+ 6 - 4
missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/GameManager.java

@@ -31,7 +31,9 @@ import org.bukkit.Location;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 @Getter
@@ -48,10 +50,10 @@ public class GameManager {
     }
 
     public void restartAll() {
-        var iterator = games.values().iterator();
-        //noinspection WhileLoopReplaceableByForEach
-        while (iterator.hasNext()) {
-            restartGame(iterator.next().getLobby(), false);
+        List<Game> gamesListCache = new ArrayList<>(games.values());
+        
+        for (Game game : gamesListCache) {
+            restartGame(game.getLobby(), false);
         }
     }
 

+ 212 - 0
missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/GameResultManager.java

@@ -0,0 +1,212 @@
+package de.butzlabben.missilewars.game;
+
+import de.butzlabben.missilewars.MissileWars;
+import de.butzlabben.missilewars.configuration.Config;
+import de.butzlabben.missilewars.configuration.Messages;
+import de.butzlabben.missilewars.game.enums.GameResult;
+import de.butzlabben.missilewars.game.enums.TeamType;
+import de.butzlabben.missilewars.player.MWPlayer;
+import de.butzlabben.missilewars.util.ColorUtil;
+import de.butzlabben.missilewars.util.MoneyUtil;
+import de.butzlabben.missilewars.util.version.ColorConverter;
+import org.bukkit.Bukkit;
+import org.bukkit.Color;
+import org.bukkit.FireworkEffect;
+import org.bukkit.Sound;
+import org.bukkit.entity.Firework;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.meta.FireworkMeta;
+
+import java.util.Random;
+
+public class GameResultManager {
+    
+    private final Game game;
+    private final TeamManager teamManager;
+    
+    public GameResultManager(Game game) {
+        this.game = game;
+        this.teamManager = game.getTeamManager();
+    }
+    
+    public void executeResult() {
+        for (Player player : game.getGameWorld().getWorld().getPlayers()) {
+            MWPlayer mwPlayer = game.getPlayer(player);
+            Team team = mwPlayer.getTeam();
+            
+            if (team.getTeamType() == TeamType.PLAYER) {
+                sendMoney(mwPlayer);
+                sendGameResultTitle(mwPlayer);
+                sendGameResultSound(mwPlayer);
+            } else {
+                sendNeutralGameResultTitle(player);
+            }
+        }
+        
+        if (!Config.isGameResultFirework()) return;
+        
+        Bukkit.getScheduler().runTaskLater(MissileWars.getInstance(), () -> spawnSetOfFireworks(15), 10);
+        Bukkit.getScheduler().runTaskLater(MissileWars.getInstance(), () -> spawnSetOfFireworks(20), 40);
+        Bukkit.getScheduler().runTaskLater(MissileWars.getInstance(), () -> spawnSetOfFireworks(20), 70);
+        Bukkit.getScheduler().runTaskLater(MissileWars.getInstance(), () -> spawnSetOfFireworks(15), 100);
+        
+    }
+    
+    /**
+     * This method sends all team members the money for playing the game
+     * with a specific amount for win and lose.
+     */
+    public void sendMoney(MWPlayer mwPlayer) {
+        int money;
+
+        switch (mwPlayer.getTeam().getGameResult()) {
+            case WIN:
+                money = game.getArena().getMoney().getWin();
+                break;
+            case LOSE:
+                money = game.getArena().getMoney().getLoss();
+                break;
+            case DRAW:
+                money = game.getArena().getMoney().getDraw();
+                break;
+            default:
+                money = 0;
+                break;
+        }
+
+        MoneyUtil.giveMoney(mwPlayer.getUuid(), money);
+    }
+
+    /**
+     * This method sends all team members the title / subtitle of the
+     * game result.
+     */
+    public void sendGameResultTitle(MWPlayer mwPlayer) {
+        String title;
+        String subTitle;
+
+        switch (mwPlayer.getTeam().getGameResult()) {
+            case WIN:
+                title = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_TITLE_WINNER);
+                subTitle = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_SUBTITLE_WINNER);
+                break;
+            case LOSE:
+                title = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_TITLE_LOSER);
+                subTitle = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_SUBTITLE_LOSER);
+                break;
+            case DRAW:
+                title = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_TITLE_DRAW);
+                subTitle = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_SUBTITLE_DRAW);
+                break;
+            default:
+                title = null;
+                subTitle = null;
+                break;
+        }
+
+        mwPlayer.getPlayer().sendTitle(title, subTitle, 10, 140, 20);
+    }
+
+    /**
+     * This method sends all team members the end-sound of the
+     * game result.
+     */
+    public void sendGameResultSound(MWPlayer mwPlayer) {
+
+        Player player = mwPlayer.getPlayer();
+
+        switch (mwPlayer.getTeam().getGameResult()) {
+            case WIN:
+                player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 100, 3);
+                break;
+            case LOSE:
+            case DRAW:
+                player.playSound(player.getLocation(), Sound.ENTITY_WITHER_DEATH, 100, 0);
+                break;
+            default:
+                break;
+        }
+    }
+    
+    /**
+     * This method sends the players the title / subtitle of the
+     * game result there are not in a team (= spectator).
+     */
+    private void sendNeutralGameResultTitle(Player player) {
+        String title;
+        String subTitle;
+
+        if (teamManager.getTeam1().getGameResult() == GameResult.WIN) {
+            title = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_TITLE_WON)
+                    .replace("%team%", teamManager.getTeam1().getName());
+            subTitle = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_SUBTITLE_WON);
+
+        } else if (teamManager.getTeam2().getGameResult() == GameResult.WIN) {
+            title = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_TITLE_WON)
+                    .replace("%team%", teamManager.getTeam2().getName());
+            subTitle = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_SUBTITLE_WON);
+
+        } else {
+            title = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_TITLE_DRAW);
+            subTitle = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_SUBTITLE_DRAW);
+
+        }
+
+        player.sendTitle(title, subTitle, 10, 140, 20);
+    }
+    
+    private void spawnSetOfFireworks(int amount) {
+        for (int i = 1; i <= amount; i++) {
+            spawnFirework();
+        }
+    }
+    
+    private void spawnFirework() {
+        Color winnerTeamColor;
+        
+        if (teamManager.getTeam1().getGameResult() == GameResult.WIN) {
+            winnerTeamColor = ColorConverter.getColorFromCode(teamManager.getTeam1().getColorCode());
+        
+        } else if (teamManager.getTeam2().getGameResult() == GameResult.WIN) {
+            winnerTeamColor = ColorConverter.getColorFromCode(teamManager.getTeam2().getColorCode());
+        
+        } else {
+            return;
+            
+        }
+        
+        Firework firework = game.getGameWorld().getWorld().spawn(game.getArena().getSpectatorSpawn(), Firework.class);
+        FireworkMeta fireworkMeta = firework.getFireworkMeta();
+        
+        fireworkMeta.clearEffects();
+
+        Random random = new Random();
+        
+        // Create the effect:
+        // https://minecraft.tools/en/firework.php
+        
+        FireworkEffect.Builder effectBuilder1 = FireworkEffect.builder()
+                .flicker(true)
+                .trail(true)
+                .with(FireworkEffect.Type.BALL)
+                .withColor(ColorUtil.darkenColor(winnerTeamColor, 0.1))
+                .withFade(ColorUtil.lightenColor(winnerTeamColor, 0.6));
+        
+        FireworkEffect.Builder effectBuilder2 = FireworkEffect.builder()
+                .flicker(false)
+                .trail(false)
+                .with(FireworkEffect.Type.BURST)
+                .withColor(ColorUtil.darkenColor(winnerTeamColor, 0.5))
+                .withFade(ColorUtil.lightenColor(winnerTeamColor, 0.2));
+        
+        // Add the effect. (Multiple effects can be added.)
+        fireworkMeta.addEffects(effectBuilder1.build(), effectBuilder2.build());
+        fireworkMeta.setPower(random.nextInt(1, 7));
+        firework.setFireworkMeta(fireworkMeta);
+        
+        // Flight behavior:
+        firework.setShotAtAngle(false);
+        
+    }
+    
+}

+ 2 - 80
missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/Team.java

@@ -20,13 +20,12 @@ package de.butzlabben.missilewars.game;
 
 import de.butzlabben.missilewars.Logger;
 import de.butzlabben.missilewars.configuration.Config;
-import de.butzlabben.missilewars.configuration.Messages;
 import de.butzlabben.missilewars.game.enums.GameResult;
 import de.butzlabben.missilewars.game.enums.TeamType;
 import de.butzlabben.missilewars.game.misc.TeamSpawnProtection;
 import de.butzlabben.missilewars.menus.MenuItem;
 import de.butzlabben.missilewars.player.MWPlayer;
-import de.butzlabben.missilewars.util.MoneyUtil;
+import de.butzlabben.missilewars.util.PlayerUtil;
 import de.butzlabben.missilewars.util.version.ColorConverter;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
@@ -35,7 +34,6 @@ import lombok.ToString;
 import org.bukkit.Color;
 import org.bukkit.Location;
 import org.bukkit.Material;
-import org.bukkit.Sound;
 import org.bukkit.entity.Player;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.inventory.meta.LeatherArmorMeta;
@@ -173,86 +171,10 @@ public class Team {
     public boolean isMember(MWPlayer mwPlayer) {
         return members.contains(mwPlayer);
     }
-
-    /**
-     * This method sends all team members the money for playing the game
-     * with a specific amount for win and lose.
-     */
-    public void sendMoney(MWPlayer mwPlayer) {
-        int money;
-
-        switch (gameResult) {
-            case WIN:
-                money = game.getArena().getMoney().getWin();
-                break;
-            case LOSE:
-                money = game.getArena().getMoney().getLoss();
-                break;
-            case DRAW:
-                money = game.getArena().getMoney().getDraw();
-                break;
-            default:
-                money = 0;
-                break;
-        }
-
-        MoneyUtil.giveMoney(mwPlayer.getUuid(), money);
-    }
-
-    /**
-     * This method sends all team members the title / subtitle of the
-     * game result.
-     */
-    public void sendGameResultTitle(MWPlayer mwPlayer) {
-        String title;
-        String subTitle;
-
-        switch (gameResult) {
-            case WIN:
-                title = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_TITLE_WINNER);
-                subTitle = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_SUBTITLE_WINNER);
-                break;
-            case LOSE:
-                title = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_TITLE_LOSER);
-                subTitle = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_SUBTITLE_LOSER);
-                break;
-            case DRAW:
-                title = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_TITLE_DRAW);
-                subTitle = Messages.getMessage(false, Messages.MessageEnum.GAME_RESULT_SUBTITLE_DRAW);
-                break;
-            default:
-                title = null;
-                subTitle = null;
-                break;
-        }
-
-        mwPlayer.getPlayer().sendTitle(title, subTitle);
-    }
-
-    /**
-     * This method sends all team members the end-sound of the
-     * game result.
-     */
-    public void sendGameResultSound(MWPlayer mwPlayer) {
-
-        Player player = mwPlayer.getPlayer();
-
-        switch (gameResult) {
-            case WIN:
-                player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 100, 3);
-                break;
-            case LOSE:
-            case DRAW:
-                player.playSound(player.getLocation(), Sound.ENTITY_WITHER_DEATH, 100, 0);
-                break;
-            default:
-                break;
-        }
-    }
     
     public void teleportToTeamSpawn(Player player) {
         TeamSpawnProtection.regenerateSpawn(this);
-        Game.teleportSafely(player, spawn);
+        PlayerUtil.teleportSafely(player, spawn);
     }
     
 }

+ 4 - 2
missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/misc/TeamSpawnProtection.java

@@ -1,6 +1,7 @@
 package de.butzlabben.missilewars.game.misc;
 
 import de.butzlabben.missilewars.game.Team;
+import de.butzlabben.missilewars.game.enums.TeamType;
 import org.bukkit.Location;
 import org.bukkit.Material;
 import org.bukkit.World;
@@ -8,12 +9,13 @@ import org.bukkit.World;
 public class TeamSpawnProtection {
 
     /**
-     * This method regenerates the team spawn by resetting the ground and 
+     * This method regenerates the player-team spawn by resetting the ground and 
      * the area and replacing all previous blocks at this point.
      * 
-     * @param targetTeam (Team) the target team
+     * @param targetTeam (Team) the target player-team
      */
     public static void regenerateSpawn(Team targetTeam) {
+        if (targetTeam.getTeamType() == TeamType.SPECTATOR) return;
 
         Location teamSpawn = targetTeam.getSpawn();
         

+ 0 - 1
missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/schematics/objects/Missile.java

@@ -104,7 +104,6 @@ public class Missile extends SchematicObject {
 
     public static Material getSpawnEgg(EntityType type) {
         if (type == EntityType.MUSHROOM_COW) {
-            //noinspection SpellCheckingInspection
             return Material.valueOf("MOOSHROOM_SPAWN_EGG");
 
         }

+ 7 - 11
missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/signs/MWSign.java

@@ -23,8 +23,7 @@ import de.butzlabben.missilewars.MissileWars;
 import de.butzlabben.missilewars.configuration.Messages;
 import de.butzlabben.missilewars.game.Game;
 import de.butzlabben.missilewars.game.GameManager;
-import java.util.ArrayList;
-import java.util.List;
+import de.butzlabben.missilewars.util.version.MaterialHelper;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -32,8 +31,9 @@ import org.bukkit.Bukkit;
 import org.bukkit.Location;
 import org.bukkit.block.Block;
 import org.bukkit.block.Sign;
-import org.bukkit.block.data.BlockData;
-import org.bukkit.block.data.type.WallSign;
+
+import java.util.ArrayList;
+import java.util.List;
 
 @Data
 @AllArgsConstructor
@@ -46,7 +46,7 @@ public class MWSign {
     public boolean isValid() {
         boolean worldExists = location.getWorld() != null;
         boolean lobbyValid = GameManager.getInstance().getGames().containsKey(lobby);
-        boolean blockIsSign = isSign(location.getBlock().getBlockData());
+        boolean blockIsSign = MaterialHelper.isSignMaterial(location.getBlock().getType());
 
         return worldExists && lobbyValid && blockIsSign;
     }
@@ -71,9 +71,9 @@ public class MWSign {
         Bukkit.getScheduler().runTask(MissileWars.getInstance(), () -> editSign(getLocation(), lines));
     }
 
-    public void editSign(Location location, List<String> lines) {
+    private void editSign(Location location, List<String> lines) {
         Block block = location.getBlock();
-        if (!(MWSign.isSign(block.getBlockData()))) {
+        if (!(MaterialHelper.isSignMaterial(block.getType()))) {
             Logger.WARN.log("Configured sign at: " + location + " is not a standing or wall sign");
             return;
         }
@@ -109,8 +109,4 @@ public class MWSign {
                 .replace("%max_players%", Integer.toString(maxPlayers))
                 .replace("%players%", Integer.toString(players));
     }
-    
-    public static boolean isSign(BlockData blockData) {
-        return ((blockData instanceof Sign) || (blockData instanceof WallSign));
-    }
 }

+ 7 - 6
missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/signs/SignRepository.java

@@ -78,7 +78,7 @@ public class SignRepository {
                  JsonReader reader = new JsonReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
                 return gson.fromJson(reader, SignRepository.class);
             } catch (IOException e) {
-                Logger.WARN.log("Could not load missilewars signs: Error: " + e.getMessage());
+                Logger.WARN.log("Could not load MissileWars signs: Error: " + e.getMessage());
             }
 
         }
@@ -89,10 +89,11 @@ public class SignRepository {
         return repository;
     }
 
-
-    public Optional<MWSign> getSign(Location location) {
-        return MissileWars.getInstance().getSignRepository().getSigns()
+    public MWSign getSign(Location location) {
+        Optional<MWSign> optional = MissileWars.getInstance().getSignRepository().getSigns()
                 .stream().filter(sign -> sign.isLocation(location)).findAny();
+
+        return optional.orElse(null);
     }
 
     public void saveData() {
@@ -103,11 +104,11 @@ public class SignRepository {
             writer.setIndent("  ");
             gson.toJson(this, SignRepository.class, writer);
         } catch (Exception e) {
-            Logger.WARN.log("Could not save missilewars signs: Error: " + e.getMessage());
+            Logger.WARN.log("Could not save MissileWars signs: Error: " + e.getMessage());
         }
     }
 
     public List<MWSign> getSigns(Game game) {
-        return signs.stream().filter(s -> s.getLobby().equals(game.getArena().getName())).collect(Collectors.toList());
+        return signs.stream().filter(s -> s.getLobby().equals(game.getLobby().getName())).collect(Collectors.toList());
     }
 }

+ 2 - 1
missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/timer/EndTimer.java

@@ -37,7 +37,8 @@ public class EndTimer extends Timer {
 
         switch (seconds) {
             case 15:
-                broadcast(Messages.getMessage(true, Messages.MessageEnum.ENDGAME_TIMER_GAME_STARTS_NEW_IN).replace("%seconds%", Integer.toString(seconds)));
+                broadcast(Messages.getMessage(true, Messages.MessageEnum.ENDGAME_TIMER_GAME_STARTS_NEW_IN)
+                        .replace("%seconds%", Integer.toString(seconds)));
                 break;
             case 0:
                 getGame().reset();

+ 39 - 10
missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/timer/GameTimer.java

@@ -18,8 +18,11 @@
 
 package de.butzlabben.missilewars.game.timer;
 
+import de.butzlabben.missilewars.configuration.Config;
 import de.butzlabben.missilewars.configuration.Messages;
 import de.butzlabben.missilewars.game.Game;
+import de.butzlabben.missilewars.game.enums.TeamType;
+import de.butzlabben.missilewars.util.PlayerUtil;
 import org.bukkit.GameMode;
 import org.bukkit.entity.Player;
 
@@ -28,7 +31,9 @@ import org.bukkit.entity.Player;
  * @since 06.01.2018
  */
 public class GameTimer extends Timer {
-
+    
+    int actionbarMsgCounter = 0;
+    
     public GameTimer(Game game) {
         super(game);
         seconds = game.getArena().getGameDuration() * 60;
@@ -47,7 +52,8 @@ public class GameTimer extends Timer {
             case 600:
             case 300:
             case 180:
-                broadcast(Messages.getMessage(true, Messages.MessageEnum.GAME_TIMER_GAME_ENDS_IN_MINUTES).replace("%minutes%", Integer.toString(seconds / 60)));
+                broadcast(Messages.getMessage(true, Messages.MessageEnum.GAME_TIMER_GAME_ENDS_IN_MINUTES)
+                        .replace("%minutes%", Integer.toString(seconds / 60)));
                 break;
             case 60:
             case 30:
@@ -57,7 +63,8 @@ public class GameTimer extends Timer {
             case 3:
             case 2:
             case 1:
-                broadcast(Messages.getMessage(true, Messages.MessageEnum.GAME_TIMER_GAME_ENDS_IN_SECONDS).replace("%seconds%", Integer.toString(seconds)));
+                broadcast(Messages.getMessage(true, Messages.MessageEnum.GAME_TIMER_GAME_ENDS_IN_SECONDS)
+                        .replace("%seconds%", Integer.toString(seconds)));
                 break;
             case 0:
                 game.sendGameResult();
@@ -67,22 +74,44 @@ public class GameTimer extends Timer {
                 break;
         }
 
-        if (seconds % 10 == 0) {
+        if (seconds % 5 == 0) {
             game.getScoreboardManager().updateScoreboard();
+            
+            game.getPlayers().values().forEach(mwPlayer -> {
+                Player player = mwPlayer.getPlayer();
+                
+                if (mwPlayer.getTeam().getTeamType() == TeamType.PLAYER) {
+                    
+                    if (mwPlayer.getPlayer().getGameMode() != GameMode.SURVIVAL) return;
+                    
+                    if (game.isInGameArea(player.getLocation())) return;
+                    
+                    player.sendMessage(Messages.getMessage(true, Messages.MessageEnum.ARENA_LEAVED));
+                    mwPlayer.getTeam().teleportToTeamSpawn(player);
+                    
+                }
+            
+            });
         }
         
-        if (seconds % 4 == 0) {
+        if ((Config.getActionbarForSpecEntries().length > 0) && (seconds % Config.getActionbarForSpecDelay() == 0)) {
             game.getPlayers().values().forEach(mwPlayer -> {
-                if (mwPlayer.getPlayer().getGameMode() != GameMode.SURVIVAL) return;
-                
                 Player player = mwPlayer.getPlayer();
-                if (game.isInGameArea(player.getLocation())) return;
                 
-                player.sendMessage(Messages.getMessage(true, Messages.MessageEnum.ARENA_LEAVED));
-                mwPlayer.getTeam().teleportToTeamSpawn(player);
+                if (mwPlayer.getTeam().getTeamType() == TeamType.PLAYER) return;
+                PlayerUtil.sendActionbarMsg(player, Config.getActionbarForSpecEntries()[actionbarMsgCounter]);
             });
+            
+            // Array-Iteration:
+            if (actionbarMsgCounter >= Config.getActionbarForSpecEntries().length - 1) {
+                actionbarMsgCounter = 0;
+            } else {
+                actionbarMsgCounter++;
+            }
         }
 
+        game.checkPortals();
+
         seconds--;
     }
 }

+ 4 - 3
missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/timer/LobbyTimer.java

@@ -73,13 +73,15 @@ public class LobbyTimer extends Timer implements Runnable {
             case 3:
             case 2:
             case 1:
-                broadcast(Messages.getMessage(true, Messages.MessageEnum.LOBBY_TIMER_GAME_STARTS_IN).replace("%seconds%", Integer.toString(seconds)));
+                broadcast(Messages.getMessage(true, Messages.MessageEnum.LOBBY_TIMER_GAME_STARTS_IN)
+                        .replace("%seconds%", Integer.toString(seconds)));
                 playPling();
                 break;
             case 10:
                 if (getGame().getLobby().getMapChooseProcedure() == MapChooseProcedure.MAPVOTING)
                     getGame().getMapVoting().setVotedArena();
-                broadcast(Messages.getMessage(true, Messages.MessageEnum.LOBBY_TIMER_GAME_STARTS_IN).replace("%seconds%", Integer.toString(seconds)));
+                broadcast(Messages.getMessage(true, Messages.MessageEnum.LOBBY_TIMER_GAME_STARTS_IN)
+                        .replace("%seconds%", Integer.toString(seconds)));
                 playPling();
                 break;
             case 0:
@@ -109,7 +111,6 @@ public class LobbyTimer extends Timer implements Runnable {
      */
     public void executeGameStart() {
         broadcast(Messages.getMessage(true, Messages.MessageEnum.GAME_GAME_STARTS));
-        playPling();
         getGame().startGame();
     }
     

+ 4 - 21
missilewars-plugin/src/main/java/de/butzlabben/missilewars/listener/ShieldListener.java

@@ -18,18 +18,14 @@
 
 package de.butzlabben.missilewars.listener;
 
-import de.butzlabben.missilewars.MissileWars;
 import de.butzlabben.missilewars.game.Game;
 import lombok.RequiredArgsConstructor;
-import org.bukkit.Bukkit;
-import org.bukkit.Sound;
 import org.bukkit.entity.Player;
 import org.bukkit.entity.Snowball;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.HandlerList;
 import org.bukkit.event.Listener;
 import org.bukkit.event.entity.ProjectileHitEvent;
-import org.bukkit.event.entity.ProjectileLaunchEvent;
 
 /**
  * @author Butzlabben
@@ -40,27 +36,14 @@ public class ShieldListener implements Listener {
 
     private final Player player;
     private final Game game;
-    private Snowball ball;
-
-    public void onThrow(ProjectileLaunchEvent event) {
-        ball = (Snowball) event.getEntity();
-        Bukkit.getPluginManager().registerEvents(this, MissileWars.getInstance());
-
-        Bukkit.getScheduler().runTaskLater(MissileWars.getInstance(), () -> {
-            if (!ball.isDead()) {
-                game.spawnShield(player, ball);
-                player.playSound(player.getLocation(), Sound.ENTITY_ENDER_DRAGON_FLAP, 1, 1);
-            }
-            HandlerList.unregisterAll(this);
-        }, game.getArena().getShieldConfiguration().getFlyTime());
-    }
-
+    private final Snowball snowball;
+    
     @EventHandler
     public void onHit(ProjectileHitEvent event) {
-        if (!event.getEntity().equals(ball)) return;
+        if (!event.getEntity().equals(snowball)) return;
 
         HandlerList.unregisterAll(this);
-        game.spawnShield(player, ball);
+        game.spawnShield(player, snowball);
     }
     
 }

+ 80 - 34
missilewars-plugin/src/main/java/de/butzlabben/missilewars/listener/SignListener.java

@@ -24,85 +24,109 @@ import de.butzlabben.missilewars.game.Game;
 import de.butzlabben.missilewars.game.GameManager;
 import de.butzlabben.missilewars.game.signs.MWSign;
 import de.butzlabben.missilewars.game.signs.SignRepository;
+import de.butzlabben.missilewars.util.version.MaterialHelper;
+import org.bukkit.ChatColor;
 import org.bukkit.block.Block;
 import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;
 import org.bukkit.event.block.Action;
 import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.event.block.BlockDropItemEvent;
 import org.bukkit.event.block.SignChangeEvent;
 import org.bukkit.event.player.PlayerInteractEvent;
 
-import java.util.Optional;
-
 public class SignListener implements Listener {
 
-    private static final String KEY_SIGN_HEADLINE = "[missilewars]";
-
     @EventHandler
     public void onSignClick(PlayerInteractEvent event) {
         if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
 
         Block block = event.getClickedBlock();
-        if (!(MWSign.isSign(block.getBlockData()))) return;
-
-        SignRepository repository = MissileWars.getInstance().getSignRepository();
-        Optional<MWSign> optional = repository.getSign(block.getLocation());
-        if (optional.isEmpty()) return;
-
-        MWSign sign = optional.get();
+        if (block == null) return;
+        if (!(MaterialHelper.isSignMaterial(block.getType()))) return;
+        
+        if (event.getPlayer().isSneaking()) return;
+        
+        MWSign sign = getSignRepository().getSign(block.getLocation());
+        if (sign == null) return;
+        
         Game game = GameManager.getInstance().getGame(sign.getLobby());
         if (game == null) return;
-
+        
+        // Cancel the event so that the Vanilla sign-edit GUI is not opened before the teleport.
+        event.setCancelled(true);
+        
         game.teleportToLobbySpawn(event.getPlayer());
     }
 
     @EventHandler
     public void onSignChange(SignChangeEvent event) {
         Block block = event.getBlock();
-        if (!(MWSign.isSign(block.getBlockData()))) return;
+        if (!(MaterialHelper.isSignMaterial(block.getType()))) return;
 
         Player player = event.getPlayer();
         if (!hasManageSignPermission(player)) return;
-
-        String headLine = event.getLine(0).toLowerCase();
-        if (!headLine.equals(KEY_SIGN_HEADLINE)) return;
-
-        String lobbyName = event.getLine(1);
+        
+        // Check Prefix (line 1):
+        String headLine = event.getLine(0);
+        if (headLine == null) return;
+        if (!isPluginKeyword(headLine)) return;
+
+        // Check Lobby name (line 2):
+        
+        /*
+        * If a sign only contains one color-code at the beginning, this is retained. In difference, 
+        * if it contains several color-codes, the colors are removed completely.
+        * 
+        * For the sake of completeness, the color is generally removed here so that the string search 
+        * with the Lobby name always works correctly.
+         */
+        String lobbyName = ChatColor.stripColor(event.getLine(1));
+        if ((lobbyName == null) || (lobbyName.isBlank())) {
+            player.sendMessage(Messages.getMessage(true, Messages.MessageEnum.SIGNEDIT_EMPTY_LOBBY));
+            event.setCancelled(true);
+            return;
+        }
+        
         Game game = GameManager.getInstance().getGame(lobbyName);
-
         if (game != null) {
-            MWSign sign = new MWSign(event.getBlock().getLocation(), lobbyName);
+            
+            // Removing old sign entry if exists:
+            MWSign sign = getSignRepository().getSign(block.getLocation());
+            if (sign != null) getSignRepository().getSigns().remove(sign);
+            
+            // Updating sign content:
+            sign = new MWSign(event.getBlock().getLocation(), lobbyName);
             sign.update();
-
-            SignRepository signRepository = MissileWars.getInstance().getSignRepository();
-            signRepository.getSigns().add(sign);
-            signRepository.saveData();
+            
+            // (Re-)Saving sign in MissileWars in '/data/signs.json':
+            getSignRepository().getSigns().add(sign);
+            getSignRepository().saveData();
 
             player.sendMessage(Messages.getMessage(true, Messages.MessageEnum.SIGNEDIT_SIGN_CREATED));
+            
         } else {
             player.sendMessage(Messages.getMessage(true, Messages.MessageEnum.SIGNEDIT_LOBBY_NOT_FOUND).replace("%input%", lobbyName));
             event.setCancelled(true);
+            
         }
     }
 
     @EventHandler
     public void onSignBreak(BlockBreakEvent event) {
         Block block = event.getBlock();
-        if (!(MWSign.isSign(block.getBlockData()))) return;
+        if (!(MaterialHelper.isSignMaterial(block.getType()))) return;
 
         Player player = event.getPlayer();
         if (!hasManageSignPermission(player)) return;
-
-        SignRepository repository = MissileWars.getInstance().getSignRepository();
-        Optional<MWSign> optional = repository.getSign(block.getLocation());
-        if (optional.isEmpty()) return;
+        
+        MWSign sign = getSignRepository().getSign(block.getLocation());
+        if (sign == null) return;
 
         if (player.isSneaking()) {
-            MWSign sign = optional.get();
-
-            repository.getSigns().remove(sign);
-            repository.saveData();
+            getSignRepository().getSigns().remove(sign);
+            getSignRepository().saveData();
 
             player.sendMessage(Messages.getMessage(true, Messages.MessageEnum.SIGNEDIT_SIGN_REMOVED));
         } else {
@@ -110,8 +134,30 @@ public class SignListener implements Listener {
             event.setCancelled(true);
         }
     }
-
+    
+    @EventHandler
+    public void onSignDrop(BlockDropItemEvent event) {
+        getSignRepository().getSigns().removeIf(mwSign -> !mwSign.isValid());
+        getSignRepository().saveData();
+    }
+    
     private boolean hasManageSignPermission(Player player) {
         return player.hasPermission("mw.sign.manage");
     }
+
+    /**
+     * This method checks whether the input string corresponds to the 
+     * MissileWars plugin keyword 'missilewars' or 'mw' (case-insensitive) 
+     * in square brackets.
+     * 
+     * @param input (String) the target string
+     * @return 'true', if it equals one of the plugin keywords
+     */
+    private boolean isPluginKeyword(String input) {
+        return ((input.equalsIgnoreCase("[missilewars]")) || (input.equalsIgnoreCase("[mw]")));
+    }
+    
+    private SignRepository getSignRepository() {
+        return MissileWars.getInstance().getSignRepository();
+    }
 }

+ 2 - 3
missilewars-plugin/src/main/java/de/butzlabben/missilewars/listener/game/GameBoundListener.java

@@ -21,15 +21,14 @@ package de.butzlabben.missilewars.listener.game;
 import de.butzlabben.missilewars.MissileWars;
 import de.butzlabben.missilewars.game.Game;
 import de.butzlabben.missilewars.player.MWPlayer;
-import org.bukkit.Bukkit;
-import org.bukkit.Location;
+import org.bukkit.*;
 import org.bukkit.entity.Player;
 import org.bukkit.event.Listener;
 
 public abstract class GameBoundListener implements Listener {
 
     private final Game game;
-
+    
     protected GameBoundListener(Game game) {
         this.game = game;
     }

+ 31 - 34
missilewars-plugin/src/main/java/de/butzlabben/missilewars/listener/game/GameListener.java

@@ -19,13 +19,13 @@
 package de.butzlabben.missilewars.listener.game;
 
 import de.butzlabben.missilewars.Logger;
+import de.butzlabben.missilewars.MissileWars;
 import de.butzlabben.missilewars.configuration.Messages;
 import de.butzlabben.missilewars.configuration.arena.FallProtectionConfiguration;
 import de.butzlabben.missilewars.event.PlayerArenaJoinEvent;
 import de.butzlabben.missilewars.event.PlayerArenaLeaveEvent;
 import de.butzlabben.missilewars.game.Game;
 import de.butzlabben.missilewars.game.Team;
-import de.butzlabben.missilewars.game.enums.GameResult;
 import de.butzlabben.missilewars.game.enums.JoinIngameBehavior;
 import de.butzlabben.missilewars.game.enums.RejoinIngameBehavior;
 import de.butzlabben.missilewars.game.enums.TeamType;
@@ -35,18 +35,16 @@ import de.butzlabben.missilewars.game.schematics.objects.Missile;
 import de.butzlabben.missilewars.listener.ShieldListener;
 import de.butzlabben.missilewars.menus.inventory.TeamSelectionMenu;
 import de.butzlabben.missilewars.player.MWPlayer;
-import de.butzlabben.missilewars.util.geometry.Geometry;
-import org.bukkit.GameMode;
-import org.bukkit.Location;
-import org.bukkit.Material;
+import de.butzlabben.missilewars.util.version.MaterialHelper;
+import org.bukkit.*;
 import org.bukkit.entity.EntityType;
 import org.bukkit.entity.Player;
 import org.bukkit.entity.Projectile;
 import org.bukkit.entity.Snowball;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.EventPriority;
+import org.bukkit.event.HandlerList;
 import org.bukkit.event.block.Action;
-import org.bukkit.event.block.BlockPhysicsEvent;
 import org.bukkit.event.entity.*;
 import org.bukkit.event.inventory.InventoryClickEvent;
 import org.bukkit.event.inventory.InventoryOpenEvent;
@@ -77,37 +75,27 @@ public class GameListener extends GameBoundListener {
         event.blockList().removeIf(b -> b.getType() == Material.NETHER_PORTAL);
     }
 
-    @EventHandler
-    public void onBlockPhysics(BlockPhysicsEvent event) {
-        if (!isInGameWorld(event.getBlock().getLocation())) return;
-
-        if (event.getChangedType() != Material.NETHER_PORTAL) return;
-
-        Location location = event.getBlock().getLocation();
-
-        Team team1 = getGame().getTeamManager().getTeam1();
-        Team team2 = getGame().getTeamManager().getTeam2();
-
-        if (Geometry.isCloser(location, team1.getSpawn(), team2.getSpawn())) {
-            team1.setGameResult(GameResult.LOSE);
-            team2.setGameResult(GameResult.WIN);
-        } else {
-            team1.setGameResult(GameResult.WIN);
-            team2.setGameResult(GameResult.LOSE);
-        }
-
-        getGame().sendGameResult();
-        getGame().stopGame();
-    }
-
     @EventHandler
     public void onInteract(PlayerInteractEvent event) {
         if (!isInGameWorld(event.getPlayer().getLocation())) return;
-
+        
+        Player player = event.getPlayer();
+        if (player.getGameMode() == GameMode.CREATIVE) return;
+        
+        // Interaction Cancelling for some objects:
+        if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
+            if (MaterialHelper.isSignMaterial(event.getClickedBlock().getType())) {
+                event.setCancelled(true);
+                Logger.DEBUG.log("Cancelling of interaction with '#ALL_SIGNS' (Gamemode: " + player.getGameMode().name() + ").");
+                return;
+            }
+        }
+        
+        // Game-Item handling:
+        
         if (event.getItem() == null) return;
         if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
-
-        Player player = event.getPlayer();
+        
         ItemStack itemStack = event.getItem();
 
         // missile spawn with using of a missile spawn egg
@@ -141,8 +129,17 @@ public class GameListener extends GameBoundListener {
         if (!(snowball.getShooter() instanceof Player)) return;
 
         Player shooter = (Player) snowball.getShooter();
-        ShieldListener shieldListener = new ShieldListener(shooter, getGame());
-        shieldListener.onThrow(event);
+        ShieldListener shieldListener = new ShieldListener(shooter, getGame(), snowball);
+        Bukkit.getPluginManager().registerEvents(shieldListener, MissileWars.getInstance());
+
+        Bukkit.getScheduler().runTaskLater(MissileWars.getInstance(), () -> {
+            HandlerList.unregisterAll(shieldListener);
+            
+            // Is the snowball-entity dead because of an invalid 'fly_time' of the shield-configuration 
+            // or a projectile hit before.
+            if (!snowball.isDead()) getGame().spawnShield(shooter, snowball);
+            
+        }, getGame().getArena().getShieldConfiguration().getFlyTime());
     }
 
     @EventHandler

+ 79 - 0
missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/ColorUtil.java

@@ -0,0 +1,79 @@
+package de.butzlabben.missilewars.util;
+
+import org.bukkit.Color;
+
+public class ColorUtil {
+    
+    /**
+     * This method lightens a given org.bukkit.Color by a specified fraction.
+     *
+     * @param color the original color to be lightened
+     * @param fraction the fraction by which to lighten the color (e.g., 0.3 for 30%)
+     * @return a new Color object representing the lightened color
+     */
+    public static Color lightenColor(Color color, double fraction) {
+        int red = color.getRed();
+        int green = color.getGreen();
+        int blue = color.getBlue();
+
+        // Calculate the new lighter values for each color component
+        red = lightenColorComponent(red, fraction);
+        green = lightenColorComponent(green, fraction);
+        blue = lightenColorComponent(blue, fraction);
+
+        // Create and return the new lightened color
+        return Color.fromRGB(red, green, blue);
+    }
+
+    /**
+     * This method darkens a given org.bukkit.Color by a specified fraction.
+     *
+     * @param color the original color to be darkened
+     * @param fraction the fraction by which to darken the color (e.g., 0.3 for 30%)
+     * @return a new Color object representing the darkened color
+     */
+    public static Color darkenColor(Color color, double fraction) {
+        int red = color.getRed();
+        int green = color.getGreen();
+        int blue = color.getBlue();
+
+        // Calculate the new darker values for each color component
+        red = darkenColorComponent(red, fraction);
+        green = darkenColorComponent(green, fraction);
+        blue = darkenColorComponent(blue, fraction);
+
+        // Create and return the new darkened color
+        return Color.fromRGB(red, green, blue);
+    }
+
+    /**
+     * This method lightens a single color component (R/G/B) by a specified percentage.
+     *
+     * @param colorComponent the original value of the color component (0-255)
+     * @param fraction the fraction by which to lighten the color component (e.g., 0.3 for 30%)
+     * @return the new lightened value of the color component, ensuring it stays within the valid range (0-255)
+     */
+    private static int lightenColorComponent(int colorComponent, double fraction) {
+        // Increase the color component by the given fraction towards 255
+        int newValue = (int) (colorComponent + (255 - colorComponent) * fraction);
+
+        // Ensure the new value is within the valid range (0-255)
+        return Math.min(255, Math.max(0, newValue));
+    }
+
+    /**
+     * This method darkens a single color component (R/G/B) by a specified percentage.
+     *
+     * @param colorComponent the original value of the color component (0-255)
+     * @param fraction the fraction by which to darken the color component (e.g., 0.3 for 30%)
+     * @return the new darkened value of the color component, ensuring it stays within the valid range (0-255)
+     */
+    private static int darkenColorComponent(int colorComponent, double fraction) {
+        // Decrease the color component by the given fraction towards 0
+        int newValue = (int) (colorComponent - (colorComponent * fraction));
+
+        // Ensure the new value is within the valid range (0-255)
+        return Math.min(255, Math.max(0, newValue));
+    }
+    
+}

+ 0 - 48
missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/MathUtil.java

@@ -1,48 +0,0 @@
-/*
- * This file is part of MissileWars (https://github.com/Butzlabben/missilewars).
- * Copyright (c) 2018-2021 Daniel Nägele.
- *
- * MissileWars is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * MissileWars 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with MissileWars.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-package de.butzlabben.missilewars.util;
-
-import org.bukkit.util.Vector;
-
-public class MathUtil {
-
-    /**
-     * Checks if two doubles are close enough to be considered "equal".
-     * As we have a limited precision, the normal "==" operator would make some of our math not working.
-     * As long as the difference is smaller than 1.0E-8D, it will return true. This value was chosen, as
-     * {@link org.bukkit.util.Vector#equals(Object)} uses a more losen tolerance.
-     *
-     * @param value1 the first double
-     * @param value2 the second double
-     *
-     * @return true if the double values are close enough to be considered equal
-     */
-    public static boolean closeEnoughEquals(final double value1, final double value2) {
-        return Math.abs(value1 - value2) < 1.0E-8D;
-    }
-
-
-    public static boolean areMultiples(final Vector vector1, final Vector vector2) {
-        double factor = 0;
-        if (vector1.getX() != 0 && vector2.getX() != 0) factor = vector1.getX() / vector2.getX();
-        if (vector1.getY() != 0 && vector2.getY() != 0 && factor != 0) factor = vector1.getY() / vector2.getY();
-        if (vector1.getZ() != 0 && vector2.getZ() != 0 && factor != 0) factor = vector1.getZ() / vector2.getZ();
-        return vector1.equals(vector2.clone().multiply(factor));
-    }
-}

+ 34 - 0
missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/PlayerUtil.java

@@ -0,0 +1,34 @@
+package de.butzlabben.missilewars.util;
+
+import net.md_5.bungee.api.ChatMessageType;
+import net.md_5.bungee.api.chat.TextComponent;
+import org.bukkit.Location;
+import org.bukkit.entity.Player;
+import org.bukkit.util.Vector;
+
+public class PlayerUtil {
+
+    /**
+     * This method sends the desired message above the player's action bar.
+     * 
+     * @param player (Player) the target player
+     * @param message (String) the actionbar message
+     */
+    public static void sendActionbarMsg(Player player, String message) {
+        player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(message));
+    }
+
+    /**
+     * This method teleports the player to the specified location. Before the teleport, however, 
+     * the velocity is set to zero so that the player does not take over any fall damage due to 
+     * the previous fall / fly. This is mainly relevant in the 'Survival-Mode'.
+     * 
+     * @param player (Player) the target player
+     * @param targetLocation (Location) the target teleport-location
+     */
+    public static void teleportSafely(Player player, Location targetLocation) {
+        player.setVelocity(new Vector(0, 0, 0));
+        player.teleport(targetLocation);
+    }
+    
+}

+ 1 - 1
missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/serialization/LocationTypeAdapter.java

@@ -65,7 +65,7 @@ public class LocationTypeAdapter extends TypeAdapter<Location> {
                     String worldName = in.nextString();
                     World world = Bukkit.getWorld(worldName);
                     if (world == null) {
-                        Logger.WARN.log("Could not find world \"" + worldName + "\" which is specified at one missilewars sign");
+                        Logger.WARN.log("Could not find world \"" + worldName + "\" which is specified at one MissileWars sign");
                     }
                     location.setWorld(world);
                     break;

+ 0 - 1
missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/stats/PlayerGuiFactory.java

@@ -122,7 +122,6 @@ public class PlayerGuiFactory {
             ItemStack itemStack = new ItemStack(Material.PLAYER_HEAD);
             SkullMeta sm = (SkullMeta) itemStack.getItemMeta();
             if (Config.isShowRealSkins()) {
-                //noinspection deprecation
                 sm.setOwner(name);
             } else {
                 sm.setOwningPlayer(Bukkit.getOfflinePlayer(item.getUuid()));

+ 0 - 1
missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/stats/PreFetcher.java

@@ -119,7 +119,6 @@ public class PreFetcher {
             return new PageGUICreator<>("§ePlayer statistics", names, (item) -> {
                 ItemStack itemStack = new ItemStack(Material.PLAYER_HEAD);
                 SkullMeta sm = (SkullMeta) itemStack.getItemMeta();
-                //noinspection deprecation
                 sm.setOwner(item);
                 sm.setDisplayName(item);
                 itemStack.setItemMeta(sm);

+ 19 - 0
missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/version/MaterialHelper.java

@@ -0,0 +1,19 @@
+package de.butzlabben.missilewars.util.version;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
+import org.bukkit.Tag;
+
+public class MaterialHelper {
+    
+    // https://jd.papermc.io/paper/1.20.4/org/bukkit/Tag.html#ALL_SIGNS
+    // https://minecraft.wiki/w/Tag#all_signs
+    static Tag<Material> signMaterials = Bukkit.getTag(Tag.REGISTRY_BLOCKS, new NamespacedKey(NamespacedKey.MINECRAFT, "all_signs"), Material.class);
+    
+    public static boolean isSignMaterial(Material material) {
+        if (signMaterials == null) return false;
+        
+        return signMaterials.isTagged(material);
+    }
+}