diff --git a/src/main/java/com/cubefury/vendingmachine/VendingMachine.java b/src/main/java/com/cubefury/vendingmachine/VendingMachine.java index 9041006..7ef5787 100644 --- a/src/main/java/com/cubefury/vendingmachine/VendingMachine.java +++ b/src/main/java/com/cubefury/vendingmachine/VendingMachine.java @@ -45,7 +45,7 @@ public class VendingMachine { public static boolean isAeLoaded = false; public static int CONTROLLER_MTE_ID = 2741; - // public static int ME_UPLINK_MTE_ID = 2742; + public static int ME_UPLINK_MTE_ID = 2742; @SidedProxy( clientSide = "com.cubefury.vendingmachine.ClientProxy", diff --git a/src/main/java/com/cubefury/vendingmachine/blocks/MTEVendingMachine.java b/src/main/java/com/cubefury/vendingmachine/blocks/MTEVendingMachine.java index 5181784..e7d6220 100644 --- a/src/main/java/com/cubefury/vendingmachine/blocks/MTEVendingMachine.java +++ b/src/main/java/com/cubefury/vendingmachine/blocks/MTEVendingMachine.java @@ -1,16 +1,19 @@ package com.cubefury.vendingmachine.blocks; -import static com.gtnewhorizon.structurelib.structure.StructureUtility.lazy; -import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlock; +import static gregtech.api.util.GTStructureUtility.ofHatchAdderOptional; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Queue; +import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.LinkedBlockingQueue; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; @@ -20,15 +23,16 @@ import net.minecraftforge.common.util.ForgeDirection; 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.VendingMachine; import com.cubefury.vendingmachine.blocks.gui.MTEVendingMachineGui; import com.cubefury.vendingmachine.blocks.gui.TradeItemDisplay; -import com.cubefury.vendingmachine.network.handlers.NetAvailableTradeSync; +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.network.handlers.NetTradeStateSync; import com.cubefury.vendingmachine.storage.NameCache; import com.cubefury.vendingmachine.trade.CurrencyItem; import com.cubefury.vendingmachine.trade.Trade; @@ -57,10 +61,26 @@ import gregtech.api.util.GTUtil; import gregtech.api.util.GTUtility; import gregtech.api.util.MultiblockTooltipBuilder; +import gregtech.common.blocks.BlockCasings11; public class MTEVendingMachine extends MTEMultiBlockBase implements ISurvivalConstructable, ISecondaryDescribable, IAlignment { + private static final IStructureDefinition STRUCTURE_DEFINITION = IStructureDefinition + .builder() + .addShape("main", new String[][] { { "cc", "c~", "cc" } }) + .addElement( + 'c', + ofHatchAdderOptional( + MTEVendingMachine::addUplinkHatch, + ((BlockCasings11) GregTechAPI.sBlockCasings11).getTextureIndex(0), + 1, + GregTechAPI.sBlockCasings11, + 0)) + .build(); + + private final ArrayList uplinkHatches = new ArrayList<>(); + public static final int INPUT_SLOTS = 6; public static final int OUTPUT_SLOTS = 4; @@ -68,11 +88,6 @@ public class MTEVendingMachine extends MTEMultiBlockBase public static final int STRUCTURE_CHECK_TICKS = 20; - private static final IStructureDefinition STRUCTURE_DEFINITION = IStructureDefinition - .builder() - .addShape("main", new String[][] { { "cc", "c~", "cc" } }) - .addElement('c', lazy(t -> ofBlock(GregTechAPI.sBlockCasings11, 0))) - .build(); private static final ITexture[] FACING_SIDE = { TextureFactory.of(Textures.BlockIcons.MACHINE_CASING_ITEM_PIPE_TIN) }; private static final ITexture[] FACING_FRONT = { @@ -95,6 +110,9 @@ public class MTEVendingMachine extends MTEMultiBlockBase public final Queue pendingTrades = new LinkedBlockingQueue<>(); private boolean newBufferedOutputs = false; private int ticksSinceOutput = 0; + private int ticksSinceTradeUpdate = 0; + + private Map inputSlotCache = new HashMap<>(); private EntityPlayer currentUser = null; @@ -102,6 +120,15 @@ public MTEVendingMachine(final int aID, final String aName, final String aNameRe super(aID, aName, aNameRegional); } + protected MTEVendingMachine(String aName) { + super(aName); + } + + @Override + public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) { + return new MTEVendingMachine(this.mName); + } + public void sendTradeRequest(TradeItemDisplay trade) { IGregTechTileEntity baseTile = getBaseMetaTileEntity(); if (baseTile == null) { @@ -196,21 +223,31 @@ private boolean processTradeOnServer(TradeRequest tradeRequest) { ) { return false; } + this.refreshInputSlotCache(); + + Trade trade = TradeDatabase.INSTANCE.getTradeGroupFromId(tradeRequest.tradeGroup) + .getTrades() + .get(tradeRequest.tradeGroupOrder); + + if ( + !this.inputCurrencySatisfied(trade.fromCurrency, tradeRequest.playerID) + || !this.inputItemsSatisfied(trade.fromItems) + ) { + return false; + } + ItemStack[] inputSlots = new ItemStack[MTEVendingMachine.INPUT_SLOTS]; for (int i = 0; i < MTEVendingMachine.INPUT_SLOTS; i++) { ItemStack curStack = this.inputItems.getStackInSlot(i); inputSlots[i] = curStack == null ? null : curStack.copy(); } - Trade trade = TradeDatabase.INSTANCE.getTradeGroupFromId(tradeRequest.tradeGroup) - .getTrades() - .get(tradeRequest.tradeGroupOrder); - Map coinInventory = TradeManager.INSTANCE.playerCurrency - .get(NameCache.INSTANCE.getUUIDFromPlayer(this.getCurrentUser())); + UUID currentPlayer = NameCache.INSTANCE.getUUIDFromPlayer(this.getCurrentUser()); + + TradeManager.INSTANCE.playerCurrency.putIfAbsent(currentPlayer, new HashMap<>()); + Map coinInventory = TradeManager.INSTANCE.playerCurrency.get(currentPlayer); + Map newCoinInventory = new HashMap<>(); - if (coinInventory == null) { - return false; - } for (CurrencyItem ci : trade.fromCurrency) { int oldValue = coinInventory.get(ci.type); if (!coinInventory.containsKey(ci.type) || oldValue < ci.value) { @@ -221,7 +258,9 @@ private boolean processTradeOnServer(TradeRequest tradeRequest) { } for (BigItemStack stack : trade.fromItems) { - ItemStack requiredStack = stack.getBaseStack(); + ItemStack requiredStack = stack.getBaseStack() + .copy(); + requiredStack.stackSize = 1; // just in case it's not pulled as 1 for some reason int requiredAmount = stack.stackSize; // Remove Items from last stacks if possible for (int i = MTEVendingMachine.INPUT_SLOTS - 1; i >= 0 && requiredAmount > 0; i--) { @@ -243,7 +282,8 @@ private boolean processTradeOnServer(TradeRequest tradeRequest) { } } } - if (requiredAmount > 0) { + requiredStack.stackSize = requiredAmount; + if (requiredAmount > 0 && !fetchItemFromAE(requiredStack, false)) { return false; } } @@ -268,17 +308,22 @@ private boolean processTradeOnServer(TradeRequest tradeRequest) { TradeDatabase.INSTANCE.getTradeGroups() .get(tradeRequest.tradeGroup) .executeTrade(tradeRequest.playerID); - NetTradeStateSync.sendTradeState(tradeRequest.player, false); + this.sendTradeUpdate(); return true; } - @Override - public boolean getDefaultHasMaintenanceChecks() { + public boolean fetchItemFromAE(ItemStack requiredStack, boolean simulate) { + for (MTEVendingUplinkHatch hatch : this.uplinkHatches) { + if (hatch.removeItem(requiredStack, simulate)) { + return true; + } + } return false; } - public MTEVendingMachine(String aName) { - super(aName); + @Override + public boolean getDefaultHasMaintenanceChecks() { + return false; } @Override @@ -286,6 +331,11 @@ public String[] getStructureDescription(ItemStack stackSize) { return getTooltip().getStructureHint(); } + @Override + public IStructureDefinition getStructureDefinition() { + return STRUCTURE_DEFINITION; + } + protected MultiblockTooltipBuilder getTooltip() { if (tooltipBuilder == null) { tooltipBuilder = new MultiblockTooltipBuilder(); @@ -294,6 +344,7 @@ protected MultiblockTooltipBuilder getTooltip() { .beginStructureBlock(2, 3, 1, false) .addController("Middle") .addOtherStructurePart("Tin Item Pipe Casings", "Everything except the controller") + .addStructureInfo("Cannot be flipped onto its side") .toolTipFinisher(); } return tooltipBuilder; @@ -306,10 +357,6 @@ protected boolean forceUseMui2() { @Override protected @NotNull MTEVendingMachineGui getGui() { - if (VendingMachine.proxy.isClient()) { - NetAvailableTradeSync.requestSync(); - NetTradeStateSync.requestSync(); - } return new MTEVendingMachineGui(this); } @@ -353,6 +400,11 @@ public String[] getSecondaryDescription() { return getTooltip().getStructureInformation(); } + @Override + public boolean isDisplaySecondaryDescription() { + return Keyboard.isKeyDown(Keyboard.KEY_LSHIFT); + } + @Override public void saveNBTData(NBTTagCompound aNBT) { super.saveNBTData(aNBT); @@ -416,17 +468,13 @@ public IAlignmentLimits getAlignmentLimits() { return (d, r, f) -> (d.flag & (ForgeDirection.UP.flag | ForgeDirection.DOWN.flag)) == 0; } - @Override - public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) { - return new MTEVendingMachine(this.mName); - } - @Override public boolean checkMachine(IGregTechTileEntity aBaseMetaTileEntity, ItemStack aStack) { if (getBaseMetaTileEntity() == null) { VendingMachine.LOG.warn("Check machine failed as Base MTE is null"); return false; } + this.uplinkHatches.clear(); return STRUCTURE_DEFINITION.check( this, "main", @@ -448,6 +496,9 @@ public void onPostTick(IGregTechTileEntity aBaseMetaTileEntity, long aTimer) { } 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); @@ -455,6 +506,62 @@ public void onPostTick(IGregTechTileEntity aBaseMetaTileEntity, long aTimer) { } } + public void sendTradeUpdate() { + this.ticksSinceTradeUpdate = 0; + if (this.currentUser == null) { + return; + } + NetCurrencySync.syncCurrencyToClient((EntityPlayerMP) this.currentUser); + NetTradeDisplaySync.syncTradesToClient((EntityPlayerMP) this.currentUser, this); + } + + public void refreshInputSlotCache() { + Map items = new HashMap<>(); + for (int i = 0; i < INPUT_SLOTS; i++) { + ItemStack stack = this.inputItems.getStackInSlot(i); + if (stack != null) { + BigItemStack tmp = new BigItemStack(stack); + tmp.stackSize = 1; + items.putIfAbsent(tmp, 0); + items.replace(tmp, items.get(tmp) + stack.stackSize); + } + } + this.inputSlotCache = items; + } + + public boolean inputItemsSatisfied(List fromItems) { + for (BigItemStack bis : fromItems) { + BigItemStack base = bis.copy(); + base.stackSize = 1; // shouldn't need this, but just in case + + ItemStack aeStackSearch = base.getBaseStack(); + aeStackSearch.stackSize = bis.stackSize; + if (this.inputSlotCache.get(base) != null) { + aeStackSearch.stackSize = Math.max(aeStackSearch.stackSize - this.inputSlotCache.get(base), 0); + } + if (aeStackSearch.stackSize == 0) { + continue; + } + if (!this.fetchItemFromAE(aeStackSearch, true)) { + return false; + } + } + return true; + } + + public boolean inputCurrencySatisfied(List currencyItems, UUID player) { + if (currencyItems == null || currencyItems.isEmpty()) { + return true; + } + Map availableCurrency = TradeManager.INSTANCE.playerCurrency.get(player); + if (availableCurrency == null) { + return false; + } + // TODO: Add AE2 coin item support + return currencyItems.stream() + .allMatch(ci -> availableCurrency.containsKey(ci.type) && availableCurrency.get(ci.type) >= ci.value); + } + public boolean getActive() { return this.getBaseMetaTileEntity() != null && this.getBaseMetaTileEntity() .isActive(); @@ -519,6 +626,8 @@ public boolean onRightclick(IGregTechTileEntity aBaseMetaTileEntity, EntityPlaye } if (canUse(aPlayer)) { this.currentUser = aPlayer; + // force trade state update now + this.ticksSinceTradeUpdate = Config.gui_refresh_interval; openGui(aPlayer); } else { aPlayer.addChatComponentMessage(new ChatComponentTranslation("vendingmachine.gui.error.player_using")); @@ -570,4 +679,15 @@ public void spawnItem(ItemStack stack) { itemEntity.motionZ = 0.05f * offsetZ; world.spawnEntityInWorld(itemEntity); } + + private boolean addUplinkHatch(IGregTechTileEntity aBaseMetaTileEntity, int aBaseCasingIndex) { + if (aBaseMetaTileEntity == null) return false; + IMetaTileEntity aMetaTileEntity = aBaseMetaTileEntity.getMetaTileEntity(); + if (aMetaTileEntity == null) return false; + if (!(aMetaTileEntity instanceof MTEVendingUplinkHatch uplinkHatch)) return false; + uplinkHatch.updateTexture(aBaseCasingIndex); + uplinkHatch.updateCraftingIcon(uplinkHatch.getMachineCraftingIcon()); + this.uplinkHatches.add(uplinkHatch); + return true; + } } diff --git a/src/main/java/com/cubefury/vendingmachine/blocks/MTEVendingUplinkHatch.java b/src/main/java/com/cubefury/vendingmachine/blocks/MTEVendingUplinkHatch.java new file mode 100644 index 0000000..10e6d6b --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/blocks/MTEVendingUplinkHatch.java @@ -0,0 +1,186 @@ +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 java.util.EnumSet; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ChatComponentTranslation; +import net.minecraftforge.common.util.ForgeDirection; + +import com.cubefury.vendingmachine.VendingMachine; +import com.cubefury.vendingmachine.items.VMItems; + +import appeng.api.config.Actionable; +import appeng.api.implementations.IPowerChannelState; +import appeng.api.networking.GridFlags; +import appeng.api.networking.IGridNode; +import appeng.api.networking.security.IActionHost; +import appeng.api.networking.security.MachineSource; +import appeng.api.networking.storage.IStorageGrid; +import appeng.api.storage.data.IAEItemStack; +import appeng.api.util.AECableType; +import appeng.api.util.DimensionalCoord; +import appeng.me.GridAccessException; +import appeng.me.helpers.AENetworkProxy; +import appeng.me.helpers.IGridProxyable; +import appeng.util.item.AEItemStack; +import gregtech.api.interfaces.ITexture; +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.metatileentity.implementations.MTEHatch; +import gregtech.api.render.TextureFactory; + +public class MTEVendingUplinkHatch extends MTEHatch implements IGridProxyable, IPowerChannelState, IActionHost { + + protected AENetworkProxy gridProxy = null; + protected boolean additionalConnection = false; + + public static final int mTier = 3; + + public MTEVendingUplinkHatch(int aID, String aName, String aNameRegional) { + super( + aID, + aName, + aNameRegional, + mTier, + 0, + new String[] { "Vending Machine Uplink hatch.", "Uses inputs directly from ME network." }); + } + + public MTEVendingUplinkHatch(MTEVendingUplinkHatch prototype) { + super(prototype.mName, prototype.mTier, 0, prototype.mDescriptionArray, prototype.mTextures); + } + + @Override + public void onFacingChange() { + updateValidGridProxySides(); + } + + @Override + public boolean isFacingValid(ForgeDirection facing) { + return true; + } + + @Override + public boolean isAccessAllowed(EntityPlayer aPlayer) { + return true; + } + + @Override + public AENetworkProxy getProxy() { + if (gridProxy == null) { + if (getBaseMetaTileEntity() instanceof IGridProxyable) { + gridProxy = new AENetworkProxy(this, "proxy", VMItems.uplinkHatch, true); + gridProxy.setFlags(GridFlags.REQUIRE_CHANNEL); + updateValidGridProxySides(); + if (getBaseMetaTileEntity().getWorld() != null) { + gridProxy.setOwner( + getBaseMetaTileEntity().getWorld() + .getPlayerEntityByName(getBaseMetaTileEntity().getOwnerName())); + } + } + } + return this.gridProxy; + } + + @Override + public boolean isPowered() { + return getProxy() != null && getProxy().isPowered(); + } + + @Override + public boolean isActive() { + return getProxy() != null && getProxy().isActive(); + } + + @Override + public DimensionalCoord getLocation() { + return new DimensionalCoord( + getBaseMetaTileEntity().getWorld(), + getBaseMetaTileEntity().getXCoord(), + getBaseMetaTileEntity().getYCoord(), + getBaseMetaTileEntity().getZCoord()); + } + + @Override + public IGridNode getGridNode(ForgeDirection dir) { + return getProxy().getNode(); + } + + @Override + public IGridNode getActionableNode() { + return getProxy().getNode(); + } + + @Override + public void securityBreak() {} + + @Override + public ITexture[] getTexturesActive(ITexture aBaseTexture) { + return new ITexture[] { aBaseTexture, TextureFactory.of(OVERLAY_ME_INPUT_FLUID_HATCH_ACTIVE) }; + } + + @Override + public ITexture[] getTexturesInactive(ITexture aBaseTexture) { + return new ITexture[] { aBaseTexture, TextureFactory.of(OVERLAY_ME_INPUT_FLUID_HATCH) }; + } + + @Override + public AECableType getCableConnectionType(ForgeDirection forgeDirection) { + return isOutputFacing(forgeDirection) ? AECableType.SMART : AECableType.NONE; + } + + @Override + public IMetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) { + return new MTEVendingUplinkHatch(this); + } + + @Override + public void onFirstTick(IGregTechTileEntity baseMetaTileEntity) { + super.onFirstTick(baseMetaTileEntity); + getProxy().onReady(); + } + + private void updateValidGridProxySides() { + if (additionalConnection) { + getProxy().setValidSides(EnumSet.complementOf(EnumSet.of(ForgeDirection.UNKNOWN))); + } else { + getProxy().setValidSides(EnumSet.of(getBaseMetaTileEntity().getFrontFacing())); + } + } + + @Override + public boolean onWireCutterRightClick(ForgeDirection side, ForgeDirection wrenchingSide, EntityPlayer aPlayer, + float aX, float aY, float aZ, ItemStack aTool) { + additionalConnection = !additionalConnection; + updateValidGridProxySides(); + aPlayer.addChatComponentMessage( + new ChatComponentTranslation("GT5U.hatch.additionalConnection." + additionalConnection)); + return true; + } + + private IStorageGrid accessStorage() { + try { + return getProxy().getStorage(); + } catch (GridAccessException gae) { + VendingMachine.LOG.warn("Could not access storage: ", gae); + gae.printStackTrace(); + } + return null; + } + + public boolean removeItem(ItemStack remove, boolean simulate) { + 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; + } +} 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 e040010..582335e 100644 --- a/src/main/java/com/cubefury/vendingmachine/blocks/gui/InterceptingSlot.java +++ b/src/main/java/com/cubefury/vendingmachine/blocks/gui/InterceptingSlot.java @@ -6,7 +6,7 @@ import com.cleanroommc.modularui.utils.item.ItemStackHandler; import com.cleanroommc.modularui.widgets.slot.ModularSlot; -import com.cubefury.vendingmachine.network.handlers.NetTradeStateSync; +import com.cubefury.vendingmachine.network.handlers.NetCurrencySync; import com.cubefury.vendingmachine.storage.NameCache; import com.cubefury.vendingmachine.trade.CurrencyItem; import com.cubefury.vendingmachine.trade.TradeManager; @@ -24,7 +24,7 @@ public boolean intercept(ItemStack newItem, boolean client, EntityPlayer player) this.putStack(null); if (!client) { TradeManager.INSTANCE.addCurrency(NameCache.INSTANCE.getUUIDFromPlayer(player), mapped); - NetTradeStateSync.sendPlayerCurrency((EntityPlayerMP) 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 3c48cf9..93d59d9 100644 --- a/src/main/java/com/cubefury/vendingmachine/blocks/gui/MTEVendingMachineGui.java +++ b/src/main/java/com/cubefury/vendingmachine/blocks/gui/MTEVendingMachineGui.java @@ -34,7 +34,8 @@ import com.cubefury.vendingmachine.blocks.MTEVendingMachine; import com.cubefury.vendingmachine.gui.GuiTextures; import com.cubefury.vendingmachine.gui.WidgetThemes; -import com.cubefury.vendingmachine.network.handlers.NetTradeStateSync; +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.TradeCategory; @@ -211,7 +212,7 @@ private void doEjectCoin(CurrencyItem.CurrencyType type) { base.spawnItem(ejectable); } TradeManager.INSTANCE.resetCurrency(currentUser, type); - NetTradeStateSync.resetPlayerCurrency((EntityPlayerMP) base.getCurrentUser(), type); + NetCurrencySync.resetPlayerCurrency((EntityPlayerMP) base.getCurrentUser(), type); this.ejectSingleCoin.put(type, false); } @@ -234,7 +235,7 @@ private void doEjectCoins() { } } TradeManager.INSTANCE.resetCurrency(currentUser, null); - NetTradeStateSync.resetPlayerCurrency((EntityPlayerMP) base.getCurrentUser(), null); + NetCurrencySync.resetPlayerCurrency((EntityPlayerMP) base.getCurrentUser(), null); ejectCoins = false; } @@ -313,10 +314,16 @@ private SlotGroupWidget createInputRow(PanelSyncManager syncManager) { this.getBase() .getCurrentUser()); if (client) { - forceRefresh = true; return; } // server side force refresh + // Not syncing the trades to client on slot change will cause a short refresh delay, but + // might be worth + // for huge AE systems + NetTradeDisplaySync.syncTradesToClient( + (EntityPlayerMP) this.getBase() + .getCurrentUser(), + this.getBase()); if (hasCoin) { this.refreshInputSlots(); } 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 adbc310..c25667b 100644 --- a/src/main/java/com/cubefury/vendingmachine/blocks/gui/TradeMainPanel.java +++ b/src/main/java/com/cubefury/vendingmachine/blocks/gui/TradeMainPanel.java @@ -18,18 +18,11 @@ import com.cleanroommc.modularui.screen.ModularScreen; import com.cleanroommc.modularui.value.sync.PanelSyncManager; import com.cubefury.vendingmachine.Config; -import com.cubefury.vendingmachine.blocks.MTEVendingMachine; import com.cubefury.vendingmachine.network.handlers.NetResetVMUser; -import com.cubefury.vendingmachine.storage.NameCache; -import com.cubefury.vendingmachine.trade.CurrencyItem; -import com.cubefury.vendingmachine.trade.Trade; import com.cubefury.vendingmachine.trade.TradeCategory; import com.cubefury.vendingmachine.trade.TradeDatabase; -import com.cubefury.vendingmachine.trade.TradeGroup; -import com.cubefury.vendingmachine.trade.TradeGroupWrapper; import com.cubefury.vendingmachine.trade.TradeManager; import com.cubefury.vendingmachine.util.BigItemStack; -import com.cubefury.vendingmachine.util.Translator; import codechicken.nei.SearchField; import codechicken.nei.api.ItemFilter; @@ -70,55 +63,31 @@ public boolean onKeyRelease(char typedChar, int keyCode) { } public void updateGui() { - boolean test = false; - if (test) { - List testTGW = new ArrayList<>(); - for (Map.Entry entry : TradeDatabase.INSTANCE.getTradeGroups() - .entrySet()) { - testTGW.add(new TradeGroupWrapper(entry.getValue(), -1, true)); - } - Map> trades = formatTrades(testTGW); - gui.updateTradeDisplay(trades); - } else if (shiftHeld) { + if (shiftHeld) { this.updateTradeInformation(gui.getTradeDisplayData()); } else { - Map> trades = formatTrades( - TradeManager.INSTANCE.getTrades(NameCache.INSTANCE.getUUIDFromPlayer(syncManager.getPlayer()))); + Map> trades = formatTrades(); gui.updateTradeDisplay(trades); } } private void updateTradeInformation(Map> currentData) { - Map availableItems = this.guiData.isClient() && this.gui.getBase() != null - ? getAvailableItems() - : new HashMap<>(); + Map> tradeMap = new HashMap<>(); + for (TradeItemDisplay tid : TradeManager.INSTANCE.tradeData) { + tradeMap.putIfAbsent(tid.tgID, new HashMap<>()); + tradeMap.get(tid.tgID) + .put(tid.tradeGroupOrder, tid); + } - Map tradeGroups = new HashMap<>(); - TradeManager.INSTANCE.getTrades(NameCache.INSTANCE.getUUIDFromPlayer(syncManager.getPlayer())) - .forEach( - (tg) -> { - tradeGroups.put( - tg.trade() - .getId(), - tg); - }); currentData.forEach((k, v) -> { for (TradeItemDisplay tid : v) { - TradeGroupWrapper cur = tradeGroups.get(tid.tgID); - tid.enabled = cur != null && cur.enabled(); - tid.hasCooldown = cur.cooldown() > 0; - tid.cooldown = cur.cooldown(); - tid.cooldownText = convertCooldownText(cur.cooldown()); - tid.tradeableNow = checkItemsSatisfied( - cur.trade() - .getTrades() - .get(tid.tradeGroupOrder).fromItems, - availableItems) - && checkCurrencySatisfied( - cur.trade() - .getTrades() - .get(tid.tradeGroupOrder).fromCurrency, - TradeManager.INSTANCE.playerCurrency.get(NameCache.INSTANCE.getUUIDFromPlayer(this.player))); + TradeItemDisplay cur = tradeMap.get(tid.tgID) + .get(tid.tradeGroupOrder); + tid.enabled = cur != null && cur.enabled; + tid.hasCooldown = cur.cooldown > 0; + tid.cooldown = cur.cooldown; + tid.cooldownText = cur.cooldownText; + tid.tradeableNow = cur.tradeableNow; } }); } @@ -137,28 +106,15 @@ public void onUpdate() { MTEVendingMachineGui.setForceRefresh(); } if ( - MTEVendingMachineGui.forceRefresh - || (this.ticksOpen % Config.gui_refresh_interval == 0 && player != null && !shiftHeld) + MTEVendingMachineGui.forceRefresh || (this.ticksOpen % Config.gui_refresh_interval == 0 && player != null) ) { updateGui(); MTEVendingMachineGui.resetForceRefresh(); + TradeManager.INSTANCE.hasCurrencyUpdate = false; } this.ticksOpen += 1; } - public String convertCooldownText(long cd) { - if (cd < 60) { - return cd + Translator.translate("vendingmachine.gui.cooldown_display.second"); - } - if (cd < 3600) { - return cd / 60 + Translator.translate("vendingmachine.gui.cooldown_display.minute"); - } - if (cd < 86400) { - return cd / 3600 + Translator.translate("vendingmachine.gui.cooldown_display.hour"); - } - return cd / 86400 + Translator.translate("vendingmachine.gui.cooldown_display.day"); // doom.jpg - } - public ItemStack convertToItemStack(BigItemStack stack) { ItemStack display = stack.getCombinedStacks() .get(0); @@ -166,85 +122,17 @@ public ItemStack convertToItemStack(BigItemStack stack) { return display; } - public boolean checkCurrencySatisfied(List currencyItems, - Map availableItems) { - if (currencyItems == null) { - return true; - } - if (availableItems == null) { - return false; - } - return currencyItems.stream() - .allMatch(ci -> availableItems.containsKey(ci.type) && availableItems.get(ci.type) >= ci.value); - } - - public boolean checkItemsSatisfied(List trade, Map availableItems) { - for (BigItemStack bis : trade) { - BigItemStack base = bis.copy(); - base.stackSize = 1; // shouldn't need this, but just in case - if (availableItems.get(base) == null || availableItems.get(base) < bis.stackSize) { - return false; - } - } - return true; - } - - public Map getAvailableItems() { - Map items = new HashMap<>(); - for (int i = 0; i < MTEVendingMachine.INPUT_SLOTS; i++) { - ItemStack stack = this.gui.getBase().inputItems.getStackInSlot(i); - if (stack != null) { - BigItemStack tmp = new BigItemStack(stack); - tmp.stackSize = 1; - items.putIfAbsent(tmp, 0); - items.replace(tmp, items.get(tmp) + stack.stackSize); - } - } - return items; - } - - public Map> formatTrades(List tradeGroups) { - Map availableItems = this.guiData.isClient() && this.gui.getBase() != null - ? getAvailableItems() - : new HashMap<>(); - + public Map> formatTrades() { Map> trades = new HashMap<>(); trades.put(TradeCategory.ALL, new ArrayList<>()); - - for (TradeGroupWrapper tgw : tradeGroups) { - List tradeList = tgw.trade() - .getTrades(); - TradeCategory category = tgw.trade() + for (TradeItemDisplay tid : TradeManager.INSTANCE.tradeData) { + TradeCategory category = TradeDatabase.INSTANCE.getTradeGroupFromId(tid.tgID) .getCategory(); trades.putIfAbsent(category, new ArrayList<>()); - for (int i = 0; i < tradeList.size(); i++) { - Trade trade = tgw.trade() - .getTrades() - .get(i); - BigItemStack displayItem = trade.toItems.get(0); - TradeItemDisplay tid = new TradeItemDisplay( - trade.fromCurrency, - trade.fromItems, - trade.toItems, - convertToItemStack(displayItem == null ? trade.displayItem : displayItem), - tgw.trade() - .getId(), - i, - tgw.trade() - .getLabel(), - tgw.cooldown(), - convertCooldownText(tgw.cooldown()), - tgw.cooldown() > 0, - tgw.enabled(), - checkItemsSatisfied(trade.fromItems, availableItems) && checkCurrencySatisfied( - trade.fromCurrency, - TradeManager.INSTANCE.playerCurrency.get(NameCache.INSTANCE.getUUIDFromPlayer(this.player)))); - - trades.get(category) - .add(tid); - trades.get(TradeCategory.ALL) - .add(tid); - } + trades.get(category) + .add(tid); + trades.get(TradeCategory.ALL) + .add(tid); } String searchString = gui.getSearchBarText(); 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 39e4555..a12091a 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,6 @@ import javax.annotation.Nullable; -import com.cubefury.vendingmachine.VendingMachine; -import com.cubefury.vendingmachine.network.handlers.NetAvailableTradeSync; import com.cubefury.vendingmachine.trade.TradeDatabase; import com.cubefury.vendingmachine.trade.TradeGroup; import com.google.common.collect.ImmutableMap; @@ -72,7 +70,6 @@ public void setQuestFinished(UUID player, UUID quest) { playerSatisfiedCache.get(player) .add(quest); } - syncAvailableTradesFromServer(); } public void setQuestUnfinished(UUID player, UUID quest) { @@ -85,7 +82,6 @@ public void setQuestUnfinished(UUID player, UUID quest) { } } } - syncAvailableTradesFromServer(); } public void resetQuests(UUID player) { @@ -97,15 +93,6 @@ public void resetQuests(UUID player) { playerSatisfiedCache.remove(player); } } - syncAvailableTradesFromServer(); - } - - public void syncAvailableTradesFromServer() { - // We have to sync these trades even though the trades are only pulled usually during VM GUI opening, - // cuz someone's teammate might finish the quest - if (VendingMachine.proxy.isClient()) { - NetAvailableTradeSync.requestSync(); - } } public boolean checkPlayerCompletedQuest(UUID player, UUID quest) { diff --git a/src/main/java/com/cubefury/vendingmachine/items/VMItems.java b/src/main/java/com/cubefury/vendingmachine/items/VMItems.java index 5b478b5..27e06a7 100644 --- a/src/main/java/com/cubefury/vendingmachine/items/VMItems.java +++ b/src/main/java/com/cubefury/vendingmachine/items/VMItems.java @@ -4,12 +4,14 @@ import com.cubefury.vendingmachine.VendingMachine; import com.cubefury.vendingmachine.blocks.MTEVendingMachine; +import com.cubefury.vendingmachine.blocks.MTEVendingUplinkHatch; import cpw.mods.fml.common.Optional; public class VMItems { public static ItemStack vendingMachine; + public static ItemStack uplinkHatch; public VMItems() {} @@ -19,5 +21,9 @@ public static void registerMultis() { VendingMachine.CONTROLLER_MTE_ID, "multimachine.vendingmachine", "Vending Machine").getStackForm(1); + uplinkHatch = new MTEVendingUplinkHatch( + VendingMachine.ME_UPLINK_MTE_ID, + "hatch.vendinguplink.me", + "ME Vending Uplink Hatch").getStackForm(1); } } diff --git a/src/main/java/com/cubefury/vendingmachine/network/PacketTypeRegistry.java b/src/main/java/com/cubefury/vendingmachine/network/PacketTypeRegistry.java index 6876caf..6def7a2 100644 --- a/src/main/java/com/cubefury/vendingmachine/network/PacketTypeRegistry.java +++ b/src/main/java/com/cubefury/vendingmachine/network/PacketTypeRegistry.java @@ -12,14 +12,14 @@ import com.cubefury.vendingmachine.api.network.IPacketRegistry; import com.cubefury.vendingmachine.api.util.Tuple2; -import com.cubefury.vendingmachine.network.handlers.NetAvailableTradeSync; 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.NetTradeRequestSync; -import com.cubefury.vendingmachine.network.handlers.NetTradeStateSync; public class PacketTypeRegistry implements IPacketRegistry { @@ -30,8 +30,8 @@ public class PacketTypeRegistry implements IPacketRegistry { public void init() { NetTradeDbSync.registerHandler(); - NetTradeStateSync.registerHandler(); - NetAvailableTradeSync.registerHandler(); + NetCurrencySync.registerHandler(); + NetTradeDisplaySync.registerHandler(); NetTradeRequestSync.registerHandler(); NetSatisfiedQuestSync.registerHandler(); NetNameSync.registerHandler(); diff --git a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetAvailableTradeSync.java b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetAvailableTradeSync.java deleted file mode 100644 index 7f84010..0000000 --- a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetAvailableTradeSync.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.cubefury.vendingmachine.network.handlers; - -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -import javax.annotation.Nullable; - -import net.minecraft.client.Minecraft; -import net.minecraft.entity.player.EntityPlayerMP; -import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.nbt.NBTTagList; -import net.minecraft.nbt.NBTTagString; -import net.minecraft.util.ResourceLocation; -import net.minecraftforge.common.util.Constants; - -import com.cubefury.vendingmachine.VendingMachine; -import com.cubefury.vendingmachine.api.network.UnserializedPacket; -import com.cubefury.vendingmachine.api.util.Tuple2; -import com.cubefury.vendingmachine.network.PacketSender; -import com.cubefury.vendingmachine.network.PacketTypeRegistry; -import com.cubefury.vendingmachine.storage.NameCache; -import com.cubefury.vendingmachine.trade.TradeManager; - -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; - -public class NetAvailableTradeSync { - - private static final ResourceLocation ID_NAME = new ResourceLocation("vendingmachine:availabletrade_sync"); - - public static void registerHandler() { - PacketTypeRegistry.INSTANCE.registerServerHandler(ID_NAME, NetAvailableTradeSync::onServer); - - if (VendingMachine.proxy.isClient()) { - PacketTypeRegistry.INSTANCE.registerClientHandler(ID_NAME, NetAvailableTradeSync::onClient); - } - } - - public static void sendSync(@Nullable EntityPlayerMP player) { - NBTTagList tradeGroups = new NBTTagList(); - for (UUID available : TradeManager.INSTANCE.getAvailableTrades(NameCache.INSTANCE.getUUIDFromPlayer(player))) { - tradeGroups.appendTag(new NBTTagString(available.toString())); - } - NBTTagCompound payload = new NBTTagCompound(); - payload.setTag("tradeGroups", tradeGroups); - PacketSender.INSTANCE.sendToPlayers(new UnserializedPacket(ID_NAME, payload), player); - - } - - @SideOnly(Side.CLIENT) - public static void requestSync() { - PacketSender.INSTANCE.sendToServer(new UnserializedPacket(ID_NAME, new NBTTagCompound())); - } - - public static void onServer(Tuple2 message) { - sendSync(message.second()); - } - - @SideOnly(Side.CLIENT) - public static void onClient(NBTTagCompound message) { - if ( // Don't sync in SP or LAN - will delete other player's data - Minecraft.getMinecraft() - .isIntegratedServerRunning() - ) { - return; - } - UUID playerId = NameCache.INSTANCE.getUUIDFromPlayer(Minecraft.getMinecraft().thePlayer); - Set tradeGroups = new HashSet<>(); - NBTTagList tradeList = message.getTagList("tradeGroups", Constants.NBT.TAG_STRING); - for (int i = 0; i < tradeList.tagCount(); i++) { - tradeGroups.add(UUID.fromString(tradeList.getStringTagAt(i))); - } - TradeManager.INSTANCE.setAvailableTrades(playerId, tradeGroups); - } -} 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 26746fa..2a80342 100644 --- a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetBulkSync.java +++ b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetBulkSync.java @@ -52,7 +52,6 @@ public static void sendSync(@Nonnull EntityPlayerMP player) { NetNameSync.sendNames(new EntityPlayerMP[] { player }, new UUID[] { playerId }, null); NetTradeDbSync.sendDatabase(player, false); - NetTradeStateSync.sendTradeState(player, false); } private static void onServer(Tuple2 message) { diff --git a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetTradeStateSync.java b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetCurrencySync.java similarity index 62% rename from src/main/java/com/cubefury/vendingmachine/network/handlers/NetTradeStateSync.java rename to src/main/java/com/cubefury/vendingmachine/network/handlers/NetCurrencySync.java index c6cd086..84d8f61 100644 --- a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetTradeStateSync.java +++ b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetCurrencySync.java @@ -12,50 +12,41 @@ import com.cubefury.vendingmachine.VendingMachine; import com.cubefury.vendingmachine.api.network.UnserializedPacket; -import com.cubefury.vendingmachine.api.util.Tuple2; 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.TradeDatabase; 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 NetTradeStateSync { +public class NetCurrencySync { - // We can send tradestate + currency data, or just currency data - // The latter is far more lightweight, and will likely see higher traffic volume - private static final ResourceLocation ID_NAME = new ResourceLocation("vendingmachine:tradestate_sync"); + private static final ResourceLocation ID_NAME = new ResourceLocation("vendingmachine:currency_sync"); public static void registerHandler() { - PacketTypeRegistry.INSTANCE.registerServerHandler(ID_NAME, NetTradeStateSync::onServer); - if (VendingMachine.proxy.isClient()) { - PacketTypeRegistry.INSTANCE.registerClientHandler(ID_NAME, NetTradeStateSync::onClient); + PacketTypeRegistry.INSTANCE.registerClientHandler(ID_NAME, NetCurrencySync::onClient); } } // server side code for sending tradegroup data when player opens gui - public static void sendTradeState(@Nonnull EntityPlayerMP player, boolean merge) { - TradeDatabase db = TradeDatabase.INSTANCE; + public static void syncCurrencyToClient(@Nonnull EntityPlayerMP player) { UUID playerId = NameCache.INSTANCE.getUUIDFromPlayer(player); NBTTagCompound payload = new NBTTagCompound(); - payload.setString("dataType", "tradeState"); - payload.setBoolean("merge", merge); + payload.setString("dataType", "currencySync"); NBTConverter.UuidValueType.PLAYER.writeId(playerId, payload); - - db.writeTradeStateToNBT(new NBTTagCompound(), playerId); + 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", "currency_add"); + payload.setString("dataType", "currencyAdd"); currencyItem.writeToNBT(payload); PacketSender.INSTANCE.sendToPlayers(new UnserializedPacket(ID_NAME, payload), player); @@ -63,31 +54,13 @@ public static void sendPlayerCurrency(@Nonnull EntityPlayerMP player, CurrencyIt public static void resetPlayerCurrency(@Nonnull EntityPlayerMP player, @Nullable CurrencyItem.CurrencyType type) { NBTTagCompound payload = new NBTTagCompound(); - payload.setString("dataType", "currency_reset"); + 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 requestSync() { - NBTTagCompound payload = new NBTTagCompound(); - payload.setString("requestType", "getTradeState"); - - PacketSender.INSTANCE.sendToServer(new UnserializedPacket(ID_NAME, payload)); - } - - public static void onServer(Tuple2 message) { - String requestType = message.first() - .getString("requestType"); - if (requestType.equals("getTradeState")) { - sendTradeState(message.second(), false); - } else { - VendingMachine.LOG.warn("Unknown trade state sync request type received: {}", requestType); - } - } - @SideOnly(Side.CLIENT) public static void onClient(NBTTagCompound message) { // Don't wipe everyone else's data if on LAN, since @@ -102,16 +75,18 @@ public static void onClient(NBTTagCompound message) { String dataType = message.getString("dataType"); UUID player = NBTConverter.UuidValueType.PLAYER.readId(message); switch (dataType) { - case "tradeState" -> { - boolean merge = message.getBoolean("merge"); - TradeDatabase.INSTANCE.populateTradeStateFromNBT(message, player, merge); + case "currencySync" -> { + TradeManager.INSTANCE.populateCurrencyFromNBT( + message.getCompoundTag("data"), + NBTConverter.UuidValueType.PLAYER.readId(message), + false); } - case "currency_add" -> { + case "currencyAdd" -> { CurrencyItem currencyItem = CurrencyItem.fromNBT(message.getCompoundTag("currencyItem")); TradeManager.INSTANCE.addCurrency(player, currencyItem); } - case "currency_reset" -> TradeManager.INSTANCE.resetCurrency( + 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/NetTradeDisplaySync.java b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetTradeDisplaySync.java new file mode 100644 index 0000000..a399f08 --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetTradeDisplaySync.java @@ -0,0 +1,165 @@ +package com.cubefury.vendingmachine.network.handlers; + +import java.util.List; +import java.util.UUID; + +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; +import net.minecraftforge.common.util.Constants; + +import com.cubefury.vendingmachine.VendingMachine; +import com.cubefury.vendingmachine.api.network.UnserializedPacket; +import com.cubefury.vendingmachine.blocks.MTEVendingMachine; +import com.cubefury.vendingmachine.blocks.gui.MTEVendingMachineGui; +import com.cubefury.vendingmachine.blocks.gui.TradeItemDisplay; +import com.cubefury.vendingmachine.network.PacketSender; +import com.cubefury.vendingmachine.network.PacketTypeRegistry; +import com.cubefury.vendingmachine.storage.NameCache; +import com.cubefury.vendingmachine.trade.Trade; +import com.cubefury.vendingmachine.trade.TradeDatabase; +import com.cubefury.vendingmachine.trade.TradeGroup; +import com.cubefury.vendingmachine.trade.TradeManager; +import com.cubefury.vendingmachine.util.NBTConverter; +import com.cubefury.vendingmachine.util.Translator; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class NetTradeDisplaySync { + + private static final ResourceLocation ID_NAME = new ResourceLocation("vendingmachine:availabletrade_sync"); + + public static void registerHandler() { + + if (VendingMachine.proxy.isClient()) { + PacketTypeRegistry.INSTANCE.registerClientHandler(ID_NAME, NetTradeDisplaySync::onClient); + } + } + + private static class Tradable { + + public UUID tgID; + public int tradeGroupOrder; + public long cooldown; + public boolean enabled; + public boolean tradableNow; + + public Tradable(UUID tgID, int tradeGroupOrder, long cooldown, boolean enabled, boolean tradableNow) { + this.tgID = tgID; + this.tradeGroupOrder = tradeGroupOrder; + this.cooldown = cooldown; + this.enabled = enabled; + this.tradableNow = tradableNow; + } + + public NBTTagCompound writeToNBT(NBTTagCompound nbt) { + NBTConverter.UuidValueType.TRADEGROUP.writeId(this.tgID, nbt); + nbt.setInteger("order", this.tradeGroupOrder); + nbt.setLong("cooldown", this.cooldown); + nbt.setBoolean("enabled", this.enabled); + nbt.setBoolean("tradableNow", this.tradableNow); + + return nbt; + } + + public static Tradable readFromNBT(NBTTagCompound nbt) { + return new Tradable( + NBTConverter.UuidValueType.TRADEGROUP.readId(nbt), + nbt.getInteger("order"), + nbt.getLong("cooldown"), + nbt.getBoolean("enabled"), + nbt.getBoolean("tradableNow")); + } + + 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.toItems, + t.displayItem == null ? t.displayItem.convertToItemStack() : displayItem, + this.tgID, + this.tradeGroupOrder, + tg.getLabel(), + this.cooldown, + convertCooldownText(this.cooldown), + this.cooldown > 0, + this.enabled, + this.tradableNow); + } + + public static String convertCooldownText(long cd) { + if (cd < 60) { + return cd + Translator.translate("vendingmachine.gui.cooldown_display.second"); + } + if (cd < 3600) { + return cd / 60 + Translator.translate("vendingmachine.gui.cooldown_display.minute"); + } + if (cd < 86400) { + return cd / 3600 + Translator.translate("vendingmachine.gui.cooldown_display.hour"); + } + return cd / 86400 + Translator.translate("vendingmachine.gui.cooldown_display.day"); // doom.jpg + } + } + + public static void syncTradesToClient(@Nonnull EntityPlayerMP player, MTEVendingMachine base) { + UUID playerId = NameCache.INSTANCE.getUUIDFromPlayer(player); + List availableGroups = TradeManager.INSTANCE.getAvailableTradeGroups(playerId); + base.refreshInputSlotCache(); + + 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; + + 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; + + for (int i = 0; i < tg.getTrades() + .size(); i++) { + Trade trade = tg.getTrades() + .get(i); + boolean tradableNow = base.inputItemsSatisfied(trade.fromItems) + && base.inputCurrencySatisfied(trade.fromCurrency, playerId); + trades.appendTag( + new Tradable(tg.getId(), i, cooldownRemaining, enabled, tradableNow) + .writeToNBT(new NBTTagCompound())); + } + payload.setTag("trades", trades); + } + PacketSender.INSTANCE.sendToPlayers(new UnserializedPacket(ID_NAME, payload), player); + + } + + @SideOnly(Side.CLIENT) + public static void onClient(NBTTagCompound message) { + // TODO: Load trade view on client + List tradeData = TradeManager.INSTANCE.tradeData; + tradeData.clear(); + + NBTTagList trades = message.getTagList("trades", Constants.NBT.TAG_COMPOUND); + for (int i = 0; i < trades.tagCount(); i++) { + tradeData.add( + Tradable.readFromNBT(trades.getCompoundTagAt(i)) + .formatItemDisplay()); + } + MTEVendingMachineGui.setForceRefresh(); + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/trade/TradeGroup.java b/src/main/java/com/cubefury/vendingmachine/trade/TradeGroup.java index 5702483..1436b1d 100644 --- a/src/main/java/com/cubefury/vendingmachine/trade/TradeGroup.java +++ b/src/main/java/com/cubefury/vendingmachine/trade/TradeGroup.java @@ -51,6 +51,10 @@ public String toString() { return this.id.toString(); } + public boolean hasNoConditions() { + return this.requirementSet.isEmpty(); + } + public void addSatisfiedCondition(UUID player, ICondition c) { synchronized (playerDone) { playerDone.computeIfAbsent(player, k -> new HashSet<>()); @@ -104,7 +108,7 @@ public void clearTradeState(UUID player) { } public boolean isUnlockedPlayer(UUID player) { - return requirementSet.equals(playerDone.get(player)); + return requirementSet.equals(playerDone.getOrDefault(player, new HashSet<>())); } public Set getAllUnlockedPlayers() { @@ -137,12 +141,24 @@ public void setTradeState(UUID player, TradeHistory history) { } public boolean canExecuteTrade(UUID player) { - List availableTrades = TradeManager.INSTANCE.getTrades(player); - for (TradeGroupWrapper trade : availableTrades) { + 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.trade().id.equals(this.id) && trade.enabled() && trade.cooldown() < 0) { + if (trade.id.equals(this.id) && enabled && cooldownRemaining < 0) { return true; } } diff --git a/src/main/java/com/cubefury/vendingmachine/trade/TradeGroupWrapper.java b/src/main/java/com/cubefury/vendingmachine/trade/TradeGroupWrapper.java deleted file mode 100644 index 935b259..0000000 --- a/src/main/java/com/cubefury/vendingmachine/trade/TradeGroupWrapper.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.cubefury.vendingmachine.trade; - -import com.github.bsideup.jabel.Desugar; - -@Desugar -// For wrapping tradegroup information for network/vending machine display -public record TradeGroupWrapper(TradeGroup trade, long cooldown, boolean enabled) {} diff --git a/src/main/java/com/cubefury/vendingmachine/trade/TradeManager.java b/src/main/java/com/cubefury/vendingmachine/trade/TradeManager.java index 8dda1bb..d527cd6 100644 --- a/src/main/java/com/cubefury/vendingmachine/trade/TradeManager.java +++ b/src/main/java/com/cubefury/vendingmachine/trade/TradeManager.java @@ -8,13 +8,12 @@ import java.util.Set; import java.util.UUID; -import javax.annotation.Nonnull; - 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.blocks.gui.TradeItemDisplay; // 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 @@ -23,12 +22,15 @@ public class TradeManager { public static TradeManager INSTANCE = new TradeManager(); private final Map> availableTrades = new HashMap<>(); + private final List noConditionTrades = new ArrayList<>(); public final Map> playerCurrency = new HashMap<>(); // For writeback to file in original format, to prevent data loss private final Map> invalidCurrency = new HashMap<>(); + public final List tradeData = new ArrayList<>(); + public boolean hasCurrencyUpdate = false; private TradeManager() {} @@ -52,29 +54,20 @@ public void removeTradeGroup(UUID player, UUID tg) { } } - public Set getAvailableTrades(@Nonnull UUID player) { - synchronized (availableTrades) { - Set trades = new HashSet<>(); - if (availableTrades.containsKey(player)) { - trades.addAll(availableTrades.get(player)); - } - return trades; - } - } - - public void setAvailableTrades(UUID player, Set tradeGroups) { - synchronized (availableTrades) { - availableTrades.put(player, new HashSet<>()); - availableTrades.get(player) - .addAll(tradeGroups); - } - } - 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()) { @@ -94,29 +87,14 @@ public void recomputeAvailableTrades(UUID player) { } } - public List getTrades(UUID player) { - long currentTimestamp = System.currentTimeMillis(); + public List getAvailableTradeGroups(UUID player) { synchronized (availableTrades) { - if (!availableTrades.containsKey(player) || availableTrades.get(player) == null) { - return new ArrayList<>(); - } - ArrayList tradeList = new ArrayList<>(); + availableTrades.computeIfAbsent(player, k -> new HashSet<>()); + availableTrades.get(player) + .addAll(noConditionTrades); + ArrayList tradeList = new ArrayList<>(); for (UUID tgId : availableTrades.get(player)) { - TradeGroup tg = TradeDatabase.INSTANCE.getTradeGroupFromId(tgId); - long lastTradeTime = tg.getTradeState(player).lastTrade; - long tradeCount = tg.getTradeState(player).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; - tradeList.add(new TradeGroupWrapper(tg, cooldownRemaining, enabled)); + tradeList.add(TradeDatabase.INSTANCE.getTradeGroupFromId(tgId)); } return tradeList; } diff --git a/src/main/java/com/cubefury/vendingmachine/util/BigItemStack.java b/src/main/java/com/cubefury/vendingmachine/util/BigItemStack.java index 0dc391e..f0e23ea 100644 --- a/src/main/java/com/cubefury/vendingmachine/util/BigItemStack.java +++ b/src/main/java/com/cubefury/vendingmachine/util/BigItemStack.java @@ -31,8 +31,8 @@ public class BigItemStack { public BigItemStack(ItemStack stack) { this.baseStack = stack.copy(); - this.stackSize = baseStack.stackSize; - baseStack.stackSize = 1; + this.stackSize = stack.stackSize; + this.baseStack.stackSize = 1; } public BigItemStack(@Nonnull Block block) { @@ -146,12 +146,12 @@ public boolean equals(Object stack) { if (stack instanceof ItemStack) { return baseStack.isItemEqual((ItemStack) stack) - && ItemStack.areItemStackTagsEqual(baseStack, (ItemStack) stack); + && ItemStack.areItemStackTagsEqual(baseStack, (ItemStack) stack) + && stackSize == ((ItemStack) stack).stackSize; } - if (stack instanceof BigItemStack) { - BigItemStack castStack = (BigItemStack) stack; - return baseStack.isItemEqual(castStack.baseStack) && baseStack.stackSize == castStack.stackSize + if (stack instanceof BigItemStack castStack) { + return baseStack.isItemEqual(castStack.baseStack) && stackSize == castStack.stackSize && ItemStack.areItemStackTagsEqual(baseStack, castStack.baseStack); } @@ -209,6 +209,13 @@ public NBTTagCompound writeToNBT(NBTTagCompound nbt) { return nbt; } + public ItemStack convertToItemStack() { + ItemStack display = this.getCombinedStacks() + .get(0); + display.stackSize = this.stackSize; + return display; + } + @Optional.Method(modid = "betterquesting") public betterquesting.api.utils.BigItemStack toBQBigItemStack() { betterquesting.api.utils.BigItemStack newBis = new betterquesting.api.utils.BigItemStack( diff --git a/src/main/resources/assets/vendingmachine/lang/en_US.lang b/src/main/resources/assets/vendingmachine/lang/en_US.lang index 47b908a..70de284 100644 --- a/src/main/resources/assets/vendingmachine/lang/en_US.lang +++ b/src/main/resources/assets/vendingmachine/lang/en_US.lang @@ -23,6 +23,7 @@ vendingmachine.gui.cooldown_display.day=d vendingmachine.gui.error.player_using=Someone is using the vending machine at the moment. gt.blockmachines.multimachine.vendingmachine.name=Vending Machine +hatch.vendinguplink.me.name=ME Vending Uplink Hatch vendingmachine.category.unknown=Unknown Category vendingmachine.category.all=All Items