Resource [Tutorial] Arena Manager

Discussion in 'Spigot Plugin Development' started by StefTheDev, Sep 20, 2018.

  1. [​IMG]

    I am sure that this has been done before but I would just like to show how I go about handling and managing arenas. I have taken advantage of using encapsulation classes and data serialisation. I am planning on extending this tutorial, so this is just a start.


    [​IMG]
    The best approach to take for any plugin development process is to work backwards. What do we need? How will we store things? What happens? These are all fundamental to the creation of projects especially when it comes to projects like this.

    • Arena Class
      • Acts as an Encapsulation Class
      • Contain data: name, location, players
      • Implements some getters and setters
    • Arena Manager:
      • Handles data serialisation
      • Returns an arena based on name/key.


    [​IMG]
    After the planning process it gets very easy. We store data in our arena class, handle the data in our arena manager class and then that is all you will need.

    Arena
    The arena class will simply act as an encapsulation class (Simply hiding data using getters and setters). You are allowed to add any data that you like within the class that you want to check for. I will be keeping it simple, however you are allowed to add more variables such as max or min players. We will take advantage of all the parameters in the constructor when we want to serialise data later.

    Code (Text):
    public class Arena {

        private final String name;
        private final Location location;
        private final Set<Player> players;

        public Arena(String name, Location location) {
            this.name = name;
            this.location = location;
            this.players = new HashSet<>();
        }

        public String getName() {
            return name;
        }

        public Location getLocation() {
            return location;
        }

        public Set<Player> getPlayers() {
            return Collections.unmodifiableSet(players);
        }

        public void addPlayer(Player player) {
            this.players.add(player);
        }

        public void removePlayer(Player player) {
            this.players.remove(player);
        }
    }
     

    Arena Manager

    The arena manager will handle the arenas and serialise all the data so we do not have to read/write at runtime. Reading from the configuration file will only happen when the plugin is initialised. So how exactly does this work?

    • Serialisation:
      • We check if the configuration section exists.
      • If it does exist we iterate through all the values. (These will be strings)
      • For every value we add a new Arena to the set.
      • When adding a new Arena to the set we input data from the config into the parameters.
      • That is all! You can add as much parameters to the constructor as you desire.
    • Deserialisation:
      • Loop through all the values of the set.
      • Write data into the config based on the getter and the appropriate path name.
      • Save the config so we write to the config. (Remember setting a value in the config will overwrite the old value)
      • Thats all and remember to set more data
    • Arena based on key/Name/Player:
      • This is very simple using lambda we iterate through all the arenas.
      • We filter out which arena has the name of the key/player is in the arena.
      • Then we return if the first value is found if no matching name has been found we return null. (Indicating that the arena does not exist)
    Code (Text):
    public class ArenaManager {

        private final Main plugin;
        private final FileConfiguration config;
        private final Set<Arena> arenaSet;

        public ArenaManager(Main plugin) {
            this.plugin = plugin;
            this.config = plugin.getConfig();
            this.arenaSet = new HashSet<>();
        }

        public void deserialise() {
            ConfigurationSection configSection = config.getConfigurationSection("Arenas");
            if(configSection == null) return;
            configSection.getKeys(false).forEach(s -> arenaSet.add(new Arena(
                    s, (Location) config.get("Arenas." + s + ".location")
            )));
        }

        public void serialise() {
            if(arenaSet.isEmpty()) return;
            arenaSet.forEach(arena -> config.set("Arenas." + arena.getName() + ".location", arena.getLocation()));
            plugin.saveConfig();
        }

        public Arena getArena(Player player) {
            return arenaSet.stream().filter(arena -> arena.getPlayers().contains(player)).findFirst().orElse(null);
        }

        public Arena getArena(String key) {
            return arenaSet.stream().filter(arena -> arena.getName().equals(key)).findFirst().orElse(null);
        }

        public Set<Arena> getArenaList() {
            return Collections.unmodifiableSet(arenaSet);
        }

        public void addArena(Arena arena) {
            this.arenaSet.add(arena);
        }

        public void removeArena(Arena arena) {
            this.arenaSet.remove(arena);
        }
    }

    Main
    In your main class create an instance the arena manager. Initialise your arena manager when you enable the plugin. As you can see when you serialise/deserialise the arena manager will do all the magic for you. It is very crucial that you have an arenaManager getter in your main class, this is a must! You will see when we put our arena manager into practise.

    Code (Text):
    public class Main extends JavaPlugin {

        private ArenaManager arenaManager;

        public void onEnable() {
            arenaManager = new ArenaManager(this);
            arenaManager.deserialise();
        }

        public void onDisable() {
            arenaManager.serialise();
        }

        public ArenaManager getArenaManager() {
            return arenaManager;
        }
    }
     

    [​IMG]
    We have done everything to setup the arenas, lets put all of this into practise. You simply will need to create an instance of the arena manager and then simply get the arena manager instance from your main class. You never want to create more than one arena managers as one arena manager will handle everything for you. Also remember that this is just an example class and I do not recommend this practise when using it for your plugin.

    The code below is intended for example purposes only and should not be put into practise.

    Code (Text):
    public class ArenaListener implements Listener {

        private ArenaManager arenaManager;

        public ArenaListener(Main plugin) {
            this.arenaManager = plugin.getArenaManager();
        }

        @EventHandler
        public void onJoin(PlayerJoinEvent event) {
            Arena arena = arenaManager.getArena("Test");
            if(arena == null) return;
            Player player = event.getPlayer();
            arena.addPlayer(player.getUniqueId());
            player.teleport(arena.getLocation());
        }

        @EventHandler
        public void onQuit(PlayerQuitEvent event) {
            Player player = event.getPlayer();
            Arena arena = arenaManager.getArena(player);
            if(arena == null) return;
            arena.removePlayer(player.getUniqueId());
        }
    }
     

    That is all for now. If you have any suggestions or recommend some changes feel free to comment below.
    (This is not final unless it is good enough, I am happy to keep updating and improving it.)

    Thanks,
    StefTheDev
     
    #1 StefTheDev, Sep 20, 2018
    Last edited: Oct 13, 2018
    • Like Like x 5
    • Creative Creative x 1
  2. Thanks a ton dude. Helped a lot.
     
  3. Thank you so much :D
     
  4. Generally, I don't really like to create getters and setters for my lists since
    1) It's easy to make mistakes such as just adding a player to the list but not actually sending a message that they've joined and doing all the checks etc.
    2) I dont like the idea of anyone and everyone having access to something that they shouldn't and it can cause problems in the case of your plugin becoming public.
     
  5. Firestar311

    Supporter

    Your concerns can be resolved if you just send them a copy of the actual list, something like:

    Code (Java):
    List<Object> mainList = new ArrayList<>();

    public List<Object> getList() {
        return new ArrayList<>(mainList);
    }
    Obviously use your own names and types
     
  6. Do you want me to give examples of some checks that the players should do to ensure safety while making their plugin based of this resource public? I am happy to implement them. What do you mean with having access to something that they shouldn't?
     
  7. Yes that is what I do. So that the list cannot be editted.
     
  8. You don't want to be able to add players to the game the "wrong" way. So make the list private and have an addPlayer method instead
     
  9. Thank you for the suggestions. It has been updated. Feel free to suggest/recommend more and I will be happy to change it :D
     
  10. Instead, return an immutable list.
    Code (Java):
    private final List<Object> mainList = new ArrayList<>();

    public List<Object> getList() {
         return Collections.unmodifiableList(mainList);
    }
     
    • Agree Agree x 1
  11. Simple but effective I like it, nice work dude.
     
    • Agree Agree x 1
  12. Yeah that is exactly what I was thinking of doing! Thank you for recommending.

    Thank you :D
     
  13. 2008Choco

    Junior Mod

    I would actually hold Player instances instead of UUIDs. Considering Arena instances will be temporary such that an arena is present and the fact that the players in the arena will frequently be referred to, a Player instance is likely best here instead.

    You have these terms backwards. Serialization means you're writing the data in a computer (or human)-readable format, deserialization means the opposite.
     
    • Agree Agree x 3
    • Like Like x 2
  14. Thank you for letting me know! I guess I got the terms mixed with loading and unloading. I am going to stick with UUID however because I prefer it over storing a whole player object. I guess one way I could approach it is by changing:

    Code (Text):
        public void addPlayer(UUID uuid) {
            this.players.add(uuid);
        }

        public void removePlayer(UUID uuid) {
            this.players.remove(uuid);
        }
    To:

    Code (Text):
        public void addPlayer(Player player) {
            this.players.add(player.getUniqueId());
        }

        public void removePlayer(Player player) {
            this.players.remove(player.getUniqueId());
        }
     
    #14 StefTheDev, Oct 12, 2018
    Last edited: Oct 12, 2018
  15. Code (Java):
     private ArenaManager arenaManager;

      public ArenaListener(Main plugin)
      {
            this.arenaManager = plugin.getArenaManager();
       }
    1. Use static getter to your main class...
    2. Make ArenaManager singleton.
     
  16. Why would you?
     
  17. I would rather store an instance of it in my main class and use a getter.

    I also use dependency injection for passing an instance of plugin
     
    • Agree Agree x 2
  18. the real question is wtf is your code suppose to mean?
     
    • Funny Funny x 1
  19. I don't know, but your suggestion doesn't follow with Java conventional programming standards.
     
  20. Excuse me, what?
     

Share This Page