diff --git a/src/main/java/cat/nyaa/HamsterEcoHelper/CommandHandler.java b/src/main/java/cat/nyaa/HamsterEcoHelper/CommandHandler.java index 3b60e77..85d4e20 100644 --- a/src/main/java/cat/nyaa/HamsterEcoHelper/CommandHandler.java +++ b/src/main/java/cat/nyaa/HamsterEcoHelper/CommandHandler.java @@ -4,6 +4,7 @@ import cat.nyaa.HamsterEcoHelper.auction.AuctionCommands; import cat.nyaa.HamsterEcoHelper.balance.BalanceCommands; import cat.nyaa.HamsterEcoHelper.market.MarketCommands; +import cat.nyaa.HamsterEcoHelper.quest.QuestCommands; import cat.nyaa.HamsterEcoHelper.requisition.RequisitionCommands; import cat.nyaa.HamsterEcoHelper.signshop.SearchCommands; import cat.nyaa.HamsterEcoHelper.signshop.SignShopCommands; @@ -38,6 +39,8 @@ public class CommandHandler extends CommandReceiver { public AdsCommands adsCommands; @SubCommand("search") public SearchCommands searchCommands; + @SubCommand("quest") + public QuestCommands questCommands; public CommandHandler(HamsterEcoHelper plugin, LanguageRepository i18n) { super(plugin, i18n); diff --git a/src/main/java/cat/nyaa/HamsterEcoHelper/quest/QuestCommands.java b/src/main/java/cat/nyaa/HamsterEcoHelper/quest/QuestCommands.java new file mode 100644 index 0000000..6f09fc0 --- /dev/null +++ b/src/main/java/cat/nyaa/HamsterEcoHelper/quest/QuestCommands.java @@ -0,0 +1,51 @@ +package cat.nyaa.HamsterEcoHelper.quest; + +import cat.nyaa.HamsterEcoHelper.HamsterEcoHelper; +import cat.nyaa.HamsterEcoHelper.I18n; +import cat.nyaa.HamsterEcoHelper.utils.database.tables.quest.QuestStation; +import cat.nyaa.nyaacore.CommandReceiver; +import cat.nyaa.nyaacore.LanguageRepository; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.Sign; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.Set; + +public class QuestCommands extends CommandReceiver{ + private final HamsterEcoHelper plugin; + + public QuestCommands(HamsterEcoHelper plugin, I18n i18n) { + super(plugin, i18n); + this.plugin = plugin; + } + + @Override + public String getHelpPrefix() { + return "quest"; + } + + @SubCommand(value = "add", permission = "heh.quest.post") + public void postQuest(CommandSender sender, Arguments args) { + Sign stationSign = getSignLookat(sender); + QuestStation station = QuestCommon.toQuestStation(stationSign.getLocation()); + if (station == null) { + msg(sender, "user.quest.not_station"); + return; + } + new QuestWizard(station.id, asPlayer(sender), 30); + } + + public Sign getSignLookat(CommandSender sender) { + Player p = asPlayer(sender); + Block b = p.getTargetBlock((Set) null, 5);// TODO use nms rayTrace + + if (b == null || !b.getType().isBlock() || (b.getType() != Material.WALL_SIGN && b.getType() != Material.SIGN_POST)) { + throw new BadCommandException("user.error.not_sign"); + } + return (Sign)b.getState(); + } + + +} diff --git a/src/main/java/cat/nyaa/HamsterEcoHelper/quest/QuestCommon.java b/src/main/java/cat/nyaa/HamsterEcoHelper/quest/QuestCommon.java index a92b1ad..314e301 100644 --- a/src/main/java/cat/nyaa/HamsterEcoHelper/quest/QuestCommon.java +++ b/src/main/java/cat/nyaa/HamsterEcoHelper/quest/QuestCommon.java @@ -19,4 +19,22 @@ public static QuestStation toQuestStation(Location loc) { return null; } } + + public static boolean hasStation(Location loc) { + return HamsterEcoHelper.instance.database.query(QuestStation.class) + .whereEq("world", loc.getWorld().getName()) + .whereEq("x", loc.getBlockX()) + .whereEq("y", loc.getBlockY()) + .whereEq("z", loc.getBlockZ()) + .count() > 0; + } + + public static void removeStation(Location loc) { + HamsterEcoHelper.instance.database.query(QuestStation.class) + .whereEq("world", loc.getWorld().getName()) + .whereEq("x", loc.getBlockX()) + .whereEq("y", loc.getBlockY()) + .whereEq("z", loc.getBlockZ()) + .delete(); + } } diff --git a/src/main/java/cat/nyaa/HamsterEcoHelper/quest/QuestListener.java b/src/main/java/cat/nyaa/HamsterEcoHelper/quest/QuestListener.java index 84f2b3b..ccfba45 100644 --- a/src/main/java/cat/nyaa/HamsterEcoHelper/quest/QuestListener.java +++ b/src/main/java/cat/nyaa/HamsterEcoHelper/quest/QuestListener.java @@ -2,16 +2,22 @@ import cat.nyaa.HamsterEcoHelper.HamsterEcoHelper; import cat.nyaa.HamsterEcoHelper.I18n; +import cat.nyaa.HamsterEcoHelper.utils.database.tables.quest.QuestEntry; import cat.nyaa.HamsterEcoHelper.utils.database.tables.quest.QuestStation; +import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.SignChangeEvent; import org.bukkit.event.player.PlayerInteractEvent; +import java.util.List; + public class QuestListener implements Listener { private final HamsterEcoHelper plugin; @@ -40,6 +46,24 @@ public void onSignChangeEvent(SignChangeEvent event) { } } + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onSignBreak(BlockBreakEvent ev) { + Block b = ev.getBlock(); + if (b.getType() == Material.SIGN_POST || b.getType() == Material.WALL_SIGN) { + Location loc = b.getLocation(); + if (QuestCommon.hasStation(loc)) { + if (!ev.getPlayer().hasPermission("heh.quest.admin")) { + ev.getPlayer().sendMessage(I18n.format("user.quest.cannot_break")); + ev.setCancelled(true); + } else { + // TODO check quests + QuestCommon.removeStation(loc); + ev.getPlayer().sendMessage(I18n.format("user.quest.station_removed")); + } + } + } + } + @EventHandler public void onPlayerInteract(PlayerInteractEvent event) { if (event.getAction() == Action.RIGHT_CLICK_BLOCK) { @@ -47,6 +71,10 @@ public void onPlayerInteract(PlayerInteractEvent event) { QuestStation station = QuestCommon.toQuestStation(block.getLocation()); if (station == null) return; event.getPlayer().sendMessage("Station clicked"); + List quests = plugin.database.query(QuestEntry.class).whereEq("station_id", station.id).select(); + for (QuestEntry q : quests) { + event.getPlayer().sendMessage("QUEST: " + q.questName); + } } } } diff --git a/src/main/java/cat/nyaa/HamsterEcoHelper/quest/QuestWizard.java b/src/main/java/cat/nyaa/HamsterEcoHelper/quest/QuestWizard.java new file mode 100644 index 0000000..4c47a00 --- /dev/null +++ b/src/main/java/cat/nyaa/HamsterEcoHelper/quest/QuestWizard.java @@ -0,0 +1,214 @@ +package cat.nyaa.HamsterEcoHelper.quest; + +import cat.nyaa.HamsterEcoHelper.HamsterEcoHelper; +import cat.nyaa.HamsterEcoHelper.I18n; +import cat.nyaa.HamsterEcoHelper.utils.database.tables.quest.QuestEntry; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerChatEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; + +import java.time.Duration; +import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; +import java.util.UUID; + +import static cat.nyaa.HamsterEcoHelper.quest.QuestWizard.State.*; + +public class QuestWizard implements Listener { + private final Player player; + private final UUID station; + private final int timeout; + private final QuestEntry entry; + private State state; + private Timer timer; + + private class Timer extends BukkitRunnable { + boolean cancelled = false; + + public Timer(int timeout) { + this.runTaskLater(HamsterEcoHelper.instance, timeout*20L); + } + + @Override + public synchronized void cancel() throws IllegalStateException { + super.cancel(); + this.cancelled = true; + } + + @Override + public void run() { + if (cancelled) { + this.cancel(); + } else { + cancelWizard(); + } + } + } + + enum State { + WAITING_NAME, + WAITING_DESCRIPTION, + + WAITING_PREREQ_TYPE, + WAITING_PREREQ_MONEY, + WAITING_PREREQ_ITEM, + + WAITING_EARLY_REWARD_ITEM, + + WAITING_TARGET_TYPE, + WAITING_TARGET_ITEM, + + WAITING_REWARD_TYPE, + WAITING_REWARD_ITEM, + WAITING_REWARD_MONEY, + + WAITING_TIME_LIMIT, + WAITING_EXPIRE_IN, + + WAITING_IS_RECURRENT, + WAITING_CLAIM_LIMIT, + WAITING_ENABLED, + + FINISH, + CANCEL; + } + + public QuestWizard(String stationUUID, Player p, int timeoutSeconds) { + player = p; + station = UUID.fromString(stationUUID); + timeout = timeoutSeconds; + entry = new QuestEntry(); + entry.id = UUID.randomUUID().toString(); + entry.publisher = p.getUniqueId().toString(); + entry.stationId = stationUUID; + state = State.WAITING_NAME; + p.sendMessage(I18n.format("user.quest.wizard." + state.name().toLowerCase())); + Bukkit.getServer().getPluginManager().registerEvents(this, HamsterEcoHelper.instance); + timer = new Timer(timeout); + } + + private void cancelWizard() { + state = CANCEL; + player.sendMessage(I18n.format("user.quest.wizard.cancelled")); + HandlerList.unregisterAll(this); + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerChat(PlayerChatEvent ev) { + if (state == CANCEL) return; + if (!player.getUniqueId().equals(ev.getPlayer().getUniqueId())) return; + timer.cancel(); + ev.setCancelled(true); + String input = ev.getMessage(); + if ("cancel".equalsIgnoreCase(input)) { + cancelWizard(); + return; + } + + switch (state) { + case WAITING_NAME: + entry.questName = input; + state = State.WAITING_DESCRIPTION; + break; + case WAITING_DESCRIPTION: + entry.questDescription = input; + state = State.WAITING_TARGET_TYPE; + break; + case WAITING_TARGET_TYPE: + if ("item".equalsIgnoreCase(input)) { + entry.targetType = QuestEntry.QuestType.ITEM; + state = State.WAITING_TARGET_ITEM; + } else { + entry.targetType = QuestEntry.QuestType.OTHER; + state = State.WAITING_REWARD_TYPE; + } + break; + case WAITING_TARGET_ITEM: + if ("end".equalsIgnoreCase(input)) { + if (entry.targetItems == null || entry.targetItems.size() <= 0) { + player.sendMessage(I18n.format("user.quest.wizard.at_least_one")); + } else { + state = State.WAITING_REWARD_TYPE; + } + } else { + ItemStack stack = player.getInventory().getItemInMainHand(); + if (stack == null || stack.getType() == Material.AIR) { + player.sendMessage(I18n.format("user.quest.wizard.hold_item_plz")); + } else { + entry.targetItems.add(stack.clone()); + } + } + break; + case WAITING_REWARD_TYPE: + if ("item".equalsIgnoreCase(input)) { + entry.rewardType = QuestEntry.QuestType.ITEM; + state = State.WAITING_REWARD_ITEM; + } else if ("money".equalsIgnoreCase(input)) { + entry.rewardType = QuestEntry.QuestType.MONEY; + state = State.WAITING_REWARD_MONEY; + } else { + entry.rewardType = QuestEntry.QuestType.NONE; + state = WAITING_TIME_LIMIT; + } + break; + case WAITING_REWARD_ITEM: // TODO remove item from user inventory + if ("end".equalsIgnoreCase(input)) { + if (entry.rewardItem == null || entry.rewardItem.size() <= 0) { + player.sendMessage(I18n.format("user.quest.wizard.at_least_one")); + } else { + state = WAITING_TIME_LIMIT; + } + } else { + ItemStack stack = player.getInventory().getItemInMainHand(); + if (stack == null || stack.getType() == Material.AIR) { + player.sendMessage(I18n.format("user.quest.wizard.hold_item_plz")); + } else { + entry.rewardItem.add(stack.clone()); + } + } + break; + case WAITING_REWARD_MONEY: + try { + double money = Double.parseDouble(input); + entry.rewardMoney = money; + state = WAITING_TIME_LIMIT; + } catch (NumberFormatException ex) { + player.sendMessage(I18n.format("user.quest.wizard.invalid_number")); + } + break; + case WAITING_TIME_LIMIT: + try { + entry.questTimeLimit = Duration.parse(input); + state = WAITING_EXPIRE_IN; + } catch (DateTimeParseException ex) { + player.sendMessage(I18n.format("user.quest.wizard.invalid_time")); + } + break; + case WAITING_EXPIRE_IN: + try { + Duration dur = Duration.parse(input); + entry.questExpire = ZonedDateTime.now().plus(dur); + state = FINISH; + } catch (DateTimeParseException ex) { + player.sendMessage(I18n.format("user.quest.wizard.invalid_time")); + } + break; + case FINISH: + entry.claimable = true; + HamsterEcoHelper.instance.database.query(QuestEntry.class).insert(entry); + HandlerList.unregisterAll(this); + player.sendMessage(I18n.format("user.quest.wizard.added")); + return; + default: + throw new IllegalStateException(); + } + player.sendMessage(I18n.format("user.quest.wizard." + state.name().toLowerCase())); + timer = new Timer(timeout); + } +} diff --git a/src/main/java/cat/nyaa/HamsterEcoHelper/utils/Utils.java b/src/main/java/cat/nyaa/HamsterEcoHelper/utils/Utils.java index be54234..3dcc0b8 100644 --- a/src/main/java/cat/nyaa/HamsterEcoHelper/utils/Utils.java +++ b/src/main/java/cat/nyaa/HamsterEcoHelper/utils/Utils.java @@ -153,6 +153,7 @@ public static String encodeItemStacks(List items) { } public static List decodeItemStacks(String items) { + if (items.length() <= 0) return new ArrayList<>(); String[] a = items.split(","); List r = new ArrayList<>(); for (String str : a) r.add(decodeItemStack(str)); diff --git a/src/main/java/cat/nyaa/HamsterEcoHelper/utils/database/tables/quest/QuestEntry.java b/src/main/java/cat/nyaa/HamsterEcoHelper/utils/database/tables/quest/QuestEntry.java index 5d5bd89..83dadfc 100644 --- a/src/main/java/cat/nyaa/HamsterEcoHelper/utils/database/tables/quest/QuestEntry.java +++ b/src/main/java/cat/nyaa/HamsterEcoHelper/utils/database/tables/quest/QuestEntry.java @@ -7,8 +7,12 @@ import org.bukkit.inventory.ItemStack; import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.List; +import java.util.UUID; @DataTable("quest_entry") public class QuestEntry { @@ -21,39 +25,39 @@ public enum QuestType { @DataColumn @PrimaryKey - public String id; + public String id = UUID.randomUUID().toString(); + @DataColumn("station_id") + public String stationId = ""; // which station this quest is in @DataColumn - public String station_id; // which station this quest is in + public String questName = ""; @DataColumn - public String questName; + public String questDescription = ""; @DataColumn - public String questDescription; + public String publisher = ""; // uuid of the player who published the quest @DataColumn - public String publisher; // uuid of the player who published the quest + public Boolean claimable = false; // if this quest can be claimed by a player @DataColumn - public Boolean claimable; // if this quest can be claimed by a player + public Boolean isRecurrentQuest = false; // if the quest can be claimed by many players. NOTE: the rewards will be created from nowhere (i.e. duplicated) @DataColumn - public Boolean isRecurrentQuest; // if the quest can be claimed by many players. NOTE: the rewards will be created from nowhere (i.e. duplicated) - @DataColumn - public Long singlePlayerClaimLimit; // how many time a single player can claim this quest, valid only if is recurrent quest + public Long singlePlayerClaimLimit = -1L; // how many time a single player can claim this quest, valid only if is recurrent quest - public QuestType prerequisiteType; // NONE, ITEM or MONEY - public List prerequisiteItems; + public QuestType prerequisiteType = QuestType.NONE; // NONE, ITEM or MONEY + public List prerequisiteItems = new ArrayList<>(); @DataColumn("prereq_money") - public Double prerequisiteMoney; + public Double prerequisiteMoney = 0D; - public List earlyRewardItems; // items will be given to player when they claimed the quest + public List earlyRewardItems = new ArrayList<>(); // items will be given to player when they claimed the quest - public QuestType targetType; // NONE, ITEM or OTHER - public List targetItems; + public QuestType targetType = QuestType.NONE; // ITEM or OTHER + public List targetItems = new ArrayList<>(); - public QuestType rewardType; // NONE, ITEM or MONEY - public List rewardItem; + public QuestType rewardType = QuestType.NONE; // NONE, ITEM or MONEY + public List rewardItem = new ArrayList<>(); @DataColumn("reward_money") - public Double rewardMoney; + public Double rewardMoney = 0D; - public ZonedDateTime questExpire; // when will the quest expire - public Duration questTimeLimit; // the time limit the quest should be finished once it's claimed + public ZonedDateTime questExpire = ZonedDateTime.ofInstant(Instant.EPOCH, ZoneId.systemDefault()); // when will the quest expire + public Duration questTimeLimit = Duration.parse("PT-1H"); // the time limit the quest should be finished once it's claimed @DataColumn("prereq_type") public String getPrerequisiteType() { diff --git a/src/main/resources/lang/en_US.yml b/src/main/resources/lang/en_US.yml index 494e295..e1e7ce3 100644 --- a/src/main/resources/lang/en_US.yml +++ b/src/main/resources/lang/en_US.yml @@ -70,6 +70,7 @@ user: bad_enum: "Not valid for enum type: %s. Values: %s" unknown_item: "unknown item: %s" shulker_box_contains_book: "You can't sell shulker box contains book" + not_sign: "You must look at a sign" warn: no_enough_money: "You do not have that much money" not_high_enough: "Invalid price, at least %.2f$" @@ -215,6 +216,43 @@ user: quest: invalid_fee: "Invalid posting fee." station_created: "Quest station created" + station_removed: "Quest station removed" + cannot_break: "You cannot break this station" + has_ongoing_quest: "There are quests in progress." + not_station: "Please look at a quest station sign." + + + wizard: + waiting_name: "Please type the quest name:" + waiting_description: "Please type the quest description:" + waiting_prereq_type: "Prerequisite type (item/money/none):" + waiting_prereq_money: "Prerequisite money amount:" + waiting_prereq_item: "Hold the item you want as prerequisite in hand and type \"add\" or \"end\"" + + waiting_early_reward_item: "Hold the item you want as early reward in hand and type \"add\" or \"end\"" + + waiting_target_type: "Target type (item/other):" + waiting_target_item: "Hold the item you want as target in hand and type \"add\" or \"end\"" + + waiting_reward_type: "Reward type (item/money/none):" + waiting_reward_money: "Reward money amount:" + waiting_reward_item: "Hold the item you want as reward in hand and type \"add\" or \"end\"" + + waiting_time_limit: "Time Limit for this quest?" + waiting_expire_in: "This quest expires in?" + + waiting_is_recurrent: "Is this a recurrent quest(y/n)?" + waiting_claim_limit: "What's the claim limit for each player?" + waiting_enabled: "Should this quest be available now?" + + finish: "Type \"confirm\" or \"cancel\" to complete the wizard." + + at_least_one: "You need at least one item. Try again." + hold_item_plz: "Please hold a item in hand." + invalid_number: "Not a valid number. Try again." + invalid_time: "Not a valid time. Try again." + added: "Quest added." + cancelled: "Quest wizard cancelled." manual: no_description: "No description" no_usage: "No usage" diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 1f9a6d0..d9ffb87 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -42,8 +42,8 @@ permissions: heh.signshop.lotto: true heh.signshop.search: true heh.ads: true - heh.quest.publish: true - heh.quest.accept: true + heh.quest.post: true + heh.quest.claim: true heh.admin: description: Permission node for operators default: op