Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 37cb4453ac | |||
| 02c53849cf | |||
| e8e4e35b6e | |||
| 087bf7eb40 | |||
| 3e1698614e | |||
| a53a4dac61 | |||
| 9eecc99f56 | |||
| e5bc3d1f14 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -30,3 +30,4 @@ out/
|
||||
# logs
|
||||
logs/
|
||||
*.log
|
||||
oyetickets/
|
||||
|
||||
@@ -9,10 +9,11 @@ description = "deterministic item-for-item chest barter"
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://repo.papermc.io/repository/maven-public/")
|
||||
maven("https://repo.papermc.io/repository/maven-snapshots/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
|
||||
compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT")
|
||||
implementation("org.xerial:sqlite-jdbc:3.47.1.0")
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,11 @@ import party.cybsec.oyeshops.OyeShopsPlugin;
|
||||
import party.cybsec.oyeshops.model.PendingActivation;
|
||||
import party.cybsec.oyeshops.model.Shop;
|
||||
import party.cybsec.oyeshops.permission.PermissionManager;
|
||||
import party.cybsec.oyeshops.listener.ShopActivationListener;
|
||||
import party.cybsec.oyeshops.gui.HelpBook;
|
||||
import party.cybsec.oyeshops.gui.SetupDialog;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.data.type.WallSign;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
@@ -42,12 +46,14 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
||||
case "toggle" -> handleToggle(sender);
|
||||
case "notify" -> handleNotifyToggle(sender);
|
||||
case "info" -> handleInfo(sender);
|
||||
case "help" -> handleHelp(sender);
|
||||
case "setup" -> handleSetup(sender);
|
||||
case "reload" -> handleReload(sender);
|
||||
case "inspect", "i" -> handleInspect(sender);
|
||||
case "spoof", "s" -> handleSpoof(sender);
|
||||
case "unregister", "delete", "remove" -> handleUnregister(sender, args);
|
||||
case "_activate" -> handleActivate(sender, args);
|
||||
default -> sendHelp(sender);
|
||||
default -> handleHelp(sender);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -55,6 +61,10 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
||||
|
||||
private void sendHelp(CommandSender sender) {
|
||||
sender.sendMessage(Component.text("=== oyeshops commands ===", NamedTextColor.GOLD));
|
||||
sender.sendMessage(Component.text("/oyeshops help", NamedTextColor.YELLOW)
|
||||
.append(Component.text(" - open interactive guide", NamedTextColor.GRAY)));
|
||||
sender.sendMessage(Component.text("/oyeshops setup", NamedTextColor.YELLOW)
|
||||
.append(Component.text(" - open shop wizard", NamedTextColor.GRAY)));
|
||||
sender.sendMessage(Component.text("/oyeshops on", NamedTextColor.YELLOW)
|
||||
.append(Component.text(" - enable shop creation", NamedTextColor.GRAY)));
|
||||
sender.sendMessage(Component.text("/oyeshops off", NamedTextColor.YELLOW)
|
||||
@@ -86,6 +96,14 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
||||
sender.sendMessage(Component.text("cybsec made this plugin", NamedTextColor.GRAY));
|
||||
}
|
||||
|
||||
private void handleHelp(CommandSender sender) {
|
||||
if (sender instanceof Player player) {
|
||||
HelpBook.open(player);
|
||||
} else {
|
||||
sendHelp(sender);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleReload(CommandSender sender) {
|
||||
if (!PermissionManager.isAdmin(sender)) {
|
||||
sender.sendMessage(Component.text("no permission", NamedTextColor.RED));
|
||||
@@ -236,6 +254,18 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
||||
plugin.getPlayerPreferenceRepository().enableShops(player.getUniqueId());
|
||||
player.sendMessage(Component.text("shop creation enabled. you can now make chest shops!",
|
||||
NamedTextColor.GREEN));
|
||||
|
||||
// show help book on first enable
|
||||
if (!plugin.getPlayerPreferenceRepository().hasSeenIntro(player.getUniqueId())) {
|
||||
plugin.getServer().getScheduler().runTaskLater(plugin, () -> {
|
||||
HelpBook.open(player);
|
||||
try {
|
||||
plugin.getPlayerPreferenceRepository().setSeenIntro(player.getUniqueId());
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, 20L); // delay slightly
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
player.sendMessage(Component.text("database error", NamedTextColor.RED));
|
||||
e.printStackTrace();
|
||||
@@ -342,7 +372,8 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
List<String> completions = new ArrayList<>();
|
||||
if (args.length == 1) {
|
||||
List<String> subCommands = new ArrayList<>(List.of("on", "off", "toggle", "notify", "enable", "disable"));
|
||||
List<String> subCommands = new ArrayList<>(
|
||||
List.of("help", "setup", "on", "off", "toggle", "notify", "info", "enable", "disable"));
|
||||
if (PermissionManager.isAdmin(sender)) {
|
||||
subCommands.addAll(List.of("reload", "inspect", "spoof", "unregister"));
|
||||
}
|
||||
@@ -365,4 +396,43 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
||||
}
|
||||
return completions;
|
||||
}
|
||||
|
||||
private void handleSetup(CommandSender sender) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage(Component.text("players only", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PermissionManager.canCreate(player)) {
|
||||
sender.sendMessage(Component.text("no permission", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
Block block = player.getTargetBlockExact(5);
|
||||
if (block == null || !(block.getBlockData() instanceof WallSign wallSign)) {
|
||||
player.sendMessage(Component.text("you must look at a wall sign to use the wizard", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
// duplicate validation logic from ShopActivationListener for safety
|
||||
BlockFace attachedFace = wallSign.getFacing().getOppositeFace();
|
||||
Block attachedBlock = block.getRelative(attachedFace);
|
||||
|
||||
if (!party.cybsec.oyeshops.listener.ShopActivationListener.isContainer(attachedBlock.getType())) {
|
||||
player.sendMessage(Component.text("sign must be on a chest or barrel", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PermissionManager.isAdmin(player)) {
|
||||
java.util.UUID sessionOwner = plugin.getContainerMemoryManager().getOwner(attachedBlock.getLocation());
|
||||
if (sessionOwner == null || !sessionOwner.equals(player.getUniqueId())) {
|
||||
player.sendMessage(
|
||||
Component.text("you can only create shops on containers you placed this session",
|
||||
NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SetupDialog.open(player, block, plugin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,10 +69,31 @@ public class DatabaseManager {
|
||||
player_uuid text primary key,
|
||||
shops_enabled boolean not null default false,
|
||||
notify_low_stock boolean not null default false,
|
||||
seen_intro boolean not null default false,
|
||||
enabled_at integer
|
||||
)
|
||||
""");
|
||||
|
||||
// migration: add seen_intro if missing
|
||||
try {
|
||||
stmt.execute("alter table player_preferences add column seen_intro boolean not null default false");
|
||||
} catch (SQLException ignored) {
|
||||
}
|
||||
|
||||
// migration: add owed_amount, enabled, created_at to shops if missing
|
||||
try {
|
||||
stmt.execute("alter table shops add column owed_amount integer not null default 0");
|
||||
} catch (SQLException ignored) {
|
||||
}
|
||||
try {
|
||||
stmt.execute("alter table shops add column enabled boolean not null default true");
|
||||
} catch (SQLException ignored) {
|
||||
}
|
||||
try {
|
||||
stmt.execute("alter table shops add column created_at integer not null default 0");
|
||||
} catch (SQLException ignored) {
|
||||
}
|
||||
|
||||
// indexes
|
||||
stmt.execute("""
|
||||
create index if not exists idx_shop_location
|
||||
|
||||
@@ -15,9 +15,9 @@ import java.util.UUID;
|
||||
public class PlayerPreferenceRepository {
|
||||
private final DatabaseManager dbManager;
|
||||
|
||||
// in-memory cache for performance
|
||||
private final Set<UUID> enabledPlayers = new HashSet<>();
|
||||
private final Set<UUID> notifyPlayers = new HashSet<>();
|
||||
private final Set<UUID> seenIntroPlayers = new HashSet<>();
|
||||
|
||||
public PlayerPreferenceRepository(DatabaseManager dbManager) {
|
||||
this.dbManager = dbManager;
|
||||
@@ -27,7 +27,7 @@ public class PlayerPreferenceRepository {
|
||||
* load all player preferences into cache
|
||||
*/
|
||||
public void loadPreferences() throws SQLException {
|
||||
String sql = "select player_uuid, shops_enabled, notify_low_stock from player_preferences";
|
||||
String sql = "select player_uuid, shops_enabled, notify_low_stock, seen_intro from player_preferences";
|
||||
|
||||
try (PreparedStatement stmt = dbManager.getConnection().prepareStatement(sql);
|
||||
ResultSet rs = stmt.executeQuery()) {
|
||||
@@ -39,6 +39,9 @@ public class PlayerPreferenceRepository {
|
||||
if (rs.getBoolean("notify_low_stock")) {
|
||||
notifyPlayers.add(uuid);
|
||||
}
|
||||
if (rs.getBoolean("seen_intro")) {
|
||||
seenIntroPlayers.add(uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,6 +60,31 @@ public class PlayerPreferenceRepository {
|
||||
return notifyPlayers.contains(playerUuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* check if player has seen the intro
|
||||
*/
|
||||
public boolean hasSeenIntro(UUID playerUuid) {
|
||||
return seenIntroPlayers.contains(playerUuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* set seen intro flag
|
||||
*/
|
||||
public void setSeenIntro(UUID playerUuid) throws SQLException {
|
||||
String sql = """
|
||||
insert into player_preferences (player_uuid, seen_intro)
|
||||
values (?, true)
|
||||
on conflict(player_uuid) do update set seen_intro = true
|
||||
""";
|
||||
|
||||
try (PreparedStatement stmt = dbManager.getConnection().prepareStatement(sql)) {
|
||||
stmt.setString(1, playerUuid.toString());
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
|
||||
seenIntroPlayers.add(playerUuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* enable shops for player
|
||||
*/
|
||||
|
||||
@@ -178,6 +178,21 @@ public class ShopRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* delete shop by location (world, x, y, z)
|
||||
*/
|
||||
public void deleteShopByLocation(Location loc) throws SQLException {
|
||||
String sql = "delete from shops where world_uuid = ? and sign_x = ? and sign_y = ? and sign_z = ?";
|
||||
|
||||
try (PreparedStatement stmt = dbManager.getConnection().prepareStatement(sql)) {
|
||||
stmt.setString(1, loc.getWorld().getUID().toString());
|
||||
stmt.setInt(2, loc.getBlockX());
|
||||
stmt.setInt(3, loc.getBlockY());
|
||||
stmt.setInt(4, loc.getBlockZ());
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get all shops owned by player
|
||||
*/
|
||||
|
||||
188
src/main/java/party/cybsec/oyeshops/gui/HelpBook.java
Normal file
188
src/main/java/party/cybsec/oyeshops/gui/HelpBook.java
Normal file
@@ -0,0 +1,188 @@
|
||||
package party.cybsec.oyeshops.gui;
|
||||
|
||||
import net.kyori.adventure.inventory.Book;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* utility to open an interactive help book for players
|
||||
* all text is lowercase for consistency
|
||||
*/
|
||||
public class HelpBook {
|
||||
|
||||
public static void open(Player player) {
|
||||
List<Component> pages = new ArrayList<>();
|
||||
|
||||
// page 1: introduction
|
||||
pages.add(Component.text()
|
||||
.append(Component.text("oyeshops guide", NamedTextColor.GOLD, TextDecoration.BOLD))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text("welcome to the simple item barter system.",
|
||||
NamedTextColor.DARK_GRAY))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text("steps to start:", NamedTextColor.GRAY))
|
||||
.append(Component.newline())
|
||||
.append(Component.text("1. type ", NamedTextColor.BLACK))
|
||||
.append(Component.text("/oyes on", NamedTextColor.BLUE)
|
||||
.clickEvent(ClickEvent.runCommand("/oyes on"))
|
||||
.hoverEvent(HoverEvent.showText(Component.text("click to enable shops",
|
||||
NamedTextColor.GRAY))))
|
||||
.append(Component.newline())
|
||||
.append(Component.text("2. place a chest", NamedTextColor.BLACK))
|
||||
.append(Component.newline())
|
||||
.append(Component.text("3. place a wall sign", NamedTextColor.BLACK))
|
||||
.build());
|
||||
|
||||
// page 2: setup wizard (intro)
|
||||
pages.add(Component.text()
|
||||
.append(Component.text("setup wizard", NamedTextColor.GOLD, TextDecoration.BOLD))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text("hate typing?", NamedTextColor.DARK_GRAY))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text("1. look at a sign", NamedTextColor.GRAY))
|
||||
.append(Component.newline())
|
||||
.append(Component.text("2. type ", NamedTextColor.GRAY))
|
||||
.append(Component.text("/oyes setup", NamedTextColor.BLUE)
|
||||
.clickEvent(ClickEvent.runCommand("/oyes setup"))
|
||||
.hoverEvent(HoverEvent.showText(Component.text("click to start wizard",
|
||||
NamedTextColor.GRAY))))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text("...or turn the page", NamedTextColor.DARK_GRAY))
|
||||
.build());
|
||||
|
||||
// page 3: setup wizard (sign trigger)
|
||||
pages.add(Component.text()
|
||||
.append(Component.text("sign trigger", NamedTextColor.GOLD, TextDecoration.BOLD))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text("just write ", NamedTextColor.GRAY))
|
||||
.append(Component.text("setup", NamedTextColor.BLUE, TextDecoration.BOLD))
|
||||
.append(Component.text(" on the first line of the sign.", NamedTextColor.GRAY))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text(
|
||||
"this opens a fancy menu where you can click to create your shop.",
|
||||
NamedTextColor.DARK_GRAY))
|
||||
.build());
|
||||
|
||||
// page 4: manual creation
|
||||
pages.add(Component.text()
|
||||
.append(Component.text("manual setup", NamedTextColor.GOLD, TextDecoration.BOLD))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text("write exactly this:", NamedTextColor.DARK_GRAY))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text("line 1: ", NamedTextColor.GRAY))
|
||||
.append(Component.text("1 diamond", NamedTextColor.BLACK))
|
||||
.append(Component.newline())
|
||||
.append(Component.text("line 2: ", NamedTextColor.GRAY))
|
||||
.append(Component.text("for", NamedTextColor.BLACK))
|
||||
.append(Component.newline())
|
||||
.append(Component.text("line 3: ", NamedTextColor.GRAY))
|
||||
.append(Component.text("64 dirt", NamedTextColor.BLACK))
|
||||
.build());
|
||||
|
||||
// page 5: auto detection
|
||||
pages.add(Component.text()
|
||||
.append(Component.text("auto detection", NamedTextColor.GOLD, TextDecoration.BOLD))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text("lazy? just put items in the chest and write:",
|
||||
NamedTextColor.DARK_GRAY))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text("auto", NamedTextColor.BLUE, TextDecoration.BOLD))
|
||||
.append(Component.newline())
|
||||
.append(Component.text("for 10 gold", NamedTextColor.BLACK))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text("the plugin will check the chest contents.",
|
||||
NamedTextColor.DARK_GRAY))
|
||||
.build());
|
||||
|
||||
// page 6: ownership
|
||||
pages.add(Component.text()
|
||||
.append(Component.text("ownership rules", NamedTextColor.GOLD, TextDecoration.BOLD))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text(
|
||||
"you can only make shops on containers you placed ",
|
||||
NamedTextColor.DARK_GRAY))
|
||||
.append(Component.text("this session", NamedTextColor.BLACK, TextDecoration.ITALIC))
|
||||
.append(Component.text(".", NamedTextColor.DARK_GRAY))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text(
|
||||
"prevents stealing old chests!",
|
||||
NamedTextColor.DARK_GRAY))
|
||||
.build());
|
||||
|
||||
// page 7: containers
|
||||
pages.add(Component.text()
|
||||
.append(Component.text("supported blocks", NamedTextColor.GOLD, TextDecoration.BOLD))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text("- chests", NamedTextColor.BLACK))
|
||||
.append(Component.newline())
|
||||
.append(Component.text("- barrels", NamedTextColor.BLACK))
|
||||
.append(Component.newline())
|
||||
.append(Component.text("- trapped chests", NamedTextColor.BLACK))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text("double chests supported.", NamedTextColor.DARK_GRAY))
|
||||
.build());
|
||||
|
||||
// page 8: commands
|
||||
pages.add(Component.text()
|
||||
.append(Component.text("commands", NamedTextColor.GOLD, TextDecoration.BOLD))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text("/oyes notify", NamedTextColor.BLUE)
|
||||
.clickEvent(ClickEvent.runCommand("/oyes notify"))
|
||||
.hoverEvent(HoverEvent.showText(Component.text("click to toggle alerts",
|
||||
NamedTextColor.GRAY))))
|
||||
.append(Component.newline())
|
||||
.append(Component.text("get low stock alerts.", NamedTextColor.DARK_GRAY))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text("/oyes info", NamedTextColor.BLUE)
|
||||
.clickEvent(ClickEvent.runCommand("/oyes info"))
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
Component.text("click for info", NamedTextColor.GRAY))))
|
||||
.append(Component.newline())
|
||||
.append(Component.text("plugin info.", NamedTextColor.DARK_GRAY))
|
||||
.build());
|
||||
|
||||
// page 9: tips
|
||||
pages.add(Component.text()
|
||||
.append(Component.text("pro tips", NamedTextColor.GOLD, TextDecoration.BOLD))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(Component.text("- use wall signs.", NamedTextColor.BLACK))
|
||||
.append(Component.newline())
|
||||
.append(Component.text("- abbreviations: ", NamedTextColor.BLACK))
|
||||
.append(Component.text("dia", NamedTextColor.BLUE))
|
||||
.append(Component.text(" = diamond.", NamedTextColor.BLACK))
|
||||
.append(Component.newline())
|
||||
.append(Component.text("- shops are ", NamedTextColor.BLACK))
|
||||
.append(Component.text("off", NamedTextColor.RED))
|
||||
.append(Component.text(" by default.", NamedTextColor.BLACK))
|
||||
.build());
|
||||
|
||||
Book book = Book.book(Component.text("oyeshops manual"), Component.text("oyeshops"), pages);
|
||||
player.openBook(book);
|
||||
}
|
||||
}
|
||||
147
src/main/java/party/cybsec/oyeshops/gui/SetupDialog.java
Normal file
147
src/main/java/party/cybsec/oyeshops/gui/SetupDialog.java
Normal file
@@ -0,0 +1,147 @@
|
||||
package party.cybsec.oyeshops.gui;
|
||||
|
||||
import io.papermc.paper.dialog.Dialog;
|
||||
import io.papermc.paper.registry.data.dialog.DialogBase;
|
||||
import io.papermc.paper.registry.data.dialog.ActionButton;
|
||||
import io.papermc.paper.registry.data.dialog.type.DialogType;
|
||||
import io.papermc.paper.registry.data.dialog.action.DialogAction;
|
||||
import io.papermc.paper.registry.data.dialog.input.DialogInput;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickCallback;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import party.cybsec.oyeshops.OyeShopsPlugin;
|
||||
import party.cybsec.oyeshops.model.PendingActivation;
|
||||
import party.cybsec.oyeshops.model.Trade;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* opens a setup wizard for creating shops using paper's dialog api
|
||||
*/
|
||||
public class SetupDialog {
|
||||
|
||||
public static void open(Player player, Block signBlock, OyeShopsPlugin plugin) {
|
||||
Dialog dialog = Dialog.create(builder -> builder.empty()
|
||||
.base(DialogBase.builder(Component.text("shop setup wizard", NamedTextColor.GOLD))
|
||||
.inputs(List.of(
|
||||
// product (selling)
|
||||
DialogInput.text("product_item",
|
||||
Component.text("what are you selling? (e.g. oak log)",
|
||||
NamedTextColor.YELLOW))
|
||||
.build(),
|
||||
DialogInput.text("product_qty",
|
||||
Component.text("how many per purchase? (e.g. 64)",
|
||||
NamedTextColor.YELLOW))
|
||||
.initial("1")
|
||||
.build(),
|
||||
|
||||
// price (buying)
|
||||
DialogInput.text("price_item",
|
||||
Component.text("what do you want? (e.g. diamond)",
|
||||
NamedTextColor.GREEN))
|
||||
.build(),
|
||||
DialogInput.text("price_qty",
|
||||
Component.text("how many? (e.g. 10)",
|
||||
NamedTextColor.GREEN))
|
||||
.initial("1")
|
||||
.build()))
|
||||
.build())
|
||||
.type(DialogType.confirmation(
|
||||
ActionButton.builder(Component.text("create shop",
|
||||
TextColor.color(0xAEFFC1)))
|
||||
.tooltip(Component
|
||||
.text("click to confirm trade details"))
|
||||
.action(DialogAction.customClick((view, audience) -> {
|
||||
String productStr = view
|
||||
.getText("product_item");
|
||||
String productQtyStr = view
|
||||
.getText("product_qty");
|
||||
String priceStr = view.getText("price_item");
|
||||
String priceQtyStr = view.getText("price_qty");
|
||||
Player p = (Player) audience;
|
||||
|
||||
// 1. parse quantities
|
||||
int productQty;
|
||||
int priceQty;
|
||||
try {
|
||||
productQty = Integer.parseInt(
|
||||
productQtyStr);
|
||||
priceQty = Integer
|
||||
.parseInt(priceQtyStr);
|
||||
} catch (NumberFormatException e) {
|
||||
p.sendMessage(Component.text(
|
||||
"invalid quantity provided",
|
||||
NamedTextColor.RED));
|
||||
// reopen
|
||||
open(p, signBlock, plugin);
|
||||
return;
|
||||
}
|
||||
|
||||
if (productQty <= 0 || priceQty <= 0) {
|
||||
p.sendMessage(Component.text(
|
||||
"quantities must be positive",
|
||||
NamedTextColor.RED));
|
||||
open(p, signBlock, plugin);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. parse materials
|
||||
Material productMat = plugin.getSignParser()
|
||||
.parseMaterial(productStr);
|
||||
if (productMat == null) {
|
||||
p.sendMessage(Component.text(
|
||||
"invalid product item: "
|
||||
+ productStr,
|
||||
NamedTextColor.RED));
|
||||
open(p, signBlock, plugin);
|
||||
return;
|
||||
}
|
||||
|
||||
Material priceMat = plugin.getSignParser()
|
||||
.parseMaterial(priceStr);
|
||||
if (priceMat == null) {
|
||||
p.sendMessage(Component.text(
|
||||
"invalid payment item: "
|
||||
+ priceStr,
|
||||
NamedTextColor.RED));
|
||||
open(p, signBlock, plugin);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. create trade & activation
|
||||
Trade trade = new Trade(priceMat, priceQty,
|
||||
productMat, productQty);
|
||||
PendingActivation activation = new PendingActivation(
|
||||
p.getUniqueId(),
|
||||
signBlock.getLocation(),
|
||||
trade,
|
||||
System.currentTimeMillis());
|
||||
|
||||
// 4. finalize shop immediately
|
||||
plugin.getLogger().info(
|
||||
"DEBUG: SetupDialog creating shop at "
|
||||
+ signBlock.getLocation());
|
||||
plugin.getServer().getScheduler()
|
||||
.runTask(plugin, () -> {
|
||||
plugin.getShopActivationListener()
|
||||
.finalizeShop(p, activation);
|
||||
});
|
||||
}, ClickCallback.Options.builder().uses(1).build()))
|
||||
.build(),
|
||||
ActionButton.builder(
|
||||
Component.text("cancel", TextColor.color(0xFFA0B1)))
|
||||
.tooltip(Component.text("discard changes"))
|
||||
.action(DialogAction.customClick((view, audience) -> {
|
||||
((Player) audience).sendMessage(Component.text(
|
||||
"setup cancelled",
|
||||
NamedTextColor.YELLOW));
|
||||
}, ClickCallback.Options.builder().build()))
|
||||
.build())));
|
||||
|
||||
player.showDialog(dialog);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package party.cybsec.oyeshops.listener;
|
||||
|
||||
import party.cybsec.oyeshops.gui.SetupDialog;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
@@ -114,6 +116,14 @@ public class ShopActivationListener implements Listener {
|
||||
: "";
|
||||
}
|
||||
|
||||
// 3. check for setup wizard
|
||||
if (lines[0].equalsIgnoreCase("setup") && lines[1].isEmpty() && lines[2].isEmpty() && lines[3].isEmpty()) {
|
||||
// clear sign text to avoid "setup" staying on the sign
|
||||
event.line(0, Component.text(""));
|
||||
SetupDialog.open(player, block, plugin);
|
||||
return;
|
||||
}
|
||||
|
||||
// parse trade
|
||||
Trade trade = parser.parse(lines);
|
||||
if (trade == null) {
|
||||
@@ -188,15 +198,24 @@ public class ShopActivationListener implements Listener {
|
||||
|
||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
try {
|
||||
// cleanup potential existing shop to avoid unique constraint error
|
||||
plugin.getShopRepository().deleteShopByLocation(signLocation);
|
||||
|
||||
int shopId = plugin.getShopRepository().createShop(shop);
|
||||
plugin.getLogger().info("DEBUG: created shop id " + shopId + " at " + signLocation);
|
||||
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
||||
// re-verify sign on main thread
|
||||
if (!(signLocation.getBlock().getState() instanceof Sign finalSign))
|
||||
if (!(signLocation.getBlock().getState() instanceof Sign finalSign)) {
|
||||
plugin.getLogger().info("DEBUG: sign missing at " + signLocation);
|
||||
return;
|
||||
}
|
||||
|
||||
Shop registeredShop = new Shop(shopId, signLocation, player.getUniqueId(), trade, 0, true,
|
||||
createdAt);
|
||||
plugin.getShopRegistry().register(registeredShop);
|
||||
plugin.getLogger().info("DEBUG: registered shop " + shopId + " in registry");
|
||||
|
||||
rewriteSignLines(finalSign, trade);
|
||||
player.sendMessage(Component.text("shop #" + shopId + " initialized!", NamedTextColor.GREEN));
|
||||
});
|
||||
@@ -339,7 +358,7 @@ public class ShopActivationListener implements Listener {
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isContainer(Material material) {
|
||||
public static boolean isContainer(Material material) {
|
||||
return material == Material.CHEST || material == Material.TRAPPED_CHEST || material == Material.BARREL;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,11 +58,6 @@ public class SignParser {
|
||||
// concatenate all lines with spaces
|
||||
String fullText = String.join(" ", lines);
|
||||
|
||||
// REQUIREMENT: must contain "." to be parsed as a shop
|
||||
if (!fullText.contains(".")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// normalize text
|
||||
String normalized = normalize(fullText);
|
||||
|
||||
@@ -259,4 +254,8 @@ public class SignParser {
|
||||
|
||||
private record ItemQuantity(Material material, int quantity) {
|
||||
}
|
||||
|
||||
public Material parseMaterial(String name) {
|
||||
return aliasRegistry.resolve(name);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user