Merge branch 'feature/quests' into develop
This commit is contained in:
commit
2701476ea8
@ -32,6 +32,11 @@
|
||||
<artifactId>mineplex-serverdata</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>mineplex-questmanager</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-dbcp2</artifactId>
|
||||
|
@ -1261,4 +1261,9 @@ public class BonusManager extends MiniClientPlugin<BonusClientData> implements I
|
||||
{
|
||||
_carlLocation = carlLocation;
|
||||
}
|
||||
|
||||
public Npc getCarl()
|
||||
{
|
||||
return _carlNpc;
|
||||
}
|
||||
}
|
||||
|
240
Plugins/Mineplex.Core/src/mineplex/core/quests/Quest.java
Normal file
240
Plugins/Mineplex.Core/src/mineplex/core/quests/Quest.java
Normal file
@ -0,0 +1,240 @@
|
||||
package mineplex.core.quests;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
import mineplex.core.game.GameCategory;
|
||||
import mineplex.core.game.GameDisplay;
|
||||
|
||||
/**
|
||||
* Quest
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class Quest
|
||||
{
|
||||
private static final Object _progressLock = new Object();
|
||||
|
||||
private int _questID;
|
||||
|
||||
private String _questName;
|
||||
private String _questTask;
|
||||
|
||||
private int _questCost;
|
||||
private String _questReward;
|
||||
|
||||
private QuestRarity _rarity;
|
||||
|
||||
private String _type;
|
||||
|
||||
private GameDisplay _game;
|
||||
private GameCategory _gameCategory;
|
||||
private boolean _generalGame;
|
||||
private boolean _overworld;
|
||||
|
||||
private TriggerType _trigger;
|
||||
private String[] _item;
|
||||
private int _statToComplete;
|
||||
|
||||
private long _lastCompleted;
|
||||
|
||||
private int _current;
|
||||
|
||||
private int _timesCompleted;
|
||||
|
||||
public Quest(int questID, String name, String task, int cost, String reward, QuestRarity rarity, String type, TriggerType trigger, String[] item, int statToComplete)
|
||||
{
|
||||
_questID = questID;
|
||||
_questName = name;
|
||||
_questTask = task;
|
||||
_questCost = cost;
|
||||
_questReward = reward;
|
||||
_rarity = rarity;
|
||||
_trigger = trigger;
|
||||
_item = item;
|
||||
_statToComplete = statToComplete;
|
||||
_type = type;
|
||||
|
||||
if (GameDisplay.matchName(type) != null)
|
||||
_game = GameDisplay.matchName(type);
|
||||
|
||||
try
|
||||
{
|
||||
_gameCategory = GameCategory.valueOf(type);
|
||||
}
|
||||
catch (IllegalArgumentException e) {}
|
||||
|
||||
_generalGame = _type.equalsIgnoreCase("General");
|
||||
|
||||
if (!_generalGame)
|
||||
_overworld = (_game == null && _gameCategory == null);
|
||||
}
|
||||
|
||||
public int getID()
|
||||
{
|
||||
return _questID;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return _questName;
|
||||
}
|
||||
|
||||
public String getTask()
|
||||
{
|
||||
return _questTask;
|
||||
}
|
||||
|
||||
public int getCost()
|
||||
{
|
||||
return _questCost;
|
||||
}
|
||||
|
||||
public String getReward()
|
||||
{
|
||||
return _questReward;
|
||||
}
|
||||
|
||||
public QuestRarity getRarity()
|
||||
{
|
||||
return _rarity;
|
||||
}
|
||||
|
||||
public GameDisplay getGame()
|
||||
{
|
||||
return _game;
|
||||
}
|
||||
|
||||
public GameCategory getGameCategory()
|
||||
{
|
||||
return _gameCategory;
|
||||
}
|
||||
|
||||
public boolean isInOverworld()
|
||||
{
|
||||
return _overworld;
|
||||
}
|
||||
|
||||
public TriggerType getTrigger()
|
||||
{
|
||||
return _trigger;
|
||||
}
|
||||
|
||||
public String[] getItem()
|
||||
{
|
||||
return _item;
|
||||
}
|
||||
|
||||
public int getStatToComplete()
|
||||
{
|
||||
return _statToComplete;
|
||||
}
|
||||
|
||||
public int getProgress()
|
||||
{
|
||||
return _current;
|
||||
}
|
||||
|
||||
public void setProgress(int progress)
|
||||
{
|
||||
_current = progress;
|
||||
}
|
||||
|
||||
public void increment(int value)
|
||||
{
|
||||
synchronized (_progressLock)
|
||||
{
|
||||
_current += value;
|
||||
}
|
||||
}
|
||||
|
||||
public void increment()
|
||||
{
|
||||
synchronized (_progressLock)
|
||||
{
|
||||
_current++;
|
||||
}
|
||||
}
|
||||
|
||||
public void decrement(int value)
|
||||
{
|
||||
synchronized (_progressLock)
|
||||
{
|
||||
_current -= value;
|
||||
}
|
||||
}
|
||||
|
||||
public void decrement()
|
||||
{
|
||||
synchronized (_progressLock)
|
||||
{
|
||||
_current--;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCompleted()
|
||||
{
|
||||
return _current >= _statToComplete;
|
||||
}
|
||||
|
||||
public void setLastCompleted(long time)
|
||||
{
|
||||
_lastCompleted = time;
|
||||
}
|
||||
|
||||
public long getLastCompleted()
|
||||
{
|
||||
return _lastCompleted;
|
||||
}
|
||||
|
||||
public String getRewardName()
|
||||
{
|
||||
return _questReward.split(":")[0];
|
||||
}
|
||||
|
||||
public int getRewardAmount()
|
||||
{
|
||||
return Integer.parseInt(_questReward.split(":")[1]);
|
||||
}
|
||||
|
||||
public void setTimesCompleted(int amount)
|
||||
{
|
||||
_timesCompleted = amount;
|
||||
}
|
||||
|
||||
public int getTimesCompleted()
|
||||
{
|
||||
return _timesCompleted;
|
||||
}
|
||||
|
||||
public boolean isActive()
|
||||
{
|
||||
return _current != -1;
|
||||
}
|
||||
|
||||
public String[] getQuestInfo()
|
||||
{
|
||||
String[] info = new String[]{
|
||||
ChatColor.LIGHT_PURPLE + getTask(), "", ChatColor.GRAY + "Reward: " + ChatColor.AQUA + getRewardAmount() + " " + getRewardName(),
|
||||
"",
|
||||
ChatColor.GRAY + "Progress: "
|
||||
+ (_current == -1 ? ChatColor.RED + "Not in your " + QuestManager.QUEST_NAME + " Inventory" :
|
||||
(isCompleted() ? ChatColor.GREEN + "Completed!" :
|
||||
ChatColor.YELLOW + "" + getProgress() + ChatColor.GRAY + "/" + ChatColor.YELLOW + getStatToComplete())),
|
||||
"",
|
||||
getRarity().getColor() + "" + ChatColor.BOLD + getRarity().toString(),
|
||||
};
|
||||
return info;
|
||||
}
|
||||
|
||||
public boolean isGeneral()
|
||||
{
|
||||
return _generalGame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Quest clone()
|
||||
{
|
||||
return new Quest(_questID, _questName, _questTask, _questCost, _questReward, _rarity, _type, _trigger, _item, _statToComplete);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package mineplex.core.quests;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import mineplex.core.hologram.Hologram;
|
||||
|
||||
/**
|
||||
* QuestClientData
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class QuestClientData
|
||||
{
|
||||
private ArrayList<Quest> _quests = new ArrayList<>();
|
||||
|
||||
private Hologram _hologram;
|
||||
|
||||
public void addQuest(Quest quest)
|
||||
{
|
||||
_quests.add(quest);
|
||||
}
|
||||
|
||||
public ArrayList<Quest> getQuests()
|
||||
{
|
||||
ArrayList<Quest> quests = new ArrayList<>();
|
||||
for (Quest quest : _quests)
|
||||
{
|
||||
if (quest.getProgress() != -1)
|
||||
quests.add(quest);
|
||||
}
|
||||
return quests;
|
||||
}
|
||||
|
||||
public boolean hasQuest(Quest quest)
|
||||
{
|
||||
for (Quest other : getQuests())
|
||||
{
|
||||
if (other.getID() == quest.getID())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Quest getQuest(int id)
|
||||
{
|
||||
for (Quest quest : getQuests())
|
||||
{
|
||||
if (quest.getID() == id)
|
||||
return quest;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Quest getQuestFromAll(int id)
|
||||
{
|
||||
for (Quest quest : _quests)
|
||||
{
|
||||
if (quest.getID() == id)
|
||||
return quest;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean hasQuestFromAll(Quest quest)
|
||||
{
|
||||
for (Quest other : _quests)
|
||||
{
|
||||
if (other.getID() == quest.getID())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void removeQuest(int id)
|
||||
{
|
||||
Quest toRemove = getQuest(id);
|
||||
toRemove.setProgress(-1);
|
||||
}
|
||||
|
||||
public ArrayList<Quest> getAllQuests()
|
||||
{
|
||||
return _quests;
|
||||
}
|
||||
|
||||
public Hologram getHologram()
|
||||
{
|
||||
return _hologram;
|
||||
}
|
||||
|
||||
public void setHologram(Hologram hologram)
|
||||
{
|
||||
_hologram = hologram;
|
||||
}
|
||||
}
|
416
Plugins/Mineplex.Core/src/mineplex/core/quests/QuestManager.java
Normal file
416
Plugins/Mineplex.Core/src/mineplex/core/quests/QuestManager.java
Normal file
@ -0,0 +1,416 @@
|
||||
package mineplex.core.quests;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Slime;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
|
||||
import mineplex.core.MiniClientPlugin;
|
||||
import mineplex.core.account.CoreClientManager;
|
||||
import mineplex.core.common.Pair;
|
||||
import mineplex.core.common.currency.GlobalCurrency;
|
||||
import mineplex.core.common.util.C;
|
||||
import mineplex.core.common.util.Callback;
|
||||
import mineplex.core.common.util.F;
|
||||
import mineplex.core.common.util.UtilPlayer;
|
||||
import mineplex.core.common.util.UtilServer;
|
||||
import mineplex.core.common.util.UtilTime;
|
||||
import mineplex.core.donation.DonationManager;
|
||||
import mineplex.core.google.GoogleSheetsManager;
|
||||
import mineplex.core.hologram.Hologram;
|
||||
import mineplex.core.hologram.HologramManager;
|
||||
import mineplex.core.inventory.InventoryManager;
|
||||
import mineplex.core.npc.Npc;
|
||||
import mineplex.core.npc.NpcManager;
|
||||
import mineplex.core.quests.command.GetQuestCommand;
|
||||
import mineplex.core.quests.command.IncrementQuestCommand;
|
||||
import mineplex.core.quests.command.OpenGuiCommand;
|
||||
import mineplex.core.quests.repository.QuestRepository;
|
||||
import mineplex.core.quests.shop.QuestShop;
|
||||
import mineplex.core.stats.StatsManager;
|
||||
import mineplex.core.updater.UpdateType;
|
||||
import mineplex.core.updater.event.UpdateEvent;
|
||||
import mineplex.quest.client.RedisQuestSupplier;
|
||||
import mineplex.quest.common.QuestSupplier;
|
||||
import mineplex.serverdata.redis.messaging.PubSubJedisClient;
|
||||
import mineplex.serverdata.redis.messaging.PubSubRouter;
|
||||
import mineplex.serverdata.servers.ServerManager;
|
||||
|
||||
/**
|
||||
* QuestManager
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class QuestManager extends MiniClientPlugin<QuestClientData>
|
||||
{
|
||||
public static final String QUEST_NAME = "Mineplex Mission";
|
||||
|
||||
private static final String GOOGLE_SHEET = "QUESTS_SHEET";
|
||||
private static final String GOOGLE_TABLE = "Quests";
|
||||
|
||||
private QuestRepository _repository;
|
||||
private GoogleSheetsManager _sheetsManager;
|
||||
private CoreClientManager _clients;
|
||||
private DonationManager _donationManager;
|
||||
private InventoryManager _inventoryManager;
|
||||
|
||||
public ArrayList<Quest> _availableQuests;
|
||||
|
||||
private QuestSupplier _questSupplier = new RedisQuestSupplier(getPlugin(), new PubSubRouter(new PubSubJedisClient(ServerManager.getMasterConnection(), ServerManager.getSlaveConnection())));
|
||||
|
||||
private Npc _questNPC;
|
||||
private boolean _enableNPC;
|
||||
private HologramManager _hologramManager;
|
||||
private boolean _visualTick;
|
||||
|
||||
public QuestManager(HologramManager hologramManager, Location npc, InventoryManager inventoryManager, DonationManager donationManager)
|
||||
{
|
||||
super("Quest Manager");
|
||||
|
||||
_repository = new QuestRepository();
|
||||
_sheetsManager = require(GoogleSheetsManager.class);
|
||||
_clients = require(CoreClientManager.class);
|
||||
_availableQuests = new ArrayList<>();
|
||||
_donationManager = donationManager;
|
||||
_inventoryManager = inventoryManager;
|
||||
_hologramManager = hologramManager;
|
||||
|
||||
setupQuests();
|
||||
|
||||
_questNPC = require(NpcManager.class).getNpcByName("Mineplex Missions");
|
||||
if (_questNPC == null)
|
||||
{
|
||||
_enableNPC = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Slime slime = (Slime) _questNPC.getEntity();
|
||||
slime.setSize(3);
|
||||
if (npc != null)
|
||||
{
|
||||
_questNPC.setLocation(npc);
|
||||
}
|
||||
_enableNPC = true;
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void updateCreeper(UpdateEvent event)
|
||||
{
|
||||
if (event.getType() != UpdateType.FASTER || !_enableNPC)
|
||||
return;
|
||||
|
||||
Slime slime = (Slime)_questNPC.getEntity();
|
||||
slime.setSize(3);
|
||||
|
||||
for (Player player : UtilServer.getPlayers())
|
||||
{
|
||||
String prefix = _visualTick ? C.cAqua : C.cDAqua;
|
||||
updateSlimeVisual(player, prefix);
|
||||
}
|
||||
|
||||
_visualTick = !_visualTick;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void updateSlimeVisual(Player player , String rewardPrefix)
|
||||
{
|
||||
if (!_enableNPC)
|
||||
return;
|
||||
|
||||
int availableQuests = 5;
|
||||
|
||||
for (int questid : getCurrentQuests())
|
||||
{
|
||||
if (Get(player).getQuestFromAll(questid) == null)
|
||||
continue;
|
||||
|
||||
Quest quest = Get(player).getQuestFromAll(questid);
|
||||
|
||||
if (quest.isActive())
|
||||
availableQuests--;
|
||||
|
||||
if (!UtilTime.elapsed(quest.getLastCompleted(), 1000*60*60*24))
|
||||
availableQuests--;
|
||||
}
|
||||
|
||||
Hologram hologram;
|
||||
|
||||
QuestClientData client = Get(player);
|
||||
|
||||
if (client.getHologram() == null)
|
||||
{
|
||||
double yAdd = 2.3;
|
||||
if(!UtilPlayer.is1_9(player))
|
||||
{
|
||||
yAdd = 2.45;
|
||||
}
|
||||
hologram = new Hologram(_hologramManager, _questNPC.getLocation().clone().add(0, yAdd, 0), "");
|
||||
hologram.setHologramTarget(Hologram.HologramTarget.WHITELIST);
|
||||
hologram.addPlayer(player);
|
||||
client.setHologram(hologram);
|
||||
hologram.start();
|
||||
}
|
||||
else
|
||||
{
|
||||
hologram = client.getHologram();
|
||||
}
|
||||
|
||||
if (availableQuests > 0)
|
||||
{
|
||||
// Hologram
|
||||
String text = rewardPrefix + availableQuests + " Mission" + (availableQuests > 1 ? "s" : "") + " Available";
|
||||
hologram.setText(text);
|
||||
}
|
||||
else
|
||||
{
|
||||
String text = C.cGray + "No Missions Available";
|
||||
hologram.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCommands()
|
||||
{
|
||||
addCommand(new OpenGuiCommand(this));
|
||||
addCommand(new GetQuestCommand(this));
|
||||
addCommand(new IncrementQuestCommand(this));
|
||||
}
|
||||
|
||||
public void setupQuests()
|
||||
{
|
||||
_availableQuests.clear();
|
||||
Map<String, List<List<String>>> sheet = _sheetsManager.getSheetData(GOOGLE_SHEET);
|
||||
List<List<String>> table = new ArrayList<>();
|
||||
for (String key : sheet.keySet())
|
||||
{
|
||||
if (key.equalsIgnoreCase(GOOGLE_TABLE))
|
||||
table = sheet.get(key);
|
||||
}
|
||||
|
||||
int size = table.size();
|
||||
|
||||
for (int i = 1; i < size; i++)
|
||||
{
|
||||
String id = table.get(i).get(0);
|
||||
String name = table.get(i).get(1);
|
||||
String task = table.get(i).get(2);
|
||||
String type = table.get(i).get(3);
|
||||
String cost = table.get(i).get(4);
|
||||
String reward = table.get(i).get(5);
|
||||
String trigger = table.get(i).get(6);
|
||||
String statcompletion = table.get(i).get(7);
|
||||
String item = table.get(i).get(8);
|
||||
String rarity = table.get(i).get(9);
|
||||
|
||||
_availableQuests.add(new Quest(Integer.parseInt(id), name, task,
|
||||
Integer.parseInt(cost),
|
||||
reward,
|
||||
QuestRarity.getByName(rarity),
|
||||
type,
|
||||
TriggerType.getByName(trigger),
|
||||
item.split(","),
|
||||
Integer.parseInt(statcompletion)));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||
public void openGui(PlayerInteractAtEntityEvent event)
|
||||
{
|
||||
if (!_enableNPC)
|
||||
return;
|
||||
|
||||
Entity entity = event.getRightClicked();
|
||||
if (entity.equals(_questNPC.getEntity()))
|
||||
{
|
||||
new QuestShop(this, _clients, _donationManager).attemptShopOpen(event.getPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void openGui(EntityDamageByEntityEvent event)
|
||||
{
|
||||
if (!_enableNPC)
|
||||
return;
|
||||
|
||||
if (event.getDamager() instanceof Player)
|
||||
{
|
||||
Player player = (Player) event.getDamager();
|
||||
if (event.getEntity().equals(_questNPC.getEntity()))
|
||||
{
|
||||
new QuestShop(this, _clients, _donationManager).attemptShopOpen(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void playerJoin(PlayerJoinEvent event)
|
||||
{
|
||||
QuestClientData questData = Get(event.getPlayer());
|
||||
_repository.getQuests(_clients.Get(event.getPlayer()), new Callback<ArrayList<Pair<Integer, Triple<Integer, Long, Integer>>>>()
|
||||
{
|
||||
@Override
|
||||
public void run(ArrayList<Pair<Integer, Triple<Integer, Long, Integer>>> data)
|
||||
{
|
||||
for (Pair<Integer, Triple<Integer, Long, Integer>> pair : data)
|
||||
{
|
||||
int id = pair.getLeft();
|
||||
int value = pair.getRight().getLeft();
|
||||
long time = pair.getRight().getMiddle();
|
||||
int timesCompleted = pair.getRight().getRight();
|
||||
|
||||
for (Quest quest : _availableQuests)
|
||||
{
|
||||
if (quest.getID() == id)
|
||||
{
|
||||
Quest clone = quest.clone();
|
||||
clone.setProgress(value);
|
||||
clone.setLastCompleted(time);
|
||||
clone.setTimesCompleted(timesCompleted);
|
||||
questData.addQuest(clone);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Quest getQuestByID(int id)
|
||||
{
|
||||
for (Quest quest : _availableQuests)
|
||||
{
|
||||
if (quest.getID() == id)
|
||||
return quest;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addNewQuest(Player player, Quest quest)
|
||||
{
|
||||
QuestClientData data = Get(player);
|
||||
for (Quest other : data.getAllQuests())
|
||||
{
|
||||
if (other.getID() == quest.getID())
|
||||
{
|
||||
other.setProgress(0);
|
||||
_repository.addQuest(_clients.Get(player), other);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Quest clone = quest.clone();
|
||||
clone.setProgress(0);
|
||||
Get(player).addQuest(clone);
|
||||
_repository.addNew(_clients.Get(player), quest);
|
||||
}
|
||||
|
||||
public void resetQuest(Player player, Quest quest, boolean completed)
|
||||
{
|
||||
if (completed)
|
||||
{
|
||||
quest.setTimesCompleted(quest.getTimesCompleted() + 1);
|
||||
quest.setLastCompleted(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
Get(player).removeQuest(quest.getID());
|
||||
_repository.resetQuest(_clients.Get(player), quest, completed);
|
||||
}
|
||||
|
||||
public void incrementQuest(Player player, Quest quest, int value)
|
||||
{
|
||||
quest.increment(value);
|
||||
if (quest.isCompleted())
|
||||
{
|
||||
UtilPlayer.message(player, F.main(QUEST_NAME, "You have completed the " + QUEST_NAME + ": " + ChatColor.YELLOW + quest.getName()));
|
||||
}
|
||||
_repository.incrementQuest(_clients.Get(player), quest, value);
|
||||
}
|
||||
|
||||
public Set<Integer> getCurrentQuests()
|
||||
{
|
||||
Set<Integer> set = new HashSet<>();
|
||||
for (mineplex.quest.common.Quest quest : _questSupplier.get())
|
||||
{
|
||||
set.add(quest.getUniqueId());
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
public void rewardQuest(Player player, Quest quest, Consumer<Boolean> callback)
|
||||
{
|
||||
if (quest.getReward().contains(":"))
|
||||
{
|
||||
ChatColor color = ChatColor.YELLOW;
|
||||
if (quest.getRewardName().equalsIgnoreCase("Shards"))
|
||||
{
|
||||
_donationManager.rewardCurrency(GlobalCurrency.TREASURE_SHARD, player, "Completing " + QUEST_NAME + ": " + quest.getID(), Integer.parseInt(quest.getReward().split(":")[1]), callback);
|
||||
color = ChatColor.AQUA;
|
||||
}
|
||||
else if (quest.getRewardName().equalsIgnoreCase("Gems"))
|
||||
{
|
||||
_donationManager.rewardCurrency(GlobalCurrency.TREASURE_SHARD, player, "Completing " + QUEST_NAME + ": " + quest.getID(), Integer.parseInt(quest.getReward().split(":")[1]), callback);
|
||||
color = ChatColor.GREEN;
|
||||
}
|
||||
else if (quest.getRewardName().equalsIgnoreCase("XP"))
|
||||
{
|
||||
require(StatsManager.class).incrementStat(player, "Global.ExpEarned", quest.getRewardAmount());
|
||||
callback.accept(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_inventoryManager.addItemToInventory(player, quest.getRewardName(), quest.getRewardAmount());
|
||||
callback.accept(true);
|
||||
}
|
||||
UtilPlayer.message(player, F.main(QUEST_NAME, "You have recieved " + color + quest.getRewardAmount() + " " + quest.getRewardName() + " " + ChatColor.GRAY + "for completing: " + ChatColor.YELLOW + quest.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
public CoreClientManager getClients()
|
||||
{
|
||||
return _clients;
|
||||
}
|
||||
|
||||
public DonationManager getDonations()
|
||||
{
|
||||
return _donationManager;
|
||||
}
|
||||
|
||||
public InventoryManager getInventoryManager()
|
||||
{
|
||||
return _inventoryManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QuestClientData addPlayer(UUID uuid)
|
||||
{
|
||||
return new QuestClientData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveData(String name, int accountId)
|
||||
{
|
||||
Get(name).getHologram().stop();
|
||||
}
|
||||
|
||||
public ArrayList<Quest> getAvailableQuests()
|
||||
{
|
||||
return _availableQuests;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package mineplex.core.quests;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
/**
|
||||
* QuestRarity
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public enum QuestRarity
|
||||
{
|
||||
COMMON("Common", ChatColor.YELLOW), RARE("Rare", ChatColor.LIGHT_PURPLE), LEGENDARY("Legendary", ChatColor.GREEN);
|
||||
|
||||
private String _name;
|
||||
private ChatColor _color;
|
||||
|
||||
private QuestRarity(String name, ChatColor color)
|
||||
{
|
||||
_name = name;
|
||||
_color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
public ChatColor getColor()
|
||||
{
|
||||
return _color;
|
||||
}
|
||||
|
||||
public static QuestRarity getByName(String name)
|
||||
{
|
||||
for (QuestRarity rarity : values())
|
||||
{
|
||||
if (rarity.toString().equalsIgnoreCase(name))
|
||||
return rarity;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package mineplex.core.quests;
|
||||
|
||||
/**
|
||||
* TriggerType
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public enum TriggerType
|
||||
{
|
||||
|
||||
KILL("Kill"), DIE("Die"), WIN("Win"), LOSE("Lose"), COLLECT("Collect"), PLAY("Play"), COMPLETE("Complete");
|
||||
|
||||
private String _name;
|
||||
|
||||
private TriggerType(String name)
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
public static TriggerType getByName(String name)
|
||||
{
|
||||
for (TriggerType type : values())
|
||||
{
|
||||
if (type.toString().equalsIgnoreCase(name))
|
||||
return type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package mineplex.core.quests.command;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import mineplex.core.command.CommandBase;
|
||||
import mineplex.core.common.Rank;
|
||||
import mineplex.core.common.util.F;
|
||||
import mineplex.core.common.util.UtilPlayer;
|
||||
import mineplex.core.quests.Quest;
|
||||
import mineplex.core.quests.QuestManager;
|
||||
|
||||
/**
|
||||
* GetQuestCommand
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class GetQuestCommand extends CommandBase<QuestManager>
|
||||
{
|
||||
public GetQuestCommand(QuestManager plugin)
|
||||
{
|
||||
super(plugin, Rank.ADMIN, "GetMineplexMission");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void Execute(Player caller, String[] args)
|
||||
{
|
||||
if (args.length < 1)
|
||||
{
|
||||
UtilPlayer.message(caller, F.main(QuestManager.QUEST_NAME, "You have to submit valid arguments"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Plugin.getQuestByID(Integer.parseInt(args[0])) == null)
|
||||
{
|
||||
UtilPlayer.message(caller, F.main(QuestManager.QUEST_NAME, QuestManager.QUEST_NAME + " not found"));
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = caller;
|
||||
if (args.length == 2)
|
||||
{
|
||||
if (UtilPlayer.searchExact(args[1]) != null)
|
||||
player = UtilPlayer.searchExact(args[1]);
|
||||
}
|
||||
|
||||
Quest quest = Plugin.getQuestByID(Integer.parseInt(args[0]));
|
||||
|
||||
Plugin.addNewQuest(player, quest);
|
||||
UtilPlayer.message(player, F.main(QuestManager.QUEST_NAME, "Added " + QuestManager.QUEST_NAME + ": " + ChatColor.YELLOW + quest.getName()));
|
||||
|
||||
if (caller != player)
|
||||
UtilPlayer.message(caller, F.main(QuestManager.QUEST_NAME, "You gave the " + QuestManager.QUEST_NAME + ": " + ChatColor.YELLOW + quest.getName() + ChatColor.GRAY + " to " + ChatColor.YELLOW + player.getName()));
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package mineplex.core.quests.command;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import mineplex.core.command.CommandBase;
|
||||
import mineplex.core.common.Rank;
|
||||
import mineplex.core.common.util.F;
|
||||
import mineplex.core.common.util.UtilPlayer;
|
||||
import mineplex.core.quests.Quest;
|
||||
import mineplex.core.quests.QuestManager;
|
||||
|
||||
/**
|
||||
* CompleteQuestCommand
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class IncrementQuestCommand extends CommandBase<QuestManager>
|
||||
{
|
||||
public IncrementQuestCommand(QuestManager plugin)
|
||||
{
|
||||
super(plugin, Rank.ADMIN, "IncrementMineplexMission");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void Execute(Player caller, String[] args)
|
||||
{
|
||||
if (args.length < 2)
|
||||
{
|
||||
UtilPlayer.message(caller, F.main(QuestManager.QUEST_NAME, "You have to submit valid arguments"));
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = caller;
|
||||
if (args.length == 3)
|
||||
{
|
||||
if (UtilPlayer.searchExact(args[2]) != null)
|
||||
player = UtilPlayer.searchExact(args[2]);
|
||||
}
|
||||
|
||||
if (Plugin.Get(player).getQuest(Integer.parseInt(args[0])) == null)
|
||||
{
|
||||
UtilPlayer.message(caller, F.main(QuestManager.QUEST_NAME, QuestManager.QUEST_NAME + " not found"));
|
||||
return;
|
||||
}
|
||||
|
||||
int increment = 0;
|
||||
try
|
||||
{
|
||||
increment = Integer.parseInt(args[1]);
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
UtilPlayer.message(caller, F.main(QuestManager.QUEST_NAME, "You have to submit a valid number"));
|
||||
return;
|
||||
}
|
||||
|
||||
Quest quest = Plugin.Get(player).getQuest(Integer.parseInt(args[0]));
|
||||
|
||||
Plugin.incrementQuest(player, quest, increment);
|
||||
UtilPlayer.message(player, F.main(QuestManager.QUEST_NAME, "Incremented " + QuestManager.QUEST_NAME + ": " + ChatColor.YELLOW + quest.getName() + ChatColor.GRAY + " by " + increment));
|
||||
|
||||
if (caller != player)
|
||||
UtilPlayer.message(caller, F.main(QuestManager.QUEST_NAME, "You incremented the " + QuestManager.QUEST_NAME + ": " + ChatColor.YELLOW + quest.getName() + ChatColor.GRAY + " for " + ChatColor.YELLOW + player.getName() + ChatColor.GRAY + " by " + increment));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package mineplex.core.quests.command;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import mineplex.core.command.CommandBase;
|
||||
import mineplex.core.common.Rank;
|
||||
import mineplex.core.quests.QuestManager;
|
||||
import mineplex.core.quests.shop.QuestShop;
|
||||
|
||||
/**
|
||||
* OpenGuiCommand
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class OpenGuiCommand extends CommandBase<QuestManager>
|
||||
{
|
||||
public OpenGuiCommand(QuestManager plugin)
|
||||
{
|
||||
super(plugin, Rank.ALL, "Missions");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void Execute(Player caller, String[] args)
|
||||
{
|
||||
new QuestShop(Plugin, Plugin.getClients(), Plugin.getDonations()).attemptShopOpen(caller);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package mineplex.core.quests.event;
|
||||
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
import mineplex.core.quests.Quest;
|
||||
|
||||
/**
|
||||
* QuestBuyEvent
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class QuestInteractEvent extends Event
|
||||
{
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
private boolean _cancelled = false;
|
||||
|
||||
private Quest _quest;
|
||||
|
||||
private String _cancelReason;
|
||||
|
||||
public QuestInteractEvent(Quest quest)
|
||||
{
|
||||
_quest = quest;
|
||||
}
|
||||
|
||||
public Quest getQuest()
|
||||
{
|
||||
return _quest;
|
||||
}
|
||||
|
||||
public HandlerList getHandlers()
|
||||
{
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList()
|
||||
{
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return _cancelled;
|
||||
}
|
||||
|
||||
public void setCancelled(String reason)
|
||||
{
|
||||
_cancelled = true;
|
||||
_cancelReason = reason;
|
||||
}
|
||||
|
||||
public String getCancelMessage()
|
||||
{
|
||||
return _cancelReason;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package mineplex.core.quests.repository;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
|
||||
import mineplex.core.account.CoreClient;
|
||||
import mineplex.core.common.Pair;
|
||||
import mineplex.core.common.util.Callback;
|
||||
import mineplex.core.quests.Quest;
|
||||
import mineplex.serverdata.database.DBPool;
|
||||
import mineplex.serverdata.database.RepositoryBase;
|
||||
import mineplex.serverdata.database.ResultSetCallable;
|
||||
import mineplex.serverdata.database.column.ColumnInt;
|
||||
import mineplex.serverdata.database.column.ColumnLong;
|
||||
|
||||
/**
|
||||
* QuestRepository
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class QuestRepository extends RepositoryBase
|
||||
{
|
||||
private static final String INSTERT_NEW_QUEST = "INSERT INTO accountQuest (accountId, questId, progress, questCompletion, lastCompleted) VALUES (?, ?, ?, ?, ?);";
|
||||
private static final String INCREMENT_QUEST = "UPDATE accountQuest SET progress=progress+? WHERE accountId=? AND questId=?;";
|
||||
private static final String RESET_QUEST = "UPDATE accountQuest SET progress=-1 WHERE accountId=? AND questId=?;";
|
||||
private static final String START_QUEST = "UPDATE accountQuest SET progress=0 WHERE accountId=? AND questId=?;";
|
||||
private static final String COMPLETE_QUEST = "UPDATE accountQuest SET progress=-1, questCompletion=questCompletion+1, lastCompleted=? WHERE accountId=? AND questId=?;";
|
||||
|
||||
private static final String FETCH_QUESTS = "SELECT questId, progress, questCompletion, lastCompleted FROM accountQuest WHERE accountId=?";
|
||||
|
||||
public String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS accountQuest (id INT NOT NULL AUTO_INCREMENT, accountId INT NOT NULL, questId INT NOT NULL, progress INT, questCompletion INT, lastCompleted BIGINT NOT NULL, PRIMARY KEY (id), FOREIGN KEY (accountId) REFERENCES accounts (id), UNIQUE INDEX questIndex (accountId, questId), INDEX progressIndex (progress), INDEX completionIndex (questCompletion));";
|
||||
|
||||
public QuestRepository()
|
||||
{
|
||||
super(DBPool.getAccount());
|
||||
|
||||
createTable();
|
||||
}
|
||||
|
||||
public void createTable()
|
||||
{
|
||||
executeUpdate(CREATE_TABLE);
|
||||
}
|
||||
|
||||
public void getQuests(CoreClient client, Callback<ArrayList<Pair<Integer, Triple<Integer, Long, Integer>>>> callback)
|
||||
{
|
||||
executeQuery(FETCH_QUESTS, new ResultSetCallable()
|
||||
{
|
||||
@Override
|
||||
public void processResultSet(ResultSet resultSet) throws SQLException
|
||||
{
|
||||
ArrayList<Pair<Integer, Triple<Integer, Long, Integer>>> list = new ArrayList<>();
|
||||
while (resultSet.next())
|
||||
{
|
||||
list.add(Pair.create(resultSet.getInt(1), Triple.of(resultSet.getInt(2), resultSet.getLong(4), resultSet.getInt(3))));
|
||||
}
|
||||
callback.run(list);
|
||||
}
|
||||
} ,new ColumnInt("accountId", client.getAccountId()));
|
||||
}
|
||||
|
||||
public void resetQuest(CoreClient client, Quest quest, boolean completed)
|
||||
{
|
||||
if (completed)
|
||||
{
|
||||
executeUpdate(COMPLETE_QUEST, new ColumnLong("lastCompleted", System.currentTimeMillis()), new ColumnInt("accountId", client.getAccountId()), new ColumnInt("questId", quest.getID()));
|
||||
}
|
||||
else
|
||||
{
|
||||
executeUpdate(RESET_QUEST, new ColumnInt("accountId", client.getAccountId()), new ColumnInt("questId", quest.getID()));
|
||||
}
|
||||
}
|
||||
|
||||
public void addQuest(CoreClient client, Quest quest)
|
||||
{
|
||||
executeUpdate(START_QUEST, new ColumnInt("accountId", client.getAccountId()), new ColumnInt("questId", quest.getID()));
|
||||
}
|
||||
|
||||
public void addNew(CoreClient client, Quest quest)
|
||||
{
|
||||
executeInsert(INSTERT_NEW_QUEST, null,
|
||||
new ColumnInt("accountId", client.getAccountId()),
|
||||
new ColumnInt("questId", quest.getID()),
|
||||
new ColumnInt("progress", 0),
|
||||
new ColumnInt("questCompletion", 0),
|
||||
new ColumnLong("lastCompleted", (long) 0));
|
||||
}
|
||||
|
||||
public void incrementQuest(CoreClient client, Quest quest, int value)
|
||||
{
|
||||
executeUpdate(INCREMENT_QUEST, new ColumnInt("progress", value), new ColumnInt("accountId", client.getAccountId()), new ColumnInt("questId", quest.getID()));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package mineplex.core.quests.shop;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.ClickType;
|
||||
|
||||
import mineplex.core.common.currency.GlobalCurrency;
|
||||
import mineplex.core.common.util.F;
|
||||
import mineplex.core.common.util.UtilPlayer;
|
||||
import mineplex.core.common.util.UtilServer;
|
||||
import mineplex.core.common.util.UtilTime;
|
||||
import mineplex.core.quests.Quest;
|
||||
import mineplex.core.quests.QuestManager;
|
||||
import mineplex.core.quests.event.QuestInteractEvent;
|
||||
import mineplex.core.recharge.Recharge;
|
||||
import mineplex.core.shop.confirmation.ConfirmationPage;
|
||||
import mineplex.core.shop.item.IButton;
|
||||
import mineplex.core.shop.item.SalesPackageBase;
|
||||
import mineplex.core.shop.item.SalesPackageProcessor;
|
||||
|
||||
/**
|
||||
* BuyQuestButton
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class BuyQuestButton implements IButton
|
||||
{
|
||||
private final static int MAX_QUESTS = 5;
|
||||
|
||||
private QuestManager _questManager;
|
||||
|
||||
private QuestPage _page;
|
||||
|
||||
private Quest _quest;
|
||||
|
||||
public BuyQuestButton(QuestManager questManager, QuestPage page, Quest quest)
|
||||
{
|
||||
_questManager = questManager;
|
||||
_quest = quest;
|
||||
_page = page;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Player player, ClickType clickType)
|
||||
{
|
||||
if (!Recharge.Instance.use(player, "Buy Mineplex Mission", 1000, false, false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_questManager.Get(player).hasQuest(_quest))
|
||||
{
|
||||
UtilPlayer.message(player, F.main(QuestManager.QUEST_NAME, "You already own that " + QuestManager.QUEST_NAME + "!"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_questManager.Get(player).getQuests().size() >= MAX_QUESTS)
|
||||
{
|
||||
UtilPlayer.message(player, F.main(QuestManager.QUEST_NAME, "You can't own more than " + MAX_QUESTS + " active " + QuestManager.QUEST_NAME + " at once!"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_questManager.Get(player).hasQuestFromAll(_quest))
|
||||
{
|
||||
if (!UtilTime.elapsed(_questManager.Get(player).getQuestFromAll(_quest.getID()).getLastCompleted(), 1000*60*60*24))
|
||||
{
|
||||
UtilPlayer.message(player, F.main(QuestManager.QUEST_NAME, "You already completed that " + QuestManager.QUEST_NAME + " today!"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QuestInteractEvent event = UtilServer.CallEvent(new QuestInteractEvent(_quest));
|
||||
if (event.isCancelled())
|
||||
{
|
||||
UtilPlayer.message(player, F.main(QuestManager.QUEST_NAME, event.getCancelMessage()));
|
||||
return;
|
||||
}
|
||||
|
||||
SalesPackageBase salesPackage = new QuestSale(ChatColor.YELLOW + "" + ChatColor.BOLD + _quest.getName(), Material.PAPER, _quest.getCost());
|
||||
_page.getShop().openPageForPlayer(player, new ConfirmationPage<>(player, _page, new SalesPackageProcessor(player, GlobalCurrency.GEM, salesPackage, _page.getDonationManager(), () ->
|
||||
{
|
||||
_questManager.addNewQuest(player, _quest);
|
||||
UtilPlayer.message(player, F.main(QuestManager.QUEST_NAME, "You have bought: " + ChatColor.YELLOW + _quest.getName()));
|
||||
player.closeInventory();
|
||||
new QuestShop(_questManager, _questManager.getClients(), _questManager.getDonations()).attemptShopOpen(player);
|
||||
|
||||
}), salesPackage.buildIcon()));
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
package mineplex.core.quests.shop;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.DyeColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.ClickType;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import mineplex.core.account.CoreClientManager;
|
||||
import mineplex.core.common.util.UtilTime;
|
||||
import mineplex.core.donation.DonationManager;
|
||||
import mineplex.core.itemstack.ItemStackFactory;
|
||||
import mineplex.core.quests.Quest;
|
||||
import mineplex.core.quests.QuestManager;
|
||||
import mineplex.core.shop.item.IButton;
|
||||
import mineplex.core.shop.page.ShopPageBase;
|
||||
|
||||
/**
|
||||
* QuestPage
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class QuestPage extends ShopPageBase<QuestManager, QuestShop>
|
||||
{
|
||||
private QuestShop _questShop;
|
||||
private QuestManager _manager;
|
||||
|
||||
public QuestPage(QuestManager plugin, QuestShop shop, CoreClientManager clientManager, DonationManager donationManager, Player player)
|
||||
{
|
||||
super(plugin, shop, clientManager, donationManager, QuestManager.QUEST_NAME + " Page", player, 27);
|
||||
|
||||
_questShop = shop;
|
||||
_manager = plugin;
|
||||
|
||||
buildPage();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildPage()
|
||||
{
|
||||
int i = 0;
|
||||
setItem(i, ItemStackFactory.Instance.CreateStack(Material.EMERALD, (byte) 0, 1, ChatColor.RESET + "" + ChatColor.BOLD + "Available " + QuestManager.QUEST_NAME + "s"));
|
||||
{
|
||||
int currentSlot = 4;
|
||||
int diff = 0;
|
||||
boolean forward = true;
|
||||
for (int questID : getQuestShop().getQuestManager().getCurrentQuests())
|
||||
{
|
||||
if (forward)
|
||||
{
|
||||
currentSlot += diff;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentSlot -= diff;
|
||||
}
|
||||
diff++;
|
||||
forward = !forward;
|
||||
|
||||
Quest playerQuest = _shop.getQuestManager().Get(_player).getQuestFromAll(questID);
|
||||
if (playerQuest != null)
|
||||
{
|
||||
if (!UtilTime.elapsed(playerQuest.getLastCompleted(), 1000*60*60*24))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (playerQuest.isActive())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Quest quest = _shop.getQuestManager().getQuestByID(questID);
|
||||
|
||||
ItemStack item = new ItemStack(Material.PAPER);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
meta.setDisplayName(ChatColor.YELLOW + "" + ChatColor.BOLD + quest.getName());
|
||||
meta.setLore(Arrays.asList(
|
||||
ChatColor.LIGHT_PURPLE + quest.getTask(),
|
||||
"",
|
||||
ChatColor.GRAY + "Cost: " + ChatColor.GREEN + quest.getCost() + " Gems",
|
||||
ChatColor.GRAY + "Reward: " + ChatColor.AQUA + quest.getRewardAmount() + " " + quest.getRewardName(),
|
||||
"",
|
||||
quest.getRarity().getColor() + "" + ChatColor.BOLD + quest.getRarity().toString(),
|
||||
"",
|
||||
ChatColor.GREEN + "Click to buy " + QuestManager.QUEST_NAME + "!"
|
||||
));
|
||||
item.setItemMeta(meta);
|
||||
|
||||
addButton(i + currentSlot, item, new BuyQuestButton(_manager, this, quest));
|
||||
}
|
||||
}
|
||||
i = 9;
|
||||
while (i < (9*2))
|
||||
{
|
||||
setItem(i, ItemStackFactory.Instance.CreateStack(Material.STAINED_GLASS_PANE, DyeColor.BLACK.getData()));
|
||||
i++;
|
||||
}
|
||||
i = 9*2;
|
||||
addButton(i, ItemStackFactory.Instance.CreateStack(Material.EMPTY_MAP, (byte) 0, 1, ChatColor.RESET + "" + ChatColor.BOLD + QuestManager.QUEST_NAME + " Stats"), new IButton()
|
||||
{
|
||||
@Override
|
||||
public void onClick(Player player, ClickType clickType)
|
||||
{
|
||||
player.closeInventory();
|
||||
new QuestStatShop(_manager, _manager.getClients(), _manager.getDonations()).attemptShopOpen(player);
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
int currentSlot = 4;
|
||||
int diff = 0;
|
||||
boolean forward = true;
|
||||
for (Quest quest : _shop.getQuestManager().Get(_player).getQuests())
|
||||
{
|
||||
if (forward)
|
||||
{
|
||||
currentSlot += diff;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentSlot -= diff;
|
||||
}
|
||||
diff++;
|
||||
forward = !forward;
|
||||
|
||||
ItemStack item = new ItemStack(Material.PAPER);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
meta.setDisplayName(ChatColor.YELLOW + "" + ChatColor.BOLD + quest.getName());
|
||||
|
||||
meta.setLore(Arrays.asList(ArrayUtils.addAll(quest.getQuestInfo(),
|
||||
"",
|
||||
(quest.isCompleted() ? ChatColor.GREEN + "Left Click to Complete" : ChatColor.RED + "Shift Right-Click to cancel")
|
||||
)));
|
||||
|
||||
item.setItemMeta(meta);
|
||||
item.addUnsafeEnchantment(Enchantment.DURABILITY, 1);
|
||||
|
||||
addButton(i + currentSlot, item, new RedeemDeclineQuestButton(_donationManager, _manager, quest));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public QuestShop getQuestShop()
|
||||
{
|
||||
return _questShop;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package mineplex.core.quests.shop;
|
||||
|
||||
import org.bukkit.Material;
|
||||
|
||||
import mineplex.core.common.currency.GlobalCurrency;
|
||||
import mineplex.core.shop.item.SalesPackageBase;
|
||||
|
||||
/**
|
||||
* QuestSale
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class QuestSale extends SalesPackageBase
|
||||
{
|
||||
|
||||
public QuestSale(String name, Material mat, int cost)
|
||||
{
|
||||
super(name, mat, (byte) 0, new String[] {}, cost);
|
||||
|
||||
KnownPackage = false;
|
||||
OneTimePurchaseOnly = false;
|
||||
// CurrencyCostMap.clear();
|
||||
CurrencyCostMap.put(GlobalCurrency.GEM, cost);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package mineplex.core.quests.shop;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import mineplex.core.account.CoreClientManager;
|
||||
import mineplex.core.donation.DonationManager;
|
||||
import mineplex.core.quests.QuestManager;
|
||||
import mineplex.core.shop.ShopBase;
|
||||
import mineplex.core.shop.page.ShopPageBase;
|
||||
|
||||
/**
|
||||
* QuestShop
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class QuestShop extends ShopBase<QuestManager>
|
||||
{
|
||||
public QuestShop(QuestManager plugin, CoreClientManager clientManager, DonationManager donationManager)
|
||||
{
|
||||
super(plugin, clientManager, donationManager, QuestManager.QUEST_NAME + " Shop");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ShopPageBase<QuestManager, ? extends ShopBase<QuestManager>> buildPagesFor(Player player)
|
||||
{
|
||||
return new QuestPage(getPlugin(), this, getClientManager(), getDonationManager(), player);
|
||||
}
|
||||
|
||||
public QuestManager getQuestManager()
|
||||
{
|
||||
return getPlugin();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package mineplex.core.quests.shop;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import mineplex.core.account.CoreClientManager;
|
||||
import mineplex.core.donation.DonationManager;
|
||||
import mineplex.core.quests.QuestManager;
|
||||
import mineplex.core.shop.ShopBase;
|
||||
import mineplex.core.shop.page.ShopPageBase;
|
||||
|
||||
/**
|
||||
* QuestStatShop
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class QuestStatShop extends ShopBase<QuestManager>
|
||||
{
|
||||
|
||||
public QuestStatShop(QuestManager plugin, CoreClientManager clientManager, DonationManager donationManager)
|
||||
{
|
||||
super(plugin, clientManager, donationManager, QuestManager.QUEST_NAME + " Stats");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ShopPageBase<QuestManager, ? extends ShopBase<QuestManager>> buildPagesFor(Player player)
|
||||
{
|
||||
return new QuestStatsPage(getPlugin(), this, getClientManager(), getDonationManager(), player, 0);
|
||||
}
|
||||
|
||||
public QuestManager getQuestManager()
|
||||
{
|
||||
return getPlugin();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
package mineplex.core.quests.shop;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.DyeColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.ClickType;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import mineplex.core.account.CoreClientManager;
|
||||
import mineplex.core.donation.DonationManager;
|
||||
import mineplex.core.itemstack.ItemStackFactory;
|
||||
import mineplex.core.quests.Quest;
|
||||
import mineplex.core.quests.QuestClientData;
|
||||
import mineplex.core.quests.QuestManager;
|
||||
import mineplex.core.shop.item.IButton;
|
||||
import mineplex.core.shop.page.ShopPageBase;
|
||||
|
||||
/**
|
||||
* QuestStatsPage
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class QuestStatsPage extends ShopPageBase<QuestManager, QuestStatShop>
|
||||
{
|
||||
private QuestStatShop _questShop;
|
||||
private QuestManager _manager;
|
||||
private int _siteIndex;
|
||||
|
||||
public QuestStatsPage(QuestManager plugin, QuestStatShop shop, CoreClientManager clientManager, DonationManager donationManager, Player player, int site)
|
||||
{
|
||||
super(plugin, shop, clientManager, donationManager, QuestManager.QUEST_NAME + " Stats", player, 6*9);
|
||||
|
||||
_siteIndex = site;
|
||||
|
||||
_questShop = shop;
|
||||
_manager = plugin;
|
||||
|
||||
buildPage();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildPage()
|
||||
{
|
||||
removeButton(5*9);
|
||||
removeButton((6*9) - 1);
|
||||
|
||||
int questID = (_siteIndex * 5 * 9);
|
||||
for (int i = 0; i < 9*5; i++)
|
||||
{
|
||||
Quest quest = _manager.getQuestByID(questID);
|
||||
|
||||
if (quest == null)
|
||||
{
|
||||
setItem(i, null);
|
||||
questID++;
|
||||
continue;
|
||||
}
|
||||
|
||||
QuestClientData data = _manager.Get(getPlayer());
|
||||
ItemStack item = ItemStackFactory.Instance.CreateStack(Material.WOOL, DyeColor.GRAY.getWoolData(), 1, ChatColor.YELLOW + quest.getName());
|
||||
|
||||
if (data.hasQuestFromAll(quest))
|
||||
{
|
||||
Quest used = data.getQuestFromAll(questID);
|
||||
|
||||
item = ItemStackFactory.Instance.CreateStack(Material.PAPER, (byte) 0, 1, ChatColor.YELLOW + used.getName());
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
meta.setLore(Arrays.asList(ArrayUtils.addAll(used.getQuestInfo(),
|
||||
"",
|
||||
ChatColor.GRAY + "Times Completed: " + ChatColor.YELLOW + used.getTimesCompleted()
|
||||
)));
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
setItem(i, item);
|
||||
|
||||
questID++;
|
||||
}
|
||||
|
||||
if (_siteIndex > 0)
|
||||
{
|
||||
addButton(5*9, ItemStackFactory.Instance.CreateStack(Material.SIGN, (byte) 0, 1, ChatColor.YELLOW + "Previous Page"), new IButton()
|
||||
{
|
||||
@Override
|
||||
public void onClick(Player player, ClickType clickType)
|
||||
{
|
||||
_siteIndex--;
|
||||
buildPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (((_siteIndex + 1) * 5 * 9) <= _manager.getAvailableQuests().size())
|
||||
{
|
||||
addButton((6*9) - 1, ItemStackFactory.Instance.CreateStack(Material.SIGN, (byte) 0, 1, ChatColor.YELLOW + "Next Page"), new IButton()
|
||||
{
|
||||
@Override
|
||||
public void onClick(Player player, ClickType clickType)
|
||||
{
|
||||
_siteIndex++;
|
||||
buildPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addButton(5*9 + 4, ItemStackFactory.Instance.CreateStack(Material.SIGN, (byte) 0, 1, ChatColor.YELLOW + "Back to " + QuestManager.QUEST_NAME + "s"), new IButton()
|
||||
{
|
||||
@Override
|
||||
public void onClick(Player player, ClickType clickType)
|
||||
{
|
||||
player.closeInventory();
|
||||
new QuestShop(_manager, getClientManager(), getDonationManager()).attemptShopOpen(player);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
public QuestStatShop getQuestStatsShop()
|
||||
{
|
||||
return _questShop;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package mineplex.core.quests.shop;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.jws.Oneway;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.ClickType;
|
||||
|
||||
import mineplex.core.common.currency.GlobalCurrency;
|
||||
import mineplex.core.common.util.F;
|
||||
import mineplex.core.common.util.UtilPlayer;
|
||||
import mineplex.core.common.util.UtilServer;
|
||||
import mineplex.core.donation.DonationManager;
|
||||
import mineplex.core.inventory.InventoryManager;
|
||||
import mineplex.core.quests.Quest;
|
||||
import mineplex.core.quests.QuestManager;
|
||||
import mineplex.core.quests.event.QuestInteractEvent;
|
||||
import mineplex.core.recharge.Recharge;
|
||||
import mineplex.core.shop.item.IButton;
|
||||
|
||||
/**
|
||||
* RedeemDeclineQuest
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class RedeemDeclineQuestButton implements IButton
|
||||
{
|
||||
private QuestManager _questManager;
|
||||
private DonationManager _donations;
|
||||
|
||||
private Quest _quest;
|
||||
|
||||
public RedeemDeclineQuestButton(DonationManager donations, QuestManager questManager, Quest quest)
|
||||
{
|
||||
_questManager = questManager;
|
||||
_donations = donations;
|
||||
_quest = quest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Player player, ClickType clickType)
|
||||
{
|
||||
if (!Recharge.Instance.use(player, "Decline Mineplex Mission", 1000, false, false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_questManager.Get(player).hasQuest(_quest))
|
||||
{
|
||||
UtilPlayer.message(player, F.main(QuestManager.QUEST_NAME, "You don't own that " + QuestManager.QUEST_NAME + "!"));
|
||||
return;
|
||||
}
|
||||
|
||||
QuestInteractEvent event = UtilServer.CallEvent(new QuestInteractEvent(_quest));
|
||||
if (event.isCancelled())
|
||||
{
|
||||
UtilPlayer.message(player, F.main(QuestManager.QUEST_NAME, event.getCancelMessage()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (clickType == ClickType.SHIFT_RIGHT)
|
||||
{
|
||||
if (_quest.isCompleted())
|
||||
{
|
||||
UtilPlayer.message(player, F.main(QuestManager.QUEST_NAME, "You can't cancel a completed " + QuestManager.QUEST_NAME + "!"));
|
||||
return;
|
||||
}
|
||||
_questManager.resetQuest(player, _quest, false);
|
||||
UtilPlayer.message(player, F.main(QuestManager.QUEST_NAME, "You have cancelled that " + QuestManager.QUEST_NAME + "!"));
|
||||
}
|
||||
|
||||
if (clickType == ClickType.LEFT)
|
||||
{
|
||||
if (!_quest.isCompleted())
|
||||
{
|
||||
UtilPlayer.message(player, F.main(QuestManager.QUEST_NAME, "You haven't completed that " + QuestManager.QUEST_NAME + " yet!"));
|
||||
return;
|
||||
}
|
||||
|
||||
_questManager.rewardQuest(player, _quest, new Consumer<Boolean>()
|
||||
{
|
||||
@Override
|
||||
public void accept(Boolean succes)
|
||||
{
|
||||
if (succes)
|
||||
{
|
||||
_questManager.resetQuest(player, _quest, true);
|
||||
player.closeInventory();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
player.closeInventory();
|
||||
new QuestShop(_questManager, _questManager.getClients(), _donations).attemptShopOpen(player);
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package mineplex.database.tables;
|
||||
|
||||
import org.jooq.impl.TableImpl;
|
||||
|
||||
/**
|
||||
* This class is generated by jOOQ.
|
||||
*/
|
||||
@javax.annotation.Generated(
|
||||
value = {
|
||||
"http://www.jooq.org",
|
||||
"jOOQ version:3.5.2"
|
||||
},
|
||||
comments = "This class is generated by jOOQ"
|
||||
)
|
||||
@java.lang.SuppressWarnings({ "all", "unchecked", "rawtypes" })
|
||||
public class AccountQuest //extends TableImpl<mineplex.database.tables.records.AccountQuestRecord> implements java.io.Serializable, java.lang.Cloneable
|
||||
{
|
||||
|
||||
// /**
|
||||
// *
|
||||
// */
|
||||
// private static final long serialVersionUID = -8158716179851336044L;
|
||||
//
|
||||
// /**
|
||||
// * The reference instance of <code>Account.accountStat</code>
|
||||
// */
|
||||
// public static final mineplex.database.tables.AccountStat accountStat = new mineplex.database.tables.AccountStat();
|
||||
//
|
||||
// /**
|
||||
// * The class holding records for this type
|
||||
// */
|
||||
// @Override
|
||||
// public java.lang.Class<mineplex.database.tables.records.AccountQuestRecord> getRecordType() {
|
||||
// return mineplex.database.tables.records.AccountQuestRecord.class;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * The column <code>Account.accountStat.accountId</code>.
|
||||
// */
|
||||
// public final org.jooq.TableField<mineplex.database.tables.records.AccountQuestRecord, java.lang.Integer> accountId = createField("accountId", org.jooq.impl.SQLDataType.INTEGER.nullable(false), this, "");
|
||||
//
|
||||
// /**
|
||||
// * The column <code>Account.accountStat.statId</code>.
|
||||
// */
|
||||
// public final org.jooq.TableField<mineplex.database.tables.records.AccountQuestRecord, java.lang.Integer> statId = createField("statId", org.jooq.impl.SQLDataType.INTEGER.nullable(false), this, "");
|
||||
//
|
||||
// /**
|
||||
// * The column <code>Account.accountStat.value</code>.
|
||||
// */
|
||||
// public final org.jooq.TableField<mineplex.database.tables.records.AccountQuestRecord, org.jooq.types.ULong> value = createField("value", org.jooq.impl.SQLDataType.BIGINTUNSIGNED.defaulted(true), this, "");
|
||||
//
|
||||
// /**
|
||||
// * Create a <code>Account.accountStat</code> table reference
|
||||
// */
|
||||
// public AccountQuest()
|
||||
// {
|
||||
// super("accountquest", null);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Create an aliased <code>Account.accountStat</code> table reference
|
||||
// */
|
||||
// public AccountQuest(java.lang.String alias) {
|
||||
// this(alias, mineplex.database.tables.AccountQuest.accountStat);
|
||||
// }
|
||||
//
|
||||
// private AccountQuest(java.lang.String alias, org.jooq.Table<mineplex.database.tables.records.AccountQuestRecord> aliased) {
|
||||
// this(alias, aliased, null);
|
||||
// }
|
||||
//
|
||||
// private AccountQuest(java.lang.String alias, org.jooq.Table<mineplex.database.tables.records.AccountQuestRecord> aliased, org.jooq.Field<?>[] parameters) {
|
||||
// super(alias, mineplex.database.Account.Account, aliased, parameters, "");
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// @Override
|
||||
// public org.jooq.UniqueKey<mineplex.database.tables.records.AccountStatRecord> getPrimaryKey() {
|
||||
// return mineplex.database.Keys.KEY_accountStat_PRIMARY;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// @Override
|
||||
// public java.util.List<org.jooq.UniqueKey<mineplex.database.tables.records.AccountStatRecord>> getKeys() {
|
||||
// return java.util.Arrays.<org.jooq.UniqueKey<mineplex.database.tables.records.AccountStatRecord>>asList(mineplex.database.Keys.KEY_accountStat_PRIMARY);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// @Override
|
||||
// public java.util.List<org.jooq.ForeignKey<mineplex.database.tables.records.AccountStatRecord, ?>> getReferences() {
|
||||
// return java.util.Arrays.<org.jooq.ForeignKey<mineplex.database.tables.records.AccountStatRecord, ?>>asList(mineplex.database.Keys.accountStat_account, mineplex.database.Keys.accountStat_stat);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// @Override
|
||||
// public mineplex.database.tables.AccountStat as(java.lang.String alias) {
|
||||
// return new mineplex.database.tables.AccountStat(alias, this);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Rename this table
|
||||
// */
|
||||
// public mineplex.database.tables.AccountStat rename(java.lang.String name) {
|
||||
// return new mineplex.database.tables.AccountStat(name, null);
|
||||
// }
|
||||
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
package mineplex.database.tables.records;
|
||||
|
||||
import org.jooq.Table;
|
||||
|
||||
/**
|
||||
* AccountQuest
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class AccountQuestRecord //extends org.jooq.impl.UpdatableRecordImpl<mineplex.database.tables.records.AccountQuestRecord> implements java.io.Serializable, java.lang.Cloneable, org.jooq.Record3<java.lang.Integer, java.lang.Integer, org.jooq.types.ULong>
|
||||
{
|
||||
//
|
||||
// /**
|
||||
// *
|
||||
// */
|
||||
// private static final long serialVersionUID = 5171965369180094201L;
|
||||
//
|
||||
// public AccountQuestRecord(Table<AccountQuestRecord> table)
|
||||
// {
|
||||
// super(table);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Setter for <code>Account.accountStat.accountId</code>.
|
||||
// */
|
||||
// public void setAccountId(java.lang.Integer value) {
|
||||
// setValue(0, value);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Getter for <code>Account.accountStat.accountId</code>.
|
||||
// */
|
||||
// public java.lang.Integer getAccountId() {
|
||||
// return (java.lang.Integer) getValue(0);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Setter for <code>Account.accountStat.statId</code>.
|
||||
// */
|
||||
// public void setStatId(java.lang.Integer value) {
|
||||
// setValue(1, value);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Getter for <code>Account.accountStat.statId</code>.
|
||||
// */
|
||||
// public java.lang.Integer getStatId() {
|
||||
// return (java.lang.Integer) getValue(1);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Setter for <code>Account.accountStat.value</code>.
|
||||
// */
|
||||
// public void setValue(org.jooq.types.ULong value) {
|
||||
// setValue(2, value);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Getter for <code>Account.accountStat.value</code>.
|
||||
// */
|
||||
// public org.jooq.types.ULong getValue() {
|
||||
// return (org.jooq.types.ULong) getValue(2);
|
||||
// }
|
||||
//
|
||||
// // -------------------------------------------------------------------------
|
||||
// // Primary key information
|
||||
// // -------------------------------------------------------------------------
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// @Override
|
||||
// public org.jooq.Record2<java.lang.Integer, java.lang.Integer> key() {
|
||||
// return (org.jooq.Record2) super.key();
|
||||
// }
|
||||
//
|
||||
// // -------------------------------------------------------------------------
|
||||
// // Record3 type implementation
|
||||
// // -------------------------------------------------------------------------
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// @Override
|
||||
// public org.jooq.Row3<java.lang.Integer, java.lang.Integer, org.jooq.types.ULong> fieldsRow() {
|
||||
// return (org.jooq.Row3) super.fieldsRow();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// @Override
|
||||
// public org.jooq.Row3<java.lang.Integer, java.lang.Integer, org.jooq.types.ULong> valuesRow() {
|
||||
// return (org.jooq.Row3) super.valuesRow();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// @Override
|
||||
// public org.jooq.Field<java.lang.Integer> field1() {
|
||||
// return mineplex.database.tables.AccountStat.accountStat.accountId;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// @Override
|
||||
// public org.jooq.Field<java.lang.Integer> field2() {
|
||||
// return mineplex.database.tables.AccountStat.accountStat.statId;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// @Override
|
||||
// public org.jooq.Field<org.jooq.types.ULong> field3() {
|
||||
// return mineplex.database.tables.AccountStat.accountStat.value;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// @Override
|
||||
// public java.lang.Integer value1() {
|
||||
// return getAccountId();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// @Override
|
||||
// public java.lang.Integer value2() {
|
||||
// return getStatId();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// @Override
|
||||
// public org.jooq.types.ULong value3() {
|
||||
// return getValue();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// @Override
|
||||
// public AccountStatRecord value1(java.lang.Integer value) {
|
||||
// setAccountId(value);
|
||||
// return this;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// @Override
|
||||
// public AccountStatRecord value2(java.lang.Integer value) {
|
||||
// setStatId(value);
|
||||
// return this;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// @Override
|
||||
// public AccountStatRecord value3(org.jooq.types.ULong value) {
|
||||
// setValue(value);
|
||||
// return this;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// @Override
|
||||
// public AccountStatRecord values(java.lang.Integer value1, java.lang.Integer value2, org.jooq.types.ULong value3) {
|
||||
// return this;
|
||||
// }
|
||||
//
|
||||
// // -------------------------------------------------------------------------
|
||||
// // Constructors
|
||||
// // -------------------------------------------------------------------------
|
||||
//
|
||||
// /**
|
||||
// * Create a detached AccountStatRecord
|
||||
// */
|
||||
// public AccountStatRecord() {
|
||||
// super(mineplex.database.tables.AccountStat.accountStat);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Create a detached, initialised AccountStatRecord
|
||||
// */
|
||||
// public AccountStatRecord(java.lang.Integer accountId, java.lang.Integer statId, org.jooq.types.ULong value) {
|
||||
// super(mineplex.database.tables.AccountStat.accountStat);
|
||||
//
|
||||
// setValue(0, accountId);
|
||||
// setValue(1, statId);
|
||||
// setValue(2, value);
|
||||
// }
|
||||
|
||||
}
|
@ -104,6 +104,7 @@ import mineplex.core.preferences.Preference;
|
||||
import mineplex.core.preferences.PreferencesManager;
|
||||
import mineplex.core.projectile.ProjectileManager;
|
||||
import mineplex.core.punish.Punish;
|
||||
import mineplex.core.quests.QuestManager;
|
||||
import mineplex.core.scoreboard.MineplexScoreboard;
|
||||
import mineplex.core.scoreboard.ScoreboardManager;
|
||||
import mineplex.core.stats.StatsManager;
|
||||
@ -178,6 +179,7 @@ public class HubManager extends MiniClientPlugin<HubClient> implements IChatMess
|
||||
// private HalloweenSpookinessManager _halloweenManager;
|
||||
// private TrickOrTreatManager _trickOrTreatManager;
|
||||
private MavericksManager _mavericksManager;
|
||||
private QuestManager _questManager;
|
||||
private final TwoFactorAuth _twofactor = Managers.require(TwoFactorAuth.class);
|
||||
|
||||
private HologramManager _hologramManager;
|
||||
@ -291,6 +293,8 @@ public class HubManager extends MiniClientPlugin<HubClient> implements IChatMess
|
||||
_hologramManager = hologramManager;
|
||||
|
||||
new EasterEggHunt(plugin, _clientManager);
|
||||
|
||||
_questManager = new QuestManager(hologramManager, null, _inventoryManager, _donationManager);
|
||||
|
||||
new TemporaryGemHuntersServerSender(_portal);
|
||||
|
||||
@ -1029,4 +1033,9 @@ public class HubManager extends MiniClientPlugin<HubClient> implements IChatMess
|
||||
{
|
||||
return _jumpManager;
|
||||
}
|
||||
|
||||
public QuestManager getQuestManager()
|
||||
{
|
||||
return _questManager;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,10 @@
|
||||
<artifactId>mineplex-parent</artifactId>
|
||||
<version>dev-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<version.guava>18.0</version.guava>
|
||||
</properties>
|
||||
|
||||
<artifactId>mineplex-serverdata</artifactId>
|
||||
|
||||
@ -33,5 +37,11 @@
|
||||
<artifactId>jooq</artifactId>
|
||||
<version>3.5.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>${version.guava}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -0,0 +1,356 @@
|
||||
package mineplex.serverdata.redis.messaging;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
|
||||
import mineplex.serverdata.Utility;
|
||||
import mineplex.serverdata.servers.ConnectionData;
|
||||
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.JedisPubSub;
|
||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||
import redis.clients.jedis.exceptions.JedisDataException;
|
||||
|
||||
/**
|
||||
* A subscription to a Redis pub/sub system through a Jedis client. Includes a publishing mechanism
|
||||
* as well.
|
||||
* <p>
|
||||
* Subscribes to Jedis and offers a variety of methods to edit the channels that this listens to
|
||||
* over time. Does not support pattern-matching, even though Jedis can. Takes a single subscriber to
|
||||
* inform of incoming messages (on all channels this is subscribed to).
|
||||
* <p>
|
||||
* For the sake of internal efficiency, this does not protect the sanity or unchangeability of
|
||||
* arguments passed into its methods. Clients should not generally interact with this directly.
|
||||
* <p>
|
||||
* The Jedis pub/sub interface is a little confusing, especially when it comes to multithreading. At
|
||||
* any given time, if this class is subscribed to any channels at all, it will have a single thread
|
||||
* that is listening for incoming messages from redis with a blocking method. After that listening
|
||||
* thread is created, we can add and remove subscriptions as desired, but the initial subscription
|
||||
* and actual listening must be done on its own thread. When all channels are unsubscribed from, the
|
||||
* listening thread returns. Note that this is stated with about 70% certainty, as the internals of
|
||||
* the pub/sub mechanism are not entirely clear to me.
|
||||
* <p>
|
||||
* This class maintains a constant connection to its redis server by subscribing to a base channel.
|
||||
* This makes it much easier to protect its operation from potentially insane client commands.
|
||||
* <p>
|
||||
* If the connection to the given redis instance fails or is interrupted, will keep attempting to
|
||||
* reconnect periodically until destroyed. Publishers and subscribers are not informed of failure in
|
||||
* any way.
|
||||
* <p>
|
||||
* When {@link #unsubscribe()} or {@link #destroy()} is called, this class ceases operation.
|
||||
*/
|
||||
public class PubSubJedisClient extends JedisPubSub implements PubSubLibraryClient
|
||||
{
|
||||
|
||||
private static final long RECONNECT_PERIOD_MILLIS = 800;
|
||||
private static final String BASE_CHANNEL = "pG8n5jp#";
|
||||
|
||||
private static final String BOLD = "\u001B[1m";
|
||||
private static final String RESET = "\u001B[0m";
|
||||
|
||||
private final String _id;
|
||||
private JedisPool _writePool;
|
||||
private final ConnectionData _readConn;
|
||||
private JedisPool _readPool;
|
||||
private final ExecutorService _threadPool = Executors.newCachedThreadPool();
|
||||
private volatile Subscriber _sub;
|
||||
|
||||
private final Set<String> _channels = Collections
|
||||
.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
|
||||
private final Map<String, SettableFuture<Boolean>> _pendingFutures = new ConcurrentHashMap<String, SettableFuture<Boolean>>();
|
||||
|
||||
private volatile boolean _subscribed; // Is there a base subscription yet?
|
||||
private volatile boolean _destroyed; // has this been deliberately destroyed?
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param writeTo The connection info for the redis instance this client should publish to.
|
||||
* @param readFrom The connection info for the redis instance this client to subscribe to.
|
||||
*/
|
||||
public PubSubJedisClient(ConnectionData writeTo, ConnectionData readFrom)
|
||||
{
|
||||
if (writeTo == null)
|
||||
{
|
||||
throw new IllegalArgumentException("redis connection info cannot be null");
|
||||
}
|
||||
|
||||
_id = writeTo.getName();
|
||||
_writePool = Utility.generatePool(writeTo);
|
||||
|
||||
_readConn = readFrom;
|
||||
_readPool = Utility.generatePool(readFrom);
|
||||
|
||||
createSubscription(BASE_CHANNEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final synchronized void setSubscriber(Subscriber sub)
|
||||
{
|
||||
_sub = sub;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ListenableFuture<Boolean> addChannel(String channel)
|
||||
{
|
||||
SettableFuture<Boolean> ret = _pendingFutures.get(channel);
|
||||
if (ret == null)
|
||||
{
|
||||
ret = SettableFuture.create();
|
||||
_pendingFutures.put(channel, ret);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_channels.add(channel);
|
||||
|
||||
if (_subscribed)
|
||||
{ // Already has a subscription thread and can just add a new channel to it.
|
||||
subscribe(channel);
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
log("Encountered issue subscribing to a channel.");
|
||||
ex.printStackTrace();
|
||||
ret.setException(ex);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void removeChannel(String channel)
|
||||
{
|
||||
if (BASE_CHANNEL.equals(channel))
|
||||
{ // Protects the base subscription
|
||||
return;
|
||||
}
|
||||
|
||||
_channels.remove(channel);
|
||||
|
||||
if (_subscribed)
|
||||
{
|
||||
unsubscribe(channel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void unsubscribe()
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void destroy()
|
||||
{
|
||||
_destroyed = true;
|
||||
try
|
||||
{
|
||||
super.unsubscribe();
|
||||
} catch (NullPointerException e)
|
||||
{
|
||||
}
|
||||
|
||||
for (SettableFuture<Boolean> fut : _pendingFutures.values())
|
||||
{
|
||||
fut.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onMessage(String channel, String message)
|
||||
{
|
||||
if (_sub == null || BASE_CHANNEL.equals(channel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_sub.onMessage(channel, message);
|
||||
} catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ListenableFuture<Boolean> publish(final String channel, final String message)
|
||||
{
|
||||
final SettableFuture<Boolean> ret = SettableFuture.create();
|
||||
_threadPool.execute(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
Jedis bJedis = null;
|
||||
try
|
||||
{
|
||||
bJedis = _writePool.getResource();
|
||||
bJedis.publish(channel, message);
|
||||
_writePool.returnResource((Jedis) bJedis);
|
||||
ret.set(true);
|
||||
|
||||
} catch (Exception e)
|
||||
{
|
||||
log("Encountered issue while publishing a message.");
|
||||
e.printStackTrace();
|
||||
if (bJedis != null)
|
||||
{
|
||||
_writePool.returnBrokenResource((Jedis) bJedis);
|
||||
}
|
||||
ret.set(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Confirms successful subscriptions/unsubscriptions.
|
||||
@Override
|
||||
public void onSubscribe(String channel, int subscribedChannels)
|
||||
{
|
||||
|
||||
// informs subscriber that this subscription completed successfully
|
||||
SettableFuture<Boolean> fut = _pendingFutures.remove(channel);
|
||||
if (fut != null)
|
||||
{
|
||||
fut.set(true);
|
||||
}
|
||||
|
||||
if (!_subscribed)
|
||||
{
|
||||
for (String subscribeTo : _channels)
|
||||
{
|
||||
subscribe(subscribeTo);
|
||||
}
|
||||
}
|
||||
_subscribed = true;
|
||||
|
||||
log("Subscribed to channel: " + channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnsubscribe(String channel, int subscribedChannels)
|
||||
{
|
||||
log("Unsubscribed from channel: " + channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the initial listening thread which blocks as it polls redis for new messages.
|
||||
* Subsequent subscriptions can simply be added using {@link #subscribe(String...)} after the
|
||||
* subscription thread has been created.
|
||||
*
|
||||
* @param firstChannel The first channel to initially subscribe to. If you do not have a first
|
||||
* channel, there is no reason to create a subscriber thread yet.
|
||||
*/
|
||||
private void createSubscription(final String firstChannel)
|
||||
{
|
||||
|
||||
final JedisPubSub pubsub = this;
|
||||
|
||||
new Thread(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
|
||||
boolean first = true;
|
||||
|
||||
while (!_destroyed)
|
||||
{
|
||||
|
||||
if (!first)
|
||||
{
|
||||
log("Jedis connection to " + _readConn.getHost() + ":"
|
||||
+ _readConn.getPort()
|
||||
+ " failed or was interrupted, attempting to reconnect");
|
||||
}
|
||||
first = false;
|
||||
|
||||
Jedis jedisInstance = null;
|
||||
|
||||
try
|
||||
{
|
||||
// gets a non-thread-safe jedis instance from the thread-safe pool.
|
||||
jedisInstance = _readPool.getResource();
|
||||
|
||||
log("Creating initial jedis subscription to channel " + firstChannel);
|
||||
// this will block as long as there are subscriptions
|
||||
jedisInstance.subscribe(pubsub, firstChannel);
|
||||
|
||||
log("jedisInstance.subscribe() returned, subscription over.");
|
||||
|
||||
// when the subscription ends (subscribe() returns), returns the instance to
|
||||
// the pool
|
||||
_readPool.returnResource(jedisInstance);
|
||||
|
||||
} catch (JedisConnectionException e)
|
||||
{
|
||||
log("Jedis connection encountered an issue.");
|
||||
e.printStackTrace();
|
||||
if (jedisInstance != null)
|
||||
{
|
||||
_readPool.returnBrokenResource((Jedis) jedisInstance);
|
||||
}
|
||||
|
||||
} catch (JedisDataException e)
|
||||
{
|
||||
log("Jedis connection encountered an issue.");
|
||||
e.printStackTrace();
|
||||
if (jedisInstance != null)
|
||||
{
|
||||
_readPool.returnBrokenResource((Jedis) jedisInstance);
|
||||
}
|
||||
}
|
||||
_subscribed = false;
|
||||
|
||||
// sleeps for a short pause, rather than constantly retrying connection
|
||||
if (!_destroyed)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(RECONNECT_PERIOD_MILLIS);
|
||||
} catch (InterruptedException e)
|
||||
{
|
||||
_destroyed = true;
|
||||
log("Reconnection pause thread was interrupted.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
// This implementation does not support pattern-matching subscriptions
|
||||
@Override
|
||||
public void onPMessage(String pattern, String channel, String message)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPSubscribe(String pattern, int subscribedChannels)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPUnsubscribe(String pattern, int subscribedChannels)
|
||||
{
|
||||
}
|
||||
|
||||
private void log(String msg)
|
||||
{
|
||||
System.out.println(BOLD + "[" + getClass().getSimpleName()
|
||||
+ (_id != null && !_id.isEmpty() ? " " + _id : "") + "] " + RESET + msg);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package mineplex.serverdata.redis.messaging;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
/**
|
||||
* A multi-channel subscription and publisher to a pub/sub messaging implementation. An interface to
|
||||
* the actual low-level pub/sub library, whatever it may be.
|
||||
*
|
||||
* For the sake of internal efficiency, this makes no guarantees for the sanity or unchangeability
|
||||
* of arguments passed into its methods. Clients should not generally interact with this directly.
|
||||
*/
|
||||
public interface PubSubLibraryClient
|
||||
{
|
||||
|
||||
/**
|
||||
* Publishes a message to all subscribers of a given channel.
|
||||
*
|
||||
* @param channel The channel to publish the message on.
|
||||
* @param message The message to send.
|
||||
* @return A future object that will complete after an unknown amount of time with
|
||||
* <code>false</code> if for some locally known reason the message definitely could not
|
||||
* be published, else completes with <code>true</code>.
|
||||
*/
|
||||
ListenableFuture<Boolean> publish(String channel, String message);
|
||||
|
||||
/**
|
||||
* Adds a channel to this subscription.
|
||||
*
|
||||
* @param channel The channel to add. Should not change after being passed in.
|
||||
* @return The asynchronous, future result of this attempt to add the channel. Will have
|
||||
* <code>true</code> when the subscription starts successfully.
|
||||
*/
|
||||
ListenableFuture<Boolean> addChannel(String channel);
|
||||
|
||||
/**
|
||||
* Removes a channel from this subscription.
|
||||
*
|
||||
* @param channel The channel to remove. Should not change after being passed in.
|
||||
*/
|
||||
void removeChannel(String channel);
|
||||
|
||||
/**
|
||||
* Removes all channels from this subscription, kills its connections, and relinquishes any
|
||||
* resources it was occupying.
|
||||
* <p>
|
||||
* Depending on the implementation, once a subscription has been destroyed, it may not be
|
||||
* reusable and it may be necessary to construct a new one in order to resume.
|
||||
* <p>
|
||||
* Call this when the subscription is no longer being used. Holding unnecessary connections can
|
||||
* cause serious performance and other issues on both ends.
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* Sets the subscriber to inform of messages received by this subscription.
|
||||
*
|
||||
* @param sub The listener for this subscription.
|
||||
*/
|
||||
void setSubscriber(Subscriber sub);
|
||||
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package mineplex.serverdata.redis.messaging;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
/**
|
||||
* Messager for standard pub/sub model. Handles multiple publishers and subscribers.
|
||||
* <p>
|
||||
* All messaging is asynchronous and non-blocking, even to local subscribers.
|
||||
* <p>
|
||||
* For more about the pub/sub messaging paradigm, see <a
|
||||
* href="http://en.wikipedia.org/wiki/Publish%E2
|
||||
* %80%93subscribe_pattern">http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern</a>
|
||||
*/
|
||||
public interface PubSubMessager
|
||||
{
|
||||
|
||||
/**
|
||||
* Publishes a message to all subscribers of a given channel.
|
||||
* <p>
|
||||
* Publishes to all connected subscribers, including local ones.
|
||||
*
|
||||
* @param channel The channel to publish the message on.
|
||||
* @param message The message to send.
|
||||
* @return A future object that will complete after an unknown amount of time with
|
||||
* <code>false</code> if for some locally known reason the message definitely could not
|
||||
* be published, else completes with <code>true</code> (which does not guarantee it
|
||||
* succeeded 100%).
|
||||
*/
|
||||
ListenableFuture<Boolean> publish(String channel, String message);
|
||||
|
||||
/**
|
||||
* Subscribes to a messaging channel.
|
||||
* <p>
|
||||
* When incoming messages arrive, the subscriber is called from an arbitrary new thread.
|
||||
*
|
||||
* @param channel The channel to subscribe to.
|
||||
* @param sub The subscriber to inform of incoming messages.
|
||||
* @return The asynchronous, future result of this attempt to subscribe to the channel. Will
|
||||
* have <code>true</code> when the subscription starts successfully.
|
||||
*/
|
||||
ListenableFuture<Boolean> subscribe(String channel, Subscriber sub);
|
||||
|
||||
/**
|
||||
* Unsubscribes from a messaging channel.
|
||||
*
|
||||
* @param channel The channel to unsubscribe from.
|
||||
* @param sub The subscriber to stop informing of incoming messages.
|
||||
*/
|
||||
void unsubscribe(String channel, Subscriber sub);
|
||||
|
||||
/**
|
||||
* Attempts to gracefully shut down this messager. Generally irreversible.
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
}
|
@ -0,0 +1,165 @@
|
||||
package mineplex.serverdata.redis.messaging;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
|
||||
/**
|
||||
* A pub/sub messager that simply routes messages to some underlying pub/sub implementation, which
|
||||
* is in turn represented by a multi-channel subscription and a publishing mechanism.
|
||||
* <p>
|
||||
* This class handles:
|
||||
* <ol>
|
||||
* <li>Providing a modular messaging interface that is thread-safe.
|
||||
* <li>Protecting pub/sub implementations from some bad client behavior/data.
|
||||
* <li>Routing messages for multiple subscribers to the same channel(s).
|
||||
* </ol>
|
||||
*/
|
||||
public class PubSubRouter implements PubSubMessager, Subscriber
|
||||
{
|
||||
|
||||
private final PubSubLibraryClient _pubSubClient;
|
||||
private final Map<String, Set<Subscriber>> _subscribers;
|
||||
|
||||
private final ExecutorService _threadPool;
|
||||
|
||||
public PubSubRouter(PubSubLibraryClient client)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
throw new IllegalArgumentException("pubsub client cannot be null");
|
||||
}
|
||||
|
||||
this._pubSubClient = client;
|
||||
this._pubSubClient.setSubscriber(this);
|
||||
this._subscribers = new ConcurrentHashMap<String, Set<Subscriber>>();
|
||||
|
||||
this._threadPool = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ListenableFuture<Boolean> publish(String channel, String message)
|
||||
{
|
||||
if (channel == null || channel.isEmpty())
|
||||
{
|
||||
throw new IllegalArgumentException("channel cannot be null or empty");
|
||||
}
|
||||
|
||||
// messages of 0 length are allowed. Null messages are treated as messages of 0 length.
|
||||
if (message == null)
|
||||
{
|
||||
message = "";
|
||||
}
|
||||
|
||||
return _pubSubClient.publish(channel, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ListenableFuture<Boolean> subscribe(String channel, Subscriber sub)
|
||||
{
|
||||
if (channel == null || channel.isEmpty() || sub == null)
|
||||
{
|
||||
throw new IllegalArgumentException("params cannot be null and channel cannot be empty");
|
||||
}
|
||||
|
||||
ListenableFuture<Boolean> ret = null;
|
||||
|
||||
Set<Subscriber> channelSubs = _subscribers.get(channel);
|
||||
if (channelSubs == null)
|
||||
{
|
||||
// uses CopyOnWriteArraySet for fast and consistent iteration (forwarding messages to
|
||||
// subscribers) but slow writes (adding/removing subscribers).
|
||||
// See a discussion of the issue here:
|
||||
// http://stackoverflow.com/questions/6720396/different-types-of-thread-safe-sets-in-java
|
||||
channelSubs = new CopyOnWriteArraySet<Subscriber>();
|
||||
_subscribers.put(channel, channelSubs);
|
||||
|
||||
// starts a jedis subscription to the channel if there were no subscribers before
|
||||
ret = _pubSubClient.addChannel(channel);
|
||||
|
||||
} else
|
||||
{
|
||||
ret = SettableFuture.create();
|
||||
((SettableFuture<Boolean>) ret).set(true); // already subscribed, calls back immediately
|
||||
}
|
||||
|
||||
channelSubs.add(sub);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void unsubscribe(String channel, Subscriber sub)
|
||||
{
|
||||
if (channel == null || channel.isEmpty() || sub == null)
|
||||
{
|
||||
throw new IllegalArgumentException("params cannot be null and channel cannot be empty");
|
||||
}
|
||||
|
||||
Set<Subscriber> channelSubs = _subscribers.get(channel);
|
||||
if (channelSubs == null)
|
||||
{ // no subscribers for the channel to begin with.
|
||||
return;
|
||||
}
|
||||
|
||||
channelSubs.remove(sub);
|
||||
|
||||
// stops the subscription to this channel if the unsubscribed was the last subscriber
|
||||
if (channelSubs.isEmpty())
|
||||
{
|
||||
_subscribers.remove(channel);
|
||||
_pubSubClient.removeChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onMessage(final String channel, final String message)
|
||||
{
|
||||
if (channel == null || message == null || channel.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Set<Subscriber> channelSubs = _subscribers.get(channel);
|
||||
|
||||
if (channelSubs == null)
|
||||
{ // We should not still be listening
|
||||
_pubSubClient.removeChannel(channel);
|
||||
return;
|
||||
} else if (channelSubs.isEmpty())
|
||||
{
|
||||
_subscribers.remove(channel);
|
||||
_pubSubClient.removeChannel(channel);
|
||||
return;
|
||||
}
|
||||
|
||||
for (final Subscriber sub : channelSubs)
|
||||
{
|
||||
|
||||
// Gives subscribers their own thread from the thread pool in which to react to the
|
||||
// message.
|
||||
// Avoids interruptions and other problems while iterating over the subscriber set.
|
||||
_threadPool.execute(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
sub.onMessage(channel, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown()
|
||||
{
|
||||
_pubSubClient.destroy();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package mineplex.serverdata.redis.messaging;
|
||||
|
||||
/**
|
||||
* A subscriber to a pub/sub channel.
|
||||
*/
|
||||
public interface Subscriber
|
||||
{
|
||||
|
||||
/**
|
||||
* Called when a message is sent on a channel that this is subscribed to.
|
||||
* <p>
|
||||
* No guarantees are made about what thread this will be called from.
|
||||
*
|
||||
* @param channel The channel that the message was sent on.
|
||||
* @param message The message that was received.
|
||||
*/
|
||||
void onMessage(String channel, String message);
|
||||
|
||||
}
|
@ -394,4 +394,9 @@ public class Arcade extends JavaPlugin
|
||||
config.GameModeMods.get(mode).put(var, value);
|
||||
}
|
||||
}
|
||||
|
||||
public ServerConfiguration getServerConfig()
|
||||
{
|
||||
return _serverConfiguration;
|
||||
}
|
||||
}
|
||||
|
@ -94,6 +94,7 @@ import mineplex.core.preferences.PreferencesManager;
|
||||
import mineplex.core.progression.KitProgressionManager;
|
||||
import mineplex.core.projectile.ProjectileManager;
|
||||
import mineplex.core.punish.Punish;
|
||||
import mineplex.core.quests.QuestManager;
|
||||
import mineplex.core.rankGiveaway.eternal.EternalGiveawayManager;
|
||||
import mineplex.core.rankGiveaway.titangiveaway.TitanGiveawayManager;
|
||||
import mineplex.core.resourcepack.ResourcePackManager;
|
||||
@ -233,8 +234,8 @@ public class ArcadeManager extends MiniPlugin implements IRelation
|
||||
private ScoreboardManager _scoreboardManager;
|
||||
private NextBestGameManager _nextBestGameManager;
|
||||
private TrackManager _trackManager;
|
||||
private QuestManager _questManager;
|
||||
private GoogleSheetsManager _sheetsManager;
|
||||
|
||||
private IncognitoManager _incognitoManager;
|
||||
|
||||
private TaskManager _taskManager;
|
||||
@ -390,6 +391,7 @@ public class ArcadeManager extends MiniPlugin implements IRelation
|
||||
_kitProgressionManager = new KitProgressionManager(getPlugin(), donationManager, clientManager);
|
||||
_progressionKitManager = new ProgressingKitManager(this);
|
||||
_serverUptimeManager = new ServerUptimeManager(this);
|
||||
_questManager = new QuestManager(hologramManager, _gameLobbyManager.getMissions(), _inventoryManager, _donationManager);
|
||||
|
||||
if (GetHost() != null && !GetHost().isEmpty() && !GetHost().startsWith("COM-"))
|
||||
{
|
||||
@ -2067,16 +2069,21 @@ public class ArcadeManager extends MiniPlugin implements IRelation
|
||||
|
||||
public TrackManager getTrackManager()
|
||||
{
|
||||
return this._trackManager;
|
||||
return _trackManager;
|
||||
}
|
||||
|
||||
public Titles getTitles()
|
||||
{
|
||||
return this._titles;
|
||||
return _titles;
|
||||
}
|
||||
|
||||
public QuestManager getQuestManager()
|
||||
{
|
||||
return _questManager;
|
||||
}
|
||||
|
||||
public GoogleSheetsManager getSheetsManager()
|
||||
{
|
||||
return _sheetsManager;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package nautilus.game.arcade.events;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* ChestRefillEvent
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class ChestRefillEvent extends Event implements Cancellable
|
||||
{
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
private boolean _cancelled = false;
|
||||
|
||||
private ArrayList<Location> _chests;
|
||||
|
||||
public ChestRefillEvent(ArrayList<Location> chests)
|
||||
{
|
||||
_chests = (ArrayList<Location>) chests.clone();
|
||||
}
|
||||
|
||||
public ArrayList<Location> getChests()
|
||||
{
|
||||
return _chests;
|
||||
}
|
||||
|
||||
public HandlerList getHandlers()
|
||||
{
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList()
|
||||
{
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return _cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled)
|
||||
{
|
||||
_cancelled = cancelled;
|
||||
}
|
||||
|
||||
}
|
@ -18,6 +18,8 @@ import mineplex.core.lifetimes.ListenerComponent;
|
||||
import mineplex.core.lifetimes.PhasedLifetime;
|
||||
import mineplex.core.packethandler.IPacketHandler;
|
||||
import mineplex.core.packethandler.PacketInfo;
|
||||
import mineplex.core.quests.QuestManager;
|
||||
import mineplex.core.quests.event.QuestInteractEvent;
|
||||
import mineplex.core.updater.UpdateType;
|
||||
import mineplex.core.updater.event.UpdateEvent;
|
||||
import mineplex.core.utils.UtilGameProfile;
|
||||
@ -39,6 +41,14 @@ import nautilus.game.arcade.game.modules.Module;
|
||||
import nautilus.game.arcade.kit.*;
|
||||
import nautilus.game.arcade.managers.chat.ChatStatData;
|
||||
import nautilus.game.arcade.managers.lobby.LobbyManager;
|
||||
import nautilus.game.arcade.quest.ChestOpenQuestTracker;
|
||||
import nautilus.game.arcade.quest.CollectQuestTracker;
|
||||
import nautilus.game.arcade.quest.KillEntityQuestTracker;
|
||||
import nautilus.game.arcade.quest.KillQuestTracker;
|
||||
import nautilus.game.arcade.quest.ParticipateQuestTracker;
|
||||
import nautilus.game.arcade.quest.PlayGameQuestTracker;
|
||||
import nautilus.game.arcade.quest.QuestTracker;
|
||||
import nautilus.game.arcade.quest.WinQuestTracker;
|
||||
import nautilus.game.arcade.scoreboard.GameScoreboard;
|
||||
import nautilus.game.arcade.stats.*;
|
||||
import nautilus.game.arcade.wineffect.WinEffectManager;
|
||||
@ -341,6 +351,7 @@ public abstract class Game extends ListenerComponent implements Lifetimed
|
||||
private NautHashMap<String, Long> _deadBodiesExpire = new NautHashMap<String, Long>();
|
||||
|
||||
private final Set<StatTracker<? extends Game>> _statTrackers = new HashSet<>();
|
||||
private final Set<QuestTracker<? extends Game>> _questTrackers = new HashSet<>();
|
||||
|
||||
private NautHashMap<Player, Player> _teamReqs = new NautHashMap<Player, Player>();
|
||||
public WinEffectManager WinEffectManager = new WinEffectManager();
|
||||
@ -413,6 +424,16 @@ public abstract class Game extends ListenerComponent implements Lifetimed
|
||||
registerStatTrackers(new KillsStatTracker(this), new DeathsStatTracker(this), new AssistsStatTracker(this),
|
||||
new ExperienceStatTracker(this), new WinStatTracker(this), new LoseStatTracker(this), new DamageDealtStatTracker(
|
||||
this), new DamageTakenStatTracker(this), new GamesPlayedStatTracker(this));
|
||||
|
||||
// Quest Trackers
|
||||
registerQuestTrackers(
|
||||
new WinQuestTracker(this),
|
||||
new KillQuestTracker(this),
|
||||
new CollectQuestTracker(this),
|
||||
new ChestOpenQuestTracker(this),
|
||||
new KillEntityQuestTracker(this),
|
||||
new PlayGameQuestTracker(this),
|
||||
new ParticipateQuestTracker(this));
|
||||
|
||||
Manager.getResourcePackManager().setResourcePack(gameType.getResourcePackUrls(this), gameType.isEnforceResourcePack(this));
|
||||
|
||||
@ -1736,6 +1757,20 @@ public abstract class Game extends ListenerComponent implements Lifetimed
|
||||
{
|
||||
return _statTrackers;
|
||||
}
|
||||
|
||||
public void registerQuestTrackers(QuestTracker<? extends Game>... questTrackers)
|
||||
{
|
||||
for (QuestTracker<? extends Game> tracker : questTrackers)
|
||||
{
|
||||
if (_questTrackers.add(tracker))
|
||||
Bukkit.getPluginManager().registerEvents(tracker, Manager.getPlugin());
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<QuestTracker<? extends Game>> getQuestTrackers()
|
||||
{
|
||||
return _questTrackers;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onHangingBreak(HangingBreakEvent event)
|
||||
@ -2010,6 +2045,13 @@ public abstract class Game extends ListenerComponent implements Lifetimed
|
||||
((CraftEntity) event.getEntity()).getHandle().dead = false;
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onQuestBuy(QuestInteractEvent event)
|
||||
{
|
||||
if (GetState() == GameState.Live || GetState() == GameState.Prepare || GetState() == GameState.End)
|
||||
event.setCancelled("You cant interact with " + QuestManager.QUEST_NAME + "s while you are ingame!");
|
||||
}
|
||||
|
||||
public NautHashMap<String, Entity> getDeadBodies()
|
||||
{
|
||||
@ -2323,6 +2365,7 @@ public abstract class Game extends ListenerComponent implements Lifetimed
|
||||
Managers.get(AntiHack.class).resetIgnoredChecks();
|
||||
getLifetime().end();
|
||||
getStatTrackers().forEach(HandlerList::unregisterAll);
|
||||
getQuestTrackers().forEach(HandlerList::unregisterAll);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
|
@ -14,6 +14,7 @@ import nautilus.game.arcade.game.games.champions.kits.KitBrute;
|
||||
import nautilus.game.arcade.game.games.champions.kits.KitKnight;
|
||||
import nautilus.game.arcade.game.games.champions.kits.KitMage;
|
||||
import nautilus.game.arcade.game.games.champions.kits.KitRanger;
|
||||
import nautilus.game.arcade.game.games.champions.quests.FlagQuestTracker;
|
||||
import nautilus.game.arcade.game.games.common.CaptureTheFlag;
|
||||
import nautilus.game.arcade.kit.Kit;
|
||||
import nautilus.game.arcade.managers.chat.ChatStatData;
|
||||
@ -83,6 +84,8 @@ public class ChampionsCTF extends CaptureTheFlag
|
||||
new ClutchStatTracker(this, "Clutch"),
|
||||
new SpecialWinStatTracker(this, "SpecialWin")
|
||||
);
|
||||
|
||||
registerQuestTrackers(new FlagQuestTracker(this));
|
||||
|
||||
registerChatStats(
|
||||
Kills,
|
||||
|
@ -14,6 +14,7 @@ import nautilus.game.arcade.game.games.champions.kits.KitBrute;
|
||||
import nautilus.game.arcade.game.games.champions.kits.KitKnight;
|
||||
import nautilus.game.arcade.game.games.champions.kits.KitMage;
|
||||
import nautilus.game.arcade.game.games.champions.kits.KitRanger;
|
||||
import nautilus.game.arcade.game.games.champions.quests.CaptureQuestTracker;
|
||||
import nautilus.game.arcade.game.games.common.Domination;
|
||||
import nautilus.game.arcade.kit.Kit;
|
||||
import nautilus.game.arcade.stats.ElectrocutionStatTracker;
|
||||
@ -77,6 +78,8 @@ public class ChampionsDominate extends Domination
|
||||
new TheLongestShotStatTracker(this),
|
||||
new SeismicSlamStatTracker(this)
|
||||
);
|
||||
|
||||
registerQuestTrackers(new CaptureQuestTracker(this));
|
||||
|
||||
registerChatStats(
|
||||
Kills,
|
||||
|
@ -0,0 +1,41 @@
|
||||
package nautilus.game.arcade.game.games.champions.events;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* CaptureEvent
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class CaptureEvent extends Event
|
||||
{
|
||||
private Collection<Player> _players;
|
||||
|
||||
public CaptureEvent(Collection<Player> players)
|
||||
{
|
||||
_players = players;
|
||||
}
|
||||
|
||||
public Collection<Player> getPlayers()
|
||||
{
|
||||
return _players;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers()
|
||||
{
|
||||
return getHandlerList();
|
||||
}
|
||||
|
||||
private static HandlerList _handlers = new HandlerList();
|
||||
|
||||
public static HandlerList getHandlerList()
|
||||
{
|
||||
return _handlers;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package nautilus.game.arcade.game.games.champions.quests;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
|
||||
import mineplex.core.quests.TriggerType;
|
||||
|
||||
import nautilus.game.arcade.game.games.champions.ChampionsDominate;
|
||||
import nautilus.game.arcade.game.games.champions.events.CaptureEvent;
|
||||
import nautilus.game.arcade.quest.QuestTracker;
|
||||
|
||||
/**
|
||||
* PointQuestTracker
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class CaptureQuestTracker extends QuestTracker<ChampionsDominate>
|
||||
{
|
||||
|
||||
public CaptureQuestTracker(ChampionsDominate game)
|
||||
{
|
||||
super(game, TriggerType.COLLECT);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void capture(CaptureEvent event)
|
||||
{
|
||||
for (Player player : event.getPlayers())
|
||||
incrementQuests(player, 1, "Point Capture");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package nautilus.game.arcade.game.games.champions.quests;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
|
||||
import mineplex.core.quests.TriggerType;
|
||||
|
||||
import nautilus.game.arcade.game.games.common.CaptureTheFlag;
|
||||
import nautilus.game.arcade.game.games.common.ctf_data.PlayerCaptureFlagEvent;
|
||||
import nautilus.game.arcade.quest.QuestTracker;
|
||||
|
||||
/**
|
||||
* FlagQuestTracker
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class FlagQuestTracker extends QuestTracker<CaptureTheFlag>
|
||||
{
|
||||
|
||||
public FlagQuestTracker(CaptureTheFlag game)
|
||||
{
|
||||
super(game, TriggerType.COLLECT);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void captureFlag(PlayerCaptureFlagEvent event)
|
||||
{
|
||||
incrementQuests(event.GetPlayer(), 1, "Flag Capture");
|
||||
}
|
||||
|
||||
}
|
@ -7,9 +7,11 @@ import mineplex.core.common.util.C;
|
||||
import mineplex.core.common.util.UtilFirework;
|
||||
import mineplex.core.common.util.UtilMath;
|
||||
import mineplex.core.common.util.UtilPlayer;
|
||||
import mineplex.core.common.util.UtilServer;
|
||||
import mineplex.core.common.util.UtilTime;
|
||||
import mineplex.core.common.util.UtilTextMiddle;
|
||||
import nautilus.game.arcade.game.GameTeam;
|
||||
import nautilus.game.arcade.game.games.champions.events.CaptureEvent;
|
||||
import nautilus.game.arcade.game.games.common.Domination;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
@ -303,6 +305,8 @@ public class CapturePoint
|
||||
}
|
||||
|
||||
UtilTextMiddle.display(null, _owner.GetColor() + _owner.GetName() + " captured " + _name, 5, 40, 5);
|
||||
|
||||
UtilServer.CallEvent(new CaptureEvent(capturers));
|
||||
}
|
||||
}
|
||||
//Count Down
|
||||
|
@ -52,6 +52,7 @@ import nautilus.game.arcade.game.GameTeam;
|
||||
import nautilus.game.arcade.game.SoloGame;
|
||||
import nautilus.game.arcade.game.games.GameScore;
|
||||
import nautilus.game.arcade.game.games.draw.kits.KitArtist;
|
||||
import nautilus.game.arcade.game.games.draw.quests.GuessQuestTracker;
|
||||
import nautilus.game.arcade.game.games.draw.tools.Tool;
|
||||
import nautilus.game.arcade.game.games.draw.tools.ToolCircle;
|
||||
import nautilus.game.arcade.game.games.draw.tools.ToolLine;
|
||||
@ -254,6 +255,8 @@ public class Draw extends SoloGame
|
||||
new DrawGuessStatTracker(this)
|
||||
);
|
||||
|
||||
registerQuestTrackers(new GuessQuestTracker(this));
|
||||
|
||||
registerChatStats(
|
||||
new ChatStatData("TotalGuess", "Total Guesses", true),
|
||||
new ChatStatData("PureLuck", "Lucky Guesses", true)
|
||||
|
@ -0,0 +1,36 @@
|
||||
package nautilus.game.arcade.game.games.draw.quests;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
|
||||
import mineplex.core.quests.TriggerType;
|
||||
|
||||
import nautilus.game.arcade.game.Game.GameState;
|
||||
import nautilus.game.arcade.game.games.draw.Draw;
|
||||
import nautilus.game.arcade.game.games.draw.DrawGuessCorrectlyEvent;
|
||||
import nautilus.game.arcade.quest.QuestTracker;
|
||||
|
||||
/**
|
||||
* GuesQuestTracker
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class GuessQuestTracker extends QuestTracker<Draw>
|
||||
{
|
||||
|
||||
public GuessQuestTracker(Draw game)
|
||||
{
|
||||
super(game, TriggerType.COLLECT);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onDrawGuessCorrectly(DrawGuessCorrectlyEvent event)
|
||||
{
|
||||
if (getGame().GetState() != GameState.Live)
|
||||
return;
|
||||
|
||||
if (System.currentTimeMillis() - event.getDrawRound().Time < 8000)
|
||||
incrementQuests(event.getPlayer(), 1, "Guesses");
|
||||
}
|
||||
|
||||
}
|
@ -46,6 +46,7 @@ import nautilus.game.arcade.game.games.hideseek.kits.KitHiderSwapper;
|
||||
import nautilus.game.arcade.game.games.hideseek.kits.KitSeekerLeaper;
|
||||
import nautilus.game.arcade.game.games.hideseek.kits.KitSeekerRadar;
|
||||
import nautilus.game.arcade.game.games.hideseek.kits.KitSeekerTNT;
|
||||
import nautilus.game.arcade.game.games.hideseek.quests.DisguiseQuestTracker;
|
||||
import nautilus.game.arcade.game.modules.compass.CompassModule;
|
||||
import nautilus.game.arcade.kit.Kit;
|
||||
import nautilus.game.arcade.kit.NullKit;
|
||||
@ -348,6 +349,8 @@ public class HideSeek extends TeamGame
|
||||
new HunterOfTheYearStatTracker(this),
|
||||
new BadHiderStatTracker(this)
|
||||
);
|
||||
|
||||
registerQuestTrackers(new DisguiseQuestTracker(this));
|
||||
|
||||
//Need ideas for this one
|
||||
registerChatStats();
|
||||
|
@ -0,0 +1,34 @@
|
||||
package nautilus.game.arcade.game.games.hideseek.quests;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
|
||||
import mineplex.core.quests.TriggerType;
|
||||
|
||||
import nautilus.game.arcade.game.Game;
|
||||
import nautilus.game.arcade.game.games.hideseek.HideSeek;
|
||||
import nautilus.game.arcade.quest.QuestTracker;
|
||||
|
||||
/**
|
||||
* DisguiseQuestTracker
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class DisguiseQuestTracker extends QuestTracker<HideSeek>
|
||||
{
|
||||
|
||||
public DisguiseQuestTracker(HideSeek game)
|
||||
{
|
||||
super(game, TriggerType.COLLECT);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onChangeForm(HideSeek.PlayerChangeFormEvent event)
|
||||
{
|
||||
if (getGame().GetState() != Game.GameState.Live)
|
||||
return;
|
||||
|
||||
incrementQuests(event.getPlayer(), 1, "Disguise");
|
||||
}
|
||||
|
||||
}
|
@ -75,6 +75,7 @@ import mineplex.minecraft.game.core.damage.CustomDamageEvent;
|
||||
|
||||
import nautilus.game.arcade.ArcadeManager;
|
||||
import nautilus.game.arcade.GameType;
|
||||
import nautilus.game.arcade.events.ChestRefillEvent;
|
||||
import nautilus.game.arcade.events.GameStateChangeEvent;
|
||||
import nautilus.game.arcade.game.Game;
|
||||
import nautilus.game.arcade.game.games.skyfall.kits.KitAeronaught;
|
||||
@ -83,6 +84,7 @@ import nautilus.game.arcade.game.games.skyfall.kits.KitDeadeye;
|
||||
import nautilus.game.arcade.game.games.skyfall.kits.KitJouster;
|
||||
import nautilus.game.arcade.game.games.skyfall.kits.KitSpeeder;
|
||||
import nautilus.game.arcade.game.games.skyfall.kits.KitStunner;
|
||||
import nautilus.game.arcade.game.games.skyfall.quests.RingQuestTracker;
|
||||
import nautilus.game.arcade.game.games.skyfall.stats.AeronaughtStatTracker;
|
||||
import nautilus.game.arcade.game.games.skyfall.stats.RingStatTracker;
|
||||
import nautilus.game.arcade.game.games.survivalgames.SupplyChestOpenEvent;
|
||||
@ -193,6 +195,8 @@ public abstract class Skyfall extends Game
|
||||
new RingStatTracker(this),
|
||||
new AeronaughtStatTracker(this));
|
||||
|
||||
registerQuestTrackers(new RingQuestTracker(this));
|
||||
|
||||
registerChatStats(
|
||||
Kills,
|
||||
Assists,
|
||||
@ -328,8 +332,12 @@ public abstract class Skyfall extends Game
|
||||
_chestsRefilled = System.currentTimeMillis();
|
||||
_refillAnnounced = false;
|
||||
|
||||
_lowerIsland.refillChests();
|
||||
ChestRefillEvent refillEvent = UtilServer.CallEvent(new ChestRefillEvent(_lowerIsland.getChests()));
|
||||
if (refillEvent.isCancelled())
|
||||
return;
|
||||
|
||||
_lowerIsland.refillChests();
|
||||
|
||||
Announce(ChatColor.AQUA + "" + ChatColor.BOLD + "Chests on the lower middle Island have been refilled!", true);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,37 @@
|
||||
package nautilus.game.arcade.game.games.skyfall.quests;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
|
||||
import mineplex.core.quests.TriggerType;
|
||||
|
||||
import nautilus.game.arcade.game.games.skyfall.PlayerBoostRingEvent;
|
||||
import nautilus.game.arcade.game.games.skyfall.Skyfall;
|
||||
import nautilus.game.arcade.quest.QuestTracker;
|
||||
|
||||
/**
|
||||
* BoosterRingQuestTracker
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class RingQuestTracker extends QuestTracker<Skyfall>
|
||||
{
|
||||
|
||||
public RingQuestTracker(Skyfall game)
|
||||
{
|
||||
super(game, TriggerType.COLLECT);
|
||||
}
|
||||
|
||||
@EventHandler(priority=EventPriority.MONITOR)
|
||||
public void boosterRing(PlayerBoostRingEvent event)
|
||||
{
|
||||
if (event.isCancelled())
|
||||
return;
|
||||
|
||||
if (!getGame().IsAlive(event.getPlayer()))
|
||||
return;
|
||||
|
||||
incrementQuests(event.getPlayer(), 1, "Booster Rings", getGame().GetKit(event.getPlayer()).GetName() + "Kit");
|
||||
}
|
||||
|
||||
}
|
@ -32,12 +32,14 @@ import nautilus.game.arcade.events.GameStateChangeEvent;
|
||||
import nautilus.game.arcade.game.GameTeam;
|
||||
import nautilus.game.arcade.game.GameTeam.PlayerState;
|
||||
import nautilus.game.arcade.game.SoloGame;
|
||||
import nautilus.game.arcade.game.games.skyfall.quests.RingQuestTracker;
|
||||
import nautilus.game.arcade.game.games.speedbuilders.data.BuildData;
|
||||
import nautilus.game.arcade.game.games.speedbuilders.data.DemolitionData;
|
||||
import nautilus.game.arcade.game.games.speedbuilders.data.MobData;
|
||||
import nautilus.game.arcade.game.games.speedbuilders.data.RecreationData;
|
||||
import nautilus.game.arcade.game.games.speedbuilders.events.PerfectBuildEvent;
|
||||
import nautilus.game.arcade.game.games.speedbuilders.kits.DefaultKit;
|
||||
import nautilus.game.arcade.game.games.speedbuilders.quests.PerfectBuildQuestTracker;
|
||||
import nautilus.game.arcade.game.games.speedbuilders.stattrackers.DependableTracker;
|
||||
import nautilus.game.arcade.game.games.speedbuilders.stattrackers.FirstBuildTracker;
|
||||
import nautilus.game.arcade.game.games.speedbuilders.stattrackers.PerfectionistTracker;
|
||||
@ -190,6 +192,8 @@ public class SpeedBuilders extends SoloGame
|
||||
new SpeediestBuilderizerTracker(this),
|
||||
new BlockPlaceStatTracker(this, new Material[] {})
|
||||
);
|
||||
|
||||
registerQuestTrackers(new PerfectBuildQuestTracker(this));
|
||||
|
||||
registerChatStats(
|
||||
new ChatStatData("BlocksPlaced", "Blocks Placed", true),
|
||||
|
@ -0,0 +1,29 @@
|
||||
package nautilus.game.arcade.game.games.speedbuilders.quests;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
|
||||
import mineplex.core.quests.TriggerType;
|
||||
|
||||
import nautilus.game.arcade.game.games.speedbuilders.SpeedBuilders;
|
||||
import nautilus.game.arcade.game.games.speedbuilders.events.PerfectBuildEvent;
|
||||
import nautilus.game.arcade.quest.QuestTracker;
|
||||
|
||||
/**
|
||||
* PerfectBuildQuestTracker
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class PerfectBuildQuestTracker extends QuestTracker<SpeedBuilders>
|
||||
{
|
||||
|
||||
public PerfectBuildQuestTracker(SpeedBuilders game)
|
||||
{
|
||||
super(game, TriggerType.COLLECT);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPerfectBuild(PerfectBuildEvent event)
|
||||
{
|
||||
incrementQuests(event.getPlayer(), 1, "Perfect Build");
|
||||
}
|
||||
}
|
@ -112,6 +112,7 @@ import mineplex.minecraft.game.core.damage.CustomDamageEvent;
|
||||
|
||||
import nautilus.game.arcade.ArcadeManager;
|
||||
import nautilus.game.arcade.GameType;
|
||||
import nautilus.game.arcade.events.ChestRefillEvent;
|
||||
import nautilus.game.arcade.events.GameStateChangeEvent;
|
||||
import nautilus.game.arcade.game.Game;
|
||||
import nautilus.game.arcade.game.GameTeam;
|
||||
@ -1485,7 +1486,11 @@ public abstract class SurvivalGames extends Game
|
||||
public void refillChests()
|
||||
{
|
||||
ArrayList<Location> list = new ArrayList<Location>(_lootedBlocks);
|
||||
|
||||
|
||||
ChestRefillEvent event = UtilServer.CallEvent(new ChestRefillEvent(list));
|
||||
if (event.isCancelled())
|
||||
return;
|
||||
|
||||
_lootedBlocks.clear();
|
||||
|
||||
WorldServer world = list.isEmpty() ? null : ((CraftWorld) list.get(0)
|
||||
|
@ -103,6 +103,7 @@ import nautilus.game.arcade.game.games.AbsorptionFix;
|
||||
import nautilus.game.arcade.game.games.uhc.components.UHCBorder;
|
||||
import nautilus.game.arcade.game.games.uhc.components.UHCFreezer;
|
||||
import nautilus.game.arcade.game.games.uhc.components.UHCSpeedMode;
|
||||
import nautilus.game.arcade.game.games.uhc.quests.TameQuestTracker;
|
||||
import nautilus.game.arcade.game.games.uhc.stat.CollectFoodStat;
|
||||
import nautilus.game.arcade.game.games.uhc.stat.HalfHeartHealStat;
|
||||
import nautilus.game.arcade.game.games.uhc.stat.HoeCraftingStat;
|
||||
@ -294,6 +295,8 @@ public abstract class UHC extends Game
|
||||
|
||||
registerStatTrackers(new CollectFoodStat(this), new HoeCraftingStat(this), new LuckyMinerStat(this), new HalfHeartHealStat(this));
|
||||
|
||||
registerQuestTrackers(new TameQuestTracker(this));
|
||||
|
||||
registerDebugCommand(new DebugCommand("startpvp", Rank.ADMIN)
|
||||
{
|
||||
@Override
|
||||
|
@ -0,0 +1,38 @@
|
||||
package nautilus.game.arcade.game.games.uhc.quests;
|
||||
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.entity.EntityTameEvent;
|
||||
|
||||
import mineplex.core.quests.TriggerType;
|
||||
|
||||
import nautilus.game.arcade.game.games.uhc.UHC;
|
||||
import nautilus.game.arcade.quest.QuestTracker;
|
||||
|
||||
/**
|
||||
* TameQuestTracker
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class TameQuestTracker extends QuestTracker<UHC>
|
||||
{
|
||||
|
||||
public TameQuestTracker(UHC game)
|
||||
{
|
||||
super(game, TriggerType.COLLECT);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void tameEvent(EntityTameEvent event)
|
||||
{
|
||||
if (!getGame().IsLive())
|
||||
return;
|
||||
|
||||
if (event.getEntityType() != EntityType.HORSE)
|
||||
return;
|
||||
|
||||
incrementQuests((Player) event.getOwner(), 1, "Tamed Horse");
|
||||
}
|
||||
|
||||
}
|
@ -83,12 +83,13 @@ public abstract class LobbyManager implements Listener
|
||||
private Location _kitText;
|
||||
private Location _teamText;
|
||||
private Location _carl;
|
||||
private Location _missions;
|
||||
private Location _spawn;
|
||||
private Location _ampStand;
|
||||
private boolean _colorTick = false;
|
||||
private boolean _generatePodiums = false;
|
||||
|
||||
public LobbyManager(ArcadeManager manager, Location carl, Location spawn, Location ampStand)
|
||||
public LobbyManager(ArcadeManager manager, Location missions, Location carl, Location spawn, Location ampStand)
|
||||
{
|
||||
_manager = manager;
|
||||
|
||||
@ -892,4 +893,14 @@ public abstract class LobbyManager implements Listener
|
||||
{
|
||||
return _generatePodiums;
|
||||
}
|
||||
|
||||
public Location getMissions()
|
||||
{
|
||||
return _missions;
|
||||
}
|
||||
|
||||
public void setMissions(Location loc)
|
||||
{
|
||||
_missions = loc;
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ public class NewGameLobbyManager extends LobbyManager
|
||||
public enum DataLoc
|
||||
{
|
||||
CARL,
|
||||
MISSIONS,
|
||||
AMP,
|
||||
SPAWN,;
|
||||
}
|
||||
@ -70,7 +71,7 @@ public class NewGameLobbyManager extends LobbyManager
|
||||
|
||||
public NewGameLobbyManager(ArcadeManager manager)
|
||||
{
|
||||
super(manager, null, null, null);
|
||||
super(manager, null, null, null, null);
|
||||
_run = CONFIG.exists();
|
||||
|
||||
if (_run)
|
||||
@ -519,7 +520,7 @@ public class NewGameLobbyManager extends LobbyManager
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!lastName.equalsIgnoreCase("SPAWN") || lastName.equalsIgnoreCase("CARL"))
|
||||
if (!lastName.equalsIgnoreCase("SPAWN") || lastName.equalsIgnoreCase("CARL") || lastName.equalsIgnoreCase("MISSIONS"))
|
||||
{
|
||||
loc.subtract(0, 0.5, 0);
|
||||
}
|
||||
@ -557,6 +558,16 @@ public class NewGameLobbyManager extends LobbyManager
|
||||
|
||||
setCarl(carl);
|
||||
}
|
||||
|
||||
Location missions = _singleLocs.get(DataLoc.MISSIONS.name());
|
||||
if (missions != null)
|
||||
{
|
||||
missions.add(0, 0.5, 0);
|
||||
float yaw = UtilAlg.GetYaw(UtilAlg.getTrajectory2d(missions, getSpawn()));
|
||||
missions.setYaw(yaw);
|
||||
|
||||
setMissions(missions);
|
||||
}
|
||||
|
||||
Location amp = _singleLocs.get(DataLoc.AMP.name());
|
||||
if (amp != null)
|
||||
|
@ -39,7 +39,7 @@ public class LegacyGameLobbyManager extends LobbyManager
|
||||
|
||||
public LegacyGameLobbyManager(ArcadeManager manager)
|
||||
{
|
||||
super(manager, null, null, new Location(UtilWorld.getWorld("world"), 0, 102.5, -15));
|
||||
super(manager, null, null, null, new Location(UtilWorld.getWorld("world"), 0, 102.5, -15));
|
||||
|
||||
setSpawn(new Location(WORLD, 0, 104, 0, 0, 0));
|
||||
|
||||
|
@ -0,0 +1,57 @@
|
||||
package nautilus.game.arcade.quest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
|
||||
import mineplex.core.quests.TriggerType;
|
||||
|
||||
import nautilus.game.arcade.game.Game;
|
||||
|
||||
/**
|
||||
* ChestOpenQuestTracker
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class ChestOpenQuestTracker extends QuestTracker<Game>
|
||||
{
|
||||
private HashMap<Player, ArrayList<Location>> _usedChests = new HashMap<>();
|
||||
|
||||
public ChestOpenQuestTracker(Game game)
|
||||
{
|
||||
super(game, TriggerType.COLLECT);
|
||||
}
|
||||
|
||||
@EventHandler(priority=EventPriority.HIGHEST)
|
||||
public void chestRegister(PlayerInteractEvent event)
|
||||
{
|
||||
if (!getGame().IsLive())
|
||||
return;
|
||||
|
||||
if (event.getAction() != Action.RIGHT_CLICK_BLOCK)
|
||||
return;
|
||||
|
||||
if (event.getClickedBlock().getType() != Material.CHEST)
|
||||
return;
|
||||
|
||||
if (!_usedChests.containsKey(event.getPlayer()))
|
||||
_usedChests.put(event.getPlayer(), new ArrayList<>());
|
||||
|
||||
ArrayList<Location> locs = _usedChests.get(event.getPlayer());
|
||||
|
||||
if (locs.contains(event.getClickedBlock().getLocation()))
|
||||
return;
|
||||
|
||||
locs.add(event.getClickedBlock().getLocation());
|
||||
|
||||
incrementQuests((Player) event.getPlayer(), 1, "Chest");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
package nautilus.game.arcade.quest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Chest;
|
||||
import org.bukkit.block.DoubleChest;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
|
||||
import mineplex.core.quests.TriggerType;
|
||||
|
||||
import nautilus.game.arcade.events.ChestRefillEvent;
|
||||
import nautilus.game.arcade.game.Game;
|
||||
|
||||
/**
|
||||
* CollectQuestTracker
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class CollectQuestTracker extends QuestTracker<Game>
|
||||
{
|
||||
private ArrayList<ItemStack> _itemsAvailable = new ArrayList<>();
|
||||
private ArrayList<Location> _usedChests = new ArrayList<>();
|
||||
|
||||
public CollectQuestTracker(Game game)
|
||||
{
|
||||
super(game, TriggerType.COLLECT);
|
||||
}
|
||||
|
||||
@EventHandler(priority=EventPriority.LOWEST)
|
||||
public void chestRefill(ChestRefillEvent event)
|
||||
{
|
||||
if (event.isCancelled())
|
||||
return;
|
||||
|
||||
_usedChests.clear();
|
||||
}
|
||||
|
||||
@EventHandler(priority=EventPriority.HIGHEST)
|
||||
public void chestRegister(PlayerInteractEvent event)
|
||||
{
|
||||
if (!getGame().IsLive())
|
||||
return;
|
||||
|
||||
if (event.getAction() != Action.RIGHT_CLICK_BLOCK)
|
||||
return;
|
||||
|
||||
if (event.getClickedBlock().getType() != Material.CHEST)
|
||||
return;
|
||||
|
||||
if (_usedChests.contains(event.getClickedBlock().getLocation()))
|
||||
return;
|
||||
|
||||
Chest chest = (Chest) event.getClickedBlock().getState();
|
||||
|
||||
if (chest instanceof DoubleChest)
|
||||
{
|
||||
DoubleChest doubleChest = (DoubleChest) chest;
|
||||
for (ItemStack item : doubleChest.getLeftSide().getInventory())
|
||||
{
|
||||
if (item == null)
|
||||
continue;
|
||||
|
||||
_itemsAvailable.add(item);
|
||||
}
|
||||
for (ItemStack item : doubleChest.getRightSide().getInventory())
|
||||
{
|
||||
if (item == null)
|
||||
continue;
|
||||
|
||||
_itemsAvailable.add(item);
|
||||
}
|
||||
}
|
||||
else if (chest instanceof Chest)
|
||||
{
|
||||
for (ItemStack item : chest.getInventory())
|
||||
{
|
||||
if (item == null)
|
||||
continue;
|
||||
|
||||
_itemsAvailable.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
_usedChests.add(event.getClickedBlock().getLocation());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void collectItem(InventoryClickEvent event)
|
||||
{
|
||||
if (!getGame().IsLive())
|
||||
return;
|
||||
|
||||
if (event.getClickedInventory() instanceof PlayerInventory)
|
||||
return;
|
||||
|
||||
if (!_itemsAvailable.contains(event.getCurrentItem()))
|
||||
return;
|
||||
|
||||
_itemsAvailable.remove(event.getCurrentItem());
|
||||
|
||||
String item = event.getCurrentItem().getType().toString();
|
||||
|
||||
if (event.getCurrentItem().hasItemMeta())
|
||||
item = event.getCurrentItem().getItemMeta().getDisplayName();
|
||||
|
||||
incrementQuests((Player) event.getWhoClicked(), event.getCurrentItem().getAmount(), ChatColor.stripColor(item));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package nautilus.game.arcade.quest;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.entity.EntityDeathEvent;
|
||||
|
||||
import mineplex.core.quests.TriggerType;
|
||||
|
||||
import nautilus.game.arcade.game.Game;
|
||||
|
||||
/**
|
||||
* KillEntityQuestTracker
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class KillEntityQuestTracker extends QuestTracker<Game>
|
||||
{
|
||||
|
||||
public KillEntityQuestTracker(Game game)
|
||||
{
|
||||
super(game, TriggerType.KILL);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void killEntity(EntityDeathEvent event)
|
||||
{
|
||||
if (!getGame().IsLive())
|
||||
return;
|
||||
|
||||
LivingEntity lEntity = event.getEntity();
|
||||
|
||||
if (!(lEntity.getKiller() instanceof Player))
|
||||
return;
|
||||
|
||||
Player player = lEntity.getKiller();
|
||||
|
||||
if (lEntity instanceof Player)
|
||||
return;
|
||||
|
||||
String name = lEntity.getType().getName();
|
||||
|
||||
if (lEntity.isCustomNameVisible())
|
||||
name = lEntity.getCustomName();
|
||||
|
||||
incrementQuests((Player) player, 1, ChatColor.stripColor(name), getGame().GetKit(player).GetName() + "Kit");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package nautilus.game.arcade.quest;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
|
||||
import mineplex.core.common.util.UtilPlayer;
|
||||
import mineplex.core.quests.TriggerType;
|
||||
import mineplex.minecraft.game.core.combat.event.CombatDeathEvent;
|
||||
|
||||
import nautilus.game.arcade.game.Game;
|
||||
|
||||
/**
|
||||
* WInQuestTracker
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class KillQuestTracker extends QuestTracker<Game>
|
||||
{
|
||||
|
||||
public KillQuestTracker(Game game)
|
||||
{
|
||||
super(game, TriggerType.KILL);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onKill(CombatDeathEvent event)
|
||||
{
|
||||
if (!getGame().IsLive())
|
||||
return;
|
||||
|
||||
if (event.GetLog().GetKiller() == null)
|
||||
return;
|
||||
|
||||
if (!event.GetLog().GetKiller().IsPlayer())
|
||||
return;
|
||||
|
||||
Player player = UtilPlayer.searchExact(event.GetLog().GetKiller().GetName());
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
incrementQuests(player, 1, "Player", getGame().GetKit(player).GetName() + "Kit");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package nautilus.game.arcade.quest;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
|
||||
import mineplex.core.common.util.UtilServer;
|
||||
import mineplex.core.quests.TriggerType;
|
||||
|
||||
import nautilus.game.arcade.Arcade;
|
||||
import nautilus.game.arcade.events.GameStateChangeEvent;
|
||||
import nautilus.game.arcade.game.Game;
|
||||
import nautilus.game.arcade.game.Game.GameState;
|
||||
|
||||
/**
|
||||
* ParticipateQuestTracker
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class ParticipateQuestTracker extends QuestTracker<Game>
|
||||
{
|
||||
|
||||
public ParticipateQuestTracker(Game game)
|
||||
{
|
||||
super(game, TriggerType.COMPLETE);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void completeGame(GameStateChangeEvent event)
|
||||
{
|
||||
if (event.GetState() != GameState.End)
|
||||
return;
|
||||
|
||||
for (Player player : getGame().GetPlayers(true))
|
||||
incrementQuests(player, 1, ((Arcade) UtilServer.getPlugin()).getServerConfig().getServerGroup().getPrefix(), getGame().GetKit(player).GetName() + "Kit");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package nautilus.game.arcade.quest;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
|
||||
import mineplex.core.common.util.UtilServer;
|
||||
import mineplex.core.quests.TriggerType;
|
||||
|
||||
import nautilus.game.arcade.Arcade;
|
||||
import nautilus.game.arcade.events.GameStateChangeEvent;
|
||||
import nautilus.game.arcade.game.Game;
|
||||
import nautilus.game.arcade.game.Game.GameState;
|
||||
|
||||
/**
|
||||
* PlayGameQuestTracker
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class PlayGameQuestTracker extends QuestTracker<Game>
|
||||
{
|
||||
|
||||
public PlayGameQuestTracker(Game game)
|
||||
{
|
||||
super(game, TriggerType.PLAY);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void gameStart(GameStateChangeEvent event)
|
||||
{
|
||||
if (event.GetState() != GameState.Live)
|
||||
return;
|
||||
|
||||
for (Player player : getGame().GetPlayers(true))
|
||||
incrementQuests(player, 1, ((Arcade) UtilServer.getPlugin()).getServerConfig().getServerGroup().getPrefix(), getGame().GetKit(player).GetName() + "Kit");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
package nautilus.game.arcade.quest;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
import mineplex.core.quests.Quest;
|
||||
import mineplex.core.quests.QuestClientData;
|
||||
import mineplex.core.quests.TriggerType;
|
||||
|
||||
import nautilus.game.arcade.game.Game;
|
||||
|
||||
/**
|
||||
* QuestTracker
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class QuestTracker<T extends Game> implements Listener
|
||||
{
|
||||
private T _game;
|
||||
private TriggerType _trigger;
|
||||
|
||||
public QuestTracker(T game, TriggerType type)
|
||||
{
|
||||
_game = game;
|
||||
_trigger = type;
|
||||
}
|
||||
|
||||
public T getGame()
|
||||
{
|
||||
return _game;
|
||||
}
|
||||
|
||||
public boolean canProgressQuests()
|
||||
{
|
||||
return getGame().CanAddStats;
|
||||
}
|
||||
|
||||
public void incrementQuests(Player player, int value)
|
||||
{
|
||||
incrementQuests(player, value, "");
|
||||
}
|
||||
|
||||
public void incrementQuests(Player player, int value, String... items)
|
||||
{
|
||||
if (canProgressQuests())
|
||||
{
|
||||
for (Quest quest : getGame().getArcadeManager().getQuestManager().getAvailableQuests())
|
||||
{
|
||||
if (!quest.isGeneral())
|
||||
{
|
||||
if (getGame().GetType().getDisplay() != quest.getGame() && getGame().GetType().getDisplay().getGameCategory() != quest.getGameCategory())
|
||||
continue;
|
||||
}
|
||||
|
||||
if (quest.getTrigger() != _trigger)
|
||||
continue;
|
||||
|
||||
if (!quest.getItem()[0].equalsIgnoreCase(""))
|
||||
{
|
||||
boolean cont = true;
|
||||
for (String questItem : quest.getItem())
|
||||
{
|
||||
for (String str : items)
|
||||
{
|
||||
String first = str.replaceAll(" ", "");
|
||||
String compare = questItem.replaceAll(" ", "");
|
||||
|
||||
if (first.equalsIgnoreCase(compare))
|
||||
{
|
||||
cont = false;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
cont = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cont)
|
||||
continue;
|
||||
}
|
||||
|
||||
QuestClientData data = _game.getArcadeManager().getQuestManager().Get(player);
|
||||
if (!data.hasQuest(quest))
|
||||
continue;
|
||||
|
||||
Quest toProgress = data.getQuest(quest.getID());
|
||||
|
||||
_game.getArcadeManager().getQuestManager().incrementQuest(player, toProgress, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TriggerType getTrigger()
|
||||
{
|
||||
return _trigger;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package nautilus.game.arcade.quest;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
|
||||
import mineplex.core.common.util.UtilServer;
|
||||
import mineplex.core.quests.TriggerType;
|
||||
|
||||
import nautilus.game.arcade.Arcade;
|
||||
import nautilus.game.arcade.events.GameStateChangeEvent;
|
||||
import nautilus.game.arcade.game.Game;
|
||||
import nautilus.game.arcade.game.Game.GameState;
|
||||
|
||||
/**
|
||||
* WinQuestTracker
|
||||
*
|
||||
* @author xXVevzZXx
|
||||
*/
|
||||
public class WinQuestTracker extends QuestTracker<Game>
|
||||
{
|
||||
|
||||
public WinQuestTracker(Game game)
|
||||
{
|
||||
super(game, TriggerType.WIN);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onWin(GameStateChangeEvent event)
|
||||
{
|
||||
if (event.GetState() != GameState.End)
|
||||
return;
|
||||
|
||||
for (Player player : getGame().getWinners())
|
||||
{
|
||||
incrementQuests(player, 1, ((Arcade) UtilServer.getPlugin()).getServerConfig().getServerGroup().getPrefix(), getGame().GetKit(player).GetName() + "Kit");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -8,6 +8,7 @@ public enum SpreadsheetType
|
||||
|
||||
GEM_HUNTERS_CHESTS("11Noztgbpu_gUKkc5F4evKKfyxS-Jv1coE0IrBToX_gg"),
|
||||
GEM_HUNTERS_SHOP("1OcYktxVZaW6Fm29Zh6w4Lb-UVyuN8r1x-TFb_3USYYI"),
|
||||
QUESTS_SHEET("1Gy1a7GCVopmOLwYE3Sk1DNVCAIwT8ReaLu4wRe0sfDE"),
|
||||
SMASH_KITS("1Z_SLBzjiIVqu25PMGw9TwNKR3wd9Y9sX7rSDBl_rpxk")
|
||||
;
|
||||
|
||||
@ -22,5 +23,4 @@ public enum SpreadsheetType
|
||||
{
|
||||
return _id;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
69
Plugins/mineplex-questmanager/pom.xml
Normal file
69
Plugins/mineplex-questmanager/pom.xml
Normal file
@ -0,0 +1,69 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.mineplex</groupId>
|
||||
<artifactId>mineplex-parent</artifactId>
|
||||
<version>dev-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>mineplex-questmanager</artifactId>
|
||||
<name>Mineplex.questmanager</name>
|
||||
<description>A centralized service that selects daily quests</description>
|
||||
|
||||
<properties>
|
||||
<version.guava>18.0</version.guava>
|
||||
<version.jline>2.12</version.jline>
|
||||
<version.spigot>1.8.8-1.9-SNAPSHOT</version.spigot>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>mineplex-serverdata</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>${version.guava}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jline</groupId>
|
||||
<artifactId>jline</artifactId>
|
||||
<version>${version.jline}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mineplex</groupId>
|
||||
<artifactId>spigot</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<configuration>
|
||||
<minimizeJar>false</minimizeJar>
|
||||
<transformers>
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>mineplex.quest.daemon.QuestDaemon</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -0,0 +1,111 @@
|
||||
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.
|
||||
* <p>
|
||||
* 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<Quest> _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<Quest> 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<Quest> get()
|
||||
{
|
||||
_lock.readLock().lock();
|
||||
try
|
||||
{
|
||||
return ImmutableSet.copyOf(_quests);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Quest> getById(int uniquePersistentId)
|
||||
{
|
||||
_lock.readLock().lock();
|
||||
try
|
||||
{
|
||||
return _quests.stream().filter(q -> q.getUniqueId() == uniquePersistentId).findFirst();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
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<Quest> _to;
|
||||
|
||||
public QuestsUpdatedEvent(Set<Quest> to)
|
||||
{
|
||||
_to = to;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The currently active quests.
|
||||
*/
|
||||
public Set<Quest> getActiveQuests()
|
||||
{
|
||||
return _to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers()
|
||||
{
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList()
|
||||
{
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
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 + "]";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
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 <b>should not be changed</b>.
|
||||
*
|
||||
* @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}.
|
||||
* <p>
|
||||
* 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 <code>true</code> if this quest should be selected as an active quest, or
|
||||
* <code>false</code> otherwise.
|
||||
*/
|
||||
boolean shouldRotate();
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package mineplex.quest.common;
|
||||
|
||||
/**
|
||||
* How rare a quest is. In other words, how often this quest should be chosen.
|
||||
*/
|
||||
public enum QuestRarity
|
||||
{
|
||||
// TODO fill in actual weights
|
||||
COMMON(0.5),
|
||||
RARE(0.3),
|
||||
LEGENDARY(0.1)
|
||||
;
|
||||
|
||||
private final double _weight;
|
||||
|
||||
private QuestRarity(double weight)
|
||||
{
|
||||
_weight = weight;
|
||||
}
|
||||
|
||||
public double getWeight()
|
||||
{
|
||||
return _weight;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
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<Set<Quest>>
|
||||
{
|
||||
|
||||
/**
|
||||
* Get an immutable set containing all of the currently active quests.
|
||||
*/
|
||||
@Override
|
||||
Set<Quest> 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<Quest> getById(int uniquePersistentId);
|
||||
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
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.
|
||||
* <p>
|
||||
* 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<Quest> QUESTS;
|
||||
|
||||
static
|
||||
{
|
||||
ImmutableSet.Builder<Quest> builder = ImmutableSet.<Quest>builder();
|
||||
|
||||
Map<String, List<List<String>>> sheets = UtilGoogleSheet.getSheetData(ALL_QUESTS_FILE);
|
||||
|
||||
List<List<String>> 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<String> 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<Quest> fromId(int uniqueId)
|
||||
{
|
||||
return QUESTS.stream().filter(quest -> quest.getUniqueId() == uniqueId).findFirst();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
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:";
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
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<Quest>
|
||||
{
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
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<Quest>.
|
||||
*/
|
||||
public class QuestTypeDeserialiazer implements JsonDeserializer<Set<Quest>>
|
||||
{
|
||||
|
||||
@Override
|
||||
public Set<Quest> 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));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
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<Quest> into a {@link JsonElement} String.
|
||||
*/
|
||||
public class QuestTypeSerializer implements JsonSerializer<Set<Quest>>
|
||||
{
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static final Type QUEST_TYPE = new TypeToken<Set<Quest>>(){}.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<Quest> 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());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
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.
|
||||
* <p>
|
||||
* Intended to be thread-safe.
|
||||
*
|
||||
* @param <E> The generic type parameter of the elements.
|
||||
*/
|
||||
public class RandomCollection<E>
|
||||
{
|
||||
|
||||
private final NavigableMap<Double, E> _map = Collections.synchronizedNavigableMap(new TreeMap<Double, E>());
|
||||
private final Random _random;
|
||||
|
||||
private double total = 0;
|
||||
|
||||
public RandomCollection(Random random)
|
||||
{
|
||||
_random = random;
|
||||
}
|
||||
|
||||
public RandomCollection()
|
||||
{
|
||||
this(ThreadLocalRandom.current());
|
||||
}
|
||||
|
||||
public void addAll(Map<E, Double> 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<E> values()
|
||||
{
|
||||
return _map.values();
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
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<String, List<List<String>>> getSheetData(String name)
|
||||
{
|
||||
return getSheetData(new File(DATA_STORE_DIR + File.separator + name + ".json"));
|
||||
}
|
||||
|
||||
public static Map<String, List<List<String>>> getSheetData(File file)
|
||||
{
|
||||
if (!file.exists())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, List<List<String>>> 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<List<String>> valuesList = new ArrayList<>(values.size());
|
||||
|
||||
for (int j = 0; j < values.size(); j++)
|
||||
{
|
||||
List<String> list = new ArrayList<>();
|
||||
Iterator<JsonElement> 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
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()) + "] ";
|
||||
}
|
||||
}
|
@ -0,0 +1,277 @@
|
||||
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.
|
||||
* <p>
|
||||
* 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<Quest> _quests = new RandomCollection<>();
|
||||
|
||||
// currently active quests
|
||||
private final Set<Quest> _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<Quest> _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<String> 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> 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<String> 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<Quest> quests)
|
||||
{
|
||||
return QuestTypeSerializer.QUEST_GSON.toJson(quests, QuestTypeSerializer.QUEST_TYPE);
|
||||
}
|
||||
}
|
@ -43,6 +43,7 @@
|
||||
<module>mavericks-review-hub</module>
|
||||
<module>mineplex-game-gemhunters</module>
|
||||
<module>mineplex-google-sheets</module>
|
||||
<module>mineplex-questmanager</module>
|
||||
</modules>
|
||||
|
||||
<repositories>
|
||||
|
Loading…
Reference in New Issue
Block a user