8 Commits
1.0.0 ... 1.1.0

Author SHA1 Message Date
37cb4453ac feat(gui): make help book commands clickable 2026-02-04 22:13:55 -05:00
02c53849cf fix(parser): remove mandatory period requirement for sign text 2026-02-04 22:12:52 -05:00
e8e4e35b6e fix(gui): repair help book formatting and pagination
- fixed numbered list on page 1

- split content into 9 pages to prevent text cutoff

- fixed unique constraint error by overwriting existing shops at location
2026-02-04 21:59:07 -05:00
087bf7eb40 fix(db): resolve unique constraint error by overwriting existing shops at location 2026-02-04 21:26:27 -05:00
3e1698614e fix(db): add migrations for missing shop columns on existing dbs 2026-02-04 21:20:05 -05:00
a53a4dac61 fix(dialog): fully integrate Paper Dialog API 1.21.11 via oyetickets reference 2026-02-04 21:07:34 -05:00
9eecc99f56 feat: setup command, help book updates, and db migration 2026-02-04 20:52:49 -05:00
e5bc3d1f14 added comprehensive interactive help book 2026-02-04 20:12:49 -05:00
10 changed files with 502 additions and 13 deletions

1
.gitignore vendored
View File

@@ -30,3 +30,4 @@ out/
# logs # logs
logs/ logs/
*.log *.log
oyetickets/

View File

@@ -9,10 +9,11 @@ description = "deterministic item-for-item chest barter"
repositories { repositories {
mavenCentral() mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/") maven("https://repo.papermc.io/repository/maven-public/")
maven("https://repo.papermc.io/repository/maven-snapshots/")
} }
dependencies { 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") implementation("org.xerial:sqlite-jdbc:3.47.1.0")
} }

View File

@@ -11,7 +11,11 @@ import party.cybsec.oyeshops.OyeShopsPlugin;
import party.cybsec.oyeshops.model.PendingActivation; import party.cybsec.oyeshops.model.PendingActivation;
import party.cybsec.oyeshops.model.Shop; import party.cybsec.oyeshops.model.Shop;
import party.cybsec.oyeshops.permission.PermissionManager; 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.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -42,12 +46,14 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
case "toggle" -> handleToggle(sender); case "toggle" -> handleToggle(sender);
case "notify" -> handleNotifyToggle(sender); case "notify" -> handleNotifyToggle(sender);
case "info" -> handleInfo(sender); case "info" -> handleInfo(sender);
case "help" -> handleHelp(sender);
case "setup" -> handleSetup(sender);
case "reload" -> handleReload(sender); case "reload" -> handleReload(sender);
case "inspect", "i" -> handleInspect(sender); case "inspect", "i" -> handleInspect(sender);
case "spoof", "s" -> handleSpoof(sender); case "spoof", "s" -> handleSpoof(sender);
case "unregister", "delete", "remove" -> handleUnregister(sender, args); case "unregister", "delete", "remove" -> handleUnregister(sender, args);
case "_activate" -> handleActivate(sender, args); case "_activate" -> handleActivate(sender, args);
default -> sendHelp(sender); default -> handleHelp(sender);
} }
return true; return true;
@@ -55,6 +61,10 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
private void sendHelp(CommandSender sender) { private void sendHelp(CommandSender sender) {
sender.sendMessage(Component.text("=== oyeshops commands ===", NamedTextColor.GOLD)); 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) sender.sendMessage(Component.text("/oyeshops on", NamedTextColor.YELLOW)
.append(Component.text(" - enable shop creation", NamedTextColor.GRAY))); .append(Component.text(" - enable shop creation", NamedTextColor.GRAY)));
sender.sendMessage(Component.text("/oyeshops off", NamedTextColor.YELLOW) 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)); 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) { private void handleReload(CommandSender sender) {
if (!PermissionManager.isAdmin(sender)) { if (!PermissionManager.isAdmin(sender)) {
sender.sendMessage(Component.text("no permission", NamedTextColor.RED)); sender.sendMessage(Component.text("no permission", NamedTextColor.RED));
@@ -236,6 +254,18 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
plugin.getPlayerPreferenceRepository().enableShops(player.getUniqueId()); plugin.getPlayerPreferenceRepository().enableShops(player.getUniqueId());
player.sendMessage(Component.text("shop creation enabled. you can now make chest shops!", player.sendMessage(Component.text("shop creation enabled. you can now make chest shops!",
NamedTextColor.GREEN)); 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) { } catch (SQLException e) {
player.sendMessage(Component.text("database error", NamedTextColor.RED)); player.sendMessage(Component.text("database error", NamedTextColor.RED));
e.printStackTrace(); e.printStackTrace();
@@ -342,7 +372,8 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) { public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
List<String> completions = new ArrayList<>(); List<String> completions = new ArrayList<>();
if (args.length == 1) { 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)) { if (PermissionManager.isAdmin(sender)) {
subCommands.addAll(List.of("reload", "inspect", "spoof", "unregister")); subCommands.addAll(List.of("reload", "inspect", "spoof", "unregister"));
} }
@@ -365,4 +396,43 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
} }
return completions; 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);
}
} }

View File

@@ -69,10 +69,31 @@ public class DatabaseManager {
player_uuid text primary key, player_uuid text primary key,
shops_enabled boolean not null default false, shops_enabled boolean not null default false,
notify_low_stock boolean not null default false, notify_low_stock boolean not null default false,
seen_intro boolean not null default false,
enabled_at integer 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 // indexes
stmt.execute(""" stmt.execute("""
create index if not exists idx_shop_location create index if not exists idx_shop_location

View File

@@ -15,9 +15,9 @@ import java.util.UUID;
public class PlayerPreferenceRepository { public class PlayerPreferenceRepository {
private final DatabaseManager dbManager; private final DatabaseManager dbManager;
// in-memory cache for performance
private final Set<UUID> enabledPlayers = new HashSet<>(); private final Set<UUID> enabledPlayers = new HashSet<>();
private final Set<UUID> notifyPlayers = new HashSet<>(); private final Set<UUID> notifyPlayers = new HashSet<>();
private final Set<UUID> seenIntroPlayers = new HashSet<>();
public PlayerPreferenceRepository(DatabaseManager dbManager) { public PlayerPreferenceRepository(DatabaseManager dbManager) {
this.dbManager = dbManager; this.dbManager = dbManager;
@@ -27,7 +27,7 @@ public class PlayerPreferenceRepository {
* load all player preferences into cache * load all player preferences into cache
*/ */
public void loadPreferences() throws SQLException { 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); try (PreparedStatement stmt = dbManager.getConnection().prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) { ResultSet rs = stmt.executeQuery()) {
@@ -39,6 +39,9 @@ public class PlayerPreferenceRepository {
if (rs.getBoolean("notify_low_stock")) { if (rs.getBoolean("notify_low_stock")) {
notifyPlayers.add(uuid); notifyPlayers.add(uuid);
} }
if (rs.getBoolean("seen_intro")) {
seenIntroPlayers.add(uuid);
}
} }
} }
} }
@@ -57,6 +60,31 @@ public class PlayerPreferenceRepository {
return notifyPlayers.contains(playerUuid); 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 * enable shops for player
*/ */

View File

@@ -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 * get all shops owned by player
*/ */

View 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);
}
}

View 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);
}
}

View File

@@ -1,5 +1,7 @@
package party.cybsec.oyeshops.listener; package party.cybsec.oyeshops.listener;
import party.cybsec.oyeshops.gui.SetupDialog;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent; 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 // parse trade
Trade trade = parser.parse(lines); Trade trade = parser.parse(lines);
if (trade == null) { if (trade == null) {
@@ -188,15 +198,24 @@ public class ShopActivationListener implements Listener {
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
try { try {
// cleanup potential existing shop to avoid unique constraint error
plugin.getShopRepository().deleteShopByLocation(signLocation);
int shopId = plugin.getShopRepository().createShop(shop); int shopId = plugin.getShopRepository().createShop(shop);
plugin.getLogger().info("DEBUG: created shop id " + shopId + " at " + signLocation);
plugin.getServer().getScheduler().runTask(plugin, () -> { plugin.getServer().getScheduler().runTask(plugin, () -> {
// re-verify sign on main thread // 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; return;
}
Shop registeredShop = new Shop(shopId, signLocation, player.getUniqueId(), trade, 0, true, Shop registeredShop = new Shop(shopId, signLocation, player.getUniqueId(), trade, 0, true,
createdAt); createdAt);
plugin.getShopRegistry().register(registeredShop); plugin.getShopRegistry().register(registeredShop);
plugin.getLogger().info("DEBUG: registered shop " + shopId + " in registry");
rewriteSignLines(finalSign, trade); rewriteSignLines(finalSign, trade);
player.sendMessage(Component.text("shop #" + shopId + " initialized!", NamedTextColor.GREEN)); player.sendMessage(Component.text("shop #" + shopId + " initialized!", NamedTextColor.GREEN));
}); });
@@ -339,7 +358,7 @@ public class ShopActivationListener implements Listener {
return null; return null;
} }
private boolean isContainer(Material material) { public static boolean isContainer(Material material) {
return material == Material.CHEST || material == Material.TRAPPED_CHEST || material == Material.BARREL; return material == Material.CHEST || material == Material.TRAPPED_CHEST || material == Material.BARREL;
} }

View File

@@ -58,11 +58,6 @@ public class SignParser {
// concatenate all lines with spaces // concatenate all lines with spaces
String fullText = String.join(" ", lines); String fullText = String.join(" ", lines);
// REQUIREMENT: must contain "." to be parsed as a shop
if (!fullText.contains(".")) {
return null;
}
// normalize text // normalize text
String normalized = normalize(fullText); String normalized = normalize(fullText);
@@ -259,4 +254,8 @@ public class SignParser {
private record ItemQuantity(Material material, int quantity) { private record ItemQuantity(Material material, int quantity) {
} }
public Material parseMaterial(String name) {
return aliasRegistry.resolve(name);
}
} }