Tutorial Team systems

Discussion in 'Resources' started by mcdorli, Mar 18, 2016.

Thread Status:
Not open for further replies.
  1. Offline

    mcdorli

    Disclaimer: I didn't made this tutorial so you can copy the code out of it. At least the the effort, to type the code in yourself.

    If you want to make a Clan or a Faction plugin, you need to implement some sort of Team system. This tutorial helps you in that.

    1.: Creating a Team class

    This class is basically what stores the information about a team (members, money, etc.). It's very essential, to have a class, and not just represent the teams as a big hashmap in your main class. I will store the players in a hashmap, so we can assign a rank to them later. We probably need some basic methods in this class.
    • isInTeam(Player player) - Returns if the current player is part of the team
    • getMembers() - Returns an arraylist (or hashmap) of the current members
    • getOwner() - returns the owner of the team
    • addMember(Player player) / addMember(Player player, String rank) - Adds a player to the member list (rank is optional)
    • removeMember(Player player) - Removes a player from the members list
    These are probably the main methods you need in you Team plugin (except if you want to work with duels, I get to that later)
    For this tutorial, I implement an extra detail. Every player will have a custom rank in the Team. Because I'm not doing a Rank system tutorial, I will use Strings to mark the different ranks.

    Now, that everything is set, let's go into details about the methods and fields we need.

    The fields:

    First, the fields. We need a members hashmap, wich also contains information about the ranks too, and the name of the Team. Both of them are private, so they don't get changed. You probably want to give a setter and a getter to the name field.
    Code:
    private String name;
    private HashMap<UUID, String> members = new HashMap<>();
    
    Note: To avoid using up too much memory, make sure you use UUID-s instead of the whole player object.


    The methods:


    Now, that we have the necessary fields, we need to create the constructor and the methods to manage these. These, of course needs to be public.


    The constructor will be simple, it will only take in the owner of the team and the name of the team.
    Code:
    public Team (Player owner, String name) {
        this.name = name;
        members.put(owner.getUniqueId(), "owner");
    }
    
    public boolean is inTeam(Player player) {
        return members.containsKey(player.getUniqueId());
    }
    
    public Set<UUID> getMembers() {
        return members.keySet();
    }
    
    public UUID getOwner() {
        Set<UUID> keySet = members.keySet();        //We get the keys inside the hashmap and
        for (UUID uuid : keySet) {       //loop through it, while checking the current player's rank.
            if (members.get(uuid).equals("owner"))    //If he is the owner, we return his UUID
                return uuid;
        }
        return null;
    }
    
    public void addMember(Player player, String rank) {
        members.put(player.getUniqueId(), rank);
    }
    
    public void removeMember(Player player) {
        if (members.containsKey(player.getUniqueId()()))    //If the hashmap contains the player, we remove him.
            members.remove(player.getUniqueId()());       //pretty self-explanatory.
    }
    
    public String getRank(Player player) {
        if (members.containsKey(player.getUniqueId()())
            return members.get(player.getUniqueId()());
        return null;
    }
    


    Now, we're pretty much done with the Team class, but we need one more very important class, a so called "TeamFactory"

    2.: Creating a Factory (or Manager, wichever you like better)

    This part is where most of the people can't get it right. They often use their main class, to store the Team and Faction objects. You should always make a separate class, to manage these stuff.

    The basics:

    To achieve this, we use a design pattern called factory (if you want to learn more about this type of stuff, go to this site). This basically means, we have a lot of small classes containing information about only themself, and one bigger class, wich effectively controls/manages them (imagine it as a particle system in modern game engines), or allows you to easily create them.
    The class needs to be able to...
    • Create new teams
    • Store the teams
    • Manage the players in the current teams (adding and removing them)
    • Communicate between the teams and the actual plugin's logic
    • Manage memory (by deleting unused objects)
    This class can be made into a singleton, because we only have one instance of it ever, but I leave that choice to you.

    When we create a team, we need to first create the team object, then store it in a HashMap with their names as the keys. We also need a HashMap, to store the connection between the players and the teams. This way it's easier to find a specific player's team rather than going trough all of the teams, getting the members of them, and checking, if the current player is part of it.

    Code:
    private HashMap<String, Team> teams = new HashMap<>();
    private HashMap<UUID, String> players = new HashMap<>();
    
    public boolean createTeam(Player owner, String name) {  //We return true, if the proccess was succesfull, else we return false
        if (players.containsKey(owner.getUniqueId()))  //First we check if the player is already in a team
            return false;
        Team team = new Team(owner, name);   //Then we create the new team,
        teams.put(name, team);    //we add the team to the teams hashmap,
        player.put(owner.getUniqueId(), name);   //and we set the player's team to the new one.
        return true;
    }
    
    public boolean addPlayer(Player player, String name, String rank) { We return boolean for the same reason as above
        if (teams.containsKey(name) && !players.containsKey(player.getUniqueId()())) {     //First, we check if the team exists and the player isn't in a team currently,
            Team team = teams.get(name);  // then we add the player to the team, and set the player's team to the new one
            team.addPlayer(player, rank);
            players.put(player.getUniqueId(), name);
            return true;
        }
        return false;
    }
    
    public void removePlayer(Player player) {
        if (players.containsKey(player.getUniqueId())) {  //We check, if the player has a team
            String name = players.get(player.getUniqueId());  //We temporarily store the team's name
            players.remove(player.getUniqueId());  //We set the player's team to none
            Team team = teams.get(name);  //We get the name (This is why we needed it's name)
            String rank = team.getRank(player);  //We get the rank of the player again, temporarily
            team.removeMember(player);  //we remove the player from the teams meber list
            if (rank.equals("owner")) {  //Then, for the sake of ease, if the player was the owner, we delete the team
                Set<UUID> members = team.getMembers();
                for (UUID uuid : members) {
                    players.remove(uuid);   //We get the members, and we set everyone's team to none
                }
                teams.remove(name);  //Lastly, we delete the team itself
            }
        }
    }
    
    //Note: If you have a Rank system, you can make it, so the guy with the highest rank will be the new owner, if you want to
    
    public boolean isInTeam(Player player) {
        return players.containsKey(player.getUniqueId());
    }
    
    public String getTeam(Player player) {
        if (players.containsKey(player.getUniqueId()))
            return players.get(player.getUniqueId());
        return null;
    }
    
    public Set<String> getTeams() {
        return teams.keySet();
    }
    
    And this is it, we have everything what we need, a system, to store team objects, and a way to manage them well organized, and it takes up only about 90 lines of code. But, there are still some things left to speak about.

    3.: Optional stuff:

    1.: Making the TeamFactory singleton:

    You probably noticed, that if you don't have a multi "team-system" system (maybe one for factions, and another for antoher minigame), then you only need 1 instance of the TeamFactory, so you can make it a singleton (if you're too lazy to pass it around in a constructor, sigh*). I personally doesn't like singletons, because they're just a lazy method to get the same instance in every class, but again, I leave this decision to you.

    The code you need for this is pretty simple, and has nothing different than a regular singleton.
    Code:
    public static instance;  //This needs to be static and you need to give it a value in the constructor.
    
    public TeamFactory() {
        instance = this;
    }
    
    public static TeamFactory getInstance(){
        return instance;
    }
    

    2.: Dueling system:

    There is a not very slight chance, you want to create a dueling system rather than a normal team system. The code I provided is almost okay for it, but you need to change some things.
    1. You need to have a start method in the Duel class, wich teleports the players to the correct locations.
    2. In top of that, you will need an end method, wich teleports them back to where they were, so you need to store their original location too.
    3. You don't need a hashmap nor an ArrayList in the Duel class, only 2 fields to store the 2 players.
    4. If one of the players dies or logs off, you need to end the duel, and set the other player as a winner.
    5. You can (if you want to) save their inventories, and give them new ones, so they don't lose their stuff.
    This is pretty much all you need to change, they're all easy to implement, so you can make them yourself too (if you can't figure out something, just post it in the comments).

    3.: Saving the teams:

    If the server shuts down, everything will be gone, except if you save the data. To achieve this very easily, you need to override the default toString method, and we need to create our own fromString method (it will work as the toString one, but reversed).

    Also, it would be overkill, if we would use a config to save all these (It can be done, but it returns a long long long long file), because no one wants to edit these anyways.

    The team class:

    Let's create our toString and fromString methods in our Team class

    Code:
    @Override 
    public String toString() { 
        StringBuilder builder = new StringBuilder();
        builder.append(name + ";");
        Set<UUID> keySet = members.keySet();
        for (UUID uuid : keySet) {
            builder.append(uuid.toString() + ":" + members.get(uuid) + "|");
        }
        builder.setLength(builder.length() - 1); //We do this to remove the last unneccessary "|".
        return builder.toString();
    }
    //You can implement the saving system however you would like to.
    //I save the player and rank connections by putting a ":" between them, and a | between the players
    //I also put the teams name on the begginning and I put a ";" between it and the rest of the stuff
    //NOTE: Make sure you use a StringBuilder for this, because you seriously can waste memory with just pure strings
    
    public Team fromString(String rawTeam) {
        String[] s1 = rawTeam.split(";");
        this.name = s1[0];
        String[] players = s1[1].split("|");
        for (String player : players) {
            String[] information = player.split(":");
            members.put(UUID.fromString(information[0]), information[1]);
        }
        return this;
    }
    
    This sums up what we need to do with the Team class, let's get to the TeamFactory itself.

    The TeamFactory:

    We need to...

    1. In save, we need to convert every team into a String, and store them in a file
    2. When we load them in, we first create every team, then we put their members in the players hashmap
    Code:
    public void save() {
        File file = new File("plugins/TeamTutorial/saves.txt");
        if (!file.exists()) {
            file.getParent().mkdirs();
            file.createNewFile();
        }
    
        FileWrite fw = new FileWriter(file.getAbsolutePath());
        BufferedWriter bw = new BufferedWriter(fw);
        
        Set<String> keySet = teams.keySet();
        for (String name : keySet) {
            Team team = teams.get(name);
            bw.newLine();
            bw.write(teams.toString());
        }
        bw.close();
    }
    //This method basically writes the String we get out of the teams to a file, each team occupies 1 line
    public void load() {
        BufferedReader r = new BufferedReader(new FileReader("plugins/TeamTutorial/saves.txt"));
        
        String line;
        while ((line = r.readLine()) != null) {
            Team team = new Team().fromString(line);
            teams.put(team.getName(), team);
            Set<UUID> members = team.getMembers();
            for (UUID uuid : members) {
                players.put(uuid, team.getName());
            }
        }
    }
    //This does what I said above, it gets the teams, then put their members back to the player hashmap
    
    You should call these methods in the onEnable and in the onDisable.

    Final

    This concludes this tutorial about making a team system for your plugins. This is my longest tutorial so far (fun fact, even the textbox couldn't handle it correctly), there are probably a lot of errors in it, so if you found one, please let me know about it in the comments.

     
    Last edited: Apr 6, 2016
    ChipDev likes this.
  2. Offline

    Gonmarte

    GG = Good Guide :D
    Too bad you didnt make this sooner, i had to make like 2 hashmaps and 4 arraylist in my first teams plugin to fix all my problems :/
     
    Last edited: Mar 18, 2016
  3. Offline

    WolfMage1

    @mcdorli 1 slight problem, Bukkit (afaik) it's getUniqueId() not getUUID(), other than that it's alright.
     
  4. Offline

    mcdorli

    Yeah, I realized that today, when I was reading trough it, I fix it once I get home
     
  5. Offline

    I Al Istannen

  6. Offline

    Mrs. bwfctower

    So why are you posting it? Did you ask the author if you could?
    I think you meant to say "so they are only accessible through the API methods"
    Using the "whole player object" is just fine as long as you remove it when they disconnect. Or use a WeakHashMap/WeakReference.
    The remove method already checks if the Map contains the key. No need for the if statement.
    Using the string "owner" throughout the class is pretty messy, and is just bad practice. If you want to have an owner (just one) I'd have a field for the owner. Strings for the ranks in general doesn't make for a good API interface, so accepting an enum may be a better choice.
    The #get method will return null if the Map doesn't contain the key, so no need for that check there. Could be a one-liner.
    A bit messy?
     
  7. Offline

    Gonmarte

    @Mrs. bwfctower I think he didnt mean to put that comma...
     
  8. Offline

    Mrs. bwfctower

    Mm yeah. Got it.
     
  9. Offline

    mcdorli

    @Mrs. bwfctower @Gonmarte Fixed

    @Mrs. bwfctower If I would use the player's objects for the hashmap, then I never could delete them, because then I couldn't get the members of a team. That's why I use UUID-s

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Mar 21, 2016
    Gonmarte likes this.
  10. Offline

    Mrs. bwfctower

    It's fine to use UUIDs, just don't say it's because of memory.
     
    Last edited: Mar 22, 2016
  11. Offline

    Gonmarte

    @mcdorli NVM I was not paying attention
     
    Last edited: Apr 6, 2016
  12. Offline

    mcdorli

    To make the code cleaner
     
  13. Offline

    Gonmarte

    NVM I was not paying attention.
     
    Last edited: Apr 6, 2016
  14. Offline

    mcdorli

    Yes, you only need to use that.
     
    Gonmarte likes this.
  15. Offline

    Gonmarte

    Thank you.

    @mcdorli In the teammanager, on the addPlayer function, its not addPlayer, its addMember.
     
    Last edited: Mar 30, 2016
  16. Offline

    Gonmarte

    Bump
    @mcdorli
    Code:
    [LIST=1]
    [*]public static instance; //This needs to be static and you need to give it a value in the constructor.
    [*]
    
    [*]public TeamFactory() {
    [*]    instance = this;
    [*]}
    [*]
    
    [*]public static TeamFactory getInstance(){
    [*]   return instance;
    [/LIST]
    
    You forget to put the name of the class before instance. in the first line.
     
  17. Offline

    Gonmarte

    Bump
    @mcdorli
    I have already found too many "mistakes" (i think) in this tutorial.

    Code:
    [LIST=1]
    [*]public Team fromString(String rawTeam) {
    [*]   String[] s1 = rawTeam.split(";");
    [*]   this.name = s1[0];
    [4]   String[] players = s1.split("|");
    [*]   for (String player : players) {
    [*]       String[] information = player.split(":");
    [*]        members.put(UUID.fromString(information[0]), information[1]);
    [*]   }
    [*]}
    [/LIST]
    
    
    Why are you saying that you want to return team, if you didnt add the return statement?
    Also in the line 4, It is correct split a array? Is that thing possible? Wouldnt you mean to splot the rawTeam?
     
    Last edited: Apr 4, 2016
  18. Offline

    mcdorli

    I don't split an array, I split a String, read it again

    90% of the mistakes where made intentionally, so yno kne stfaoght copies the code
     
    Last edited: Apr 5, 2016
  19. Offline

    Zombie_Striker

    Here's the thing. You either fully explain what you're doing and do not rely on the code to explain what you're doing, or you turn this more into a Lib and release the code in working condition. You can't just say the reader has to do something, not explain the steps on how you will do it, and only include code that is broken.

    What I would recommend is leaving empty parameters and just post comments on what needs to be inputted. That way, they can't copy and paste the code. They would still need to know what they're writing. The thing is, they would not need to troubleshoot and debug in order to figure out why their code isn't working (and by them doing so would make them have to come to the PD forums and create a few threads about their problem).

    BTW: IMHO, adding comments to each line of code is hard to read (especially if you use long comments and don't divide them up onto separate lines).
     
    WolfMage1 and Gonmarte like this.
  20. Offline

    Gonmarte

    I read it again and you are spliting the array called s1.
     
    Last edited: Apr 6, 2016
  21. Offline

    mcdorli

    I thought you meant the other one. I fix it
     
  22. Offline

    Gonmarte

    If your point its to dont let peaple copy, let it stay that way... Although doesnt make sense put errors in a tutorial...
     
  23. Offline

    Gonmarte

    Bump
    @mcdorli
    In your removePlayer method , you are checking if the rank is equal to owner, if so, you want to delete the team. So, you delete all the players from the players map, you remove the teams, from the teams map, but dont you also need to remove all the players of that team in the arrayList of your Team class? Arent they still there? Because you are only deleting the team and the players from the maps....
    I debugged your code and the players (in this case their uuids) are still in the array list. Ofc its impossible to get them, once the team has been deleted from the map, but i advice you to remove the players of that team from the arraylist.
    EDIT: With arraylist i mean hashmap :p i forgot u have a hashmap instead of a list.
     
    Last edited: Apr 8, 2016
  24. Offline

    mcdorli

    I thought it's pretty much unnecessary, as 1.: UUID-s doesn't weight much, 2.: If U remove the team, and set the variable to null, the team will get garbage collected in no time
     
    Gonmarte likes this.
  25. Offline

    Gonmarte

    I know its impossible to get once you deleted the team, but imagine that some1 will use this code in a server with 30k of players. They are creating teams and deleting teams everyday. After months, uuids will weight a bit on the memory.
    You could just add a comment to say that if we want we can remove the players from the list although its unncessary for that 2 reasons.

    EDIT: @mcdorli , just for being sure, this would work to remove all the members of that team right? Idk why, i tested and i think it clears all the keys on the map, but that doesnt make sense, once we are removing only the keys of that team.
    Code:
    team.getMembers.clear();
    
    EDIT: @mcdorli Another reason to do this, once the player uuid will stay in the list, in the team is disbanded and some other team wants to invite that player of the team that was disbanded wont really work, because when u call the addMember method u check if the uuid is already there, so for other words, if the player has already a team.
     
    Last edited: Apr 8, 2016
  26. Offline

    mcdorli

    1: Again, it gets garbage collected almost immedialitely.
    2.: Yes
    3.: I actually dont understand what you mean, I remo e the player from botj collection
     
Thread Status:
Not open for further replies.

Share This Page