From 26708c12cece43b94b5ad5b77aaa6401c6d8e004 Mon Sep 17 00:00:00 2001 From: nahkd123 Date: Fri, 22 Dec 2023 23:48:33 +0700 Subject: [PATCH 1/3] The biggest commit for Stonks 2.1.0 --- .../main/resources/{default-config => config} | 0 .../provided/button/CategoryNativeButton.java | 81 +++++++++++++++++++ .../provided/button/CloseNativeButton.java | 48 +++++++++++ .../provided/button/ProductNativeButton.java | 68 ++++++++++++++++ 4 files changed, 197 insertions(+) rename fabric/src/main/resources/{default-config => config} (100%) create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/CategoryNativeButton.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/CloseNativeButton.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/ProductNativeButton.java diff --git a/fabric/src/main/resources/default-config b/fabric/src/main/resources/config similarity index 100% rename from fabric/src/main/resources/default-config rename to fabric/src/main/resources/config diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/CategoryNativeButton.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/CategoryNativeButton.java new file mode 100644 index 0000000..90d09b3 --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/CategoryNativeButton.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.gui.provided.button; + +import io.github.nahkd123.stonks.market.catalogue.Category; +import io.github.nahkd123.stonks.market.catalogue.ProductsCatalogue; +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.gui.ClickType; +import io.github.nahkd123.stonks.minecraft.gui.ContainerGui; +import io.github.nahkd123.stonks.minecraft.gui.LazyLoadedNativeButton; +import io.github.nahkd123.stonks.minecraft.gui.provided.gui.AbstractContainerGui; +import io.github.nahkd123.stonks.minecraft.gui.provided.gui.MainContainerGui; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; +import io.github.nahkd123.stonks.minecraft.utils.TriStates; +import io.github.nahkd123.stonks.utils.lazy.LazyLoader; +import io.github.nahkd123.stonks.utils.lazy.LoadState; + +public class CategoryNativeButton implements LazyLoadedNativeButton { + public static final String ID = "category"; + private LazyLoader cache; + private int ordinal; + + public CategoryNativeButton(LazyLoader cache, int ordinal) { + this.cache = cache; + this.ordinal = ordinal; + } + + public CategoryNativeButton(AbstractContainerGui gui, int ordinal) { + this(gui.getServer().getMarketCache().productsCatalogue, ordinal); + } + + @Override + public void onClick(ContainerGui gui, Player player, ClickType type) { + if (cache.load() != LoadState.SUCCESS) return; + if (getLoader().get() == null) return; + + MainContainerGui main = gui instanceof MainContainerGui m ? m : new MainContainerGui(gui.getServer(), ordinal); + main.setCategoryIndex(ordinal); + // TODO + + if (!(gui instanceof MainContainerGui)) player.openGui(main); + } + + @Override + public TextComponent replacePlaceholder(TextFactory factory, String name) { + return switch (name) { + case "category.id" -> TriStates.of(getLoader().map(v -> factory.literal(v.getId())), factory); + case "category.name" -> TriStates.of(getLoader().map(v -> factory.literal(v.getDisplayName())), factory); + case "category.productsCount" -> TriStates + .of(getLoader().map(v -> factory.literal(Integer.toString(v.getProducts().size()))), factory); + default -> null; + }; + } + + @Override + public LazyLoader getLoader() { + return cache + .map(v -> v.getCategories()) + .map(v -> ordinal < v.size() ? v.get(ordinal) : null); + } +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/CloseNativeButton.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/CloseNativeButton.java new file mode 100644 index 0000000..6dc37de --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/CloseNativeButton.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.gui.provided.button; + +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.gui.ClickType; +import io.github.nahkd123.stonks.minecraft.gui.ContainerGui; +import io.github.nahkd123.stonks.minecraft.gui.NativeButton; +import io.github.nahkd123.stonks.minecraft.gui.provided.gui.AbstractContainerGui; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; + +public class CloseNativeButton implements NativeButton { + public static final String ID = "close"; + + public CloseNativeButton(AbstractContainerGui gui, int ordinal) {} + + public CloseNativeButton() {} + + @Override + public void onClick(ContainerGui gui, Player player, ClickType type) { + player.openGui(null); + } + + @Override + public TextComponent replacePlaceholder(TextFactory factory, String name) { + return null; + } +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/ProductNativeButton.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/ProductNativeButton.java new file mode 100644 index 0000000..559070b --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/ProductNativeButton.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.gui.provided.button; + +import io.github.nahkd123.stonks.market.catalogue.Product; +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.gui.ClickType; +import io.github.nahkd123.stonks.minecraft.gui.ContainerGui; +import io.github.nahkd123.stonks.minecraft.gui.LazyLoadedNativeButton; +import io.github.nahkd123.stonks.minecraft.gui.provided.gui.AbstractContainerGui; +import io.github.nahkd123.stonks.minecraft.gui.provided.gui.MainContainerGui; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; +import io.github.nahkd123.stonks.minecraft.utils.TriStates; +import io.github.nahkd123.stonks.utils.lazy.LazyLoader; + +public class ProductNativeButton implements LazyLoadedNativeButton { + public static final String ID = "product"; + private AbstractContainerGui gui; + private int ordinal; + + public ProductNativeButton(AbstractContainerGui gui, int ordinal) { + this.gui = gui; + this.ordinal = ordinal; + } + + @Override + public TextComponent replacePlaceholder(TextFactory factory, String name) { + return switch (name) { + case "product.id" -> TriStates.of(getLoader().map(p -> factory.literal(p.getId())), factory); + case "product.name" -> TriStates.of(getLoader().map(p -> factory.literal(p.getDisplayName())), factory); + default -> null; + }; + } + + @Override + public LazyLoader getLoader() { + if (gui instanceof MainContainerGui main) { + return main.getCategoryCache() + .map(c -> ordinal < c.getProducts().size() ? c.getProducts().get(ordinal) : null); + } + + // TODO add loader for product menu + return LazyLoader.ofFailed(new RuntimeException("Unable to obtain product based on current GUI")); + } + + @Override + public void onClick(ContainerGui gui, Player player, ClickType type) {} +} From 9a065cc5b7a3bb8521da4f4a1e7ec62322f9a3f9 Mon Sep 17 00:00:00 2001 From: nahkd123 Date: Sat, 23 Dec 2023 00:03:01 +0700 Subject: [PATCH 2/3] Forgot to add the rest --- core/build.gradle | 4 +- .../io/github/nahkd123/stonks/Instance.java | 50 +++ .../io/github/nahkd123/stonks/Platform.java | 41 +++ .../nahkd123/stonks/market/MarketCache.java | 36 ++ .../nahkd123/stonks/market/MarketService.java | 214 +++++++++++ .../nahkd123/stonks/market/OrderInfo.java | 89 +++++ .../stonks/market/catalogue/Category.java | 34 ++ .../market/catalogue/MutableCategory.java | 52 +++ .../market/catalogue/MutableProduct.java | 47 +++ .../catalogue/MutableProductsCatalogue.java | 46 +++ .../stonks/market/catalogue/Product.java | 46 +++ .../market/catalogue/ProductsCatalogue.java | 35 ++ .../market/helper/InstantOrdersProcessor.java | 75 ++++ .../stonks/market/helper/MutableOrder.java | 115 ++++++ .../stonks/market/helper/OrdersBuffer.java | 128 +++++++ .../stonks/market/helper/OrdersIterator.java | 57 +++ .../helper/ProductSummaryGenerator.java | 95 +++++ .../impl/legacy/LegacyCategoryWrapper.java | 53 +++ .../impl/legacy/LegacyMarketService.java | 208 +++++++++++ .../impl/legacy/LegacyOrderWrapper.java | 68 ++++ .../impl/legacy/LegacyProductWrapper.java | 43 +++ .../impl/legacy/LegacyProductsCatalogue.java | 47 +++ .../impl/queue/QueuedMarketService.java | 154 ++++++++ .../stonks/market/impl/sql/Column.java | 53 +++ .../stonks/market/impl/sql/ColumnType.java | 42 +++ .../market/impl/sql/MappedResultSet.java | 67 ++++ .../market/impl/sql/SqlMarketService.java | 347 ++++++++++++++++++ .../stonks/market/impl/sql/Table.java | 152 ++++++++ .../stonks/market/result/ClaimResult.java | 42 +++ .../market/result/InstantBuyResult.java | 32 ++ .../market/result/InstantOfferResult.java | 26 ++ .../market/result/InstantSellResult.java | 28 ++ .../result/MutableInstantBuyResult.java | 78 ++++ .../result/MutableInstantSellResult.java | 72 ++++ .../nahkd123/stonks/market/result/Result.java | 32 ++ .../market/result/SimpleClaimResult.java | 63 ++++ .../stonks/market/summary/ProductSummary.java | 63 ++++ .../stonks/market/summary/SummaryEntry.java | 26 ++ .../github/nahkd123/stonks/utils/Async.java | 38 ++ .../nahkd123/stonks/utils/Response.java | 28 ++ .../stonks/utils/ServiceException.java | 33 ++ .../stonks/utils/UncheckedException.java | 33 ++ .../stonks/utils/lazy/CachingLazyLoader.java | 72 ++++ .../lazy/CompletableFutureLazyLoader.java | 70 ++++ .../stonks/utils/lazy/LazyLoader.java | 104 ++++++ .../stonks/utils/lazy/LazyMapper.java | 47 +++ .../utils/lazy/LegacyTaskLazyLoader.java | 62 ++++ .../nahkd123/stonks/utils/lazy/LoadState.java | 28 ++ .../stonks/utils/lazy/LoadedLazyLoader.java | 46 +++ .../core/exec/InstantOfferExecutor.java | 6 +- .../java/stonks/core/service/Emittable.java | 16 + .../stonks/core/service/StonksService.java | 32 ++ .../service/memory/StonksMemoryService.java | 30 +- .../testing/UnstableStonksService.java | 6 + .../market/impl/sql/SqlMarketServiceTest.java | 119 ++++++ .../nahkd123/stonks/test/SqlTestUtils.java | 80 ++++ .../nahkd123/stonks/test/TestCatalogue.java | 46 +++ fabric/build.gradle | 1 + .../minecraft/fabric/FabricPlatform.java | 45 +++ .../stonks/minecraft/fabric/FabricServer.java | 117 ++++++ .../minecraft/fabric/ModernStonksFabric.java | 80 ++++ .../minecraft/fabric/PlayerWrapper.java | 85 +++++ .../fabric/command/DevelopmentCommand.java | 64 ++++ .../commodity/LegacyCommoditiesService.java | 58 +++ .../commodity/LegacyCommodityWrapper.java | 63 ++++ .../economy/AdapterResponseToTransaction.java | 65 ++++ .../fabric/economy/LegacyEconomyService.java | 85 +++++ .../fabric/gui/ContainerGuiFrontend.java | 71 ++++ .../stonks/minecraft/fabric/gui/Fill.java | 105 ++++++ .../minecraft/fabric/gui/FillConsumer.java | 27 ++ .../minecraft/fabric/gui/TestBook.jpage | 0 .../fabric/gui/template/ButtonTemplate.java | 78 ++++ .../gui/template/ButtonTemplateFactory.java | 41 +++ .../gui/template/DynamicButtonTemplate.java | 174 +++++++++ .../gui/template/EmptyButtonTemplate.java | 80 ++++ .../gui/template/FailbackButtonTemplate.java | 83 +++++ .../fabric/gui/template/GuiTemplate.java | 79 ++++ .../gui/template/LazyButtonTemplate.java | 142 +++++++ .../gui/template/StaticButtonTemplate.java | 96 +++++ .../fabric/text/MinecraftTextColor.java | 39 ++ .../fabric/text/MinecraftTextFactory.java | 85 +++++ .../minecraft/fabric/text/TextProxy.java | 158 ++++++++ .../fabric/utils/ItemStacksDeriver.java | 53 +++ .../main/java/stonks/fabric/StonksFabric.java | 35 +- .../stonks/fabric/StonksFabricPlatform.java | 3 + .../fabric/adapter/provided/ItemsAdapter.java | 1 - .../fabric/mixin/MinecraftServerMixin.java | 10 + fabric/src/main/resources/gui.txt | 50 +++ minecraft/build.gradle | 15 + .../stonks/minecraft/MinecraftPlatform.java | 29 ++ .../stonks/minecraft/MinecraftServer.java | 60 +++ .../nahkd123/stonks/minecraft/Player.java | 78 ++++ .../commodity/CommoditiesService.java | 64 ++++ .../stonks/minecraft/commodity/Commodity.java | 80 ++++ .../economy/CachedEconomyService.java | 86 +++++ .../stonks/minecraft/economy/Currency.java | 57 +++ .../economy/DoubleBackedCurrency.java | 69 ++++ .../minecraft/economy/EconomyService.java | 99 +++++ .../stonks/minecraft/economy/Transaction.java | 74 ++++ .../minecraft/economy/TransactionType.java | 27 ++ .../stonks/minecraft/gui/ClickType.java | 30 ++ .../stonks/minecraft/gui/ContainerGui.java | 50 +++ .../minecraft/gui/LazyLoadedNativeButton.java | 28 ++ .../stonks/minecraft/gui/NativeButton.java | 42 +++ .../provided/gui/AbstractContainerGui.java | 68 ++++ .../gui/provided/gui/MainContainerGui.java | 52 +++ .../stonks/minecraft/text/ClickEvent.java | 25 ++ .../minecraft/text/ClickEventAction.java | 38 ++ .../stonks/minecraft/text/FriendlyParser.java | 240 ++++++++++++ .../stonks/minecraft/text/LegacyColor.java | 51 +++ .../stonks/minecraft/text/TextColor.java | 35 ++ .../stonks/minecraft/text/TextComponent.java | 84 +++++ .../stonks/minecraft/text/TextFactory.java | 40 ++ .../text/placeholder/MergedPlaceholders.java | 44 +++ .../text/placeholder/Placeholders.java | 48 +++ .../text/placeholder/PlayerPlaceholders.java | 53 +++ .../stonks/minecraft/utils/TriStates.java | 35 ++ minecraft/src/main/resources/fabric.mod.json | 13 + settings.gradle | 1 + 119 files changed, 7512 insertions(+), 32 deletions(-) create mode 100644 core/src/main/java/io/github/nahkd123/stonks/Instance.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/Platform.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/MarketCache.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/MarketService.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/OrderInfo.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/catalogue/Category.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/catalogue/MutableCategory.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/catalogue/MutableProduct.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/catalogue/MutableProductsCatalogue.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/catalogue/Product.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/catalogue/ProductsCatalogue.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/helper/InstantOrdersProcessor.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/helper/MutableOrder.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/helper/OrdersBuffer.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/helper/OrdersIterator.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/helper/ProductSummaryGenerator.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyCategoryWrapper.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyMarketService.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyOrderWrapper.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyProductWrapper.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyProductsCatalogue.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/impl/queue/QueuedMarketService.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/Column.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/ColumnType.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/MappedResultSet.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/SqlMarketService.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/Table.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/result/ClaimResult.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/result/InstantBuyResult.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/result/InstantOfferResult.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/result/InstantSellResult.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/result/MutableInstantBuyResult.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/result/MutableInstantSellResult.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/result/Result.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/result/SimpleClaimResult.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/summary/ProductSummary.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/market/summary/SummaryEntry.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/utils/Async.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/utils/Response.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/utils/ServiceException.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/utils/UncheckedException.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/utils/lazy/CachingLazyLoader.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/utils/lazy/CompletableFutureLazyLoader.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LazyLoader.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LazyMapper.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LegacyTaskLazyLoader.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LoadState.java create mode 100644 core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LoadedLazyLoader.java create mode 100644 core/src/test/java/io/github/nahkd123/stonks/market/impl/sql/SqlMarketServiceTest.java create mode 100644 core/src/test/java/io/github/nahkd123/stonks/test/SqlTestUtils.java create mode 100644 core/src/test/java/io/github/nahkd123/stonks/test/TestCatalogue.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/FabricPlatform.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/FabricServer.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/ModernStonksFabric.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/PlayerWrapper.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/command/DevelopmentCommand.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/commodity/LegacyCommoditiesService.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/commodity/LegacyCommodityWrapper.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/economy/AdapterResponseToTransaction.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/economy/LegacyEconomyService.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/ContainerGuiFrontend.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/Fill.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/FillConsumer.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/TestBook.jpage create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/ButtonTemplate.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/ButtonTemplateFactory.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/DynamicButtonTemplate.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/EmptyButtonTemplate.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/FailbackButtonTemplate.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/GuiTemplate.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/LazyButtonTemplate.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/StaticButtonTemplate.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/text/MinecraftTextColor.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/text/MinecraftTextFactory.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/text/TextProxy.java create mode 100644 fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/utils/ItemStacksDeriver.java create mode 100644 fabric/src/main/resources/gui.txt create mode 100644 minecraft/build.gradle create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/MinecraftPlatform.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/MinecraftServer.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/Player.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/commodity/CommoditiesService.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/commodity/Commodity.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/CachedEconomyService.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/Currency.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/DoubleBackedCurrency.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/EconomyService.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/Transaction.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/TransactionType.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/ClickType.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/ContainerGui.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/LazyLoadedNativeButton.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/NativeButton.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/AbstractContainerGui.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/MainContainerGui.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/ClickEvent.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/ClickEventAction.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/FriendlyParser.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/LegacyColor.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/TextColor.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/TextComponent.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/TextFactory.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/placeholder/MergedPlaceholders.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/placeholder/Placeholders.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/placeholder/PlayerPlaceholders.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/utils/TriStates.java create mode 100644 minecraft/src/main/resources/fabric.mod.json diff --git a/core/build.gradle b/core/build.gradle index 1e86b03..17a7ce9 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,5 +1,7 @@ dependencies { - testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' + testImplementation 'org.xerial:sqlite-jdbc:3.44.1.0' + testImplementation 'org.slf4j:slf4j-simple:2.0.9' api naharaToolkit('nahara-common-tasks') } diff --git a/core/src/main/java/io/github/nahkd123/stonks/Instance.java b/core/src/main/java/io/github/nahkd123/stonks/Instance.java new file mode 100644 index 0000000..c52b415 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/Instance.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks; + +import io.github.nahkd123.stonks.market.MarketCache; +import io.github.nahkd123.stonks.market.MarketService; + +public interface Instance { + /** + *

+ * Get the market service, where you can place buy/sell orders and perform + * instant buy/sell. + *

+ * + * @return The market service. + */ + public MarketService getMarketService(); + + /** + *

+ * Get the cache that cache data from {@link #getMarketService()}. + *

+ *

+ * The cached data are suitable for displaying only. Any real actions should be + * performed directly on the service itself. + *

+ * + * @return The market data cache. + */ + public MarketCache getMarketCache(); +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/Platform.java b/core/src/main/java/io/github/nahkd123/stonks/Platform.java new file mode 100644 index 0000000..2a57722 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/Platform.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks; + +import java.nio.file.Path; + +/** + *

+ * The Stonks platform interface. + *

+ */ +public interface Platform { + /** + *

+ * Get the path to Stonks data directory. This directory is shared for all + * Stonks instances running under the same platform (eg: Minecraft servers). + *

+ * + * @return The Stonks data directory path. + */ + public Path getDataDir(); +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/MarketCache.java b/core/src/main/java/io/github/nahkd123/stonks/market/MarketCache.java new file mode 100644 index 0000000..532536f --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/MarketCache.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market; + +import io.github.nahkd123.stonks.market.catalogue.ProductsCatalogue; +import io.github.nahkd123.stonks.utils.lazy.CachingLazyLoader; + +public class MarketCache { + public final MarketService service; + public final CachingLazyLoader productsCatalogue; + + public MarketCache(MarketService service) { + this.service = service; + this.productsCatalogue = new CachingLazyLoader<>(() -> service.queryCatalogue() + .thenApply(r -> r.data()), 60_000); + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/MarketService.java b/core/src/main/java/io/github/nahkd123/stonks/market/MarketService.java new file mode 100644 index 0000000..d131ada --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/MarketService.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import io.github.nahkd123.stonks.market.catalogue.Product; +import io.github.nahkd123.stonks.market.catalogue.ProductsCatalogue; +import io.github.nahkd123.stonks.market.result.ClaimResult; +import io.github.nahkd123.stonks.market.result.InstantBuyResult; +import io.github.nahkd123.stonks.market.result.InstantSellResult; +import io.github.nahkd123.stonks.market.summary.ProductSummary; +import io.github.nahkd123.stonks.utils.Response; +import nahara.common.tasks.Task; +import stonks.core.service.Emittable; +import stonks.core.service.StonksService; + +/** + *

+ * The modern implementation of {@link StonksService}. We are transitioning away + * from the problematic {@link Task} to the battle-tested + * {@link CompletableFuture} API. + *

+ *

+ * The {@link StonksService} will not be deprecated for now. We'll deprecate it + * once {@link MarketService} is fully completed. + *

+ *

+ * First off, we will keep the same "ease of setting up" idea from + * {@link StonksService}. The categories and products information will still be + * provided by this service. This allows users to deploy Stonks to multiple game + * servers while having only a single configuration that can be shared and + * updated to all connected game servers. + *

+ *

+ * Next, we will use {@link CompletableFuture} instead of {@link Task}. This + * allows smaller mod size (is it though?) and less dependencies wacks because, + * well, we have 1 less dependency to care about. + *

+ */ +public interface MarketService { + /** + *

+ * Get the catalogue information, including all products and categories. The + * implementation must returns the latest catalogue information (a.k.a no + * caching. + *

+ * + * @return The catalogue information. + */ + public CompletableFuture> queryCatalogue(); + + /** + *

+ * Get the product summary. This includes product's instant buy/sell price and + * top offers for buy and sell. + *

+ * + * @param product The product. + * @return The product summary. + */ + public CompletableFuture> querySummary(Product product); + + /** + *

+ * Get a list of order made by user. + *

+ * + * @param user User's UUID. + * @return A list of orders, including buy and sell offers. + */ + public CompletableFuture>> queryOrders(UUID user); + + /** + *

+ * Get the order with given order ID. If the order with specified ID does not + * exists, it will returns {@code null} (null inside {@link Response}). + *

+ * + * @param orderId The ID of the order. + * @return Order info. + */ + public CompletableFuture> queryOrder(UUID orderId); + + /** + *

+ * List a new buy order offer. It might takes a while before the offer become + * available for instant sell. + *

+ * + * @param user The user's ID. + * @param product The product. + * @param amount How much units you want to purchase. + * @param pricePerUnit The price per unit for the product. + * @return The order information. This can be used for opening offer info menu + * instantly. + */ + public CompletableFuture> makeBuyOrder(UUID user, Product product, long amount, long pricePerUnit); + + /** + *

+ * List a new sell order offer. It might takes a while before the offer become + * available for instant buy. + *

+ * + * @param user The user's ID. + * @param product The product. + * @param amount How much units you are willing to sell. + * @param pricePerUnit The price per unit for the product. + * @return The order information. This can be used for opening offer info menu + * instantly. + */ + public CompletableFuture> makeSellOrder(UUID user, Product product, long amount, long pricePerUnit); + + /** + *

+ * Attempt to claim offer. If {@link ClaimResult#isFullAfterClaim()} returns + * {@code true}, the offer data on the service could have been removed (because + * it is fully claimed). + *

+ * + * @param orderId The ID of the offer to claim. + * @return The claim result. + */ + public CompletableFuture> claimOrder(UUID orderId); + + // Lowest in sell offer -> Top offer for instant buy + // + // SQL: SELECT * FROM sellOffers ORDER BY PricePerUnit ASC LIMIT , ; + // This statement allows us to make a "sliding window" that slides + // across the database until we decided "Yep, that's enough data". + // This approach needs index on PricePerUnit column to speed up query time. + // + // Another approach is to use Statement.setFetchSize(Integer.MIN_VALUE) and keep + // calling ResultSet.next() until we have enough data. We need a way to get the + // rows one by one without having to sort every time we request new row. + /** + *

+ * Perform instant buy. + *

+ *

+ * Example implementation details: The market operator define the minimum units + * for buffer; The service attempts to fill the buffer by collecting top offers + * until the buffer is filled; When an instant offer is made, the service runs + * through its buffer and update the records (eg: perform SQL statement); When + * the buffer is below the defined minimum units, the service will collect top + * offers once again (the existing buffer will be discarded). Note that the + * refilling buffer phase should be performed every N seconds to avoid + * overloading the database. + *

+ *

+ * We choose to use buffer instead of collecting an entire database because we + * don't want to fill the memory quickly, but we also want to perform instant + * offers as fast as possible. + *

+ * + * @param user User's UUID. + * @param product The product. + * @param amount The units count (a.k.a how much you want to buy). + * @param balance The amount of money that user is willing to spend, typically + * 101% of total instant buy price for slippage tolerance. + * @return The instant buy result. + * @see #instantSell(Product, long) + */ + public CompletableFuture> instantBuy(UUID user, Product product, long amount, long balance); + + /** + *

+ * Perform instant sell. + *

+ *

+ * See {@link #instantBuy(Product, long, long)} for example on how to + * implementation the system. + *

+ * + * @param user User's UUID. + * @param product The product. + * @param amount The units to sell. + * @return The instant sell result. + * @see #instantBuy(Product, long, long) + */ + public CompletableFuture> instantSell(UUID user, Product product, long amount); + + /** + *

+ * Get the {@link Emittable} that emits event when an order is marked as filled. + * This will be used for notifying player about their filled order. + *

+ * + * @return The emittable. + */ + public Emittable onOrderFilled(); +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/OrderInfo.java b/core/src/main/java/io/github/nahkd123/stonks/market/OrderInfo.java new file mode 100644 index 0000000..0c99b03 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/OrderInfo.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market; + +import java.util.UUID; + +import io.github.nahkd123.stonks.market.catalogue.Product; +import io.github.nahkd123.stonks.market.helper.MutableOrder; + +public interface OrderInfo { + /** + *

+ * Get the database ID for this order entry. + *

+ * + * @return The order ID. + */ + public UUID getOrderId(); + + /** + *

+ * Get the user's UUID that made this order. In Minecraft, this is player's + * UUID. + *

+ * + * @return The user's UUID. + */ + public UUID getOwnerUuid(); + + public Product getProduct(); + + public long getTotalUnits(); + + public long getFilledUnits(); + + public long getClaimedUnits(); + + default long getUnclaimedUnits() { return getFilledUnits() - getClaimedUnits(); } + + public long getPricePerUnit(); + + default long getTotalPrice() { return getPricePerUnit() * getTotalUnits(); } + + default long getUnclaimedMoney() { return getPricePerUnit() * getUnclaimedUnits(); } + + public boolean isBuyOffer(); + + default boolean isSellOffer() { return !isBuyOffer(); } + + default boolean shouldRemoveFromListing() { + return getFilledUnits() >= getTotalUnits(); + } + + default boolean shouldRemoveFromPlayer() { + return getClaimedUnits() >= getTotalUnits(); + } + + default MutableOrder asMutable() { + MutableOrder mutable = new MutableOrder(); + mutable.setOrderId(getOrderId()); + mutable.setOwnerUuid(getOwnerUuid()); + mutable.setProduct(getProduct()); + mutable.setTotalUnits(getTotalUnits()); + mutable.setFilledUnits(getFilledUnits()); + mutable.setClaimedUnits(getClaimedUnits()); + mutable.setPricePerUnits(getPricePerUnit()); + mutable.setBuyOffer(isBuyOffer()); + return mutable; + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/Category.java b/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/Category.java new file mode 100644 index 0000000..6bf3ddb --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/Category.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.catalogue; + +import java.util.List; + +public interface Category { + public String getId(); + + public String getDisplayName(); + + public boolean isUncategorized(); + + public List getProducts(); +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/MutableCategory.java b/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/MutableCategory.java new file mode 100644 index 0000000..57324db --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/MutableCategory.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.catalogue; + +import java.util.ArrayList; +import java.util.List; + +public class MutableCategory implements Category { + private String id; + private String displayName; + private boolean uncategorized; + private List products = new ArrayList<>(); + + public MutableCategory(String id, String displayName, boolean uncategorized) { + this.id = id; + this.displayName = displayName; + this.uncategorized = uncategorized; + } + + @Override + public String getId() { return id; } + + @Override + public String getDisplayName() { return displayName; } + + public void setDisplayName(String displayName) { this.displayName = displayName; } + + @Override + public boolean isUncategorized() { return uncategorized; } + + @Override + public List getProducts() { return products; } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/MutableProduct.java b/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/MutableProduct.java new file mode 100644 index 0000000..abccf80 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/MutableProduct.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.catalogue; + +public class MutableProduct implements Product { + private String id; + private String displayName; + private String commodityString; + + public MutableProduct(String id, String displayName, String commodityString) { + this.id = id; + this.displayName = displayName; + this.commodityString = commodityString; + } + + @Override + public String getId() { return id; } + + @Override + public String getDisplayName() { return displayName; } + + public void setDisplayName(String displayName) { this.displayName = displayName; } + + @Override + public String getCommodityString() { return commodityString; } + + public void setCommodityString(String commodityString) { this.commodityString = commodityString; } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/MutableProductsCatalogue.java b/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/MutableProductsCatalogue.java new file mode 100644 index 0000000..824fcad --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/MutableProductsCatalogue.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.catalogue; + +import java.util.ArrayList; +import java.util.List; + +import io.github.nahkd123.stonks.market.MarketService; +import io.github.nahkd123.stonks.market.impl.sql.SqlMarketService; + +/** + *

+ * Mutable version of {@link ProductsCatalogue}. Mainly used in + * {@link MarketService} where you can set the catalogue at anytime (like + * {@link SqlMarketService} for example). + *

+ */ +public class MutableProductsCatalogue implements ProductsCatalogue { + private List categories = new ArrayList<>(); + private List products = new ArrayList<>(); + + @Override + public List getCategories() { return categories; } + + @Override + public List getProducts() { return products; } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/Product.java b/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/Product.java new file mode 100644 index 0000000..52f2d74 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/Product.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.catalogue; + +public interface Product { + public String getId(); + + public String getDisplayName(); + + /** + *

+ * Get the commodity/item information as a string, which will be used in + * {@code CommoditiesService#typeFromString(String)} in Stonks for Minecraft. + *

+ *

+ * We have to use string here for historical purpose, but once Stonks 3.0 came + * out, we'll mark this method as deprecated in favor of namespaced system. + *

+ *

+ * This is the equivalent of + * {@link stonks.core.Product#getProductConstructionData()}. + *

+ * + * @return The commodity string. + */ + public String getCommodityString(); +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/ProductsCatalogue.java b/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/ProductsCatalogue.java new file mode 100644 index 0000000..76914dc --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/catalogue/ProductsCatalogue.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.catalogue; + +import java.util.List; +import java.util.Optional; + +public interface ProductsCatalogue { + public List getCategories(); + + public List getProducts(); + + default Optional getProductById(String id) { + return getProducts().stream().filter(v -> v.getId().equals(id)).findFirst(); + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/helper/InstantOrdersProcessor.java b/core/src/main/java/io/github/nahkd123/stonks/market/helper/InstantOrdersProcessor.java new file mode 100644 index 0000000..b54ed40 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/helper/InstantOrdersProcessor.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.helper; + +import java.util.UUID; + +import io.github.nahkd123.stonks.market.result.InstantBuyResult; +import io.github.nahkd123.stonks.market.result.InstantSellResult; +import io.github.nahkd123.stonks.market.result.MutableInstantBuyResult; +import io.github.nahkd123.stonks.market.result.MutableInstantSellResult; + +public class InstantOrdersProcessor { + public static InstantBuyResult instantBuy(OrdersIterator iter, UUID user, long requested, long initialBalance) { + MutableInstantBuyResult result = new MutableInstantBuyResult(user, iter + .getProduct(), requested, initialBalance); + + while (iter.hasNext() && result.getPendingAmount() > 0L) { + MutableOrder order = iter.next().asMutable(); // Sell offers - lowest + if (order.getFilledUnits() == order.getTotalUnits()) continue; + + long available = order.getTotalUnits() - order.getFilledUnits(); + long canBuy = result.getNewBalance() / order.getPricePerUnit(); + long unitsBought = Math.min(available, canBuy); + long moneySpent = unitsBought * order.getPricePerUnit(); + if (unitsBought == 0L) return result; + + order.setFilledUnits(order.getFilledUnits() + unitsBought); + result.setBoughtAmount(result.getBoughtAmount() + unitsBought); + result.setNewBalance(result.getNewBalance() - moneySpent); + iter.update(order); + } + + return result; + } + + public static InstantSellResult instantSell(OrdersIterator iter, UUID user, long requested) { + MutableInstantSellResult result = new MutableInstantSellResult(user, iter.getProduct(), requested); + + while (iter.hasNext() && result.getLeftoverAmount() > 0L) { + MutableOrder order = iter.next().asMutable(); // Buy offers - highest + if (order.getFilledUnits() == order.getTotalUnits()) continue; + + long available = order.getTotalUnits() - order.getFilledUnits(); + long unitsSold = Math.min(available, result.getLeftoverAmount()); + long moneyEarn = unitsSold * order.getPricePerUnit(); + if (unitsSold == 0L) return result; + + order.setFilledUnits(order.getFilledUnits() + unitsSold); + result.setLeftoverAmount(result.getLeftoverAmount() - unitsSold); + result.setCollectedBalance(result.getCollectedBalance() + moneyEarn); + iter.update(order); + } + + return result; + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/helper/MutableOrder.java b/core/src/main/java/io/github/nahkd123/stonks/market/helper/MutableOrder.java new file mode 100644 index 0000000..3963169 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/helper/MutableOrder.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.helper; + +import java.util.UUID; +import java.util.function.Function; + +import io.github.nahkd123.stonks.market.OrderInfo; +import io.github.nahkd123.stonks.market.catalogue.Product; + +/** + *

+ * A mutable version of {@link OrderInfo}. + *

+ */ +public class MutableOrder implements OrderInfo { + private Function productsRegistry; + private Product product; + private UUID orderId; + private UUID ownerId; + private String productId; + private boolean isBuy; + private long units; + private long pricePerUnit; + private long filled; + private long claimed; + + public MutableOrder(Function productsRegistry, UUID orderId, UUID ownerId, String productId, boolean isBuy, long units, long pricePerUnit, long filled, long claimed) { + this.productsRegistry = productsRegistry; + this.isBuy = isBuy; + this.orderId = orderId; + this.ownerId = ownerId; + this.productId = productId; + this.units = units; + this.pricePerUnit = pricePerUnit; + this.filled = filled; + this.claimed = claimed; + } + + public MutableOrder() {} + + @Override + public UUID getOrderId() { return orderId; } + + public void setOrderId(UUID orderId) { this.orderId = orderId; } + + @Override + public UUID getOwnerUuid() { return ownerId; } + + public void setOwnerUuid(UUID ownerId) { this.ownerId = ownerId; } + + @Override + public Product getProduct() { return product != null ? product : (product = productsRegistry.apply(productId)); } + + public void setProduct(Product product) { + this.product = product; + this.productId = product.getId(); + } + + public String getProductId() { return productId; } + + public void setProductId(String productId) { + this.productId = productId; + if (productsRegistry != null) this.product = productsRegistry.apply(productId); + } + + @Override + public long getTotalUnits() { return units; } + + public void setTotalUnits(long units) { this.units = units; } + + @Override + public long getFilledUnits() { return filled; } + + public void setFilledUnits(long filled) { this.filled = filled; } + + @Override + public long getClaimedUnits() { return claimed; } + + public void setClaimedUnits(long claimed) { this.claimed = claimed; } + + @Override + public long getPricePerUnit() { return pricePerUnit; } + + public void setPricePerUnits(long pricePerUnit) { this.pricePerUnit = pricePerUnit; } + + @Override + public boolean isBuyOffer() { return isBuy; } + + public void setBuyOffer(boolean isBuy) { this.isBuy = isBuy; } + + @Override + public MutableOrder asMutable() { + return this; + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/helper/OrdersBuffer.java b/core/src/main/java/io/github/nahkd123/stonks/market/helper/OrdersBuffer.java new file mode 100644 index 0000000..542d373 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/helper/OrdersBuffer.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.helper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import io.github.nahkd123.stonks.market.OrderInfo; +import io.github.nahkd123.stonks.market.catalogue.Product; + +/** + *

+ * A buffer that holds a limited number of units. Primarily for generating + * product summary every N seconds. + *

+ */ +public class OrdersBuffer { + private List orders = new ArrayList<>(); + private Product product; + private boolean isBuy; + private long trackedUnits = 0L; + + public OrdersBuffer(Product product, boolean isBuy) { + this.product = product; + this.isBuy = isBuy; + } + + public Product getProduct() { return product; } + + public long getTrackedUnits() { return trackedUnits; } + + public List getOrders() { return Collections.unmodifiableList(orders); } + + public void insertOrder(MutableOrder order) { + int search = Collections.binarySearch(orders, order, (a, b) -> isBuy + ? Long.compare(b.getPricePerUnit(), a.getPricePerUnit()) + : Long.compare(a.getPricePerUnit(), b.getPricePerUnit())); + + if (search < 0) { + // search = -(insertion point) - 1 + // search + 1 = -insertion point + search = -(search + 1); + } + + orders.add(search, order); + trackedUnits += order.getTotalPrice() - order.getFilledUnits(); + } + + public void fillBuffer(long limit, Supplier supplier) { + while (limit == -1 || trackedUnits < limit) { + MutableOrder next = supplier.get(); + if (next == null) return; + insertOrder(next); + } + } + + public void reset() { + orders.clear(); + trackedUnits = 0L; + } + + public OrdersIterator iterator(Consumer orderUpdater) { + return new OrdersIterator() { + Iterator underlying = orders.iterator(); + long currentFilled; + + @Override + public void update(OrderInfo newInfo) { + orderUpdater.accept(newInfo.asMutable()); + long unitsFilled = newInfo.getFilledUnits() - currentFilled; + trackedUnits -= unitsFilled; + if (newInfo.getFilledUnits() >= newInfo.getTotalUnits()) underlying.remove(); + } + + @Override + public OrderInfo next() { + if (!hasNext()) throw new NoSuchElementException(); + OrderInfo current = null; + + do { + current = underlying.next(); + if (current.getFilledUnits() >= current.getTotalUnits()) underlying.remove(); + } while (current.getFilledUnits() < current.getTotalUnits()); + + currentFilled = current.getFilledUnits(); + return current; + } + + @Override + public boolean hasNext() { + return underlying.hasNext(); + } + + @Override + public Product getProduct() { return product; } + }; + } + + public long getRecommendedInstantOfferCap(long limit, long requestsPerCycle) { + // "Rapid limit" is the limit that scales with requests per N seconds. + long rapidLimit = trackedUnits / Math.max(requestsPerCycle, 1L); + return Math.min(rapidLimit, limit); + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/helper/OrdersIterator.java b/core/src/main/java/io/github/nahkd123/stonks/market/helper/OrdersIterator.java new file mode 100644 index 0000000..54dfaa0 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/helper/OrdersIterator.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.helper; + +import java.util.NoSuchElementException; + +import io.github.nahkd123.stonks.market.OrderInfo; +import io.github.nahkd123.stonks.market.catalogue.Product; + +public interface OrdersIterator { + public Product getProduct(); + + public boolean hasNext(); + + public OrderInfo next(); + + public void update(OrderInfo newInfo); + + public static OrdersIterator empty(Product product) { + return new OrdersIterator() { + @Override + public void update(OrderInfo newInfo) {} + + @Override + public OrderInfo next() { + throw new NoSuchElementException(); + } + + @Override + public boolean hasNext() { + return false; + } + + @Override + public Product getProduct() { return product; } + }; + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/helper/ProductSummaryGenerator.java b/core/src/main/java/io/github/nahkd123/stonks/market/helper/ProductSummaryGenerator.java new file mode 100644 index 0000000..3c16d0a --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/helper/ProductSummaryGenerator.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.helper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +import io.github.nahkd123.stonks.market.OrderInfo; +import io.github.nahkd123.stonks.market.catalogue.Product; +import io.github.nahkd123.stonks.market.summary.ProductSummary; +import io.github.nahkd123.stonks.market.summary.SummaryEntry; + +public class ProductSummaryGenerator { + private Product product; + private long lastGenerated; + private long cacheRetentionDuration; + private ProductSummary cached; + private Supplier buyOrders; + private Supplier sellOrders; + + public ProductSummaryGenerator(Product product, long cacheRetentionDuration, Supplier buyOrders, Supplier sellOrders) { + this.product = product; + this.cacheRetentionDuration = cacheRetentionDuration; + this.lastGenerated = -1L; + this.buyOrders = buyOrders; + this.sellOrders = sellOrders; + } + + public Product getProduct() { return product; } + + public long getLastGeneratedTime() { return lastGenerated; } + + public ProductSummary forceGenerate(int buyEntries, int sellEntries) { + List buy = new ArrayList<>(); + List sell = new ArrayList<>(); + fill(buy, buyOrders.get(), buyEntries); + fill(sell, sellOrders.get(), sellEntries); + lastGenerated = System.currentTimeMillis(); + cached = new ProductSummary(product, Collections.unmodifiableList(buy), Collections.unmodifiableList(sell)); + return cached; + } + + public ProductSummary generate(int buyEntries, int sellEntries) { + long timeDelta = System.currentTimeMillis() - lastGenerated; + if (cached == null || timeDelta >= cacheRetentionDuration) return forceGenerate(buyEntries, sellEntries); + return cached; + } + + private void fill(List list, OrdersIterator orders, int entries) { + long currentOffers = 0L; + long currentUnits = 0L; + long currentPrice = -1L; + + while (orders.hasNext() && list.size() < entries) { + OrderInfo info = orders.next(); + if (info.getFilledUnits() >= info.getTotalUnits()) continue; + + if (currentPrice == -1L) currentPrice = info.getPricePerUnit(); + else if (currentPrice != info.getPricePerUnit()) { + list.add(new SummaryEntry(currentUnits, currentOffers, currentPrice)); + currentOffers = 0L; + currentUnits = 0L; + currentPrice = info.getPricePerUnit(); + + if (list.size() >= entries) return; + } + + currentOffers += 1L; + currentUnits += info.getTotalUnits() - info.getFilledUnits(); + } + + if (currentOffers > 0L) list.add(new SummaryEntry(currentUnits, currentOffers, currentPrice)); + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyCategoryWrapper.java b/core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyCategoryWrapper.java new file mode 100644 index 0000000..4ef5383 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyCategoryWrapper.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.impl.legacy; + +import java.util.List; +import java.util.function.Consumer; + +import io.github.nahkd123.stonks.market.catalogue.Category; +import io.github.nahkd123.stonks.market.catalogue.Product; + +public class LegacyCategoryWrapper implements Category { + private stonks.core.product.Category legacy; + private List products; + + public LegacyCategoryWrapper(Consumer productsRegisterer, stonks.core.product.Category legacy) { + this.legacy = legacy; + this.products = legacy.getProducts().stream() + .map(l -> (Product) new LegacyProductWrapper(l)) + .toList(); + for (Product p : products) productsRegisterer.accept(p); + } + + @Override + public String getId() { return legacy.getCategoryId(); } + + @Override + public String getDisplayName() { return legacy.getCategoryName(); } + + @Override + public boolean isUncategorized() { return false; } + + @Override + public List getProducts() { return products.stream().map(v -> (Product) v).toList(); } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyMarketService.java b/core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyMarketService.java new file mode 100644 index 0000000..e0b6789 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyMarketService.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.impl.legacy; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.function.LongSupplier; + +import io.github.nahkd123.stonks.market.MarketService; +import io.github.nahkd123.stonks.market.OrderInfo; +import io.github.nahkd123.stonks.market.catalogue.Product; +import io.github.nahkd123.stonks.market.catalogue.ProductsCatalogue; +import io.github.nahkd123.stonks.market.result.ClaimResult; +import io.github.nahkd123.stonks.market.result.InstantBuyResult; +import io.github.nahkd123.stonks.market.result.InstantSellResult; +import io.github.nahkd123.stonks.market.result.MutableInstantBuyResult; +import io.github.nahkd123.stonks.market.result.MutableInstantSellResult; +import io.github.nahkd123.stonks.market.result.SimpleClaimResult; +import io.github.nahkd123.stonks.market.summary.ProductSummary; +import io.github.nahkd123.stonks.market.summary.SummaryEntry; +import io.github.nahkd123.stonks.utils.Async; +import io.github.nahkd123.stonks.utils.Response; +import io.github.nahkd123.stonks.utils.ServiceException; +import stonks.core.market.Offer; +import stonks.core.market.OfferType; +import stonks.core.market.OverviewOffer; +import stonks.core.market.ProductMarketOverview; +import stonks.core.service.Emittable; +import stonks.core.service.StonksService; + +/** + *

+ * The market service wrapper for {@link StonksService}. + *

+ */ +@Deprecated +public class LegacyMarketService implements MarketService { + private StonksService legacy; + private LongSupplier decimals; + private Emittable onOrderFilled = new Emittable<>(); + + public LegacyMarketService(StonksService legacy, LongSupplier decimals) { + this.legacy = legacy; + this.decimals = decimals; + legacy.subscribeToOfferFilledEvents(o -> onOrderFilled.emit(new LegacyOrderWrapper(o, decimals))); + } + + public StonksService getLegacy() { return legacy; } + + public static long doubleToLong(double money, LongSupplier decimals) { + long d = decimals.getAsLong(); + long wholeDollar = Math.round(Math.pow(10L, d)); + return Math.round(money * wholeDollar); + } + + public static double longToDouble(long money, LongSupplier decimals) { + long d = decimals.getAsLong(); + long wholeDollar = Math.round(Math.pow(10L, d)); + long dollars = money / wholeDollar; + long cents = money % wholeDollar; + return dollars + (cents / (double) wholeDollar); + } + + @Override + public CompletableFuture> queryCatalogue() { + return Async.futureOf(legacy.queryAllCategories()) + .thenApply(LegacyProductsCatalogue::new) + .thenApply(Response::new); + } + + public ProductSummary convert(ProductMarketOverview overview) { + Product w = new LegacyProductWrapper(overview.getProduct()); + List buy = overview.getBuyOffers().getEntries().stream().map(this::convert).toList(); + List sell = overview.getSellOffers().getEntries().stream().map(this::convert).toList(); + return new ProductSummary(w, buy, sell); + } + + public SummaryEntry convert(OverviewOffer overview) { + long ppu = doubleToLong(overview.pricePerUnit(), decimals); + return new SummaryEntry(overview.totalAvailableUnits(), overview.offers(), ppu); + } + + public OrderInfo convert(Offer offer) { + if (offer == null) return null; + return new LegacyOrderWrapper(offer, decimals); + } + + @Override + public CompletableFuture> querySummary(Product product) { + if (!(product instanceof LegacyProductWrapper w)) + return CompletableFuture.failedFuture(new IllegalArgumentException("product is not a legacy wrapper")); + + return Async.futureOf(legacy.queryProductMarketOverview(w.getLegacy())) + .thenApply(this::convert) + .thenApply(Response::new); + } + + @Override + public CompletableFuture>> queryOrders(UUID user) { + return Async.futureOf(legacy.getOffers(user)) + .thenApply(l -> l.stream().map(this::convert).toList()) + .thenApply(Response::new); + } + + @Override + public CompletableFuture> queryOrder(UUID orderId) { + return Async.futureOf(legacy.getOfferById(orderId)) + .thenApply(this::convert) + .thenApply(Response::new); + } + + @Override + public CompletableFuture> makeBuyOrder(UUID user, Product product, long amount, long pricePerUnit) { + if (!(product instanceof LegacyProductWrapper w)) + return CompletableFuture.failedFuture(new IllegalArgumentException("product is not a legacy wrapper")); + + return Async + .futureOf(legacy.listOffer(user, w.getLegacy(), OfferType.BUY, (int) amount, + longToDouble(pricePerUnit, decimals))) + .thenApply(this::convert) + .thenApply(Response::new); + } + + @Override + public CompletableFuture> makeSellOrder(UUID user, Product product, long amount, long pricePerUnit) { + if (!(product instanceof LegacyProductWrapper w)) + return CompletableFuture.failedFuture(new IllegalArgumentException("product is not a legacy wrapper")); + + return Async + .futureOf(legacy.listOffer(user, w.getLegacy(), OfferType.SELL, (int) amount, + longToDouble(pricePerUnit, decimals))) + .thenApply(this::convert) + .thenApply(Response::new); + } + + @Override + public CompletableFuture> claimOrder(UUID orderId) { + return queryOrder(orderId).thenCompose(res -> { + if (res.data() == null) + return CompletableFuture.failedStage(new ServiceException("No order with given UUID found")); + + OrderInfo previous = res.data(); + long previousClaimed = res.data().getClaimedUnits(); + return Async.futureOf(legacy.claimOffer(res.data().getOwnerUuid(), orderId)) + .thenApply(this::convert) + .thenApply(next -> { + long claimed = next.getClaimedUnits() - previousClaimed; + long worth = claimed * next.getPricePerUnit(); + boolean fullAfterClaim = next.getClaimedUnits() == next.getTotalUnits(); + SimpleClaimResult result = new SimpleClaimResult(previous.getOwnerUuid(), previous + .getProduct(), orderId, claimed, worth, fullAfterClaim); + return new Response<>(result); + }); + }); + } + + @Override + public CompletableFuture> instantBuy(UUID user, Product product, long amount, long balance) { + if (!(product instanceof LegacyProductWrapper w)) + return CompletableFuture.failedFuture(new IllegalArgumentException("product is not a legacy wrapper")); + + return Async.futureOf(legacy.instantBuy(w.getLegacy(), (int) amount, balance)) + .thenApply(result -> { + long moneySpent = doubleToLong(result.balance(), decimals); + long moneyLeft = balance - moneySpent; + return new MutableInstantBuyResult(user, product, amount, amount - result.units(), balance, moneyLeft); + }) + .thenApply(Response::new); + } + + @Override + public CompletableFuture> instantSell(UUID user, Product product, long amount) { + if (!(product instanceof LegacyProductWrapper w)) + return CompletableFuture.failedFuture(new IllegalArgumentException("product is not a legacy wrapper")); + + return Async.futureOf(legacy.instantSell(w.getLegacy(), (int) amount)) + .thenApply(result -> { + long moneyEarned = doubleToLong(result.balance(), decimals); + return new MutableInstantSellResult(user, product, amount, result.units(), moneyEarned); + }) + .thenApply(Response::new); + } + + @Override + public Emittable onOrderFilled() { + return onOrderFilled; + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyOrderWrapper.java b/core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyOrderWrapper.java new file mode 100644 index 0000000..7ea8bf3 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyOrderWrapper.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.impl.legacy; + +import java.util.UUID; +import java.util.function.LongSupplier; + +import io.github.nahkd123.stonks.market.OrderInfo; +import io.github.nahkd123.stonks.market.catalogue.Product; +import stonks.core.market.Offer; +import stonks.core.market.OfferType; + +public class LegacyOrderWrapper implements OrderInfo { + private Offer legacy; + private LongSupplier decimals; + + public LegacyOrderWrapper(Offer legacy, LongSupplier decimals) { + this.legacy = legacy; + this.decimals = decimals; + } + + public Offer getLegacy() { return legacy; } + + @Override + public UUID getOrderId() { return legacy.getOfferId(); } + + @Override + public UUID getOwnerUuid() { return legacy.getOffererId(); } + + @Override + public Product getProduct() { return new LegacyProductWrapper(legacy.getProduct()); } + + @Override + public long getTotalUnits() { return legacy.getTotalUnits(); } + + @Override + public long getFilledUnits() { return legacy.getFilledUnits(); } + + @Override + public long getClaimedUnits() { return legacy.getClaimedUnits(); } + + @SuppressWarnings("deprecation") + @Override + public long getPricePerUnit() { return LegacyMarketService.doubleToLong(legacy.getPricePerUnit(), decimals); } + + @Override + public boolean isBuyOffer() { return legacy.getType() == OfferType.BUY; } + +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyProductWrapper.java b/core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyProductWrapper.java new file mode 100644 index 0000000..89e63c5 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyProductWrapper.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.impl.legacy; + +import io.github.nahkd123.stonks.market.catalogue.Product; + +public class LegacyProductWrapper implements Product { + private stonks.core.product.Product legacy; + + public LegacyProductWrapper(stonks.core.product.Product legacy) { + this.legacy = legacy; + } + + public stonks.core.product.Product getLegacy() { return legacy; } + + @Override + public String getId() { return legacy.getProductId(); } + + @Override + public String getDisplayName() { return legacy.getProductName(); } + + @Override + public String getCommodityString() { return legacy.getProductConstructionData(); } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyProductsCatalogue.java b/core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyProductsCatalogue.java new file mode 100644 index 0000000..3adf117 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/impl/legacy/LegacyProductsCatalogue.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.impl.legacy; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import io.github.nahkd123.stonks.market.catalogue.Category; +import io.github.nahkd123.stonks.market.catalogue.Product; +import io.github.nahkd123.stonks.market.catalogue.ProductsCatalogue; + +public class LegacyProductsCatalogue implements ProductsCatalogue { + private List categories; + private List products = new ArrayList<>(); + + public LegacyProductsCatalogue(List legacyCategories) { + categories = legacyCategories.stream() + .map(l -> (Category) new LegacyCategoryWrapper(products::add, l)) + .toList(); + } + + @Override + public List getCategories() { return categories; } + + @Override + public List getProducts() { return Collections.unmodifiableList(products); } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/impl/queue/QueuedMarketService.java b/core/src/main/java/io/github/nahkd123/stonks/market/impl/queue/QueuedMarketService.java new file mode 100644 index 0000000..201d921 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/impl/queue/QueuedMarketService.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.impl.queue; + +import java.util.List; +import java.util.Queue; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executor; +import java.util.function.Supplier; + +import io.github.nahkd123.stonks.market.MarketService; +import io.github.nahkd123.stonks.market.OrderInfo; +import io.github.nahkd123.stonks.market.catalogue.Product; +import io.github.nahkd123.stonks.market.catalogue.ProductsCatalogue; +import io.github.nahkd123.stonks.market.impl.sql.SqlMarketService; +import io.github.nahkd123.stonks.market.result.ClaimResult; +import io.github.nahkd123.stonks.market.result.InstantBuyResult; +import io.github.nahkd123.stonks.market.result.InstantSellResult; +import io.github.nahkd123.stonks.market.summary.ProductSummary; +import io.github.nahkd123.stonks.utils.Response; +import stonks.core.service.Emittable; + +/** + *

+ * A requests queue variant of {@link MarketService}. This will queue the task + * while the executor process each requests one by one. + *

+ *

+ * The use case for this is to avoid race condition, as well as executing tasks + * without blocking current thread, unlike {@link SqlMarketService} for example. + *

+ */ +public class QueuedMarketService implements MarketService { + private static record QueueEntry(Supplier> executor, CompletableFuture result) { + public QueueEntry(Supplier> executor) { + this(executor, new CompletableFuture<>()); + } + + public void executeInCurrentThread() { + executor.get() + .handle((val, err) -> { + if (err != null) result.completeExceptionally(err); + else result.complete(val); + return err != null ? null : val; + }) + .thenAccept($ -> {}); + } + } + + private Queue> pending = new ConcurrentLinkedQueue<>(); + private MarketService underlying; + private Executor executor; + private boolean isIterating = false; + + public QueuedMarketService(MarketService underlying, Executor executor) { + this.underlying = underlying; + this.executor = executor; + } + + public MarketService getUnderlying() { return underlying; } + + public Executor getExecutor() { return executor; } + + public CompletableFuture queueTask(Supplier> taskExecutor) { + QueueEntry entry = new QueueEntry<>(taskExecutor); + pending.add(entry); + + if (!isIterating) { + isIterating = true; + + executor.execute(() -> { + while (!pending.isEmpty()) { + QueueEntry e = pending.poll(); + e.executeInCurrentThread(); + } + + isIterating = false; + }); + } + + return entry.result; + } + + @Override + public CompletableFuture> queryCatalogue() { + return queueTask(() -> underlying.queryCatalogue()); + } + + @Override + public CompletableFuture> querySummary(Product product) { + return queueTask(() -> underlying.querySummary(product)); + } + + @Override + public CompletableFuture>> queryOrders(UUID user) { + return queueTask(() -> underlying.queryOrders(user)); + } + + @Override + public CompletableFuture> queryOrder(UUID orderId) { + return queueTask(() -> underlying.queryOrder(orderId)); + } + + @Override + public CompletableFuture> makeBuyOrder(UUID user, Product product, long amount, long pricePerUnit) { + return queueTask(() -> underlying.makeBuyOrder(user, product, amount, pricePerUnit)); + } + + @Override + public CompletableFuture> makeSellOrder(UUID user, Product product, long amount, long pricePerUnit) { + return queueTask(() -> underlying.makeSellOrder(user, product, amount, pricePerUnit)); + } + + @Override + public CompletableFuture> claimOrder(UUID orderId) { + return queueTask(() -> underlying.claimOrder(orderId)); + } + + @Override + public CompletableFuture> instantBuy(UUID user, Product product, long amount, long balance) { + return queueTask(() -> underlying.instantBuy(user, product, amount, balance)); + } + + @Override + public CompletableFuture> instantSell(UUID user, Product product, long amount) { + return queueTask(() -> underlying.instantSell(user, product, amount)); + } + + @Override + public Emittable onOrderFilled() { + return underlying.onOrderFilled(); + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/Column.java b/core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/Column.java new file mode 100644 index 0000000..2b0821b --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/Column.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.impl.sql; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class Column { + private String name; + private ColumnType type; + private Function getter; + private BiConsumer setter; + + public Column(String name, ColumnType type, Function getter, BiConsumer setter) { + this.name = name; + this.type = type; + this.getter = getter; + this.setter = setter; + } + + public String getName() { return name; } + + public ColumnType getType() { return type; } + + public T get(R rec) { + return getter.apply(rec); + } + + public void set(R rec, T value) { + setter.accept(rec, value); + } + + public String getColumnSpec() { return name + " " + type.sqlType(); } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/ColumnType.java b/core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/ColumnType.java new file mode 100644 index 0000000..ab4ce3c --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/ColumnType.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.impl.sql; + +import java.util.UUID; +import java.util.function.Function; + +public record ColumnType(String sqlType, Function mapper, Function reverseMapper) { + public ColumnType map(Function mapper, Function reverseMapper) { + return new ColumnType<>(sqlType, this.mapper.andThen(mapper), reverseMapper.andThen(this.reverseMapper)); + } + + public static ColumnType varchar(int characters) { + return new ColumnType("varchar(" + characters + ")", o -> o.toString(), s -> s); + } + + //@formatter:off + public static final ColumnType TEXT = new ColumnType<>("text", o -> o.toString(), s -> s); + public static final ColumnType BIGINT = new ColumnType<>("bigint", o -> ((Number) o).longValue(), l -> l); + public static final ColumnType UUID = varchar(36).map(java.util.UUID::fromString, java.util.UUID::toString); + public static final ColumnType BOOL = new ColumnType("bool", o -> ((Integer) o == 1), b -> b); + //@formatter:on +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/MappedResultSet.java b/core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/MappedResultSet.java new file mode 100644 index 0000000..cea9844 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/MappedResultSet.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.impl.sql; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Optional; +import java.util.stream.Collector; + +public class MappedResultSet { + private Table table; + private ResultSet underlying; + + public MappedResultSet(Table table, ResultSet set) { + this.table = table; + this.underlying = set; + } + + public Table getTable() { return table; } + + public ResultSet getUnderlying() { return underlying; } + + public boolean next() throws SQLException { + return underlying.next(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public R getCurrent() throws SQLException { + R rec = table.newRecord(); + + for (int i = 0; i < table.getColumns().size(); i++) { + Column column = table.getColumns().get(i); + ((Column) column).set(rec, column.getType().mapper().apply(underlying.getObject(i + 1))); + } + + return rec; + } + + public Optional first() throws SQLException { + return next() ? Optional.of(getCurrent()) : Optional.empty(); + } + + public L collect(Collector collector) throws SQLException { + A list = collector.supplier().get(); + while (next()) collector.accumulator().accept(list, getCurrent()); + return collector.finisher().apply(list); + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/SqlMarketService.java b/core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/SqlMarketService.java new file mode 100644 index 0000000..437e3b4 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/SqlMarketService.java @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.impl.sql; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.UUID; +import java.util.WeakHashMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import io.github.nahkd123.stonks.market.MarketService; +import io.github.nahkd123.stonks.market.OrderInfo; +import io.github.nahkd123.stonks.market.catalogue.Product; +import io.github.nahkd123.stonks.market.catalogue.ProductsCatalogue; +import io.github.nahkd123.stonks.market.helper.InstantOrdersProcessor; +import io.github.nahkd123.stonks.market.helper.MutableOrder; +import io.github.nahkd123.stonks.market.helper.OrdersIterator; +import io.github.nahkd123.stonks.market.helper.ProductSummaryGenerator; +import io.github.nahkd123.stonks.market.impl.queue.QueuedMarketService; +import io.github.nahkd123.stonks.market.result.ClaimResult; +import io.github.nahkd123.stonks.market.result.InstantBuyResult; +import io.github.nahkd123.stonks.market.result.InstantSellResult; +import io.github.nahkd123.stonks.market.result.SimpleClaimResult; +import io.github.nahkd123.stonks.market.summary.ProductSummary; +import io.github.nahkd123.stonks.utils.Response; +import io.github.nahkd123.stonks.utils.ServiceException; +import io.github.nahkd123.stonks.utils.UncheckedException; +import stonks.core.service.Emittable; + +/** + *

+ * A market service that uses SQL database for storing orders data. + *

+ *

+ * The operations are synchronous and can block current thread! Although + * all operations returns {@link CompletableFuture}, it is actually synchronous. + * If you are looking for asynchronous operations, consider wrapping this + * service in {@link QueuedMarketService}. {@link QueuedMarketService} will + * still performs your operations sequentially, but operations are performed on + * a separate thread that the {@link Executor} belongs to. + *

+ */ +public class SqlMarketService implements MarketService { + private Emittable onOrderFilled = new Emittable<>(); + private ProductsCatalogue catalogue; + private Connection connection; + private Table ordersTable; + private Map summaries = new WeakHashMap<>(); + + public SqlMarketService(ProductsCatalogue catalogue, Connection connection) throws SQLException { + if (catalogue == null) throw new NullPointerException("catalogue can't be null"); + if (connection == null) throw new NullPointerException("connection can't be null (seriously?)"); + this.catalogue = catalogue; + this.connection = connection; + initializeDatabase(); + } + + private void initializeDatabase() throws SQLException { + ordersTable = new Table("orders", MutableOrder::new) + .setPrimary("id") + .addColumn("id", ColumnType.UUID, MutableOrder::getOrderId, MutableOrder::setOrderId) + .addColumn("owner", ColumnType.UUID, MutableOrder::getOwnerUuid, MutableOrder::setOwnerUuid) + .addColumn("product", ColumnType.varchar(50), MutableOrder::getProductId, MutableOrder::setProductId) + .addColumn("isBuy", ColumnType.BOOL, MutableOrder::isBuyOffer, MutableOrder::setBuyOffer) + .addColumn("units", ColumnType.BIGINT, MutableOrder::getTotalUnits, MutableOrder::setTotalUnits) + .addColumn("pricePerUnit", ColumnType.BIGINT, MutableOrder::getPricePerUnit, MutableOrder::setPricePerUnits) + .addColumn("filled", ColumnType.BIGINT, MutableOrder::getFilledUnits, MutableOrder::setFilledUnits) + .addColumn("claimed", ColumnType.BIGINT, MutableOrder::getClaimedUnits, MutableOrder::setClaimedUnits) + .initializeTable(connection); + } + + /** + *

+ * Update a specific order in the database with new data. If the order is fully + * filled and claimed, it will be removed from database. + *

+ *

+ * If the order ID does not exists in the database, it will creates a new + * record, otherwise it will attempts to update existing record. + *

+ * + * @param order The order. + * @throws SQLException + */ + public void updateOrder(MutableOrder order) throws SQLException { + if (order.getTotalUnits() == order.getClaimedUnits() && order.getTotalUnits() == order.getFilledUnits()) { + ordersTable.delete(connection, order); + return; + } + + ordersTable.update(connection, order); + if (order.getFilledUnits() == order.getTotalUnits()) onOrderFilled.emit(order); + } + + /** + *

+ * Filter only orders from specific product and order type. The result set is + * sorted in descending order for buy orders and ascending order for sell + * orders. + *

+ * + * @param product The product. + * @param isBuy true if you want to get all buy orders, otherwise false for + * sell orders. + * @return A mapped {@link ResultSet} that maps each record into + * {@link MutableOrder}. Any changes in {@link MutableOrder} will NOT + * reflects back to the database; you'll have to use + * {@link #updateOrder(MutableOrder)} if you want. + * @throws SQLException + */ + public MappedResultSet filterOrders(Product product, boolean isBuy) throws SQLException { + PreparedStatement s = connection + .prepareStatement("SELECT * FROM " + ordersTable.getTableName() + + " WHERE product=? AND isBuy=? ORDER BY pricePerUnit " + (isBuy ? "DESC" : "ASC")); + s.setString(1, product.getId()); + s.setBoolean(2, isBuy); + s.setFetchSize(10); + return ordersTable.mapResults(s.executeQuery()); + } + + /** + *

+ * Create orders iterator that you can iterate the orders in sorted order and + * update them if needed. + *

+ * + * @param product The product. + * @param isBuy true if you want to get all buy orders, otherwise false for + * sell orders. + * @return An iterator. + * @throws SQLException + */ + public OrdersIterator createIterator(Product product, boolean isBuy) throws SQLException { + MappedResultSet orders = filterOrders(product, isBuy); + return new OrdersIterator() { + MutableOrder current = null; + boolean ended = false; + + @Override + public void update(OrderInfo newInfo) { + try { + updateOrder(newInfo.asMutable()); + } catch (SQLException e) { + throw new UncheckedException(e); + } + } + + @Override + public OrderInfo next() { + if (!hasNext()) throw new NoSuchElementException(); + MutableOrder temp = current; + current = null; + return temp; + } + + @Override + public boolean hasNext() { + try { + if (ended) return false; + if (current != null) return true; + + if (!orders.next()) { + ended = true; + return false; + } + + current = orders.getCurrent(); + return true; + } catch (SQLException e) { + throw new UncheckedException(e); + } + } + + @Override + public Product getProduct() { return product; } + }; + } + + public ProductsCatalogue getCatalogue() { return catalogue; } + + public void setCatalogue(ProductsCatalogue catalogue) { + if (catalogue == null) throw new NullPointerException("catalogue can't be null"); + this.catalogue = catalogue; + } + + @Override + public CompletableFuture> queryCatalogue() { + return CompletableFuture.completedFuture(new Response<>(getCatalogue())); + } + + @Override + public CompletableFuture> querySummary(Product product) { + ProductSummaryGenerator generator = summaries.get(product.getId()); + if (generator == null) { + Supplier buy = () -> { + try { + return createIterator(product, true); + } catch (SQLException e) { + e.printStackTrace(); + return OrdersIterator.empty(product); + } + }; + Supplier sell = () -> { + try { + return createIterator(product, false); + } catch (SQLException e) { + e.printStackTrace(); + return OrdersIterator.empty(product); + } + }; + + summaries.put(product.getId(), generator = new ProductSummaryGenerator(product, 1000L, buy, sell)); + } + + return CompletableFuture.completedFuture(new Response<>(generator.generate(5, 5))); // TODO customizable + } + + @Override + public CompletableFuture>> queryOrders(UUID user) { + try { + PreparedStatement s = connection + .prepareStatement("SELECT * FROM " + ordersTable.getTableName() + " WHERE owner=?"); + s.setString(1, user.toString()); + List orderInfo = ordersTable.mapResults(s.executeQuery()).collect(Collectors.toList()); + return CompletableFuture.completedFuture(new Response<>(orderInfo.stream() + .map(v -> (OrderInfo) v) + .toList())); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + @Override + public CompletableFuture> queryOrder(UUID orderId) { + try { + PreparedStatement s = connection + .prepareStatement("SELECT * FROM " + ordersTable.getTableName() + " WHERE id=?"); + s.setString(1, orderId.toString()); + MutableOrder orderInfo = ordersTable.mapResults(s.executeQuery()).first().orElse(null); + return CompletableFuture.completedFuture(new Response<>(orderInfo)); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + @Override + public CompletableFuture> makeBuyOrder(UUID user, Product product, long amount, long pricePerUnit) { + try { + MutableOrder order = new MutableOrder(id -> getCatalogue().getProductById(id).orElse(null), UUID + .randomUUID(), user, product.getId(), true, amount, pricePerUnit, 0L, 0L); + updateOrder(order); + return CompletableFuture.completedFuture(new Response<>(order)); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + @Override + public CompletableFuture> makeSellOrder(UUID user, Product product, long amount, long pricePerUnit) { + try { + MutableOrder order = new MutableOrder(id -> getCatalogue().getProductById(id).orElse(null), UUID + .randomUUID(), user, product.getId(), false, amount, pricePerUnit, 0L, 0L); + updateOrder(order); + return CompletableFuture.completedFuture(new Response<>(order)); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + @Override + public CompletableFuture> claimOrder(UUID orderId) { + return queryOrder(orderId) + .thenCompose(res -> { + if (res.data() == null) + return CompletableFuture.failedStage(new ServiceException("No order with given UUID found")); + + MutableOrder info = res.data().asMutable(); + long newlyClaimed = info.getUnclaimedUnits(); + info.setClaimedUnits(info.getFilledUnits()); + + try { + updateOrder(info); + } catch (SQLException e) { + return CompletableFuture.failedStage(e); + } + + SimpleClaimResult result = new SimpleClaimResult(info.getOwnerUuid(), info + .getProduct(), orderId, newlyClaimed, newlyClaimed + * info.getPricePerUnit(), info.getClaimedUnits() == info.getTotalUnits()); + return CompletableFuture.completedStage(new Response<>(result)); + }); + } + + @Override + public CompletableFuture> instantBuy(UUID user, Product product, long amount, long balance) { + try { + OrdersIterator sellIter = createIterator(product, false); + return CompletableFuture + .completedFuture(new Response<>(InstantOrdersProcessor.instantBuy(sellIter, user, amount, balance))); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + @Override + public CompletableFuture> instantSell(UUID user, Product product, long amount) { + try { + OrdersIterator buyIter = createIterator(product, true); + return CompletableFuture + .completedFuture(new Response<>(InstantOrdersProcessor.instantSell(buyIter, user, amount))); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + @Override + public Emittable onOrderFilled() { + return onOrderFilled; + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/Table.java b/core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/Table.java new file mode 100644 index 0000000..84cc48a --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/impl/sql/Table.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.impl.sql; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class Table { + private String tableName; + private Supplier factory; + private String primary = null; + private List> columns = new ArrayList<>(); + + public Table(String tableName, Supplier factory) { + this.tableName = tableName; + this.factory = factory; + } + + public String getTableName() { return tableName; } + + public R newRecord() { + return factory.get(); + } + + public List> getColumns() { return Collections.unmodifiableList(columns); } + + public Table addColumn(String name, ColumnType type, Function getter, BiConsumer setter) { + columns.add(new Column<>(name, type, getter, setter)); + return this; + } + + public boolean removeColumn(String name) { + Iterator> iterator = columns.iterator(); + + while (iterator.hasNext()) { + Column column = iterator.next(); + if (column.getName().equals(name)) { + iterator.remove(); + return true; + } + } + + return false; + } + + public Column getColumn(String name) { + return columns.stream().filter(c -> c.getName().equals(name)).findFirst().orElse(null); + } + + public Table setPrimary(String primary) { + this.primary = primary; + return this; + } + + public String getPrimary() { return primary; } + + public Table initializeTable(Connection connection) throws SQLException { + try (ResultSet set = connection.getMetaData().getTables(null, null, tableName, null)) { + if (!set.next()) { + // No table, create a new one + Statement s = connection.createStatement(); + String tableSpec = columns.stream() + .map(c -> { + String columnSpec = c.getColumnSpec(); + if (c.getName().equals(primary)) columnSpec += " PRIMARY KEY"; + return columnSpec; + }) + .collect(Collectors.joining(", ")); + s.execute("CREATE TABLE " + tableName + " (" + tableSpec + ")"); + return this; + } + } + + // TODO check columns to see if we need to update anything + return this; + } + + public String getColumnsAsString() { + return columns.stream().map(Column::getName).collect(Collectors.joining(", ")); + } + + public Table insert(Connection connection, R data) throws SQLException { + String cols = getColumnsAsString(); + String colsPlaceholder = columns.stream().map($ -> "?").collect(Collectors.joining(", ")); + PreparedStatement s = connection + .prepareStatement("INSERT INTO " + tableName + " (" + cols + ") VALUES (" + colsPlaceholder + ")"); + fillIn(s, data); + s.execute(); + return this; + } + + public Table update(Connection connection, R data) throws SQLException { + if (primary == null) return insert(connection, data); + String cols = getColumnsAsString(); + String colsPlaceholder = columns.stream().map($ -> "?").collect(Collectors.joining(", ")); + PreparedStatement s = connection + .prepareStatement("REPLACE INTO " + tableName + " (" + cols + ") VALUES (" + colsPlaceholder + ")"); + fillIn(s, data); + s.execute(); + return this; + } + + public Table delete(Connection connection, R data) throws SQLException { + if (primary == null) return this; + PreparedStatement s = connection.prepareStatement("DELETE FROM " + tableName + " WHERE " + primary + "=?"); + s.setObject(1, getColumn(primary).get(data)); + s.execute(); + return this; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void fillIn(PreparedStatement s, R data) throws SQLException { + for (int i = 0; i < columns.size(); i++) { + Column column = columns.get(i); + s.setObject(i + 1, ((Function) column.getType().reverseMapper()).apply(column.get(data))); + } + } + + public MappedResultSet mapResults(ResultSet set) { + return new MappedResultSet<>(this, set); + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/result/ClaimResult.java b/core/src/main/java/io/github/nahkd123/stonks/market/result/ClaimResult.java new file mode 100644 index 0000000..5c75d6d --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/result/ClaimResult.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.result; + +import java.util.UUID; + +public interface ClaimResult extends Result { + public UUID getOrderId(); + + public long getClaimedAmount(); + + public long getClaimedWorth(); + + /** + *

+ * Check fully claimed state once user claimed the items/money. This will be + * used for closing the GUI when user claim stuffs. + *

+ * + * @return Full state. + */ + public boolean isFullAfterClaim(); +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/result/InstantBuyResult.java b/core/src/main/java/io/github/nahkd123/stonks/market/result/InstantBuyResult.java new file mode 100644 index 0000000..333da49 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/result/InstantBuyResult.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.result; + +public interface InstantBuyResult extends InstantOfferResult { + public long getInitialBalance(); + + public long getBoughtAmount(); + + public long getNewBalance(); + + default long getPendingAmount() { return getRequestedAmount() - getBoughtAmount(); } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/result/InstantOfferResult.java b/core/src/main/java/io/github/nahkd123/stonks/market/result/InstantOfferResult.java new file mode 100644 index 0000000..70f525c --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/result/InstantOfferResult.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.result; + +public interface InstantOfferResult extends Result { + public long getRequestedAmount(); +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/result/InstantSellResult.java b/core/src/main/java/io/github/nahkd123/stonks/market/result/InstantSellResult.java new file mode 100644 index 0000000..0c05037 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/result/InstantSellResult.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.result; + +public interface InstantSellResult extends InstantOfferResult { + public long getLeftoverAmount(); + + public long getCollectedBalance(); +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/result/MutableInstantBuyResult.java b/core/src/main/java/io/github/nahkd123/stonks/market/result/MutableInstantBuyResult.java new file mode 100644 index 0000000..1d49146 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/result/MutableInstantBuyResult.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.result; + +import java.util.UUID; + +import io.github.nahkd123.stonks.market.catalogue.Product; + +public class MutableInstantBuyResult implements InstantBuyResult { + private UUID user; + private Product product; + private long requested; + private long bought; + private long initialBalance; + private long newBalance; + + public MutableInstantBuyResult(UUID user, Product product, long requested, long bought, long initialBalance, long newBalance) { + this.user = user; + this.product = product; + this.requested = requested; + this.bought = bought; + this.initialBalance = initialBalance; + this.newBalance = newBalance; + } + + public MutableInstantBuyResult(UUID user, Product product, long requested, long initialBalance) { + this(user, product, requested, 0L, initialBalance, initialBalance); + } + + @Override + public long getRequestedAmount() { return requested; } + + public void setRequestedAmount(long requested) { this.requested = requested; } + + @Override + public UUID getUserId() { return user; } + + public void setUserId(UUID user) { this.user = user; } + + @Override + public Product getProduct() { return product; } + + public void setProduct(Product product) { this.product = product; } + + @Override + public long getInitialBalance() { return initialBalance; } + + public void setInitialBalance(long initialBalance) { this.initialBalance = initialBalance; } + + @Override + public long getBoughtAmount() { return bought; } + + public void setBoughtAmount(long bought) { this.bought = bought; } + + @Override + public long getNewBalance() { return newBalance; } + + public void setNewBalance(long newBalance) { this.newBalance = newBalance; } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/result/MutableInstantSellResult.java b/core/src/main/java/io/github/nahkd123/stonks/market/result/MutableInstantSellResult.java new file mode 100644 index 0000000..c5bd546 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/result/MutableInstantSellResult.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.result; + +import java.util.UUID; + +import io.github.nahkd123.stonks.market.catalogue.Product; + +public class MutableInstantSellResult implements InstantSellResult { + private UUID user; + private Product product; + private long requested; + private long leftover; + private long balance; + + public MutableInstantSellResult(UUID user, Product product, long requested, long leftover, long balance) { + this.user = user; + this.product = product; + this.requested = requested; + this.leftover = leftover; + this.balance = balance; + } + + public MutableInstantSellResult(UUID user, Product product, long requested) { + this(user, product, requested, requested, 0L); + } + + @Override + public long getRequestedAmount() { return requested; } + + public void setRequestedAmount(long requested) { this.requested = requested; } + + @Override + public UUID getUserId() { return user; } + + public void setUserId(UUID user) { this.user = user; } + + @Override + public Product getProduct() { return product; } + + public void setProduct(Product product) { this.product = product; } + + @Override + public long getLeftoverAmount() { return leftover; } + + public void setLeftoverAmount(long leftover) { this.leftover = leftover; } + + @Override + public long getCollectedBalance() { return balance; } + + public void setCollectedBalance(long balance) { this.balance = balance; } + +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/result/Result.java b/core/src/main/java/io/github/nahkd123/stonks/market/result/Result.java new file mode 100644 index 0000000..cfc638c --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/result/Result.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.result; + +import java.util.UUID; + +import io.github.nahkd123.stonks.market.catalogue.Product; + +public interface Result { + public UUID getUserId(); + + public Product getProduct(); +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/result/SimpleClaimResult.java b/core/src/main/java/io/github/nahkd123/stonks/market/result/SimpleClaimResult.java new file mode 100644 index 0000000..49d9b0c --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/result/SimpleClaimResult.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.result; + +import java.util.UUID; + +import io.github.nahkd123.stonks.market.catalogue.Product; + +public class SimpleClaimResult implements ClaimResult { + private UUID user; + private Product product; + private UUID orderId; + private long claimedAmount; + private long claimedWorth; + private boolean fullAfterClaim; + + public SimpleClaimResult(UUID user, Product product, UUID orderId, long claimedAmount, long claimedWorth, boolean fullAfterClaim) { + this.user = user; + this.product = product; + this.orderId = orderId; + this.claimedAmount = claimedAmount; + this.claimedWorth = claimedWorth; + this.fullAfterClaim = fullAfterClaim; + } + + @Override + public UUID getUserId() { return user; } + + @Override + public Product getProduct() { return product; } + + @Override + public UUID getOrderId() { return orderId; } + + @Override + public long getClaimedAmount() { return claimedAmount; } + + @Override + public long getClaimedWorth() { return claimedWorth; } + + @Override + public boolean isFullAfterClaim() { return fullAfterClaim; } + +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/summary/ProductSummary.java b/core/src/main/java/io/github/nahkd123/stonks/market/summary/ProductSummary.java new file mode 100644 index 0000000..339b734 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/summary/ProductSummary.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.summary; + +import java.util.List; + +import io.github.nahkd123.stonks.market.catalogue.Product; + +public class ProductSummary { + private Product product; + private List buySummary; + private List sellSummary; + private long instantBuyPrice, instantSellPrice; + + public ProductSummary(Product product, List buySummary, List sellSummary) { + this.product = product; + this.buySummary = buySummary; + this.sellSummary = sellSummary; + this.instantBuyPrice = avg(sellSummary); + this.instantSellPrice = avg(buySummary); + } + + public Product getProduct() { return product; } + + public List getBuySummary() { return buySummary; } + + public List getSellSummary() { return sellSummary; } + + public long getInstantBuyPrice() { return instantBuyPrice; } + + public long getInstantSellPrice() { return instantSellPrice; } + + public static long avg(List entries) { + long sum = 0; + long totalUnits = 0; + + for (SummaryEntry entry : entries) { + totalUnits += entry.totalUnits(); + sum += entry.totalUnits() * entry.pricePerUnit(); + } + + return totalUnits == 0 ? 0L : (sum / totalUnits); + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/summary/SummaryEntry.java b/core/src/main/java/io/github/nahkd123/stonks/market/summary/SummaryEntry.java new file mode 100644 index 0000000..44f76cb --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/market/summary/SummaryEntry.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.summary; + +// 100x of PRODUCT NAME in 727 offers for $69.42/ea +public record SummaryEntry(long totalUnits, long totalOffers, long pricePerUnit) { +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/utils/Async.java b/core/src/main/java/io/github/nahkd123/stonks/utils/Async.java new file mode 100644 index 0000000..a06d766 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/utils/Async.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.utils; + +import java.util.concurrent.CompletableFuture; + +import nahara.common.tasks.Task; + +public class Async { + public static CompletableFuture futureOf(Task task) { + CompletableFuture future = new CompletableFuture<>(); + task.onCompleted(result -> { + if (result.isSuccess()) future.complete(result.getSuccess()); + else future.completeExceptionally(result.getFailure()); + }); + + return future; + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/utils/Response.java b/core/src/main/java/io/github/nahkd123/stonks/utils/Response.java new file mode 100644 index 0000000..b2170f7 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/utils/Response.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.utils; + +public record Response(long timestamp, T data) { + public Response(T data) { + this(System.currentTimeMillis(), data); + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/utils/ServiceException.java b/core/src/main/java/io/github/nahkd123/stonks/utils/ServiceException.java new file mode 100644 index 0000000..f4f60fe --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/utils/ServiceException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.utils; + +import java.io.Serial; + +public class ServiceException extends RuntimeException { + @Serial + private static final long serialVersionUID = 1479903348806928786L; + + public ServiceException(String message) { + super(message); + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/utils/UncheckedException.java b/core/src/main/java/io/github/nahkd123/stonks/utils/UncheckedException.java new file mode 100644 index 0000000..519b76f --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/utils/UncheckedException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.utils; + +import java.io.Serial; + +public class UncheckedException extends RuntimeException { + @Serial + private static final long serialVersionUID = -5822649304326326887L; + + public UncheckedException(Throwable t) { + super(t); + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/CachingLazyLoader.java b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/CachingLazyLoader.java new file mode 100644 index 0000000..ae7cd19 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/CachingLazyLoader.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.utils.lazy; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +public class CachingLazyLoader implements LazyLoader { + private Supplier> loader; + private long retentionTime; + private long lastFetched = -1L; + private LoadState lastState = null; + private Throwable lastError = null; + private T lastResult = null; + + public CachingLazyLoader(Supplier> loader, long retentionTime) { + this.loader = loader; + this.retentionTime = retentionTime; + } + + public void refresh() { + lastFetched = System.currentTimeMillis(); + lastState = LoadState.LOADING; + loader.get().handle((result, err) -> { + if (err != null) { + lastState = LoadState.FAILED; + lastError = err; + return null; + } + + lastState = LoadState.SUCCESS; + lastResult = result; + return result; + }); + } + + @Override + public LoadState load() { + if (lastState == null || System.currentTimeMillis() >= lastFetched + retentionTime) refresh(); + return lastState; + } + + @Override + public Throwable getFailure() { return lastError; } + + @Override + public T get() throws IllegalStateException { + if (lastState != LoadState.SUCCESS) + throw new IllegalStateException("This lazy loader is not loaded successfully or is currently loading!"); + if (lastState == LoadState.FAILED) throw new IllegalStateException(lastError); + return lastResult; + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/CompletableFutureLazyLoader.java b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/CompletableFutureLazyLoader.java new file mode 100644 index 0000000..67eefcf --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/CompletableFutureLazyLoader.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.utils.lazy; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public class CompletableFutureLazyLoader implements LazyLoader { + private CompletableFuture future; + + public CompletableFutureLazyLoader(CompletableFuture future) { + this.future = future; + } + + public Future getFuture() { return future; } + + @Override + public LoadState load() { + return future.isDone() + ? future.isCancelled() || future.isCompletedExceptionally() + ? LoadState.FAILED + : LoadState.SUCCESS + : LoadState.LOADING; + } + + @Override + public Throwable getFailure() { + if (!future.isDone()) return null; + if (future.isCompletedExceptionally()) { + try { + future.get(); + } catch (InterruptedException | ExecutionException e) { + return e; + } + } + if (future.isCancelled()) return new CancellationException(); + return null; + } + + @Override + public T get() throws IllegalStateException { + if (!future.isDone()) throw new IllegalStateException("The CompletableFuture is still loading!"); + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LazyLoader.java b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LazyLoader.java new file mode 100644 index 0000000..3e88de7 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LazyLoader.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.utils.lazy; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.function.Supplier; + +import nahara.common.tasks.Task; + +/** + *

+ * Any objects that implements {@link LazyLoader} can be loaded lazily (that is, + * it will be loaded when you request it to be loaded for display). + *

+ */ +public interface LazyLoader { + /** + *

+ * Load the object if needed and returns the loaded state. + *

+ * + * @return The loaded state. + */ + public LoadState load(); + + /** + *

+ * Get the {@link Throwable} that makes this loader failed to load the object. + *

+ * + * @return The error/exception. + */ + public Throwable getFailure(); + + /** + *

+ * Get the object that has been loaded. May returns {@code null}. + *

+ * + * @return The object that has been lazily loaded. + * @throws IllegalStateException if {@link #load()} returns {@code false} + * because it hasn't been loaded, it is currently + * loading or an error occurred. + */ + public T get() throws IllegalStateException; + + /** + *

+ * Get the object based on current loading state (loading, failed or success). + *

+ * + * @param onLoading The object supplier if the object is still being loaded. + * @param onFail The object supplier if the loading process failed. + * @return The object. + */ + default T getTriState(Supplier onLoading, Function onFail) { + return switch (load()) { + case LOADING -> onLoading.get(); + case FAILED -> onFail.apply(getFailure()); + case SUCCESS -> get(); + default -> onLoading.get(); + }; + } + + default LazyMapper map(Function mapper) { + return new LazyMapper<>(this, mapper); + } + + public static LazyLoader wrap(CompletableFuture future) { + return new CompletableFutureLazyLoader<>(future); + } + + public static LazyLoader wrap(Task task) { + return new LegacyTaskLazyLoader<>(task); + } + + public static LazyLoader ofFinished(T result) { + return new LoadedLazyLoader<>(result, null); + } + + public static LazyLoader ofFailed(Throwable throwable) { + return new LoadedLazyLoader<>(null, throwable); + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LazyMapper.java b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LazyMapper.java new file mode 100644 index 0000000..fff960b --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LazyMapper.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.utils.lazy; + +import java.util.function.Function; + +public class LazyMapper implements LazyLoader { + private LazyLoader loader; + private Function mapper; + + public LazyMapper(LazyLoader loader, Function mapper) { + this.loader = loader; + this.mapper = mapper; + } + + @Override + public LoadState load() { + return loader.load(); + } + + @Override + public Throwable getFailure() { return loader.getFailure(); } + + @Override + public B get() throws IllegalStateException { + return mapper.apply(loader.get()); + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LegacyTaskLazyLoader.java b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LegacyTaskLazyLoader.java new file mode 100644 index 0000000..b412859 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LegacyTaskLazyLoader.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.utils.lazy; + +import nahara.common.tasks.Task; + +public class LegacyTaskLazyLoader implements LazyLoader { + private Task task; + private LoadState state = LoadState.LOADING; + private Throwable throwable; + private T result; + + public LegacyTaskLazyLoader(Task task) { + this.task = task; + this.task.onCompleted(result -> { + if (result.isSuccess()) { + this.result = result.getSuccess(); + this.state = LoadState.SUCCESS; + } else { + this.throwable = result.getFailure(); + this.state = LoadState.FAILED; + } + }); + } + + public Task getTask() { return task; } + + @Override + public LoadState load() { + return state; + } + + @Override + public Throwable getFailure() { return throwable; } + + @Override + public T get() throws IllegalStateException { + if (state != LoadState.SUCCESS) + throw new IllegalStateException("This lazy loader is not loaded successfully or is currently loading!"); + if (state == LoadState.FAILED) throw new IllegalStateException(throwable); + return result; + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LoadState.java b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LoadState.java new file mode 100644 index 0000000..bedfc37 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LoadState.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.utils.lazy; + +public enum LoadState { + LOADING, + SUCCESS, + FAILED; +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LoadedLazyLoader.java b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LoadedLazyLoader.java new file mode 100644 index 0000000..21da1a2 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LoadedLazyLoader.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.utils.lazy; + +public class LoadedLazyLoader implements LazyLoader { + private T result; + private Throwable error; + + public LoadedLazyLoader(T result, Throwable error) { + this.result = result; + this.error = error; + } + + @Override + public LoadState load() { + return error == null ? LoadState.SUCCESS : LoadState.FAILED; + } + + @Override + public Throwable getFailure() { return error; } + + @Override + public T get() throws IllegalStateException { + if (error != null) throw new IllegalStateException(error); + return result; + } +} diff --git a/core/src/main/java/stonks/core/exec/InstantOfferExecutor.java b/core/src/main/java/stonks/core/exec/InstantOfferExecutor.java index 516b265..fda724b 100644 --- a/core/src/main/java/stonks/core/exec/InstantOfferExecutor.java +++ b/core/src/main/java/stonks/core/exec/InstantOfferExecutor.java @@ -45,7 +45,8 @@ public InstantOfferExecutor(double currentBalance, int currentUnits) { /** *

- * Execute instant buy. + * Execute instant buy. Current balance is how much user spent and current units + * is how much units to buy. *

* * @param sellOffersIterator An iterator of sorted sell offers. This method @@ -77,7 +78,8 @@ public InstantOfferExecutor executeInstantBuy(Iterator sellOffersIterator /** *

- * Execute instant sell. + * Execute instant sell. Current balance should be 0 to store earnings and + * current units is how much units to sell. *

* * @param buyOffersIterator An iterator of sorted buy offers. This method may diff --git a/core/src/main/java/stonks/core/service/Emittable.java b/core/src/main/java/stonks/core/service/Emittable.java index 61ec47a..e6163ac 100644 --- a/core/src/main/java/stonks/core/service/Emittable.java +++ b/core/src/main/java/stonks/core/service/Emittable.java @@ -30,6 +30,7 @@ public class Emittable { private boolean isEmitting = false; private List pendingObjects; private List> pendingConsumers; + private List> pendingRemoval; public void listen(Consumer consumer) { if (!isEmitting) { @@ -41,6 +42,16 @@ public void listen(Consumer consumer) { pendingConsumers.add(consumer); } + public void remove(Consumer consumer) { + if (!isEmitting) { + consumers.remove(consumer); + return; + } + + if (pendingRemoval == null) pendingRemoval = new ArrayList<>(); + pendingRemoval.add(consumer); + } + public void emit(T obj) { if (!isEmitting) { isEmitting = true; @@ -67,6 +78,11 @@ public void emit(T obj) { pendingConsumers = null; } + if (pendingRemoval != null) { + for (var c : pendingRemoval) consumers.remove(c); + pendingRemoval = null; + } + if (pendingObjects != null && pendingObjects.size() > 0) { obj = pendingObjects.remove(0); } else { diff --git a/core/src/main/java/stonks/core/service/StonksService.java b/core/src/main/java/stonks/core/service/StonksService.java index 0808eaf..587c204 100644 --- a/core/src/main/java/stonks/core/service/StonksService.java +++ b/core/src/main/java/stonks/core/service/StonksService.java @@ -25,6 +25,7 @@ import java.util.UUID; import java.util.function.Consumer; +import io.github.nahkd123.stonks.market.impl.legacy.LegacyMarketService; import nahara.common.tasks.Task; import stonks.core.exec.InstantOfferExecuteResult; import stonks.core.market.Offer; @@ -33,6 +34,23 @@ import stonks.core.product.Category; import stonks.core.product.Product; +/** + *

+ * The Stonks service interface. You can perform service calls with this + * interface. Note that when initializing the service, it must (or actually, it + * shouldn't) throw any exceptions and it will only initiate the + * connection when a service call is made. + *

+ *

+ * Connection failures should be held until another service call is made. Once a + * call is performed, the failures will be thrown as exception. + *

+ *

+ * Note that this service interface might be removed in the future as the + * current interface is way too janky. + *

+ */ +@Deprecated public interface StonksService { /** *

@@ -62,6 +80,20 @@ public interface StonksService { */ public Task> getOffers(UUID offerer); + /** + *

+ * Get offer by offer ID. + *

+ *

+ * This method was added to support {@link LegacyMarketService#queryOrder(UUID)} + *

+ * + * @param id The id of the offer. + * @return The offer with matching id. + * @since Stonks 2.1.0 + */ + public Task getOfferById(UUID id); + /** *

* Claim specified offer. diff --git a/core/src/main/java/stonks/core/service/memory/StonksMemoryService.java b/core/src/main/java/stonks/core/service/memory/StonksMemoryService.java index 3231bd3..82320fa 100644 --- a/core/src/main/java/stonks/core/service/memory/StonksMemoryService.java +++ b/core/src/main/java/stonks/core/service/memory/StonksMemoryService.java @@ -89,13 +89,13 @@ public OverviewOffersList collectOffers(OfferType type, int maxEntries, Iterator private List categories = new ArrayList<>(); private Map entries = new HashMap<>(); - private Map> offers = new HashMap<>(); + private Map> offersByUser = new HashMap<>(); private Emittable offerFilledEvents = new Emittable<>(); public List getModifiableCategories() { return categories; } public Iterator offersIterator() { - return offers.values().stream().flatMap(v -> v.stream()).iterator(); + return offersByUser.values().stream().flatMap(v -> v.stream()).iterator(); } @Override @@ -122,14 +122,26 @@ protected ProductEntry getProductEntry(Product product) { @Override public Task> getOffers(UUID offerer) { - var offers = this.offers.get(offerer); - if (offers == null) this.offers.put(offerer, offers = new ArrayList<>()); + var offers = this.offersByUser.get(offerer); + if (offers == null) this.offersByUser.put(offerer, offers = new ArrayList<>()); return Task.resolved(Collections.unmodifiableList(offers)); } + @Override + public Task getOfferById(UUID id) { + Iterator iter = offersIterator(); + + while (iter.hasNext()) { + Offer next = iter.next(); + if (next.getOfferId().equals(id)) return Task.resolved(next); + } + + return Task.resolved(null); + } + @Override public Task claimOffer(UUID offerer, UUID offerId) { - var playerOffers = offers.get(offerer); + var playerOffers = offersByUser.get(offerer); if (playerOffers == null) return Task.failed(new ServiceException("User " + offerer + " does not have offer with ID " + offerId)); @@ -147,7 +159,7 @@ public Task claimOffer(UUID offerer, UUID offerId) { @Override public Task cancelOffer(UUID offerer, UUID offerId) { - var playerOffers = offers.get(offerer); + var playerOffers = offersByUser.get(offerer); if (playerOffers == null) return Task.failed(new ServiceException("User " + offerer + " does not have offer with ID " + offerId)); @@ -180,8 +192,8 @@ protected void insertOffer(Offer offer) { throw new IllegalArgumentException("StonksMemoryService: Unknown product id: " + offer.getProduct().getProductId()); - var playerOffers = this.offers.get(offer.getOffererId()); - if (playerOffers == null) this.offers.put(offer.getOffererId(), playerOffers = new ArrayList<>()); + var playerOffers = this.offersByUser.get(offer.getOffererId()); + if (playerOffers == null) this.offersByUser.put(offer.getOffererId(), playerOffers = new ArrayList<>()); var list = offer.getType() == OfferType.BUY ? productEntry.buyOffers : productEntry.sellOffers; var searchResult = Collections.binarySearch(list, offer, @@ -229,7 +241,7 @@ public void saveServiceData() { @Override public void loadServiceData() { // Clear all - offers.clear(); + offersByUser.clear(); entries.values().forEach(v -> { v.buyOffers.clear(); v.sellOffers.clear(); diff --git a/core/src/main/java/stonks/core/service/testing/UnstableStonksService.java b/core/src/main/java/stonks/core/service/testing/UnstableStonksService.java index e449ae9..36c6c66 100644 --- a/core/src/main/java/stonks/core/service/testing/UnstableStonksService.java +++ b/core/src/main/java/stonks/core/service/testing/UnstableStonksService.java @@ -42,6 +42,7 @@ * unexpected results from service. *

*/ +@Deprecated public class UnstableStonksService implements StonksService { private StonksService underlying; private double failRate; @@ -86,6 +87,11 @@ public Task> getOffers(UUID offerer) { return wrapTask(underlying.getOffers(offerer)); } + @Override + public Task getOfferById(UUID id) { + return wrapTask(underlying.getOfferById(id)); + } + @Override public Task claimOffer(UUID offerer, UUID offerId) { return wrapTask(underlying.claimOffer(offerer, offerId)); diff --git a/core/src/test/java/io/github/nahkd123/stonks/market/impl/sql/SqlMarketServiceTest.java b/core/src/test/java/io/github/nahkd123/stonks/market/impl/sql/SqlMarketServiceTest.java new file mode 100644 index 0000000..264473c --- /dev/null +++ b/core/src/test/java/io/github/nahkd123/stonks/market/impl/sql/SqlMarketServiceTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.market.impl.sql; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import org.junit.jupiter.api.Test; + +import io.github.nahkd123.stonks.market.OrderInfo; +import io.github.nahkd123.stonks.market.helper.MutableOrder; +import io.github.nahkd123.stonks.market.result.InstantSellResult; +import io.github.nahkd123.stonks.market.summary.ProductSummary; +import io.github.nahkd123.stonks.test.SqlTestUtils; +import io.github.nahkd123.stonks.test.TestCatalogue; + +class SqlMarketServiceTest { + Connection newDatabase() throws SQLException { + Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:"); + conn.setAutoCommit(false); + return conn; + } + + @Test + void testInsert() throws SQLException { + Connection db = newDatabase(); + TestCatalogue catalogue = new TestCatalogue(); + SqlMarketService service = new SqlMarketService(catalogue, db); + + UUID user = UUID.randomUUID(); + service.updateOrder(new MutableOrder(id -> catalogue.getProductById(id).orElse(null), UUID + .randomUUID(), user, catalogue.apple.getId(), true, 100, 50, 0, 0)); + service.updateOrder(new MutableOrder(id -> catalogue.getProductById(id).orElse(null), UUID + .randomUUID(), user, catalogue.rice.getId(), true, 200, 50, 0, 0)); + + assertTrue(service.createIterator(catalogue.apple, true).hasNext()); + assertTrue(service.createIterator(catalogue.rice, true).hasNext()); + assertFalse(service.createIterator(catalogue.apple, false).hasNext()); + assertFalse(service.createIterator(catalogue.rice, false).hasNext()); + + Statement s = db.createStatement(); + SqlTestUtils.printResultSet(s.executeQuery("SELECT * FROM orders")); + } + + @Test + void testInstantSell() throws SQLException, InterruptedException, ExecutionException, SQLException { + Connection db = newDatabase(); + TestCatalogue catalogue = new TestCatalogue(); + SqlMarketService service = new SqlMarketService(catalogue, db); + + UUID userA = UUID.randomUUID(); + UUID userB = UUID.randomUUID(); + + // Place buy orders + OrderInfo buyOrder = service.makeBuyOrder(userA, catalogue.apple, 100, 20).get().data(); + assertEquals(100, buyOrder.getTotalUnits()); + assertEquals(20, buyOrder.getPricePerUnit()); + + buyOrder = service.makeBuyOrder(userB, catalogue.apple, 150, 10).get().data(); + assertEquals(150, buyOrder.getTotalUnits()); + assertEquals(10, buyOrder.getPricePerUnit()); + + buyOrder = service.makeBuyOrder(userB, catalogue.apple, 250, 20).get().data(); + assertEquals(250, buyOrder.getTotalUnits()); + assertEquals(20, buyOrder.getPricePerUnit()); + + // Get summary + ProductSummary summary = service.querySummary(catalogue.apple).get().data(); + assertEquals(2, summary.getBuySummary().size()); + assertEquals(0, summary.getSellSummary().size()); + assertEquals(350, summary.getBuySummary().get(0).totalUnits()); + assertEquals(20, summary.getBuySummary().get(0).pricePerUnit()); + assertEquals(150, summary.getBuySummary().get(1).totalUnits()); + assertEquals(10, summary.getBuySummary().get(1).pricePerUnit()); + + // Perform instant sell + InstantSellResult sellResult = service.instantSell(userB, catalogue.rice, 100).get().data(); + assertEquals(100, sellResult.getLeftoverAmount()); + assertEquals(0, sellResult.getCollectedBalance()); + + sellResult = service.instantSell(userB, catalogue.apple, 150).get().data(); + assertEquals(0, sellResult.getLeftoverAmount()); + assertEquals(150 * 20, sellResult.getCollectedBalance()); + + sellResult = service.instantSell(userB, catalogue.apple, 500).get().data(); + assertEquals(500 - 350, sellResult.getLeftoverAmount()); + assertEquals(200 * 20 + 150 * 10, sellResult.getCollectedBalance()); + + Statement s = db.createStatement(); + SqlTestUtils.printResultSet(s.executeQuery("SELECT * FROM orders")); + } +} diff --git a/core/src/test/java/io/github/nahkd123/stonks/test/SqlTestUtils.java b/core/src/test/java/io/github/nahkd123/stonks/test/SqlTestUtils.java new file mode 100644 index 0000000..4c8e01b --- /dev/null +++ b/core/src/test/java/io/github/nahkd123/stonks/test/SqlTestUtils.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.test; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SqlTestUtils { + public static final Logger LOGGER = LoggerFactory.getLogger("SqlTestUtils"); + + public static void printResultSet(ResultSet set) throws SQLException { + List rows = new ArrayList<>(); + int cols = set.getMetaData().getColumnCount(); + + String[] names = new String[cols]; + int[] width = new int[cols]; + for (int i = 0; i < cols; i++) { + names[i] = set.getMetaData().getColumnName(i + 1); + if (names[i].length() > width[i]) width[i] = names[i].length(); + } + + while (set.next()) { + String[] rec = new String[cols]; + + for (int i = 0; i < cols; i++) { + rec[i] = set.getString(i + 1); + if (rec[i].length() > width[i]) width[i] = rec[i].length(); + } + + rows.add(rec); + } + + printSeparator(width); + printRow(names, width); + printSeparator(width); + for (String[] row : rows) printRow(row, width); + printSeparator(width); + } + + public static void printSeparator(int[] width) { + String s = String.join("-+-", IntStream.of(width).mapToObj(v -> { + String w = ""; + while (w.length() < v) w += "-"; + return w; + }).toArray(String[]::new)); + LOGGER.info("+-" + s + "-+"); + } + + public static void printRow(String[] row, int[] width) { + String placeholders = String.join(" | ", IntStream.of(width) + .mapToObj(v -> "%-" + v + "s") + .toArray(String[]::new)); + LOGGER.info("| " + String.format(placeholders, (Object[]) row) + " |"); + } +} diff --git a/core/src/test/java/io/github/nahkd123/stonks/test/TestCatalogue.java b/core/src/test/java/io/github/nahkd123/stonks/test/TestCatalogue.java new file mode 100644 index 0000000..2c99a9f --- /dev/null +++ b/core/src/test/java/io/github/nahkd123/stonks/test/TestCatalogue.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.test; + +import io.github.nahkd123.stonks.market.catalogue.Category; +import io.github.nahkd123.stonks.market.catalogue.MutableCategory; +import io.github.nahkd123.stonks.market.catalogue.MutableProduct; +import io.github.nahkd123.stonks.market.catalogue.MutableProductsCatalogue; +import io.github.nahkd123.stonks.market.catalogue.Product; + +public class TestCatalogue extends MutableProductsCatalogue { + public final Product apple = new MutableProduct("apple", "Apple", "item apple"); + public final Product rice = new MutableProduct("rice", "Rice", "item bag_of_rice"); + + public final Category foods = new MutableCategory("foods", "Foods", false); + { + foods.getProducts().add(apple); + foods.getProducts().add(rice); + } + + { + // Add products here + getCategories().add(foods); + getProducts().add(apple); + getProducts().add(rice); + } +} diff --git a/fabric/build.gradle b/fabric/build.gradle index 0f4d39f..6f7bcee 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -33,6 +33,7 @@ dependencies { modImplementation include("eu.pb4:sgui:$rootProject.sgui_version") modImplementation include("eu.pb4:common-economy-api:$rootProject.common_economy_version") api include(project(':core')) + api include(project(':minecraft')) api include(naharaToolkit('nahara-common-tasks')) api include(naharaToolkit('nahara-common-configurations')) } diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/FabricPlatform.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/FabricPlatform.java new file mode 100644 index 0000000..1bb673d --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/FabricPlatform.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric; + +import java.nio.file.Path; + +import io.github.nahkd123.stonks.minecraft.MinecraftPlatform; +import io.github.nahkd123.stonks.minecraft.fabric.text.MinecraftTextFactory; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; +import net.minecraft.server.MinecraftServer; +import stonks.fabric.StonksFabric; +import stonks.fabric.StonksFabricPlatform; + +public class FabricPlatform implements MinecraftPlatform { + private TextFactory textFactory = new MinecraftTextFactory(); + + @Override + public Path getDataDir() { return StonksFabric.getConfigDir(); } + + @Override + public TextFactory getTextFactory() { return textFactory; } + + public FabricServer wrap(MinecraftServer server) { + return ((StonksFabricPlatform) server).getModernWrapper(); + } +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/FabricServer.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/FabricServer.java new file mode 100644 index 0000000..83c397f --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/FabricServer.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Executors; + +import io.github.nahkd123.stonks.market.MarketCache; +import io.github.nahkd123.stonks.market.MarketService; +import io.github.nahkd123.stonks.market.impl.legacy.LegacyMarketService; +import io.github.nahkd123.stonks.market.impl.queue.QueuedMarketService; +import io.github.nahkd123.stonks.minecraft.MinecraftPlatform; +import io.github.nahkd123.stonks.minecraft.MinecraftServer; +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.commodity.CommoditiesService; +import io.github.nahkd123.stonks.minecraft.economy.CachedEconomyService; +import io.github.nahkd123.stonks.minecraft.fabric.commodity.LegacyCommoditiesService; +import io.github.nahkd123.stonks.minecraft.fabric.economy.LegacyEconomyService; +import io.github.nahkd123.stonks.minecraft.fabric.gui.template.GuiTemplate; +import io.github.nahkd123.stonks.minecraft.fabric.utils.ItemStacksDeriver; +import io.github.nahkd123.stonks.minecraft.gui.ContainerGui; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; +import net.minecraft.server.command.CommandManager; +import net.minecraft.util.WorldSavePath; +import stonks.fabric.StonksFabricPlatform; +import stonks.fabric.adapter.StonksFabricAdapter; + +@SuppressWarnings("deprecation") +public class FabricServer implements MinecraftServer { + private FabricPlatform platform; + private net.minecraft.server.MinecraftServer underlying; + private MarketService market; + private MarketCache marketCache; + private CachedEconomyService economy; + private List commodities = new ArrayList<>(); + + private Map guiTemplates = new HashMap<>(); + private ItemStacksDeriver stacksDeriver; + + public FabricServer(FabricPlatform platform, net.minecraft.server.MinecraftServer underlying) { + StonksFabricPlatform legacy = (StonksFabricPlatform) underlying; + TextFactory textFactory = platform.getTextFactory(); + StonksFabricAdapter adapter = legacy.getStonksAdapter(); + + this.platform = platform; + this.underlying = underlying; + this.market = new QueuedMarketService(new LegacyMarketService(legacy + .getStonksService(), () -> legacy.getPlatformConfig().decimals), Executors.newCachedThreadPool()); + this.marketCache = new MarketCache(market); + this.economy = new CachedEconomyService(new LegacyEconomyService(textFactory, legacy + .getPlatformConfig().decimals, adapter)); + this.commodities.add(new LegacyCommoditiesService(textFactory, adapter)); + + this.stacksDeriver = new ItemStacksDeriver(CommandManager + .createRegistryAccess(underlying.getRegistryManager())); + } + + public static FabricServer of(net.minecraft.server.MinecraftServer server) { + return ((StonksFabricPlatform) server).getModernWrapper(); + } + + @Override + public MarketService getMarketService() { return market; } + + @Override + public MarketCache getMarketCache() { return marketCache; } + + @Override + public MinecraftPlatform getPlatform() { return platform; } + + @Override + public Path getServerDir() { return underlying.getSavePath(WorldSavePath.ROOT); } + + @Override + public Player getPlayerByUUID(UUID uuid) { + // TODO cache player object to somewhere? + return new PlayerWrapper(underlying, uuid); + } + + @Override + public CachedEconomyService getEconomyService() { return economy; } + + @Override + public List getCommoditiesServices() { return commodities; } + + public Map getGuiTemplates() { return guiTemplates; } + + public GuiTemplate getGuiTemplate(ContainerGui backend) { + return getGuiTemplates().get(backend.getId()); + } + + public ItemStacksDeriver getStacksDeriver() { return stacksDeriver; } +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/ModernStonksFabric.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/ModernStonksFabric.java new file mode 100644 index 0000000..d6cfdc0 --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/ModernStonksFabric.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric; + +import io.github.nahkd123.stonks.minecraft.fabric.command.DevelopmentCommand; +import io.github.nahkd123.stonks.minecraft.fabric.gui.template.ButtonTemplateFactory; +import io.github.nahkd123.stonks.minecraft.fabric.gui.template.DynamicButtonTemplate; +import io.github.nahkd123.stonks.minecraft.fabric.gui.template.EmptyButtonTemplate; +import io.github.nahkd123.stonks.minecraft.fabric.gui.template.GuiTemplate; +import io.github.nahkd123.stonks.minecraft.fabric.gui.template.LazyButtonTemplate; +import io.github.nahkd123.stonks.minecraft.fabric.gui.template.StaticButtonTemplate; +import nahara.common.configurations.Config; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.util.Identifier; +import stonks.fabric.StonksFabric; + +public class ModernStonksFabric { + private static FabricPlatform platform; + + public static void initialize() { + platform = new FabricPlatform(); + + StonksFabric.LOGGER.info("Initializing modern Stonks API..."); + StonksFabric.LOGGER.info("The modern Stonks API is experimental!"); + initializeGuiSystem(); + initializeDevelopmentEnvironment(); + } + + public static FabricPlatform getPlatform() { return platform; } + + private static void initializeGuiSystem() { + // Register button template factories here! + ButtonTemplateFactory.register(new Identifier(StonksFabric.MODID, "empty"), EmptyButtonTemplate.FACTORY); + ButtonTemplateFactory.register(new Identifier(StonksFabric.MODID, "static"), StaticButtonTemplate.FACTORY); + ButtonTemplateFactory.register(new Identifier(StonksFabric.MODID, "dynamic"), DynamicButtonTemplate.FACTORY); + ButtonTemplateFactory.register(new Identifier(StonksFabric.MODID, "lazy_loaded"), LazyButtonTemplate.FACTORY); + + ServerLifecycleEvents.SERVER_STARTING.register(server -> { + FabricServer wrapper = platform.wrap(server); + + for (Config guiConfig : StonksFabric.getOrCreateConfig("gui.txt").getChildren()) { + StonksFabric.LOGGER.info("Loading configured GUI: {}...", guiConfig.getKey()); + wrapper.getGuiTemplates().put(guiConfig.getKey(), new GuiTemplate().configure(guiConfig)); + } + }); + + StonksFabric.LOGGER.info("Initialized GUI system!"); + } + + private static void initializeDevelopmentEnvironment() { + if (FabricLoader.getInstance().isDevelopmentEnvironment()) { + StonksFabric.LOGGER.info("Development environment detected! Loading some funny debugging stuffs..."); + + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { + dispatcher.register(DevelopmentCommand.ROOT); + }); + } + } +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/PlayerWrapper.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/PlayerWrapper.java new file mode 100644 index 0000000..6d4075a --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/PlayerWrapper.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric; + +import java.util.UUID; + +import io.github.nahkd123.stonks.minecraft.MinecraftServer; +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.fabric.gui.ContainerGuiFrontend; +import io.github.nahkd123.stonks.minecraft.fabric.gui.template.GuiTemplate; +import io.github.nahkd123.stonks.minecraft.fabric.text.TextProxy; +import io.github.nahkd123.stonks.minecraft.gui.ContainerGui; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import stonks.fabric.StonksFabricPlatform; + +public class PlayerWrapper implements Player { + private net.minecraft.server.MinecraftServer server; + private UUID uuid; + + public PlayerWrapper(net.minecraft.server.MinecraftServer server, UUID uuid) { + this.server = server; + this.uuid = uuid; + } + + @Override + public MinecraftServer getServer() { return ((StonksFabricPlatform) server).getModernWrapper(); } + + @Override + public UUID getUuid() { return uuid; } + + public ServerPlayerEntity getEntity() { return server.getPlayerManager().getPlayer(uuid); } + + @Override + public String getUsername() { return getEntity().getName().getString(); } + + @Override + public TextComponent getDisplayName() { + ServerPlayerEntity player = getEntity(); + return new TextProxy(player.getDisplayName() instanceof MutableText m ? m : Text.literal(getUsername())); + } + + @Override + public void sendSystemMessage(TextComponent message) { + if (message instanceof TextProxy proxy) getEntity().sendMessage(proxy.getUnderlying(), false); + } + + @Override + public void sendActionBarMessage(TextComponent message) { + if (message instanceof TextProxy proxy) getEntity().sendMessage(proxy.getUnderlying(), true); + } + + @Override + public void openGui(ContainerGui gui) { + if (gui == null) { + getEntity().closeHandledScreen(); + return; + } + + GuiTemplate template = FabricServer.of(server).getGuiTemplate(gui); + ContainerGuiFrontend frontend = new ContainerGuiFrontend(getEntity(), false, gui, template); + frontend.open(); + } +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/command/DevelopmentCommand.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/command/DevelopmentCommand.java new file mode 100644 index 0000000..8220ec0 --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/command/DevelopmentCommand.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.command; + +import static net.minecraft.server.command.CommandManager.literal; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; + +import io.github.nahkd123.stonks.minecraft.MinecraftPlatform; +import io.github.nahkd123.stonks.minecraft.fabric.FabricServer; +import io.github.nahkd123.stonks.minecraft.fabric.text.TextProxy; +import io.github.nahkd123.stonks.minecraft.gui.provided.gui.MainContainerGui; +import io.github.nahkd123.stonks.minecraft.text.FriendlyParser; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; +import net.minecraft.server.command.CommandManager; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; + +public class DevelopmentCommand { + public static final LiteralArgumentBuilder ROOT = literal("stonksdev") + .requires(s -> s.hasPermissionLevel(CommandManager.field_31841)) + .then(literal("test") + .then(literal("friendlyparser").executes(context -> { + FabricServer server = FabricServer.of(context.getSource().getServer()); + MinecraftPlatform platform = server.getPlatform(); + TextFactory text = platform.getTextFactory(); + + for (String line : new String[] { + "Plain text no styles", + "[b]Plain text bold[/]", + "[color:red]Mixed[/] styles[b]![/]", + "Mixed [i]styles[/]!" + }) { + context.getSource() + .sendMessage(((TextProxy) FriendlyParser.parse(text, line, null)).getUnderlying()); + } + return 1; + }))) + .then(literal("openmoderngui").executes(context -> { + ServerPlayerEntity player = context.getSource().getPlayerOrThrow(); + FabricServer server = FabricServer.of(context.getSource().getServer()); + server.getPlayerByUUID(player.getUuid()).openGui(new MainContainerGui(server, 0)); + return 1; + })); +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/commodity/LegacyCommoditiesService.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/commodity/LegacyCommoditiesService.java new file mode 100644 index 0000000..85403a6 --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/commodity/LegacyCommoditiesService.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.commodity; + +import java.util.HashMap; +import java.util.Map; + +import io.github.nahkd123.stonks.minecraft.commodity.CommoditiesService; +import io.github.nahkd123.stonks.minecraft.commodity.Commodity; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; +import stonks.core.product.Product; +import stonks.fabric.adapter.StonksFabricAdapter; + +public class LegacyCommoditiesService implements CommoditiesService { + private StonksFabricAdapter adapter; + private TextFactory textFactory; + private Map wrappers = new HashMap<>(); + + public LegacyCommoditiesService(TextFactory textFactory, StonksFabricAdapter adapter) { + this.textFactory = textFactory; + this.adapter = adapter; + } + + public TextFactory getTextFactory() { return textFactory; } + + public StonksFabricAdapter getLegacyAdapter() { return adapter; } + + @Override + public Commodity typeFromString(String str) { + return wrappers.get(str); + } + + public Commodity typeFromLegacy(Product product) { + LegacyCommodityWrapper wrapper = wrappers.get(product.getProductConstructionData()); + if (wrapper == null) + wrappers.put(product.getProductConstructionData(), wrapper = new LegacyCommodityWrapper(this, product)); + return wrapper; + } +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/commodity/LegacyCommodityWrapper.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/commodity/LegacyCommodityWrapper.java new file mode 100644 index 0000000..509ec4f --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/commodity/LegacyCommodityWrapper.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.commodity; + +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.commodity.Commodity; +import io.github.nahkd123.stonks.minecraft.fabric.PlayerWrapper; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import stonks.core.product.Product; + +public class LegacyCommodityWrapper implements Commodity { + private LegacyCommoditiesService service; + private Product legacy; + + public LegacyCommodityWrapper(LegacyCommoditiesService service, Product legacy) { + this.service = service; + this.legacy = legacy; + } + + public Product getLegacy() { return legacy; } + + @Override + public TextComponent getDisplayName() { return service.getTextFactory().literal(legacy.getProductName()); } + + @Override + public String getDisplayItemString() { return "minecraft:barrier"; } + + @Override + public long getAmountAvailable(Player player) { + return service.getLegacyAdapter().getUnits(((PlayerWrapper) player).getEntity(), legacy).or(0); + } + + @Override + public long takeAmount(Player player, long amount) { + service.getLegacyAdapter().removeUnitsFrom(((PlayerWrapper) player).getEntity(), legacy, (int) amount); + return 0; + } + + @Override + public long giveAmount(Player player, long amount) { + service.getLegacyAdapter().addUnitsTo(((PlayerWrapper) player).getEntity(), legacy, (int) amount); + return 0; + } +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/economy/AdapterResponseToTransaction.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/economy/AdapterResponseToTransaction.java new file mode 100644 index 0000000..a6562d8 --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/economy/AdapterResponseToTransaction.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.economy; + +import java.util.UUID; + +import io.github.nahkd123.stonks.minecraft.economy.Transaction; +import io.github.nahkd123.stonks.minecraft.economy.TransactionType; +import stonks.fabric.adapter.AdapterResponse; + +public class AdapterResponseToTransaction implements Transaction { + private UUID uuid; + private AdapterResponse response; + private long amount; + private long newBalance; + private TransactionType type; + + public AdapterResponseToTransaction(UUID uuid, AdapterResponse response, TransactionType type, long amount, long newBalance) { + this.uuid = uuid; + this.response = response; + this.type = type; + this.amount = amount; + this.newBalance = newBalance; + } + + @Override + public UUID getPlayerUuid() { return uuid; } + + @Override + public long getAmount() { return amount; } + + @Override + public long getNewBalance() { return newBalance; } + + @Override + public TransactionType getType() { return type; } + + @Override + public boolean isSuccess() { return response.isSuccess(); } + + @Override + public int getCode() { return response.isFailed() ? CODE_FAIL_UNKNOWN : CODE_SUCCESS; } + + @Override + public String getErrorMessage() { return response.message().getString(); } +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/economy/LegacyEconomyService.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/economy/LegacyEconomyService.java new file mode 100644 index 0000000..7d93484 --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/economy/LegacyEconomyService.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.economy; + +import java.util.concurrent.CompletableFuture; + +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.economy.Currency; +import io.github.nahkd123.stonks.minecraft.economy.DoubleBackedCurrency; +import io.github.nahkd123.stonks.minecraft.economy.EconomyService; +import io.github.nahkd123.stonks.minecraft.economy.Transaction; +import io.github.nahkd123.stonks.minecraft.economy.TransactionType; +import io.github.nahkd123.stonks.minecraft.fabric.PlayerWrapper; +import io.github.nahkd123.stonks.minecraft.fabric.text.TextProxy; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; +import net.minecraft.text.Text; +import stonks.fabric.adapter.AdapterResponse; +import stonks.fabric.adapter.StonksFabricAdapter; + +public class LegacyEconomyService implements EconomyService { + private DoubleBackedCurrency currency; + private StonksFabricAdapter adapter; + + public LegacyEconomyService(TextFactory textFactory, int decimals, StonksFabricAdapter adapter) { + this.adapter = adapter; + this.currency = new DoubleBackedCurrency(textFactory, decimals); + } + + @Override + public TextComponent getDisplayName() { return new TextProxy(Text.literal("Legacy Economy Service")); } + + @Override + public Currency getCurrency() { return currency; } + + public long getBalanceNow(Player player) { + if (!(player instanceof PlayerWrapper wrapper)) throw new IllegalArgumentException("Not a PlayerWrapper"); + AdapterResponse res = adapter.accountBalance(wrapper.getEntity()); + return currency.fromDouble(res.or(0d)); + } + + @Override + public CompletableFuture getBalance(Player player) { + return CompletableFuture.completedFuture(getBalanceNow(player)); + } + + @Override + public CompletableFuture depositTo(Player player, long amount) { + if (!(player instanceof PlayerWrapper wrapper)) throw new IllegalArgumentException("Not a PlayerWrapper"); + AdapterResponse res = adapter.accountDeposit( + wrapper.getEntity(), + currency.toDouble(amount)); + return CompletableFuture.completedFuture(new AdapterResponseToTransaction<>(player + .getUuid(), res, TransactionType.DEPOSIT, amount, getBalanceNow(player))); + } + + @Override + public CompletableFuture withdrawFrom(Player player, long amount) { + if (!(player instanceof PlayerWrapper wrapper)) throw new IllegalArgumentException("Not a PlayerWrapper"); + AdapterResponse res = adapter.accountWithdraw( + wrapper.getEntity(), + currency.toDouble(amount)); + return CompletableFuture.completedFuture(new AdapterResponseToTransaction<>(player + .getUuid(), res, TransactionType.WITHDRAW, amount, getBalanceNow(player))); + } +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/ContainerGuiFrontend.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/ContainerGuiFrontend.java new file mode 100644 index 0000000..cff67be --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/ContainerGuiFrontend.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.gui; + +import eu.pb4.sgui.api.elements.GuiElementInterface; +import eu.pb4.sgui.api.gui.SimpleGui; +import io.github.nahkd123.stonks.minecraft.fabric.gui.template.ButtonTemplate; +import io.github.nahkd123.stonks.minecraft.fabric.gui.template.GuiTemplate; +import io.github.nahkd123.stonks.minecraft.fabric.text.TextProxy; +import io.github.nahkd123.stonks.minecraft.gui.ClickType; +import io.github.nahkd123.stonks.minecraft.gui.ContainerGui; +import net.minecraft.server.network.ServerPlayerEntity; + +public class ContainerGuiFrontend extends SimpleGui { + private ContainerGui backend; + private GuiTemplate template; + + public ContainerGuiFrontend(ServerPlayerEntity player, boolean manipulatePlayerSlots, ContainerGui backend, GuiTemplate template) { + super(template.type, player, manipulatePlayerSlots); + this.backend = backend; + this.template = template; + } + + public ContainerGui getBackend() { return backend; } + + @Override + public void beforeOpen() { + refresh(); + } + + public void refresh() { + setTitle(template.title.apply(this) instanceof TextProxy p ? p.getUnderlying() : null); + + for (ButtonTemplate buttonTemplate : template.buttons) { + buttonTemplate.forEachInFillRects((x, y, ordinal) -> { + int index = (y - 1) * getWidth() + (x - 1); + GuiElementInterface guiElement = buttonTemplate.createGuiElement(this, ordinal); + setSlot(index, guiElement); + }); + } + } + + public static ClickType toCommonClickType(eu.pb4.sgui.api.ClickType sgui) { + return switch (sgui) { + case MOUSE_LEFT -> ClickType.LEFT; + case MOUSE_RIGHT -> ClickType.RIGHT; + case MOUSE_LEFT_SHIFT -> ClickType.LEFT; + case MOUSE_RIGHT_SHIFT -> ClickType.SHIFT_RIGHT; + default -> ClickType.UNKNOWN; + }; + } +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/Fill.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/Fill.java new file mode 100644 index 0000000..53bd6f6 --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/Fill.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.gui; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public record Fill(int xMin, int yMin, int xMax, int yMax, boolean reversedX, boolean reversedY) { + + public static final Pattern PATTERN_FULL = Pattern.compile( + "\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)\\s*(to|--?>|==?>)\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)(\\s*reversed-x)?(\\s*reversed-y)?"); + public static final Pattern PATTERN_MINIMAL = Pattern.compile("\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)"); + + public Fill { + if (xMin > xMax) throw new IllegalArgumentException("Min x is larger than max x (" + xMin + " > " + xMax + ")"); + if (yMin > yMax) throw new IllegalArgumentException("Min y is larger than max y (" + yMin + " > " + yMax + ")"); + } + + public Fill(int x, int y) { + this(x, y, x, y, false, false); + } + + public static Fill fromString(String s) { + s = s.trim(); + Matcher matcher; + + if ((matcher = PATTERN_FULL.matcher(s)).matches()) { + int x1 = Integer.parseInt(matcher.group(1)); + int y1 = Integer.parseInt(matcher.group(2)); + int x2 = Integer.parseInt(matcher.group(4)); + int y2 = Integer.parseInt(matcher.group(5)); + int xMin = Math.min(x1, x2); + int yMin = Math.min(y1, y2); + int xMax = Math.max(x1, x2); + int yMax = Math.max(y1, y2); + boolean reversedX = matcher.group(6) != null; + boolean reversedY = matcher.group(7) != null; + return new Fill(xMin, yMin, xMax, yMax, reversedX, reversedY); + } + + if ((matcher = PATTERN_MINIMAL.matcher(s)).matches()) { + int x = Integer.parseInt(matcher.group(1)); + int y = Integer.parseInt(matcher.group(2)); + return new Fill(x, y); + } + + return null; + } + + public int width() { + return xMax - xMin + 1; + } + + public int height() { + return yMax - yMin + 1; + } + + public int area() { + return width() * height(); + } + + public void forEach(FillConsumer callback) { + int xStart = reversedX ? xMax : xMin, + xEnd = reversedX ? xMin : xMax, + xStep = reversedX ? -1 : 1; + int yStart = reversedY ? yMax : yMin, + yEnd = reversedY ? yMin : yMax, + yStep = reversedY ? -1 : 1; + int ordinal = 0; + + for (int y = yStart; reversedY ? y >= yEnd : y <= yEnd; y += yStep) { + for (int x = xStart; reversedX ? x >= xEnd : x <= xEnd; x += xStep) { + callback.accept(x, y, ordinal); + ordinal++; + } + } + } + + @Override + public String toString() { + if (xMin == xMax && yMin == yMax) return "(" + xMin + ", " + yMin + ")"; + return "(" + xMin + ", " + yMin + ") to (" + xMax + ", " + yMax + ")" + + (reversedX ? " reversed-x" : "") + + (reversedY ? " reversed-y" : ""); + } +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/FillConsumer.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/FillConsumer.java new file mode 100644 index 0000000..4ce46e7 --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/FillConsumer.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.gui; + +@FunctionalInterface +public interface FillConsumer { + public void accept(int x, int y, int ordinal); +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/TestBook.jpage b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/TestBook.jpage new file mode 100644 index 0000000..e69de29 diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/ButtonTemplate.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/ButtonTemplate.java new file mode 100644 index 0000000..df45b0f --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/ButtonTemplate.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.gui.template; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import eu.pb4.sgui.api.elements.GuiElementInterface; +import io.github.nahkd123.stonks.minecraft.fabric.gui.ContainerGuiFrontend; +import io.github.nahkd123.stonks.minecraft.fabric.gui.Fill; +import io.github.nahkd123.stonks.minecraft.fabric.gui.FillConsumer; +import io.github.nahkd123.stonks.minecraft.gui.ContainerGui; +import nahara.common.configurations.Config; +import net.minecraft.util.Identifier; + +/** + *

+ * The button template, usually constructed from configurations. + *

+ */ +public interface ButtonTemplate { + /** + *

+ * Get a list of fill rectangles. + *

+ * + * @return A list of fill rectangles. + */ + public List getFillRects(); + + default void forEachInFillRects(FillConsumer callback) { + AtomicInteger last = new AtomicInteger(0); + + for (Fill rect : getFillRects()) { + rect.forEach((x, y, ordinal) -> callback.accept(x, y, last.get() + ordinal)); + last.addAndGet(rect.area()); + } + } + + /** + *

+ * Get the ID for the native button. The native button can be obtained by using + * {@link ContainerGuiFrontend#getBackend()} and + * {@link ContainerGui#getNative(String, int)}. The native button ordinal/index + * can be obtained from {@link #forEachInFillRects(FillConsumer)}. + *

+ * + * @return The native button ID. + */ + public String getNativeButtonId(); + + public GuiElementInterface createGuiElement(ContainerGuiFrontend gui, int ordinal); + + public static ButtonTemplate fromConfig(Identifier id, Config config, List fillRects, String nativeButtonId) { + ButtonTemplateFactory factory = ButtonTemplateFactory.FACTORIES.get(id); + if (factory != null) return factory.createFromConfig(config, fillRects, nativeButtonId); + return new FailbackButtonTemplate(id, fillRects, nativeButtonId); + } +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/ButtonTemplateFactory.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/ButtonTemplateFactory.java new file mode 100644 index 0000000..2cf2755 --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/ButtonTemplateFactory.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.gui.template; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.github.nahkd123.stonks.minecraft.fabric.gui.Fill; +import nahara.common.configurations.Config; +import net.minecraft.util.Identifier; + +public interface ButtonTemplateFactory { + public ButtonTemplate createFromConfig(Config config, List fillRects, String nativeButtonId); + + public static final Map FACTORIES = new HashMap<>(); + + public static void register(Identifier id, ButtonTemplateFactory factory) { + // TODO safe-guard + FACTORIES.put(id, factory); + } +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/DynamicButtonTemplate.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/DynamicButtonTemplate.java new file mode 100644 index 0000000..a6e13e8 --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/DynamicButtonTemplate.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.gui.template; + +import java.util.List; + +import eu.pb4.sgui.api.elements.GuiElementBuilder; +import eu.pb4.sgui.api.elements.GuiElementInterface; +import eu.pb4.sgui.api.elements.GuiElementInterface.ClickCallback; +import eu.pb4.sgui.api.gui.GuiInterface; +import io.github.nahkd123.stonks.market.catalogue.Product; +import io.github.nahkd123.stonks.market.impl.legacy.LegacyProductWrapper; +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.commodity.CommoditiesService; +import io.github.nahkd123.stonks.minecraft.commodity.Commodity; +import io.github.nahkd123.stonks.minecraft.fabric.FabricServer; +import io.github.nahkd123.stonks.minecraft.fabric.commodity.LegacyCommoditiesService; +import io.github.nahkd123.stonks.minecraft.fabric.gui.ContainerGuiFrontend; +import io.github.nahkd123.stonks.minecraft.fabric.gui.Fill; +import io.github.nahkd123.stonks.minecraft.fabric.text.TextProxy; +import io.github.nahkd123.stonks.minecraft.gui.ClickType; +import io.github.nahkd123.stonks.minecraft.gui.LazyLoadedNativeButton; +import io.github.nahkd123.stonks.minecraft.gui.NativeButton; +import io.github.nahkd123.stonks.minecraft.text.FriendlyParser; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; +import io.github.nahkd123.stonks.minecraft.text.placeholder.MergedPlaceholders; +import io.github.nahkd123.stonks.minecraft.text.placeholder.Placeholders; +import io.github.nahkd123.stonks.utils.lazy.LazyLoader; +import io.github.nahkd123.stonks.utils.lazy.LoadState; +import nahara.common.configurations.Config; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.registry.Registries; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import stonks.fabric.StonksFabricPlatform; + +public class DynamicButtonTemplate implements ButtonTemplate { + private boolean typeFromNative = false; + private Item type; + private List fillRects; + private String nativeButtonId; + + public int amount = 1; + public String name; + public List lore; + + public DynamicButtonTemplate(Item type, List fillRects, String nativeButtonId) { + this.type = type; + this.typeFromNative = type == null; + this.fillRects = fillRects; + this.nativeButtonId = nativeButtonId; + } + + @Override + public List getFillRects() { return fillRects; } + + @Override + public String getNativeButtonId() { return nativeButtonId; } + + @Override + public GuiElementInterface createGuiElement(ContainerGuiFrontend gui, int ordinal) { + NativeButton nativeButton = gui.getBackend().getNative(nativeButtonId, ordinal); + TextFactory textFactory = gui.getBackend().getServer().getPlatform().getTextFactory(); + Placeholders placeholders = new MergedPlaceholders(gui.getBackend(), nativeButton); + + ClickCallback callback = (index, type, action, $) -> { + if (nativeButton != null) { + Player player = ((StonksFabricPlatform) gui.getPlayer().getServer()) + .getModernWrapper() + .getPlayerByUUID(gui.getPlayer().getUuid()); + ClickType clickType = ContainerGuiFrontend.toCommonClickType(type); + nativeButton.onClick(gui.getBackend(), player, clickType); + } + }; + + return new GuiElementInterface() { + @Override + public ItemStack getItemStack() { + ItemStack base = typeFromNative + ? deriveStackFromObject(gui, nativeButton) + : new ItemStack(type, amount); + if (base == ItemStack.EMPTY) return base; + + GuiElementBuilder builder = GuiElementBuilder.from(base); + if (name != null) builder.setName(unwrap(FriendlyParser.parse(textFactory, name, placeholders))); + if (lore.size() > 0) builder.setLore(lore.stream() + .map(line -> unwrap(FriendlyParser.parse(textFactory, line, placeholders))) + .toList()); + + return builder.asStack(); + } + + @Override + public ItemStack getItemStackForDisplay(GuiInterface gui) { + return getItemStack(); + } + + @Override + public ClickCallback getGuiCallback() { return callback; } + }; + } + + private static Text unwrap(TextComponent wrapped) { + return ((TextProxy) wrapped).getUnderlying(); + } + + private static ItemStack deriveStackFromObject(ContainerGuiFrontend gui, Object obj) { + if (obj instanceof LazyLoadedNativeButton llnb) return deriveStackFromObject(gui, llnb.getLoader()); + + if (obj instanceof LazyLoader ll) { + if (ll.load() == LoadState.SUCCESS) return deriveStackFromObject(gui, ll.get()); + return ItemStack.EMPTY; + } + + if (obj instanceof Product product) { + String commodityString = product.getCommodityString(); + List commoditiesServices = gui.getBackend().getServer().getCommoditiesServices(); + + for (CommoditiesService s : commoditiesServices) { + // Dumb legacy hack please ignore + if (s instanceof LegacyCommoditiesService ls && product instanceof LegacyProductWrapper lp) + return ls.getLegacyAdapter().createDisplayStack(lp.getLegacy()); + + Commodity commodity = s.typeFromString(commodityString); + if (commodity != null) { + String itemString = commodity.getDisplayItemString(); + return ((FabricServer) gui.getBackend().getServer()).getStacksDeriver().fromString(itemString, 1); + } + } + + return ItemStack.EMPTY; + } + + return ItemStack.EMPTY; + } + + public static final ButtonTemplateFactory FACTORY = new ButtonTemplateFactory() { + @Override + public ButtonTemplate createFromConfig(Config config, List fillRects, String nativeButtonId) { + Item type = config.firstChild("type") + .flatMap(Config::getValue) + .map(v -> v.equals("native") ? null : Registries.ITEM.get(new Identifier(v))) + .orElse(null); + DynamicButtonTemplate template = new DynamicButtonTemplate(type, fillRects, nativeButtonId); + + config.firstChild("amount").flatMap(s -> s.getValue(Integer::parseInt)).ifPresent(v -> template.amount = v); + config.firstChild("name").flatMap(Config::getValue).ifPresent(v -> template.name = v); + template.lore = config.children("lore").flatMap(c -> c.getValue().stream()).toList(); + // TODO more fun stuffs here! + return template; + } + }; +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/EmptyButtonTemplate.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/EmptyButtonTemplate.java new file mode 100644 index 0000000..638fbbd --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/EmptyButtonTemplate.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.gui.template; + +import java.util.List; + +import eu.pb4.sgui.api.elements.GuiElementInterface; +import eu.pb4.sgui.api.elements.GuiElementInterface.ClickCallback; +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.fabric.gui.ContainerGuiFrontend; +import io.github.nahkd123.stonks.minecraft.fabric.gui.Fill; +import io.github.nahkd123.stonks.minecraft.gui.ClickType; +import io.github.nahkd123.stonks.minecraft.gui.NativeButton; +import nahara.common.configurations.Config; +import net.minecraft.item.ItemStack; +import stonks.fabric.StonksFabricPlatform; + +public class EmptyButtonTemplate implements ButtonTemplate { + private List fillRects; + private String nativeButtonId; + + public EmptyButtonTemplate(List fillRects, String nativeButtonId) { + this.fillRects = fillRects; + this.nativeButtonId = nativeButtonId; + } + + @Override + public List getFillRects() { return fillRects; } + + @Override + public String getNativeButtonId() { return nativeButtonId; } + + @Override + public GuiElementInterface createGuiElement(ContainerGuiFrontend gui, int ordinal) { + NativeButton nativeButton = gui.getBackend().getNative(nativeButtonId, ordinal); + ClickCallback callback = (index, type, action, $) -> { + if (nativeButton != null) { + Player player = ((StonksFabricPlatform) gui.getPlayer().getServer()) + .getModernWrapper() + .getPlayerByUUID(gui.getPlayer().getUuid()); + ClickType clickType = ContainerGuiFrontend.toCommonClickType(type); + nativeButton.onClick(gui.getBackend(), player, clickType); + } + }; + + return new GuiElementInterface() { + @Override + public ItemStack getItemStack() { return ItemStack.EMPTY; } + + @Override + public ClickCallback getGuiCallback() { return callback; } + }; + } + + public static final ButtonTemplateFactory FACTORY = new ButtonTemplateFactory() { + @Override + public ButtonTemplate createFromConfig(Config config, List fillRects, String nativeButtonId) { + return new EmptyButtonTemplate(fillRects, nativeButtonId); + } + }; +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/FailbackButtonTemplate.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/FailbackButtonTemplate.java new file mode 100644 index 0000000..6d62998 --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/FailbackButtonTemplate.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.gui.template; + +import java.util.List; + +import eu.pb4.sgui.api.elements.GuiElementBuilder; +import eu.pb4.sgui.api.elements.GuiElementInterface; +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.fabric.gui.ContainerGuiFrontend; +import io.github.nahkd123.stonks.minecraft.fabric.gui.Fill; +import io.github.nahkd123.stonks.minecraft.gui.ClickType; +import io.github.nahkd123.stonks.minecraft.gui.NativeButton; +import nahara.common.configurations.Config; +import net.minecraft.item.Items; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; +import stonks.fabric.StonksFabricPlatform; + +public class FailbackButtonTemplate implements ButtonTemplate { + private Identifier typeId; + private List fillRects; + private String nativeButtonId; + + public FailbackButtonTemplate(Identifier typeId, List fillRects, String nativeButtonId) { + this.typeId = typeId; + this.fillRects = fillRects; + this.nativeButtonId = nativeButtonId; + } + + @Override + public List getFillRects() { return fillRects; } + + @Override + public String getNativeButtonId() { return nativeButtonId; } + + @Override + public GuiElementInterface createGuiElement(ContainerGuiFrontend gui, int ordinal) { + return new GuiElementBuilder(Items.BARRIER) + .setName(Text.literal("Failback Button / Error").styled(s -> s.withColor(Formatting.RED))) + .addLoreLine(Text.literal("Unknown type: " + typeId + " / Ordinal " + ordinal) + .styled(s -> s.withColor(Formatting.GRAY))) + .setCallback((index, type, action, $) -> { + NativeButton nativeButton = gui.getBackend().getNative(nativeButtonId, ordinal); + + if (nativeButton != null) { + Player player = ((StonksFabricPlatform) gui.getPlayer().getServer()) + .getModernWrapper() + .getPlayerByUUID(gui.getPlayer().getUuid()); + ClickType clickType = ContainerGuiFrontend.toCommonClickType(type); + nativeButton.onClick(gui.getBackend(), player, clickType); + } + }) + .build(); + } + + public static final ButtonTemplateFactory FACTORY = new ButtonTemplateFactory() { + @Override + public ButtonTemplate createFromConfig(Config config, List fillRects, String nativeButtonId) { + return new FailbackButtonTemplate(new Identifier("minecraft", "missingno"), fillRects, nativeButtonId); + } + }; +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/GuiTemplate.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/GuiTemplate.java new file mode 100644 index 0000000..6273efe --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/GuiTemplate.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.gui.template; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import io.github.nahkd123.stonks.minecraft.fabric.gui.ContainerGuiFrontend; +import io.github.nahkd123.stonks.minecraft.fabric.gui.Fill; +import io.github.nahkd123.stonks.minecraft.text.FriendlyParser; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; +import nahara.common.configurations.Config; +import net.minecraft.registry.Registries; +import net.minecraft.screen.ScreenHandlerType; +import net.minecraft.util.Identifier; +import stonks.fabric.StonksFabric; + +public class GuiTemplate { + public Function title; + public ScreenHandlerType type; + public final List buttons = new ArrayList<>(); + + public GuiTemplate configure(Config config) { + config.firstChild("title").flatMap(Config::getValue).ifPresent(title -> { + this.title = frontend -> { + TextFactory textFactory = frontend.getBackend().getTextFactory(); + return FriendlyParser.parse(textFactory, title, null); // TODO placeholder here + }; + }); + + config.firstChild("type").flatMap(Config::getValue).ifPresent(type -> { + this.type = Registries.SCREEN_HANDLER.get(new Identifier(type)); + }); + + config.children("button").forEach(buttonConfig -> { + Optional idOpt = buttonConfig.getValue(Identifier::tryParse); + if (idOpt.isEmpty()) { + StonksFabric.LOGGER.warn("Invaild button type ID: {}", + buttonConfig.getValue().orElse("")); + return; + } + + Identifier id = idOpt.get(); + String nativeButtonId = buttonConfig.firstChild("nativeButton").flatMap(Config::getValue).orElse(null); + List fillRects = buttonConfig.children("fill") + .map(Config::getValue) + .flatMap(Optional::stream) + .map(Fill::fromString) + .filter(v -> v != null) + .toList(); + + buttons.add(ButtonTemplate.fromConfig(id, buttonConfig, fillRects, nativeButtonId)); + }); + + return this; + } +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/LazyButtonTemplate.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/LazyButtonTemplate.java new file mode 100644 index 0000000..357b7b4 --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/LazyButtonTemplate.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.gui.template; + +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import eu.pb4.sgui.api.elements.GuiElementBuilder; +import eu.pb4.sgui.api.elements.GuiElementInterface; +import eu.pb4.sgui.api.elements.GuiElementInterface.ClickCallback; +import eu.pb4.sgui.api.gui.GuiInterface; +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.fabric.gui.ContainerGuiFrontend; +import io.github.nahkd123.stonks.minecraft.fabric.gui.Fill; +import io.github.nahkd123.stonks.minecraft.gui.ClickType; +import io.github.nahkd123.stonks.minecraft.gui.LazyLoadedNativeButton; +import io.github.nahkd123.stonks.minecraft.gui.NativeButton; +import io.github.nahkd123.stonks.utils.lazy.LoadState; +import nahara.common.configurations.Config; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; +import stonks.fabric.StonksFabricPlatform; + +public class LazyButtonTemplate implements ButtonTemplate { + private List fillRects; + private String nativeButtonId; + public ButtonTemplate onLoading, onLoaded, onPresent, onAbsent, onError; + + public LazyButtonTemplate(List fillRects, String nativeButtonId) { + this.fillRects = fillRects; + this.nativeButtonId = nativeButtonId; + } + + @Override + public List getFillRects() { return fillRects; } + + @Override + public String getNativeButtonId() { return nativeButtonId; } + + @Override + public GuiElementInterface createGuiElement(ContainerGuiFrontend gui, int ordinal) { + NativeButton nativeButton = gui.getBackend().getNative(nativeButtonId, ordinal); + ClickCallback callback = (index, type, action, $) -> { + if (nativeButton != null) { + Player player = ((StonksFabricPlatform) gui.getPlayer().getServer()) + .getModernWrapper() + .getPlayerByUUID(gui.getPlayer().getUuid()); + ClickType clickType = ContainerGuiFrontend.toCommonClickType(type); + nativeButton.onClick(gui.getBackend(), player, clickType); + } + }; + + if (nativeButton == null || !(nativeButton instanceof LazyLoadedNativeButton llnb)) { + return new GuiElementBuilder(Items.BARRIER) + .setName(Text.literal("Failback Button / Error").styled(s -> s.withColor(Formatting.RED))) + .addLoreLine(Text.literal("The native button can't be lazy loaded!") + .styled(s -> s.withColor(Formatting.GRAY))) + .setCallback(callback) + .build(); + } + + GuiElementInterface onLoading = this.onLoading != null ? this.onLoading.createGuiElement(gui, ordinal) : null; + GuiElementInterface onLoaded = this.onLoaded != null ? this.onLoaded.createGuiElement(gui, ordinal) : null; + GuiElementInterface onPresent = this.onPresent != null ? this.onPresent.createGuiElement(gui, ordinal) : null; + GuiElementInterface onAbsent = this.onAbsent != null ? this.onAbsent.createGuiElement(gui, ordinal) : null; + GuiElementInterface onError = this.onError != null ? this.onError.createGuiElement(gui, ordinal) : null; + + return new GuiElementInterface() { + private GuiElementInterface pick() { + LoadState state = llnb.getLoader().load(); + if (state != LoadState.SUCCESS) return switch (state) { + case LOADING -> onLoading; + case FAILED -> onError; + default -> onError; + }; + + if (onLoaded != null) return onLoaded; + if (llnb.getLoader().get() == null) return onAbsent; + return onPresent; + } + + @Override + public ItemStack getItemStack() { + GuiElementInterface picked = pick(); + return picked != null ? picked.getItemStack() : ItemStack.EMPTY; + } + + @Override + public ItemStack getItemStackForDisplay(GuiInterface gui) { + GuiElementInterface picked = pick(); + return picked != null ? picked.getItemStackForDisplay(gui) : ItemStack.EMPTY; + } + + @Override + public ClickCallback getGuiCallback() { return callback; } + }; + } + + private void applyConfig(Optional config, Consumer setter) { + if (config.isEmpty()) return; + Config cfg = config.get(); + Identifier templateType = cfg.getValue().map(Identifier::new).orElseGet(() -> new Identifier("missingno")); + ButtonTemplate childTemplate = ButtonTemplate.fromConfig(templateType, cfg, fillRects, nativeButtonId); + setter.accept(childTemplate); + } + + public static final ButtonTemplateFactory FACTORY = new ButtonTemplateFactory() { + @Override + public ButtonTemplate createFromConfig(Config config, List fillRects, String nativeButtonId) { + LazyButtonTemplate template = new LazyButtonTemplate(fillRects, nativeButtonId); + template.applyConfig(config.firstChild("onLoading"), t -> template.onLoading = t); + template.applyConfig(config.firstChild("onLoaded"), t -> template.onLoaded = t); + template.applyConfig(config.firstChild("onPresent"), t -> template.onPresent = t); + template.applyConfig(config.firstChild("onAbsent"), t -> template.onAbsent = t); + template.applyConfig(config.firstChild("onError"), t -> template.onError = t); + return template; + } + }; +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/StaticButtonTemplate.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/StaticButtonTemplate.java new file mode 100644 index 0000000..9bc1148 --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/StaticButtonTemplate.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.gui.template; + +import java.util.List; + +import eu.pb4.sgui.api.elements.GuiElementBuilder; +import eu.pb4.sgui.api.elements.GuiElementInterface; +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.fabric.FabricServer; +import io.github.nahkd123.stonks.minecraft.fabric.gui.ContainerGuiFrontend; +import io.github.nahkd123.stonks.minecraft.fabric.gui.Fill; +import io.github.nahkd123.stonks.minecraft.gui.ClickType; +import io.github.nahkd123.stonks.minecraft.gui.NativeButton; +import nahara.common.configurations.Config; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import stonks.fabric.StonksFabric; +import stonks.fabric.StonksFabricPlatform; + +public class StaticButtonTemplate implements ButtonTemplate { + private String stackString; + private ItemStack stack; + private int amount; + private List fillRects; + private String nativeButtonId; + + public StaticButtonTemplate(String stackString, int amount, List fillRects, String nativeButtonId) { + this.stackString = stackString; + this.amount = amount; + this.fillRects = fillRects; + this.nativeButtonId = nativeButtonId; + } + + @Override + public List getFillRects() { return fillRects; } + + @Override + public String getNativeButtonId() { return nativeButtonId; } + + @Override + public GuiElementInterface createGuiElement(ContainerGuiFrontend gui, int ordinal) { + if (stack == null) { + try { + stack = ((FabricServer) gui.getBackend().getServer()) + .getStacksDeriver() + .fromString(stackString, amount); + } catch (IllegalArgumentException e) { + StonksFabric.LOGGER.warn("Error while parsing static GUI button: {}", e.getMessage()); + stack = new ItemStack(Items.BARRIER); + } + } + + return GuiElementBuilder.from(stack) + .setCallback((index, type, action, $) -> { + NativeButton nativeButton = gui.getBackend().getNative(nativeButtonId, ordinal); + + if (nativeButton != null) { + Player player = ((StonksFabricPlatform) gui.getPlayer().getServer()) + .getModernWrapper() + .getPlayerByUUID(gui.getPlayer().getUuid()); + ClickType clickType = ContainerGuiFrontend.toCommonClickType(type); + nativeButton.onClick(gui.getBackend(), player, clickType); + } + }) + .build(); + } + + public static final ButtonTemplateFactory FACTORY = new ButtonTemplateFactory() { + @Override + public ButtonTemplate createFromConfig(Config config, List fillRects, String nativeButtonId) { + String stackString = config.firstChild("item").flatMap(Config::getValue).orElse("minecraft:barrier"); + int amount = config.firstChild("amount").flatMap(s -> s.getValue(Integer::parseInt)).orElse(1); + return new StaticButtonTemplate(stackString, amount, fillRects, nativeButtonId); + } + }; +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/text/MinecraftTextColor.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/text/MinecraftTextColor.java new file mode 100644 index 0000000..8328cfc --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/text/MinecraftTextColor.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.text; + +import io.github.nahkd123.stonks.minecraft.text.TextColor; + +public class MinecraftTextColor implements TextColor { + private net.minecraft.text.TextColor underlying; + + public MinecraftTextColor(net.minecraft.text.TextColor underlying) { + this.underlying = underlying; + } + + public net.minecraft.text.TextColor getUnderlying() { return underlying; } + + @Override + public String asJsonColor() { + return underlying.getName(); + } +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/text/MinecraftTextFactory.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/text/MinecraftTextFactory.java new file mode 100644 index 0000000..4df40f2 --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/text/MinecraftTextFactory.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.text; + +import io.github.nahkd123.stonks.minecraft.text.LegacyColor; +import io.github.nahkd123.stonks.minecraft.text.TextColor; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +public class MinecraftTextFactory implements TextFactory { + @Override + public TextComponent empty() { + return new TextProxy(Text.empty()); + } + + @Override + public TextComponent literal(String text) { + return new TextProxy(Text.literal(text)); + } + + @Override + public TextComponent translate(String key, Object... args) { + return new TextProxy(Text.translatable(key, args)); + } + + @Override + public TextComponent translateFailback(String key, String failback, Object... args) { + return new TextProxy(Text.translatableWithFallback(key, failback, args)); + } + + @Override + public TextColor hexColor(int r, int g, int b) { + return new MinecraftTextColor(net.minecraft.text.TextColor.fromRgb((r << 16) | (g << 8) | b)); + } + + @Override + public TextColor namedColor(String name) { + return new MinecraftTextColor(net.minecraft.text.TextColor.parse(name).result() + .orElse(net.minecraft.text.TextColor.fromFormatting(Formatting.RESET))); + } + + @Override + public TextColor legacyColor(LegacyColor color) { + return new MinecraftTextColor(net.minecraft.text.TextColor.fromFormatting(switch (color) { + case BLACK -> Formatting.BLACK; + case DARK_BLUE -> Formatting.DARK_BLUE; + case DARK_GREEN -> Formatting.DARK_GREEN; + case DARK_AQUA -> Formatting.DARK_AQUA; + case DARK_RED -> Formatting.DARK_RED; + case DARK_PURPLE -> Formatting.DARK_PURPLE; + case GOLD -> Formatting.GOLD; + case GRAY -> Formatting.GRAY; + case DARK_GRAY -> Formatting.DARK_GRAY; + case BLUE -> Formatting.BLUE; + case GREEN -> Formatting.GREEN; + case AQUA -> Formatting.AQUA; + case RED -> Formatting.RED; + case LIGHT_PURPLE -> Formatting.LIGHT_PURPLE; + case YELLOW -> Formatting.YELLOW; + case WHITE -> Formatting.WHITE; + default -> Formatting.RESET; + })); + } +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/text/TextProxy.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/text/TextProxy.java new file mode 100644 index 0000000..4cc323d --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/text/TextProxy.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.text; + +import io.github.nahkd123.stonks.minecraft.text.ClickEvent; +import io.github.nahkd123.stonks.minecraft.text.TextColor; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import net.minecraft.text.MutableText; +import net.minecraft.util.Identifier; + +public class TextProxy implements TextComponent { + private MutableText underlying; + + public TextProxy(MutableText underlying) { + this.underlying = underlying; + } + + public MutableText getUnderlying() { return underlying; } + + @Override + public TextComponent withBold(Boolean bold) { + underlying = underlying.styled(s -> s.withBold(bold)); + return this; + } + + @Override + public TextComponent withItalic(Boolean italic) { + underlying = underlying.styled(s -> s.withItalic(italic)); + return this; + } + + @Override + public TextComponent withUnderline(Boolean underline) { + underlying = underlying.styled(s -> s.withUnderline(underline)); + return this; + } + + @Override + public TextComponent withStrikethrough(Boolean strike) { + underlying = underlying.styled(s -> s.withStrikethrough(strike)); + return this; + } + + @Override + public TextComponent withObfuscated(Boolean obfuscated) { + underlying = underlying.styled(s -> s.withObfuscated(obfuscated)); + return this; + } + + @Override + public TextComponent withFont(String font) { + underlying = underlying.styled(s -> s.withFont(font != null ? new Identifier(font) : null)); + return null; + } + + @Override + public TextComponent withColor(TextColor color) { + underlying = underlying + .styled(s -> color == null + ? null + : s.withColor(color instanceof MinecraftTextColor mtc + ? mtc.getUnderlying() + : net.minecraft.text.TextColor.parse(color.asJsonColor()).result().orElse(null))); + return null; + } + + @Override + public TextComponent withInsertion(String text) { + underlying = underlying.styled(s -> s.withInsertion(text)); + return this; + } + + @Override + public TextComponent withClickEvent(ClickEvent event) { + // TODO Auto-generated method stub + return null; + } + + @Override + public TextComponent withHover(TextComponent hover) { + // TODO Auto-generated method stub + return this; + } + + @Override + public TextComponent withExtras(TextComponent... children) { + for (TextComponent child : children) { + if (child instanceof TextProxy proxied) underlying.append(proxied.underlying); + } + + return this; + } + + @Override + public Boolean getBold() { return underlying.getStyle().isBold(); } + + @Override + public Boolean getItalic() { return underlying.getStyle().isItalic(); } + + @Override + public Boolean getUnderline() { return underlying.getStyle().isUnderlined(); } + + @Override + public Boolean getStrikethrough() { return underlying.getStyle().isStrikethrough(); } + + @Override + public Boolean getObfuscated() { return underlying.getStyle().isObfuscated(); } + + @Override + public String getFont() { + Identifier id = underlying.getStyle().getFont(); + return id != null ? id.toString() : null; + } + + @Override + public TextColor getColor() { return new MinecraftTextColor(underlying.getStyle().getColor()); } + + @Override + public String getInsertion() { return underlying.getStyle().getInsertion(); } + + @Override + public ClickEvent getClickEvent() { // TODO Auto-generated method stub + return null; + } + + @Override + public TextComponent getHover() { // TODO Auto-generated method stub + return null; + } + + @Override + public TextComponent[] getExtras() { + return underlying.getSiblings().stream() + .map(v -> v instanceof MutableText m ? m : null) // TODO not rely on MutableText + .filter(v -> v != null) + .map(v -> new TextProxy(v)) + .toArray(TextProxy[]::new); + } +} diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/utils/ItemStacksDeriver.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/utils/ItemStacksDeriver.java new file mode 100644 index 0000000..fa88e8b --- /dev/null +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/utils/ItemStacksDeriver.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.fabric.utils; + +import java.util.Map; +import java.util.WeakHashMap; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; + +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.command.argument.ItemStackArgument; +import net.minecraft.command.argument.ItemStackArgumentType; +import net.minecraft.item.ItemStack; + +public class ItemStacksDeriver { + private CommandRegistryAccess registryAccess; + private Map stackFromString = new WeakHashMap<>(); + + public ItemStacksDeriver(CommandRegistryAccess registryAccess) { + this.registryAccess = registryAccess; + } + + public ItemStack fromString(String s, int amount) { + try { + ItemStackArgument arg = stackFromString.get(s); + if (arg == null) stackFromString.put(s, + arg = ItemStackArgumentType.itemStack(registryAccess).parse(new StringReader(s))); + return arg.createStack(amount, false); + } catch (CommandSyntaxException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/fabric/src/main/java/stonks/fabric/StonksFabric.java b/fabric/src/main/java/stonks/fabric/StonksFabric.java index 5f6b922..6a83b1b 100644 --- a/fabric/src/main/java/stonks/fabric/StonksFabric.java +++ b/fabric/src/main/java/stonks/fabric/StonksFabric.java @@ -22,14 +22,15 @@ package stonks.fabric; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.github.nahkd123.stonks.minecraft.fabric.ModernStonksFabric; import nahara.common.configurations.Config; import nahara.modkit.annotations.v1.Dependencies; import nahara.modkit.annotations.v1.Dependency; @@ -82,7 +83,6 @@ public class StonksFabric { public static void init() { LOGGER.info("Stonks2 is now initializing..."); LOGGER.info("Configuration directory is '{}'", getConfigDir()); - LOGGER.info("Main configuration file is '{}'", getMainConfigFile()); // Register configurable stuffs IntegratedStonksService.register(); @@ -99,6 +99,8 @@ public static void init() { ServerLifecycleEvents.SERVER_STOPPING.register(StonksFabric::onServerStop); ServerTickEvents.END_SERVER_TICK.register(StonksFabric::onServerTick); + ModernStonksFabric.initialize(); + // Commands CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { dispatcher.register(StonksCommand.ROOT); @@ -111,7 +113,7 @@ private static void onServerStart(MinecraftServer server) { var adapters = new ArrayList(); var config = new PlatformConfig(); - for (var child : getMainConfig().getChildren()) { + for (var child : getOrCreateConfig("config").getChildren()) { if (child.getKey().equals("useService")) { var altService = StonksProvidersRegistry.getServiceProvider(child); if (altService != null) service = altService; @@ -197,34 +199,29 @@ public static Path getConfigDir() { return dir; } - public static Path getMainConfigFile() { - return Optional.ofNullable(getConfigDir()) - .map(v -> v.resolve("config")) - .orElse(null); - } - - public static Config getMainConfig() { - var pathToConfig = getMainConfigFile(); - if (pathToConfig == null) { + public static Config getOrCreateConfig(String name) { + Path file = getConfigDir(); + if (file == null) { LOGGER.error("Unable to obtain path to configuration file"); return new Config(); } - if (Files.notExists(pathToConfig)) { - LOGGER.warn("Configuration file not found! Creating new configuration file..."); - var clsLoader = StonksFabric.class.getClassLoader(); + file = file.resolve(name); + if (Files.notExists(file)) { + LOGGER.warn("Configuration file '{}' not found! Creating new one...", name); + ClassLoader classLoader = StonksFabric.class.getClassLoader(); - try (var inStream = clsLoader.getResourceAsStream("default-config")) { - Files.copy(inStream, pathToConfig); + try (InputStream stream = classLoader.getResourceAsStream(name)) { + Files.copy(stream, file); } catch (IOException e) { e.printStackTrace(); - LOGGER.error("Failed to create configuration file, skipping"); + LOGGER.error("Failed to create {} file, skipping", name); return new Config(); } } try { - return Config.parseConfig(pathToConfig); + return Config.parseConfig(file); } catch (IOException e) { e.printStackTrace(); return new Config(); diff --git a/fabric/src/main/java/stonks/fabric/StonksFabricPlatform.java b/fabric/src/main/java/stonks/fabric/StonksFabricPlatform.java index 3364f18..226f162 100644 --- a/fabric/src/main/java/stonks/fabric/StonksFabricPlatform.java +++ b/fabric/src/main/java/stonks/fabric/StonksFabricPlatform.java @@ -23,6 +23,7 @@ import java.util.List; +import io.github.nahkd123.stonks.minecraft.fabric.FabricServer; import stonks.core.caching.StonksServiceCache; import stonks.core.platform.StonksPlatform; import stonks.core.product.Product; @@ -82,5 +83,7 @@ public interface StonksFabricPlatform extends StonksPlatform { */ public StonksSounds getSounds(); + public FabricServer getModernWrapper(); + public void startStonks(PlatformConfig config, StonksServiceProvider service, List adapters); } diff --git a/fabric/src/main/java/stonks/fabric/adapter/provided/ItemsAdapter.java b/fabric/src/main/java/stonks/fabric/adapter/provided/ItemsAdapter.java index 93dbe5a..c94ee8e 100644 --- a/fabric/src/main/java/stonks/fabric/adapter/provided/ItemsAdapter.java +++ b/fabric/src/main/java/stonks/fabric/adapter/provided/ItemsAdapter.java @@ -120,7 +120,6 @@ public AdapterResponse removeUnitsFrom(ServerPlayerEntity player, Product var refStack = convert(product); if (refStack == null) return AdapterResponse.pass(); if (amount == 0) return AdapterResponse.success(null); - ; var inv = player.getInventory(); diff --git a/fabric/src/main/java/stonks/fabric/mixin/MinecraftServerMixin.java b/fabric/src/main/java/stonks/fabric/mixin/MinecraftServerMixin.java index e3bcc5c..feef4f1 100644 --- a/fabric/src/main/java/stonks/fabric/mixin/MinecraftServerMixin.java +++ b/fabric/src/main/java/stonks/fabric/mixin/MinecraftServerMixin.java @@ -26,6 +26,8 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; +import io.github.nahkd123.stonks.minecraft.fabric.FabricServer; +import io.github.nahkd123.stonks.minecraft.fabric.ModernStonksFabric; import nahara.modkit.annotations.v1.AutoMixin; import net.minecraft.server.MinecraftServer; import stonks.core.caching.StonksServiceCache; @@ -57,6 +59,8 @@ public abstract class MinecraftServerMixin implements StonksFabricPlatform { private PlatformConfig stonks$config; @Unique private StonksSounds stonks$sounds; + @Unique + private FabricServer stonks$modernWrapper; @Override public StonksService getStonksService() { return stonks$service; } @@ -76,6 +80,9 @@ public abstract class MinecraftServerMixin implements StonksFabricPlatform { @Override public StonksSounds getSounds() { return stonks$sounds; } + @Override + public FabricServer getModernWrapper() { return stonks$modernWrapper; } + @Override public void startStonks(PlatformConfig config, StonksServiceProvider service, List adapters) { stonks$service = service.createService((MinecraftServer) (Object) this); @@ -98,5 +105,8 @@ public void startStonks(PlatformConfig config, StonksServiceProvider service, Li StonksFabric.LOGGER.info("Local service found! Loading data..."); localService.loadServiceData(); } + + stonks$modernWrapper = new FabricServer(ModernStonksFabric + .getPlatform(), (MinecraftServer) (Object) this); } } diff --git a/fabric/src/main/resources/gui.txt b/fabric/src/main/resources/gui.txt new file mode 100644 index 0000000..51ee367 --- /dev/null +++ b/fabric/src/main/resources/gui.txt @@ -0,0 +1,50 @@ +// Stonks vNext GUI configuration +// You can configure almost all GUIs with this configuration file! + +main + // The title of the GUI. Usually displayed on top of the chest GUI. + title Market + + // The menu type. This supports modded screen handlers! + type minecraft:generic_9x6 + + // Define a new button. In this case, we want static button + button stonks:static + // You can fill the buttons using "fill (x1, y1) to (x2, y2)" (inclusive bounds) + // or you can use "fill (x, y)" + // The "fill" syntax: fill (x1, y1) to (x2, y2) [reversed-x] [reversed-y] + // The "reversed-?" in "fill" is for "indexed buttons", like stonks:dynamic with category native button + // The default indexing direction is from left to right first, then from top to bottom + fill (1, 1) to (9, 1) + fill (2, 2) to (2, 6) + fill (9, 2) to (9, 6) + item minecraft:black_stained_glass_pane{display:{Name:'{"text":""}'}} + amount 1 + + button stonks:lazy_loaded + fill (1, 2) to (1, 6) + nativeButton category + onLoading stonks:static + item minecraft:clock{display:{Name:'{"text":"Loading...","color":"gray","italic":false}'}} + amount 1 + onPresent stonks:dynamic + type minecraft:paper + name [color:gray]>>[/] [color:aqua]{category.name}[/] + lore [color:yellow]Click[/] [color:gray]to open[/] + onError stonks:static + item minecraft:barrier{display:{Name:'{"text":"Error!","color":"red","italic":false}'}} + amount 1 + + button stonks:lazy_loaded + fill (3, 2) to (9, 6) + nativeButton product + onLoading stonks:static + item minecraft:clock{display:{Name:'{"text":"Loading...","color":"gray","italic":false}'}} + amount 1 + onPresent stonks:dynamic + type native + name [color:aqua]{product.name}[/] + lore [color:yellow]Click[/] [color:gray]to open[/] + onError stonks:static + item minecraft:barrier{display:{Name:'{"text":"Error!","color":"red","italic":false}'}} + amount 1 \ No newline at end of file diff --git a/minecraft/build.gradle b/minecraft/build.gradle new file mode 100644 index 0000000..752b30b --- /dev/null +++ b/minecraft/build.gradle @@ -0,0 +1,15 @@ +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' + implementation project(':core') +} + +processResources { + inputs.property "version", rootProject.stonks_version + filesMatching('fabric.mod.json') { + expand "version": rootProject.stonks_version + } +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/MinecraftPlatform.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/MinecraftPlatform.java new file mode 100644 index 0000000..c602b62 --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/MinecraftPlatform.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft; + +import io.github.nahkd123.stonks.Platform; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; + +public interface MinecraftPlatform extends Platform { + public TextFactory getTextFactory(); +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/MinecraftServer.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/MinecraftServer.java new file mode 100644 index 0000000..d08502a --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/MinecraftServer.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft; + +import java.nio.file.Path; +import java.util.List; +import java.util.UUID; + +import io.github.nahkd123.stonks.Instance; +import io.github.nahkd123.stonks.minecraft.commodity.CommoditiesService; +import io.github.nahkd123.stonks.minecraft.economy.CachedEconomyService; + +/** + *

+ * The Minecraft server interface. Please note that each platform can have more + * than 1 running Minecraft server instance. + *

+ */ +public interface MinecraftServer extends Instance { + public MinecraftPlatform getPlatform(); + + /** + *

+ * Get the path to server directory. + *

+ *

+ * For integrated servers, this will points to the world associated with this + * server. For dedicated servers, this will have the same value as + * {@link MinecraftPlatform#getDataDir()}. + *

+ * + * @return The path to server directory. + */ + public Path getServerDir(); + + public Player getPlayerByUUID(UUID uuid); + + public CachedEconomyService getEconomyService(); + + public List getCommoditiesServices(); +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/Player.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/Player.java new file mode 100644 index 0000000..41576c9 --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/Player.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft; + +import java.util.UUID; + +import io.github.nahkd123.stonks.minecraft.gui.ContainerGui; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; + +public interface Player { + public MinecraftServer getServer(); + + default MinecraftPlatform getPlatform() { return getServer().getPlatform(); } + + /** + *

+ * Get player's UUID. If you want to have player to object mapping, consider use + * {@link UUID} instead of {@link Player} as map's key. + *

+ * + * @return Player's unique identifier. + */ + public UUID getUuid(); + + /** + *

+ * Get the player's username. While it is okay to use username for player to + * object mapping when you are trying to calculate something, it is better to + * use {@link #getUUID()} as map's key. + *

+ * + * @return Player's username. + */ + public String getUsername(); + + public TextComponent getDisplayName(); + + /** + *

+ * Send the message as system message to player's chat box. System messages will + * have "system message" indicator in player's chat HUD. + *

+ * + * @param message The message to send. + */ + public void sendSystemMessage(TextComponent message); + + /** + *

+ * Send the message as action bar message that will be displayed above player's + * hotbar. + *

+ * + * @param message The message to send. + */ + public void sendActionBarMessage(TextComponent message); + + public void openGui(ContainerGui gui); +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/commodity/CommoditiesService.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/commodity/CommoditiesService.java new file mode 100644 index 0000000..573e9ea --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/commodity/CommoditiesService.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.commodity; + +import io.github.nahkd123.stonks.market.MarketService; +import io.github.nahkd123.stonks.minecraft.economy.EconomyService; +import stonks.core.service.StonksService; + +/** + *

+ * A service for managing commodities. Unlike {@link StonksService} and + * {@link EconomyService}, there can be more than 1 {@link CommoditiesService} + * active on a single server. + *

+ *

+ * Commodities can be anything, ranging from items in player's inventory to + * hypothetical stuffs like player's souls or mana from Botania (if that mod + * ever supports Fabric) + *

+ *

+ * All operations are synchronous: it might blocks main thread while + * performing operations like removing items from player's inventory or adding + * items. + *

+ *

+ * Unlike other services, all commodities services that has been registered by + * mods/plugins will be loaded when server starts. + * Convert the string to {@link Commodity}. + *

+ * + * @param str The string representation of commodity/product type. Usually + * obtained from {@link MarketService}. + * @return The commodity handler, or {@code null} if the string can't be + * converted by this service (which will forwards to other services). + */ + public Commodity typeFromString(String str); +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/commodity/Commodity.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/commodity/Commodity.java new file mode 100644 index 0000000..51cb611 --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/commodity/Commodity.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.commodity; + +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; + +public interface Commodity { + /** + *

+ * Get the display name of this commodity. + *

+ * + * @return The display name of this commodity. + */ + public TextComponent getDisplayName(); + + /** + *

+ * Get the display item as item string. The string representation is + * "[namespace:]<id>{NBT data...}". For example: + * "minecraft:apple{display:{Name:'{"text": "Red Apple"}'}}" + *

+ * + * @return The item string that follows the format. + */ + public String getDisplayItemString(); + + /** + *

+ * Count how much of this items the player currently have. + *

+ * + * @param player The player. + * @return Amount of items. + */ + public long getAmountAvailable(Player player); + + /** + *

+ * Take from target player. + *

+ * + * @param player Player to take items. + * @param amount Amount of items. + * @return Amount of leftover items that couldn't be taken (either not enough + * items or something else). + */ + public long takeAmount(Player player, long amount); + + /** + *

+ * Give to target player. + *

+ * + * @param player Player to get items. + * @param amount Amount of items. + * @return Amount of leftover items that couldn't be added to player. + */ + public long giveAmount(Player player, long amount); +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/CachedEconomyService.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/CachedEconomyService.java new file mode 100644 index 0000000..93041fb --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/CachedEconomyService.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.economy; + +import java.util.Map; +import java.util.UUID; +import java.util.WeakHashMap; +import java.util.concurrent.CompletableFuture; + +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import io.github.nahkd123.stonks.utils.lazy.LazyLoader; + +/** + *

+ * The cached version of {@link EconomyService}, using {@link WeakHashMap} as + * cache. The cached version will try to return the player's balance + * immediately, and it will only request balance query if the cached value does + * not exists or it has been garbage collected. + *

+ */ +public class CachedEconomyService implements EconomyService { + private EconomyService service; + private Map cachedBalance = new WeakHashMap<>(); + + public CachedEconomyService(EconomyService service) { + this.service = service; + } + + public EconomyService getService() { return service; } + + @Override + public TextComponent getDisplayName() { return service.getDisplayName(); } + + @Override + public Currency getCurrency() { return service.getCurrency(); } + + @Override + public CompletableFuture getBalance(Player player) { + return service.getBalance(player).thenApply(v -> { + cachedBalance.put(player.getUuid(), v); + return v; + }); + } + + public LazyLoader getCachedBalance(Player player) { + Long balance = cachedBalance.get(player.getUuid()); + if (balance != null) return LazyLoader.ofFinished(balance); + return LazyLoader.wrap(getBalance(player)); + } + + @Override + public CompletableFuture depositTo(Player player, long amount) { + return service.depositTo(player, amount).thenApply(v -> { + cachedBalance.put(player.getUuid(), v.getNewBalance()); + return v; + }); + } + + @Override + public CompletableFuture withdrawFrom(Player player, long amount) { + return service.withdrawFrom(player, amount).thenApply(v -> { + cachedBalance.put(player.getUuid(), v.getNewBalance()); + return v; + }); + } +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/Currency.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/Currency.java new file mode 100644 index 0000000..22895df --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/Currency.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.economy; + +import io.github.nahkd123.stonks.minecraft.text.TextComponent; + +/** + *

+ * The currency. + *

+ */ +public interface Currency { + public TextComponent getDisplay(long underlying); + + /** + *

+ * Convert the underlying integer to string, which can be stored inside + * configuration files. Use {@link #convertFromString(String)} to get the + * {@link Long} as underlying integer. + *

+ * + * @param underlying The underlying integer. + * @return The string representation of the underlying integer. + */ + public String convertToString(long underlying); + + /** + *

+ * Convert the string to underlying integer, which will be used with the + * currency. + *

+ * + * @param str The string representation of the underlying integer. + * @return The underlying integer. + */ + public long convertFromString(String str); + +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/DoubleBackedCurrency.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/DoubleBackedCurrency.java new file mode 100644 index 0000000..be28a6e --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/DoubleBackedCurrency.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.economy; + +import java.text.DecimalFormat; + +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; + +public class DoubleBackedCurrency implements Currency { + private TextFactory textFactory; + private int decimals; + private long wholeDollar; + private DecimalFormat format = new DecimalFormat("#,##0.##"); + + public DoubleBackedCurrency(TextFactory textFactory, int decimals) { + this.textFactory = textFactory; + this.decimals = decimals; + this.wholeDollar = (long) Math.pow(10L, decimals); + } + + public double toDouble(long value) { + long dollars = value / wholeDollar; + long cents = value % wholeDollar; + return dollars + (cents / (double) wholeDollar); + } + + public long fromDouble(double value) { + long dollars = (long) Math.floor(value); + long cents = Math.round(wholeDollar * (value - dollars)); + return dollars * wholeDollar + cents; + } + + public int getDecimals() { return decimals; } + + @Override + public TextComponent getDisplay(long underlying) { + return textFactory.literal(format.format(decimals)); + } + + @Override + public String convertToString(long underlying) { + return Double.toString(toDouble(underlying)); + } + + @Override + public long convertFromString(String str) { + return fromDouble(Double.parseDouble(str)); + } +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/EconomyService.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/EconomyService.java new file mode 100644 index 0000000..bd6fc76 --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/EconomyService.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.economy; + +import java.util.concurrent.CompletableFuture; + +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; + +/** + *

+ * The economy service. In Stonks, there can be at most 1 economy service. This + * is because a single stocks market can't have more than 1 economy system + * (otherwise it would be chaos). + *

+ *

+ * All operations are asynchronous: they can be executed without blocking + * main thread, but the implementation must have requests queue to prevent race + * conditions. + *

+ */ +public interface EconomyService { + public TextComponent getDisplayName(); + + public Currency getCurrency(); + + /** + *

+ * Get player's main account balance. + *

+ *

+ * Some plugins/mods may allows player to have more than 1 active account. In + * this case, the primary account will be picked. If there is no primary/main + * accounts, the first account will be picked. If there is no accounts, this + * will returns zero value. + *

+ *

+ * The task only completed exceptionally when an error occurred on the service + * adapter side (such as {@link NullPointerException}). Things like connection + * drops or insufficient balance should be presented on {@link Transaction}. + *

+ * + * @param player The player. + * @return The player's balance. + */ + public CompletableFuture getBalance(Player player); + + /** + *

+ * Attempt to deposit money to player's main account. + *

+ *

+ * The task only completed exceptionally when an error occurred on the service + * adapter side (such as {@link NullPointerException}). Things like connection + * drops or insufficient balance should be presented on {@link Transaction}. + *

+ * + * @param player The player. + * @param amount Amount of money to deposit. + * @return Transaction result. + */ + public CompletableFuture depositTo(Player player, long amount); + + /** + *

+ * Attempt to withdraw money from player's main account. Will fails if player + * doesn't have sufficient balance. + *

+ *

+ * The task only completed exceptionally when an error occurred on the service + * adapter side (such as {@link NullPointerException}). Things like connection + * drops or insufficient balance should be presented on {@link Transaction}. + *

+ * + * @param player The player. + * @param amount Amount of money to withdraw. + * @return Transaction result. + */ + public CompletableFuture withdrawFrom(Player player, long amount); +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/Transaction.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/Transaction.java new file mode 100644 index 0000000..db17eeb --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/Transaction.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.economy; + +import java.util.UUID; + +public interface Transaction { + public static final int CODE_SUCCESS = 0; + public static final int CODE_FAIL_UNKNOWN = -1; + public static final int CODE_FAIL_INSUFFICIENT_BALANCE = 1; + + public UUID getPlayerUuid(); + + /** + *

+ * Get the amount of money involved in this transaction. + *

+ * + * @return The amount of money. + */ + public long getAmount(); + + public long getNewBalance(); + + public TransactionType getType(); + + public boolean isSuccess(); + + /** + *

+ * Get the error code for this transaction. Negative value means the error is + * not known to Stonks and should use the message from + * {@link #getErrorMessage()} instead. Positive value means the error is known + * to Stonks and will use the localized message. See static fields of + * {@link Transaction} for codes. + *

+ * + * @return The error code. + * @see #CODE_SUCCESS + * @see #CODE_FAIL_UNKNOWN + * @see #CODE_FAIL_INSUFFICIENT_BALANCE + */ + public int getCode(); + + /** + *

+ * The optional error message if {@link #isSuccess()} is false. This will be + * used by Stonks if {@link #getCode()} returns negative value or Stonks can't + * find localized message. + *

+ * + * @return The error message. + */ + public String getErrorMessage(); +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/TransactionType.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/TransactionType.java new file mode 100644 index 0000000..b4b4f05 --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/economy/TransactionType.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.economy; + +public enum TransactionType { + WITHDRAW, + DEPOSIT; +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/ClickType.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/ClickType.java new file mode 100644 index 0000000..c5a12df --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/ClickType.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.gui; + +public enum ClickType { + LEFT, + RIGHT, + SHIFT_LEFT, + SHIFT_RIGHT, + UNKNOWN; +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/ContainerGui.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/ContainerGui.java new file mode 100644 index 0000000..7aeb666 --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/ContainerGui.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.gui; + +import io.github.nahkd123.stonks.minecraft.MinecraftServer; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; +import io.github.nahkd123.stonks.minecraft.text.placeholder.Placeholders; + +public interface ContainerGui extends Placeholders { + public MinecraftServer getServer(); + + public String getId(); + + default TextFactory getTextFactory() { return getServer().getPlatform().getTextFactory(); } + + /** + *

+ * Get the native button from Stonks native button ID. The native button is + * normally obtained from global Stonks for Minecraft native buttons registry, + * which means you can display the products category button inside buy menu!. + *

+ * + * @param id Native button ID. + * @param ordinal The ordinal of the button. This will be used in configurations + * to determine the index of "list" that the button is trying to + * refers to. For example, {@code CategoryButton} takes in + * ordinal to display category to the GUI. + * @return Native button. + */ + public NativeButton getNative(String id, int ordinal); +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/LazyLoadedNativeButton.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/LazyLoadedNativeButton.java new file mode 100644 index 0000000..b5c32f7 --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/LazyLoadedNativeButton.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.gui; + +import io.github.nahkd123.stonks.utils.lazy.LazyLoader; + +public interface LazyLoadedNativeButton extends NativeButton { + public LazyLoader getLoader(); +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/NativeButton.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/NativeButton.java new file mode 100644 index 0000000..e953cc9 --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/NativeButton.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.gui; + +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.text.placeholder.Placeholders; + +/** + *

+ * The native button in the container GUI. Native buttons will be implemented by + * "Stonks for Minecraft" and implementations like "Stonks for Fabric" will + * implements the "rendering" part. + *

+ *

+ * Basically, Stonks for Minecraft implements the GUI behaviour, like perform + * transactions then update the button state, while the frontends will check the + * button type (like {@code instanceof ProductButton} for example) and place the + * {@code ItemStack} that reflects the button's current states. + *

+ */ +public interface NativeButton extends Placeholders { + public void onClick(ContainerGui gui, Player player, ClickType type); +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/AbstractContainerGui.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/AbstractContainerGui.java new file mode 100644 index 0000000..f129a3f --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/AbstractContainerGui.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.gui.provided.gui; + +import java.util.HashMap; +import java.util.Map; + +import io.github.nahkd123.stonks.minecraft.MinecraftServer; +import io.github.nahkd123.stonks.minecraft.gui.ContainerGui; +import io.github.nahkd123.stonks.minecraft.gui.NativeButton; +import io.github.nahkd123.stonks.minecraft.gui.provided.button.CategoryNativeButton; +import io.github.nahkd123.stonks.minecraft.gui.provided.button.CloseNativeButton; +import io.github.nahkd123.stonks.minecraft.gui.provided.button.ProductNativeButton; + +public abstract class AbstractContainerGui implements ContainerGui { + @FunctionalInterface + public static interface NativeButtonFactory { + NativeButton create(AbstractContainerGui gui, int ordinal); + } + + private static final Map GLOBAL_REGISTRY = new HashMap<>(); + + static { + GLOBAL_REGISTRY.put(CloseNativeButton.ID, CloseNativeButton::new); + GLOBAL_REGISTRY.put(CategoryNativeButton.ID, CategoryNativeButton::new); + GLOBAL_REGISTRY.put(ProductNativeButton.ID, ProductNativeButton::new); + } + + private MinecraftServer server; + private String id; + + public AbstractContainerGui(MinecraftServer server, String id) { + this.server = server; + this.id = id; + } + + @Override + public String getId() { return id; } + + @Override + public MinecraftServer getServer() { return server; } + + @Override + public NativeButton getNative(String id, int ordinal) { + if (id == null) return null; + NativeButtonFactory ctor = GLOBAL_REGISTRY.get(id); + return ctor != null ? ctor.create(this, ordinal) : null; + } +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/MainContainerGui.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/MainContainerGui.java new file mode 100644 index 0000000..617dbf2 --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/MainContainerGui.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.gui.provided.gui; + +import io.github.nahkd123.stonks.market.catalogue.Category; +import io.github.nahkd123.stonks.minecraft.MinecraftServer; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; +import io.github.nahkd123.stonks.utils.lazy.LazyLoader; + +public class MainContainerGui extends AbstractContainerGui { + private int categoryIndex; + + public MainContainerGui(MinecraftServer server, int categoryIndex) { + super(server, "main"); + this.categoryIndex = categoryIndex; + } + + public int getCategoryIndex() { return categoryIndex; } + + public void setCategoryIndex(int categoryIndex) { this.categoryIndex = categoryIndex; } + + public LazyLoader getCategoryCache() { + return getServer().getMarketCache().productsCatalogue + .map(c -> categoryIndex < c.getCategories().size() ? c.getCategories().get(categoryIndex) : null); + } + + @Override + public TextComponent replacePlaceholder(TextFactory factory, String name) { + // TODO + return null; + } +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/ClickEvent.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/ClickEvent.java new file mode 100644 index 0000000..f436ce6 --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/ClickEvent.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.text; + +public record ClickEvent(ClickEventAction action, String value) { +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/ClickEventAction.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/ClickEventAction.java new file mode 100644 index 0000000..948bd7d --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/ClickEventAction.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.text; + +public enum ClickEventAction { + OPEN_URL("open_url"), + RUN_COMMAND("run_command"), + SUGGEST_COMMAND("suggest_command"), + CHANGE_PAGE("change_page"), + COPY_TO_CLIPBOARD("copy_to_clipboard"); + + private String jsonType; + + private ClickEventAction(String jsonType) { + this.jsonType = jsonType; + } + + public String getJsonType() { return jsonType; } +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/FriendlyParser.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/FriendlyParser.java new file mode 100644 index 0000000..cb4b9ee --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/FriendlyParser.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.text; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.github.nahkd123.stonks.minecraft.text.placeholder.Placeholders; + +public class FriendlyParser { + private static final class CharStream { + private CharSequence input; + private int index = 0; + + public CharStream(CharSequence input) { + this.input = input; + } + + public char get() { + return input.charAt(index); + } + + public void advance(int by) { + index += by; + } + + public boolean hasNext() { + return index < input.length(); + } + } + + public static TextComponent parse(TextFactory factory, String input, Placeholders placeholders) { + if (input == null || input.isEmpty()) return factory.empty(); + return parseInternal(factory, new CharStream(input), placeholders); + } + + private static String nextEnclosure(CharStream input, char start, char end) { + if (!input.hasNext() || input.get() != start) return null; + String tagName = ""; + boolean escape = false; + + input.advance(1); + while (input.hasNext()) { + if (escape) { + escape = false; + tagName += input.get(); + input.advance(1); + continue; + } + + if (input.get() == '\\') { + escape = true; + input.advance(1); + continue; + } + + if (input.get() == end) { + input.advance(1); + return tagName; + } + + tagName += input.get(); + input.advance(1); + } + + input.advance(-tagName.length() - 1); + return null; + } + + private static TextComponent parseInternal(TextFactory factory, CharStream input, Placeholders placeholders) { + // When we reach the end OR [/], end the parsing + String content = ""; + String tagName; + boolean escape = false; + boolean styledInBetween = false; + + String parent = ""; + List extras = new ArrayList<>(); + + while (input.hasNext()) { + if (escape) { + escape = false; + content += input.get(); + input.advance(1); + continue; + } + + if (input.get() == '\\') { + escape = true; + input.advance(1); + continue; + } + + if ((tagName = nextEnclosure(input, '[', ']')) != null) { + if (tagName.equals("/")) break; + + // TODO duplicated code + if (!content.isEmpty()) { + if (!styledInBetween) { + parent = content; + } else { + if (!parent.isEmpty()) { + extras.add(0, factory.literal(parent)); + parent = ""; + } + + extras.add(factory.literal(content)); + } + + content = ""; + } + + TextComponent next = parseInternal(factory, input, placeholders); + applyStyling(tagName, factory, next); + extras.add(next); + styledInBetween = true; + continue; + } + + if (placeholders != null && (tagName = nextEnclosure(input, '{', '}')) != null) { + // TODO duplicated code + if (!content.isEmpty()) { + if (!styledInBetween) { + parent = content; + } else { + if (!parent.isEmpty()) { + extras.add(0, factory.literal(parent)); + parent = ""; + } + + extras.add(factory.literal(content)); + } + + content = ""; + } + + TextComponent placeholderContent; + if ((placeholderContent = placeholders.replacePlaceholder(factory, tagName)) != null) { + extras.add(placeholderContent); + styledInBetween = true; + } + + continue; + } + + content += input.get(); + input.advance(1); + } + + if (!content.isEmpty()) { + if (styledInBetween) { + extras.add(factory.literal(content)); + } else { + parent = content; + } + } + + return (parent.isEmpty() ? factory.empty() : factory.literal(parent)) + .withExtras(extras.toArray(TextComponent[]::new)); + } + + private static final Pattern HEX_COLOR_PATTERN = Pattern + .compile("#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})"); + + private static record TextModifier(String shortName, String longName, Consumer positive, Consumer negative, Consumer absent) { + } + + private static final List MODIFIERS = Arrays.asList( + new TextModifier("b", "bold", t -> t.withBold(true), t -> t.withBold(false), t -> t.withBold(null)), + new TextModifier("i", "italic", t -> t.withItalic(true), t -> t.withItalic(false), t -> t.withItalic(null)), + new TextModifier("u", "underline", t -> t.withUnderline(true), t -> t.withUnderline(false), t -> t + .withUnderline(null)), + new TextModifier("s", "strike", t -> t.withStrikethrough(true), t -> t.withStrikethrough(false), t -> t + .withStrikethrough(null)), + new TextModifier("o", "obfuscate", t -> t.withObfuscated(true), t -> t.withObfuscated(false), t -> t + .withObfuscated(null))); + + private static void applyStyling(String tag, TextFactory factory, TextComponent text) { + if (tag.startsWith("font:")) { + text.withFont(tag.substring("font:".length())); + return; + } + + if (tag.startsWith("color:")) { + text.withColor(factory.namedColor(tag.substring("color:".length()))); + return; + } + + if (tag.startsWith("#")) { + Matcher matcher = HEX_COLOR_PATTERN.matcher(tag); + if (!matcher.matches()) return; + + int r = Integer.parseInt(matcher.group(1), 16); + int g = Integer.parseInt(matcher.group(2), 16); + int b = Integer.parseInt(matcher.group(3), 16); + text.withColor(factory.hexColor(r, g, b)); + return; + } + + for (TextModifier mod : MODIFIERS) { + if (tag.equalsIgnoreCase(mod.shortName) || tag.equalsIgnoreCase(mod.longName)) { + mod.positive.accept(text); + return; + } + + if (tag.equalsIgnoreCase("!" + mod.shortName) || tag.equalsIgnoreCase("not-" + mod.longName)) { + mod.negative.accept(text); + return; + } + + if (tag.equalsIgnoreCase("~" + mod.shortName) || tag.equalsIgnoreCase("no-" + mod.longName)) { + mod.absent.accept(text); + return; + } + } + } +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/LegacyColor.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/LegacyColor.java new file mode 100644 index 0000000..8c0ef7a --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/LegacyColor.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.text; + +public enum LegacyColor { + BLACK("black"), + DARK_BLUE("dark_blue"), + DARK_GREEN("dark_green"), + DARK_AQUA("dark_aqua"), + DARK_RED("dark_red"), + DARK_PURPLE("dark_purple"), + GOLD("gold"), + GRAY("gray"), + DARK_GRAY("dark_gray"), + BLUE("blue"), + GREEN("green"), + AQUA("aqua"), + RED("red"), + LIGHT_PURPLE("light_purple"), + YELLOW("yellow"), + WHITE("white"); + + private String name; + + private LegacyColor(String name) { + this.name = name; + } + + public TextColor get(TextFactory factory) { + return factory.namedColor(name); + } +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/TextColor.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/TextColor.java new file mode 100644 index 0000000..f609953 --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/TextColor.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.text; + +public interface TextColor { + /** + *

+ * Get the color name that will be used in JSON chat format. In Minecraft 1.16+, + * the color name can be a web color or hex color following the format + * {@code #RRGGBB}. + *

+ * + * @return The color for JSON chat format. + */ + public String asJsonColor(); +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/TextComponent.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/TextComponent.java new file mode 100644 index 0000000..75bebaf --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/TextComponent.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.text; + +/** + *

+ * The text component that will be used by Stonks. This interface will sits on + * top of existing text component system implementation (either being a proxy or + * through Mixins interface injection). + *

+ */ +public interface TextComponent { + public TextComponent withBold(Boolean bold); + + public TextComponent withItalic(Boolean italic); + + public TextComponent withUnderline(Boolean underline); + + public TextComponent withStrikethrough(Boolean strike); + + public TextComponent withObfuscated(Boolean obfuscated); + + public TextComponent withFont(String font); + + public TextComponent withColor(TextColor color); + + /** + *

+ * When player click on this text component, it will replaces the player's chat + * content with insertion content in this component. + *

+ * + * @param text The insertion text, or {@code null} to unset. + * @return {@code this} for chaining. + */ + public TextComponent withInsertion(String text); + + public TextComponent withClickEvent(ClickEvent event); + + public TextComponent withHover(TextComponent hover); + + public TextComponent withExtras(TextComponent... children); + + public Boolean getBold(); + + public Boolean getItalic(); + + public Boolean getUnderline(); + + public Boolean getStrikethrough(); + + public Boolean getObfuscated(); + + public String getFont(); + + public TextColor getColor(); + + public String getInsertion(); + + public ClickEvent getClickEvent(); + + public TextComponent getHover(); + + public TextComponent[] getExtras(); +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/TextFactory.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/TextFactory.java new file mode 100644 index 0000000..9e295ee --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/TextFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.text; + +public interface TextFactory { + public TextComponent empty(); + + public TextComponent literal(String text); + + public TextComponent translate(String key, Object... args); + + public TextComponent translateFailback(String key, String failback, Object... args); + + public TextColor hexColor(int r, int g, int b); + + public TextColor namedColor(String name); + + default TextColor legacyColor(LegacyColor color) { + return namedColor(color.name()); + } +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/placeholder/MergedPlaceholders.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/placeholder/MergedPlaceholders.java new file mode 100644 index 0000000..4c5d592 --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/placeholder/MergedPlaceholders.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.text.placeholder; + +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; + +public class MergedPlaceholders implements Placeholders { + private Placeholders[] placeholders; + + public MergedPlaceholders(Placeholders... placeholders) { + this.placeholders = placeholders; + } + + @Override + public TextComponent replacePlaceholder(TextFactory factory, String name) { + for (Placeholders p : placeholders) { + if (p == null) continue; + TextComponent replaced = p.replacePlaceholder(factory, name); + if (replaced != null) return replaced; + } + + return null; + } +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/placeholder/Placeholders.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/placeholder/Placeholders.java new file mode 100644 index 0000000..9a97ffd --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/placeholder/Placeholders.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.text.placeholder; + +import io.github.nahkd123.stonks.minecraft.MinecraftServer; +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; + +/** + *

+ * Scoped placeholders thing. It is "scoped" because there are placeholders that + * only exists under certain context, like {@link Player} or + * {@link MinecraftServer}. + *

+ */ +public interface Placeholders { + /** + *

+ * Replace placeholder with specified name with a text component. + *

+ * + * @param factory The text components factory. + * @param name The name of the placeholder. + * @return The content, or {@code null} if no placeholder with specified name + * exists (in current context). + */ + public TextComponent replacePlaceholder(TextFactory factory, String name); +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/placeholder/PlayerPlaceholders.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/placeholder/PlayerPlaceholders.java new file mode 100644 index 0000000..2e27d69 --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/text/placeholder/PlayerPlaceholders.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.text.placeholder; + +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.economy.CachedEconomyService; +import io.github.nahkd123.stonks.minecraft.text.LegacyColor; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; + +public class PlayerPlaceholders implements Placeholders { + private Player player; + + public PlayerPlaceholders(Player player) { + this.player = player; + } + + public Player getPlayer() { return player; } + + @Override + public TextComponent replacePlaceholder(TextFactory text, String name) { + CachedEconomyService economy = player.getServer().getEconomyService(); + + return switch (name) { + case "player.name" -> player.getDisplayName(); + case "player.balance" -> economy.getCachedBalance(player) + .map(v -> economy.getCurrency().getDisplay(v)) + .getTriState( + () -> text.literal("...").withColor(text.legacyColor(LegacyColor.GRAY)), + err -> text.literal("Error!").withColor(text.legacyColor(LegacyColor.RED))); + default -> null; + }; + } +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/utils/TriStates.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/utils/TriStates.java new file mode 100644 index 0000000..43490c3 --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/utils/TriStates.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.utils; + +import io.github.nahkd123.stonks.minecraft.text.LegacyColor; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; +import io.github.nahkd123.stonks.utils.lazy.LazyLoader; + +public class TriStates { + public static TextComponent of(LazyLoader loader, TextFactory factory) { + return loader.getTriState( + () -> factory.literal("Loading...").withColor(factory.legacyColor(LegacyColor.GRAY)), + t -> factory.literal("Error!").withColor(factory.legacyColor(LegacyColor.RED))); + } +} diff --git a/minecraft/src/main/resources/fabric.mod.json b/minecraft/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..da4ecd0 --- /dev/null +++ b/minecraft/src/main/resources/fabric.mod.json @@ -0,0 +1,13 @@ +{ + "schemaVersion": 1, + "id": "stonks-minecraft", + "version": "${version}", + "name": "Stonks2 for Minecraft (Library)", + "description": "An universal library for Minecraft platforms (Bukkit, Fabric, Sponge, etc.)", + "custom": { + "modmenu": { + "parent": "stonks-fabric", + "badges": ["library"] + } + } +} diff --git a/settings.gradle b/settings.gradle index d39769c..eed3acb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,3 +11,4 @@ plugins { include 'core' include 'fabric' +include 'minecraft' From 102894cfb827b9df3fa8dc972f124e2f006f865f Mon Sep 17 00:00:00 2001 From: nahkd123 Date: Sat, 23 Dec 2023 17:50:44 +0700 Subject: [PATCH 3/3] Add market data cache + back native button --- .../nahkd123/stonks/market/MarketCache.java | 14 +++++ .../stonks/utils/lazy/LazyFlatMapper.java | 61 +++++++++++++++++++ .../stonks/utils/lazy/LazyLoader.java | 4 ++ .../fabric/command/DevelopmentCommand.java | 2 +- .../fabric/gui/ContainerGuiFrontend.java | 3 +- .../gui/template/DynamicButtonTemplate.java | 2 +- .../fabric/gui/template/GuiTemplate.java | 2 +- .../minecraft/fabric/text/TextProxy.java | 4 +- fabric/src/main/resources/gui.txt | 26 +++++++- .../stonks/minecraft/gui/ContainerGui.java | 2 + .../gui/provided/button/BackNativeButton.java | 48 +++++++++++++++ .../provided/button/CategoryNativeButton.java | 15 ++--- .../provided/button/ProductNativeButton.java | 39 +++++++++--- .../provided/gui/AbstractContainerGui.java | 8 ++- .../gui/provided/gui/MainContainerGui.java | 5 +- .../gui/provided/gui/ProductContainerGui.java | 27 ++++++++ .../utils/{TriStates.java => LazyTexts.java} | 17 +++++- 17 files changed, 251 insertions(+), 28 deletions(-) create mode 100644 core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LazyFlatMapper.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/BackNativeButton.java create mode 100644 minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/ProductContainerGui.java rename minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/utils/{TriStates.java => LazyTexts.java} (73%) diff --git a/core/src/main/java/io/github/nahkd123/stonks/market/MarketCache.java b/core/src/main/java/io/github/nahkd123/stonks/market/MarketCache.java index 532536f..d9179ed 100644 --- a/core/src/main/java/io/github/nahkd123/stonks/market/MarketCache.java +++ b/core/src/main/java/io/github/nahkd123/stonks/market/MarketCache.java @@ -21,16 +21,30 @@ */ package io.github.nahkd123.stonks.market; +import java.util.Map; +import java.util.WeakHashMap; + +import io.github.nahkd123.stonks.market.catalogue.Product; import io.github.nahkd123.stonks.market.catalogue.ProductsCatalogue; +import io.github.nahkd123.stonks.market.summary.ProductSummary; import io.github.nahkd123.stonks.utils.lazy.CachingLazyLoader; public class MarketCache { public final MarketService service; public final CachingLazyLoader productsCatalogue; + public final Map> productSummaries = new WeakHashMap<>(); public MarketCache(MarketService service) { this.service = service; this.productsCatalogue = new CachingLazyLoader<>(() -> service.queryCatalogue() .thenApply(r -> r.data()), 60_000); } + + public CachingLazyLoader getSummary(Product product) { + CachingLazyLoader cache = productSummaries.get(product.getId()); + if (cache == null) productSummaries.put(product.getId(), cache = new CachingLazyLoader<>(() -> service + .querySummary(product) + .thenApply(r -> r.data()), 10_000)); + return cache; + } } diff --git a/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LazyFlatMapper.java b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LazyFlatMapper.java new file mode 100644 index 0000000..7168822 --- /dev/null +++ b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LazyFlatMapper.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.utils.lazy; + +import java.util.function.Function; + +public class LazyFlatMapper implements LazyLoader { + private LazyLoader
loader; + private Function> mapper; + + public LazyFlatMapper(LazyLoader loader, Function> mapper) { + this.loader = loader; + this.mapper = mapper; + } + + @Override + public LoadState load() { + return switch (loader.load()) { + case LOADING -> LoadState.LOADING; + case FAILED -> LoadState.FAILED; + case SUCCESS -> mapper.apply(loader.get()).load(); + }; + } + + @Override + public Throwable getFailure() { + return switch (loader.load()) { + case LOADING -> null; + case FAILED -> loader.getFailure(); + case SUCCESS -> mapper.apply(loader.get()).getFailure(); + }; + } + + @Override + public B get() throws IllegalStateException { + return switch (loader.load()) { + case LOADING -> throw new IllegalStateException("The loader is still loading!"); + case FAILED -> throw new IllegalStateException(loader.getFailure()); + case SUCCESS -> mapper.apply(loader.get()).get(); + }; + } +} diff --git a/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LazyLoader.java b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LazyLoader.java index 3e88de7..626642d 100644 --- a/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LazyLoader.java +++ b/core/src/main/java/io/github/nahkd123/stonks/utils/lazy/LazyLoader.java @@ -86,6 +86,10 @@ default LazyMapper map(Function mapper) { return new LazyMapper<>(this, mapper); } + default LazyFlatMapper flatMap(Function> mapper) { + return new LazyFlatMapper<>(this, mapper); + } + public static LazyLoader wrap(CompletableFuture future) { return new CompletableFutureLazyLoader<>(future); } diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/command/DevelopmentCommand.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/command/DevelopmentCommand.java index 8220ec0..85d27c6 100644 --- a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/command/DevelopmentCommand.java +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/command/DevelopmentCommand.java @@ -58,7 +58,7 @@ public class DevelopmentCommand { .then(literal("openmoderngui").executes(context -> { ServerPlayerEntity player = context.getSource().getPlayerOrThrow(); FabricServer server = FabricServer.of(context.getSource().getServer()); - server.getPlayerByUUID(player.getUuid()).openGui(new MainContainerGui(server, 0)); + server.getPlayerByUUID(player.getUuid()).openGui(new MainContainerGui(null, server, 0)); return 1; })); } diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/ContainerGuiFrontend.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/ContainerGuiFrontend.java index cff67be..fd6b56a 100644 --- a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/ContainerGuiFrontend.java +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/ContainerGuiFrontend.java @@ -48,8 +48,9 @@ public void beforeOpen() { } public void refresh() { - setTitle(template.title.apply(this) instanceof TextProxy p ? p.getUnderlying() : null); + if (template == null) return; + setTitle(template.title.apply(this) instanceof TextProxy p ? p.getUnderlying() : null); for (ButtonTemplate buttonTemplate : template.buttons) { buttonTemplate.forEachInFillRects((x, y, ordinal) -> { int index = (y - 1) * getWidth() + (x - 1); diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/DynamicButtonTemplate.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/DynamicButtonTemplate.java index a6e13e8..746702f 100644 --- a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/DynamicButtonTemplate.java +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/DynamicButtonTemplate.java @@ -166,7 +166,7 @@ public ButtonTemplate createFromConfig(Config config, List fillRects, Stri config.firstChild("amount").flatMap(s -> s.getValue(Integer::parseInt)).ifPresent(v -> template.amount = v); config.firstChild("name").flatMap(Config::getValue).ifPresent(v -> template.name = v); - template.lore = config.children("lore").flatMap(c -> c.getValue().stream()).toList(); + template.lore = config.children("lore").map(c -> c.getValue().orElse("")).toList(); // TODO more fun stuffs here! return template; } diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/GuiTemplate.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/GuiTemplate.java index 6273efe..344adee 100644 --- a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/GuiTemplate.java +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/gui/template/GuiTemplate.java @@ -46,7 +46,7 @@ public GuiTemplate configure(Config config) { config.firstChild("title").flatMap(Config::getValue).ifPresent(title -> { this.title = frontend -> { TextFactory textFactory = frontend.getBackend().getTextFactory(); - return FriendlyParser.parse(textFactory, title, null); // TODO placeholder here + return FriendlyParser.parse(textFactory, title, frontend.getBackend()); }; }); diff --git a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/text/TextProxy.java b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/text/TextProxy.java index 4cc323d..78a2da4 100644 --- a/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/text/TextProxy.java +++ b/fabric/src/main/java/io/github/nahkd123/stonks/minecraft/fabric/text/TextProxy.java @@ -80,7 +80,7 @@ public TextComponent withColor(TextColor color) { : s.withColor(color instanceof MinecraftTextColor mtc ? mtc.getUnderlying() : net.minecraft.text.TextColor.parse(color.asJsonColor()).result().orElse(null))); - return null; + return this; } @Override @@ -92,7 +92,7 @@ public TextComponent withInsertion(String text) { @Override public TextComponent withClickEvent(ClickEvent event) { // TODO Auto-generated method stub - return null; + return this; } @Override diff --git a/fabric/src/main/resources/gui.txt b/fabric/src/main/resources/gui.txt index 51ee367..5eab451 100644 --- a/fabric/src/main/resources/gui.txt +++ b/fabric/src/main/resources/gui.txt @@ -44,7 +44,31 @@ main onPresent stonks:dynamic type native name [color:aqua]{product.name}[/] + lore + lore [color:gray]Instant buy price: [/][color:yellow]{product.instant.buy}[/] + lore [color:gray]Instant sell price: [/][color:yellow]{product.instant.sell}[/] + lore lore [color:yellow]Click[/] [color:gray]to open[/] onError stonks:static item minecraft:barrier{display:{Name:'{"text":"Error!","color":"red","italic":false}'}} - amount 1 \ No newline at end of file + amount 1 + +product + title Market > {product.name} + type minecraft:generic_9x4 + + button stonks:static + fill (1, 1) to (9, 1) + item minecraft:black_stained_glass_pane{display:{Name:'{"text":""}'}} + amount 1 + + button stonks:static + fill (2, 1) + nativeButton back + item minecraft:arrow{display:{Name:'[{"text":"<-- ","color":"gray","italic":false},{"text":"Back","color":"white","italic":false}]'}} + amount 1 + + button stonks:dynamic + fill (5, 3) + nativeButton product + type native \ No newline at end of file diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/ContainerGui.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/ContainerGui.java index 7aeb666..6e27833 100644 --- a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/ContainerGui.java +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/ContainerGui.java @@ -26,6 +26,8 @@ import io.github.nahkd123.stonks.minecraft.text.placeholder.Placeholders; public interface ContainerGui extends Placeholders { + public ContainerGui getPrevious(); + public MinecraftServer getServer(); public String getId(); diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/BackNativeButton.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/BackNativeButton.java new file mode 100644 index 0000000..c5e2dd0 --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/BackNativeButton.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 nahkd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.nahkd123.stonks.minecraft.gui.provided.button; + +import io.github.nahkd123.stonks.minecraft.Player; +import io.github.nahkd123.stonks.minecraft.gui.ClickType; +import io.github.nahkd123.stonks.minecraft.gui.ContainerGui; +import io.github.nahkd123.stonks.minecraft.gui.NativeButton; +import io.github.nahkd123.stonks.minecraft.gui.provided.gui.AbstractContainerGui; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; + +public class BackNativeButton implements NativeButton { + public static final String ID = "back"; + + public BackNativeButton(AbstractContainerGui gui, int ordinal) {} + + public BackNativeButton() {} + + @Override + public void onClick(ContainerGui gui, Player player, ClickType type) { + player.openGui(gui.getPrevious()); + } + + @Override + public TextComponent replacePlaceholder(TextFactory factory, String name) { + return null; + } +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/CategoryNativeButton.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/CategoryNativeButton.java index 90d09b3..c016331 100644 --- a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/CategoryNativeButton.java +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/CategoryNativeButton.java @@ -31,7 +31,7 @@ import io.github.nahkd123.stonks.minecraft.gui.provided.gui.MainContainerGui; import io.github.nahkd123.stonks.minecraft.text.TextComponent; import io.github.nahkd123.stonks.minecraft.text.TextFactory; -import io.github.nahkd123.stonks.minecraft.utils.TriStates; +import io.github.nahkd123.stonks.minecraft.utils.LazyTexts; import io.github.nahkd123.stonks.utils.lazy.LazyLoader; import io.github.nahkd123.stonks.utils.lazy.LoadState; @@ -54,9 +54,10 @@ public void onClick(ContainerGui gui, Player player, ClickType type) { if (cache.load() != LoadState.SUCCESS) return; if (getLoader().get() == null) return; - MainContainerGui main = gui instanceof MainContainerGui m ? m : new MainContainerGui(gui.getServer(), ordinal); + MainContainerGui main = gui instanceof MainContainerGui m + ? m + : new MainContainerGui(gui, gui.getServer(), ordinal); main.setCategoryIndex(ordinal); - // TODO if (!(gui instanceof MainContainerGui)) player.openGui(main); } @@ -64,10 +65,10 @@ public void onClick(ContainerGui gui, Player player, ClickType type) { @Override public TextComponent replacePlaceholder(TextFactory factory, String name) { return switch (name) { - case "category.id" -> TriStates.of(getLoader().map(v -> factory.literal(v.getId())), factory); - case "category.name" -> TriStates.of(getLoader().map(v -> factory.literal(v.getDisplayName())), factory); - case "category.productsCount" -> TriStates - .of(getLoader().map(v -> factory.literal(Integer.toString(v.getProducts().size()))), factory); + case "category.id" -> LazyTexts.loading(getLoader().map(v -> factory.literal(v.getId())), factory); + case "category.name" -> LazyTexts.loading(getLoader().map(v -> factory.literal(v.getDisplayName())), factory); + case "category.productsCount" -> LazyTexts + .loading(getLoader().map(v -> factory.literal(Integer.toString(v.getProducts().size()))), factory); default -> null; }; } diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/ProductNativeButton.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/ProductNativeButton.java index 559070b..b3608ab 100644 --- a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/ProductNativeButton.java +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/button/ProductNativeButton.java @@ -22,16 +22,20 @@ package io.github.nahkd123.stonks.minecraft.gui.provided.button; import io.github.nahkd123.stonks.market.catalogue.Product; +import io.github.nahkd123.stonks.market.summary.ProductSummary; import io.github.nahkd123.stonks.minecraft.Player; import io.github.nahkd123.stonks.minecraft.gui.ClickType; import io.github.nahkd123.stonks.minecraft.gui.ContainerGui; import io.github.nahkd123.stonks.minecraft.gui.LazyLoadedNativeButton; import io.github.nahkd123.stonks.minecraft.gui.provided.gui.AbstractContainerGui; import io.github.nahkd123.stonks.minecraft.gui.provided.gui.MainContainerGui; +import io.github.nahkd123.stonks.minecraft.gui.provided.gui.ProductContainerGui; +import io.github.nahkd123.stonks.minecraft.text.LegacyColor; import io.github.nahkd123.stonks.minecraft.text.TextComponent; import io.github.nahkd123.stonks.minecraft.text.TextFactory; -import io.github.nahkd123.stonks.minecraft.utils.TriStates; +import io.github.nahkd123.stonks.minecraft.utils.LazyTexts; import io.github.nahkd123.stonks.utils.lazy.LazyLoader; +import io.github.nahkd123.stonks.utils.lazy.LoadState; public class ProductNativeButton implements LazyLoadedNativeButton { public static final String ID = "product"; @@ -45,24 +49,41 @@ public ProductNativeButton(AbstractContainerGui gui, int ordinal) { @Override public TextComponent replacePlaceholder(TextFactory factory, String name) { + TextComponent notAvailable = factory.literal("Not Available!").withColor(factory.legacyColor(LegacyColor.RED)); + LazyLoader summary = getLoader().flatMap(p -> gui.getServer().getMarketCache().getSummary(p)); + LazyLoader instantBuyPrice = summary.map(s -> s.getSellSummary().size() == 0 + ? notAvailable + : gui.getServer().getEconomyService().getCurrency().getDisplay(s.getInstantBuyPrice())); + LazyLoader instantSellPrice = summary.map(s -> s.getBuySummary().size() == 0 + ? notAvailable + : gui.getServer().getEconomyService().getCurrency().getDisplay(s.getInstantSellPrice())); + return switch (name) { - case "product.id" -> TriStates.of(getLoader().map(p -> factory.literal(p.getId())), factory); - case "product.name" -> TriStates.of(getLoader().map(p -> factory.literal(p.getDisplayName())), factory); + case "product.id" -> LazyTexts.loading(getLoader().map(p -> factory.literal(p.getId())), factory); + case "product.name" -> LazyTexts.loading(getLoader().map(p -> factory.literal(p.getDisplayName())), factory); + case "product.instant.buy" -> LazyTexts.loading(instantBuyPrice, factory); + case "product.instant.sell" -> LazyTexts.loading(instantSellPrice, factory); default -> null; }; } @Override public LazyLoader getLoader() { - if (gui instanceof MainContainerGui main) { - return main.getCategoryCache() - .map(c -> ordinal < c.getProducts().size() ? c.getProducts().get(ordinal) : null); - } + if (gui instanceof MainContainerGui main) return main + .getCategoryCache() + .map(c -> ordinal < c.getProducts().size() ? c.getProducts().get(ordinal) : null); + if (gui instanceof ProductContainerGui product) return LazyLoader.ofFinished(product.getProduct()); - // TODO add loader for product menu return LazyLoader.ofFailed(new RuntimeException("Unable to obtain product based on current GUI")); } @Override - public void onClick(ContainerGui gui, Player player, ClickType type) {} + public void onClick(ContainerGui gui, Player player, ClickType type) { + if (gui instanceof MainContainerGui main) { + if (getLoader().load() == LoadState.SUCCESS) { + player.openGui(new ProductContainerGui(gui, player.getServer(), getLoader().get())); + return; + } + } + } } diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/AbstractContainerGui.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/AbstractContainerGui.java index f129a3f..fef0ff9 100644 --- a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/AbstractContainerGui.java +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/AbstractContainerGui.java @@ -27,6 +27,7 @@ import io.github.nahkd123.stonks.minecraft.MinecraftServer; import io.github.nahkd123.stonks.minecraft.gui.ContainerGui; import io.github.nahkd123.stonks.minecraft.gui.NativeButton; +import io.github.nahkd123.stonks.minecraft.gui.provided.button.BackNativeButton; import io.github.nahkd123.stonks.minecraft.gui.provided.button.CategoryNativeButton; import io.github.nahkd123.stonks.minecraft.gui.provided.button.CloseNativeButton; import io.github.nahkd123.stonks.minecraft.gui.provided.button.ProductNativeButton; @@ -41,18 +42,23 @@ public static interface NativeButtonFactory { static { GLOBAL_REGISTRY.put(CloseNativeButton.ID, CloseNativeButton::new); + GLOBAL_REGISTRY.put(BackNativeButton.ID, BackNativeButton::new); GLOBAL_REGISTRY.put(CategoryNativeButton.ID, CategoryNativeButton::new); GLOBAL_REGISTRY.put(ProductNativeButton.ID, ProductNativeButton::new); } + private ContainerGui previous; private MinecraftServer server; private String id; - public AbstractContainerGui(MinecraftServer server, String id) { + public AbstractContainerGui(ContainerGui previous, MinecraftServer server, String id) { + this.previous = previous; this.server = server; this.id = id; } + public ContainerGui getPrevious() { return previous; } + @Override public String getId() { return id; } diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/MainContainerGui.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/MainContainerGui.java index 617dbf2..05d7e31 100644 --- a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/MainContainerGui.java +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/MainContainerGui.java @@ -23,6 +23,7 @@ import io.github.nahkd123.stonks.market.catalogue.Category; import io.github.nahkd123.stonks.minecraft.MinecraftServer; +import io.github.nahkd123.stonks.minecraft.gui.ContainerGui; import io.github.nahkd123.stonks.minecraft.text.TextComponent; import io.github.nahkd123.stonks.minecraft.text.TextFactory; import io.github.nahkd123.stonks.utils.lazy.LazyLoader; @@ -30,8 +31,8 @@ public class MainContainerGui extends AbstractContainerGui { private int categoryIndex; - public MainContainerGui(MinecraftServer server, int categoryIndex) { - super(server, "main"); + public MainContainerGui(ContainerGui previous, MinecraftServer server, int categoryIndex) { + super(previous, server, "main"); this.categoryIndex = categoryIndex; } diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/ProductContainerGui.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/ProductContainerGui.java new file mode 100644 index 0000000..f5b6725 --- /dev/null +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/gui/provided/gui/ProductContainerGui.java @@ -0,0 +1,27 @@ +package io.github.nahkd123.stonks.minecraft.gui.provided.gui; + +import io.github.nahkd123.stonks.market.catalogue.Product; +import io.github.nahkd123.stonks.minecraft.MinecraftServer; +import io.github.nahkd123.stonks.minecraft.gui.ContainerGui; +import io.github.nahkd123.stonks.minecraft.text.TextComponent; +import io.github.nahkd123.stonks.minecraft.text.TextFactory; + +public class ProductContainerGui extends AbstractContainerGui { + private Product product; + + public ProductContainerGui(ContainerGui previous, MinecraftServer server, Product product) { + super(previous, server, "product"); + this.product = product; + } + + public Product getProduct() { return product; } + + @Override + public TextComponent replacePlaceholder(TextFactory factory, String name) { + return switch (name) { + case "product.id" -> factory.literal(product.getId()); + case "product.name" -> factory.literal(product.getDisplayName()); + default -> null; + }; + } +} diff --git a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/utils/TriStates.java b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/utils/LazyTexts.java similarity index 73% rename from minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/utils/TriStates.java rename to minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/utils/LazyTexts.java index 43490c3..bc6db87 100644 --- a/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/utils/TriStates.java +++ b/minecraft/src/main/java/io/github/nahkd123/stonks/minecraft/utils/LazyTexts.java @@ -25,9 +25,22 @@ import io.github.nahkd123.stonks.minecraft.text.TextComponent; import io.github.nahkd123.stonks.minecraft.text.TextFactory; import io.github.nahkd123.stonks.utils.lazy.LazyLoader; +import io.github.nahkd123.stonks.utils.lazy.LoadState; -public class TriStates { - public static TextComponent of(LazyLoader loader, TextFactory factory) { +public class LazyTexts { + /** + *

+ * Get the text component depending on the current loading state. If the loading + * state is {@link LoadState#SUCCESS}, it will returns the value. Returns + * "Loading..." if the state is {@link LoadState#LOADING} and "Error!" if the + * state is {@link LoadState#FAILED}. + *

+ * + * @param loader The loader. + * @param factory The text components factory. + * @return The text component. + */ + public static TextComponent loading(LazyLoader loader, TextFactory factory) { return loader.getTriState( () -> factory.literal("Loading...").withColor(factory.legacyColor(LegacyColor.GRAY)), t -> factory.literal("Error!").withColor(factory.legacyColor(LegacyColor.RED)));