Przeglądaj źródła

Merge pull request #67 from RedstoneFuture/Improvements/AreaCalculator

Reworking area calculation & adding cmds to change the areas
Daniel 2 lat temu
rodzic
commit
0186018200

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

@@ -66,7 +66,7 @@ public class MWCommands extends BaseCommand {
         sendHelpMessage(sender, "mw.debug", "/mw debug", "Show debug info.");
         sendHelpMessage(sender, "mw.restartall", "/mw restartall", "Restart all games.");
 
-        sendHelpMessage(sender, "mw.setup", "/mw setup <main|lobby|arena> <value> <set|teleport> [lobby]", "Setup the MW locations or the lobby/arena locations.");
+        sendHelpMessage(sender, "mw.setup", "/mw setup <main|lobby|arena> ...", "Setup the MW locations or the lobby/arena locations.");
     }
     
     @Subcommand("listgames|list|games")

+ 128 - 10
missilewars-plugin/src/main/java/de/butzlabben/missilewars/commands/SetupCommands.java

@@ -37,14 +37,14 @@ public class SetupCommands extends BaseCommand {
     @Default
     @CommandPermission("mw.setup")
     public void setupCommands(CommandSender sender, String[] args) {
-        sender.sendMessage(Messages.getPrefix() + "§fSetup usage: §7/mw setup <main|lobby|arena> <value> <set|teleport> [lobby]");
+        sender.sendMessage(Messages.getPrefix() + "§fSetup usage: §7/mw setup <main|lobby|arena> ...");
     }
 
     @Subcommand("main")
-    public class mainSetupCommands extends BaseCommand {
+    public class MainSetupCommands extends BaseCommand {
 
         @Subcommand("fallbackspawn")
-        public class fallbackspawnSetup extends BaseCommand {
+        public class FallbackspawnSetup extends BaseCommand {
 
             @Subcommand("set")
             @CommandCompletion("@nothing")
@@ -68,10 +68,10 @@ public class SetupCommands extends BaseCommand {
     }
 
     @Subcommand("lobby")
-    public class lobbySetupCommands extends BaseCommand {
+    public class LobbySetupCommands extends BaseCommand {
 
         @Subcommand("spawnpoint")
-        public class spawnpointSetup extends BaseCommand {
+        public class SpawnpointSetup extends BaseCommand {
 
             @Subcommand("set")
             @CommandCompletion("@games")
@@ -97,7 +97,7 @@ public class SetupCommands extends BaseCommand {
         }
 
         @Subcommand("aftergamespawn")
-        public class aftergamespawnSetup extends BaseCommand {
+        public class AftergamespawnSetup extends BaseCommand {
 
             @Subcommand("set")
             @CommandCompletion("@games")
@@ -121,13 +121,72 @@ public class SetupCommands extends BaseCommand {
             }
 
         }
+
+        @Subcommand("area")
+        public class AreaSetup extends BaseCommand {
+
+            @Subcommand("pos1")
+            public class Pos1Setup extends BaseCommand {
+
+                @Subcommand("set")
+                @CommandCompletion("@games")
+                public void set(CommandSender sender, String[] args) {
+                    if (!senderIsPlayer(sender)) return;
+                    if (!isValidGame(args)) return;
+
+                    game.getLobby().getArea().setPosition1(player.getLocation());
+                    game.getLobby().setAreaConfig(game.getLobby().getArea().getAreaConfiguration());
+                    game.getLobby().updateConfig();
+                    player.sendMessage(Messages.getPrefix() + "§fSet new 'lobby area' (position 1) to " + player.getLocation() + ".");
+                }
+
+                @Subcommand("teleport|tp")
+                @CommandCompletion("@games")
+                public void teleport(CommandSender sender, String[] args) {
+                    if (!senderIsPlayer(sender)) return;
+                    if (!isValidGame(args)) return;
+
+                    player.teleport(game.getLobby().getArea().getPosition1());
+                    player.sendMessage(Messages.getPrefix() + "§fTeleported to 'lobby area' (position 1): " + game.getLobby().getArea().getPosition1().toString());
+                }
+                
+            }
+
+            @Subcommand("pos2")
+            public class Pos2Setup extends BaseCommand {
+
+                @Subcommand("set")
+                @CommandCompletion("@games")
+                public void set(CommandSender sender, String[] args) {
+                    if (!senderIsPlayer(sender)) return;
+                    if (!isValidGame(args)) return;
+
+                    game.getLobby().getArea().setPosition2(player.getLocation());
+                    game.getLobby().setAreaConfig(game.getLobby().getArea().getAreaConfiguration());
+                    game.getLobby().updateConfig();
+                    player.sendMessage(Messages.getPrefix() + "§fSet new 'lobby area' (position 2) to " + player.getLocation() + ".");
+                }
+
+                @Subcommand("teleport|tp")
+                @CommandCompletion("@games")
+                public void teleport(CommandSender sender, String[] args) {
+                    if (!senderIsPlayer(sender)) return;
+                    if (!isValidGame(args)) return;
+
+                    player.teleport(game.getLobby().getArea().getPosition2());
+                    player.sendMessage(Messages.getPrefix() + "§fTeleported to 'lobby area' (position 2): " + game.getLobby().getArea().getPosition2().toString());
+                }
+
+            }
+            
+        }
     }
 
     @Subcommand("arena")
-    public class arenaSetupCommands extends BaseCommand {
+    public class ArenaSetupCommands extends BaseCommand {
 
         @Subcommand("spectatorspawn")
-        public class spectatorspawnSetup extends BaseCommand {
+        public class SpectatorspawnSetup extends BaseCommand {
 
             @Subcommand("set")
             @CommandCompletion("@games")
@@ -153,7 +212,7 @@ public class SetupCommands extends BaseCommand {
         }
 
         @Subcommand("team1spawn")
-        public class team1spawnSetup extends BaseCommand {
+        public class Team1spawnSetup extends BaseCommand {
 
             @Subcommand("set")
             @CommandCompletion("@games")
@@ -179,7 +238,7 @@ public class SetupCommands extends BaseCommand {
         }
 
         @Subcommand("team2spawn")
-        public class team2spawnSetup extends BaseCommand {
+        public class Team2spawnSetup extends BaseCommand {
 
             @Subcommand("set")
             @CommandCompletion("@games")
@@ -203,6 +262,65 @@ public class SetupCommands extends BaseCommand {
             }
 
         }
+
+        @Subcommand("area")
+        public class AreaSetup extends BaseCommand {
+
+            @Subcommand("pos1")
+            public class Pos1Setup extends BaseCommand {
+
+                @Subcommand("set")
+                @CommandCompletion("@games")
+                public void set(CommandSender sender, String[] args) {
+                    if (!senderIsPlayer(sender)) return;
+                    if (!isValidGame(args)) return;
+
+                    game.getArena().getArea().setPosition1(player.getLocation());
+                    game.getArena().setAreaConfig(game.getArena().getArea().getAreaConfiguration());
+                    game.getArena().updateConfig();
+                    player.sendMessage(Messages.getPrefix() + "§fSet new 'arena area' (position 1) to " + player.getLocation() + ".");
+                }
+
+                @Subcommand("teleport|tp")
+                @CommandCompletion("@games")
+                public void teleport(CommandSender sender, String[] args) {
+                    if (!senderIsPlayer(sender)) return;
+                    if (!isValidGame(args)) return;
+
+                    player.teleport(game.getArena().getArea().getPosition1());
+                    player.sendMessage(Messages.getPrefix() + "§fTeleported to 'arena area' (position 1): " + game.getArena().getArea().getPosition1().toString());
+                }
+
+            }
+
+            @Subcommand("pos2")
+            public class Pos2Setup extends BaseCommand {
+
+                @Subcommand("set")
+                @CommandCompletion("@games")
+                public void set(CommandSender sender, String[] args) {
+                    if (!senderIsPlayer(sender)) return;
+                    if (!isValidGame(args)) return;
+
+                    game.getArena().getArea().setPosition2(player.getLocation());
+                    game.getArena().setAreaConfig(game.getArena().getArea().getAreaConfiguration());
+                    game.getArena().updateConfig();
+                    player.sendMessage(Messages.getPrefix() + "§fSet new 'arena area' (position 2) to " + player.getLocation() + ".");
+                }
+
+                @Subcommand("teleport|tp")
+                @CommandCompletion("@games")
+                public void teleport(CommandSender sender, String[] args) {
+                    if (!senderIsPlayer(sender)) return;
+                    if (!isValidGame(args)) return;
+
+                    player.teleport(game.getArena().getArea().getPosition2());
+                    player.sendMessage(Messages.getPrefix() + "§fTeleported to 'arena area' (position 2): " + game.getArena().getArea().getPosition2().toString());
+                }
+
+            }
+
+        }
     }
 
     /**

+ 15 - 15
missilewars-plugin/src/main/java/de/butzlabben/missilewars/configuration/Lobby.java

@@ -20,10 +20,11 @@ package de.butzlabben.missilewars.configuration;
 
 import com.google.gson.annotations.SerializedName;
 import de.butzlabben.missilewars.Logger;
+import de.butzlabben.missilewars.configuration.arena.AreaConfiguration;
 import de.butzlabben.missilewars.configuration.arena.Arena;
 import de.butzlabben.missilewars.game.Arenas;
 import de.butzlabben.missilewars.game.enums.MapChooseProcedure;
-import de.butzlabben.missilewars.util.geometry.Area;
+import de.butzlabben.missilewars.util.geometry.GameArea;
 import de.butzlabben.missilewars.util.serialization.Serializer;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
@@ -45,10 +46,11 @@ import java.util.stream.Collectors;
 @RequiredArgsConstructor
 public class Lobby {
 
+    // The values defined here are only valid if there is no Config yet.
     private String name = "lobby0";
     @SerializedName("display_name") private String displayName = "&eDefault game";
     @SerializedName("auto_load") private boolean autoLoad = true;
-    @SerializedName("world") private String world = Bukkit.getWorlds().get(0).getName();
+    @SerializedName("world") private String worldName = getBukkitDefaultWorld().getName();
     @SerializedName("lobby_time") private int lobbyTime = 60;
     @SerializedName("join_ongoing_game") private boolean joinOngoingGame = false;
     @SerializedName("min_size") private int minSize = 2;
@@ -57,24 +59,30 @@ public class Lobby {
     @SerializedName("team1_color") private String team1Color = "&c";
     @SerializedName("team2_name") private String team2Name = "Team2";
     @SerializedName("team2_color") private String team2Color = "&a";
-    @Setter @SerializedName("spawn_point") private Location spawnPoint = Bukkit.getWorlds().get(0).getSpawnLocation();
-    @Setter @SerializedName("after_game_spawn") private Location afterGameSpawn = Bukkit.getWorlds().get(0).getSpawnLocation();
-    private Area area = Area.defaultAreaAround(Bukkit.getWorlds().get(0).getSpawnLocation());
+    @Setter @SerializedName("spawn_point") private Location spawnPoint = getBukkitDefaultWorld().getSpawnLocation();
+    @Setter @SerializedName("after_game_spawn") private Location afterGameSpawn = getBukkitDefaultWorld().getSpawnLocation();
+    @Setter @SerializedName("area") private AreaConfiguration areaConfig = new AreaConfiguration(-30, 0, -30, 30, 256, 30);
     @SerializedName("map_choose_procedure") private MapChooseProcedure mapChooseProcedure = MapChooseProcedure.FIRST;
     @SerializedName("possible_arenas") private List<String> possibleArenas = new ArrayList<>() {{
         add("arena0");
     }};
 
+    // These values are only set after the Config has been read.
+    @Setter private transient GameArea area;
     @Setter private transient File file;
 
     public World getBukkitWorld() {
-        World world = Bukkit.getWorld(getWorld());
+        World world = Bukkit.getWorld(worldName);
         if (world == null) {
-            Logger.ERROR.log("Could not find any world with the name: " + this.world);
+            Logger.ERROR.log("Could not find any world with the name: " + worldName);
             Logger.ERROR.log("Please correct this in the configuration of lobby \"" + name + "\"");
         }
         return world;
     }
+    
+    private World getBukkitDefaultWorld() {
+        return Bukkit.getWorlds().get(0);
+    }
 
     public void checkForWrongArenas() {
         for (String arenaName : possibleArenas) {
@@ -101,12 +109,4 @@ public class Lobby {
             throw new RuntimeException(e);
         }
     }
-
-    public Location getAreaMinLocation() {
-        return new Location(getBukkitWorld(), area.getMinX(), area.getMinY(), area.getMinZ());
-    }
-
-    public Location getAreaMaxLocation() {
-        return new Location(getBukkitWorld(), area.getMaxX(), area.getMaxY(), area.getMaxZ());
-    }
 }

+ 56 - 0
missilewars-plugin/src/main/java/de/butzlabben/missilewars/configuration/arena/AreaConfiguration.java

@@ -0,0 +1,56 @@
+/*
+ * 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.configuration.arena;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.ToString;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Getter
+@ToString
+@AllArgsConstructor
+public class AreaConfiguration implements ConfigurationSerializable {
+
+    @SerializedName("min_x") private int minX;
+    @SerializedName("min_y") private int minY;
+    @SerializedName("min_z") private int minZ;
+    @SerializedName("max_x") private int maxX;
+    @SerializedName("max_y") private int maxY;
+    @SerializedName("max_z") private int maxZ;
+
+    /**
+     * This method is used to save the config entries in the config file.
+     */
+    @Override
+    public Map<String, Object> serialize() {
+        Map<String, Object> serialized = new HashMap<>();
+        serialized.put("min_x", minX);
+        serialized.put("min_y", minY);
+        serialized.put("min_z", minZ);
+        serialized.put("max_x", maxX);
+        serialized.put("max_y", maxY);
+        serialized.put("max_z", maxZ);
+        return serialized;
+    }
+}

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

@@ -19,14 +19,12 @@
 package de.butzlabben.missilewars.configuration.arena;
 
 import com.google.gson.annotations.SerializedName;
-import de.butzlabben.missilewars.util.geometry.FlatArea;
-import de.butzlabben.missilewars.util.geometry.Plane;
+import de.butzlabben.missilewars.util.geometry.GameArea;
 import de.butzlabben.missilewars.util.serialization.Serializer;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.ToString;
 import org.bukkit.Location;
-import org.bukkit.util.Vector;
 
 import java.io.File;
 import java.io.IOException;
@@ -35,6 +33,7 @@ import java.io.IOException;
 @ToString
 public class Arena implements Cloneable {
 
+    // The values defined here are only valid if there is no Config yet.
     private String name = "arena0";
     @SerializedName("display_name") private String displayName = "&eDefault map";
     @SerializedName("display_material") private String displayMaterial = "STONE";
@@ -56,43 +55,26 @@ public class Arena implements Cloneable {
     @SerializedName("money") private MoneyConfiguration money = new MoneyConfiguration();
     @SerializedName("equipment_interval") private EquipmentIntervalConfiguration interval = new EquipmentIntervalConfiguration();
     @SerializedName("missile") private MissileConfiguration missileConfiguration = new MissileConfiguration();
-    @SerializedName("area") private FlatArea gameArea = new FlatArea(-30, -72, 30, 72);
-    
+    @Setter @SerializedName("area") private AreaConfiguration areaConfig = new AreaConfiguration(-30, 0, -72, 30, 256, 72);
+
     @SerializedName("spectator_spawn")
     @Setter
     private Location spectatorSpawn = new Location(null, 0, 100, 0, 90, 0);
-    
+
     @SerializedName("team1_spawn")
     @Setter
     private Location team1Spawn = new Location(null, 0.5, 100, 45.5, 180, 0);
-    
+
     @SerializedName("team2_spawn")
     @Setter
     private Location team2Spawn = new Location(null, 0.5, 100, -45.5, 0, 0);
 
+    // These values are only set after the Config has been read.
+    @Setter private transient GameArea area;
     @Setter private transient File file;
-    
-    public Arena() {
-
-    }
-
-    public Plane getPlane1() {
-        Vector spawn1 = team1Spawn.toVector();
-        Vector normal = team2Spawn.toVector().subtract(spawn1);
-        return new Plane(spawn1, normal);
-    }
 
-    public Plane getPlane2() {
-        Vector spawn2 = team2Spawn.toVector();
-        Vector normal = team1Spawn.toVector().subtract(spawn2);
-        return new Plane(spawn2, normal);
-    }
+    public Arena() {
 
-    public boolean isInBetween(Vector point, Plane plane1, Plane plane2) {
-        double distanceBetween = plane1.distanceSquared(plane2.getSupport());
-        double distance1 = plane1.distanceSquared(point);
-        double distance2 = plane2.distanceSquared(point);
-        return distanceBetween > distance1 + distance2;
     }
 
     @Override

+ 91 - 22
missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/Game.java

@@ -49,6 +49,8 @@ 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.PlayerDataProvider;
+import de.butzlabben.missilewars.util.geometry.GameArea;
+import de.butzlabben.missilewars.util.geometry.Geometry;
 import de.butzlabben.missilewars.util.serialization.Serializer;
 import de.butzlabben.missilewars.util.version.VersionUtil;
 import lombok.Getter;
@@ -94,6 +96,8 @@ public class Game {
     private boolean ready = false;
     private boolean restart = false;
     private GameWorld gameWorld;
+    private GameArea gameArea;
+    private GameArea innerGameArea;
     private long timestart;
     private Arena arena;
     private ScoreboardManager scoreboardManager;
@@ -129,9 +133,7 @@ public class Game {
             Logger.ERROR.log("None of the specified arenas match a real arena for the lobby " + lobby.getName());
             return;
         }
-
-        gameWorld = new GameWorld(this, "");
-
+        
         team1 = new Team(lobby.getTeam1Name(), lobby.getTeam1Color(), this);
         team2 = new Team(lobby.getTeam2Name(), lobby.getTeam2Color(), this);
 
@@ -432,29 +434,59 @@ public class Game {
         }
     }
 
+    /**
+     * This method checks if the location is inside in the Lobby-Area.
+     * 
+     * @param location (Location) the location to be checked
+     * @return true, if it's in the Lobby-Area
+     */
     public boolean isInLobbyArea(Location location) {
-        World world = location.getWorld();
-        if (world == null) return false;
-        if (world.getName().equals(lobby.getWorld())) return lobby.getArea().isInArea(location);
-        return false;
+        if (!Geometry.isInsideIn(location, lobby.getArea())) return false;
+        return true;
     }
 
-
+    /**
+     * This method checks if the location is inside in the Game-Area.
+     *
+     * @param location (Location) the location to be checked
+     * @return true, if it's in the Game-Area
+     */
     public boolean isInGameArea(Location location) {
-        if (isInGameWorld(location)) return arena.getGameArea().isInArea(location);
-        return false;
+        if (!Geometry.isInsideIn(location, gameArea)) return false;
+        return true;
     }
 
-    public boolean isInGameWorld(Location location) {
-        World world = location.getWorld();
-        if (world == null) return false;
+    /**
+     * This method checks if the location is inside in the Inner Game-Area.
+     * It's the arena from the Team 1 spawn position to the Team 2 spawn 
+     * position ("length") with the same "width" of the (major) Game-Area.
+     *
+     * @param location (Location) the location to be checked
+     * @return true, if it's in the Inner Game-Area
+     */
+    public boolean isInInnerGameArea(Location location) {
+        if (!Geometry.isInsideIn(location, innerGameArea)) return false;
+        return true;
+    }
 
-        if (gameWorld != null) {
-            return gameWorld.isWorld(world);
-        }
-        return false;
+    /**
+     * This method checks if the location is in the game world.
+     *
+     * @param location (Location) the location to be checked
+     * @return true, if it's in the game world
+     */
+    public boolean isInGameWorld(Location location) {
+        if (!Geometry.isInWorld(location, gameArea.getWorld())) return false;
+        return true;
     }
 
+    /**
+     * This (shortcut) method checks if the location is inside in the
+     * Lobby-Area or inside in the game world.
+     *
+     * @param location (Location) the location to be checked
+     * @return true, if the statement is correct
+     */
     public boolean isIn(Location location) {
         return isInLobbyArea(location) || isInGameWorld(location);
     }
@@ -608,7 +640,7 @@ public class Game {
         // Are missiles only allowed to spawn inside the arena, between the two arena spawn points?
         boolean isOnlyBetweenSpawnPlaceable = this.arena.getMissileConfiguration().isOnlyBetweenSpawnPlaceable();
         if (isOnlyBetweenSpawnPlaceable) {
-            if (!this.arena.isInBetween(player.getLocation().toVector(), this.arena.getPlane1(), this.arena.getPlane2())) {
+            if (!isInInnerGameArea(player.getLocation())) {
                 player.sendMessage(Messages.getMessage("missile_place_deny"));
                 return;
             }
@@ -657,10 +689,9 @@ public class Game {
         }
 
         this.arena = arena.clone();
-
-        // Load world
-        this.gameWorld = new GameWorld(this, this.arena.getTemplateWorld());
-        this.gameWorld.load();
+        gameWorld = new GameWorld(this, arena.getTemplateWorld());
+        gameWorld.load();
+        gameArea = new GameArea(gameWorld.getWorld(), arena.getAreaConfig());
 
         try {
             Serializer.setWorldAtAllLocations(this.arena, gameWorld.getWorld());
@@ -671,6 +702,8 @@ public class Game {
             exception.printStackTrace();
             return;
         }
+        
+        createInnerGameArea();
 
         if (lobby.getMapChooseProcedure() == MapChooseProcedure.MAPVOTING) {
             this.broadcast(Messages.getMessage("vote.finished").replace("%map%", this.arena.getDisplayName()));
@@ -680,6 +713,42 @@ public class Game {
         ready = true;
     }
 
+    private void createInnerGameArea() {
+        
+        // Depending on the rotation of the (major) Game-Area, the spawn points 
+        // of both teams are primarily on the X or Z axis opposite each other.
+        // The Inner Game-Area is a copy of the (major) Game-Area, with the X or Z 
+        // axis going only to spawn. The X or Z distance is thus reduced.
+        // So this algorithm allows the spawn points to face each other even if 
+        // they are offset.
+
+        int x1, x2, z1, z2;
+        Location position1, position2;
+        
+        if (gameArea.getDirection() == GameArea.Direction.NORTH_SOUTH) {
+            
+            x1 = gameArea.getMinX();
+            x2 = gameArea.getMaxX();
+            
+            z1 = team1.getSpawn().getBlockZ();
+            z2 = team2.getSpawn().getBlockZ();
+            
+        } else {
+            
+            z1 = gameArea.getMinZ();
+            z2 = gameArea.getMaxZ();
+            
+            x1 = team1.getSpawn().getBlockX();
+            x2 = team2.getSpawn().getBlockX();
+            
+        }
+        
+        position1 = new Location(gameArea.getWorld(), x1, gameArea.getMinY(), z1);
+        position2 = new Location(gameArea.getWorld(), x2, gameArea.getMaxY(), z2);
+
+        innerGameArea = new GameArea(position1, position2);
+    }
+
     public void applyForAllPlayers(Consumer<Player> consumer) {
         for (Player player : Bukkit.getOnlinePlayers()) {
             if (!isIn(player.getLocation())) continue;

+ 2 - 0
missilewars-plugin/src/main/java/de/butzlabben/missilewars/game/GameManager.java

@@ -22,6 +22,7 @@ import de.butzlabben.missilewars.Logger;
 import de.butzlabben.missilewars.MissileWars;
 import de.butzlabben.missilewars.configuration.Config;
 import de.butzlabben.missilewars.configuration.Lobby;
+import de.butzlabben.missilewars.util.geometry.GameArea;
 import de.butzlabben.missilewars.util.serialization.Serializer;
 import lombok.Getter;
 import org.bukkit.Bukkit;
@@ -150,6 +151,7 @@ public class GameManager {
         try {
             Lobby lobby = Serializer.deserialize(targetLobby.getFile(), Lobby.class);
             lobby.setFile(targetLobby.getFile());
+            lobby.setArea(new GameArea(lobby.getBukkitWorld(), lobby.getAreaConfig()));
             lobby.updateConfig();
 
             Logger.BOOTDONE.log("Reloaded lobby \"" + targetLobbyName + "\" (" + targetLobby.getFile().getName() + ")");

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

@@ -52,13 +52,14 @@ public class GameWorld {
     }
 
     public boolean isWorld(World world) {
-        if (world == null) {
-            return false;
-        }
+        if (world == null) return false;
+
+        if ((worldName == null) || (worldName.isEmpty())) throw new IllegalArgumentException("GameWorld must be loaded first: 'gameWorld.load()'");
         return world.getName().equals(worldName);
     }
 
     public World getWorld() {
+        if ((worldName == null) || (worldName.isEmpty())) throw new IllegalArgumentException("GameWorld must be loaded first: 'gameWorld.load()'");
         return Bukkit.getWorld(worldName);
     }
 

+ 10 - 6
missilewars-plugin/src/main/java/de/butzlabben/missilewars/listener/game/GameListener.java

@@ -28,6 +28,7 @@ import de.butzlabben.missilewars.game.enums.GameResult;
 import de.butzlabben.missilewars.game.misc.RespawnGoldBlock;
 import de.butzlabben.missilewars.game.misc.Shield;
 import de.butzlabben.missilewars.player.MWPlayer;
+import de.butzlabben.missilewars.util.geometry.Geometry;
 import de.butzlabben.missilewars.util.version.VersionUtil;
 import org.bukkit.GameMode;
 import org.bukkit.Location;
@@ -75,13 +76,16 @@ public class GameListener extends GameBoundListener {
         if (event.getChangedType() != VersionUtil.getPortal()) return;
 
         Location location = event.getBlock().getLocation();
-
-        if (getGame().getArena().getPlane1().distance(location.toVector()) > getGame().getArena().getPlane2().distance(location.toVector())) {
-            getGame().getTeam1().setGameResult(GameResult.WIN);
-            getGame().getTeam2().setGameResult(GameResult.LOSE);
+        
+        Team team1 = getGame().getTeam1();
+        Team team2 = getGame().getTeam2();
+        
+        if (Geometry.isCloser(location, team1.getSpawn(), team2.getSpawn())) {
+            team1.setGameResult(GameResult.LOSE);
+            team2.setGameResult(GameResult.WIN);
         } else {
-            getGame().getTeam1().setGameResult(GameResult.LOSE);
-            getGame().getTeam2().setGameResult(GameResult.WIN);
+            team1.setGameResult(GameResult.WIN);
+            team2.setGameResult(GameResult.LOSE);
         }
         
         getGame().sendGameResult();

+ 0 - 113
missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/geometry/Area.java

@@ -1,113 +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.geometry;
-
-import com.google.gson.annotations.SerializedName;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.ToString;
-import org.bukkit.Location;
-import org.bukkit.configuration.serialization.ConfigurationSerializable;
-
-import java.util.HashMap;
-import java.util.Map;
-
-@ToString
-@AllArgsConstructor
-@Getter
-public class Area implements ConfigurationSerializable {
-
-    @SerializedName("min_x") private int minX;
-    @SerializedName("min_y") private int minY;
-    @SerializedName("min_z") private int minZ;
-    @SerializedName("max_x") private int maxX;
-    @SerializedName("max_y") private int maxY;
-    @SerializedName("max_z") private int maxZ;
-    private transient boolean checked;
-
-    public static Area deserialize(Map<String, Object> serialized) {
-        int minX = (int) serialized.get("min_x");
-        int minY = (int) serialized.get("min_y");
-        int minZ = (int) serialized.get("min_z");
-
-        int maxX = (int) serialized.get("max_x");
-        int maxY = (int) serialized.get("max_y");
-        int maxZ = (int) serialized.get("max_z");
-        return new Area(minX, minY, minZ, maxX, maxY, maxZ, false);
-    }
-
-    public static Area defaultAreaAround(Location location) {
-        return new Area(location.getBlockX() - 20,
-                location.getBlockY() - 20,
-                location.getBlockZ() - 20,
-                location.getBlockX() + 20,
-                location.getBlockY() + 20,
-                location.getBlockZ() + 20, true);
-    }
-
-    public boolean isInArea(double x, double y, double z) {
-        checkValues();
-        return x >= minX && x <= maxX &&
-                y >= minY && y <= maxY &&
-                z >= minZ && z <= maxZ;
-    }
-
-    public boolean isInArea(Location loc) {
-        double x = loc.getX();
-        double y = loc.getY();
-        double z = loc.getZ();
-        return isInArea(x, y, z);
-    }
-
-    void checkValues() {
-        if (checked)
-            return;
-
-        if (minX >= maxX) {
-            int oldMin = minX;
-            this.minX = maxX;
-            this.maxX = oldMin;
-        }
-
-        if (minY >= maxY) {
-            int oldMin = minY;
-            this.minY = maxY;
-            this.maxY = oldMin;
-        }
-
-        if (minZ >= maxZ) {
-            int oldMin = minZ;
-            this.minZ = maxZ;
-            this.maxZ = oldMin;
-        }
-        checked = true;
-    }
-
-    @Override
-    public Map<String, Object> serialize() {
-        Map<String, Object> serialized = new HashMap<>();
-        serialized.put("min_x", minX);
-        serialized.put("min_y", minY);
-        serialized.put("min_z", minZ);
-        serialized.put("max_x", maxX);
-        serialized.put("max_y", maxY);
-        serialized.put("max_z", maxZ);
-        return serialized;
-    }
-}

+ 0 - 40
missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/geometry/FlatArea.java

@@ -1,40 +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.geometry;
-
-
-import org.bukkit.Location;
-
-public class FlatArea extends Area {
-
-    public FlatArea(int minX, int minZ, int maxX, int maxZ) {
-        super(minX, 0, minZ, maxX, 0, maxZ, false);
-    }
-
-    public boolean isInArea(double x, double z) {
-        return super.isInArea(x, 0, z);
-    }
-
-    @Override
-    public boolean isInArea(Location loc) {
-        long x = Math.round(loc.getX());
-        long z = Math.round(loc.getZ());
-        return isInArea(x, z);
-    }
-}

+ 201 - 0
missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/geometry/GameArea.java

@@ -0,0 +1,201 @@
+/*
+ * 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.geometry;
+
+import de.butzlabben.missilewars.configuration.arena.AreaConfiguration;
+import lombok.Getter;
+import lombok.Setter;
+import org.bukkit.Location;
+import org.bukkit.World;
+
+@Getter
+public class GameArea {
+
+    private World world;
+    @Setter private Location position1, position2;
+
+    private int minX, minY, minZ;
+    private int maxX, maxY, maxZ;
+
+    private Direction direction;
+    
+    /**
+     * This method creates a new GameArena object.
+     *
+     * The GameArena is a rectangular area. Its border (1 block wide) is 
+     * still part of the arena (= inside).
+     *
+     * @param pos1 (Location) one corner of the desired area border
+     * @param pos2 (Location) the opposite corner of the desired area border
+     */
+    public GameArea(Location pos1, Location pos2) {
+        
+        if (!Geometry.bothLocInSameWorld(pos1, pos2)) throw new IllegalArgumentException("Defined positions are not in the same world!");
+        if (pos1.equals(pos2)) throw new IllegalArgumentException("The selected positions do not differ.");
+        
+        this.world = pos1.getWorld();
+        
+        this.position1 = pos1;
+        this.position2 = pos2;
+        
+        initialize();
+    }
+
+    /**
+     * This method creates a new GameArena object.
+     *
+     * The GameArena is a rectangular area. Its border (1 block wide) is 
+     * still part of the arena (= inside).
+     *
+     * @param center (Location) the horizontal center of the desired area; it's also the vertical minimum of the area (the floor)
+     * @param offset (int) the horizontal offset, which should go from the center to the end of the desired area
+     * @param height (int) the height of the desired area
+     */
+    public GameArea(Location center, int offset, int height) {
+        
+        if (offset < 1) throw new IllegalArgumentException("The offset must be higher than 0.");
+        if (height < 2) throw new IllegalArgumentException("The height must be higher than 1.");
+        
+        this.world = center.getWorld();
+        
+        long x1 = center.getBlockX() + offset;
+        long x2 = center.getBlockX() - offset;
+        long z1 = center.getBlockZ() + offset;
+        long z2 = center.getBlockZ() - offset;
+
+        long y1 = center.getBlockY();
+        long y2 = y1 + height;
+
+        this.position1 = new Location(center.getWorld(), x1, y1, z1);
+        this.position2 = new Location(center.getWorld(), x2, y2, z2);
+
+        initialize();
+    }
+
+    /**
+     * This method creates a new GameArena object.
+     *
+     * The GameArena is a rectangular area. Its border (1 block wide) is 
+     * still part of the arena (= inside).
+     *
+     * @param world (World) the target world for the desired area
+     * @param areaConfig (AreaConfiguration) the loaded Area-Configuration from which the data is taken
+     */
+    public GameArea(World world, AreaConfiguration areaConfig) {
+        
+        this.world = world;
+        
+        this.position1 = new Location(world, areaConfig.getMinX(), areaConfig.getMinY(), areaConfig.getMinZ());
+        this.position2 = new Location(world, areaConfig.getMaxX(), areaConfig.getMaxY(), areaConfig.getMaxZ());
+
+        if (position1.equals(position2)) throw new IllegalArgumentException("The selected positions do not differ.");
+        
+        initialize();
+    }
+
+    /**
+     * This method calculates and saves the MIN and MAX positions 
+     * according to the current values. The assigned MIN and MAX 
+     * information can be used to later compare the GameArea more 
+     * easily with current live positions/areas. In addition, the 
+     * area direction is calculated afterwards.
+     */
+    private void initialize() {
+
+        // Calculation of min & max X coordinate:
+        if (position1.getBlockX() < position2.getBlockX()) {
+            minX = position1.getBlockX();
+            maxX = position2.getBlockX();
+        } else {
+            maxX = position1.getBlockX();
+            minX = position2.getBlockX();
+        }
+
+        // Calculation of min & max Y coordinate:
+        if (position1.getBlockY() < position2.getBlockY()) {
+            minY = position1.getBlockY();
+            maxY = position2.getBlockY();
+        } else {
+            maxY = position1.getBlockY();
+            minY = position2.getBlockY();
+        }
+
+        // Calculation of min & max Z coordinate:
+        if (position1.getBlockZ() < position2.getBlockZ()) {
+            minZ = position1.getBlockZ();
+            maxZ = position2.getBlockZ();
+        } else {
+            maxZ = position1.getBlockZ();
+            minZ = position2.getBlockZ();
+        }
+
+        // Calculation of area direction:
+        if (getXSize() < getZSize()) {
+            direction = Direction.NORTH_SOUTH;
+        } else {
+            direction = Direction.EAST_WEST;
+        }
+    }
+
+    public AreaConfiguration getAreaConfiguration() {
+        AreaConfiguration newAreaConfig = new AreaConfiguration(position1.getBlockX(), position1.getBlockY(), position1.getBlockZ(), 
+                position2.getBlockX(), position2.getBlockY(), position2.getBlockZ());
+        return newAreaConfig;
+    }
+
+    /**
+     * This method defines the horizontal direction / rotation of the 
+     * area based on the alignment of the team spawn points.
+     * 
+     * NORTH-SOUTH = primarily along the Z axis
+     * EAST-WEST = primarily along the X axis
+     */
+    public enum Direction {
+        NORTH_SOUTH,
+        EAST_WEST
+    }
+
+    /**
+     * This method returns the arena length along the X coordinate.
+     * 
+     * @return (Integer) the X size
+     */
+    public int getXSize() {
+        return maxX - minX;
+    }
+
+    /**
+     * This method returns the arena length along the Y coordinate.
+     *
+     * @return (Integer) the Y size
+     */
+    public int getYSize() {
+        return maxY - minY;
+    }
+
+    /**
+     * This method returns the arena length along the Z coordinate.
+     *
+     * @return (Integer) the Z size
+     */
+    public int getZSize() {
+        return maxZ - minZ;
+    }
+    
+}

+ 87 - 0
missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/geometry/Geometry.java

@@ -0,0 +1,87 @@
+/*
+ * 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.geometry;
+
+import org.bukkit.Location;
+import org.bukkit.World;
+
+public class Geometry {
+
+    /**
+     * This method checks if a location is closer to a target location
+     * than another location.
+     *
+     * @param targetLocation (Location) the start location for the distance measure
+     * @param closerLocation (Location) the closer location
+     * @param furtherAwayLocation (Location) the location that is further away
+     * @return true, if the statement is correct
+     */
+    public static boolean isCloser(Location targetLocation, Location closerLocation, Location furtherAwayLocation) {
+        return targetLocation.distanceSquared(closerLocation) < targetLocation.distanceSquared(furtherAwayLocation);
+    }
+
+    /**
+     * This method checks if a location is within an arena. The border of the
+     * arena (1 block wide) is still part of the arena (= inside).
+     *
+     * @param targetLocation (Location) the location to be checked
+     * @param area (Location) the arena, which should be around the location
+     * @return true, if the statement is correct
+     */
+    public static boolean isInsideIn(Location targetLocation, GameArea area) {
+
+        if (!Geometry.bothLocInSameWorld(targetLocation, area.getPosition1())) return false;
+
+        if (targetLocation.getBlockX() > area.getMaxX()) return false;
+        if (targetLocation.getBlockX() < area.getMinX()) return false;
+
+        if (targetLocation.getBlockY() > area.getMaxY()) return false;
+        if (targetLocation.getBlockY() < area.getMinY()) return false;
+
+        if (targetLocation.getBlockZ() > area.getMaxZ()) return false;
+        if (targetLocation.getBlockZ() < area.getMinZ()) return false;
+
+        return true;
+    }
+
+    /**
+     * This method checks, if both locations are in the same world.
+     * 
+     * @param pos1 (Location) location 1
+     * @param pos2 (Location) location 2
+     * @return true, if they are in the same world
+     */
+    public static boolean bothLocInSameWorld(Location pos1, Location pos2) {
+        if ((pos1.getWorld() == null) || (pos2.getWorld() == null)) return false;
+        if (pos1.getWorld().getName().equals(pos2.getWorld().getName())) return true;
+        return false;
+    }
+
+    /**
+     * This method checks if a location is in a specified world.
+     * 
+     * @param targetLocation (Location) the location to be checked
+     * @param world (World) the target world
+     * @return true, if the statement is correct
+     */
+    public static boolean isInWorld(Location targetLocation, World world) {
+        return targetLocation.getWorld().getName().equals(world.getName());
+    }
+    
+}

+ 0 - 71
missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/geometry/Line.java

@@ -1,71 +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.geometry;
-
-import de.butzlabben.missilewars.util.MathUtil;
-import lombok.AllArgsConstructor;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-import org.bukkit.util.Vector;
-
-/**
- * A 3-dimensional line in the form:
- * supporter + t*direction
- */
-@AllArgsConstructor
-@ToString
-@EqualsAndHashCode
-public class Line {
-
-    private final Vector support, direction;
-
-    public static Line fromPoints(final Vector point1, final Vector point2) {
-        return new Line(point1.clone(), point2.clone().subtract(point1.clone()));
-    }
-
-    public boolean isIn(final Vector point) {
-        final double tX = this.getTEquationSolved(this.support.getX(), this.direction.getX(), point.getX());
-        final double tY = this.getTEquationSolved(this.support.getY(), this.direction.getY(), point.getY());
-        final double tZ = this.getTEquationSolved(this.support.getZ(), this.direction.getZ(), point.getZ());
-        return MathUtil.closeEnoughEquals(tX, tY) && MathUtil.closeEnoughEquals(tX, tZ) && MathUtil.closeEnoughEquals(tY, tZ);
-    }
-
-    public double distance(final Vector point) {
-        final Vector closestPoint = this.closestPointTo(point);
-        return point.distance(closestPoint);
-    }
-
-    public Vector closestPointTo(final Vector point) {
-        if (this.isIn(point)) return point.clone();
-        final Plane helperPlane = new Plane(point, this.direction);
-        return helperPlane.getBreakThroughPoint(this).get();
-    }
-
-    private double getTEquationSolved(final double supportValue, final double directionValue, final double pointValue) {
-        return (pointValue - supportValue) / directionValue;
-    }
-
-    public Vector getSupport() {
-        return this.support.clone();
-    }
-
-    public Vector getDirection() {
-        return this.direction.clone();
-    }
-}

+ 0 - 70
missilewars-plugin/src/main/java/de/butzlabben/missilewars/util/geometry/Plane.java

@@ -1,70 +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.geometry;
-
-import de.butzlabben.missilewars.util.MathUtil;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.ToString;
-import org.bukkit.util.Vector;
-
-import java.util.Optional;
-
-@Getter
-@AllArgsConstructor
-@ToString
-public class Plane {
-
-    private final Vector support, normal;
-
-    public boolean isIn(Vector point) {
-        return MathUtil.closeEnoughEquals(point.clone().subtract(support).dot(normal), 0);
-    }
-
-    public Vector closestPointTo(Vector point) {
-        if (isIn(point)) return point.clone();
-
-        Line supportLine = new Line(point, normal);
-        // we can safely get the value, as we know that this plane and the line are not parallel
-        return getBreakThroughPoint(supportLine).get();
-    }
-
-    public double distance(Vector point) {
-        Vector closestPoint = closestPointTo(point);
-        return point.distance(closestPoint);
-    }
-
-    public double distanceSquared(Vector point) {
-        Vector closestPoint = closestPointTo(point);
-        return point.distanceSquared(closestPoint);
-    }
-
-    public Optional<Vector> getBreakThroughPoint(Line line) {
-        if (MathUtil.closeEnoughEquals(line.getDirection().dot(normal), 0)) return Optional.empty();
-        double d = support.dot(normal);
-        double a = normal.getX();
-        double b = normal.getY();
-        double c = normal.getZ();
-        Vector x = line.getSupport();
-        Vector y = line.getDirection();
-        double t = (d - a * x.getX() - b * x.getY() - c * x.getZ()) / (a * y.getX() + b * y.getY() + c * y.getZ());
-        Vector result = line.getSupport().add(line.getDirection().multiply(t)).clone();
-        return Optional.of(result);
-    }
-}