Search code examples
javapluginsminecraftbukkit

How to use getConfig() to edit config.yml in a method outside of the Main class


I've had a persistent issue for the path couple days that no amount of searching has helped me overcome. I only recently began dabbling into Bukkit plugin development, and all this simple plugin current does is set join/quit messages and AFK status. However, the latter part is giving me issues.

My Spigot server console is outputting Could not pass event PlayerJoinEvent to ServerBasics when a player joins and Unhandled exception executing command 'afk' in plugin ServerBasics when /afk is typed by a player.

The logic: Main class's onEnable() sets command executor for AfkCommand and event listener for JoinListener, which has the main focus of monitor when a player joins. At this point, it's supposed to call the toggle() method in ToggleAFK when a player joins, passing the player's name and a false boolean to add an entry for them in the config.yml and make sure they and not afk, should they type the /afk command later. ToggleAFK should then save the config with afk.[player] equaling false.

At the same time, AfkCommand should also call ToggleAFK with the player's name and true/false parameter, and ToggleAFK should handle it in the same way. However, it's failing to do so.

I've set broadcast messages all through the code to see when it's misfiring. When a player joins the server, JoinListener broadcasts init and joined but not toggled, and AfkCommand broadcasts a, b, and c but not d, so we know the problem is in ToggleAFK. ToggleAFK does not broadcast instanced or g.

The problem seems to be with me creating an instance of the Main class, but I don't know how else to enable the getConfig() commands in another class, and it wouldn't be at all neat if all my config.yml edits were handled in Main. I've read that the Main class shouldn't be instanced again, but I also found a tutorial in which it was instanced in the way I've done it in ToggleAFK, yet mine doesn't work (although it seems just fine when I do that in AfkCommand).

What are my options? Is there a different way to edit the config file from other classes? Am I forced to edit them in Main? Is the issue because ToggleAFK is called two classes after Main instead of just one? Any help is appreciated.

My Main class:

package com.timothyreavis.serverbasics;

import org.bukkit.plugin.java.JavaPlugin;

public class Main extends JavaPlugin {

    @Override
    public void onEnable()
    {
        getCommand("afk").setExecutor(new AfkCommand(this));

        getServer().getPluginManager().registerEvents(new JoinListener(null), this);
    }

}

My JoinListener class (Sets afk to false when player joins. I'll soft code the join messages and stuff later):

package com.timothyreavis.serverbasics;

import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;

import net.md_5.bungee.api.ChatColor;

public class JoinListener implements Listener
{
    // create ToggleAFK class instance
    ToggleAFK afk;
    public JoinListener(ToggleAFK instance) {
        afk = instance;
    }

    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent event)
    {
        Bukkit.broadcastMessage("init");
        // Player joins
        Player player = event.getPlayer();
        Bukkit.broadcastMessage("joined");
        afk.toggle(player, false);
        Bukkit.broadcastMessage("toggled");
        // set player entry as not afk in config.yml
        Bukkit.broadcastMessage("h");

        if (!player.hasPlayedBefore()) {
            // if player is new
            event.setJoinMessage(ChatColor.DARK_PURPLE + "Welcome " + ChatColor.GREEN + event.getPlayer().getName() + ChatColor.DARK_PURPLE + " to the server!");

            // tell player to read rules
            event.getPlayer().sendMessage(ChatColor.YELLOW + "Hi, " + player.getName() + "! " + Bukkit.getServerName() + " is a rather unique server with custom plugins, so please read our rules and commands before getting started.");
        } else {
            // if player isn't new
            event.setJoinMessage(ChatColor.GREEN + "[+] " + event.getPlayer().getName());
        }
    }
    @EventHandler
    public void onPlayerQuit(PlayerQuitEvent event)
    {
        // Player quits
        event.setQuitMessage(ChatColor.RED + "[-] " + event.getPlayer().getName());
    }
}

My ToggleAFK class (this should edit the config.yml file when called):

package com.timothyreavis.serverbasics;

import org.bukkit.Bukkit;
import org.bukkit.entity.Player;

import net.md_5.bungee.api.ChatColor;

public class ToggleAFK {
    // create Main class instance
    Main plugin;
    public ToggleAFK(Main instance) {
        plugin = instance;
        Bukkit.broadcastMessage("instanced");
    }

    public Boolean toggle(Player player, Boolean status) {
        Bukkit.broadcastMessage("g");
        plugin.getConfig().set("afk." + player, status);
        plugin.saveConfig();
        if (status) {
            Bukkit.broadcastMessage(ChatColor.DARK_PURPLE + player.getName() + " is now AFK.");
        } else {
            Bukkit.broadcastMessage(ChatColor.DARK_PURPLE + player.getName() + " is no longer AFK.");
        }
        return true;
    }
}

And my AfkCommand class (handles /afk and calls ToggleAFK):

package com.timothyreavis.serverbasics;

import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
//import org.bukkit.event.player.PlayerMoveEvent;

public class AfkCommand implements CommandExecutor {
    // create Main class instance
    Main plugin;
    public AfkCommand (Main instance) {
        plugin = instance;
    }
    // create ToggleAFK class instance
    ToggleAFK afk;
    public AfkCommand (ToggleAFK instance) {
        afk = instance;
    }

    @Override
    public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
        Bukkit.broadcastMessage("a");
        if (sender instanceof Player) {
            Player player = (Player) sender;
            Bukkit.broadcastMessage("b");
            if (plugin.getConfig().getBoolean("afk."+sender) == false) {
                Bukkit.broadcastMessage("c");
                afk.toggle(player, true);
                Bukkit.broadcastMessage("d");
            } else {
                afk.toggle(player, false);
                Bukkit.broadcastMessage("d");
            }
        } else {
            Bukkit.broadcastMessage("e");
            return false;
        }
        // if command is used correctly, return true
        Bukkit.broadcastMessage("f");
        return true;
    }

}

Solution

  • Your code, as far as I can tell, correctly passes references of the main plugin class to constructors of other classes, and there seem to be no creations of new instances of your main plugin class, so there should be no hurdles there. When I tested your code, both when a player joins and when using the /afk command, a NullPointerException is thrown because the afk fields in the AfkCommand and JoinListener class are never initialized.

    Since your JoinListener class takes a ToggleAFK object as an argument in its constructor, instead of initializing it with new JoinListener(null) like your code currently does (which produces the NullPointerException when the line afk.toggle(player, false) is reached), I passed a new ToggleAFK object to the constructor with new Listener(new ToggleAFK(this)).

    The alternate constructor of your AfkCommand class that would set the afk field seems to never be used. You can initialize the afk field here by again creating a new ToggleAFK object this time using the reference to your main plugin class already provided in the other constructor of the AfkCommand class (the one you're using in the onEnable() method), like so:

    Main plugin;
    ToggleAFK afk;
    public AfkCommand (Main instance) {
        plugin = instance;
        afk = new ToggleAFK(plugin);
    }
    

    These changes seem to have made the code work as you intended when I tested it.

    Two things to note: It might be better to save the player's AFK status in the config file using their unique ID (player.getUniqueID()), as right now the set method is calling toString() on the Player object which results in the entry starting like this: CraftPlayer{name=Steve}. This not only looks odd but uses the player's name, which can potentially change. Also, it might be easier to make the toggle method static and place it in a Util class so that you don't have to create all the instances of the ToggleAFK class to access that method. Something like this might work for you:

    import org.bukkit.Bukkit;
    import org.bukkit.ChatColor;
    import org.bukkit.entity.Player;
    
    public class Util {
    
        private static Main plugin;
    
        public static void initialize(Main instance) {
            plugin = instance;
        }
    
        public static void toggleAFK(Player player, boolean isAFK) {
            if (plugin == null) {
                throw new IllegalStateException("This class has not been initialized yet!");
            }
            plugin.getConfig().set("AFK." + player.getUniqueId(), isAFK);
            plugin.saveConfig();
    
            String message = ChatColor.DARK_PURPLE + player.getName();
            message += (isAFK) ? " is now AFK." : " is no longer AFK.";
            Bukkit.broadcastMessage(message);
        }
    
    }
    

    For this specific example you'd need to "initialize" the Util class, preferably in your onEnable() method with Util.initialize(this).