diff --git a/Plugins/mineplex-questmanager/pom.xml b/Plugins/mineplex-questmanager/pom.xml deleted file mode 100644 index 5678aecff..000000000 --- a/Plugins/mineplex-questmanager/pom.xml +++ /dev/null @@ -1,69 +0,0 @@ - - 4.0.0 - - com.mineplex - mineplex-parent - dev-SNAPSHOT - - mineplex-questmanager - Mineplex.questmanager - A centralized service that selects daily quests - - - 23.0 - 2.12 - 1.8.8-1.9-SNAPSHOT - - - - - ${project.groupId} - mineplex-serverdata - ${project.version} - - - com.google.guava - guava - ${version.guava} - compile - - - jline - jline - ${version.jline} - compile - - - com.mineplex - spigot - provided - - - - - - - org.apache.maven.plugins - maven-shade-plugin - - false - - - mineplex.quest.daemon.QuestDaemon - - - - - - package - - shade - - - - - - - \ No newline at end of file diff --git a/Plugins/mineplex-questmanager/src/mineplex/quest/client/RedisQuestSupplier.java b/Plugins/mineplex-questmanager/src/mineplex/quest/client/RedisQuestSupplier.java deleted file mode 100644 index 127862a66..000000000 --- a/Plugins/mineplex-questmanager/src/mineplex/quest/client/RedisQuestSupplier.java +++ /dev/null @@ -1,111 +0,0 @@ -package mineplex.quest.client; - -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import org.bukkit.plugin.java.JavaPlugin; - -import com.google.common.collect.ImmutableSet; - -import mineplex.quest.client.event.QuestsUpdatedEvent; -import mineplex.quest.common.Quest; -import mineplex.quest.common.QuestSupplier; -import mineplex.quest.common.redis.PubSubChannels; -import mineplex.quest.common.redis.QuestTypeSerializer; -import mineplex.serverdata.redis.messaging.PubSubMessager; - -/** - * Provides methods to retrieve currently active quests. Retrieves active quests from the - * centralized quest service via redis. - *

- * Intended to be thread-safe. - */ -public class RedisQuestSupplier implements QuestSupplier -{ - - private final JavaPlugin _plugin; - private final String _serverUniqueId; - private final PubSubMessager _pubSub; - private final ReadWriteLock _lock = new ReentrantReadWriteLock(); - - private final Set _quests = new HashSet<>(); - - public RedisQuestSupplier(JavaPlugin plugin, PubSubMessager pubSub) - { - _plugin = plugin; - _pubSub = pubSub; - - _serverUniqueId = plugin.getConfig().getString("serverstatus.name"); - - // update quests sent specifically to this server when it requests them (like on startup) - _pubSub.subscribe(PubSubChannels.QUEST_REQUEST_BASE + _serverUniqueId, this::updateQuests); - - requestActiveQuests(); - - // update quests when received - _pubSub.subscribe(PubSubChannels.QUEST_SUPPLIER_CHANNEL, this::updateQuests); - } - - private void updateQuests(String channel, String message) - { - _lock.writeLock().lock(); - try - { - _quests.clear(); - _quests.addAll(deserialize(message)); - - System.out.println("[QUEST-SUPPLIER] Quest update received from daemon, active quests: "); - _quests.forEach(q -> System.out.println("[QUEST-SUPPLIER] " + q.toString())); - - // notify - _plugin.getServer().getPluginManager().callEvent(new QuestsUpdatedEvent(get())); - } - finally - { - _lock.writeLock().unlock(); - } - } - - private Set deserialize(String json) - { - return QuestTypeSerializer.QUEST_GSON.fromJson(json, QuestTypeSerializer.QUEST_TYPE); - } - - private void requestActiveQuests() - { - System.out.println("[QUEST-SUPPLIER] Requesting active quests from QuestDaemon"); - // request current active quests, send server unique id so we can send a response just to this server - _pubSub.publish(PubSubChannels.QUEST_REQUEST_BASE, _serverUniqueId); - } - - @Override - public Set get() - { - _lock.readLock().lock(); - try - { - return ImmutableSet.copyOf(_quests); - } - finally - { - _lock.readLock().unlock(); - } - } - - @Override - public Optional getById(int uniquePersistentId) - { - _lock.readLock().lock(); - try - { - return _quests.stream().filter(q -> q.getUniqueId() == uniquePersistentId).findFirst(); - } - finally - { - _lock.readLock().unlock(); - } - } -} \ No newline at end of file diff --git a/Plugins/mineplex-questmanager/src/mineplex/quest/client/event/QuestsUpdatedEvent.java b/Plugins/mineplex-questmanager/src/mineplex/quest/client/event/QuestsUpdatedEvent.java deleted file mode 100644 index 82b70450a..000000000 --- a/Plugins/mineplex-questmanager/src/mineplex/quest/client/event/QuestsUpdatedEvent.java +++ /dev/null @@ -1,44 +0,0 @@ -package mineplex.quest.client.event; - -import java.util.Set; - -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; - -import mineplex.quest.common.Quest; - -/** - * An event called when the currently active quests are rotated at the end of a day. - */ -public class QuestsUpdatedEvent extends Event -{ - - private static final HandlerList HANDLERS = new HandlerList(); - - private final Set _to; - - public QuestsUpdatedEvent(Set to) - { - _to = to; - } - - /** - * @return The currently active quests. - */ - public Set getActiveQuests() - { - return _to; - } - - @Override - public HandlerList getHandlers() - { - return HANDLERS; - } - - public static HandlerList getHandlerList() - { - return HANDLERS; - } - -} diff --git a/Plugins/mineplex-questmanager/src/mineplex/quest/common/BaseQuest.java b/Plugins/mineplex-questmanager/src/mineplex/quest/common/BaseQuest.java deleted file mode 100644 index afc387d3a..000000000 --- a/Plugins/mineplex-questmanager/src/mineplex/quest/common/BaseQuest.java +++ /dev/null @@ -1,59 +0,0 @@ -package mineplex.quest.common; - -/** - * Implementation of baseline {@link Quest}. - */ -public class BaseQuest implements Quest -{ - - private final int _uniqueId; - private final String _name; - private final QuestRarity _rarity; - private final int _cost; - - public BaseQuest(int uniqueId, String name, int cost, QuestRarity rarity) - { - _uniqueId = uniqueId; - _name = name; - _cost = cost; - _rarity = rarity; - } - - @Override - public String getName() - { - return _name; - } - - @Override - public int getUniqueId() - { - return _uniqueId; - } - - @Override - public QuestRarity getRarity() - { - return _rarity; - } - - @Override - public String getDataId() - { - return _uniqueId + ""; - } - - @Override - public boolean shouldRotate() - { - return _cost != -1; - } - - @Override - public String toString() - { - return "BaseQuest [uniqueId=" + _uniqueId + ", name=" + _name + ", rarity=" + _rarity - + ", cost=" + _cost + "]"; - } - -} diff --git a/Plugins/mineplex-questmanager/src/mineplex/quest/common/Quest.java b/Plugins/mineplex-questmanager/src/mineplex/quest/common/Quest.java deleted file mode 100644 index f2c7ac799..000000000 --- a/Plugins/mineplex-questmanager/src/mineplex/quest/common/Quest.java +++ /dev/null @@ -1,53 +0,0 @@ -package mineplex.quest.common; - -import mineplex.quest.daemon.QuestManager; -import mineplex.serverdata.data.Data; -import mineplex.serverdata.data.DataRepository; - -/** - * A quest that can be completed by users for a reward. - */ -public interface Quest extends Data -{ - /** - * Gets the name of this quest. - * - * @return The name of this quest. - */ - String getName(); - - /** - * Gets the unique persistent id for this quest. This id will be used to store quests per user - * and should not be changed. - * - * @return The unique persistent id for this quest. - */ - int getUniqueId(); - - /** - * Gets the {@link QuestRarity} of this quest. - * - * @return The rarity of this quest. - */ - QuestRarity getRarity(); - - /** - * Get the unique persistent id for this quest as a String. Intended to be used for storage - * within Redis via {@link DataRepository}. - *

- * Don't use this to get the quest unique id, use {@link Quest#getUniqueId()} instead. - * - * @return A string version of the unique persistent id for this quest. - */ - @Override - String getDataId(); - - /** - * Checks whether this quest should be selected by the {@link QuestManager} daemon process. - * Quests with a cost of -1 should not ever be selected as active quests by the daemon. - * - * @return true if this quest should be selected as an active quest, or - * false otherwise. - */ - boolean shouldRotate(); -} diff --git a/Plugins/mineplex-questmanager/src/mineplex/quest/common/QuestRarity.java b/Plugins/mineplex-questmanager/src/mineplex/quest/common/QuestRarity.java deleted file mode 100644 index 4ef625ee0..000000000 --- a/Plugins/mineplex-questmanager/src/mineplex/quest/common/QuestRarity.java +++ /dev/null @@ -1,24 +0,0 @@ -package mineplex.quest.common; - -/** - * How rare a quest is. In other words, how often this quest should be chosen. - */ -public enum QuestRarity -{ - COMMON(1.0), - RARE(0.5), - LEGENDARY(0.1) - ; - - private final double _weight; - - private QuestRarity(double weight) - { - _weight = weight; - } - - public double getWeight() - { - return _weight; - } -} diff --git a/Plugins/mineplex-questmanager/src/mineplex/quest/common/QuestSupplier.java b/Plugins/mineplex-questmanager/src/mineplex/quest/common/QuestSupplier.java deleted file mode 100644 index 9e32a2d80..000000000 --- a/Plugins/mineplex-questmanager/src/mineplex/quest/common/QuestSupplier.java +++ /dev/null @@ -1,29 +0,0 @@ -package mineplex.quest.common; - -import java.util.Optional; -import java.util.Set; -import java.util.function.Supplier; - -/** - * Provides access to {@link Quest}s tracked by this QuestManager. - */ -public interface QuestSupplier extends Supplier> -{ - - /** - * Get an immutable set containing all of the currently active quests. - */ - @Override - Set get(); - - /** - * Attempts to get the {@link Quest} matching the supplied persistent id. - * - * @param uniquePersistentId The unique id of the quest. - * - * @return An {@link Optional} describing the {@link Quest}, or an empty Optional if none is - * found. - */ - Optional getById(int uniquePersistentId); - -} diff --git a/Plugins/mineplex-questmanager/src/mineplex/quest/common/Quests.java b/Plugins/mineplex-questmanager/src/mineplex/quest/common/Quests.java deleted file mode 100644 index 1b1bd41dd..000000000 --- a/Plugins/mineplex-questmanager/src/mineplex/quest/common/Quests.java +++ /dev/null @@ -1,73 +0,0 @@ -package mineplex.quest.common; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import com.google.common.collect.ImmutableSet; - -import mineplex.quest.common.util.UtilGoogleSheet; -import mineplex.quest.daemon.QuestDaemon; - -/** - * Provides access to all quests. - *

- * Loads quests from a google sheets json file. - */ -public class Quests -{ - - private static final String ALL_QUESTS_FILE = "QUESTS_SHEET"; - private static final String QUEST_SHEET_KEY = "Quests"; - - private static final int UNIQUE_ID_COLUMN = 0; - private static final int NAME_COLUMN = 1; - private static final int COST_COLUMN = 4; - private static final int RARITY_COLUMN = 9; - - public static final Set QUESTS; - - static - { - ImmutableSet.Builder builder = ImmutableSet.builder(); - - Map>> sheets = UtilGoogleSheet.getSheetData(ALL_QUESTS_FILE); - - List> rows = sheets.getOrDefault(QUEST_SHEET_KEY, Collections.emptyList()); - - // get each row of spreadsheet, start at 1 since row 0 contains headers - for (int i = 1; i < rows.size(); i++) - { - List row = rows.get(i); - - // attempt to parse quest data we need - try - { - int uniqueId = Integer.parseInt(row.get(UNIQUE_ID_COLUMN)); - String name = row.get(NAME_COLUMN); - int cost = Integer.parseInt(row.get(COST_COLUMN)); - QuestRarity rarity = QuestRarity.valueOf(row.get(RARITY_COLUMN).toUpperCase()); - - Quest quest = new BaseQuest(uniqueId, name, cost, rarity); - - builder.add(quest); - } - catch (Exception e) - { - QuestDaemon.log("Exception encountered while parsing quest sheet row: " + row + ", " - + e.getMessage()); - e.printStackTrace(); - } - } - - QUESTS = builder.build(); - } - - public static Optional fromId(int uniqueId) - { - return QUESTS.stream().filter(quest -> quest.getUniqueId() == uniqueId).findFirst(); - } - -} diff --git a/Plugins/mineplex-questmanager/src/mineplex/quest/common/redis/PubSubChannels.java b/Plugins/mineplex-questmanager/src/mineplex/quest/common/redis/PubSubChannels.java deleted file mode 100644 index f28490ec8..000000000 --- a/Plugins/mineplex-questmanager/src/mineplex/quest/common/redis/PubSubChannels.java +++ /dev/null @@ -1,13 +0,0 @@ -package mineplex.quest.common.redis; - -/** - * Provides constants for Quests redis pub sub channels. - */ -public class PubSubChannels -{ - - public static final String QUEST_SUPPLIER_CHANNEL = "quest-manager"; - - public static final String QUEST_REQUEST_BASE = "quest-manager-request:"; - -} diff --git a/Plugins/mineplex-questmanager/src/mineplex/quest/common/redis/QuestRedisDataRepository.java b/Plugins/mineplex-questmanager/src/mineplex/quest/common/redis/QuestRedisDataRepository.java deleted file mode 100644 index 825c1364e..000000000 --- a/Plugins/mineplex-questmanager/src/mineplex/quest/common/redis/QuestRedisDataRepository.java +++ /dev/null @@ -1,39 +0,0 @@ -package mineplex.quest.common.redis; - -import mineplex.quest.common.Quest; -import mineplex.quest.common.Quests; -import mineplex.serverdata.Region; -import mineplex.serverdata.redis.RedisDataRepository; -import mineplex.serverdata.servers.ConnectionData; - -/** - * A {@link RedisDataRepository} that can serialize & deserialize (and thus store & retrieve from - * redis) Quest instances. - */ -public class QuestRedisDataRepository extends RedisDataRepository -{ - - public QuestRedisDataRepository(ConnectionData writeConn, ConnectionData readConn, Region region, - String elementLabel) - { - super(writeConn, readConn, region, Quest.class, elementLabel); - } - - @Override - protected Quest deserialize(String json) - { - if (json == null || json.isEmpty()) - { - return null; - } - - return Quests.fromId(Integer.parseInt(json)).orElse(null); - } - - @Override - protected String serialize(Quest quest) - { - return quest.getDataId(); - } - -} diff --git a/Plugins/mineplex-questmanager/src/mineplex/quest/common/redis/QuestTypeDeserialiazer.java b/Plugins/mineplex-questmanager/src/mineplex/quest/common/redis/QuestTypeDeserialiazer.java deleted file mode 100644 index 4b71efc77..000000000 --- a/Plugins/mineplex-questmanager/src/mineplex/quest/common/redis/QuestTypeDeserialiazer.java +++ /dev/null @@ -1,36 +0,0 @@ -package mineplex.quest.common.redis; - -import java.lang.reflect.Type; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import com.google.gson.Gson; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; - -import mineplex.quest.common.Quest; -import mineplex.quest.common.Quests; - -/** - * An implementation of a {@link JsonDeserializer} intended for use in {@link Gson}. Deserializes a - * {@link JsonElement} String into a Set. - */ -public class QuestTypeDeserialiazer implements JsonDeserializer> -{ - - @Override - public Set deserialize(JsonElement json, Type typeOfT, - JsonDeserializationContext context) throws JsonParseException - { - String[] split = json.getAsString().split(QuestTypeSerializer.SEPARATOR); - return Arrays.stream(split).map(questId -> Quests.fromId(Integer.valueOf(questId))) - .filter(Optional::isPresent).map(Optional::get) - .collect(Collectors.toCollection(HashSet::new)); - } - -} diff --git a/Plugins/mineplex-questmanager/src/mineplex/quest/common/redis/QuestTypeSerializer.java b/Plugins/mineplex-questmanager/src/mineplex/quest/common/redis/QuestTypeSerializer.java deleted file mode 100644 index 67905f1a2..000000000 --- a/Plugins/mineplex-questmanager/src/mineplex/quest/common/redis/QuestTypeSerializer.java +++ /dev/null @@ -1,44 +0,0 @@ -package mineplex.quest.common.redis; - -import java.lang.reflect.Type; -import java.util.Set; -import java.util.stream.Collectors; - -import com.google.common.base.Joiner; -import com.google.common.reflect.TypeToken; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; - -import mineplex.quest.common.Quest; - -/** - * An implementation of a {@link JsonSerializer} intended for use in {@link Gson}. Serializes a - * Set into a {@link JsonElement} String. - */ -public class QuestTypeSerializer implements JsonSerializer> -{ - - @SuppressWarnings("serial") - public static final Type QUEST_TYPE = new TypeToken>(){}.getType(); - - public static final Gson QUEST_GSON = new GsonBuilder() - .registerTypeAdapter(QuestTypeSerializer.QUEST_TYPE, new QuestTypeDeserialiazer()) - .registerTypeAdapter(QuestTypeSerializer.QUEST_TYPE, new QuestTypeSerializer()) - .create(); - - public static final String SEPARATOR = ","; - - @Override - public JsonElement serialize(Set src, Type typeOfSrc, JsonSerializationContext context) - { - StringBuilder builder = new StringBuilder(); - Joiner.on(SEPARATOR).appendTo(builder, - src.stream().map(Quest::getDataId).collect(Collectors.toSet())); - return new JsonPrimitive(builder.toString()); - } - -} diff --git a/Plugins/mineplex-questmanager/src/mineplex/quest/common/util/RandomCollection.java b/Plugins/mineplex-questmanager/src/mineplex/quest/common/util/RandomCollection.java deleted file mode 100644 index c74225c91..000000000 --- a/Plugins/mineplex-questmanager/src/mineplex/quest/common/util/RandomCollection.java +++ /dev/null @@ -1,62 +0,0 @@ -package mineplex.quest.common.util; - -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.NavigableMap; -import java.util.Random; -import java.util.TreeMap; -import java.util.concurrent.ThreadLocalRandom; - -/** - * Provides random, weighted access to a collection of elements. - *

- * Intended to be thread-safe. - * - * @param The generic type parameter of the elements. - */ -public class RandomCollection -{ - - private final NavigableMap _map = Collections.synchronizedNavigableMap(new TreeMap()); - private final Random _random; - - private double total = 0; - - public RandomCollection(Random random) - { - _random = random; - } - - public RandomCollection() - { - this(ThreadLocalRandom.current()); - } - - public void addAll(Map values) - { - values.forEach(this::add); - } - - public void add(E result, double weight) - { - if (weight <= 0) - { - return; - } - - total += weight; - _map.put(total, result); - } - - public E next() - { - double value = _random.nextDouble() * total; - return _map.ceilingEntry(value).getValue(); - } - - public Collection values() - { - return _map.values(); - } -} diff --git a/Plugins/mineplex-questmanager/src/mineplex/quest/common/util/UtilGoogleSheet.java b/Plugins/mineplex-questmanager/src/mineplex/quest/common/util/UtilGoogleSheet.java deleted file mode 100644 index 6dee08b44..000000000 --- a/Plugins/mineplex-questmanager/src/mineplex/quest/common/util/UtilGoogleSheet.java +++ /dev/null @@ -1,75 +0,0 @@ -package mineplex.quest.common.util; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -/** - * Provides utility methods for deserializing google sheets json files. - */ -public class UtilGoogleSheet -{ - private static final File DATA_STORE_DIR = new File( - ".." + File.separatorChar + ".." + File.separatorChar + "update" + File.separatorChar - + "files"); - - public static Map>> getSheetData(String name) - { - return getSheetData(new File(DATA_STORE_DIR + File.separator + name + ".json")); - } - - public static Map>> getSheetData(File file) - { - if (!file.exists()) - { - return null; - } - - Map>> valuesMap = new HashMap<>(); - - try - { - JsonParser parser = new JsonParser(); - JsonElement data = parser.parse(new FileReader(file)); - JsonArray parent = data.getAsJsonObject().getAsJsonArray("data"); - - for (int i = 0; i < parent.size(); i++) - { - JsonObject sheet = parent.get(i).getAsJsonObject(); - String name = sheet.get("name").getAsString(); - JsonArray values = sheet.getAsJsonArray("values"); - List> valuesList = new ArrayList<>(values.size()); - - for (int j = 0; j < values.size(); j++) - { - List list = new ArrayList<>(); - Iterator iterator = values.get(j).getAsJsonArray().iterator(); - - while (iterator.hasNext()) - { - String value = iterator.next().getAsString(); - list.add(value); - } - - valuesList.add(list); - } - - valuesMap.put(name, valuesList); - } - } - catch (FileNotFoundException e) - {} - - return valuesMap; - } -} \ No newline at end of file diff --git a/Plugins/mineplex-questmanager/src/mineplex/quest/daemon/QuestDaemon.java b/Plugins/mineplex-questmanager/src/mineplex/quest/daemon/QuestDaemon.java deleted file mode 100644 index 15dbb2d55..000000000 --- a/Plugins/mineplex-questmanager/src/mineplex/quest/daemon/QuestDaemon.java +++ /dev/null @@ -1,180 +0,0 @@ -package mineplex.quest.daemon; - -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.logging.FileHandler; -import java.util.logging.Formatter; -import java.util.logging.LogRecord; -import java.util.logging.Logger; - -import org.fusesource.jansi.AnsiConsole; - -import com.google.common.base.Throwables; - -import mineplex.serverdata.redis.messaging.PubSubJedisClient; -import mineplex.serverdata.redis.messaging.PubSubRouter; -import mineplex.serverdata.servers.ServerManager; - -import jline.console.ConsoleReader; - -/** - * Entry point for a {@link QuestManager} service. - */ -public class QuestDaemon -{ - - public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MM:dd:yyyy HH:mm:ss"); - - private static final Logger _logger = Logger.getLogger("QuestManager"); - static - { - FileHandler fileHandler; - try - { - fileHandler = new FileHandler("monitor.log", true); - fileHandler.setFormatter(new Formatter() - { - @Override - public String format(LogRecord record) - { - return record.getMessage() + "\n"; - } - }); - _logger.addHandler(fileHandler); - _logger.setUseParentHandlers(false); - } - catch (SecurityException | IOException e) - { - log("COuld not initialize log file!"); - log(e); - } - } - - private volatile boolean _alive = true; - - private QuestManager _questManager; - - public static void main(String[] args) - { - try - { - new QuestDaemon().run(); - System.exit(0); - } - catch (Throwable t) - { - log("Error in startup/console thread."); - log(t); - System.exit(1); - } - } - - private void run() throws Exception - { - log("Starting QuestDaemon..."); - - _questManager = new QuestManager(new PubSubRouter(new PubSubJedisClient( - ServerManager.getMasterConnection(), ServerManager.getSlaveConnection()))); - _questManager.start(); - - Runtime.getRuntime().addShutdownHook(new Thread(() -> _questManager.onShutdown())); - - AnsiConsole.systemInstall(); - ConsoleReader consoleReader = new ConsoleReader(); - consoleReader.setExpandEvents(false); - - String command; - while (_alive && (command = consoleReader.readLine(">")) != null) - { - try - { - if (command.equals("help")) - { - log("QuestManager commands:"); - log("stop: Shuts down this QuestManager instance."); - log("clearactivequests: Clears active quests. New ones will be selected on this" - + " instance's next iteration."); - log("clearrecentrequests: Clear recently selected quests. This effectively allows " - + "any quest to be set to active, even ones selected within the past few days."); - log("getactivequests: Displays the currently active quests."); - } - else if (command.contains("stop")) - { - stopCommand(); - } - else if (command.contains("clearactivequests")) - { - clearQuestsCommand(); - } - else if (command.contains("clearrecentquests")) - { - clearRecentQuestsCommand(); - } - else if (command.contains("getactivequests")) - { - getActiveQuestsCommand(); - } - } - catch (Throwable t) - { - log("Exception encountered while executing command " + command + ": " - + t.getMessage()); - log(t); - } - } - } - - private void stopCommand() throws Exception - { - log("Shutting down QuestDaemon..."); - - _alive = false; - - System.exit(0); - } - - private void clearQuestsCommand() - { - _questManager.clearActiveQuests(); - - log("Cleared active quests. New ones will be selected on this instance's next iteration."); - } - - private void clearRecentQuestsCommand() - { - _questManager.clearRecentlyActiveQuests(); - - log("Cleared recently active quests. This means that any quest can be chosen to be active now, even ones selected within the past few days."); - } - - private void getActiveQuestsCommand() - { - _questManager.displayActiveQuests(); - } - - public static void log(String message) - { - log(message, false); - } - - public static void log(Throwable t) - { - log(Throwables.getStackTraceAsString(t)); - } - - public static void log(String message, boolean fileOnly) - { - _logger.info("[" + DATE_FORMAT.format(new Date()) + "] " + message); - - if (!fileOnly) - { - System.out.println("[" + DATE_FORMAT.format(new Date()) + "] " + message); - } - } - - public static String getLogPrefix(Object loggingClass) - { - return "[" + DATE_FORMAT.format(new Date()) + "] "; - } -} diff --git a/Plugins/mineplex-questmanager/src/mineplex/quest/daemon/QuestManager.java b/Plugins/mineplex-questmanager/src/mineplex/quest/daemon/QuestManager.java deleted file mode 100644 index 46686de98..000000000 --- a/Plugins/mineplex-questmanager/src/mineplex/quest/daemon/QuestManager.java +++ /dev/null @@ -1,277 +0,0 @@ -package mineplex.quest.daemon; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.time.LocalDate; -import java.time.ZoneId; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import mineplex.quest.common.Quest; -import mineplex.quest.common.QuestRarity; -import mineplex.quest.common.Quests; -import mineplex.quest.common.redis.PubSubChannels; -import mineplex.quest.common.redis.QuestRedisDataRepository; -import mineplex.quest.common.redis.QuestTypeSerializer; -import mineplex.quest.common.util.RandomCollection; -import mineplex.serverdata.Region; -import mineplex.serverdata.data.DataRepository; -import mineplex.serverdata.redis.messaging.PubSubMessager; -import mineplex.serverdata.servers.ServerManager; - -/** - * A centralized service that handles setting {@link Quest} instances as active for servers to - * display to players. Uses redis to notify servers of active quests changes and to store recently - * selected quests. - *

- * Uses {@link QuestRarity} to randomly select active quests based on relative weight. - */ -public class QuestManager extends Thread -{ - - private static final int RECENT_QUESTS_EXPIRE_SECONDS = (int) TimeUnit.DAYS.toSeconds(5); - private static final long SLEEP_MILLIS = TimeUnit.MINUTES.toMillis(1); - private static final int NUM_ACTIVE_QUESTS = 5; - - private static final ZoneId EST_TIMEZONE = ZoneId.of("America/New_York"); - private static final String LAST_UPDATE_FILE = "last-quest-update.dat"; - - // all quests, mapped from rarity weight to quest - private final RandomCollection _quests = new RandomCollection<>(); - - // currently active quests - private final Set _activeQuests = Collections.synchronizedSet(new HashSet<>()); - - // redis pubsub messager, used to publish active quests to servers - private final PubSubMessager _pubSub; - - // redis repository to track recently selected quests, to prevent selecting a quest too soon - // after it's been active - private final DataRepository _recentlySelectedQuestsRepo; - - // the current date, e.g. the last date active quests were updated - private volatile LocalDate _currentDate; - - // whether this instance is running or not - private volatile boolean _alive = true; - - public QuestManager(PubSubMessager pubSub) - { - _pubSub = pubSub; - - _recentlySelectedQuestsRepo = new QuestRedisDataRepository( - ServerManager.getMasterConnection(), ServerManager.getSlaveConnection(), - Region.currentRegion(), "recently-selected-quests"); - - _quests.addAll(Quests.QUESTS.stream().collect( - Collectors.toMap(q -> q, q -> q.getRarity().getWeight()))); - - loadLastActiveQuests(); - - if (_activeQuests.size() > 0) - { - QuestDaemon.log("Active quests loaded from file:"); - _activeQuests.forEach(quest -> QuestDaemon.log(quest.toString())); - } - - // listen for servers requesting active quests on startup - _pubSub.subscribe(PubSubChannels.QUEST_REQUEST_BASE, this::handleQuestRequest); - } - - private void handleQuestRequest(String channel, String message) - { - QuestDaemon.log("Quests requestesd by server: " + message); - // first make sure we have some active quests selected - if (_activeQuests.isEmpty()) - { - selectRandomQuests(); - } - - // send active quests to the server - String server = message; - publishActiveQuests(PubSubChannels.QUEST_REQUEST_BASE + server); - } - - /** - * Loads last set active quests & the date they were set to active from a flat file, if the file - * exists. - */ - private void loadLastActiveQuests() - { - File file = new File(LAST_UPDATE_FILE); - if (!file.exists()) - { - _currentDate = LocalDate.now(EST_TIMEZONE); - - return; - } - - try - { - List lines = Files.readAllLines(Paths.get(file.getAbsolutePath())); - _currentDate = LocalDate.parse(lines.get(0)); - - if (lines.size() > 1) - { - for (int i = 1; i < lines.size(); i++) - { - int uniqueId = Integer.parseInt(lines.get(i)); - Optional quest = Quests.fromId(uniqueId); - if (!quest.isPresent()) - { - QuestDaemon.log("Tried to load active quest that doesn't exist: " + uniqueId); - continue; - } - - _activeQuests.add(quest.get()); - } - } - } - catch (Exception e) - { - QuestDaemon.log( - "Exception encountered while loading last updated quests: " + e.getMessage()); - QuestDaemon.log(e); - - _currentDate = LocalDate.now(EST_TIMEZONE); - } - } - - @Override - public void run() - { - try - { - while (_alive) - { - // purge recently selected quests repo of expired entries - _recentlySelectedQuestsRepo.clean(); - - LocalDate now = LocalDate.now(EST_TIMEZONE); - // check if date has changed; if so we need to choose new quests - if (_currentDate.isBefore(now) || _activeQuests.isEmpty()) - { - QuestDaemon.log("Updating active quests..."); - _currentDate = now; - - // select new quests - selectRandomQuests(); - - // publish new quests - publishActiveQuests(PubSubChannels.QUEST_SUPPLIER_CHANNEL); - - QuestDaemon.log("Done updating active quests."); - } - - // take a small break, important so CPU isn't constantly running - Thread.sleep(SLEEP_MILLIS); - } - } - catch (InterruptedException e) - { - QuestDaemon.log("Exception encountered updating active quests repo: " + e.getMessage()); - QuestDaemon.log(e); - } - } - - private void publishActiveQuests(String channel) - { - QuestDaemon.log("publishing active quests to channel: " + channel); - QuestDaemon.log("Active quests: " + serialize(_activeQuests)); - _pubSub.publish(channel, - serialize(_activeQuests)); - } - - /** - * Called on shutdown of this service. Writes the date quests were last updated to a file, so - * this service will know whether to update them or not on the next startup. This is all that's - * needed to keep active quests in a sane state because they are stored in redis. - */ - public void onShutdown() - { - _alive = false; - - try - { - File file = new File(LAST_UPDATE_FILE); - if (!file.exists()) - { - file.createNewFile(); - } - - List lines = new ArrayList<>(); - - // add active quests date - lines.add(_currentDate.toString()); - - // add currently active quests - _activeQuests.stream().map(Quest::getDataId).forEach(lines::add); - - Files.write(Paths.get(file.getAbsolutePath()), lines); - } - catch (IOException e) - { - QuestDaemon.log("Exception encountered saving " + LAST_UPDATE_FILE + " file: " - + e.getMessage()); - QuestDaemon.log(e); - } - } - - protected void clearActiveQuests() - { - _activeQuests.clear(); - } - - protected void clearRecentlyActiveQuests() - { - _recentlySelectedQuestsRepo.getElements() - .forEach(_recentlySelectedQuestsRepo::removeElement); - } - - protected void displayActiveQuests() - { - QuestDaemon.log("Active quests:"); - _activeQuests.forEach(q -> QuestDaemon.log(q.toString())); - } - - private void selectRandomQuests() - { - if (!_activeQuests.isEmpty()) - { - _activeQuests.clear(); - } - - while (_activeQuests.size() < NUM_ACTIVE_QUESTS && _activeQuests.size() < _quests.values().size()) - { - Quest q = _quests.next(); - // select random weighted quest, ignore those recently selected - if (!q.shouldRotate() || _activeQuests.contains(q) - || _recentlySelectedQuestsRepo.elementExists(q.getDataId())) - { - // quest is already active or it's been active recently - continue; - } - - // add active quest - _activeQuests.add(q); - - QuestDaemon.log("Selected quest: " + q.getName()); - - // flag quest as recently selected - _recentlySelectedQuestsRepo.addElement(q, RECENT_QUESTS_EXPIRE_SECONDS); - } - } - - private String serialize(Set quests) - { - return QuestTypeSerializer.QUEST_GSON.toJson(quests, QuestTypeSerializer.QUEST_TYPE); - } -}