diff --git a/.github/workflows/optimize-images.yml b/.github/workflows/optimize-images.yml new file mode 100644 index 0000000..0ebed01 --- /dev/null +++ b/.github/workflows/optimize-images.yml @@ -0,0 +1,17 @@ +name: Optimize images + +on: + pull_request: + branches: [ master, main, release/** ] + paths: + - "**/*.png" + + push: + branches: [ master, main, release/** ] + paths: + - "**/*.png" + +jobs: + optimize-images: + uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/optimize-images.yml@master + secrets: inherit \ No newline at end of file diff --git a/README.md b/README.md index c193ac2..32a041b 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,20 @@ This mod adds a vending machine, unlocking trades based on questbook data. If yo ### Interface ![img_1.png](img_1.png) +### Command + +**Note: All commands require OP to run**. + +If your command still refuses to run, ensure your OP permission level in server.properties is set to 4. + +|Task| Command | +|--|---------------------------------------------------| +|**Set coin**| `/vending set [player] ` | +|**Add coin**| `/vending add [player] ` | +|**Reset coins**| `/vending reset [player] ` | +|**Reload database** | `/vending reload database` | +|**Reload trade state**| `/vending reload tradestate [player]` | + ## Current Status Alpha Testing @@ -18,7 +32,7 @@ Alpha Testing This can be used as a standalone mod with several dependencies. The vending machine block and ME Vending Uplink do not come with default recipes. ### Required Dependencies: -- GT5U +- GT5Unofficial-GTNH (Not compatible with main GT5U branch!) - ModularUI 2 - NotEnoughItems - Applied Energistics 2 diff --git a/dependencies.gradle b/dependencies.gradle index d4a2fbb..8846c55 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -34,13 +34,13 @@ * For more details, see https://docs.gradle.org/8.0.1/userguide/java_library_plugin.html#sec:java_library_configurations_graph */ dependencies { - implementation("com.github.GTNewHorizons:NotEnoughItems:2.7.77-GTNH:dev") - implementation("com.github.GTNewHorizons:GTNHLib:0.6.39:dev") - compileOnly("com.github.GTNewHorizons:BetterQuesting:3.7.11-GTNH:dev") - implementation("com.github.GTNewHorizons:ModularUI2:2.2.18-1.7.10:dev") - // implementation("com.github.GTNewHorizons:ModularUI2:99.99:dev") - implementation("com.github.GTNewHorizons:StructureLib:1.4.18:dev") - implementation("com.github.GTNewHorizons:GT5-Unofficial:5.09.51.440:dev") + implementation("com.github.GTNewHorizons:NotEnoughItems:2.8.60-GTNH:dev") + implementation("com.github.GTNewHorizons:GTNHLib:0.9.10:dev") + compileOnly("com.github.GTNewHorizons:BetterQuesting:3.8.26-GTNH:dev") + + implementation("com.github.GTNewHorizons:ModularUI2:2.3.39-1.7.10:dev") + implementation("com.github.GTNewHorizons:StructureLib:1.4.28:dev") + implementation("com.github.GTNewHorizons:GT5-Unofficial:5.09.52.375:dev") } // deps may transitively add Baubles, so we replace it @@ -49,7 +49,7 @@ project.getConfigurations() final DependencySubstitutions ds = c.getResolutionStrategy() .getDependencySubstitution() ds.substitute(ds.module("com.github.GTNewHorizons:Baubles")) - .using(ds.module("com.github.GTNewHorizons:Baubles-Expanded:2.1.9-GTNH")) + .using(ds.module("com.github.GTNewHorizons:Baubles-Expanded:2.2.6-GTNH")) .withClassifier("dev") .because("Baubles-Expanded replaces Baubles") }) diff --git a/gradle.properties b/gradle.properties index 7d6b721..5eee7b3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -39,9 +39,33 @@ remoteMappings = https\://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/co # `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE. developmentEnvironmentUserName = Developer -# Enables using modern Java syntax (up to version 17) via Jabel, while still targeting JVM 8. -# See https://github.com/bsideup/jabel for details on how this works. -enableModernJavaSyntax = true +# Enables modern Java syntax support. Valid values: +# - false: No modern syntax, Java 8 only +# - jabel: Jabel syntax-only support, compiles to J8 bytecode +# - jvmDowngrader: Full modern Java via JVM Downgrader (syntax + stdlib APIs) +# - modern: Native modern Java bytecode, no downgrading +enableModernJavaSyntax = jabel + +# If set, ignores the above setting and compiles with the given toolchain. This may cause unexpected issues, +# and should *not* be used in most situations. -1 disables this. +# forceToolchainVersion = -1 + +# Target JVM version for JVM Downgrader bytecode downgrading. +# Only used when enableModernJavaSyntax = jvmDowngrader +# downgradeTargetVersion = 8 + +# Comma-separated list of Java versions for multi-release jar support (JVM Downgrader only). +# Classes will be available in META-INF/versions/N/ for each version N in this list. +# Default: "21,25" (J25+ gets native classes, J21-24 gets partial downgrade, J8-20 gets full downgrade). +# jvmDowngraderMultiReleaseVersions = 21,25 + +# Specifies how JVM Downgrader API stubs are provided. Options: +# - shade: Shade minimized stubs into the jar +# - gtnhlib: GTNHLib provides stubs at runtime (adds version constraint) +# - external: Another dependency provides stubs (no constraint, no warning) +# - (empty): Warning reminding you to configure stubs +# Note: 'shade' option requires you to verify license compliance, see: https://github.com/unimined/JvmDowngrader/blob/main/LICENSE.md +# jvmDowngraderStubsProvider = # Enables injecting missing generics into the decompiled source code for a better coding experience. # Turns most publicly visible List, Map, etc. into proper List, Map types. @@ -87,7 +111,9 @@ usesMixinDebug = false # Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise. mixinPlugin = -# Specify the package that contains all of your Mixins. You may only place Mixins in this package or the build will fail! +# Specify the package that contains all of your Mixins. The package must exist or +# the build will fail. If you have a package property defined in your mixins..json, +# it must match with this or the build will fail. mixinsPackage = # Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin! @@ -163,6 +189,12 @@ curseForgeRelations = # projects. New projects should not use this parameter. # customArchiveBaseName = +# Optional parameter to customize the default working directory used by the runClient* tasks. Relative to the project directory. +# runClientWorkingDirectory = run/client + +# Optional parameter to customize the default working directory used by the runServer* tasks. Relative to the project directory. +# runServerWorkingDirectory = run/server + # Optional parameter to have the build automatically fail if an illegal version is used. # This can be useful if you e.g. only want to allow versions in the form of '1.1.xxx'. # The check is ONLY performed if the version is a git tag. @@ -192,3 +224,6 @@ curseForgeRelations = # This is meant to be set in $HOME/.gradle/gradle.properties. # ideaCheckSpotlessOnBuild = true +# Non-GTNH properties +org.gradle.configuration-cache = true +org.gradle.parallel = true diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties new file mode 100644 index 0000000..b30b550 --- /dev/null +++ b/gradle/gradle-daemon-jvm.properties @@ -0,0 +1,12 @@ +#This file is generated by updateDaemonJvm +toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/df211d3c3eefdc408b462041881bc575/redirect +toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/b41931cf1e70bc8e08d7dd19c343ef00/redirect +toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/df211d3c3eefdc408b462041881bc575/redirect +toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/b41931cf1e70bc8e08d7dd19c343ef00/redirect +toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/46949723aaa20c7b64d7ecfed7207034/redirect +toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/d6690dfd71c4c91e08577437b5b2beb0/redirect +toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/df211d3c3eefdc408b462041881bc575/redirect +toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/b41931cf1e70bc8e08d7dd19c343ef00/redirect +toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/3cd7045fca9a72cd9bc7d14a385e594c/redirect +toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/552c7bffe0370c66410a51c55985b511/redirect +toolchainVersion=25 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 9bbc975..f8e1ee3 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f853b..23449a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index faf9300..adff685 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9d21a21..c4bdd3a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/settings.gradle.kts b/settings.gradle.kts index cbf0d00..e5a5ba9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,5 +17,5 @@ pluginManagement { } plugins { - id("com.gtnewhorizons.gtnhsettingsconvention") version("1.0.41") + id("com.gtnewhorizons.gtnhsettingsconvention") version("2.0.19") } diff --git a/src/main/java/com/cubefury/vendingmachine/ClientProxy.java b/src/main/java/com/cubefury/vendingmachine/ClientProxy.java index a64f4a3..8434acc 100644 --- a/src/main/java/com/cubefury/vendingmachine/ClientProxy.java +++ b/src/main/java/com/cubefury/vendingmachine/ClientProxy.java @@ -2,8 +2,11 @@ import net.minecraftforge.common.MinecraftForge; +import com.cubefury.vendingmachine.handlers.ClientEventHandler; +import com.cubefury.vendingmachine.handlers.SaveLoadHandler; import com.cubefury.vendingmachine.integration.nei.NEIConfig; +import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.event.FMLInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; @@ -20,6 +23,7 @@ public void preInit(FMLPreInitializationEvent event) { public void init(FMLInitializationEvent event) { MinecraftForge.EVENT_BUS.register(new NEIConfig()); super.init(event); + SaveLoadHandler.INSTANCE.clientInit(); } public boolean isClient() { @@ -29,6 +33,10 @@ public boolean isClient() { @Override public void registerHandlers() { super.registerHandlers(); + MinecraftForge.EVENT_BUS.register(ClientEventHandler.INSTANCE); + FMLCommonHandler.instance() + .bus() + .register(ClientEventHandler.INSTANCE); } } diff --git a/src/main/java/com/cubefury/vendingmachine/CommonProxy.java b/src/main/java/com/cubefury/vendingmachine/CommonProxy.java index fe27b2d..ebd74a5 100644 --- a/src/main/java/com/cubefury/vendingmachine/CommonProxy.java +++ b/src/main/java/com/cubefury/vendingmachine/CommonProxy.java @@ -5,6 +5,7 @@ import net.minecraft.server.MinecraftServer; import net.minecraftforge.common.MinecraftForge; +import com.cubefury.vendingmachine.command.CommandVending; import com.cubefury.vendingmachine.handlers.EventHandler; import com.cubefury.vendingmachine.handlers.SaveLoadHandler; @@ -20,7 +21,6 @@ public class CommonProxy { // GameRegistry." (Remove if not needed) public void preInit(FMLPreInitializationEvent event) { VendingMachine.LOG.info("Loading Vending Machine " + Tags.VERSION); - Config.init(event.getSuggestedConfigurationFile()); } // load "Do your mod setup. Build whatever data structures you care about. Register recipes." (Remove if not needed) @@ -35,6 +35,8 @@ public void serverStarting(FMLServerStartingEvent event) { ICommandManager command = server.getCommandManager(); ServerCommandManager manager = (ServerCommandManager) command; + manager.registerCommand(new CommandVending()); + SaveLoadHandler.INSTANCE.init(server); } diff --git a/src/main/java/com/cubefury/vendingmachine/Config.java b/src/main/java/com/cubefury/vendingmachine/Config.java deleted file mode 100644 index 8972650..0000000 --- a/src/main/java/com/cubefury/vendingmachine/Config.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.cubefury.vendingmachine; - -import java.io.File; - -import net.minecraftforge.common.config.Configuration; - -public class Config { - - private static final String CONFIG_CATEGORY_VM = "Vending Machine Settings"; - - public static String data_dir = "vendingmachine"; - public static String config_dir = "config/vendingmachine"; - public static int gui_refresh_interval = 20; - public static int dispense_frequency = 10; - public static int dispense_amount = 16; - - public static File worldDir = null; - - public static void init(File configFile) { - Configuration configuration = new Configuration(configFile); - - data_dir = configuration - .getString("data_dir", Configuration.CATEGORY_GENERAL, data_dir, "World vendingmachine data directory"); - config_dir = configuration - .getString("config_dir", Configuration.CATEGORY_GENERAL, config_dir, "Configuration directory"); - - configuration.addCustomCategoryComment(CONFIG_CATEGORY_VM, "Vending Machine Settings"); - gui_refresh_interval = configuration - .getInt("gui_refresh_interval", CONFIG_CATEGORY_VM, gui_refresh_interval, 20, 3600, "In number of ticks"); - dispense_frequency = configuration - .getInt("dispense_frequency", CONFIG_CATEGORY_VM, dispense_frequency, 1, 9000, "In number of ticks"); - dispense_amount = configuration.getInt( - "dispense_amount", - CONFIG_CATEGORY_VM, - dispense_amount, - 1, - Integer.MAX_VALUE, - "Number of items per dispense cycle"); - - if (configuration.hasChanged()) { - configuration.save(); - } - } -} diff --git a/src/main/java/com/cubefury/vendingmachine/VMConfig.java b/src/main/java/com/cubefury/vendingmachine/VMConfig.java new file mode 100644 index 0000000..134ab8c --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/VMConfig.java @@ -0,0 +1,80 @@ +package com.cubefury.vendingmachine; + +import java.io.File; + +import com.cubefury.vendingmachine.blocks.gui.DisplayType; +import com.cubefury.vendingmachine.blocks.gui.SortMode; +import com.gtnewhorizon.gtnhlib.config.Config; + +@Config(modid = VendingMachine.MODID, category = "vendingmachine", filename = "vendingmachine") +public class VMConfig { + + @Config.Comment("Vending Machine Settings") + public static final VendingMachineSettings vendingMachineSettings = new VendingMachineSettings(); + + @Config.Comment("GUI Settings") + public static final GUI gui = new GUI(); + + @Config.Comment("Developer Settings") + public static final Developer developer = new Developer(); + + public static class VendingMachineSettings { + + @Config.Comment("How often the vending machine refreshes its data, in number of ticks") + @Config.DefaultInt(20) + @Config.RequiresWorldRestart + public int gui_refresh_interval; + + @Config.Comment("Enable restock notifications, disabling on server will disable notifications for everyone") + @Config.DefaultBoolean(true) + @Config.RequiresWorldRestart + public boolean restock_notifications_enabled; + + @Config.Comment("How often the server checks for restocked trades, in number of ticks.") + @Config.DefaultInt(200) + @Config.RequiresWorldRestart + public int restock_notifications_interval; + + @Config.Comment("How often items items are ejected, in number of ticks") + @Config.DefaultInt(10) + @Config.RequiresWorldRestart + public int dispense_frequency; + + @Config.Comment("How many items are dispensed from the queue at once") + @Config.DefaultInt(16) + @Config.RequiresWorldRestart + public int dispense_amount; + } + + public static class GUI { + + @Config.Comment("Default trade display format, either TILE or LIST. Case sensitive.") + @Config.DefaultEnum("TILE") + public DisplayType display_type = DisplayType.TILE; + + @Config.Comment("Default sort mode, either SMART or ALPHABET. Case sensitive.") + @Config.DefaultEnum("SMART") + public SortMode sort_mode = SortMode.SMART; + } + + public static class Developer { + + @Config.Comment("subdirectory for vending machine data in world save") + @Config.DefaultString("vendingmachine") + @Config.RequiresMcRestart + public String data_dir; + + @Config.Comment("folder where trade database file is located") + @Config.DefaultString("config/vendingmachine") + @Config.RequiresMcRestart + public String trade_db_dir = "config/vendingmachine"; + + @Config.Comment("Force rewrite database on load, for add/remove trades or change of format") + @Config.DefaultBoolean(false) + @Config.RequiresWorldRestart + public boolean force_rewrite_database = false; + } + + @Config.Ignore + public static File world_dir = null; +} diff --git a/src/main/java/com/cubefury/vendingmachine/VendingMachine.java b/src/main/java/com/cubefury/vendingmachine/VendingMachine.java index 7ef5787..cf59fee 100644 --- a/src/main/java/com/cubefury/vendingmachine/VendingMachine.java +++ b/src/main/java/com/cubefury/vendingmachine/VendingMachine.java @@ -1,13 +1,19 @@ package com.cubefury.vendingmachine; +import net.minecraft.util.EnumChatFormatting; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.cubefury.vendingmachine.gui.WidgetThemes; +import com.cubefury.vendingmachine.handlers.SaveLoadHandler; import com.cubefury.vendingmachine.items.VMItems; import com.cubefury.vendingmachine.network.PacketTypeRegistry; import com.cubefury.vendingmachine.network.SerializedPacket; +import com.cubefury.vendingmachine.storage.NameCache; import com.cubefury.vendingmachine.util.ItemPlaceholder; +import com.gtnewhorizon.gtnhlib.config.ConfigException; +import com.gtnewhorizon.gtnhlib.config.ConfigurationManager; import cpw.mods.fml.common.Loader; import cpw.mods.fml.common.Mod; @@ -19,6 +25,7 @@ import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.event.FMLServerStartingEvent; import cpw.mods.fml.common.event.FMLServerStoppedEvent; +import cpw.mods.fml.common.event.FMLServerStoppingEvent; import cpw.mods.fml.common.network.NetworkRegistry; import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper; import cpw.mods.fml.common.registry.GameRegistry; @@ -29,13 +36,28 @@ modid = VendingMachine.MODID, version = Tags.VERSION, name = VendingMachine.NAME, + guiFactory = "com.cubefury.vendingmachine.gui.client.VMGuiFactory", acceptedMinecraftVersions = "[1.7.10]") public class VendingMachine { + static { + try { + ConfigurationManager.registerConfig(VMConfig.class); + } catch (ConfigException e) { + throw new RuntimeException(e); + } + } + public static final String MODID = "vendingmachine"; public static final Logger LOG = LogManager.getLogger(MODID); public static final String CHANNEL = "VM_NET_CHAN"; public static final String NAME = "Vending Machine"; + public static final String AUTHOR_CUBEFURY = "Author: " + EnumChatFormatting.AQUA + + EnumChatFormatting.BOLD + + "Cube" + + EnumChatFormatting.BLUE + + EnumChatFormatting.BOLD + + "Fury"; @Mod.Instance(MODID) public static VendingMachine instance; @@ -62,13 +84,13 @@ public void preInit(FMLPreInitializationEvent event) { proxy.registerHandlers(); PacketTypeRegistry.INSTANCE.init(); + // MUI2 + WidgetThemes.init(); + // Register network handlers network.registerMessage(SerializedPacket.HandleClient.class, SerializedPacket.class, 0, Side.CLIENT); network.registerMessage(SerializedPacket.HandleServer.class, SerializedPacket.class, 0, Side.SERVER); - // ModularUI - WidgetThemes.register(); - } @Mod.EventHandler @@ -105,6 +127,11 @@ public void serverStarting(FMLServerStartingEvent event) { proxy.serverStarting(event); } + @Mod.EventHandler + public void serverStopping(FMLServerStoppingEvent event) { + SaveLoadHandler.INSTANCE.writeTradeState(NameCache.INSTANCE.getAllUUIDS()); + } + @Mod.EventHandler public void serverStop(FMLServerStoppedEvent event) {} diff --git a/src/main/java/com/cubefury/vendingmachine/api/enums/Textures.java b/src/main/java/com/cubefury/vendingmachine/api/enums/Textures.java index 13af61d..b79798f 100644 --- a/src/main/java/com/cubefury/vendingmachine/api/enums/Textures.java +++ b/src/main/java/com/cubefury/vendingmachine/api/enums/Textures.java @@ -1,24 +1,28 @@ package com.cubefury.vendingmachine.api.enums; -import gregtech.api.enums.Textures.BlockIcons.CustomIcon; +import gregtech.api.enums.Textures.BlockIcons; import gregtech.api.interfaces.IIconContainer; public class Textures { - public static final CustomIcon VM_MACHINE_FRONT_OFF = new CustomIcon("vendingmachine:vending_machine_front_off"), - VM_MACHINE_FRONT_ON = new CustomIcon("vendingmachine:vending_machine_front_on"), - VM_MACHINE_FRONT_ON_GLOW = new CustomIcon("vendingmachine:vending_machine_front_on_glow"), + public static final IIconContainer VM_MACHINE_FRONT_OFF = BlockIcons + .custom("vendingmachine:vending_machine_front_off"), + VM_MACHINE_FRONT_ON = BlockIcons.custom("vendingmachine:vending_machine_front_on"), + VM_MACHINE_FRONT_ON_GLOW = BlockIcons.custom("vendingmachine:vending_machine_front_on_glow"), - VM_OVERLAY_0 = new CustomIcon("vendingmachine:vending_machine_overlay_0"), - VM_OVERLAY_1 = new CustomIcon("vendingmachine:vending_machine_overlay_1"), - VM_OVERLAY_2 = new CustomIcon("vendingmachine:vending_machine_overlay_2"), - VM_OVERLAY_3 = new CustomIcon("vendingmachine:vending_machine_overlay_3"), - VM_OVERLAY_4 = new CustomIcon("vendingmachine:vending_machine_overlay_4"), + VM_OVERLAY_0 = BlockIcons.custom("vendingmachine:vending_machine_overlay_0"), + VM_OVERLAY_1 = BlockIcons.custom("vendingmachine:vending_machine_overlay_1"), + VM_OVERLAY_2 = BlockIcons.custom("vendingmachine:vending_machine_overlay_2"), + VM_OVERLAY_3 = BlockIcons.custom("vendingmachine:vending_machine_overlay_3"), + VM_OVERLAY_4 = BlockIcons.custom("vendingmachine:vending_machine_overlay_4"), - VM_OVERLAY_ACTIVE_0 = new CustomIcon("vendingmachine:vending_machine_overlay_active_0"), - VM_OVERLAY_ACTIVE_1 = new CustomIcon("vendingmachine:vending_machine_overlay_active_1"), - VM_OVERLAY_ACTIVE_2 = new CustomIcon("vendingmachine:vending_machine_overlay_active_2"), - VM_OVERLAY_ACTIVE_3 = new CustomIcon("vendingmachine:vending_machine_overlay_active_3"); + VM_OVERLAY_ACTIVE_0 = BlockIcons.custom("vendingmachine:vending_machine_overlay_active_0"), + VM_OVERLAY_ACTIVE_1 = BlockIcons.custom("vendingmachine:vending_machine_overlay_active_1"), + VM_OVERLAY_ACTIVE_2 = BlockIcons.custom("vendingmachine:vending_machine_overlay_active_2"), + VM_OVERLAY_ACTIVE_3 = BlockIcons.custom("vendingmachine:vending_machine_overlay_active_3"), + + VUPLINK_OVERLAY_INACTIVE = BlockIcons.custom("vendingmachine:vending_uplink_machine_overlay_inactive"), + VUPLINK_OVERLAY_ACTIVE = BlockIcons.custom("vendingmachine:vending_uplink_machine_overlay_active"); public static final IIconContainer[] VM_OVERLAY_ACTIVE = { VM_OVERLAY_ACTIVE_0, VM_OVERLAY_ACTIVE_1, VM_OVERLAY_ACTIVE_2, VM_OVERLAY_ACTIVE_3, VM_OVERLAY_4 // bottom right not animated diff --git a/src/main/java/com/cubefury/vendingmachine/api/storage/INameCache.java b/src/main/java/com/cubefury/vendingmachine/api/storage/INameCache.java index 5aa211c..86be29e 100644 --- a/src/main/java/com/cubefury/vendingmachine/api/storage/INameCache.java +++ b/src/main/java/com/cubefury/vendingmachine/api/storage/INameCache.java @@ -20,6 +20,8 @@ public interface INameCache { List getAllNames(); + List getAllUUIDS(); + /** * Used primarily to know if a user is an OP client side
*/ diff --git a/src/main/java/com/cubefury/vendingmachine/blocks/MTEVendingMachine.java b/src/main/java/com/cubefury/vendingmachine/blocks/MTEVendingMachine.java index 54df808..3c9979b 100644 --- a/src/main/java/com/cubefury/vendingmachine/blocks/MTEVendingMachine.java +++ b/src/main/java/com/cubefury/vendingmachine/blocks/MTEVendingMachine.java @@ -1,10 +1,13 @@ package com.cubefury.vendingmachine.blocks; +import static com.cubefury.vendingmachine.VendingMachine.AUTHOR_CUBEFURY; import static com.cubefury.vendingmachine.api.enums.Textures.VM_MACHINE_FRONT_OFF; import static com.cubefury.vendingmachine.api.enums.Textures.VM_MACHINE_FRONT_ON; import static com.cubefury.vendingmachine.api.enums.Textures.VM_MACHINE_FRONT_ON_GLOW; import static com.cubefury.vendingmachine.api.enums.Textures.VM_OVERLAY; import static com.cubefury.vendingmachine.api.enums.Textures.VM_OVERLAY_ACTIVE; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlock; +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofChain; import static gregtech.api.util.GTStructureUtility.ofHatchAdderOptional; import java.util.ArrayList; @@ -15,6 +18,7 @@ import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.LinkedBlockingQueue; +import java.util.stream.IntStream; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; @@ -26,26 +30,28 @@ import net.minecraft.world.World; import net.minecraftforge.common.util.Constants; import net.minecraftforge.common.util.ForgeDirection; +import net.minecraftforge.oredict.OreDictionary; import org.jetbrains.annotations.NotNull; import org.lwjgl.input.Keyboard; import com.cleanroommc.modularui.utils.item.ItemStackHandler; -import com.cubefury.vendingmachine.Config; +import com.cubefury.vendingmachine.VMConfig; import com.cubefury.vendingmachine.VendingMachine; import com.cubefury.vendingmachine.blocks.gui.MTEVendingMachineGui; import com.cubefury.vendingmachine.blocks.gui.TradeItemDisplay; -import com.cubefury.vendingmachine.network.handlers.NetCurrencySync; import com.cubefury.vendingmachine.network.handlers.NetTradeDisplaySync; import com.cubefury.vendingmachine.network.handlers.NetTradeRequestSync; import com.cubefury.vendingmachine.storage.NameCache; import com.cubefury.vendingmachine.trade.CurrencyItem; +import com.cubefury.vendingmachine.trade.CurrencyType; import com.cubefury.vendingmachine.trade.Trade; import com.cubefury.vendingmachine.trade.TradeDatabase; import com.cubefury.vendingmachine.trade.TradeManager; import com.cubefury.vendingmachine.trade.TradeRequest; import com.cubefury.vendingmachine.util.BigItemStack; import com.cubefury.vendingmachine.util.OverlayHelper; +import com.cubefury.vendingmachine.util.Translator; import com.gtnewhorizon.structurelib.StructureLibAPI; import com.gtnewhorizon.structurelib.alignment.IAlignment; import com.gtnewhorizon.structurelib.alignment.IAlignmentLimits; @@ -79,18 +85,20 @@ public class MTEVendingMachine extends MTEMultiBlockBase .addShape("main", new String[][] { { "cc", "c~", "cc" } }) .addElement( 'c', - ofHatchAdderOptional( - MTEVendingMachine::addUplinkHatch, - ((BlockCasings11) GregTechAPI.sBlockCasings11).getTextureIndex(0), - 1, - GregTechAPI.sBlockCasings11, - 0)) + ofChain( + ofHatchAdderOptional( + MTEVendingMachine::addUplinkHatch, + ((BlockCasings11) GregTechAPI.sBlockCasings11).getTextureIndex(0), + 1, + GregTechAPI.sBlockCasings11, + 0), + ofBlock(GregTechAPI.sBlockCasings11, 0))) .build(); private final ArrayList uplinkHatches = new ArrayList<>(); - public static final int INPUT_SLOTS = 6; - public static final int OUTPUT_SLOTS = 6; + public static final int INPUT_SLOTS = 8; + public static final int OUTPUT_SLOTS = 8; public static final int MAX_TRADES = 300; @@ -108,8 +116,8 @@ public class MTEVendingMachine extends MTEMultiBlockBase private MultiblockTooltipBuilder tooltipBuilder; public int mUpdate = 0; - public boolean mMachine = false; - private boolean mIsAnimated; + + private final boolean mIsAnimated; public ItemStackHandler inputItems = new ItemStackHandler(INPUT_SLOTS); public ItemStackHandler outputItems = new ItemStackHandler(OUTPUT_SLOTS); @@ -146,7 +154,7 @@ public boolean usingAnimations() { public void sendTradeRequest(TradeItemDisplay trade) { IGregTechTileEntity baseTile = getBaseMetaTileEntity(); - if (baseTile == null) { + if (baseTile == null || !baseTile.isActive()) { return; } NetTradeRequestSync.sendTradeRequest( @@ -162,6 +170,9 @@ public void addTradeRequest(TradeRequest trade) { } public void dispenseItems() { + if (!this.getActive()) { + return; + } if (!this.pendingTrades.isEmpty()) { TradeRequest tradeRequest = this.pendingTrades.poll(); if (!processTradeOnServer(tradeRequest)) { @@ -171,10 +182,10 @@ public void dispenseItems() { NetTradeRequestSync.sendAck(tradeRequest.player); } if ( - this.newBufferedOutputs - || (!this.outputBuffer.isEmpty() && this.ticksSinceOutput % Config.dispense_frequency == 0) + this.newBufferedOutputs || (!this.outputBuffer.isEmpty() + && this.ticksSinceOutput % VMConfig.vendingMachineSettings.dispense_frequency == 0) ) { - int remainingDispensables = Config.dispense_amount; + int remainingDispensables = VMConfig.vendingMachineSettings.dispense_amount; while (!this.outputBuffer.isEmpty() && remainingDispensables > 0) { ItemStack next = this.outputBuffer.peek(); @@ -228,13 +239,14 @@ public void dispenseItems() { } ticksSinceOutput = this.newBufferedOutputs ? 0 : ticksSinceOutput + 1; this.newBufferedOutputs = false; + this.markDirty(); } private boolean processTradeOnServer(TradeRequest tradeRequest) { if ( - tradeRequest == null || !TradeDatabase.INSTANCE.getTradeGroups() - .get(tradeRequest.tradeGroup) - .canExecuteTrade(tradeRequest.playerID) + tradeRequest == null || !TradeManager.INSTANCE.canExecuteTrade( + tradeRequest.playerID, + TradeDatabase.INSTANCE.getTradeGroupFromId(tradeRequest.tradeGroup)) ) { return false; } @@ -247,6 +259,7 @@ private boolean processTradeOnServer(TradeRequest tradeRequest) { if ( !this.inputCurrencySatisfied(trade.fromCurrency, tradeRequest.playerID) || !this.inputItemsSatisfied(trade.fromItems) + || !this.inputItemsSatisfied(trade.nonConsumedItems) ) { return false; } @@ -260,9 +273,9 @@ private boolean processTradeOnServer(TradeRequest tradeRequest) { UUID currentPlayer = NameCache.INSTANCE.getUUIDFromPlayer(this.getCurrentUser()); TradeManager.INSTANCE.playerCurrency.putIfAbsent(currentPlayer, new HashMap<>()); - Map coinInventory = TradeManager.INSTANCE.playerCurrency.get(currentPlayer); + Map coinInventory = TradeManager.INSTANCE.playerCurrency.get(currentPlayer); - Map newCoinInventory = new HashMap<>(); + Map newCoinInventory = new HashMap<>(); for (CurrencyItem ci : trade.fromCurrency) { int oldValue = coinInventory.get(ci.type); if (!coinInventory.containsKey(ci.type) || oldValue < ci.value) { @@ -273,21 +286,14 @@ private boolean processTradeOnServer(TradeRequest tradeRequest) { } for (BigItemStack stack : trade.fromItems) { - ItemStack requiredStack = stack.getBaseStack() - .copy(); - requiredStack.stackSize = 1; // just in case it's not pulled as 1 for some reason + ItemStack requiredStack = stack.getBaseStack(); int requiredAmount = stack.stackSize; - // Remove Items from last stacks if possible + // Remove items from last stacks if possible (exact matches) for (int i = MTEVendingMachine.INPUT_SLOTS - 1; i >= 0 && requiredAmount > 0; i--) { if (inputSlots[i] == null) { continue; } - ItemStack tmp = inputSlots[i].copy(); - tmp.stackSize = 1; - if ( - ItemStack.areItemStacksEqual(requiredStack, tmp) - && ItemStack.areItemStackTagsEqual(requiredStack, tmp) - ) { + if (requiredStack.isItemEqual(inputSlots[i])) { if (requiredAmount >= inputSlots[i].stackSize) { requiredAmount -= inputSlots[i].stackSize; inputSlots[i] = null; @@ -297,13 +303,45 @@ private boolean processTradeOnServer(TradeRequest tradeRequest) { } } } + // Remove items from last stacks if possible (oredict matches) + if (requiredAmount > 0 && stack.hasOreDict()) { + String ore = stack.getOreDict(); + for (int i = MTEVendingMachine.INPUT_SLOTS - 1; i >= 0 && requiredAmount > 0; i--) { + if (inputSlots[i] == null) { + continue; + } + if ( + IntStream.of(OreDictionary.getOreIDs(inputSlots[i])) + .mapToObj(OreDictionary::getOreName) + .anyMatch(s -> s.equals(ore)) + ) { + if ( + requiredStack.isItemStackDamageable() + && requiredStack.getItemDamage() != inputSlots[i].getItemDamage() + ) { + continue; + } + if (requiredAmount >= inputSlots[i].stackSize) { + requiredAmount -= inputSlots[i].stackSize; + inputSlots[i] = null; + } else { + inputSlots[i].stackSize -= requiredAmount; + requiredAmount = 0; + } + } + } + } + requiredStack.stackSize = requiredAmount; - if (requiredAmount > 0 && !fetchItemFromAE(requiredStack, false)) { + if ( + requiredAmount > 0 + && !fetchItemFromAE(requiredStack, false, stack.hasOreDict() ? stack.getOreDict() : null) + ) { return false; } } - for (Map.Entry entry : newCoinInventory.entrySet()) { + for (Map.Entry entry : newCoinInventory.entrySet()) { if (entry.getValue() == 0) { coinInventory.remove(entry.getKey()); } else { @@ -320,16 +358,18 @@ private boolean processTradeOnServer(TradeRequest tradeRequest) { this.outputBuffer.addAll(toItem.getCombinedStacks()); this.newBufferedOutputs = true; } - TradeDatabase.INSTANCE.getTradeGroups() - .get(tradeRequest.tradeGroup) - .executeTrade(tradeRequest.playerID); + TradeManager.INSTANCE.executeTrade( + tradeRequest.playerID, + TradeDatabase.INSTANCE.getTradeGroups() + .get(tradeRequest.tradeGroup)); this.sendTradeUpdate(); + this.markDirty(); return true; } - public boolean fetchItemFromAE(ItemStack requiredStack, boolean simulate) { + public boolean fetchItemFromAE(ItemStack requiredStack, boolean simulate, String ore) { for (MTEVendingUplinkHatch hatch : this.uplinkHatches) { - if (hatch.removeItem(requiredStack, simulate)) { + if (hatch.removeItem(requiredStack, simulate, ore)) { return true; } } @@ -343,7 +383,7 @@ public boolean getDefaultHasMaintenanceChecks() { @Override public String[] getStructureDescription(ItemStack stackSize) { - return getTooltip().getStructureHint(); + return new String[] { Translator.translate("structure.vendingmachine.hint.1") }; } @Override @@ -359,8 +399,9 @@ protected MultiblockTooltipBuilder getTooltip() { .beginStructureBlock(2, 3, 1, false) .addController("Middle") .addOtherStructurePart("Tin Item Pipe Casings", "Everything except the controller") + .addOtherStructurePart("ME Vending Uplink Hatch", "Any Pipe Casing, Optional") .addStructureInfo("Cannot be flipped onto its side") - .toolTipFinisher(); + .toolTipFinisher(AUTHOR_CUBEFURY); } return tooltipBuilder; } @@ -460,15 +501,47 @@ public void saveNBTData(NBTTagCompound aNBT) { @Override public void loadNBTData(NBTTagCompound aNBT) { super.loadNBTData(aNBT); + boolean loadedLegacyData = false; + + NBTTagList pendingOutputs = aNBT.getTagList("outputBuffer", Constants.NBT.TAG_COMPOUND); + for (int i = 0; i < pendingOutputs.tagCount(); i++) { + outputBuffer.add(GTUtility.loadItem(pendingOutputs.getCompoundTagAt(i))); + } + if (inputItems != null) { inputItems.deserializeNBT(aNBT.getCompoundTag("inputs")); + if (inputItems.getSlots() != MTEVendingMachine.INPUT_SLOTS) { + loadedLegacyData = true; + List oldStacks = inputItems.getStacks(); + inputItems.setSize(MTEVendingMachine.INPUT_SLOTS); + for (int i = 0; i < oldStacks.size(); i++) { + if (i >= MTEVendingMachine.INPUT_SLOTS) { + outputBuffer.add(oldStacks.get(i)); + } else { + inputItems.setStackInSlot(i, oldStacks.get(i)); + } + } + } } + if (outputItems != null) { outputItems.deserializeNBT(aNBT.getCompoundTag("outputs")); + if (outputItems.getSlots() != MTEVendingMachine.OUTPUT_SLOTS) { + loadedLegacyData = true; + List oldStacks = outputItems.getStacks(); + outputItems.setSize(MTEVendingMachine.OUTPUT_SLOTS); + for (int i = 0; i < oldStacks.size(); i++) { + if (i >= MTEVendingMachine.OUTPUT_SLOTS) { + outputBuffer.add(oldStacks.get(i)); + } else { + outputItems.setStackInSlot(i, oldStacks.get(i)); + } + } + } } - NBTTagList pendingOutputs = aNBT.getTagList("outputBuffer", Constants.NBT.TAG_COMPOUND); - for (int i = 0; i < pendingOutputs.tagCount(); i++) { - outputBuffer.add(GTUtility.loadItem(pendingOutputs.getCompoundTagAt(i))); + + if (loadedLegacyData) { + this.markDirty(); } } @@ -536,18 +609,20 @@ public boolean checkMachine(IGregTechTileEntity aBaseMetaTileEntity, ItemStack a @Override public void onPostTick(IGregTechTileEntity aBaseMetaTileEntity, long aTimer) { - if (aBaseMetaTileEntity.isClientSide() && !aBaseMetaTileEntity.isActive()) { - OverlayHelper.clearVMOverlay(overlayTickets); - } - if (aBaseMetaTileEntity.isServerSide()) { - dispenseItems(); - if (this.ticksSinceTradeUpdate++ >= Config.gui_refresh_interval) { - this.sendTradeUpdate(); - } - if (this.mUpdate++ % STRUCTURE_CHECK_TICKS == 0) { - this.mMachine = checkMachine(aBaseMetaTileEntity, null); - aBaseMetaTileEntity.setActive(this.mMachine); + super.onPostTick(aBaseMetaTileEntity, aTimer); + if (aBaseMetaTileEntity.isClientSide()) { + if (!aBaseMetaTileEntity.isActive()) { + OverlayHelper.clearVMOverlay(overlayTickets); } + return; + } else if (this.mUpdate++ % STRUCTURE_CHECK_TICKS == 0) { + this.mMachine = checkMachine(aBaseMetaTileEntity, null); + } + aBaseMetaTileEntity.setActive(this.mMachine); + if (!this.mMachine) return; + dispenseItems(); + if (this.ticksSinceTradeUpdate++ >= VMConfig.vendingMachineSettings.gui_refresh_interval) { + this.sendTradeUpdate(); } } @@ -556,7 +631,6 @@ public void sendTradeUpdate() { if (this.currentUser == null) { return; } - NetCurrencySync.syncCurrencyToClient((EntityPlayerMP) this.currentUser); NetTradeDisplaySync.syncTradesToClient((EntityPlayerMP) this.currentUser, this); } @@ -566,6 +640,7 @@ public void refreshInputSlotCache() { ItemStack stack = this.inputItems.getStackInSlot(i); if (stack != null) { BigItemStack tmp = new BigItemStack(stack); + tmp.setTagCompound(null); tmp.stackSize = 1; items.putIfAbsent(tmp, 0); items.replace(tmp, items.get(tmp) + stack.stackSize); @@ -577,6 +652,8 @@ public void refreshInputSlotCache() { public boolean inputItemsSatisfied(List fromItems) { for (BigItemStack bis : fromItems) { BigItemStack base = bis.copy(); + boolean hasOreDict = bis.hasOreDict(); + base.setTagCompound(null); base.stackSize = 1; // shouldn't need this, but just in case ItemStack aeStackSearch = base.getBaseStack(); @@ -585,10 +662,33 @@ public boolean inputItemsSatisfied(List fromItems) { aeStackSearch.stackSize = Math .max(aeStackSearch.stackSize - this.inputSlotCache.getOrDefault(base, 0), 0); } + + if (aeStackSearch.stackSize > 0 && hasOreDict) { + String ore = bis.getOreDict(); + for (Map.Entry item : this.inputSlotCache.entrySet()) { + if ( + IntStream.of( + OreDictionary.getOreIDs( + item.getKey() + .getBaseStack())) + .mapToObj(OreDictionary::getOreName) + .anyMatch(s -> s.equals(ore)) + ) { + if ( + aeStackSearch.isItemStackDamageable() && aeStackSearch.getItemDamage() != item.getKey() + .getBaseStack() + .getItemDamage() + ) { + continue; + } + aeStackSearch.stackSize = Math.max(aeStackSearch.stackSize - item.getValue(), 0); + } + } + } if (aeStackSearch.stackSize == 0) { continue; } - if (!this.fetchItemFromAE(aeStackSearch, true)) { + if (!this.fetchItemFromAE(aeStackSearch, true, hasOreDict ? bis.getOreDict() : null)) { return false; } } @@ -599,7 +699,7 @@ public boolean inputCurrencySatisfied(List currencyItems, UUID pla if (currencyItems == null || currencyItems.isEmpty()) { return true; } - Map availableCurrency = TradeManager.INSTANCE.playerCurrency.get(player); + Map availableCurrency = TradeManager.INSTANCE.playerCurrency.get(player); if (availableCurrency == null) { return false; } @@ -681,7 +781,7 @@ public boolean onRightclick(IGregTechTileEntity aBaseMetaTileEntity, EntityPlaye if (canUse(aPlayer)) { this.currentUser = aPlayer; // force trade state update now - this.ticksSinceTradeUpdate = Config.gui_refresh_interval; + this.ticksSinceTradeUpdate = VMConfig.vendingMachineSettings.gui_refresh_interval; openGui(aPlayer); } else { aPlayer.addChatComponentMessage(new ChatComponentTranslation("vendingmachine.gui.error.player_using")); @@ -715,22 +815,19 @@ public void spawnItem(ItemStack stack) { .getYCoord(); int posZ = this.getBaseMetaTileEntity() .getZCoord(); - int offsetX = this.getExtendedFacing() - .getDirection().offsetX; - int offsetY = this.getExtendedFacing() - .getDirection().offsetY; - int offsetZ = this.getExtendedFacing() - .getDirection().offsetZ; + + ForgeDirection frontFacing = this.getBaseMetaTileEntity() + .getFrontFacing(); final EntityItem itemEntity = new EntityItem( world, - posX + offsetX * 0.5, - posY + offsetY * 0.5, - posZ + offsetZ * 0.5, + posX + 0.5 + frontFacing.offsetX * 0.7, + posY + 0.5 + frontFacing.offsetY * 0.7, + posZ + 0.5 + frontFacing.offsetZ * 0.7, stack); itemEntity.delayBeforeCanPickup = 0; - itemEntity.motionX = 0.05f * offsetX; - itemEntity.motionY = 0.05f * offsetY; - itemEntity.motionZ = 0.05f * offsetZ; + itemEntity.motionX = 0.1f * frontFacing.offsetX; + itemEntity.motionY = 0.1f * frontFacing.offsetY; + itemEntity.motionZ = 0.1f * frontFacing.offsetZ; world.spawnEntityInWorld(itemEntity); } @@ -744,4 +841,10 @@ private boolean addUplinkHatch(IGregTechTileEntity aBaseMetaTileEntity, int aBas this.uplinkHatches.add(uplinkHatch); return true; } + + public void refreshMeItemCache() { + for (MTEVendingUplinkHatch hatch : this.uplinkHatches) { + hatch.refreshStorageContents(); + } + } } diff --git a/src/main/java/com/cubefury/vendingmachine/blocks/MTEVendingUplinkHatch.java b/src/main/java/com/cubefury/vendingmachine/blocks/MTEVendingUplinkHatch.java index 10e6d6b..66fe473 100644 --- a/src/main/java/com/cubefury/vendingmachine/blocks/MTEVendingUplinkHatch.java +++ b/src/main/java/com/cubefury/vendingmachine/blocks/MTEVendingUplinkHatch.java @@ -1,14 +1,18 @@ package com.cubefury.vendingmachine.blocks; -import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_ME_INPUT_FLUID_HATCH; -import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_ME_INPUT_FLUID_HATCH_ACTIVE; +import static com.cubefury.vendingmachine.api.enums.Textures.VUPLINK_OVERLAY_ACTIVE; +import static com.cubefury.vendingmachine.api.enums.Textures.VUPLINK_OVERLAY_INACTIVE; import java.util.EnumSet; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.IntStream; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.util.ChatComponentTranslation; import net.minecraftforge.common.util.ForgeDirection; +import net.minecraftforge.oredict.OreDictionary; import com.cubefury.vendingmachine.VendingMachine; import com.cubefury.vendingmachine.items.VMItems; @@ -21,6 +25,7 @@ import appeng.api.networking.security.MachineSource; import appeng.api.networking.storage.IStorageGrid; import appeng.api.storage.data.IAEItemStack; +import appeng.api.storage.data.IItemList; import appeng.api.util.AECableType; import appeng.api.util.DimensionalCoord; import appeng.me.GridAccessException; @@ -37,6 +42,7 @@ public class MTEVendingUplinkHatch extends MTEHatch implements IGridProxyable, I protected AENetworkProxy gridProxy = null; protected boolean additionalConnection = false; + private IItemList cachedItems; public static final int mTier = 3; @@ -120,12 +126,12 @@ public void securityBreak() {} @Override public ITexture[] getTexturesActive(ITexture aBaseTexture) { - return new ITexture[] { aBaseTexture, TextureFactory.of(OVERLAY_ME_INPUT_FLUID_HATCH_ACTIVE) }; + return new ITexture[] { aBaseTexture, TextureFactory.of(VUPLINK_OVERLAY_ACTIVE) }; } @Override public ITexture[] getTexturesInactive(ITexture aBaseTexture) { - return new ITexture[] { aBaseTexture, TextureFactory.of(OVERLAY_ME_INPUT_FLUID_HATCH) }; + return new ITexture[] { aBaseTexture, TextureFactory.of(VUPLINK_OVERLAY_INACTIVE) }; } @Override @@ -144,6 +150,14 @@ public void onFirstTick(IGregTechTileEntity baseMetaTileEntity) { getProxy().onReady(); } + @Override + public void onPostTick(IGregTechTileEntity baseMetaTileEntity, long tick) { + if (baseMetaTileEntity.isServerSide() && tick % 20 == 0) { + baseMetaTileEntity.setActive(isActive()); + } + super.onPostTick(baseMetaTileEntity, tick); + } + private void updateValidGridProxySides() { if (additionalConnection) { getProxy().setValidSides(EnumSet.complementOf(EnumSet.of(ForgeDirection.UNKNOWN))); @@ -172,15 +186,78 @@ private IStorageGrid accessStorage() { return null; } - public boolean removeItem(ItemStack remove, boolean simulate) { + public void refreshStorageContents() { + IStorageGrid storage = accessStorage(); + if (storage == null) return; + + cachedItems = storage.getItemInventory() + .getStorageList(); + } + + public boolean removeItem(ItemStack remove, boolean simulate, String ore) { if (remove == null || remove.stackSize <= 0) return true; IStorageGrid storage = accessStorage(); if (storage == null) return false; - IAEItemStack stack = storage.getItemInventory() - .extractItems( - AEItemStack.create(remove), - simulate ? Actionable.SIMULATE : Actionable.MODULATE, - new MachineSource(this)); - return stack != null && stack.getStackSize() >= remove.stackSize; + + MachineSource source = new MachineSource(this); + + // shortcut for exact item matches to save compute for majority of trades + if (!remove.isItemStackDamageable() && ore == null) { + IAEItemStack stack = storage.getItemInventory() + .extractItems(AEItemStack.create(remove), simulate ? Actionable.SIMULATE : Actionable.MODULATE, source); + return stack != null && stack.getStackSize() >= remove.stackSize; + } + + if (cachedItems == null) { + return false; + } + + List modulateList = new LinkedList<>(); + + long remain = remove.stackSize; + for (IAEItemStack stack : cachedItems) { + if ( + ore == null && stack.getItem() != remove.getItem() + || ore != null && IntStream.of(OreDictionary.getOreIDs(stack.getItemStack())) + .mapToObj(OreDictionary::getOreName) + .noneMatch(s -> s.equals(ore)) + ) { + continue; + } + + if (remove.isItemStackDamageable() && stack.getItemDamage() != remove.getItemDamage()) { + continue; + } + + IAEItemStack copy = stack.copy(); + copy.setStackSize(Math.min(stack.getStackSize(), remain)); + if ( + storage.getItemInventory() + .extractItems(copy, Actionable.SIMULATE, source) == null + ) { + continue; + } + remain -= copy.getStackSize(); + + if (stack.getItem() == remove.getItem()) { + modulateList.add(0, copy); + } else { + modulateList.add(copy); + } + + if (remain <= 0) { + break; + } + } + + if (simulate || remain > 0) { + return remain <= 0; + } + + for (IAEItemStack modulate : modulateList) { + storage.getItemInventory() + .extractItems(modulate, Actionable.MODULATE, source); + } + return true; } } diff --git a/src/main/java/com/cubefury/vendingmachine/blocks/gui/CoinButton.java b/src/main/java/com/cubefury/vendingmachine/blocks/gui/CoinButton.java index 88f4b14..e1610e6 100644 --- a/src/main/java/com/cubefury/vendingmachine/blocks/gui/CoinButton.java +++ b/src/main/java/com/cubefury/vendingmachine/blocks/gui/CoinButton.java @@ -5,14 +5,14 @@ import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.api.widget.Interactable; import com.cleanroommc.modularui.widgets.ToggleButton; -import com.cubefury.vendingmachine.trade.CurrencyItem; +import com.cubefury.vendingmachine.trade.CurrencyType; public class CoinButton extends ToggleButton { private final TradeMainPanel panel; - private final CurrencyItem.CurrencyType type; + private final CurrencyType type; - public CoinButton(TradeMainPanel panel, CurrencyItem.CurrencyType type) { + public CoinButton(TradeMainPanel panel, CurrencyType type) { super(); background(IDrawable.EMPTY); selectedBackground(IDrawable.EMPTY); @@ -26,15 +26,10 @@ public CoinButton(TradeMainPanel panel, CurrencyItem.CurrencyType type) { if (!panel.shiftHeld) { return Result.IGNORE; } - switch (mouseButton) { - case 0: - next(); - Interactable.playButtonClickSound(); - return Result.SUCCESS; - case 1: - prev(); - Interactable.playButtonClickSound(); - return Result.SUCCESS; + if (mouseButton == 0) { + next(); + Interactable.playButtonClickSound(); + return Result.SUCCESS; } return Result.IGNORE; } diff --git a/src/main/java/com/cubefury/vendingmachine/blocks/gui/DisplayType.java b/src/main/java/com/cubefury/vendingmachine/blocks/gui/DisplayType.java new file mode 100644 index 0000000..8a4efbe --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/blocks/gui/DisplayType.java @@ -0,0 +1,31 @@ +package com.cubefury.vendingmachine.blocks.gui; + +import static com.cubefury.vendingmachine.gui.GuiTextures.MODE_LIST; +import static com.cubefury.vendingmachine.gui.GuiTextures.MODE_TILE; + +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.drawable.Icon; +import com.cleanroommc.modularui.drawable.UITexture; + +public enum DisplayType { + + TILE("tile", MODE_TILE), + LIST("list", MODE_LIST); + + private final String type; + private final Icon texture; + + DisplayType(String type, UITexture texture) { + this.type = type; + this.texture = texture.asIcon(); + } + + public String getLocalizedName() { + return IKey.lang("vendingmachine.gui.display_mode_" + this.type) + .toString(); + } + + public Icon getTexture() { + return this.texture; + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/blocks/gui/InterceptingSlot.java b/src/main/java/com/cubefury/vendingmachine/blocks/gui/InterceptingSlot.java index 582335e..a340e1c 100644 --- a/src/main/java/com/cubefury/vendingmachine/blocks/gui/InterceptingSlot.java +++ b/src/main/java/com/cubefury/vendingmachine/blocks/gui/InterceptingSlot.java @@ -1,30 +1,34 @@ package com.cubefury.vendingmachine.blocks.gui; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; import com.cleanroommc.modularui.utils.item.ItemStackHandler; import com.cleanroommc.modularui.widgets.slot.ModularSlot; -import com.cubefury.vendingmachine.network.handlers.NetCurrencySync; +import com.cubefury.vendingmachine.blocks.MTEVendingMachine; import com.cubefury.vendingmachine.storage.NameCache; import com.cubefury.vendingmachine.trade.CurrencyItem; import com.cubefury.vendingmachine.trade.TradeManager; public class InterceptingSlot extends ModularSlot { - public InterceptingSlot(ItemStackHandler inputItems, int index) { + private MTEVendingMachine vm; + + public InterceptingSlot(ItemStackHandler inputItems, int index, MTEVendingMachine vm) { super(inputItems, index); + this.vm = vm; } // intercept item on both ends, but only do the post-intercept actions on server side public boolean intercept(ItemStack newItem, boolean client, EntityPlayer player) { + if (vm == null || !vm.getActive()) { + return false; + } CurrencyItem mapped = mapToCurrency(newItem); if (mapped != null) { this.putStack(null); if (!client) { TradeManager.INSTANCE.addCurrency(NameCache.INSTANCE.getUUIDFromPlayer(player), mapped); - NetCurrencySync.sendPlayerCurrency((EntityPlayerMP) player, mapped); } return true; } diff --git a/src/main/java/com/cubefury/vendingmachine/blocks/gui/MTEVendingMachineGui.java b/src/main/java/com/cubefury/vendingmachine/blocks/gui/MTEVendingMachineGui.java index 5fcfd38..277b4dd 100644 --- a/src/main/java/com/cubefury/vendingmachine/blocks/gui/MTEVendingMachineGui.java +++ b/src/main/java/com/cubefury/vendingmachine/blocks/gui/MTEVendingMachineGui.java @@ -1,6 +1,7 @@ package com.cubefury.vendingmachine.blocks.gui; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -13,40 +14,50 @@ import com.cleanroommc.modularui.api.drawable.IKey; import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.drawable.DynamicDrawable; import com.cleanroommc.modularui.factory.PosGuiData; import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.screen.RichTooltip; import com.cleanroommc.modularui.screen.UISettings; import com.cleanroommc.modularui.utils.Alignment; +import com.cleanroommc.modularui.value.IntValue; import com.cleanroommc.modularui.value.sync.BooleanSyncValue; +import com.cleanroommc.modularui.value.sync.IntSyncValue; import com.cleanroommc.modularui.value.sync.PanelSyncManager; import com.cleanroommc.modularui.widget.ParentWidget; import com.cleanroommc.modularui.widget.SingleChildWidget; +import com.cleanroommc.modularui.widgets.CycleButtonWidget; import com.cleanroommc.modularui.widgets.ListWidget; import com.cleanroommc.modularui.widgets.PagedWidget; import com.cleanroommc.modularui.widgets.SlotGroupWidget; +import com.cleanroommc.modularui.widgets.TextWidget; import com.cleanroommc.modularui.widgets.ToggleButton; import com.cleanroommc.modularui.widgets.layout.Column; import com.cleanroommc.modularui.widgets.layout.Flow; import com.cleanroommc.modularui.widgets.layout.Row; import com.cleanroommc.modularui.widgets.slot.ItemSlot; import com.cleanroommc.modularui.widgets.slot.ModularSlot; +import com.cubefury.vendingmachine.VMConfig; import com.cubefury.vendingmachine.VendingMachine; import com.cubefury.vendingmachine.blocks.MTEVendingMachine; import com.cubefury.vendingmachine.gui.GuiTextures; import com.cubefury.vendingmachine.gui.WidgetThemes; -import com.cubefury.vendingmachine.network.handlers.NetCurrencySync; import com.cubefury.vendingmachine.network.handlers.NetTradeDisplaySync; import com.cubefury.vendingmachine.storage.NameCache; import com.cubefury.vendingmachine.trade.CurrencyItem; +import com.cubefury.vendingmachine.trade.CurrencyType; +import com.cubefury.vendingmachine.trade.FavouritesTracker; import com.cubefury.vendingmachine.trade.TradeCategory; import com.cubefury.vendingmachine.trade.TradeDatabase; +import com.cubefury.vendingmachine.trade.TradeGroup; import com.cubefury.vendingmachine.trade.TradeManager; import com.cubefury.vendingmachine.util.BigItemStack; import com.cubefury.vendingmachine.util.Translator; +import com.gtnewhorizon.gtnhlib.config.ConfigurationManager; -import gregtech.api.metatileentity.implementations.gui.MTEMultiBlockBaseGui; import gregtech.api.modularui2.GTGuiTextures; import gregtech.api.modularui2.GTWidgetThemes; +import gregtech.common.gui.modularui.multiblock.base.MTEMultiBlockBaseGui; public class MTEVendingMachineGui extends MTEMultiBlockBaseGui { @@ -56,23 +67,31 @@ public class MTEVendingMachineGui extends MTEMultiBlockBaseGui { private boolean ejectItems = false; private boolean ejectCoins = false; - private final Map ejectSingleCoin = new HashMap<>(); - private final Map> displayedTrades = new HashMap<>(); + private final Map ejectSingleCoin = new HashMap<>(); + public final Map> displayedTradesTiles = new HashMap<>(); + public final Map> displayedTradesList = new HashMap<>(); private final List tradeCategories = new ArrayList<>(); private final List inputSlots = new ArrayList<>(); private PosGuiData guiData; private final PagedWidget.Controller tabController; + public IWidget favouritesTabWidget; private final SearchBar searchBar; public static String lastSearch = ""; public static int lastPage = 0; + public static SortMode sortMode = VMConfig.gui.sort_mode; public static final int CUSTOM_UI_HEIGHT = 320; - public static final int ITEMS_PER_ROW = 3; - public static final int ITEM_HEIGHT = 25; - public static final int ITEM_WIDTH = 47; + // Trade Item Display + public static final int TRADE_ROW_WIDTH = 154; + public static final int TILE_ITEMS_PER_ROW = 3; + public static final int TILE_ITEM_HEIGHT = 25; + public static final int TILE_ITEM_WIDTH = 47; + public static final int LIST_ITEM_HEIGHT = 14; + public static final int LIST_ITEM_WIDTH = 153; + private static final int COIN_COLUMN_WIDTH = 40; private static final int COIN_COLUMN_ROW_COUNT = 4; @@ -80,18 +99,24 @@ public MTEVendingMachineGui(MTEVendingMachine base) { super(base); this.base = base; - for (CurrencyItem.CurrencyType type : CurrencyItem.CurrencyType.values()) { + for (CurrencyType type : CurrencyType.values()) { ejectSingleCoin.put(type, false); } + this.tradeCategories.add(TradeCategory.FAVOURITES); this.tradeCategories.add(TradeCategory.ALL); this.tradeCategories.addAll(TradeDatabase.INSTANCE.getTradeCategories()); for (TradeCategory c : this.tradeCategories) { - displayedTrades.put(c, new ArrayList<>(MTEVendingMachine.MAX_TRADES)); + displayedTradesTiles.put(c, new ArrayList<>(MTEVendingMachine.MAX_TRADES)); + for (int i = 0; i < MTEVendingMachine.MAX_TRADES; i++) { + displayedTradesTiles.get(c) + .add(new TradeItemDisplayWidget(null, this.base, DisplayType.TILE)); + } + displayedTradesList.put(c, new ArrayList<>(MTEVendingMachine.MAX_TRADES)); for (int i = 0; i < MTEVendingMachine.MAX_TRADES; i++) { - displayedTrades.get(c) - .add(new TradeItemDisplayWidget(null)); + displayedTradesList.get(c) + .add(new TradeItemDisplayWidget(null, this.base, DisplayType.LIST)); } } @@ -115,20 +140,30 @@ public ModularPanel build(PosGuiData guiData, PanelSyncManager syncManager, UISe ModularPanel panel = new TradeMainPanel("MTEMultiBlockBase", this, guiData, syncManager) .size(178, CUSTOM_UI_HEIGHT) .padding(4); + panel.onCloseAction(() -> { + if (VendingMachine.proxy.isClient()) { + FavouritesTracker.INSTANCE.saveFavourites(); + } + }); panel.child(createCategoryTabs(this.tabController)); Flow mainColumn = new Column().width(170); - if (VendingMachine.proxy.isClient()) { // client side filtering - mainColumn.child(createTitleTextStyle(base.getLocalName())) + if (VendingMachine.proxy.isClient()) { // client side sort and filtering + panel.child(createQolButtonColumn()); + mainColumn.child( + createTitleTextStyle( + IKey.lang("gt.blockmachines.multimachine.vendingmachine.name.gui") + .style(IKey.DARK_GRAY) + .get())) .child(this.searchBar) .child(createTradeUI((TradeMainPanel) panel, this.tabController)); - mainColumn.child(createCoinInventoryRow((TradeMainPanel) panel)); + mainColumn.child(createCoinInventoryRow((TradeMainPanel) panel, syncManager)); } mainColumn.child(createInventoryRow()); panel.child(mainColumn); panel.child( new Column().size(20) .right(5)); - panel.child(createIOColumn(syncManager)); + panel.child(createIOColumn()); return panel; } @@ -139,9 +174,55 @@ public void restorePreviousSettings() { this.searchBar.setText(lastSearch); } + public IWidget createQolButtonColumn() { + Flow buttonColumn = new Column().width(8) + .height(20) + .left(-17) + .top(1) + .coverChildren(); + buttonColumn.child( + new CycleButtonWidget().size(14) + .overlay( + new DynamicDrawable( + () -> VMConfig.gui.display_type.getTexture() + .size(14))) + .stateCount(DisplayType.values().length) + .value(new IntValue.Dynamic(() -> VMConfig.gui.display_type.ordinal(), val -> { + VMConfig.gui.display_type = DisplayType.values()[val]; + ConfigurationManager.save(VMConfig.class); + })) + .tooltipDynamic(builder -> { + builder.clearText(); + builder.addLine( + IKey.lang("vendingmachine.gui.display_mode") + " " + + VMConfig.gui.display_type.getLocalizedName()); + }) + .tooltipAutoUpdate(true)); + buttonColumn.child( + new CycleButtonWidget().size(14) + .top(17) + .overlay( + new DynamicDrawable( + () -> VMConfig.gui.sort_mode.getTexture() + .size(14))) + .stateCount(SortMode.values().length) + .value(new IntValue.Dynamic(() -> VMConfig.gui.sort_mode.ordinal(), val -> { + VMConfig.gui.sort_mode = SortMode.values()[val]; + ConfigurationManager.save(VMConfig.class); + })) + .tooltipDynamic(builder -> { + builder.clearText(); + builder.addLine( + IKey.lang("vendingmachine.gui.display_sort") + " " + VMConfig.gui.sort_mode.getLocalizedName()); + setForceRefresh(); + }) + .tooltipAutoUpdate(true)); + return buttonColumn; + } + public IWidget createCategoryTabs(PagedWidget.Controller tabController) { Flow tabColumn = new Column().width(40) - .height(100) + .height(300) .left(-29) .top(40) .coverChildren(); @@ -163,10 +244,21 @@ public IWidget createCategoryTabs(PagedWidget.Controller tabController) { this.tradeCategories.get(index) .getUnlocalized_name())); })); + + if (tradeCategories.get(i) == TradeCategory.FAVOURITES) { + favouritesTabWidget = tabColumn.getChildren() + .get( + tabColumn.getChildren() + .size() - 1); + } } return tabColumn; } + public TradeCategory getActiveTradeCategory() { + return this.tradeCategories.get(this.tabController.getActivePageIndex()); + } + // why is the original method private lmao private IWidget createTitleTextStyle(String title) { return new SingleChildWidget<>().coverChildren() @@ -176,7 +268,7 @@ private IWidget createTitleTextStyle(String title) { .child( IKey.str(title) .asWidget() - .alignment(Alignment.Center) + .textAlign(Alignment.Center) .widgetTheme(GTWidgetThemes.TEXT_TITLE) .marginLeft(5) .marginRight(5) @@ -188,12 +280,12 @@ private SearchBar createSearchBar() { return new SearchBar(this).width(162) .left(3) .top(5) - .height(10); + .height(14); } // Eject code is in GUI instead of MTE since the syncers are per-gui instance - private void doEjectCoin(CurrencyItem.CurrencyType type) { - if (this.guiData.isClient()) { + private void doEjectCoin(CurrencyType type) { + if (this.guiData.isClient() || !this.base.getActive()) { return; } UUID currentUser = NameCache.INSTANCE.getUUIDFromPlayer(base.getCurrentUser()); @@ -212,7 +304,6 @@ private void doEjectCoin(CurrencyItem.CurrencyType type) { base.spawnItem(ejectable); } TradeManager.INSTANCE.resetCurrency(currentUser, type); - NetCurrencySync.resetPlayerCurrency((EntityPlayerMP) base.getCurrentUser(), type); this.ejectSingleCoin.put(type, false); } @@ -221,21 +312,25 @@ private void doEjectCoins() { return; } + if (!this.base.getActive()) { + ejectCoins = false; + return; + } + UUID currentUser = NameCache.INSTANCE.getUUIDFromPlayer(base.getCurrentUser()); if (!TradeManager.INSTANCE.playerCurrency.containsKey(currentUser)) { ejectCoins = false; return; } - Map coins = TradeManager.INSTANCE.playerCurrency + Map coins = TradeManager.INSTANCE.playerCurrency .getOrDefault(currentUser, new HashMap<>()); - for (Map.Entry entry : coins.entrySet()) { + for (Map.Entry entry : coins.entrySet()) { for (ItemStack ejectable : new CurrencyItem(entry.getKey(), entry.getValue()).itemize()) { base.spawnItem(ejectable); } } TradeManager.INSTANCE.resetCurrency(currentUser, null); - NetCurrencySync.resetPlayerCurrency((EntityPlayerMP) base.getCurrentUser(), null); ejectCoins = false; } @@ -243,6 +338,11 @@ private void doEjectItems() { if (this.guiData.isClient()) { return; } + if (!this.base.getActive()) { + ejectItems = false; + return; + } + for (int i = 0; i < MTEVendingMachine.INPUT_SLOTS; i++) { ItemStack stack = base.inputItems.getStackInSlot(i); if (stack != null) { @@ -253,10 +353,10 @@ private void doEjectItems() { ejectItems = false; } - private IWidget createIOColumn(PanelSyncManager syncManager) { - return new ParentWidget<>().excludeAreaInNEI() + private IWidget createIOColumn() { + return new ParentWidget<>().excludeAreaInRecipeViewer() .width(50) - .height(178) + .height(214) .right(-48) .top(40) .widgetTheme(WidgetThemes.BACKGROUND_SIDEPANEL) @@ -268,9 +368,9 @@ private IWidget createIOColumn(PanelSyncManager syncManager) { .width(30) .height(20)) .child( - new Row().child(createInputRow().center()) + new Row().child(createInputSlots().center()) .top(20) - .height(18 * 3)) + .height(18 * 4)) .child( new Row().child( new ToggleButton().overlay(GTGuiTextures.OVERLAY_BUTTON_CYCLIC) @@ -284,26 +384,26 @@ private IWidget createIOColumn(PanelSyncManager syncManager) { .tooltipBuilder(t -> t.addLine(IKey.lang("vendingmachine.gui.coin_eject"))) .syncHandler("ejectCoins") .left(6)) - .top(80) + .top(98) .height(18)) .child( GuiTextures.OUTPUT_SPRITE.asWidget() .leftRel(0.5f) - .bottom(52) + .bottom(70) .width(30) .height(20)) .child( new Row().child(createOutputSlots().center()) .bottom(6) - .height(18 * 3)) + .height(18 * 4)) .right(1)); } - private SlotGroupWidget createInputRow() { + private SlotGroupWidget createInputSlots() { return SlotGroupWidget.builder() - .matrix("II", "II", "II") + .matrix("II", "II", "II", "II") .key('I', index -> { - InterceptingSlot slot = new InterceptingSlot(base.inputItems, index); + InterceptingSlot slot = new InterceptingSlot(base.inputItems, index, this.base); this.inputSlots.add(slot); return new ItemSlot().slot( slot.slotGroup("inputSlotGroup") @@ -327,6 +427,7 @@ private SlotGroupWidget createInputRow() { if (hasCoin) { this.refreshInputSlots(); } + base.markDirty(); })); }) .build(); @@ -334,76 +435,177 @@ private SlotGroupWidget createInputRow() { private SlotGroupWidget createOutputSlots() { return SlotGroupWidget.builder() - .matrix("II", "II", "II") + .matrix("II", "II", "II", "II") .key('I', index -> { - ModularSlot ms = new ModularSlot(base.outputItems, index).accessibility(false, true) - .slotGroup("outputSlotGroup"); - ms.changeListener((newItem, onlyAmountChanged, client, init) -> {}); - return new ItemSlot().slot(ms); + return new ItemSlot().slot( + new ModularSlot(base.outputItems, index).accessibility(false, true) + .slotGroup("outputSlotGroup") + .changeListener((newitem, onlyAmountChanged, client, init) -> { base.markDirty(); })); }) .build(); } + private void constructTradeTooltip(RichTooltip builder, TradeItemDisplay cur) { + if (cur != null) { + for (BigItemStack toItem : cur.toItems) { + builder.addLine( + IKey.str( + toItem.stackSize + " " + + toItem.getBaseStack() + .getDisplayName()) + .style(IKey.AQUA)); + // builder.add(new ItemDrawable(toItem.getBaseStack())); + } + builder.emptyLine(); + + if (!cur.fromCurrency.isEmpty() || !cur.fromItems.isEmpty()) { + builder.addLine( + IKey.lang("vendingmachine.gui.required_inputs") + .style(IKey.DARK_GREEN, IKey.ITALIC)); + for (CurrencyItem currencyItem : cur.fromCurrency) { + builder.addLine( + IKey.str(currencyItem.value + " " + currencyItem.type.getLocalizedName()) + .style(IKey.DARK_GREEN)); + } + for (BigItemStack fromItem : cur.fromItems) { + builder.addLine( + IKey.str( + fromItem.stackSize + " " + + fromItem.getBaseStack() + .getDisplayName() + + (fromItem.hasOreDict() + ? " (" + IKey.lang("vendingmachine.gui.alternative_oredict") + + " " + + fromItem.getOreDict() + + ")" + : "")) + .style(IKey.DARK_GREEN)); + } + builder.emptyLine(); + } + if (!cur.ncItems.isEmpty()) { + builder.addLine( + IKey.lang("vendingmachine.gui.nc_inputs") + .style(IKey.DARK_GREEN, IKey.ITALIC)); + for (BigItemStack fromItem : cur.ncItems) { + builder.addLine( + IKey.str( + fromItem.stackSize + " " + + fromItem.getBaseStack() + .getDisplayName()) + .style(IKey.DARK_GREEN)); + } + builder.emptyLine(); + } + + TradeGroup tg = TradeDatabase.INSTANCE.getTradeGroupFromId(cur.tgID); + if ( + tg != null && tg.getTrades() + .size() > 1 + ) { + builder.addLine( + IKey.str( + Translator.translate( + "vendingmachine.gui.shared_trades_tooltip", + tg.getTrades() + .size() - 1))); + } + builder.emptyLine(); + + builder.addLine( + IKey.str(Translator.translate("vendingmachine.gui.trade_hint")) + .style(IKey.GRAY)); + builder.addLine( + IKey.str(Translator.translate("vendingmachine.gui.favourite_hint")) + .style(IKey.GRAY)); + } + } + // spotless:off private IWidget createTradeUI(TradeMainPanel rootPanel, PagedWidget.Controller tabController) { PagedWidget paged = new PagedWidget<>() + .name("paged") .width(162) - .debugName("paged") .controller(tabController) .background(GuiTextures.TEXT_FIELD_BACKGROUND) .height(146); for (TradeCategory category : this.tradeCategories) { - ListWidget tradeList = new ListWidget<>().debugName("items") - .width(156) + ListWidget tradeList = new ListWidget<>() + .name("items") + .width(161) .top(1) .height(144) .collapseDisabledChild(true); tradeList.child(new Row().height(2)); + + // Incomplete Structure status message + Flow statusRow = new Row().height(10).width(TRADE_ROW_WIDTH).marginLeft(2) + .child(new TextWidget(IKey.lang("vendingmachine.gui.error.incomplete_structure"))) + .setEnabledIf(slot -> !this.getBase().getActive()); + tradeList.child(statusRow); + // Higher first row top margin - Flow row = new TradeRow().height(ITEM_HEIGHT+2).left(2); + Flow row = new TradeRow().height(TILE_ITEM_HEIGHT + 4).width(TRADE_ROW_WIDTH).marginLeft(2); + // Tiles Display for (int i = 0; i < MTEVendingMachine.MAX_TRADES; i++) { int index = i; - displayedTrades.get(category).get(i).setRootPanel(rootPanel); - row.child(displayedTrades.get(category).get(i) + displayedTradesTiles.get(category).get(i).setRootPanel(rootPanel); + row.child(displayedTradesTiles.get(category).get(i) .tooltipDynamic(builder -> { builder.clearText(); - synchronized (displayedTrades) { - if (index < displayedTrades.get(category).size()) { - TradeItemDisplay cur = displayedTrades.get(category).get(index).getDisplay(); - if (cur != null) { - for (BigItemStack toItem : cur.toItems) { - builder.addLine(IKey.str(toItem.stackSize + " " + toItem.getBaseStack().getDisplayName()).style(IKey.AQUA)); - // builder.add(new ItemDrawable(toItem.getBaseStack())); - } - builder.emptyLine(); - builder.addLine(IKey.str(Translator.translate("vendingmachine.gui.required_inputs")).style(IKey.DARK_GREEN, IKey.ITALIC)); - for (CurrencyItem currencyItem: cur.fromCurrency) { - builder.addLine(IKey.str(currencyItem.value + " " + currencyItem.type.getLocalizedName()).style(IKey.DARK_GREEN)); - } - for (BigItemStack fromItem : cur.fromItems) { - builder.addLine(IKey.str(fromItem.stackSize + " " + fromItem.getBaseStack().getDisplayName()).style(IKey.DARK_GREEN)); - } - - builder.emptyLine(); - builder.addLine(IKey.str(cur.label).style(IKey.GRAY)); + synchronized (displayedTradesTiles) { + if (index < displayedTradesTiles.get(category).size()) { + constructTradeTooltip(builder, displayedTradesTiles.get(category).get(index).getDisplay()); } } - } }) .tooltipAutoUpdate(true) - .setEnabledIf(slot -> ((TradeItemDisplayWidget) slot).getDisplay() != null) + .setEnabledIf(slot -> { + if (!this.getBase().getActive()) { + return false; + } + TradeItemDisplayWidget display = ((TradeItemDisplayWidget) slot); + return VMConfig.gui.display_type == display.displayType && display.getDisplay() != null; + }) .margin(2)); - if (i % ITEMS_PER_ROW == ITEMS_PER_ROW - 1) { + if (i % TILE_ITEMS_PER_ROW == TILE_ITEMS_PER_ROW - 1) { tradeList.child(row); - row = new TradeRow().height(ITEM_HEIGHT+2).left(2); + row = new TradeRow().height(TILE_ITEM_HEIGHT + 4).width(TRADE_ROW_WIDTH).marginLeft(2); } } if (row.hasChildren()) { tradeList.child(row); } + + // List Display + row = new TradeRow().height(LIST_ITEM_HEIGHT).width(TRADE_ROW_WIDTH).marginLeft(2); + for (int i = 0; i < MTEVendingMachine.MAX_TRADES; i++) { + int index = i; + displayedTradesList.get(category).get(i).setRootPanel(rootPanel); + row.child(displayedTradesList.get(category).get(i) + .tooltipDynamic(builder -> { + builder.clearText(); + synchronized (displayedTradesList) { + if (index < displayedTradesList.get(category).size()) { + constructTradeTooltip(builder, displayedTradesList.get(category).get(index).getDisplay()); + } + } + }) + .tooltipAutoUpdate(true) + .setEnabledIf(slot -> { + if (!this.getBase().getActive()) { + return false; + } + TradeItemDisplayWidget display = ((TradeItemDisplayWidget) slot); + return VMConfig.gui.display_type == display.displayType && display.getDisplay() != null; + })); + tradeList.child(row); + row = new TradeRow().height(LIST_ITEM_HEIGHT).width(TRADE_ROW_WIDTH).marginLeft(2); + } + tradeList.child(new Row().height(2)); // bottom padding for last row paged.addPage(tradeList); } @@ -424,7 +626,7 @@ private static String getReadableStringFromCoinAmount(int amount) { } } - private IWidget createCoinInventoryRow(TradeMainPanel panel) { + private IWidget createCoinInventoryRow(TradeMainPanel panel, PanelSyncManager syncManager) { Flow parent = new Row() // .background(GuiTextures.TEXT_FIELD_BACKGROUND) .width(162) .height(36) @@ -432,36 +634,9 @@ private IWidget createCoinInventoryRow(TradeMainPanel panel) { .left(3); Flow coinColumn = new Column().width(COIN_COLUMN_WIDTH); int coinCount = 0; - Map currentAmounts = TradeManager.INSTANCE.playerCurrency - .getOrDefault(NameCache.INSTANCE.getUUIDFromPlayer(getBase().getCurrentUser()), new HashMap<>()); - for (CurrencyItem.CurrencyType type : CurrencyItem.CurrencyType.values()) { - coinColumn.child( - new Row().child( - new CoinButton(panel, type).overlay( - type.texture.asIcon() - .size(12)) - .size(12) - .left(0) - .syncHandler("ejectCoin_" + type.id) - .tooltipDynamic((builder) -> { - builder.clearText(); - builder.addLine(currentAmounts.getOrDefault(type, 0) + " " + type.getLocalizedName()); - builder.emptyLine(); - builder.addLine( - IKey.str(Translator.translate("vendingmachine.gui.single_coin_type_eject_hint")) - .style(IKey.GRAY, IKey.ITALIC)); - builder.setAutoUpdate(true); - })) - .child( - IKey.dynamic( - () -> getReadableStringFromCoinAmount( - currentAmounts.get(type) == null ? 0 : currentAmounts.get(type))) - .scale(0.8f) - .asWidget() - .top(3) - .left(14) - .width(21)) - .height(14)); + + for (CurrencyType type : CurrencyType.values()) { + coinColumn.child(createCoinDisplay(panel, type, syncManager)); if (++coinCount % COIN_COLUMN_ROW_COUNT == 0) { parent.child(coinColumn.left(3 + COIN_COLUMN_WIDTH * (coinCount / COIN_COLUMN_ROW_COUNT - 1))); coinColumn = new Column().width(COIN_COLUMN_WIDTH); @@ -473,6 +648,34 @@ private IWidget createCoinInventoryRow(TradeMainPanel panel) { return parent; } + private IWidget createCoinDisplay(TradeMainPanel panel, CurrencyType type, PanelSyncManager syncManager) { + IntSyncValue coinSyncValue = syncManager.findSyncHandler("coinAmount_" + type.id, 0, IntSyncValue.class); + return new Row().child( + new CoinButton(panel, type).overlay( + type.texture.asIcon() + .size(12)) + .size(12) + .left(0) + .syncHandler("ejectCoin_" + type.id) + .tooltipDynamic((builder) -> { + builder.clearText(); + builder.addLine(coinSyncValue.getValue() + " " + type.getLocalizedName()); + builder.emptyLine(); + builder.addLine( + IKey.str(Translator.translate("vendingmachine.gui.single_coin_type_eject_hint")) + .style(IKey.GRAY, IKey.ITALIC)); + builder.setAutoUpdate(true); + })) + .child( + IKey.dynamic(() -> getReadableStringFromCoinAmount(coinSyncValue.getValue())) + .scale(0.8f) + .asWidget() + .top(3) + .left(14) + .width(21)) + .height(14); + } + // why is the original method private lmao private IWidget createInventoryRow() { return new Row().widthRel(1) @@ -481,15 +684,16 @@ private IWidget createInventoryRow() { .bottom(5) .childIf( base.doesBindPlayerInventory(), - SlotGroupWidget.playerInventory(false) + () -> SlotGroupWidget.playerInventory(false) .marginLeft(4)); } @Override protected void registerSyncValues(PanelSyncManager syncManager) { super.registerSyncValues(syncManager); - syncManager.registerSlotGroup("inputSlotGroup", 6, true); - syncManager.registerSlotGroup("outputSlotGroup", 4, false); + + syncManager.registerSlotGroup("inputSlotGroup", 2, true); + syncManager.registerSlotGroup("outputSlotGroup", 2, false); BooleanSyncValue ejectItemsSyncer = new BooleanSyncValue(() -> this.ejectItems, val -> { this.ejectItems = val; @@ -497,16 +701,23 @@ protected void registerSyncValues(PanelSyncManager syncManager) { doEjectItems(); } }); + syncManager.syncValue("ejectItems", ejectItemsSyncer); + BooleanSyncValue ejectCoinsSyncer = new BooleanSyncValue(() -> this.ejectCoins, val -> { this.ejectCoins = val; if (this.ejectCoins) { doEjectCoins(); } }); - syncManager.syncValue("ejectItems", ejectItemsSyncer); syncManager.syncValue("ejectCoins", ejectCoinsSyncer); - for (CurrencyItem.CurrencyType type : CurrencyItem.CurrencyType.values()) { + UUID playerId = NameCache.INSTANCE.getUUIDFromPlayer(getBase().getCurrentUser()); + for (CurrencyType type : CurrencyType.values()) { + IntSyncValue coinAmountSyncer = new IntSyncValue( + () -> TradeManager.INSTANCE.playerCurrency.getOrDefault(playerId, Collections.emptyMap()) + .getOrDefault(type, 0)); + syncManager.syncValue("coinAmount_" + type.id, coinAmountSyncer); + BooleanSyncValue ejectCoinSyncer = new BooleanSyncValue(() -> this.ejectSingleCoin.get(type), val -> { this.ejectSingleCoin.put(type, val); if (val) { @@ -515,6 +726,7 @@ protected void registerSyncValues(PanelSyncManager syncManager) { }); syncManager.syncValue("ejectCoin_" + type.id, ejectCoinSyncer); } + } public void attemptPurchase(TradeItemDisplay display) { @@ -533,9 +745,10 @@ public static void resetForceRefresh() { forceRefresh = false; } - public void updateTradeDisplay(Map> trades) { - synchronized (displayedTrades) { - for (Map.Entry> entry : displayedTrades.entrySet()) { + private void updateTradeDisplay(Map> trades, + Map> display) { + synchronized (display) { + for (Map.Entry> entry : display.entrySet()) { int displayedSize = trades.get(entry.getKey()) == null ? 0 : trades.get(entry.getKey()) .size(); @@ -556,10 +769,27 @@ public void updateTradeDisplay(Map> trades } } - public Map> getTradeDisplayData() { + public void updateTradeDisplay(Map> trades) { + if (favouritesTabWidget != null) { + favouritesTabWidget.setEnabled( + !trades.get(TradeCategory.FAVOURITES) + .isEmpty()); + if ( + trades.get(TradeCategory.FAVOURITES) + .isEmpty() && this.tabController.getActivePageIndex() == 0 + ) { + this.tabController.setPage(1); + } + } + this.updateTradeDisplay(trades, displayedTradesTiles); + this.updateTradeDisplay(trades, displayedTradesList); + } + + public Map> getCurrentTradeDisplayData() { Map> currentData = new HashMap<>(); - synchronized (displayedTrades) { - this.displayedTrades.forEach((k, v) -> { + + synchronized (displayedTradesTiles) { + this.displayedTradesTiles.forEach((k, v) -> { currentData.put( k, v.stream() @@ -568,6 +798,18 @@ public Map> getTradeDisplayData() { .collect(Collectors.toList())); }); } + + synchronized (displayedTradesList) { + this.displayedTradesList.forEach((k, v) -> { + currentData.get(k) + .addAll( + v.stream() + .map(TradeItemDisplayWidget::getDisplay) + .filter(Objects::nonNull) + .collect(Collectors.toList())); + }); + } + return currentData; } diff --git a/src/main/java/com/cubefury/vendingmachine/blocks/gui/SearchBar.java b/src/main/java/com/cubefury/vendingmachine/blocks/gui/SearchBar.java index 72de631..b611874 100644 --- a/src/main/java/com/cubefury/vendingmachine/blocks/gui/SearchBar.java +++ b/src/main/java/com/cubefury/vendingmachine/blocks/gui/SearchBar.java @@ -5,9 +5,9 @@ import org.jetbrains.annotations.NotNull; import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; -import com.cleanroommc.modularui.theme.WidgetTextFieldTheme; +import com.cleanroommc.modularui.theme.TextFieldTheme; import com.cleanroommc.modularui.widgets.textfield.BaseTextFieldWidget; -import com.cubefury.vendingmachine.gui.GuiTextures; +import com.cubefury.vendingmachine.gui.WidgetThemes; import com.cubefury.vendingmachine.util.Translator; public class SearchBar extends BaseTextFieldWidget { @@ -19,8 +19,7 @@ public SearchBar(MTEVendingMachineGui gui) { super(); this.gui = gui; - - background(GuiTextures.TEXT_FIELD_BACKGROUND); + widgetTheme(WidgetThemes.BACKGROUND_SEARCH_BAR); setText(""); this.previousText = ""; hintText(Translator.translate("vendingmachine.gui.search")); @@ -69,9 +68,12 @@ public String getText() { } @Override - protected void setupDrawText(ModularGuiContext context, WidgetTextFieldTheme widgetTheme) { + protected void setupDrawText(ModularGuiContext context, TextFieldTheme widgetTheme) { this.renderer.setSimulate(false); - this.renderer.setPos(getArea().getPadding().left, 0); + this.renderer.setPos( + getArea().getPadding() + .getLeft(), + 0); this.renderer.setScale(this.scale); this.renderer.setAlignment(this.textAlignment, -1, getArea().height); } diff --git a/src/main/java/com/cubefury/vendingmachine/blocks/gui/SortMode.java b/src/main/java/com/cubefury/vendingmachine/blocks/gui/SortMode.java new file mode 100644 index 0000000..93d425e --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/blocks/gui/SortMode.java @@ -0,0 +1,31 @@ +package com.cubefury.vendingmachine.blocks.gui; + +import static com.cubefury.vendingmachine.gui.GuiTextures.SORT_ALPHABET; +import static com.cubefury.vendingmachine.gui.GuiTextures.SORT_SMART; + +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.drawable.Icon; +import com.cleanroommc.modularui.drawable.UITexture; + +public enum SortMode { + + SMART("smart", SORT_SMART), + ALPHABET("alphabet", SORT_ALPHABET); + + private String mode; + private Icon texture; + + SortMode(String mode, UITexture texture) { + this.mode = mode; + this.texture = texture.asIcon(); + } + + public String getLocalizedName() { + return IKey.lang("vendingmachine.gui.display_sort_" + this.mode) + .toString(); + } + + public Icon getTexture() { + return this.texture; + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/blocks/gui/TradeItemDisplay.java b/src/main/java/com/cubefury/vendingmachine/blocks/gui/TradeItemDisplay.java index 1845e84..2ac1147 100644 --- a/src/main/java/com/cubefury/vendingmachine/blocks/gui/TradeItemDisplay.java +++ b/src/main/java/com/cubefury/vendingmachine/blocks/gui/TradeItemDisplay.java @@ -14,39 +14,37 @@ public class TradeItemDisplay { public List fromCurrency; public List fromItems; + public List ncItems; public List toItems; public ItemStack display; public UUID tgID; public int tradeGroupOrder; - public String label; public long cooldown; public String cooldownText; public boolean hasCooldown; public boolean enabled; public boolean tradeableNow; + public boolean isFavourite; - public TradeItemDisplay(List fromCurrency, List fromItems, List toItems, - ItemStack display, UUID tgID, int tradeGroupOrder, String label, long cooldown, String cooldownText, - boolean hasCooldown, boolean enabled, boolean tradeableNow) { + public TradeItemDisplay(List fromCurrency, List fromItems, List ncItems, + List toItems, ItemStack display, UUID tgID, int tradeGroupOrder, long cooldown, + String cooldownText, boolean hasCooldown, boolean enabled, boolean tradeableNow) { this.fromCurrency = fromCurrency; this.fromItems = fromItems; + this.ncItems = ncItems; this.toItems = toItems; this.display = display; this.tgID = tgID; this.tradeGroupOrder = tradeGroupOrder; - this.label = label; this.cooldown = cooldown; this.cooldownText = cooldownText; this.hasCooldown = hasCooldown; this.enabled = enabled; this.tradeableNow = tradeableNow; + this.isFavourite = false; } public boolean satisfiesSearch(ItemFilter filter, String searchStringNoCase) { - if (filter == null) { - return this.label.toLowerCase() - .contains(searchStringNoCase); - } return filter.matches(this.display) || this.toItems.stream() .anyMatch(bis -> filter.matches(bis.getBaseStack())) || this.fromItems.stream() diff --git a/src/main/java/com/cubefury/vendingmachine/blocks/gui/TradeItemDisplayWidget.java b/src/main/java/com/cubefury/vendingmachine/blocks/gui/TradeItemDisplayWidget.java index 65641ef..acf10f7 100644 --- a/src/main/java/com/cubefury/vendingmachine/blocks/gui/TradeItemDisplayWidget.java +++ b/src/main/java/com/cubefury/vendingmachine/blocks/gui/TradeItemDisplayWidget.java @@ -5,31 +5,49 @@ import org.jetbrains.annotations.NotNull; import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.value.ISyncOrValue; import com.cleanroommc.modularui.api.value.IValue; import com.cleanroommc.modularui.api.widget.Interactable; import com.cleanroommc.modularui.drawable.DynamicDrawable; import com.cleanroommc.modularui.drawable.GuiDraw; import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; -import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.theme.WidgetThemeEntry; import com.cleanroommc.modularui.utils.Platform; -import com.cleanroommc.modularui.value.sync.GenericSyncValue; -import com.cleanroommc.modularui.value.sync.SyncHandler; +import com.cleanroommc.modularui.value.ObjectValue; import com.cleanroommc.modularui.widgets.ItemDisplayWidget; +import com.cubefury.vendingmachine.blocks.MTEVendingMachine; import com.cubefury.vendingmachine.gui.GuiTextures; +import com.cubefury.vendingmachine.gui.WidgetThemes; +import com.cubefury.vendingmachine.trade.FavouritesTracker; +import com.cubefury.vendingmachine.util.Translator; public class TradeItemDisplayWidget extends ItemDisplayWidget implements Interactable { + private MTEVendingMachine vm; private TradeMainPanel rootPanel; private boolean pressed = false; private IValue value; + public final DisplayType displayType; private TradeItemDisplay display; - public TradeItemDisplayWidget(TradeItemDisplay display) { - height(MTEVendingMachineGui.ITEM_HEIGHT); - width(MTEVendingMachineGui.ITEM_WIDTH); - background( - new DynamicDrawable(() -> pressed ? GuiTextures.TRADE_BUTTON_PRESSED : GuiTextures.TRADE_BUTTON_UNPRESSED)); + public TradeItemDisplayWidget(TradeItemDisplay display, MTEVendingMachine base, DisplayType displayType) { + this.vm = base; + this.displayType = displayType; + widgetTheme(WidgetThemes.THEME_TRADE_BUTTON); + if (displayType == DisplayType.TILE) { + height(MTEVendingMachineGui.TILE_ITEM_HEIGHT); + width(MTEVendingMachineGui.TILE_ITEM_WIDTH); + background( + new DynamicDrawable( + () -> pressed ? GuiTextures.TILE_TRADE_BUTTON_PRESSED : GuiTextures.TILE_TRADE_BUTTON_UNPRESSED)); + } else if (displayType == DisplayType.LIST) { + height(MTEVendingMachineGui.LIST_ITEM_HEIGHT); + width(MTEVendingMachineGui.LIST_ITEM_WIDTH); + background( + new DynamicDrawable( + () -> pressed ? GuiTextures.LIST_TRADE_BUTTON_PRESSED : GuiTextures.LIST_TRADE_BUTTON_UNPRESSED)); + } this.display = display; this.item((ItemStack) null); @@ -40,62 +58,124 @@ public void setDisplay(TradeItemDisplay display) { this.item(display == null ? null : display.display); } + private boolean checkVmActive() { + return this.vm != null && this.vm.getActive(); + } + public TradeItemDisplay getDisplay() { return this.display; } public @NotNull Interactable.Result onMousePressed(int mouseButton) { + // Only do something if either shift or control is held exclusively + if (rootPanel.shiftHeld == rootPanel.ctrlHeld) { + return Result.IGNORE; + } if (rootPanel.shiftHeld) { rootPanel.attemptPurchase(this.display); pressed = true; return Result.SUCCESS; } + if (rootPanel.ctrlHeld) { + FavouritesTracker.INSTANCE.toggleFavourites(this.display.tgID, this.display.tradeGroupOrder); + rootPanel.forceGuiRefresh(); + return Result.SUCCESS; + } return Result.IGNORE; } @Override - public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { + public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { + int textColor = Translator.getColor("vendingmachine.gui.display_text_color"); ItemStack item = value.getValue(); if (!Platform.isStackEmpty(item)) { - GuiDraw.drawText(" " + this.display.display.stackSize, 4, 9, 1.0f, 0x0, false); - GuiDraw.drawItem(item, 26, 4, 16, 16, context.getCurrentDrawingZ()); - if (this.display.tradeableNow) { - GuiDraw.drawOutline(1, 1, 45, 23, 0x883CFF00, 2); - } - if (this.display.hasCooldown || !this.display.enabled) { - GuiDraw.drawRoundedRect( + if (this.displayType == DisplayType.TILE) { + GuiDraw.drawText(" " + this.display.display.stackSize, 4, 9, 1.0f, textColor, false); + GuiDraw.drawItem(item, 26, 4, 16, 16, context.getCurrentDrawingZ()); + if (this.display.tradeableNow) { + GuiDraw.drawBorderInsideLTRB(1, 1, 45, 23, 2, 0x883CFF00); + } + if (this.display.tgID.equals(this.rootPanel.currentSelected)) { + GuiDraw.drawBorderInsideLTRB(1, 1, 45, 23, 1, 0xAA039BE5); + } + if (!this.checkVmActive() || this.display.hasCooldown || !this.display.enabled) { + GuiDraw.drawRoundedRect( + 1, + 1, + MTEVendingMachineGui.TILE_ITEM_WIDTH - 2, + MTEVendingMachineGui.TILE_ITEM_HEIGHT - 2, + 0xBB000000, + 1, + 1); + } + this.overlay( + IKey.str(display.hasCooldown ? this.display.cooldownText : "") + .style(IKey.WHITE)); + if (this.display.isFavourite) { + GuiTextures.FAVOURITE_SPRITE.draw(context, 4, 4, 6, 6, widgetTheme.getTheme()); + } + } else if (this.displayType == DisplayType.LIST) { + GuiDraw.drawText("" + this.display.display.stackSize, 6, 4, 0.9f, textColor, false); + GuiDraw.drawItem(item, 24, 2, 9, 9, context.getCurrentDrawingZ()); + GuiDraw.drawText( + this.display.display.getDisplayName() + .length() > 21 + ? this.display.display.getDisplayName() + .substring(0, 21) + "..." + : this.display.display.getDisplayName(), + 36, + 4, + 0.9f, + textColor, + false); + GuiDraw.drawRect( 1, 1, - MTEVendingMachineGui.ITEM_WIDTH - 2, - MTEVendingMachineGui.ITEM_HEIGHT - 2, - 0xBB000000, - 1, - 1); + 3, + MTEVendingMachineGui.LIST_ITEM_HEIGHT - 3, + this.display.tradeableNow ? 0x883CFF00 : 0x88333333); + if (this.display.tgID.equals(this.rootPanel.currentSelected)) { + GuiDraw.drawRect(1, 1, 2, MTEVendingMachineGui.LIST_ITEM_HEIGHT - 3, 0xAA039BE5); + } + if (!this.checkVmActive() || this.display.hasCooldown || !this.display.enabled) { + GuiDraw.drawRect( + 1, + 1, + MTEVendingMachineGui.LIST_ITEM_WIDTH - 2, + MTEVendingMachineGui.LIST_ITEM_HEIGHT - 2, + 0xBB000000); + } + this.overlay( + IKey.str(display.hasCooldown && this.display.enabled ? this.display.cooldownText : "") + .style(IKey.WHITE) + .scale(0.9f)); + if (this.display.isFavourite) { + GuiTextures.FAVOURITE_SPRITE.draw(context, 139, 2, 10, 10, widgetTheme.getTheme()); + } } - this.overlay( - IKey.str(display.hasCooldown ? this.display.cooldownText : "") - .style(IKey.WHITE)); } } @Override - public boolean isValidSyncHandler(SyncHandler syncHandler) { - if (syncHandler instanceof GenericSyncValuegenericSyncValue && genericSyncValue.isOfType(ItemStack.class)) { - this.value = genericSyncValue.cast(); + public boolean isValidSyncOrValue(@NotNull ISyncOrValue syncOrValue) { + if (syncOrValue instanceof ObjectValuesyncValue && syncValue.isValueOfType(ItemStack.class)) { + this.value = syncValue.castValueNullable(ItemStack.class); return true; + } return false; } public ItemDisplayWidget item(IValue itemSupplier) { this.value = itemSupplier; - setValue(itemSupplier); + setSyncOrValue(itemSupplier); return this; } @Override public void onMouseEndHover() { pressed = false; + super.onMouseEndHover(); } @Override diff --git a/src/main/java/com/cubefury/vendingmachine/blocks/gui/TradeMainPanel.java b/src/main/java/com/cubefury/vendingmachine/blocks/gui/TradeMainPanel.java index c25667b..6d1908e 100644 --- a/src/main/java/com/cubefury/vendingmachine/blocks/gui/TradeMainPanel.java +++ b/src/main/java/com/cubefury/vendingmachine/blocks/gui/TradeMainPanel.java @@ -12,15 +12,18 @@ import net.minecraft.item.ItemStack; import org.jetbrains.annotations.NotNull; +import org.lwjgl.input.Keyboard; import com.cleanroommc.modularui.factory.PosGuiData; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.screen.ModularScreen; import com.cleanroommc.modularui.value.sync.PanelSyncManager; -import com.cubefury.vendingmachine.Config; +import com.cubefury.vendingmachine.VMConfig; import com.cubefury.vendingmachine.network.handlers.NetResetVMUser; +import com.cubefury.vendingmachine.trade.FavouritesTracker; import com.cubefury.vendingmachine.trade.TradeCategory; import com.cubefury.vendingmachine.trade.TradeDatabase; +import com.cubefury.vendingmachine.trade.TradeGroup; import com.cubefury.vendingmachine.trade.TradeManager; import com.cubefury.vendingmachine.util.BigItemStack; @@ -30,11 +33,13 @@ public class TradeMainPanel extends ModularPanel { public boolean shiftHeld = false; + public boolean ctrlHeld = false; private final MTEVendingMachineGui gui; private final PanelSyncManager syncManager; private final PosGuiData guiData; private EntityPlayer player = null; private int ticksOpen = 0; + public UUID currentSelected = null; public TradeMainPanel(@NotNull String name, MTEVendingMachineGui gui, PosGuiData guiData, PanelSyncManager syncManager) { @@ -46,25 +51,30 @@ public TradeMainPanel(@NotNull String name, MTEVendingMachineGui gui, PosGuiData @Override public boolean onKeyPressed(char typedChar, int keyCode) { - // left or right shift - if (keyCode == 0x2A || keyCode == 0x36) { + if (keyCode == Keyboard.KEY_LSHIFT || keyCode == Keyboard.KEY_RSHIFT) { shiftHeld = true; } + if (keyCode == Keyboard.KEY_LCONTROL || keyCode == Keyboard.KEY_RCONTROL) { + ctrlHeld = true; + } return super.onKeyPressed(typedChar, keyCode); } @Override public boolean onKeyRelease(char typedChar, int keyCode) { - // left or right shift - if (keyCode == 0x2A || keyCode == 0x36) { + if (keyCode == Keyboard.KEY_LSHIFT || keyCode == Keyboard.KEY_RSHIFT) { shiftHeld = false; } + if (keyCode == Keyboard.KEY_LCONTROL || keyCode == Keyboard.KEY_RCONTROL) { + ctrlHeld = false; + gui.setForceRefresh(); + } return super.onKeyRelease(typedChar, keyCode); } public void updateGui() { - if (shiftHeld) { - this.updateTradeInformation(gui.getTradeDisplayData()); + if (shiftHeld || ctrlHeld) { + this.updateTradeInformation(gui.getCurrentTradeDisplayData()); } else { Map> trades = formatTrades(); gui.updateTradeDisplay(trades); @@ -73,6 +83,12 @@ public void updateGui() { private void updateTradeInformation(Map> currentData) { Map> tradeMap = new HashMap<>(); + List favouritedTrades = FavouritesTracker.INSTANCE + .filterTrades(currentData.get(TradeCategory.ALL)); + if (gui.favouritesTabWidget != null) { + gui.favouritesTabWidget.setEnabled(!favouritedTrades.isEmpty()); + } + currentData.put(TradeCategory.FAVOURITES, favouritedTrades); for (TradeItemDisplay tid : TradeManager.INSTANCE.tradeData) { tradeMap.putIfAbsent(tid.tgID, new HashMap<>()); tradeMap.get(tid.tgID) @@ -88,6 +104,7 @@ private void updateTradeInformation(Map> c tid.cooldown = cur.cooldown; tid.cooldownText = cur.cooldownText; tid.tradeableNow = cur.tradeableNow; + tid.isFavourite = FavouritesTracker.INSTANCE.isFavourite(tid); } }); } @@ -106,12 +123,26 @@ public void onUpdate() { MTEVendingMachineGui.setForceRefresh(); } if ( - MTEVendingMachineGui.forceRefresh || (this.ticksOpen % Config.gui_refresh_interval == 0 && player != null) + MTEVendingMachineGui.forceRefresh + || (this.ticksOpen % VMConfig.vendingMachineSettings.gui_refresh_interval == 0 && player != null) ) { updateGui(); MTEVendingMachineGui.resetForceRefresh(); TradeManager.INSTANCE.hasCurrencyUpdate = false; } + TradeCategory activeCategory = gui.getActiveTradeCategory(); + Map> displayedTrades = VMConfig.gui.display_type == DisplayType.TILE + ? gui.displayedTradesTiles + : gui.displayedTradesList; + + this.currentSelected = null; + for (TradeItemDisplayWidget display : displayedTrades.get(activeCategory)) { + if (display.isBelowMouse()) { + this.currentSelected = display.getDisplay().tgID; + break; + } + } + this.ticksOpen += 1; } @@ -125,9 +156,15 @@ public ItemStack convertToItemStack(BigItemStack stack) { public Map> formatTrades() { Map> trades = new HashMap<>(); trades.put(TradeCategory.ALL, new ArrayList<>()); + SortMode sortMode = VMConfig.gui.sort_mode; + for (TradeItemDisplay tid : TradeManager.INSTANCE.tradeData) { - TradeCategory category = TradeDatabase.INSTANCE.getTradeGroupFromId(tid.tgID) - .getCategory(); + TradeGroup group = TradeDatabase.INSTANCE.getTradeGroupFromId(tid.tgID); + if (group == null) { + continue; + } + tid.isFavourite = FavouritesTracker.INSTANCE.isFavourite(tid); + TradeCategory category = group.getCategory(); trades.putIfAbsent(category, new ArrayList<>()); trades.get(category) .add(tid); @@ -152,31 +189,49 @@ public Map> formatTrades() { if (a.display.getItem() == null) return 1; if (b.display.getItem() == null) return -1; - // enabled or has cooldown - int rankA = getRank(a); - int rankB = getRank(b); + if (sortMode == SortMode.ALPHABET) { + if (a.isFavourite != b.isFavourite) { + return Boolean.compare(b.isFavourite, a.isFavourite); + } + return (a.display.getDisplayName() + .compareTo(b.display.getDisplayName())); + } else if (sortMode == SortMode.SMART) { + // favourited + if (a.isFavourite != b.isFavourite) { + return Boolean.compare(b.isFavourite, a.isFavourite); + } - if (rankA != rankB) { - return Integer.compare(rankA, rankB); - } + // enabled or has cooldown + int rankA = getRank(a); + int rankB = getRank(b); - // cooldown time - int cooldownCmp = Long.compare(b.cooldown, a.cooldown); - if (cooldownCmp != 0) return cooldownCmp; + if (rankA != rankB) { + return Integer.compare(rankA, rankB); + } - // display item ordering - int idCmp = Integer - .compare(Item.getIdFromItem(a.display.getItem()), Item.getIdFromItem(b.display.getItem())); - if (idCmp != 0) return idCmp; - int dmgCmp = Integer.compare(a.display.getItemDamage(), b.display.getItemDamage()); - if (dmgCmp != 0) return dmgCmp; + // cooldown time + int cooldownCmp = Long.compare(b.cooldown, a.cooldown); + if (cooldownCmp != 0) return cooldownCmp; - // sort by tradegroup Order - return Integer.compare(a.tradeGroupOrder, b.tradeGroupOrder); + // display item ordering + int idCmp = Integer + .compare(Item.getIdFromItem(a.display.getItem()), Item.getIdFromItem(b.display.getItem())); + if (idCmp != 0) return idCmp; + int dmgCmp = Integer.compare(a.display.getItemDamage(), b.display.getItemDamage()); + if (dmgCmp != 0) return dmgCmp; + // sort by tradegroup Order + return Integer.compare(a.tradeGroupOrder, b.tradeGroupOrder); + } + + // impossible + return 0; }); trades.replace(category, filteredTrades); } + List favouritedTrades = FavouritesTracker.INSTANCE + .filterTrades(trades.get(TradeCategory.ALL)); + trades.put(TradeCategory.FAVOURITES, favouritedTrades); return trades; } @@ -194,6 +249,10 @@ public void attemptPurchase(TradeItemDisplay display) { gui.attemptPurchase(display); } + public void forceGuiRefresh() { + gui.setForceRefresh(); + } + @Override public void dispose() { this.gui.getBase() diff --git a/src/main/java/com/cubefury/vendingmachine/command/CommandVending.java b/src/main/java/com/cubefury/vendingmachine/command/CommandVending.java new file mode 100644 index 0000000..11ce899 --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/command/CommandVending.java @@ -0,0 +1,73 @@ +package com.cubefury.vendingmachine.command; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.minecraft.command.CommandBase; +import net.minecraft.command.ICommandSender; +import net.minecraft.util.ChatComponentText; + +import com.cubefury.vendingmachine.command.vending.IVendingSubcommand; +import com.cubefury.vendingmachine.command.vending.SubCmdAdd; +import com.cubefury.vendingmachine.command.vending.SubCmdReload; +import com.cubefury.vendingmachine.command.vending.SubCmdReset; +import com.cubefury.vendingmachine.command.vending.SubCmdSet; + +public class CommandVending extends CommandBase { + + private static final Map SUBCOMMAND_MAP = new HashMap<>(); + + static { + register(new SubCmdAdd()); + register(new SubCmdSet()); + register(new SubCmdReset()); + register(new SubCmdReload()); + } + + private static void register(IVendingSubcommand cmd) { + SUBCOMMAND_MAP.put(cmd.getName(), cmd); + } + + @Override + public String getCommandName() { + return "vending"; + } + + @Override + public String getCommandUsage(ICommandSender sender) { + return "/vending <" + String.join("|", SUBCOMMAND_MAP.keySet()) + "> [...] "; + } + + @Override + public boolean canCommandSenderUseCommand(ICommandSender sender) { + return sender.canCommandSenderUseCommand(getRequiredPermissionLevel(), getCommandName()); + } + + @Override + public List addTabCompletionOptions(ICommandSender sender, String[] args) { + if (args.length == 1) { + return getListOfStringsFromIterableMatchingLastWord(args, SUBCOMMAND_MAP.keySet()); + } + if (args.length > 1) { + IVendingSubcommand sub = SUBCOMMAND_MAP.get(args[0]); + if (sub == null) { + return null; + } + return sub.tabComplete(sender, Arrays.copyOfRange(args, 1, args.length)); + } + return null; + } + + @Override + public void processCommand(ICommandSender sender, String[] args) { + if (args.length > 0 && SUBCOMMAND_MAP.containsKey(args[0])) { + SUBCOMMAND_MAP.get(args[0]) + .execute(sender, Arrays.copyOfRange(args, 1, args.length)); + } else { + sender.addChatMessage(new ChatComponentText("Usage: " + getCommandUsage(sender))); + } + } + +} diff --git a/src/main/java/com/cubefury/vendingmachine/command/Utils.java b/src/main/java/com/cubefury/vendingmachine/command/Utils.java new file mode 100644 index 0000000..19b5f96 --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/command/Utils.java @@ -0,0 +1,45 @@ +package com.cubefury.vendingmachine.command; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import net.minecraft.server.MinecraftServer; + +import com.cubefury.vendingmachine.trade.CurrencyType; + +public class Utils { + + public static List getCoinTypes() { + return Arrays.stream(CurrencyType.values()) + .map(c -> c.id) + .collect(Collectors.toList()); + } + + public static List getCoinTypesOrAll() { + List types = getCoinTypes(); + types.add("all"); + return types; + } + + public static List getCurrentPlayers() { + return Arrays.stream( + MinecraftServer.getServer() + .getAllUsernames()) + .collect(Collectors.toList()); + } + + public static List getPlayersAndCoinTypesOrAll() { + return Stream.concat(getCoinTypesOrAll().stream(), getCurrentPlayers().stream()) + .collect(Collectors.toList()); + } + + public static int parseAmount(String arg) { + try { + return Integer.parseInt(arg); + } catch (NumberFormatException except) { + return 0; + } + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/command/vending/IVendingSubcommand.java b/src/main/java/com/cubefury/vendingmachine/command/vending/IVendingSubcommand.java new file mode 100644 index 0000000..426538a --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/command/vending/IVendingSubcommand.java @@ -0,0 +1,17 @@ +package com.cubefury.vendingmachine.command.vending; + +import java.util.List; + +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; + +public interface IVendingSubcommand { + + String getName(); + + String getUsage(ICommandSender sender); + + void execute(ICommandSender sender, String[] args) throws CommandException; + + List tabComplete(ICommandSender sender, String[] args); +} diff --git a/src/main/java/com/cubefury/vendingmachine/command/vending/SubCmdAdd.java b/src/main/java/com/cubefury/vendingmachine/command/vending/SubCmdAdd.java new file mode 100644 index 0000000..6d6d718 --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/command/vending/SubCmdAdd.java @@ -0,0 +1,103 @@ +package com.cubefury.vendingmachine.command.vending; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.command.PlayerNotFoundException; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.util.ChatComponentText; + +import com.cubefury.vendingmachine.command.Utils; +import com.cubefury.vendingmachine.storage.NameCache; +import com.cubefury.vendingmachine.trade.CurrencyType; +import com.cubefury.vendingmachine.trade.TradeManager; + +public class SubCmdAdd implements IVendingSubcommand { + + @Override + public String getName() { + return "add"; + } + + @Override + public String getUsage(ICommandSender sender) { + return "/vending add [player] "; + } + + @Override + public void execute(ICommandSender sender, String[] args) throws CommandException { + EntityPlayerMP target = null; + boolean allCurrency = false; + CurrencyType type = null; + int amount = 0; + + switch (args.length) { + case 2: { + target = CommandBase.getCommandSenderAsPlayer(sender); + allCurrency = args[0].equals("all"); + type = CurrencyType.getTypeFromId(args[0]); + amount = Utils.parseAmount(args[1]); + break; + } + case 3: { + try { + target = CommandBase.getPlayer(sender, args[0]); + allCurrency = args[1].equals("all"); + type = CurrencyType.getTypeFromId(args[1]); + amount = Utils.parseAmount(args[2]); + } catch (PlayerNotFoundException ignored) {} + break; + } + default: + } + boolean validCurrency = allCurrency || type != null; + if (target == null || !validCurrency || amount == 0) { + sender.addChatMessage(new ChatComponentText("Usage: " + getUsage(sender))); + return; + } + + UUID playerId = NameCache.INSTANCE.getUUIDFromPlayer(target); + TradeManager.INSTANCE.playerCurrency.putIfAbsent(playerId, new HashMap<>()); + Map coinInventory = TradeManager.INSTANCE.playerCurrency.get(playerId); + if (allCurrency) { + for (CurrencyType cur : CurrencyType.values()) { + coinInventory.put(cur, coinInventory.getOrDefault(cur, 0) + amount); + } + sender.addChatMessage( + new ChatComponentText(String.format("Added %dx all coins for %s", amount, target.getDisplayName()))); + } else { + coinInventory.put(type, coinInventory.getOrDefault(type, 0) + amount); + sender.addChatMessage( + new ChatComponentText(String.format("Added %dx %s for %s", amount, type.id, target.getDisplayName()))); + } + } + + @Override + public List tabComplete(ICommandSender sender, String[] args) { + switch (args.length) { + case 0: { + return Utils.getPlayersAndCoinTypesOrAll(); + } + case 1: { + return CommandBase + .getListOfStringsFromIterableMatchingLastWord(args, Utils.getPlayersAndCoinTypesOrAll()); + } + case 2: { + if ( + Utils.getCurrentPlayers() + .contains(args[0]) + ) { + return CommandBase.getListOfStringsFromIterableMatchingLastWord(args, Utils.getCoinTypesOrAll()); + } + } + default: { + return null; + } + } + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/command/vending/SubCmdReload.java b/src/main/java/com/cubefury/vendingmachine/command/vending/SubCmdReload.java new file mode 100644 index 0000000..51d1ba8 --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/command/vending/SubCmdReload.java @@ -0,0 +1,93 @@ +package com.cubefury.vendingmachine.command.vending; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.ChatComponentText; + +import com.cleanroommc.modularui.factory.PosGuiData; +import com.cleanroommc.modularui.screen.ModularContainer; +import com.cubefury.vendingmachine.blocks.MTEVendingMachine; +import com.cubefury.vendingmachine.handlers.SaveLoadHandler; +import com.cubefury.vendingmachine.network.handlers.NetTradeDbSync; +import com.cubefury.vendingmachine.storage.NameCache; +import com.cubefury.vendingmachine.trade.TradeManager; + +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; + +public class SubCmdReload implements IVendingSubcommand { + + @Override + public String getName() { + return "reload"; + } + + @Override + public String getUsage(ICommandSender sender) { + return "/vending reload database, /vending reload tradestate [player]"; + } + + @Override + public void execute(ICommandSender sender, String[] args) throws CommandException { + if (args.length == 1 && args[0].equals("database")) { + SaveLoadHandler.INSTANCE.reloadDatabase(); + NetTradeDbSync.sendDatabase(null, false); + + sender.addChatMessage(new ChatComponentText("Reloaded Trade Database")); + } else if ((args.length == 1 || args.length == 2) && args[0].equals("tradestate")) { + UUID target = args.length == 1 + ? NameCache.INSTANCE.getUUIDFromPlayer(CommandBase.getCommandSenderAsPlayer(sender)) + : NameCache.INSTANCE.getUUID(args[1]); + if (target == null) { + sender.addChatMessage(new ChatComponentText("Could not resolve UUID of player.")); + return; + } + MinecraftServer server = MinecraftServer.getServer(); + if (server != null) { + EntityPlayerMP player = server.getConfigurationManager() + .func_152612_a(NameCache.INSTANCE.getName(target)); + if ( + player != null && player.openContainer instanceof ModularContainer container + && container.getGuiData() instanceof PosGuiData guiData + && guiData.getTileEntity() instanceof IGregTechTileEntity gte + && gte.getMetaTileEntity() instanceof MTEVendingMachine + ) { + sender.addChatMessage( + new ChatComponentText( + "Cannot reload trade state for a player currently accessing a vending machine.")); + return; + } + } + TradeManager.INSTANCE.clearTradeState(target); + SaveLoadHandler.INSTANCE.loadTradeState(target); + sender.addChatMessage( + new ChatComponentText("Reloaded trade state for " + NameCache.INSTANCE.getName(target))); + } else { + sender.addChatMessage(new ChatComponentText("Usage: " + getUsage(sender))); + } + } + + @Override + public List tabComplete(ICommandSender sender, String[] args) { + switch (args.length) { + case 1: { + return CommandBase + .getListOfStringsFromIterableMatchingLastWord(args, Arrays.asList("database", "tradestate")); + } + case 2: { + return args[0].equals("tradestate") + ? CommandBase.getListOfStringsFromIterableMatchingLastWord(args, NameCache.INSTANCE.getAllNames()) + : null; + } + default: { + return null; + } + } + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/command/vending/SubCmdReset.java b/src/main/java/com/cubefury/vendingmachine/command/vending/SubCmdReset.java new file mode 100644 index 0000000..b705c3b --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/command/vending/SubCmdReset.java @@ -0,0 +1,98 @@ +package com.cubefury.vendingmachine.command.vending; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.command.PlayerNotFoundException; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.util.ChatComponentText; + +import com.cubefury.vendingmachine.command.Utils; +import com.cubefury.vendingmachine.storage.NameCache; +import com.cubefury.vendingmachine.trade.CurrencyType; +import com.cubefury.vendingmachine.trade.TradeManager; + +public class SubCmdReset implements IVendingSubcommand { + + @Override + public String getName() { + return "reset"; + } + + @Override + public String getUsage(ICommandSender sender) { + return "/vending reset [player] "; + } + + @Override + public void execute(ICommandSender sender, String[] args) throws CommandException { + EntityPlayerMP target = null; + boolean allCurrency = false; + CurrencyType type = null; + + switch (args.length) { + case 1: { + target = CommandBase.getCommandSenderAsPlayer(sender); + allCurrency = args[0].equals("all"); + type = CurrencyType.getTypeFromId(args[0]); + break; + } + case 2: { + try { + target = CommandBase.getPlayer(sender, args[0]); + allCurrency = args[1].equals("all"); + type = CurrencyType.getTypeFromId(args[1]); + } catch (PlayerNotFoundException ignored) {} + break; + } + default: + } + boolean validCurrency = allCurrency || type != null; + if (target == null || !validCurrency) { + sender.addChatMessage(new ChatComponentText("Usage: " + getUsage(sender))); + return; + } + + UUID playerId = NameCache.INSTANCE.getUUIDFromPlayer(target); + TradeManager.INSTANCE.playerCurrency.putIfAbsent(playerId, new HashMap<>()); + Map coinInventory = TradeManager.INSTANCE.playerCurrency.get(playerId); + if (allCurrency) { + coinInventory.clear(); + sender.addChatMessage( + new ChatComponentText(String.format("Reset all coins for %s", target.getDisplayName()))); + } else { + coinInventory.remove(type); + sender.addChatMessage( + new ChatComponentText(String.format("Reset %s for %s", type.id, target.getDisplayName()))); + } + } + + @Override + public List tabComplete(ICommandSender sender, String[] args) { + switch (args.length) { + case 0: { + return Utils.getPlayersAndCoinTypesOrAll(); + } + case 1: { + return CommandBase + .getListOfStringsFromIterableMatchingLastWord(args, Utils.getPlayersAndCoinTypesOrAll()); + } + case 2: { + if ( + Utils.getCurrentPlayers() + .contains(args[0]) + ) { + return CommandBase.getListOfStringsFromIterableMatchingLastWord(args, Utils.getCoinTypesOrAll()); + } + } + default: { + return null; + } + } + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/command/vending/SubCmdSet.java b/src/main/java/com/cubefury/vendingmachine/command/vending/SubCmdSet.java new file mode 100644 index 0000000..37ad7e2 --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/command/vending/SubCmdSet.java @@ -0,0 +1,103 @@ +package com.cubefury.vendingmachine.command.vending; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.command.PlayerNotFoundException; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.util.ChatComponentText; + +import com.cubefury.vendingmachine.command.Utils; +import com.cubefury.vendingmachine.storage.NameCache; +import com.cubefury.vendingmachine.trade.CurrencyType; +import com.cubefury.vendingmachine.trade.TradeManager; + +public class SubCmdSet implements IVendingSubcommand { + + @Override + public String getName() { + return "set"; + } + + @Override + public String getUsage(ICommandSender sender) { + return "/vending set [player] "; + } + + @Override + public void execute(ICommandSender sender, String[] args) throws CommandException { + EntityPlayerMP target = null; + boolean allCurrency = false; + CurrencyType type = null; + int amount = 0; + + switch (args.length) { + case 2: { + target = CommandBase.getCommandSenderAsPlayer(sender); + allCurrency = args[0].equals("all"); + type = CurrencyType.getTypeFromId(args[0]); + amount = Utils.parseAmount(args[1]); + break; + } + case 3: { + try { + target = CommandBase.getPlayer(sender, args[0]); + allCurrency = args[1].equals("all"); + type = CurrencyType.getTypeFromId(args[1]); + amount = Utils.parseAmount(args[2]); + } catch (PlayerNotFoundException ignored) {} + break; + } + default: + } + boolean validCurrency = allCurrency || type != null; + if (target == null || !validCurrency || amount == 0) { + sender.addChatMessage(new ChatComponentText("Usage: " + getUsage(sender))); + return; + } + + UUID playerId = NameCache.INSTANCE.getUUIDFromPlayer(target); + TradeManager.INSTANCE.playerCurrency.putIfAbsent(playerId, new HashMap<>()); + Map coinInventory = TradeManager.INSTANCE.playerCurrency.get(playerId); + if (allCurrency) { + for (CurrencyType cur : CurrencyType.values()) { + coinInventory.put(cur, amount); + } + sender.addChatMessage( + new ChatComponentText(String.format("Set all coins = %d for %s", amount, target.getDisplayName()))); + } else { + coinInventory.put(type, amount); + sender.addChatMessage( + new ChatComponentText(String.format("Set %s = %d for %s", type.id, amount, target.getDisplayName()))); + } + } + + @Override + public List tabComplete(ICommandSender sender, String[] args) { + switch (args.length) { + case 0: { + return Utils.getPlayersAndCoinTypesOrAll(); + } + case 1: { + return CommandBase + .getListOfStringsFromIterableMatchingLastWord(args, Utils.getPlayersAndCoinTypesOrAll()); + } + case 2: { + if ( + Utils.getCurrentPlayers() + .contains(args[0]) + ) { + return CommandBase.getListOfStringsFromIterableMatchingLastWord(args, Utils.getCoinTypesOrAll()); + } + } + default: { + return null; + } + } + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/gui/GuiTextures.java b/src/main/java/com/cubefury/vendingmachine/gui/GuiTextures.java index 8ee5829..1948d19 100644 --- a/src/main/java/com/cubefury/vendingmachine/gui/GuiTextures.java +++ b/src/main/java/com/cubefury/vendingmachine/gui/GuiTextures.java @@ -1,6 +1,9 @@ package com.cubefury.vendingmachine.gui; +import net.minecraft.util.ResourceLocation; + import com.cleanroommc.modularui.api.GuiAxis; +import com.cleanroommc.modularui.drawable.ColorType; import com.cleanroommc.modularui.drawable.TabTexture; import com.cleanroommc.modularui.drawable.UITexture; import com.cubefury.vendingmachine.VendingMachine; @@ -45,20 +48,63 @@ public final class GuiTextures { .name("text_field_background") .build(); - public static final UITexture TRADE_BUTTON_UNPRESSED = UITexture.builder() - .location(VendingMachine.MODID, "gui/background/trade_button_unpressed_color_corrected") + // TODO: Restore canApplyTheme to trade button textures after scrolling texture bug is fixed in MUI2 + public static final UITexture TILE_TRADE_BUTTON_UNPRESSED = UITexture.builder() + .location(VendingMachine.MODID, "gui/background/trade_button_unpressed") + .canApplyTheme() .imageSize(195, 136) .adaptable(4) .name("trade_button_unpressed") .build(); - public static final UITexture TRADE_BUTTON_PRESSED = UITexture.builder() - .location(VendingMachine.MODID, "gui/background/trade_button_pressed_color_corrected") + public static final UITexture TILE_TRADE_BUTTON_PRESSED = UITexture.builder() + .location(VendingMachine.MODID, "gui/background/trade_button_pressed") + .canApplyTheme() .imageSize(195, 136) .adaptable(4) .name("trade_button_pressed") .build(); + public static final UITexture LIST_TRADE_BUTTON_UNPRESSED = UITexture.builder() + .location(VendingMachine.MODID, "gui/background/list_trade_button_unpressed") + .canApplyTheme() + .imageSize(195, 136) + .adaptable(2) + .name("list_trade_button_unpressed") + .build(); + + public static final UITexture LIST_TRADE_BUTTON_PRESSED = UITexture.builder() + .location(VendingMachine.MODID, "gui/background/list_trade_button_pressed") + .canApplyTheme() + .imageSize(195, 136) + .adaptable(2) + .name("list_trade_button_pressed") + .build(); + + public static final UITexture MODE_TILE = UITexture.builder() + .location(VendingMachine.MODID, "gui/overlay/mode_tile") + .imageSize(32, 32) + .name("mode_tile") + .build(); + + public static final UITexture MODE_LIST = UITexture.builder() + .location(VendingMachine.MODID, "gui/overlay/mode_list") + .imageSize(32, 32) + .name("mode_list") + .build(); + + public static final UITexture SORT_SMART = UITexture.builder() + .location(VendingMachine.MODID, "gui/overlay/sort_smart") + .imageSize(32, 32) + .name("sort_smart") + .build(); + + public static final UITexture SORT_ALPHABET = UITexture.builder() + .location(VendingMachine.MODID, "gui/overlay/sort_alphabet") + .imageSize(32, 32) + .name("sort_alphabet") + .build(); + public static final UITexture INPUT_SPRITE = UITexture.builder() .location(VendingMachine.MODID, "gui/background/input") .imageSize(30, 20) @@ -77,6 +123,19 @@ public final class GuiTextures { .name("coin_eject") .build(); - public static final TabTexture TAB_LEFT = TabTexture - .of(UITexture.fullImage(VendingMachine.MODID, "gui/tabs_left", true), GuiAxis.X, false, 32, 28, 4); + public static final TabTexture TAB_LEFT = TabTexture.of( + UITexture.fullImage(new ResourceLocation(VendingMachine.MODID, "gui/tabs_left"), ColorType.DEFAULT), + GuiAxis.X, + false, + 32, + 28, + 4); + + public static final UITexture FAVOURITE_SPRITE = UITexture.builder() + .location(VendingMachine.MODID, "gui/icons/favourite_indicator") + .imageSize(16, 16) + .fullImage() + .name("favourite_indicator") + .build(); + } diff --git a/src/main/java/com/cubefury/vendingmachine/gui/WidgetThemes.java b/src/main/java/com/cubefury/vendingmachine/gui/WidgetThemes.java index caf00cf..3aa882f 100644 --- a/src/main/java/com/cubefury/vendingmachine/gui/WidgetThemes.java +++ b/src/main/java/com/cubefury/vendingmachine/gui/WidgetThemes.java @@ -1,23 +1,42 @@ package com.cubefury.vendingmachine.gui; import com.cleanroommc.modularui.api.IThemeApi; -import com.cleanroommc.modularui.drawable.UITexture; +import com.cleanroommc.modularui.theme.TextFieldTheme; import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.theme.WidgetThemeKey; import com.cleanroommc.modularui.utils.Color; public final class WidgetThemes { - public static final String BACKGROUND_SIDEPANEL = "background_side_panel"; + public static void init() {} - public static void register() { - IThemeApi themeApi = IThemeApi.get(); - registerThemedTexture(themeApi, BACKGROUND_SIDEPANEL, GuiTextures.SIDE_PANEL_BACKGROUND); - } + private static final IThemeApi themeApi = IThemeApi.get(); - private static void registerThemedTexture(IThemeApi themeApi, String textureThemeId, UITexture background) { - themeApi.registerWidgetTheme( - textureThemeId, - new WidgetTheme(background, null, Color.WHITE.main, 0xFF404040, false), - WidgetTheme::new); - } + public static final WidgetThemeKey BACKGROUND_SIDEPANEL = themeApi + .widgetThemeKeyBuilder("background_side_panel", WidgetTheme.class) + .defaultTheme(new WidgetTheme(0, 0, GuiTextures.SIDE_PANEL_BACKGROUND, Color.WHITE.main, 0xFF404040, false, 0)) + .defaultHoverTheme(null) + .register(); + + public static final WidgetThemeKey BACKGROUND_SEARCH_BAR = themeApi + .widgetThemeKeyBuilder("background_search_bar", TextFieldTheme.class) + .defaultTheme( + new TextFieldTheme( + 0, + 0, + GuiTextures.TEXT_FIELD_BACKGROUND, + Color.WHITE.main, + 0xFF404040, + false, + 0, + 0, + 0xFF404040)) + .defaultHoverTheme(null) + .register(); + + public static final WidgetThemeKey THEME_TRADE_BUTTON = themeApi + .widgetThemeKeyBuilder("background_tile_trade_button", WidgetTheme.class) + .defaultTheme(new WidgetTheme(0, 0, null, Color.WHITE.main, 0xFF404040, false, 0)) + .defaultHoverTheme(null) + .register(); } diff --git a/src/main/java/com/cubefury/vendingmachine/gui/client/VMGuiClientConfig.java b/src/main/java/com/cubefury/vendingmachine/gui/client/VMGuiClientConfig.java new file mode 100644 index 0000000..e5b6d45 --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/gui/client/VMGuiClientConfig.java @@ -0,0 +1,15 @@ +package com.cubefury.vendingmachine.gui.client; + +import net.minecraft.client.gui.GuiScreen; + +import com.cubefury.vendingmachine.VMConfig; +import com.cubefury.vendingmachine.VendingMachine; +import com.gtnewhorizon.gtnhlib.config.ConfigException; +import com.gtnewhorizon.gtnhlib.config.SimpleGuiConfig; + +public class VMGuiClientConfig extends SimpleGuiConfig { + + public VMGuiClientConfig(GuiScreen parentScreen) throws ConfigException { + super(parentScreen, VendingMachine.MODID, VendingMachine.NAME, true, VMConfig.class); + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/gui/client/VMGuiFactory.java b/src/main/java/com/cubefury/vendingmachine/gui/client/VMGuiFactory.java new file mode 100644 index 0000000..4aff084 --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/gui/client/VMGuiFactory.java @@ -0,0 +1,13 @@ +package com.cubefury.vendingmachine.gui.client; + +import net.minecraft.client.gui.GuiScreen; + +import com.gtnewhorizon.gtnhlib.config.SimpleGuiFactory; + +public class VMGuiFactory implements SimpleGuiFactory { + + @Override + public Class mainConfigGuiClass() { + return VMGuiClientConfig.class; + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/handlers/ClientEventHandler.java b/src/main/java/com/cubefury/vendingmachine/handlers/ClientEventHandler.java new file mode 100644 index 0000000..d847b6c --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/handlers/ClientEventHandler.java @@ -0,0 +1,37 @@ +package com.cubefury.vendingmachine.handlers; + +import net.minecraft.client.Minecraft; + +import com.cubefury.vendingmachine.storage.NameCache; +import com.cubefury.vendingmachine.trade.FavouritesTracker; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.TickEvent; +import cpw.mods.fml.common.network.FMLNetworkEvent; + +public class ClientEventHandler { + + public static final ClientEventHandler INSTANCE = new ClientEventHandler(); + private static boolean pendingWorldInit = false; + + @SubscribeEvent + public void onClientConnect(FMLNetworkEvent.ClientConnectedToServerEvent event) { + pendingWorldInit = true; + } + + @SubscribeEvent + public void onClientTick(TickEvent.ClientTickEvent event) { + if (event.phase != TickEvent.Phase.END) return; + + if (pendingWorldInit) { + Minecraft mc = Minecraft.getMinecraft(); + + if (mc.theWorld != null) { + SaveLoadHandler.INSTANCE.readFavourites( + NameCache.INSTANCE.getUUIDFromPlayer(mc.thePlayer), + FavouritesTracker.INSTANCE.computeWorldKey()); + pendingWorldInit = false; + } + } + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/handlers/EventHandler.java b/src/main/java/com/cubefury/vendingmachine/handlers/EventHandler.java index 06febcf..82c592e 100644 --- a/src/main/java/com/cubefury/vendingmachine/handlers/EventHandler.java +++ b/src/main/java/com/cubefury/vendingmachine/handlers/EventHandler.java @@ -2,7 +2,6 @@ import java.util.ArrayDeque; import java.util.Collections; -import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; @@ -12,13 +11,13 @@ import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.server.MinecraftServer; import net.minecraft.server.integrated.IntegratedServer; -import net.minecraft.tileentity.TileEntity; import net.minecraftforge.event.entity.living.LivingDeathEvent; import org.apache.commons.lang3.Validate; import com.cleanroommc.modularui.factory.PosGuiData; import com.cleanroommc.modularui.screen.ModularContainer; +import com.cubefury.vendingmachine.VMConfig; import com.cubefury.vendingmachine.VendingMachine; import com.cubefury.vendingmachine.blocks.MTEVendingMachine; import com.cubefury.vendingmachine.events.MarkDirtyDbEvent; @@ -26,6 +25,7 @@ import com.cubefury.vendingmachine.network.handlers.NetBulkSync; import com.cubefury.vendingmachine.network.handlers.NetTradeDbSync; import com.cubefury.vendingmachine.storage.NameCache; +import com.cubefury.vendingmachine.trade.TradeManager; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFutureTask; @@ -59,11 +59,9 @@ public void onMarkDirtyNames(MarkDirtyNamesEvent event) { public void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { if ( event.player.worldObj.isRemote || MinecraftServer.getServer() == null - || !(event.player instanceof EntityPlayerMP) + || !(event.player instanceof EntityPlayerMP mpPlayer) ) return; - EntityPlayerMP mpPlayer = (EntityPlayerMP) event.player; - if ( VendingMachine.proxy.isClient() && !MinecraftServer.getServer() .isDedicatedServer() @@ -78,8 +76,6 @@ public void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { } NetBulkSync.sendReset(mpPlayer, true, true); - - UUID playerId = NameCache.INSTANCE.getUUIDFromPlayer(mpPlayer); } @SubscribeEvent @@ -131,7 +127,7 @@ public void onServerTick(TickEvent.ServerTickEvent event) { if (!server.isDedicatedServer()) { boolean tmp = openToLAN; - openToLAN = server instanceof IntegratedServer && ((IntegratedServer) server).getPublic(); + openToLAN = server instanceof IntegratedServer iServer && iServer.getPublic(); if (openToLAN && !tmp) opQueue.addAll(server.getConfigurationManager().playerEntityList); } else if (!openToLAN) { openToLAN = true; @@ -143,27 +139,35 @@ public void onServerTick(TickEvent.ServerTickEvent event) { NameCache.INSTANCE.updateName(playerMP); } } - } - private void terminateVendingSession(@Nonnull EntityPlayer player) { - VendingMachine.LOG.info("terminating session for {}", player); - if (VendingMachine.proxy.isClient()) { - return; + for (EntityPlayerMP player : server.getConfigurationManager().playerEntityList) { + livingPlayerTick(player); } + } + + private void livingPlayerTick(@Nonnull EntityPlayerMP player) { if ( - !(player.openContainer instanceof ModularContainer - && ((ModularContainer) player.openContainer).getGuiData() instanceof PosGuiData) + !VMConfig.vendingMachineSettings.restock_notifications_enabled || player.ticksExisted == 0 + || player.ticksExisted % VMConfig.vendingMachineSettings.restock_notifications_interval != 0 ) { return; } - TileEntity te = ((PosGuiData) ((ModularContainer) player.openContainer).getGuiData()).getTileEntity(); + TradeManager.INSTANCE.sendTradeNotifications(player); + } + + private void terminateVendingSession(@Nonnull EntityPlayer player) { + if (VendingMachine.proxy.isClient()) { + return; + } if ( - te instanceof IGregTechTileEntity - && ((IGregTechTileEntity) te).getMetaTileEntity() instanceof MTEVendingMachine + player.openContainer instanceof ModularContainer container + && container.getGuiData() instanceof PosGuiData guiData + && guiData.getTileEntity() instanceof IGregTechTileEntity gte + && gte.getMetaTileEntity() instanceof MTEVendingMachine vm ) { - VendingMachine.LOG.info("found VM MTE terminating session for {}", player); - ((MTEVendingMachine) ((IGregTechTileEntity) te).getMetaTileEntity()).resetCurrentUser(player); + VendingMachine.LOG.info("Force terminating VM session for {}", player); + vm.resetCurrentUser(player); SaveLoadHandler.INSTANCE .writeTradeState(Collections.singleton(NameCache.INSTANCE.getUUIDFromPlayer(player))); } diff --git a/src/main/java/com/cubefury/vendingmachine/handlers/NotificationHandler.java b/src/main/java/com/cubefury/vendingmachine/handlers/NotificationHandler.java new file mode 100644 index 0000000..a23d642 --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/handlers/NotificationHandler.java @@ -0,0 +1,76 @@ +package com.cubefury.vendingmachine.handlers; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import net.minecraft.client.Minecraft; +import net.minecraft.util.ChatComponentTranslation; +import net.minecraft.util.EnumChatFormatting; + +import com.cleanroommc.modularui.factory.PosGuiData; +import com.cleanroommc.modularui.screen.ModularContainer; +import com.cubefury.vendingmachine.blocks.MTEVendingMachine; +import com.cubefury.vendingmachine.trade.Trade; +import com.cubefury.vendingmachine.trade.TradeDatabase; +import com.cubefury.vendingmachine.trade.TradeGroup; +import com.cubefury.vendingmachine.util.BigItemStack; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; + +@SideOnly(Side.CLIENT) +public class NotificationHandler { + + private static final int MAX_NOTIFICATION_ITEMS = 4; + public static NotificationHandler INSTANCE = new NotificationHandler(); + + private NotificationHandler() {} + + public void displayNotification(List tradeGroups) { + Minecraft mc = Minecraft.getMinecraft(); + + // If player is already accessing a vending machine, don't send notifications + if ( + mc.thePlayer.openContainer instanceof ModularContainer container + && container.getGuiData() instanceof PosGuiData guiData + && guiData.getTileEntity() instanceof IGregTechTileEntity gte + && gte.getMetaTileEntity() instanceof MTEVendingMachine + ) { + return; + } + + List refreshedTrades = new ArrayList<>(); + for (UUID tgid : tradeGroups) { + TradeGroup tradeGroup = TradeDatabase.INSTANCE.getTradeGroupFromId(tgid); + if (tradeGroup == null) { + continue; + } + for (Trade trade : tradeGroup.getTrades()) { + for (BigItemStack stack : trade.toItems) { + refreshedTrades.add( + stack.getBaseStack() + .getDisplayName()); + } + } + } + + int count = refreshedTrades.size(); + String items_output = ""; + if (count == 0) { + return; + } else if (count > MAX_NOTIFICATION_ITEMS) { + refreshedTrades = refreshedTrades.subList(0, MAX_NOTIFICATION_ITEMS); + items_output += String.join(", ", refreshedTrades) + ", ... [+" + (count - MAX_NOTIFICATION_ITEMS) + "]"; + } else { + items_output += String.join(", ", refreshedTrades); + } + + mc.thePlayer.addChatComponentMessage( + new ChatComponentTranslation( + "vendingmachine.chat.trade_restock", + EnumChatFormatting.YELLOW + items_output)); + mc.thePlayer.playSound("random.orb", 0.2F, 1.8F); + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/handlers/SaveLoadHandler.java b/src/main/java/com/cubefury/vendingmachine/handlers/SaveLoadHandler.java index 8bb920e..fa9a3fe 100644 --- a/src/main/java/com/cubefury/vendingmachine/handlers/SaveLoadHandler.java +++ b/src/main/java/com/cubefury/vendingmachine/handlers/SaveLoadHandler.java @@ -6,19 +6,24 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.concurrent.Future; import java.util.stream.Collectors; +import javax.annotation.Nullable; + import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.server.MinecraftServer; -import com.cubefury.vendingmachine.Config; +import com.cubefury.vendingmachine.VMConfig; import com.cubefury.vendingmachine.VendingMachine; import com.cubefury.vendingmachine.storage.NameCache; +import com.cubefury.vendingmachine.trade.FavouritesTracker; import com.cubefury.vendingmachine.trade.TradeDatabase; +import com.cubefury.vendingmachine.trade.TradeManager; import com.cubefury.vendingmachine.util.FileIO; import com.cubefury.vendingmachine.util.JsonHelper; import com.cubefury.vendingmachine.util.NBTConverter; @@ -30,28 +35,51 @@ public class SaveLoadHandler { private File fileDatabase = null; private File fileNames = null; private File dirTradeState = null; + private File dirBackupTradeState = null; + + private File dirFavourites = null; private SaveLoadHandler() {} public void init(MinecraftServer server) { if (VendingMachine.proxy.isClient()) { - Config.worldDir = server.getFile("saves/" + server.getFolderName() + "/" + Config.data_dir); + VMConfig.world_dir = server.getFile("saves/" + server.getFolderName() + "/" + VMConfig.developer.data_dir); } else { - Config.worldDir = server.getFile(server.getFolderName() + "/" + Config.data_dir); + VMConfig.world_dir = server.getFile(server.getFolderName() + "/" + VMConfig.developer.data_dir); } - fileDatabase = new File(Config.config_dir, "tradeDatabase.json"); - dirTradeState = new File(Config.worldDir, "tradeState"); - fileNames = new File(Config.worldDir, "names.json"); + fileDatabase = new File(VMConfig.developer.trade_db_dir, "tradeDatabase.json"); + dirTradeState = new File(VMConfig.world_dir, "tradeState"); + dirBackupTradeState = new File(VMConfig.world_dir, "backup/tradeState"); + fileNames = new File(VMConfig.world_dir, "names.json"); createFilesAndDirectories(); + unloadAll(); + loadDatabase(); - loadTradeState(); + loadTradeState(null); loadNames(); } + public void clientInit() { + dirFavourites = new File(VMConfig.developer.trade_db_dir, "favourites"); + + if (dirFavourites.mkdirs()) { + VendingMachine.LOG.info("Created favourited trades directory"); + } + } + public void createFilesAndDirectories() { + if (!fileDatabase.exists()) { + try { + if (fileDatabase.createNewFile()) { + VendingMachine.LOG.info("Created new trade database file"); + } + } catch (Exception ignored) { + VendingMachine.LOG.warn("Could not create new trade database file"); + } + } if (!fileNames.exists()) { try { if (fileNames.createNewFile()) { @@ -71,15 +99,19 @@ public void loadDatabase() { } public Future writeDatabase() { - CopyPaste(fileDatabase, new File(Config.config_dir + "/backup", "tradeDatabase.json")); + CopyPaste(fileDatabase, new File(VMConfig.developer.trade_db_dir + "/backup", "tradeDatabase.json")); return FileIO.WriteToFile( fileDatabase, out -> NBTConverter.NBTtoJSON_Compound(TradeDatabase.INSTANCE.writeToNBT(new NBTTagCompound()), out, true)); } - public void loadTradeState() { - if (dirTradeState.exists()) { - CopyPaste(dirTradeState, new File(Config.worldDir + "/backup", "tradeState")); + public void loadTradeState(@Nullable UUID player) { + if (!dirTradeState.exists()) { + JsonHelper.populateTradeStateFromFiles(Collections.emptyList()); + return; + } + + if (player == null) { File[] fileList = dirTradeState.listFiles(); if (fileList != null) { @@ -90,18 +122,30 @@ public void loadTradeState() { .endsWith(".json")) .collect(Collectors.toList())); } else { - JsonHelper.populateTradeStateFromFiles(new ArrayList<>()); + JsonHelper.populateTradeStateFromFiles(Collections.emptyList()); } + return; + } + + File playerFile = new File(dirTradeState, player + ".json"); + + if (playerFile.exists() && playerFile.isFile()) { + JsonHelper.populateTradeStateFromFiles(Collections.singletonList(playerFile)); + } else { + JsonHelper.populateTradeStateFromFiles(Collections.emptyList()); } } public List> writeTradeState(Collection players) { - TradeDatabase db = TradeDatabase.INSTANCE; + if (!dirBackupTradeState.exists()) { + CopyPaste(dirTradeState, dirBackupTradeState); + } + List> futures = new ArrayList<>(); for (UUID player : players) { File playerFile = new File(dirTradeState, player.toString() + ".json"); - CopyPaste(playerFile, new File(Config.worldDir + "/backup", player.toString() + ".json")); - NBTTagCompound state = db.writeTradeStateToNBT(new NBTTagCompound(), player); + CopyPaste(playerFile, new File(dirBackupTradeState, player.toString() + ".json")); + NBTTagCompound state = TradeManager.INSTANCE.writeTradeStateToNBT(new NBTTagCompound(), player); futures.add(FileIO.WriteToFile(playerFile, out -> NBTConverter.NBTtoJSON_Compound(state, out, true))); } return futures; @@ -120,7 +164,38 @@ public Future writeNames() { public void unloadAll() { NameCache.INSTANCE.clear(); TradeDatabase.INSTANCE.clear(); - TradeDatabase.INSTANCE.clearTradeState(null); + TradeManager.INSTANCE.clearTradeState(null); + } + + public void reloadDatabase() { + TradeDatabase.INSTANCE.clear(); + TradeManager.INSTANCE.clearTradeState(null); + + loadDatabase(); + loadTradeState(null); + } + + public Future writeFavourites(UUID player, String world_identifier) { + if (player == null || world_identifier == null) { + return null; + } + NBTTagCompound json = FavouritesTracker.INSTANCE.writeToNBT(new NBTTagCompound()); + File playerDir = new File(dirFavourites, player.toString()); + playerDir.mkdirs(); + File worldFavourites = new File(playerDir, world_identifier + ".json"); + return FileIO.WriteToFile(worldFavourites, out -> NBTConverter.NBTtoJSON_Compound(json, out, true)); + } + + public void readFavourites(UUID player, String world_identifier) { + FavouritesTracker.INSTANCE.clearFavourites(); + File playerDir = new File(dirFavourites, player.toString()); + if (!playerDir.exists()) { + return; + } + File worldFavourites = new File(playerDir, world_identifier + ".json"); + if (worldFavourites.exists()) { + JsonHelper.populateFavouritesFromFile(worldFavourites); + } } } diff --git a/src/main/java/com/cubefury/vendingmachine/integration/betterquesting/BqAdapter.java b/src/main/java/com/cubefury/vendingmachine/integration/betterquesting/BqAdapter.java index a12091a..03d1e44 100644 --- a/src/main/java/com/cubefury/vendingmachine/integration/betterquesting/BqAdapter.java +++ b/src/main/java/com/cubefury/vendingmachine/integration/betterquesting/BqAdapter.java @@ -8,8 +8,8 @@ import javax.annotation.Nullable; -import com.cubefury.vendingmachine.trade.TradeDatabase; import com.cubefury.vendingmachine.trade.TradeGroup; +import com.cubefury.vendingmachine.trade.TradeManager; import com.google.common.collect.ImmutableMap; import cpw.mods.fml.relauncher.Side; @@ -23,6 +23,7 @@ public class BqAdapter { // cache of quests that player has completed, for NEI integration not having // to look it up so much + // Server calculates this cache and syncs it directly to players private final Map> playerSatisfiedCache = new HashMap<>(); private BqAdapter() {} @@ -50,7 +51,7 @@ public Map> getPlayerSatisfiedCache() { } @SideOnly(Side.CLIENT) - public void setPlayerSatisifedCache(Map> newCache) { + public void setPlayerSatisfiedCache(Map> newCache) { // Player -> Set synchronized (playerSatisfiedCache) { playerSatisfiedCache.clear(); @@ -63,10 +64,10 @@ public void setQuestFinished(UUID player, UUID quest) { return; } for (TradeGroup tradeGroup : questUpdateTriggers.get(quest)) { - tradeGroup.addSatisfiedCondition(player, new BqCondition(quest)); + TradeManager.INSTANCE.addSatisfiedCondition(tradeGroup, player, new BqCondition(quest)); } synchronized (playerSatisfiedCache) { - playerSatisfiedCache.computeIfAbsent(player, k -> new HashSet<>()); + playerSatisfiedCache.putIfAbsent(player, new HashSet<>()); playerSatisfiedCache.get(player) .add(quest); } @@ -74,7 +75,7 @@ public void setQuestFinished(UUID player, UUID quest) { public void setQuestUnfinished(UUID player, UUID quest) { for (TradeGroup tradeGroup : questUpdateTriggers.get(quest)) { - tradeGroup.removeSatisfiedCondition(player, new BqCondition(quest)); + TradeManager.INSTANCE.removeSatisfiedCondition(tradeGroup, player, new BqCondition(quest)); synchronized (playerSatisfiedCache) { if (playerSatisfiedCache.get(player) != null) { playerSatisfiedCache.get(player) @@ -85,7 +86,14 @@ public void setQuestUnfinished(UUID player, UUID quest) { } public void resetQuests(UUID player) { - TradeDatabase.INSTANCE.removeAllSatisfiedBqConditions(player); + for (Map.Entry> entry : questUpdateTriggers.entrySet()) { + if (entry.getValue() == null) { + continue; + } + for (TradeGroup tg : entry.getValue()) { + TradeManager.INSTANCE.removeSatisfiedCondition(tg, player, new BqCondition(entry.getKey())); + } + } synchronized (playerSatisfiedCache) { if (player == null) { playerSatisfiedCache.clear(); diff --git a/src/main/java/com/cubefury/vendingmachine/integration/betterquesting/gui/PanelQBTrade.java b/src/main/java/com/cubefury/vendingmachine/integration/betterquesting/gui/PanelQBTrade.java index 78dfc7a..39564bc 100644 --- a/src/main/java/com/cubefury/vendingmachine/integration/betterquesting/gui/PanelQBTrade.java +++ b/src/main/java/com/cubefury/vendingmachine/integration/betterquesting/gui/PanelQBTrade.java @@ -42,6 +42,17 @@ public void initPanel() { x_offset += 20; } + for (int i = 0; i < trade.nonConsumedItems.size(); i++) { + BigItemStack stack = trade.nonConsumedItems.get(i) + .toBQBigItemStack(); + GuiRectangle rectangle = new GuiRectangle(x_offset, 0, 18, 18, 0); + PanelItemSlot is = PanelItemSlotBuilder.forValue(stack, rectangle) + .showCount(true) + .build(); + this.addPanel(is); + x_offset += 20; + } + for (int i = 0; i < trade.fromCurrency.size(); i++) { CurrencyItem currencyItem = trade.fromCurrency.get(i); BigItemStack stack = new BigItemStack(currencyItem.getItemRepresentation()); diff --git a/src/main/java/com/cubefury/vendingmachine/integration/nei/NEIConfig.java b/src/main/java/com/cubefury/vendingmachine/integration/nei/NEIConfig.java index 9a16209..cfcc638 100644 --- a/src/main/java/com/cubefury/vendingmachine/integration/nei/NEIConfig.java +++ b/src/main/java/com/cubefury/vendingmachine/integration/nei/NEIConfig.java @@ -32,7 +32,9 @@ public String getVersion() { @SubscribeEvent public void registerHandlerInfo(NEIRegisterHandlerInfosEvent event) { event.registerHandlerInfo( - new HandlerInfo.Builder("vendingmachine", VendingMachine.NAME, VendingMachine.MODID).setMaxRecipesPerPage(3) + new HandlerInfo.Builder("vendingmachine", VendingMachine.NAME, VendingMachine.MODID).setHeight(140) + .setWidth(166) + .setMaxRecipesPerPage(3) .setDisplayStack(VMItems.vendingMachine) .build()); } diff --git a/src/main/java/com/cubefury/vendingmachine/integration/nei/NeiRecipeHandler.java b/src/main/java/com/cubefury/vendingmachine/integration/nei/NeiRecipeHandler.java index 3e5f78a..9960c18 100644 --- a/src/main/java/com/cubefury/vendingmachine/integration/nei/NeiRecipeHandler.java +++ b/src/main/java/com/cubefury/vendingmachine/integration/nei/NeiRecipeHandler.java @@ -47,6 +47,7 @@ import codechicken.nei.api.API; import codechicken.nei.recipe.GuiCraftingRecipe; import codechicken.nei.recipe.GuiRecipe; +import codechicken.nei.recipe.GuiRecipeCatalyst; import codechicken.nei.recipe.TemplateRecipeHandler; import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.Optional; @@ -58,7 +59,7 @@ public class NeiRecipeHandler extends TemplateRecipeHandler { private static final int GUI_WIDTH = 166; private static final int GRID_COUNT = 4; private static final int LINE_SPACE = GuiDraw.fontRenderer.FONT_HEIGHT + 1; - private static final int CONDITIONS_START_Y = 27 + LINE_SPACE; + private static final int CONDITIONS_START_Y = 63 + LINE_SPACE; private UUID currentPlayerId; private int textColorConditionDefault; private int textColorConditionSatisfied; @@ -128,12 +129,22 @@ public void loadCraftingRecipes(ItemStack result) { public void loadUsageRecipes(ItemStack ingredient) { setTextColors(); for (NeiRecipeCache.CacheEntry entry : NeiRecipeCache.recipeCache) { + boolean addRecipe = false; for (BigItemStack compareTo : entry.trade().fromItems) { if (matchStack(ingredient, compareTo)) { - this.arecipes.add(new CachedTradeRecipe(entry.trade(), entry.requirements())); + addRecipe = true; + break; + } + } + for (CurrencyItem currency : entry.trade().fromCurrency) { + if (currency.type.isMatchingType(ingredient)) { + addRecipe = true; break; } } + if (addRecipe) { + this.arecipes.add(new CachedTradeRecipe(entry.trade(), entry.requirements())); + } } } @@ -178,7 +189,7 @@ public String getGuiTexture() { public void drawBackground(int recipe) { GL11.glColor4f(1, 1, 1, 1); changeTexture(getGuiTexture()); - drawTexturedModalRect(0, 0, 0, 0, GUI_WIDTH, 105); + drawTexturedModalRect(0, 0, 0, 0, GUI_WIDTH, 140); } // Caching the last hovered valid quest here is a bit jank, but it works I guess @@ -197,8 +208,12 @@ public boolean isMouseOverBqCondition(int recipeIndex, int curY, UUID questId, S Point pos = GuiDraw.getMousePosition(); - int guiLeft = (gui.width - gui.getWidgetSize().width) / 2; - int guiTop = 19 + (gui.height - gui.getWidgetSize().height) / 2; + // very cursed I'm sorry :doom: + GuiRecipeCatalyst catalystWidget = gui.getRecipeCatalystWidget(); + + int guiLeft = catalystWidget.x + catalystWidget.w - 6; + int guiTop = catalystWidget.y + 9; + Point relMousePos = new Point(pos.x - guiLeft - offset.x, pos.y - guiTop - offset.y); Rectangle textArea = new Rectangle(2, curY - GuiDraw.fontRenderer.FONT_HEIGHT, width + 2, height + 1); if (textArea.contains(relMousePos)) { @@ -214,8 +229,10 @@ public boolean isMouseOnLastHovered(GuiRecipe gui, int recipeIndex) { if (lastHoveredTextArea == null || lastHoveredQuestId == null || lastHoveredRecipeIndex != recipeIndex) { return false; } - int guiLeft = (gui.width - gui.getWidgetSize().width) / 2; - int guiTop = 19 + (gui.height - gui.getWidgetSize().height) / 2; + GuiRecipeCatalyst catalystWidget = gui.getRecipeCatalystWidget(); + + int guiLeft = catalystWidget.x + catalystWidget.w - 6; + int guiTop = catalystWidget.y + 9; Point offset = gui.getRecipePosition(recipeIndex); Point pos = GuiDraw.getMousePosition(); @@ -264,10 +281,23 @@ public void processBqGui() { public void drawExtras(int recipeIndex) { CachedTradeRecipe recipe = (CachedTradeRecipe) this.arecipes.get(recipeIndex); + float scale = 0.5f; + GL11.glPushMatrix(); + GL11.glScalef(scale, scale, 1); + for (PositionedStack ps : recipe.ncInputs) { + GuiDraw.fontRenderer.drawString( + "NC", + (int) (ps.relx / scale), + (int) (ps.rely / scale), + Translator.getColor("vendingmachine.gui.nc_inputs_overlay_color"), + false); + } + GL11.glPopMatrix(); + GuiDraw.drawString( Translator.translate("vendingmachine.gui.requirementHeader"), 2, - 27, + 63, textColorConditionDefault, false); int y = CONDITIONS_START_Y; @@ -286,7 +316,9 @@ public void drawExtras(int recipeIndex) { } else { String translatedQuestKey = getTextWithoutFormattingCodes( QuestTranslation.translateQuestName(questId, quest)); - unformatted.append(translatedQuestKey); + unformatted.append( + translatedQuestKey.length() <= 18 ? translatedQuestKey + : translatedQuestKey.substring(0, 18) + "..."); requirementString.append( isMouseOverBqCondition(recipeIndex, y, questId, unformatted.toString()) ? UNDERLINE : ""); requirementString.append(unformatted); @@ -310,6 +342,7 @@ public void drawExtras(int recipeIndex) { public class CachedTradeRecipe extends CachedRecipe { private final List inputs = new ArrayList<>(); + private final List ncInputs = new ArrayList<>(); private final List outputs = new ArrayList<>(); private final List requirements = new ArrayList<>(); @@ -325,20 +358,36 @@ private void loadInputs(Trade trade) { int index = 0; for (BigItemStack stack : trade.fromItems) { if (index >= GRID_COUNT) { - break; + y += SLOT_SIZE; + index = 0; } int x = xOffset + index * SLOT_SIZE; inputs.add(new PositionedStack(extractStacks(stack), x, y)); index++; } + for (CurrencyItem ci : trade.fromCurrency) { if (index >= GRID_COUNT) { - break; + y += SLOT_SIZE; + index = 0; } int x = xOffset + index * SLOT_SIZE; inputs.add(new PositionedStack(ci.getItemRepresentation(), x, y)); index++; } + + y += SLOT_SIZE; + index = 0; + for (BigItemStack stack : trade.nonConsumedItems) { + if (index >= GRID_COUNT) { + y += SLOT_SIZE; + index = 0; + } + int x = xOffset + index * SLOT_SIZE; + ncInputs.add(new PositionedStack(extractStacks(stack), x, y)); + index++; + } + } private void loadOutputs(Trade trade) { @@ -361,7 +410,10 @@ public PositionedStack getResult() { @Override public List getIngredients() { - return getCycledIngredients(cycleticks / 20, inputs); + List allInputs = new ArrayList<>(); + allInputs.addAll(inputs); + allInputs.addAll(ncInputs); + return getCycledIngredients(cycleticks / 20, allInputs); } @Override @@ -372,6 +424,6 @@ public List getOtherStacks() { @Override public void loadTransferRects() { - transferRects.add(new RecipeTransferRect(new Rectangle(75, 0, 16, 24), getOverlayIdentifier())); + transferRects.add(new RecipeTransferRect(new Rectangle(75, 5, 16, 55), getOverlayIdentifier())); } } diff --git a/src/main/java/com/cubefury/vendingmachine/network/PacketAssembly.java b/src/main/java/com/cubefury/vendingmachine/network/PacketAssembly.java index b9231fc..772e3be 100644 --- a/src/main/java/com/cubefury/vendingmachine/network/PacketAssembly.java +++ b/src/main/java/com/cubefury/vendingmachine/network/PacketAssembly.java @@ -20,7 +20,7 @@ public final class PacketAssembly { - public static final betterquesting.network.PacketAssembly INSTANCE = new betterquesting.network.PacketAssembly(); + public static final PacketAssembly INSTANCE = new PacketAssembly(); // TODO: Allow for simultaneous packet assembly (may not be necessary) // TODO: Implement PROPER thread safety that doesn't cause dirty read/writes diff --git a/src/main/java/com/cubefury/vendingmachine/network/PacketTypeRegistry.java b/src/main/java/com/cubefury/vendingmachine/network/PacketTypeRegistry.java index 6def7a2..99cf435 100644 --- a/src/main/java/com/cubefury/vendingmachine/network/PacketTypeRegistry.java +++ b/src/main/java/com/cubefury/vendingmachine/network/PacketTypeRegistry.java @@ -13,12 +13,12 @@ import com.cubefury.vendingmachine.api.network.IPacketRegistry; import com.cubefury.vendingmachine.api.util.Tuple2; import com.cubefury.vendingmachine.network.handlers.NetBulkSync; -import com.cubefury.vendingmachine.network.handlers.NetCurrencySync; import com.cubefury.vendingmachine.network.handlers.NetNameSync; import com.cubefury.vendingmachine.network.handlers.NetResetVMUser; import com.cubefury.vendingmachine.network.handlers.NetSatisfiedQuestSync; import com.cubefury.vendingmachine.network.handlers.NetTradeDbSync; import com.cubefury.vendingmachine.network.handlers.NetTradeDisplaySync; +import com.cubefury.vendingmachine.network.handlers.NetTradeNotification; import com.cubefury.vendingmachine.network.handlers.NetTradeRequestSync; public class PacketTypeRegistry implements IPacketRegistry { @@ -30,13 +30,13 @@ public class PacketTypeRegistry implements IPacketRegistry { public void init() { NetTradeDbSync.registerHandler(); - NetCurrencySync.registerHandler(); NetTradeDisplaySync.registerHandler(); NetTradeRequestSync.registerHandler(); NetSatisfiedQuestSync.registerHandler(); NetNameSync.registerHandler(); NetBulkSync.registerHandler(); NetResetVMUser.registerHandler(); + NetTradeNotification.registerHandler(); } @Override diff --git a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetBulkSync.java b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetBulkSync.java index 2a80342..be1aa33 100644 --- a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetBulkSync.java +++ b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetBulkSync.java @@ -9,10 +9,12 @@ import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.MinecraftForge; import com.cubefury.vendingmachine.VendingMachine; import com.cubefury.vendingmachine.api.network.UnserializedPacket; import com.cubefury.vendingmachine.api.util.Tuple2; +import com.cubefury.vendingmachine.events.MarkDirtyNamesEvent; import com.cubefury.vendingmachine.handlers.SaveLoadHandler; import com.cubefury.vendingmachine.network.PacketSender; import com.cubefury.vendingmachine.network.PacketTypeRegistry; @@ -23,7 +25,7 @@ public class NetBulkSync { - private static final ResourceLocation ID_NAME = new ResourceLocation("vending_machine:main_sync"); + private static final ResourceLocation ID_NAME = new ResourceLocation("vendingmachine:bulk_sync"); public static void registerHandler() { PacketTypeRegistry.INSTANCE.registerServerHandler(ID_NAME, NetBulkSync::onServer); @@ -48,6 +50,8 @@ public static void sendReset(@Nullable EntityPlayerMP player, boolean reset, boo public static void sendSync(@Nonnull EntityPlayerMP player) { NameCache.INSTANCE.updateName(player); + MinecraftForge.EVENT_BUS.post(new MarkDirtyNamesEvent()); + UUID playerId = NameCache.INSTANCE.getUUIDFromPlayer(player); NetNameSync.sendNames(new EntityPlayerMP[] { player }, new UUID[] { playerId }, null); diff --git a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetCurrencySync.java b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetCurrencySync.java deleted file mode 100644 index 84d8f61..0000000 --- a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetCurrencySync.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.cubefury.vendingmachine.network.handlers; - -import java.util.UUID; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import net.minecraft.client.Minecraft; -import net.minecraft.entity.player.EntityPlayerMP; -import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.util.ResourceLocation; - -import com.cubefury.vendingmachine.VendingMachine; -import com.cubefury.vendingmachine.api.network.UnserializedPacket; -import com.cubefury.vendingmachine.network.PacketSender; -import com.cubefury.vendingmachine.network.PacketTypeRegistry; -import com.cubefury.vendingmachine.storage.NameCache; -import com.cubefury.vendingmachine.trade.CurrencyItem; -import com.cubefury.vendingmachine.trade.TradeManager; -import com.cubefury.vendingmachine.util.NBTConverter; - -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; - -public class NetCurrencySync { - - private static final ResourceLocation ID_NAME = new ResourceLocation("vendingmachine:currency_sync"); - - public static void registerHandler() { - if (VendingMachine.proxy.isClient()) { - PacketTypeRegistry.INSTANCE.registerClientHandler(ID_NAME, NetCurrencySync::onClient); - } - } - - // server side code for sending tradegroup data when player opens gui - public static void syncCurrencyToClient(@Nonnull EntityPlayerMP player) { - UUID playerId = NameCache.INSTANCE.getUUIDFromPlayer(player); - - NBTTagCompound payload = new NBTTagCompound(); - payload.setString("dataType", "currencySync"); - NBTConverter.UuidValueType.PLAYER.writeId(playerId, payload); - payload.setTag("data", TradeManager.INSTANCE.writeCurrencyToNBT(playerId)); - - PacketSender.INSTANCE.sendToPlayers(new UnserializedPacket(ID_NAME, payload), player); - } - - public static void sendPlayerCurrency(@Nonnull EntityPlayerMP player, CurrencyItem currencyItem) { - NBTTagCompound payload = new NBTTagCompound(); - payload.setString("dataType", "currencyAdd"); - currencyItem.writeToNBT(payload); - - PacketSender.INSTANCE.sendToPlayers(new UnserializedPacket(ID_NAME, payload), player); - } - - public static void resetPlayerCurrency(@Nonnull EntityPlayerMP player, @Nullable CurrencyItem.CurrencyType type) { - NBTTagCompound payload = new NBTTagCompound(); - payload.setString("dataType", "currencyReset"); - if (type != null) { - payload.setString("type", type.id); - } - PacketSender.INSTANCE.sendToPlayers(new UnserializedPacket(ID_NAME, payload), player); - } - - @SideOnly(Side.CLIENT) - public static void onClient(NBTTagCompound message) { - // Don't wipe everyone else's data if on LAN, since - // we receive only the requested player's data - // In SP and LAN, we'll have this data already anyway - if ( - Minecraft.getMinecraft() - .isIntegratedServerRunning() - ) { - return; - } - String dataType = message.getString("dataType"); - UUID player = NBTConverter.UuidValueType.PLAYER.readId(message); - switch (dataType) { - case "currencySync" -> { - TradeManager.INSTANCE.populateCurrencyFromNBT( - message.getCompoundTag("data"), - NBTConverter.UuidValueType.PLAYER.readId(message), - false); - } - case "currencyAdd" -> { - CurrencyItem currencyItem = CurrencyItem.fromNBT(message.getCompoundTag("currencyItem")); - - TradeManager.INSTANCE.addCurrency(player, currencyItem); - } - case "currencyReset" -> TradeManager.INSTANCE.resetCurrency( - player, - message.hasKey("type") ? CurrencyItem.CurrencyType.getTypeFromId(message.getString("type")) : null); - default -> VendingMachine.LOG.warn("Unknown trade state sync data received: {}", dataType); - } - } -} diff --git a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetSatisfiedQuestSync.java b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetSatisfiedQuestSync.java index dc1a561..b38a9ba 100644 --- a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetSatisfiedQuestSync.java +++ b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetSatisfiedQuestSync.java @@ -52,7 +52,7 @@ public static void sendSync() { if (cache != null && cache.get(playerId) != null) { for (UUID quest : cache.get(playerId)) { NBTTagCompound questInfo = new NBTTagCompound(); - NBTConverter.UuidValueType.QUEST.writeId(quest); + NBTConverter.UuidValueType.QUEST.writeId(quest, questInfo); questList.appendTag(questInfo); } } @@ -68,6 +68,7 @@ public static void onClient(NBTTagCompound message) { Minecraft.getMinecraft() .isIntegratedServerRunning() ) { + // Don't override in singleplayer or on LAN return; } @@ -80,6 +81,6 @@ public static void onClient(NBTTagCompound message) { newCache.get(playerId) .add(NBTConverter.UuidValueType.QUEST.readId(questList.getCompoundTagAt(i))); } - BqAdapter.INSTANCE.setPlayerSatisifedCache(newCache); + BqAdapter.INSTANCE.setPlayerSatisfiedCache(newCache); } } diff --git a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetTradeDbSync.java b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetTradeDbSync.java index 1529d58..8ec2b08 100644 --- a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetTradeDbSync.java +++ b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetTradeDbSync.java @@ -68,6 +68,6 @@ public static void onClient(NBTTagCompound message) { ) { return; } - TradeDatabase.INSTANCE.readFromNBT(message.getCompoundTag("data"), message.getBoolean("merge")); + TradeDatabase.INSTANCE.readFromNBT(message.getCompoundTag("data"), message.getBoolean("merge"), false); } } diff --git a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetTradeDisplaySync.java b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetTradeDisplaySync.java index a399f08..576b5a7 100644 --- a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetTradeDisplaySync.java +++ b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetTradeDisplaySync.java @@ -6,7 +6,6 @@ import javax.annotation.Nonnull; import net.minecraft.entity.player.EntityPlayerMP; -import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.util.ResourceLocation; @@ -23,6 +22,7 @@ import com.cubefury.vendingmachine.trade.Trade; import com.cubefury.vendingmachine.trade.TradeDatabase; import com.cubefury.vendingmachine.trade.TradeGroup; +import com.cubefury.vendingmachine.trade.TradeHistory; import com.cubefury.vendingmachine.trade.TradeManager; import com.cubefury.vendingmachine.util.NBTConverter; import com.cubefury.vendingmachine.util.Translator; @@ -80,16 +80,14 @@ public TradeItemDisplay formatItemDisplay() { TradeGroup tg = TradeDatabase.INSTANCE.getTradeGroupFromId(this.tgID); Trade t = tg.getTrades() .get(this.tradeGroupOrder); - ItemStack displayItem = t.toItems.get(0) - .convertToItemStack(); return new TradeItemDisplay( t.fromCurrency, t.fromItems, + t.nonConsumedItems, t.toItems, - t.displayItem == null ? t.displayItem.convertToItemStack() : displayItem, + t.getDisplayItem(), this.tgID, this.tradeGroupOrder, - tg.getLabel(), this.cooldown, convertCooldownText(this.cooldown), this.cooldown > 0, @@ -115,14 +113,16 @@ public static void syncTradesToClient(@Nonnull EntityPlayerMP player, MTEVending UUID playerId = NameCache.INSTANCE.getUUIDFromPlayer(player); List availableGroups = TradeManager.INSTANCE.getAvailableTradeGroups(playerId); base.refreshInputSlotCache(); + base.refreshMeItemCache(); long currentTimestamp = System.currentTimeMillis(); NBTTagCompound payload = new NBTTagCompound(); NBTTagList trades = new NBTTagList(); for (TradeGroup tg : availableGroups) { - long lastTradeTime = tg.getTradeState(playerId).lastTrade; - long tradeCount = tg.getTradeState(playerId).tradeCount; + TradeHistory history = TradeManager.INSTANCE.getTradeState(playerId, tg); + long lastTradeTime = history.lastTrade; + long tradeCount = history.tradeCount; long cooldownRemaining; if (tg.cooldown != -1 && lastTradeTime != -1 && (currentTimestamp - lastTradeTime) / 1000 < tg.cooldown) { @@ -137,6 +137,7 @@ public static void syncTradesToClient(@Nonnull EntityPlayerMP player, MTEVending Trade trade = tg.getTrades() .get(i); boolean tradableNow = base.inputItemsSatisfied(trade.fromItems) + && base.inputItemsSatisfied(trade.nonConsumedItems) && base.inputCurrencySatisfied(trade.fromCurrency, playerId); trades.appendTag( new Tradable(tg.getId(), i, cooldownRemaining, enabled, tradableNow) @@ -150,7 +151,6 @@ public static void syncTradesToClient(@Nonnull EntityPlayerMP player, MTEVending @SideOnly(Side.CLIENT) public static void onClient(NBTTagCompound message) { - // TODO: Load trade view on client List tradeData = TradeManager.INSTANCE.tradeData; tradeData.clear(); diff --git a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetTradeNotification.java b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetTradeNotification.java new file mode 100644 index 0000000..77cd71b --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetTradeNotification.java @@ -0,0 +1,61 @@ +package com.cubefury.vendingmachine.network.handlers; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import javax.annotation.Nonnull; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.util.Constants; + +import com.cubefury.vendingmachine.VMConfig; +import com.cubefury.vendingmachine.VendingMachine; +import com.cubefury.vendingmachine.api.network.UnserializedPacket; +import com.cubefury.vendingmachine.handlers.NotificationHandler; +import com.cubefury.vendingmachine.network.PacketSender; +import com.cubefury.vendingmachine.network.PacketTypeRegistry; +import com.cubefury.vendingmachine.util.NBTConverter; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class NetTradeNotification { + + private static final ResourceLocation ID_NAME = new ResourceLocation("vendingmachine:trade_notification"); + + public static void registerHandler() { + if (VendingMachine.proxy.isClient()) { + PacketTypeRegistry.INSTANCE.registerClientHandler(ID_NAME, NetTradeNotification::onClient); + } + } + + public static void sendNotification(@Nonnull EntityPlayerMP player, List tradeGroups) { + NBTTagCompound payload = new NBTTagCompound(); + NBTTagList notifications = new NBTTagList(); + for (UUID tgId : tradeGroups) { + notifications.appendTag(NBTConverter.UuidValueType.TRADEGROUP.writeId(tgId)); + } + payload.setTag("notifications", notifications); + PacketSender.INSTANCE.sendToPlayers(new UnserializedPacket(ID_NAME, payload), player); + } + + @SideOnly(Side.CLIENT) + public static void onClient(NBTTagCompound message) { + if (!VMConfig.vendingMachineSettings.restock_notifications_enabled) { + return; + } + + List tradeNotifications = new ArrayList<>(); + + NBTTagList notifNbt = message.getTagList("notifications", Constants.NBT.TAG_COMPOUND); + for (int i = 0; i < notifNbt.tagCount(); i++) { + tradeNotifications.add(NBTConverter.UuidValueType.TRADEGROUP.readId(notifNbt.getCompoundTagAt(i))); + } + + NotificationHandler.INSTANCE.displayNotification(tradeNotifications); + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/storage/NameCache.java b/src/main/java/com/cubefury/vendingmachine/storage/NameCache.java index 10b812f..36ec99e 100644 --- a/src/main/java/com/cubefury/vendingmachine/storage/NameCache.java +++ b/src/main/java/com/cubefury/vendingmachine/storage/NameCache.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -145,6 +146,13 @@ public synchronized List getAllNames() { return Collections.unmodifiableList(nameCache); } + @Override + public List getAllUUIDS() { + return cache.keySet() + .stream() + .collect(Collectors.toList()); + } + @Override public UUID getUUIDFromPlayer(EntityPlayer player) { if (player == null) { diff --git a/src/main/java/com/cubefury/vendingmachine/trade/CurrencyItem.java b/src/main/java/com/cubefury/vendingmachine/trade/CurrencyItem.java index a0535af..495ae3f 100644 --- a/src/main/java/com/cubefury/vendingmachine/trade/CurrencyItem.java +++ b/src/main/java/com/cubefury/vendingmachine/trade/CurrencyItem.java @@ -9,18 +9,11 @@ import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; -import com.cleanroommc.modularui.drawable.UITexture; -import com.cubefury.vendingmachine.VendingMachine; -import com.cubefury.vendingmachine.util.Translator; - -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; - public class CurrencyItem { public CurrencyType type; public int value; - private static final Map typeMap = new HashMap<>(); + public static final Map typeMap = new HashMap<>(); private static final String[] coinSuffixes = new String[] { "IV", "III", "II", "I", "" }; private static final int[] coinValues = new int[] { 10000, 1000, 100, 10, 1 }; @@ -44,7 +37,7 @@ public List itemize() { return outputs; } for (int i = 0; i < coinValues.length; i++) { - while (this.value > coinValues[i]) { + while (this.value >= coinValues[i]) { Item outputItem = (Item) Item.itemRegistry.getObject(this.type.itemPrefix + coinSuffixes[i]); int stackSize = Math.min(this.value / coinValues[i], outputItem.getItemStackLimit()); outputs.add(new ItemStack(outputItem, stackSize)); @@ -59,51 +52,6 @@ public ItemStack getItemRepresentation() { return new ItemStack(outputItem, value); } - public enum CurrencyType { - - ADVENTURE("adventure", "dreamcraft:item.CoinAdventure", "gui/icons/itemCoinAdventure.png"), - BEES("bees", "dreamcraft:item.CoinBees", "gui/icons/itemCoinBees.png"), - BLOOD("blood", "dreamcraft:item.CoinBlood", "gui/icons/itemCoinBlood.png"), - CHEMIST("chemist", "dreamcraft:item.CoinChemist", "gui/icons/itemCoinChemist.png"), - COOK("cook", "dreamcraft:item.CoinCook", "gui/icons/itemCoinCook.png"), - DARK_WIZARD("darkWizard", "dreamcraft:item.CoinDarkWizard", "gui/icons/itemCoinDarkWizard.png"), - FARMER("farmer", "dreamcraft:item.CoinFarmer", "gui/icons/itemCoinFarmer.png"), - FLOWER("flower", "dreamcraft:item.CoinFlower", "gui/icons/itemCoinFlower.png"), - FORESTRY("forestry", "dreamcraft:item.CoinForestry", "gui/icons/itemCoinForestry.png"), - SMITH("smith", "dreamcraft:item.CoinSmith", "gui/icons/itemCoinSmith.png"), - SPACE("space", "dreamcraft:item.CoinSpace", "gui/icons/itemCoinSpace.png"), - SURVIVOR("survivor", "dreamcraft:item.CoinSurvivor", "gui/icons/itemCoinSurvivor.png"), - TECHNICIAN("technician", "dreamcraft:item.CoinTechnician", "gui/icons/itemCoinTechnician.png"), - WITCH("witch", "dreamcraft:item.CoinWitch", "gui/icons/itemCoinWitch.png"), - // comment before semicolon to reduce merge conflicts - ; - - public final String id; - public final String itemPrefix; - public final UITexture texture; - - CurrencyType(String id, String itemPrefix, String texture) { - this.id = id; - this.itemPrefix = itemPrefix; - this.texture = UITexture.builder() - .location(VendingMachine.MODID, texture) - .imageSize(32, 32) - .name("VM_UI_Coin_" + id) - .build(); - - typeMap.put(this.id, this); - } - - public static CurrencyType getTypeFromId(String type) { - return typeMap.get(type); - } - - @SideOnly(Side.CLIENT) - public String getLocalizedName() { - return Translator.translate("vendingmachine.coin." + this.id); - } - } - public CurrencyItem(CurrencyType type, int value) { this.type = type; this.value = value; diff --git a/src/main/java/com/cubefury/vendingmachine/trade/CurrencyType.java b/src/main/java/com/cubefury/vendingmachine/trade/CurrencyType.java new file mode 100644 index 0000000..61b7ebd --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/trade/CurrencyType.java @@ -0,0 +1,61 @@ +package com.cubefury.vendingmachine.trade; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +import com.cleanroommc.modularui.drawable.UITexture; +import com.cubefury.vendingmachine.VendingMachine; +import com.cubefury.vendingmachine.util.Translator; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public enum CurrencyType { + + ADVENTURE("adventure", "dreamcraft:CoinAdventure", "gui/icons/itemCoinAdventure.png"), + BEES("bees", "dreamcraft:CoinBees", "gui/icons/itemCoinBees.png"), + BLOOD("blood", "dreamcraft:CoinBlood", "gui/icons/itemCoinBlood.png"), + CHEMIST("chemist", "dreamcraft:CoinChemist", "gui/icons/itemCoinChemist.png"), + COOK("cook", "dreamcraft:CoinCook", "gui/icons/itemCoinCook.png"), + DARK_WIZARD("darkWizard", "dreamcraft:CoinDarkWizard", "gui/icons/itemCoinDarkWizard.png"), + FARMER("farmer", "dreamcraft:CoinFarmer", "gui/icons/itemCoinFarmer.png"), + FLOWER("flower", "dreamcraft:CoinFlower", "gui/icons/itemCoinFlower.png"), + FORESTRY("forestry", "dreamcraft:CoinForestry", "gui/icons/itemCoinForestry.png"), + SMITH("smith", "dreamcraft:CoinSmith", "gui/icons/itemCoinSmith.png"), + SPACE("space", "dreamcraft:CoinSpace", "gui/icons/itemCoinSpace.png"), + SURVIVOR("survivor", "dreamcraft:CoinSurvivor", "gui/icons/itemCoinSurvivor.png"), + TECHNICIAN("technician", "dreamcraft:CoinTechnician", "gui/icons/itemCoinTechnician.png"), + WITCH("witch", "dreamcraft:CoinWitch", "gui/icons/itemCoinWitch.png"), + // comment before semicolon to reduce merge conflicts + ; + + public final String id; + public final String itemPrefix; + public final UITexture texture; + + CurrencyType(String id, String itemPrefix, String texture) { + this.id = id; + this.itemPrefix = itemPrefix; + this.texture = UITexture.builder() + .location(VendingMachine.MODID, texture) + .imageSize(32, 32) + .name("VM_UI_Coin_" + id) + .build(); + + CurrencyItem.typeMap.put(this.id, this); + } + + public static CurrencyType getTypeFromId(String type) { + return CurrencyItem.typeMap.get(type); + } + + public boolean isMatchingType(ItemStack item) { + return Item.itemRegistry.getNameForObject(item.getItem()) + .startsWith(itemPrefix); + } + + @SideOnly(Side.CLIENT) + public String getLocalizedName() { + return Translator.translate("vendingmachine.coin." + this.id); + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/trade/FavouritesTracker.java b/src/main/java/com/cubefury/vendingmachine/trade/FavouritesTracker.java new file mode 100644 index 0000000..2c36cf1 --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/trade/FavouritesTracker.java @@ -0,0 +1,138 @@ +package com.cubefury.vendingmachine.trade; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraftforge.common.util.Constants; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import com.cubefury.vendingmachine.VendingMachine; +import com.cubefury.vendingmachine.blocks.gui.TradeItemDisplay; +import com.cubefury.vendingmachine.handlers.SaveLoadHandler; +import com.cubefury.vendingmachine.storage.NameCache; +import com.cubefury.vendingmachine.util.NBTConverter; + +public class FavouritesTracker { + + public static final FavouritesTracker INSTANCE = new FavouritesTracker(); + private boolean dirty = false; + + public final Set> favourites = new HashSet<>(); + + private FavouritesTracker() {} + + public void toggleFavourites(UUID tradeGroupId, int tradeGroupOrder) { + Pair pair = new ImmutablePair<>(tradeGroupId, tradeGroupOrder); + if (favourites.contains(pair)) { + favourites.remove(pair); + } else { + favourites.add(pair); + } + dirty = true; + } + + private static boolean isPrivateAddress(String host) { + return host.equalsIgnoreCase("localhost") || host.equals("127.0.0.1") + || host.startsWith("192.168") + || host.startsWith("10.") + || host.matches("^172\\.(1[6-9]|2\\d|3[0-1])\\..*"); + } + + private static String sanitizeFilename(String s) { + return s.replaceAll("[<>:\"/\\\\|?*]", "_"); + } + + public String computeWorldKey() { + Minecraft mc = Minecraft.getMinecraft(); + + if (mc.isSingleplayer()) { + return "SP_" + mc.getIntegratedServer() + .getFolderName(); + } + + ServerData data = mc.func_147104_D(); + if (data == null) { + return null; + } + + String ip = data.serverIP; + String host = ip; + String port = ""; + + int colon = ip.indexOf(':'); + if (colon >= 0) { + host = ip.substring(0, colon); + port = ip.substring(colon); + } + + if (isPrivateAddress(host)) { + // Because open to lan does not have a constant port, we don't include port for LAN saves + return sanitizeFilename("LAN_" + host); + } + + return sanitizeFilename("MP_" + host + port); + } + + public void saveFavourites() { + if (dirty) { + SaveLoadHandler.INSTANCE.writeFavourites( + NameCache.INSTANCE.getUUIDFromPlayer(Minecraft.getMinecraft().thePlayer), + computeWorldKey()); + } + dirty = false; + } + + public NBTTagCompound writeToNBT(NBTTagCompound nbt) { + NBTTagList favouritesNbt = new NBTTagList(); + for (Pair fave : favourites) { + NBTTagCompound faveNbt = new NBTTagCompound(); + NBTConverter.UuidValueType.TRADEGROUP.writeId(fave.getLeft(), faveNbt); + faveNbt.setInteger("tgOrder", fave.getRight()); + favouritesNbt.appendTag(faveNbt); + } + nbt.setTag("favourites", favouritesNbt); + return nbt; + } + + public void readFromNBT(NBTTagCompound nbt, boolean merge) { + if (!merge) { + favourites.clear(); + } + NBTTagList faveListNbt = nbt.getTagList("favourites", Constants.NBT.TAG_COMPOUND); + for (int i = 0; i < faveListNbt.tagCount(); i++) { + NBTTagCompound faveNbt = faveListNbt.getCompoundTagAt(i); + favourites.add( + new ImmutablePair<>( + NBTConverter.UuidValueType.TRADEGROUP.readId(faveNbt), + faveNbt.getInteger("tgOrder"))); + } + VendingMachine.LOG.info("Loaded {} favourited trades", favourites.size()); + } + + public List filterTrades(List trades) { + List filteredTrades = new ArrayList<>(); + for (TradeItemDisplay trade : trades) { + if (favourites.contains(new ImmutablePair<>(trade.tgID, trade.tradeGroupOrder))) { + filteredTrades.add(trade); + } + } + return filteredTrades; + } + + public boolean isFavourite(TradeItemDisplay display) { + return favourites.contains(new ImmutablePair<>(display.tgID, display.tradeGroupOrder)); + } + + public void clearFavourites() { + favourites.clear(); + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/trade/Trade.java b/src/main/java/com/cubefury/vendingmachine/trade/Trade.java index a0f9a7c..e347f1d 100644 --- a/src/main/java/com/cubefury/vendingmachine/trade/Trade.java +++ b/src/main/java/com/cubefury/vendingmachine/trade/Trade.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.List; +import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraftforge.common.util.Constants; @@ -22,6 +23,7 @@ public class Trade { public final List fromCurrency = new ArrayList<>(); public final List fromItems = new ArrayList<>(); + public final List nonConsumedItems = new ArrayList<>(); public final List toItems = new ArrayList<>(); public BigItemStack displayItem = new BigItemStack(ItemPlaceholder.placeholder); @@ -44,6 +46,14 @@ public NBTTagCompound writeToNBT(NBTTagCompound nbt) { nbt.setTag("fromItems", fromItemsArray); } + if (!this.nonConsumedItems.isEmpty()) { + NBTTagList ncArray = new NBTTagList(); + for (BigItemStack stack : this.nonConsumedItems) { + ncArray.appendTag(JsonHelper.ItemStackToJson(stack, new NBTTagCompound())); + } + nbt.setTag("nonConsumedItems", ncArray); + } + if (!this.toItems.isEmpty()) { NBTTagList toItemsArray = new NBTTagList(); for (BigItemStack stack : this.toItems) { @@ -56,6 +66,11 @@ public NBTTagCompound writeToNBT(NBTTagCompound nbt) { } public void readFromNBT(NBTTagCompound nbt) { + if (nbt.hasKey("displayItem")) { + BigItemStack readStack = BigItemStack.loadItemStackFromNBT(nbt.getCompoundTag("displayItem")); + displayItem = readStack == null ? displayItem : readStack; + } + fromCurrency.clear(); fromItems.clear(); toItems.clear(); @@ -70,15 +85,29 @@ public void readFromNBT(NBTTagCompound nbt) { fromItems.add(JsonHelper.JsonToItemStack(fromList.getCompoundTagAt(i))); } + NBTTagList ncList = nbt.getTagList("nonConsumedItems", Constants.NBT.TAG_COMPOUND); + for (int i = 0; i < ncList.tagCount(); i++) { + nonConsumedItems.add(JsonHelper.JsonToItemStack(ncList.getCompoundTagAt(i))); + } + NBTTagList toList = nbt.getTagList("toItems", Constants.NBT.TAG_COMPOUND); for (int i = 0; i < toList.tagCount(); i++) { toItems.add(JsonHelper.JsonToItemStack(toList.getCompoundTagAt(i))); } } + public ItemStack getDisplayItem() { + return this.displayItem.getBaseStack() + .isItemEqual(new ItemStack(ItemPlaceholder.placeholder)) + ? this.toItems.get(0) + .convertToItemStack() + : this.displayItem.getBaseStack(); + } + @Optional.Method(modid = "betterquesting") @SideOnly(Side.CLIENT) public IGuiPanel getTradeGui(IGuiRect rect) { return new PanelQBTrade(rect, this); } + } diff --git a/src/main/java/com/cubefury/vendingmachine/trade/TradeCategory.java b/src/main/java/com/cubefury/vendingmachine/trade/TradeCategory.java index da740a3..2086753 100644 --- a/src/main/java/com/cubefury/vendingmachine/trade/TradeCategory.java +++ b/src/main/java/com/cubefury/vendingmachine/trade/TradeCategory.java @@ -9,6 +9,7 @@ public enum TradeCategory { + FAVOURITES("favourites", "vendingmachine.category.favourites", "gui/icons/favourites.png"), UNKNOWN("unknown", "vendingmachine.category.unknown", "gui/icons/unknown.png"), ALL("all", "vendingmachine.category.all", "gui/icons/all.png"), COMPONENTS("components", "vendingmachine.category.components", "gui/icons/components.png"), diff --git a/src/main/java/com/cubefury/vendingmachine/trade/TradeDatabase.java b/src/main/java/com/cubefury/vendingmachine/trade/TradeDatabase.java index a6e669a..a912adc 100644 --- a/src/main/java/com/cubefury/vendingmachine/trade/TradeDatabase.java +++ b/src/main/java/com/cubefury/vendingmachine/trade/TradeDatabase.java @@ -13,12 +13,11 @@ import net.minecraft.nbt.NBTTagList; import net.minecraftforge.common.util.Constants; +import com.cubefury.vendingmachine.VMConfig; import com.cubefury.vendingmachine.VendingMachine; import com.cubefury.vendingmachine.integration.betterquesting.BqAdapter; import com.cubefury.vendingmachine.integration.nei.NeiRecipeCache; -import com.cubefury.vendingmachine.util.NBTConverter; -import cpw.mods.fml.common.Optional; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; @@ -29,17 +28,16 @@ public class TradeDatabase { private final Map tradeGroups = new HashMap<>(); private final Map> tradeCategories = new HashMap<>(); + public final List noConditionTrades = new ArrayList<>(); + private TradeDatabase() {} public void clear() { + noConditionTrades.clear(); tradeGroups.clear(); tradeCategories.clear(); } - public void clearTradeState(UUID player) { - tradeGroups.forEach((k, v) -> v.clearTradeState(player)); - } - public TradeGroup getTradeGroupFromId(UUID tgId) { return tradeGroups.get(tgId); } @@ -71,7 +69,7 @@ public int getTradeCount() { .sum(); } - public void readFromNBT(NBTTagCompound nbt, boolean merge) { + public void readFromNBT(NBTTagCompound nbt, boolean merge, boolean isFileLoad) { if (!merge) { this.clear(); if (VendingMachine.isBqLoaded) { @@ -85,16 +83,19 @@ public void readFromNBT(NBTTagCompound nbt, boolean merge) { TradeGroup tg = new TradeGroup(); newMetadataCount += tg.readFromNBT(trades.getCompoundTagAt(i)) ? 1 : 0; if (tradeGroups.containsKey(tg.getId())) { - VendingMachine.LOG.error("Multiple trade groups with id {} exist in the file!", tg); + VendingMachine.LOG.error("Multiple trade groups with id {} exist in the file!", tg.getId()); continue; } + if (tg.hasNoConditions()) { + noConditionTrades.add(tg); + } tradeCategories.computeIfAbsent(tg.getCategory(), k -> new HashSet<>()); tradeCategories.get(tg.getCategory()) .add(tg.getId()); tradeGroups.put(tg.getId(), tg); } - if (newMetadataCount > 0) { + if (isFileLoad && (VMConfig.developer.force_rewrite_database || newMetadataCount > 0)) { VendingMachine.LOG.info("Appended metadata to {} new trades", newMetadataCount); DirtyDbMarker.markDirty(); } @@ -102,63 +103,23 @@ public void readFromNBT(NBTTagCompound nbt, boolean merge) { refreshNeiCache(); } - TradeManager.INSTANCE.recomputeAvailableTrades(null); VendingMachine.LOG.info("Loaded {} trade groups containing {} trades.", getTradeGroupCount(), getTradeCount()); } public NBTTagCompound writeToNBT(NBTTagCompound nbt) { nbt.setInteger("version", this.version); NBTTagList tgList = new NBTTagList(); - for (TradeGroup tg : tradeGroups.values()) { - tgList.appendTag(tg.writeToNBT(new NBTTagCompound())); - } + tradeGroups.values() + .stream() + .sorted(Comparator.comparing(TradeGroup::getId)) + .forEach(tg -> tgList.appendTag(tg.writeToNBT(new NBTTagCompound()))); nbt.setTag("tradeGroups", tgList); return nbt; } - public void populateTradeStateFromNBT(NBTTagCompound nbt, UUID player, boolean merge) { - NBTTagList tradeStateList = nbt.getTagList("tradeState", Constants.NBT.TAG_COMPOUND); - if (!merge) { - clearTradeState(player); - } - for (int i = 0; i < tradeStateList.tagCount(); i++) { - NBTTagCompound state = tradeStateList.getCompoundTagAt(i); - UUID tgId = NBTConverter.UuidValueType.TRADEGROUP.readId(state); - TradeGroup tg = TradeDatabase.INSTANCE.getTradeGroupFromId(tgId); - TradeHistory th = new TradeHistory(state.getLong("lastTrade"), state.getInteger("tradeCount")); - tg.setTradeState(player, th); - } - TradeManager.INSTANCE.populateCurrencyFromNBT(nbt, player, merge); - } - - public NBTTagCompound writeTradeStateToNBT(NBTTagCompound nbt, UUID player) { - NBTTagList tradeStateList = new NBTTagList(); - for (Map.Entry entry : tradeGroups.entrySet()) { - TradeHistory history = entry.getValue() - .getTradeState(player); - if (!history.equals(TradeHistory.DEFAULT)) { - NBTTagCompound state = new NBTTagCompound(); - NBTConverter.UuidValueType.TRADEGROUP.writeId(entry.getKey(), state); - state.setLong("lastTrade", history.lastTrade); - state.setInteger("tradeCount", history.tradeCount); - tradeStateList.appendTag(state); - } - } - nbt.setTag("tradeState", tradeStateList); - nbt.setTag("playerCurrency", TradeManager.INSTANCE.writeCurrencyToNBT(player)); - return nbt; - } - @SideOnly(Side.CLIENT) public void refreshNeiCache() { NeiRecipeCache.refreshCache(); } - @Optional.Method(modid = "betterquesting") - public void removeAllSatisfiedBqConditions(UUID player) { - for (TradeGroup tg : tradeGroups.values()) { - tg.removeAllSatisfiedBqConditions(player); - } - TradeManager.INSTANCE.recomputeAvailableTrades(player); - } } diff --git a/src/main/java/com/cubefury/vendingmachine/trade/TradeGroup.java b/src/main/java/com/cubefury/vendingmachine/trade/TradeGroup.java index 1436b1d..77e02dd 100644 --- a/src/main/java/com/cubefury/vendingmachine/trade/TradeGroup.java +++ b/src/main/java/com/cubefury/vendingmachine/trade/TradeGroup.java @@ -1,11 +1,8 @@ package com.cubefury.vendingmachine.trade; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.UUID; @@ -15,31 +12,19 @@ import com.cubefury.vendingmachine.VendingMachine; import com.cubefury.vendingmachine.api.trade.ICondition; -import com.cubefury.vendingmachine.handlers.SaveLoadHandler; import com.cubefury.vendingmachine.integration.betterquesting.BqAdapter; import com.cubefury.vendingmachine.integration.betterquesting.BqCondition; import com.cubefury.vendingmachine.util.NBTConverter; -import cpw.mods.fml.common.Optional; - public class TradeGroup { private UUID id = new UUID(0, 0); // placeholder UUID private final List trades = new ArrayList<>(); public int cooldown = -1; public int maxTrades = -1; - public String label = ""; private TradeCategory category = TradeCategory.UNKNOWN; private String original_category_str = ""; - private final Set requirementSet = new HashSet<>(); - - // List of completed conditions for each player - // This is only updated server-side, since players only need to know what trades - // they have and their status. - private final Map> playerDone = new HashMap<>(); - - // List of players with trade history - private final Map tradeState = new HashMap<>(); + public final Set requirementSet = new HashSet<>(); public TradeGroup() {} @@ -55,36 +40,6 @@ public boolean hasNoConditions() { return this.requirementSet.isEmpty(); } - public void addSatisfiedCondition(UUID player, ICondition c) { - synchronized (playerDone) { - playerDone.computeIfAbsent(player, k -> new HashSet<>()); - playerDone.get(player) - .add(c); - if ( - playerDone.get(player) - .equals(requirementSet) - ) { - TradeManager.INSTANCE.addTradeGroup(player, this.id); - } - } - } - - public void removeSatisfiedCondition(UUID player, ICondition c) { - synchronized (playerDone) { - if (!playerDone.containsKey(player) || playerDone.get(player) == null) { - return; - } - playerDone.get(player) - .remove(c); - if ( - !playerDone.get(player) - .equals(requirementSet) - ) { - TradeManager.INSTANCE.removeTradeGroup(player, this.id); - } - } - } - public List getTrades() { return trades; } @@ -93,83 +48,8 @@ public TradeCategory getCategory() { return category; } - public List getRequirements() { - return new ArrayList<>(requirementSet); - } - - public void clearTradeState(UUID player) { - synchronized (tradeState) { - if (player == null) { - tradeState.clear(); - } else { - tradeState.remove(player); - } - } - } - - public boolean isUnlockedPlayer(UUID player) { - return requirementSet.equals(playerDone.getOrDefault(player, new HashSet<>())); - } - - public Set getAllUnlockedPlayers() { - Set playerList = new HashSet<>(); - for (Map.Entry> entry : playerDone.entrySet()) { - if ( - entry.getValue() - .equals(requirementSet) - ) { - playerList.add(entry.getKey()); - } - } - return playerList; - } - - public TradeHistory getTradeState(UUID player) { - synchronized (tradeState) { - if (!tradeState.containsKey(player) || tradeState.get(player) == null) { - return new TradeHistory(); - } - return tradeState.get(player); - } - } - - public void setTradeState(UUID player, TradeHistory history) { - synchronized (tradeState) { - tradeState.put(player, history); - - } - } - - public boolean canExecuteTrade(UUID player) { - List availableTrades = TradeManager.INSTANCE.getAvailableTradeGroups(player); - long currentTimestamp = System.currentTimeMillis(); - long lastTradeTime = this.getTradeState(player).lastTrade; - long tradeCount = this.getTradeState(player).tradeCount; - long cooldownRemaining; - if (this.cooldown != -1 && lastTradeTime != -1 && (currentTimestamp - lastTradeTime) / 1000 < this.cooldown) { - cooldownRemaining = this.cooldown - (currentTimestamp - lastTradeTime) / 1000; - } else { - cooldownRemaining = -1; - } - - boolean enabled = this.maxTrades == -1 || tradeCount < this.maxTrades; - - for (TradeGroup trade : availableTrades) { - if (trade == null) { // shouldn't happen - continue; - } - if (trade.id.equals(this.id) && enabled && cooldownRemaining < 0) { - return true; - } - } - return false; - } - - public void executeTrade(UUID player) { - TradeHistory newTradeHistory = getTradeState(player); - newTradeHistory.executeTrade(); - setTradeState(player, newTradeHistory); - SaveLoadHandler.INSTANCE.writeTradeState(Collections.singleton(player)); + public Set getRequirements() { + return requirementSet; } public boolean readFromNBT(NBTTagCompound nbt) { @@ -193,7 +73,6 @@ public boolean readFromNBT(NBTTagCompound nbt) { } this.cooldown = nbt.getInteger("cooldown"); this.maxTrades = nbt.getInteger("maxTrades"); - this.label = nbt.getString("label"); NBTTagList tradeList = nbt.getTagList("trades", Constants.NBT.TAG_COMPOUND); for (int i = 0; i < tradeList.tagCount(); i++) { NBTTagCompound trade = tradeList.getCompoundTagAt(i); @@ -217,7 +96,6 @@ public NBTTagCompound writeToNBT(NBTTagCompound nbt) { nbt.setTag("id", NBTConverter.UuidValueType.TRADEGROUP.writeId(this.id)); nbt.setInteger("cooldown", this.cooldown); nbt.setInteger("maxTrades", this.maxTrades); - nbt.setString("label", this.label); nbt.setString("category", this.category.getKey()); NBTTagList tList = new NBTTagList(); for (Trade t : trades) { @@ -232,25 +110,4 @@ public NBTTagCompound writeToNBT(NBTTagCompound nbt) { return nbt; } - public String getLabel() { - return this.label; - } - - @Optional.Method(modid = "betterquesting") - public void removeAllSatisfiedBqConditions(UUID player) { - synchronized (tradeState) { - if (player == null) { - for (Map.Entry> entry : playerDone.entrySet()) { - if (entry.getValue() == null) { // just in case - continue; - } - entry.getValue() - .removeIf((condition) -> condition instanceof BqCondition); - } - } else if (playerDone.get(player) != null) { - playerDone.get(player) - .removeIf((condition) -> condition instanceof BqCondition); - } - } - } } diff --git a/src/main/java/com/cubefury/vendingmachine/trade/TradeGroupState.java b/src/main/java/com/cubefury/vendingmachine/trade/TradeGroupState.java new file mode 100644 index 0000000..c9590d0 --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/trade/TradeGroupState.java @@ -0,0 +1,71 @@ +package com.cubefury.vendingmachine.trade; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.cubefury.vendingmachine.api.trade.ICondition; + +public class TradeGroupState { + + public TradeGroup tradeGroup; + + // List of completed conditions for each player + // This is only updated server-side, since players only need to know what trades + // they have and their status. + private final Map> playerSatisfied = new HashMap<>(); + + // List of players with trade history + private final Map tradeState = new HashMap<>(); + + public TradeGroupState(TradeGroup tradeGroup) { + this.tradeGroup = tradeGroup; + } + + public void clearTradeState(@Nullable UUID player) { + if (player == null) { + tradeState.clear(); + } else { + tradeState.remove(player); + } + } + + public void setTradeState(@Nonnull UUID player, TradeHistory history) { + tradeState.put(player, history); + } + + public TradeHistory getTradeState(@Nonnull UUID player) { + return tradeState.getOrDefault(player, new TradeHistory()); + } + + public void addConditionSatisfied(@Nonnull UUID player, ICondition c) { + playerSatisfied.putIfAbsent(player, new HashSet<>()); + playerSatisfied.get(player) + .add(c); + } + + public void removeConditionSatisfied(@Nullable UUID player, ICondition c) { + if (player == null) { + for (Set conditions : playerSatisfied.values()) { + conditions.remove(c); + } + } else if (playerSatisfied.containsKey(player)) { + playerSatisfied.get(player) + .remove(c); + } + } + + public boolean satisfiesTrade(@Nonnull UUID player) { + return playerSatisfied.get(player) + .equals(tradeGroup.getRequirements()); + } + + public Set getPlayersWithConditionData() { + return playerSatisfied.keySet(); + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/trade/TradeHistory.java b/src/main/java/com/cubefury/vendingmachine/trade/TradeHistory.java index 537a2c4..bb80b77 100644 --- a/src/main/java/com/cubefury/vendingmachine/trade/TradeHistory.java +++ b/src/main/java/com/cubefury/vendingmachine/trade/TradeHistory.java @@ -4,28 +4,37 @@ public class TradeHistory { public long lastTrade = -1; public int tradeCount = 0; + public boolean notificationQueued = false; public static TradeHistory DEFAULT = new TradeHistory(); public TradeHistory() {} - public TradeHistory(long lastTrade, int tradeCount) { + public TradeHistory(long lastTrade, int tradeCount, boolean notificationQueued) { this.lastTrade = lastTrade; - this.tradeCount = 0; + this.tradeCount = tradeCount; + this.notificationQueued = notificationQueued; } - public void executeTrade() { + public void executeTrade(int maxTrades, boolean hasCooldown) { lastTrade = System.currentTimeMillis(); tradeCount += 1; + notificationQueued = hasCooldown && (maxTrades == -1 || tradeCount < maxTrades); + } + + public void setNotified() { + notificationQueued = false; } public void resetData() { lastTrade = -1; tradeCount = 0; + notificationQueued = false; } - public void resetTradeAvailability() { + public void resetTradeAvailability(boolean notifyPlayer) { lastTrade = -1; + notificationQueued = notifyPlayer; } @Override diff --git a/src/main/java/com/cubefury/vendingmachine/trade/TradeManager.java b/src/main/java/com/cubefury/vendingmachine/trade/TradeManager.java index d527cd6..18f4ee5 100644 --- a/src/main/java/com/cubefury/vendingmachine/trade/TradeManager.java +++ b/src/main/java/com/cubefury/vendingmachine/trade/TradeManager.java @@ -1,6 +1,7 @@ package com.cubefury.vendingmachine.trade; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -8,23 +9,42 @@ import java.util.Set; import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraftforge.common.util.Constants; import com.cubefury.vendingmachine.VendingMachine; +import com.cubefury.vendingmachine.api.trade.ICondition; import com.cubefury.vendingmachine.blocks.gui.TradeItemDisplay; +import com.cubefury.vendingmachine.handlers.SaveLoadHandler; +import com.cubefury.vendingmachine.network.handlers.NetTradeNotification; +import com.cubefury.vendingmachine.storage.NameCache; +import com.cubefury.vendingmachine.util.NBTConverter; -// This is a cache of available trades, maintained server-side -// so we don't have to recompute what trades are available every time we send it +// Sync the following objects to the client every GUI refresh cycle: +// tradedata, No-condition trades and currency +// Everything else is stored server-side public class TradeManager { public static TradeManager INSTANCE = new TradeManager(); + // availableTrades and noCondition trades technically have information that + // is extractable from tradegroupStates, but querying that every second + // for gui display is more expensive so we cache it here private final Map> availableTrades = new HashMap<>(); - private final List noConditionTrades = new ArrayList<>(); - public final Map> playerCurrency = new HashMap<>(); + // Map for tradegroup id -> player trade states and unlock status + public final Map tradeGroupStates = new HashMap<>(); + + // Map for player id -> currency data + public final Map> playerCurrency = new HashMap<>(); + + // Map for player id -> trades with pending refresh notifications + public final Map> notificationQueue = new HashMap<>(); // For writeback to file in original format, to prevent data loss private final Map> invalidCurrency = new HashMap<>(); @@ -35,82 +55,74 @@ public class TradeManager { private TradeManager() {} - public void addTradeGroup(UUID player, UUID tg) { - synchronized (availableTrades) { - if (!availableTrades.containsKey(player) || availableTrades.get(player) == null) { - availableTrades.put(player, new HashSet<>()); - } + public void addTradeGroup(@Nonnull UUID player, UUID tg) { + availableTrades.putIfAbsent(player, new HashSet<>()); + availableTrades.get(player) + .add(tg); + } + + public void removeTradeGroup(UUID player, UUID tg) { + if (availableTrades.containsKey(player)) { availableTrades.get(player) - .add(tg); + .remove(tg); } } - public void removeTradeGroup(UUID player, UUID tg) { - synchronized (availableTrades) { - if (availableTrades.get(player) != null) { - availableTrades.get(player) - .remove(tg); + public void addSatisfiedCondition(TradeGroup tradeGroup, @Nonnull UUID player, ICondition c) { + UUID tradeGroupId = tradeGroup.getId(); + tradeGroupStates.putIfAbsent(tradeGroupId, new TradeGroupState(tradeGroup)); + tradeGroupStates.get(tradeGroupId) + .addConditionSatisfied(player, c); + + updateAvailableTrades(tradeGroupId, player); + } + + public void removeSatisfiedCondition(TradeGroup tradeGroup, @Nullable UUID player, ICondition c) { + UUID tradeGroupId = tradeGroup.getId(); + tradeGroupStates.putIfAbsent(tradeGroupId, new TradeGroupState(tradeGroup)); + tradeGroupStates.get(tradeGroupId) + .removeConditionSatisfied(player, c); + + if (player == null) { + for (UUID p : tradeGroupStates.get(tradeGroupId) + .getPlayersWithConditionData()) { + updateAvailableTrades(tradeGroupId, p); } + } else { + updateAvailableTrades(tradeGroupId, player); } } - public void recomputeAvailableTrades(UUID player) { - synchronized (availableTrades) { - availableTrades.clear(); - if (player == null) { // only reset no condition trades for entire database reload - noConditionTrades.clear(); - } - for (Map.Entry entry : TradeDatabase.INSTANCE.getTradeGroups() - .entrySet()) { - if ( - entry.getValue() - .hasNoConditions() - ) { - noConditionTrades.add(entry.getKey()); - } - if (player == null) { - for (UUID p : entry.getValue() - .getAllUnlockedPlayers()) { - availableTrades.computeIfAbsent(p, k -> new HashSet<>()); - availableTrades.get(p) - .add(entry.getKey()); - } - } else if ( - entry.getValue() - .isUnlockedPlayer(player) - ) { - availableTrades.computeIfAbsent(player, k -> new HashSet<>()); - availableTrades.get(player) - .add(entry.getKey()); - } - } + private void updateAvailableTrades(UUID tradeGroupId, @Nonnull UUID player) { + if ( + tradeGroupStates.get(tradeGroupId) + .satisfiesTrade(player) + ) { + addTradeGroup(player, tradeGroupId); + } else { + removeTradeGroup(player, tradeGroupId); } } public List getAvailableTradeGroups(UUID player) { - synchronized (availableTrades) { - availableTrades.computeIfAbsent(player, k -> new HashSet<>()); - availableTrades.get(player) - .addAll(noConditionTrades); - ArrayList tradeList = new ArrayList<>(); - for (UUID tgId : availableTrades.get(player)) { - tradeList.add(TradeDatabase.INSTANCE.getTradeGroupFromId(tgId)); - } - return tradeList; + availableTrades.putIfAbsent(player, new HashSet<>()); + ArrayList tradeList = new ArrayList<>(); + for (UUID tgId : availableTrades.get(player)) { + tradeList.add(TradeDatabase.INSTANCE.getTradeGroupFromId(tgId)); } + tradeList.addAll(TradeDatabase.INSTANCE.noConditionTrades); + return tradeList; } public void populateCurrencyFromNBT(NBTTagCompound nbt, UUID player, boolean merge) { NBTTagList tagList = nbt.getTagList("playerCurrency", Constants.NBT.TAG_COMPOUND); if (!merge) { - this.playerCurrency.clear(); - this.invalidCurrency.clear(); + this.clearCurrency(player); } this.playerCurrency.computeIfAbsent(player, k -> new HashMap<>()); for (int i = 0; i < tagList.tagCount(); i++) { NBTTagCompound currencyEntry = tagList.getCompoundTagAt(i); - CurrencyItem.CurrencyType type = CurrencyItem.CurrencyType - .getTypeFromId(currencyEntry.getString("currency")); + CurrencyType type = CurrencyType.getTypeFromId(currencyEntry.getString("currency")); if (type == null) { VendingMachine.LOG.warn("Unknown currency type found: {}", currencyEntry.getString("currency")); this.invalidCurrency.computeIfAbsent(player, k -> new ArrayList<>()); @@ -130,28 +142,129 @@ public void populateCurrencyFromNBT(NBTTagCompound nbt, UUID player, boolean mer this.hasCurrencyUpdate = true; } - public NBTTagList writeCurrencyToNBT(UUID player) { - NBTTagList nbt = new NBTTagList(); + public NBTTagCompound writeCurrencyToNBT(NBTTagCompound nbt, @Nonnull UUID player) { if (this.playerCurrency.get(player) == null) { return nbt; } - for (Map.Entry entry : this.playerCurrency.get(player) + NBTTagList nbtCurrencyList = new NBTTagList(); + for (Map.Entry entry : this.playerCurrency.get(player) .entrySet()) { NBTTagCompound currencyEntry = new NBTTagCompound(); currencyEntry.setString("currency", entry.getKey().id); currencyEntry.setInteger("amount", entry.getValue()); - nbt.appendTag(currencyEntry); + nbtCurrencyList.appendTag(currencyEntry); } if (this.invalidCurrency.get(player) != null) { for (NBTTagCompound tag : this.invalidCurrency.get(player)) { - nbt.appendTag(tag); + nbtCurrencyList.appendTag(tag); } } + nbt.setTag("playerCurrency", nbtCurrencyList); return nbt; } - public void resetCurrency(UUID playerId, CurrencyItem.CurrencyType type) { + public void clearTradeState(@Nullable UUID player) { + tradeGroupStates.forEach((uuid, tgs) -> tgs.clearTradeState(player)); + clearCurrency(player); + clearNotificationQueue(player); + if (player == null) { + availableTrades.clear(); + } else { + availableTrades.remove(player); + } + } + + public TradeHistory getTradeState(@Nonnull UUID player, TradeGroup tg) { + tradeGroupStates.putIfAbsent(tg.getId(), new TradeGroupState(tg)); + return tradeGroupStates.get(tg.getId()) + .getTradeState(player); + } + + public void setTradeState(@Nonnull UUID player, TradeGroup tg, TradeHistory history) { + tradeGroupStates.putIfAbsent(tg.getId(), new TradeGroupState(tg)); + tradeGroupStates.get(tg.getId()) + .setTradeState(player, history); + } + + public boolean canExecuteTrade(@Nonnull UUID player, TradeGroup tg) { + if (tg == null) { + return false; + } + long currentTimestamp = System.currentTimeMillis(); + TradeHistory history = getTradeState(player, tg); + long lastTradeTime = history.lastTrade; + long tradeCount = history.tradeCount; + long cooldownRemaining; + if (tg.cooldown != -1 && lastTradeTime != -1 && (currentTimestamp - lastTradeTime) / 1000 < tg.cooldown) { + cooldownRemaining = tg.cooldown - (currentTimestamp - lastTradeTime) / 1000; + } else { + cooldownRemaining = -1; + } + + boolean enabled = tg.maxTrades == -1 || tradeCount < tg.maxTrades; + + return availableTrades.getOrDefault(player, Collections.emptySet()) + .contains(tg.getId()) && enabled + && cooldownRemaining < 0; + } + + public void executeTrade(@Nonnull UUID player, TradeGroup tg) { + TradeHistory newTradeHistory = getTradeState(player, tg); + newTradeHistory.executeTrade(tg.maxTrades, tg.cooldown != -1); + setTradeState(player, tg, newTradeHistory); + SaveLoadHandler.INSTANCE.writeTradeState(Collections.singleton(player)); + if (newTradeHistory.notificationQueued) { + addNotification(player, tg); + } + } + + public void populateTradeStateFromNBT(NBTTagCompound nbt, @Nonnull UUID player, boolean merge) { + NBTTagList tradeStateList = nbt.getTagList("tradeState", Constants.NBT.TAG_COMPOUND); + if (!merge) { + clearTradeState(player); + } + for (int i = 0; i < tradeStateList.tagCount(); i++) { + NBTTagCompound state = tradeStateList.getCompoundTagAt(i); + UUID tgId = NBTConverter.UuidValueType.TRADEGROUP.readId(state); + TradeGroup tg = TradeDatabase.INSTANCE.getTradeGroupFromId(tgId); + boolean notificationQueued = state.getBoolean("notificationQueued"); + TradeHistory th = new TradeHistory( + state.getLong("lastTrade"), + state.getInteger("tradeCount"), + notificationQueued); + if (tg != null) { + tradeGroupStates.putIfAbsent(tg.getId(), new TradeGroupState(tg)); + tradeGroupStates.get(tg.getId()) + .setTradeState(player, th); + if (notificationQueued) { + addNotification(player, tg); + } + } + } + populateCurrencyFromNBT(nbt, player, merge); + } + + public NBTTagCompound writeTradeStateToNBT(NBTTagCompound nbt, @Nonnull UUID player) { + NBTTagList tradeStateList = new NBTTagList(); + for (Map.Entry entry : tradeGroupStates.entrySet()) { + TradeHistory history = entry.getValue() + .getTradeState(player); + if (!history.equals(TradeHistory.DEFAULT)) { + NBTTagCompound state = new NBTTagCompound(); + NBTConverter.UuidValueType.TRADEGROUP.writeId(entry.getKey(), state); + state.setLong("lastTrade", history.lastTrade); + state.setInteger("tradeCount", history.tradeCount); + state.setBoolean("notificationQueued", history.notificationQueued); + tradeStateList.appendTag(state); + } + } + nbt.setTag("tradeState", tradeStateList); + writeCurrencyToNBT(nbt, player); + return nbt; + } + + public void resetCurrency(UUID playerId, CurrencyType type) { this.playerCurrency.computeIfAbsent(playerId, k -> new HashMap<>()); if (type == null) { this.playerCurrency.get(playerId) @@ -176,4 +289,60 @@ public void addCurrency(UUID playerId, CurrencyItem mapped) { } this.hasCurrencyUpdate = true; } + + public void clearCurrency(UUID player) { + if (player == null) { + this.playerCurrency.clear(); + this.invalidCurrency.clear(); + } else { + this.playerCurrency.remove(player); + this.invalidCurrency.remove(player); + } + } + + public void clearNotificationQueue(UUID playerOrNull) { + if (playerOrNull == null) { + this.notificationQueue.clear(); + } else { + this.notificationQueue.remove(playerOrNull); + } + } + + public void addNotification(UUID player, TradeGroup tg) { + this.notificationQueue.putIfAbsent(player, new HashSet<>()); + this.notificationQueue.get(player) + .add(tg.getId()); + } + + public void sendTradeNotifications(EntityPlayerMP player) { + UUID playerId = NameCache.INSTANCE.getUUIDFromPlayer(player); + Set notifGroups = this.notificationQueue.get(playerId); + if (notifGroups == null) { + return; + } + + long currentTimestamp = System.currentTimeMillis(); + + List notifList = new ArrayList<>(); + for (UUID tgId : notifGroups) { + TradeGroup tradeGroup = TradeDatabase.INSTANCE.getTradeGroupFromId(tgId); + if (tradeGroup == null) { + continue; + } + TradeHistory th = getTradeState(playerId, tradeGroup); + if ((currentTimestamp - th.lastTrade) / 1000 > tradeGroup.cooldown) { + notifList.add(tgId); + th.setNotified(); + } + } + + if (!notifList.isEmpty()) { + NetTradeNotification.sendNotification(player, notifList); + SaveLoadHandler.INSTANCE.writeTradeState(Collections.singleton(playerId)); + } + + for (UUID tg : notifList) { + notifGroups.remove(tg); + } + } } diff --git a/src/main/java/com/cubefury/vendingmachine/util/BigItemStack.java b/src/main/java/com/cubefury/vendingmachine/util/BigItemStack.java index f0e23ea..a84d2a6 100644 --- a/src/main/java/com/cubefury/vendingmachine/util/BigItemStack.java +++ b/src/main/java/com/cubefury/vendingmachine/util/BigItemStack.java @@ -72,7 +72,7 @@ public ItemStack getBaseStack() { @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean hasOreDict() { - return !StringUtils.isNullOrEmpty(this.oreDict) && this.oreIng.getMatchingStacks().length > 0; + return !StringUtils.isNullOrEmpty(this.oreDict); } @Nonnull diff --git a/src/main/java/com/cubefury/vendingmachine/util/JsonHelper.java b/src/main/java/com/cubefury/vendingmachine/util/JsonHelper.java index 0dc336f..efef577 100644 --- a/src/main/java/com/cubefury/vendingmachine/util/JsonHelper.java +++ b/src/main/java/com/cubefury/vendingmachine/util/JsonHelper.java @@ -13,7 +13,9 @@ import net.minecraftforge.common.util.Constants; import com.cubefury.vendingmachine.storage.NameCache; +import com.cubefury.vendingmachine.trade.FavouritesTracker; import com.cubefury.vendingmachine.trade.TradeDatabase; +import com.cubefury.vendingmachine.trade.TradeManager; import com.google.gson.JsonObject; public class JsonHelper { @@ -50,34 +52,36 @@ public static NBTTagCompound ItemStackToJson(BigItemStack stack, NBTTagCompound public static void populateTradeDatabaseFromFile(File file) { TradeDatabase db = TradeDatabase.INSTANCE; - db.clear(); Function readNbt = f -> NBTConverter .JSONtoNBT_Object(FileIO.ReadFromFile(f), new NBTTagCompound(), true); - db.readFromNBT(readNbt.apply(file), false); + db.readFromNBT(readNbt.apply(file), false, true); } public static void populateTradeStateFromFiles(List files) { - TradeDatabase db = TradeDatabase.INSTANCE; - db.clearTradeState(null); files.forEach(JsonHelper::populateTradeStateFromFile); } public static void populateTradeStateFromFile(File file) { JsonObject json = FileIO.ReadFromFile(file); NBTTagCompound nbt = NBTConverter.JSONtoNBT_Object(json, new NBTTagCompound(), true); - TradeDatabase.INSTANCE.populateTradeStateFromNBT(nbt, UUID.fromString(FileIO.getFileName(file)), false); + TradeManager.INSTANCE.populateTradeStateFromNBT(nbt, UUID.fromString(FileIO.getFileName(file)), false); } public static void populateNameCacheFromFile(File file) { - NameCache.INSTANCE.clear(); JsonObject json = FileIO.ReadFromFile(file); NBTTagCompound nbt = NBTConverter.JSONtoNBT_Object(json, new NBTTagCompound(), true); NameCache.INSTANCE.readFromNBT(nbt.getTagList("nameCache", Constants.NBT.TAG_COMPOUND), false); } + public static void populateFavouritesFromFile(File file) { + JsonObject json = FileIO.ReadFromFile(file); + NBTTagCompound nbt = NBTConverter.JSONtoNBT_Object(json, new NBTTagCompound(), true); + FavouritesTracker.INSTANCE.readFromNBT(nbt, false); + } + @FunctionalInterface public interface IOConsumer { diff --git a/src/main/java/com/cubefury/vendingmachine/util/NBTConverter.java b/src/main/java/com/cubefury/vendingmachine/util/NBTConverter.java index 5daf73f..660194f 100644 --- a/src/main/java/com/cubefury/vendingmachine/util/NBTConverter.java +++ b/src/main/java/com/cubefury/vendingmachine/util/NBTConverter.java @@ -10,7 +10,6 @@ import java.util.List; import java.util.Map.Entry; import java.util.Optional; -import java.util.Set; import java.util.TreeSet; import java.util.UUID; import java.util.stream.Collectors; @@ -176,20 +175,11 @@ else if (value instanceof NBTTagByteArray) { out.endArray(); } else if (value instanceof NBTTagList) { List tagList = getTagList((NBTTagList) value); - if (format) { - out.beginObject(); - for (int i = 0; i < tagList.size(); i++) { - NBTBase tag = tagList.get(i); - out.name(i + ":" + tag.getId()); - NBTtoJSON_Base(tag, true, out); - } - out.endObject(); - } else { - out.beginArray(); - for (NBTBase tag : tagList) { - NBTtoJSON_Base(tag, false, out); - } + out.beginArray(); + for (NBTBase tag : tagList) { + NBTtoJSON_Base(tag, format, out); } + out.endArray(); } else if (value instanceof NBTTagCompound) { NBTtoJSON_Compound((NBTTagCompound) value, out, format); } else { @@ -203,17 +193,20 @@ else if (value instanceof NBTTagByteArray) { public static void NBTtoJSON_Compound(NBTTagCompound parent, JsonWriter out, boolean format) throws IOException { out.beginObject(); - if (parent != null) for (String key : (Set) parent.func_150296_c()) { - NBTBase tag = parent.getTag(key); + if (parent != null) for (String key : parent.func_150296_c() + .stream() + .sorted(String::compareTo) + .collect(Collectors.toList())) { + NBTBase tag = parent.getTag(key); - if (format) { - out.name(key + ":" + tag.getId()); - NBTtoJSON_Base(tag, true, out); - } else { - out.name(key); - NBTtoJSON_Base(tag, false, out); + if (format) { + out.name(key + ":" + tag.getId()); + NBTtoJSON_Base(tag, true, out); + } else { + out.name(key); + NBTtoJSON_Base(tag, false, out); + } } - } out.endObject(); } @@ -233,31 +226,15 @@ private static JsonElement NBTtoJSON_Base(NBTBase tag, boolean format) { } else if (tag instanceof NBTTagCompound) { return NBTtoJSON_Compound((NBTTagCompound) tag, new JsonObject(), format); } else if (tag instanceof NBTTagList) { - if (format) { - JsonObject jAry = new JsonObject(); - - List tagList = getTagList((NBTTagList) tag); - - for (int i = 0; i < tagList.size(); i++) { - jAry.add( - i + ":" - + tagList.get(i) - .getId(), - NBTtoJSON_Base(tagList.get(i), true)); - } - - return jAry; - } else { - JsonArray jAry = new JsonArray(); - - List tagList = getTagList((NBTTagList) tag); + JsonArray jAry = new JsonArray(); - for (NBTBase t : tagList) { - jAry.add(NBTtoJSON_Base(t, false)); - } + List tagList = getTagList((NBTTagList) tag); - return jAry; + for (NBTBase t : tagList) { + jAry.add(NBTtoJSON_Base(t, format)); } + + return jAry; } else if (tag instanceof NBTTagByteArray) { JsonArray jAry = new JsonArray(); diff --git a/src/main/resources/assets/vendingmachine/lang/en_US.lang b/src/main/resources/assets/vendingmachine/lang/en_US.lang index 70de284..089a342 100644 --- a/src/main/resources/assets/vendingmachine/lang/en_US.lang +++ b/src/main/resources/assets/vendingmachine/lang/en_US.lang @@ -2,6 +2,8 @@ item.vendingmachine.placeholder.name=Placeholder Item tooltip.vendingmachine=Who's even restocking this... +structure.vendingmachine.hint.1=Hint 1 Dot: Tin Item Pipe Casing, ME Vending Uplink Hatch + vendingmachine.gui.requirementHeader=Requirements: vendingmachine.gui.requirement.unknown=Unknown Requirement vendingmachine.gui.requirement.betterquesting=Quest @@ -14,17 +16,33 @@ vendingmachine.gui.coin_eject=Eject All Coins vendingmachine.gui.single_coin_type_eject_hint=Shift-Click to Extract vendingmachine.gui.search=Search vendingmachine.gui.required_inputs=Requires: - +vendingmachine.gui.nc_inputs=Requires (Not Consumed): +vendingmachine.gui.alternative_oredict=Accepts Oredict: +vendingmachine.gui.nc_inputs_overlay_color=FDD835 +vendingmachine.gui.shared_trades_tooltip=Shares cooldown with %s other trade(s) +vendingmachine.gui.trade_hint=Shift-Click to Purchase +vendingmachine.gui.favourite_hint=Ctrl-Click to Toggle as Favourite +vendingmachine.gui.display_mode=Display: +vendingmachine.gui.display_mode_tile=Tiles +vendingmachine.gui.display_mode_list=List +vendingmachine.gui.display_sort=Sort: +vendingmachine.gui.display_sort_smart=Smart +vendingmachine.gui.display_sort_alphabet=Alphabetical Order +vendingmachine.gui.display_text_color=000000 vendingmachine.gui.cooldown_display.second=s vendingmachine.gui.cooldown_display.minute=m vendingmachine.gui.cooldown_display.hour=h vendingmachine.gui.cooldown_display.day=d +vendingmachine.gui.error.incomplete_structure=Incomplete Structure. vendingmachine.gui.error.player_using=Someone is using the vending machine at the moment. +vendingmachine.chat.trade_restock=Vending Machine Restocked: %s gt.blockmachines.multimachine.vendingmachine.name=Vending Machine +gt.blockmachines.multimachine.vendingmachine.name.gui=Vending Machine hatch.vendinguplink.me.name=ME Vending Uplink Hatch +vendingmachine.category.favourites=Favourites vendingmachine.category.unknown=Unknown Category vendingmachine.category.all=All Items vendingmachine.category.components=Components @@ -35,17 +53,17 @@ vendingmachine.category.magic=Magic vendingmachine.category.bees=Bees vendingmachine.category.misc=Miscellaneous -vendingmachine.coin.adventure=Adventure Coin -vendingmachine.coin.bees=Bees Coin -vendingmachine.coin.blood=Blood Magic Coin -vendingmachine.coin.chemist=Chemistry Coin +vendingmachine.coin.adventure=Explorer Coin +vendingmachine.coin.bees=Beekeeper Coin +vendingmachine.coin.blood=Vampire Coin +vendingmachine.coin.chemist=Chemist Coin vendingmachine.coin.cook=Cook Coin vendingmachine.coin.darkWizard=Dark Wizard Coin -vendingmachine.coin.farmer=Farming Coin -vendingmachine.coin.flower=Flower Coin -vendingmachine.coin.forestry=Forestry Coin -vendingmachine.coin.smith=Smith Coin -vendingmachine.coin.space=Space Coin +vendingmachine.coin.farmer=Farmer Coin +vendingmachine.coin.flower=Gardener Coin +vendingmachine.coin.forestry=Forest Ranger Coin +vendingmachine.coin.smith=Blacksmith Coin +vendingmachine.coin.space=Space Invaders Coin vendingmachine.coin.survivor=Survivor Coin vendingmachine.coin.technician=Technician Coin -vendingmachine.coin.witch=Witchery Coin +vendingmachine.coin.witch=Spirit Coin diff --git a/src/main/resources/assets/vendingmachine/lang/zh_CN.lang b/src/main/resources/assets/vendingmachine/lang/zh_CN.lang index 2893c68..40f3eea 100644 --- a/src/main/resources/assets/vendingmachine/lang/zh_CN.lang +++ b/src/main/resources/assets/vendingmachine/lang/zh_CN.lang @@ -2,6 +2,8 @@ item.vendingmachine.placeholder.name=占位符物品 tooltip.vendingmachine=到底是谁在补货啊... +structure.vendingmachine.hint.1=提示 1:锡质物品管道外壳,ME自动售货接入接口 + vendingmachine.gui.requirementHeader=需求条件: vendingmachine.gui.requirement.unknown=未知需求 vendingmachine.gui.requirement.betterquesting=任务 @@ -10,15 +12,22 @@ vendingmachine.gui.neiColor.conditionDefault=000000 vendingmachine.gui.neiColor.conditionSatisfied=55D441 vendingmachine.gui.neiColor.conditionUnsatisfied=A87A5E vendingmachine.gui.item_eject=取出物品 +vendingmachine.gui.coin_eject=取出所有代币 +vendingmachine.gui.single_coin_type_eject_hint=Shift-点击提取 vendingmachine.gui.search=搜索 vendingmachine.gui.required_inputs=需要: +vendingmachine.gui.trade_hint=Shift-点击购买 vendingmachine.gui.cooldown_display.second=秒 vendingmachine.gui.cooldown_display.minute=分 vendingmachine.gui.cooldown_display.hour=时 vendingmachine.gui.cooldown_display.day=天 +vendingmachine.gui.error.player_using=当前有其他玩家正在使用此自动售货机 + gt.blockmachines.multimachine.vendingmachine.name=自动售货机 +gt.blockmachines.multimachine.vendingmachine.name.gui=自动售货机 +hatch.vendinguplink.me.name=ME自动售货接入接口 vendingmachine.category.unknown=未知分类 vendingmachine.category.all=全部物品 @@ -29,3 +38,18 @@ vendingmachine.category.chemistry=化工 vendingmachine.category.magic=魔法 vendingmachine.category.bees=蜜蜂 vendingmachine.category.misc=杂项 + +vendingmachine.coin.adventure=探险家代币 +vendingmachine.coin.bees=养蜂员代币 +vendingmachine.coin.blood=吸血鬼代币 +vendingmachine.coin.chemist=化学家代币 +vendingmachine.coin.cook=厨师代币 +vendingmachine.coin.darkWizard=魔法师代币 +vendingmachine.coin.farmer=农民代币 +vendingmachine.coin.flower=园艺代币 +vendingmachine.coin.forestry=护林员代币 +vendingmachine.coin.smith=匠师代币 +vendingmachine.coin.space=太空代币 +vendingmachine.coin.survivor=幸存者代币 +vendingmachine.coin.technician=技术员代币 +vendingmachine.coin.witch=巫师代币 diff --git a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_front_off.png b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_front_off.png index 00dd320..145c55d 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_front_off.png and b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_front_off.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_front_on.png b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_front_on.png index 077e216..1acf033 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_front_on.png and b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_front_on.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_front_on_glow.png b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_front_on_glow.png index 4e8f026..97594e1 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_front_on_glow.png and b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_front_on_glow.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_0.png b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_0.png index 01f9b6a..e982eee 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_0.png and b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_0.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_1.png b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_1.png index 6b13f22..accdb81 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_1.png and b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_1.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_2.png b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_2.png index 3a3d665..f6c2574 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_2.png and b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_2.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_3.png b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_3.png index 8202d98..26d2982 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_3.png and b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_3.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_4.png b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_4.png index 69d5b65..3825c65 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_4.png and b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_4.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_active_0.png b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_active_0.png index 9586849..907c905 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_active_0.png and b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_active_0.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_active_1.png b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_active_1.png index 5f668fc..0f62ed0 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_active_1.png and b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_active_1.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_active_2.png b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_active_2.png index 31c1708..1fbb94e 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_active_2.png and b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_active_2.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_active_3.png b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_active_3.png index 564c5f6..7bc2f7a 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_active_3.png and b/src/main/resources/assets/vendingmachine/textures/blocks/vending_machine_overlay_active_3.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/blocks/vending_uplink_machine_overlay_active.png b/src/main/resources/assets/vendingmachine/textures/blocks/vending_uplink_machine_overlay_active.png new file mode 100644 index 0000000..ae64b49 Binary files /dev/null and b/src/main/resources/assets/vendingmachine/textures/blocks/vending_uplink_machine_overlay_active.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/blocks/vending_uplink_machine_overlay_inactive.png b/src/main/resources/assets/vendingmachine/textures/blocks/vending_uplink_machine_overlay_inactive.png new file mode 100644 index 0000000..cda493b Binary files /dev/null and b/src/main/resources/assets/vendingmachine/textures/blocks/vending_uplink_machine_overlay_inactive.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/background/input.png b/src/main/resources/assets/vendingmachine/textures/gui/background/input.png index 67cd44c..d1f01f7 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/background/input.png and b/src/main/resources/assets/vendingmachine/textures/gui/background/input.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/background/trade_button_unpressed_color_corrected.png b/src/main/resources/assets/vendingmachine/textures/gui/background/list_trade_button_pressed.png similarity index 83% rename from src/main/resources/assets/vendingmachine/textures/gui/background/trade_button_unpressed_color_corrected.png rename to src/main/resources/assets/vendingmachine/textures/gui/background/list_trade_button_pressed.png index 13c9e01..dc9637f 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/background/trade_button_unpressed_color_corrected.png and b/src/main/resources/assets/vendingmachine/textures/gui/background/list_trade_button_pressed.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/background/list_trade_button_unpressed.png b/src/main/resources/assets/vendingmachine/textures/gui/background/list_trade_button_unpressed.png new file mode 100644 index 0000000..bd92298 Binary files /dev/null and b/src/main/resources/assets/vendingmachine/textures/gui/background/list_trade_button_unpressed.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/background/output.png b/src/main/resources/assets/vendingmachine/textures/gui/background/output.png index 8aa56a6..08ca9e6 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/background/output.png and b/src/main/resources/assets/vendingmachine/textures/gui/background/output.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/background/panel_side.png b/src/main/resources/assets/vendingmachine/textures/gui/background/panel_side.png index 030802c..9a40710 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/background/panel_side.png and b/src/main/resources/assets/vendingmachine/textures/gui/background/panel_side.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/background/trade_available.png b/src/main/resources/assets/vendingmachine/textures/gui/background/trade_available.png index ef1ac0c..a28035d 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/background/trade_available.png and b/src/main/resources/assets/vendingmachine/textures/gui/background/trade_available.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/background/trade_button_pressed.png b/src/main/resources/assets/vendingmachine/textures/gui/background/trade_button_pressed.png index 2b58e44..666d207 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/background/trade_button_pressed.png and b/src/main/resources/assets/vendingmachine/textures/gui/background/trade_button_pressed.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/background/trade_button_pressed_color_corrected.png b/src/main/resources/assets/vendingmachine/textures/gui/background/trade_button_pressed_color_corrected.png deleted file mode 100644 index 1c15f01..0000000 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/background/trade_button_pressed_color_corrected.png and /dev/null differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/background/trade_button_unpressed.png b/src/main/resources/assets/vendingmachine/textures/gui/background/trade_button_unpressed.png index f94c958..3226a22 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/background/trade_button_unpressed.png and b/src/main/resources/assets/vendingmachine/textures/gui/background/trade_button_unpressed.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/all.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/all.png index 20ae98f..fb0f7d2 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/all.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/all.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/bees.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/bees.png index 09fad74..989104d 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/bees.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/bees.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/chemistry.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/chemistry.png index a3c751d..e95e646 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/chemistry.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/chemistry.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/components.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/components.png index 23359d7..1771d2d 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/components.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/components.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/farming.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/farming.png index e6a2850..6120f2e 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/farming.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/farming.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/favourite_indicator.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/favourite_indicator.png new file mode 100644 index 0000000..563c958 Binary files /dev/null and b/src/main/resources/assets/vendingmachine/textures/gui/icons/favourite_indicator.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/favourites.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/favourites.png new file mode 100644 index 0000000..db3c47d Binary files /dev/null and b/src/main/resources/assets/vendingmachine/textures/gui/icons/favourites.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinAdventure.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinAdventure.png index ec3bc2c..eb13149 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinAdventure.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinAdventure.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinBees.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinBees.png index 98e159d..df10327 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinBees.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinBees.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinBlood.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinBlood.png index 1212f7d..9add9b7 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinBlood.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinBlood.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinChemist.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinChemist.png index c5f2ca3..6dc767d 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinChemist.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinChemist.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinCook.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinCook.png index a39e441..5311dba 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinCook.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinCook.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinDarkWizard.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinDarkWizard.png index 1cd7b01..92057a5 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinDarkWizard.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinDarkWizard.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinFarmer.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinFarmer.png index bd1661f..a64b0d0 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinFarmer.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinFarmer.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinFlower.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinFlower.png index ce3c23a..4b30e3a 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinFlower.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinFlower.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinForestry.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinForestry.png index feb9468..3bc81b8 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinForestry.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinForestry.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinSmith.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinSmith.png index 98855aa..9636ce7 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinSmith.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinSmith.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinSpace.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinSpace.png index 17155bc..a75994c 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinSpace.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinSpace.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinSurvivor.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinSurvivor.png index 1c3362a..386354f 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinSurvivor.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinSurvivor.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinTechnician.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinTechnician.png index efcbd29..06f0aec 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinTechnician.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinTechnician.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinWitch.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinWitch.png index 890d83e..7353dfe 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinWitch.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/itemCoinWitch.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/magic.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/magic.png index a91a627..65f5fbd 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/magic.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/magic.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/misc.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/misc.png index 1413982..784576b 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/misc.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/misc.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/raw.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/raw.png index 5c7fe4c..029a798 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/raw.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/raw.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/icons/unknown.png b/src/main/resources/assets/vendingmachine/textures/gui/icons/unknown.png index 25bddf9..acaf3e0 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/icons/unknown.png and b/src/main/resources/assets/vendingmachine/textures/gui/icons/unknown.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/nei.png b/src/main/resources/assets/vendingmachine/textures/gui/nei.png index be099bd..15186a3 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/nei.png and b/src/main/resources/assets/vendingmachine/textures/gui/nei.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/nei_header.png b/src/main/resources/assets/vendingmachine/textures/gui/nei_header.png index 963b277..474cb19 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/nei_header.png and b/src/main/resources/assets/vendingmachine/textures/gui/nei_header.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/overlay/coinEject.png b/src/main/resources/assets/vendingmachine/textures/gui/overlay/coinEject.png index ab26b18..a4e157e 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/overlay/coinEject.png and b/src/main/resources/assets/vendingmachine/textures/gui/overlay/coinEject.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/overlay/mode_list.png b/src/main/resources/assets/vendingmachine/textures/gui/overlay/mode_list.png new file mode 100644 index 0000000..90874bc Binary files /dev/null and b/src/main/resources/assets/vendingmachine/textures/gui/overlay/mode_list.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/overlay/mode_tile.png b/src/main/resources/assets/vendingmachine/textures/gui/overlay/mode_tile.png new file mode 100644 index 0000000..0b06ee1 Binary files /dev/null and b/src/main/resources/assets/vendingmachine/textures/gui/overlay/mode_tile.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/overlay/sort_alphabet.png b/src/main/resources/assets/vendingmachine/textures/gui/overlay/sort_alphabet.png new file mode 100644 index 0000000..4f2f7e1 Binary files /dev/null and b/src/main/resources/assets/vendingmachine/textures/gui/overlay/sort_alphabet.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/overlay/sort_smart.png b/src/main/resources/assets/vendingmachine/textures/gui/overlay/sort_smart.png new file mode 100644 index 0000000..279ab7d Binary files /dev/null and b/src/main/resources/assets/vendingmachine/textures/gui/overlay/sort_smart.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/gui/tabs_left.png b/src/main/resources/assets/vendingmachine/textures/gui/tabs_left.png index 0c532c6..ba713ee 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/gui/tabs_left.png and b/src/main/resources/assets/vendingmachine/textures/gui/tabs_left.png differ diff --git a/src/main/resources/assets/vendingmachine/textures/items/placeholder.png b/src/main/resources/assets/vendingmachine/textures/items/placeholder.png index 06caf89..98ec8ae 100644 Binary files a/src/main/resources/assets/vendingmachine/textures/items/placeholder.png and b/src/main/resources/assets/vendingmachine/textures/items/placeholder.png differ