I refactored so hard I might as well have rewritten the entire thing.
* Added `/wasteland reload` command, which was trivial as a result of the rewrite. * Added support for using the alternative ranks via `wasteland.prefer-rank`. * Improved configuration format.master
parent
2b4d8c95a2
commit
2f1fb0ca92
|
@ -1,5 +1,4 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'java-library'
|
||||
}
|
||||
|
||||
|
@ -35,4 +34,6 @@ repositories {
|
|||
dependencies {
|
||||
compileOnly 'org.spigotmc:spigot-api:1.16.4-R0.1-SNAPSHOT'
|
||||
compileOnly 'com.palmergames.bukkit.towny:Towny:0.96.2.17'
|
||||
compileOnly 'com.google.guava:guava:21.0'
|
||||
// compileOnly 'com.google.guava:guava:27.1
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package me.jamestmartin.wasteland;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
class CommandWasteland implements CommandExecutor {
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length < 1) {
|
||||
sender.sendMessage("Not enough arguments.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (args.length > 1) {
|
||||
sender.sendMessage("Too many arguments.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!args[0].equals("reload")) {
|
||||
sender.sendMessage("Unknown subcommand: " + args[0]);
|
||||
return false;
|
||||
}
|
||||
|
||||
sender.sendMessage("Reloading...");
|
||||
Wasteland.getInstance().reload();
|
||||
sender.sendMessage("Done.");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package me.jamestmartin.wasteland;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public interface Substate {
|
||||
void register(JavaPlugin plugin);
|
||||
void unregister(JavaPlugin plugin);
|
||||
}
|
|
@ -5,126 +5,113 @@ import java.io.IOException;
|
|||
import java.sql.SQLException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import me.jamestmartin.wasteland.commands.CommandDebugSpawn;
|
||||
import me.jamestmartin.wasteland.commands.CommandDebugSpawnWeights;
|
||||
import me.jamestmartin.wasteland.commands.CommandOfficial;
|
||||
import me.jamestmartin.wasteland.commands.CommandRank;
|
||||
import me.jamestmartin.wasteland.commands.CommandRankEligibleMobs;
|
||||
import me.jamestmartin.wasteland.commands.CommandRanks;
|
||||
import me.jamestmartin.wasteland.commands.CommandSetKills;
|
||||
import me.jamestmartin.wasteland.config.WastelandConfig;
|
||||
import me.jamestmartin.wasteland.listeners.ChatListener;
|
||||
import me.jamestmartin.wasteland.listeners.RankListener;
|
||||
import me.jamestmartin.wasteland.spawns.WastelandSpawner;
|
||||
import me.jamestmartin.wasteland.towny.TownyDependency;
|
||||
import me.jamestmartin.wasteland.config.ConfigParser;
|
||||
import me.jamestmartin.wasteland.store.SqliteDatabase;
|
||||
import me.jamestmartin.wasteland.towny.TownyEnabled;
|
||||
import me.jamestmartin.wasteland.towny.TownyDisabled;
|
||||
import me.jamestmartin.wasteland.towny.TownAbbreviationProvider;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import me.jamestmartin.wasteland.towny.TownyDependency;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public class Wasteland extends JavaPlugin {
|
||||
private static Wasteland instance;
|
||||
|
||||
private Database database;
|
||||
private WastelandConfig config;
|
||||
private RankListener rankListener;
|
||||
private TownAbbreviationProvider townyAbbreviationProvider;
|
||||
private WastelandSpawner spawner;
|
||||
private static TownyDependency towny;
|
||||
|
||||
private WastelandConfig config;
|
||||
private SqliteDatabase database;
|
||||
private WastelandState state;
|
||||
|
||||
public static Wasteland getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public Database getDatabase() {
|
||||
return database;
|
||||
}
|
||||
|
||||
public WastelandConfig getSettings() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public TownAbbreviationProvider getTownyAbbreviationProvider() {
|
||||
return townyAbbreviationProvider;
|
||||
}
|
||||
|
||||
public WastelandSpawner getSpawner() {
|
||||
return spawner;
|
||||
}
|
||||
|
||||
public void updatePlayerRank(Player player) throws SQLException {
|
||||
rankListener.updatePlayerRank(player);
|
||||
}
|
||||
|
||||
private void initializeConfig() {
|
||||
this.saveDefaultConfig();
|
||||
this.config = new WastelandConfig(this.getConfig());
|
||||
}
|
||||
|
||||
private void initializeTowny() {
|
||||
if (Wasteland.getInstance().getServer().getPluginManager().isPluginEnabled("Towny")) {
|
||||
this.townyAbbreviationProvider = new TownyDependency();
|
||||
} else {
|
||||
this.townyAbbreviationProvider = new TownyDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeDatabase() {
|
||||
String databaseFilename = this.getConfig().getString("databaseFile");
|
||||
try {
|
||||
database = new Database(new File(this.getDataFolder(), databaseFilename));
|
||||
} catch (IOException e) {
|
||||
this.getLogger().log(Level.SEVERE, "Failed to write database file: " + e.getMessage());
|
||||
this.getPluginLoader().disablePlugin(this);
|
||||
} catch (ClassNotFoundException e) {
|
||||
this.getLogger().log(Level.SEVERE, "You need the SQLite JBDC library. Google it. Put it in /lib folder.");
|
||||
this.getPluginLoader().disablePlugin(this);
|
||||
} catch (SQLException e) {
|
||||
this.getLogger().log(Level.SEVERE, "SQLite exception on initialize.", e);
|
||||
this.getPluginLoader().disablePlugin(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerCommands() {
|
||||
this.getCommand("rank").setExecutor(new CommandRank(config.eligibleMobsName()));
|
||||
this.getCommand("rankeligiblemobs").setExecutor(new CommandRankEligibleMobs(config.eligibleMobsName(), config.eligibleMobs()));
|
||||
this.getCommand("ranks").setExecutor(new CommandRanks());
|
||||
this.getCommand("setkills").setExecutor(new CommandSetKills());
|
||||
this.getCommand("official").setExecutor(new CommandOfficial(config.chat()));
|
||||
|
||||
// debug commands
|
||||
this.getCommand("debugspawn").setExecutor(new CommandDebugSpawn());
|
||||
this.getCommand("debugspawnweights").setExecutor(new CommandDebugSpawnWeights());
|
||||
}
|
||||
|
||||
private void registerListeners() {
|
||||
PluginManager manager = this.getServer().getPluginManager();
|
||||
manager.registerEvents(new RankListener(config.eligibleMobs()), this);
|
||||
manager.registerEvents(new ChatListener(config.chat()), this);
|
||||
public static TownyDependency getTowny() {
|
||||
return towny;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
instance = this;
|
||||
initializeConfig();
|
||||
initializeTowny();
|
||||
initializeDatabase();
|
||||
registerCommands();
|
||||
registerListeners();
|
||||
this.spawner = new WastelandSpawner(config.spawns());
|
||||
|
||||
if (Wasteland.getInstance().getServer().getPluginManager().isPluginEnabled("Towny")) {
|
||||
towny = new TownyEnabled();
|
||||
} else {
|
||||
towny = new TownyDisabled();
|
||||
}
|
||||
|
||||
initialize();
|
||||
register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (rankListener != null)
|
||||
rankListener.close();
|
||||
rankListener = null;
|
||||
try {
|
||||
if (database != null)
|
||||
database.close();
|
||||
} catch (SQLException e) {
|
||||
this.getLogger().log(Level.SEVERE, "Failed to close database.", e);
|
||||
}
|
||||
// No need to unregister stuff; Bukkit will do that for us.
|
||||
state = null;
|
||||
|
||||
deinitialize();
|
||||
|
||||
towny = null;
|
||||
instance = null;
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
reloadConfig();
|
||||
reinitialize();
|
||||
|
||||
unregister();
|
||||
register();
|
||||
}
|
||||
|
||||
private void reinitialize() {
|
||||
deinitialize();
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
initializeConfig();
|
||||
initializeDatabase();
|
||||
}
|
||||
|
||||
private void initializeConfig() {
|
||||
saveDefaultConfig();
|
||||
config = ConfigParser.parseConfig(getConfig());
|
||||
}
|
||||
|
||||
private void initializeDatabase() {
|
||||
String databaseFilename = config.getDatabaseFilename();
|
||||
try {
|
||||
database = new SqliteDatabase(new File(this.getDataFolder(), databaseFilename));
|
||||
} catch (IOException e) {
|
||||
this.getLogger().log(Level.SEVERE, "Failed to write database file: " + e.getMessage());
|
||||
this.getPluginLoader().disablePlugin(this);
|
||||
} catch (ClassNotFoundException e) {
|
||||
this.getLogger().log(Level.SEVERE, "You need the SQLite JBDC library. Google it. Put it in /lib folder.");
|
||||
this.getPluginLoader().disablePlugin(this);
|
||||
} catch (SQLException e) {
|
||||
this.getLogger().log(Level.SEVERE, "SQLite exception on initialize.", e);
|
||||
this.getPluginLoader().disablePlugin(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void deinitialize() {
|
||||
config = null;
|
||||
|
||||
try {
|
||||
if (database != null)
|
||||
database.close();
|
||||
} catch (SQLException e) {
|
||||
this.getLogger().log(Level.SEVERE, "Failed to close database.", e);
|
||||
}
|
||||
database = null;
|
||||
}
|
||||
|
||||
private void register() {
|
||||
state = new WastelandState(config, database);
|
||||
state.register(this);
|
||||
}
|
||||
|
||||
private void unregister() {
|
||||
state.unregister(this);
|
||||
state = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package me.jamestmartin.wasteland;
|
||||
|
||||
import me.jamestmartin.wasteland.chat.ChatConfig;
|
||||
import me.jamestmartin.wasteland.kills.KillsConfig;
|
||||
import me.jamestmartin.wasteland.ranks.AllRanks;
|
||||
import me.jamestmartin.wasteland.spawns.SpawnsConfig;
|
||||
|
||||
public class WastelandConfig {
|
||||
private final String databaseFilename;
|
||||
private final ChatConfig chatConfig;
|
||||
private final KillsConfig killsConfig;
|
||||
private final AllRanks ranks;
|
||||
private final SpawnsConfig spawnsConfig;
|
||||
|
||||
public WastelandConfig(
|
||||
String databaseFilename,
|
||||
ChatConfig chatConfig,
|
||||
KillsConfig killsConfig,
|
||||
AllRanks ranks,
|
||||
SpawnsConfig spawnsConfig) {
|
||||
this.databaseFilename = databaseFilename;
|
||||
this.chatConfig = chatConfig;
|
||||
this.killsConfig = killsConfig;
|
||||
this.ranks = ranks;
|
||||
this.spawnsConfig = spawnsConfig;
|
||||
}
|
||||
|
||||
public String getDatabaseFilename() {
|
||||
return databaseFilename;
|
||||
}
|
||||
|
||||
public ChatConfig getChatConfig() {
|
||||
return chatConfig;
|
||||
}
|
||||
|
||||
public KillsConfig getKillsConfig() {
|
||||
return killsConfig;
|
||||
}
|
||||
|
||||
public AllRanks getRanks() {
|
||||
return ranks;
|
||||
}
|
||||
|
||||
public SpawnsConfig getSpawnsConfig() {
|
||||
return spawnsConfig;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package me.jamestmartin.wasteland;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.jamestmartin.wasteland.chat.ChatState;
|
||||
import me.jamestmartin.wasteland.kills.KillsState;
|
||||
import me.jamestmartin.wasteland.ranks.PermissionsPlayerRankProvider;
|
||||
import me.jamestmartin.wasteland.ranks.PlayerRankProvider;
|
||||
import me.jamestmartin.wasteland.ranks.RanksState;
|
||||
import me.jamestmartin.wasteland.spawns.SpawnsState;
|
||||
import me.jamestmartin.wasteland.store.SqliteDatabase;
|
||||
|
||||
public class WastelandState implements Substate {
|
||||
private final CommandWasteland commandWasteland;
|
||||
|
||||
private final ChatState chatState;
|
||||
private final KillsState killsState;
|
||||
private final RanksState ranksState;
|
||||
private final SpawnsState spawnsState;
|
||||
|
||||
public WastelandState(WastelandConfig config, SqliteDatabase database) {
|
||||
this.commandWasteland = new CommandWasteland();
|
||||
|
||||
PlayerRankProvider rankProvider = new PermissionsPlayerRankProvider(config.getRanks());
|
||||
this.ranksState = new RanksState(config.getKillsConfig(), database, rankProvider);
|
||||
this.chatState = new ChatState(config.getChatConfig(), rankProvider);
|
||||
this.killsState = new KillsState(config.getKillsConfig(), ranksState.getPlayerKillsStore(), rankProvider);
|
||||
this.spawnsState = new SpawnsState(config.getSpawnsConfig());
|
||||
}
|
||||
|
||||
private Substate[] getSubstates() {
|
||||
Substate[] substates = { chatState, killsState, ranksState, spawnsState };
|
||||
return substates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(JavaPlugin plugin) {
|
||||
plugin.getCommand("wasteland").setExecutor(commandWasteland);
|
||||
|
||||
for (Substate substate : getSubstates()) {
|
||||
substate.register(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(JavaPlugin plugin) {
|
||||
plugin.getCommand("wasteland").setExecutor(null);
|
||||
|
||||
for (Substate substate : getSubstates()) {
|
||||
substate.unregister(plugin);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package me.jamestmartin.wasteland.chat;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import me.jamestmartin.wasteland.Wasteland;
|
||||
import me.jamestmartin.wasteland.ranks.PlayerRanks;
|
||||
import me.jamestmartin.wasteland.ranks.Rank;
|
||||
|
||||
public class ChatConfig implements ChatFormatter {
|
||||
private final String chatFormat;
|
||||
private final String officerChatFormat;
|
||||
private final String officialFormat;
|
||||
private final String consoleFormat;
|
||||
|
||||
private final String rankPrefixFormat;
|
||||
private final String townyPrefixFormat;
|
||||
|
||||
public ChatConfig(
|
||||
final String chatFormat,
|
||||
final String officerChatFormat,
|
||||
final String officialFormat,
|
||||
final String consoleFormat,
|
||||
final String rankPrefixFormat,
|
||||
final String townyPrefixFormat
|
||||
) {
|
||||
this.chatFormat = chatFormat;
|
||||
this.officerChatFormat = officerChatFormat;
|
||||
this.officialFormat = officialFormat;
|
||||
this.consoleFormat = consoleFormat;
|
||||
this.rankPrefixFormat = rankPrefixFormat;
|
||||
this.townyPrefixFormat = townyPrefixFormat;
|
||||
}
|
||||
|
||||
public String getChatFormat() {
|
||||
return chatFormat;
|
||||
}
|
||||
|
||||
public String getOfficerChatFormat() {
|
||||
return officerChatFormat;
|
||||
}
|
||||
|
||||
public String getOfficialFormat() {
|
||||
return officialFormat;
|
||||
}
|
||||
|
||||
public String getConsoleFormat() {
|
||||
return consoleFormat;
|
||||
}
|
||||
|
||||
public String getRankPrefixFormat() {
|
||||
return rankPrefixFormat;
|
||||
}
|
||||
|
||||
public String getTownyPrefixFormat() {
|
||||
return townyPrefixFormat;
|
||||
}
|
||||
|
||||
private String formatRankPrefix(Rank rank) {
|
||||
return getRankPrefixFormat().replace("{abbr}", rank.formatAbbreviated());
|
||||
}
|
||||
|
||||
private String formatPlayerRankPrefix(Optional<Rank> rank) {
|
||||
if (rank.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return formatRankPrefix(rank.get());
|
||||
}
|
||||
|
||||
private String formatPlayerTownPrefix(Player player) {
|
||||
Optional<String> townTag = Wasteland.getTowny().getTownAbbreviation(player);
|
||||
if (townTag.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return getTownyPrefixFormat().replace("{tag}", townTag.get());
|
||||
}
|
||||
|
||||
private String substituteRankPrefixes(PlayerRanks ranks, String format) {
|
||||
// This is an inefficient way to do it, but chat doesn't need optimization for now.
|
||||
String enlistedRankPrefix = formatPlayerRankPrefix(ranks.getEnlistedRank().map(x -> x));
|
||||
String officerRankPrefix = formatPlayerRankPrefix(ranks.getOfficerRank());
|
||||
String highestRankPrefix = formatPlayerRankPrefix(ranks.getHighestRank());
|
||||
return format
|
||||
.replace("{enlisted}", enlistedRankPrefix)
|
||||
.replace("{officer}", officerRankPrefix)
|
||||
.replace("{rank}", highestRankPrefix);
|
||||
}
|
||||
|
||||
private String substitutePlayerTownPrefix(Player player, String format) {
|
||||
return format.replace("{towny}", formatPlayerTownPrefix(player));
|
||||
}
|
||||
|
||||
private String substitutePlayerPrefixes(Player player, PlayerRanks ranks, String format) {
|
||||
return substitutePlayerTownPrefix(player, substituteRankPrefixes(ranks, format));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatPlayerChat(Player player, PlayerRanks ranks) {
|
||||
return substitutePlayerPrefixes(player, ranks, getChatFormat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatOfficerChat(Player player, PlayerRanks ranks) {
|
||||
return substitutePlayerPrefixes(player, ranks, getOfficerChatFormat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatOfficial(Player player, PlayerRanks ranks) {
|
||||
return substitutePlayerPrefixes(player, ranks, getOfficialFormat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatConsole(Rank consoleRank) {
|
||||
return getConsoleFormat()
|
||||
.replace("{console}", consoleRank.formatAbbreviated())
|
||||
.replace("{console_full}", consoleRank.formatFull());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package me.jamestmartin.wasteland.chat;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import me.jamestmartin.wasteland.ranks.PlayerRanks;
|
||||
import me.jamestmartin.wasteland.ranks.Rank;
|
||||
|
||||
/** Creates chat format strings for players. */
|
||||
interface ChatFormatter {
|
||||
/**
|
||||
* Calculates the chat format string for a player.
|
||||
* It will contain two `%s` variables: the player's name, and the actual message.
|
||||
*/
|
||||
String formatPlayerChat(Player player, PlayerRanks ranks);
|
||||
|
||||
/**
|
||||
* Calculates the officer format string for a player.
|
||||
* It will contain two `%s` variables: the player's name, and the actual message.
|
||||
*/
|
||||
String formatOfficerChat(Player player, PlayerRanks ranks);
|
||||
|
||||
/**
|
||||
* Calculates the `/official` format string for a player.
|
||||
* It will contain two `%s` variables: the player's name, and the actual message.
|
||||
*/
|
||||
String formatOfficial(Player player, PlayerRanks ranks);
|
||||
|
||||
/**
|
||||
* Calculates the `/official` format string for the console.
|
||||
* It will contain just one `%s` variable: the actual message.
|
||||
*/
|
||||
String formatConsole(Rank consoleRank);
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package me.jamestmartin.wasteland.chat;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
|
||||
import me.jamestmartin.wasteland.ranks.PlayerRankProvider;
|
||||
|
||||
class ChatListener implements Listener {
|
||||
private final ChatFormatter formatter;
|
||||
private final PlayerRankProvider ranks;
|
||||
|
||||
public ChatListener(ChatFormatter formatter, PlayerRankProvider ranks) {
|
||||
this.formatter = formatter;
|
||||
this.ranks = ranks;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onPlayerChat(AsyncPlayerChatEvent event) {
|
||||
final Player player = event.getPlayer();
|
||||
if (player.hasPermission("wasteland.chat.officer")) {
|
||||
event.setFormat(formatter.formatOfficerChat(player, ranks.getPlayerRanks(player)));
|
||||
} else {
|
||||
event.setFormat(formatter.formatPlayerChat(player, ranks.getPlayerRanks(player)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package me.jamestmartin.wasteland.chat;
|
||||
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.jamestmartin.wasteland.Substate;
|
||||
import me.jamestmartin.wasteland.ranks.PlayerRankProvider;
|
||||
|
||||
public class ChatState implements Substate {
|
||||
private final ChatListener chatListener;
|
||||
private final CommandOfficial commandOfficial;
|
||||
|
||||
public ChatState(ChatConfig config, PlayerRankProvider rankProvider) {
|
||||
this.chatListener = new ChatListener(config, rankProvider);
|
||||
this.commandOfficial = new CommandOfficial(config, rankProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(JavaPlugin plugin) {
|
||||
plugin.getServer().getPluginManager().registerEvents(chatListener, plugin);
|
||||
plugin.getCommand("official").setExecutor(commandOfficial);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(JavaPlugin plugin) {
|
||||
AsyncPlayerChatEvent.getHandlerList().unregister(chatListener);
|
||||
plugin.getCommand("official").setExecutor(null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package me.jamestmartin.wasteland.chat;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import me.jamestmartin.wasteland.ranks.PlayerRankProvider;
|
||||
import me.jamestmartin.wasteland.ranks.PlayerRanks;
|
||||
|
||||
class CommandOfficial implements CommandExecutor {
|
||||
private final PlayerRankProvider ranks;
|
||||
private final ChatFormatter formatter;
|
||||
|
||||
public CommandOfficial(ChatFormatter formatter, PlayerRankProvider ranks) {
|
||||
this.formatter = formatter;
|
||||
this.ranks = ranks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length < 1) {
|
||||
sender.sendMessage("Don't you actually have something to say?");
|
||||
return false;
|
||||
}
|
||||
|
||||
String message = String.join(" ", args);
|
||||
|
||||
if (sender instanceof Player) {
|
||||
Player player = (Player) sender;
|
||||
PlayerRanks playerRanks = ranks.getPlayerRanks(player);
|
||||
if (!playerRanks.getOfficerRank().isPresent()) {
|
||||
sender.sendMessage("You are not an officer.");
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.getServer().broadcastMessage(
|
||||
String.format(formatter.formatOfficial(player, playerRanks), player.getDisplayName(), message));
|
||||
} else {
|
||||
sender.getServer().broadcastMessage(
|
||||
String.format(formatter.formatConsole(ranks.getRanks().getConsoleRank()), message));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
package me.jamestmartin.wasteland.commands;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import me.jamestmartin.wasteland.config.ChatConfig;
|
||||
import me.jamestmartin.wasteland.ranks.Rank;
|
||||
|
||||
public class CommandOfficial implements CommandExecutor {
|
||||
private final ChatConfig config;
|
||||
|
||||
public CommandOfficial(ChatConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length < 1) {
|
||||
sender.sendMessage("Don't you actually have something to say?");
|
||||
return false;
|
||||
}
|
||||
|
||||
final String message = String.join(" ", args);
|
||||
|
||||
final String format;
|
||||
if (sender instanceof Player) {
|
||||
final Player player = (Player) sender;
|
||||
if (!Rank.getOfficerRank(player).isPresent()) {
|
||||
sender.sendMessage("You are not a staff member.");
|
||||
return true;
|
||||
}
|
||||
|
||||
format = config.getOfficialFormat(player).replaceFirst("%s", player.getDisplayName());
|
||||
} else {
|
||||
if (!Rank.getConsoleRank().isPresent()) {
|
||||
sender.sendMessage("No console rank is configured to send messages with!");
|
||||
return true;
|
||||
}
|
||||
|
||||
format = config.getConsoleFormat();
|
||||
}
|
||||
|
||||
sender.getServer().broadcastMessage(format.replaceFirst("%s", message));
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package me.jamestmartin.wasteland.commands;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
public class CommandRankEligibleMobs implements CommandExecutor {
|
||||
private final String eligibleMobsName;
|
||||
private final Collection<EntityType> eligibleMobs;
|
||||
|
||||
public CommandRankEligibleMobs(String eligibleMobsName, Collection<EntityType> eligibleMobs) {
|
||||
this.eligibleMobsName = eligibleMobsName;
|
||||
this.eligibleMobs = eligibleMobs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length > 0) {
|
||||
sender.sendMessage("Too many arguments!");
|
||||
sender.sendMessage(command.getUsage());
|
||||
return false;
|
||||
}
|
||||
|
||||
sender.sendMessage("Killing " + eligibleMobsName + " will count towards your next promotion.");
|
||||
sender.sendMessage("Specifically, any of these mobs will work: " +
|
||||
eligibleMobs.stream().map(Object::toString).sorted().collect(Collectors.joining(", ")));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
package me.jamestmartin.wasteland.config;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import me.jamestmartin.wasteland.Wasteland;
|
||||
import me.jamestmartin.wasteland.ranks.Rank;
|
||||
import me.jamestmartin.wasteland.ranks.RankType;
|
||||
|
||||
public class ChatConfig {
|
||||
private final String chatFormat;
|
||||
private final String officerChatFormat;
|
||||
private final String officialFormat;
|
||||
private final String consoleFormat;
|
||||
|
||||
private final String rankPrefixFormat;
|
||||
private final String townyPrefixFormat;
|
||||
|
||||
public ChatConfig(ConfigurationSection c) {
|
||||
ConfigurationSection formats = c.getConfigurationSection("formats");
|
||||
this.chatFormat = formats.getString("chat");
|
||||
this.officerChatFormat = formats.getString("officer");
|
||||
this.officialFormat = formats.getString("official");
|
||||
this.consoleFormat = formats.getString("console");
|
||||
|
||||
ConfigurationSection prefixes = c.getConfigurationSection("prefixes");
|
||||
this.rankPrefixFormat = prefixes.getString("rank");
|
||||
this.townyPrefixFormat = prefixes.getString("towny");
|
||||
}
|
||||
|
||||
public String formatRankPrefix(Player player, Rank rank) {
|
||||
return rankPrefixFormat.replace("{abbr}", rank.formatAbbreviated());
|
||||
}
|
||||
|
||||
public String formatPlayerRankPrefix(Player player, RankType rankType) {
|
||||
Optional<Rank> rank = Rank.getRank(rankType, player);
|
||||
if (rank.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return formatRankPrefix(player, rank.get());
|
||||
}
|
||||
|
||||
public String formatPlayerTownPrefix(Player player) {
|
||||
Optional<String> townTag = Wasteland.getInstance().getTownyAbbreviationProvider().getTownAbbreviation(player);
|
||||
if (townTag.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return townyPrefixFormat.replace("{tag}", townTag.get());
|
||||
}
|
||||
|
||||
private String substituteRankPrefixes(String format, Player player) {
|
||||
// This is an inefficient way to do it, but chat doesn't need optimization.
|
||||
String enlistedRankPrefix = formatPlayerRankPrefix(player, RankType.ENLISTED);
|
||||
String officerRankPrefix = formatPlayerRankPrefix(player, RankType.OFFICER);
|
||||
String highestRankPrefix = formatPlayerRankPrefix(player, RankType.HIGHEST);
|
||||
return format
|
||||
.replace("{enlisted}", enlistedRankPrefix)
|
||||
.replace("{officer}", officerRankPrefix)
|
||||
.replace("{rank}", highestRankPrefix);
|
||||
}
|
||||
|
||||
private String substitutePlayerTownPrefix(String format, Player player) {
|
||||
return format.replace("{towny}", formatPlayerTownPrefix(player));
|
||||
}
|
||||
|
||||
private String substitutePlayerPrefixes(String format, Player player) {
|
||||
return substitutePlayerTownPrefix(substituteRankPrefixes(format, player), player);
|
||||
}
|
||||
|
||||
public String getPlayerChatFormat(Player player) {
|
||||
return substitutePlayerPrefixes(chatFormat, player);
|
||||
}
|
||||
|
||||
public String getOfficerChatFormat(Player player) {
|
||||
return substitutePlayerPrefixes(officerChatFormat, player);
|
||||
}
|
||||
|
||||
public String getOfficialFormat(Player player) {
|
||||
return substitutePlayerPrefixes(officialFormat, player);
|
||||
}
|
||||
|
||||
public String getConsoleFormat() {
|
||||
return consoleFormat
|
||||
.replace("{console}", Rank.getConsoleRank().get().formatAbbreviated())
|
||||
.replace("{console_full}", Rank.getConsoleRank().get().formatFull());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,290 @@
|
|||
package me.jamestmartin.wasteland.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
|
||||
import com.google.common.graph.ImmutableValueGraph;
|
||||
import com.google.common.graph.MutableValueGraph;
|
||||
import com.google.common.graph.ValueGraphBuilder;
|
||||
|
||||
import me.jamestmartin.wasteland.WastelandConfig;
|
||||
import me.jamestmartin.wasteland.chat.ChatConfig;
|
||||
import me.jamestmartin.wasteland.kills.KillsConfig;
|
||||
import me.jamestmartin.wasteland.ranks.AllRanks;
|
||||
import me.jamestmartin.wasteland.ranks.EnlistedRank;
|
||||
import me.jamestmartin.wasteland.ranks.EnlistedRanks;
|
||||
import me.jamestmartin.wasteland.ranks.Rank;
|
||||
import me.jamestmartin.wasteland.ranks.Ranks;
|
||||
import me.jamestmartin.wasteland.spawns.MonsterSpawnConfig;
|
||||
import me.jamestmartin.wasteland.spawns.MonsterType;
|
||||
import me.jamestmartin.wasteland.spawns.SpawnsConfig;
|
||||
import me.jamestmartin.wasteland.util.Pair;
|
||||
import me.jamestmartin.wasteland.world.MoonPhase;
|
||||
|
||||
public class ConfigParser {
|
||||
public static WastelandConfig parseConfig(ConfigurationSection c) {
|
||||
String databaseFilename = c.getString("databaseFile", "wasteland.sqlite3");
|
||||
ChatConfig chatConfig = parseChatConfig(c.getConfigurationSection("chat"));
|
||||
KillsConfig killsConfig = parseKillsConfig(c.getConfigurationSection("kills"));
|
||||
SpawnsConfig spawnsConfig = parseSpawnsConfig(c.getConfigurationSection("spawns"));
|
||||
|
||||
ConfigurationSection enlistedSection = c.getConfigurationSection("enlisted");
|
||||
ConfigurationSection officerSection = c.getConfigurationSection("officer");
|
||||
AllRanks ranks = parseRanks(enlistedSection, officerSection);
|
||||
|
||||
return new WastelandConfig(databaseFilename, chatConfig, killsConfig, ranks, spawnsConfig);
|
||||
}
|
||||
|
||||
private static ChatConfig parseChatConfig(ConfigurationSection c) {
|
||||
final ConfigurationSection formats = c.getConfigurationSection("formats");
|
||||
final String chatFormat = formats.getString("chat");
|
||||
final String officerChatFormat = formats.getString("officer");
|
||||
final String officialFormat = formats.getString("official");
|
||||
final String consoleFormat = formats.getString("console");
|
||||
|
||||
final ConfigurationSection prefixes = c.getConfigurationSection("prefixes");
|
||||
final String rankPrefixFormat = prefixes.getString("rank");
|
||||
final String townyPrefixFormat = prefixes.getString("towny");
|
||||
|
||||
return new ChatConfig(chatFormat, officerChatFormat, officialFormat, consoleFormat, rankPrefixFormat, townyPrefixFormat);
|
||||
}
|
||||
|
||||
private static KillsConfig parseKillsConfig(ConfigurationSection c) {
|
||||
final ConfigurationSection eligibleSection = c.getConfigurationSection("eligible");
|
||||
final String eligibleMobsName = eligibleSection.getString("name");
|
||||
|
||||
final Set<EntityType> eligibleMobs = new HashSet<>();
|
||||
final List<String> eligibleMobTypes = eligibleSection.getStringList("entities");
|
||||
for (final String mobType : eligibleMobTypes) {
|
||||
eligibleMobs.addAll(Arrays.asList(EntityTypes.lookupEntityType(mobType)));
|
||||
}
|
||||
|
||||
return new KillsConfig(eligibleMobsName, eligibleMobs);
|
||||
}
|
||||
|
||||
private static AllRanks parseRanks(ConfigurationSection enlisted, ConfigurationSection officer) {
|
||||
EnlistedRanks enlistedRanks = parseEnlistedRanks(enlisted);
|
||||
Ranks<Rank> officerRanks = parseOfficerRanks(officer);
|
||||
Rank consoleRank = officerRanks.getRank(officer.getString("console")).get();
|
||||
|
||||
return new AllRanks(enlistedRanks, officerRanks, consoleRank);
|
||||
}
|
||||
|
||||
private static EnlistedRanks parseEnlistedRanks(ConfigurationSection c) {
|
||||
Optional<ChatColor> defaultDecoration = readColor(c, "decoration");
|
||||
Optional<String> defaultDescription = Optional.ofNullable(c.getString("description"));
|
||||
|
||||
List<EnlistedRank> originalOrder = new ArrayList<>();
|
||||
Map<String, EnlistedRank> ranks = new HashMap<>();
|
||||
Map<String, String> predecessors = new HashMap<>();
|
||||
Map<String, String> preferredSuccessors = new HashMap<>();
|
||||
|
||||
ConfigurationSection ranksSection = c.getConfigurationSection("ranks");
|
||||
for (String id : ranksSection.getKeys(false)) {
|
||||
Pair<EnlistedRank, Pair<Optional<String>, Optional<String>>> parse =
|
||||
parseEnlistedRank(ranksSection.getConfigurationSection(id), id, defaultDecoration, defaultDescription);
|
||||
EnlistedRank rank = parse.x;
|
||||
Optional<String> predecessor = parse.y.x;
|
||||
Optional<String> preferredSuccessor = parse.y.y;
|
||||
|
||||
originalOrder.add(rank);
|
||||
ranks.put(id, rank);
|
||||
predecessor.ifPresent(x -> predecessors.put(id, x));
|
||||
preferredSuccessor.ifPresent(x -> preferredSuccessors.put(id, x));
|
||||
}
|
||||
|
||||
for (Entry<String, String> entry : predecessors.entrySet()) {
|
||||
String rank = entry.getKey();
|
||||
String predecessor = entry.getValue();
|
||||
// Read: There is only one rank whose predecessor is this rank's predecessor,
|
||||
// i.e. the predecessor has only this rank as a successor.
|
||||
if (predecessors.values().stream().filter(predecessor::equals).count() == 1) {
|
||||
// If there is only one successor, that successor will be preferred automatically.
|
||||
// TODO: Allow explicitly *not* making the only successor preferred.
|
||||
preferredSuccessors.put(predecessor, rank);
|
||||
}
|
||||
}
|
||||
|
||||
// The commented code should be used instead once Guava is upgraded to 28+.
|
||||
// ImmutableValueGraph.Builder<EnlistedRank, Boolean> builder = ValueGraphBuilder.directed().immutable();
|
||||
MutableValueGraph<EnlistedRank, Boolean> builder = ValueGraphBuilder.directed().build();
|
||||
for (EnlistedRank rank : ranks.values()) {
|
||||
builder.addNode(rank);
|
||||
}
|
||||
|
||||
for (Entry<String, String> entry : predecessors.entrySet()) {
|
||||
EnlistedRank successor = ranks.get(entry.getKey());
|
||||
EnlistedRank predecessor = ranks.get(entry.getValue());
|
||||
boolean preferred = successor.getId().equals(preferredSuccessors.get(predecessor.getId()));
|
||||
builder.putEdgeValue(predecessor, successor, preferred);
|
||||
}
|
||||
|
||||
// return new EnlistedRanks(builder.build());
|
||||
return new EnlistedRanks(ImmutableValueGraph.copyOf(builder), originalOrder);
|
||||
}
|
||||
|
||||
private static Pair<EnlistedRank, Pair<Optional<String>, Optional<String>>> parseEnlistedRank(
|
||||
ConfigurationSection c,
|
||||
String id,
|
||||
Optional<ChatColor> defaultDecoration,
|
||||
Optional<String> defaultDescription) {
|
||||
final String name = c.getString("name");
|
||||
final String abbr = c.getString("abbreviation");
|
||||
|
||||
final Optional<Integer> kills;
|
||||
if (c.contains("kills")) {
|
||||
kills = Optional.of(c.getInt("kills"));
|
||||
} else {
|
||||
kills = Optional.empty();
|
||||
}
|
||||
|
||||
final Optional<ChatColor> color = readColor(c, "color");
|
||||
final Optional<ChatColor> decoration = readColor(c, "decoration").or(() -> defaultDecoration);
|
||||
final Optional<String> description =
|
||||
Optional.ofNullable(c.getString("description")).or(() -> defaultDescription)
|
||||
.map(descr -> kills.map(k -> descr.replace("{kills}", k.toString())).orElse(descr));
|
||||
|
||||
final Optional<String> predecessor = Optional.ofNullable(c.getString("succeeds"));
|
||||
final Map<String, Boolean> permissionChildren = predecessor.isPresent() ? Map.of("wasteland.rank." + predecessor.get(), true) : Map.of();
|
||||
|
||||
final Optional<String> preferredSuccessor = Optional.ofNullable(c.getString("preferred"));
|
||||
|
||||
final boolean isDefault = kills.isPresent() && kills.get() == 0;
|
||||
final PermissionDefault permissionDefault = isDefault ? PermissionDefault.TRUE : PermissionDefault.FALSE;
|
||||
|
||||
final Permission permission = new Permission("wasteland.rank." + id, permissionDefault, permissionChildren);
|
||||
|
||||
final Permission preferencePermission = new Permission("wasteland.prefer-rank." + id, PermissionDefault.FALSE);
|
||||
|
||||
final Pair<Optional<String>, Optional<String>> relatedRanks = new Pair<>(predecessor, preferredSuccessor);
|
||||
|
||||
return new Pair<>(new EnlistedRank(id, name, abbr, description, color, decoration, permission, kills, preferencePermission), relatedRanks);
|
||||
}
|
||||
|
||||
private static Ranks<Rank> parseOfficerRanks(ConfigurationSection c) {
|
||||
Optional<ChatColor> defaultDecoration = readColor(c, "decoration");
|
||||
Optional<String> defaultDescription = Optional.ofNullable(c.getString("description"));
|
||||
|
||||
List<Rank> originalOrder = new ArrayList<>();
|
||||
Map<String, Rank> ranks = new HashMap<>();
|
||||
Map<String, String> predecessors = new HashMap<>();
|
||||
|
||||
ConfigurationSection ranksSection = c.getConfigurationSection("ranks");
|
||||
for (String id : ranksSection.getKeys(false)) {
|
||||
Pair<Rank,Optional<String>> parse =
|
||||
parseOfficerRank(ranksSection.getConfigurationSection(id), id, defaultDecoration, defaultDescription);
|
||||
Rank rank = parse.x;
|
||||
Optional<String> predecessor = parse.y;
|
||||
|
||||
originalOrder.add(rank);
|
||||
ranks.put(id, rank);
|
||||
predecessor.ifPresent(x -> predecessors.put(id, x));
|
||||
}
|
||||
// The commented code should be used instead once Guava is upgraded to 28+.
|
||||
// ImmutableValueGraph.Builder<Rank, Boolean> builder = ValueGraphBuilder.directed().immutable();
|
||||
MutableValueGraph<Rank, Boolean> builder = ValueGraphBuilder.directed().build();
|
||||
for (Rank rank : ranks.values()) {
|
||||
builder.addNode(rank);
|
||||
}
|
||||
|
||||
for (Entry<String, String> entry : predecessors.entrySet()) {
|
||||
Rank successor = ranks.get(entry.getKey());
|
||||
Rank predecessor = ranks.get(entry.getValue());
|
||||
builder.putEdgeValue(predecessor, successor, false);
|
||||
}
|
||||
|
||||
//return new Ranks<>(builder.build());
|
||||
return new Ranks<>(ImmutableValueGraph.copyOf(builder), originalOrder);
|
||||
}
|
||||
|
||||
private static Pair<Rank, Optional<String>> parseOfficerRank(
|
||||
ConfigurationSection c,
|
||||
String id,
|
||||
Optional<ChatColor> defaultDecoration,
|
||||
Optional<String> defaultDescription) {
|
||||
final String name = c.getString("name");
|
||||
final String abbr = c.getString("abbreviation");
|
||||
|
||||
final Optional<ChatColor> color = readColor(c, "color");
|
||||
final Optional<ChatColor> decoration = readColor(c, "decoration").or(() -> defaultDecoration);
|
||||
final Optional<String> description =
|
||||
Optional.ofNullable(c.getString("description")).or(() -> defaultDescription);
|
||||
|
||||
final Optional<String> predecessor = Optional.ofNullable(c.getString("succeeds"));
|
||||
final Map<String, Boolean> permissionChildren = predecessor.isPresent() ? Map.of(predecessor.get(), true) : Map.of();
|
||||
|
||||
final Permission permission = new Permission("wasteland.rank." + id, PermissionDefault.FALSE, permissionChildren);
|
||||
|
||||
return new Pair<>(new Rank(id, name, abbr, description, color, decoration, permission), predecessor);
|
||||
}
|
||||
|
||||
private static SpawnsConfig parseSpawnsConfig(ConfigurationSection c) {
|
||||
final HashMap<MonsterType, MonsterSpawnConfig> spawns = new HashMap<>();
|
||||
if (c != null) {
|
||||
for (String typeName : c.getKeys(false)) {
|
||||
MonsterType type = MonsterType.valueOf(typeName);
|
||||
MonsterSpawnConfig config = parseMonsterSpawnConfig(c.getConfigurationSection(typeName));
|
||||
spawns.put(type, config);
|
||||
}
|
||||
}
|
||||
|
||||
return new SpawnsConfig(spawns);
|
||||
}
|
||||
|
||||
private static MonsterSpawnConfig parseMonsterSpawnConfig(ConfigurationSection c) {
|
||||
final double maximumLightLevel;
|
||||
final double blocklightWeight;
|
||||
final double sunlightWeight;
|
||||
final double moonlightWeight;
|
||||
|
||||
if (!c.isConfigurationSection("light")) {
|
||||
maximumLightLevel = c.getInt("light", 9);
|
||||
blocklightWeight = 1.0f;
|
||||
sunlightWeight = 1.0f;
|
||||
moonlightWeight = 0.0f;
|
||||
} else {
|
||||
final ConfigurationSection cLight = c.getConfigurationSection("light");
|
||||
maximumLightLevel = cLight.getInt("maximum", 9);
|
||||
blocklightWeight = (float) cLight.getDouble("weights.block", 1.0);
|
||||
sunlightWeight = (float) cLight.getDouble("weights.sun", 1.0);
|
||||
moonlightWeight = (float) cLight.getDouble("weights.moon", 0.0);
|
||||
}
|
||||
|
||||
final int minimumYLevel = c.getInt("height.minimum", 0);
|
||||
final int maximumYLevel = c.getInt("height.maximum", Integer.MAX_VALUE);
|
||||
|
||||
final ConfigurationSection cPhases = c.getConfigurationSection("phases");
|
||||
final HashMap<MoonPhase, Double> phaseMultipliers = new HashMap<>();
|
||||
if (cPhases != null) {
|
||||
for (String phaseKey : cPhases.getKeys(false)) {
|
||||
MoonPhase phase = MoonPhase.valueOf(phaseKey);
|
||||
double multiplier = cPhases.getDouble(phaseKey);
|
||||
phaseMultipliers.put(phase, multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
return new MonsterSpawnConfig(
|
||||
maximumLightLevel,
|
||||
blocklightWeight, sunlightWeight, moonlightWeight,
|
||||
minimumYLevel, maximumYLevel,
|
||||
phaseMultipliers);
|
||||
}
|
||||
|
||||
/** Orphaned method. */
|
||||
private static Optional<ChatColor> readColor(ConfigurationSection c, String path) {
|
||||
return (Optional<ChatColor>) Optional.ofNullable(c.getString(path)).map(ChatColor::valueOf);
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
package me.jamestmartin.wasteland.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import me.jamestmartin.wasteland.world.MoonPhase;
|
||||
|
||||
public class MonsterSpawnConfig {
|
||||
private final int maximumLightLevel;
|
||||
private final float blocklightWeight;
|
||||
private final float sunlightWeight;
|
||||
private final float moonlightWeight;
|
||||
|
||||
private final int maximumYLevel;
|
||||
private final int minimumYLevel;
|
||||
|
||||
private final Map<MoonPhase, Float> phaseMultipliers;
|
||||
|
||||
public MonsterSpawnConfig(final ConfigurationSection c) {
|
||||
if (!c.isConfigurationSection("light")) {
|
||||
this.maximumLightLevel = c.getInt("light", 9);
|
||||
this.blocklightWeight = 1.0f;
|
||||
this.sunlightWeight = 1.0f;
|
||||
this.moonlightWeight = 0.0f;
|
||||
} else {
|
||||
final ConfigurationSection cLight = c.getConfigurationSection("light");
|
||||
this.maximumLightLevel = cLight.getInt("maximum", 9);
|
||||
this.blocklightWeight = (float) cLight.getDouble("weights.block", 1.0);
|
||||
this.sunlightWeight = (float) cLight.getDouble("weights.sun", 1.0);
|
||||
this.moonlightWeight = (float) cLight.getDouble("weights.moon", 0.0);
|
||||
}
|
||||
|
||||
this.minimumYLevel = c.getInt("height.minimum", 0);
|
||||
this.maximumYLevel = c.getInt("height.maximum", Integer.MAX_VALUE);
|
||||
|
||||
final ConfigurationSection cPhases = c.getConfigurationSection("phases");
|
||||
this.phaseMultipliers = new HashMap<>();
|
||||
if (cPhases != null) {
|
||||
for (String phaseKey : cPhases.getKeys(false)) {
|
||||
MoonPhase phase = MoonPhase.valueOf(phaseKey);
|
||||
float multiplier = (float) cPhases.getDouble(phaseKey);
|
||||
this.phaseMultipliers.put(phase, multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int maximumLightLevel() {
|
||||
return maximumLightLevel;
|
||||
}
|
||||
|
||||
public float blocklightWeight() {
|
||||
return blocklightWeight;
|
||||
}
|
||||
|
||||
public float sunlightWeight() {
|
||||
return sunlightWeight;
|
||||
}
|
||||
|
||||
public float moonlightWeight() {
|
||||
return moonlightWeight;
|
||||
}
|
||||
|
||||
public int minimumYLevel() {
|
||||
return minimumYLevel;
|
||||
}
|
||||
|
||||
public int maximumYLevel() {
|
||||
return maximumYLevel;
|
||||
}
|
||||
|
||||
public Map<MoonPhase, Float> phaseMultipliers() {
|
||||
return phaseMultipliers;
|
||||
}
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
package me.jamestmartin.wasteland.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
import me.jamestmartin.wasteland.ranks.EnlistedRank;
|
||||
import me.jamestmartin.wasteland.ranks.Rank;
|
||||
import me.jamestmartin.wasteland.spawns.MonsterType;
|
||||
|
||||
public class WastelandConfig {
|
||||
private final String databaseFile;
|
||||
|
||||
private final ChatConfig chat;
|
||||
|
||||
private final Collection<EnlistedRank> enlistedRanks;
|
||||
private final Collection<Rank> officerRanks;
|
||||
private final Optional<Rank> consoleRank;
|
||||
|
||||
private final Set<EntityType> eligibleMobs;
|
||||
private final String eligibleMobsName;
|
||||
|
||||
private final Map<MonsterType, MonsterSpawnConfig> spawns;
|
||||
|
||||
/** Orphaned method. */
|
||||
public static Optional<ChatColor> readColor(ConfigurationSection c, String path) {
|
||||
return (Optional<ChatColor>) Optional.ofNullable(c.getString(path)).map(ChatColor::valueOf);
|
||||
}
|
||||
|
||||
public WastelandConfig(ConfigurationSection c) {
|
||||
this.databaseFile = c.getString("databaseFile", "wasteland.sqlite3");
|
||||
|
||||
this.chat = new ChatConfig(c.getConfigurationSection("chat"));
|
||||
|
||||
ConfigurationSection enlistedSection = c.getConfigurationSection("enlisted");
|
||||
ArrayList<EnlistedRank> enlistedRanks = new ArrayList<>();
|
||||
this.enlistedRanks = enlistedRanks;
|
||||
this.eligibleMobs = new HashSet<>();
|
||||
if (enlistedSection != null) {
|
||||
Optional<ChatColor> defaultDecoration = readColor(enlistedSection, "decoration");
|
||||
Optional<String> defaultDescription =
|
||||
Optional.ofNullable(enlistedSection.getString("description"));
|
||||
|
||||
ConfigurationSection enlistedRanksSection = enlistedSection.getConfigurationSection("ranks");
|
||||
Set<String> rankIDs = enlistedRanksSection.getKeys(false);
|
||||
enlistedRanks.ensureCapacity(rankIDs.size());
|
||||
for (String id : rankIDs) {
|
||||
EnlistedRank result = new EnlistedRank(defaultDescription, defaultDecoration,
|
||||
enlistedRanksSection.getConfigurationSection(id));
|
||||
enlistedRanks.add(result);
|
||||
}
|
||||
enlistedRanks.sort(new EnlistedRank.EnlistedRankComparator(enlistedRanks));
|
||||
|
||||
|
||||
ConfigurationSection promotionSection = enlistedSection.getConfigurationSection("promotions");
|
||||
ConfigurationSection eligibleSection = promotionSection.getConfigurationSection("eligible");
|
||||
|
||||
List<String> eligibleMobTypes = eligibleSection.getStringList("entities");
|
||||
for (String mobType : eligibleMobTypes) {
|
||||
this.eligibleMobs.addAll(Arrays.asList(EntityTypes.lookupEntityType(mobType)));
|
||||
}
|
||||
|
||||
this.eligibleMobsName = eligibleSection.getString("name");
|
||||
} else {
|
||||
this.eligibleMobsName = "nothing";
|
||||
}
|
||||
|
||||
ConfigurationSection officerSection = c.getConfigurationSection("officer");
|
||||
ArrayList<Rank> officerRanks = new ArrayList<>();
|
||||
this.officerRanks = officerRanks;
|
||||
if (officerSection != null) {
|
||||
Optional<ChatColor> defaultDecoration = readColor(officerSection, "decoration");
|
||||
|
||||
ConfigurationSection officerRanksSection = officerSection.getConfigurationSection("ranks");
|
||||
Set<String> rankIDs = officerRanksSection.getKeys(false);
|
||||
officerRanks.ensureCapacity(rankIDs.size());
|
||||
for (String id : rankIDs) {
|
||||
ConfigurationSection rank = officerRanksSection.getConfigurationSection(id);
|
||||
Rank result = new Rank(Optional.empty(), defaultDecoration, rank);
|
||||
officerRanks.add(result);
|
||||
}
|
||||
officerRanks.sort(new Rank.RankComparator(officerRanks));
|
||||
|
||||
String consoleRankID = officerSection.getString("console", null);
|
||||
if (consoleRankID == null) {
|
||||
this.consoleRank = Optional.of(officerRanks.get(officerRanks.size() - 1));
|
||||
} else {
|
||||
this.consoleRank = Optional.of(officerRanks.stream().filter(rank -> rank.getId().equals(consoleRankID)).findFirst().get());
|
||||
}
|
||||
} else {
|
||||
this.consoleRank = Optional.empty();
|
||||
}
|
||||
|
||||
ConfigurationSection mss = c.getConfigurationSection("spawns");
|
||||
this.spawns = new HashMap<>();
|
||||
if (mss != null) {
|
||||
for (String typeName : mss.getKeys(false)) {
|
||||
MonsterType type = MonsterType.valueOf(typeName);
|
||||
MonsterSpawnConfig config = new MonsterSpawnConfig(mss.getConfigurationSection(typeName));
|
||||
this.spawns.put(type, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String databaseFile() { return this.databaseFile; }
|
||||
|
||||
public ChatConfig chat() { return this.chat; }
|
||||
|
||||
public Collection<EnlistedRank> enlistedRanks() { return this.enlistedRanks; }
|
||||
public Collection<Rank> officerRanks() { return this.officerRanks; }
|
||||
public Optional<Rank> consoleRank() { return this.consoleRank; }
|
||||
|
||||
/** The entity types which, if killed, will count towards your enlisted rank. */
|
||||
public Set<EntityType> eligibleMobs() { return this.eligibleMobs; }
|
||||
/** The term for the eligible mobs, e.g. "zombies" or "hostile mobs". */
|
||||
public String eligibleMobsName() { return this.eligibleMobsName; }
|
||||
|
||||
public Map<MonsterType, MonsterSpawnConfig> spawns() { return this.spawns; }
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package me.jamestmartin.wasteland.kills;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
class CommandRankEligibleMobs implements CommandExecutor {
|
||||
private final KillsConfig config;
|
||||
|
||||
public CommandRankEligibleMobs(KillsConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length > 0) {
|
||||
sender.sendMessage("Too many arguments!");
|
||||
sender.sendMessage(command.getUsage());
|
||||
return false;
|
||||
}
|
||||
|
||||
sender.sendMessage("Killing " + config.getEligibleMobsName() + " will count towards your next promotion.");
|
||||
sender.sendMessage("Specifically, any of these mobs will work: " +
|
||||
config.getEligibleMobs().stream().map(Object::toString).sorted().collect(Collectors.joining(", ")));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package me.jamestmartin.wasteland.commands;
|
||||
package me.jamestmartin.wasteland.kills;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
|
@ -10,7 +9,13 @@ import org.bukkit.entity.Player;
|
|||
|
||||
import me.jamestmartin.wasteland.Wasteland;
|
||||
|
||||
public class CommandSetKills implements CommandExecutor {
|
||||
class CommandSetKills implements CommandExecutor {
|
||||
private final PlayerKillsStore store;
|
||||
|
||||
public CommandSetKills(PlayerKillsStore store) {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!sender.hasPermission("wasteland.kills.set")) {
|
||||
|
@ -66,13 +71,11 @@ public class CommandSetKills implements CommandExecutor {
|
|||
}
|
||||
|
||||
try {
|
||||
int previousKills = Wasteland.getInstance().getDatabase().getPlayerKills(subject);
|
||||
|
||||
Wasteland.getInstance().getDatabase().setPlayerKills(subject, kills);
|
||||
Wasteland.getInstance().updatePlayerRank(subject);
|
||||
int previousKills = store.getPlayerKills(subject);
|
||||
store.setPlayerKills(subject, kills);
|
||||
sender.sendMessage(playerNowHas + " " + kills + " kills.");
|
||||
Wasteland.getInstance().getLogger().info(sender.getName() + " has changed the number of kills " + subject.getName() + " has from " + previousKills + " to " + kills);
|
||||
} catch (SQLException e) {
|
||||
} catch (Exception e) {
|
||||
Wasteland.getInstance().getLogger().log(Level.SEVERE, "Failed to set player kills.", e);
|
||||
sender.sendMessage("ERROR: Failed to update player kills. Please notify a server administrator.");
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package me.jamestmartin.wasteland.kills;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
public class KillsConfig {
|
||||
private final String eligibleMobsName;
|
||||
private final Set<EntityType> eligibleMobs;
|
||||
|
||||
public KillsConfig(String eligibleMobsName, Set<EntityType> eligibleMobs) {
|
||||
this.eligibleMobsName = eligibleMobsName;
|
||||
this.eligibleMobs = eligibleMobs;
|
||||
}
|
||||
|
||||
public String getEligibleMobsName() {
|
||||
return eligibleMobsName;
|
||||
}
|
||||
|
||||
public Set<EntityType> getEligibleMobs() {
|
||||
return eligibleMobs;
|
||||
}
|
||||
|
||||
public boolean isMobEligible(EntityType type) {
|
||||
return getEligibleMobs().contains(type);
|
||||
}
|
||||
|
||||
public boolean isMobEligible(Entity entity) {
|
||||
return isMobEligible(entity.getType());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package me.jamestmartin.wasteland.kills;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDeathEvent;
|
||||
import me.jamestmartin.wasteland.Wasteland;
|
||||
import me.jamestmartin.wasteland.ranks.EnlistedRank;
|
||||
import me.jamestmartin.wasteland.ranks.PlayerRankProvider;
|
||||
|
||||
class KillsListener implements Listener {
|
||||
private final KillsConfig config;
|
||||
private final PlayerKillsStore store;
|
||||
private final PlayerRankProvider provider;
|
||||
|
||||
public KillsListener(KillsConfig config, PlayerKillsStore store, PlayerRankProvider provider) {
|
||||
this.config = config;
|
||||
this.store = store;
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onEntityDeath(EntityDeathEvent event) {
|
||||
Player player = event.getEntity().getKiller();
|
||||
if (player == null) return;
|
||||
if (!config.isMobEligible(event.getEntityType())) return;
|
||||
|
||||
try {
|
||||
// TODO: Rank-related logic does not belong in this listener.
|
||||
Optional<EnlistedRank> oldRank = provider.getEnlistedRank(player);
|
||||
store.incrementPlayerKills(player);
|
||||
Optional<EnlistedRank> newRank = provider.getEnlistedRank(player);
|
||||
|
||||
if (newRank.isPresent()) {
|
||||
final String formatString;
|
||||
if (oldRank.isPresent()) {
|
||||
if (!newRank.get().equals(oldRank.get())) {
|
||||
formatString = "%s" + ChatColor.RESET + " has been promoted from %s " + ChatColor.RESET + "to %s" + ChatColor.RESET + "!";
|
||||
player.getServer().broadcastMessage(
|
||||
String.format(formatString, player.getDisplayName(),
|
||||
oldRank.get().formatFull(), newRank.get().formatFull()));
|
||||
}
|
||||
} else {
|
||||
formatString = "%s" + ChatColor.RESET + " has been promoted to %s" + ChatColor.RESET + "!";
|
||||
player.getServer().broadcastMessage(
|
||||
String.format(formatString, player.getDisplayName(), newRank.get()));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Wasteland.getInstance().getLogger().log(Level.SEVERE, "Failed to increment player kills.", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package me.jamestmartin.wasteland.kills;
|
||||
|
||||
import org.bukkit.event.entity.EntityDeathEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.jamestmartin.wasteland.Substate;
|
||||
import me.jamestmartin.wasteland.ranks.PlayerRankProvider;
|
||||
|
||||
public class KillsState implements Substate {
|
||||
private final KillsListener killsListener;
|
||||
private final CommandRankEligibleMobs commandRankEligibleMobs;
|
||||
private final CommandSetKills commandSetKills;
|
||||
|
||||
public KillsState(KillsConfig config, PlayerKillsStore store, PlayerRankProvider rankProvider) {
|
||||
this.killsListener = new KillsListener(config, store, rankProvider);
|
||||
this.commandRankEligibleMobs = new CommandRankEligibleMobs(config);
|
||||
this.commandSetKills = new CommandSetKills(store);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(JavaPlugin plugin) {
|
||||
plugin.getServer().getPluginManager().registerEvents(killsListener, plugin);
|
||||
plugin.getCommand("setkills").setExecutor(commandSetKills);
|
||||
plugin.getCommand("rankeligiblemobs").setExecutor(commandRankEligibleMobs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(JavaPlugin plugin) {
|
||||
EntityDeathEvent.getHandlerList().unregister(killsListener);
|
||||
plugin.getCommand("setkills").setExecutor(null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package me.jamestmartin.wasteland.kills;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public interface PlayerKillsProvider {
|
||||
/** Get how many monsters a player has killed. */
|
||||
public int getPlayerKills(Player player) throws Exception;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package me.jamestmartin.wasteland.kills;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/** A data store which stores how many monsters a player has killed. */
|
||||
public interface PlayerKillsStore extends PlayerKillsProvider {
|
||||
/** Set how many monsters a player has killed. */
|
||||
public void setPlayerKills(Player player, int kills) throws Exception;
|
||||
/** Add to the number of monsters a player has killed. */
|
||||
public void addPlayerKills(Player player, int kills) throws Exception;
|
||||
/** Add one to the number of monsters a player has killed. */
|
||||
public default void incrementPlayerKills(Player player) throws Exception {
|
||||
addPlayerKills(player, 1);
|
||||
}
|
||||
|
||||
/** Add a player to the store if they are not already present, if necessary. */
|
||||
public default void initPlayer(Player player) throws Exception { }
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package me.jamestmartin.wasteland.listeners;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
import me.jamestmartin.wasteland.config.ChatConfig;
|
||||
|
||||
public class ChatListener implements Listener {
|
||||
private final ChatConfig config;
|
||||
|
||||
public ChatListener(final ChatConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onPlayerChat(final AsyncPlayerChatEvent event) {
|
||||
final Player player = event.getPlayer();
|
||||
if (player.hasPermission("wasteland.chat.officer")) {
|
||||
event.setFormat(config.getOfficerChatFormat(player));
|
||||
} else {
|
||||
event.setFormat(config.getPlayerChatFormat(player));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
package me.jamestmartin.wasteland.listeners;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDeathEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.permissions.PermissionAttachment;
|
||||
|
||||
import me.jamestmartin.wasteland.Wasteland;
|
||||
import me.jamestmartin.wasteland.ranks.EnlistedRank;
|
||||
|
||||
public class RankListener implements Listener, AutoCloseable {
|
||||
private Map<UUID, PermissionAttachment> attachments = new HashMap<>();
|
||||
private final Collection<EntityType> eligibleMobs;
|
||||
|
||||
public RankListener(Collection<EntityType> eligibleMobs) {
|
||||
this.eligibleMobs = eligibleMobs;
|
||||
|
||||
for (Player player : Wasteland.getInstance().getServer().getOnlinePlayers()) {
|
||||
try {
|
||||
initializePlayer(player);
|
||||
} catch (SQLException e) {
|
||||
Wasteland.getInstance().getLogger().log(Level.SEVERE, "Failed to get player's kills.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createAttachment(Player player) throws SQLException {
|
||||
PermissionAttachment attachment = player.addAttachment(Wasteland.getInstance());
|
||||
attachments.put(player.getUniqueId(), attachment);
|
||||
int kills = Wasteland.getInstance().getDatabase().getPlayerKills(player);
|
||||
Optional<EnlistedRank> rank = EnlistedRank.getRankFromKills(kills);
|
||||
if (rank.isPresent()) {
|
||||
attachment.setPermission(rank.get().getPermission(), true);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeAttachment(Player player) {
|
||||
PermissionAttachment attachment = attachments.remove(player.getUniqueId());
|
||||
player.removeAttachment(attachment);
|
||||
}
|
||||
|
||||
private void initializePlayer(Player player) throws SQLException {
|
||||
Wasteland.getInstance().getDatabase().initPlayerKills(player);
|
||||
createAttachment(player);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
try {
|
||||
initializePlayer(player);
|
||||
} catch (SQLException e) {
|
||||
Wasteland.getInstance().getLogger().log(Level.SEVERE, "Failed to get player's kills.", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void updatePlayerRank(Player player) throws SQLException {
|
||||
removeAttachment(player);
|
||||
createAttachment(player);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onEntityDeath(EntityDeathEvent event) {
|
||||
Player player = event.getEntity().getKiller();
|
||||
if (player == null) return;
|
||||
if (!eligibleMobs.contains(event.getEntityType())) return;
|
||||
|
||||
try {
|
||||
Wasteland.getInstance().getDatabase().incrementPlayerKills(player);
|
||||
Optional<EnlistedRank> oldRank = EnlistedRank.getEnlistedRank(player);
|
||||
updatePlayerRank(player);
|
||||
Optional<EnlistedRank> newRank = EnlistedRank.getEnlistedRank(player);
|
||||
|
||||
|
||||
if (newRank.isPresent()) {
|
||||
final String formatString;
|
||||
if (oldRank.isPresent()) {
|
||||
if (!newRank.get().equals(oldRank.get())) {
|
||||
formatString = "%s" + ChatColor.RESET + " has been promoted from %s " + ChatColor.RESET + "to %s" + ChatColor.RESET + "!";
|
||||
player.getServer().broadcastMessage(
|
||||
String.format(formatString, player.getDisplayName(),
|
||||
oldRank.get().formatFull(), newRank.get().formatFull()));
|
||||
}
|
||||
} else {
|
||||
formatString = "%s" + ChatColor.RESET + " has been promoted to %s" + ChatColor.RESET + "!";
|
||||
player.getServer().broadcastMessage(
|
||||
String.format(formatString, player.getDisplayName(), newRank.get()));
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
Wasteland.getInstance().getLogger().log(Level.SEVERE, "Failed to increment player kills.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerLeave(PlayerQuitEvent event) {
|
||||
PermissionAttachment attachment = attachments.remove(event.getPlayer().getUniqueId());
|
||||
event.getPlayer().removeAttachment(attachment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// Trying to remove attachments throws an error. Perhaps it's done automatically?
|
||||
/*for(Map.Entry<UUID, PermissionAttachment> attachment : attachments.entrySet()) {
|
||||
Wasteland.getInstance().getServer().getPlayer(attachment.getKey())
|
||||
.removeAttachment(attachment.getValue());
|
||||
attachments.remove(attachment.getKey());
|
||||
}*/
|
||||
attachments = null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package me.jamestmartin.wasteland.ranks;
|
||||
|
||||
public class AllRanks {
|
||||
private final EnlistedRanks enlistedRanks;
|
||||
private final Ranks<Rank> officerRanks;
|
||||
private final Rank consoleRank;
|
||||
|
||||
public AllRanks(EnlistedRanks enlistedRanks, Ranks<Rank> officerRanks, Rank consoleRank) {
|
||||
this.enlistedRanks = enlistedRanks;
|
||||
this.officerRanks = officerRanks;
|
||||
this.consoleRank = consoleRank;
|
||||
}
|
||||
|
||||
public EnlistedRanks getEnlistedRanks() {
|
||||
return enlistedRanks;
|
||||
}
|
||||
|
||||
public Ranks<Rank> getOfficerRanks() {
|
||||
return officerRanks;
|
||||
}
|
||||
|
||||
public Rank getConsoleRank() {
|
||||
return consoleRank;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package me.jamestmartin.wasteland.commands;
|
||||
package me.jamestmartin.wasteland.ranks;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
|
||||
|
@ -11,13 +10,18 @@ import org.bukkit.command.CommandSender;
|
|||
import org.bukkit.entity.Player;
|
||||
|
||||
import me.jamestmartin.wasteland.Wasteland;
|
||||
import me.jamestmartin.wasteland.ranks.EnlistedRank;
|
||||
import me.jamestmartin.wasteland.kills.KillsConfig;
|
||||
import me.jamestmartin.wasteland.kills.PlayerKillsProvider;
|
||||
|
||||
public class CommandRank implements CommandExecutor {
|
||||
private final PlayerKillsProvider killsProvider;
|
||||
private final PlayerRankProvider rankProvider;
|
||||
private final String eligibleMobsName;
|
||||
|
||||
public CommandRank(String eligibleMobsName) {
|
||||
this.eligibleMobsName = eligibleMobsName;
|
||||
public CommandRank(KillsConfig killsConfig, PlayerKillsProvider killsProvider, PlayerRankProvider rankProvider) {
|
||||
this.killsProvider = killsProvider;
|
||||
this.rankProvider = rankProvider;
|
||||
this.eligibleMobsName = killsConfig.getEligibleMobsName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -70,9 +74,9 @@ public class CommandRank implements CommandExecutor {
|
|||
}
|
||||
|
||||
try {
|
||||
int kills = Wasteland.getInstance().getDatabase().getPlayerKills(subject);
|
||||
Optional<EnlistedRank> rank = EnlistedRank.getRankFromKills(kills);
|
||||
Optional<EnlistedRank> nextRank = EnlistedRank.getNextRank(subject);
|
||||
int kills = killsProvider.getPlayerKills(subject);
|
||||
Optional<EnlistedRank> rank = rankProvider.getEnlistedRank(subject);
|
||||
Optional<EnlistedRank> nextRank = rankProvider.getNextRank(subject);
|
||||
if (rank.isPresent()) {
|
||||
sender.sendMessage(playerName + is + "rank " + rank.get().formatFull() + ChatColor.RESET + ".");
|
||||
}
|
||||
|
@ -92,7 +96,7 @@ public class CommandRank implements CommandExecutor {
|
|||
} else {
|
||||
sender.sendMessage(playerName + has + "reached maximum rank.");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
} catch (Exception e) {
|
||||
sender.sendMessage("Command failed due to database exception. Contact the server administrator.");
|
||||
Wasteland.getInstance().getLogger().log(Level.SEVERE, "Failed to get player kills.", e);
|
||||
}
|
|
@ -1,15 +1,16 @@
|
|||
package me.jamestmartin.wasteland.commands;
|
||||
package me.jamestmartin.wasteland.ranks;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import me.jamestmartin.wasteland.Wasteland;
|
||||
import me.jamestmartin.wasteland.config.WastelandConfig;
|
||||
import me.jamestmartin.wasteland.ranks.EnlistedRank;
|
||||
import me.jamestmartin.wasteland.ranks.Rank;
|
||||
|
||||
public class CommandRanks implements CommandExecutor {
|
||||
private final AllRanks ranks;
|
||||
|
||||
public CommandRanks(AllRanks ranks) {
|
||||
this.ranks = ranks;
|
||||
}
|
||||
|
||||
private String makeElement(Rank rank) {
|
||||
if (rank.getDescription().isPresent())
|
||||
return rank.formatExtended() + ": " + rank.getDescription().get();
|
||||
|
@ -23,15 +24,13 @@ public class CommandRanks implements CommandExecutor {
|
|||
return false;
|
||||
}
|
||||
|
||||
WastelandConfig config = Wasteland.getInstance().getSettings();
|
||||
|
||||
sender.sendMessage("Enlisted ranks:");
|
||||
for (EnlistedRank rank : config.enlistedRanks()) {
|
||||
for (EnlistedRank rank : ranks.getEnlistedRanks().getRanksList()) {
|
||||
sender.sendMessage("* " + makeElement(rank));
|
||||
}
|
||||
|
||||
sender.sendMessage("Officer (server staff) ranks:");
|
||||
for (Rank rank : config.officerRanks()) {
|
||||
for (Rank rank : ranks.getOfficerRanks().getRanksList()) {
|
||||
sender.sendMessage("* " + makeElement(rank));
|
||||
}
|
||||
|
|
@ -1,90 +1,45 @@
|
|||
package me.jamestmartin.wasteland.ranks;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
|
||||
import me.jamestmartin.wasteland.Wasteland;
|
||||
import org.bukkit.permissions.Permission;
|
||||
|
||||
public class EnlistedRank extends Rank {
|
||||
private final Optional<Integer> kills;
|
||||
|
||||
public EnlistedRank(Optional<String> defaultDescription, Optional<ChatColor> defaultDecoration,
|
||||
ConfigurationSection c) {
|
||||
super(defaultDescription, defaultDecoration, c);
|
||||
if (c.getKeys(false).contains("kills")) this.kills = Optional.of(c.getInt("kills"));
|
||||
else this.kills = Optional.empty();
|
||||
PermissionDefault def;
|
||||
if (kills.isPresent() && kills.get() == 0) def = PermissionDefault.TRUE;
|
||||
else def = PermissionDefault.FALSE;
|
||||
super.getPermission().setDefault(def);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getDescription() {
|
||||
Optional<String> format = super.getDescription();
|
||||
String descriptionFormat;
|
||||
if (format.isPresent()) descriptionFormat = format.get();
|
||||
else return Optional.empty();
|
||||
|
||||
String result;
|
||||
if (kills.isPresent()) result = descriptionFormat.replace("{kills}", kills.get().toString());
|
||||
else result = descriptionFormat;
|
||||
return Optional.of(result);
|
||||
}
|
||||
|
||||
public final Optional<Integer> getKills() {
|
||||
return kills;
|
||||
}
|
||||
|
||||
public static Optional<EnlistedRank> getEnlistedRank(Player player) {
|
||||
EnlistedRank result = null;
|
||||
for (EnlistedRank rank : Wasteland.getInstance().getSettings().enlistedRanks()) {
|
||||
if (player.hasPermission(rank.getPermission())) result = rank;
|
||||
}
|
||||
return Optional.ofNullable(result);
|
||||
}
|
||||
|
||||
public static Optional<EnlistedRank> getRankFromKills(int kills) {
|
||||
if (kills < 0) throw new IllegalArgumentException("Number of kills must not be negative.");
|
||||
return Wasteland.getInstance().getSettings().enlistedRanks()
|
||||
.stream().filter(rank -> rank.getKills().map(k -> k <= kills).orElse(false))
|
||||
.reduce((acc, rank) -> rank);
|
||||
}
|
||||
|
||||
public static Optional<EnlistedRank> getNextRank(Player player) {
|
||||
Optional<EnlistedRank> currentRank = getEnlistedRank(player);
|
||||
Stream<EnlistedRank> ranks = Wasteland.getInstance().getSettings().enlistedRanks().stream();
|
||||
if (!currentRank.isPresent()) {
|
||||
return ranks.filter(rank -> rank.kills.isPresent()).findFirst();
|
||||
}
|
||||
return ranks.filter(rank -> rank.kills.isPresent() && rank.isSuccessorOf(
|
||||
Wasteland.getInstance().getSettings().enlistedRanks().stream().map(x -> (Rank) x).collect(Collectors.toList())
|
||||
, currentRank.get())).findFirst();
|
||||
}
|
||||
|
||||
public static class EnlistedRankComparator implements Comparator<EnlistedRank> {
|
||||
private final Comparator<Rank> delegate;
|
||||
|
||||
public EnlistedRankComparator(Collection<EnlistedRank> ranks) {
|
||||
this.delegate = new RankComparator(
|
||||
ranks.stream().map((EnlistedRank x) -> (Rank) x).collect(Collectors.toList()));
|
||||
}
|
||||
private final Optional<Integer> kills;
|
||||
private final Permission preferencePermission;
|
||||
|
||||
@Override
|
||||
public int compare(EnlistedRank a, EnlistedRank b) {
|
||||
if (a.getKills().isPresent() && b.getKills().isPresent()) {
|
||||
if (a.getKills().get() > b.getKills().get()) return 1;
|
||||
if (b.getKills().get() > a.getKills().get()) return -1;
|
||||
}
|
||||
return delegate.compare(a, b);
|
||||
}
|
||||
}
|
||||
public EnlistedRank(
|
||||
String id,
|
||||
String fullName,
|
||||
String abbreviation,
|
||||
Optional<String> description,
|
||||
Optional<ChatColor> color,
|
||||
Optional<ChatColor> decoration,
|
||||
Permission permission,
|
||||
Optional<Integer> kills,
|
||||
Permission preferencePermission) {
|
||||
super(id, fullName, abbreviation, description, color, decoration, permission);
|
||||
|
||||
this.kills = kills;
|
||||
this.preferencePermission = preferencePermission;
|
||||
}
|
||||
|
||||
/** The number of kills you must receive to be promoted to this rank. */
|
||||
public Optional<Integer> getKills() {
|
||||
return kills;
|
||||
}
|
||||
|
||||
/** Whether this rank has a set number of kills required to receive promotion. */
|
||||
public boolean hasKills() {
|
||||
return getKills().isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* If a player has this permission set, they will receive this rank
|
||||
* instead of any alternative ranks, even if another rank is preferred by default.
|
||||
*/
|
||||
public Permission getPreferencePermission() {
|
||||
return preferencePermission;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
package me.jamestmartin.wasteland.ranks;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.google.common.graph.ImmutableValueGraph;
|
||||
|
||||
/** A collection of enlisted ranks and their successor/predecessor relationships. */
|
||||
public class EnlistedRanks extends Ranks<EnlistedRank> {
|
||||
/**
|
||||
* @param ranks
|
||||
* The edges represent predecessor/successor relationships,
|
||||
* and the edge value represents whether the successor is the preferred successor.
|
||||
* This graph *must* be a tree.
|
||||
* @param originalOrder
|
||||
* The order in which the ranks are listed in the configuration file.
|
||||
*/
|
||||
public EnlistedRanks(ImmutableValueGraph<EnlistedRank, Boolean> ranks, Optional<List<EnlistedRank>> originalOrder) {
|
||||
super(ranks, originalOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ranks
|
||||
* The edges represent predecessor/successor relationships,
|
||||
* and the edge value represents whether the successor is the preferred successor.
|
||||
* This graph *must* be a tree.
|
||||
* @param originalOrder
|
||||
* The order in which the ranks are listed in the configuration file.
|
||||
*/
|
||||
public EnlistedRanks(ImmutableValueGraph<EnlistedRank, Boolean> ranks, List<EnlistedRank> originalOrder) {
|
||||
super(ranks, originalOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ranks
|
||||
* The edges represent predecessor/successor relationships,
|
||||
* and the edge value represents whether the successor is the preferred successor.
|
||||
* This graph *must* be a tree.
|
||||
*/
|
||||
public EnlistedRanks(ImmutableValueGraph<EnlistedRank, Boolean> ranks) {
|
||||
super(ranks);
|
||||
}
|
||||
|
||||
/** Get the direct successors to the rank which can be automatically rewarded for killing monsters. */
|
||||
public Set<EnlistedRank> getPromotableSuccessors(EnlistedRank rank) {
|
||||
return getSuccessors(rank).stream().filter(EnlistedRank::hasKills).collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
/** Get the preferred direct successor to the rank if it can be automatically rewarded for killing monsters. */
|
||||
public Optional<EnlistedRank> getPreferredPromotableSuccessor(EnlistedRank rank) {
|
||||
return getPromotableSuccessors(rank).stream().filter(super::isPreferredSuccessor).findAny();
|
||||
}
|
||||
|
||||
/** Get the next rank that a player would get promoted to after the given rank, if any exists. */
|
||||
public Optional<EnlistedRank> getNextRank(Player player, EnlistedRank rank) {
|
||||
for (EnlistedRank successor : getPromotableSuccessors(rank)) {
|
||||
if (player.hasPermission(successor.getPreferencePermission())) {
|
||||
return Optional.of(successor);
|
||||
}
|
||||
}
|
||||
|
||||
return getPreferredPromotableSuccessor(rank);
|
||||
}
|
||||
|
||||
/** The rank which is automatically assigned to all players. */
|
||||
public EnlistedRank getDefaultRank() {
|
||||
return getRanks().stream().filter(rank -> rank.getKills().map(x -> x == 0).orElse(false)).findAny().get();
|
||||
}
|
||||
|
||||
/** The rank a player would have if they killed some number of monsters. */
|
||||
public EnlistedRank getRankAt(Player player, int kills) {
|
||||
EnlistedRank rank = getDefaultRank();
|
||||
while (true) {
|
||||
Optional<EnlistedRank> maybe = getNextRank(player, rank);
|
||||
if (!maybe.isPresent()) {
|
||||
break;
|
||||
}
|
||||
|
||||
EnlistedRank nextRank = maybe.get();
|
||||
|
||||
if (!nextRank.getKills().map(k -> kills >= k).orElseGet(() -> player.hasPermission(nextRank.getPermission()))) {
|
||||
break;
|
||||
}
|
||||
|
||||
rank = nextRank;
|
||||
}
|
||||
|
||||
return rank;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package me.jamestmartin.wasteland.ranks;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class PermissionsPlayerRankProvider implements PlayerRankProvider {
|
||||
private final AllRanks ranks;
|
||||
|
||||
public PermissionsPlayerRankProvider(AllRanks ranks) {
|
||||
this.ranks = ranks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AllRanks getRanks() {
|
||||
return ranks;
|
||||
}
|
||||
|
||||
private<T extends Rank> Optional<T> getRank(Player player, Ranks<T> ranks) {
|
||||
Set<T> playerRanks = new HashSet<>();
|
||||
for (T rank : ranks.getRanks()) {
|
||||
if (player.hasPermission(rank.getPermission())) {
|
||||
playerRanks.add(rank);
|
||||
}
|
||||
}
|
||||
|
||||
return ranks.getHighestRank(playerRanks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<EnlistedRank> getEnlistedRank(Player player) {
|
||||
return getRank(player, ranks.getEnlistedRanks());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Rank> getOfficerRank(Player player) {
|
||||
return getRank(player, ranks.getOfficerRanks());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package me.jamestmartin.wasteland.ranks;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public interface PlayerRankProvider {
|
||||
/** Get the ranks this player rank provider can choose from. */
|
||||
AllRanks getRanks();
|
||||
|
||||
/** Get the player's highest rank achieved by monster kills. */
|
||||
Optional<EnlistedRank> getEnlistedRank(Player player);
|
||||
|
||||
/** Get the player's highest staff rank. */
|
||||
Optional<Rank> getOfficerRank(Player player);
|
||||
|
||||
default PlayerRanks getPlayerRanks(final Player player) {
|
||||
return new PlayerRanks() {
|
||||
@Override
|
||||
public Optional<EnlistedRank> getEnlistedRank() {
|
||||
return PlayerRankProvider.this.getEnlistedRank(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Rank> getOfficerRank() {
|
||||
return PlayerRankProvider.this.getOfficerRank(player);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Get the player's highest rank overall. */
|
||||
default Optional<Rank> getHighestRank(Player player) {
|
||||
return getPlayerRanks(player).getHighestRank();
|
||||
}
|
||||
|
||||
/** The next rank a player will get promoted to from killing monsters. */
|
||||
default Optional<EnlistedRank> getNextRank(Player player) {
|
||||
return getEnlistedRank(player).flatMap(rank -> getRanks().getEnlistedRanks().getNextRank(player, rank));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package me.jamestmartin.wasteland.ranks;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface PlayerRanks {
|
||||
/** Get the player's highest rank achieved by monster kills. */
|
||||
Optional<EnlistedRank> getEnlistedRank();
|
||||
|
||||
/** Get the player's highest staff rank. */
|
||||
Optional<Rank> getOfficerRank();
|
||||
|
||||
/** Get the player's highest rank overall. */
|
||||
default Optional<Rank> getHighestRank() {
|
||||
return getOfficerRank().or(() -> getOfficerRank());
|
||||
}
|
||||
}
|
|
@ -1,151 +1,101 @@
|
|||
package me.jamestmartin.wasteland.ranks;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import me.jamestmartin.wasteland.Wasteland;
|
||||
import me.jamestmartin.wasteland.config.WastelandConfig;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
|
||||
public class Rank {
|
||||
private final String id, abbreviation, name;
|
||||
private final Optional<String> description;
|
||||
private final Optional<ChatColor> color, decoration;
|
||||
private final Permission permission;
|
||||
private final Optional<String> predecessor;
|
||||
private final boolean preferred;
|
||||
|
||||
public Rank(Optional<String> defaultDescription, Optional<ChatColor> defaultDecoration,
|
||||
ConfigurationSection c) {
|
||||
this.id = c.getName();
|
||||
this.name = c.getString("name", id);
|
||||
this.abbreviation = c.getString("abbreviation", name);
|
||||
Optional<String> description = Optional.ofNullable(c.getString("description"));
|
||||
if (!description.isPresent()) this.description = defaultDescription;
|
||||
else this.description = description;
|
||||
this.color = WastelandConfig.readColor(c, "color");
|
||||
Optional<ChatColor> decoration = WastelandConfig.readColor(c, "decoration");
|
||||
if (!decoration.isPresent()) this.decoration = defaultDecoration;
|
||||
else this.decoration = decoration;
|
||||
this.predecessor = Optional.ofNullable(c.getString("succeeds"));
|
||||
this.preferred = c.getBoolean("preferred", true);
|
||||
Map<String, Boolean> children = new HashMap<>();
|
||||
if (predecessor.isPresent()) {
|
||||
children.put(predecessor.get(), true);
|
||||
}
|
||||
this.permission =
|
||||
new Permission("wasteland.rank." + id, description.orElse(null), PermissionDefault.FALSE, children);
|
||||
}
|
||||
|
||||
public String getId() { return this.id; }
|
||||
public Permission getPermission() { return this.permission; }
|
||||
public String getAbbreviation() { return this.abbreviation; }
|
||||
public String getFullName() { return this.name; }
|
||||
public Optional<String> getDescription() { return description; }
|
||||
public Optional<ChatColor> getColor() { return this.color; }
|
||||
public Optional<ChatColor> getDecoration() { return this.decoration; }
|
||||
public Optional<String> getPredecessor() { return this.predecessor; }
|
||||
public boolean isPreferred() { return this.preferred; }
|
||||
|
||||
public boolean isSuccessorOf(Collection<Rank> ranks, Rank other) {
|
||||
if (this == other) return false;
|
||||
if (this.getId().equals(other.getId())) return false;
|
||||
if (!this.getPredecessor().isPresent()) return false;
|
||||
if (this.getPredecessor().get().equals(other.getId())) return true;
|
||||
String predecessorId = this.getPredecessor().get();
|
||||
for (Rank predecessor : ranks) {
|
||||
if (predecessor.getId().equals(predecessorId))
|
||||
return predecessor.isSuccessorOf(ranks, other);
|
||||
}
|
||||
throw new IllegalArgumentException("Predecessor rank " + predecessorId + " not found.");
|
||||
}
|
||||
|
||||
public final String getFormat() {
|
||||
String color = this.color.map(ChatColor::toString).orElse("");
|
||||
String decoration = this.decoration.map(ChatColor::toString).orElse("");
|
||||
return color + decoration;
|
||||
}
|
||||
|
||||
/** The rank abbreviation with chat formatting codes. */
|
||||
public final String formatAbbreviated() {
|
||||
return getFormat() + getAbbreviation();
|
||||
}
|
||||
|
||||
/** The rank name with chat formatting codes. */
|
||||
public final String formatFull() {
|
||||
return getFormat() + getFullName();
|
||||
}
|
||||
|
||||
/** `Rank Name (RankAbbr)` with chat formatting codes. */
|
||||
public final String formatExtended() {
|
||||
return formatFull() + ChatColor.RESET + " (" + formatAbbreviated() + ChatColor.RESET + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param player
|
||||
* @return Does the player have this rank?
|
||||
*/
|
||||
public final boolean hasRank(Player player) {
|
||||
return player.hasPermission(getPermission());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.abbreviation;
|
||||
}
|
||||
|
||||
public static Optional<Rank> getOfficerRank(Player player) {
|
||||
Rank result = null;
|
||||
for (Rank rank : Wasteland.getInstance().getSettings().officerRanks()) {
|
||||
if (player.hasPermission(rank.getPermission())) result = rank;
|
||||
}
|
||||
return Optional.ofNullable(result);
|
||||
}
|
||||
|
||||
public static Optional<Rank> getHighestRank(Player player) {
|
||||
Optional<Rank> officerRank = getOfficerRank(player);
|
||||
if (officerRank.isPresent()) return officerRank;
|
||||
return EnlistedRank.getEnlistedRank(player).map(x -> (Rank) x);
|
||||
}
|
||||
|
||||
public static Optional<Rank> getConsoleRank() {
|
||||
return Wasteland.getInstance().getSettings().consoleRank();
|
||||
}
|
||||
|
||||
public static Optional<Rank> getRank(RankType type, Player player) {
|
||||
switch (type) {
|
||||
case CONSOLE:
|
||||
return getConsoleRank();
|
||||
case ENLISTED:
|
||||
return EnlistedRank.getEnlistedRank(player).map(x -> x);
|
||||
case HIGHEST:
|
||||
return getHighestRank(player);
|
||||
case OFFICER:
|
||||
return getOfficerRank(player);
|
||||
}
|
||||
throw new IllegalStateException("Unknown rank type.");
|
||||
}
|
||||
|
||||
public static class RankComparator implements Comparator<Rank> {
|
||||
private final Collection<Rank> ranks;
|
||||
|
||||
public RankComparator(Collection<Rank> ranks) { this.ranks = ranks; }
|
||||
|
||||
@Override
|
||||
public int compare(Rank a, Rank b) {
|
||||
if (a == b) return 0;
|
||||
if (a.getId().equals(b.getId())) return 0;
|
||||
if (a.isSuccessorOf(ranks, b)) return 1;
|
||||
if (b.isSuccessorOf(ranks, a)) return -1;
|
||||
// incomparable
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
private final String id;
|
||||
private final String fullName;
|
||||
private final String abbreviation;
|
||||
private final Optional<String> description;
|
||||
private final Optional<ChatColor> color;
|
||||
private final Optional<ChatColor> decoration;
|
||||
private final Permission permission;
|
||||
|
||||
public Rank(
|
||||
String id,
|
||||
String fullName,
|
||||
String abbreviation,
|
||||
Optional<String> description,
|
||||
Optional<ChatColor> color,
|
||||
Optional<ChatColor> decoration,
|
||||
Permission permission) {
|
||||
this.id = id;
|
||||
this.fullName = fullName;
|
||||
this.abbreviation = abbreviation;
|
||||
this.description = description;
|
||||
this.color = color;
|
||||
this.decoration = decoration;
|
||||
this.permission = permission;
|
||||
}
|
||||
|
||||
/** Get this rank's internal identifier, e.g. gysgt. */
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/** Get the full name of this rank, e.g. Gunnery Sergeant. */
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
/** Get the abbreviation for this rank, e.g. GySgt. */
|
||||
public String getAbbreviation() {
|
||||
return abbreviation;
|
||||
}
|
||||
|
||||
/** Get this rank's description, if it has one. */
|
||||
public Optional<String> getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/** Get this rank's color, if it has one. */
|
||||
public Optional<ChatColor> getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/** Get this rank's decoration (e.g. bold or italic), if it has one. */
|
||||
public Optional<ChatColor> getDecoration() {
|
||||
return decoration;
|
||||
}
|
||||
|
||||
/** Get the permission which all players with this rank have. */
|
||||
public Permission getPermission() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
/** Check whether a player has this rank via permissions. */
|
||||
public boolean hasRank(Player player) {
|
||||
return player.hasPermission(getPermission());
|
||||
}
|
||||
|
||||
/** Get the chat formatting codes for this rank, i.e. the color and decoration combined. */
|
||||
public String getFormat() {
|
||||
String color = this.color.map(ChatColor::toString).orElse("");
|
||||
String decoration = this.decoration.map(ChatColor::toString).orElse("");
|
||||
return color + decoration;
|
||||
}
|
||||
|
||||
/** The rank abbreviation with chat formatting codes. */
|
||||
public String formatAbbreviated() {
|
||||
return getFormat() + getAbbreviation();
|
||||
}
|
||||
|
||||
/** The rank name with chat formatting codes. */
|
||||
public String formatFull() {
|
||||
return getFormat() + getFullName();
|
||||
}
|
||||
|
||||
/** `Rank Name (RankAbbr)` with chat formatting codes. */
|
||||
public String formatExtended() {
|
||||
return formatFull() + ChatColor.RESET + " (" + formatAbbreviated() + ChatColor.RESET + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getAbbreviation();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
package me.jamestmartin.wasteland.ranks;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.permissions.PermissionAttachment;
|
||||
|
||||
import me.jamestmartin.wasteland.Wasteland;
|
||||
import me.jamestmartin.wasteland.kills.PlayerKillsStore;
|
||||
|
||||
public class RankAttachments {
|
||||
private final EnlistedRanks ranks;
|
||||
private final PlayerKillsStore killsStore;
|
||||
|
||||
private final Map<UUID, PermissionAttachment> attachments = new HashMap<>();
|
||||
|
||||
public RankAttachments(EnlistedRanks ranks, PlayerKillsStore killsStore) {
|
||||
this.ranks = ranks;
|
||||
this.killsStore = killsStore;
|
||||
}
|
||||
|
||||
public void removeAttachment(Player player) {
|
||||
PermissionAttachment attachment = attachments.remove(player.getUniqueId());
|
||||
player.removeAttachment(attachment);
|
||||
}
|
||||
|
||||
private void createAttachment(Player player, int kills) throws Exception {
|
||||
PermissionAttachment attachment = player.addAttachment(Wasteland.getInstance());
|
||||
attachments.put(player.getUniqueId(), attachment);
|
||||
EnlistedRank rank = ranks.getRankAt(player, kills);
|
||||
for (EnlistedRank child : ranks.getTransitiveReflexivePredecessors(rank)) {
|
||||
attachment.setPermission(child.getPermission(), true);
|
||||
}
|
||||
}
|
||||
|
||||
private void createAttachment(Player player) throws Exception {
|
||||
int kills = killsStore.getPlayerKills(player);
|
||||
createAttachment(player, kills);
|
||||
}
|
||||
|
||||
public void initializePlayer(Player player) throws Exception {
|
||||
killsStore.initPlayer(player);
|
||||
createAttachment(player);
|
||||
}
|
||||
|
||||
public void updatePlayerRank(Player player) throws Exception {
|
||||
removeAttachment(player);
|
||||
createAttachment(player);
|
||||
}
|
||||
|
||||
private void updatePlayerRank(Player player, int kills) throws Exception {
|
||||
removeAttachment(player);
|
||||
createAttachment(player, kills);
|
||||
}
|
||||
|
||||
public void register() {
|
||||
for (Player player : Wasteland.getInstance().getServer().getOnlinePlayers()) {
|
||||
try {
|
||||
initializePlayer(player);
|
||||
} catch (Exception e) {
|
||||
Wasteland.getInstance().getLogger().log(Level.SEVERE, "Failed to get player's kills.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void unregister() {
|
||||
// Trying to remove attachments throws an error. Perhaps it's done automatically?
|
||||
for(Map.Entry<UUID, PermissionAttachment> attachment : attachments.entrySet()) {
|
||||
Wasteland.getInstance().getServer().getPlayer(attachment.getKey())
|
||||
.removeAttachment(attachment.getValue());
|
||||
attachments.remove(attachment.getKey());
|
||||
}
|
||||
attachments.clear();
|
||||
}
|
||||
|
||||
public class AttachmentUpdatingPlayerKillsStore implements PlayerKillsStore {
|
||||
@Override
|
||||
public int getPlayerKills(Player player) throws Exception {
|
||||
return killsStore.getPlayerKills(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerKills(Player player, int kills) throws Exception {
|
||||
killsStore.setPlayerKills(player, kills);
|
||||
RankAttachments.this.updatePlayerRank(player, kills);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPlayerKills(Player player, int kills) throws Exception {
|
||||
killsStore.addPlayerKills(player, kills);
|
||||
RankAttachments.this.updatePlayerRank(player);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package me.jamestmartin.wasteland.ranks;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import me.jamestmartin.wasteland.Wasteland;
|
||||
|
||||
public class RankAttachmentsListener implements Listener {
|
||||
private final RankAttachments attachments;
|
||||
|
||||
public RankAttachmentsListener(RankAttachments attachments) {
|
||||
this.attachments = attachments;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
try {
|
||||
attachments.initializePlayer(player);
|
||||
} catch (Exception e) {
|
||||
Wasteland.getInstance().getLogger().log(Level.SEVERE, "Failed to get player's kills.", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerLeave(PlayerQuitEvent event) {
|
||||
attachments.removeAttachment(event.getPlayer());
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package me.jamestmartin.wasteland.ranks;
|
||||
|
||||
public enum RankType {
|
||||
ENLISTED,
|
||||
OFFICER,
|
||||
CONSOLE,
|
||||
/** Either enlisted, or officer if the player is one. */
|
||||
HIGHEST;
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
package me.jamestmartin.wasteland.ranks;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.graph.ImmutableValueGraph;
|
||||
|
||||
/** A collection of ranks and their successor/predecessor relationships. */
|
||||
public class Ranks<T extends Rank> {
|
||||
protected final ImmutableValueGraph<T, Boolean> ranks;
|
||||
protected final Optional<List<T>> originalOrder;
|
||||
|
||||
/**
|
||||
* @param ranks
|
||||
* The edges represent predecessor/successor relationships,
|
||||
* and the edge value represents whether the successor is the preferred successor.
|
||||
* This graph *must* be a tree.
|
||||
* @param originalOrder
|
||||
* The order in which the ranks are listed in the configuration file.
|
||||
*/
|
||||
public Ranks(ImmutableValueGraph<T, Boolean> ranks, Optional<List<T>> originalOrder) {
|
||||
this.ranks = ranks;
|
||||
this.originalOrder = originalOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ranks
|
||||
* The edges represent predecessor/successor relationships,
|
||||
* and the edge value represents whether the successor is the preferred successor.
|
||||
* This graph *must* be a tree.
|
||||
* @param originalOrder
|
||||
* The order in which the ranks are listed in the configuration file.
|
||||
*/
|
||||
public Ranks(ImmutableValueGraph<T, Boolean> ranks, List<T> originalOrder) {
|
||||
this(ranks, Optional.of(originalOrder));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ranks
|
||||
* The edges represent predecessor/successor relationships,
|
||||
* and the edge value represents whether the successor is the preferred successor.
|
||||
* This graph *must* be a tree.
|
||||
*/
|
||||
public Ranks(ImmutableValueGraph<T, Boolean> ranks) {
|
||||
this(ranks, Optional.empty());
|
||||
}
|
||||
|
||||
/** Get all of the ranks in this collection. */
|
||||
public Set<T> getRanks() {
|
||||
return ranks.nodes();
|
||||
}
|
||||
|
||||
/** Get a list of the ranks in their original order, or, if that is not specified, ordered least to greatest. */
|
||||
public List<T> getRanksList() {
|
||||
return originalOrder.orElseGet(() -> Lists.reverse(getRanks().stream().sorted(getComparator()).collect(Collectors.toUnmodifiableList())));
|
||||
}
|
||||
|
||||
/** Get a rank by its id. */
|
||||
public Optional<T> getRank(String id) {
|
||||
return getRanks().stream().filter(rank -> rank.getId().equals(id)).findAny();
|
||||
}
|
||||
|
||||
/** The rank's unique predecessor, if it exists. */
|
||||
public Optional<T> getPredecessor(T rank) {
|
||||
return ranks.predecessors(rank).stream().findAny();
|
||||
}
|
||||
|
||||
public boolean hasPredecessor(T rank) {
|
||||
return getPredecessor(rank).isPresent();
|
||||
}
|
||||
|
||||
/** Every rank which is a transitive predecessor to the given rank. */
|
||||
public Set<T> getTransitivePredecessors(T rank) {
|
||||
Set<T> predecessors = new HashSet<>();
|
||||
while (hasPredecessor(rank)) {
|
||||
rank = getPredecessor(rank).get();
|
||||
predecessors.add(rank);
|
||||
}
|
||||
return predecessors;
|
||||
}
|
||||
|
||||
/** Every rank which is a transitive predecessor to the given rank, including the rank itself. */
|
||||
public Set<T> getTransitiveReflexivePredecessors(T rank) {
|
||||
Set<T> predecessors = getTransitivePredecessors(rank);
|
||||
predecessors.add(rank);
|
||||
return predecessors;
|
||||
}
|
||||
|
||||
/** The set of *direct* successors to the rank. */
|
||||
public Set<T> getSuccessors(T rank) {
|
||||
return ranks.successors(rank);
|
||||
}
|
||||
|
||||
/** The rank's preferred successor, if it exists. */
|
||||
public Optional<T> getPreferredSuccessor(T rank) {
|
||||
return getSuccessors(rank).stream().filter(successor -> ranks.edgeValueOrDefault(rank, successor, false)).findAny();
|
||||
}
|
||||
|
||||
/** Is the rank the preferred successor to its predecessor, if it has one? */
|
||||
public boolean isPreferredSuccessor(final T rank) {
|
||||
// Future Guava versions return an Optional here in the first place.
|
||||
return getPredecessor(rank).flatMap(predecessor -> Optional.ofNullable(ranks.edgeValue(predecessor, rank))).orElse(false);
|
||||
}
|
||||
|
||||
/** Is the second rank a *direct* successor of the first rank? */
|
||||
public boolean isSuccessorOf(T predecessor, T successor) {
|
||||
// Replace with this code for Guava 25+.
|
||||
//return ranks.hasEdgeConnecting(predecessor, successor);
|
||||
return ranks.successors(predecessor).contains(successor);
|
||||
}
|
||||
|
||||
/** Is the second rank a transitive successor of the first rank? */
|
||||
public boolean isTransitiveSuccessorOf(T predecessor, T successor) {
|
||||
return isSuccessorOf(predecessor, successor)
|
||||
|| getPredecessor(successor).map(rank -> isTransitiveSuccessorOf(predecessor, rank)).orElse(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* The highest ranks of a collection of ranks is any rank which has no transitive successor.
|
||||
* If there are multiple such ranks in this collection, the first is returned.
|
||||
*/
|
||||
public Optional<T> getHighestRank(Collection<T> ranks) {
|
||||
return ranks.stream().filter(rank -> !ranks.stream().anyMatch(other -> isTransitiveSuccessorOf(rank, other))).findFirst();
|
||||
}
|
||||
|
||||
/** Compare ranks by their predecessor/successor relationship. */
|
||||
public class RankComparator implements Comparator<T> {
|
||||
@Override
|
||||
public int compare(T a, T b) {
|
||||
if (a == b) return 0;
|
||||
if (a.getId().equals(b.getId())) return 0;
|
||||
if (Ranks.this.isSuccessorOf(a, b)) return 1;
|
||||
if (Ranks.this.isSuccessorOf(b, a)) return -1;
|
||||
|
||||
// Preferred ranks are given precedence over other ranks.
|
||||
Optional<T> predA = Ranks.this.getPredecessor(a);
|
||||
Optional<T> predB = Ranks.this.getPredecessor(b);
|
||||
if (predA.isPresent() && predB.isPresent() && predA.get().getId().equals(predB.get().getId())) {
|
||||
if (Ranks.this.isPreferredSuccessor(a)) {
|
||||
return 1;
|
||||
}
|
||||
if (Ranks.this.isPreferredSuccessor(b)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// incomparable
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public RankComparator getComparator() {
|
||||
return new RankComparator();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package me.jamestmartin.wasteland.ranks;
|
||||
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.jamestmartin.wasteland.Substate;
|
||||
import me.jamestmartin.wasteland.kills.KillsConfig;
|
||||
import me.jamestmartin.wasteland.kills.PlayerKillsStore;
|
||||
|
||||
public class RanksState implements Substate {
|
||||
private final RankAttachmentsListener rankAttachmentsListener;
|
||||
|
||||
private final CommandRank commandRank;
|
||||
private final CommandRanks commandRanks;
|
||||
|
||||
private final RankAttachments rankAttachments;
|
||||
private final PlayerKillsStore killsStore;
|
||||
|
||||
public RanksState(KillsConfig killsConfig, PlayerKillsStore killsStore, PlayerRankProvider rankProvider) {
|
||||
this.rankAttachments = new RankAttachments(rankProvider.getRanks().getEnlistedRanks(), killsStore);
|
||||
this.rankAttachmentsListener = new RankAttachmentsListener(rankAttachments);
|
||||
this.killsStore = rankAttachments.new AttachmentUpdatingPlayerKillsStore();
|
||||
|
||||
this.commandRank = new CommandRank(killsConfig, this.killsStore, rankProvider);
|
||||
this.commandRanks = new CommandRanks(rankProvider.getRanks());
|
||||
}
|
||||
|
||||
public PlayerKillsStore getPlayerKillsStore() {
|
||||
return killsStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(JavaPlugin plugin) {
|
||||
rankAttachments.register();
|
||||
plugin.getServer().getPluginManager().registerEvents(rankAttachmentsListener, plugin);
|
||||
|
||||
plugin.getCommand("rank").setExecutor(commandRank);
|
||||
plugin.getCommand("ranks").setExecutor(commandRanks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(JavaPlugin plugin) {
|
||||
PlayerJoinEvent.getHandlerList().unregister(rankAttachmentsListener);
|
||||
PlayerQuitEvent.getHandlerList().unregister(rankAttachmentsListener);
|
||||
|
||||
plugin.getCommand("rank").setExecutor(null);
|
||||
plugin.getCommand("ranks").setExecutor(null);
|
||||
|
||||
rankAttachments.unregister();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package me.jamestmartin.wasteland.commands;
|
||||
package me.jamestmartin.wasteland.spawns;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
|
@ -10,9 +10,12 @@ import org.bukkit.command.CommandSender;
|
|||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import me.jamestmartin.wasteland.Wasteland;
|
||||
|
||||
public class CommandDebugSpawn implements CommandExecutor {
|
||||
private final WastelandSpawner spawner;
|
||||
|
||||
public CommandDebugSpawn(WastelandSpawner spawner) {
|
||||
this.spawner = spawner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
|
@ -40,7 +43,7 @@ public class CommandDebugSpawn implements CommandExecutor {
|
|||
|
||||
int successfulSpawns = 0;
|
||||
for (int attempt = 0; attempt < attempts; attempt++) {
|
||||
Optional<LivingEntity> tryMonster = Wasteland.getInstance().getSpawner().trySpawn(rand, player.getLocation());
|
||||
Optional<LivingEntity> tryMonster = spawner.trySpawn(rand, player.getLocation());
|
||||
if (tryMonster.isEmpty()) {
|
||||
continue;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package me.jamestmartin.wasteland.commands;
|
||||
package me.jamestmartin.wasteland.spawns;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map.Entry;
|
||||
|
@ -10,12 +10,15 @@ import org.bukkit.command.CommandExecutor;
|
|||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import me.jamestmartin.wasteland.Wasteland;
|
||||
import me.jamestmartin.wasteland.spawns.MonsterType;
|
||||
import me.jamestmartin.wasteland.spawns.WastelandSpawner;
|
||||
import me.jamestmartin.wasteland.world.MoonPhase;
|
||||
|
||||
public class CommandDebugSpawnWeights implements CommandExecutor {
|
||||
private final WastelandSpawner spawner;
|
||||
|
||||
public CommandDebugSpawnWeights(WastelandSpawner spawner) {
|
||||
this.spawner = spawner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length > 0) {
|
||||
|
@ -28,8 +31,6 @@ public class CommandDebugSpawnWeights implements CommandExecutor {
|
|||
return false;
|
||||
}
|
||||
|
||||
WastelandSpawner spawner = Wasteland.getInstance().getSpawner();
|
||||
|
||||
Player player = (Player) sender;
|
||||
Block target = player.getTargetBlockExact(25).getRelative(BlockFace.UP);
|
||||
|
||||
|
@ -39,18 +40,18 @@ public class CommandDebugSpawnWeights implements CommandExecutor {
|
|||
int moonlight = MoonPhase.getMoonlight(target);
|
||||
int sunlight = MoonPhase.getSunlight(target);
|
||||
int blocklight = target.getLightFromBlocks();
|
||||
HashMap<MonsterType, Float> weights = spawner.calculateSpawnProbabilities(target);
|
||||
HashMap<MonsterType, Double> weights = spawner.calculateSpawnProbabilities(target);
|
||||
|
||||
sender.sendMessage("Moon phase: " + phase);
|
||||
sender.sendMessage("Moonlight: " + moonlight + ", Sunlight: " + sunlight + ", Blocklight: " + blocklight);
|
||||
sender.sendMessage("Monsters: " + monsters + ", Players: " + players);
|
||||
sender.sendMessage("Monster weights at " + target.getX() + ", " + target.getY() + ", " + target.getZ() + ":");
|
||||
for (Entry<MonsterType, Float> entry : weights.entrySet()) {
|
||||
for (Entry<MonsterType, Double> entry : weights.entrySet()) {
|
||||
MonsterType type = entry.getKey();
|
||||
float weight = entry.getValue();
|
||||
float lightProb = spawner.calculateLightLevelProbability(type, target);
|
||||
float phaseMult = spawner.getMoonPhaseMultiplier(type, target);
|
||||
float entitiesMult = WastelandSpawner.calculateNearbyEntitiesMultiplier(target);
|
||||
double weight = entry.getValue();
|
||||
double lightProb = spawner.calculateLightLevelProbability(type, target);
|
||||
double phaseMult = spawner.getMoonPhaseMultiplier(type, target);
|
||||
double entitiesMult = WastelandSpawner.calculateNearbyEntitiesMultiplier(target);
|
||||
sender.sendMessage(String.format("* %s: %.2f (light: %.2f, phase: %.2f, entities: %.2f)", type, weight, lightProb, phaseMult, entitiesMult));
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
package me.jamestmartin.wasteland.spawns;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
|
||||
import me.jamestmartin.wasteland.world.MoonPhase;
|
||||
|
||||
public class MonsterSpawnConfig {
|
||||
private final double maximumLightLevel;
|
||||
private final double blocklightWeight;
|
||||
private final double sunlightWeight;
|
||||
private final double moonlightWeight;
|
||||
|
||||
private final int maximumYLevel;
|
||||
private final int minimumYLevel;
|
||||
|
||||
private final Map<MoonPhase, Double> phaseMultipliers;
|
||||
|
||||
public MonsterSpawnConfig(
|
||||
double maximumLightLevel,
|
||||
double blocklightWeight,
|
||||
double sunlightWeight,
|
||||
double moonlightWeight,
|
||||
int maximumYLevel,
|
||||
int minimumYLevel,
|
||||
Map<MoonPhase, Double> phaseMultipliers
|
||||
) {
|
||||
if (maximumLightLevel < 0) {
|
||||
throw new IllegalArgumentException("Maximum light level cannot be negative.");
|
||||
}
|
||||
if (maximumYLevel < 0) {
|
||||
throw new IllegalArgumentException("Maximum Y level cannot be negative.");
|
||||
}
|
||||
if (minimumYLevel < 0) {
|
||||
throw new IllegalArgumentException("Minimum Y level cannot be negative.");
|
||||
}
|
||||
|
||||
this.maximumLightLevel = maximumLightLevel;
|
||||
this.blocklightWeight = blocklightWeight;
|
||||
this.sunlightWeight = sunlightWeight;
|
||||
this.moonlightWeight = moonlightWeight;
|
||||
this.maximumYLevel = maximumYLevel;
|
||||
this.minimumYLevel = minimumYLevel;
|
||||
this.phaseMultipliers = phaseMultipliers;
|
||||
}
|
||||
|
||||
public double getMaximumLightLevel() {
|
||||
return maximumLightLevel;
|
||||
}
|
||||
|
||||
public double getBlocklightWeight() {
|
||||
return blocklightWeight;
|
||||
}
|
||||
|
||||
public double getSunlightWeight() {
|
||||
return sunlightWeight;
|
||||
}
|
||||
|
||||
public double getMoonlightWeight() {
|
||||
return moonlightWeight;
|
||||
}
|
||||
|
||||
public double calculateWeightedBlocklightLevel(int blocklight) {
|
||||
return blocklight * getBlocklightWeight();
|
||||
}
|
||||
|
||||
public double calculateWeightedBlocklightLevel(Block block) {
|
||||
return calculateWeightedBlocklightLevel(block.getLightFromBlocks());
|
||||
}
|
||||
|
||||
public double calculateWeightedSkylightLevel(int sunlight, int moonlight) {
|
||||
return sunlight * getSunlightWeight() + moonlight * getMoonlightWeight();
|
||||
}
|
||||
|
||||
public double calculateWeightedSkylightLevel(Block block) {
|
||||
return calculateWeightedSkylightLevel(MoonPhase.getSunlight(block), MoonPhase.getMoonlight(block));
|
||||
}
|
||||
|
||||
public double calculateWeightedLightLevel(int blocklight, int sunlight, int moonlight) {
|
||||
return Math.max(calculateWeightedBlocklightLevel(blocklight), calculateWeightedSkylightLevel(sunlight, moonlight));
|
||||
}
|
||||
|
||||
public double calculateWeightedLightLevel(Block block) {
|
||||
return Math.max(calculateWeightedBlocklightLevel(block), calculateWeightedSkylightLevel(block));
|
||||
}
|
||||
|
||||
public boolean isWeightedLightLevelWithinBounds(int blocklight, int sunlight, int moonlight) {
|
||||
return calculateWeightedLightLevel(blocklight, sunlight, moonlight) < getMaximumLightLevel();
|
||||
}
|
||||
|
||||
public boolean isWeightedLightLevelWithinBounds(Block block) {
|
||||
return calculateWeightedLightLevel(block) < getMaximumLightLevel();
|
||||
}
|
||||
|
||||
public int getMinimumYLevel() {
|
||||
return minimumYLevel;
|
||||
}
|
||||
|
||||
public int getMaximumYLevel() {
|
||||
return maximumYLevel;
|
||||
}
|
||||
|
||||
public boolean isYLevelWithinBounds(int yLevel) {
|
||||
return yLevel > getMinimumYLevel() && yLevel < getMaximumYLevel();
|
||||
}
|
||||
|
||||
public boolean isWithinBounds(Block block) {
|
||||
return isYLevelWithinBounds(block.getY());
|
||||
}
|
||||
|
||||
public boolean isWithinBounds(Location location) {
|
||||
return isYLevelWithinBounds(location.getBlockY());
|
||||
}
|
||||
|
||||
public Map<MoonPhase, Double> getPhaseMultipliers() {
|
||||
return phaseMultipliers;
|
||||
}
|
||||
|
||||
public Double getPhaseMultiplier(MoonPhase phase) {
|
||||
return getPhaseMultipliers().get(phase);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package me.jamestmartin.wasteland.spawns;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class SpawnsConfig {
|
||||
private final Map<MonsterType, MonsterSpawnConfig> monsterConfigs;
|
||||
|
||||
public SpawnsConfig(Map<MonsterType, MonsterSpawnConfig> monsterConfigs) {
|
||||
this.monsterConfigs = monsterConfigs;
|
||||
}
|
||||
|
||||
public Map<MonsterType, MonsterSpawnConfig> getMonsterConfigs() {
|
||||
return monsterConfigs;
|
||||
}
|
||||
|
||||
public MonsterSpawnConfig getMonster(MonsterType type) {
|
||||
return monsterConfigs.get(type);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package me.jamestmartin.wasteland.spawns;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.jamestmartin.wasteland.Substate;
|
||||
|
||||
public class SpawnsState implements Substate {
|
||||
private final CommandDebugSpawn commandDebugSpawn;
|
||||
private final CommandDebugSpawnWeights commandDebugSpawnWeights;
|
||||
|
||||
public SpawnsState(SpawnsConfig config) {
|
||||
WastelandSpawner spawner = new WastelandSpawner(config);
|
||||
|
||||
commandDebugSpawn = new CommandDebugSpawn(spawner);
|
||||
commandDebugSpawnWeights = new CommandDebugSpawnWeights(spawner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(JavaPlugin plugin) {
|
||||
plugin.getCommand("debugspawn").setExecutor(commandDebugSpawn);
|
||||
plugin.getCommand("debugspawnweights").setExecutor(commandDebugSpawnWeights);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(JavaPlugin plugin) {
|
||||
plugin.getCommand("debugspawn").setExecutor(commandDebugSpawn);
|
||||
plugin.getCommand("debugspawnweights").setExecutor(commandDebugSpawnWeights);
|
||||
}
|
||||
}
|
|
@ -17,15 +17,14 @@ import org.bukkit.entity.LivingEntity;
|
|||
import org.bukkit.entity.Monster;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import me.jamestmartin.wasteland.config.MonsterSpawnConfig;
|
||||
import me.jamestmartin.wasteland.util.Pair;
|
||||
import me.jamestmartin.wasteland.world.MoonPhase;
|
||||
|
||||
public class WastelandSpawner {
|
||||
private final Map<MonsterType, MonsterSpawnConfig> monsters;
|
||||
private final SpawnsConfig config;
|
||||
|
||||
public WastelandSpawner(Map<MonsterType, MonsterSpawnConfig> monsters) {
|
||||
this.monsters = monsters;
|
||||
public WastelandSpawner(SpawnsConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public static Collection<MonsterType> spawnableMonstersAt(Location location) {
|
||||
|
@ -70,47 +69,39 @@ public class WastelandSpawner {
|
|||
*
|
||||
* Graph: https://www.wolframalpha.com/input/?i=f%28x%29+%3D++e%5E%286%282x+-+1%29%29+%2F+%28e%5E%286%282x+-+1%29%29+%2B+1%29++from+0+to+1
|
||||
*/
|
||||
private static float logistic(float x) {
|
||||
float eToX = (float) Math.pow(Math.E, 12 * x - 6);
|
||||
private static double logistic(double x) {
|
||||
double eToX = Math.pow(Math.E, 12 * x - 6);
|
||||
return eToX / (eToX + 1);
|
||||
}
|
||||
|
||||
public static float calculateWeightedLightLevel(Block block, float sunlightWeight, float moonlightWeight, float blocklightWeight) {
|
||||
float weightedSkylight = MoonPhase.getMoonlight(block) * moonlightWeight + MoonPhase.getSunlight(block) * sunlightWeight;
|
||||
float weightedBlocklight = block.getLightFromBlocks() * blocklightWeight;
|
||||
return Math.max(weightedSkylight, weightedBlocklight);
|
||||
}
|
||||
|
||||
public static float calculateWeightedLightLevelProbability(Block block, int maximumLight, float sunlightWeight, float moonlightWeight, float blocklightWeight) {
|
||||
float weightedLightLevel = calculateWeightedLightLevel(block, sunlightWeight, moonlightWeight, blocklightWeight);
|
||||
if (weightedLightLevel >= maximumLight) {
|
||||
public static double calculateWeightedLightLevelProbability(Block block, MonsterSpawnConfig cfg) {
|
||||
if (!cfg.isWeightedLightLevelWithinBounds(block)) {
|
||||
return 0.0f;
|
||||
}
|
||||
return 1 - logistic(weightedLightLevel / maximumLight);
|
||||
return 1 - logistic(cfg.calculateWeightedLightLevel(block) / cfg.getMaximumLightLevel());
|
||||
}
|
||||
|
||||
public float calculateLightLevelProbability(MonsterType type, Block block) {
|
||||
MonsterSpawnConfig cfg = monsters.get(type);
|
||||
return calculateWeightedLightLevelProbability(block, cfg.maximumLightLevel(), cfg.sunlightWeight(), cfg.moonlightWeight(), cfg.blocklightWeight());
|
||||
public double calculateLightLevelProbability(MonsterType type, Block block) {
|
||||
return calculateWeightedLightLevelProbability(block, config.getMonster(type));
|
||||
}
|
||||
|
||||
public float calculateLightLevelProbability(MonsterType type, Location location) {
|
||||
public double calculateLightLevelProbability(MonsterType type, Location location) {
|
||||
return calculateLightLevelProbability(type, location.getBlock());
|
||||
}
|
||||
|
||||
public float getMoonPhaseMultiplier(MonsterType type, MoonPhase phase) {
|
||||
return monsters.get(type).phaseMultipliers().getOrDefault(phase, 1.0f);
|
||||
public double getMoonPhaseMultiplier(MonsterType type, MoonPhase phase) {
|
||||
return config.getMonster(type).getPhaseMultipliers().getOrDefault(phase, 1.0);
|
||||
}
|
||||
|
||||
public float getMoonPhaseMultiplier(MonsterType type, World world) {
|
||||
public double getMoonPhaseMultiplier(MonsterType type, World world) {
|
||||
return getMoonPhaseMultiplier(type, MoonPhase.fromWorld(world));
|
||||
}
|
||||
|
||||
public float getMoonPhaseMultiplier(MonsterType type, Location location) {
|
||||
public double getMoonPhaseMultiplier(MonsterType type, Location location) {
|
||||
return getMoonPhaseMultiplier(type, location.getWorld());
|
||||
}
|
||||
|
||||
public float getMoonPhaseMultiplier(MonsterType type, Block block) {
|
||||
public double getMoonPhaseMultiplier(MonsterType type, Block block) {
|
||||
return getMoonPhaseMultiplier(type, block.getWorld());
|
||||
}
|
||||
|
||||
|
@ -126,7 +117,7 @@ public class WastelandSpawner {
|
|||
return countNearbyEntities(Player.class, location);
|
||||
}
|
||||
|
||||
public static float calculateNearbyEntitiesMultiplier(Location location) {
|
||||
public static double calculateNearbyEntitiesMultiplier(Location location) {
|
||||
final int nearbyMonsters = countNearbyMonsters(location);
|
||||
// TODO: Change to be a weighted value based on player distance.
|
||||
final int nearbyPlayers = countNearbyPlayers(location);
|
||||
|
@ -138,22 +129,22 @@ public class WastelandSpawner {
|
|||
// and screwed unless all the other players were helping you fight.)
|
||||
// It's also not a hard spawn cap, but past a point, the spawn rate needs to be severely diminished
|
||||
// to prevent massive lag and infinitely-sized hordes.
|
||||
final float idealMobQuantityMultiplier = (float) Math.max(0.5, Math.sqrt(nearbyPlayers)) * 25;
|
||||
final double idealMobQuantityMultiplier = Math.max(0.5, Math.sqrt(nearbyPlayers)) * 25;
|
||||
|
||||
// constant factor of 2 allows some mobs to spawn beyond the ideal quantity,
|
||||
// which in the logistic curve makes spawning mobs up to that point far more likely.
|
||||
return 1 - logistic(nearbyMonsters / idealMobQuantityMultiplier / 2);
|
||||
}
|
||||
|
||||
public static float calculateNearbyEntitiesMultiplier(Block block) {
|
||||
public static double calculateNearbyEntitiesMultiplier(Block block) {
|
||||
return calculateNearbyEntitiesMultiplier(block.getLocation());
|
||||
}
|
||||
|
||||
public float calculateSpawnProbability(MonsterType type, Location location) {
|
||||
final float lightLevelProbability = calculateLightLevelProbability(type, location);
|
||||
public double calculateSpawnProbability(MonsterType type, Location location) {
|
||||
final double lightLevelProbability = calculateLightLevelProbability(type, location);
|
||||
|
||||
long time = location.getWorld().getTime();
|
||||
float moonPhaseMultiplier = 0f;
|
||||
double moonPhaseMultiplier = 0f;
|
||||
if (time > 12000) { // dawn, dusk, or night
|
||||
moonPhaseMultiplier = getMoonPhaseMultiplier(type, location);
|
||||
if (time < 13000 || time > 23000) { // dawn or dusk
|
||||
|
@ -161,24 +152,24 @@ public class WastelandSpawner {
|
|||
}
|
||||
}
|
||||
|
||||
final float nearbyEntitiesMultiplier = calculateNearbyEntitiesMultiplier(location);
|
||||
final double nearbyEntitiesMultiplier = calculateNearbyEntitiesMultiplier(location);
|
||||
|
||||
return lightLevelProbability * moonPhaseMultiplier * nearbyEntitiesMultiplier;
|
||||
}
|
||||
|
||||
public float calculateSpawnProbability(MonsterType type, Block block) {
|
||||
public double calculateSpawnProbability(MonsterType type, Block block) {
|
||||
return calculateSpawnProbability(type, block.getLocation());
|
||||
}
|
||||
|
||||
public HashMap<MonsterType, Float> calculateSpawnProbabilities(Location location) {
|
||||
HashMap<MonsterType, Float> spawnWeights = new HashMap<>();
|
||||
public HashMap<MonsterType, Double> calculateSpawnProbabilities(Location location) {
|
||||
HashMap<MonsterType, Double> spawnWeights = new HashMap<>();
|
||||
for (MonsterType type : spawnableMonstersAt(location)) {
|
||||
spawnWeights.put(type, calculateSpawnProbability(type, location));
|
||||
}
|
||||
return spawnWeights;
|
||||
}
|
||||
|
||||
public HashMap<MonsterType, Float> calculateSpawnProbabilities(Block block) {
|
||||
public HashMap<MonsterType, Double> calculateSpawnProbabilities(Block block) {
|
||||
return calculateSpawnProbabilities(block.getLocation());
|
||||
}
|
||||
|
||||
|
@ -200,14 +191,14 @@ public class WastelandSpawner {
|
|||
return new Location(center.getWorld(), center.getX() + offX, center.getY() + offY, center.getZ() + offZ);
|
||||
}
|
||||
|
||||
public static Optional<Pair<MonsterType, Double>> chooseWeightedRandomMonster(Random rand, Map<MonsterType, Float> weights) {
|
||||
double overallSpawnProbability = weights.values().stream().reduce(0.0f, (x, y) -> x + y);
|
||||
public static Optional<Pair<MonsterType, Double>> chooseWeightedRandomMonster(Random rand, Map<MonsterType, Double> weights) {
|
||||
double overallSpawnProbability = weights.values().stream().reduce(0.0, (x, y) -> x + y);
|
||||
if (rand.nextDouble() >= overallSpawnProbability) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
double whichMonster = rand.nextDouble() * overallSpawnProbability;
|
||||
for (Entry<MonsterType, Float> monster : weights.entrySet()) {
|
||||
for (Entry<MonsterType, Double> monster : weights.entrySet()) {
|
||||
double successMargin = monster.getValue() - whichMonster;
|
||||
if (successMargin > 0) {
|
||||
return Optional.of(new Pair<>(monster.getKey(), successMargin));
|
||||
|
@ -220,7 +211,7 @@ public class WastelandSpawner {
|
|||
}
|
||||
|
||||
public Optional<Pair<MonsterType, Double>> pickRandomMonster(Random rand, Block block) {
|
||||
Map<MonsterType, Float> weights = calculateSpawnProbabilities(block);
|
||||
Map<MonsterType, Double> weights = calculateSpawnProbabilities(block);
|
||||
return chooseWeightedRandomMonster(rand, weights);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package me.jamestmartin.wasteland;
|
||||
package me.jamestmartin.wasteland.store;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
@ -10,7 +10,9 @@ import java.sql.SQLException;
|
|||
import java.sql.Statement;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class Database implements AutoCloseable {
|
||||
import me.jamestmartin.wasteland.kills.PlayerKillsStore;
|
||||
|
||||
public class SqliteDatabase implements AutoCloseable, PlayerKillsStore {
|
||||
private static final String CREATE_KILLS_TABLE =
|
||||
"CREATE TABLE IF NOT EXISTS player_kills"
|
||||
+ "( `player` VARCHAR(36) PRIMARY KEY"
|
||||
|
@ -20,18 +22,18 @@ public class Database implements AutoCloseable {
|
|||
"INSERT OR IGNORE INTO `player_kills`(`player`, `kills`) VALUES (?, 0)";
|
||||
private static final String GET_PLAYER_KILLS =
|
||||
"SELECT `kills` FROM `player_kills` WHERE `player` = ?";
|
||||
private static final String INCREMENT_PLAYER_KILLS =
|
||||
"UPDATE `player_kills` SET `kills`=`kills` + 1 WHERE `player` = ?";
|
||||
private static final String ADD_PLAYER_KILLS =
|
||||
"UPDATE `player_kills` SET `kills`=`kills` + ? WHERE `player` = ?";
|
||||
private static final String SET_PLAYER_KILLS =
|
||||
"UPDATE `player_kills` SET `kills` = ? WHERE `player` = ?";
|
||||
|
||||
private final Connection connection;
|
||||
private final PreparedStatement psInitPlayerKills;
|
||||
private final PreparedStatement psGetPlayerKills;
|
||||
private final PreparedStatement psIncrementPlayerKills;
|
||||
private final PreparedStatement psAddPlayerKills;
|
||||
private final PreparedStatement psSetPlayerKills;
|
||||
|
||||
public Database(File file)
|
||||
public SqliteDatabase(File file)
|
||||
throws IOException, ClassNotFoundException, SQLException {
|
||||
if (!file.exists()) {
|
||||
file.getParentFile().mkdirs();
|
||||
|
@ -51,17 +53,19 @@ public class Database implements AutoCloseable {
|
|||
|
||||
psInitPlayerKills = connection.prepareStatement(INIT_PLAYER_KILLS);
|
||||
psGetPlayerKills = connection.prepareStatement(GET_PLAYER_KILLS);
|
||||
psIncrementPlayerKills = connection.prepareStatement(INCREMENT_PLAYER_KILLS);
|
||||
psAddPlayerKills = connection.prepareStatement(ADD_PLAYER_KILLS);
|
||||
psSetPlayerKills = connection.prepareStatement(SET_PLAYER_KILLS);
|
||||
}
|
||||
|
||||
public void initPlayerKills(Player player) throws SQLException {
|
||||
@Override
|
||||
public void initPlayer(Player player) throws SQLException {
|
||||
String playerUUID = player.getUniqueId().toString();
|
||||
psInitPlayerKills.setString(1, playerUUID);
|
||||
psInitPlayerKills.executeUpdate();
|
||||
connection.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPlayerKills(Player player) throws SQLException
|
||||
{
|
||||
String playerUUID = player.getUniqueId().toString();
|
||||
|
@ -72,10 +76,12 @@ public class Database implements AutoCloseable {
|
|||
}
|
||||
}
|
||||
|
||||
public void incrementPlayerKills(Player player) throws SQLException {
|
||||
@Override
|
||||
public void addPlayerKills(Player player, int kills) throws SQLException {
|
||||
String playerUUID = player.getUniqueId().toString();
|
||||
psIncrementPlayerKills.setString(1, playerUUID);
|
||||
psIncrementPlayerKills.executeUpdate();;
|
||||
psAddPlayerKills.setInt(1, kills);
|
||||
psAddPlayerKills.setString(2, playerUUID);
|
||||
psAddPlayerKills.executeUpdate();;
|
||||
connection.commit();
|
||||
}
|
||||
|
||||
|
@ -85,7 +91,7 @@ public class Database implements AutoCloseable {
|
|||
String playerUUID = player.getUniqueId().toString();
|
||||
psSetPlayerKills.setInt(1, kills);
|
||||
psSetPlayerKills.setString(2, playerUUID);
|
||||
psSetPlayerKills.executeUpdate();;
|
||||
psSetPlayerKills.executeUpdate();
|
||||
connection.commit();
|
||||
}
|
||||
|
||||
|
@ -93,7 +99,7 @@ public class Database implements AutoCloseable {
|
|||
public void close() throws SQLException {
|
||||
psInitPlayerKills.close();
|
||||
psGetPlayerKills.close();
|
||||
psIncrementPlayerKills.close();
|
||||
psAddPlayerKills.close();
|
||||
psSetPlayerKills.close();
|
||||
connection.commit();
|
||||
connection.close();
|
|
@ -1,24 +1,5 @@
|
|||
package me.jamestmartin.wasteland.towny;
|
||||
|
||||
import java.util.Optional;
|
||||
public interface TownyDependency extends TownAbbreviationProvider {
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.palmergames.bukkit.towny.TownyAPI;
|
||||
import com.palmergames.bukkit.towny.exceptions.NotRegisteredException;
|
||||
import com.palmergames.bukkit.towny.object.Resident;
|
||||
|
||||
public class TownyDependency implements TownAbbreviationProvider {
|
||||
@Override
|
||||
public Optional<String> getTownAbbreviation(Player player) {
|
||||
try {
|
||||
Resident resident = TownyAPI.getInstance().getDataSource().getResident(player.getName());
|
||||
String tag = resident.getTown().getTag();
|
||||
if (tag != null && !tag.equals("")) {
|
||||
return Optional.of(tag);
|
||||
}
|
||||
} catch (NotRegisteredException e) {
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import java.util.Optional;
|
|||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class TownyDisabled implements TownAbbreviationProvider {
|
||||
public class TownyDisabled implements TownyDependency {
|
||||
@Override
|
||||
public Optional<String> getTownAbbreviation(Player player) {
|
||||
return Optional.empty();
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package me.jamestmartin.wasteland.towny;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.palmergames.bukkit.towny.TownyAPI;
|
||||
import com.palmergames.bukkit.towny.exceptions.NotRegisteredException;
|
||||
import com.palmergames.bukkit.towny.object.Resident;
|
||||
|
||||
public class TownyEnabled implements TownyDependency {
|
||||
@Override
|
||||
public Optional<String> getTownAbbreviation(Player player) {
|
||||
try {
|
||||
Resident resident = TownyAPI.getInstance().getDataSource().getResident(player.getName());
|
||||
String tag = resident.getTown().getTag();
|
||||
if (tag != null && !tag.equals("")) {
|
||||
return Optional.of(tag);
|
||||
}
|
||||
} catch (NotRegisteredException e) {
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
|
@ -32,16 +32,30 @@ chat:
|
|||
# abbreviation: <rank abbreviation> # optional, defaults to the name
|
||||
# description: <rank description> # optional, defaults to `enlisted.description`.
|
||||
# succeeds: <rankID> # optional, the prior rank (defaults to nothing)
|
||||
# preferred: <bool> # optional, used to disambiguate multiple ranks
|
||||
# preferred: <rankID> # optional, used to disambiguate multiple ranks
|
||||
# # which both succeed the same rank.
|
||||
# # Defaults to `true`.
|
||||
# # If there is only one successor, it defaults to that.
|
||||
# color: <ColorCode> # optional
|
||||
# decoration: <ColorCode> # optional, defaults to `enlisted.decoration`
|
||||
# kills: <0+> # optional, but the rank won't be used without it
|
||||
# kills: <0+> # optional, but the rank won't be automatically used without it
|
||||
# ...
|
||||
#
|
||||
# The last listed rank that a player qualifies for will always be the one chosen.
|
||||
# Players will automatically be promoted to any ranks they have enough kills for.
|
||||
# You *must* set a default enlisted rank by setting the rank's kills to 0; the plugin will break if you don't.
|
||||
# Setting multiple default ranks is undefined behavior.
|
||||
# It may be possible for a player to have no rank in the future, but it is not for now.
|
||||
#
|
||||
# Players will automatically be promoted to their preferred rank if they have enough kills for it.
|
||||
# If there are ambiguous ranks, then:
|
||||
# * If the player has permission `wasteland.prefer-rank.<rankId>`, then they will be promoted to their preferred rank.
|
||||
# * If one of the next ranks is `preferred`, then they will be promoted to that rank.
|
||||
# * Otherwise, they will *not* be promoted until a `prefer-rank` permission is set.
|
||||
#
|
||||
# Setting a player's preferred rank will retroactively update their rank to that rank
|
||||
# (e.g. if a player is MGySgt and you set `wasteland.prefer-rank.fstsgt`, they will become SgtMaj),
|
||||
# but only when the plugin's configuration (or the whole plugin) is reloaded.
|
||||
#
|
||||
# It is also possible to set a player's rank to a rank they do not have the kills for,
|
||||
# by using the `wasteland.rank.<rankID>` permission.
|
||||
#
|
||||
|
||||
# The default ranks are based on those of the U.S. marines.
|
||||
|
@ -51,28 +65,6 @@ chat:
|
|||
|
||||
enlisted:
|
||||
description: "{kills} kills."
|
||||
promotions:
|
||||
# The eligible monsters are the entity types which, if killed, will count towards your next promotion.
|
||||
# Each value must be a valid Bukkit EntityType (e.g. `WITHER_SKELETON` or `ENDERMAN`),
|
||||
# or one of these built-in entitiy collections:
|
||||
# * `bosses`: The ender dragon or wither.
|
||||
# * `hostiles`: Mobs which will attack you on sight, including the bosses.
|
||||
# * `neutrals`: Mobs which will only attack you under certain conditions,
|
||||
# e.g. wolves attack you if attacked, enderman attack you if looked at, spiders attack in the dark.
|
||||
# * `monsters`: Mobs which are eligiable for the "Monster Hunter" achievement,
|
||||
# which is to say all of the hostile mobs, endermen, spiders, and zombified piglins.
|
||||
# * `zombies`: All zombies, including zombified piglins.
|
||||
# * `spiders`: All spiders: regular spiders and cave spiders.
|
||||
#
|
||||
# The wiki lists which mobs are bosses, hostile, or neutral: https://minecraft.gamepedia.com/Mob#List_of_mobs
|
||||
# Monsters are defined as the mobs which are eligible for the Monster Hunter achievement
|
||||
# (all hostile mobs plus endermen, spiders, and zombified piglins).
|
||||
#
|
||||
# This defaults to monsters.
|
||||
eligible:
|
||||
# You must give a name for messages to the player, for e.g. "You must kill X more ??? before your next promotion."
|
||||
name: monsters
|
||||
entities: [bosses, monsters]
|
||||
ranks:
|
||||
# A default rank which is not part of the marines.
|
||||
fodder:
|
||||
|
@ -123,15 +115,15 @@ enlisted:
|
|||
name: Gunnery Sergeant
|
||||
abbreviation: GySgt
|
||||
succeeds: ssgt
|
||||
preferred: msgt
|
||||
color: AQUA
|
||||
kills: 10000
|
||||
# Alternative DARK_RED rank, currently unused.
|
||||
# Alternative DARK_RED rank.
|
||||
fstsgt:
|
||||
name: First Sergeant
|
||||
abbreviation: 1stSgt
|
||||
description: Unused alternative rank for {kills} kills.
|
||||
description: {kills} kills. Alternative rank to GySgt.
|
||||
succeeds: gysgt
|
||||
preferred: false
|
||||
color: DARK_RED
|
||||
kills: 25000
|
||||
msgt:
|
||||
|
@ -140,11 +132,11 @@ enlisted:
|
|||
succeeds: gysgt
|
||||
color: DARK_RED
|
||||
kills: 25000
|
||||
# Alternative DARK_PURPLE rank, currently unused.
|
||||
# Alternative DARK_PURPLE rank.
|
||||
sgtmaj:
|
||||
name: Sergeant Major
|
||||
abbreviation: SgtMaj
|
||||
description: Unused alternative rank for {kills} kills.
|
||||
description: A survivor of the wasteland, with over {kills} kills. Alternative rank to MGySgt, promoted from 1stSgt.
|
||||
succeeds: fstsgt
|
||||
color: DARK_PURPLE
|
||||
kills: 50000
|
||||
|
@ -168,7 +160,7 @@ enlisted:
|
|||
officer:
|
||||
decoration: BOLD
|
||||
# The rank used by the console when it makes an official message.
|
||||
# Defaults to whatever the last rank listed is.
|
||||
# This *must* be specified or the plugin will break.
|
||||
console: gas
|
||||
ranks:
|
||||
sndlt:
|
||||
|
@ -226,6 +218,27 @@ officer:
|
|||
description: The server console.
|
||||
color: GOLD
|
||||
|
||||
kills:
|
||||
# The eligible monsters are the entity types which, if killed, will count towards your next promotion.
|
||||
# Each value must be a valid Bukkit EntityType (e.g. `WITHER_SKELETON` or `ENDERMAN`),
|
||||
# or one of these built-in entitiy collections:
|
||||
# * `bosses`: The ender dragon or wither.
|
||||
# * `hostiles`: Mobs which will attack you on sight, including the bosses.
|
||||
# * `neutrals`: Mobs which will only attack you under certain conditions,
|
||||
# e.g. wolves attack you if attacked, enderman attack you if looked at, spiders attack in the dark.
|
||||
# * `monsters`: Mobs which are eligiable for the "Monster Hunter" achievement,
|
||||
# which is to say all of the hostile mobs, endermen, spiders, and zombified piglins.
|
||||
# * `zombies`: All zombies, including zombified piglins.
|
||||
# * `spiders`: All spiders: regular spiders and cave spiders.
|
||||
#
|
||||
# The wiki lists which mobs are bosses, hostile, or neutral: https://minecraft.gamepedia.com/Mob#List_of_mobs
|
||||
# Monsters are defined as the mobs which are eligible for the Monster Hunter achievement
|
||||
# (all hostile mobs plus endermen, spiders, and zombified piglins).
|
||||
eligible:
|
||||
# You must give a name for messages to the player, for e.g. "You must kill X more ??? before your next promotion."
|
||||
name: monsters
|
||||
entities: [bosses, monsters]
|
||||
|
||||
#
|
||||
# spawns:
|
||||
# # Valid monster types are are CREEPER, SKELETON, SPIDER, and ZOMBIE.
|
||||
|
|
|
@ -7,30 +7,38 @@ api-version: 1.16
|
|||
softdepend: [Towny]
|
||||
|
||||
commands:
|
||||
rank:
|
||||
description: View your rank and how many monsters you have killed.
|
||||
usage: "Usage: /<command> [<player>]"
|
||||
permission: wasteland.view-rank
|
||||
permission-message: You do not have permission to view your rank.
|
||||
rankeligiblemobs:
|
||||
description: View a list of monsters which you can kill to get promoted.
|
||||
usage: "Usage: /<command>"
|
||||
permission: wasteland.view-eligible-mobs
|
||||
permission-message: You cannot view the list of promotion-eligible monsters.
|
||||
ranks:
|
||||
description: List the server ranks.
|
||||
usage: "Usage: /<command>"
|
||||
setkills:
|
||||
description: Set another player's number of kills for debugging.
|
||||
usage: "Usage: /<command> [<player>] <kills>"
|
||||
permission: wasteland.kills.set
|
||||
permission-message: You do not have permission to set anyone's kills.
|
||||
wasteland:
|
||||
description: Reload the plugin's configuration.
|
||||
usage: "Usage: /<command> reload"
|
||||
permission: wasteland.reload
|
||||
permission-message: You do not have permission to reload the plugin's configuration.
|
||||
|
||||
official:
|
||||
description: Make an official announcement using your staff rank.
|
||||
usage: "Usage: /<command> <message>"
|
||||
permission: wasteland.official
|
||||
permission-message: You are not a staff member with an officer rank.
|
||||
|
||||
rank:
|
||||
description: View your rank and how many monsters you have killed.
|
||||
usage: "Usage: /<command> [<player>]"
|
||||
permission: wasteland.view-rank
|
||||
permission-message: You do not have permission to view your rank.
|
||||
ranks:
|
||||
description: List the server ranks.
|
||||
usage: "Usage: /<command>"
|
||||
|
||||
rankeligiblemobs:
|
||||
description: View a list of monsters which you can kill to get promoted.
|
||||
usage: "Usage: /<command>"
|
||||
permission: wasteland.view-eligible-mobs
|
||||
permission-message: You cannot view the list of promotion-eligible monsters.
|
||||
setkills:
|
||||
description: Set another player's number of kills for debugging.
|
||||
usage: "Usage: /<command> [<player>] <kills>"
|
||||
permission: wasteland.kills.set
|
||||
permission-message: You do not have permission to set anyone's kills.
|
||||
|
||||
# debug commands
|
||||
debugspawn:
|
||||
description: Spawn a random monster.
|
||||
|
@ -44,6 +52,10 @@ commands:
|
|||
permission-message: You do not have permission to view spawn debug information.
|
||||
|
||||
permissions:
|
||||
wasteland.reload:
|
||||
description: Allows you to reload the plugin's configuration.
|
||||
default: op
|
||||
|
||||
wasteland.chat.officer:
|
||||
description: Show a player's officer rank even in normal chat.
|
||||
default: false
|
||||
|
|
Loading…
Reference in New Issue