Solved 1.16.5 Time based command (ex: you have 10 seconds to /confirm or /deny)

Discussion in 'Spigot Plugin Development' started by LetsGoRidePandas, Jun 10, 2021.

  1. Hi everyone, this is my first time here, so hopefully I formatted everything properly. What I'm looking for is at least a push in the right direction or suggestions on how to do this. So here's what I'm trying to do.

    I'm making a simple town plugin and I'm almost finished. All I need is to make the commands to invite and accept/deny the invitation (so /mayor invite [player] and /towninvite accept|deny). I want to make it so /towninvite only works for the invited player and only works for a specified amount of time like 5 minutes.

    I'ved look into hashmaps and they seem like they might work (kind of like a reverse cooldown) but I'm not sure the best way to implement that for this situation

    I've also looked at persistantdatacontainers and that seems like it would work too but it seems like I can't set an expiration on that.

    Does anyone know how other town plugins do this or at least have any ideas on how to go about this?
     
  2. Strahan

    Benefactor

    You'd want to store a timestamp when the invite was issued, then when you accept it check if stored timestamp >= duration and if so, bounce it as expired. If not, process it.
     
  3. I kinda figured it would be like that. I'm just not sure how to attach that timestamp to the player and then delete it once it expires
     
  4. runTaskLater maybe can help you
     
  5. How have you implemented the invitation itself? If it is an object, I would implement this behavior by storing the system time when each invitation is created in a variable inside the invitation, create a <UUID, Invitation> hashmap, then when a player does /towninvite accept you first check if the hashMap containsKey(UUID) (check if the player has an active invitation), then check if the invitation has expired (compare the stored time to the current time). Then, if it's not expired add the player to the town. Regardless of if it's expired, then delete the UUID key from the hashmap. The only downside of this is that if a player is invited to two different towns, the most recent invitation will overwrite the older one (key can only have one value in the hashmap).
     
  6. try using java Cache object for instance
    Code (Java):
    private final Cache<Player, Invitation> invitation = CacheBuilder.newBuilder()
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .build();
     
    • Informative Informative x 4
    • Useful Useful x 1
  7. Thanks for the suggestions everyone. I'll try some of them out and let you know how it goes
     
  8. I'm quite sure this is a guava class, if I'm not mistaken.
    It's not too hard to make your own class for it, I'd personally avoid using external libraries if you don't have to.

    If you don't want to make your own cache object, or if you don't want to use Guava's cache object, you could do something like:
    Code (Text):
    private final Map<UUID, Long> map = new HashMap<>();

    public void containsUUID(UUID uuid) {
        if(map.containsKey(uuid) && System.currentTimeMillis() - map.get(uuid) >= 0) {
            this.map.remove(uuid);
            return false;
        }

        return true;
    |

    public void registerUUID(UUID uuid, long cooldown) {
        this.map.put(uuid, System.currentTimeMillis() + cooldown);
    }
    (untested piece of code)

    edit: remove uuid from map
     
  9. Strahan

    Benefactor

    Depends how fancy you wanna get. I'd probably make a class called Invitation and use that to map out who is getting the invite, who it is from, what it's for, time tracking and such. Then just have a collection of Invitation objects in a centrally accessible class.
     
    • Agree Agree x 1
  10. Hi Everyone. Just wanted to give an update. I finally got around to testing this yesterday and I did get it to work with a combination of a public hashmap and a bukkit runnable
    Here's where the invite is made, and has the runnable to remove it if ignored for 5 minutes
    Code (Java):
     if (p instanceof Player) {
                                    if (!TownInvites.hasInvite(p.getUniqueId().toString())) {
                                        TownInvites.setInvites(p.getUniqueId().toString(), pm.getTown());
                                        player.sendMessage(ChatColor.GREEN + "You have invited " + p.getName() + " to join " + pm.getTown());
                                        p.sendMessage(ChatColor.DARK_GREEN + player.getName() + ChatColor.GREEN + " has invited you to join " + ChatColor.DARK_GREEN + pm.getTown());
                                        p.sendMessage(ChatColor.DARK_GREEN + "use /towninvite <accept|deny> within " + ChatColor.GREEN + "5 minutes " + ChatColor.DARK_GREEN + "to respond.");
                                        new BukkitRunnable() {

                                            @Override
                                            public void run() {
                                                // What you want to schedule goes here
                                                if (TownInvites.hasInvite(p.getUniqueId().toString())) {
                                                    TownInvites.removeInvite(p.getUniqueId().toString());
                                                }
                                            }

                                        }.runTaskLater(BuxTowns.getInstance(), 20L * 60 * 5);
                                    }else{
                                        player.sendMessage(ChatColor.RED + "That player already has an active town invite");
                                        p.sendMessage(ChatColor.RED+player.getName()+" has tried to invite you to join "+tm.getName()+" but you already " +
                                                "have an active invite from "+TownInvites.getTown(p.getUniqueId().toString()));
                                        p.sendMessage(ChatColor.RED+"Please do /towninvite <accept|deny> first");
                                    }
                                }
    Here's where the invitation gets accepted/denied (just the relevant part)
    Code (Java):
     boolean hasInvite = TownInvites.hasInvite(player.getUniqueId().toString());
                if (hasInvite){
                    if(args.length==0) {
                        player.sendMessage(ChatColor.GREEN + "You have an active invite from " + TownInvites.getTown(player.getUniqueId().toString()));
                        return true;
                    }
                    else if(args.length==1){
                        if(args[0].equalsIgnoreCase("accept")){
                        //do the stuff
             
    player.sendMessage(ChatColor.GREEN+"You have "+ChatColor.DARK_GREEN+"ACCEPTED "+ChatColor.GREEN+"the invitation of "+TownInvites.getTown(player.getUniqueId().toString()));
                            TownInvites.removeInvite(player.getUniqueId().toString());
    Here's the hashmap

    Code (Java):
    public class TownInvites {

            private static HashMap<String, String> invites = new HashMap<String, String>();

            public static boolean hasInvite(String uuid){
                if(invites.get(uuid)!=null)
                    return true;
                else
                    return false;
            }
            public static String getTown(String uuid){
                return invites.get(uuid);
            }

            public static void setInvites(String uuid, String town){
                invites.put(uuid,town);
            }

            public static void removeInvite(String uuid){
                invites.remove(uuid);
            }
    }
    I may be able to remove the static but I'm not sure if it would be able to read from two different commands otherwise
     
  11. For removing the static methods and fields. You can create an invite manager class (which you already have made, just completely static) and have it in your Main plugin class.
    After that just give the reference to the constructor of the commands and you are done!
     
    • Agree Agree x 2
  12. I dunno, maybe it's just me, but some of these suggestions seem to be way more complicated than necessary.
    1. Make a hashmap with UUID and a Long; Save the UUID and time when they issue the command.
    2. When they do the accept, check the timestamp, (optional: remove the entry from the hashmap), then process the accept if it's within the current time. Note that this also handles the case if they're doing an accept without an invite.
    3. When they log out, remove the entry from the hashmap. This handles the case where they never do the accept and if you didn't remove it in step 2.
    Easy as 1, 2, 3. No manager classes needed. It's like 4 lines of code. You do need to worry about concurrent access if any of the above is not on the main thread.
     
  13. Depending on the situation and how you want to handle it there are shortcuts to be made. I personally dislike having any lists or data in my listeners and command classes. That's why I would use a different class to keep track of the invitations. Especially if you have different kinds of invites.

    That being said, you point out exactly how it should be done logic wise.
     
  14. Strahan

    Benefactor

    Only problem with that is it only allows for one invitation at a time. That's why I'd make an object for each invitation then store a collection of them, so they won't overwrite or block each other.
     
  15. Oh, one invitation per player. Good point.