Resource Minigame Framework

Discussion in 'Spigot Plugin Development' started by FiXed, May 31, 2016.

  1. ~~ This framework was built in 1.8.8, I have no garuntee of it working for other versions ~~

    So you want to make a minigame but don't know where to start, well that's the case with quite a few people. This will show you how to make an abstract class to handle all minigame arenas that you'd like to set up. For now this framework will only include FFA minigames but I will add to it in the future. This framework will include everything from listening to players join, cancelling damage pregame, cancelling block breaking for players in the minigame and click to join signs. This will also cover some examples as to how to integrate the framework into your server.

    First, let's make the basic class.

    We will call this class MinigameFFA, since that just seems like a nice class name. We will need a few methods for these classes, these methods will be abstract since we won't want to use them inside of the API, rather inside the arena extending this API. One will be called startGame(), one startPreGame(), and endGame(). Now we will make these all void since we don't want them returning anything. This is what your class should look like right now.
    Code (Java):
    package $__PackageName;

    public abstract class MinigameFFA {
       
        public abstract void startPreGame();
        public abstract void startGame();
        public abstract void endGame();
       
    }
    We want this class abstract cause we don't want new instances of this class anywhere else, just classes that extend it to use its features (if you don't know what abstraction is please relearn basic Java as it's a pretty simple concept). This class is a great start, now we're going to put in a constructor to basically tell the class what to do, in this constructor we will need quite a few variables, I'll list them out for us:



      • Main sign (location) -- the one they click to get into the game.
      • Info sign (location) -- the sign that shows info about the game.
      • Lobby spawn (location) -- where they will go when they enter the arena.
      • Max players (Integer) -- how many players can this game hold.
      • Min players (Integer) -- how many players does this game have to hold.
    Now lets make our constructor set all of these variables as needed, it may seem a bit hectic and out of place now but you will understand how to use it soon, now we want to listen for events in here right? So lets let this class implement Listener and register the event, for this we will need to add a Plugin into the constructor as well, it should look like this.
    Code (Java):
    package $__PackageName;

    public abstract class MinigameFFA implements Listener{
       
        public final Location mainSignLoc;
        public final Location infoSignLoc;
        public final Location lobbySpawnLoc;
        public final int MAX_PLAYERS;
        public final int MIN_PLAYERS;
        public MinigameFFA(Location mainSign, Location infoSign, Location lobbySpawn, int max, int min, Plugin plugin) {
            this.mainSignLoc = mainSign;
            this.infoSignLoc = infoSign;
            this.lobbySpawnLoc = lobbySpawn;
            this.MAX_PLAYERS = max;
            this.MIN_PLAYERS = min;
            Bukkit.getPluginManager().registerEvents(this, plugin);
        }
       
        public abstract void startPreGame();
        public abstract void startGame();
        public abstract void endGame();
       
    }
    Great! now we have a lot of we need already set up, now to make the signs actually work we need to find the signs in the world and if not throw an error, however we won't throw an error we will just disable the arena, so we will add a boolean called "disabled" and set it to false at first. Now before we register the listener we will check for the signs then if it gets disabled we can stop it from registering. We will also want to send the console a nice message as to why it's disabled. It should look like this with the new disabled info with it, explaining it is easy, we first get the block at the location of the bukkit world the location is in, then we find the blockstate and if it's a sign, then we have what we want. For this we will also need 2 more variables which should hold the signs, we can name them mainSign and infoSign. It should look like this.
    Code (Java):
    package $__PackageName;

    public abstract class MinigameFFA implements Listener {

        public final Location mainSignLoc;
        public final Location infoSignLoc;
        public final Location lobbySpawnLoc;
        public final int MAX_PLAYERS;
        public final int MIN_PLAYERS;
        public Sign mainSign;
        public Sign infoSign;
        public boolean disabled = false;

        public MinigameFFA(Location mainSign, Location infoSign, Location lobbySpawn, int max, int min, Plugin plugin) {
            this.mainSignLoc = mainSign;
            this.infoSignLoc = infoSign;
            this.lobbySpawnLoc = lobbySpawn;
            this.MAX_PLAYERS = max;
            this.MIN_PLAYERS = min;
            if (!(mainSign.getWorld().getBlockAt(mainSign).getState() instanceof Sign)) {
                this.disabled = true;
                System.out.println("Arena disabled because it's missing the main sign.");
            } else
                this.mainSign = (Sign) mainSign.getWorld().getBlockAt(mainSign).getState();
            if (!(infoSign.getWorld().getBlockAt(infoSign).getState() instanceof Sign)) {
                this.disabled = true;
                System.out.println("Arena disabled because it's missing the info sign.");
            } else
                this.infoSign = (Sign) mainSign.getWorld().getBlockAt(infoSign).getState();
            if (!disabled) {
                Bukkit.getPluginManager().registerEvents(this, plugin);
            }
        }

        public abstract void startPreGame();

        public abstract void startGame();

        public abstract void endGame();

    }
    Beautiful! now we have almost everything set up, if you're having trouble following along please read the updates over again and the descriptions to them and try to follow along with all of this. Next we will want to have a private enum in the class called GameStatus, so we can check the game's status. Now this will get a bit tricky since we need to have the extending class set the status when they start game or start pre game but I'll go over that later. This enum will consist of "DISABLED, IDLE, PRE_GAME, IN_GAME, and ENDING" just so we have an idea of what the game is currently doing. The local inner-class will look a little something like this:
    Code (Java):
        public enum GameStatus {
            DISABLED, IDLE, PRE_GAME, IN_GAME, ENDING
        }
    not much code but still very significant, not let's create a variable called "status" with the type being GameStatus and let's start out with it being "IDLE". However if it gets disabled let's switch it to "DISABLED". Pretty simple concept I don't think I need a class update for that.
    Next we will want to update the signs with info and a kind of description. For this let's create another variable in the constructor called, "name", this will be a string and it will be used to define the name of the arena. We will want to create a simple method called updateSigns() to handle the updating, here's how it should look like once all this is together, this of course will have my personal preference to how I like sings to look:
    Code (Java):
    package $__PackageName;

    public abstract class MinigameFFA implements Listener {

        public final Location mainSignLoc;
        public final Location infoSignLoc;
        public final Location lobbySpawnLoc;
        public final int MAX_PLAYERS;
        public final int MIN_PLAYERS;
        public final String name;
        public Sign mainSign;
        public Sign infoSign;
        public boolean disabled = false;
        public GameStatus status;

        public MinigameFFA(Location mainSign, Location infoSign, Location lobbySpawn, int max, int min, Plugin plugin,
                String name) {
            this.mainSignLoc = mainSign;
            this.infoSignLoc = infoSign;
            this.lobbySpawnLoc = lobbySpawn;
            this.MAX_PLAYERS = max;
            this.MIN_PLAYERS = min;
            this.name = name;
            if (!(mainSign.getWorld().getBlockAt(mainSign).getState() instanceof Sign)) {
                this.disabled = true;
                System.out.println("Arena disabled because it's missing the main sign.");
            } else
                this.mainSign = (Sign) mainSign.getWorld().getBlockAt(mainSign).getState();
            if (!(infoSign.getWorld().getBlockAt(infoSign).getState() instanceof Sign)) {
                this.disabled = true;
                System.out.println("Arena disabled because it's missing the info sign.");
            } else
                this.infoSign = (Sign) mainSign.getWorld().getBlockAt(infoSign).getState();
            if (!disabled) {
                Bukkit.getPluginManager().registerEvents(this, plugin);
                status = GameStatus.IDLE;
                updateSigns() // Added this to update signs when the arena becomes enabled.
            } else
                status = GameStatus.DISABLED;
        }

        public void updateSigns() {
            mainSign.setLine(0, name);
            mainSign.setLine(2, "Right click to");
            mainSign.setLine(3, "join");
            mainSign.update();
            infoSign.setLine(0, "--Info--");
            infoSign.setLine(1, status.toString());
            infoSign.setLine(3, "Players: 0/" + MAX_PLAYERS); // We will be setting this line later as well.
            infoSign.update();
        }

        public abstract void startPreGame();

        public abstract void startGame();

        public abstract void endGame();

        public enum GameStatus {
            DISABLED, IDLE, PRE_GAME, IN_GAME, ENDING;
            public String toString() {
                return super.toString().toLowerCase().replace("_", " "); // added this to make it look nicer
            };
        }

    }
    Great! Now we have nice looking signs, a game status, some start methods, but we want to store the game players in here as well so lets make a new list of players to store them, we will be storing them by UUID so there's no mixups. Let's call it "players", we will also be creating a sign click event to update the players, we will add this by making an interact event and make sure the player is interacting with the main sign so we can allow them to join the arena. Of course making sure they're not joining when it's full or ending. This is how the new interact event should work along with the new thing to allow players to join. This will also start the pre-game if the min players is sufficient. It should look something like this:
    Code (Java):
    package $__PackageName;

    public abstract class MinigameFFA implements Listener {

        public final Location mainSignLoc;
        public final Location infoSignLoc;
        public final Location lobbySpawnLoc;
        public final int MAX_PLAYERS;
        public final int MIN_PLAYERS;
        public final List<UUID> gamePlayers;
        public final String name;
        public Sign mainSign;
        public Sign infoSign;
        public boolean disabled = false;
        public GameStatus status;

        public MinigameFFA(Location mainSign, Location infoSign, Location lobbySpawn, int max, int min, Plugin plugin,
                String name) {
            this.mainSignLoc = mainSign;
            this.infoSignLoc = infoSign;
            this.lobbySpawnLoc = lobbySpawn;
            this.MAX_PLAYERS = max;
            this.MIN_PLAYERS = min;
            this.name = name;
            this.gamePlayers = new ArrayList<UUID>();
            if (!(mainSign.getWorld().getBlockAt(mainSign).getState() instanceof Sign)) {
                this.disabled = true;
                System.out.println("Arena disabled because it's missing the main sign.");
            } else
                this.mainSign = (Sign) mainSign.getWorld().getBlockAt(mainSign).getState();
            if (!(infoSign.getWorld().getBlockAt(infoSign).getState() instanceof Sign)) {
                this.disabled = true;
                System.out.println("Arena disabled because it's missing the info sign.");
            } else
                this.infoSign = (Sign) mainSign.getWorld().getBlockAt(infoSign).getState();
            if (!disabled) {
                Bukkit.getPluginManager().registerEvents(this, plugin);
                status = GameStatus.IDLE;
            } else
                status = GameStatus.DISABLED;
        }

        public void updateSigns() {
            mainSign.setLine(0, name);
            mainSign.setLine(2, "Right click to");
            mainSign.setLine(3, "join");
            mainSign.update();
            infoSign.setLine(0, "--Info--");
            infoSign.setLine(1, status.toString());
            infoSign.setLine(3, "Players: 0/" + MAX_PLAYERS);
            infoSign.update();
        }
       
        public boolean arenaJoin(Player player) {
            if(status == GameStatus.IN_GAME || status == GameStatus.ENDING) {
                player.sendMessage("You cannot join, this arena is not available.");
                return false;
            }
            if(gamePlayers.size() >= MAX_PLAYERS) {
                player.sendMessage("This arena is currently full.");
                return false;
            }
            player.teleport(lobbySpawnLoc);
            player.sendMessage("You have joined arena " + name);
            gamePlayers.add(player.getUniqueId());
            if(gamePlayers.size() >= MIN_PLAYERS && status != GameStatus.PRE_GAME) {
                status = GameStatus.PRE_GAME;
                startPreGame();
            }
            return true;
        }
       
        @EventHandler
        public void onInteract(PlayerInteractEvent event) {
            if (event.getAction() == Action.RIGHT_CLICK_BLOCK
                    && event.getClickedBlock().getLocation().equals(mainSignLoc))
                arenaJoin(event.getPlayer());
        }

        public abstract void startPreGame();

        public abstract void startGame();

        public abstract void endGame();

        public enum GameStatus {
            DISABLED, IDLE, PRE_GAME, IN_GAME, ENDING;
            public String toString() {
                return super.toString().toLowerCase().replace("_", " ");
            };
        }

    }
    Now to finish up the super class we will add a player quit event, to make sure that the game updates with players if they leave. We will also add a damage and break/place event to cancel if the player is in the arena the updates should look like this.
    Code (Java):
        @EventHandler
        public void onDamage(EntityDamageEvent event) {
            if(gamePlayers.contains(event.getEntity().getUniqueId()) && status != GameStatus.IN_GAME) {
                event.setCancelled(true);
            }
        }
       
        @EventHandler
        public void onPlace(BlockPlaceEvent event) {
            if(gamePlayers.contains(event.getPlayer().getUniqueId()) && status != GameStatus.IN_GAME) {
                event.setCancelled(true);
            }
        }
       
        @EventHandler
        public void onBreak(BlockBreakEvent event) {
            if(gamePlayers.contains(event.getPlayer().getUniqueId()) && status != GameStatus.IN_GAME) {
                event.setCancelled(true);
            }
        }
       
        @EventHandler
        public void onQuit(PlayerQuitEvent event) {
            if(gamePlayers.contains(event.getPlayer())) {
                gamePlayers.remove(event.getPlayer());
                if(gamePlayers.size() < MIN_PLAYERS)
                    status = GameStatus.IDLE;
            }
        }
    So let's create an example class, I've premade a little setup class to show, it doesn't have anything in it except for a couple of locations and sizes to basically start it up.
    Code (Java):
    package framework;

    import org.bukkit.Bukkit;
    import org.bukkit.Location;

    import $__PackageName.MinigameFFA;

    public class ExampleArena extends MinigameFFA {

        public ExampleArena() {
            super(new Location(Bukkit.getWorld("world"), 163, 71, 79), new Location(Bukkit.getWorld("world"), 163, 70, 79),
                    new Location(Bukkit.getWorld("world"), 163, 67, 79), 1, 5, Main.getPlugin(), "Example");
        }

        @Override
        public void startPreGame() {
            super.updateSigns();
            Bukkit.broadcastMessage("");
        }

        @Override
        public void startGame() {

        }

        @Override
        public void endGame() {

        }

    }
    To explain this, it extends MinigameFFA, it sets up the constructor with some preset locations. Here's how the signs look in game:
    http://i.imgur.com/FRgGKVI.png <== just a little pic of the signs
    http://i.imgur.com/xVakuBk.png <== when I right click the sign
    http://i.imgur.com/jILi2aQ.png <== the sign after I join

    The final Minigame framework class:
    http://hastebin.com/enoleraley.avrasm
    (kinda hit the character limit so I needed to put the code in hastebin instead)

    This is pretty much it, except for a clean method we should make in the super class which would look like this:
    Code (Java):
        public void clean() {
            gamePlayers.clear();
            updateSigns();
            status = GameStatus.IDLE;
        }

    Now this code is will NOT work on it's own, you have to look at the methods and create the mechanics yourself but it should order and create your minigame quite well, the framework is simple yet effective. I hope you found this useful, if so please mark this thread as so and if you have questions or ideas as to what I should add then I'm all ears and will add just about anything you need. Just pm me or write it in the description, if I didn't explain a part well just let me know and I will go more in depth about it.

    I hope everyone got something out of this as it took me quite a bit of time to make, this isn't the best tutorial (most likely) but I think it should get the job done. I hope this was useful and informative to people not knowing where to start when they want to make a minigame.

    If you think this is spoonfeeding than I don't agree, if they just copy the code they won't know where else to go or how to actually make it work, however if you know what you're doing with Java and have read over the class with descriptions of updates you will most likely understand what is going on and why.

    If I made an error somewhere please let me know and I will fix it up, or just change it in your version because you should NOT be just copying the code.
     
    • Useful Useful x 5
    • Winner Winner x 4
  2. "So you wanna make a minigame..."

    no 1 can make a minigame coz ur all crap coderz even that kek who mate fetherbord is gay
    u should all give up on coding coz ur all crap

    Joking, this is a great resource, you will help a lot of people out with this Exotic :p
     
  3. very nice guide,
     
  4. Very nice guide, this will be helpful to at least most new developers here who wish to extend their knowledge from just ordinary plugins to mini games.
     
  5. If you're going to write a guide, I'd suggest following all proper Java conventions, for example: all class field members should be private, or in some cases protected. If you need public access to a field, use getters and/or setters.
    I would also suggest changing the title to an overall description of the guide, rather than posing a rhetorical question so people who search for "Minigame framework tutorial blah whatever" get your guide as a result.
     
  6. Thank you, thank you, and yes I agree, this wasn't made for advanced minigames or advanced coders but was more towards the newer and more basic audience to help introduce them to minigames, I have just gotten into them and thought I'd like to expand my knowledge as to how I've learned to the community. I think I did pretty well.
    The tutorial given in my opinion is fine, I'll change it all to getters and setters with a private modifier in time, I just felt like writing up a quick resource and not spending a lot of time to make sure every single piece of code is correct convention, and if I did that and showed the getters/setters I would reach my character limit and it wouldn't let me post the resource. (I tried it already)

    For now public modifiers are fine and are pretty basic, if someone wants to use the pseudo code I provided and change it than that would fit into the category of "you should do this".
     
  7. Your MiniGameFFA Class has way, way, WAY too much responsibility. Single responsibility principle. Something a lot nicer would be a clearer separation of concerns, for which presently there is very little. A suggested approached would be to maybe utilize a few separate controllers for each part of the logic of your overall game, maybe like so:
    • A game state controller, for controlling timings, when the game begins, ends, etc
    • A locational controller, which will place players according to the game state
    • A data controller, which will take responsibility over what players are present in the game
    The reason why this is better, especially in the case of minigames, is that it promotes a more extensible structure, which really helps support larger projects (As minigames generally tend to be for the average developer on spigot).
     
    • Agree Agree x 1
  8. Again, I'm not looking to please the more advanced crowd however in the Example class I made which utilizes this takes care of everything, of course if you'd like you can change the super class to handle everything and just input the locations and size into the super constructor of the "example class" and use it that way. This isn't a final product by any means, I didn't write this for people to copy and paste I wrote this to give people an idea of how to start and where to go. Everything you stated can be controlled in the example class but can be moved over to the super class. Thanks for your feedback though I appreciate it.

    For the more advanced structure, I do use a different structure for minigames myself but this is the most basic of ways to create a mini game, which I think will help beginners move into minigame making and yes I know most people can make them but a lot can't as well, which is the target audience.
     
  9. You're missing the point a little, I totally understand that you just wanted to create something that people can refer to as a working example that they can build on, but something that would improve the overall usage of that while still being just as simple is a bit of clear cut separation.

    Ask yourself this, if you wanted to modify the location code, where people spawn, etc, where in your program do you go? You go to the big MiniGameFFA and you starting digging around for where that list is used. If you think about the example I provided, you go straight to the location class and just read the interface that it provides, which will help you understand what you need to do.

    Look, I 100% appreciate your contributions, I really like seeing other people help newer developers, but there are some small changes that you can make (particularly breaking the big class into some smaller ones) which will present a huge increase in the usefulness of this resource :)
     
    • Agree Agree x 1
  10. I would love to do that and I 100% agree on what you're saying, I would have done it if it weren't for one thing blocking me... character limit.

    I completely understand what you're getting at, in my plugins I use the same structure except for this it's quite impossible to do it without breaking character limit early, breaking each class up, creating examples for each class, I'd just hit character limit very fast. If I could than I would write a much more advanced and laid out resource for Minigame creation, if you could link me to a place where I could type one up than I'd be more than happy to create the framework that way and describe it in all aspects, I will spend 5+ hours to explain every detail of it if you can find me a place :)
     
  11. Awesome guide. Will surely help starters!
     
  12. Thank you, I plan to find a place to post a more extensive resource and link it here with a better and more advanced structure but for now this should do it :)
     
  13. I have my own extensive Minigame API aswell to make a few more miniagmes.

    This one is nice. But it's just weird.

    Like why do you need to set a sign location or an info sign location just to have it work? And why only one? Why not just store the sign locations into a array list. And a listener that when a player types in a certain set of values will hook the array list. Then when they click it has them join. Like what if they have 10 signs. I just don't understand that if anything then it should not even need a sign to start and someone would need to implement it a command to join


    Also All created miniagmes will reset totally when you restart :p
     
  14. This is more of a pseudo code framework for devs to mess around with, this is by no means a final product, I meant for it more to be a starting off point for other developers.
     
    • Like Like x 1
  15. Ah ok. Very nice then! I learned also though a Minigame tutorial like this. It taught me object oriented stuff. Your right I looked to deep into it sorry :p

    Very nice! Uses abstraction and teaches how to extend and add new features to different arena types.
     
  16. Thank you! That is exactly what I was going for, I wanted to allow other users to know how to use aspects of abstraction to accomplish a task easily.
     
    • Like Like x 1