added custom shit and fixed the double chest issue. and the setup sign issue
This commit is contained in:
@@ -13,6 +13,7 @@ import party.cybsec.oyeshops.model.Shop;
|
|||||||
import party.cybsec.oyeshops.permission.PermissionManager;
|
import party.cybsec.oyeshops.permission.PermissionManager;
|
||||||
import party.cybsec.oyeshops.gui.HelpBook;
|
import party.cybsec.oyeshops.gui.HelpBook;
|
||||||
import party.cybsec.oyeshops.gui.SetupDialog;
|
import party.cybsec.oyeshops.gui.SetupDialog;
|
||||||
|
import party.cybsec.oyeshops.gui.ConfigDialog;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.block.BlockFace;
|
import org.bukkit.block.BlockFace;
|
||||||
import org.bukkit.block.data.type.WallSign;
|
import org.bukkit.block.data.type.WallSign;
|
||||||
@@ -48,6 +49,7 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
case "info" -> handleInfo(sender);
|
case "info" -> handleInfo(sender);
|
||||||
case "help" -> handleHelp(sender);
|
case "help" -> handleHelp(sender);
|
||||||
case "setup" -> handleSetup(sender);
|
case "setup" -> handleSetup(sender);
|
||||||
|
case "config" -> handleConfig(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);
|
||||||
@@ -65,6 +67,8 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
.append(Component.text(" - open interactive guide", NamedTextColor.GRAY)));
|
.append(Component.text(" - open interactive guide", NamedTextColor.GRAY)));
|
||||||
sender.sendMessage(Component.text("/oyeshops setup", NamedTextColor.YELLOW)
|
sender.sendMessage(Component.text("/oyeshops setup", NamedTextColor.YELLOW)
|
||||||
.append(Component.text(" - open shop wizard", NamedTextColor.GRAY)));
|
.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)
|
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)
|
||||||
@@ -373,7 +377,7 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
List<String> completions = new ArrayList<>();
|
List<String> completions = new ArrayList<>();
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
List<String> subCommands = new ArrayList<>(
|
List<String> subCommands = new ArrayList<>(
|
||||||
List.of("help", "setup", "on", "off", "toggle", "notify", "info", "enable", "disable"));
|
List.of("help", "setup", "config", "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"));
|
||||||
}
|
}
|
||||||
@@ -435,4 +439,37 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
|
|
||||||
SetupDialog.open(player, block, plugin);
|
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;
|
package party.cybsec.oyeshops.config;
|
||||||
|
|
||||||
import org.bukkit.configuration.file.FileConfiguration;
|
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 {
|
public class ConfigManager {
|
||||||
private final Plugin plugin;
|
private final OyeShopsPlugin plugin;
|
||||||
private FileConfiguration config;
|
private FileConfiguration config;
|
||||||
|
|
||||||
public ConfigManager(Plugin plugin) {
|
public ConfigManager(OyeShopsPlugin plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
reload();
|
reload();
|
||||||
}
|
}
|
||||||
@@ -18,26 +18,23 @@ public class ConfigManager {
|
|||||||
public void reload() {
|
public void reload() {
|
||||||
plugin.saveDefaultConfig();
|
plugin.saveDefaultConfig();
|
||||||
plugin.reloadConfig();
|
plugin.reloadConfig();
|
||||||
this.config = plugin.getConfig();
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileConfiguration getConfig() {
|
public FileConfiguration getConfig() {
|
||||||
return config;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,25 @@ public class DatabaseManager {
|
|||||||
stmt.execute("alter table shops add column created_at integer not null default 0");
|
stmt.execute("alter table shops add column created_at integer not null default 0");
|
||||||
} catch (SQLException ignored) {
|
} 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
|
// indexes
|
||||||
stmt.execute("""
|
stmt.execute("""
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ public class ShopRepository {
|
|||||||
String sql = """
|
String sql = """
|
||||||
insert into shops (world_uuid, sign_x, sign_y, sign_z, owner_uuid,
|
insert into shops (world_uuid, sign_x, sign_y, sign_z, owner_uuid,
|
||||||
price_item, price_quantity, product_item, product_quantity,
|
price_item, price_quantity, product_item, product_quantity,
|
||||||
owed_amount, enabled, created_at)
|
owed_amount, enabled, created_at, custom_title,
|
||||||
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
cosmetic_sign, disc)
|
||||||
|
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""";
|
""";
|
||||||
|
|
||||||
try (PreparedStatement stmt = dbManager.getConnection().prepareStatement(sql,
|
try (PreparedStatement stmt = dbManager.getConnection().prepareStatement(sql,
|
||||||
@@ -48,6 +49,9 @@ public class ShopRepository {
|
|||||||
stmt.setInt(10, shop.getOwedAmount());
|
stmt.setInt(10, shop.getOwedAmount());
|
||||||
stmt.setBoolean(11, shop.isEnabled());
|
stmt.setBoolean(11, shop.isEnabled());
|
||||||
stmt.setLong(12, shop.getCreatedAt());
|
stmt.setLong(12, shop.getCreatedAt());
|
||||||
|
stmt.setString(13, shop.getCustomTitle());
|
||||||
|
stmt.setBoolean(14, shop.isCosmeticSign());
|
||||||
|
stmt.setString(15, shop.getDisc());
|
||||||
|
|
||||||
stmt.executeUpdate();
|
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
|
* delete shop
|
||||||
*/
|
*/
|
||||||
@@ -247,6 +268,9 @@ public class ShopRepository {
|
|||||||
int owedAmount = rs.getInt("owed_amount");
|
int owedAmount = rs.getInt("owed_amount");
|
||||||
boolean enabled = rs.getBoolean("enabled");
|
boolean enabled = rs.getBoolean("enabled");
|
||||||
long createdAt = rs.getLong("created_at");
|
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);
|
World world = Bukkit.getWorld(worldUuid);
|
||||||
if (world == null) {
|
if (world == null) {
|
||||||
@@ -256,6 +280,8 @@ public class ShopRepository {
|
|||||||
Location location = new Location(world, x, y, z);
|
Location location = new Location(world, x, y, z);
|
||||||
Trade trade = new Trade(priceItem, priceQty, productItem, productQty);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,6 @@ package party.cybsec.oyeshops.gui;
|
|||||||
|
|
||||||
import net.kyori.adventure.inventory.Book;
|
import net.kyori.adventure.inventory.Book;
|
||||||
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.HoverEvent;
|
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import net.kyori.adventure.text.format.TextDecoration;
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@@ -32,10 +30,7 @@ public class HelpBook {
|
|||||||
.append(Component.text("steps to start:", NamedTextColor.GRAY))
|
.append(Component.text("steps to start:", NamedTextColor.GRAY))
|
||||||
.append(Component.newline())
|
.append(Component.newline())
|
||||||
.append(Component.text("1. type ", NamedTextColor.BLACK))
|
.append(Component.text("1. type ", NamedTextColor.BLACK))
|
||||||
.append(Component.text("/oyes on", NamedTextColor.BLUE)
|
.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.newline())
|
||||||
.append(Component.text("2. place a chest", NamedTextColor.BLACK))
|
.append(Component.text("2. place a chest", NamedTextColor.BLACK))
|
||||||
.append(Component.newline())
|
.append(Component.newline())
|
||||||
@@ -53,10 +48,7 @@ public class HelpBook {
|
|||||||
.append(Component.text("1. look at a sign", NamedTextColor.GRAY))
|
.append(Component.text("1. look at a sign", NamedTextColor.GRAY))
|
||||||
.append(Component.newline())
|
.append(Component.newline())
|
||||||
.append(Component.text("2. type ", NamedTextColor.GRAY))
|
.append(Component.text("2. type ", NamedTextColor.GRAY))
|
||||||
.append(Component.text("/oyes setup", NamedTextColor.BLUE)
|
.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.newline())
|
.append(Component.newline())
|
||||||
.append(Component.text("...or turn the page", NamedTextColor.DARK_GRAY))
|
.append(Component.text("...or turn the page", NamedTextColor.DARK_GRAY))
|
||||||
@@ -150,18 +142,12 @@ public class HelpBook {
|
|||||||
.append(Component.text("commands", NamedTextColor.GOLD, TextDecoration.BOLD))
|
.append(Component.text("commands", NamedTextColor.GOLD, TextDecoration.BOLD))
|
||||||
.append(Component.newline())
|
.append(Component.newline())
|
||||||
.append(Component.newline())
|
.append(Component.newline())
|
||||||
.append(Component.text("/oyes notify", NamedTextColor.BLUE)
|
.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.newline())
|
||||||
.append(Component.text("get low stock alerts.", NamedTextColor.DARK_GRAY))
|
.append(Component.text("get low stock alerts.", NamedTextColor.DARK_GRAY))
|
||||||
.append(Component.newline())
|
.append(Component.newline())
|
||||||
.append(Component.newline())
|
.append(Component.newline())
|
||||||
.append(Component.text("/oyes info", NamedTextColor.BLUE)
|
.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.newline())
|
||||||
.append(Component.text("plugin info.", NamedTextColor.DARK_GRAY))
|
.append(Component.text("plugin info.", NamedTextColor.DARK_GRAY))
|
||||||
.build());
|
.build());
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ public class SetupDialog {
|
|||||||
.inputs(List.of(
|
.inputs(List.of(
|
||||||
// product (selling)
|
// product (selling)
|
||||||
DialogInput.text("product_item",
|
DialogInput.text("product_item",
|
||||||
Component.text("what are you selling? (e.g. oak log)",
|
Component.text("selling what? (e.g. dirt)",
|
||||||
NamedTextColor.YELLOW))
|
NamedTextColor.YELLOW))
|
||||||
.build(),
|
.build(),
|
||||||
DialogInput.text("product_qty",
|
DialogInput.text("product_qty",
|
||||||
Component.text("how many per purchase? (e.g. 64)",
|
Component.text("how many? (e.g. 1)",
|
||||||
NamedTextColor.YELLOW))
|
NamedTextColor.YELLOW))
|
||||||
.initial("1")
|
.initial("1")
|
||||||
.build(),
|
.build(),
|
||||||
@@ -48,6 +48,15 @@ public class SetupDialog {
|
|||||||
Component.text("how many? (e.g. 10)",
|
Component.text("how many? (e.g. 10)",
|
||||||
NamedTextColor.GREEN))
|
NamedTextColor.GREEN))
|
||||||
.initial("1")
|
.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()))
|
||||||
.build())
|
.build())
|
||||||
.type(DialogType.confirmation(
|
.type(DialogType.confirmation(
|
||||||
@@ -64,43 +73,20 @@ public class SetupDialog {
|
|||||||
String priceQtyStr = view.getText("price_qty");
|
String priceQtyStr = view.getText("price_qty");
|
||||||
Player p = (Player) audience;
|
Player p = (Player) audience;
|
||||||
|
|
||||||
// 1. parse quantities
|
// 1. parse price qty
|
||||||
int productQty;
|
|
||||||
int priceQty;
|
int priceQty;
|
||||||
try {
|
try {
|
||||||
productQty = Integer.parseInt(
|
|
||||||
productQtyStr);
|
|
||||||
priceQty = Integer
|
priceQty = Integer
|
||||||
.parseInt(priceQtyStr);
|
.parseInt(priceQtyStr);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
p.sendMessage(Component.text(
|
p.sendMessage(Component.text(
|
||||||
"invalid quantity provided",
|
"invalid price quantity",
|
||||||
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));
|
NamedTextColor.RED));
|
||||||
open(p, signBlock, plugin);
|
open(p, signBlock, plugin);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. parse price material
|
||||||
Material priceMat = plugin.getSignParser()
|
Material priceMat = plugin.getSignParser()
|
||||||
.parseMaterial(priceStr);
|
.parseMaterial(priceStr);
|
||||||
if (priceMat == null) {
|
if (priceMat == null) {
|
||||||
@@ -112,19 +98,43 @@ public class SetupDialog {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. create trade & activation
|
// 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,
|
Trade trade = new Trade(priceMat, priceQty,
|
||||||
productMat, productQty);
|
productMat, productQty);
|
||||||
|
|
||||||
|
boolean cosmeticSign = view
|
||||||
|
.getBoolean("cosmetic_sign");
|
||||||
|
|
||||||
PendingActivation activation = new PendingActivation(
|
PendingActivation activation = new PendingActivation(
|
||||||
p.getUniqueId(),
|
p.getUniqueId(),
|
||||||
signBlock.getLocation(),
|
signBlock.getLocation(),
|
||||||
trade,
|
trade,
|
||||||
System.currentTimeMillis());
|
System.currentTimeMillis(),
|
||||||
|
cosmeticSign);
|
||||||
|
|
||||||
// 4. finalize shop immediately
|
// 4. finalize
|
||||||
plugin.getLogger().info(
|
|
||||||
"DEBUG: SetupDialog creating shop at "
|
|
||||||
+ signBlock.getLocation());
|
|
||||||
plugin.getServer().getScheduler()
|
plugin.getServer().getScheduler()
|
||||||
.runTask(plugin, () -> {
|
.runTask(plugin, () -> {
|
||||||
plugin.getShopActivationListener()
|
plugin.getShopActivationListener()
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import net.kyori.adventure.text.Component;
|
|||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.Sound;
|
||||||
import org.bukkit.block.Barrel;
|
import org.bukkit.block.Barrel;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.block.BlockFace;
|
import org.bukkit.block.BlockFace;
|
||||||
import org.bukkit.block.Chest;
|
import org.bukkit.block.Chest;
|
||||||
|
import org.bukkit.block.DoubleChest;
|
||||||
import org.bukkit.block.data.type.WallSign;
|
import org.bukkit.block.data.type.WallSign;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
@@ -15,7 +17,9 @@ import org.bukkit.event.EventPriority;
|
|||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.block.Action;
|
import org.bukkit.event.block.Action;
|
||||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||||
import org.bukkit.event.player.PlayerInteractEvent;
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
|
import org.bukkit.inventory.DoubleChestInventory;
|
||||||
import org.bukkit.inventory.Inventory;
|
import org.bukkit.inventory.Inventory;
|
||||||
import org.bukkit.inventory.InventoryHolder;
|
import org.bukkit.inventory.InventoryHolder;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
@@ -59,7 +63,8 @@ public class ChestInteractionListener implements Listener {
|
|||||||
return;
|
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);
|
Shop shop = findShopForContainer(block);
|
||||||
if (shop == null) {
|
if (shop == null) {
|
||||||
return; // not a shop container
|
return; // not a shop container
|
||||||
@@ -138,7 +143,7 @@ public class ChestInteractionListener implements Listener {
|
|||||||
// update database
|
// update database
|
||||||
int newOwed = owed - withdrawn;
|
int newOwed = owed - withdrawn;
|
||||||
shop.setOwedAmount(newOwed);
|
shop.setOwedAmount(newOwed);
|
||||||
plugin.getShopRepository().updateOwedAmount(shop.getId(), -withdrawn);
|
plugin.getShopRepository().updateOwedAmount(shop.getId(), newOwed);
|
||||||
|
|
||||||
player.sendMessage(
|
player.sendMessage(
|
||||||
Component.text("withdrew " + withdrawn + " " + formatMaterial(priceItem), NamedTextColor.GREEN)
|
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) {
|
private void openShopGui(Player player, Shop shop, Block containerBlock) {
|
||||||
Trade trade = shop.getTrade();
|
Trade trade = shop.getTrade();
|
||||||
|
|
||||||
// create fake inventory showing only product items
|
// create title
|
||||||
Inventory shopInventory = Bukkit.createInventory(
|
Component title = Component.text("shop: " + trade.priceQuantity() + " " + formatMaterial(trade.priceItem()) +
|
||||||
new ShopInventoryHolder(shop),
|
" → " + trade.productQuantity() + " " + formatMaterial(trade.productItem()));
|
||||||
27,
|
|
||||||
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);
|
Inventory realInventory = getContainerInventory(containerBlock);
|
||||||
if (realInventory == null) {
|
if (realInventory == null) {
|
||||||
return;
|
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;
|
int shopSlot = 0;
|
||||||
for (ItemStack item : realInventory.getContents()) {
|
for (ItemStack item : realInventory.getContents()) {
|
||||||
if (shopSlot >= 27)
|
if (shopSlot >= invSize)
|
||||||
break;
|
break;
|
||||||
if (item != null && item.getType() == trade.productItem()) {
|
if (item != null && trade.matchesProduct(item.getType())) {
|
||||||
shopInventory.setItem(shopSlot++, item.clone());
|
shopInventory.setItem(shopSlot++, item.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewingShop.put(player, shop);
|
viewingShop.put(player, shop);
|
||||||
player.openInventory(shopInventory);
|
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
|
@EventHandler
|
||||||
@@ -202,20 +266,24 @@ public class ChestInteractionListener implements Listener {
|
|||||||
// if clicked in the shop inventory area
|
// if clicked in the shop inventory area
|
||||||
if (event.getRawSlot() < event.getInventory().getSize()) {
|
if (event.getRawSlot() < event.getInventory().getSize()) {
|
||||||
ItemStack clicked = event.getCurrentItem();
|
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
|
// determine quantity based on click type
|
||||||
int units = 1;
|
int units = 1;
|
||||||
if (event.isShiftClick()) {
|
if (event.isShiftClick()) {
|
||||||
// shift-click: calculate max units based on items clicked
|
// shift-click: calculate max units based on items clicked
|
||||||
units = clicked.getAmount() / shop.getTrade().productQuantity();
|
units = clicked.getAmount() / trade.productQuantity();
|
||||||
if (units < 1)
|
if (units < 1)
|
||||||
units = 1;
|
units = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// open confirmation GUI
|
// open confirmation gui
|
||||||
player.closeInventory();
|
player.closeInventory();
|
||||||
openConfirmationGui(player, shop, units);
|
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;
|
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
|
* 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) {
|
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
|
// 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 }) {
|
for (BlockFace face : new BlockFace[] { BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST }) {
|
||||||
Block adjacent = containerBlock.getRelative(face);
|
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) {
|
private Inventory getContainerInventory(Block block) {
|
||||||
if (block.getState() instanceof Chest chest) {
|
if (block.getState() instanceof Chest chest) {
|
||||||
return chest.getInventory();
|
return chest.getInventory(); // returns DoubleChestInventory if double chest
|
||||||
} else if (block.getState() instanceof Barrel barrel) {
|
} else if (block.getState() instanceof Barrel barrel) {
|
||||||
return barrel.getInventory();
|
return barrel.getInventory();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,8 +116,15 @@ public class ShopActivationListener implements Listener {
|
|||||||
: "";
|
: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. check for setup wizard
|
// check for setup wizard
|
||||||
if (lines[0].equalsIgnoreCase("setup") && lines[1].isEmpty() && lines[2].isEmpty() && lines[3].isEmpty()) {
|
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
|
// clear sign text to avoid "setup" staying on the sign
|
||||||
event.line(0, Component.text(""));
|
event.line(0, Component.text(""));
|
||||||
SetupDialog.open(player, block, plugin);
|
SetupDialog.open(player, block, plugin);
|
||||||
@@ -142,7 +149,7 @@ public class ShopActivationListener implements Listener {
|
|||||||
|
|
||||||
// trigger confirmation instead of immediate creation
|
// trigger confirmation instead of immediate creation
|
||||||
PendingActivation activation = new PendingActivation(player.getUniqueId(), block.getLocation(), trade,
|
PendingActivation activation = new PendingActivation(player.getUniqueId(), block.getLocation(), trade,
|
||||||
System.currentTimeMillis());
|
System.currentTimeMillis(), false);
|
||||||
plugin.getActivationManager().add(player.getUniqueId(), activation);
|
plugin.getActivationManager().add(player.getUniqueId(), activation);
|
||||||
|
|
||||||
sendConfirmationMessage(player, trade);
|
sendConfirmationMessage(player, trade);
|
||||||
@@ -187,14 +194,15 @@ public class ShopActivationListener implements Listener {
|
|||||||
Block block = signLocation.getBlock();
|
Block block = signLocation.getBlock();
|
||||||
|
|
||||||
// verify it's still a sign
|
// 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));
|
player.sendMessage(Component.text("activation failed: sign is gone", NamedTextColor.RED));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Trade trade = activation.trade();
|
Trade trade = activation.trade();
|
||||||
long createdAt = System.currentTimeMillis();
|
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, () -> {
|
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
try {
|
try {
|
||||||
@@ -212,11 +220,11 @@ public class ShopActivationListener implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Shop registeredShop = new Shop(shopId, signLocation, player.getUniqueId(), trade, 0, true,
|
Shop registeredShop = new Shop(shopId, signLocation, player.getUniqueId(), trade, 0, true,
|
||||||
createdAt);
|
createdAt, null, activation.cosmeticSign(), null);
|
||||||
plugin.getShopRegistry().register(registeredShop);
|
plugin.getShopRegistry().register(registeredShop);
|
||||||
plugin.getLogger().info("DEBUG: registered shop " + shopId + " in registry");
|
plugin.getLogger().info("DEBUG: registered shop " + shopId + " in registry");
|
||||||
|
|
||||||
rewriteSignLines(finalSign, trade);
|
rewriteSignLines(finalSign, registeredShop);
|
||||||
player.sendMessage(Component.text("shop #" + shopId + " initialized!", NamedTextColor.GREEN));
|
player.sendMessage(Component.text("shop #" + shopId + " initialized!", NamedTextColor.GREEN));
|
||||||
});
|
});
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
@@ -228,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 pricePart = trade.priceQuantity() + " " + abbreviateMaterial(trade.priceItem());
|
||||||
|
|
||||||
String productPart = trade.productQuantity() + " " + abbreviateMaterial(trade.productItem());
|
String productPart = trade.productQuantity() + " " + abbreviateMaterial(trade.productItem());
|
||||||
|
|
||||||
sign.line(0, Component.text(pricePart));
|
sign.line(0, Component.text(pricePart));
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ public record PendingActivation(
|
|||||||
UUID owner,
|
UUID owner,
|
||||||
Location location,
|
Location location,
|
||||||
Trade trade,
|
Trade trade,
|
||||||
long createdAt) {
|
long createdAt,
|
||||||
|
boolean cosmeticSign) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* check if this activation has expired
|
* check if this activation has expired
|
||||||
@@ -28,6 +29,6 @@ public record PendingActivation(
|
|||||||
trade.productQuantity(),
|
trade.productQuantity(),
|
||||||
trade.priceItem(),
|
trade.priceItem(),
|
||||||
trade.priceQuantity());
|
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 int owedAmount;
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private final long createdAt;
|
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,
|
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.id = id;
|
||||||
this.signLocation = signLocation;
|
this.signLocation = signLocation;
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
@@ -25,6 +28,9 @@ public class Shop {
|
|||||||
this.owedAmount = owedAmount;
|
this.owedAmount = owedAmount;
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
this.createdAt = createdAt;
|
this.createdAt = createdAt;
|
||||||
|
this.customTitle = customTitle;
|
||||||
|
this.cosmeticSign = cosmeticSign;
|
||||||
|
this.disc = disc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getId() {
|
public int getId() {
|
||||||
@@ -63,6 +69,30 @@ public class Shop {
|
|||||||
return createdAt;
|
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
|
* get chest location from sign location
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import org.bukkit.Material;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* immutable trade definition
|
* 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(
|
public record Trade(
|
||||||
Material priceItem,
|
Material priceItem,
|
||||||
@@ -12,38 +12,46 @@ public record Trade(
|
|||||||
Material productItem,
|
Material productItem,
|
||||||
int productQuantity) {
|
int productQuantity) {
|
||||||
|
|
||||||
|
// primary constructor
|
||||||
public Trade {
|
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) {
|
if (priceQuantity <= 0 && priceQuantity != -1) {
|
||||||
throw new IllegalArgumentException("price quantity must be positive or -1 for auto");
|
throw new IllegalArgumentException("price quantity must be positive or -1 for auto");
|
||||||
}
|
}
|
||||||
if (productQuantity <= 0 && productQuantity != -1) {
|
if (productQuantity <= 0 && productQuantity != -1) {
|
||||||
throw new IllegalArgumentException("product quantity must be positive or -1 for auto");
|
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) {
|
if (priceQuantity != -1 && productQuantity != -1 && priceItem == productItem && priceItem != Material.AIR) {
|
||||||
throw new IllegalArgumentException("price and product must be different materials");
|
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() {
|
public boolean isAutoProduct() {
|
||||||
return productQuantity == -1;
|
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() {
|
public boolean isAutoPrice() {
|
||||||
return priceQuantity == -1;
|
return priceQuantity == -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* check if this trade needs any AUTO detection
|
* check if this trade needs any auto detection
|
||||||
*/
|
*/
|
||||||
public boolean isAutoDetect() {
|
public boolean isAutoDetect() {
|
||||||
return isAutoProduct() || isAutoPrice();
|
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
|
// short forms
|
||||||
Map.entry("dia", "diamond"),
|
Map.entry("dia", "diamond"),
|
||||||
|
Map.entry("d", "diamond"),
|
||||||
Map.entry("dias", "diamond"),
|
Map.entry("dias", "diamond"),
|
||||||
Map.entry("em", "emerald"),
|
Map.entry("em", "emerald"),
|
||||||
Map.entry("ems", "emerald"),
|
Map.entry("ems", "emerald"),
|
||||||
@@ -133,6 +134,7 @@ public class MaterialAliasRegistry {
|
|||||||
Map.entry("netherrack", "netherrack"),
|
Map.entry("netherrack", "netherrack"),
|
||||||
Map.entry("endstone", "end_stone"),
|
Map.entry("endstone", "end_stone"),
|
||||||
Map.entry("end stone", "end_stone"),
|
Map.entry("end stone", "end_stone"),
|
||||||
|
Map.entry("bamboo block", "bamboo_block"),
|
||||||
|
|
||||||
// food
|
// food
|
||||||
Map.entry("steak", "cooked_beef"),
|
Map.entry("steak", "cooked_beef"),
|
||||||
@@ -202,13 +204,13 @@ public class MaterialAliasRegistry {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* resolve material from normalized text
|
* resolve material from normalized text
|
||||||
* tries multiple strategies:
|
* priority order:
|
||||||
* 1. exact alias match (longest first)
|
* 1. exact full text match (space to underscore) - handles "bamboo block" ->
|
||||||
* 2. word-by-word alias match
|
* BAMBOO_BLOCK
|
||||||
* 3. space-to-underscore conversion for direct enum match
|
* 2. exact alias match (longest first for multi-word aliases)
|
||||||
* 4. direct material enum match
|
* 3. direct material enum match for each word
|
||||||
* 5. with _INGOT/_BLOCK suffixes
|
* 4. with _INGOT/_BLOCK suffixes
|
||||||
* 6. strip trailing 's' for plurals
|
* 5. strip trailing 's' for plurals
|
||||||
*/
|
*/
|
||||||
public Material resolve(String text) {
|
public Material resolve(String text) {
|
||||||
text = text.toLowerCase().trim();
|
text = text.toLowerCase().trim();
|
||||||
@@ -217,13 +219,25 @@ public class MaterialAliasRegistry {
|
|||||||
return null;
|
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;
|
String longestMatch = null;
|
||||||
Material longestMaterial = null;
|
Material longestMaterial = null;
|
||||||
|
|
||||||
for (Map.Entry<String, Material> entry : aliases.entrySet()) {
|
for (Map.Entry<String, Material> entry : aliases.entrySet()) {
|
||||||
String alias = entry.getKey();
|
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()) {
|
if (longestMatch == null || alias.length() > longestMatch.length()) {
|
||||||
longestMatch = alias;
|
longestMatch = alias;
|
||||||
longestMaterial = entry.getValue();
|
longestMaterial = entry.getValue();
|
||||||
@@ -235,30 +249,19 @@ public class MaterialAliasRegistry {
|
|||||||
return longestMaterial;
|
return longestMaterial;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. try word-by-word alias match
|
// 3. try each word directly as material
|
||||||
String[] words = text.split("\\s+");
|
String[] words = materialText.split("\\s+");
|
||||||
for (String word : words) {
|
for (String word : words) {
|
||||||
if (aliases.containsKey(word)) {
|
if (aliases.containsKey(word)) {
|
||||||
return aliases.get(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 {
|
try {
|
||||||
return Material.valueOf(word.toUpperCase());
|
return Material.valueOf(word.toUpperCase());
|
||||||
} catch (IllegalArgumentException ignored) {
|
} catch (IllegalArgumentException ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. try with common suffixes
|
// 4. try with common suffixes
|
||||||
try {
|
try {
|
||||||
return Material.valueOf(word.toUpperCase() + "_INGOT");
|
return Material.valueOf(word.toUpperCase() + "_INGOT");
|
||||||
} catch (IllegalArgumentException ignored) {
|
} catch (IllegalArgumentException ignored) {
|
||||||
@@ -269,7 +272,7 @@ public class MaterialAliasRegistry {
|
|||||||
} catch (IllegalArgumentException ignored) {
|
} catch (IllegalArgumentException ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. try stripping trailing 's' for plurals
|
// 5. try stripping trailing 's' for plurals
|
||||||
if (word.endsWith("s") && word.length() > 1) {
|
if (word.endsWith("s") && word.length() > 1) {
|
||||||
String singular = word.substring(0, word.length() - 1);
|
String singular = word.substring(0, word.length() - 1);
|
||||||
try {
|
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()) {
|
for (Material material : Material.values()) {
|
||||||
String materialName = material.name().toLowerCase();
|
String materialName = material.name().toLowerCase();
|
||||||
String materialSpaced = materialName.replace("_", " ");
|
String materialSpaced = materialName.replace("_", " ");
|
||||||
|
|
||||||
if (text.contains(materialSpaced) || text.contains(materialName)) {
|
if (materialText.equals(materialSpaced) || materialText.equals(materialName)) {
|
||||||
if (longestMatch == null || materialName.length() > longestMatch.length()) {
|
return material; // exact match
|
||||||
longestMatch = materialName;
|
}
|
||||||
longestMaterial = material;
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public class SignParser {
|
|||||||
* parse sign lines into a trade
|
* parse sign lines into a trade
|
||||||
*
|
*
|
||||||
* @return trade if valid, null if invalid or ambiguous
|
* @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) {
|
public Trade parse(String[] lines) {
|
||||||
// concatenate all lines with spaces
|
// concatenate all lines with spaces
|
||||||
@@ -74,6 +74,7 @@ public class SignParser {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check for auto detection
|
||||||
boolean isAuto = productSection.toLowerCase().contains(AUTO_KEYWORD);
|
boolean isAuto = productSection.toLowerCase().contains(AUTO_KEYWORD);
|
||||||
ItemQuantity product;
|
ItemQuantity product;
|
||||||
if (isAuto) {
|
if (isAuto) {
|
||||||
|
|||||||
@@ -97,6 +97,25 @@ public class ShopRegistry {
|
|||||||
shopsById.clear();
|
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
|
* create location key for map lookup
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -97,22 +97,7 @@ public class TransactionManager {
|
|||||||
return Result.INVENTORY_FULL;
|
return Result.INVENTORY_FULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update owed amount in database
|
finalizeTransaction(buyer, shop, units, totalPrice);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.SUCCESS;
|
return Result.SUCCESS;
|
||||||
|
|
||||||
} catch (SQLException e) {
|
} 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
|
* check if inventory has required items
|
||||||
*/
|
*/
|
||||||
@@ -185,7 +188,7 @@ public class TransactionManager {
|
|||||||
/**
|
/**
|
||||||
* get the shop's container inventory
|
* get the shop's container inventory
|
||||||
*/
|
*/
|
||||||
private Inventory getShopInventory(Shop shop) {
|
public Inventory getShopInventory(Shop shop) {
|
||||||
Location signLoc = shop.getSignLocation();
|
Location signLoc = shop.getSignLocation();
|
||||||
Block signBlock = signLoc.getBlock();
|
Block signBlock = signLoc.getBlock();
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,11 @@
|
|||||||
# hopper protection
|
# oyeShops configuration
|
||||||
hoppers:
|
|
||||||
allow-product-output: true
|
|
||||||
block-price-input: true
|
|
||||||
|
|
||||||
# transaction history
|
# transaction settings
|
||||||
history:
|
transactions:
|
||||||
max-transactions-per-shop: 100
|
auto-prune: false
|
||||||
auto-prune: true
|
max-per-shop: 100
|
||||||
|
|
||||||
# material aliases
|
# custom material aliases (add your own shortcuts)
|
||||||
aliases:
|
aliases:
|
||||||
# common abbreviations
|
# example:
|
||||||
dia: diamond
|
# myalias: 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
|
|
||||||
|
|||||||
Reference in New Issue
Block a user