diff --git a/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocal.java b/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocal.java index e68589a8f..ec0cb4155 100644 --- a/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocal.java +++ b/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocal.java @@ -33,6 +33,7 @@ import net.minecraft.core.entity.player.Player; import net.minecraft.core.entity.vehicle.EntityBoat; import net.minecraft.core.item.ItemStack; import net.minecraft.core.lang.I18n; +import net.minecraft.core.net.ChatEmotes; import net.minecraft.core.net.command.TextFormatting; import net.minecraft.client.net.command.ClientCommandSource; import net.minecraft.core.player.Session; @@ -336,7 +337,7 @@ public class PlayerLocal extends Player { this.mc.thePlayer.sendMessage(TextFormatting.RED + e.getMessage()); } } else { - this.mc.hudIngame.addChatMessage("<" + getDisplayName() + TextFormatting.RESET + "> " + TextFormatting.WHITE + s); + this.mc.hudIngame.addChatMessage("<" + getDisplayName() + TextFormatting.RESET + "> " + TextFormatting.WHITE + ChatEmotes.process(s)); } } diff --git a/game/client/src/main/java/net/minecraft/client/gui/IntegerSliderElement.java b/game/client/src/main/java/net/minecraft/client/gui/IntegerSliderElement.java index 950bb3256..71b58e899 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/IntegerSliderElement.java +++ b/game/client/src/main/java/net/minecraft/client/gui/IntegerSliderElement.java @@ -60,7 +60,7 @@ public class IntegerSliderElement extends ButtonElement { int maxValue = this.option.size() - 1; if (this.dragging) { int segments = maxValue; - float percentage = (float) (mouseX - (this.xPosition + 4) - (this.width / (2 * segments))) / (float) (this.width - sliderWidth); + float percentage = (float)(mouseX - (this.xPosition + 4) - this.width / (this.width * maxValue)) / (float)(this.width - sliderWidth); if (percentage > 1.0f) percentage = 1.0f; diff --git a/game/client/src/main/java/net/minecraft/client/gui/chat/GuiElementChatEmotePicker.java b/game/client/src/main/java/net/minecraft/client/gui/chat/GuiElementChatEmotePicker.java index 7d95ca20e..58a6a410b 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/chat/GuiElementChatEmotePicker.java +++ b/game/client/src/main/java/net/minecraft/client/gui/chat/GuiElementChatEmotePicker.java @@ -11,6 +11,9 @@ import java.util.Map; public class GuiElementChatEmotePicker extends Gui { private static final int CELL_WIDTH = 16; private static final int CELL_HEIGHT = 16; + private static final int SCROLLBAR_WIDTH = 2; + private static final int BACKGROUND_COLOR = 0xC0000000; + private static final int SCROLLBAR_COLOR = 0xFF808080; private final Minecraft mc; private final ScreenChat parent; @@ -34,8 +37,8 @@ public class GuiElementChatEmotePicker extends Gui { this.height = yCells * CELL_HEIGHT; this.yCellsInner = (int) Math.ceil((double) ChatEmotes.getEmotes().size() / xCells); - this.heightInner = yCellsInner * CELL_HEIGHT; - this.maxScroll = heightInner - height; + this.heightInner = Math.max(yCellsInner * CELL_HEIGHT, 1); + this.maxScroll = Math.max(heightInner - height, 0); this.emotes = new Map.Entry[yCellsInner][xCells]; int i = 0; @@ -65,9 +68,19 @@ public class GuiElementChatEmotePicker extends Gui { } public void draw(int x, int y, int mouseX, int mouseY) { + draw(x, y, mouseX, mouseY, 1.0f, x, y); + } + + + public void draw(int x, int y, int mouseX, int mouseY, float scale, int anchorX, int anchorY) { drawOuter(x, y); - Scissor.enable(x, y, width, height); + Scissor.enable( + Math.round(anchorX + (x - anchorX) * scale), + Math.round(anchorY + (y - anchorY) * scale), + Math.round(width * scale), + Math.round(height * scale) + ); drawInner(x, y, mouseX, mouseY); Scissor.disable(); } @@ -90,19 +103,21 @@ public class GuiElementChatEmotePicker extends Gui { } private void drawOuter(int x, int y) { - drawRect(x, y, x + width + 2, y + height, 0xFF000000); + drawRect(x, y, x + width + SCROLLBAR_WIDTH, y + height, BACKGROUND_COLOR); - // Draw scrollbar + // Draw scrollbar, unless everything already fits + if (maxScroll <= 0) return; int scrollbarHeight = height * height / heightInner; int scrollbarY = y + scroll * (height - scrollbarHeight) / maxScroll; - drawRect(x + width, scrollbarY, x + width + 2, scrollbarY + scrollbarHeight, 0xFF808080); + drawRect(x + width, scrollbarY, x + width + SCROLLBAR_WIDTH, scrollbarY + scrollbarHeight, SCROLLBAR_COLOR); } private void drawInner(int x, int y, int mouseX, int mouseY) { mouseX = mouseX - x; mouseY = mouseY - y; - int cellX = mouseX / CELL_WIDTH; - int cellY = (mouseY + scroll) / CELL_HEIGHT; + boolean mouseInside = mouseX >= 0 && mouseX < width && mouseY >= 0 && mouseY < height; + int cellX = mouseInside ? mouseX / CELL_WIDTH : -1; + int cellY = mouseInside ? (mouseY + scroll) / CELL_HEIGHT : -1; for (int cy = 0; cy < emotes.length; cy++) { Map.Entry[] row = emotes[cy]; diff --git a/game/client/src/main/java/net/minecraft/client/gui/chat/ScreenChat.java b/game/client/src/main/java/net/minecraft/client/gui/chat/ScreenChat.java index 94e1074de..33e0470ac 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/chat/ScreenChat.java +++ b/game/client/src/main/java/net/minecraft/client/gui/chat/ScreenChat.java @@ -3,6 +3,7 @@ package net.minecraft.client.gui.chat; import com.mojang.logging.LogUtils; import net.minecraft.client.gui.Screen; import net.minecraft.client.gui.hud.component.ComponentAnchor; +import net.minecraft.client.gui.hud.component.HudComponentChatInput; import net.minecraft.client.gui.hud.component.HudComponents; import net.minecraft.client.gui.keyboard.ScreenKeyboard; import net.minecraft.client.gui.text.TextFieldEditor; @@ -17,6 +18,9 @@ import net.minecraft.client.gui.text.ITextField; import org.lwjgl.input.Mouse; import org.slf4j.Logger; +import java.util.ArrayList; +import java.util.List; + public class ScreenChat extends Screen implements ITextField { protected static final Logger LOGGER = LogUtils.getLogger(); public String message; @@ -27,6 +31,7 @@ public class ScreenChat extends Screen implements ITextField { protected GuiElementChatEmotePicker emotePicker; protected boolean showEmotePicker = false; private GuiElementChatSuggestions suggestionsElement; + private final List lineStarts = new ArrayList<>(); public ScreenChat() { this(""); @@ -37,6 +42,7 @@ public class ScreenChat extends Screen implements ITextField { this.updateCounter = 0; this.editor = new TextFieldEditor(this); this.editor.setCursor(10000); + this.lineStarts.add(0); } @Override @@ -50,7 +56,7 @@ public class ScreenChat extends Screen implements ITextField { this.mc, this.editor, this, - (parent, child, minecraft, followParameters) -> HudComponents.CHAT_INPUT.getLayout().getComponentX(HudComponents.CHAT_INPUT, minecraft.resolution.getScaledWidthScreenCoords()) + 14 + (followParameters ? this.suggestionsElement.getDefaultParameterPosition() - 1 : 0), + (parent, child, minecraft, followParameters) -> HudComponents.CHAT_INPUT.getLayout().getComponentX(HudComponents.CHAT_INPUT, minecraft.resolution.getScaledWidthScreenCoords()) + HudComponentChatInput.FIELD_X_OFFSET + (followParameters ? getFieldXOffset(MathHelper.clamp(this.suggestionsElement.getSuggestionRangeStart(), 0, this.message.length())) - 1 : 0), (parent, child, minecraft, followParameters) -> HudComponents.CHAT_INPUT.getLayout().getComponentY(HudComponents.CHAT_INPUT, minecraft.resolution.getScaledHeightScreenCoords()) - 2, ComponentAnchor.BOTTOM_LEFT ); @@ -83,6 +89,7 @@ public class ScreenChat extends Screen implements ITextField { public void removed() { Keyboard.enableRepeatEvents(false); HudComponents.CHAT.chatScroll = 0; + HudComponents.CHAT_INPUT.setLineCount(1); } @Override @@ -206,19 +213,26 @@ public class ScreenChat extends Screen implements ITextField { @Override public void render(int mx, int my, float partialTick) { try { - int inputX = HudComponents.CHAT_INPUT.getLayout().getComponentX(HudComponents.CHAT_INPUT, this.width); - int inputY = HudComponents.CHAT_INPUT.getLayout().getComponentY(HudComponents.CHAT_INPUT, this.height); - float scale = HudComponents.CHAT_INPUT.getScale(); - int boxWidth = HudComponents.CHAT_INPUT.getBaseXSize(); + HudComponentChatInput input = HudComponents.CHAT_INPUT; + + updateWrappedLines(input.getTextWrapWidth()); + input.setLineCount(this.lineStarts.size()); + + int inputX = input.getLayout().getComponentX(input, this.width); + int inputY = input.getLayout().getComponentY(input, this.height); + float scale = input.getScale(); + int boxWidth = input.getBaseXSize(); + int boxHeight = input.getBoxHeight(); // Convert mouse coords to component-local (unscaled) space for hit-testing. int localMx = unscaleX(mx, inputX, scale); int localMy = unscaleY(my, inputY, scale); - // Draw emote picker button + // Draw emote picker button, kept level with the bottom line of the box + int buttonY = inputY + boxHeight - 12; int emotePickerButtonColor = 0x80000000; int emotePickerTextColor = 0xE0E0E0; - if (localMx >= inputX && localMx < inputX + 12 && localMy >= inputY && localMy < inputY + 12) + if (localMx >= inputX && localMx < inputX + 12 && localMy >= buttonY && localMy < buttonY + 12) { emotePickerButtonColor = 0x80ffffff; emotePickerTextColor = 0xffffffa0; @@ -232,27 +246,29 @@ public class ScreenChat extends Screen implements ITextField { GLRenderer.modelM4f().translate(-inputX, -inputY, 0.0f); } - drawRect(inputX, inputY, inputX + 12, inputY + 12, emotePickerButtonColor); - drawStringCenteredShadow(this.fontRenderer, Character.toString('\u263a'), inputX + 6, inputY + 2, emotePickerTextColor); + drawRect(inputX, buttonY, inputX + 12, buttonY + 12, emotePickerButtonColor); + drawStringCenteredShadow(this.fontRenderer, Character.toString('\u263a'), inputX + 6, buttonY + 2, emotePickerTextColor); + + drawRect(inputX + HudComponentChatInput.FIELD_X_OFFSET, inputY, inputX + boxWidth, inputY + boxHeight, 0x80000000); + + int suggestionStart = MathHelper.clamp(this.suggestionsElement.getSuggestionRangeStart(), 0, this.message.length()); + drawStringShadow(this.fontRenderer, this.suggestionsElement.getSuggestionPreview(), + inputX + HudComponentChatInput.FIELD_X_OFFSET + getFieldXOffset(suggestionStart), + inputY + 2 + getFieldYOffset(suggestionStart), 0xFFFFFF); - drawRect(inputX + 14, inputY, inputX + boxWidth, inputY + 12, 0x80000000); + drawWrappedMessage(inputX, inputY); - drawStringShadow(this.fontRenderer, this.suggestionsElement.getSuggestionPreview(), inputX + 14 + this.suggestionsElement.getDefaultParameterPosition(), inputY + 2, 0xFFFFFF); boolean drawCursor = (this.updateCounter / 6) % 2 == 0; - int cursor = this.editor.getCursor(); - drawStringShadow(this.fontRenderer, this.suggestionsElement.colorCodeText(this.message, true), inputX + 16, inputY + 2, 0xE0E0E0); if (drawCursor) { - int width = MathHelper.ceil(this.fontRenderer.stringWidthDouble(this.message)); - if (cursor < this.message.length()) - { - width = MathHelper.ceil(this.fontRenderer.stringWidthDouble(this.message.substring(0, cursor))); - } - drawStringShadow(this.fontRenderer, "_", inputX + 16 + width, inputY + 2, 0xE0E0E0); + int cursor = MathHelper.clamp(this.editor.getCursor(), 0, this.message.length()); + drawStringShadow(this.fontRenderer, "_", + inputX + HudComponentChatInput.FIELD_X_OFFSET + getFieldXOffset(cursor), + inputY + 2 + getFieldYOffset(cursor), 0xE0E0E0); } if (this.showEmotePicker) { - this.emotePicker.draw(inputX, inputY - 2 - this.emotePicker.getHeight(), localMx, localMy); + this.emotePicker.draw(inputX, inputY - 2 - this.emotePicker.getHeight(), localMx, localMy, scale, inputX, inputY); } if (scaled) { @@ -278,19 +294,130 @@ public class ScreenChat extends Screen implements ITextField { return Math.round(anchorY + (screenY - anchorY) / scale); } + /** + * Word wraps {@link #message} into {@link #lineStarts}. Unlike the font renderer's own splitting + * this keeps indices into the original string. + */ + private void updateWrappedLines(int wrapWidth) { + this.lineStarts.clear(); + this.lineStarts.add(0); + + double lineWidth = 0; + double wordWidth = 0; + int wordStart = 0; + for (int i = 0; i < this.message.length(); i++) { + char c = this.message.charAt(i); + // format codes don't take up any space + if (c == '§' && i + 1 < this.message.length()) { + i++; + continue; + } + + double charWidth = this.fontRenderer.stringWidthDouble(String.valueOf(c)); + if (c == ' ') { + lineWidth += wordWidth + charWidth; + wordWidth = 0; + wordStart = i + 1; + continue; + } + + if (lineWidth + wordWidth + charWidth > wrapWidth) { + int currentLineStart = this.lineStarts.get(this.lineStarts.size() - 1); + if (wordStart > currentLineStart) { + // Carry the whole word down to the next line + this.lineStarts.add(wordStart); + lineWidth = 0; + } else if (i > currentLineStart) { + // single word is too long, so break it midway + this.lineStarts.add(i); + wordStart = i; + wordWidth = 0; + lineWidth = 0; + } + } + wordWidth += charWidth; + } + } + + /** Index of the wrapped line that the given index into {@link #message} sits on. */ + private int getLineForIndex(int index) { + for (int i = this.lineStarts.size() - 1; i > 0; i--) { + if (this.lineStarts.get(i) <= index) return i; + } + return 0; + } + + /** X offset of an index into {@link #message}, measured from the left edge of the text box. */ + private int getFieldXOffset(int index) { + int lineStart = this.lineStarts.get(getLineForIndex(index)); + return 2 + MathHelper.ceil(this.fontRenderer.stringWidthDouble(TextFieldEditor.substring(this.message, lineStart, index))); + } + + /** Y offset of an index into {@link #message}, measured from the first line of the text box. */ + private int getFieldYOffset(int index) { + return getLineForIndex(index) * HudComponentChatInput.LINE_HEIGHT; + } + + private void drawWrappedMessage(int inputX, int inputY) { + String colored = this.suggestionsElement.colorCodeText(this.message, true); + int[] coloredIndices = mapRawIndicesToColored(colored, this.message.length()); + + for (int line = 0; line < this.lineStarts.size(); line++) { + int start = this.lineStarts.get(line); + int end = line + 1 < this.lineStarts.size() ? this.lineStarts.get(line + 1) : this.message.length(); + String text = TextFieldEditor.substring(colored, coloredIndices[start], coloredIndices[end]); + + drawStringShadow(this.fontRenderer, formattingAt(colored, coloredIndices[start]) + text, + inputX + HudComponentChatInput.FIELD_X_OFFSET + 2, + inputY + 2 + line * HudComponentChatInput.LINE_HEIGHT, 0xE0E0E0); + } + } + + /** Maps each index of the raw message onto the matching index of its color coded form.*/ + private static int[] mapRawIndicesToColored(String colored, int rawLength) { + int[] indices = new int[rawLength + 1]; + int raw = 0; + int i = 0; + while (i < colored.length() && raw < rawLength) { + if (colored.charAt(i) == '§' && i + 1 < colored.length()) { + i += 2; + continue; + } + indices[raw++] = i++; + } + while (raw <= rawLength) { + indices[raw++] = colored.length(); + } + return indices; + } + + /** The last formatting code appearing before the given index, or an empty string if there is none. */ + private static String formattingAt(String colored, int index) { + String formatting = ""; + for (int i = 0; i + 1 < Math.min(index, colored.length()); i++) { + if (colored.charAt(i) == '§') { + formatting = colored.substring(i, i + 2); + i++; + } + } + return formatting; + } + @Override public void mouseClicked(int mx, int my, int buttonNum) { super.mouseClicked(mx, my, buttonNum); try { boolean handled = false; if (buttonNum == 0) { - int inputX = HudComponents.CHAT_INPUT.getLayout().getComponentX(HudComponents.CHAT_INPUT, this.width); - int inputY = HudComponents.CHAT_INPUT.getLayout().getComponentY(HudComponents.CHAT_INPUT, this.height); - float scale = HudComponents.CHAT_INPUT.getScale(); + HudComponentChatInput input = HudComponents.CHAT_INPUT; + int inputX = input.getLayout().getComponentX(input, this.width); + int inputY = input.getLayout().getComponentY(input, this.height); + float scale = input.getScale(); int localMx = unscaleX(mx, inputX, scale); int localMy = unscaleY(my, inputY, scale); + int buttonY = inputY + input.getBoxHeight() - 12; - if (localMx >= inputX && localMx < inputX + 12 && localMy >= inputY && localMy < inputY + 12) { + if (localMx >= inputX && localMx < inputX + 12 && localMy >= buttonY && localMy < buttonY + 12) { this.showEmotePicker = !this.showEmotePicker; handled = true; } diff --git a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenContainerAbstract.java b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenContainerAbstract.java index fe7d2dbb7..0fac3002c 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenContainerAbstract.java +++ b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenContainerAbstract.java @@ -309,11 +309,7 @@ public abstract class ScreenContainerAbstract extends Screen { } Slot slot = this.inventorySlots.getSlot(getSlotId(mx, my)); if (slot != null) { - if (slot.getItemStack() == null) { - this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.SORT, new int[]{slot.index, 64}, this.mc.thePlayer); - } else { - this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.CREATIVE_GRAB, new int[]{slot.index, 64}, this.mc.thePlayer); - } + creativeGrabStack(slot); } return; } @@ -345,6 +341,15 @@ public abstract class ScreenContainerAbstract extends Screen { int catalogIndex = slot instanceof SlotCreative creativeSlot ? MenuInventoryCreative.getCreativeItemIndex(creativeSlot.item) : -1; return new int[]{slot.index, amount, stack != null ? stack.itemID : 0, stack != null ? stack.getMetadata() : 0, catalogIndex}; } + + protected void creativeGrabStack(@NotNull Slot slot) { + ItemStack stack = slot.getItemStack(); + if (stack == null) { + this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.SORT, new int[]{slot.index, 64}, this.mc.thePlayer); + } else { + this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.CREATIVE_GRAB, creativeSlotArgs(slot, stack.getMaxStackSize()), this.mc.thePlayer); + } + } protected int[] hotbarSwapArgs(int slotId, int hotbarSlotNumber) { Slot slot = this.inventorySlots.getSlot(slotId); @@ -354,6 +359,10 @@ public abstract class ScreenContainerAbstract extends Screen { return new int[]{slotId, hotbarSlotNumber}; } + public void clickSlot(@NotNull Slot slot, int mouseButton) { + clickInventory(slot.x + 8 + (this.width - this.xSize) / 2, slot.y + 8 + (this.height - this.ySize) / 2, mouseButton); + } + /** * This method controls what InventoryAction is sent to the server */ @@ -374,12 +383,7 @@ public abstract class ScreenContainerAbstract extends Screen { if (!this.mc.thePlayer.getGamemode().hasBlockConsumption()) { // Duplicate items with middle mouse button if (mouseButton == 2) { - Slot slot = this.inventorySlots.getSlot(slotId); - if (slot.getItemStack() == null) { - this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.SORT, new int[]{slotId, 64}, this.mc.thePlayer); - } else { - this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.CREATIVE_GRAB, new int[]{slotId, 64}, this.mc.thePlayer); - } + creativeGrabStack(this.inventorySlots.getSlot(slotId)); return; } } diff --git a/game/client/src/main/java/net/minecraft/client/gui/guidebook/SlotGuidebook.java b/game/client/src/main/java/net/minecraft/client/gui/guidebook/SlotGuidebook.java index e1bcf2f0f..188d635b1 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/guidebook/SlotGuidebook.java +++ b/game/client/src/main/java/net/minecraft/client/gui/guidebook/SlotGuidebook.java @@ -7,6 +7,7 @@ import net.minecraft.core.WeightedRandomLootObject; import net.minecraft.core.achievement.stat.StatList; import net.minecraft.core.data.registry.recipe.RecipeEntryBase; import net.minecraft.core.data.registry.recipe.RecipeSymbol; +import net.minecraft.core.data.registry.recipe.SearchQuery; import net.minecraft.core.data.registry.recipe.entry.RecipeEntryTrommel; import net.minecraft.core.entity.player.Player; import net.minecraft.core.item.ItemStack; @@ -14,6 +15,7 @@ import net.minecraft.core.player.gamemode.Gamemodes; import net.minecraft.core.player.inventory.slot.Slot; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -21,9 +23,11 @@ public class SlotGuidebook extends Slot { public @Nullable ItemStack item; public @Nullable RecipeSymbol symbol; + public @Nullable List searchFilteredItems; public @Nullable RecipeEntryBase recipe; public int recipeIndex = 0; public int recipeAmount = 0; + private int cycleIndex = 0; public boolean isOutput = false; public SlotGuidebook(final int id, final int x, final int y, final @Nullable RecipeSymbol symbol, final boolean discovered, final @Nullable RecipeEntryBase recipe) { @@ -31,7 +35,7 @@ public class SlotGuidebook extends Slot this.index = id; this.symbol = symbol; if(symbol != null && symbol.getItemGroup() != null){ - showRandomItem(); + randomizeCycleStart(); } else if (symbol != null) { this.item = symbol.resolve().get(0); if(recipe instanceof RecipeEntryTrommel entryTrommel){ @@ -50,7 +54,7 @@ public class SlotGuidebook extends Slot super(null, id, x, y); this.symbol = symbol; if(symbol != null && symbol.getItemGroup() != null){ - showRandomItem(); + randomizeCycleStart(); } else if (symbol != null) { this.item = symbol.resolve().get(0); } @@ -104,16 +108,53 @@ public class SlotGuidebook extends Slot public void showRandomItem(){ if(this.symbol != null && this.symbol.getItemGroup() != null){ - Random r = new Random(); - List list = this.symbol.resolve(); - ItemStack newItem = list.get(r.nextInt(list.size())); - if (list.size() > 1) { - while (newItem == this.item){ - newItem = list.get(r.nextInt(list.size())); - } + List list = this.searchFilteredItems != null ? this.searchFilteredItems : this.symbol.resolve(); + if (list.isEmpty()) return; + this.cycleIndex = (this.cycleIndex + 1) % list.size(); + this.item = list.get(this.cycleIndex); + } + } + + public void randomizeCycleStart(){ + if(this.symbol != null && this.symbol.getItemGroup() != null){ + List list = this.searchFilteredItems != null ? this.searchFilteredItems : this.symbol.resolve(); + if (!list.isEmpty()){ + this.cycleIndex = new Random().nextInt(list.size()); + this.item = list.get(this.cycleIndex); + } + } + } + + public void applySearchFilter(@Nullable SearchQuery query){ + if(query == null || this.symbol == null || this.symbol.getItemGroup() == null) return; + List all = this.symbol.resolve(); + if(all == null || all.size() < 2) return; + List matches = new ArrayList<>(); + for(ItemStack stack : all){ + if(itemMatchesQuery(stack, query)) matches.add(stack); + } + if(!matches.isEmpty() && matches.size() < all.size()){ + this.searchFilteredItems = matches; + this.item = matches.get(0); + } + } + + private static boolean itemMatchesQuery(ItemStack stack, SearchQuery query){ + if(query.query.getLeft() == SearchQuery.QueryType.NAME){ + String term = query.query.getRight(); + if(term == null || term.isEmpty()) return false; + return query.strict + ? stack.getDisplayName().equalsIgnoreCase(term) + : stack.getDisplayName().toLowerCase().contains(term.toLowerCase()); + } else if(query.query.getLeft() == SearchQuery.QueryType.GROUP && query.query.getRight() != null && !query.query.getRight().isEmpty()){ + try { + List groupStacks = new RecipeSymbol(query.query.getRight()).resolve(); + return groupStacks != null && groupStacks.contains(stack); + } catch (RuntimeException e){ + return false; } - this.item = newItem; } + return false; } public boolean getIsDiscovered(Player player){ if (this.item == null || this.discovered) return true; diff --git a/game/client/src/main/java/net/minecraft/client/gui/guidebook/crafting/GuidebookSectionCrafting.java b/game/client/src/main/java/net/minecraft/client/gui/guidebook/crafting/GuidebookSectionCrafting.java index dc3d3fd41..3ad07e202 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/guidebook/crafting/GuidebookSectionCrafting.java +++ b/game/client/src/main/java/net/minecraft/client/gui/guidebook/crafting/GuidebookSectionCrafting.java @@ -2,20 +2,26 @@ package net.minecraft.client.gui.guidebook.crafting; import net.minecraft.client.gui.guidebook.GuidebookPage; import net.minecraft.client.gui.guidebook.SearchableGuidebookSection; -import net.minecraft.core.data.registry.recipe.SearchQuery; import net.minecraft.core.block.Blocks; +import net.minecraft.core.data.registry.recipe.SearchQuery; import net.minecraft.core.data.registry.recipe.entry.RecipeEntryCrafting; import net.minecraft.core.data.registry.Registries; import net.minecraft.core.item.ItemStack; import net.minecraft.core.util.collection.Pair; import net.minecraft.core.util.helper.MathHelper; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; public class GuidebookSectionCrafting extends SearchableGuidebookSection { + private final List pages = new ArrayList<>(); private Pair> filteredPages = null; @@ -26,8 +32,7 @@ public class GuidebookSectionCrafting public void reloadRecipes(){ pages.clear(); - List> allRecipes = new ArrayList<>(Registries.RECIPES.getAllCraftingRecipes()); - allRecipes.removeIf((R)->(!RecipePageCrafting.guidebookSupportsRecipe(R))); + List> allRecipes = getSupportedRecipes(); int totalRecipes = allRecipes.size(); int totalPages = MathHelper.ceilInt(totalRecipes, RecipePageCrafting.RECIPES_PER_PAGE); for (int i = 0; i < totalPages; i++) { @@ -51,10 +56,14 @@ public class GuidebookSectionCrafting public List searchPages(SearchQuery query) { if(filteredPages == null || !Objects.equals(filteredPages.getLeft(), query.rawQuery)) { ArrayList> filteredRecipes = new ArrayList<>(); - List> allRecipes = new ArrayList<>(Registries.RECIPES.getAllCraftingRecipes()); - allRecipes.removeIf((R)->(!RecipePageCrafting.guidebookSupportsRecipe(R))); - for (RecipeEntryCrafting recipe : allRecipes) { - if(recipe.matchesQueryIgnoreExceptions(query)){ + for (RecipeEntryCrafting recipe : getSupportedRecipes()) { + if (recipe instanceof RecipeEntryCraftingGroup group) { + // Lock a consolidated entry to the variants that actually match the query. + RecipeEntryCraftingGroup narrowed = group.filterToQuery(query); + if (narrowed != null) { + filteredRecipes.add(narrowed); + } + } else if (recipe.matchesQueryIgnoreExceptions(query)) { filteredRecipes.add(recipe); } } @@ -65,7 +74,7 @@ public class GuidebookSectionCrafting int j = i * RecipePageCrafting.RECIPES_PER_PAGE; ArrayList> recipes = new ArrayList<>(filteredRecipes.subList(Math.min(j, filteredRecipeSize), Math.min(j + RecipePageCrafting.RECIPES_PER_PAGE, filteredRecipeSize))); if (!recipes.isEmpty()) { - filteredPages.add(new RecipePageCrafting(this, recipes)); + filteredPages.add(new RecipePageCrafting(this, recipes, query)); } } this.filteredPages = Pair.of(query.rawQuery, filteredPages); @@ -74,4 +83,33 @@ public class GuidebookSectionCrafting return filteredPages.getRight(); } } + + private static @NotNull List> getSupportedRecipes() { + List> allRecipes = new ArrayList<>(Registries.RECIPES.getAllCraftingRecipes()); + allRecipes.removeIf((R) -> (!RecipePageCrafting.guidebookSupportsRecipe(R))); + return consolidateGroups(allRecipes); + } + + private static @NotNull List> consolidateGroups(@NotNull List> recipes) { + Map>> groups = new LinkedHashMap<>(); + for (RecipeEntryCrafting recipe : recipes) { + if (recipe.consolidationGroup != null) { + groups.computeIfAbsent(recipe.consolidationGroup, (k) -> new ArrayList<>()).add(recipe); + } + } + + List> result = new ArrayList<>(); + Set emitted = new HashSet<>(); + for (RecipeEntryCrafting recipe : recipes) { + String group = recipe.consolidationGroup; + if (group != null && groups.get(group).size() >= 2) { + if (emitted.add(group)) { + result.add(new RecipeEntryCraftingGroup(groups.get(group))); + } + continue; + } + result.add(recipe); + } + return result; + } } diff --git a/game/client/src/main/java/net/minecraft/client/gui/guidebook/crafting/RecipeEntryCraftingGroup.java b/game/client/src/main/java/net/minecraft/client/gui/guidebook/crafting/RecipeEntryCraftingGroup.java new file mode 100644 index 000000000..07b82e2ee --- /dev/null +++ b/game/client/src/main/java/net/minecraft/client/gui/guidebook/crafting/RecipeEntryCraftingGroup.java @@ -0,0 +1,57 @@ +package net.minecraft.client.gui.guidebook.crafting; + +import net.minecraft.core.data.registry.recipe.SearchQuery; +import net.minecraft.core.data.registry.recipe.entry.RecipeEntryCrafting; +import net.minecraft.core.item.ItemStack; +import net.minecraft.core.player.inventory.container.ContainerCrafting; + +import java.util.ArrayList; +import java.util.List; + +public class RecipeEntryCraftingGroup extends RecipeEntryCrafting { + public final RecipeEntryCrafting representative; + public final List> variants; + + public RecipeEntryCraftingGroup(List> variants) { + this.variants = variants; + this.representative = variants.get(0); + this.parent = representative.parent; + } + + @Override + public boolean matches(ContainerCrafting containerCrafting) { + return false; + } + + @Override + public boolean matchesQuery(SearchQuery query) { + for (RecipeEntryCrafting variant : variants) { + if (variant.matchesQueryIgnoreExceptions(query)) { + return true; + } + } + return false; + } + + public RecipeEntryCraftingGroup filterToQuery(SearchQuery query) { + List> matching = new ArrayList<>(); + for (RecipeEntryCrafting variant : variants) { + if (variant.matchesQueryIgnoreExceptions(query)) { + matching.add(variant); + } + } + if (matching.isEmpty()) return null; + if (matching.size() == variants.size()) return this; + return new RecipeEntryCraftingGroup(matching); + } + + @Override + public ItemStack getCraftingResult(ContainerCrafting containerCrafting) { + return representative.getCraftingResult(containerCrafting); + } + + @Override + public int getRecipeSize() { + return representative.getRecipeSize(); + } +} diff --git a/game/client/src/main/java/net/minecraft/client/gui/guidebook/crafting/RecipePageCrafting.java b/game/client/src/main/java/net/minecraft/client/gui/guidebook/crafting/RecipePageCrafting.java index e0b69fe01..37421a4a6 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/guidebook/crafting/RecipePageCrafting.java +++ b/game/client/src/main/java/net/minecraft/client/gui/guidebook/crafting/RecipePageCrafting.java @@ -8,6 +8,7 @@ import net.minecraft.client.gui.guidebook.GuidebookSection; import net.minecraft.client.gui.guidebook.GuidebookSections; import net.minecraft.client.gui.guidebook.GuidebookPageManager; import net.minecraft.client.gui.guidebook.RecipePage; +import net.minecraft.client.gui.guidebook.crafting.displays.DisplayAdapterCraftingGroup; import net.minecraft.client.gui.guidebook.crafting.displays.DisplayAdapterDyeing; import net.minecraft.client.gui.guidebook.crafting.displays.DisplayAdapterLabel; import net.minecraft.client.gui.guidebook.crafting.displays.DisplayAdapterLabelDye; @@ -67,6 +68,7 @@ public class RecipePageCrafting recipeToDisplayAdapterMap.put(RecipeEntryMapDuplication.class, new DisplayAdapterMapDuplication()); recipeToDisplayAdapterMap.put(RecipeEntryUndyeing.class, new DisplayAdapterUndye()); recipeToDisplayAdapterMap.put(RecipeEntryDyeing.class, new DisplayAdapterDyeing()); + recipeToDisplayAdapterMap.put(RecipeEntryCraftingGroup.class, new DisplayAdapterCraftingGroup()); } private final TooltipElement tooltipElement; @@ -74,7 +76,7 @@ public class RecipePageCrafting private static final Minecraft mc = Minecraft.getMinecraft(); private static long ticks = 0; - public RecipePageCrafting(GuidebookSection section, List> recipes) { + public RecipePageCrafting(GuidebookSection section, List> recipes, SearchQuery searchQuery) { super(section); this.recipes = recipes; this.slots = new ArrayList<>(); @@ -88,6 +90,11 @@ public class RecipePageCrafting if (recipeToDisplayAdapterMap.containsKey(recipe.getClass())) { RecipeDisplayAdapter adapter = recipeToDisplayAdapterMap.get(recipe.getClass()); List recipeSlots = adapter.getSlots(recipe, recipeAmount, xOffset, yOffset); + if (searchQuery != null) { + for (SlotGuidebook slot : recipeSlots) { + slot.applySearchFilter(searchQuery); + } + } this.map.put(recipe, recipeSlots); this.slots.addAll(recipeSlots); yOffset += 10; @@ -97,6 +104,10 @@ public class RecipePageCrafting } + public RecipePageCrafting(GuidebookSection section, List> recipes) { + this(section, recipes, null); + } + @Override public void onTick() { ticks++; @@ -110,6 +121,13 @@ public class RecipePageCrafting } } + @Override + public void onBecomeVisible() { + for (SlotGuidebook slot : this.slots) { + slot.randomizeCycleStart(); + } + } + @Override protected void renderForeground(TextureManager re, FontRenderer sr, int x, int y, int mouseX, int mouseY, float partialTicks) { if (this.recipes.isEmpty()) { diff --git a/game/client/src/main/java/net/minecraft/client/gui/guidebook/crafting/displays/DisplayAdapterCraftingGroup.java b/game/client/src/main/java/net/minecraft/client/gui/guidebook/crafting/displays/DisplayAdapterCraftingGroup.java new file mode 100644 index 000000000..d120be396 --- /dev/null +++ b/game/client/src/main/java/net/minecraft/client/gui/guidebook/crafting/displays/DisplayAdapterCraftingGroup.java @@ -0,0 +1,221 @@ +package net.minecraft.client.gui.guidebook.crafting.displays; + +import net.minecraft.client.gui.guidebook.SlotGuidebook; +import net.minecraft.client.gui.guidebook.crafting.RecipeEntryCraftingGroup; +import net.minecraft.core.data.registry.recipe.RecipeEntryBase; +import net.minecraft.core.data.registry.recipe.RecipeSymbol; +import net.minecraft.core.data.registry.recipe.entry.RecipeEntryCrafting; +import net.minecraft.core.data.registry.recipe.entry.RecipeEntryCraftingShaped; +import net.minecraft.core.data.registry.recipe.entry.RecipeEntryCraftingShapeless; +import net.minecraft.core.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +public class DisplayAdapterCraftingGroup implements RecipeDisplayAdapter { + @Override + public List getSlots(@NotNull RecipeEntryCraftingGroup recipe, int recipeAmount, int xOffset, int yOffset) { + List> variants = recipe.variants; + + List> variantInputs = new ArrayList<>(); + List variantOutputs = new ArrayList<>(); + for (RecipeEntryCrafting variant : variants) { + variantInputs.add(inputSymbols(variant)); + variantOutputs.add(outputStack(variant)); + } + + GroupCursor cursor = new GroupCursor(variants.size()); + if (recipe.representative instanceof RecipeEntryCraftingShaped shaped) { + return buildGrid(shaped.recipeWidth, shaped.recipeHeight, variantInputs, variantOutputs, recipeAmount, xOffset, yOffset, cursor, recipe); + } + return buildShapeless(variantInputs, variantOutputs, recipeAmount, xOffset, yOffset, cursor, recipe); + } + + private static List inputSymbols(RecipeEntryCrafting variant) { + if (variant instanceof RecipeEntryCraftingShaped shaped) return Arrays.asList(shaped.getInput()); + if (variant instanceof RecipeEntryCraftingShapeless shapeless) return shapeless.getInput(); + return new ArrayList<>(); + } + + private static ItemStack outputStack(RecipeEntryCrafting variant) { + if (variant instanceof RecipeEntryCraftingShaped shaped) return shaped.getOutput(); + if (variant instanceof RecipeEntryCraftingShapeless shapeless) return shapeless.getOutput(); + return null; + } + + private static List buildGrid( + int width, int height, List> variantInputs, List variantOutputs, + int recipeAmount, int xOffset, int yOffset, GroupCursor cursor, RecipeEntryBase recipe) { + List recipeSlots = new ArrayList<>(); + boolean[] driverAssigned = {false}; + + if (height > 2 || width > 2) { + for (int i = 0; i < 9; i++) { + final int slotX = i % 3; + final int slotY = i / 3; + final int x = 1 + 18 * (i % 3) + xOffset; + final int y = 1 + 18 * ((i / 3) + recipeAmount * 3) + yOffset; + if (slotX >= width || slotY >= height) { + recipeSlots.add(new SlotGuidebook(i, x, y, null, false, recipe)); + } else { + recipeSlots.add(makeInputSlot(i, x, y, slotX + slotY * width, variantInputs, cursor, driverAssigned, recipe)); + } + } + int centerY = (recipeSlots.get(recipeSlots.size() - 1).y + recipeSlots.get(recipeSlots.size() - 8).y) / 2; + recipeSlots.add(makeOutputSlot(9, 81 + xOffset, centerY, variantOutputs, cursor, driverAssigned, recipe)); + } else { + for (int i = 0; i < 4; i++) { + final int slotX = i % 2; + final int slotY = i / 2; + final int x = 13 + 18 * (i % 2) + xOffset; + final int y = 10 + 18 * ((i / 2) + recipeAmount * 3) + yOffset; + if (slotX >= width || slotY >= height) { + recipeSlots.add(new SlotGuidebook(i, x, y, null, false, recipe)); + } else { + recipeSlots.add(makeInputSlot(i, x, y, slotX + slotY * width, variantInputs, cursor, driverAssigned, recipe)); + } + } + int centerY = (recipeSlots.get(recipeSlots.size() - 1).y + recipeSlots.get(recipeSlots.size() - 3).y) / 2; + recipeSlots.add(makeOutputSlot(4, 81 + xOffset, centerY, variantOutputs, cursor, driverAssigned, recipe)); + } + return recipeSlots; + } + + private static List buildShapeless(List> variantInputs, List variantOutputs, + int recipeAmount, int xOffset, int yOffset, GroupCursor cursor, RecipeEntryBase recipe) { + List recipeSlots = new ArrayList<>(); + boolean[] driverAssigned = {false}; + int inputCount = variantInputs.get(0).size(); + + if (inputCount > 4) { + for (int i = 0; i < 9; i++) { + final int x = 1 + 18 * (i % 3) + xOffset; + final int y = 1 + 18 * ((i / 3) + recipeAmount * 3) + yOffset; + if (i >= inputCount) { + recipeSlots.add(new SlotGuidebook(i, x, y, null, false, recipe)); + } else { + recipeSlots.add(makeInputSlot(i, x, y, i, variantInputs, cursor, driverAssigned, recipe)); + } + } + int centerY = (recipeSlots.get(recipeSlots.size() - 1).y + recipeSlots.get(recipeSlots.size() - 8).y) / 2; + recipeSlots.add(makeOutputSlot(9, 81 + xOffset, centerY, variantOutputs, cursor, driverAssigned, recipe)); + } else { + for (int i = 0; i < 4; i++) { + final int x = 13 + 18 * (i % 2) + xOffset; + final int y = 10 + 18 * ((i / 2) + recipeAmount * 3) + yOffset; + if (i >= inputCount) { + recipeSlots.add(new SlotGuidebook(i, x, y, null, false, recipe)); + } else { + recipeSlots.add(makeInputSlot(i, x, y, i, variantInputs, cursor, driverAssigned, recipe)); + } + } + int centerY = (recipeSlots.get(recipeSlots.size() - 1).y + recipeSlots.get(recipeSlots.size() - 3).y) / 2; + recipeSlots.add(makeOutputSlot(4, 81 + xOffset, centerY, variantOutputs, cursor, driverAssigned, recipe)); + } + return recipeSlots; + } + + private static @NotNull SlotGuidebook makeInputSlot(int id, int x, int y, int inputIndex, @NotNull List> variantInputs, + GroupCursor cursor, boolean[] driverAssigned, RecipeEntryBase recipe) { + List symbols = new ArrayList<>(); + for (List inputs : variantInputs) { + symbols.add(inputs.get(inputIndex)); + } + boolean driver = !driverAssigned[0]; + if (driver) driverAssigned[0] = true; + return SlotGuidebookCraftingGroup.forInputs(id, x, y, symbols, cursor, driver, recipe); + } + + private static @NotNull SlotGuidebook makeOutputSlot(int id, int x, int y, @NotNull List variantOutputs, + GroupCursor cursor, boolean[] driverAssigned, RecipeEntryBase recipe) { + List frames = new ArrayList<>(); + for (ItemStack output : variantOutputs) { + frames.add(output == null ? null : output.copy()); + } + boolean driver = !driverAssigned[0]; + if (driver) driverAssigned[0] = true; + return SlotGuidebookCraftingGroup.forOutputs(id, x, y, frames, cursor, driver, recipe).setAsOutput(); + } + + static final class GroupCursor { + private static final Random RANDOM = new Random(); + final int variantCount; + int main; + int nested; + + GroupCursor(int variantCount) { + this.variantCount = variantCount; + randomizeStart(); + } + + void advance() { + main = variantCount <= 0 ? 0 : (main + 1) % variantCount; + nested++; + } + + void randomizeStart() { + main = variantCount <= 0 ? 0 : RANDOM.nextInt(variantCount); + nested = RANDOM.nextInt(4096); + } + } + + public static class SlotGuidebookCraftingGroup extends SlotGuidebook { + private final List symbols; // input slots; null for output slots + private final List items; // output slots; null for input slots + private final GroupCursor cursor; + private final boolean driver; + + private SlotGuidebookCraftingGroup(int id, int x, int y, List symbols, List items, + GroupCursor cursor, boolean driver, RecipeEntryBase recipe) { + super(id, x, y, null, false, recipe); + this.symbols = symbols; + this.items = items; + this.cursor = cursor; + this.driver = driver; + resolve(); + } + + static SlotGuidebookCraftingGroup forInputs(int id, int x, int y, List symbols, GroupCursor cursor, boolean driver, RecipeEntryBase recipe) { + return new SlotGuidebookCraftingGroup(id, x, y, symbols, null, cursor, driver, recipe); + } + + static SlotGuidebookCraftingGroup forOutputs(int id, int x, int y, List items, GroupCursor cursor, boolean driver, RecipeEntryBase recipe) { + return new SlotGuidebookCraftingGroup(id, x, y, null, items, cursor, driver, recipe); + } + + @Override + public SlotGuidebookCraftingGroup setAsOutput() { + super.setAsOutput(); + return this; + } + + private void resolve() { + int index = cursor.variantCount <= 0 ? 0 : Math.min(cursor.main, cursor.variantCount - 1); + if (symbols != null) { + if (symbols.isEmpty()) { this.item = null; return; } + RecipeSymbol symbol = symbols.get(index); + if (symbol == null) { this.item = null; return; } + List members = symbol.resolve(); + if (members == null || members.isEmpty()) { this.item = null; return; } + this.item = members.get(Math.floorMod(cursor.nested, members.size())); + } else if (items != null) { + this.item = items.isEmpty() ? null : items.get(index); + } + } + + @Override + public void showRandomItem() { + if (driver) cursor.advance(); + resolve(); + } + + @Override + public void randomizeCycleStart() { + if (driver) cursor.randomizeStart(); + resolve(); + } + } +} diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentChat.java b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentChat.java index 665fac4a2..db08a3f14 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentChat.java +++ b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentChat.java @@ -3,6 +3,7 @@ package net.minecraft.client.gui.hud.component; import net.minecraft.client.gui.Gui; import net.minecraft.client.gui.hud.HudIngame; import net.minecraft.client.gui.hud.component.layout.Layout; +import net.minecraft.client.gui.options.components.BooleanToggleComponent; import net.minecraft.client.gui.options.components.FloatSliderComponent; import net.minecraft.client.option.GameSettings; import net.minecraft.core.enums.ChatVisibility; @@ -15,8 +16,10 @@ import net.minecraft.client.gui.chat.ScreenChat; import net.minecraft.core.util.helper.MathHelper; import org.jetbrains.annotations.NotNull; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.Deque; import java.util.List; public class HudComponentChat extends HudComponentMovable { @@ -28,11 +31,22 @@ public class HudComponentChat extends HudComponentMovable { public static final int MAX_RESIZE_HEIGHT = 1000; private static final int CHAT_PADDING = 2; private static final int LINE_SPACING = 2; + private static final int LINE_HEIGHT = 9; + private static final int LINE_STEP = LINE_HEIGHT + LINE_SPACING; public final @NotNull List<@NotNull ChatLine> chatMessageList = new ArrayList<>(); public int chatScroll = 0; + /** Newest first, one entry per visual line */ + private final @NotNull List<@NotNull VisualLine> visualLines = new ArrayList<>(); + private boolean visualLinesDirty = true; + private int visualLinesWrapWidth = -1; + private Object visualLinesFont = null; + + private static final boolean DEFAULT_FULL_WIDTH_BACKDROP = false; + private int customWidth; private int customHeight; + private boolean fullWidthBackdrop = DEFAULT_FULL_WIDTH_BACKDROP; public HudComponentChat(String key, int xSize, int ySize, Layout layout) { super(key, xSize, ySize, layout); @@ -60,6 +74,13 @@ public class HudComponentChat extends HudComponentMovable { value -> this.customHeight = Math.round(value), value -> Math.round(value) + "px" )); + + addOptionComponentSupplier(() -> new BooleanToggleComponent( + "gui.options.hudeditor.chat_full_width_backdrop", + DEFAULT_FULL_WIDTH_BACKDROP, + () -> this.fullWidthBackdrop, + value -> this.fullWidthBackdrop = value + )); } @Override @@ -74,7 +95,7 @@ public class HudComponentChat extends HudComponentMovable { @Override protected String toSettingsParametersString() { - return "w=" + this.customWidth + ",h=" + this.customHeight; + return "w=" + this.customWidth + ",h=" + this.customHeight + ",backdrop=" + (this.fullWidthBackdrop ? 1 : 0); } @Override @@ -91,6 +112,8 @@ public class HudComponentChat extends HudComponentMovable { this.customWidth = MathHelper.clamp(parsed, MIN_CHAT_WIDTH, MAX_RESIZE_WIDTH); } else if (key.equals("h")) { this.customHeight = MathHelper.clamp(parsed, MIN_CHAT_HEIGHT, MAX_RESIZE_HEIGHT); + } else if (key.equals("backdrop")) { + this.fullWidthBackdrop = parsed != 0; } } catch (NumberFormatException ignored) {} } @@ -156,6 +179,7 @@ public class HudComponentChat extends HudComponentMovable { public void clearChatMessages() { this.chatMessageList.clear(); + this.visualLinesDirty = true; } public void addChatMessage(final CharSequence s) { @@ -163,121 +187,195 @@ public class HudComponentChat extends HudComponentMovable { while (this.chatMessageList.size() > 100) { this.chatMessageList.remove(this.chatMessageList.size() - 1); } + this.visualLinesDirty = true; } private int getMessageWrapWidth() { return Math.max(1, this.customWidth - (CHAT_PADDING * 2)); } + /** + * A single wrapped line of a chat message. Multi line messages produce several of these, + * all sharing the {@link ChatLine} they came from so they fade out together. + */ + private static class VisualLine { + final @NotNull ChatLine source; + final @NotNull String text; + + VisualLine(@NotNull ChatLine source, @NotNull String text) { + this.source = source; + this.text = text; + } + } + + /** + * Returns every chat message split into wrapped lines, newest line first. The result is cached + * until the message list, the wrap width or the font changes. + */ + private @NotNull List<@NotNull VisualLine> getVisualLines(int wrapWidth) { + Object font = mc.font.getFont(); + if (!this.visualLinesDirty && this.visualLinesWrapWidth == wrapWidth && this.visualLinesFont == font) { + return this.visualLines; + } + + this.visualLines.clear(); + List split = new ArrayList<>(); + for (ChatLine line : this.chatMessageList) { + + mc.font.splitCharsIntoLines(line.message, wrapWidth, split); + carryFormattingBetweenLines(split); + + for (int i = split.size() - 1; i >= 0; i--) { + this.visualLines.add(new VisualLine(line, split.get(i))); + } + + } + + this.visualLinesDirty = false; + this.visualLinesWrapWidth = wrapWidth; + this.visualLinesFont = font; + return this.visualLines; + } + + /** + * Re-apply codes that were still active at the end of the previous line to the start of the next one. + */ + private static void carryFormattingBetweenLines(@NotNull List lines) { + StringBuilder active = new StringBuilder(); + Deque pushed = new ArrayDeque<>(); + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + if (i > 0 && !active.isEmpty()) { + lines.set(i, active + line); + } + for (int j = 0; j < line.length() - 1; j++) { + if (line.charAt(j) != '§') continue; + if (line.charAt(j + 1) == '<') { + int end = line.indexOf('>', j + 2); + if (end == -1) continue; + active.setLength(0); + active.append(line, j, end + 1); + j = end; + continue; + } + + char code = Character.toLowerCase(line.charAt(j + 1)); + j++; + if (code == '+') { + pushed.push(active.toString()); + } else if (code == '-') { + active.setLength(0); + if (!pushed.isEmpty()) active.append(pushed.pop()); + } else if (code == 'r') { + active.setLength(0); + } else { + // A color code clears every style before it, so drop what we have. + if ((code >= '0' && code <= '9') || (code >= 'a' && code <= 'f')) { + active.setLength(0); + } + active.append('§').append(code); + } + } + } + } + private void drawChat(HudIngame hud, int xSizeScreen, int ySizeScreen) { int x = this.getLayout().getComponentX(this, xSizeScreen); int y = this.getLayout().getComponentY(this, ySizeScreen); int wrapWidth = getMessageWrapWidth(); - int singleLineHeight = 9 + LINE_SPACING; - int linesToShow = Math.max(1, (this.customHeight - CHAT_PADDING * 2) / singleLineHeight); + int linesToShow = Math.max(1, (this.customHeight - CHAT_PADDING * 2) / LINE_STEP); boolean ignoreFadeout = false; if (mc.currentScreen instanceof ScreenChat) { int acceptableSize = ySizeScreen - 48 - 48; - linesToShow = Math.max(linesToShow, acceptableSize / singleLineHeight); + linesToShow = Math.max(linesToShow, acceptableSize / LINE_STEP); ignoreFadeout = true; } - if (this.chatScroll > this.chatMessageList.size() - linesToShow) - this.chatScroll = this.chatMessageList.size() - linesToShow; - if (this.chatScroll < 0) this.chatScroll = 0; + List lines = getVisualLines(wrapWidth); + + this.chatScroll = MathHelper.clamp(this.chatScroll, 0, Math.max(0, lines.size() - linesToShow)); GLRenderer.enableState(State.BLEND); GLRenderer.setBlendFunc(BlendFactor.SRC_ALPHA, BlendFactor.ONE_MINUS_SRC_ALPHA); - int linesUsed = Math.min(linesToShow, this.chatMessageList.size() - this.chatScroll); - int totalHeight = computeTotalHeight(this.chatMessageList, this.chatScroll, linesUsed, ignoreFadeout, wrapWidth); + int linesUsed = Math.min(linesToShow, lines.size() - this.chatScroll); + int totalHeight = 0; + for (int i = 0; i < linesUsed; i++) { + if (getLineAlpha(lines.get(this.chatScroll + i).source, ignoreFadeout) > 0) totalHeight += LINE_STEP; + } float yMult = GameSettings.VERTICAL_CHAT_TEXT_ALIGNMENT.value.multiplier; int alignmentSpace = this.getBaseYSize() - (CHAT_PADDING * 2) - totalHeight; int currentY = y + this.getBaseYSize() - CHAT_PADDING - (int) (alignmentSpace * (1 - yMult)); - for (int i = 0; i < linesToShow; i++) { - if (this.chatScroll + i >= this.chatMessageList.size()) continue; - final ChatLine line = this.chatMessageList.get(this.chatScroll + i); - if (line.updateCounter >= 190 && !ignoreFadeout) { - continue; - } - double fadeAmount = (double) line.updateCounter / 200D; - fadeAmount = 1.0D - fadeAmount; - fadeAmount *= 10D; - if (fadeAmount < 0.0D) { - fadeAmount = 0.0D; - } - if (fadeAmount > 1.0D) { - fadeAmount = 1.0D; - } - fadeAmount *= fadeAmount; - int alpha = (int) (255D * fadeAmount); - if (ignoreFadeout) { - alpha = 255; - } + for (int i = 0; i < linesUsed; i++) { + final VisualLine line = lines.get(this.chatScroll + i); + int alpha = getLineAlpha(line.source, ignoreFadeout); if (alpha > 0) { - final CharSequence message = line.message; - final int h = MathHelper.ceil(mc.font.heightOfConstrainedChars(message, wrapWidth)); - currentY -= (h + LINE_SPACING); - drawAlignedLine(hud, mc.font, message, x, currentY, h, alpha, wrapWidth); + currentY -= LINE_STEP; + drawAlignedLine(hud, mc.font, line.text, x, currentY, alpha, wrapWidth); } } GLRenderer.disableState(State.BLEND); } + private static int getLineAlpha(@NotNull ChatLine line, boolean ignoreFadeout) { + if (ignoreFadeout) return 255; + if (line.updateCounter >= 190) return 0; + double fadeAmount = 1.0D - (double) line.updateCounter / 200D; + fadeAmount *= 10D; + if (fadeAmount < 0.0D) { + fadeAmount = 0.0D; + } + if (fadeAmount > 1.0D) { + fadeAmount = 1.0D; + } + fadeAmount *= fadeAmount; + return (int) (255D * fadeAmount); + } + private void drawLines(Gui gui, FontRenderer sr, @NotNull List messages, int x, int y, int alpha) { GLRenderer.enableState(State.BLEND); GLRenderer.setBlendFunc(BlendFactor.SRC_ALPHA, BlendFactor.ONE_MINUS_SRC_ALPHA); int wrapWidth = getMessageWrapWidth(); - int maxContentHeight = Math.max(1, this.getBaseYSize() - (CHAT_PADDING * 2)); - List visibleMessages = new ArrayList<>(); - int totalHeight = 0; + int maxLines = Math.max(1, (this.getBaseYSize() - (CHAT_PADDING * 2)) / LINE_STEP); + + List visibleLines = new ArrayList<>(); + List split = new ArrayList<>(); for (String message : messages) { - int lineHeight = MathHelper.ceil(sr.heightOfConstrainedChars(message, wrapWidth)) + LINE_SPACING; - if (totalHeight + lineHeight > maxContentHeight) { - break; + if (visibleLines.size() >= maxLines) break; + sr.splitCharsIntoLines(message, wrapWidth, split); + carryFormattingBetweenLines(split); + for (int i = split.size() - 1; i >= 0 && visibleLines.size() < maxLines; i--) { + visibleLines.add(split.get(i)); } - visibleMessages.add(message); - totalHeight += lineHeight; } float yMult = GameSettings.VERTICAL_CHAT_TEXT_ALIGNMENT.value.multiplier; + int totalHeight = visibleLines.size() * LINE_STEP; int alignmentSpace = this.getBaseYSize() - (CHAT_PADDING * 2) - totalHeight; int currentY = y + this.getBaseYSize() - CHAT_PADDING - (int) (alignmentSpace * (1 - yMult)); - for (String message : visibleMessages) { - final int h = MathHelper.ceil(sr.heightOfConstrainedChars(message, wrapWidth)); - currentY -= (h + LINE_SPACING); - drawAlignedLine(gui, sr, message, x, currentY, h, alpha, wrapWidth); + for (String line : visibleLines) { + currentY -= LINE_STEP; + drawAlignedLine(gui, sr, line, x, currentY, alpha, wrapWidth); } GLRenderer.disableState(State.BLEND); } - private void drawAlignedLine(@NotNull Gui gui, @NotNull FontRenderer sr, CharSequence message, int x, int currentY, int h, int alpha, int wrapWidth) { + /** Draws one already wrapped line, honoring the horizontal alignment option. */ + private void drawAlignedLine(@NotNull Gui gui, @NotNull FontRenderer sr, CharSequence line, int x, int currentY, int alpha, int wrapWidth) { float xMult = GameSettings.HORIZONTAL_CHAT_TEXT_ALIGNMENT.value.multiplier; - int textWidth = Math.min(wrapWidth, (int) sr.stringWidthDouble(message)); + int textWidth = Math.min(wrapWidth, MathHelper.ceil(sr.stringWidthDouble(line))); int boxWidth = this.getBaseXSize() - (CHAT_PADDING * 2); int renderX = x + CHAT_PADDING + (int) ((boxWidth - textWidth) * xMult); - int boxLeft = renderX - 1; - int boxRight = renderX + textWidth + 1; - gui.drawRect(boxLeft, currentY - 1, boxRight, currentY + h + 1, alpha / 2 << 24); + int backdropLeft = this.fullWidthBackdrop ? x : renderX - 1; + int backdropRight = this.fullWidthBackdrop ? x + this.getBaseXSize() : renderX + textWidth + 1; + gui.drawRect(backdropLeft, currentY - 1, backdropRight, currentY + LINE_HEIGHT + 1, alpha / 2 << 24); GLRenderer.enableState(State.BLEND); - sr.renderWidthConstrained(message, renderX, currentY, wrapWidth).setShadow().setColor(0xFFFFFF + (alpha << 24)).call(); - } - - private int computeTotalHeight(List messages, int scroll, int linesUsed, boolean ignoreFadeout, int wrapWidth) { - int total = 0; - for (int i = 0; i < linesUsed; i++) { - int idx = scroll + i; - if (idx >= messages.size()) continue; - ChatLine line = messages.get(idx); - if (line.updateCounter >= 190 && !ignoreFadeout) continue; - total += MathHelper.ceil(mc.font.heightOfConstrainedChars(line.message, wrapWidth)) + LINE_SPACING; - } - return total; + sr.render(line, renderX, currentY).setShadow().setColor(0xFFFFFF + (alpha << 24)).call(); } } diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentChatInput.java b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentChatInput.java index d3f78bb73..b01eba9cd 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentChatInput.java +++ b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentChatInput.java @@ -11,7 +11,12 @@ import net.minecraft.client.render.renderer.State; import org.jetbrains.annotations.NotNull; public class HudComponentChatInput extends HudComponentMovable { + public static final int LINE_HEIGHT = 10; + public static final int FIELD_X_OFFSET = 14; + private static final int FIELD_PADDING = 2; + private HudComponentChat linkedChat; + private int lineCount = 1; public HudComponentChatInput(String key, int xSize, int ySize, Layout layout) { super(key, xSize, ySize, layout); @@ -21,11 +26,32 @@ public class HudComponentChatInput extends HudComponentMovable { this.linkedChat = chat; } + public void setLineCount(int lineCount) { + this.lineCount = Math.max(1, lineCount); + } + + public int getLineCount() { + return this.lineCount; + } + + public int getTextWrapWidth() { + return Math.max(1, getBaseXSize() - FIELD_X_OFFSET - FIELD_PADDING * 2); + } + + public int getBoxHeight() { + return FIELD_PADDING + this.lineCount * LINE_HEIGHT; + } + @Override public int getBaseXSize() { return this.linkedChat != null ? this.linkedChat.getBaseXSize() : super.getBaseXSize(); } + @Override + public int getBaseYSize() { + return super.getBaseYSize() + (this.lineCount - 1) * LINE_HEIGHT; + } + @Override public OptionGuiScaleOverride getGuiScaleOverrideOption() { return this.linkedChat != null ? this.linkedChat.getGuiScaleOverrideOption() : super.getGuiScaleOverrideOption(); diff --git a/game/client/src/main/java/net/minecraft/client/render/PostProcessingManager.java b/game/client/src/main/java/net/minecraft/client/render/PostProcessingManager.java index 9e56daab6..8e5b5df1c 100644 --- a/game/client/src/main/java/net/minecraft/client/render/PostProcessingManager.java +++ b/game/client/src/main/java/net/minecraft/client/render/PostProcessingManager.java @@ -95,13 +95,13 @@ public class PostProcessingManager { this.lastHumidity = humidity; // Perform base temperature change - if (this.mc.currentWorld.getWorldType().hasTag(WorldTypeTags.OVERWORLD)) { + if (ccValue != 0 && this.mc.currentWorld.getWorldType().hasTag(WorldTypeTags.OVERWORLD)) { this.lastTemperature = temperature; final double tt = (temperature * 2) - 1.0; // Ranges from -1.0 to 1.0 this.current.saturation += (float) (0.25 * tt * surfaceness); this.current.contrast += (float) (-0.1 * tt * surfaceness); } - if (this.mc.currentWorld.getWorldType().hasTag(WorldTypeTags.HOT)) { + if (ccValue != 0 && this.mc.currentWorld.getWorldType().hasTag(WorldTypeTags.HOT)) { this.lastTemperature = temperature; if (temperature < 0.5) { final double tt = temperature * 2; // Ranges from 0.0 to 1.0 diff --git a/game/client/src/main/java/net/minecraft/client/render/RenderGlobal.java b/game/client/src/main/java/net/minecraft/client/render/RenderGlobal.java index 3fe26f5b6..d7db4cc43 100644 --- a/game/client/src/main/java/net/minecraft/client/render/RenderGlobal.java +++ b/game/client/src/main/java/net/minecraft/client/render/RenderGlobal.java @@ -16,6 +16,8 @@ import net.minecraft.client.render.culling.CameraFrustum; import net.minecraft.client.render.particle.Particle; import net.minecraft.client.render.particle.ParticleDispatcher; import net.minecraft.client.render.particle.ParticleEntry; +import net.minecraft.client.render.particle.ParticleSiphon; +import net.minecraft.core.block.material.Materials; import net.minecraft.client.render.renderer.BlendFactor; import net.minecraft.client.render.renderer.DrawMode; import net.minecraft.client.render.renderer.GLRenderer; @@ -230,6 +232,7 @@ public final class RenderGlobal world.addListener(this); AURORA_PROVIDER.init(world); this.allChanged(); + this.mc.terrainRenderer.resetChunkSorting(); } public void allChanged() { @@ -923,159 +926,194 @@ public final class RenderGlobal GLRenderer.popFrame(); } + private @Nullable RenderBuffer cloudBuffer; + private int cachedRadius = -1; + public void renderCloudsFancy(float partialTick) { GLRenderer.pushFrame(); GLRenderer.setLightmapCoord2i(15, 15); + assert this.mc.currentWorld != null; WorldTypeFX worldTypeFX = WorldTypeFXDispatcher.getInstance().getDispatch(this.mc.currentWorld.getWorldType()); this.mc.renderer.beginRenderClouds(partialTick); - - float celestialAngle = this.world.getCelestialAngle(partialTick); + float celestialAngle = world.getCelestialAngle(partialTick); float[] sunriseColor = worldTypeFX.getSunriseColor(celestialAngle, partialTick); - float rSun = 0.0f, gSun = 0.0f, bSun = 0.0f, aSun = 0.0f; + float rSun = 0.0F; + float gSun = 0.0F; + float bSun = 0.0F; + float aSun = 0.0F; if (sunriseColor != null) { rSun = sunriseColor[0]; gSun = sunriseColor[1]; bSun = sunriseColor[2]; - aSun = sunriseColor[3] / 2; + aSun = sunriseColor[3] / 2.0F; } - Vector3fc dimensionColor = this.world.getDimensionColor(this.mc.activeCamera, partialTick); - final float rDim = dimensionColor.x(); - final float gDim = dimensionColor.y(); - final float bDim = dimensionColor.z(); + Vector3fc dimensionColor = world.getDimensionColor(this.mc.activeCamera, partialTick); + float rDim = dimensionColor.x(); + float gDim = dimensionColor.y(); + float bDim = dimensionColor.z(); GLRenderer.disableState(State.CULL_FACE); - float cameraY = (float) this.mc.activeCamera.getY(partialTick); - TessellatorShader tessellator = GLRenderer.getTessellator(); - float ss = 12F; - float h = 4F; - double xo = (this.mc.activeCamera.getX(partialTick) + (double) ((this.lastCloudOffsetX + (this.cloudOffsetX - this.lastCloudOffsetX) * partialTick) * 0.03F)) / (double) ss; - double zo = (this.mc.activeCamera.getZ(partialTick) + (double) ((this.lastCloudOffsetZ + (this.cloudOffsetZ - this.lastCloudOffsetZ) * partialTick) * 0.03F)) / (double) ss + 0.33D; - float yy = (worldTypeFX.getCloudHeight(this.world) - cameraY) + 0.33F + getCloudHeightModifier(); - int xOffs = MathHelper.floor(xo / 2048D); - int zOffs = MathHelper.floor(zo / 2048D); + + float cameraY = (float)this.mc.activeCamera.getY(partialTick); + + float modelScale = 12.0F; + float deltaHeight = 4.0F; + ICamera camera = mc.activeCamera; + + double xo = (camera.getX(partialTick) + (lastCloudOffsetX + (cloudOffsetX - lastCloudOffsetX) * partialTick) * 0.03F) / modelScale; + double zo = (camera.getZ(partialTick) + (lastCloudOffsetZ + (cloudOffsetZ - lastCloudOffsetZ) * partialTick) * 0.03F) / modelScale + 0.33; + float cloudMinY = worldTypeFX.getCloudHeight(this.world) - cameraY + 0.33F + getCloudHeightModifier(); + int xOffs = MathHelper.floor(xo / 2048.0); + int zOffs = MathHelper.floor(zo / 2048.0); xo -= xOffs * 2048; zo -= zOffs * 2048; - boolean noBFCMode = ( (yy > -h - 1) && (yy <= h + 1) ); - if( noBFCMode ) { + boolean noBFCMode = cloudMinY > -deltaHeight - 1.0F && cloudMinY <= deltaHeight + 1.0F; + + if (noBFCMode) { GLRenderer.disableState(State.CULL_FACE); - } - else { + } else { GLRenderer.enableState(State.CULL_FACE); } this.textureManager.loadTexture("/assets/minecraft/textures/environment/clouds.png").bind(); GLRenderer.enableState(State.BLEND); GLRenderer.setBlendFunc(BlendFactor.SRC_ALPHA, BlendFactor.ONE_MINUS_SRC_ALPHA); - float scale = 1F / 256F; - float uo = (float) MathHelper.floor(xo) * scale; - float vo = (float) MathHelper.floor(zo) * scale; - float xoffs2 = (float) (xo - (double) MathHelper.floor(xo)); - float zoffs2 = (float) (zo - (double) MathHelper.floor(zo)); - final int D = 8; - final byte radius = (byte) Math.max(1, 3 * GameSettings.CLOUD_RENDER_DISTANCE.value); - float e = 1F / 1024F; - GLRenderer.modelM4f().scale(ss, 1.0F, ss); + float scale = 0.00390625F; + + float xOffset = (float)xo; + float zOffset = (float)zo; + byte radius = (byte)Math.max(1.0F, 3.0F * GameSettings.CLOUD_RENDER_DISTANCE.value); float red = rDim + (rSun - rDim) * aSun; float green = gDim + (gSun - gDim) * aSun; float blue = bDim + (bSun - bDim) * aSun; - tessellator.startDrawingQuads(); - { - float width = (radius * 2 + 2) * D; - float xx = (-radius - 1) * D; - float zz = (-radius - 1) * D; - float xp = xx - xoffs2; - float zp = zz - zoffs2; - if (yy > -h - 1.0F) { - tessellator.setColor4f(red * 0.7F, green * 0.7F, blue * 0.7F, 0.8F); - tessellator.setNormal(0.0F, -1F, 0.0F); - tessellator.addVertexWithUV(xp + 0.0F, yy + 0.0F, zp + 0.0F, (xx + 0.0F) * scale + uo, (zz + 0.0F) * scale + vo); - tessellator.addVertexWithUV(xp + width, yy + 0.0F, zp + 0.0F, (xx + width) * scale + uo, (zz + 0.0F) * scale + vo); - tessellator.addVertexWithUV(xp + width, yy + 0.0F, zp + width, (xx + width) * scale + uo, (zz + width) * scale + vo); - tessellator.addVertexWithUV(xp + 0.0F, yy + 0.0F, zp + width, (xx + 0.0F) * scale + uo, (zz + width) * scale + vo); - } - if (yy <= h + 1.0F) { - tessellator.setColor4f(red, green, blue, 0.8F); - tessellator.setNormal(0.0F, 1.0F, 0.0F); - tessellator.addVertexWithUV(xp + 0.0F, (yy + h) - e, zp + 0.0F, (xx + 0.0F) * scale + uo, (zz + 0.0F) * scale + vo); - tessellator.addVertexWithUV(xp + 0.0F, (yy + h) - e, zp + width, (xx + 0.0F) * scale + uo, (zz + width) * scale + vo); - tessellator.addVertexWithUV(xp + width, (yy + h) - e, zp + width, (xx + width) * scale + uo, (zz + width) * scale + vo); - tessellator.addVertexWithUV(xp + width, (yy + h) - e, zp + 0.0F, (xx + width) * scale + uo, (zz + 0.0F) * scale + vo); + boolean needsRebuild = this.cloudBuffer == null || radius != cachedRadius; + if (needsRebuild) { + if (cloudBuffer != null) { + cloudBuffer.delete(); } + cloudBuffer = meshFancyClouds( + deltaHeight, + scale, + radius + ); + cachedRadius = radius; } - for (int ix = -radius - 1; ix <= radius + 1; ix++) { - for (int iz = -radius - 1; iz <= radius + 1; iz++) { - float xx = ix * D; - float zz = iz * D; - float cloudX = xx - xoffs2; - float cloudZ = zz - zoffs2; - tessellator.setColor4f(red * 0.9F, green * 0.9F, blue * 0.9F, 0.8F); - if (ix > -1) { - tessellator.setNormal(-1F, 0.0F, 0.0F); - for (int s = 0; s < D; s++) { - final float u = (xx + s + 0.5F) * scale + uo; - tessellator.addVertexWithUV(cloudX + s + 0.0F, yy + 0.0F, cloudZ + 0.0F, u, (zz + 0.0F) * scale + vo); - tessellator.addVertexWithUV(cloudX + s + 0.0F, yy + 0.0F, cloudZ + D, u, (zz + D) * scale + vo); - tessellator.addVertexWithUV(cloudX + s + 0.0F, yy + h, cloudZ + D, u, (zz + D) * scale + vo); - tessellator.addVertexWithUV(cloudX + s + 0.0F, yy + h, cloudZ + 0.0F, u, (zz + 0.0F) * scale + vo); + + GLRenderer.modelM4f() + .translate(0.0F, cloudMinY, 0.0F) + .scale(modelScale, 1.0F, modelScale); + + GLRenderer.setShader(Shaders.CLOUDS); + GLRenderer.globalSetUniforms(Shaders.CLOUDS); + + GLRenderer.getShader().uniformFloat("xOffset", xOffset); + GLRenderer.getShader().uniformFloat("zOffset", zOffset); +// GLRenderer.getShader().uniformVec3f("uColor", red, green, blue); + GLRenderer.setColor3f(red, green, blue); + + GLRenderer.setColorMask(false, false, false, true); + GLRenderer.render(this.cloudBuffer); + GLRenderer.setColorMask(true, true, true, true); + + GLRenderer.render(this.cloudBuffer); + + GLRenderer.popFrame(); + this.mc.renderer.endRenderClouds(partialTick); + } + + private RenderBuffer meshFancyClouds( + float deltaHeight, + float scale, + byte radius + ) { + TessellatorShader tessellator = GLRenderer.getTessellator(); + + float e = 9.765625E-4F; + tessellator.startDrawingQuads(); + float width = (radius * 2 + 2) * 8; + float scaledX = (-radius - 1) * 8; + float scaledZ = (-radius - 1) * 8; + float adjustedX = scaledX; + float adjustedZ = scaledZ; + + // Bottom faces + tessellator.setColor4f(0.7F, 0.7F, 0.7F, 0.8F); + tessellator.setNormal(0.0F, -1.0F, 0.0F); + tessellator.addVertexWithUV(adjustedX, 0.0F, adjustedZ, scaledX * scale, scaledZ * scale); + tessellator.addVertexWithUV(adjustedX + width, 0.0F, adjustedZ, (scaledX + width) * scale, scaledZ * scale); + tessellator.addVertexWithUV(adjustedX + width, 0.0F, adjustedZ + width, (scaledX + width) * scale, (scaledZ + width) * scale); + tessellator.addVertexWithUV(adjustedX, 0.0F, adjustedZ + width, scaledX * scale, (scaledZ + width) * scale); + + // Top faces + tessellator.setColor4f(1, 1, 1, 0.8F); + tessellator.setNormal(0.0F, 1.0F, 0.0F); + tessellator.addVertexWithUV(adjustedX, deltaHeight - e, adjustedZ, scaledX * scale, scaledZ * scale); + tessellator.addVertexWithUV(adjustedX, deltaHeight - e, adjustedZ + width, scaledX * scale, (scaledZ + width) * scale); + tessellator.addVertexWithUV(adjustedX + width, deltaHeight - e, adjustedZ + width, (scaledX + width) * scale, (scaledZ + width) * scale); + tessellator.addVertexWithUV(adjustedX + width, deltaHeight - e, adjustedZ, (scaledX + width) * scale, scaledZ * scale); + + // Side faces + for (int x = -radius - 1; x <= radius + 1; x++) { + for (int z = -radius - 1; z <= radius + 1; z++) { + scaledZ = x * 8; + adjustedX = z * 8; + adjustedZ = scaledZ; + + float cloudZ = adjustedX; + + tessellator.setColor4f(0.9F, 0.9F, 0.9F, 0.8F); + if (x > -1) { + tessellator.setNormal(-1.0F, 0.0F, 0.0F); + for (int delta = 0; delta < 8; delta++) { + float u = (scaledZ + delta + 0.5F) * scale; + tessellator.addVertexWithUV(adjustedZ + delta, 0.0F, cloudZ, u, adjustedX * scale); + tessellator.addVertexWithUV(adjustedZ + delta, 0.0F, cloudZ + 8.0F, u, (adjustedX + 8.0F) * scale); + tessellator.addVertexWithUV(adjustedZ + delta, deltaHeight, cloudZ + 8.0F, u, (adjustedX + 8.0F) * scale); + tessellator.addVertexWithUV(adjustedZ + delta, deltaHeight, cloudZ, u, adjustedX * scale); } } - if (ix <= 1) { + if (x <= 1) { tessellator.setNormal(1.0F, 0.0F, 0.0F); - for (int s = 0; s < D; s++) { - final float u = (xx + s + 0.5F) * scale + uo; - tessellator.addVertexWithUV((cloudX + s + 1.0F) - e, yy + 0.0F, cloudZ + 0.0F, u, (zz + 0.0F) * scale + vo); - tessellator.addVertexWithUV((cloudX + s + 1.0F) - e, yy + h, cloudZ + 0.0F, u, (zz + 0.0F) * scale + vo); - tessellator.addVertexWithUV((cloudX + s + 1.0F) - e, yy + h, cloudZ + D, u, (zz + D) * scale + vo); - tessellator.addVertexWithUV((cloudX + s + 1.0F) - e, yy + 0.0F, cloudZ + D, u, (zz + D) * scale + vo); + for (int delta = 0; delta < 8; delta++) { + float u = (scaledZ + delta + 0.5F) * scale; + tessellator.addVertexWithUV(adjustedZ + delta + 1.0F - e, 0.0F, cloudZ, u, adjustedX * scale); + tessellator.addVertexWithUV(adjustedZ + delta + 1.0F - e, deltaHeight, cloudZ, u, adjustedX * scale); + tessellator.addVertexWithUV(adjustedZ + delta + 1.0F - e, deltaHeight, cloudZ + 8.0F, u, (adjustedX + 8.0F) * scale); + tessellator.addVertexWithUV(adjustedZ + delta + 1.0F - e, 0.0F, cloudZ + 8.0F, u, (adjustedX + 8.0F) * scale); } } - tessellator.setColor4f(red * 0.8F, green * 0.8F, blue * 0.8F, 0.8F); - tessellator.setNormal(0.0F, 0.0F, -1F); - if (iz > -1) { - for (int s = 0; s < D; s++) { - final float v = (zz + s + 0.5F) * scale + vo; - tessellator.addVertexWithUV(cloudX + 0.0F, yy + 0.0F, cloudZ + s + 0.0F, (xx + 0.0F) * scale + uo, v); - tessellator.addVertexWithUV(cloudX + 0.0F, yy + h, cloudZ + s + 0.0F, (xx + 0.0F) * scale + uo, v); - tessellator.addVertexWithUV(cloudX + D, yy + h, cloudZ + s + 0.0F, (xx + D) * scale + uo, v); - tessellator.addVertexWithUV(cloudX + D, yy + 0.0F, cloudZ + s + 0.0F, (xx + D) * scale + uo, v); + tessellator.setColor4f(0.8F, 0.8F, 0.8F, 0.8F); + tessellator.setNormal(0.0F, 0.0F, -1.0F); + if (z > -1) { + for (int s = 0; s < 8; s++) { + float v = (adjustedX + s + 0.5F) * scale; + tessellator.addVertexWithUV(adjustedZ, 0.0F, cloudZ + s, scaledZ * scale, v); + tessellator.addVertexWithUV(adjustedZ, deltaHeight, cloudZ + s, scaledZ * scale, v); + tessellator.addVertexWithUV(adjustedZ + 8.0F, deltaHeight, cloudZ + s, (scaledZ + 8.0F) * scale, v); + tessellator.addVertexWithUV(adjustedZ + 8.0F, 0.0F, cloudZ + s, (scaledZ + 8.0F) * scale, v); } } tessellator.setNormal(0.0F, 0.0F, 1.0F); - if (iz <= 1) { - for (int s = 0; s < D; s++) { - final float v = (zz + s + 0.5F) * scale + vo; - tessellator.addVertexWithUV(cloudX + 0.0, yy + 0.0, (cloudZ + s + 1.0) - e, (xx + 0.0) * scale + uo, v); - tessellator.addVertexWithUV(cloudX + D, yy + 0.0, (cloudZ + s + 1.0) - e, (xx + D) * scale + uo, v); - tessellator.addVertexWithUV(cloudX + D, yy + h, (cloudZ + s + 1.0) - e, (xx + D) * scale + uo, v); - tessellator.addVertexWithUV(cloudX + 0., yy + h, (cloudZ + s + 1.0) - e, (xx + 0.0) * scale + uo, v); + if (z <= 1) { + for (int delta = 0; delta < 8; delta++) { + float v = (adjustedX + delta + 0.5F) * scale; + tessellator.addVertexWithUV(adjustedZ, 0.0F, cloudZ + delta + 1.0 - e, scaledZ * scale, v); + tessellator.addVertexWithUV(adjustedZ + 8.0F, 0.0F, cloudZ + delta + 1.0 - e, (scaledZ + 8.0F) * scale, v); + tessellator.addVertexWithUV(adjustedZ + 8.0F, deltaHeight, cloudZ + delta + 1.0 - e, (scaledZ + 8.0F) * scale, v); + tessellator.addVertexWithUV(adjustedZ, deltaHeight, cloudZ + delta + 1.0 - e, scaledZ * scale, v); } } - } - } - GLRenderer.setShader(Shaders.CLOUDS); - GLRenderer.setColorMask(false, false, false, true); - tessellator.draw(); - - // Hack for not needing to record the cloud buffer twice - GLRenderer.setColorMask(true, true, true, true); - GLRenderer.getShader().bind(); - GL41.glBindVertexArray(tessellator.vaos[tessellator.config]); - GL41.glBindBuffer(GL41.GL_ARRAY_BUFFER, tessellator.vbos[tessellator.config]); - GL41.glDrawArrays(tessellator.drawMode.cap, 0, tessellator.vertexCount); - GL41.glBindVertexArray(0); - - GLRenderer.popFrame(); - this.mc.renderer.endRenderClouds(partialTick); + return tessellator.record(GL41.glGenVertexArrays(), GL41.glGenBuffers()); } private final DirtyChunkRendererSorter dirtyChunkRendererSorter = new DirtyChunkRendererSorter(); @@ -1879,6 +1917,39 @@ public final class RenderGlobal addParticle(particleKey, x, y, z, motionX, motionY, motionZ, data, 16D, serverSided); } + private void spawnSiphonParticle( + @NotNull TilePos meshPos, @NotNull Direction face, + double minRadius, double radiusRange, + double minDepth, double depthRange, + @NotNull ParticleSiphon.Look look) + { + + final java.util.Random rand = this.world.rand; + + double angle = rand.nextDouble() * java.lang.Math.PI * 2.0; + double radius = minRadius + rand.nextDouble() * radiusRange; + double depth = minDepth + rand.nextDouble() * depthRange; + + double offA = java.lang.Math.cos(angle) * radius; + double offB = java.lang.Math.sin(angle) * radius; + + double px = meshPos.x + 0.5 + face.offsetX() * (0.5 + depth); + double py = meshPos.y + 0.5 + face.offsetY() * (0.5 + depth); + double pz = meshPos.z + 0.5 + face.offsetZ() * (0.5 + depth); + + switch (face.axis()) { + case Y -> { px += offA; pz += offB; } + case Z -> { px += offA; py += offB; } + default -> { py += offA; pz += offB; } + } + + if (this.world.getBlockMaterial(new TilePos(px, py, pz)) != Materials.WATER) { + return; + } + + this.mc.particleEngine.add(new ParticleSiphon(this.world, px, py, pz, meshPos, face, this.world.dimension, look)); + } + @Override public void addParticle(String particleId, double x, double y, double z, double motionX, double motionY, double motionZ, int data, double maxDistance, boolean serverSided) { @@ -2044,6 +2115,27 @@ public final class RenderGlobal break; + case EVENT_MESH_SIPHON_PARTICLES: // Mesh siphon funnel + + Direction meshFace = Direction.fromIdOrDefault(null, data); + if (meshFace == null) break; + + TilePos meshPos = new TilePos(x, y, z); + + for (int i = 0; i < 2; i++) { + spawnSiphonParticle(meshPos, meshFace, 0.10, 0.32, 0.10, 0.80, ParticleSiphon.Look.BUBBLE); + } + + if (random.nextInt(2) == 0) { + spawnSiphonParticle(meshPos, meshFace, 0.10, 0.30, 0.15, 0.65, ParticleSiphon.Look.SPLASH); + } + + for (int i = 0; i < 2; i++) { + spawnSiphonParticle(meshPos, meshFace, 0.06, 0.32, 0.03, 0.16, ParticleSiphon.Look.BUBBLE); + } + + break; + case EVENT_ACID_SPREAD: this.world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, (double) x + 0.5D, (double) y + 0.5D, (double) z + 0.5D, "tile.acidbubble", 0.75F, 1.0F); this.world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, (double) x + 0.5D, (double) y + 0.5D, (double) z + 0.5D, "random.fizz", 0.5F, 2.6F + (random.nextFloat() - random.nextFloat()) * 0.8F); diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelDispatcher.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelDispatcher.java index 3bb84b6bb..2e3339d3c 100644 --- a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelDispatcher.java +++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelDispatcher.java @@ -259,7 +259,6 @@ public final class BlockModelDispatcher addDispatch(new BlockModelGenericAxis<>(Blocks.LOG_OAK_MOSSY, loadDataModel("minecraft:block/log/oak_mossy"))); addDispatch(new BlockModelGenericAxis<>(Blocks.LOG_THORN, loadDataModel("minecraft:block/log/thorn"))); addDispatch(new BlockModelGenericAxis<>(Blocks.LOG_PALM, loadDataModel("minecraft:block/log/palm"))); - addDispatch(new BlockModelGenericAxis<>(Blocks.LOG_PETRIFIED, loadDataModel("minecraft:block/log/petrified"))); addDispatch(new BlockModelGenericLeaves<>(Blocks.LEAVES_OAK, "minecraft:block/leaves/oak")); addDispatch(new BlockModelGenericLeaves<>(Blocks.LEAVES_OAK_RETRO, "minecraft:block/leaves/oak_retro")); diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/EntityRenderer.java b/game/client/src/main/java/net/minecraft/client/render/entity/EntityRenderer.java index 28ea1fa62..150f2615e 100644 --- a/game/client/src/main/java/net/minecraft/client/render/entity/EntityRenderer.java +++ b/game/client/src/main/java/net/minecraft/client/render/entity/EntityRenderer.java @@ -1,5 +1,6 @@ package net.minecraft.client.render.entity; +import net.minecraft.client.Minecraft; import net.minecraft.client.option.GameSettings; import net.minecraft.client.render.font.FontRenderer; import net.minecraft.client.render.renderer.BlendFactor; @@ -7,6 +8,11 @@ import net.minecraft.client.render.renderer.GLRenderer; import net.minecraft.client.render.renderer.Shaders; import net.minecraft.client.render.renderer.State; import net.minecraft.core.entity.EntityDispatcher; +import net.minecraft.core.entity.animal.MobButterfly; +import net.minecraft.core.item.Item; +import net.minecraft.core.item.ItemStack; +import net.minecraft.core.item.Items; +import net.minecraft.core.net.command.TextFormatting; import net.minecraft.core.util.collection.NamespaceID; import net.minecraft.core.util.helper.LightIndexHelper; import net.minecraft.core.world.pos.TilePos; @@ -328,6 +334,77 @@ public abstract class EntityRenderer { } } + private boolean isCharSequenceBlank(CharSequence cs) { + if (cs == null || cs.isEmpty()) { + return true; + } + for (int i = 0; i < cs.length(); i++) { + final char c = cs.charAt(i); + if (c == TextFormatting.SPECIAL_CHAR) { + i++; + continue; + } + if (!Character.isWhitespace(c) && !Character.isSpaceChar(c)) { + return false; + } + } + return true; + } + + protected void renderLivingLabel(final @NotNull TessellatorGeneral t, final @NotNull T entity, final @NotNull CharSequence text, final double x, final double y, final double z, final int maxDistance, final boolean depthTest) { + + boolean holdingLabel = false; + if (Minecraft.getMinecraft().thePlayer != null) { + ItemStack heldStack = Minecraft.getMinecraft().thePlayer.getHeldItem(); + if (heldStack != null) { + holdingLabel = heldStack.getItem() == Items.LABEL; + } + } + + if (!holdingLabel && isCharSequenceBlank(text)) { + return; + } + + + final float cameraDistance = (float) Minecraft.getMinecraft().activeCamera.distanceTo(entity); + if (cameraDistance > maxDistance) { + return; + } + final FontRenderer sr = getFont(); + final float scale = 1.6f / 60f; + + GLRenderer.pushFrame(); + GLRenderer.modelM4f().translate((float) x, (float) y + entity.getHeadHeight() + 0.8f, (float) z); + GLRenderer.modelM4f().rotateY(Math.toRadians(-this.renderDispatcher.viewLerpYaw)); + GLRenderer.modelM4f().rotateX(Math.toRadians(this.renderDispatcher.viewLerpPitch)); + GLRenderer.modelM4f().scale(-scale, -scale, scale); + GLRenderer.globalSetLightEnabled(false); + GLRenderer.setDepthMask(false); + if (!depthTest) GLRenderer.disableState(State.DEPTH_TEST); + GLRenderer.enableState(State.BLEND); + GLRenderer.setBlendFunc(BlendFactor.SRC_ALPHA, BlendFactor.ONE_MINUS_SRC_ALPHA); + + GLRenderer.pushFrame(); + GLRenderer.setShader(Shaders.COLOR); + t.startDrawingQuads(); + final int halfTextWidth = sr.stringWidth(text) / 2; + t.setColor4f(0.0F, 0.0F, 0.0F, 0.25F); + t.addVertex(-halfTextWidth - 1, -1, 0.0D); + t.addVertex(-halfTextWidth - 1, 8, 0.0D); + t.addVertex(halfTextWidth, 8, 0.0D); + t.addVertex(halfTextWidth, -1, 0.0D); + t.draw(); + GLRenderer.popFrame(); + + sr.render(text, -halfTextWidth, 0).setColor(0x20ffffff).call(); + if (!depthTest) GLRenderer.enableState(State.DEPTH_TEST); + GLRenderer.setDepthMask(true); + sr.render(text, -halfTextWidth, 0).setColor(0xFFFFFF).call(); + GLRenderer.globalSetLightEnabled(true); + GLRenderer.disableState(State.BLEND); + GLRenderer.popFrame(); + } + public @NotNull FontRenderer getFont() { return this.renderDispatcher.font; } diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/MobRenderer.java b/game/client/src/main/java/net/minecraft/client/render/entity/MobRenderer.java index 3393df3ae..57bdb44a4 100644 --- a/game/client/src/main/java/net/minecraft/client/render/entity/MobRenderer.java +++ b/game/client/src/main/java/net/minecraft/client/render/entity/MobRenderer.java @@ -188,46 +188,6 @@ public abstract class MobRenderer extends EntityRenderer { } } - protected void renderLivingLabel(final @NotNull TessellatorGeneral t, final @NotNull T entity, final @NotNull CharSequence text, final double x, final double y, final double z, final int maxDistance, final boolean depthTest) { - final float cameraDistance = (float) Minecraft.getMinecraft().activeCamera.distanceTo(entity); - if (cameraDistance > maxDistance) { - return; - } - final FontRenderer sr = getFont(); - final float scale = 1.6f / 60f; - - GLRenderer.pushFrame(); - GLRenderer.modelM4f().translate((float) x, (float) y + entity.getHeadHeight() + 0.8f, (float) z); - GLRenderer.modelM4f().rotateY(Math.toRadians(-this.renderDispatcher.viewLerpYaw)); - GLRenderer.modelM4f().rotateX(Math.toRadians(this.renderDispatcher.viewLerpPitch)); - GLRenderer.modelM4f().scale(-scale, -scale, scale); - GLRenderer.globalSetLightEnabled(false); - GLRenderer.setDepthMask(false); - if (!depthTest) GLRenderer.disableState(State.DEPTH_TEST); - GLRenderer.enableState(State.BLEND); - GLRenderer.setBlendFunc(BlendFactor.SRC_ALPHA, BlendFactor.ONE_MINUS_SRC_ALPHA); - - GLRenderer.pushFrame(); - GLRenderer.setShader(Shaders.COLOR); - t.startDrawingQuads(); - final int halfTextWidth = sr.stringWidth(text) / 2; - t.setColor4f(0.0F, 0.0F, 0.0F, 0.25F); - t.addVertex(-halfTextWidth - 1, -1, 0.0D); - t.addVertex(-halfTextWidth - 1, 8, 0.0D); - t.addVertex(halfTextWidth, 8, 0.0D); - t.addVertex(halfTextWidth, -1, 0.0D); - t.draw(); - GLRenderer.popFrame(); - - sr.render(text, -halfTextWidth, 0).setColor(0x20ffffff).call(); - if (!depthTest) GLRenderer.enableState(State.DEPTH_TEST); - GLRenderer.setDepthMask(true); - sr.render(text, -halfTextWidth, 0).setColor(0xFFFFFF).call(); - GLRenderer.globalSetLightEnabled(true); - GLRenderer.disableState(State.BLEND); - GLRenderer.popFrame(); - } - protected int getOverlayColor(final @NotNull T entity, final float partialTick) { return 0; } diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererButterfly.java b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererButterfly.java index a3a303683..fe66802e4 100644 --- a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererButterfly.java +++ b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererButterfly.java @@ -22,6 +22,10 @@ public class MobRendererButterfly extends EntityRenderer { final float scale = 0.45f + (sizeRandom.nextFloat() * 0.35f); final float yRot = MathHelper.lerp(butterfly.yRotO, butterfly.yRot, partialTick); drawButterfly(tessellator, butterfly.tickCount, scale, yRot, x, y, z, partialTick, this.renderDispatcher.textureManager, getTexture(butterfly)); + + if (!butterfly.nickname.isEmpty()) { + renderLivingLabel(tessellator, butterfly, butterfly.getDisplayName(), x, y, z, 64, true); + } } @Override diff --git a/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSiphon.java b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSiphon.java new file mode 100644 index 000000000..5cf1c61ba --- /dev/null +++ b/game/client/src/main/java/net/minecraft/client/render/particle/ParticleSiphon.java @@ -0,0 +1,95 @@ +package net.minecraft.client.render.particle; + +import net.minecraft.core.block.entity.TileEntityMesh; +import net.minecraft.core.block.material.Materials; +import net.minecraft.core.util.helper.Direction; +import net.minecraft.core.world.Dimension; +import net.minecraft.core.world.World; +import net.minecraft.core.world.pos.TilePos; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector3d; + +/** + * A bubble caught in a mesh's siphon. Samples {@link TileEntityMesh#computeSiphonVelocity} + * every tick exactly like a siphoned item does + */ +public class ParticleSiphon extends Particle { + public enum Look { + BUBBLE, + SPLASH + } + + private final @NotNull TilePos meshPos; + private final @NotNull Direction direction; + private final double wanderPhase; + private final double swirlJitter; + private final double pullJitter; + private final @NotNull Vector3d velocity = new Vector3d(); + + public ParticleSiphon(@NotNull World world, double x, double y, double z, + @NotNull TilePos meshPos, @NotNull Direction direction, + @NotNull Dimension dimension, @NotNull Look look) { + super(world, x, y, z, 0.0, 0.0, 0.0); + + this.meshPos = meshPos; + this.direction = direction; + + this.noPhysics = true; + this.gravity = 0.0f; + + this.rCol = this.gCol = this.bCol = 1.0F; + if (look == Look.SPLASH) { + this.tex = (dimension == Dimension.NETHER ? ParticleTextureCache.ICON_SPLASH_RAIN_BLOOD : ParticleTextureCache.ICON_SPLASH_RAIN)[random.nextInt(4)]; + this.size = this.size * (random.nextFloat() * 0.3F + 0.25F); + } else { + this.tex = dimension == Dimension.NETHER ? ParticleTextureCache.ICON_BUBBLE_BOILING : ParticleTextureCache.ICON_BUBBLE; + this.size = this.size * (random.nextFloat() * 0.4F + 0.2F); + } + + this.wanderPhase = random.nextFloat() * Math.PI * 2.0; + this.swirlJitter = 0.8 + random.nextFloat() * 0.4; + this.pullJitter = 0.85 + random.nextFloat() * 0.3; + + this.xd = this.yd = this.zd = 0.0; + this.lifetime = (int)((60 + random.nextInt(40)) / TileEntityMesh.SIPHON_SPEED); + } + + @Override + public void tick() { + this.cachedLightmapCoord = calcLightIndex(1f); + this.xo = this.x; + this.yo = this.y; + this.zo = this.z; + + double wander = Math.sin(this.age * 0.15 + this.wanderPhase) * 0.1; + TileEntityMesh.computeSiphonVelocity(this.meshPos, this.direction, this.x, this.y, this.z, + wander, this.swirlJitter, this.pullJitter, this.velocity); + + this.xd = this.velocity.x; + this.yd = this.velocity.y; + this.zd = this.velocity.z; + this.move(this.xd, this.yd, this.zd); + + this.age++; + if (this.lifetime-- <= 0) { + remove(); + return; + } + + double dx = (this.meshPos.x + 0.5) - this.x; + double dy = (this.meshPos.y + 0.5) - this.y; + double dz = (this.meshPos.z + 0.5) - this.z; + if (dx * dx + dy * dy + dz * dz < 0.35 * 0.35) { + remove(); + return; + } + + if (this.world.getBlockMaterial(Cache.queryPos.set(this.x, this.y, this.z)) != Materials.WATER) { + remove(); + } + } + + private static final class Cache { + private static final @NotNull TilePos queryPos = new TilePos(); + } +} diff --git a/game/client/src/main/java/net/minecraft/client/render/renderer/GLRenderer.java b/game/client/src/main/java/net/minecraft/client/render/renderer/GLRenderer.java index 2f7cb3cd8..ca38a8a69 100644 --- a/game/client/src/main/java/net/minecraft/client/render/renderer/GLRenderer.java +++ b/game/client/src/main/java/net/minecraft/client/render/renderer/GLRenderer.java @@ -164,6 +164,7 @@ public class GLRenderer { shader.uniformFloat("uLightMapStrength", instance.currentLightMapStrength); shader.uniformFloat("uAlphaTest", instance.currentAlphaTest); shader.uniformVec4f("uColor", Color.redFromInt(instance.currentColor)/255f, Color.greenFromInt(instance.currentColor)/255f, Color.blueFromInt(instance.currentColor)/255f, Color.alphaFromInt(instance.currentColor)/255f); + shader.uniformFloat("uCelestialAngle", (instance.mc.currentWorld != null ? instance.mc.currentWorld.getCelestialAngle(instance.mc.timer.partialTicks) : 0f)); shader.uniformFloat("uCurrentTick", instance.mc.ticksRan + instance.mc.timer.partialTicks); shader.uniformFloat("uPartialTick", instance.mc.timer.partialTicks); shader.uniformFloat("frameTimeCounter", ShaderHelper.getFrameTimeCounter()); diff --git a/game/client/src/main/java/net/minecraft/client/render/shader/framebuffer/FrameBuffer.java b/game/client/src/main/java/net/minecraft/client/render/shader/framebuffer/FrameBuffer.java index c787d848f..137740ba5 100644 --- a/game/client/src/main/java/net/minecraft/client/render/shader/framebuffer/FrameBuffer.java +++ b/game/client/src/main/java/net/minecraft/client/render/shader/framebuffer/FrameBuffer.java @@ -22,6 +22,7 @@ public abstract class FrameBuffer { private final @NotNull Vector2ic size; private boolean deleted = false; + private boolean validated = false; protected FrameBuffer(final @NotNull FrameBufferAttachment @NotNull ... attachments) { if (attachments.length == 0) { @@ -67,9 +68,12 @@ public abstract class FrameBuffer { this.id = GL41.glGenFramebuffers(); + GL41.glBindFramebuffer(GL41.GL_FRAMEBUFFER, this.id); for (final @NotNull FrameBufferAttachment attachment : attachments) { attachment.attachToFramebuffer(this); } + this.checkComplete(); + GL41.glBindFramebuffer(GL41.GL_FRAMEBUFFER, 0); } public @Nullable FrameBufferAttachment getAttachment(final @NotNull AttachmentType attachmentType) { @@ -103,10 +107,6 @@ public abstract class FrameBuffer { return this.deleted; } - public boolean isComplete() { - return GL41.glCheckFramebufferStatus(this.id) == GL41.GL_FRAMEBUFFER_COMPLETE; - } - private void checkComplete() { final int status = GL41.glCheckFramebufferStatus(GL41.GL_FRAMEBUFFER); @@ -152,9 +152,9 @@ public abstract class FrameBuffer { throw new IllegalStateException("Frame buffer deleted!"); } - this.whileBound(this::checkComplete); - this.blitInner(); + + GL41.glBindFramebuffer(GL41.GL_FRAMEBUFFER, 0); } public final void bind() { @@ -162,8 +162,6 @@ public abstract class FrameBuffer { throw new IllegalStateException("Frame buffer deleted!"); } - this.whileBound(this::checkComplete); - this.bindInner(); } @@ -174,8 +172,6 @@ public abstract class FrameBuffer { throw new IllegalStateException("Frame buffer deleted!"); } - this.whileBound(this::checkComplete); - GL41.glBindFramebuffer(GL41.GL_FRAMEBUFFER, 0); } diff --git a/game/client/src/main/java/net/minecraft/client/render/terrain/TerrainRenderer.java b/game/client/src/main/java/net/minecraft/client/render/terrain/TerrainRenderer.java index cff4b9765..66dc9ca31 100644 --- a/game/client/src/main/java/net/minecraft/client/render/terrain/TerrainRenderer.java +++ b/game/client/src/main/java/net/minecraft/client/render/terrain/TerrainRenderer.java @@ -16,8 +16,10 @@ public abstract class TerrainRenderer { } public abstract void renderSolidTerrain(float partialTicks); - + public abstract void renderTranslucentTerrain(float partialTicks); + + public void resetChunkSorting() { } public abstract @NotNull ChunkRenderer createChunkRenderer( @NotNull World world, diff --git a/game/client/src/main/java/net/minecraft/client/render/terrain/TerrainRendererMultiDraw.java b/game/client/src/main/java/net/minecraft/client/render/terrain/TerrainRendererMultiDraw.java index 0d09677e7..c46509529 100644 --- a/game/client/src/main/java/net/minecraft/client/render/terrain/TerrainRendererMultiDraw.java +++ b/game/client/src/main/java/net/minecraft/client/render/terrain/TerrainRendererMultiDraw.java @@ -43,6 +43,7 @@ public class TerrainRendererMultiDraw extends TerrainRenderer { private final @NotNull Vector3d renderPos = new Vector3d(); private final @NotNull Vector3d prevSort = new Vector3d(); private final @NotNull Vector3d d = new Vector3d(); + private boolean forceResort = true; public TerrainRendererMultiDraw(final @NotNull Minecraft minecraft) { super(minecraft); @@ -62,7 +63,8 @@ public class TerrainRendererMultiDraw extends TerrainRenderer { this.d.set(this.renderPos).sub(this.prevSort); - if (this.d.dot(this.d) > 64) { + if (this.forceResort || this.d.dot(this.d) > 64) { + this.forceResort = false; this.prevSort.set(this.renderPos); this.mc.renderGlobal.resortChunks( @@ -189,6 +191,11 @@ public class TerrainRendererMultiDraw extends TerrainRenderer { return renderList; } + @Override + public void resetChunkSorting() { + this.forceResort = true; + } + @Override public @NotNull ChunkRenderer createChunkRenderer( final @NotNull World world, diff --git a/game/client/src/main/java/net/minecraft/client/render/terrain/VertexBuffer.java b/game/client/src/main/java/net/minecraft/client/render/terrain/VertexBuffer.java index f52c164ab..937819794 100644 --- a/game/client/src/main/java/net/minecraft/client/render/terrain/VertexBuffer.java +++ b/game/client/src/main/java/net/minecraft/client/render/terrain/VertexBuffer.java @@ -36,7 +36,7 @@ public class VertexBuffer { this.vbo = glGenBuffers(); this.vao = GL41.glGenVertexArrays(); glBindBuffer(GL_ARRAY_BUFFER, this.vbo); - glBufferData(GL_ARRAY_BUFFER, this.capacity, GL_DYNAMIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, this.capacity, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); } @@ -139,7 +139,7 @@ public class VertexBuffer { final int newBuffer = glGenBuffers(); glBindBuffer(GL_ARRAY_BUFFER, newBuffer); - glBufferData(GL_ARRAY_BUFFER, newCapacity, GL_DYNAMIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, newCapacity, GL_STATIC_DRAW); OpenGLHelper.checkError("create expand buffer"); diff --git a/game/client/src/main/java/net/minecraft/client/render/tileentity/TileEntityRendererSign.java b/game/client/src/main/java/net/minecraft/client/render/tileentity/TileEntityRendererSign.java index 52f300e11..7678ac024 100644 --- a/game/client/src/main/java/net/minecraft/client/render/tileentity/TileEntityRendererSign.java +++ b/game/client/src/main/java/net/minecraft/client/render/tileentity/TileEntityRendererSign.java @@ -173,6 +173,7 @@ public class TileEntityRendererSign extends TileEntityRenderer { private static void drawTexturedModalRect(final double width, final double height, final boolean blended, final @NotNull IconCoordinate coordinate) { if (blended) { GLRenderer.globalSetLightEnabled(false); + GLRenderer.setLightMapStrength(0); } coordinate.parentAtlas.bind(); final TessellatorGeneral tessellator = GLRenderer.getTessellator(); @@ -184,6 +185,7 @@ public class TileEntityRendererSign extends TileEntityRenderer { tessellator.addVertexWithUV(width / 2, height / 2, 0, coordinate.getIconUMax(), coordinate.getIconVMin()); tessellator.draw(); GLRenderer.globalSetLightEnabled(true); + GLRenderer.setLightMapStrength(1); } @Override diff --git a/game/client/src/main/java/net/minecraft/client/util/helper/ItemDragHandler.java b/game/client/src/main/java/net/minecraft/client/util/helper/ItemDragHandler.java index c2507eac2..928f1b498 100644 --- a/game/client/src/main/java/net/minecraft/client/util/helper/ItemDragHandler.java +++ b/game/client/src/main/java/net/minecraft/client/util/helper/ItemDragHandler.java @@ -36,6 +36,9 @@ public class ItemDragHandler extends Gui { protected ItemStack draggingItemStack; protected final List draggedSlots = new ArrayList<>(); + protected int modDragButton = -1; + protected final List modDraggedSlots = new ArrayList<>(); + private int lastX = Integer.MAX_VALUE; private int lastY = Integer.MAX_VALUE; private final ArrayList dragOverBuffer = new ArrayList<>(2); @@ -58,16 +61,22 @@ public class ItemDragHandler extends Gui { this.renderOffsetX = 0; this.renderOffsetY = 0; - if(isDragging()) { - this.dragOverBuffer.clear(); - if (Math.max(this.lastX, this.lastY) == Integer.MAX_VALUE) { - final var slot = this.container.getSlotAtPosition(mouseX, mouseY); - if (slot != null) this.dragOverBuffer.add(slot); - } else { - this.container.getSlotsBetweenPositions(this.lastX, this.lastY, mouseX, mouseY, this.dragOverBuffer); + if(isModDragging()) { + sweepSlotsSinceLastFrame(mouseX, mouseY); + for (final var slot : this.dragOverBuffer) { + if (this.modDraggedSlots.contains(slot)) { + continue; + } + this.modDraggedSlots.add(slot); + if (slot.hasItem()) { + this.container.clickSlot(slot, this.modDragButton); + } } - this.lastX = mouseX; - this.lastY = mouseY; + return; + } + + if(isDragging()) { + sweepSlotsSinceLastFrame(mouseX, mouseY); if (this.dragOverBuffer.isEmpty()) this.onDragOutsideOfSlots(); else for (final var slot : this.dragOverBuffer) onDragOverSlot(slot); @@ -98,7 +107,19 @@ public class ItemDragHandler extends Gui { } } } - + + private void sweepSlotsSinceLastFrame(int mouseX, int mouseY) { + this.dragOverBuffer.clear(); + if (Math.max(this.lastX, this.lastY) == Integer.MAX_VALUE) { + final var slot = this.container.getSlotAtPosition(mouseX, mouseY); + if (slot != null) this.dragOverBuffer.add(slot); + } else { + this.container.getSlotsBetweenPositions(this.lastX, this.lastY, mouseX, mouseY, this.dragOverBuffer); + } + this.lastX = mouseX; + this.lastY = mouseY; + } + protected void onDragOverSlot(Slot slot) { if(slot == null) return; @@ -156,11 +177,15 @@ public class ItemDragHandler extends Gui { if(pressed) { if(mod) { this.cancelRelease.add(button); + startModDragging(x, y, slot, button); this.container.clickInventory(x, y, button); }else { mousePressed(x, y, slot, button); } }else { + if(button == this.modDragButton) { + stopModDragging(); + } if(this.cancelRelease.contains(button)) { this.cancelRelease.remove(button); return; @@ -169,6 +194,29 @@ public class ItemDragHandler extends Gui { } } + + private void startModDragging(final int x, final int y, final @Nullable Slot slot, int button) { + if(button != 0 && button != 1) { + return; + } + this.modDragButton = button; + this.modDraggedSlots.clear(); + if(slot != null) { + this.modDraggedSlots.add(slot); + } + this.lastX = x; + this.lastY = y; + } + + protected final void stopModDragging() { + this.modDragButton = -1; + this.modDraggedSlots.clear(); + this.lastX = this.lastY = Integer.MAX_VALUE; + } + + public boolean isModDragging() { + return this.modDragButton != -1; + } public void click(InventoryAction action, int[] args) { this.mc.playerController.handleInventoryMouseClick(this.container.inventorySlots.containerId, action, args, this.mc.thePlayer); diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/motion_sensor/active_front.png b/game/client/src/main/resources/assets/minecraft/textures/block/motion_sensor/active_front.png index e9cd558a4..65a1d6a18 100644 Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/motion_sensor/active_front.png and b/game/client/src/main/resources/assets/minecraft/textures/block/motion_sensor/active_front.png differ diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/motion_sensor/idle_front.png b/game/client/src/main/resources/assets/minecraft/textures/block/motion_sensor/idle_front.png index 759ddd82a..b7efd41d6 100644 Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/motion_sensor/idle_front.png and b/game/client/src/main/resources/assets/minecraft/textures/block/motion_sensor/idle_front.png differ diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/motion_sensor/side.png b/game/client/src/main/resources/assets/minecraft/textures/block/motion_sensor/side.png index 2ae603dfd..0aabaa65f 100644 Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/motion_sensor/side.png and b/game/client/src/main/resources/assets/minecraft/textures/block/motion_sensor/side.png differ diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/sheep/wool_overlay.png b/game/client/src/main/resources/assets/minecraft/textures/entity/sheep/wool_overlay.png index fedd749ba..3491d4243 100644 Binary files a/game/client/src/main/resources/assets/minecraft/textures/entity/sheep/wool_overlay.png and b/game/client/src/main/resources/assets/minecraft/textures/entity/sheep/wool_overlay.png differ diff --git a/game/client/src/main/resources/credits.txt b/game/client/src/main/resources/credits.txt index 02d46efa7..7d4766ad1 100644 --- a/game/client/src/main/resources/credits.txt +++ b/game/client/src/main/resources/credits.txt @@ -32,8 +32,12 @@ Snek *Special Thanks AndroidDr (@Pedro270707) --Brigadier command parser implementation +Apollointhehouse +--Fancy Cloud Optimizations Astronomical360 --Eucalyptus log texture tweak +big sir (@.xdddd) +--Integer Slider fix bones (@frazzelbones) --Retired BTA helper Celeste diff --git a/game/client/src/main/resources/shaders/clouds.fsh b/game/client/src/main/resources/shaders/clouds.fsh index a482602c3..67f978238 100644 --- a/game/client/src/main/resources/shaders/clouds.fsh +++ b/game/client/src/main/resources/shaders/clouds.fsh @@ -26,15 +26,16 @@ layout (std140) uniform Matrices { uniform float uAlphaTest; uniform int isEyeInLiquid; uniform vec2 uViewPortSize; +uniform vec4 uColor; uniform sampler2D colortex; uniform sampler2D lighttex; float distance() { - vec4 fragPos = matrices.projectionInv * vec4((gl_FragCoord.xy / uViewPortSize) * 2.0 - 1.0, gl_FragCoord.z * 2.0 - 1.0, 1.0); + vec4 fragPos = matrices.viewInv * matrices.projectionInv * vec4((gl_FragCoord.xy / uViewPortSize) * 2.0 - 1.0, gl_FragCoord.z * 2.0 - 1.0, 1.0); fragPos /= fragPos.w; - return length(fragPos.xyz); + return length(fragPos.xz); } void main() { @@ -50,7 +51,7 @@ void main() { discard; } - vec4 col = Color; + vec4 col = Color * uColor; float fogStrength = 0.0; diff --git a/game/client/src/main/resources/shaders/clouds.vsh b/game/client/src/main/resources/shaders/clouds.vsh index cb437bb97..04756272e 100644 --- a/game/client/src/main/resources/shaders/clouds.vsh +++ b/game/client/src/main/resources/shaders/clouds.vsh @@ -1,25 +1,40 @@ #version 410 core + layout (location = 0) in vec3 aPos; layout (location = 1) in vec4 aColor; layout (location = 2) in vec2 aUV; -//layout (location = 3) in vec2 aLightmap; -//layout (location = 4) in vec3 aNormal; out vec4 Color; out vec2 TexCoord; -uniform mat4 model; +uniform float xOffset; +uniform float zOffset; layout (std140) uniform Matrices { - mat4 projection; - mat4 projectionInv; - mat4 view; - mat4 viewInv; + mat4 projection; + mat4 projectionInv; + mat4 view; + mat4 viewInv; } matrices; +uniform mat4 model; + void main() { - TexCoord = aUV; - Color = aColor; + Color = aColor; + + float xInt = floor(xOffset); + float zInt = floor(zOffset); + float xFrac = fract(xOffset); + float zFrac = fract(zOffset); + + TexCoord = aUV + vec2( + xInt * 1/256f, + zInt * 1/256f + ); + + vec3 pos = aPos; + pos.x -= xFrac; + pos.z -= zFrac; - gl_Position = matrices.projection * matrices.view * model * vec4(aPos, 1); -} \ No newline at end of file + gl_Position = matrices.projection * matrices.view * model * vec4(pos, 1.0); +} diff --git a/game/core/src/main/java/net/minecraft/core/InventoryAction.java b/game/core/src/main/java/net/minecraft/core/InventoryAction.java index 217dc78f6..65d757ad6 100644 --- a/game/core/src/main/java/net/minecraft/core/InventoryAction.java +++ b/game/core/src/main/java/net/minecraft/core/InventoryAction.java @@ -78,19 +78,23 @@ public enum InventoryAction { setId(CREATIVE_GRAB, 13); setId(CREATIVE_MOVE, 14); setId(CREATIVE_DELETE, 15); - setId(CREATIVE_DRAG, 20); setId(INTERACT_SLOT, 16); setId(INTERACT_GRABBED, 17); - + setId(EQUIP_ARMOR, 18); setId(SORT, 19); setId(LOCK, 20); + + setId(CREATIVE_DRAG, 21); } - + private static void setId(InventoryAction action, int id) { + if(actions[id] != null) { + throw new IllegalStateException("Inventory action id " + id + " is already used by " + actions[id] + ", cannot also assign it to " + action + "!"); + } actions[id] = action; action.id = id; } diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicConduit.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicConduit.java index 8c1af0d91..df602716e 100644 --- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicConduit.java +++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicConduit.java @@ -72,6 +72,7 @@ public class BlockLogicConduit extends BlockLogic private static final int CONNECT_LEVER = pc++; private static final int CONNECT_TORCH = pc++; private static final int CONNECT_REDSTONE_LANTERN = pc++; + private static final int CONNECT_REDSTONE_ORE = pc++; private static final int CONNECT_REDSTONE_BLOCK = pc++; private static final int CONNECT_MOTION_SENSOR = pc++; private static final int CONNECT_MATCHER = pc++; @@ -426,6 +427,10 @@ public class BlockLogicConduit extends BlockLogic } } } + + if (nBlockType.isEmittingSignal(world1, nTilePos, side)) { + return PROPAGATION_LENGTH; + } } else { if (nBlockType == Blocks.PUMPKIN_REDSTONE) { Side pumpkinFace = Side.fromId(world1.getBlockData(nTilePos)); @@ -584,6 +589,9 @@ public class BlockLogicConduit extends BlockLogic if (nBlock == Blocks.BLOCK_REDSTONE) { return CONNECT_REDSTONE_BLOCK; } + if (logic instanceof BlockLogicOreRedstone) { + return CONNECT_REDSTONE_ORE; + } if (logic instanceof BlockLogicRepeater) { Side axis = BlockLogicRepeater.getSideFromMeta(nData); return (side == axis || side == axis.opposite()) ? CONNECT_REPEATER : CONNECT_NONE; diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicMesh.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicMesh.java index e413fb88e..5b7288751 100644 --- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicMesh.java +++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicMesh.java @@ -4,10 +4,14 @@ import net.minecraft.core.block.entity.TileEntityMesh; import net.minecraft.core.block.material.Materials; import net.minecraft.core.entity.Entity; import net.minecraft.core.entity.EntityItem; +import net.minecraft.core.util.helper.Direction; +import net.minecraft.core.world.LevelListener; import net.minecraft.core.world.World; import net.minecraft.core.world.pos.TilePosc; import org.jetbrains.annotations.NotNull; +import java.util.Random; + public class BlockLogicMesh extends BlockLogicTransparent { @@ -23,6 +27,16 @@ public class BlockLogicMesh extends BlockLogicTransparent return false; } + @Override + public void animationTick(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Random rand) { + if (!(world.getTileEntity(tilePos) instanceof TileEntityMesh mesh)) return; + + Direction direction = mesh.getSiphonDirection(); + if (direction == null) return; + + world.playBlockEvent(tilePos, LevelListener.EVENT_MESH_SIPHON_PARTICLES, direction.id); + } + @Override public boolean collidesWithEntity(@NotNull Entity entity, @NotNull World world, @NotNull TilePosc tilePos) { return !(entity instanceof EntityItem); diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicStatue.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicStatue.java index 3b136a987..6b542f61f 100644 --- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicStatue.java +++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicStatue.java @@ -159,7 +159,7 @@ public class BlockLogicStatue extends BlockLogic implements BlockLogic.MatcherEn @Override public boolean pushesEntities(@NotNull WorldSource source, @NotNull TilePosc tilePos, @NotNull Class entityClass) { - return true; + return false; } @Override diff --git a/game/core/src/main/java/net/minecraft/core/block/Blocks.java b/game/core/src/main/java/net/minecraft/core/block/Blocks.java index c78a3e4f8..2ec2342a0 100644 --- a/game/core/src/main/java/net/minecraft/core/block/Blocks.java +++ b/game/core/src/main/java/net/minecraft/core/block/Blocks.java @@ -468,9 +468,6 @@ public final class Blocks { public static final @NotNull Block LOG_PALM = register("log.palm", "minecraft:block/log_palm", 287, BlockLogicLog::new) .withSound(BlockSounds.WOOD).withHardness(2.0F) .withTags(BlockTags.FENCES_CONNECT, BlockTags.MINEABLE_BY_AXE); - public static final @NotNull Block LOG_PETRIFIED = register("log.petrified", "minecraft:block/log_petrified", 288, b -> new BlockLogicAxisAligned(b, Materials.STONE)) - .withSound(BlockSounds.STONE).withHardness(8.0F) - .withTags(BlockTags.FENCES_CONNECT, BlockTags.CHAINLINK_FENCES_CONNECT, BlockTags.MINEABLE_BY_PICKAXE); public static final @NotNull Block LEAVES_OAK = register("leaves.oak", "minecraft:block/leaves_oak", 290, BlockLogicLeavesOak::new) .withSound(BlockSounds.GRASS).withHardness(0.2F).withLightBlock(1) diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityMesh.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityMesh.java index 6f08d6bd5..d5b4f7c59 100644 --- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityMesh.java +++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityMesh.java @@ -2,6 +2,8 @@ package net.minecraft.core.block.entity; import com.mojang.nbt.tags.CompoundTag; import net.minecraft.core.achievement.Achievements; import net.minecraft.core.block.Blocks; +import net.minecraft.core.block.material.Materials; +import net.minecraft.core.util.helper.MathHelper; import net.minecraft.core.current.wire.WireHandler; import net.minecraft.core.entity.Entity; import net.minecraft.core.entity.EntityItem; @@ -9,7 +11,9 @@ import net.minecraft.core.entity.player.Player; import net.minecraft.core.util.helper.Axis; import net.minecraft.core.util.helper.Direction; import net.minecraft.core.world.pos.TilePos; +import net.minecraft.core.world.pos.TilePosc; import org.jetbrains.annotations.NotNull; +import org.joml.Vector3d; import org.jetbrains.annotations.Nullable; import org.joml.primitives.AABBd; import org.joml.primitives.AABBdc; @@ -20,8 +24,18 @@ import java.util.stream.Collectors; public class TileEntityMesh extends TileEntity { public final @NotNull List trackedItemsSiphoning = new ArrayList<>(List.of()); protected final Map siphonDirValidityCache = new EnumMap<>(Direction.class); - protected final int siphonWidth = 5; - protected final int siphonHeight = 2; + public static final int SIPHON_WIDTH = 5; + public static final int SIPHON_HEIGHT = 2; + public static final double SIPHON_SPEED = 0.75; + protected final int siphonWidth = SIPHON_WIDTH; + protected final int siphonHeight = SIPHON_HEIGHT; + private final Vector3d siphonVelocity = new Vector3d(); + private final TilePos pathQueryPos = new TilePos(); + private static final int REACH_CAPACITY = SIPHON_WIDTH * SIPHON_WIDTH * SIPHON_HEIGHT; + private final boolean[] reachableWater = new boolean[REACH_CAPACITY]; + private final int[] reachQueue = new int[REACH_CAPACITY]; + private int reachBaseX, reachBaseY, reachBaseZ; + private int reachSpanX, reachSpanY, reachSpanZ; public int ticksRan = 0; public TileEntityMesh() { } @@ -32,88 +46,291 @@ public class TileEntityMesh extends TileEntity { return; } + if (ticksRan == 0){ + ticksRan = worldObj.rand.nextInt(360); + } + ticksRan++; + + handleSiphon(getSiphonDirection()); + } + + public @Nullable Direction getSiphonDirection(){ + if (!isSiphonEnabled()) { + return null; + } + siphonDirValidityCache.clear(); for (Direction direction : WireHandler.Directions.ALL) { siphonDirValidityCache.put(direction, this.isValidWater(direction)); } - handleSiphon(siphonDirValidityCache); + return getSingleValidFace(siphonDirValidityCache); + } - if (ticksRan == 0){ - ticksRan = worldObj.rand.nextInt(360); - } - ticksRan++; + protected boolean isSiphonEnabled(){ + return true; } @Override - public void readAdditionalData(@NotNull CompoundTag compoundTag) { - + public void invalidate() { + super.invalidate(); + releaseAllTrackedItems(); } @Override - public void writeAdditionalData(@NotNull CompoundTag compoundTag) { + public void readAdditionalData(@NotNull CompoundTag compoundTag) {} - } + @Override + public void writeAdditionalData(@NotNull CompoundTag compoundTag) {} - protected void handleSiphon(@NotNull Map validSiphonDirections){ + protected void handleSiphon(@Nullable Direction direction){ assert this.worldObj != null; - Direction direction = getSingleValidFace(validSiphonDirections); - if(direction == Direction.NONE) { return; } + if(direction == null) { + releaseAllTrackedItems(); + return; + } AABBdc siphonBox = getSiphonBox(tilePos, direction); List entitiesWithinSiphonBox = this.worldObj.getEntitiesWithinAABBExcludingEntity(null, siphonBox); - if (!this.worldObj.isClientSide) { - removeTrackedItems(entitiesWithinSiphonBox); - } + computeReachableWater(siphonBox, direction); + removeTrackedItems(entitiesWithinSiphonBox); for (Entity entity : entitiesWithinSiphonBox) { if (entity instanceof EntityItem item && entity.isInWater()) { - if(item.meshSiphoning == null && item.siphonDirection == Direction.NONE){ - item.siphonDirection = direction.opposite(); - item.meshSiphoning = this; + if (!canSiphonItem(item)) continue; + if (!canReachItem(item)) continue; + if (!tryClaimItem(item, direction)) continue; + + if (!this.worldObj.isClientSide) { + Player player = entity.world.getClosestPlayer(tilePos.x, tilePos.y, tilePos.z, 12); + if(player != null) { + player.triggerAchievement(Achievements.DOWN_THE_DRAIN); + } } + addItemsToBeTracked(item); + siphonItemIntoBlock(item, direction); + } + } + } - if(direction.opposite().equals(item.siphonDirection) && item.meshSiphoning == this) { - if (!this.worldObj.isClientSide) { - Player player = entity.world.getClosestPlayer(tilePos.x, tilePos.y, tilePos.z, 12); - if(player != null) { - player.triggerAchievement(Achievements.DOWN_THE_DRAIN); - } - addItemsToBeTracked(item); - } - siphonItemIntoBlock(item, direction); + protected boolean canSiphonItem(@NotNull EntityItem item){ + return true; + } + + protected void computeReachableWater(@NotNull AABBdc siphonBox, @NotNull Direction direction){ + assert this.worldObj != null; + + reachBaseX = MathHelper.floor(siphonBox.minX()); + reachBaseY = MathHelper.floor(siphonBox.minY()); + reachBaseZ = MathHelper.floor(siphonBox.minZ()); + reachSpanX = (int) Math.ceil(siphonBox.maxX()) - reachBaseX; + reachSpanY = (int) Math.ceil(siphonBox.maxY()) - reachBaseY; + reachSpanZ = (int) Math.ceil(siphonBox.maxZ()) - reachBaseZ; + + Arrays.fill(reachableWater, false); + + int seed = reachIndex( + this.tilePos.x + direction.offsetX(), + this.tilePos.y + direction.offsetY(), + this.tilePos.z + direction.offsetZ() + ); + if (seed < 0) { + return; + } + + reachableWater[seed] = true; + int head = 0; + int tail = 0; + reachQueue[tail++] = seed; + + while (head < tail) { + int index = reachQueue[head++]; + + int x = reachBaseX + (index / reachSpanZ) / reachSpanY; + int y = reachBaseY + (index / reachSpanZ) % reachSpanY; + int z = reachBaseZ + index % reachSpanZ; + + for (Direction step : WireHandler.Directions.ALL) { + int nextX = x + step.offsetX(); + int nextY = y + step.offsetY(); + int nextZ = z + step.offsetZ(); + + if (!movesAwayFromMesh(x, y, z, nextX, nextY, nextZ)) { + continue; + } + + int neighbour = reachIndex(nextX, nextY, nextZ); + if (neighbour < 0 || reachableWater[neighbour]) { + continue; } + + if (this.worldObj.getBlockMaterial(pathQueryPos.set(nextX, nextY, nextZ)) != Materials.WATER) { + continue; + } + + reachableWater[neighbour] = true; + reachQueue[tail++] = neighbour; + } + } + } + + private boolean movesAwayFromMesh(int x, int y, int z, int nextX, int nextY, int nextZ){ + if (nextX != x) return Math.abs(nextX - this.tilePos.x) > Math.abs(x - this.tilePos.x); + if (nextY != y) return Math.abs(nextY - this.tilePos.y) > Math.abs(y - this.tilePos.y); + return Math.abs(nextZ - this.tilePos.z) > Math.abs(z - this.tilePos.z); + } + + private int reachIndex(int x, int y, int z){ + int localX = x - reachBaseX; + int localY = y - reachBaseY; + int localZ = z - reachBaseZ; + + if (localX < 0 || localY < 0 || localZ < 0 + || localX >= reachSpanX || localY >= reachSpanY || localZ >= reachSpanZ) { + return -1; + } + + return (localX * reachSpanY + localY) * reachSpanZ + localZ; + } + + protected boolean canReachItem(@NotNull EntityItem item){ + return isInReachableWater(item) && hasLineOfSightTo(item); + } + + protected boolean hasLineOfSightTo(@NotNull EntityItem item){ + if (this.worldObj == null) { + return false; + } + + final double originX = item.x; + final double originY = item.y; + final double originZ = item.z; + + final double dx = (this.tilePos.x + 0.5) - originX; + final double dy = (this.tilePos.y + 0.5) - originY; + final double dz = (this.tilePos.z + 0.5) - originZ; + + int x = MathHelper.floor(originX); + int y = MathHelper.floor(originY); + int z = MathHelper.floor(originZ); + + final int stepX = dx > 0 ? 1 : (dx < 0 ? -1 : 0); + final int stepY = dy > 0 ? 1 : (dy < 0 ? -1 : 0); + final int stepZ = dz > 0 ? 1 : (dz < 0 ? -1 : 0); + + final double tDeltaX = stepX == 0 ? Double.MAX_VALUE : Math.abs(1.0 / dx); + final double tDeltaY = stepY == 0 ? Double.MAX_VALUE : Math.abs(1.0 / dy); + final double tDeltaZ = stepZ == 0 ? Double.MAX_VALUE : Math.abs(1.0 / dz); + + double tMaxX = stepX == 0 ? Double.MAX_VALUE : (stepX > 0 ? (x + 1 - originX) : (originX - x)) * tDeltaX; + double tMaxY = stepY == 0 ? Double.MAX_VALUE : (stepY > 0 ? (y + 1 - originY) : (originY - y)) * tDeltaY; + double tMaxZ = stepZ == 0 ? Double.MAX_VALUE : (stepZ > 0 ? (z + 1 - originZ) : (originZ - z)) * tDeltaZ; + + int guard = 4 * (SIPHON_WIDTH + SIPHON_HEIGHT); + while (!(x == this.tilePos.x && y == this.tilePos.y && z == this.tilePos.z)) { + if (guard-- <= 0) { + return false; + } + + if (tMaxX < tMaxY && tMaxX < tMaxZ) { + x += stepX; + tMaxX += tDeltaX; + } else if (tMaxY < tMaxZ) { + y += stepY; + tMaxY += tDeltaY; + } else { + z += stepZ; + tMaxZ += tDeltaZ; + } + + if (x == this.tilePos.x && y == this.tilePos.y && z == this.tilePos.z) { + break; + } + + if (this.worldObj.getBlockMaterial(pathQueryPos.set(x, y, z)) != Materials.WATER) { + return false; } } + + return true; + } + + protected boolean isInReachableWater(@NotNull EntityItem item){ + int index = reachIndex(MathHelper.floor(item.x), MathHelper.floor(item.y), MathHelper.floor(item.z)); + if (index >= 0 && reachableWater[index]) { + return true; + } + + int below = reachIndex(MathHelper.floor(item.x), MathHelper.floor(item.y - 0.25), MathHelper.floor(item.z)); + return below >= 0 && reachableWater[below]; + } + + + protected boolean tryClaimItem(@NotNull EntityItem item, @NotNull Direction direction){ + TileEntityMesh currentOwner = item.meshSiphoning; + + if (currentOwner == this) { + item.siphonDirection = direction.opposite(); + return true; + } + + if (currentOwner != null && !currentOwner.isInvalid() && currentOwner.worldObj == this.worldObj) { + double ownerDist = currentOwner.getDistanceFrom(item.x, item.y, item.z); + if (ownerDist <= this.getDistanceFrom(item.x, item.y, item.z)) { + return false; + } + currentOwner.releaseItem(item); + } + + item.meshSiphoning = this; + item.siphonDirection = direction.opposite(); + return true; } protected void removeTrackedItems(@NotNull List entitiesWithinSiphonBox){ Iterator iterator = trackedItemsSiphoning.iterator(); while (iterator.hasNext()) { EntityItem item = iterator.next(); - if (!entitiesWithinSiphonBox.contains(item)) { - item.siphonDirection = Direction.NONE; - item.meshSiphoning = null; + if (item.removed || !canSiphonItem(item) || !canReachItem(item) || !entitiesWithinSiphonBox.contains(item)) { + clearOwnership(item); iterator.remove(); - break; } } } + protected void releaseAllTrackedItems(){ + for (EntityItem item : trackedItemsSiphoning) { + clearOwnership(item); + } + trackedItemsSiphoning.clear(); + } + + protected void releaseItem(@NotNull EntityItem item){ + trackedItemsSiphoning.remove(item); + clearOwnership(item); + } + + private void clearOwnership(@NotNull EntityItem item){ + if (item.meshSiphoning == this) { + item.meshSiphoning = null; + item.siphonDirection = null; + } + } + protected void addItemsToBeTracked(@NotNull EntityItem item){ if(!trackedItemsSiphoning.contains(item)) trackedItemsSiphoning.add((item)); } - protected @NotNull Direction getSingleValidFace(@NotNull Map validSiphonDirections){ - //If none are valid or if more than one side is valid, returns Direction.NONE + protected @Nullable Direction getSingleValidFace(@NotNull Map validSiphonDirections){ + //If none are valid or if more than one side is valid, returns null //else, returns the valid Direction - Direction direction = Direction.NONE; + Direction direction = null; int amountTrue = 0; for (boolean value : validSiphonDirections.values()) { @@ -135,7 +352,7 @@ public class TileEntityMesh extends TileEntity { } if (direction == Direction.DOWN){ - return Direction.NONE; + return null; } return direction; @@ -188,79 +405,105 @@ public class TileEntityMesh extends TileEntity { return new AABBd(minX, minY, minZ, maxX, maxY, maxZ); } + // Deterministic item noise so that the items agree between client and server + protected static double itemNoise(@NotNull EntityItem item, int salt){ + int h = item.id * 0x9E3779B9 + salt * 0x85EBCA6B; + h ^= h >>> 15; + h *= 0x2545F491; + h ^= h >>> 13; + return (h >>> 8) / (double)(1 << 24); + } + protected void siphonItemIntoBlock(@NotNull EntityItem item, @NotNull Direction direction){ - double targetX = this.tilePos.x + 0.5; - double targetY = this.tilePos.y + 0.5; - double targetZ = this.tilePos.z + 0.5; + double wanderPhase = itemNoise(item, 1) * Math.PI * 2.0; + double swirlJitter = 0.8 + itemNoise(item, 3) * 0.4; + double pullJitter = 0.85 + itemNoise(item, 4) * 0.3; + double wander = Math.sin(item.age * 0.15 + wanderPhase) * 0.1; + + computeSiphonVelocity(this.tilePos, direction, item.x, item.y, item.z, + wander, swirlJitter, pullJitter, this.siphonVelocity); + + item.xd = this.siphonVelocity.x; + item.yd = this.siphonVelocity.y; + item.zd = this.siphonVelocity.z; + } + + public static @NotNull Vector3d computeSiphonVelocity( + @NotNull TilePosc meshPos, @NotNull Direction direction, + double x, double y, double z, + double wander, double swirlJitter, double pullJitter, + @NotNull Vector3d out + ){ + double targetX = meshPos.x() + 0.5 + (direction.axis() == Axis.X ? 0.0 : wander); + double targetY = meshPos.y() + 0.5 + (direction.axis() == Axis.Y ? 0.0 : wander * 0.5); + double targetZ = meshPos.z() + 0.5 + (direction.axis() == Axis.Z ? 0.0 : -wander); //difference vectors - double diffX = targetX - item.x; - double diffY = targetY - item.y; - double diffZ = targetZ - item.z; + double diffX = targetX - x; + double diffY = targetY - y; + double diffZ = targetZ - z; - //distance from item + //distance from the mesh double dist = Math.sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ); //we don't divide by zero in this household - if (dist > 0.01) { - - //Siphon variables yippee!!!! - double maxRange = Math.max(siphonWidth / 2.0, siphonHeight); - double clampedDist = Math.min(dist, maxRange); - double proximityFactor = 1.0 + (maxRange / (clampedDist + 0.5)); - double pullStrength = 0.05; - double dynSwirlStrength = (pullStrength * 1.5) * proximityFactor; - - /* - //normalize the pull speed and apply the dynamic speed multiplier - double pullX = (diffX / dist) * speedMultiplier; - double pullY = (diffY / dist) * speedMultiplier; - double pullZ = (diffZ / dist) * speedMultiplier; - */ - - //non-normalize implementation - double speedMultiplier = pullStrength * proximityFactor; - double pullX = diffX * speedMultiplier; - double pullY = diffY * speedMultiplier; - double pullZ = diffZ * speedMultiplier; - - //axis check to make sure the swirl behaves correctly depending on facing direction - double sX = 0, sY = 0, sZ = 0; - if (direction.axis() == Axis.Y) { - sX = -diffZ; - sZ = diffX; - } else if (direction.axis() == Axis.Z) { - sX = -diffY; - sY = diffX; - } else { - sY = -diffZ; - sZ = diffY; - } + if (dist <= 0.01) { + return out.set(0.0, 0.0, 0.0); + } - double swirlDist = Math.sqrt(sX * sX + sY * sY + sZ * sZ); - if (swirlDist > 0.01) { - // swirl also gets faster as it gets closer - sX = (sX / swirlDist) * dynSwirlStrength; - sY = (sY / swirlDist) * dynSwirlStrength; - sZ = (sZ / swirlDist) * dynSwirlStrength; - } + //Siphon variables yippee!!!! + double maxRange = Math.max(SIPHON_WIDTH / 2.0, SIPHON_HEIGHT); + double clampedDist = Math.min(dist, maxRange); + double proximityFactor = 1.0 + (maxRange / (clampedDist + 0.5)); + double pullStrength = 0.05 * pullJitter; + double swirlFade = Math.min(1.0, dist / 0.75); + double dynSwirlStrength = (pullStrength * 1.5) * proximityFactor * swirlJitter * swirlFade; + + double speedMultiplier = pullStrength * proximityFactor; + + double lateralBoost = 3.0; + double pullX = diffX * speedMultiplier * (direction.axis() == Axis.X ? 1.0 : lateralBoost); + double pullY = diffY * speedMultiplier * (direction.axis() == Axis.Y ? 1.0 : lateralBoost); + double pullZ = diffZ * speedMultiplier * (direction.axis() == Axis.Z ? 1.0 : lateralBoost); + + //axis check to make sure the swirl behaves correctly depending on facing direction + double sX = 0, sY = 0, sZ = 0; + if (direction.axis() == Axis.Y) { + sX = -diffZ; + sZ = diffX; + } else if (direction.axis() == Axis.Z) { + sX = -diffY; + sY = diffX; + } else { + sY = -diffZ; + sZ = diffY; + } - item.xd = pullX + sX; - item.yd = pullY + sY; - item.zd = pullZ + sZ; - - //kinda gave up here, don't know what I was on but it kinda works so..? - double slowStrength = 0.5 - (0.2 * (1.0 - (clampedDist / maxRange))); - switch (direction) { - case UP -> {if (item.yd < 0) item.yd *= slowStrength;} - case DOWN -> {if (item.yd > 0) item.yd *= slowStrength;} - case NORTH -> {if (item.zd > 0) item.zd *= slowStrength;} - case SOUTH -> {if (item.zd < 0) item.zd *= slowStrength;} - case WEST -> {if (item.xd > 0) item.xd *= slowStrength;} - case EAST -> {if (item.xd < 0) item.xd *= slowStrength;} - } + double swirlDist = Math.sqrt(sX * sX + sY * sY + sZ * sZ); + if (swirlDist > 0.01) { + // swirl also gets faster as it gets closer + sX = (sX / swirlDist) * dynSwirlStrength; + sY = (sY / swirlDist) * dynSwirlStrength; + sZ = (sZ / swirlDist) * dynSwirlStrength; + } else { + sX = sY = sZ = 0; } + + out.set(pullX + sX, pullY + sY, pullZ + sZ); + + //kinda gave up here, don't know what I was on but it kinda works so..? + double slowStrength = 0.5 - (0.2 * (1.0 - (clampedDist / maxRange))); + switch (direction) { + case UP -> {if (out.y < 0) out.y *= slowStrength;} + case DOWN -> {if (out.y > 0) out.y *= slowStrength;} + case NORTH -> {if (out.z > 0) out.z *= slowStrength;} + case SOUTH -> {if (out.z < 0) out.z *= slowStrength;} + case WEST -> {if (out.x > 0) out.x *= slowStrength;} + case EAST -> {if (out.x < 0) out.x *= slowStrength;} + } + + return out.mul(SIPHON_SPEED); } protected boolean isValidWater(@NotNull Direction direction){ diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityMeshGold.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityMeshGold.java index 1680d12e8..afcaa9081 100644 --- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityMeshGold.java +++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityMeshGold.java @@ -1,51 +1,44 @@ package net.minecraft.core.block.entity; import com.mojang.nbt.tags.CompoundTag; -import net.minecraft.core.achievement.Achievements; -import net.minecraft.core.current.wire.WireHandler; -import net.minecraft.core.entity.Entity; import net.minecraft.core.entity.EntityItem; import net.minecraft.core.entity.player.Player; import net.minecraft.core.item.ItemStack; import net.minecraft.core.net.packet.Packet; import net.minecraft.core.net.packet.PacketTileEntityData; -import net.minecraft.core.util.helper.Direction; import net.minecraft.core.world.World; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.joml.primitives.AABBdc; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; public class TileEntityMeshGold extends TileEntityMesh { @Nullable public ItemStack filterItem; - public int ticksRan = 0; + public TileEntityMeshGold() { } @Override public void tick() { - if (this.worldObj == null || this.worldObj.isClientSide) { + if (this.worldObj == null) { return; } - if(filterItem != null){ - siphonDirValidityCache.clear(); - for (Direction direction : WireHandler.Directions.ALL) { - siphonDirValidityCache.put(direction, this.isValidWater(direction)); - } - handleSiphon(siphonDirValidityCache); - } - if (ticksRan == 0){ ticksRan = worldObj.rand.nextInt(360); } ticksRan++; + + if (this.worldObj.isClientSide) { + return; + } + + handleSiphon(getSiphonDirection()); + } + + @Override + protected boolean isSiphonEnabled(){ + return this.filterItem != null; } @Override @@ -97,39 +90,11 @@ public class TileEntityMeshGold extends TileEntityMesh { } @Override - protected void handleSiphon(@NotNull Map validSiphonDirections){ - //null-checks were already made - assert this.worldObj != null; - assert this.filterItem != null; - - Direction direction = getSingleValidFace(validSiphonDirections); - if(direction == Direction.NONE){return;} - AABBdc siphonBox = getSiphonBox(tilePos, direction); - List entitiesWithinSiphonBox = this.worldObj.getEntitiesWithinAABBExcludingEntity(null, siphonBox); - removeTrackedItems(entitiesWithinSiphonBox); - - for (Entity entity : entitiesWithinSiphonBox) - { - if (entity instanceof EntityItem item && entity.isInWater()) - { - if(item.meshSiphoning == null && item.siphonDirection == Direction.NONE){ - item.siphonDirection = direction.opposite(); - item.meshSiphoning = this; - } - if(direction.opposite() == item.siphonDirection && item.meshSiphoning == this) { - if (item.item.getItem() == this.filterItem.getItem() && item.item.getMetadata() == this.filterItem.getMetadata()) { - - Player player = entity.world.getClosestPlayer(tilePos.x, tilePos.y, tilePos.z, 12); - if(player != null) { - player.triggerAchievement(Achievements.DOWN_THE_DRAIN); - } - - siphonItemIntoBlock(item, direction); - addItemsToBeTracked(item); - } - } - } - } + protected boolean canSiphonItem(@NotNull EntityItem item){ + return this.filterItem != null + && item.item != null + && item.item.getItem() == this.filterItem.getItem() + && item.item.getMetadata() == this.filterItem.getMetadata(); } @Override diff --git a/game/core/src/main/java/net/minecraft/core/data/registry/recipe/adapter/RecipeCraftingShapedJsonAdapter.java b/game/core/src/main/java/net/minecraft/core/data/registry/recipe/adapter/RecipeCraftingShapedJsonAdapter.java index 5cc1a36ee..371cc2092 100644 --- a/game/core/src/main/java/net/minecraft/core/data/registry/recipe/adapter/RecipeCraftingShapedJsonAdapter.java +++ b/game/core/src/main/java/net/minecraft/core/data/registry/recipe/adapter/RecipeCraftingShapedJsonAdapter.java @@ -25,7 +25,9 @@ public class RecipeCraftingShapedJsonAdapter implements RecipeJsonAdapter symbols = obj.get("inputs").getAsJsonArray().asList().stream().map((E)->context.deserialize(E,RecipeSymbol.class)).collect(Collectors.toList()); ItemStack result = context.deserialize(obj.get("result").getAsJsonObject(),ItemStack.class); - return new RecipeEntryCraftingShapeless(symbols,result); + RecipeEntryCraftingShapeless recipe = new RecipeEntryCraftingShapeless(symbols,result); + if (obj.has("group")) recipe.consolidationGroup = obj.get("group").getAsString(); + return recipe; } @Override @@ -27,6 +29,9 @@ public class RecipeCraftingShapelessJsonAdapter implements RecipeJsonAdapter symbols = src.getInput(); obj.add("inputs",context.serialize(symbols)); obj.add("result",context.serialize(src.getOutput())); + if (src.consolidationGroup != null) { + obj.addProperty("group", src.consolidationGroup); + } return obj; } } diff --git a/game/core/src/main/java/net/minecraft/core/data/registry/recipe/entry/RecipeEntryCrafting.java b/game/core/src/main/java/net/minecraft/core/data/registry/recipe/entry/RecipeEntryCrafting.java index c8796b640..e8b816128 100644 --- a/game/core/src/main/java/net/minecraft/core/data/registry/recipe/entry/RecipeEntryCrafting.java +++ b/game/core/src/main/java/net/minecraft/core/data/registry/recipe/entry/RecipeEntryCrafting.java @@ -8,6 +8,8 @@ import net.minecraft.core.player.inventory.container.ContainerCrafting; public abstract class RecipeEntryCrafting extends RecipeEntryBase { + public String consolidationGroup = null; + public RecipeEntryCrafting(I input, O output) { super(input, output, null); } diff --git a/game/core/src/main/java/net/minecraft/core/entity/Entity.java b/game/core/src/main/java/net/minecraft/core/entity/Entity.java index ebba016a8..cf52c9be9 100644 --- a/game/core/src/main/java/net/minecraft/core/entity/Entity.java +++ b/game/core/src/main/java/net/minecraft/core/entity/Entity.java @@ -164,6 +164,7 @@ public abstract class Entity public boolean isWalking = false; protected boolean muteStepSounds = false; public float footSize = 0.0F; + public float stepDownSize = 0.0F; // Tick private boolean firstTick = true; @@ -445,6 +446,7 @@ public abstract class Entity this.z = (this.bb.minZ + this.bb.maxZ) / 2.0; return; } + boolean wasOnGround = this.onGround; this.ySlideOffset *= 0.4f; double d3 = this.x; double d4 = this.z; @@ -608,6 +610,22 @@ public abstract class Entity this.bb.set(workingBox4); } } + + if (this.stepDownSize > 0.0f + && wasOnGround + && !isSneaking() + && (oldXd != 0.0 || oldZd != 0.0) + && yd < 0.0 && oldYd == yd) { + double snap = -this.stepDownSize; + List below = this.world.getCubes(this, MathHelper.aabbExpand(this.bb, 0.0, snap, 0.0, workingBox2)); + for (int i = 0; i < below.size(); i++) { + snap = MathHelper.aabbClipYCollide(below.get(i), this.bb, snap); + } + if (snap > -this.stepDownSize) { + this.bb.translate(0.0, snap, 0.0); + yd += snap; + } + } //fix for entity jitter when blasted by cannonballs (can cause some client-side jitter when entity is under a ceiling. unsure exactly why) if (!this.world.isClientSide || this instanceof Player || !(this instanceof Mob) || this.locallySimulated) { this.x = (this.bb.minX + this.bb.maxX) / 2D; diff --git a/game/core/src/main/java/net/minecraft/core/entity/EntityItem.java b/game/core/src/main/java/net/minecraft/core/entity/EntityItem.java index 552eacf7e..e4b4dd745 100644 --- a/game/core/src/main/java/net/minecraft/core/entity/EntityItem.java +++ b/game/core/src/main/java/net/minecraft/core/entity/EntityItem.java @@ -28,6 +28,7 @@ import net.minecraft.core.util.helper.MathHelper; import net.minecraft.core.world.World; import net.minecraft.core.world.pos.TilePos; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.joml.primitives.AABBd; public class EntityItem extends Entity @@ -35,7 +36,7 @@ public class EntityItem extends Entity public ItemStack item; public int lifetime; public int age; - public Direction siphonDirection = Direction.NONE; + public @Nullable Direction siphonDirection = null; public TileEntityMesh meshSiphoning = null; public int pickupDelay; private int health; @@ -459,7 +460,7 @@ public class EntityItem extends Entity } public boolean isBeingSiphoned(){ - return siphonDirection != Direction.NONE; + return siphonDirection != null; } @Override diff --git a/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java b/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java index be0aa4c9a..3bdbf6ee9 100644 --- a/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java +++ b/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java @@ -40,6 +40,7 @@ public class MobPig super(world); setTextureIdentifier("minecraft", "pig"); setSize(0.9F, 0.9F); + this.stepDownSize = 0.5F; this.mobDrops.add(new WeightedRandomLootObject(Items.FOOD_PORKCHOP_RAW.getDefaultStack(), 1, 2)); this.burningMobDrops.add(new WeightedRandomLootObject(Items.FOOD_PORKCHOP_COOKED.getDefaultStack(), 1, 2)); // age = MobAge.newRandom(this, MobAge.YEAR_LENGTH_DAYS * 4, 14, MobAge.YEAR_LENGTH_DAYS * 3); diff --git a/game/core/src/main/java/net/minecraft/core/entity/monster/MobMonster.java b/game/core/src/main/java/net/minecraft/core/entity/monster/MobMonster.java index 4b5c6377f..74cc5cb67 100644 --- a/game/core/src/main/java/net/minecraft/core/entity/monster/MobMonster.java +++ b/game/core/src/main/java/net/minecraft/core/entity/monster/MobMonster.java @@ -93,6 +93,7 @@ public abstract class MobMonster extends MobPathfinder return true; } if (attacker != this) { + if (attacker instanceof Player player && !player.gamemode.hasHostileMobs()) return true; this.target = attacker; } return true; diff --git a/game/core/src/main/java/net/minecraft/core/entity/monster/MobSkeleton.java b/game/core/src/main/java/net/minecraft/core/entity/monster/MobSkeleton.java index 21a1d54af..bdc2ecc6e 100644 --- a/game/core/src/main/java/net/minecraft/core/entity/monster/MobSkeleton.java +++ b/game/core/src/main/java/net/minecraft/core/entity/monster/MobSkeleton.java @@ -55,13 +55,6 @@ public class MobSkeleton extends MobMonsterArmored entityData.define(DATA_ATTACK_TIME, attackTime, Integer.class); } - @Override - public void spawnInit() { - super.spawnInit(); - - setHeldItem(DEFAULT_HELD_ITEM.copy()); - } - @Override public void onLivingUpdate() { diff --git a/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombiePig.java b/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombiePig.java index 80250b69e..795fc27fd 100644 --- a/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombiePig.java +++ b/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombiePig.java @@ -15,6 +15,7 @@ import net.minecraft.core.world.World; import net.minecraft.core.util.helper.DamageType; import net.minecraft.core.world.pos.TilePos; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.joml.primitives.AABBd; import java.util.ArrayList; @@ -41,13 +42,6 @@ public class MobZombiePig extends MobZombie { this.scoreValue = 500; } - @Override - public void spawnInit() { - super.spawnInit(); - - setHeldItem(DEFAULT_HELD_ITEM.copy()); - } - @Override public void tick() { if (this.tickCount % 200 == 0 && this.target instanceof Player && ((Player) this.target).getStat(Achievements.MOST_WANTED) <= 0) { @@ -173,4 +167,9 @@ public class MobZombiePig extends MobZombie { public boolean hurtByNetherWater() { return false; } + + @Override + public @Nullable ItemStack getHeldItem() { + return DEFAULT_HELD_ITEM; + } } diff --git a/game/core/src/main/java/net/minecraft/core/entity/player/Player.java b/game/core/src/main/java/net/minecraft/core/entity/player/Player.java index d7dc626da..d77fea5ab 100644 --- a/game/core/src/main/java/net/minecraft/core/entity/player/Player.java +++ b/game/core/src/main/java/net/minecraft/core/entity/player/Player.java @@ -180,7 +180,9 @@ public abstract class Player protected void setupScale() { this.isDwarf = this.world.getGameRuleValue(GameRules.DWARF_MODE); - if (this.isDwarf) { + if (this.sleeping) { + setSize(0.2f, 0.2f); + } else if (this.isDwarf) { setSize(0.6f, isSneaking() ? 0.5f : 0.8f); } else { setSize(0.6f, isSneaking() ? 1.5f : 1.8f); @@ -1268,9 +1270,9 @@ public abstract class Player damage++; } boolean wasAlive = entity.isAlive(); - entity.hurt(this, damage, DamageType.COMBAT); + boolean damageDealt = entity.hurt(this, damage, DamageType.COMBAT); ItemStack itemstack = getCurrentEquippedItem(); - if (itemstack != null && (entity instanceof Mob) && wasAlive) { + if (damageDealt && itemstack != null && (entity instanceof Mob) && wasAlive) { itemstack.hitEntity((Mob) entity, this); if (itemstack.stackSize <= 0) { destroyCurrentEquippedItem(); @@ -1350,23 +1352,23 @@ public abstract class Player if (this.world.isBlockLoaded(x, y, z)) { int meta = this.world.getBlockMetadata(x, y, z); int dir = BlockLogicBed.DIRECTION.get(meta); - float xOff = 0.5F; - float zOff = 0.5F; + double xOff = 0.5D; + double zOff = 0.5D; if (dir == 0) { - zOff = 0.9f; + zOff = 0.9D; } else if (dir == 1) { - xOff = 0.1f; + xOff = 0.1D; } else if (dir == 2) { - zOff = 0.1f; + zOff = 0.1D; } else if (dir == 3) { - xOff = 0.9f; + xOff = 0.9D; } func_22052_e(dir); - setPos(x + xOff, y + 0.9375F, z + zOff); + setPos(x + xOff, y + 0.9375D, z + zOff); } else { - setPos(x + 0.5F, y + 0.9375F, z + 0.5F); + setPos(x + 0.5D, y + 0.9375D, z + 0.5D); } this.sleeping = true; this.sleepTimer = 0; @@ -1401,6 +1403,8 @@ public abstract class Player } public void wakeUpPlayer(boolean flag, boolean flag1) { + // Cleared before setupScale so the standing hitbox is restored for the setPos below + this.sleeping = false; setupScale(); TilePos bedPos = this.bedTilePos; Block b; @@ -1411,9 +1415,8 @@ public abstract class Player if (emptyPos == null) { emptyPos = bedPos.up(new TilePos()); } - setPos((float) emptyPos.x + 0.5F, (float) emptyPos.y + this.heightOffset + 0.1F, (float) emptyPos.z + 0.5F); + setPos(emptyPos.x + 0.5D, emptyPos.y + (double) this.heightOffset + 0.1D, emptyPos.z + 0.5D); } - this.sleeping = false; if (!this.world.isClientSide && flag1) { this.world.updateEnoughPlayersSleepingFlag(this); } diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemLabel.java b/game/core/src/main/java/net/minecraft/core/item/ItemLabel.java index 4a3831aa0..b2a2a3b9d 100644 --- a/game/core/src/main/java/net/minecraft/core/item/ItemLabel.java +++ b/game/core/src/main/java/net/minecraft/core/item/ItemLabel.java @@ -4,6 +4,7 @@ import com.b100.utils.StringUtils; import net.minecraft.core.block.entity.TileEntityActivator; import net.minecraft.core.entity.Entity; import net.minecraft.core.entity.Mob; +import net.minecraft.core.entity.animal.MobButterfly; import net.minecraft.core.entity.player.Player; import net.minecraft.core.util.helper.Direction; import net.minecraft.core.world.World; @@ -23,6 +24,11 @@ public class ItemLabel extends Item { setMaxStackSize(1); } + @Override + public boolean interactsWithEntity(@NotNull ItemStack selfStack, @NotNull Entity entity) { + return super.interactsWithEntity(selfStack, entity) || entity instanceof MobButterfly; + } + @Override public @Nullable ItemStack onUse(final @NotNull ItemStack selfStack, final @NotNull World world, final @NotNull Player player) { if (selfStack.hasCustomName()) { diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemWandNBT.java b/game/core/src/main/java/net/minecraft/core/item/ItemWandNBT.java index 3e0999e68..45116aa20 100644 --- a/game/core/src/main/java/net/minecraft/core/item/ItemWandNBT.java +++ b/game/core/src/main/java/net/minecraft/core/item/ItemWandNBT.java @@ -2,7 +2,10 @@ package net.minecraft.core.item; import com.mojang.nbt.tags.CompoundTag; import net.minecraft.core.block.entity.TileEntity; +import net.minecraft.core.entity.Entity; import net.minecraft.core.entity.Mob; +import net.minecraft.core.entity.animal.MobButterfly; +import net.minecraft.core.entity.animal.MobFireflyCluster; import net.minecraft.core.entity.player.Player; import net.minecraft.core.player.inventory.slot.Slot; import net.minecraft.core.util.helper.Side; @@ -17,6 +20,11 @@ public class ItemWandNBT extends Item{ super(translationKey, namespaceId, id); } + @Override + public boolean interactsWithEntity(@NotNull ItemStack selfStack, @NotNull Entity entity) { + return super.interactsWithEntity(selfStack, entity) || entity instanceof MobButterfly || entity instanceof MobFireflyCluster; + } + @Override public ItemStack onInventoryInteract(@NotNull Player player, @NotNull Slot slot, @Nullable ItemStack stackInSlot, boolean isItemGrabbed) { if (slot.getItemStack() != null) { diff --git a/game/core/src/main/java/net/minecraft/core/item/tool/ItemToolPickaxe.java b/game/core/src/main/java/net/minecraft/core/item/tool/ItemToolPickaxe.java index 030363c7b..5b4410d79 100644 --- a/game/core/src/main/java/net/minecraft/core/item/tool/ItemToolPickaxe.java +++ b/game/core/src/main/java/net/minecraft/core/item/tool/ItemToolPickaxe.java @@ -16,7 +16,6 @@ public class ItemToolPickaxe extends ItemTool { static { miningLevels.put(Blocks.OBSIDIAN, 3); - miningLevels.put(Blocks.LOG_PETRIFIED, 3); miningLevels.put(Blocks.BLOCK_DIAMOND, 2); miningLevels.put(Blocks.ORE_DIAMOND_STONE, 2); diff --git a/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandAchievement.java b/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandAchievement.java index cc2f87441..982e2fd4b 100644 --- a/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandAchievement.java +++ b/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandAchievement.java @@ -12,6 +12,7 @@ import net.minecraft.core.entity.player.Player; import net.minecraft.core.lang.I18n; import net.minecraft.core.net.command.CommandManager; import net.minecraft.core.net.command.CommandSource; +import net.minecraft.core.net.command.TextFormatting; import net.minecraft.core.net.command.arguments.ArgumentTypeAchievement; import net.minecraft.core.net.command.exceptions.CommandExceptions; import org.jetbrains.annotations.NotNull; @@ -38,7 +39,7 @@ public class CommandAchievement implements CommandManager.CommandRegistry { } grantAchievements(player, collectWithAncestors(achievement)); - source.sendTranslatableMessage("command.commands.achievement.grant.success_single_entity", achievement.getStatName().trim(), player.getDisplayName()); + source.sendTranslatableMessage("command.commands.achievement.grant.success_single_entity", achievement.getStatName().trim(), TextFormatting.scoped(player.getDisplayName())); return Command.SINGLE_SUCCESS; })) @@ -51,7 +52,7 @@ public class CommandAchievement implements CommandManager.CommandRegistry { grantAchievements(player, collectWithAncestors(achievement)); } - source.sendTranslatableMessage("command.commands.achievement.grant.all.success_single_entity", player.getDisplayName()); + source.sendTranslatableMessage("command.commands.achievement.grant.all.success_single_entity", TextFormatting.scoped(player.getDisplayName())); return Command.SINGLE_SUCCESS; }))) @@ -63,7 +64,7 @@ public class CommandAchievement implements CommandManager.CommandRegistry { Achievement achievement = c.getArgument("achievement", Achievement.class); revokeAchievements(player, collectWithDescendants(achievement)); - source.sendTranslatableMessage("command.commands.achievement.revoke.success_single_entity", achievement.getStatName().trim(), player.getDisplayName()); + source.sendTranslatableMessage("command.commands.achievement.revoke.success_single_entity", achievement.getStatName().trim(), TextFormatting.scoped(player.getDisplayName())); return Command.SINGLE_SUCCESS; })) @@ -73,7 +74,7 @@ public class CommandAchievement implements CommandManager.CommandRegistry { Player player = requireSender(source); revokeAchievements(player, collectAllAchievementsByDepthDesc()); - source.sendTranslatableMessage("command.commands.achievement.revoke.all.success_single_entity", player.getDisplayName()); + source.sendTranslatableMessage("command.commands.achievement.revoke.all.success_single_entity", TextFormatting.scoped(player.getDisplayName())); return Command.SINGLE_SUCCESS; }))))); diff --git a/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandKill.java b/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandKill.java index d3dce5ee7..dcd42795a 100644 --- a/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandKill.java +++ b/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandKill.java @@ -12,6 +12,7 @@ import net.minecraft.core.lang.I18n; import net.minecraft.core.net.command.CommandManager; import net.minecraft.core.net.command.util.CommandHelper; import net.minecraft.core.net.command.CommandSource; +import net.minecraft.core.net.command.TextFormatting; import net.minecraft.core.net.command.arguments.ArgumentTypeEntity; import net.minecraft.core.net.command.exceptions.CommandExceptions; import net.minecraft.core.net.command.helpers.EntitySelector; @@ -32,7 +33,7 @@ public class CommandKill implements CommandManager.CommandRegistry { sender.killPlayer(); - source.sendTranslatableMessage("command.commands.kill.single_entity", sender.getDisplayName()); + source.sendTranslatableMessage("command.commands.kill.single_entity", TextFormatting.scoped(sender.getDisplayName())); return Command.SINGLE_SUCCESS; }) diff --git a/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandTeleport.java b/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandTeleport.java index 171d645df..4709551af 100644 --- a/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandTeleport.java +++ b/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandTeleport.java @@ -11,6 +11,7 @@ import net.minecraft.core.net.command.CommandManager; import net.minecraft.core.net.command.arguments.ArgumentTypeDimension; import net.minecraft.core.net.command.util.CommandHelper; import net.minecraft.core.net.command.CommandSource; +import net.minecraft.core.net.command.TextFormatting; import net.minecraft.core.net.command.arguments.ArgumentTypeEntity; import net.minecraft.core.net.command.arguments.ArgumentTypeVector3d; import net.minecraft.core.net.command.exceptions.CommandExceptions; @@ -34,7 +35,7 @@ public class CommandTeleport implements CommandManager.CommandRegistry { if (source.getSender() != null) { source.movePlayerToDimension(source.getSender(), dimension.id); source.teleportPlayerToPos(source.getSender(), targetCoordinates.getX(source), targetCoordinates.getY(source, true), targetCoordinates.getZ(source)); - source.sendTranslatableMessage("command.commands.teleport.dimension_location.success_single_entity", source.getSender().getDisplayName(), dimension.languageKey, targetCoordinates.getX(source), targetCoordinates.getY(source, true), targetCoordinates.getZ(source)); + source.sendTranslatableMessage("command.commands.teleport.dimension_location.success_single_entity", TextFormatting.scoped(source.getSender().getDisplayName()), dimension.languageKey, targetCoordinates.getX(source), targetCoordinates.getY(source, true), targetCoordinates.getZ(source)); } else { throw CommandExceptions.notInWorld().create(); } @@ -47,7 +48,7 @@ public class CommandTeleport implements CommandManager.CommandRegistry { if (source.getSender() != null) { source.movePlayerToDimension(source.getSender(), dimension.id); - source.sendTranslatableMessage("command.commands.teleport.dimension.success_single_entity", source.getSender().getDisplayName(), dimension.languageKey); + source.sendTranslatableMessage("command.commands.teleport.dimension.success_single_entity", TextFormatting.scoped(source.getSender().getDisplayName()), dimension.languageKey); } else { throw CommandExceptions.notInWorld().create(); } @@ -62,7 +63,7 @@ public class CommandTeleport implements CommandManager.CommandRegistry { if (source.getSender() != null) { source.teleportPlayerToPos(source.getSender(), targetCoordinates.getX(source), targetCoordinates.getY(source, true), targetCoordinates.getZ(source)); - source.sendTranslatableMessage("command.commands.teleport.location.success_single_entity", source.getSender().getDisplayName(), targetCoordinates.getX(source), targetCoordinates.getY(source, true), targetCoordinates.getZ(source)); + source.sendTranslatableMessage("command.commands.teleport.location.success_single_entity", TextFormatting.scoped(source.getSender().getDisplayName()), targetCoordinates.getX(source), targetCoordinates.getY(source, true), targetCoordinates.getZ(source)); } else { throw CommandExceptions.notInWorld().create(); } diff --git a/game/core/src/main/java/net/minecraft/core/net/command/util/CommandHelper.java b/game/core/src/main/java/net/minecraft/core/net/command/util/CommandHelper.java index 00c9503b1..2592309e7 100644 --- a/game/core/src/main/java/net/minecraft/core/net/command/util/CommandHelper.java +++ b/game/core/src/main/java/net/minecraft/core/net/command/util/CommandHelper.java @@ -10,6 +10,7 @@ import net.minecraft.core.block.Blocks; import net.minecraft.core.block.entity.TileEntity; import net.minecraft.core.entity.Entity; import net.minecraft.core.net.command.CommandSource; +import net.minecraft.core.net.command.TextFormatting; import net.minecraft.core.net.command.helpers.IntegerCoordinates; import net.minecraft.core.util.collection.NamespaceID; import net.minecraft.core.util.helper.MathHelper; @@ -102,7 +103,7 @@ public class CommandHelper { } public static String getEntityName(Entity entity) { - return Entity.getNameFromEntity(entity, true); + return TextFormatting.scoped(Entity.getNameFromEntity(entity, true)); } public static int getVolume(CommandSource source, IntegerCoordinates first, IntegerCoordinates second) throws CommandSyntaxException { diff --git a/game/core/src/main/java/net/minecraft/core/player/inventory/CreativeMenuContents.java b/game/core/src/main/java/net/minecraft/core/player/inventory/CreativeMenuContents.java index fde062915..ee8b0f514 100644 --- a/game/core/src/main/java/net/minecraft/core/player/inventory/CreativeMenuContents.java +++ b/game/core/src/main/java/net/minecraft/core/player/inventory/CreativeMenuContents.java @@ -207,8 +207,7 @@ public final class CreativeMenuContents { list.add(new ItemStack(Items.HANDCANNON_UNLOADED)); list.add(new ItemStack(Items.AMMO_CHARGE_EXPLOSIVE)); - list.add(new ItemStack(Items.AMMO_FIREBALL)); - addGap(list, 2); + addGap(list, 3); list.add(new ItemStack(Items.TOOL_CALENDAR)); newLine(list); } diff --git a/game/core/src/main/java/net/minecraft/core/world/LevelListener.java b/game/core/src/main/java/net/minecraft/core/world/LevelListener.java index 96a6e15e7..deb4521fd 100644 --- a/game/core/src/main/java/net/minecraft/core/world/LevelListener.java +++ b/game/core/src/main/java/net/minecraft/core/world/LevelListener.java @@ -66,6 +66,10 @@ public interface LevelListener * data: unused */ int EVENT_ACID_SPREAD = 1010; + /** + * data: the {@link net.minecraft.core.util.helper.Direction} id of the mesh's open water face + */ + int EVENT_MESH_SIPHON_PARTICLES = 1011; /** * Redraw block in singleplayer and send block change packet in multiplayer.
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/tree/WorldFeatureTreeThorn.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/tree/WorldFeatureTreeThorn.java index 191d15527..17a83e367 100644 --- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/tree/WorldFeatureTreeThorn.java +++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/tree/WorldFeatureTreeThorn.java @@ -8,14 +8,17 @@ import java.util.ArrayList; import java.util.List; public class WorldFeatureTreeThorn extends WorldFeatureSpoonerTreeProcedural { + private final int baseHeight; + @MethodParametersAnnotation(names = {"height", "trunkId", "trunkData", "leavesId", "leavesData"}) public WorldFeatureTreeThorn(int height, int trunkId, int trunkData, int leavesId, int leavesData) { super(height, trunkId, trunkData, leavesId, leavesData); + this.baseHeight = height; } @Override protected void prepare(World world) { - this.height = height + random.nextInt(3); + this.height = baseHeight + random.nextInt(3); this.foliageShape = new double[] { 4.5, 3.5, 2 }; this.branchSlope = 1; this.trunkRadius = 1; diff --git a/game/core/src/main/resources/assets/minecraft/lang/en_US/gui.lang b/game/core/src/main/resources/assets/minecraft/lang/en_US/gui.lang index d97a06674..3bc1b04c6 100644 --- a/game/core/src/main/resources/assets/minecraft/lang/en_US/gui.lang +++ b/game/core/src/main/resources/assets/minecraft/lang/en_US/gui.lang @@ -320,6 +320,7 @@ gui.options.hudeditor.snap_to_fill=Snap To Fill gui.options.hudeditor.scale=GUI Scale gui.options.hudeditor.chat_width=Chat Width gui.options.hudeditor.chat_height=Chat Height +gui.options.hudeditor.chat_full_width_backdrop=Full Width Chat Backdrop gui.sound_test.label.title=Sound Test gui.sound_test.label.repo=Sound Repository: %s diff --git a/game/server/src/main/java/net/minecraft/server/entity/player/PlayerServer.java b/game/server/src/main/java/net/minecraft/server/entity/player/PlayerServer.java index 3d3aab87e..385f139a7 100644 --- a/game/server/src/main/java/net/minecraft/server/entity/player/PlayerServer.java +++ b/game/server/src/main/java/net/minecraft/server/entity/player/PlayerServer.java @@ -470,7 +470,12 @@ public class PlayerServer extends Player implements ContainerListener { if (isPlayerSleeping()) { EntityTrackerImpl entitytracker = mcServer.getEntityTracker(dimension); - entitytracker.sendPacketToTrackedPlayersAndTrackedEntity(this, new PacketAnimate(this, 3)); + PacketAnimate wakePacket = new PacketAnimate(this, 3); + entitytracker.sendPacketToTrackedPlayers(this, wakePacket); + if (playerNetServerHandler != null) + { + playerNetServerHandler.sendPacket(wakePacket); + } super.wakeUpPlayer(flag, flag1); } if (playerNetServerHandler != null) diff --git a/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerServer.java b/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerServer.java index f9368fbc7..7c8f7725f 100644 --- a/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerServer.java +++ b/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerServer.java @@ -558,8 +558,9 @@ public class PacketHandlerServer extends PacketHandler } case PacketUpdatePlayerState.STATE_UN_SNEAK -> this.playerEntity.setSneaking(false); case PacketUpdatePlayerState.STATE_LEAVE_BED -> { - this.playerEntity.wakeUpPlayer(false, true); - this.hasMoved = false; + if (this.playerEntity.isPlayerSleeping()) { + this.playerEntity.wakeUpPlayer(false, true); + } } case PacketUpdatePlayerState.STATE_NO_CLIP -> { this.playerEntity.setNoclip(true); diff --git a/game/server/src/main/java/net/minecraft/server/world/WorldServer.java b/game/server/src/main/java/net/minecraft/server/world/WorldServer.java index 216a26b2b..7e2f79cc2 100644 --- a/game/server/src/main/java/net/minecraft/server/world/WorldServer.java +++ b/game/server/src/main/java/net/minecraft/server/world/WorldServer.java @@ -135,8 +135,6 @@ public class WorldServer extends World { send_message: { if (MinecraftServer.getInstance() == null) break send_message; - if (players.size() <= 1) break send_message; - if (this.getPlayersRequiredToSkipNight() <= 1) break send_message; Player sleeper = null; for (final var p : players) { diff --git a/util/datagen/src/main/java/net/minecraft/datagen/ConsolidationGrouper.java b/util/datagen/src/main/java/net/minecraft/datagen/ConsolidationGrouper.java new file mode 100644 index 000000000..2f96f67e6 --- /dev/null +++ b/util/datagen/src/main/java/net/minecraft/datagen/ConsolidationGrouper.java @@ -0,0 +1,108 @@ +package net.minecraft.datagen; + +import net.minecraft.core.data.registry.recipe.RecipeGroup; +import net.minecraft.core.data.registry.recipe.RecipeSymbol; +import net.minecraft.core.data.registry.recipe.entry.RecipeEntryCrafting; +import net.minecraft.core.data.registry.recipe.entry.RecipeEntryCraftingShaped; +import net.minecraft.core.data.registry.recipe.entry.RecipeEntryCraftingShapeless; +import net.minecraft.core.item.ItemStack; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +final class ConsolidationGrouper { + private ConsolidationGrouper() {} + + private static final String[][] FAMILY_PREFIXES = { + {"pressure_plate_", "pressure_plates"}, + {"fence_gate_", "fence_gates"}, + {"fence_", "fences"}, + {"slab_", "slabs"}, + {"stairs_", "stairs"}, + {"wall_", "walls"}, + {"button_", "buttons"}, + {"trapdoor_", "trapdoors"}, + {"chest_", "chests"}, + {"door_", "doors"}, + {"sign", "signs"}, + {"brick_", "bricks"}, + {"planks_", "planks"}, + }; + + private static final Set MATERIALS = Set.of( + "diamond", "gold", "iron", "steel", "lapis", "olivine", "quartz", "rubyglass", "clay", "glass"); + + private static final Set RAW_STONE = Set.of( + "stone", "basalt", "granite", "limestone", "permafrost", "netherrack", + "gloomstone", "sandstone", "marble", "brimstone", "slate"); + + static void apply(RecipeGroup workbench) { + for (Object o : workbench) { + if (!(o instanceof RecipeEntryCrafting recipe)) continue; + if (recipe.consolidationGroup != null) continue; // hand-declared groups win + ItemStack output = outputOf(recipe); + if (output == null) continue; + String family = familyOf(cleanName(output)); + if (family == null) continue; + String bucket = bucketOfInputs(recipe); + if (bucket == null) continue; + recipe.consolidationGroup = bucket + "_" + family; + } + } + + private static String familyOf(String outputName) { + for (String[] entry : FAMILY_PREFIXES) { + if (outputName.startsWith(entry[0])) return entry[1]; + } + + if (outputName.endsWith("_polished")) return "polished"; + return null; + } + + private static String bucketOfInputs(RecipeEntryCrafting recipe) { + for (RecipeSymbol symbol : inputsOf(recipe)) { + if (symbol == null || symbol.getItemGroup() != null || symbol.getStack() == null) continue; + String bucket = bucketOf(cleanName(symbol.getStack())); + if (bucket != null) return bucket; + } + return null; + } + + static String bucketOf(String inputName) { + if (inputName.startsWith("planks_oak") || inputName.startsWith("log_")) return "wood"; // planks + logs + if (inputName.startsWith("cobble_")) return "cobble"; + if (inputName.endsWith("_polished")) return "polished"; + if (inputName.startsWith("brick_") || inputName.endsWith("_brick")) { + return containsMaterial(inputName) ? "material_brick" : "stone_brick"; + } + if (RAW_STONE.contains(inputName)) return "stone"; + if (containsMaterial(inputName)) return "material"; + return null; + } + + private static boolean containsMaterial(String name) { + for (String material : MATERIALS) { + if (name.contains(material)) return true; + } + return false; + } + + private static String cleanName(ItemStack stack) { + String value = stack.getItem().namespaceID.value(); + int slash = value.indexOf('/'); + return slash >= 0 ? value.substring(slash + 1) : value; + } + + private static ItemStack outputOf(RecipeEntryCrafting recipe) { + if (recipe instanceof RecipeEntryCraftingShaped shaped) return shaped.getOutput(); + if (recipe instanceof RecipeEntryCraftingShapeless shapeless) return shapeless.getOutput(); + return null; + } + + private static List inputsOf(RecipeEntryCrafting recipe) { + if (recipe instanceof RecipeEntryCraftingShaped shaped) return Arrays.asList(shaped.getInput()); + if (recipe instanceof RecipeEntryCraftingShapeless shapeless) return shapeless.getInput(); + return List.of(); + } +} diff --git a/util/datagen/src/main/java/net/minecraft/datagen/RecipesGenerator.java b/util/datagen/src/main/java/net/minecraft/datagen/RecipesGenerator.java index 8d1c21f92..c69e02cfe 100644 --- a/util/datagen/src/main/java/net/minecraft/datagen/RecipesGenerator.java +++ b/util/datagen/src/main/java/net/minecraft/datagen/RecipesGenerator.java @@ -57,6 +57,7 @@ public class RecipesGenerator { List remainingEntries; WorkbenchGenerator.generate(); + ConsolidationGrouper.apply(Registries.RECIPES.WORKBENCH); if (DEBUG) { remainingEntries = getMissingIds(Registries.RECIPES.WORKBENCH, new File("src/main/resources/recipes/workbench.json")); if (remainingEntries.isEmpty()) { diff --git a/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java b/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java index ec3d8fba8..828a9b86e 100644 --- a/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java +++ b/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java @@ -18,18 +18,20 @@ import static net.minecraft.datagen.RecipesGenerator.CORE_NAMESPACE; class WorkbenchGenerator { public static void generate() { - generatePickaxes(); - generateAxes(); - generateShovels(); - generateHoes(); - generateSwords(); + + RecipeBuilder.consolidationGroup("pickaxes", WorkbenchGenerator::generatePickaxes); + RecipeBuilder.consolidationGroup("axes", WorkbenchGenerator::generateAxes); + RecipeBuilder.consolidationGroup("shovels", WorkbenchGenerator::generateShovels); + RecipeBuilder.consolidationGroup("hoes", WorkbenchGenerator::generateHoes); + RecipeBuilder.consolidationGroup("swords", WorkbenchGenerator::generateSwords); + generateMiscTools(); - generateHelmets(); - generateChestPlates(); - generateLeggings(); - generateBoots(); - generateWolfArmors(); + RecipeBuilder.consolidationGroup("helmets", WorkbenchGenerator::generateHelmets); + RecipeBuilder.consolidationGroup("chestplates", WorkbenchGenerator::generateChestPlates); + RecipeBuilder.consolidationGroup("leggings", WorkbenchGenerator::generateLeggings); + RecipeBuilder.consolidationGroup("boots", WorkbenchGenerator::generateBoots); + RecipeBuilder.consolidationGroup("wolf_armors", WorkbenchGenerator::generateWolfArmors); generateBlockCompression(); generateBlockDecompression(); @@ -61,8 +63,8 @@ class WorkbenchGenerator { generateUndyeingRecipes(); generateDyeingRecipes(); - generateStatueRecipes(); - generateAcidConversions(); + RecipeBuilder.consolidationGroup("statues", WorkbenchGenerator::generateStatueRecipes); + RecipeBuilder.consolidationGroup("acid_conversions", WorkbenchGenerator::generateAcidConversions); generateMiscNewRecipes(); @@ -92,18 +94,12 @@ class WorkbenchGenerator { .addInput('X', Items.WHEAT) .create("basket", Items.BASKET.getDefaultStack()); - RecipeBuilder.Shaped(CORE_NAMESPACE) - .setShape( - "#", - "#", - "#") - .addInput('#', Items.PAPER) - .create("book_vertical", Items.BOOK.getDefaultStack()); - - RecipeBuilder.Shaped(CORE_NAMESPACE) - .setShape("###") - .addInput('#', Items.PAPER) - .create("book_horizontal", Items.BOOK.getDefaultStack()); + RecipeBuilder.Shapeless(CORE_NAMESPACE) + .addInput(Items.PAPER) + .addInput(Items.PAPER) + .addInput(Items.PAPER) + .addInput(Items.LEATHER) + .create("book", Items.BOOK.getDefaultStack()); RecipeBuilder.Shaped(CORE_NAMESPACE) .setShape( @@ -198,21 +194,23 @@ class WorkbenchGenerator { .addInput('W', "minecraft:wools") .create("painting", new ItemStack(Items.PAINTING, 1, Items.PAINTING.getMaxDamage())); - RecipeBuilder.Shaped(CORE_NAMESPACE) - .setShape( - "XXX", - "X X", - "XXX") - .addInput('X', Items.INGOT_IRON) - .create("mesh", new ItemStack(Blocks.MESH, 8)); + RecipeBuilder.consolidationGroup("mesh", () -> { + RecipeBuilder.Shaped(CORE_NAMESPACE) + .setShape( + "XXX", + "X X", + "XXX") + .addInput('X', Items.INGOT_IRON) + .create("mesh", new ItemStack(Blocks.MESH, 8)); - RecipeBuilder.Shaped(CORE_NAMESPACE) - .setShape( - "XXX", - "X X", - "XXX") - .addInput('X', Items.INGOT_GOLD) - .create("mesh_gold", new ItemStack(Blocks.MESH_GOLD, 8)); + RecipeBuilder.Shaped(CORE_NAMESPACE) + .setShape( + "XXX", + "X X", + "XXX") + .addInput('X', Items.INGOT_GOLD) + .create("mesh_gold", new ItemStack(Blocks.MESH_GOLD, 8)); + }); RecipeBuilder.Shaped(CORE_NAMESPACE) .setShape( @@ -258,40 +256,57 @@ class WorkbenchGenerator { shape22.addInput('#', Items.AMMO_SNOWBALL).create("snow_block", Blocks.BLOCK_SNOW.getDefaultStack()); shape22.addInput('#', Items.STRING).create("string_to_wool", Blocks.WOOL.getDefaultStack()); shape22.addInput('#', Items.AMMO_PEBBLE).create("pebbles_to_cobblestone", new ItemStack(Blocks.COBBLE_STONE, 2)); - shape22.addInput('#', Blocks.SAND).create("sandstone", new ItemStack(Blocks.SANDSTONE, 2)); + + RecipeBuilder.consolidationGroup("sand_to_stone", () -> + shape22.addInput('#', Blocks.SAND).create("sandstone", new ItemStack(Blocks.SANDSTONE, 2)) + ); + RecipeBuilder.consolidationGroup("sand_to_stone", () -> + shape22.addInput('#', Blocks.BRIMSAND).create("brimstone", new ItemStack(Blocks.BRIMSTONE,2)) + ); + shape22.addInput('#', "minecraft:planks").create("workbench", Blocks.WORKBENCH.getDefaultStack()); - RecipeBuilder.Shaped(CORE_NAMESPACE) - .setShape( - "#X", - "X#") - .addInput('#', Items.AMMO_PEBBLE) - .addInput('X', Items.COAL) - .create("pebbles_to_basalt", new ItemStack(Blocks.COBBLE_BASALT, 2)); + RecipeBuilder.consolidationGroup("cobble_variants", () -> { + RecipeBuilder.Shaped(CORE_NAMESPACE) + .setShape( + "#X", + "X#") + .addInput('#', Items.AMMO_PEBBLE) + .addInput('X', Items.COAL) + .create("pebbles_to_basalt", new ItemStack(Blocks.COBBLE_BASALT, 2)); - RecipeBuilder.Shaped(CORE_NAMESPACE) - .setShape( - "#X", - "X#") - .addInput('#', Items.AMMO_PEBBLE) - .addInput('X', Items.QUARTZ) - .create("pebbles_to_granite", new ItemStack(Blocks.COBBLE_GRANITE, 2)); + RecipeBuilder.Shaped(CORE_NAMESPACE) + .setShape( + "#X", + "X#") + .addInput('#', Items.AMMO_PEBBLE) + .addInput('X', Items.QUARTZ) + .create("pebbles_to_granite", new ItemStack(Blocks.COBBLE_GRANITE, 2)); - RecipeBuilder.Shaped(CORE_NAMESPACE) - .setShape( - "#X", - "X#") - .addInput('#', Items.AMMO_PEBBLE) - .addInput('X', Blocks.SAND) - .create("pebbles_to_limestone", new ItemStack(Blocks.COBBLE_LIMESTONE, 2)); + RecipeBuilder.Shaped(CORE_NAMESPACE) + .setShape( + "#X", + "X#") + .addInput('#', Items.AMMO_PEBBLE) + .addInput('X', Blocks.SAND) + .create("pebbles_to_limestone", new ItemStack(Blocks.COBBLE_LIMESTONE, 2)); - RecipeBuilder.Shaped(CORE_NAMESPACE) - .setShape( - "#X", - "X#") - .addInput('#', Items.AMMO_PEBBLE) - .addInput('X', Blocks.ICE) - .create("pebbles_to_permafrost", new ItemStack(Blocks.COBBLE_PERMAFROST, 2)); + RecipeBuilder.Shaped(CORE_NAMESPACE) + .setShape( + "#X", + "X#") + .addInput('#', Items.AMMO_PEBBLE) + .addInput('X', Blocks.ICE) + .create("pebbles_to_permafrost", new ItemStack(Blocks.COBBLE_PERMAFROST, 2)); + + RecipeBuilder.Shaped(CORE_NAMESPACE) + .setShape( + "#X", + "X#") + .addInput('#', Items.AMMO_PEBBLE) + .addInput('X', Blocks.OBSIDIAN) + .create("pebbles_to_gloomstone", new ItemStack(Blocks.COBBLE_GLOOMSTONE, 2)); + }); RecipeBuilder.Shaped(CORE_NAMESPACE) .setShape( @@ -316,21 +331,23 @@ class WorkbenchGenerator { .addInput('#', Items.STRING) .create("rope", new ItemStack(Items.ROPE, 2)); - RecipeBuilder.Shaped(CORE_NAMESPACE) - .setShape( - "#X", - "X#") - .addInput('#', Blocks.GLASS) - .addInput('X', Items.INGOT_STEEL_CRUDE) - .create("steel_glass", new ItemStack(Blocks.GLASS_STEEL, 4)); + RecipeBuilder.consolidationGroup("treated_glass", () -> { + RecipeBuilder.Shaped(CORE_NAMESPACE) + .setShape( + "#X", + "X#") + .addInput('#', Blocks.GLASS) + .addInput('X', Items.INGOT_STEEL_CRUDE) + .create("steel_glass", new ItemStack(Blocks.GLASS_STEEL, 4)); - RecipeBuilder.Shaped(CORE_NAMESPACE) - .setShape( - "#X", - "X#") - .addInput('#', Blocks.GLASS) - .addInput('X', Items.OLIVINE) - .create("tinted_glass", new ItemStack(Blocks.GLASS_TINTED, 4)); + RecipeBuilder.Shaped(CORE_NAMESPACE) + .setShape( + "#X", + "X#") + .addInput('#', Blocks.GLASS) + .addInput('X', Items.OLIVINE) + .create("tinted_glass", new ItemStack(Blocks.GLASS_TINTED, 4)); + }); RecipeBuilder.Shaped(CORE_NAMESPACE) .setShape( @@ -340,37 +357,50 @@ class WorkbenchGenerator { .addInput('X', "minecraft:planks") .create("paper_wall", new ItemStack(Blocks.PAPER_WALL, 4)); - RecipeBuilder.Shaped(CORE_NAMESPACE) - .setShape( - "X", - "#") - .addInput('#', Items.STICK) - .addInput('X', Items.COAL) - .create("torch_coal", new ItemStack(Blocks.TORCH_COAL, 4)); + RecipeBuilder.consolidationGroup("torches", () -> { + RecipeBuilder.Shaped(CORE_NAMESPACE) + .setShape( + "X", + "#") + .addInput('#', Items.STICK) + .addInput('X', Items.COAL) + .create("torch_coal", new ItemStack(Blocks.TORCH_COAL, 4)); - RecipeBuilder.Shaped(CORE_NAMESPACE) - .setShape( - "X", - "#") - .addInput('#', Items.STICK) - .addInput('X', Items.COAL, 1) - .create("torch_charcoal", new ItemStack(Blocks.TORCH_COAL, 4)); + RecipeBuilder.Shaped(CORE_NAMESPACE) + .setShape( + "X", + "#") + .addInput('#', Items.STICK) + .addInput('X', Items.COAL, 1) + .create("torch_charcoal", new ItemStack(Blocks.TORCH_COAL, 4)); - RecipeBuilder.Shaped(CORE_NAMESPACE) - .setShape( - "X", - "#") - .addInput('#', Items.STICK) - .addInput('X', Items.NETHERCOAL) - .create("torch_nethercoal", new ItemStack(Blocks.TORCH_COAL, 8)); + RecipeBuilder.Shaped(CORE_NAMESPACE) + .setShape( + "X", + "#") + .addInput('#', Items.STICK) + .addInput('X', Items.NETHERCOAL) + .create("torch_nethercoal", new ItemStack(Blocks.TORCH_COAL, 8)); + }); - RecipeBuilder.Shaped(CORE_NAMESPACE) - .setShape( - "X", - "#") - .addInput('X', Blocks.PUMPKIN_CARVED_IDLE) - .addInput('#', Blocks.TORCH_COAL) - .create("carved_pumpkin_to_jack_o_lantern", Blocks.PUMPKIN_CARVED_ACTIVE.getDefaultStack()); + + RecipeBuilder.consolidationGroup("jack_o_lanterns", () -> { + RecipeBuilder.Shaped(CORE_NAMESPACE) + .setShape( + "X", + "#") + .addInput('X', Blocks.PUMPKIN_CARVED_IDLE) + .addInput('#', Blocks.TORCH_COAL) + .create("carved_pumpkin_to_jack_o_lantern", Blocks.PUMPKIN_CARVED_ACTIVE.getDefaultStack()); + + RecipeBuilder.Shaped(CORE_NAMESPACE) + .setShape( + "X", + "#") + .addInput('X', Blocks.PUMPKIN_CARVED_IDLE) + .addInput('#', Blocks.TORCH_REDSTONE_ACTIVE) + .create("carved_pumpkin_to_redstone_jack_o_lantern", Blocks.PUMPKIN_REDSTONE.getDefaultStack()); + }); RecipeBuilder.Shapeless(CORE_NAMESPACE) .addInput(Blocks.SPONGE_DRY) @@ -386,23 +416,23 @@ class WorkbenchGenerator { .addInput(Blocks.PUMPKIN) .create("pumpkin_to_pumpkin_seeds", new ItemStack(Items.SEEDS_PUMPKIN, 4)); - RecipeBuilder.Shapeless(CORE_NAMESPACE) - .addInput(Blocks.SLATE) - .create("slate_to_slate_layers", new ItemStack(Blocks.LAYER_SLATE, 8)); + RecipeBuilder.consolidationGroup("layers", () -> { + RecipeBuilder.Shapeless(CORE_NAMESPACE) + .addInput(Blocks.SLATE) + .create("slate_to_slate_layers", new ItemStack(Blocks.LAYER_SLATE, 8)); - RecipeBuilder.Shapeless(CORE_NAMESPACE) - .addInput(Blocks.BLOCK_ASH) - .create("ash_to_ash_layers", new ItemStack(Blocks.LAYER_ASH, 8)); + RecipeBuilder.Shapeless(CORE_NAMESPACE) + .addInput(Blocks.BLOCK_ASH) + .create("ash_to_ash_layers", new ItemStack(Blocks.LAYER_ASH, 8)); - RecipeBuilder.Shapeless(CORE_NAMESPACE) - .addInput(Blocks.LEAVES_OAK) - .create("oak_leaves_to_leaf_pile", new ItemStack(Blocks.LAYER_LEAVES_OAK, 8)); + RecipeBuilder.Shapeless(CORE_NAMESPACE) + .addInput(Blocks.LEAVES_OAK) + .create("oak_leaves_to_leaf_pile", new ItemStack(Blocks.LAYER_LEAVES_OAK, 8)); - RecipeBuilder.Shaped(CORE_NAMESPACE) - .setShape( - "##") - .addInput('#', Items.AMMO_SNOWBALL) - .create("snowballs_to_snow_layer", new ItemStack(Blocks.LAYER_SNOW, 2)); + RecipeBuilder.Shapeless(CORE_NAMESPACE) + .addInput(Blocks.BLOCK_SNOW) + .create("snow_block_to_snow_layers", new ItemStack(Blocks.LAYER_SNOW, 8)); + }); RecipeBuilder.Shaped(CORE_NAMESPACE) .setShape( @@ -452,23 +482,25 @@ class WorkbenchGenerator { .addInput('S', Items.SULFUR) .create("ember", Blocks.EMBER.getDefaultStack()); - RecipeBuilder.Shapeless(CORE_NAMESPACE) - .addInput(Items.SULFUR) - .addInput(Items.COAL) - .addInput(Items.PAPER) - .create("gunpowder_with_coal", new ItemStack(Items.GUNPOWDER, 2)); - - RecipeBuilder.Shapeless(CORE_NAMESPACE) - .addInput(Items.SULFUR) - .addInput(new ItemStack(Items.COAL, 1, 1)) - .addInput(Items.PAPER) - .create("gunpowder_with_charcoal", new ItemStack(Items.GUNPOWDER, 2)); - - RecipeBuilder.Shapeless(CORE_NAMESPACE) - .addInput(Items.SULFUR) - .addInput(Items.NETHERCOAL) - .addInput(Items.PAPER) - .create("gunpowder_with_nethercoal", new ItemStack(Items.GUNPOWDER, 4)); + RecipeBuilder.consolidationGroup("gunpowder", () -> { + RecipeBuilder.Shapeless(CORE_NAMESPACE) + .addInput(Items.SULFUR) + .addInput(Items.COAL) + .addInput(Items.PAPER) + .create("gunpowder_with_coal", new ItemStack(Items.GUNPOWDER, 2)); + + RecipeBuilder.Shapeless(CORE_NAMESPACE) + .addInput(Items.SULFUR) + .addInput(new ItemStack(Items.COAL, 1, 1)) + .addInput(Items.PAPER) + .create("gunpowder_with_charcoal", new ItemStack(Items.GUNPOWDER, 2)); + + RecipeBuilder.Shapeless(CORE_NAMESPACE) + .addInput(Items.SULFUR) + .addInput(Items.NETHERCOAL) + .addInput(Items.PAPER) + .create("gunpowder_with_nethercoal", new ItemStack(Items.GUNPOWDER, 4)); + }); RecipeBuilder.Shapeless(CORE_NAMESPACE) .addInput(Blocks.RUBYGLASS_COLUMN) @@ -576,11 +608,15 @@ class WorkbenchGenerator { " X", "X "); - shears.addInput('X', Items.INGOT_IRON).create("shears", Items.TOOL_SHEARS.getDefaultStack()); - shears.addInput('X', Items.INGOT_STEEL).create("steel_shears", Items.TOOL_SHEARS_STEEL.getDefaultStack()); + RecipeBuilder.consolidationGroup("shears", () -> { + shears.addInput('X', Items.INGOT_IRON).create("shears", Items.TOOL_SHEARS.getDefaultStack()); + shears.addInput('X', Items.INGOT_STEEL).create("steel_shears", Items.TOOL_SHEARS_STEEL.getDefaultStack()); + }); - RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Items.INGOT_IRON).addInput(Items.FLINT).create("fire_striker", Items.TOOL_FIRESTRIKER_IRON.getDefaultStack()); - RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Items.INGOT_STEEL).addInput(Items.FLINT).create("steel_fire_striker", Items.TOOL_FIRESTRIKER_STEEL.getDefaultStack()); + RecipeBuilder.consolidationGroup("fire_strikers", () -> { + RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Items.INGOT_IRON).addInput(Items.FLINT).create("fire_striker", Items.TOOL_FIRESTRIKER_IRON.getDefaultStack()); + RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Items.INGOT_STEEL).addInput(Items.FLINT).create("steel_fire_striker", Items.TOOL_FIRESTRIKER_STEEL.getDefaultStack()); + }); RecipeBuilder.Shaped(CORE_NAMESPACE) .setShape( @@ -784,37 +820,43 @@ class WorkbenchGenerator { "XXX" ); - block.addInput('X', Items.INGOT_GOLD).create("block_of_gold", Blocks.BLOCK_GOLD.getDefaultStack()); - block.addInput('X', Items.INGOT_IRON).create("block_of_iron", Blocks.BLOCK_IRON.getDefaultStack()); - block.addInput('X', Items.INGOT_STEEL).create("block_of_steel", Blocks.BLOCK_STEEL.getDefaultStack()); - block.addInput('X', Items.DYE, DyeColor.BLUE.itemMeta).create("block_of_lapis_lazuli", Blocks.BLOCK_LAPIS.getDefaultStack()); - block.addInput('X', Items.DUST_REDSTONE).create("block_of_redstone", Blocks.BLOCK_REDSTONE.getDefaultStack()); - block.addInput('X', Items.DIAMOND).create("block_of_diamond", Blocks.BLOCK_DIAMOND.getDefaultStack()); - block.addInput('X', Items.QUARTZ).create("block_of_quartz", Blocks.BLOCK_QUARTZ.getDefaultStack()); - block.addInput('X', Items.SUGARCANE).create("block_of_sugarcane", Blocks.BLOCK_SUGARCANE.getDefaultStack()); - block.addInput('X', Items.RUBYGLASS).create("block_of_rubyglass", Blocks.BLOCK_RUBYGLASS.getDefaultStack()); - - blockFuel.addInput('X', Items.COAL, 0).create("block_of_coal", Blocks.BLOCK_COAL.getDefaultStack()); - blockFuel.addInput('X', Items.COAL, 1).create("block_of_charcoal", Blocks.BLOCK_CHARCOAL.getDefaultStack()); - blockFuel.addInput('X', Items.NETHERCOAL).create("block_of_nethercoal", Blocks.BLOCK_NETHER_COAL.getDefaultStack()); - blockFuel.addInput('X', Items.OLIVINE).create("block_of_olivine", Blocks.BLOCK_OLIVINE.getDefaultStack()); + RecipeBuilder.consolidationGroup("material_blocks", () -> { + block.addInput('X', Items.INGOT_GOLD).create("block_of_gold", Blocks.BLOCK_GOLD.getDefaultStack()); + block.addInput('X', Items.INGOT_IRON).create("block_of_iron", Blocks.BLOCK_IRON.getDefaultStack()); + block.addInput('X', Items.INGOT_STEEL).create("block_of_steel", Blocks.BLOCK_STEEL.getDefaultStack()); + block.addInput('X', Items.DYE, DyeColor.BLUE.itemMeta).create("block_of_lapis_lazuli", Blocks.BLOCK_LAPIS.getDefaultStack()); + block.addInput('X', Items.DUST_REDSTONE).create("block_of_redstone", Blocks.BLOCK_REDSTONE.getDefaultStack()); + block.addInput('X', Items.DIAMOND).create("block_of_diamond", Blocks.BLOCK_DIAMOND.getDefaultStack()); + block.addInput('X', Items.QUARTZ).create("block_of_quartz", Blocks.BLOCK_QUARTZ.getDefaultStack()); + block.addInput('X', Items.SUGARCANE).create("block_of_sugarcane", Blocks.BLOCK_SUGARCANE.getDefaultStack()); + block.addInput('X', Items.RUBYGLASS).create("block_of_rubyglass", Blocks.BLOCK_RUBYGLASS.getDefaultStack()); + }); + + RecipeBuilder.consolidationGroup("burnable_material_blocks", () -> { + blockFuel.addInput('X', Items.COAL, 0).create("block_of_coal", Blocks.BLOCK_COAL.getDefaultStack()); + blockFuel.addInput('X', Items.COAL, 1).create("block_of_charcoal", Blocks.BLOCK_CHARCOAL.getDefaultStack()); + blockFuel.addInput('X', Items.NETHERCOAL).create("block_of_nethercoal", Blocks.BLOCK_NETHER_COAL.getDefaultStack()); + blockFuel.addInput('X', Items.OLIVINE).create("block_of_olivine", Blocks.BLOCK_OLIVINE.getDefaultStack()); + }); } private static void generateBlockDecompression() { - RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_GOLD).create("block_of_gold_to_gold_ingot", new ItemStack(Items.INGOT_GOLD, 9)); - RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_IRON).create("block_of_iron_to_iron_ingot", new ItemStack(Items.INGOT_IRON, 9)); - RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_STEEL).create("block_of_steel_to_steel_ingot", new ItemStack(Items.INGOT_STEEL, 9)); - RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_LAPIS).create("block_of_lapis_lazuli_to_lapis_lazuli", new ItemStack(Items.DYE, 9, DyeColor.BLUE.itemMeta)); - RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_REDSTONE).create("block_of_redstone_to_redstone", new ItemStack(Items.DUST_REDSTONE, 9)); - RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_DIAMOND).create("block_of_diamond_to_diamond", new ItemStack(Items.DIAMOND, 9)); - RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_QUARTZ).create("block_of_quartz_to_quartz", new ItemStack(Items.QUARTZ, 9)); - RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_RUBYGLASS).create("block_of_rubyglass_to_rubyglass", new ItemStack(Items.RUBYGLASS, 9)); - RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_SUGARCANE).create("block_of_sugarcane_to_sugarcane", new ItemStack(Items.SUGARCANE, 9)); - - RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_COAL).create("block_of_coal_to_coal", new ItemStack(Items.COAL, 8, 0)); - RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_CHARCOAL).create("block_of_charcoal_to_charcoal", new ItemStack(Items.COAL, 8, 1)); - RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_NETHER_COAL).create("block_of_nethercoal_to_nethercoal", new ItemStack(Items.NETHERCOAL, 8)); - RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_OLIVINE).create("block_of_olivine_to_olivine", new ItemStack(Items.OLIVINE, 8)); + RecipeBuilder.consolidationGroup("material_block_breakdown", () -> { + RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_GOLD).create("block_of_gold_to_gold_ingot", new ItemStack(Items.INGOT_GOLD, 9)); + RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_IRON).create("block_of_iron_to_iron_ingot", new ItemStack(Items.INGOT_IRON, 9)); + RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_STEEL).create("block_of_steel_to_steel_ingot", new ItemStack(Items.INGOT_STEEL, 9)); + RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_LAPIS).create("block_of_lapis_lazuli_to_lapis_lazuli", new ItemStack(Items.DYE, 9, DyeColor.BLUE.itemMeta)); + RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_REDSTONE).create("block_of_redstone_to_redstone", new ItemStack(Items.DUST_REDSTONE, 9)); + RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_DIAMOND).create("block_of_diamond_to_diamond", new ItemStack(Items.DIAMOND, 9)); + RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_QUARTZ).create("block_of_quartz_to_quartz", new ItemStack(Items.QUARTZ, 9)); + RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_RUBYGLASS).create("block_of_rubyglass_to_rubyglass", new ItemStack(Items.RUBYGLASS, 9)); + RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_SUGARCANE).create("block_of_sugarcane_to_sugarcane", new ItemStack(Items.SUGARCANE, 9)); + + RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_COAL).create("block_of_coal_to_coal", new ItemStack(Items.COAL, 8, 0)); + RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_CHARCOAL).create("block_of_charcoal_to_charcoal", new ItemStack(Items.COAL, 8, 1)); + RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_NETHER_COAL).create("block_of_nethercoal_to_nethercoal", new ItemStack(Items.NETHERCOAL, 8)); + RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.BLOCK_OLIVINE).create("block_of_olivine_to_olivine", new ItemStack(Items.OLIVINE, 8)); + }); } private static void generateBricks() { @@ -825,26 +867,31 @@ class WorkbenchGenerator { ); final int stackSize = 4; - bricks.addInput('X', Items.RUBYGLASS).create("rubyglass_bricks", new ItemStack(Blocks.BRICK_RUBYGLASS, stackSize)); - bricks.addInput('X', Items.DIAMOND).create("diamond_bricks", new ItemStack(Blocks.BRICK_DIAMOND, stackSize)); - bricks.addInput('X', Items.BRICK_CLAY).create("clay_bricks", new ItemStack(Blocks.BRICK_CLAY, stackSize)); - bricks.addInput('X', Items.INGOT_GOLD).create("golden_bricks", new ItemStack(Blocks.BRICK_GOLD, stackSize)); - bricks.addInput('X', Items.INGOT_IRON).create("iron_bricks", new ItemStack(Blocks.BRICK_IRON, stackSize)); - bricks.addInput('X', Items.INGOT_STEEL_CRUDE).create("steel_bricks", new ItemStack(Blocks.BRICK_STEEL, stackSize)); - bricks.addInput('X', Items.DYE, DyeColor.BLUE.itemMeta).create("lapis_lazuli_bricks", new ItemStack(Blocks.BRICK_LAPIS, stackSize)); - bricks.addInput('X', Items.OLIVINE).create("olivine_bricks", new ItemStack(Blocks.BRICK_OLIVINE, stackSize)); - bricks.addInput('X', Items.QUARTZ).create("quartz_bricks", new ItemStack(Blocks.BRICK_QUARTZ, stackSize)); - bricks.addInput('X', Blocks.SANDSTONE).create("sandstone_bricks", new ItemStack(Blocks.BRICK_SANDSTONE, stackSize)); - bricks.addInput('X', Blocks.STONE).create("stone_bricks", new ItemStack(Blocks.BRICK_STONE, stackSize)); - bricks.addInput('X', Blocks.BASALT).create("basalt_bricks", new ItemStack(Blocks.BRICK_BASALT, stackSize)); - bricks.addInput('X', Blocks.GRANITE).create("granite_bricks", new ItemStack(Blocks.BRICK_GRANITE, stackSize)); - bricks.addInput('X', Blocks.LIMESTONE).create("limestone_bricks", new ItemStack(Blocks.BRICK_LIMESTONE, stackSize)); - bricks.addInput('X', Blocks.MARBLE).create("marble_bricks", new ItemStack(Blocks.BRICK_MARBLE, stackSize)); - bricks.addInput('X', Blocks.SLATE).create("slate_bricks", new ItemStack(Blocks.BRICK_SLATE, stackSize)); - bricks.addInput('X', Blocks.PERMAFROST).create("permafrost_bricks", new ItemStack(Blocks.BRICK_PERMAFROST, stackSize)); - bricks.addInput('X', Blocks.NETHERRACK).create("netherrack_bricks", new ItemStack(Blocks.BRICK_NETHERRACK, stackSize)); - bricks.addInput('X', Blocks.GLOOMSTONE).create("gloomstone_bricks", new ItemStack(Blocks.BRICK_GLOOMSTONE, stackSize)); - bricks.addInput('X', Blocks.STONE_POLISHED).create("polished_stone_bricks", new ItemStack(Blocks.BRICK_STONE_POLISHED, stackSize)); + RecipeBuilder.consolidationGroup("material_type_bricks", () -> { + bricks.addInput('X', Items.RUBYGLASS).create("rubyglass_bricks", new ItemStack(Blocks.BRICK_RUBYGLASS, stackSize)); + bricks.addInput('X', Items.DIAMOND).create("diamond_bricks", new ItemStack(Blocks.BRICK_DIAMOND, stackSize)); + bricks.addInput('X', Items.BRICK_CLAY).create("clay_bricks", new ItemStack(Blocks.BRICK_CLAY, stackSize)); + bricks.addInput('X', Items.INGOT_GOLD).create("golden_bricks", new ItemStack(Blocks.BRICK_GOLD, stackSize)); + bricks.addInput('X', Items.INGOT_IRON).create("iron_bricks", new ItemStack(Blocks.BRICK_IRON, stackSize)); + bricks.addInput('X', Items.INGOT_STEEL_CRUDE).create("steel_bricks", new ItemStack(Blocks.BRICK_STEEL, stackSize)); + bricks.addInput('X', Items.DYE, DyeColor.BLUE.itemMeta).create("lapis_lazuli_bricks", new ItemStack(Blocks.BRICK_LAPIS, stackSize)); + bricks.addInput('X', Items.OLIVINE).create("olivine_bricks", new ItemStack(Blocks.BRICK_OLIVINE, stackSize)); + bricks.addInput('X', Items.QUARTZ).create("quartz_bricks", new ItemStack(Blocks.BRICK_QUARTZ, stackSize)); + }); + RecipeBuilder.consolidationGroup("stone_type_bricks", () -> { + bricks.addInput('X', Blocks.SANDSTONE).create("sandstone_bricks", new ItemStack(Blocks.BRICK_SANDSTONE, stackSize)); + bricks.addInput('X', Blocks.STONE).create("stone_bricks", new ItemStack(Blocks.BRICK_STONE, stackSize)); + bricks.addInput('X', Blocks.BASALT).create("basalt_bricks", new ItemStack(Blocks.BRICK_BASALT, stackSize)); + bricks.addInput('X', Blocks.GRANITE).create("granite_bricks", new ItemStack(Blocks.BRICK_GRANITE, stackSize)); + bricks.addInput('X', Blocks.LIMESTONE).create("limestone_bricks", new ItemStack(Blocks.BRICK_LIMESTONE, stackSize)); + bricks.addInput('X', Blocks.MARBLE).create("marble_bricks", new ItemStack(Blocks.BRICK_MARBLE, stackSize)); + bricks.addInput('X', Blocks.SLATE).create("slate_bricks", new ItemStack(Blocks.BRICK_SLATE, stackSize)); + bricks.addInput('X', Blocks.PERMAFROST).create("permafrost_bricks", new ItemStack(Blocks.BRICK_PERMAFROST, stackSize)); + bricks.addInput('X', Blocks.NETHERRACK).create("netherrack_bricks", new ItemStack(Blocks.BRICK_NETHERRACK, stackSize)); + bricks.addInput('X', Blocks.GLOOMSTONE).create("gloomstone_bricks", new ItemStack(Blocks.BRICK_GLOOMSTONE, stackSize)); + bricks.addInput('X', Blocks.BRIMSTONE).create("brimstone_bricks", new ItemStack(Blocks.BRICK_BRIMSTONE, stackSize)); + bricks.addInput('X', Blocks.STONE_POLISHED).create("polished_stone_bricks", new ItemStack(Blocks.BRICK_STONE_POLISHED, stackSize)); + }); } private static void generateSlabs() { @@ -1065,7 +1112,6 @@ class WorkbenchGenerator { RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.LOG_PALM).create(String.format(format, "palm", "yellow"), new ItemStack(Blocks.PLANKS_OAK_PAINTED, stackSize, DyeColor.YELLOW.blockMeta)); RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.LOG_THORN).create(String.format(format, "thorn", "red"), new ItemStack(Blocks.PLANKS_OAK_PAINTED, stackSize, DyeColor.RED.blockMeta)); RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.LOG_PINE).create(String.format(format, "pine", "brown"), new ItemStack(Blocks.PLANKS_OAK_PAINTED, stackSize, DyeColor.BROWN.blockMeta)); - RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.LOG_PETRIFIED).create(String.format(format, "petrified", "gray"), new ItemStack(Blocks.PLANKS_OAK_PAINTED, 2, DyeColor.SILVER.blockMeta)); } private static void generatePolishedStones() { @@ -1094,9 +1140,11 @@ class WorkbenchGenerator { " X " ).addInput('#', Items.DUST_REDSTONE); - info.addInput('X', "minecraft:planks").create("rotary_calendar", Items.TOOL_CALENDAR.getDefaultStack()); - info.addInput('X', Items.INGOT_IRON).create("compass", Items.TOOL_COMPASS.getDefaultStack()); - info.addInput('X', Items.INGOT_GOLD).create("clock", Items.TOOL_CLOCK.getDefaultStack()); + RecipeBuilder.consolidationGroup("info_tools", () -> { + info.addInput('X', "minecraft:planks").create("rotary_calendar", Items.TOOL_CALENDAR.getDefaultStack()); + info.addInput('X', Items.INGOT_IRON).create("compass", Items.TOOL_COMPASS.getDefaultStack()); + info.addInput('X', Items.INGOT_GOLD).create("clock", Items.TOOL_CLOCK.getDefaultStack()); + }); RecipeBuilder.Shaped(CORE_NAMESPACE) .setShape( @@ -1129,6 +1177,15 @@ class WorkbenchGenerator { .addInput(new ItemStack(Items.DYE, 1, DyeColor.BROWN.itemMeta)) .create("cookie", new ItemStack(Items.FOOD_COOKIE, 16)); + RecipeBuilder.Shaped(CORE_NAMESPACE) + .setShape( + "GGG", + "GAG", + "GGG") + .addInput('G', Items.INGOT_GOLD) + .addInput('A', Items.FOOD_APPLE) + .create("apple_gold", Items.FOOD_APPLE_GOLD.getDefaultStack()); + RecipeBuilderShaped cakeBase = RecipeBuilder.Shaped(CORE_NAMESPACE) .setShape( "AAA", @@ -1393,14 +1450,6 @@ class WorkbenchGenerator { .addInput('I', "minecraft:stones") .create("redstone_timer", new ItemStack(Items.TIMER, 1)); - RecipeBuilder.Shaped(CORE_NAMESPACE) - .setShape( - "X", - "#") - .addInput('X', Blocks.PUMPKIN_CARVED_IDLE) - .addInput('#', Blocks.TORCH_REDSTONE_ACTIVE) - .create("carved_pumpkin_to_redstone_jack_o_lantern", Blocks.PUMPKIN_REDSTONE.getDefaultStack()); - RecipeBuilder.Shaped(CORE_NAMESPACE) .setShape( "X X", @@ -1442,9 +1491,9 @@ class WorkbenchGenerator { RecipeBuilder.Shaped(CORE_NAMESPACE) .setShape( - "#Q#", - "#C#", - "#Q#" + "###", + "QCQ", + "###" ) .addInput('#', Blocks.NETHERRACK) .addInput('Q', Items.QUARTZ) @@ -1453,11 +1502,11 @@ class WorkbenchGenerator { RecipeBuilder.Shaped(CORE_NAMESPACE) .setShape( - "XOX", - "ORO", - "XOX") - .addInput('R', Items.DUST_REDSTONE) - .addInput('O', Items.RUBYGLASS) + "XRX", + "DDD", + "XRX") + .addInput('D', Items.DUST_REDSTONE) + .addInput('R', Items.RUBYGLASS) .addInput('X', Blocks.NETHERRACK) .create("conduit", new ItemStack(Blocks.CONDUIT, 1)); @@ -1474,19 +1523,23 @@ class WorkbenchGenerator { } private static void generateScrapRecipes() { - Registries.RECIPES.WORKBENCH.register("scrap_chainmail_boots", new RecipeEntryScrap(Items.ARMOR_BOOTS_CHAINMAIL, Items.CHAINLINK, 4)); - Registries.RECIPES.WORKBENCH.register("scrap_chainmail_leggings", new RecipeEntryScrap(Items.ARMOR_LEGGINGS_CHAINMAIL, Items.CHAINLINK, 4)); - Registries.RECIPES.WORKBENCH.register("scrap_chainmail_chestplate", new RecipeEntryScrap(Items.ARMOR_CHESTPLATE_CHAINMAIL, Items.CHAINLINK, 4)); - Registries.RECIPES.WORKBENCH.register("scrap_chainmail_helmet", new RecipeEntryScrap(Items.ARMOR_HELMET_CHAINMAIL, Items.CHAINLINK, 4)); - Registries.RECIPES.WORKBENCH.register("scrap_chainmail_wolf", new RecipeEntryScrap(Items.ARMOR_WOLF_CHAINMAIL, Items.CHAINLINK, 4)); + RecipeBuilder.consolidationGroup("scrap", () -> { + Registries.RECIPES.WORKBENCH.register("scrap_chainmail_boots", new RecipeEntryScrap(Items.ARMOR_BOOTS_CHAINMAIL, Items.CHAINLINK, 4)); + Registries.RECIPES.WORKBENCH.register("scrap_chainmail_leggings", new RecipeEntryScrap(Items.ARMOR_LEGGINGS_CHAINMAIL, Items.CHAINLINK, 4)); + Registries.RECIPES.WORKBENCH.register("scrap_chainmail_chestplate", new RecipeEntryScrap(Items.ARMOR_CHESTPLATE_CHAINMAIL, Items.CHAINLINK, 4)); + Registries.RECIPES.WORKBENCH.register("scrap_chainmail_helmet", new RecipeEntryScrap(Items.ARMOR_HELMET_CHAINMAIL, Items.CHAINLINK, 4)); + Registries.RECIPES.WORKBENCH.register("scrap_chainmail_wolf", new RecipeEntryScrap(Items.ARMOR_WOLF_CHAINMAIL, Items.CHAINLINK, 4)); + }); } private static void generateRepairableRecipes() { - Registries.RECIPES.WORKBENCH.register("repair_chainmail_boots", new RecipeEntryRepairable(Items.ARMOR_BOOTS_CHAINMAIL.getDefaultStack(), new RecipeSymbol(Items.CHAINLINK.getDefaultStack()))); - Registries.RECIPES.WORKBENCH.register("repair_chainmail_leggings", new RecipeEntryRepairable(Items.ARMOR_LEGGINGS_CHAINMAIL.getDefaultStack(), new RecipeSymbol(Items.CHAINLINK.getDefaultStack()))); - Registries.RECIPES.WORKBENCH.register("repair_chainmail_chestplate", new RecipeEntryRepairable(Items.ARMOR_CHESTPLATE_CHAINMAIL.getDefaultStack(), new RecipeSymbol(Items.CHAINLINK.getDefaultStack()))); - Registries.RECIPES.WORKBENCH.register("repair_chainmail_helmet", new RecipeEntryRepairable(Items.ARMOR_HELMET_CHAINMAIL.getDefaultStack(), new RecipeSymbol(Items.CHAINLINK.getDefaultStack()))); - Registries.RECIPES.WORKBENCH.register("repair_chainmail_wolf", new RecipeEntryRepairable(Items.ARMOR_WOLF_CHAINMAIL.getDefaultStack(), new RecipeSymbol(Items.CHAINLINK.getDefaultStack()))); + RecipeBuilder.consolidationGroup("repair", () -> { + Registries.RECIPES.WORKBENCH.register("repair_chainmail_boots", new RecipeEntryRepairable(Items.ARMOR_BOOTS_CHAINMAIL.getDefaultStack(), new RecipeSymbol(Items.CHAINLINK.getDefaultStack()))); + Registries.RECIPES.WORKBENCH.register("repair_chainmail_leggings", new RecipeEntryRepairable(Items.ARMOR_LEGGINGS_CHAINMAIL.getDefaultStack(), new RecipeSymbol(Items.CHAINLINK.getDefaultStack()))); + Registries.RECIPES.WORKBENCH.register("repair_chainmail_chestplate", new RecipeEntryRepairable(Items.ARMOR_CHESTPLATE_CHAINMAIL.getDefaultStack(), new RecipeSymbol(Items.CHAINLINK.getDefaultStack()))); + Registries.RECIPES.WORKBENCH.register("repair_chainmail_helmet", new RecipeEntryRepairable(Items.ARMOR_HELMET_CHAINMAIL.getDefaultStack(), new RecipeSymbol(Items.CHAINLINK.getDefaultStack()))); + Registries.RECIPES.WORKBENCH.register("repair_chainmail_wolf", new RecipeEntryRepairable(Items.ARMOR_WOLF_CHAINMAIL.getDefaultStack(), new RecipeSymbol(Items.CHAINLINK.getDefaultStack()))); + }); } private static void generateToolRecipes() { @@ -1494,10 +1547,22 @@ class WorkbenchGenerator { } private static void generateStatueRecipes() { - final String[] statueNames = new String[]{"stone", "basalt", "limestone", "granite", "marble", "slate", "permafrost", "netherrack", "gloomstone"}; - final Item[] statueItems = new Item[]{Items.STATUE_STONE, Items.STATUE_BASALT, Items.STATUE_LIMESTONE, Items.STATUE_GRANITE, Items.STATUE_MARBLE, Items.STATUE_SLATE, Items.STATUE_PERMAFROST, Items.STATUE_NETHERRACK, Items.STATUE_GLOOMSTONE}; - final Block[] statueBodyBlocks = new Block[]{Blocks.STONE, Blocks.BASALT, Blocks.LIMESTONE, Blocks.GRANITE, Blocks.MARBLE, Blocks.SLATE, Blocks.PERMAFROST, Blocks.NETHERRACK, Blocks.GLOOMSTONE}; - final Block[] statueBaseBlocks = new Block[]{Blocks.SLAB_STONE_POLISHED, Blocks.SLAB_BASALT_POLISHED, Blocks.SLAB_LIMESTONE_POLISHED, Blocks.SLAB_GRANITE_POLISHED, Blocks.SLAB_CAPSTONE_MARBLE, Blocks.SLAB_SLATE_POLISHED, Blocks.SLAB_PERMAFROST_POLISHED, Blocks.SLAB_NETHERRACK_POLISHED, Blocks.SLAB_GLOOMSTONE_POLISHED}; + final String[] statueNames = new String[]{ + "stone", "basalt", "limestone", + "granite", "marble", "slate", + "permafrost", "netherrack", "gloomstone"}; + final Item[] statueItems = new Item[]{ + Items.STATUE_STONE, Items.STATUE_BASALT, Items.STATUE_LIMESTONE, + Items.STATUE_GRANITE, Items.STATUE_MARBLE, Items.STATUE_SLATE, + Items.STATUE_PERMAFROST, Items.STATUE_NETHERRACK, Items.STATUE_GLOOMSTONE}; + final Block[] statueBodyBlocks = new Block[]{ + Blocks.STONE, Blocks.BASALT, Blocks.LIMESTONE, + Blocks.GRANITE, Blocks.MARBLE, Blocks.SLATE, + Blocks.PERMAFROST, Blocks.NETHERRACK, Blocks.GLOOMSTONE}; + final Block[] statueBaseBlocks = new Block[]{ + Blocks.SLAB_STONE_POLISHED, Blocks.SLAB_BASALT_POLISHED, Blocks.SLAB_LIMESTONE_POLISHED, + Blocks.SLAB_GRANITE_POLISHED, Blocks.SLAB_CAPSTONE_MARBLE, Blocks.SLAB_SLATE_POLISHED, + Blocks.SLAB_PERMAFROST_POLISHED, Blocks.SLAB_NETHERRACK_POLISHED, Blocks.SLAB_GLOOMSTONE_POLISHED}; for (int i = 0; i < statueItems.length; i++) { RecipeBuilder.Shaped(CORE_NAMESPACE) @@ -1546,16 +1611,13 @@ class WorkbenchGenerator { private static void generateMiscNewRecipes() { // Brimstone Formatting - RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("SS", "SS").addInput('S', Blocks.BRIMSAND).create("brimstone", new ItemStack(Blocks.BRIMSTONE, 2)); RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("SSS").addInput('S', Blocks.BRIMSTONE).create("brimstone_slab", new ItemStack(Blocks.SLAB_BRIMSTONE, 6)); RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("S ", "SS ", "SSS").addInput('S', Blocks.BRIMSTONE).create("brimstone_stairs", new ItemStack(Blocks.STAIRS_BRIMSTONE, 6)); - RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("SS", "SS").addInput('S', Blocks.BRIMSTONE).create("brimstone_bricks", new ItemStack(Blocks.BRICK_BRIMSTONE, 4)); RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("SSS").addInput('S', Blocks.BRICK_BRIMSTONE).create("brick_brimstone_slab", new ItemStack(Blocks.SLAB_BRICK_BRIMSTONE, 6)); RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("S ", "SS ", "SSS").addInput('S', Blocks.BRICK_BRIMSTONE).create("brick_brimstone_stairs", new ItemStack(Blocks.STAIRS_BRICK_BRIMSTONE, 6)); // Misc additions RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("SSS").addInput('S', Blocks.SLATE_POLISHED).create("polished_slate_slab", new ItemStack(Blocks.SLAB_SLATE_POLISHED, 6)); - RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("PO", "OP").addInput('P', Items.AMMO_PEBBLE).addInput('O', Blocks.OBSIDIAN).create("pebbles_to_gloomstone", new ItemStack(Blocks.COBBLE_GLOOMSTONE, 2)); RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Items.SULFUR).addInput(Items.PAPER).addInput(Items.AMMO_ARROW).addInput(Items.SULFUR).create("flaming_arrow", new ItemStack(Items.AMMO_ARROW_FLAMING, 1)); RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("SS", "SS").addInput('S', Items.SULFUR).create("sulfur_block", new ItemStack(Blocks.SULFUR, 1)); diff --git a/util/datagen/src/main/java/net/minecraft/datagen/recipeBuilders/RecipeBuilder.java b/util/datagen/src/main/java/net/minecraft/datagen/recipeBuilders/RecipeBuilder.java index 3fcfa9d36..305581fd4 100644 --- a/util/datagen/src/main/java/net/minecraft/datagen/recipeBuilders/RecipeBuilder.java +++ b/util/datagen/src/main/java/net/minecraft/datagen/recipeBuilders/RecipeBuilder.java @@ -182,6 +182,18 @@ public final class RecipeBuilder { return new RecipeBuilderTrommel(modID); } + public static String currentConsolidationGroup = null; + + public static void consolidationGroup(String name, Runnable recipes) { + String previous = currentConsolidationGroup; + currentConsolidationGroup = name; + try { + recipes.run(); + } finally { + currentConsolidationGroup = previous; + } + } + public static boolean isExporting = false; /** diff --git a/util/datagen/src/main/java/net/minecraft/datagen/recipeBuilders/RecipeBuilderShaped.java b/util/datagen/src/main/java/net/minecraft/datagen/recipeBuilders/RecipeBuilderShaped.java index 1bafdf30a..f41c7013c 100644 --- a/util/datagen/src/main/java/net/minecraft/datagen/recipeBuilders/RecipeBuilderShaped.java +++ b/util/datagen/src/main/java/net/minecraft/datagen/recipeBuilders/RecipeBuilderShaped.java @@ -223,6 +223,8 @@ public class RecipeBuilderShaped extends RecipeBuilderBase{ } RecipeGroup> group = ((RecipeGroup>) RecipeBuilder.getRecipeGroup(modID, "workbench", new RecipeSymbol(Blocks.WORKBENCH.getDefaultStack()))); if (group.getItem(recipeID) != null) throw new IllegalArgumentException("RecipeID '" + recipeID + "' already exists!"); - group.register(recipeID, new RecipeEntryCraftingShaped(width, height, recipe, outputStack, consumeContainer, allowMirrored)); + RecipeEntryCraftingShaped entry = new RecipeEntryCraftingShaped(width, height, recipe, outputStack, consumeContainer, allowMirrored); + entry.consolidationGroup = RecipeBuilder.currentConsolidationGroup; + group.register(recipeID, entry); } } diff --git a/util/datagen/src/main/java/net/minecraft/datagen/recipeBuilders/RecipeBuilderShapeless.java b/util/datagen/src/main/java/net/minecraft/datagen/recipeBuilders/RecipeBuilderShapeless.java index f73b51f76..46ee1cf11 100644 --- a/util/datagen/src/main/java/net/minecraft/datagen/recipeBuilders/RecipeBuilderShapeless.java +++ b/util/datagen/src/main/java/net/minecraft/datagen/recipeBuilders/RecipeBuilderShapeless.java @@ -85,6 +85,8 @@ public class RecipeBuilderShapeless extends RecipeBuilderBase{ public void create(String recipeID, ItemStack outputStack) { RecipeGroup> group = ((RecipeGroup>) RecipeBuilder.getRecipeGroup(modID, "workbench", new RecipeSymbol(Blocks.WORKBENCH.getDefaultStack()))); if (group.getItem(recipeID) != null) throw new IllegalArgumentException("RecipeID '" + recipeID + "' already exists!"); - group.register(recipeID, new RecipeEntryCraftingShapeless(symbolShapelessList, outputStack)); + RecipeEntryCraftingShapeless entry = new RecipeEntryCraftingShapeless(symbolShapelessList, outputStack); + entry.consolidationGroup = RecipeBuilder.currentConsolidationGroup; + group.register(recipeID, entry); } }