I'm receiving the error Plugin already initialized!
. I already know that this means that I'm initializing my main class twice and Bukkit/Spigot doesn't like this.
I cannot seem to figure out where I'm doing this, or what portion of my code is doing this. I've, in order of operation, combed through my code in order: QLottery.java
, CommandManager.java
, Statistics.java
, and then PlayerStats.java
. From what I can tell, I am only carrying an already-initialized instance of QLottery and not initializing another.
The compiler gives no errors, and this only seems to happen when I'm running the /ql stats
command. All other commands work with no issues.
Here's the relevant code:
QLottery.java (main class):
package com.quietwind01;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import com.quietwind01.Listeners.CommandManager;
import com.quietwind01.Utils.EconomyUtils;
import com.quietwind01.YAML.PlayerStats;
public class QLottery extends JavaPlugin {
public ConcurrentHashMap<String, Integer> playerTickets;
public double totalPool;
public double poolDefaultAmount = getConfig().getDouble("pool-default-amount");
public int drawInterval = getConfig().getInt("draw-interval");
private final AtomicInteger interval;
ConcurrentHashMap<String, String> allTickets = new ConcurrentHashMap<>();
private int taskID = -1;
public String chatPrefix = "§5[§a§l§nQLottery§5]§f "; // Get plugin name for chat
private PlayerStats stats;
public QLottery() {
totalPool = poolDefaultAmount;
interval = new AtomicInteger(drawInterval);
}
@Override
public final void onEnable() {
try {
// Initialize hashmaps
playerTickets = new ConcurrentHashMap<>();
// Create the config file if it doesn't exist
saveDefaultConfig();
// Create the stats.yml file if it doesn't exist
saveStatsYAML();
// Send startup messages
startMessages(chatPrefix);
// Start DrawInterval timer
startMainTimer(interval);
// Register commands
getCommand("ql").setExecutor(new CommandManager(this));
} catch (Exception e) {
Bukkit.getServer().broadcastMessage(chatPrefix + "§cSomething went severely wrong. Please check logs. QLottery has been disabled.");
e.printStackTrace();
disablePlugin();
return;
}
}
}
CommandManager.java:
package com.quietwind01.Listeners;
import com.quietwind01.QLottery;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class CommandManager implements CommandExecutor {
private final QLottery plugin;
boolean debugMode = false;
public CommandManager(QLottery plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
// Check args length
if (args.length == 0) {
sender.sendMessage(plugin.chatPrefix + "§cUsage: /ql <subcommand>");
return true;
}
// Get parent subcommand
String parentSubcommand = args[0].toLowerCase();
String childSubcommand = "";
if (args.length > 1) {
childSubcommand = args[1].toLowerCase();
}
if (debugMode == true) {
Player player = (Player) sender;
player.sendMessage(plugin.chatPrefix + "Parent subcommand: " + parentSubcommand);
player.sendMessage(plugin.chatPrefix + "Child subcommand: " + childSubcommand);
player.sendMessage(plugin.chatPrefix + "Args Length: " + args.length);
player.sendMessage(plugin.chatPrefix + "Is Ticket Command: " + "ticket".equals(parentSubcommand));
player.sendMessage(plugin.chatPrefix + "Is Pool Command: " + "pool".equals(parentSubcommand));
player.sendMessage(plugin.chatPrefix + "Is Stats Command: " + "stats".equals(parentSubcommand));
}
// Ticket subcommands
if ("ticket".equals(parentSubcommand)) {
switch (childSubcommand) {
case "buy":
return new TicketBuy(plugin).onCommand(sender, command, label, args);
case "sell":
return new TicketSell(plugin).onCommand(sender, command, label, args);
case "cost":
return new TicketCost(plugin).onCommand(sender, command, label, args);
default:
sender.sendMessage(plugin.chatPrefix + "§cUnknown subcommand. Usage: /ql <subcommand>");
}
}
// Tickets command
if ("tickets".equals(parentSubcommand)) {
return new TicketsOwned(plugin).onCommand(sender, command, label, args);
}
// Pool subcommands
if ("pool".equals(parentSubcommand)) {
return new TotalPool(plugin).onCommand(sender, command, label, args);
}
// Stats command
if ("stats".equals(parentSubcommand)) {
return new Statistics(plugin).onCommand(sender, command, label, args);
}
return true;
}
}
Statistics.java:
package com.quietwind01.Listeners;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import com.quietwind01.QLottery;
import com.quietwind01.YAML.PlayerStats;
public class Statistics implements CommandExecutor {
private final QLottery plugin;
private final PlayerStats stats;
public Statistics(QLottery plugin) {
this.plugin = plugin;
this.stats = new PlayerStats();
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage(plugin.chatPrefix + "§cOnly players can use this command.");
return true;
}
Player player = (Player) sender;
// Check that player has proper permissions
if (!player.hasPermission("qlottery.stats")) {
player.sendMessage(plugin.chatPrefix + "§cYou do not have permission to use this command.");
return true;
}
// Debug
//player.sendMessage("TicketBuy Args: " + args.length);
// Check that the command has valid amount of arguments
if (args.length < 1) {
player.sendMessage(plugin.chatPrefix + "§eUsage: /ql stats");
return true;
}
// Check if the two arguments are "pool" and "amount"
if (!args[0].equalsIgnoreCase("stats")) {
player.sendMessage(plugin.chatPrefix + "§eUsage: /ql stats");
return true;
}
// Fetch the stats for the server
double serverTotalPayouts = stats.getServerPayoutTotal();
double serverTotalDraws = stats.getServerDrawsTotal();
double serverTotalWins = stats.getServerWinsTotal();
double lotteryTotalTickets = stats.getTotalTicketsPurchased("__LOTTERY__");
// Player stats
String playerName = player.getName();
stats.createNewPlayer(playerName); // Create the player in the stats file if they don't exist yet
double playerTotalWins = stats.getTotalWins(playerName);
double playerTotalPayout = stats.getTotalAmountWon(playerName);
double playerTotalTickets = stats.getTotalTicketsPurchased(playerName);
// Send the stats to the player
player.sendMessage(plugin.chatPrefix + "§3You have §6" + playerTotalWins + " §3wins, you've purchased §6" + playerTotalTickets + " §3tickets, and you've won a total of §a$" + playerTotalPayout + "§3!");
player.sendMessage(plugin.chatPrefix + "§3The server has drawn §6" + serverTotalDraws + " §3times, paid out §a$" + serverTotalPayouts + " §3and there have been a total of §6" + serverTotalWins + " §3jackpots won! There have been §6" + lotteryTotalTickets + " §3total tickets sold.");
return true;
}
}
PlayerStats.java:
package com.quietwind01.YAML;
import java.io.File;
import java.io.IOException;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
public class PlayerStats extends JavaPlugin {
private File file;
private YamlConfiguration config;
public PlayerStats() {
try {
// QLottery folder
File dir = getDataFolder();
// Create file object
this.file = new File(dir, "stats.yml");
// Get config
this.config = YamlConfiguration.loadConfiguration(file);
} catch (Exception e) {
Bukkit.broadcastMessage("§cCouldn't fetch stats.yml file.");
e.printStackTrace();
}
}
public boolean createNewPlayer(String playerName) {
try {
if (!config.contains("player-stats." + playerName)) {
config.set("player-stats." + playerName + ".total-wins", 0);
config.set("player-stats." + playerName + ".total-amount-won", 0);
config.set("player-stats." + playerName + ".total-tickets-purchased", 0);
config.save(file);
}
return true;
} catch (IOException e) {
Bukkit.broadcastMessage("§cCouldn't create new player (§b" + playerName + "§c).");
e.printStackTrace();
return false;
}
}
/*
* Server stats
*/
public double getServerPayoutTotal() {
return config.getDouble("player-stats.all-time-payout-total");
}
public double getServerDrawsTotal() {
return config.getDouble("player-stats.all-time-draws-total");
}
public double getServerWinsTotal() {
return config.getDouble("player-stats.all-time-wins");
}
public boolean updateServerPayoutTotal(double amountToAdd) {
try {
config.set("player-stats.all-time-payout-total", amountToAdd + getServerPayoutTotal());
config.save(file);
return true;
} catch (IOException e) {
Bukkit.broadcastMessage("§cCouldn't update total payout (server).");
e.printStackTrace();
return false;
}
}
public boolean updateServerDrawsTotal(double amountToAdd) {
try {
config.set("player-stats.all-time-draws-total", amountToAdd + getServerDrawsTotal());
config.save(file);
return true;
} catch (IOException e) {
Bukkit.broadcastMessage("§cCouldn't update total draws (server).");
e.printStackTrace();
return false;
}
}
public boolean updateServerWinsTotal(double amountToAdd) {
try {
config.set("player-stats.all-time-wins", amountToAdd + getServerWinsTotal());
config.save(file);
return true;
} catch (IOException e) {
Bukkit.broadcastMessage("§cCouldn't update total wins (server).");
e.printStackTrace();
return false;
}
}
/*
* Player stats
*/
public double getTotalWins(String playerName) {
return config.getDouble("player-stats." + playerName + ".total-wins");
}
public double getTotalAmountWon(String playerName) {
return config.getDouble("player-stats." + playerName + ".total-amount-won");
}
public double getTotalTicketsPurchased(String playerName) {
return config.getDouble("player-stats." + playerName + ".total-tickets-purchased");
}
public boolean updateTotalWins(String playerName, double amountToAdd) {
try {
config.set("player-stats." + playerName + ".total-wins", amountToAdd + getTotalWins(playerName));
config.save(file);
return true;
} catch (IOException e) {
Bukkit.broadcastMessage("§cCouldn't update total wins. (Player)");
e.printStackTrace();
return false;
}
}
public boolean updateTotalAmountWon(String playerName, double amountToAdd) {
try {
config.set("player-stats." + playerName + ".total-amount-won", amountToAdd + getTotalAmountWon(playerName));
config.save(file);
return true;
} catch (IOException e) {
Bukkit.broadcastMessage("§cCouldn't update total amount won (player).");
e.printStackTrace();
return false;
}
}
public boolean updateTotalTicketsPurchased(String playerName, double amountToAdd) {
try {
config.set("player-stats." + playerName + ".total-tickets-purchased", amountToAdd + getTotalTicketsPurchased(playerName));
config.save(file);
return true;
} catch (IOException e) {
Bukkit.broadcastMessage("§cCouldn't update total tickets purchased (player).");
e.printStackTrace();
return false;
}
}
}
Here's the console stack trace:
org.bukkit.command.CommandException: Unhandled exception executing command 'ql' in plugin Qlottery v1.0.0-RC.3
at org.bukkit.command.PluginCommand.execute(PluginCommand.java:47) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:149) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
at org.bukkit.craftbukkit.v1_20_R3.CraftServer.dispatchCommand(CraftServer.java:887) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at org.bukkit.craftbukkit.v1_20_R3.command.BukkitCommandWrapper.run(BukkitCommandWrapper.java:50) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at com.mojang.brigadier.context.ContextChain.runExecutable(ContextChain.java:73) ~[brigadier-1.2.9.jar:?]
at net.minecraft.commands.execution.tasks.ExecuteCommand.a(SourceFile:29) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.commands.execution.tasks.ExecuteCommand.execute(SourceFile:13) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.commands.execution.UnboundEntryAction.a(SourceFile:8) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.commands.execution.CommandQueueEntry.a(SourceFile:8) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.commands.execution.ExecutionContext.a(SourceFile:107) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.commands.CommandDispatcher.a(CommandDispatcher.java:413) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.commands.CommandDispatcher.performCommand(CommandDispatcher.java:335) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.commands.CommandDispatcher.a(CommandDispatcher.java:322) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.server.network.PlayerConnection.a(PlayerConnection.java:1856) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.server.network.PlayerConnection.lambda$15(PlayerConnection.java:1818) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.util.thread.IAsyncTaskHandler.b(SourceFile:67) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1768) ~[?:?]
at net.minecraft.server.TickTask.run(SourceFile:18) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.util.thread.IAsyncTaskHandler.d(SourceFile:156) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.util.thread.IAsyncTaskHandlerReentrant.d(SourceFile:23) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.server.MinecraftServer.b(MinecraftServer.java:1191) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.server.MinecraftServer.d(MinecraftServer.java:1) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.util.thread.IAsyncTaskHandler.x(SourceFile:130) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.server.MinecraftServer.bl(MinecraftServer.java:1170) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.server.MinecraftServer.x(MinecraftServer.java:1163) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.util.thread.IAsyncTaskHandler.c(SourceFile:139) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.server.MinecraftServer.w_(MinecraftServer.java:1147) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.server.MinecraftServer.w(MinecraftServer.java:1060) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.server.MinecraftServer.lambda$0(MinecraftServer.java:304) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at java.lang.Thread.run(Thread.java:1570) ~[?:?]
Caused by: java.lang.IllegalArgumentException: Plugin already initialized!
at org.bukkit.plugin.java.PluginClassLoader.initialize(PluginClassLoader.java:238) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
at org.bukkit.plugin.java.JavaPlugin.<init>(JavaPlugin.java:55) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
at com.quietwind01.YAML.PlayerStats.<init>(PlayerStats.java:16) ~[?:?]
at com.quietwind01.Listeners.Statistics.<init>(Statistics.java:14) ~[?:?]
at com.quietwind01.Listeners.CommandManager.onCommand(CommandManager.java:74) ~[?:?]
at org.bukkit.command.PluginCommand.execute(PluginCommand.java:45) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
... 29 more
Caused by: java.lang.IllegalStateException: Initial initialization
at org.bukkit.plugin.java.PluginClassLoader.initialize(PluginClassLoader.java:241) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
at org.bukkit.plugin.java.JavaPlugin.<init>(JavaPlugin.java:55) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
at com.quietwind01.QLottery.<init>(QLottery.java:33) ~[?:?]
at jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62) ~[?:?]
at java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502) ~[?:?]
at java.lang.reflect.Constructor.newInstance(Constructor.java:486) ~[?:?]
at org.bukkit.plugin.java.PluginClassLoader.<init>(PluginClassLoader.java:88) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
at org.bukkit.plugin.java.JavaPluginLoader.loadPlugin(JavaPluginLoader.java:145) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
at org.bukkit.plugin.SimplePluginManager.loadPlugin(SimplePluginManager.java:405) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
at org.bukkit.plugin.SimplePluginManager.loadPlugins(SimplePluginManager.java:312) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
at org.bukkit.plugin.SimplePluginManager.loadPlugins(SimplePluginManager.java:121) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
at org.bukkit.craftbukkit.v1_20_R3.CraftServer.loadPlugins(CraftServer.java:430) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.server.dedicated.DedicatedServer.e(DedicatedServer.java:223) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
at net.minecraft.server.MinecraftServer.w(MinecraftServer.java:1000) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
... 2 more
What portion of the above code would cause this? From what I can tell I don't seem to be initializing QLottery
twice...
The plugin loader loads your plugin by creating an instance of QLottery
, which calls the constructor of JavaPlugin
, which initialises your plugin.
You then create an instance of PlayerStats
in Statistics
:
public Statistics(QLottery plugin) {
this.plugin = plugin;
this.stats = new PlayerStats(); // here!
}
This causes the constructor of JavaPlugin
to be called again, and this is where the plugin class loader detects that your plugin has already been initialised and throws the exception you got.
You should not have multiple JavaPlugin
subclasses in the same plugin. It seems like PlayerStats
should just be a regular class. Take the data folder directory as a constructor parameter
public class PlayerStats {
private File file;
private YamlConfiguration config;
public PlayerStats(File dataDir) {
try {
this.file = new File(dataDir, "stats.yml");
// ...
and pass the parameter like this:
public Statistics(QLottery plugin) {
this.plugin = plugin;
this.stats = new PlayerStats(plugin.getDataFolder());
}
If you need to use other methods from JavaPlugin
, consider passing the entire QLottery
instance, in the same way you passed it to Statistics
.
If you actually want to create multiple plugins, you should have multiple plugin.yml files identifying each one, and they should be outputted as separated jars.