diff --git a/src/main/java/com/glodblock/github/client/gui/GuiLevelMaintainer.java b/src/main/java/com/glodblock/github/client/gui/GuiLevelMaintainer.java index 5848de6c8..cf5c1fc80 100644 --- a/src/main/java/com/glodblock/github/client/gui/GuiLevelMaintainer.java +++ b/src/main/java/com/glodblock/github/client/gui/GuiLevelMaintainer.java @@ -43,6 +43,9 @@ public class GuiLevelMaintainer extends GuiSub { private Widget focusedWidget; private final FontRenderer render; private GuiToggleButton liteMode; + private Widget refreshRate; + /** Last refresh interval the server reported, in ticks; shown unless the player is editing the field. */ + private int refreshRateTicks; public GuiLevelMaintainer(InventoryPlayer ipl, TileLevelMaintainer tile) { super(new ContainerLevelMaintainer(ipl, tile)); @@ -59,6 +62,9 @@ public GuiLevelMaintainer(InventoryPlayer ipl, TileLevelMaintainer tile) { @Override public void initGui() { super.initGui(); + // All widgets below are rebuilt here, so drop the reference to the discarded ones; a resize re-runs this and a + // stale widget would make the index based checks below read the wrong one. + this.focusedWidget = null; for (int i = 0; i < TileLevelMaintainer.REQ_COUNT; i++) { VirtualMEPhantomSlot slot = new VirtualMEPhantomSlot( @@ -91,6 +97,17 @@ public void initGui() { this.buttonList, this.cont); } + // Refresh rate. The player types seconds, the tile stores ticks; it sits in the free strip between the request + // rows and the inventory. Unlike the rows it draws its background, so its white value stands out from them. + this.refreshRate = new Widget( + new FCGuiTextField(this.fontRendererObj, guiLeft + 60, guiTop + 115, 44, 14), + NameConst.TT_LEVEL_MAINTAINER_REFRESH_RATE, + -1, + Action.SetRefreshRate); + this.refreshRate.textField.setEnableBackgroundDrawing(true); + this.refreshRate.validTextColor = FCGuiColors.guiTextColorWhite.getColor(); + // A resize re-runs this, and the widget is rebuilt as "0", so put the value the block is using back. + showRefreshRate(); this.buttonList.add( this.liteMode = new GuiToggleButton( guiLeft - 18, @@ -122,6 +139,7 @@ public void drawScreen(final int mouseX, final int mouseY, final float btn) { com.getBatch().textField.handleTooltip(mouseX, mouseY, this); com.getLine().handleTooltip(mouseX, mouseY, this); } + this.refreshRate.textField.handleTooltip(mouseX, mouseY, this); } @Override @@ -132,6 +150,7 @@ public void drawBG(int offsetX, int offsetY, int mouseX, int mouseY) { for (int i = 0; i < TileLevelMaintainer.REQ_COUNT; i++) { this.component[i].draw(); } + this.refreshRate.draw(); } @Override @@ -141,12 +160,22 @@ public void drawFG(int offsetX, int offsetY, int mouseX, int mouseY) { 8, 6, FCGuiColors.guiTextColorGray.getColor()); + fontRendererObj.drawString( + NameConst.i18n(NameConst.GUI_LEVEL_MAINTAINER_REFRESH_RATE, "\n", false), + 8, + 118, + FCGuiColors.guiTextColorGray.getColor()); mouseRegions.render(mouseX, mouseY); } @Override protected void mouseClicked(final int xCoord, final int yCoord, final int btn) { if (btn == 0) { + if (this.refreshRate.textField.isMouseIn(xCoord, yCoord)) { + this.focusWidget(this.refreshRate); + super.mouseClicked(xCoord, yCoord, btn); + return; + } for (Component com : this.component) { Widget textField = com.isMouseIn(xCoord, yCoord); if (textField != null) { @@ -168,14 +197,20 @@ protected void keyTyped(final char character, final int key) { } if (!this.checkHotbarKeys(key)) { if (!((character == ' ') && this.focusedWidget.textField.getText().isEmpty())) { + final String before = this.focusedWidget.textField.getText(); this.focusedWidget.textField.textboxKeyTyped(character, key); + // Control keys (Tab, arrows) must not count as an edit, or Enter would commit the displayed default + // as an override. + if (!before.equals(this.focusedWidget.textField.getText())) { + this.focusedWidget.dirty = true; + } } super.keyTyped(character, key); this.focusedWidget.validate(); if (key == Keyboard.KEY_RETURN || key == Keyboard.KEY_NUMPADENTER) { - this.component[this.focusedWidget.componentIndex].submit(); + this.submitFocused(); this.focusWidget(null); } @@ -188,6 +223,11 @@ protected void keyTyped(final char character, final int key) { private void focusWidget(@Nullable Widget widget) { if (this.focusedWidget != null) { this.focusedWidget.textField.setFocused(false); + if (this.focusedWidget != widget && this.focusedWidget == this.refreshRate && this.refreshRate.dirty) { + // Focus moved away, which discards the edit exactly like the request rows, so the field never keeps + // showing a value the block is not using. Clicking back into the box being typed in must not reset it. + showRefreshRate(); + } } this.focusedWidget = widget; if (this.focusedWidget != null) { @@ -201,23 +241,67 @@ private void focusAdjacentWidget(boolean backwards) { } private Widget getAdjacentWidget(boolean backwards) { - int index = this.focusedWidget.componentIndex; - boolean isBatch = this.focusedWidget.action == Action.Batch; + final Widget current = this.focusedWidget; + if (current.componentIndex < 0) { + // The refresh rate sits after the last request row, so Tab walks out through it and wraps around. + return backwards ? this.component[TileLevelMaintainer.REQ_COUNT - 1].getBatch() + : this.component[0].getQty(); + } + final int index = current.componentIndex; + final boolean isBatch = current.action == Action.Batch; if (backwards) { if (isBatch) { return this.component[index].getQty(); } - return this.component[(index + TileLevelMaintainer.REQ_COUNT - 1) % TileLevelMaintainer.REQ_COUNT] - .getBatch(); + return index == 0 ? this.refreshRate : this.component[index - 1].getBatch(); } if (isBatch) { - return this.component[(index + 1) % TileLevelMaintainer.REQ_COUNT].getQty(); + return index == TileLevelMaintainer.REQ_COUNT - 1 ? this.refreshRate : this.component[index + 1].getQty(); } return this.component[index].getBatch(); } + /** Enter either applies one request row or the block's refresh rate. */ + private void submitFocused() { + final Widget widget = this.focusedWidget; + if (widget.componentIndex < 0) { + // Submitting an untouched field would pin the effective rate in as an override and stop the block from + // following the server config, so only an actual edit is sent. + if (!widget.dirty) return; + widget.validate(); + final Long typed = widget.getAmount(); + if (typed != null) { + // The field takes seconds; keep the sent value in range so the packet cannot overflow, and echo the + // clamped number back straight away. + final long seconds = Math.max( + 0L, + Math.min(TileLevelMaintainer.MAX_REFRESH_TICKS / TileLevelMaintainer.TICKS_PER_SECOND, typed)); + FluidCraft.proxy.netHandler.sendToServer( + new CPacketLevelMaintainer(widget.action, -1, seconds * TileLevelMaintainer.TICKS_PER_SECOND)); + widget.textField.setText(String.valueOf(seconds)); + } + widget.dirty = false; + return; + } + this.component[widget.componentIndex].submit(); + } + + /** Shows the interval the block re-checks at, unless the player is editing it right now. */ + public void updateRefreshRate(int ticks) { + this.refreshRateTicks = ticks; + if (this.focusedWidget == this.refreshRate || this.refreshRate.dirty) return; + showRefreshRate(); + } + + private void showRefreshRate() { + this.refreshRate.textField + .setText(String.valueOf(this.refreshRateTicks / TileLevelMaintainer.TICKS_PER_SECOND)); + this.refreshRate.dirty = false; + this.refreshRate.validate(); + } + @Override protected void actionPerformed(final GuiButton btn) { for (Component com : this.component) { @@ -448,6 +532,12 @@ private class Widget { public final int componentIndex; public final Action action; public final FCGuiTextField textField; + /** + * Set while the player has typed into this field without submitting, so the server cannot overwrite the edit. + */ + public boolean dirty; + /** Colour of a valid value; invalid input is always drawn in the error colour. */ + public int validTextColor = FCGuiColors.guiTextColorGray.getColor(); private final String tooltip; private Long amount; @@ -497,9 +587,12 @@ public void validate() { this.textField.setTextColor(FCGuiColors.guiLevelMaintainerError.getColor()); } else { this.amount = (long) ArithHelper.round(result, 0); - this.textField.setTextColor(FCGuiColors.guiTextColorGray.getColor()); + this.textField.setTextColor(this.validTextColor); } + // A negative index is the block level field: there is no request slot to write the amount back to. + if (this.componentIndex < 0) return; + IAEStack stack = component[this.componentIndex].getStack(); if (stack != null) { Long amount = component[this.componentIndex].getQty().getAmount(); diff --git a/src/main/java/com/glodblock/github/client/gui/container/ContainerLevelMaintainer.java b/src/main/java/com/glodblock/github/client/gui/container/ContainerLevelMaintainer.java index 0feb2bc6b..9c4a930a3 100644 --- a/src/main/java/com/glodblock/github/client/gui/container/ContainerLevelMaintainer.java +++ b/src/main/java/com/glodblock/github/client/gui/container/ContainerLevelMaintainer.java @@ -109,13 +109,21 @@ public void updateGui() { new PacketVirtualSlot(StorageName.NONE, list), (EntityPlayerMP) this.getInventoryPlayer().player); } - FluidCraft.proxy.netHandler.sendTo( - new SPacketLevelMaintainerGuiUpdate(this.tile.requests, !this.isFirstUpdate, this.tile.isLiteMode()), - (EntityPlayerMP) this.getInventoryPlayer().player); + this.sendGuiUpdate((EntityPlayerMP) this.getInventoryPlayer().player, !this.isFirstUpdate); this.isFirstUpdate = false; this.updateCount = 0; } + private void sendGuiUpdate(EntityPlayerMP player, boolean onlyState) { + FluidCraft.proxy.netHandler.sendTo( + new SPacketLevelMaintainerGuiUpdate( + this.tile.requests, + onlyState, + this.tile.isLiteMode(), + this.tile.getRefreshTicks()), + player); + } + @Override public void receiveSlotStacks(StorageName invName, Int2ObjectMap> slotStacks) { for (var entry : slotStacks.int2ObjectEntrySet()) { @@ -125,9 +133,7 @@ public void receiveSlotStacks(StorageName invName, Int2ObjectMap> sl for (var player : this.crafters) { NetworkHandler.instance .sendTo(new PacketVirtualSlot(StorageName.NONE, slotStacks), (EntityPlayerMP) player); - FluidCraft.proxy.netHandler.sendTo( - new SPacketLevelMaintainerGuiUpdate(this.tile.requests, false, this.tile.isLiteMode()), - (EntityPlayerMP) player); + this.sendGuiUpdate((EntityPlayerMP) player, false); } } } diff --git a/src/main/java/com/glodblock/github/common/Config.java b/src/main/java/com/glodblock/github/common/Config.java index 2b2ad1c5c..af27e212a 100644 --- a/src/main/java/com/glodblock/github/common/Config.java +++ b/src/main/java/com/glodblock/github/common/Config.java @@ -20,8 +20,9 @@ public class Config { public static int packetSize; public static int packetRate; public static boolean replaceEC2; - public static int levelMaintainerMinTicks; public static int levelMaintainerMaxTicks; + public static int levelMaintainerMinRefreshTicks; + public static int levelMaintainerMaxRefreshTicks; public static int reStockTime; public static int magnetRange; @@ -71,9 +72,23 @@ private static void loadProperty() { true, "Set true to handle missing item mappings from EC2. Note to work properly, you must have all relevant parts."); - levelMaintainerMinTicks = Config.get("LevelMaintainer", "minTick", 5, "Number on ticks for minimal request") + levelMaintainerMaxTicks = Config.get( + "LevelMaintainer", + "maxTick", + 120, + "Default refresh interval, in ticks (120 = 6 seconds), used by requesters that have not set their own. Values are clamped into the minRefreshTicks/maxRefreshTicks range and to whole seconds.") .getInt(); - levelMaintainerMaxTicks = Config.get("LevelMaintainer", "maxTick", 120, "Number on ticks for maximal request") + levelMaintainerMinRefreshTicks = Config.get( + "LevelMaintainer", + "minRefreshTicks", + 20, + "Smallest refresh interval a player may set on a requester, in ticks (20 = 1 second)").getInt(); + levelMaintainerMaxRefreshTicks = Config + .get( + "LevelMaintainer", + "maxRefreshTicks", + 1728000, + "Largest refresh interval a player may set on a requester, in ticks (1728000 = 24 hours)") .getInt(); reStockTime = Config.get("UltraWireless", "reStockTime", 1000, "Time between restocks").getInt(); diff --git a/src/main/java/com/glodblock/github/common/tile/TileLevelMaintainer.java b/src/main/java/com/glodblock/github/common/tile/TileLevelMaintainer.java index 5d1a727c1..95197f59d 100644 --- a/src/main/java/com/glodblock/github/common/tile/TileLevelMaintainer.java +++ b/src/main/java/com/glodblock/github/common/tile/TileLevelMaintainer.java @@ -76,14 +76,29 @@ public class TileLevelMaintainer extends AENetworkTile public static final String NBT_STATE = "state"; public static final String NBT_LINK = "link"; public static final String NBT_LITE_MODE = "lite_mode"; + public static final String NBT_REFRESH = "refresh_ticks"; + public static final int TICKS_PER_SECOND = 20; + /** + * Hard bounds for the re-check interval. The lower bound is one second, the upper one a day; anything slower than + * that is better served by disabling the request. + */ + public static final int MIN_REFRESH_TICKS = TICKS_PER_SECOND; + public static final int MAX_REFRESH_TICKS = 24 * 60 * 60 * TICKS_PER_SECOND; public final RequestInfo[] requests = new RequestInfo[REQ_COUNT]; private final LevelMaintainerInventory inventory = new LevelMaintainerInventory(requests); private int firstRequest = 0; + /** World time the tick manager last ran this tile. Transient; only used for tooltips. */ + private long lastCheckTick = 0; private final BaseActionSource source; private boolean isPowered = false; private boolean isLiteModeOverridden = false; private boolean isLiteMode = false; + /** + * How long it waits between re-checks, in ticks. This is the block's only interval, used whether or not there is + * work to submit. 0 follows {@link Config#levelMaintainerMaxTicks}. + */ + private int refreshTicks = 0; public TileLevelMaintainer() { getProxy().setIdlePowerUsage(1D); @@ -156,11 +171,17 @@ public void jobStateChange(ICraftingLink link) { @Override public TickingRequest getTickingRequest(IGridNode node) { - return new TickingRequest(Config.levelMaintainerMinTicks, Config.levelMaintainerMaxTicks, false, true); + // Both ends are the interval, so the tracker starts and stays on it instead of on the midpoint of a range, and + // the grid cannot drift off the value the GUI shows. The tile still answers SAME while it is working and IDLE + // once every request is satisfied, being crafted, or has no pattern. + final int refresh = getRefreshTicks(); + return new TickingRequest(refresh, refresh, false, true); } @Override public TickRateModulation tickingRequest(IGridNode node, int TicksSinceLastCall) { + // Remember when the tick manager ran us, so tooltips can count down to the next check. + this.lastCheckTick = this.getWorldObj() == null ? 0 : this.getWorldObj().getTotalWorldTime(); return canDoBusWork() ? doWork() : TickRateModulation.IDLE; } @@ -381,6 +402,74 @@ public void updateStack(int idx, @Nullable IAEStack stack) { this.saveChanges(); } + /** Interval between re-checks, in ticks, effective for this block. */ + public int getRefreshTicks() { + // The config default goes through the same clamp, so every interval the block reports sits inside the bounds + // the GUI and the Waila line show. + return clampRefreshTicks( + this.refreshTicks <= 0 ? Math.max(1, Config.levelMaintainerMaxTicks) : this.refreshTicks); + } + + /** Stores the interval a player typed in the GUI; 0 restores the server default. */ + public void setRefreshTicks(int ticks) { + this.refreshTicks = readRefreshTicks(ticks); + this.saveChanges(); + this.notifyTickRateChange(); + } + + private void notifyTickRateChange() { + // The tick manager only reads the request when the tile registers or is told to, so without this the new rate + // would not take effect until the chunk or the grid is rebuilt. + final IGridNode node = this.getProxy().getNode(); + if (node == null) return; + try { + this.getProxy().getTick().updateTickRate(node); + // The grid restarted its timer as part of that call, so the next check is a full interval away. Re-base the + // deadline the tooltips count down to, or they would point at a time that has already passed. + if (this.getWorldObj() != null) { + this.lastCheckTick = this.getWorldObj().getTotalWorldTime(); + } + } catch (final GridAccessException ignored) {} + } + + /** World time the next check is due at, or 0 before the tile has been ticked at all. */ + public long getNextCheckTick() { + if (this.lastCheckTick <= 0) return 0L; + return this.lastCheckTick + getRefreshTicks(); + } + + /** Smallest refresh interval a player may set, from the config, on a whole second and inside the hard bounds. */ + private static int minRefreshTicks() { + return configBound(Config.levelMaintainerMinRefreshTicks); + } + + /** Largest refresh interval a player may set, never below the smallest. */ + private static int maxRefreshTicks() { + return Math.max(minRefreshTicks(), configBound(Config.levelMaintainerMaxRefreshTicks)); + } + + /** A configured bound, forced into the hard bounds and onto a whole second. */ + private static int configBound(int ticks) { + return snapToSeconds(Math.max(MIN_REFRESH_TICKS, Math.min(MAX_REFRESH_TICKS, ticks))); + } + + /** + * Keeps an interval inside the configured range and on a whole second, so the seconds shown in the GUI and in Waila + * are exactly the interval the block uses. + */ + private static int clampRefreshTicks(int ticks) { + return Math.max(minRefreshTicks(), Math.min(maxRefreshTicks(), snapToSeconds(ticks))); + } + + private static int snapToSeconds(int ticks) { + return Math.max(1, Math.round(ticks / (float) TICKS_PER_SECOND)) * TICKS_PER_SECOND; + } + + /** Reads a stored refresh interval; anything absent or non-positive means "follow the config". */ + private static int readRefreshTicks(int stored) { + return stored > 0 ? clampRefreshTicks(stored) : 0; + } + private boolean getLiteModeDefault() { try { final ICraftingGrid craftingGrid = getProxy().getCrafting(); @@ -460,10 +549,16 @@ public void writeToNBTEvent(NBTTagCompound data) { if (this.isLiteModeOverridden) { data.setBoolean(NBT_LITE_MODE, this.isLiteMode); } + if (this.refreshTicks != 0) { + data.setInteger(NBT_REFRESH, this.refreshTicks); + } } @TileEvent(TileEventType.WORLD_NBT_READ) public void readFromNBTEvent(NBTTagCompound data) { + // getInteger answers 0 for a missing key, which is exactly the "follow the config" value, so old tiles load + // unchanged. + this.refreshTicks = readRefreshTicks(data.getInteger(NBT_REFRESH)); if (data.hasKey(NBT_REQUESTS)) { NBTTagList tagList = data.getTagList(NBT_REQUESTS, Constants.NBT.TAG_COMPOUND); for (int i = 0; i < tagList.tagCount(); i++) { @@ -595,7 +690,11 @@ public void uploadSettings(SettingsFrom from, NBTTagCompound compound) { } else { this.isLiteModeOverridden = false; } + // A card from a block that follows the config default arrives without the key, so this block falls back to the + // config default as well. + this.refreshTicks = readRefreshTicks(compound.getInteger(NBT_REFRESH)); this.saveChanges(); + this.notifyTickRateChange(); } @Override @@ -614,6 +713,9 @@ public NBTTagCompound downloadSettings(SettingsFrom from) { if (isLiteModeOverridden) { compound.setBoolean(NBT_LITE_MODE, isLiteMode); } + if (this.refreshTicks != 0) { + compound.setInteger(NBT_REFRESH, this.refreshTicks); + } return compound; } diff --git a/src/main/java/com/glodblock/github/crossmod/waila/Tooltip.java b/src/main/java/com/glodblock/github/crossmod/waila/Tooltip.java index f3db60973..2ffa451d4 100644 --- a/src/main/java/com/glodblock/github/crossmod/waila/Tooltip.java +++ b/src/main/java/com/glodblock/github/crossmod/waila/Tooltip.java @@ -6,6 +6,7 @@ import net.minecraft.client.resources.I18n; import net.minecraftforge.fluids.FluidStack; +import com.glodblock.github.common.tile.TileLevelMaintainer; import com.glodblock.github.util.NameConst; import com.glodblock.github.util.Util; @@ -44,6 +45,12 @@ public static String tileLevelMaintainerFormat(String name, long quantity, long isEnable ? I18n.format(NameConst.WAILA_ENABLE) : I18n.format(NameConst.WAILA_DISABLE)); } + /** @param ticksLeft ticks until the next check, rounded up to whole seconds */ + public static String tileLevelMaintainerRateFormat(long ticksLeft, int rateTicks) { + final int second = TileLevelMaintainer.TICKS_PER_SECOND; + return I18n.format(NameConst.WAILA_NEXT_REQUEST, (ticksLeft + second - 1) / second, rateTicks / second); + } + public static String partFluidTerminalFluidFormat(FluidStack fs) { int fid = Util.getFluidID(fs.getFluid()); if (fid == -1) { diff --git a/src/main/java/com/glodblock/github/crossmod/waila/tile/LevelMaintainerWailaDataProvider.java b/src/main/java/com/glodblock/github/crossmod/waila/tile/LevelMaintainerWailaDataProvider.java index 5da4f56ec..5b225cb7e 100644 --- a/src/main/java/com/glodblock/github/crossmod/waila/tile/LevelMaintainerWailaDataProvider.java +++ b/src/main/java/com/glodblock/github/crossmod/waila/tile/LevelMaintainerWailaDataProvider.java @@ -19,12 +19,25 @@ public class LevelMaintainerWailaDataProvider extends BaseWailaDataProvider { + /** Refresh interval in effect, in ticks. Sent even when the block follows the config default. */ + private static final String NBT_RATE = "ae2fc_rate"; + /** World time the next check is due at, or 0 before the tile has been ticked. */ + private static final String NBT_NEXT = "ae2fc_next"; + @Override public List getWailaBody(final ItemStack itemStack, final List currentToolTip, final IWailaDataAccessor accessor, final IWailaConfigHandler config) { final TileEntity te = accessor.getTileEntity(); if (te instanceof TileLevelMaintainer tileLevelMaintainer) { NBTTagCompound data = accessor.getNBTData(); + final long due = data.getLong(NBT_NEXT); + // Without a deadline the tile has not been ticked yet, and "next in 0s" would be a lie. + if (due > 0) { + currentToolTip.add( + Tooltip.tileLevelMaintainerRateFormat( + ticksUntilNextCheck(due, accessor), + data.getInteger(NBT_RATE))); + } if (data.hasKey(TileLevelMaintainer.NBT_REQUESTS)) { NBTTagList tagList = data.getTagList(TileLevelMaintainer.NBT_REQUESTS, Constants.NBT.TAG_COMPOUND); for (int i = 0; i < tagList.tagCount(); i++) { @@ -48,11 +61,23 @@ public List getWailaBody(final ItemStack itemStack, final List c return currentToolTip; } + /** Ticks left until the next check, counted down client side against the deadline the server sent. */ + private static long ticksUntilNextCheck(final long due, final IWailaDataAccessor accessor) { + final World world = accessor.getWorld(); + if (world == null) return 0L; + return Math.max(0L, due - world.getTotalWorldTime()); + } + @Override public NBTTagCompound getNBTData(final EntityPlayerMP player, final TileEntity te, final NBTTagCompound tag, final World world, final int x, final int y, final int z) { - if (te instanceof TileLevelMaintainer) { - te.writeToNBT(tag); + if (te instanceof TileLevelMaintainer tile) { + tile.writeToNBT(tag); + // The tile tag only carries the refresh rate when the block overrides the config, so always send the + // effective + // one along with the world time the next check is due at. + tag.setInteger(NBT_RATE, tile.getRefreshTicks()); + tag.setLong(NBT_NEXT, tile.getNextCheckTick()); } return tag; } diff --git a/src/main/java/com/glodblock/github/network/CPacketLevelMaintainer.java b/src/main/java/com/glodblock/github/network/CPacketLevelMaintainer.java index 6674c58e5..f95389cb9 100644 --- a/src/main/java/com/glodblock/github/network/CPacketLevelMaintainer.java +++ b/src/main/java/com/glodblock/github/network/CPacketLevelMaintainer.java @@ -18,6 +18,8 @@ public enum Action { Disable, ToggleLiteMode, ClearLiteMode, + // Appended last on purpose: the ordinal is what travels over the wire, so existing actions keep theirs. + SetRefreshRate, } private Action action; @@ -72,6 +74,8 @@ public IMessage onMessage(CPacketLevelMaintainer message, MessageContext ctx) { case Disable -> clm.getTile().updateStatus(message.slotIndex, true); case ToggleLiteMode -> clm.getTile().toggleLiteMode(); case ClearLiteMode -> clm.getTile().clearLiteMode(); + case SetRefreshRate -> clm.getTile() + .setRefreshTicks((int) Math.max(0L, Math.min(Integer.MAX_VALUE, message.size))); } clm.updateGui(); } diff --git a/src/main/java/com/glodblock/github/network/SPacketLevelMaintainerGuiUpdate.java b/src/main/java/com/glodblock/github/network/SPacketLevelMaintainerGuiUpdate.java index da814cde4..ee548cc57 100644 --- a/src/main/java/com/glodblock/github/network/SPacketLevelMaintainerGuiUpdate.java +++ b/src/main/java/com/glodblock/github/network/SPacketLevelMaintainerGuiUpdate.java @@ -19,14 +19,17 @@ public class SPacketLevelMaintainerGuiUpdate implements IMessage { private Info[] infoList; private boolean onlyState; private boolean isLiteMode; + private int refreshTicks; @SuppressWarnings("unused") public SPacketLevelMaintainerGuiUpdate() {} - public SPacketLevelMaintainerGuiUpdate(RequestInfo[] requests, boolean onlyState, boolean isLiteMode) { + public SPacketLevelMaintainerGuiUpdate(RequestInfo[] requests, boolean onlyState, boolean isLiteMode, + int refreshTicks) { this.infoList = new Info[REQ_COUNT]; this.onlyState = onlyState; this.isLiteMode = isLiteMode; + this.refreshTicks = refreshTicks; for (int i = 0; i < REQ_COUNT; i++) { if (requests[i] == null) { @@ -46,6 +49,7 @@ public void fromBytes(ByteBuf buf) { this.infoList = new Info[REQ_COUNT]; this.onlyState = buf.readBoolean(); this.isLiteMode = buf.readBoolean(); + this.refreshTicks = buf.readInt(); for (int i = 0; i < REQ_COUNT; i++) { if (buf.readBoolean()) { @@ -68,6 +72,7 @@ public void fromBytes(ByteBuf buf) { public void toBytes(ByteBuf buf) { buf.writeBoolean(this.onlyState); buf.writeBoolean(this.isLiteMode); + buf.writeInt(this.refreshTicks); for (Info info : this.infoList) { buf.writeBoolean(info != null); if (info == null) continue; @@ -120,6 +125,7 @@ public IMessage onMessage(SPacketLevelMaintainerGuiUpdate message, MessageContex } gui.updateComponent(message.isLiteMode); + gui.updateRefreshRate(message.refreshTicks); } return null; diff --git a/src/main/java/com/glodblock/github/util/NameConst.java b/src/main/java/com/glodblock/github/util/NameConst.java index de6a0a31b..28d4efe9d 100644 --- a/src/main/java/com/glodblock/github/util/NameConst.java +++ b/src/main/java/com/glodblock/github/util/NameConst.java @@ -75,6 +75,7 @@ public class NameConst { public static final String TT_LEVEL_MAINTAINER_NONE = TT_LEVEL_MAINTAINER + "none"; public static final String TT_LEVEL_MAINTAINER_REQUEST_SIZE = TT_LEVEL_MAINTAINER + "request_size"; public static final String TT_LEVEL_MAINTAINER_BATCH_SIZE = TT_LEVEL_MAINTAINER + "batch_size"; + public static final String TT_LEVEL_MAINTAINER_REFRESH_RATE = TT_LEVEL_MAINTAINER + "refresh_rate"; public static final String TT_LEVEL_MAINTAINER_IDLE = TT_LEVEL_MAINTAINER + "idle"; public static final String TT_LEVEL_MAINTAINER_IDLE_DESC = TT_LEVEL_MAINTAINER + "idle_desc"; public static final String TT_LEVEL_MAINTAINER_LINK = TT_LEVEL_MAINTAINER + "link"; @@ -109,10 +110,12 @@ public class NameConst { public static final String WAILA_KEY = FluidCraft.MODID + ".waila."; public static final String WAILA_ENABLE = WAILA_KEY + "enable"; public static final String WAILA_DISABLE = WAILA_KEY + "disable"; + public static final String WAILA_NEXT_REQUEST = WAILA_KEY + "next_request"; public static final String RES_KEY = FluidCraft.MODID + ":"; public static final String GUI_KEY = FluidCraft.MODID + ".gui."; public static final String GUI_LEVEL_MAINTAINER = GUI_KEY + BLOCK_LEVEL_MAINTAINER; + public static final String GUI_LEVEL_MAINTAINER_REFRESH_RATE = GUI_LEVEL_MAINTAINER + ".refresh_rate"; public static final String GUI_LEVEL_TERMINAL = GUI_KEY + ITEM_PART_LEVEL_TERMINAL; public static final String GUI_FLUID_PATTERN_ENCODER = GUI_KEY + BLOCK_FLUID_PATTERN_ENCODER; public static final String GUI_FLUID_PACKET_DECODER = GUI_KEY + BLOCK_FLUID_PACKET_DECODER; diff --git a/src/main/resources/assets/ae2fc/lang/en_US.lang b/src/main/resources/assets/ae2fc/lang/en_US.lang index 2bc90621f..6ae42e560 100644 --- a/src/main/resources/assets/ae2fc/lang/en_US.lang +++ b/src/main/resources/assets/ae2fc/lang/en_US.lang @@ -75,6 +75,7 @@ tile.certus_quartz_tank_empty.name=Certus Quartz Tank ae2fc.waila.enable=Enable ae2fc.waila.disable=Disable +ae2fc.waila.next_request=§aNext request in %ss (every %ss) ae2fc.tooltip.shift_for_more=§r> Hold §3Shift§r for more information ae2fc.tooltip.ctrl_for_more=§r> Hold §3Ctrl§r for more information @@ -102,6 +103,8 @@ ae2fc.tooltip.level_maintainer.cant_craft_desc=The Requester is trying to schedu ae2fc.tooltip.level_maintainer.lite_craft_desc=Toggle lite crafting mode. Shift-click to clear override and restore network defaults. ae2fc.tooltip.level_maintainer.batch_size=§6Crafting Batch Size§r ae2fc.tooltip.level_maintainer.batch_size.hint=When craft requests are emitted, the Requester will use the batch size for the request. Using larger batches will increase the speed of stocking up items but will require more crafting storage. +ae2fc.tooltip.level_maintainer.refresh_rate=§6Refresh Rate§r +ae2fc.tooltip.level_maintainer.refresh_rate.hint=How long the Requester waits between re-checks of its request list, in seconds. The server limits this between its own minimum and maximum; 0 restores the server default. ae2fc.tooltip.level_maintainer.request_size=§6Amount to Maintain§r ae2fc.tooltip.level_maintainer.request_size.hint=The count defines how many items should be held in stock before the Requester starts requesting new crafts. ae2fc.tooltip.ultra_terminal.WIRELESS_CRAFTING_TERMINAL=Mode: Crafting Terminal @@ -187,6 +190,7 @@ ae2fc.gui.ingredient_buffer=Ingredient Buffer ae2fc.gui.large_ingredient_buffer=Large Ingredient Buffer ae2fc.gui.fluid_interface=ME Dual Interface ae2fc.gui.level_maintainer=ME Level Maintainer +ae2fc.gui.level_maintainer.refresh_rate=Refresh: ae2fc.gui.fluid_interface.0=D ae2fc.gui.fluid_interface.1=U ae2fc.gui.fluid_interface.2=N