Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 191a02234e | |||
| 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,12 @@ 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 party.cybsec.oyeshops.gui.ConfigDialog;
|
||||
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 +47,15 @@ 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 "config" -> handleConfig(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 +63,12 @@ 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 config", NamedTextColor.YELLOW)
|
||||
.append(Component.text(" - configure looking shop", 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 +100,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 +258,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 +376,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", "config", "on", "off", "toggle", "notify", "info", "enable", "disable"));
|
||||
if (PermissionManager.isAdmin(sender)) {
|
||||
subCommands.addAll(List.of("reload", "inspect", "spoof", "unregister"));
|
||||
}
|
||||
@@ -365,4 +400,76 @@ 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);
|
||||
}
|
||||
|
||||
private void handleConfig(CommandSender sender) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage(Component.text("players only", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
Block block = player.getTargetBlockExact(5);
|
||||
if (block == null) {
|
||||
player.sendMessage(Component.text("you must look at a shop sign or its container", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
Shop shop = null;
|
||||
if (block.getBlockData() instanceof WallSign) {
|
||||
shop = plugin.getShopRegistry().getShop(block.getLocation());
|
||||
} else if (party.cybsec.oyeshops.listener.ShopActivationListener.isContainer(block.getType())) {
|
||||
// try to find shop on any of the adjacent wall signs
|
||||
shop = plugin.getShopRegistry().getShopByContainer(block);
|
||||
}
|
||||
|
||||
if (shop == null) {
|
||||
player.sendMessage(Component.text("that is not a part of any shop", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shop.getOwner().equals(player.getUniqueId()) && !PermissionManager.isAdmin(player)) {
|
||||
player.sendMessage(Component.text("you do not own this shop", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigDialog.open(player, shop, plugin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package party.cybsec.oyeshops.config;
|
||||
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import party.cybsec.oyeshops.OyeShopsPlugin;
|
||||
|
||||
/**
|
||||
* configuration loading and management
|
||||
* manages plugin configuration
|
||||
*/
|
||||
public class ConfigManager {
|
||||
private final Plugin plugin;
|
||||
private final OyeShopsPlugin plugin;
|
||||
private FileConfiguration config;
|
||||
|
||||
public ConfigManager(Plugin plugin) {
|
||||
public ConfigManager(OyeShopsPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
reload();
|
||||
}
|
||||
@@ -18,26 +18,23 @@ public class ConfigManager {
|
||||
public void reload() {
|
||||
plugin.saveDefaultConfig();
|
||||
plugin.reloadConfig();
|
||||
this.config = plugin.getConfig();
|
||||
}
|
||||
|
||||
public boolean isAllowProductOutput() {
|
||||
return config.getBoolean("hoppers.allow-product-output", true);
|
||||
}
|
||||
|
||||
public boolean isBlockPriceInput() {
|
||||
return config.getBoolean("hoppers.block-price-input", true);
|
||||
}
|
||||
|
||||
public int getMaxTransactionsPerShop() {
|
||||
return config.getInt("history.max-transactions-per-shop", 100);
|
||||
}
|
||||
|
||||
public boolean isAutoPrune() {
|
||||
return config.getBoolean("history.auto-prune", true);
|
||||
config = plugin.getConfig();
|
||||
}
|
||||
|
||||
public FileConfiguration getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
// transaction settings
|
||||
public boolean isAutoPrune() {
|
||||
return config.getBoolean("transactions.auto-prune", false);
|
||||
}
|
||||
|
||||
public int getMaxTransactionsPerShop() {
|
||||
return config.getInt("transactions.max-per-shop", 100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,10 +69,50 @@ 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) {
|
||||
}
|
||||
try {
|
||||
stmt.execute("alter table shops add column merchant_ui boolean not null default false");
|
||||
} catch (SQLException ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
stmt.execute("alter table shops add column cosmetic_sign boolean not null default false");
|
||||
} catch (SQLException ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
stmt.execute("alter table shops add column custom_title text");
|
||||
} catch (SQLException ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
stmt.execute("alter table shops add column disc text");
|
||||
} 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
|
||||
*/
|
||||
|
||||
@@ -29,8 +29,9 @@ public class ShopRepository {
|
||||
String sql = """
|
||||
insert into shops (world_uuid, sign_x, sign_y, sign_z, owner_uuid,
|
||||
price_item, price_quantity, product_item, product_quantity,
|
||||
owed_amount, enabled, created_at)
|
||||
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
owed_amount, enabled, created_at, custom_title,
|
||||
cosmetic_sign, disc)
|
||||
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
|
||||
try (PreparedStatement stmt = dbManager.getConnection().prepareStatement(sql,
|
||||
@@ -48,6 +49,9 @@ public class ShopRepository {
|
||||
stmt.setInt(10, shop.getOwedAmount());
|
||||
stmt.setBoolean(11, shop.isEnabled());
|
||||
stmt.setLong(12, shop.getCreatedAt());
|
||||
stmt.setString(13, shop.getCustomTitle());
|
||||
stmt.setBoolean(14, shop.isCosmeticSign());
|
||||
stmt.setString(15, shop.getDisc());
|
||||
|
||||
stmt.executeUpdate();
|
||||
|
||||
@@ -166,6 +170,23 @@ public class ShopRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update shop configuration fields
|
||||
*/
|
||||
public void updateShopConfig(Shop shop) throws SQLException {
|
||||
String sql = """
|
||||
update shops set custom_title = ?, disc = ?
|
||||
where shop_id = ?
|
||||
""";
|
||||
|
||||
try (PreparedStatement stmt = dbManager.getConnection().prepareStatement(sql)) {
|
||||
stmt.setString(1, shop.getCustomTitle());
|
||||
stmt.setString(2, shop.getDisc());
|
||||
stmt.setInt(3, shop.getId());
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* delete shop
|
||||
*/
|
||||
@@ -178,6 +199,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
|
||||
*/
|
||||
@@ -232,6 +268,9 @@ public class ShopRepository {
|
||||
int owedAmount = rs.getInt("owed_amount");
|
||||
boolean enabled = rs.getBoolean("enabled");
|
||||
long createdAt = rs.getLong("created_at");
|
||||
String customTitle = rs.getString("custom_title");
|
||||
boolean cosmeticSign = rs.getBoolean("cosmetic_sign");
|
||||
String disc = rs.getString("disc");
|
||||
|
||||
World world = Bukkit.getWorld(worldUuid);
|
||||
if (world == null) {
|
||||
@@ -241,6 +280,8 @@ public class ShopRepository {
|
||||
Location location = new Location(world, x, y, z);
|
||||
Trade trade = new Trade(priceItem, priceQty, productItem, productQty);
|
||||
|
||||
return new Shop(id, location, ownerUuid, trade, owedAmount, enabled, createdAt);
|
||||
return new Shop(id, location, ownerUuid, trade, owedAmount, enabled, createdAt,
|
||||
customTitle, cosmeticSign, disc);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
95
src/main/java/party/cybsec/oyeshops/gui/ConfigDialog.java
Normal file
95
src/main/java/party/cybsec/oyeshops/gui/ConfigDialog.java
Normal file
@@ -0,0 +1,95 @@
|
||||
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.entity.Player;
|
||||
import party.cybsec.oyeshops.OyeShopsPlugin;
|
||||
import party.cybsec.oyeshops.model.Shop;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* config dialog for shop settings
|
||||
*/
|
||||
public class ConfigDialog {
|
||||
|
||||
// valid disc names
|
||||
private static final Set<String> VALID_DISCS = Set.of(
|
||||
"none", "blocks", "chirp", "far", "mall", "mellohi", "stal", "strad", "ward", "wait");
|
||||
|
||||
/**
|
||||
* open config dialog for a specific shop
|
||||
*/
|
||||
public static void open(Player player, Shop shop, OyeShopsPlugin plugin) {
|
||||
Dialog dialog = Dialog.create(builder -> builder.empty()
|
||||
.base(DialogBase.builder(Component.text("shop #" + shop.getId() + " config", NamedTextColor.GOLD))
|
||||
.inputs(List.of(
|
||||
DialogInput.text("custom_title",
|
||||
Component.text("custom title (optional)", NamedTextColor.YELLOW))
|
||||
.initial(shop.getCustomTitle() != null ? shop.getCustomTitle() : "")
|
||||
.build(),
|
||||
DialogInput.text("disc",
|
||||
Component.text(
|
||||
"music disc: none/blocks/chirp/far/mall/mellohi/stal/strad/ward/wait",
|
||||
NamedTextColor.AQUA))
|
||||
.initial(shop.getDisc() != null ? shop.getDisc() : "none")
|
||||
.build()))
|
||||
.build())
|
||||
.type(DialogType.confirmation(
|
||||
ActionButton.builder(Component.text("save", TextColor.color(0xB0FFA0)))
|
||||
.tooltip(Component.text("save configuration"))
|
||||
.action(DialogAction.customClick((view, audience) -> {
|
||||
Player p = (Player) audience;
|
||||
String title = view.getText("custom_title");
|
||||
String disc = view.getText("disc");
|
||||
|
||||
// validate disc
|
||||
disc = disc.toLowerCase().trim();
|
||||
if (!disc.isEmpty() && !VALID_DISCS.contains(disc)) {
|
||||
p.sendMessage(Component.text("invalid disc: " + disc
|
||||
+ ". valid options: none, blocks, chirp, far, mall, mellohi, stal, strad, ward, wait",
|
||||
NamedTextColor.RED));
|
||||
open(p, shop, plugin); // reopen dialog
|
||||
return;
|
||||
}
|
||||
|
||||
shop.setCustomTitle(title.isEmpty() ? null : title);
|
||||
shop.setDisc(disc.isEmpty() || disc.equals("none") ? null : disc);
|
||||
|
||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
try {
|
||||
plugin.getShopRepository().updateShopConfig(shop);
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
||||
p.sendMessage(
|
||||
Component.text("shop config saved", NamedTextColor.GREEN));
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
||||
p.sendMessage(Component.text("database error", NamedTextColor.RED));
|
||||
});
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}, 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("config cancelled", NamedTextColor.YELLOW));
|
||||
}, ClickCallback.Options.builder().build()))
|
||||
.build())));
|
||||
|
||||
player.showDialog(dialog);
|
||||
}
|
||||
}
|
||||
174
src/main/java/party/cybsec/oyeshops/gui/HelpBook.java
Normal file
174
src/main/java/party/cybsec/oyeshops/gui/HelpBook.java
Normal file
@@ -0,0 +1,174 @@
|
||||
package party.cybsec.oyeshops.gui;
|
||||
|
||||
import net.kyori.adventure.inventory.Book;
|
||||
import net.kyori.adventure.text.Component;
|
||||
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))
|
||||
.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))
|
||||
.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))
|
||||
.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))
|
||||
.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);
|
||||
}
|
||||
}
|
||||
157
src/main/java/party/cybsec/oyeshops/gui/SetupDialog.java
Normal file
157
src/main/java/party/cybsec/oyeshops/gui/SetupDialog.java
Normal file
@@ -0,0 +1,157 @@
|
||||
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("selling what? (e.g. dirt)",
|
||||
NamedTextColor.YELLOW))
|
||||
.build(),
|
||||
DialogInput.text("product_qty",
|
||||
Component.text("how many? (e.g. 1)",
|
||||
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(),
|
||||
|
||||
// cosmetic sign toggle
|
||||
DialogInput.bool("cosmetic_sign",
|
||||
Component.text("cosmetic sign? (don't rewrite text)",
|
||||
NamedTextColor.AQUA))
|
||||
.initial(false)
|
||||
.onTrue("enabled")
|
||||
.onFalse("disabled")
|
||||
.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 price qty
|
||||
int priceQty;
|
||||
try {
|
||||
priceQty = Integer
|
||||
.parseInt(priceQtyStr);
|
||||
} catch (NumberFormatException e) {
|
||||
p.sendMessage(Component.text(
|
||||
"invalid price quantity",
|
||||
NamedTextColor.RED));
|
||||
open(p, signBlock, plugin);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. parse price material
|
||||
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. Regular parsing logic
|
||||
int productQty;
|
||||
try {
|
||||
productQty = Integer.parseInt(
|
||||
productQtyStr);
|
||||
} catch (NumberFormatException e) {
|
||||
p.sendMessage(Component.text(
|
||||
"invalid product quantity",
|
||||
NamedTextColor.RED));
|
||||
open(p, signBlock, plugin);
|
||||
return;
|
||||
}
|
||||
Material productMat = plugin
|
||||
.getSignParser()
|
||||
.parseMaterial(productStr);
|
||||
if (productMat == null) {
|
||||
p.sendMessage(Component.text(
|
||||
"invalid product: "
|
||||
+ productStr,
|
||||
NamedTextColor.RED));
|
||||
open(p, signBlock, plugin);
|
||||
return;
|
||||
}
|
||||
Trade trade = new Trade(priceMat, priceQty,
|
||||
productMat, productQty);
|
||||
|
||||
boolean cosmeticSign = view
|
||||
.getBoolean("cosmetic_sign");
|
||||
|
||||
PendingActivation activation = new PendingActivation(
|
||||
p.getUniqueId(),
|
||||
signBlock.getLocation(),
|
||||
trade,
|
||||
System.currentTimeMillis(),
|
||||
cosmeticSign);
|
||||
|
||||
// 4. finalize
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,12 @@ import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.block.Barrel;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.Chest;
|
||||
import org.bukkit.block.DoubleChest;
|
||||
import org.bukkit.block.data.type.WallSign;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@@ -15,7 +17,9 @@ import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.DoubleChestInventory;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
@@ -59,7 +63,8 @@ public class ChestInteractionListener implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if there's a shop sign attached
|
||||
// check if there's a shop sign attached to this container or its double chest
|
||||
// partner
|
||||
Shop shop = findShopForContainer(block);
|
||||
if (shop == null) {
|
||||
return; // not a shop container
|
||||
@@ -138,7 +143,7 @@ public class ChestInteractionListener implements Listener {
|
||||
// update database
|
||||
int newOwed = owed - withdrawn;
|
||||
shop.setOwedAmount(newOwed);
|
||||
plugin.getShopRepository().updateOwedAmount(shop.getId(), -withdrawn);
|
||||
plugin.getShopRepository().updateOwedAmount(shop.getId(), newOwed);
|
||||
|
||||
player.sendMessage(
|
||||
Component.text("withdrew " + withdrawn + " " + formatMaterial(priceItem), NamedTextColor.GREEN)
|
||||
@@ -154,36 +159,95 @@ public class ChestInteractionListener implements Listener {
|
||||
}
|
||||
|
||||
/**
|
||||
* open shop GUI for buyer
|
||||
* open shop gui for buyer
|
||||
*/
|
||||
private void openShopGui(Player player, Shop shop, Block containerBlock) {
|
||||
Trade trade = shop.getTrade();
|
||||
|
||||
// create fake inventory showing only product items
|
||||
Inventory shopInventory = Bukkit.createInventory(
|
||||
new ShopInventoryHolder(shop),
|
||||
27,
|
||||
Component.text("shop: " + trade.priceQuantity() + " " + formatMaterial(trade.priceItem()) +
|
||||
" → " + trade.productQuantity() + " " + formatMaterial(trade.productItem())));
|
||||
// create title
|
||||
Component title = Component.text("shop: " + trade.priceQuantity() + " " + formatMaterial(trade.priceItem()) +
|
||||
" → " + trade.productQuantity() + " " + formatMaterial(trade.productItem()));
|
||||
|
||||
// get real container inventory
|
||||
// respect custom title if set
|
||||
if (shop.getCustomTitle() != null && !shop.getCustomTitle().isEmpty()) {
|
||||
title = Component.text(shop.getCustomTitle());
|
||||
}
|
||||
|
||||
// get real container inventory (handles double chests correctly)
|
||||
Inventory realInventory = getContainerInventory(containerBlock);
|
||||
if (realInventory == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// copy only product items to fake inventory (first 27 found)
|
||||
// determine inventory size based on container type
|
||||
int invSize = realInventory.getSize();
|
||||
if (invSize > 54)
|
||||
invSize = 54; // cap at double chest
|
||||
if (invSize < 27)
|
||||
invSize = 27; // minimum single chest
|
||||
|
||||
// create fake inventory showing only product items
|
||||
Inventory shopInventory = Bukkit.createInventory(
|
||||
new ShopInventoryHolder(shop),
|
||||
invSize,
|
||||
title);
|
||||
|
||||
// copy matching items to fake inventory
|
||||
int shopSlot = 0;
|
||||
for (ItemStack item : realInventory.getContents()) {
|
||||
if (shopSlot >= 27)
|
||||
if (shopSlot >= invSize)
|
||||
break;
|
||||
if (item != null && item.getType() == trade.productItem()) {
|
||||
if (item != null && trade.matchesProduct(item.getType())) {
|
||||
shopInventory.setItem(shopSlot++, item.clone());
|
||||
}
|
||||
}
|
||||
|
||||
viewingShop.put(player, shop);
|
||||
player.openInventory(shopInventory);
|
||||
|
||||
// play shop owner's configured disc if set
|
||||
String discName = shop.getDisc();
|
||||
if (discName != null && !discName.isEmpty()) {
|
||||
playDisc(player, discName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* play a music disc for a player
|
||||
*/
|
||||
private void playDisc(Player player, String discName) {
|
||||
Sound discSound = getDiscSound(discName);
|
||||
if (discSound != null) {
|
||||
player.playSound(player.getLocation(), discSound, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* stop a music disc for a player
|
||||
*/
|
||||
private void stopDisc(Player player, String discName) {
|
||||
Sound discSound = getDiscSound(discName);
|
||||
if (discSound != null) {
|
||||
player.stopSound(discSound);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get Sound enum for disc name
|
||||
*/
|
||||
private Sound getDiscSound(String discName) {
|
||||
return switch (discName.toLowerCase()) {
|
||||
case "blocks" -> Sound.MUSIC_DISC_BLOCKS;
|
||||
case "chirp" -> Sound.MUSIC_DISC_CHIRP;
|
||||
case "far" -> Sound.MUSIC_DISC_FAR;
|
||||
case "mall" -> Sound.MUSIC_DISC_MALL;
|
||||
case "mellohi" -> Sound.MUSIC_DISC_MELLOHI;
|
||||
case "stal" -> Sound.MUSIC_DISC_STAL;
|
||||
case "strad" -> Sound.MUSIC_DISC_STRAD;
|
||||
case "ward" -> Sound.MUSIC_DISC_WARD;
|
||||
case "wait" -> Sound.MUSIC_DISC_WAIT;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@@ -202,20 +266,24 @@ public class ChestInteractionListener implements Listener {
|
||||
// if clicked in the shop inventory area
|
||||
if (event.getRawSlot() < event.getInventory().getSize()) {
|
||||
ItemStack clicked = event.getCurrentItem();
|
||||
if (clicked != null && clicked.getType() == shop.getTrade().productItem()) {
|
||||
Trade trade = shop.getTrade();
|
||||
if (clicked != null && trade.matchesProduct(clicked.getType())) {
|
||||
// determine quantity based on click type
|
||||
int units = 1;
|
||||
if (event.isShiftClick()) {
|
||||
// shift-click: calculate max units based on items clicked
|
||||
units = clicked.getAmount() / shop.getTrade().productQuantity();
|
||||
units = clicked.getAmount() / trade.productQuantity();
|
||||
if (units < 1)
|
||||
units = 1;
|
||||
}
|
||||
|
||||
// open confirmation GUI
|
||||
// open confirmation gui
|
||||
player.closeInventory();
|
||||
openConfirmationGui(player, shop, units);
|
||||
}
|
||||
} else {
|
||||
// clicked in player inventory - play negative feedback sound
|
||||
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1.0f, 1.0f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -240,6 +308,20 @@ public class ChestInteractionListener implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClose(InventoryCloseEvent event) {
|
||||
if (event.getPlayer() instanceof Player player) {
|
||||
// stop disc when closing shop
|
||||
Shop shop = viewingShop.remove(player);
|
||||
if (shop != null) {
|
||||
String discName = shop.getDisc();
|
||||
if (discName != null && !discName.isEmpty()) {
|
||||
stopDisc(player, discName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* open confirmation GUI
|
||||
*/
|
||||
@@ -267,9 +349,46 @@ public class ChestInteractionListener implements Listener {
|
||||
}
|
||||
|
||||
/**
|
||||
* find shop attached to a container block
|
||||
* find shop attached to a container block (handles double chests)
|
||||
*/
|
||||
private Shop findShopForContainer(Block containerBlock) {
|
||||
// first check this block directly
|
||||
Shop shop = findShopOnBlock(containerBlock);
|
||||
if (shop != null) {
|
||||
return shop;
|
||||
}
|
||||
|
||||
// if this is a chest, check the other half of a double chest
|
||||
if (containerBlock.getState() instanceof Chest chest) {
|
||||
Inventory inv = chest.getInventory();
|
||||
if (inv instanceof DoubleChestInventory doubleInv) {
|
||||
DoubleChest doubleChest = doubleInv.getHolder();
|
||||
if (doubleChest != null) {
|
||||
// check both sides
|
||||
Chest left = (Chest) doubleChest.getLeftSide();
|
||||
Chest right = (Chest) doubleChest.getRightSide();
|
||||
|
||||
if (left != null) {
|
||||
shop = findShopOnBlock(left.getBlock());
|
||||
if (shop != null)
|
||||
return shop;
|
||||
}
|
||||
if (right != null) {
|
||||
shop = findShopOnBlock(right.getBlock());
|
||||
if (shop != null)
|
||||
return shop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* find shop sign on a specific block
|
||||
*/
|
||||
private Shop findShopOnBlock(Block containerBlock) {
|
||||
// check all adjacent blocks for a wall sign pointing at this container
|
||||
for (BlockFace face : new BlockFace[] { BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST }) {
|
||||
Block adjacent = containerBlock.getRelative(face);
|
||||
@@ -287,11 +406,12 @@ public class ChestInteractionListener implements Listener {
|
||||
}
|
||||
|
||||
/**
|
||||
* get container inventory
|
||||
* get container inventory (handles double chests to return full 54 slot
|
||||
* inventory)
|
||||
*/
|
||||
private Inventory getContainerInventory(Block block) {
|
||||
if (block.getState() instanceof Chest chest) {
|
||||
return chest.getInventory();
|
||||
return chest.getInventory(); // returns DoubleChestInventory if double chest
|
||||
} else if (block.getState() instanceof Barrel barrel) {
|
||||
return barrel.getInventory();
|
||||
}
|
||||
|
||||
@@ -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,21 @@ public class ShopActivationListener implements Listener {
|
||||
: "";
|
||||
}
|
||||
|
||||
// check for setup wizard
|
||||
if (lines[0].equalsIgnoreCase("setup") && lines[1].isEmpty() && lines[2].isEmpty() && lines[3].isEmpty()) {
|
||||
// check if player has use permission (needed to activate shops)
|
||||
if (!PermissionManager.canUse(player)) {
|
||||
player.sendMessage(
|
||||
Component.text("you need oyeshops.use permission to use the setup wizard", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -132,7 +149,7 @@ public class ShopActivationListener implements Listener {
|
||||
|
||||
// trigger confirmation instead of immediate creation
|
||||
PendingActivation activation = new PendingActivation(player.getUniqueId(), block.getLocation(), trade,
|
||||
System.currentTimeMillis());
|
||||
System.currentTimeMillis(), false);
|
||||
plugin.getActivationManager().add(player.getUniqueId(), activation);
|
||||
|
||||
sendConfirmationMessage(player, trade);
|
||||
@@ -177,27 +194,37 @@ public class ShopActivationListener implements Listener {
|
||||
Block block = signLocation.getBlock();
|
||||
|
||||
// verify it's still a sign
|
||||
if (!(block.getState() instanceof Sign sign)) {
|
||||
if (!(block.getState() instanceof Sign)) {
|
||||
player.sendMessage(Component.text("activation failed: sign is gone", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
Trade trade = activation.trade();
|
||||
long createdAt = System.currentTimeMillis();
|
||||
Shop shop = new Shop(-1, signLocation, player.getUniqueId(), trade, 0, true, createdAt);
|
||||
Shop shop = new Shop(-1, signLocation, player.getUniqueId(), trade, 0, true, createdAt, null,
|
||||
activation.cosmeticSign(), null);
|
||||
|
||||
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);
|
||||
createdAt, null, activation.cosmeticSign(), null);
|
||||
plugin.getShopRegistry().register(registeredShop);
|
||||
rewriteSignLines(finalSign, trade);
|
||||
plugin.getLogger().info("DEBUG: registered shop " + shopId + " in registry");
|
||||
|
||||
rewriteSignLines(finalSign, registeredShop);
|
||||
player.sendMessage(Component.text("shop #" + shopId + " initialized!", NamedTextColor.GREEN));
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
@@ -209,8 +236,14 @@ public class ShopActivationListener implements Listener {
|
||||
});
|
||||
}
|
||||
|
||||
private void rewriteSignLines(Sign sign, Trade trade) {
|
||||
public void rewriteSignLines(Sign sign, Shop shop) {
|
||||
if (shop.isCosmeticSign()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Trade trade = shop.getTrade();
|
||||
String pricePart = trade.priceQuantity() + " " + abbreviateMaterial(trade.priceItem());
|
||||
|
||||
String productPart = trade.productQuantity() + " " + abbreviateMaterial(trade.productItem());
|
||||
|
||||
sign.line(0, Component.text(pricePart));
|
||||
@@ -339,7 +372,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ public record PendingActivation(
|
||||
UUID owner,
|
||||
Location location,
|
||||
Trade trade,
|
||||
long createdAt) {
|
||||
long createdAt,
|
||||
boolean cosmeticSign) {
|
||||
|
||||
/**
|
||||
* check if this activation has expired
|
||||
@@ -28,6 +29,6 @@ public record PendingActivation(
|
||||
trade.productQuantity(),
|
||||
trade.priceItem(),
|
||||
trade.priceQuantity());
|
||||
return new PendingActivation(owner, location, invertedTrade, createdAt);
|
||||
return new PendingActivation(owner, location, invertedTrade, createdAt, cosmeticSign);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,12 @@ public class Shop {
|
||||
private int owedAmount;
|
||||
private boolean enabled;
|
||||
private final long createdAt;
|
||||
private String customTitle;
|
||||
private boolean cosmeticSign;
|
||||
private String disc;
|
||||
|
||||
public Shop(int id, Location signLocation, UUID owner, Trade trade, int owedAmount, boolean enabled,
|
||||
long createdAt) {
|
||||
long createdAt, String customTitle, boolean cosmeticSign, String disc) {
|
||||
this.id = id;
|
||||
this.signLocation = signLocation;
|
||||
this.owner = owner;
|
||||
@@ -25,6 +28,9 @@ public class Shop {
|
||||
this.owedAmount = owedAmount;
|
||||
this.enabled = enabled;
|
||||
this.createdAt = createdAt;
|
||||
this.customTitle = customTitle;
|
||||
this.cosmeticSign = cosmeticSign;
|
||||
this.disc = disc;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
@@ -63,6 +69,30 @@ public class Shop {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public String getCustomTitle() {
|
||||
return customTitle;
|
||||
}
|
||||
|
||||
public void setCustomTitle(String customTitle) {
|
||||
this.customTitle = customTitle;
|
||||
}
|
||||
|
||||
public boolean isCosmeticSign() {
|
||||
return cosmeticSign;
|
||||
}
|
||||
|
||||
public void setCosmeticSign(boolean cosmeticSign) {
|
||||
this.cosmeticSign = cosmeticSign;
|
||||
}
|
||||
|
||||
public String getDisc() {
|
||||
return disc;
|
||||
}
|
||||
|
||||
public void setDisc(String disc) {
|
||||
this.disc = disc;
|
||||
}
|
||||
|
||||
/**
|
||||
* get chest location from sign location
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,7 @@ import org.bukkit.Material;
|
||||
|
||||
/**
|
||||
* immutable trade definition
|
||||
* quantity of -1 indicates AUTO detection needed for that side
|
||||
* quantity of -1 indicates auto detection needed for that side
|
||||
*/
|
||||
public record Trade(
|
||||
Material priceItem,
|
||||
@@ -12,38 +12,46 @@ public record Trade(
|
||||
Material productItem,
|
||||
int productQuantity) {
|
||||
|
||||
// primary constructor
|
||||
public Trade {
|
||||
// allow -1 for AUTO detection, but otherwise must be positive
|
||||
// allow -1 for auto detection, but otherwise must be positive
|
||||
if (priceQuantity <= 0 && priceQuantity != -1) {
|
||||
throw new IllegalArgumentException("price quantity must be positive or -1 for auto");
|
||||
}
|
||||
if (productQuantity <= 0 && productQuantity != -1) {
|
||||
throw new IllegalArgumentException("product quantity must be positive or -1 for auto");
|
||||
}
|
||||
// for AUTO, materials might be AIR (unknown)
|
||||
// for auto, materials might be air (unknown)
|
||||
if (priceQuantity != -1 && productQuantity != -1 && priceItem == productItem && priceItem != Material.AIR) {
|
||||
throw new IllegalArgumentException("price and product must be different materials");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if this trade needs AUTO detection on the product side
|
||||
* check if this trade needs auto detection on the product side
|
||||
*/
|
||||
public boolean isAutoProduct() {
|
||||
return productQuantity == -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if this trade needs AUTO detection on the price side
|
||||
* check if this trade needs auto detection on the price side
|
||||
*/
|
||||
public boolean isAutoPrice() {
|
||||
return priceQuantity == -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if this trade needs any AUTO detection
|
||||
* check if this trade needs any auto detection
|
||||
*/
|
||||
public boolean isAutoDetect() {
|
||||
return isAutoProduct() || isAutoPrice();
|
||||
}
|
||||
|
||||
/**
|
||||
* check if a material matches this trade's product
|
||||
*/
|
||||
public boolean matchesProduct(Material material) {
|
||||
return productItem == material;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ public class MaterialAliasRegistry {
|
||||
|
||||
// short forms
|
||||
Map.entry("dia", "diamond"),
|
||||
Map.entry("d", "diamond"),
|
||||
Map.entry("dias", "diamond"),
|
||||
Map.entry("em", "emerald"),
|
||||
Map.entry("ems", "emerald"),
|
||||
@@ -133,6 +134,7 @@ public class MaterialAliasRegistry {
|
||||
Map.entry("netherrack", "netherrack"),
|
||||
Map.entry("endstone", "end_stone"),
|
||||
Map.entry("end stone", "end_stone"),
|
||||
Map.entry("bamboo block", "bamboo_block"),
|
||||
|
||||
// food
|
||||
Map.entry("steak", "cooked_beef"),
|
||||
@@ -202,13 +204,13 @@ public class MaterialAliasRegistry {
|
||||
|
||||
/**
|
||||
* resolve material from normalized text
|
||||
* tries multiple strategies:
|
||||
* 1. exact alias match (longest first)
|
||||
* 2. word-by-word alias match
|
||||
* 3. space-to-underscore conversion for direct enum match
|
||||
* 4. direct material enum match
|
||||
* 5. with _INGOT/_BLOCK suffixes
|
||||
* 6. strip trailing 's' for plurals
|
||||
* priority order:
|
||||
* 1. exact full text match (space to underscore) - handles "bamboo block" ->
|
||||
* BAMBOO_BLOCK
|
||||
* 2. exact alias match (longest first for multi-word aliases)
|
||||
* 3. direct material enum match for each word
|
||||
* 4. with _INGOT/_BLOCK suffixes
|
||||
* 5. strip trailing 's' for plurals
|
||||
*/
|
||||
public Material resolve(String text) {
|
||||
text = text.toLowerCase().trim();
|
||||
@@ -217,13 +219,25 @@ public class MaterialAliasRegistry {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 1. try longest alias match first (for multi-word aliases)
|
||||
// remove numbers and extra whitespace for material matching
|
||||
String materialText = text.replaceAll("\\d+", "").trim().replaceAll("\\s+", " ");
|
||||
|
||||
// 1. try exact full text match with underscore conversion first
|
||||
// this handles "bamboo block" -> "bamboo_block" -> BAMBOO_BLOCK
|
||||
String underscored = materialText.replace(" ", "_");
|
||||
try {
|
||||
return Material.valueOf(underscored.toUpperCase());
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
|
||||
// 2. try exact alias match (longest first for multi-word aliases)
|
||||
String longestMatch = null;
|
||||
Material longestMaterial = null;
|
||||
|
||||
for (Map.Entry<String, Material> entry : aliases.entrySet()) {
|
||||
String alias = entry.getKey();
|
||||
if (text.contains(alias)) {
|
||||
// check if the text equals or contains the alias
|
||||
if (materialText.equals(alias) || materialText.contains(alias)) {
|
||||
if (longestMatch == null || alias.length() > longestMatch.length()) {
|
||||
longestMatch = alias;
|
||||
longestMaterial = entry.getValue();
|
||||
@@ -235,30 +249,19 @@ public class MaterialAliasRegistry {
|
||||
return longestMaterial;
|
||||
}
|
||||
|
||||
// 2. try word-by-word alias match
|
||||
String[] words = text.split("\\s+");
|
||||
// 3. try each word directly as material
|
||||
String[] words = materialText.split("\\s+");
|
||||
for (String word : words) {
|
||||
if (aliases.containsKey(word)) {
|
||||
return aliases.get(word);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. try space-to-underscore conversion for multi-word materials
|
||||
// e.g., "netherite pickaxe" -> "netherite_pickaxe"
|
||||
String underscored = text.replace(" ", "_");
|
||||
try {
|
||||
return Material.valueOf(underscored.toUpperCase());
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
|
||||
// 4. try each word directly as material
|
||||
for (String word : words) {
|
||||
try {
|
||||
return Material.valueOf(word.toUpperCase());
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
|
||||
// 5. try with common suffixes
|
||||
// 4. try with common suffixes
|
||||
try {
|
||||
return Material.valueOf(word.toUpperCase() + "_INGOT");
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
@@ -269,7 +272,7 @@ public class MaterialAliasRegistry {
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
|
||||
// 6. try stripping trailing 's' for plurals
|
||||
// 5. try stripping trailing 's' for plurals
|
||||
if (word.endsWith("s") && word.length() > 1) {
|
||||
String singular = word.substring(0, word.length() - 1);
|
||||
try {
|
||||
@@ -279,19 +282,30 @@ public class MaterialAliasRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
// 7. try the whole text with underscores for complex names
|
||||
// 6. last resort: scan all materials for longest match
|
||||
Material bestMatch = null;
|
||||
int bestLength = 0;
|
||||
|
||||
for (Material material : Material.values()) {
|
||||
String materialName = material.name().toLowerCase();
|
||||
String materialSpaced = materialName.replace("_", " ");
|
||||
|
||||
if (text.contains(materialSpaced) || text.contains(materialName)) {
|
||||
if (longestMatch == null || materialName.length() > longestMatch.length()) {
|
||||
longestMatch = materialName;
|
||||
longestMaterial = material;
|
||||
if (materialText.equals(materialSpaced) || materialText.equals(materialName)) {
|
||||
return material; // exact match
|
||||
}
|
||||
|
||||
if (materialText.contains(materialSpaced) || materialText.contains(materialName)) {
|
||||
if (materialName.length() > bestLength) {
|
||||
bestLength = materialName.length();
|
||||
bestMatch = material;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return longestMaterial;
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
public Material parseMaterial(String name) {
|
||||
return resolve(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,19 +50,14 @@ public class SignParser {
|
||||
|
||||
/**
|
||||
* parse sign lines into a trade
|
||||
*
|
||||
*
|
||||
* @return trade if valid, null if invalid or ambiguous
|
||||
* trade with quantity -1 means AUTO detection needed
|
||||
* trade with quantity -1 means auto detection needed
|
||||
*/
|
||||
public Trade parse(String[] lines) {
|
||||
// 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);
|
||||
|
||||
@@ -79,6 +74,7 @@ public class SignParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
// check for auto detection
|
||||
boolean isAuto = productSection.toLowerCase().contains(AUTO_KEYWORD);
|
||||
ItemQuantity product;
|
||||
if (isAuto) {
|
||||
@@ -259,4 +255,8 @@ public class SignParser {
|
||||
|
||||
private record ItemQuantity(Material material, int quantity) {
|
||||
}
|
||||
|
||||
public Material parseMaterial(String name) {
|
||||
return aliasRegistry.resolve(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,25 @@ public class ShopRegistry {
|
||||
shopsById.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* find shop by container block (checks adjacent faces for signs)
|
||||
*/
|
||||
public Shop getShopByContainer(org.bukkit.block.Block container) {
|
||||
for (org.bukkit.block.BlockFace face : new org.bukkit.block.BlockFace[] {
|
||||
org.bukkit.block.BlockFace.NORTH, org.bukkit.block.BlockFace.SOUTH,
|
||||
org.bukkit.block.BlockFace.EAST, org.bukkit.block.BlockFace.WEST }) {
|
||||
org.bukkit.block.Block adjacent = container.getRelative(face);
|
||||
if (adjacent.getBlockData() instanceof org.bukkit.block.data.type.WallSign wallSign) {
|
||||
if (wallSign.getFacing().getOppositeFace() == face.getOppositeFace()) {
|
||||
Shop shop = getShop(adjacent.getLocation());
|
||||
if (shop != null)
|
||||
return shop;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* create location key for map lookup
|
||||
*/
|
||||
|
||||
@@ -97,22 +97,7 @@ public class TransactionManager {
|
||||
return Result.INVENTORY_FULL;
|
||||
}
|
||||
|
||||
// update owed amount in database
|
||||
plugin.getShopRepository().updateOwedAmount(shop.getId(), totalPrice);
|
||||
shop.setOwedAmount(shop.getOwedAmount() + totalPrice);
|
||||
|
||||
// record transaction
|
||||
plugin.getTransactionRepository().recordTransaction(
|
||||
shop.getId(),
|
||||
buyer.getUniqueId(),
|
||||
units);
|
||||
|
||||
// prune old transactions if configured
|
||||
if (plugin.getConfigManager().isAutoPrune()) {
|
||||
int maxHistory = plugin.getConfigManager().getMaxTransactionsPerShop();
|
||||
plugin.getTransactionRepository().pruneTransactions(shop.getId(), maxHistory);
|
||||
}
|
||||
|
||||
finalizeTransaction(buyer, shop, units, totalPrice);
|
||||
return Result.SUCCESS;
|
||||
|
||||
} catch (SQLException e) {
|
||||
@@ -124,6 +109,24 @@ public class TransactionManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void finalizeTransaction(Player buyer, Shop shop, int units, int totalPrice) throws SQLException {
|
||||
// update owed amount in database
|
||||
plugin.getShopRepository().updateOwedAmount(shop.getId(), shop.getOwedAmount() + totalPrice);
|
||||
shop.setOwedAmount(shop.getOwedAmount() + totalPrice);
|
||||
|
||||
// record transaction
|
||||
plugin.getTransactionRepository().recordTransaction(
|
||||
shop.getId(),
|
||||
buyer != null ? buyer.getUniqueId() : null,
|
||||
units);
|
||||
|
||||
// prune old transactions if configured
|
||||
if (plugin.getConfigManager().isAutoPrune()) {
|
||||
int maxHistory = plugin.getConfigManager().getMaxTransactionsPerShop();
|
||||
plugin.getTransactionRepository().pruneTransactions(shop.getId(), maxHistory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if inventory has required items
|
||||
*/
|
||||
@@ -185,7 +188,7 @@ public class TransactionManager {
|
||||
/**
|
||||
* get the shop's container inventory
|
||||
*/
|
||||
private Inventory getShopInventory(Shop shop) {
|
||||
public Inventory getShopInventory(Shop shop) {
|
||||
Location signLoc = shop.getSignLocation();
|
||||
Block signBlock = signLoc.getBlock();
|
||||
|
||||
|
||||
@@ -1,29 +1,11 @@
|
||||
# hopper protection
|
||||
hoppers:
|
||||
allow-product-output: true
|
||||
block-price-input: true
|
||||
# oyeShops configuration
|
||||
|
||||
# transaction history
|
||||
history:
|
||||
max-transactions-per-shop: 100
|
||||
auto-prune: true
|
||||
# transaction settings
|
||||
transactions:
|
||||
auto-prune: false
|
||||
max-per-shop: 100
|
||||
|
||||
# material aliases
|
||||
# custom material aliases (add your own shortcuts)
|
||||
aliases:
|
||||
# common abbreviations
|
||||
dia: diamond
|
||||
dias: diamond
|
||||
iron: iron_ingot
|
||||
gold: gold_ingot
|
||||
emerald: emerald
|
||||
ems: emerald
|
||||
|
||||
# blocks
|
||||
stone: stone
|
||||
dirt: dirt
|
||||
cobble: cobblestone
|
||||
|
||||
# tools
|
||||
pick: diamond_pickaxe
|
||||
sword: diamond_sword
|
||||
axe: diamond_axe
|
||||
# example:
|
||||
# myalias: diamond
|
||||
|
||||
Reference in New Issue
Block a user