diff --git a/gradle.properties b/gradle.properties index ff14496..28b18ca 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.21.1 yarn_mappings=1.21.1+build.3 loader_version=0.16.2 # Mod Properties -mod_version=1.2 +mod_version=1.3 maven_group=systems.brn archives_base_name=servershop # Dependencies diff --git a/src/main/java/systems/brn/servershop/ServerShop.java b/src/main/java/systems/brn/servershop/ServerShop.java index f3cbeb9..02ab49a 100644 --- a/src/main/java/systems/brn/servershop/ServerShop.java +++ b/src/main/java/systems/brn/servershop/ServerShop.java @@ -1,137 +1,35 @@ package systems.brn.servershop; -import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.arguments.IntegerArgumentType; -import com.mojang.brigadier.arguments.LongArgumentType; import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; -import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; -import net.minecraft.command.CommandRegistryAccess; -import net.minecraft.command.argument.EntityArgumentType; -import net.minecraft.command.argument.ItemStackArgumentType; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.command.CommandManager; -import net.minecraft.server.command.ServerCommandSource; -import net.minecraft.server.network.ServerPlayNetworkHandler; -import net.minecraft.server.network.ServerPlayerEntity; import systems.brn.servershop.commands.*; -import systems.brn.servershop.lib.BalanceManager; -import systems.brn.servershop.lib.PriceStorage; - -import java.util.HashMap; - -import static net.minecraft.server.command.CommandManager.argument; -import static net.minecraft.server.command.CommandManager.literal; +import systems.brn.servershop.lib.EventHandler; +import systems.brn.servershop.lib.storages.AuctionStorage; +import systems.brn.servershop.lib.storages.BalanceStorage; +import systems.brn.servershop.lib.storages.PriceStorage; public class ServerShop implements ModInitializer { public static PriceStorage priceStorage = null; - public static BalanceManager balanceManager = null; + public static BalanceStorage balanceStorage = null; + public static AuctionStorage auctionStorage = null; public static final String MOD_ID = "servershop"; - public static final HashMap balances = new HashMap<>(); @Override public void onInitialize() { - ServerLifecycleEvents.SERVER_STARTED.register(this::onServerStarted); - ServerLifecycleEvents.SERVER_STOPPED.register(this::onServerStopped); - CommandRegistrationCallback.EVENT.register(this::commandRegister); + ServerLifecycleEvents.SERVER_STARTED.register(EventHandler::onServerStarted); + ServerLifecycleEvents.SERVER_STOPPED.register(EventHandler::onServerStopped); + ServerPlayConnectionEvents.JOIN.register(EventHandler::onPlayerJoin); + + CommandRegistrationCallback.EVENT.register(CommandRegistry::commandRegister); - ServerPlayConnectionEvents.JOIN.register(this::onPlayerJoin); PolymerResourcePackUtils.addModAssets(MOD_ID); PolymerResourcePackUtils.markAsRequired(); } - private void onPlayerJoin(ServerPlayNetworkHandler serverPlayNetworkHandler, PacketSender packetSender, MinecraftServer server) { - if (packetSender instanceof ServerPlayerEntity player) { - balanceManager.setBalance(player, 0L); - } - } - private void commandRegister(CommandDispatcher dispatcher, CommandRegistryAccess commandRegistryAccess, CommandManager.RegistrationEnvironment registrationEnvironment) { - dispatcher.register( - literal("shop") - .executes(ShopCommand::run)); - dispatcher.register( - literal("pay") - .then(argument("recipient", EntityArgumentType.player()) - .then(argument("amount", IntegerArgumentType.integer(1))) - ) - .executes(PayCommand::run) - ); - dispatcher.register( - literal("balance") - .then(argument("recipient", EntityArgumentType.player() - ) - .requires(serverCommandSource -> serverCommandSource.hasPermissionLevel(2)) - .executes(BalanceCommand::others) - - ) - .then(literal("set") - .requires(serverCommandSource -> serverCommandSource.hasPermissionLevel(2)) - .then(argument("balance", LongArgumentType.longArg(1)) - .executes(BalanceCommand::selfSet) - .then(argument("recipient", EntityArgumentType.player()) - .executes(BalanceCommand::othersSet)) - ) - ) - .executes(BalanceCommand::self) - ); - dispatcher.register(literal("shopprices") - .requires(serverCommandSource -> serverCommandSource.hasPermissionLevel(2)) - .then(literal("load").executes(ShopPricesCommand::load)) - .then(literal("save").executes(ShopPricesCommand::save)) - .then(literal("reset").executes(ShopPricesCommand::reset)) - .then(literal("set") - .then(argument("item", ItemStackArgumentType.itemStack(commandRegistryAccess)) - .then(argument("buyprice", IntegerArgumentType.integer()) - .then(argument("sellprice", IntegerArgumentType.integer()) - .executes(ShopPricesCommand::set) - ) - ) - ) - ) - .then(literal("setHand") - .then(argument("buyprice", IntegerArgumentType.integer()) - .then(argument("sellprice", IntegerArgumentType.integer()) - .executes(ShopPricesCommand::setHand) - ) - ) - ) - ); - - dispatcher.register(literal("buy") - .then(argument("item", ItemStackArgumentType.itemStack(commandRegistryAccess)) - .then(argument("count", IntegerArgumentType.integer()) - .executes(StoreCommands::buyCount)) - .executes(StoreCommands::buyOne) - ) - ); - dispatcher.register(literal("sell") - .then(argument("item", ItemStackArgumentType.itemStack(commandRegistryAccess)) - .then(argument("count", IntegerArgumentType.integer()) - .executes(StoreCommands::sellCount)) - .executes(StoreCommands::sellOne) - ) - ); - dispatcher.register(literal("price") - .then(argument("item", ItemStackArgumentType.itemStack(commandRegistryAccess)) - .executes(PriceCommand::run) - ) - .executes(PriceCommand::runHand) - ); - - } - - private void onServerStarted(MinecraftServer server) { - priceStorage = new PriceStorage(server); - balanceManager = new BalanceManager(server); - } - - private void onServerStopped(MinecraftServer server) { - balanceManager.saveBalances(); - } } diff --git a/src/main/java/systems/brn/servershop/commands/AuctionCommands.java b/src/main/java/systems/brn/servershop/commands/AuctionCommands.java new file mode 100644 index 0000000..915a2cd --- /dev/null +++ b/src/main/java/systems/brn/servershop/commands/AuctionCommands.java @@ -0,0 +1,60 @@ +package systems.brn.servershop.commands; + +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.item.ItemStack; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import systems.brn.servershop.ServerShop; +import systems.brn.servershop.lib.storages.AuctionStorage; +import systems.brn.servershop.screens.AuctionBrowserScreen; +import systems.brn.servershop.screens.AuctionCreateScreen; + +import static systems.brn.servershop.ServerShop.auctionStorage; + +public class AuctionCommands { + public static int load(CommandContext ctx) { + boolean success = auctionStorage.load(); + ctx.getSource().sendFeedback(() -> + Text.translatable(success ? "message.servershop.auction.load" : "message.servershop.auction.load_fail"), false); + return 1; + } + + public static int save(CommandContext ctx) { + boolean success = auctionStorage.save(); + ctx.getSource().sendFeedback(() -> + Text.translatable(success ? "message.servershop.auction.save" : "message.servershop.auction.save_fail"), false); + return 1; + } + + public static int browse(CommandContext ctx) { + ServerPlayerEntity player = ctx.getSource().getPlayer(); + AuctionBrowserScreen auctionBrowserScreen = new AuctionBrowserScreen(player); + auctionBrowserScreen.open(); + return 1; + } + + public static int create(CommandContext ctx) { + ServerPlayerEntity player = ctx.getSource().getPlayer(); + if (player != null) { + AuctionCreateScreen auctionCreateScreen = new AuctionCreateScreen(null, player); + auctionCreateScreen.open(); + } + return 1; + } + + public static int createHand(CommandContext ctx) { + int sellPrice = IntegerArgumentType.getInteger(ctx, "sellprice"); + ServerPlayerEntity player = ctx.getSource().getPlayer(); + if (player != null) { + ItemStack itemStack = player.getMainHandStack(); + if (!itemStack.isEmpty()) { + auctionStorage.addAuction(player, sellPrice, itemStack.copy(), false); + } else { + ctx.getSource().sendFeedback(() -> Text.translatable("message.servershop.auction.empty"), false); + } + } + return 1; + } +} diff --git a/src/main/java/systems/brn/servershop/commands/BalanceCommand.java b/src/main/java/systems/brn/servershop/commands/BalanceCommand.java index 5ce60ac..05aa1a0 100644 --- a/src/main/java/systems/brn/servershop/commands/BalanceCommand.java +++ b/src/main/java/systems/brn/servershop/commands/BalanceCommand.java @@ -7,7 +7,8 @@ import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; -import static systems.brn.servershop.ServerShop.balanceManager; +import static systems.brn.servershop.ServerShop.auctionStorage; +import static systems.brn.servershop.ServerShop.balanceStorage; public class BalanceCommand { public static int others(CommandContext ctx) { @@ -16,7 +17,7 @@ public class BalanceCommand { ServerPlayerEntity player = ctx.getSource().getPlayer(); ServerPlayerEntity target = EntityArgumentType.getPlayer(ctx, "recipient"); if (player != null && target != null) { - long senderBalance = balanceManager.getBalance(target); + long senderBalance = balanceStorage.getBalance(target); playerObj.sendFeedback(() -> Text.translatable("message.servershop.balance.other", target.getName(), senderBalance), false); } } catch (Exception ignored) { @@ -30,7 +31,7 @@ public class BalanceCommand { ServerCommandSource playerObj = ctx.getSource(); ServerPlayerEntity player = ctx.getSource().getPlayer(); if (player != null) { - long senderBalance = balanceManager.getBalance(player); + long senderBalance = balanceStorage.getBalance(player); playerObj.sendFeedback(() -> Text.translatable("message.servershop.balance.self", senderBalance), false); } } catch (Exception ignored) { @@ -44,7 +45,7 @@ public class BalanceCommand { ServerPlayerEntity player = ctx.getSource().getPlayer(); long senderBalance = LongArgumentType.getLong(ctx, "balance"); if (player != null) { - balanceManager.setBalance(player, senderBalance); + balanceStorage.setBalance(player, senderBalance); } } catch (Exception ignored) { return 1; @@ -59,7 +60,7 @@ public class BalanceCommand { long senderBalance = LongArgumentType.getLong(ctx, "balance"); ServerPlayerEntity target = EntityArgumentType.getPlayer(ctx, "recipient"); if (player != null && target != null) { - balanceManager.setBalance(target, senderBalance); + balanceStorage.setBalance(target, senderBalance); playerObj.sendFeedback(() -> Text.translatable("message.servershop.balance.set_other", target.getName(), senderBalance), false); } } catch (Exception ignored) { @@ -67,4 +68,18 @@ public class BalanceCommand { } return 0; } + + public static int load(CommandContext ctx) { + boolean success = balanceStorage.loadBalances(); + ctx.getSource().sendFeedback(() -> + Text.translatable(success ? "message.servershop.balance.load" : "message.servershop.balance.load_fail"), false); + return 1; + } + + public static int save(CommandContext ctx) { + boolean success = balanceStorage.saveBalances(); + ctx.getSource().sendFeedback(() -> + Text.translatable(success ? "message.servershop.balance.save" : "message.servershop.balance.save_fail"), false); + return 1; + } } \ No newline at end of file diff --git a/src/main/java/systems/brn/servershop/commands/CommandRegistry.java b/src/main/java/systems/brn/servershop/commands/CommandRegistry.java new file mode 100644 index 0000000..4aaaafe --- /dev/null +++ b/src/main/java/systems/brn/servershop/commands/CommandRegistry.java @@ -0,0 +1,111 @@ +package systems.brn.servershop.commands; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.LongArgumentType; +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.command.argument.EntityArgumentType; +import net.minecraft.command.argument.ItemStackArgumentType; +import net.minecraft.server.command.CommandManager; +import net.minecraft.server.command.ServerCommandSource; + +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; + +public class CommandRegistry { + public static void commandRegister(CommandDispatcher dispatcher, CommandRegistryAccess commandRegistryAccess, CommandManager.RegistrationEnvironment registrationEnvironment) { + dispatcher.register( + literal("shop") + .then(literal("load") + .requires(serverCommandSource -> serverCommandSource.hasPermissionLevel(2)) + .executes(ShopCommands::load)) + .then(literal("save") + .requires(serverCommandSource -> serverCommandSource.hasPermissionLevel(2)) + .executes(ShopCommands::save)) + .then(literal("set") + .requires(serverCommandSource -> serverCommandSource.hasPermissionLevel(2)) + .then(argument("item", ItemStackArgumentType.itemStack(commandRegistryAccess)) + .then(argument("buyprice", IntegerArgumentType.integer()) + .then(argument("sellprice", IntegerArgumentType.integer()) + .executes(ShopCommands::set) + ) + ) + ) + ) + .then(literal("setHand") + .requires(serverCommandSource -> serverCommandSource.hasPermissionLevel(2)) + .then(argument("buyprice", IntegerArgumentType.integer()) + .then(argument("sellprice", IntegerArgumentType.integer()) + .executes(ShopCommands::setHand) + ) + ) + ) + .executes(ShopCommands::shop)); + + dispatcher.register( + literal("auction") + .then(literal("load") + .requires(serverCommandSource -> serverCommandSource.hasPermissionLevel(2)) + .executes(AuctionCommands::load)) + .then(literal("save") + .requires(serverCommandSource -> serverCommandSource.hasPermissionLevel(2)) + .executes(AuctionCommands::save)) + .then(literal("create") + .then(argument("sellprice", IntegerArgumentType.integer()) + .executes(AuctionCommands::createHand) + ) + .executes(AuctionCommands::create) + ) + .executes(AuctionCommands::browse)); + dispatcher.register( + literal("pay") + .then(argument("recipient", EntityArgumentType.player()) + .then(argument("amount", IntegerArgumentType.integer(1)) + .executes(PayCommand::run) + ) + ) + ); + dispatcher.register( + literal("balance") + .then(argument("recipient", EntityArgumentType.player() + ) + .requires(serverCommandSource -> serverCommandSource.hasPermissionLevel(2)) + .executes(BalanceCommand::others) + + ) + .then(literal("load").executes(BalanceCommand::load)) + .then(literal("save").executes(BalanceCommand::save)) + .then(literal("set") + .requires(serverCommandSource -> serverCommandSource.hasPermissionLevel(2)) + .then(argument("balance", LongArgumentType.longArg(1)) + .executes(BalanceCommand::selfSet) + .then(argument("recipient", EntityArgumentType.player()) + .executes(BalanceCommand::othersSet)) + ) + ) + .executes(BalanceCommand::self) + ); + + dispatcher.register(literal("buy") + .then(argument("item", ItemStackArgumentType.itemStack(commandRegistryAccess)) + .then(argument("count", IntegerArgumentType.integer()) + .executes(StoreCommands::buyCount)) + .executes(StoreCommands::buyOne) + ) + ); + dispatcher.register(literal("sell") + .then(argument("item", ItemStackArgumentType.itemStack(commandRegistryAccess)) + .then(argument("count", IntegerArgumentType.integer()) + .executes(StoreCommands::sellCount)) + .executes(StoreCommands::sellOne) + ) + ); + dispatcher.register(literal("price") + .then(argument("item", ItemStackArgumentType.itemStack(commandRegistryAccess)) + .executes(PriceCommand::run) + ) + .executes(PriceCommand::runHand) + ); + + } +} diff --git a/src/main/java/systems/brn/servershop/commands/PayCommand.java b/src/main/java/systems/brn/servershop/commands/PayCommand.java index d2f6c88..f8a56b8 100644 --- a/src/main/java/systems/brn/servershop/commands/PayCommand.java +++ b/src/main/java/systems/brn/servershop/commands/PayCommand.java @@ -7,7 +7,7 @@ import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; -import static systems.brn.servershop.ServerShop.balanceManager; +import static systems.brn.servershop.ServerShop.balanceStorage; public class PayCommand { public static int run(CommandContext ctx) { @@ -17,10 +17,10 @@ public class PayCommand { ServerPlayerEntity target = EntityArgumentType.getPlayer(ctx, "recipient"); int amount = IntegerArgumentType.getInteger(ctx, "amount"); if (sender != null) { - long senderBalance = balanceManager.getBalance(sender); + long senderBalance = balanceStorage.getBalance(sender); if (senderBalance >= amount && amount > 0) { - balanceManager.removeBalance(sender, amount); - balanceManager.addBalance(target, amount); + balanceStorage.removeBalance(sender, amount); + balanceStorage.addBalance(target, amount); senderObj.sendFeedback(() -> Text.translatable("message.servershop.pay.success", amount, target.getName(), senderBalance), false); } else { senderObj.sendFeedback(() -> Text.translatable("message.servershop.pay.bad_amount", amount, senderBalance), false); diff --git a/src/main/java/systems/brn/servershop/commands/PriceCommand.java b/src/main/java/systems/brn/servershop/commands/PriceCommand.java index 2a425a7..06c97ba 100644 --- a/src/main/java/systems/brn/servershop/commands/PriceCommand.java +++ b/src/main/java/systems/brn/servershop/commands/PriceCommand.java @@ -8,7 +8,7 @@ import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; import org.jetbrains.annotations.NotNull; -import systems.brn.servershop.ItemPrice; +import systems.brn.servershop.lib.records.ItemPriceRecord; import static systems.brn.servershop.ServerShop.priceStorage; @@ -20,10 +20,10 @@ public class PriceCommand { } private static int runStack(CommandContext ctx, ItemStack stack) { - ItemPrice itemPrice = priceStorage.getPrices(stack); + ItemPriceRecord itemPriceRecord = priceStorage.getPrices(stack); ServerCommandSource src = ctx.getSource(); - int buyPrice = itemPrice.buyPrice(); - int sellPrice = itemPrice.sellPrice(); + int buyPrice = itemPriceRecord.buyPrice(); + int sellPrice = itemPriceRecord.sellPrice(); String itemName = stack.getItem().toString(); Text text = getText(buyPrice, sellPrice, itemName); src.sendFeedback(() -> text, false); diff --git a/src/main/java/systems/brn/servershop/commands/ShopCommand.java b/src/main/java/systems/brn/servershop/commands/ShopCommand.java deleted file mode 100644 index 658767a..0000000 --- a/src/main/java/systems/brn/servershop/commands/ShopCommand.java +++ /dev/null @@ -1,15 +0,0 @@ -package systems.brn.servershop.commands; - -import com.mojang.brigadier.context.CommandContext; -import net.minecraft.server.command.ServerCommandSource; -import net.minecraft.server.network.ServerPlayerEntity; -import systems.brn.servershop.screens.ShopScreen; - -public class ShopCommand { - public static int run(CommandContext serverCommandSourceCommandContext) { - ServerPlayerEntity player = serverCommandSourceCommandContext.getSource().getPlayer(); - ShopScreen shopScreen = new ShopScreen(player); - shopScreen.open(); - return 0; - } -} diff --git a/src/main/java/systems/brn/servershop/commands/ShopPricesCommand.java b/src/main/java/systems/brn/servershop/commands/ShopCommands.java similarity index 67% rename from src/main/java/systems/brn/servershop/commands/ShopPricesCommand.java rename to src/main/java/systems/brn/servershop/commands/ShopCommands.java index dcd0f96..cbd8638 100644 --- a/src/main/java/systems/brn/servershop/commands/ShopPricesCommand.java +++ b/src/main/java/systems/brn/servershop/commands/ShopCommands.java @@ -4,36 +4,31 @@ import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.context.CommandContext; import net.minecraft.command.argument.ItemStackArgumentType; import net.minecraft.item.ItemStack; -import net.minecraft.registry.RegistryWrapper; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; import systems.brn.servershop.ServerShop; +import systems.brn.servershop.screens.ShopScreen; -public class ShopPricesCommand { - public static int load(CommandContext ctx) { - ServerShop.priceStorage.load(); - ctx.getSource().sendFeedback(() -> Text.translatable("message.servershop.storage.load"), false); - return 1; +public class ShopCommands { + public static int shop(CommandContext serverCommandSourceCommandContext) { + ServerPlayerEntity player = serverCommandSourceCommandContext.getSource().getPlayer(); + ShopScreen shopScreen = new ShopScreen(player); + shopScreen.open(); + return 0; } - public static int loadLegacy(CommandContext ctx) { - ServerShop.priceStorage.loadLegacy(); - ctx.getSource().sendFeedback(() -> Text.translatable("message.servershop.storage.load"), false); + public static int load(CommandContext ctx) { + boolean success = ServerShop.priceStorage.load(); + ctx.getSource().sendFeedback(() -> + Text.translatable(success ? "message.servershop.prices.load" : "message.servershop.prices.load_fail"), false); return 1; } public static int save(CommandContext ctx) { boolean success = ServerShop.priceStorage.save(); ctx.getSource().sendFeedback(() -> - Text.translatable(success ? "message.servershop.storage.save" : "message.servershop.storage.save_fail"), false); - return 1; - } - - public static int reset(CommandContext ctx) { - ServerShop.priceStorage.generateEmpty(); - ctx.getSource().sendFeedback(() -> Text.translatable("message.servershop.storage.reset"), false); + Text.translatable(success ? "message.servershop.prices.save" : "message.servershop.prices.save_fail"), false); return 1; } @@ -59,7 +54,7 @@ public class ShopPricesCommand { int buyPrice = IntegerArgumentType.getInteger(ctx, "buyprice"); int sellPrice = IntegerArgumentType.getInteger(ctx, "sellprice"); ServerShop.priceStorage.setPrices(stack, buyPrice, sellPrice); - ctx.getSource().sendFeedback(() -> Text.translatable("message.servershop.storage.set", itemName, buyPrice, sellPrice), false); + ctx.getSource().sendFeedback(() -> Text.translatable("message.servershop.prices.set", itemName, buyPrice, sellPrice), false); return 0; } } diff --git a/src/main/java/systems/brn/servershop/lib/EventHandler.java b/src/main/java/systems/brn/servershop/lib/EventHandler.java new file mode 100644 index 0000000..2f6a01d --- /dev/null +++ b/src/main/java/systems/brn/servershop/lib/EventHandler.java @@ -0,0 +1,31 @@ +package systems.brn.servershop.lib; + +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import systems.brn.servershop.lib.storages.AuctionStorage; +import systems.brn.servershop.lib.storages.BalanceStorage; +import systems.brn.servershop.lib.storages.PriceStorage; + +import static systems.brn.servershop.ServerShop.*; + +public class EventHandler { + public static void onPlayerJoin(ServerPlayNetworkHandler serverPlayNetworkHandler, PacketSender packetSender, MinecraftServer server) { + if (packetSender instanceof ServerPlayerEntity player) { + balanceStorage.setBalance(player, 0L); + } + } + + public static void onServerStarted(MinecraftServer server) { + priceStorage = new PriceStorage(server); + balanceStorage = new BalanceStorage(server); + auctionStorage = new AuctionStorage(server); + } + + public static void onServerStopped(MinecraftServer server) { + balanceStorage.saveBalances(); + auctionStorage.save(); + priceStorage.save(); + } +} diff --git a/src/main/java/systems/brn/servershop/lib/ItemPriceStorage.java b/src/main/java/systems/brn/servershop/lib/ItemPriceStorage.java deleted file mode 100644 index 9c24da3..0000000 --- a/src/main/java/systems/brn/servershop/lib/ItemPriceStorage.java +++ /dev/null @@ -1,5 +0,0 @@ -package systems.brn.servershop.lib; - -public class ItemPriceStorage { - -} diff --git a/src/main/java/systems/brn/servershop/lib/PagedGui.java b/src/main/java/systems/brn/servershop/lib/PagedGui.java index d9e4881..84f2149 100644 --- a/src/main/java/systems/brn/servershop/lib/PagedGui.java +++ b/src/main/java/systems/brn/servershop/lib/PagedGui.java @@ -27,6 +27,7 @@ public abstract class PagedGui extends SimpleGui { public static final String GUI_NEXT_PAGE = "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzg2MTg1YjFkNTE5YWRlNTg1ZjE4NGMzNGYzZjNlMjBiYjY0MWRlYjg3OWU4MTM3OGU0ZWFmMjA5Mjg3In19fQ"; public static final String GUI_NEXT_PAGE_BLOCKED = "ewogICJ0aW1lc3RhbXAiIDogMTY0MDYxNjExMDQ4OCwKICAicHJvZmlsZUlkIiA6ICIxZjEyNTNhYTVkYTQ0ZjU5YWU1YWI1NmFhZjRlNTYxNyIsCiAgInByb2ZpbGVOYW1lIiA6ICJOb3RNaUt5IiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdlNTc3MjBhNDg3OGM4YmNhYjBlOWM5YzQ3ZDllNTUxMjhjY2Q3N2JhMzQ0NWE1NGE5MWUzZTFlMWEyNzM1NmUiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ=="; public static final String GUI_QUESTION_MARK = "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmM4ZWExZjUxZjI1M2ZmNTE0MmNhMTFhZTQ1MTkzYTRhZDhjM2FiNWU5YzZlZWM4YmE3YTRmY2I3YmFjNDAifX19"; + public static final String GUI_PLUS = "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjBiNTVmNzQ2ODFjNjgyODNhMWMxY2U1MWYxYzgzYjUyZTI5NzFjOTFlZTM0ZWZjYjU5OGRmMzk5MGE3ZTcifX19"; public static final int PAGE_SIZE = 9 * 5; protected final Runnable closeCallback; @@ -104,6 +105,7 @@ public abstract class PagedGui extends SimpleGui { return switch (id) { case 0 -> DisplayElement.previousPage(this); case 1 -> this.search(); + case 2 -> this.create(); case 7 -> DisplayElement.nextPage(this); case 8 -> DisplayElement.of( new GuiElementBuilder(Items.STRUCTURE_VOID) @@ -122,6 +124,9 @@ public abstract class PagedGui extends SimpleGui { return DisplayElement.filler(); } + protected DisplayElement create(){ + return DisplayElement.filler(); + } public record DisplayElement(@Nullable GuiElementInterface element, @Nullable Slot slot) { private static final DisplayElement EMPTY = DisplayElement.of(new GuiElement(ItemStack.EMPTY, GuiElementInterface.EMPTY_CALLBACK)); diff --git a/src/main/java/systems/brn/servershop/lib/ShopFunctions.java b/src/main/java/systems/brn/servershop/lib/ShopFunctions.java index aa4be2e..87fe266 100644 --- a/src/main/java/systems/brn/servershop/lib/ShopFunctions.java +++ b/src/main/java/systems/brn/servershop/lib/ShopFunctions.java @@ -1,30 +1,29 @@ package systems.brn.servershop.lib; import net.minecraft.entity.player.PlayerInventory; -import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; -import systems.brn.servershop.ItemPrice; +import systems.brn.servershop.lib.records.ItemPriceRecord; -import static systems.brn.servershop.ServerShop.balanceManager; +import static systems.brn.servershop.ServerShop.balanceStorage; import static systems.brn.servershop.ServerShop.priceStorage; import static systems.brn.servershop.lib.Util.*; public class ShopFunctions { public static void buy(ItemStack itemStack, ServerPlayerEntity player, boolean overlay) { PlayerInventory playerInventory = player.getInventory(); - ItemPrice price = priceStorage.getPrices(itemStack); + ItemPriceRecord price = priceStorage.getPrices(itemStack); int buyPrice = price.buyPrice() * itemStack.getCount(); - long playerBalance = balanceManager.getBalance(player); + long playerBalance = balanceStorage.getBalance(player); if (buyPrice > 0) { if (playerBalance >= buyPrice) { if (canInsertItemIntoInventory(playerInventory, itemStack.copy()) >= itemStack.getCount()) { ItemStack remaining = insertStackIntoInventory(playerInventory, itemStack.copy()); int toDeduce = buyPrice - (price.buyPrice() * remaining.getCount()); int boughtCount = itemStack.getCount() - remaining.getCount(); - balanceManager.removeBalance(player, toDeduce); - playerBalance = balanceManager.getBalance(player); + balanceStorage.removeBalance(player, toDeduce); + playerBalance = balanceStorage.getBalance(player); player.sendMessage(Text.translatable("message.servershop.buy.success", boughtCount, itemStack.getName(), toDeduce, playerBalance), overlay); } else { player.sendMessage(Text.translatable("message.servershop.buy.inventory"), overlay); @@ -39,17 +38,17 @@ public class ShopFunctions { public static void sell(ItemStack itemStack, ServerPlayerEntity player, boolean overlay) { PlayerInventory playerInventory = player.getInventory(); - ItemPrice price = priceStorage.getPrices(itemStack); + ItemPriceRecord price = priceStorage.getPrices(itemStack); int sellPrice = price.sellPrice(); if (sellPrice > 0) { int remaining = removeFromInventory(playerInventory, itemStack.copy(), itemStack.getCount()); int toAdd = sellPrice * (itemStack.getCount() - remaining); int soldCount = itemStack.getCount() - remaining; - balanceManager.addBalance(player, toAdd); + balanceStorage.addBalance(player, toAdd); if (soldCount == 0) { player.sendMessage(Text.translatable("message.servershop.sell.not_enough"), overlay); } else { - long playerBalance = balanceManager.getBalance(player); + long playerBalance = balanceStorage.getBalance(player); player.sendMessage(Text.translatable("message.servershop.sell.success", soldCount, itemStack.getName(), toAdd, playerBalance), overlay); } } else { diff --git a/src/main/java/systems/brn/servershop/lib/Util.java b/src/main/java/systems/brn/servershop/lib/Util.java index 4554238..51924e7 100644 --- a/src/main/java/systems/brn/servershop/lib/Util.java +++ b/src/main/java/systems/brn/servershop/lib/Util.java @@ -1,23 +1,26 @@ package systems.brn.servershop.lib; -import eu.pb4.polymer.core.api.other.PolymerComponent; -import net.minecraft.component.ComponentType; +import com.mojang.authlib.GameProfile; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.LoreComponent; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.inventory.Inventory; import net.minecraft.item.ItemStack; -import net.minecraft.registry.Registries; -import net.minecraft.registry.Registry; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.*; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; -import systems.brn.servershop.ItemPrice; +import net.minecraft.util.UserCache; +import org.jetbrains.annotations.NotNull; import systems.brn.servershop.ServerShop; +import systems.brn.servershop.lib.records.AuctionRecord; +import systems.brn.servershop.lib.records.ItemPriceRecord; import java.util.ArrayList; import java.util.List; -import java.util.function.UnaryOperator; +import java.util.Optional; +import java.util.UUID; public class Util { @@ -124,7 +127,12 @@ public class Util { TextContent textContent = mutableText.getContent(); if (textContent instanceof TranslatableTextContent translatableTextContent) { String key = translatableTextContent.getKey(); - if (!key.equals("gui.servershop.item.buyprice") && !key.equals("gui.servershop.item.sellprice")) { + if (!key.equals("gui.servershop.item.buy_price") && + !key.equals("gui.servershop.item.sell_price") && + !key.equals("gui.servershop.item.stack_info") && + !key.equals("gui.servershop.item.seller_me") && + !key.equals("gui.servershop.item.seller") + ) { filteredLines.add(text); } } @@ -139,41 +147,79 @@ public class Util { } } - public static ItemStack addPrices(ItemPrice price) { + public static GameProfile getGameProfile(UUID playerUUID, MinecraftServer server) { + UserCache userCache = server.getUserCache(); + GameProfile gameProfile = null; + Optional gameProfileTemp; + + if (userCache != null) { + gameProfileTemp = userCache.getByUuid(playerUUID); + boolean exists = gameProfileTemp.isPresent(); + gameProfile = exists ? gameProfileTemp.get() : null; + } + return gameProfile; + } + + public static ItemStack addPrices(ItemPriceRecord price) { ItemStack stack = price.stack(); int buyPrice = price.buyPrice(); int sellPrice = price.sellPrice(); int count = stack.getCount(); if (count > 0) { - ItemStack newStack = stack.copy(); - LoreComponent lore = stack.get(DataComponentTypes.LORE); - Text buyLine = Text.translatable("gui.servershop.item.buyprice", buyPrice).setStyle(Style.EMPTY.withColor(Formatting.DARK_GREEN).withItalic(true)); - Text sellLine = Text.translatable("gui.servershop.item.sellprice", sellPrice).setStyle(Style.EMPTY.withColor(Formatting.AQUA).withItalic(true)); - - LoreComponent newLore; - if (lore == null) { - List loreList = new ArrayList<>(); - if (buyPrice > 0) { - loreList.addFirst(buyLine); - } - if (sellPrice > 0) { - loreList.addFirst(sellLine); - } - newLore = new LoreComponent(loreList); - } else { - List newLines = new ArrayList<>(lore.lines()); - if (buyPrice > 0) { - newLines.addFirst(buyLine); - } - if (sellPrice > 0) { - newLines.addFirst(sellLine); - } - newLore = new LoreComponent(newLines); - } - newStack.set(DataComponentTypes.LORE, newLore); - return newStack; + return addLore(buyPrice, sellPrice, stack, null, false); } else { return stack; } } + + public static ItemStack addPrices(AuctionRecord auctionRecord, ServerPlayerEntity looker) { + ItemStack stack = auctionRecord.stack(); + int buyPrice = auctionRecord.buyPrice(); + int sellPrice = 0; + int count = stack.getCount(); + if (count > 0) { + return addLore(buyPrice, sellPrice, stack, auctionRecord.getProfile(looker.getServer()).getName(), auctionRecord.sellerUUID().equals(looker.getUuid())); + } else { + return stack; + } + } + + @NotNull + private static ItemStack addLore(int buyPrice, int sellPrice, ItemStack stack, String sellerName, boolean me) { + Text buyLine = Text.translatable("gui.servershop.item.buy_price", buyPrice).setStyle(Style.EMPTY.withColor(Formatting.DARK_GREEN).withItalic(true)); + Text sellLine = Text.translatable("gui.servershop.item.sell_price", sellPrice).setStyle(Style.EMPTY.withColor(Formatting.AQUA).withItalic(true)); + Text infoLine = Text.translatable("gui.servershop.item.stack_info").setStyle(Style.EMPTY.withColor(Formatting.YELLOW).withItalic(true)); + Text sellerLine; + if (me) { + sellerLine = Text.translatable("gui.servershop.item.seller_me", sellerName).setStyle(Style.EMPTY.withColor(Formatting.GREEN).withItalic(true)); + } else { + sellerLine = Text.translatable("gui.servershop.item.seller", sellerName).setStyle(Style.EMPTY.withColor(Formatting.RED).withItalic(true)); + } + LoreComponent newLore; + LoreComponent lore = stack.get(DataComponentTypes.LORE); + + List loreList; + if (lore == null) { + loreList = new ArrayList<>(); + } else { + loreList = new ArrayList<>(lore.lines()); + } + if (sellerName == null || sellerName.isEmpty()) { + if (buyPrice > 0 || sellPrice > 0) { + loreList.addFirst(infoLine); + } + } else { + loreList.addFirst(sellerLine); + } + if (sellPrice > 0) { + loreList.addFirst(sellLine); + } + if (buyPrice > 0) { + loreList.addFirst(buyLine); + } + newLore = new LoreComponent(loreList); + ItemStack newStack = stack.copy(); + newStack.set(DataComponentTypes.LORE, newLore); + return newStack; + } } diff --git a/src/main/java/systems/brn/servershop/lib/records/AuctionRecord.java b/src/main/java/systems/brn/servershop/lib/records/AuctionRecord.java new file mode 100644 index 0000000..3ee7dc1 --- /dev/null +++ b/src/main/java/systems/brn/servershop/lib/records/AuctionRecord.java @@ -0,0 +1,46 @@ +package systems.brn.servershop.lib.records; + +import com.mojang.authlib.GameProfile; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.registry.RegistryWrapper; +import net.minecraft.server.MinecraftServer; + +import java.util.Optional; +import java.util.UUID; + +import static systems.brn.servershop.lib.Util.getGameProfile; + +public record AuctionRecord(int buyPrice, ItemStack stack, UUID sellerUUID) { + + + public NbtCompound toNbt(RegistryWrapper.WrapperLookup wrapperLookup) { + NbtCompound nbt = new NbtCompound(); + nbt.putInt("BuyPrice", this.buyPrice); + nbt.putUuid("SellerUUID", sellerUUID); + + // Serialize the ItemStack to NBT and add it to the compound + NbtElement stackNbt = stack.encode(wrapperLookup); + nbt.put("ItemStack", stackNbt); // Adds the ItemStack's NBT data to the main NBT compound + + return nbt; + } + + public GameProfile getProfile(MinecraftServer server) { + return getGameProfile(sellerUUID, server); + } + + public static AuctionRecord fromNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapperLookup) { + int buyPrice = nbt.getInt("BuyPrice"); + UUID sellerUUID = nbt.getUuid("SellerUUID"); + + // Deserialize the ItemStack from the NBT + NbtElement stackElement = nbt.get("ItemStack"); + + Optional stack = ItemStack.fromNbt(wrapperLookup, stackElement); + return stack.map(itemStack -> new AuctionRecord(buyPrice, itemStack, sellerUUID)).orElse(null); + + } + +} diff --git a/src/main/java/systems/brn/servershop/ItemPrice.java b/src/main/java/systems/brn/servershop/lib/records/ItemPriceRecord.java similarity index 67% rename from src/main/java/systems/brn/servershop/ItemPrice.java rename to src/main/java/systems/brn/servershop/lib/records/ItemPriceRecord.java index 9233860..356e77b 100644 --- a/src/main/java/systems/brn/servershop/ItemPrice.java +++ b/src/main/java/systems/brn/servershop/lib/records/ItemPriceRecord.java @@ -1,4 +1,4 @@ -package systems.brn.servershop; +package systems.brn.servershop.lib.records; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; @@ -7,7 +7,7 @@ import net.minecraft.registry.RegistryWrapper; import java.util.Optional; -public record ItemPrice(int buyPrice, int sellPrice, ItemStack stack) { +public record ItemPriceRecord(int buyPrice, int sellPrice, ItemStack stack) { public NbtCompound toNbt(RegistryWrapper.WrapperLookup wrapperLookup) { @@ -22,15 +22,19 @@ public record ItemPrice(int buyPrice, int sellPrice, ItemStack stack) { return nbt; } - public static ItemPrice fromNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapperLookup) { + public static ItemPriceRecord fromNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapperLookup) { int buyPrice = nbt.getInt("BuyPrice"); int sellPrice = nbt.getInt("SellPrice"); + if(sellPrice > buyPrice && buyPrice != 0) { + buyPrice = sellPrice; + } // Deserialize the ItemStack from the NBT NbtElement stackElement = nbt.get("ItemStack"); Optional stack = ItemStack.fromNbt(wrapperLookup, stackElement); - return stack.map(itemStack -> new ItemPrice(buyPrice, sellPrice, itemStack)).orElse(null); + int finalBuyPrice = buyPrice; + return stack.map(itemStack -> new ItemPriceRecord(finalBuyPrice, sellPrice, itemStack)).orElse(null); } diff --git a/src/main/java/systems/brn/servershop/lib/storages/AuctionStorage.java b/src/main/java/systems/brn/servershop/lib/storages/AuctionStorage.java new file mode 100644 index 0000000..a50a554 --- /dev/null +++ b/src/main/java/systems/brn/servershop/lib/storages/AuctionStorage.java @@ -0,0 +1,169 @@ +package systems.brn.servershop.lib.storages; + +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.*; +import net.minecraft.registry.RegistryWrapper; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.text.Text; +import net.minecraft.util.WorldSavePath; +import systems.brn.servershop.lib.records.AuctionRecord; +import systems.brn.servershop.lib.records.ItemPriceRecord; + +import java.io.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Optional; +import java.util.concurrent.locks.ReentrantLock; + +import static systems.brn.servershop.ServerShop.balanceStorage; +import static systems.brn.servershop.ServerShop.priceStorage; +import static systems.brn.servershop.lib.Util.*; + +public class AuctionStorage { + + public final MinecraftServer server; + public final File auctionStorageFIle; + public RegistryWrapper.WrapperLookup wrapperLookup; + public final ArrayList auctions = new ArrayList<>(); + private static final ReentrantLock lock = new ReentrantLock(); // Lock for in-memory operations + + public AuctionStorage(MinecraftServer server) { + this.server = server; + auctionStorageFIle = server.getSavePath(WorldSavePath.ROOT).resolve("auctions.dat").toFile(); + wrapperLookup = null; + for (ServerWorld world : server.getWorlds()) { + wrapperLookup = world.getRegistryManager(); + break; + } + + load(); + } + + public void addAuction(ServerPlayerEntity seller, int price, ItemStack itemStack, boolean fromCursorStack) { + PlayerInventory playerInventory = seller.getInventory(); + if (itemStack.isEmpty()) { + seller.sendMessage(Text.translatable("message.servershop.auction.empty"), true); + return; + } + if (price > 0) { + int remaining; + if (fromCursorStack) { + remaining = 0; + } else { + remaining = removeFromInventory(playerInventory, itemStack.copy(), itemStack.getCount()); + } + int soldCount = itemStack.getCount() - remaining; + ItemStack sellingStack = itemStack.copy(); + sellingStack.setCount(soldCount); + if (soldCount == 0) { + seller.sendMessage(Text.translatable("message.servershop.sell.not_enough"), true); + } else { + seller.sendMessage(Text.translatable("message.servershop.sell.auction", soldCount, itemStack.getName(), price), true); + if(fromCursorStack) { + itemStack.setCount(0); + } + auctions.add(new AuctionRecord(price, sellingStack, seller.getUuid())); + save(); + } + } else { + seller.sendMessage(Text.translatable("message.servershop.sell.bad_price", price), true); + } + } + + public void buyAuction(ServerPlayerEntity buyer, AuctionRecord auction) { + PlayerInventory playerInventory = buyer.getInventory(); + ItemStack itemStack = removePrices(auction.stack()); + int buyPrice = auction.buyPrice() * itemStack.getCount(); + long playerBalance = balanceStorage.getBalance(buyer); + if (buyPrice > 0 && auctions.contains(auction)) { + if (playerBalance >= buyPrice) { + if (canInsertItemIntoInventory(playerInventory, itemStack.copy()) >= itemStack.getCount()) { + ItemStack remaining = insertStackIntoInventory(playerInventory, itemStack.copy()); + int toDeduce = buyPrice - (auction.buyPrice() * remaining.getCount()); + int boughtCount = itemStack.getCount() - remaining.getCount(); + + if (!buyer.getUuid().equals(auction.sellerUUID())) { + balanceStorage.removeBalance(buyer, toDeduce); + balanceStorage.addBalance(auction.sellerUUID(), toDeduce); + playerBalance = balanceStorage.getBalance(buyer); + buyer.sendMessage(Text.translatable("message.servershop.buy.success", boughtCount, itemStack.getName(), toDeduce, playerBalance), true); + } else { + buyer.sendMessage(Text.translatable("message.servershop.buy.own", boughtCount, itemStack.getName(), toDeduce, playerBalance), true); + } + auctions.remove(auction); + if (!remaining.isEmpty()) { + auctions.add(new AuctionRecord(buyPrice - toDeduce, remaining, auction.sellerUUID())); + } + save(); + } else { + buyer.sendMessage(Text.translatable("message.servershop.buy.inventory"), true); + } + } else { + buyer.sendMessage(Text.translatable("message.servershop.buy.not_enough", buyPrice, playerBalance, buyPrice - playerBalance), true); + } + } else { + buyer.sendMessage(Text.translatable("message.servershop.buy.not_available", itemStack.getName()), true); + } + } + + public boolean load() { + if (wrapperLookup == null) { + return false; + } + auctions.clear(); + lock.lock(); + + try { + NbtCompound nbtCompound = NbtIo.read(auctionStorageFIle.toPath()); + if (nbtCompound != null && nbtCompound.contains("Auctions") && auctionStorageFIle.exists()) { + NbtList nbtList = nbtCompound.getList("Auctions", 10); // 10 is the type ID for NbtCompound + for (NbtElement element : nbtList) { + if (element instanceof NbtCompound nbt) { + AuctionRecord auctionRecord = AuctionRecord.fromNbt(nbt, wrapperLookup); + if (auctionRecord.stack() != null) { + auctions.add(auctionRecord); + } + } + } + lock.unlock(); + return true; + } + } catch (IOException ignored) { + } + lock.unlock(); + return false; + } + + public boolean save() { + if (wrapperLookup == null) { + return false; + } + NbtList nbtList = new NbtList(); + lock.lock(); + + for (AuctionRecord auction : auctions) { + if (!auction.stack().isEmpty()) { + nbtList.add(auction.toNbt(wrapperLookup)); + } + } + + NbtCompound nbtCompound = new NbtCompound(); + nbtCompound.put("Auctions", nbtList); + + // Write the NbtList to a file + try (FileOutputStream fos = new FileOutputStream(auctionStorageFIle)) { + DataOutputStream dos = new DataOutputStream(fos); + NbtIo.write(nbtCompound, dos); + dos.close(); + fos.close(); + lock.unlock(); + return true; + } catch (IOException e) { + lock.unlock(); + return false; + } + } +} diff --git a/src/main/java/systems/brn/servershop/lib/BalanceManager.java b/src/main/java/systems/brn/servershop/lib/storages/BalanceStorage.java similarity index 83% rename from src/main/java/systems/brn/servershop/lib/BalanceManager.java rename to src/main/java/systems/brn/servershop/lib/storages/BalanceStorage.java index 101b409..a63dda3 100644 --- a/src/main/java/systems/brn/servershop/lib/BalanceManager.java +++ b/src/main/java/systems/brn/servershop/lib/storages/BalanceStorage.java @@ -1,27 +1,26 @@ -package systems.brn.servershop.lib; +package systems.brn.servershop.lib.storages; import net.minecraft.nbt.*; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; -import net.minecraft.util.Identifier; import net.minecraft.util.WorldSavePath; import java.io.*; import java.util.HashMap; -import java.util.Optional; import java.util.Scanner; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; -public class BalanceManager { - private final HashMap balances = new HashMap<>(); +public class BalanceStorage { + private final ConcurrentHashMap balances = new ConcurrentHashMap<>(); public final MinecraftServer server; private final File balanceStorageFile; private final File balanceStorageCSVFile; private static final ReentrantLock lock = new ReentrantLock(); // Lock for in-memory operations - public BalanceManager(MinecraftServer server) { + public BalanceStorage(MinecraftServer server) { this.server = server; balanceStorageFile = server.getSavePath(WorldSavePath.ROOT).resolve("balances.dat").toFile(); balanceStorageCSVFile = server.getSavePath(WorldSavePath.ROOT).resolve("balances.csv").toFile(); @@ -66,9 +65,9 @@ public class BalanceManager { announceBalance(player, true); } - public void saveBalances() { - lock.lock(); + public boolean saveBalances() { try { + lock.lock(); NbtList nbtList = new NbtList(); for (UUID uuid : balances.keySet()) { NbtCompound nbtCompound = new NbtCompound(); @@ -83,16 +82,20 @@ public class BalanceManager { try (FileOutputStream fos = new FileOutputStream(balanceStorageFile); DataOutputStream dos = new DataOutputStream(fos)) { NbtIo.write(root, dos); + dos.close(); + fos.close(); + lock.unlock(); + return true; } } catch (IOException ignored) { - } finally { lock.unlock(); + return false; } } - public void loadBalances() { - lock.lock(); + public boolean loadBalances() { if (balanceStorageFile.exists()) { + lock.lock(); try { NbtCompound root = NbtIo.read(balanceStorageFile.toPath()); if (root != null) { @@ -105,23 +108,27 @@ public class BalanceManager { balances.put(uuid, balance); } } + lock.unlock(); + return true; } } catch ( IOException ignored) { - } finally { lock.unlock(); + return false; } + lock.unlock(); } else { - loadLegacy(); + return loadLegacy(); } + return false; } - public void loadLegacy() { - lock.lock(); + public boolean loadLegacy() { if (!balances.isEmpty()) { balances.clear(); } try { + lock.lock(); Scanner scanner = new Scanner(balanceStorageCSVFile); while (scanner.hasNextLine()) { String line = scanner.nextLine(); @@ -132,12 +139,14 @@ public class BalanceManager { balances.put(uuid, amount); } } - saveBalances(); + lock.unlock(); + return saveBalances(); } catch (FileNotFoundException ignored) { + lock.unlock(); saveBalances(); + return false; } - lock.unlock(); } public void announceBalance(ServerPlayerEntity player, boolean overlay) { diff --git a/src/main/java/systems/brn/servershop/lib/PriceStorage.java b/src/main/java/systems/brn/servershop/lib/storages/PriceStorage.java similarity index 69% rename from src/main/java/systems/brn/servershop/lib/PriceStorage.java rename to src/main/java/systems/brn/servershop/lib/storages/PriceStorage.java index d031f15..31ba950 100644 --- a/src/main/java/systems/brn/servershop/lib/PriceStorage.java +++ b/src/main/java/systems/brn/servershop/lib/storages/PriceStorage.java @@ -1,4 +1,4 @@ -package systems.brn.servershop.lib; +package systems.brn.servershop.lib.storages; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -9,12 +9,13 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.Identifier; import net.minecraft.util.WorldSavePath; -import systems.brn.servershop.ItemPrice; +import systems.brn.servershop.lib.records.ItemPriceRecord; import java.io.*; import java.util.HashMap; -import java.util.Optional; import java.util.Scanner; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; public class PriceStorage { @@ -22,6 +23,8 @@ public class PriceStorage { public final File priceStorageFile; public final File priceStorageCSVFile; public RegistryWrapper.WrapperLookup wrapperLookup; + public final ConcurrentHashMap prices = new ConcurrentHashMap<>(); + private static final ReentrantLock lock = new ReentrantLock(); // Lock for in-memory operations public PriceStorage(MinecraftServer server) { this.server = server; @@ -36,15 +39,14 @@ public class PriceStorage { load(); } - public final HashMap prices = new HashMap<>(); public void generateEmpty() { if (!prices.isEmpty()) { prices.clear(); } for (Item item : Registries.ITEM) { - ItemPrice itemPrice = new ItemPrice(0, 0, item.getDefaultStack()); - prices.put(item.getDefaultStack(), itemPrice); + ItemPriceRecord itemPriceRecord = new ItemPriceRecord(0, 0, item.getDefaultStack()); + prices.put(item.getDefaultStack(), itemPriceRecord); } } @@ -52,60 +54,56 @@ public class PriceStorage { boolean found = false; for (ItemStack priceStack : prices.keySet()) { if (ItemStack.areItemsAndComponentsEqual(inStack, priceStack)) { - prices.put(priceStack, new ItemPrice(buyPrice, sellPrice, priceStack)); + prices.put(priceStack, new ItemPriceRecord(buyPrice, sellPrice, priceStack)); found = true; break; } } if (!found) { - prices.put(inStack, new ItemPrice(buyPrice, sellPrice, inStack)); + prices.put(inStack, new ItemPriceRecord(buyPrice, sellPrice, inStack)); } } - public ItemPrice getPrices(ItemStack inStack) { + public ItemPriceRecord getPrices(ItemStack inStack) { for (ItemStack priceStack : prices.keySet()) { if (ItemStack.areItemsAndComponentsEqual(inStack, priceStack)) { return prices.get(priceStack); } } - return new ItemPrice(0, 0, inStack.copy()); + return new ItemPriceRecord(0, 0, inStack.copy()); } - public void load() { + public boolean load() { if (wrapperLookup == null) { - return; + return false; } prices.clear(); try { + lock.lock(); NbtCompound nbtCompound = NbtIo.read(priceStorageFile.toPath()); if (nbtCompound != null && nbtCompound.contains("Prices") && priceStorageFile.exists()) { NbtList nbtList = nbtCompound.getList("Prices", 10); // 10 is the type ID for NbtCompound for (NbtElement element : nbtList) { if (element instanceof NbtCompound nbt) { - // Deserialize the ItemStack from the NbtCompound - NbtCompound stackNbt = nbt.getCompound("ItemStack"); - Optional stackTemp = ItemStack.fromNbt(wrapperLookup, stackNbt); - if (stackTemp.isPresent()) { - ItemStack stack = stackTemp.get(); - // Retrieve the buy and sell prices - int buyPrice = nbt.getInt("BuyPrice"); - int sellPrice = nbt.getInt("SellPrice"); - if(sellPrice > buyPrice && buyPrice != 0) { - buyPrice = sellPrice; - } - // Add the deserialized ItemStack and ItemPrice to the prices map - prices.put(stack, new ItemPrice(buyPrice, sellPrice, stack)); + ItemPriceRecord itemPriceRecord = ItemPriceRecord.fromNbt(nbt, wrapperLookup); + if (itemPriceRecord.stack() != null) { + prices.put(itemPriceRecord.stack(), itemPriceRecord); } } } + return true; } else { - loadLegacy(); + boolean status = loadLegacy(); + lock.unlock(); save(); + return status; } } catch (IOException e) { generateEmpty(); + lock.unlock(); save(); + return false; } } @@ -114,11 +112,12 @@ public class PriceStorage { return false; } NbtList nbtList = new NbtList(); + lock.lock(); for (ItemStack stack : prices.keySet()) { - ItemPrice itemPrice = prices.get(stack); + ItemPriceRecord itemPriceRecord = prices.get(stack); if (!stack.isEmpty()) { - nbtList.add(itemPrice.toNbt(wrapperLookup)); + nbtList.add(itemPriceRecord.toNbt(wrapperLookup)); } } @@ -129,19 +128,23 @@ public class PriceStorage { try (FileOutputStream fos = new FileOutputStream(priceStorageFile)) { DataOutputStream dos = new DataOutputStream(fos); NbtIo.write(nbtCompound, dos); + dos.close(); + lock.unlock(); } catch (IOException e) { + lock.unlock(); return false; } - + lock.unlock(); return true; } - public void loadLegacy() { + public boolean loadLegacy() { if (!prices.isEmpty()) { prices.clear(); } try { + lock.lock(); Scanner scanner = new Scanner(priceStorageCSVFile); while (scanner.hasNextLine()) { String line = scanner.nextLine(); @@ -154,13 +157,17 @@ public class PriceStorage { int buyPrice = Integer.parseInt(lineParts[1]); int sellPrice = Integer.parseInt(lineParts[2]); Item item = Registries.ITEM.get(itemIdentifier); - prices.put(item.getDefaultStack(), new ItemPrice(buyPrice, sellPrice, item.getDefaultStack())); + prices.put(item.getDefaultStack(), new ItemPriceRecord(buyPrice, sellPrice, item.getDefaultStack())); } } } + lock.unlock(); + return true; } catch (FileNotFoundException ignored) { generateEmpty(); + lock.unlock(); + return false; } } } diff --git a/src/main/java/systems/brn/servershop/screens/AuctionBrowserScreen.java b/src/main/java/systems/brn/servershop/screens/AuctionBrowserScreen.java new file mode 100644 index 0000000..00f18e8 --- /dev/null +++ b/src/main/java/systems/brn/servershop/screens/AuctionBrowserScreen.java @@ -0,0 +1,135 @@ +package systems.brn.servershop.screens; + +import eu.pb4.sgui.api.ClickType; +import eu.pb4.sgui.api.elements.GuiElementBuilder; +import eu.pb4.sgui.api.elements.GuiElementInterface; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import systems.brn.servershop.ServerShop; +import systems.brn.servershop.lib.PagedGui; +import systems.brn.servershop.lib.records.AuctionRecord; +import systems.brn.servershop.lib.records.ItemPriceRecord; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static systems.brn.servershop.lib.ShopFunctions.buy; +import static systems.brn.servershop.lib.ShopFunctions.sell; +import static systems.brn.servershop.lib.Util.addPrices; +import static systems.brn.servershop.lib.Util.removePrices; + +public class AuctionBrowserScreen extends PagedGui { + + public String searchQuery = ""; + + final public ArrayList filteredAuctions = new ArrayList<>(); + + public AuctionBrowserScreen(ServerPlayerEntity player) { + super(player, null); + setTitle(Text.translatable("gui.servershop.auction.title")); + } + + @Override + public boolean open() { + updateDisplay(); + return super.open(); + } + + @Override + public void updateDisplay() { + filterAuctions(); + super.updateDisplay(); + } + + public void filterAuctions() { + filteredAuctions.clear(); + for (AuctionRecord auctionRecord : ServerShop.auctionStorage.auctions) { + String itemName = auctionRecord.stack().getItem().toString(); + String sellerName = auctionRecord.getProfile(player.getServer()).getName(); + if (itemName.contains(searchQuery) || sellerName.contains(searchQuery)) { + if (auctionRecord.buyPrice() > 0) { + filteredAuctions.add(auctionRecord); + } + } + } + } + + public void doSearch(String query) { + searchQuery = query; + updateDisplay(); + } + + @Override + protected int getPageAmount() { + return Math.ceilDivExact(filteredAuctions.size(), PAGE_SIZE); + } + + + @Override + public boolean onClick(int index, ClickType type, SlotActionType action, GuiElementInterface element) { + if (index < filteredAuctions.size()) { + if (type.isLeft) { + AuctionRecord auctionRecord = filteredAuctions.get(index); + ServerShop.auctionStorage.buyAuction(player, auctionRecord); + updateDisplay(); + } + } + return false; + } + + @Override + protected DisplayElement getElement(int id) { + if (id < filteredAuctions.size()) { + AuctionRecord auction = filteredAuctions.get(id); + ItemStack stack = addPrices(auction, player); + GuiElementBuilder elementBuilder = new GuiElementBuilder(stack); + return DisplayElement.of(elementBuilder); + + } + return DisplayElement.filler(); + } + + @Override + protected DisplayElement search() { + if (searchQuery == null || searchQuery.isEmpty() || searchQuery.equals("*")) { + searchQuery = "Filter not set"; + } + return DisplayElement.of( + new GuiElementBuilder(Items.PLAYER_HEAD) + .setName(Text.literal(searchQuery).formatted(Formatting.WHITE)) + .hideDefaultTooltip().noDefaults() + .setSkullOwner(GUI_QUESTION_MARK) + .setCallback((x, y, z) -> { + playClickSound(getPlayer()); + if (y.isRight) { + doSearch(""); + } else if (y.isLeft) { + SearchScreen searchScreen = new SearchScreen(this, ""); + searchScreen.open(); + } + }) + ); + } + + @Override + protected DisplayElement create() { + return DisplayElement.of( + new GuiElementBuilder(Items.PLAYER_HEAD) + .setName(Text.translatable("gui.servershop.auction.create").formatted(Formatting.WHITE)) + .hideDefaultTooltip().noDefaults() + .setSkullOwner(GUI_PLUS) + .setCallback((x, y, z) -> { + playClickSound(getPlayer()); + if (y.isLeft) { + AuctionCreateScreen auctionCreateScreen = new AuctionCreateScreen(this, player); + auctionCreateScreen.open(); + } + }) + ); + } +} diff --git a/src/main/java/systems/brn/servershop/screens/AuctionCreateScreen.java b/src/main/java/systems/brn/servershop/screens/AuctionCreateScreen.java new file mode 100644 index 0000000..1b9c50a --- /dev/null +++ b/src/main/java/systems/brn/servershop/screens/AuctionCreateScreen.java @@ -0,0 +1,57 @@ +package systems.brn.servershop.screens; + +import eu.pb4.sgui.api.ClickType; +import eu.pb4.sgui.api.elements.GuiElementInterface; +import eu.pb4.sgui.api.gui.AnvilInputGui; +import eu.pb4.sgui.api.gui.SimpleGui; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; +import systems.brn.servershop.ServerShop; + +public class AuctionCreateScreen extends AnvilInputGui { + + private final SimpleGui parentScreen; + + public AuctionCreateScreen(@Nullable SimpleGui parentScreen, ServerPlayerEntity player) { + super(player, parentScreen != null && parentScreen.getLockPlayerInventory()); + this.parentScreen = parentScreen; + setTitle(Text.translatable("gui.servershop.auction.create.title")); + this.setDefaultInputValue("Price"); + if (parentScreen != null) { + parentScreen.close(); + } + } + + @Override + public void onClose() { + super.onClose(); + if (parentScreen != null) { + parentScreen.open(); + if (parentScreen instanceof AuctionBrowserScreen browserScreen){ + browserScreen.updateDisplay(); + } + } + } + + @Override + public boolean insertItem(ItemStack stack, int startIndex, int endIndex, boolean fromLast) { + int price = Integer.parseInt(this.getInput()); + ServerShop.auctionStorage.addAuction(player, price, stack.copy(), false); + close(); + return super.insertItem(stack, startIndex, endIndex, fromLast); + } + + @Override + public boolean onClick(int index, ClickType type, SlotActionType action, GuiElementInterface element) { + ItemStack cursorStack = getPlayer().currentScreenHandler.getCursorStack(); + if (!cursorStack.isEmpty()) { + int price = Integer.parseInt(this.getInput()); + ServerShop.auctionStorage.addAuction(player, price, cursorStack, true); + close(); + } + return super.onClick(index, type, action, element); + } +} diff --git a/src/main/java/systems/brn/servershop/screens/SearchScreen.java b/src/main/java/systems/brn/servershop/screens/SearchScreen.java index ce0dfe9..bcb8250 100644 --- a/src/main/java/systems/brn/servershop/screens/SearchScreen.java +++ b/src/main/java/systems/brn/servershop/screens/SearchScreen.java @@ -2,6 +2,7 @@ package systems.brn.servershop.screens; import eu.pb4.sgui.api.gui.AnvilInputGui; import eu.pb4.sgui.api.gui.SimpleGui; +import net.minecraft.text.Text; public class SearchScreen extends AnvilInputGui { @@ -10,6 +11,7 @@ public class SearchScreen extends AnvilInputGui { public SearchScreen(SimpleGui parentScreen, String defaultText) { super(parentScreen.getPlayer(), parentScreen.getLockPlayerInventory()); this.parentScreen = parentScreen; + setTitle(Text.translatable("gui.servershop.search.title")); parentScreen.close(); this.setDefaultInputValue(defaultText); } diff --git a/src/main/java/systems/brn/servershop/screens/ShopScreen.java b/src/main/java/systems/brn/servershop/screens/ShopScreen.java index 42aa301..3334644 100644 --- a/src/main/java/systems/brn/servershop/screens/ShopScreen.java +++ b/src/main/java/systems/brn/servershop/screens/ShopScreen.java @@ -9,7 +9,7 @@ import net.minecraft.screen.slot.SlotActionType; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; import net.minecraft.util.Formatting; -import systems.brn.servershop.ItemPrice; +import systems.brn.servershop.lib.records.ItemPriceRecord; import systems.brn.servershop.ServerShop; import systems.brn.servershop.lib.PagedGui; @@ -23,7 +23,7 @@ public class ShopScreen extends PagedGui { public String searchQuery = ""; - final public HashMap filteredPrices = new HashMap<>(); + final public HashMap filteredPrices = new HashMap<>(); public ShopScreen(ServerPlayerEntity player) { super(player, null); @@ -47,7 +47,7 @@ public class ShopScreen extends PagedGui { for (ItemStack stack : ServerShop.priceStorage.prices.keySet()) { String itemName = stack.getItem().toString(); if (itemName.contains(searchQuery)) { - ItemPrice price = ServerShop.priceStorage.getPrices(stack); + ItemPriceRecord price = ServerShop.priceStorage.getPrices(stack); if (price.buyPrice() > 0 || price.sellPrice() > 0) { filteredPrices.put(stack, price); } @@ -94,9 +94,9 @@ public class ShopScreen extends PagedGui { @Override protected DisplayElement getElement(int id) { - List> list = new ArrayList<>(filteredPrices.entrySet()); + List> list = new ArrayList<>(filteredPrices.entrySet()); if (id < list.size()) { - Map.Entry itemPriceEntry = list.get(id); + Map.Entry itemPriceEntry = list.get(id); ItemStack stack = addPrices(itemPriceEntry.getValue()); GuiElementBuilder elementBuilder = new GuiElementBuilder(stack); return DisplayElement.of(elementBuilder); diff --git a/src/main/resources/assets/servershop/lang/en_us.json b/src/main/resources/assets/servershop/lang/en_us.json index 09db1c8..e749d5b 100644 --- a/src/main/resources/assets/servershop/lang/en_us.json +++ b/src/main/resources/assets/servershop/lang/en_us.json @@ -1,13 +1,29 @@ { "gui.servershop.shop.title": "Shop", - "gui.servershop.item.buyprice": "Buy for %d", - "gui.servershop.item.sellprice": "Sell for %d", - "message.servershop.storage.load": "Loaded prices from disk", - "message.servershop.storage.save": "Saved prices to disk", - "message.servershop.storage.save_fail": "Failed saving prices to disk", - "message.servershop.storage.reset": "Reset all prices to 0", - "message.servershop.storage.set": "Set price for %s, buy price: is %d and sell price is: %d", - "message.servershop.storage.set_fail": "Setting price for %s failed", + "gui.servershop.auction.title": "Auction", + "gui.servershop.auction.create": "Create auction", + "gui.servershop.search.title": "Search", + "gui.servershop.item.seller_me": "Sold by you(%s)", + "gui.servershop.item.seller": "Sold by %s", + "gui.servershop.auction.create.title": "Type price, insert item", + "gui.servershop.item.buy_price": "Buy for %d with left click", + "gui.servershop.item.sell_price": "Sell for %d with right click", + "gui.servershop.item.stack_info": "Holding shift tries to do a stack", + "message.servershop.prices.load": "Loaded prices from disk", + "message.servershop.prices.load_fail": "Failed loading prices from disk", + "message.servershop.prices.save": "Saved prices to disk", + "message.servershop.prices.save_fail": "Failed saving prices to disk", + "message.servershop.auction.load": "Loaded auctions from disk", + "message.servershop.auction.load_fail": "Failed loading auctions from disk", + "message.servershop.auction.save": "Saved auctions to disk", + "message.servershop.auction.save_fail": "Failed auctions prices to disk", + "message.servershop.auction.empty": "You can't sell an empty stack", + "message.servershop.balance.load": "Loaded balances from disk", + "message.servershop.balance.load_fail": "Failed loading balances from disk", + "message.servershop.balance.save": "Saved balances to disk", + "message.servershop.balance.save_fail": "Failed balances to disk", + "message.servershop.prices.set": "Set price for %s, buy price: is %d and sell price is: %d", + "message.servershop.prices.set_fail": "Setting price for %s failed", "message.servershop.pay.success": "You paid %d to %s, now you have %d", "message.servershop.pay.bad_amount": "%d is more than you have(%d), or an invalid amount", "message.servershop.balance.other": "%s has %d on his account", @@ -17,10 +33,14 @@ "message.servershop.buy.not_enough": "You don't have enough money (%d>%d), you need %d more", "message.servershop.buy.inventory": "You don't have enough inventory space", "message.servershop.buy.success": "You bought %d %s for %d, now you have %d", + "message.servershop.buy.own": "You bought your own %d %s for %d, now you have %d", "message.servershop.buy.not_available": "This item (%s) is not available for buying", "message.servershop.sell.not_enough": "You don't have this item", "message.servershop.sell.success": "You sold %d %s for %d, now you have %d", "message.servershop.sell.not_available": "This item (%s) is not available for sale", + "message.servershop.sell.auction" : "You put up %d of %s on auction for %d", + "message.servershop.sell.bad_price" : "You can't use this price: %d", + "message.servershop.price.both" : "This item (%s) can be bought for %d and sold for %d", "message.servershop.price.buy" : "This item (%s) can be bought for %d and can't be sold", "message.servershop.price.sell" : "This item (%s) can be sold for %d and can't bought",