diff --git a/game/client/src/main/java/net/betterthanadventure/utils/DataDumper.java b/game/client/src/main/java/net/betterthanadventure/utils/DataDumper.java index 5e06c9c25..1e4457da4 100644 --- a/game/client/src/main/java/net/betterthanadventure/utils/DataDumper.java +++ b/game/client/src/main/java/net/betterthanadventure/utils/DataDumper.java @@ -71,8 +71,7 @@ public class DataDumper implements MinecraftAccessor { Blocks.init(); Items.init(); - File wikiDumpFile = new File(getMinecraftDir(), "infodump"); - wikiDumpFile.mkdirs(); + File wikiDumpFile = DumpFolder.resolve(getMinecraftDir()); BufferedWriter writer; writer = new BufferedWriter(new FileWriter(new File(wikiDumpFile, "blocks.csv"))); diff --git a/game/client/src/main/java/net/betterthanadventure/utils/DumpFolder.java b/game/client/src/main/java/net/betterthanadventure/utils/DumpFolder.java new file mode 100644 index 000000000..c2596db40 --- /dev/null +++ b/game/client/src/main/java/net/betterthanadventure/utils/DumpFolder.java @@ -0,0 +1,21 @@ +package net.betterthanadventure.utils; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +/** + * Shared location for all debug dump output (data dumper CSVs, wiki dump, inventory sprites). + */ +public final class DumpFolder { + /** Name of the dump folder inside the Minecraft directory. */ + public static final String NAME = "dump"; + + private DumpFolder() {} + + public static @NotNull File resolve(@NotNull File minecraftDir) { + final File dir = new File(minecraftDir, NAME); + dir.mkdirs(); + return dir; + } +} diff --git a/game/client/src/main/java/net/betterthanadventure/utils/InvSpriteAPNGDumper.java b/game/client/src/main/java/net/betterthanadventure/utils/InvSpriteAPNGDumper.java new file mode 100644 index 000000000..438361555 --- /dev/null +++ b/game/client/src/main/java/net/betterthanadventure/utils/InvSpriteAPNGDumper.java @@ -0,0 +1,213 @@ +package net.betterthanadventure.utils; + +import com.mojang.logging.LogUtils; +import net.betterthanadventure.utils.InvSpriteDumper.InvSpriteEntry; +import net.minecraft.client.Minecraft; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; + +import java.awt.image.BufferedImage; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.util.List; +import java.util.zip.CRC32; +import java.util.zip.Deflater; + + +public final class InvSpriteAPNGDumper { + private static final Logger LOGGER = LogUtils.getLogger(); + + private static final int DEFAULT_FRAME_COUNT = 60; + private static final int DEFAULT_TICKS_PER_FRAME = 1; + + private InvSpriteAPNGDumper() {} + + public static @NotNull File dump(@NotNull File minecraftDir) throws IOException { + return dump(minecraftDir, DEFAULT_FRAME_COUNT, DEFAULT_TICKS_PER_FRAME); + } + + public static @NotNull File dump(@NotNull File minecraftDir, int frameCount, int ticksPerFrame) throws IOException { + final File dumpDir = DumpFolder.resolve(minecraftDir); + final File apngFile = new File(dumpDir, "InvSprite.apng"); + + final Minecraft mc = Minecraft.getMinecraft(); + final List entries = InvSpriteDumper.collectInvSpriteEntries(); + + final int[] fbo = InvSpriteDumper.createFramebuffer(); + final var originalColors = InvSpriteDumper.applyBiomeSeasonColors(); + try (APNGSequenceWriter apng = new APNGSequenceWriter(apngFile, frameCount, ticksPerFrame * 50, true)) { + // Render and stream each frame + for (int frame = 0; frame < frameCount; frame++) { + advanceAnimations(mc, ticksPerFrame); + final BufferedImage sheet = InvSpriteDumper.newSheet(entries.size()); + InvSpriteDumper.renderSheet(fbo[1], entries, sheet); + apng.writeFrame(sheet); + } + } finally { + InvSpriteDumper.restoreBlockColors(originalColors); + InvSpriteDumper.destroyFramebuffer(fbo); + } + + LOGGER.info("Dumped {} frame animated inventory sprite sheet to {}", frameCount, apngFile.getName()); + return dumpDir; + } + + private static void advanceAnimations(@NotNull Minecraft mc, int ticks) { + for (int i = 0; i < ticks; i++) { + mc.textureManager.updateDynamicTextures(false); + } + } + + //modified apng writer + private static final class APNGSequenceWriter implements Closeable { + private static final byte[] SIGNATURE = {(byte) 0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n'}; + + private final DataOutputStream out; + private final int frameCount; + private final int delayNum; + private final int numPlays; + private final CRC32 crc = new CRC32(); + + private int sequenceNumber = 0; + private int width = -1; + private int height = -1; + private boolean wroteHeader = false; + private boolean wroteFirstFrame = false; + + APNGSequenceWriter(@NotNull File file, int frameCount, int delayMillis, boolean loop) throws IOException { + this.frameCount = frameCount; + this.delayNum = Math.max(1, delayMillis); + // num_plays of 0 means loop forever; otherwise play once. + this.numPlays = loop ? 0 : 1; + this.out = new DataOutputStream(new BufferedOutputStream(Files.newOutputStream(file.toPath()))); + this.out.write(SIGNATURE); + } + + void writeFrame(@NotNull BufferedImage frame) throws IOException { + if (!this.wroteHeader) { + this.width = frame.getWidth(); + this.height = frame.getHeight(); + writeHeaderChunks(); + this.wroteHeader = true; + } else if (frame.getWidth() != this.width || frame.getHeight() != this.height) { + throw new IOException("APNG frames must all share the same dimensions"); + } + + writeFrameControl(); + + final byte[] compressed = deflate(toRawScanlines(frame)); + if (!this.wroteFirstFrame) { + writeChunk("IDAT", compressed); + this.wroteFirstFrame = true; + } else { + // fdAT data is prefixed with its own sequence number, followed by the compressed scanlines. + final byte[] data = new byte[compressed.length + 4]; + writeInt(data, 0, this.sequenceNumber++); + System.arraycopy(compressed, 0, data, 4, compressed.length); + writeChunk("fdAT", data); + } + } + + private void writeHeaderChunks() throws IOException { + final byte[] ihdr = new byte[13]; + writeInt(ihdr, 0, this.width); + writeInt(ihdr, 4, this.height); + ihdr[8] = 8; // bit depth + ihdr[9] = 6; // color type: truecolor with alpha (RGBA) + ihdr[10] = 0; // compression: deflate + ihdr[11] = 0; // filter: adaptive + ihdr[12] = 0; // interlace: none + writeChunk("IHDR", ihdr); + + final byte[] actl = new byte[8]; + writeInt(actl, 0, this.frameCount); + writeInt(actl, 4, this.numPlays); + writeChunk("acTL", actl); + } + + private void writeFrameControl() throws IOException { + final byte[] fctl = new byte[26]; + writeInt(fctl, 0, this.sequenceNumber++); + writeInt(fctl, 4, this.width); + writeInt(fctl, 8, this.height); + writeInt(fctl, 12, 0); // x offset + writeInt(fctl, 16, 0); // y offset + writeShort(fctl, 20, this.delayNum); // delay numerator (ms) + writeShort(fctl, 22, 1000); // delay denominator -> delay in milliseconds + fctl[24] = 1; // dispose_op = APNG_DISPOSE_OP_BACKGROUND: clear to transparent before next frame + fctl[25] = 0; // blend_op = APNG_BLEND_OP_SOURCE: overwrite pixels (keep exact alpha) + writeChunk("fcTL", fctl); + } + + private byte @NotNull [] toRawScanlines(@NotNull BufferedImage image) { + final int w = this.width; + final int h = this.height; + final byte[] raw = new byte[h * (1 + w * 4)]; + final int[] row = new int[w]; + int pos = 0; + for (int y = 0; y < h; y++) { + image.getRGB(0, y, w, 1, row, 0, w); + raw[pos++] = 0; // filter type: None + for (int x = 0; x < w; x++) { + final int argb = row[x]; + raw[pos++] = (byte) ((argb >> 16) & 0xFF); // R + raw[pos++] = (byte) ((argb >> 8) & 0xFF); // G + raw[pos++] = (byte) (argb & 0xFF); // B + raw[pos++] = (byte) ((argb >>> 24) & 0xFF); // A + } + } + return raw; + } + + private static byte @NotNull [] deflate(byte @NotNull [] data) { + final Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION); + deflater.setInput(data); + deflater.finish(); + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(Math.max(64, data.length / 2)); + final byte[] chunk = new byte[8192]; + while (!deflater.finished()) { + final int n = deflater.deflate(chunk); + buffer.write(chunk, 0, n); + } + deflater.end(); + return buffer.toByteArray(); + } + + private void writeChunk(@NotNull String type, byte @NotNull [] data) throws IOException { + this.out.writeInt(data.length); + final byte[] typeBytes = type.getBytes(java.nio.charset.StandardCharsets.US_ASCII); + this.out.write(typeBytes); + this.out.write(data); + + this.crc.reset(); + this.crc.update(typeBytes); + this.crc.update(data); + this.out.writeInt((int) this.crc.getValue()); + } + + private static void writeInt(byte @NotNull [] dst, int offset, int value) { + dst[offset] = (byte) ((value >>> 24) & 0xFF); + dst[offset + 1] = (byte) ((value >>> 16) & 0xFF); + dst[offset + 2] = (byte) ((value >>> 8) & 0xFF); + dst[offset + 3] = (byte) (value & 0xFF); + } + + private static void writeShort(byte @NotNull [] dst, int offset, int value) { + dst[offset] = (byte) ((value >>> 8) & 0xFF); + dst[offset + 1] = (byte) (value & 0xFF); + } + + @Override + public void close() throws IOException { + try (OutputStream ignored = this.out) { + writeChunk("IEND", new byte[0]); + } + } + } +} diff --git a/game/client/src/main/java/net/betterthanadventure/utils/InvSpriteDumper.java b/game/client/src/main/java/net/betterthanadventure/utils/InvSpriteDumper.java new file mode 100644 index 000000000..079891e1b --- /dev/null +++ b/game/client/src/main/java/net/betterthanadventure/utils/InvSpriteDumper.java @@ -0,0 +1,373 @@ +package net.betterthanadventure.utils; + +import com.mojang.logging.LogUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.render.Lighting; +import net.minecraft.client.render.block.color.BlockColor; +import net.minecraft.client.render.block.color.BlockColorCustom; +import net.minecraft.client.render.block.color.BlockColorDispatcher; +import net.minecraft.client.render.colorizer.ColorMap; +import net.minecraft.client.render.colorizer.Colorizer; +import net.minecraft.client.render.colorizer.Colorizers; +import net.minecraft.client.render.item.model.ItemModel; +import net.minecraft.client.render.item.model.ItemModelDispatcher; +import net.minecraft.client.render.renderer.GLRenderer; +import net.minecraft.client.render.renderer.State; +import net.minecraft.client.render.texturepack.TexturePack; +import net.minecraft.client.util.helper.Buffer; +import net.minecraft.client.util.helper.Textures; +import net.minecraft.core.block.Block; +import net.minecraft.core.block.Blocks; +import net.minecraft.core.block.IPainted; +import net.minecraft.core.item.Item; +import net.minecraft.core.item.ItemBucket; +import net.minecraft.core.item.ItemStack; +import net.minecraft.core.lang.I18n; +import net.minecraft.core.util.collection.NamespaceID; +import net.minecraft.core.util.helper.DyeColor; +import net.minecraft.core.util.helper.LightIndexHelper; +import net.minecraft.core.world.WorldSource; +import net.minecraft.core.world.biome.Biomes; +import net.minecraft.core.world.pos.TilePosc; +import net.minecraft.core.world.season.Seasons; +import org.jetbrains.annotations.NotNull; +import org.lwjgl.opengl.GL41; +import org.slf4j.Logger; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.Writer; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Debug utility that renders every registered block and item exactly as it appears in the inventory + * into a single {@code InvSprite.png} sheet and writes a Lua module mapping each name to its sprite index, + * matching the format used by the BTA wiki. + */ +public final class InvSpriteDumper { + private static final Logger LOGGER = LogUtils.getLogger(); + + private static final int SPRITE_SIZE = 32; + private static final int SHEET_SIZE = 512; + private static final int COLUMNS = SHEET_SIZE / SPRITE_SIZE; + private static final int FILLER_INDEX = 0; + private static final int GENERATED_START_INDEX = 500; + + private InvSpriteDumper() {} + + public static @NotNull File dump(@NotNull File minecraftDir) throws IOException { + final File dumpDir = DumpFolder.resolve(minecraftDir); + + final List entries = collectInvSpriteEntries(); + + final BufferedImage sheet = newSheet(entries.size()); + + final int[] fbo = createFramebuffer(); + // Render biome/season as if in a mid-summer forest + final Map, BlockColor> originalColors = applyBiomeSeasonColors(); + try { + renderSheet(fbo[1], entries, sheet); + } finally { + restoreBlockColors(originalColors); + destroyFramebuffer(fbo); + } + + final File pngFile = new File(dumpDir, "InvSprite.png"); + Textures.saveImage(pngFile, sheet); + + final File luaFile = new File(dumpDir, "invsprite.lua"); + try (Writer writer = Files.newBufferedWriter(luaFile.toPath(), StandardCharsets.UTF_8)) { + writeLua(writer, entries); + } + + LOGGER.info("Dumped {} inventory sprites to {} and {}", entries.size(), pngFile.getName(), luaFile.getName()); + return dumpDir; + } + + private static void writeLua(@NotNull Writer writer, @NotNull List entries) throws IOException { + writer.write("return {\n"); + writer.write("\tsettings = {\n"); + writer.write("\t\talign = 'middle',\n"); + writer.write("\t\timage = 'InvSprite.png',\n"); + writer.write("\t\tsheetsize = " + SHEET_SIZE + ",\n"); + writer.write("\t\tsize = " + SPRITE_SIZE + ",\n"); + writer.write("\t},\n"); + writer.write("\tids = {\n"); + writer.write("\t\t-- Manually-added entries (ex. removed/legacy features) occupy indices " + FILLER_INDEX + "-" + (GENERATED_START_INDEX - 1) + ".\n"); + writer.write("\t\t[\"Filler\"] = " + FILLER_INDEX + ",\n"); + writer.write("\t\t-- Generated entries start at " + GENERATED_START_INDEX + ".\n"); + for (int i = 0; i < entries.size(); i++) { + writer.write("\t\t[\"" + escape(entries.get(i).name()) + "\"] = " + (GENERATED_START_INDEX + i) + ",\n"); + } + writer.write("\t}\n"); + writer.write("}\n"); + } + + static @NotNull BufferedImage newSheet(int entryCount) { + final int maxIndex = Math.max(GENERATED_START_INDEX + entryCount - 1, FILLER_INDEX); + final int rows = maxIndex / COLUMNS + 1; + return new BufferedImage(SHEET_SIZE, rows * SPRITE_SIZE, BufferedImage.TYPE_INT_ARGB); + } + + static void renderSheet(int colorTexture, @NotNull List entries, @NotNull BufferedImage sheet) { + // Filler stone block at index 0; manual (removed/legacy) entries occupy indices up to GENERATED_START_INDEX. + blit(sheet, renderSprite(colorTexture, new ItemStack(Blocks.STONE, 1, 0)), FILLER_INDEX); + for (int i = 0; i < entries.size(); i++) { + blit(sheet, renderSprite(colorTexture, entries.get(i).stack()), GENERATED_START_INDEX + i); + } + } + + private static @NotNull BufferedImage renderSprite(int colorTexture, @NotNull ItemStack stack) { + GL41.glViewport(0, 0, SPRITE_SIZE, SPRITE_SIZE); + + GLRenderer.pushFrame(); + GLRenderer.projectionM4f().identity().ortho(0.0f, 16.0f, 16.0f, 0.0f, 1000.0f, 3000.0f); + GLRenderer.viewM4f().identity().translate(0.0f, 0.0f, -2000.0f); + GLRenderer.modelM4f().identity(); + + GL41.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + GL41.glClear(GL41.GL_COLOR_BUFFER_BIT | GL41.GL_DEPTH_BUFFER_BIT); + + Lighting.enableInventoryLight(); + GLRenderer.setColor4f(1.0f, 1.0f, 1.0f, 1.0f); + GLRenderer.enableState(State.DEPTH_TEST); + try { + final ItemModel model = ItemModelDispatcher.getInstance().getDispatch(stack.getItem()); + model.renderGui(GLRenderer.getTessellator(), null, stack, 0, 0, LightIndexHelper.lightIndex2i(15, 15), 1.0f); + } catch (final Exception e) { + LOGGER.error("Failed to render inventory sprite for '{}'", stack.getItem().namespaceID, e); + } + GLRenderer.disableState(State.DEPTH_TEST); + Lighting.disable(); + GLRenderer.popFrame(); + + final int size = SPRITE_SIZE * SPRITE_SIZE * 4; + Buffer.checkBufferSize(size); + final ByteBuffer buffer = Buffer.buffer; + buffer.position(0).limit(size); + GL41.glPixelStorei(GL41.GL_PACK_ALIGNMENT, 1); + GL41.glBindTexture(GL41.GL_TEXTURE_2D, colorTexture); + GL41.glGetTexImage(GL41.GL_TEXTURE_2D, 0, GL41.GL_RGBA, GL41.GL_UNSIGNED_BYTE, buffer); + buffer.position(0); + + return Textures.getImage(buffer, SPRITE_SIZE, SPRITE_SIZE, true, true); + } + + private static void blit(@NotNull BufferedImage sheet, @NotNull BufferedImage sprite, int index) { + final int x = (index % COLUMNS) * SPRITE_SIZE; + final int y = (index / COLUMNS) * SPRITE_SIZE; + final int[] pixels = sprite.getRGB(0, 0, SPRITE_SIZE, SPRITE_SIZE, null, 0, SPRITE_SIZE); + sheet.setRGB(x, y, SPRITE_SIZE, SPRITE_SIZE, pixels, 0, SPRITE_SIZE); + } + + static int @NotNull [] createFramebuffer() { + final int fbo = GL41.glGenFramebuffers(); + GL41.glBindFramebuffer(GL41.GL_FRAMEBUFFER, fbo); + + final int colorTexture = GL41.glGenTextures(); + GL41.glBindTexture(GL41.GL_TEXTURE_2D, colorTexture); + GL41.glTexImage2D(GL41.GL_TEXTURE_2D, 0, GL41.GL_RGBA8, SPRITE_SIZE, SPRITE_SIZE, 0, GL41.GL_RGBA, GL41.GL_UNSIGNED_BYTE, (ByteBuffer) null); + GL41.glTexParameteri(GL41.GL_TEXTURE_2D, GL41.GL_TEXTURE_MIN_FILTER, GL41.GL_NEAREST); + GL41.glTexParameteri(GL41.GL_TEXTURE_2D, GL41.GL_TEXTURE_MAG_FILTER, GL41.GL_NEAREST); + GL41.glFramebufferTexture2D(GL41.GL_FRAMEBUFFER, GL41.GL_COLOR_ATTACHMENT0, GL41.GL_TEXTURE_2D, colorTexture, 0); + + final int depthBuffer = GL41.glGenRenderbuffers(); + GL41.glBindRenderbuffer(GL41.GL_RENDERBUFFER, depthBuffer); + GL41.glRenderbufferStorage(GL41.GL_RENDERBUFFER, GL41.GL_DEPTH_COMPONENT24, SPRITE_SIZE, SPRITE_SIZE); + GL41.glFramebufferRenderbuffer(GL41.GL_FRAMEBUFFER, GL41.GL_DEPTH_ATTACHMENT, GL41.GL_RENDERBUFFER, depthBuffer); + + final int status = GL41.glCheckFramebufferStatus(GL41.GL_FRAMEBUFFER); + if (status != GL41.GL_FRAMEBUFFER_COMPLETE) { + throw new IllegalStateException("InvSprite framebuffer incomplete: 0x" + Integer.toHexString(status)); + } + + return new int[]{fbo, colorTexture, depthBuffer}; + } + + static void destroyFramebuffer(int @NotNull [] fbo) { + GL41.glBindFramebuffer(GL41.GL_FRAMEBUFFER, 0); + GL41.glDeleteRenderbuffers(fbo[2]); + GL41.glDeleteTextures(fbo[1]); + GL41.glDeleteFramebuffers(fbo[0]); + + final Minecraft mc = Minecraft.getMinecraft(); + GL41.glViewport(0, 0, mc.gameWindow.getWidthPixels(), mc.gameWindow.getHeightPixels()); + } + + + public record InvSpriteEntry(@NotNull String name, @NotNull ItemStack stack) {} + + public static @NotNull List collectInvSpriteEntries() { + final I18n i18n = I18n.getInstance(); + final List entries = new ArrayList<>(); + + for (final Block block : Blocks.blocksList) { + if (block == null || block.id() == 0) continue; + + if (block.getLogic() instanceof IPainted painted && WikiDumper.hasPaintedColorNames(i18n, block.getKey())) { + collectPaintedBlockEntries(entries, i18n, block, painted); + } else { + final String name = i18n.translateKey(block.getKey() + ".name"); + entries.add(new InvSpriteEntry(name, new ItemStack(block.asItem(), 1, 0))); + } + } + + for (final Item item : Item.itemsList) { + if (item == null) continue; + if (item.id < Blocks.blocksList.length) continue; + if (item instanceof ItemBucket bucket) { + collectBucketEntries(entries, i18n, bucket); + continue; + } + collectItemEntries(entries, i18n, item); + } + + return entries; + } + + private static void collectPaintedBlockEntries(@NotNull List entries, @NotNull I18n i18n, + @NotNull Block block, @NotNull IPainted painted) { + final String key = block.getKey(); + + int minMeta = Integer.MAX_VALUE; + for (final DyeColor color : DyeColor.values()) { + minMeta = Math.min(minMeta, painted.toMetadata(color)); + } + + final String mysteryName = i18n.translateKey(key + ".name"); + entries.add(new InvSpriteEntry(mysteryName, new ItemStack(block.asItem(), 1, minMeta))); + + for (final DyeColor color : DyeColor.values()) { + final String colorKey = key + "." + color.colorID; + final String colorName = i18n.translateKey(colorKey + ".name"); + entries.add(new InvSpriteEntry(colorName, new ItemStack(block.asItem(), 1, painted.toMetadata(color)))); + } + } + + private static void collectItemEntries(@NotNull List entries, @NotNull I18n i18n, @NotNull Item item) { + final String baseKey = item.getKey(); + + final Map variants = new LinkedHashMap<>(); + for (int meta = 0; meta <= DyeColor.MASK_COLOR; meta++) { + String languageKey; + try { + languageKey = item.getLanguageKey(new ItemStack(item, 1, meta)); + } catch (final Exception e) { + languageKey = baseKey; + } + if (WikiDumper.hasTranslation(i18n, languageKey + ".name")) { + variants.putIfAbsent(languageKey, meta); + } + } + + final boolean variesByMetadata = variants.size() > 1 + || (variants.size() == 1 && !variants.containsKey(baseKey)); + + if (!variesByMetadata) { + entries.add(new InvSpriteEntry(WikiDumper.getWikiDisplayName(item, i18n), new ItemStack(item, 1, 0))); + return; + } + + if (!variants.containsKey(baseKey) && WikiDumper.hasTranslation(i18n, baseKey + ".name")) { + entries.add(new InvSpriteEntry(WikiDumper.getWikiDisplayName(item, i18n), new ItemStack(item, 1, 0))); + } + + for (final Map.Entry variant : variants.entrySet()) { + final String name = i18n.translateKey(variant.getKey() + ".name"); + entries.add(new InvSpriteEntry(name, new ItemStack(item, 1, variant.getValue()))); + } + } + + private static void collectBucketEntries(@NotNull List entries, @NotNull I18n i18n, @NotNull ItemBucket bucket) { + entries.add(new InvSpriteEntry(i18n.translateKey(bucket.getKey() + ".name"), new ItemStack(bucket, 1, 0))); + + for (final NamespaceID stateId : ItemBucket.getRegisteredStateIds()) { + if (ItemBucket.STATE_EMPTY.equals(stateId)) continue; + + final ItemStack sample = ItemBucket.createItemStack(bucket, stateId, 1); + final String stateName = i18n.translateKey(bucket.getLanguageKey(sample) + ".name"); + + for (int charges = 1; charges <= bucket.maxCharges; charges++) { + final ItemStack stack = ItemBucket.createItemStack(bucket, stateId, charges); + final String name = bucket.maxCharges > 1 ? stateName + " (" + charges + ")" : stateName; + entries.add(new InvSpriteEntry(name, stack)); + } + } + } + + static @NotNull Map, BlockColor> applyBiomeSeasonColors() { + final BlockColorDispatcher dispatcher = BlockColorDispatcher.getInstance(); + final Map colorByColorizer = new HashMap<>(); + final Map, BlockColor> originals = new LinkedHashMap<>(); + + for (final Block block : Blocks.blocksList) { + if (block == null) continue; + if (!dispatcher.hasDispatch(block)) continue; + if (!(dispatcher.getDispatch(block) instanceof BlockColorCustom custom)) continue; + + final int color = colorByColorizer.computeIfAbsent(custom.colorizer.name, n -> biomeSeasonColor(custom.colorizer)); + originals.put(block, custom); + dispatcher.addDispatch(block, new BiomeSeasonBlockColor(color)); + } + + return originals; + } + + static void restoreBlockColors(@NotNull Map, BlockColor> originals) { + final BlockColorDispatcher dispatcher = BlockColorDispatcher.getInstance(); + for (final Map.Entry, BlockColor> entry : originals.entrySet()) { + dispatcher.addDispatch(entry.getKey(), entry.getValue()); + } + } + + private static int biomeSeasonColor(@NotNull Colorizer colorizer) { + final Minecraft mc = Minecraft.getMinecraft(); + final String base = "/assets/minecraft/textures/colormap/" + colorizer.name + "/"; + final String defaultPath = base + "default.png"; + + final TexturePack pack = Colorizers.findTexturePackWithFile(mc, defaultPath); + if (pack == null) return 0xFFFFFFFF; + + final String seasonPath = base + Seasons.OVERWORLD_SUMMER.getId().replace('.', '_').toLowerCase() + ".png"; + ColorMap colorMap = pack.hasFile(seasonPath) ? Colorizers.loadColorData(pack, seasonPath) : null; + if (colorMap == null) colorMap = Colorizers.loadColorData(pack, defaultPath); + if (colorMap == null) return 0xFFFFFFFF; + + final int rgb = colorMap.getColor(Biomes.OVERWORLD_FOREST.defaultTemperature, Biomes.OVERWORLD_FOREST.defaultHumidity); + return 0xFF000000 | (rgb & 0xFFFFFF); + } + + private static final class BiomeSeasonBlockColor extends BlockColor { + private final int color; + + private BiomeSeasonBlockColor(int color) { + this.color = color; + } + + @Override + public int getFallbackColor(int meta, int tintIndex) { + return this.color; + } + + @Override + public int getWorldColor(@NotNull WorldSource source, @NotNull TilePosc tilePos, int tintIndex) { + return this.color; + } + } + + private static @NotNull String escape(@NotNull String value) { + return value + .replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\r", " ") + .replace("\n", " "); + } +} diff --git a/game/client/src/main/java/net/betterthanadventure/utils/WikiDumper.java b/game/client/src/main/java/net/betterthanadventure/utils/WikiDumper.java new file mode 100644 index 000000000..c84a790fb --- /dev/null +++ b/game/client/src/main/java/net/betterthanadventure/utils/WikiDumper.java @@ -0,0 +1,216 @@ +package net.betterthanadventure.utils; + +import net.minecraft.core.block.Block; +import net.minecraft.core.block.Blocks; +import net.minecraft.core.block.IPainted; +import net.minecraft.core.block.tag.BlockTags; +import net.minecraft.core.data.tag.ITaggable; +import net.minecraft.core.data.tag.Tag; +import net.minecraft.core.item.Item; +import net.minecraft.core.item.ItemDiscMusic; +import net.minecraft.core.item.ItemFood; +import net.minecraft.core.item.ItemStack; +import net.minecraft.core.item.tag.ItemTags; +import net.minecraft.core.item.tool.ItemToolPickaxe; +import net.minecraft.core.lang.I18n; +import net.minecraft.core.util.helper.DyeColor; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Debug utility that dumps every registered block and item into a Lua table + * matching the format used by the BTA wiki in Data Value Table. + */ +public final class WikiDumper { + private WikiDumper() {} + + public static @NotNull File dump(@NotNull File minecraftDir) throws IOException { + final File outFile = new File(DumpFolder.resolve(minecraftDir), "wiki_dump.lua"); + try (Writer writer = Files.newBufferedWriter(outFile.toPath(), StandardCharsets.UTF_8)) { + writeDump(writer); + } + return outFile; + } + + private static void writeDump(@NotNull Writer writer) throws IOException { + final I18n i18n = I18n.getInstance(); + + writer.write("-- Block: { key, namespaceId, id, hardness, blastResistance, miningLevel, stackSize, description, tags }\n"); + writer.write("-- Item: { key, namespaceId, id, durability, restores, stackSize, description, tags }\n"); + + writer.write("return {\n"); + + writer.write("\t[\"Block\"] = {\n"); + boolean first = true; + for (final Block block : Blocks.blocksList) { + if (block == null || block.id() == 0) continue; + + if (block.getLogic() instanceof IPainted painted && hasPaintedColorNames(i18n, block.getKey())) { + first = writePaintedBlockEntries(writer, i18n, block, painted, first); + } else { + if (!first) writer.write(",\n"); + first = false; + + final String name = i18n.translateKey(block.getKey() + ".name"); + writer.write(formatEntry(name, block.getKey(), block.namespaceId().toString(), String.valueOf(block.id()), + blockExtra(block, block.getKey(), i18n))); + } + } + writer.write("\n\t},\n"); + + writer.write("\t[\"Item\"] = {\n"); + first = true; + for (final Item item : Item.itemsList) { + if (item == null) continue; + if (item.id < Blocks.blocksList.length) continue; + first = writeItemEntries(writer, i18n, item, first); + } + writer.write("\n\t}\n"); + + writer.write("}\n"); + } + + public static @NotNull String getWikiDisplayName(@NotNull Item item, @NotNull I18n i18n) { + if (item instanceof ItemDiscMusic disc && disc.recordName != null) { + return "Music Disc - " + i18n.translateKey(disc.recordName); + } + return i18n.translateKey(item.getKey() + ".name"); + } + + private static boolean writeItemEntries(@NotNull Writer writer, @NotNull I18n i18n, @NotNull Item item, boolean first) throws IOException { + final String baseKey = item.getKey(); + final String namespaceId = item.namespaceID.toString(); + final String id = String.valueOf(item.id); + + final Map variants = new LinkedHashMap<>(); + for (int meta = 0; meta <= DyeColor.MASK_COLOR; meta++) { + String languageKey; + try { + languageKey = item.getLanguageKey(new ItemStack(item, 1, meta)); + } catch (final Exception e) { + languageKey = baseKey; + } + if (hasTranslation(i18n, languageKey + ".name")) { + variants.putIfAbsent(languageKey, meta); + } + } + + final boolean variesByMetadata = variants.size() > 1 + || (variants.size() == 1 && !variants.containsKey(baseKey)); + + if (!variesByMetadata) { + if (!first) writer.write(",\n"); + writer.write(formatEntry(getWikiDisplayName(item, i18n), baseKey, namespaceId, id, itemExtra(item, baseKey, i18n))); + return false; + } + + if (!variants.containsKey(baseKey) && hasTranslation(i18n, baseKey + ".name")) { + if (!first) writer.write(",\n"); + first = false; + writer.write(formatEntry(getWikiDisplayName(item, i18n), baseKey, namespaceId, id, itemExtra(item, baseKey, i18n))); + } + + for (final Map.Entry variant : variants.entrySet()) { + if (!first) writer.write(",\n"); + first = false; + final String name = i18n.translateKey(variant.getKey() + ".name"); + writer.write(formatEntry(name, variant.getKey(), namespaceId, id + ":" + variant.getValue(), + itemExtra(item, variant.getKey(), i18n))); + } + + return first; + } + + public static boolean hasPaintedColorNames(@NotNull I18n i18n, @NotNull String key) { + return hasTranslation(i18n, key + "." + DyeColor.WHITE.colorID + ".name"); + } + + public static boolean hasTranslation(@NotNull I18n i18n, @NotNull String key) { + return !i18n.translateKey(key).equals(key); + } + + private static boolean writePaintedBlockEntries(@NotNull Writer writer, @NotNull I18n i18n, + @NotNull Block block, @NotNull IPainted painted, + boolean first) throws IOException { + final String key = block.getKey(); + final String namespaceId = block.namespaceId().toString(); + final String id = String.valueOf(block.id()); + + int minMeta = Integer.MAX_VALUE; + int maxMeta = Integer.MIN_VALUE; + for (final DyeColor color : DyeColor.values()) { + final int meta = painted.toMetadata(color); + minMeta = Math.min(minMeta, meta); + maxMeta = Math.max(maxMeta, meta); + } + + final String mysteryName = i18n.translateKey(key + ".name"); + final String mysteryMeta = minMeta == maxMeta ? String.valueOf(minMeta) : "[" + minMeta + "-" + maxMeta + "]"; + if (!first) writer.write(",\n"); + first = false; + writer.write(formatEntry(mysteryName, key, namespaceId, id + ":" + mysteryMeta, blockExtra(block, key, i18n))); + + for (final DyeColor color : DyeColor.values()) { + final String colorKey = key + "." + color.colorID; + final String colorName = i18n.translateKey(colorKey + ".name"); + writer.write(",\n"); + writer.write(formatEntry(colorName, colorKey, namespaceId, id + ":" + painted.toMetadata(color), + blockExtra(block, colorKey, i18n))); + } + + return first; + } + + private static @NotNull String formatEntry(@NotNull String name, @NotNull String key, @NotNull String namespaceId, @NotNull String id, @NotNull String extra) { + return "\t\t[\"" + escape(name) + "\"] = { \"" + key + "\", \"" + namespaceId + "\", \"" + id + "\", " + extra + " }"; + } + + private static @NotNull String blockExtra(@NotNull Block block, @NotNull String descKey, @NotNull I18n i18n) { + final float hardness = block.getHardness(); + final float blastResistance = block.getBlastResistance(null); + final int miningLevel = ItemToolPickaxe.miningLevels.getOrDefault(block, 0); + final int stackSize = block.asItem().getItemStackLimit(null); + final String description = i18n.translateKey(descKey + ".desc"); + final String tags = tagList(BlockTags.TAG_LIST, block); + return hardness + ", " + blastResistance + ", " + miningLevel + ", " + stackSize + + ", \"" + escape(description) + "\", \"" + escape(tags) + "\""; + } + + + private static @NotNull String itemExtra(@NotNull Item item, @NotNull String descKey, @NotNull I18n i18n) { + final int durability = item.getMaxDamage(); + final int restores = item instanceof ItemFood food ? food.getHealAmount(new ItemStack(item)) : 0; + final int stackSize = item.getItemStackLimit(null); + final String description = i18n.translateKey(descKey + ".desc"); + final String tags = tagList(ItemTags.TAG_LIST, item); + return durability + ", " + restores + ", " + stackSize + + ", \"" + escape(description) + "\", \"" + escape(tags) + "\""; + } + + private static > @NotNull String tagList(@NotNull List> tagList, @NotNull T taggable) { + final StringBuilder builder = new StringBuilder(); + for (final Tag tag : tagList) { + if (tag.appliesTo(taggable)) { + if (!builder.isEmpty()) builder.append(", "); + builder.append(tag.getName()); + } + } + return builder.toString(); + } + + private static @NotNull String escape(@NotNull String value) { + return value + .replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\r", " ") + .replace("\n", " "); + } +} diff --git a/game/client/src/main/java/net/minecraft/client/Minecraft.java b/game/client/src/main/java/net/minecraft/client/Minecraft.java index 7ae77976a..943af4200 100644 --- a/game/client/src/main/java/net/minecraft/client/Minecraft.java +++ b/game/client/src/main/java/net/minecraft/client/Minecraft.java @@ -1384,7 +1384,7 @@ public class Minecraft if (delta <= 200L) { int scrollDelta = Mouse.getEventDWheel(); if (scrollDelta != 0) { - if (this.thePlayer.noPhysics && this.toggleFlyPressed) { + if (this.thePlayer.hasNoPhysics() && this.toggleFlyPressed) { final float steps = 20; int step = Math.round(GameSettings.FLIGHT_SPEED.value * steps); @@ -1415,7 +1415,7 @@ public class Minecraft if (this.toggleFlyPressed && !GameSettings.KEY_FLY.isPressed()) { this.toggleFlyPressed = false; if (this.flyPressedTime + 400 > System.currentTimeMillis()) { - this.thePlayer.setNoclip(!this.thePlayer.noPhysics); + this.thePlayer.setNoclip(!this.thePlayer.hasNoPhysics()); } } while (Keyboard.next()) { @@ -1950,7 +1950,7 @@ public class Minecraft } } if (controller.digitalPad.left.pressedThisFrame() && this.thePlayer.getGamemode().hasPlayerFlight()) { - this.thePlayer.setNoclip(!this.thePlayer.noPhysics); + this.thePlayer.setNoclip(!this.thePlayer.hasNoPhysics()); } if (controller.buttonLeftShoulder.pressedThisFrame()) { this.thePlayer.inventory.changeCurrentSlot(1); 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 9c354cfe4..46ca4805d 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 @@ -92,7 +92,7 @@ public class PlayerLocal extends Player { } public @Nullable AABBdc getCollisionAABB() { - if (this.noPhysics) { + if (this.hasNoPhysics()) { return null; } else { return super.getCollisionAABB(); @@ -117,7 +117,7 @@ public class PlayerLocal extends Player { } this.inventoryMenu = newContainer; if (!gamemode.hasPlayerFlight()) { - this.noPhysics = false; + setNoclip(false); } this.fireImmune = gamemode.hasFireImmunity(); @@ -144,7 +144,7 @@ public class PlayerLocal extends Player { } this.prevTimeInPortal = this.timeInPortal; - if (this.inPortal && !this.noPhysics) { + if (this.inPortal && !this.hasNoPhysics()) { if (!this.world.isClientSide && this.vehicle != null) { startRiding(null); } @@ -192,7 +192,7 @@ public class PlayerLocal extends Player { checkAndPushInTile(this.x - (double) this.bbWidth * 0.35D, this.bb.minY + 0.5D, this.z - (double) this.bbWidth * 0.35D); checkAndPushInTile(this.x + (double) this.bbWidth * 0.35D, this.bb.minY + 0.5D, this.z - (double) this.bbWidth * 0.35D); checkAndPushInTile(this.x + (double) this.bbWidth * 0.35D, this.bb.minY + 0.5D, this.z + (double) this.bbWidth * 0.35D); - boolean canSprint = this.world.getGameRuleValue(GameRules.ALLOW_SPRINTING) && !this.noPhysics; + boolean canSprint = this.world.getGameRuleValue(GameRules.ALLOW_SPRINTING) && !this.hasNoPhysics(); if (this.onGround && !forwardInput && this.input.moveForward >= f && !isSprinting() && canSprint) { if (this.sprintTimer == 0) { this.sprintTimer = 7; @@ -388,12 +388,19 @@ public class PlayerLocal extends Player { public void animate4() { } + @Override + public void revokeAchievement(Stat statbase) { + if (statbase != null && statbase.isAchievement()) { + this.mc.statsCounter.remove(statbase); + } + } + @Override public void addStat(@Nullable Stat stat, int i) { if (stat == null) { return; } - if (stat.isAchievement()) { + if (i > 0 && stat.isAchievement()) { Achievement achievement = (Achievement) stat; if (achievement.parent == null || this.mc.statsCounter.isUnlocked(achievement.parent)) { if (!this.mc.statsCounter.isUnlocked(achievement)) { @@ -404,6 +411,7 @@ public class PlayerLocal extends Player { } else { this.mc.statsCounter.add(stat, i); } + } @Override @@ -413,7 +421,7 @@ public class PlayerLocal extends Player { @Override protected boolean checkAndPushInTile(double x, double y, double z) { - if (this.noPhysics) return false; + if (this.hasNoPhysics()) return false; final boolean isShort = this.isDwarf || this.isSneaking(); final int blockX = MathHelper.floor(x); final int blockY = MathHelper.floor(y); diff --git a/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocalMultiplayer.java b/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocalMultiplayer.java index b0cc54832..556f24d0d 100644 --- a/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocalMultiplayer.java +++ b/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocalMultiplayer.java @@ -150,6 +150,12 @@ public class PlayerLocalMultiplayer extends PlayerLocal implements Player.Player } } + @Override + public void setNoclip(boolean noclip) { + super.setNoclip(noclip); + this.sendQueue.addToSendQueue(noclip ? new PacketUpdatePlayerState(PacketUpdatePlayerState.STATE_NO_CLIP) : new PacketUpdatePlayerState(PacketUpdatePlayerState.STATE_DO_CLIP)); + } + @Override public void dropCurrentItem(boolean dropFullStack) { sendQueue.addToSendQueue(new PacketPlayerAction(dropFullStack ? PacketPlayerAction.ACTION_DROP_ITEM_STACK : PacketPlayerAction.ACTION_DROP_ITEM_SINGLE, 0, 0, 0, Side.NONE, 0.0, 0.0)); diff --git a/game/client/src/main/java/net/minecraft/client/entity/player/PlayerRemote.java b/game/client/src/main/java/net/minecraft/client/entity/player/PlayerRemote.java index fc4ce6ff5..5ebb6ca53 100644 --- a/game/client/src/main/java/net/minecraft/client/entity/player/PlayerRemote.java +++ b/game/client/src/main/java/net/minecraft/client/entity/player/PlayerRemote.java @@ -31,7 +31,7 @@ public class PlayerRemote extends Player this.username = username; this.uuid = uuid; footSize = 0.0F; - noPhysics = true; + setNoPhysics(true); sleepOffY = 0.25F; viewScale = 10D; diff --git a/game/client/src/main/java/net/minecraft/client/gui/TexturedButtonElement.java b/game/client/src/main/java/net/minecraft/client/gui/TexturedButtonElement.java index f8f270919..9838ff6c8 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/TexturedButtonElement.java +++ b/game/client/src/main/java/net/minecraft/client/gui/TexturedButtonElement.java @@ -2,6 +2,7 @@ package net.minecraft.client.gui; import net.minecraft.client.Minecraft; import net.minecraft.client.render.renderer.GLRenderer; +import net.minecraft.client.render.window.CursorShape; public class TexturedButtonElement extends ButtonElement { private final String texturePath; @@ -23,6 +24,11 @@ public class TexturedButtonElement extends ButtonElement { mc.textureManager.loadTexture(this.texturePath).bind(); GLRenderer.setColor4f(1.0F, 1.0F, 1.0F, 1.0F); boolean isHovered = mouseX >= this.xPosition && mouseY >= this.yPosition && mouseX < this.xPosition + this.width && mouseY < this.yPosition + this.height; + + if (isHovered && this.visible && this.enabled) { + mc.currentScreen.setDesiredCursor(CursorShape.HAND); + } + if (!this.enabled) { drawTexturedModalRect(this.xPosition, this.yPosition, this.u, this.v, this.width, this.height); } else if (!isHovered) { diff --git a/game/client/src/main/java/net/minecraft/client/gui/achievements/ScreenAchievements.java b/game/client/src/main/java/net/minecraft/client/gui/achievements/ScreenAchievements.java index 8485dd9dc..1fef0562c 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/achievements/ScreenAchievements.java +++ b/game/client/src/main/java/net/minecraft/client/gui/achievements/ScreenAchievements.java @@ -791,22 +791,31 @@ public class ScreenAchievements extends Screen return Math.sin(((double)(System.currentTimeMillis() % period) / period) * Math.PI * 2D) * amplitude; } private void drawConnectingLines(int mouseX, int mouseY, double shiftX, double shiftY){ + drawConnectingLinesPass(mouseX, mouseY, shiftX, shiftY, false); + drawConnectingLinesPass(mouseX, mouseY, shiftX, shiftY, true); + } + + private void drawConnectingLinesPass(int mouseX, int mouseY, double shiftX, double shiftY, boolean unlockedPass){ double zoom = this.viewportZoom; for(AchievementPage.AchievementEntry entry : this.currentPage.getAchievementEntries()) { Achievement ach = entry.achievement; - AchievementPage.AchievementEntry parentEntry = this.currentPage.getEntry(ach.parent); - if(parentEntry == null) { + Achievement parent = ach.parent; + if (parent == null || !this.currentPage.hasAchievement(parent)) { continue; } - if (!this.currentPage.hasAchievement(ach) || !this.currentPage.hasAchievement(ach.parent)){ + if (isSecretUndiscovered(ach) || isSecretUndiscovered(parent)) { + continue; + } + boolean unlocked = this.statsCounter.isUnlocked(ach); + if (unlocked != unlockedPass) { continue; } + AchievementPage.AchievementEntry parentEntry = this.currentPage.getEntry(parent); double childX = (this.viewportLeft + this.viewportWidth /2d) + ((entry.x * ACHIEVEMENT_CELL_WIDTH - shiftX) + 11) * zoom; double childY = (this.viewportTop + this.viewportHeight /2d) + ((entry.y * ACHIEVEMENT_CELL_HEIGHT - shiftY) + 11) * zoom; double parentX = (this.viewportLeft + this.viewportWidth /2d) + ((parentEntry.x * ACHIEVEMENT_CELL_WIDTH - shiftX) + 11) * zoom; double parentY = (this.viewportTop + this.viewportHeight /2d) + ((parentEntry.y * ACHIEVEMENT_CELL_HEIGHT - shiftY) + 11) * zoom; - boolean unlocked = this.statsCounter.isUnlocked(ach); boolean canUnlock = this.statsCounter.canUnlock(ach); final double zoomOff = 11 * zoom; @@ -848,6 +857,10 @@ public class ScreenAchievements extends Screen } } + public boolean isSecretUndiscovered(@NotNull Achievement achievement) { + return achievement.getType().hidden() && !this.statsCounter.isUnlocked(achievement); + } + @Nullable private Achievement drawAchievementIcons(int mouseX, int mouseY, double shiftX, double shiftY){ double zoom = this.viewportZoom; @@ -855,6 +868,7 @@ public class ScreenAchievements extends Screen Achievement hoveredAchievment = null; for(AchievementPage.AchievementEntry entry : this.currentPage.getAchievementEntries()) { Achievement ach = entry.achievement; + boolean secretUndiscovered = isSecretUndiscovered(ach); double achViewX = (this.viewportLeft + this.viewportWidth /2d) + (entry.x * ACHIEVEMENT_CELL_WIDTH - shiftX ) * zoom; double achViewY = (this.viewportTop + this.viewportHeight /2d) + (entry.y * ACHIEVEMENT_CELL_HEIGHT - shiftY) * zoom; if(achViewX < this.viewportLeft - ACHIEVEMENT_CELL_WIDTH * zoom || achViewY < this.viewportTop - ACHIEVEMENT_CELL_HEIGHT * zoom || achViewX > this.viewportRight || achViewY > this.viewportBottom) { // Continue if outside viewport @@ -864,7 +878,7 @@ public class ScreenAchievements extends Screen if(this.statsCounter.isUnlocked(ach)) { float brightness = 1.0F; GLRenderer.setColor4f(brightness, brightness, brightness, 1.0F); - } else if(this.statsCounter.canUnlock(ach)) { + } else if(!secretUndiscovered && this.statsCounter.canUnlock(ach)) { // Flicker if can unlock float brightness = timeSin(1, 600) >= 0.6 ? 0.6F : 0.8F; GLRenderer.setColor4f(brightness, brightness, brightness, 1.0F); @@ -876,23 +890,25 @@ public class ScreenAchievements extends Screen drawGuiIconDouble(achViewX - (ACHIEVEMENT_ICON_WIDTH - ACHIEVEMENT_CELL_WIDTH) * zoom, achViewY - (ACHIEVEMENT_ICON_HEIGHT - ACHIEVEMENT_CELL_HEIGHT) * zoom, ACHIEVEMENT_ICON_WIDTH * zoom, ACHIEVEMENT_ICON_HEIGHT * zoom, this.currentPage.getAchievementIcon(ach)); - if(!this.statsCounter.canUnlock(ach)) { + if(!secretUndiscovered && !this.statsCounter.canUnlock(ach)) { float brightness = 0.1F; GLRenderer.setColor4f(brightness, brightness, brightness, 1.0F); } - GLRenderer.pushFrame(); - GLRenderer.globalSetLightEnabled(true); - GLRenderer.enableState(State.CULL_FACE); - ItemStack achievementItem = ach.iconStack; + if (!secretUndiscovered) { + GLRenderer.pushFrame(); + GLRenderer.globalSetLightEnabled(true); + GLRenderer.enableState(State.CULL_FACE); + ItemStack achievementItem = ach.iconStack; - GLRenderer.modelM4f().translate((float) (achViewX + 3 * zoom), (float) (achViewY + 3 * zoom), 0); - GLRenderer.modelM4f().scale((float) zoom, (float) zoom, 1); -// ItemModelDispatcher.getInstance().getDispatch(achievementItem).renderItemIntoGui(GLRenderer.getTessellator(), mc.font, mc.textureManager, achievementItem.getDefaultStack(), 0, 0, 1.0f); - ItemModelDispatcher.getInstance().getDispatch(achievementItem).renderGui(GLRenderer.getTessellator(), null, achievementItem, 0, 0, LightIndexHelper.lightIndex2i(15, 15), 1f); + GLRenderer.modelM4f().translate((float) (achViewX + 3 * zoom), (float) (achViewY + 3 * zoom), 0); + GLRenderer.modelM4f().scale((float) zoom, (float) zoom, 1); + // ItemModelDispatcher.getInstance().getDispatch(achievementItem).renderItemIntoGui(GLRenderer.getTessellator(), mc.font, mc.textureManager, achievementItem.getDefaultStack(), 0, 0, 1.0f); + ItemModelDispatcher.getInstance().getDispatch(achievementItem).renderGui(GLRenderer.getTessellator(), null, achievementItem, 0, 0, LightIndexHelper.lightIndex2i(15, 15), 1f); - GLRenderer.globalSetLightEnabled(false); - GLRenderer.popFrame(); + GLRenderer.globalSetLightEnabled(false); + GLRenderer.popFrame(); + } GLRenderer.setColor4f(1.0F, 1.0F, 1.0F, 1.0F); @@ -904,10 +920,21 @@ public class ScreenAchievements extends Screen return hoveredAchievment; } private void drawAchievementToolTip(Achievement achievement, int mouseX, int mouseY){ - String name = achievement.getStatName(); - String desc = achievement.getDescription(); final int padding = this.tooltip.getPadding(); int[] boxRender; + if (isSecretUndiscovered(achievement)) { + String name = I18n.getInstance().translateKey("gui.achievements.label.secret"); + String desc = I18n.getInstance().translateKey("gui.achievements.label.secret.desc"); + int boxWidth = Math.max(Math.max(MathHelper.ceil(this.fontRenderer.stringWidthDouble(name)), MathHelper.ceil(this.fontRenderer.stringWidthDouble(desc))), TOOLTIP_BOX_WIDTH_MIN); + int boxHeight = MathHelper.ceil(this.fontRenderer.heightOfConstrainedChars(desc, boxWidth)); + boxRender = this.tooltip.drawBackground(mouseX, mouseY, TOOLTIP_OFF_X, TOOLTIP_OFF_Y, boxWidth + 3, boxHeight + 3 + 12); + this.fontRenderer.renderWidthConstrained(desc, boxRender[0] + padding, boxRender[1] + 12 + padding, boxWidth).setColor(0xff705050).call(); + drawStringShadow(this.fontRenderer, name, boxRender[0] + padding, boxRender[1] + padding, achievement.getType().colorNameLocked()); + return; + } + + String name = achievement.getStatName(); + String desc = achievement.getDescription(); if(this.statsCounter.canUnlock(achievement)) { // Can unlock/ is unlocked int boxWidth = Math.max(MathHelper.ceil(this.fontRenderer.stringWidthDouble(name)), TOOLTIP_BOX_WIDTH_MIN); int boxHeight = MathHelper.ceil(this.fontRenderer.heightOfConstrainedChars(desc, boxWidth)); diff --git a/game/client/src/main/java/net/minecraft/client/gui/achievements/data/AchievementPages.java b/game/client/src/main/java/net/minecraft/client/gui/achievements/data/AchievementPages.java index 9d8b31348..2f5448dad 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/achievements/data/AchievementPages.java +++ b/game/client/src/main/java/net/minecraft/client/gui/achievements/data/AchievementPages.java @@ -52,17 +52,39 @@ public class AchievementPages { overworldPage.addAchievement(Achievements.TRIPLE_HIT, 11, -2); netherPage = register(new AchievementPageNether("gui.achievements.page.nether", Blocks.COBBLE_NETHERRACK.getDefaultStack())); - netherPage.addAchievement(Achievements.ENTER_NETHER, 2, 0); - netherPage.addAchievement(Achievements.GET_NETHERCOAL, 1, 2); - netherPage.addAchievement(Achievements.LIGHT_SIGN, 4, 0); - netherPage.addAchievement(Achievements.SWIM_NETHER, 3, 2); - netherPage.addAchievement(Achievements.HIT_FIREBALL, 3, -2); - netherPage.addAchievement(Achievements.SLEEP_NETHER, 1, -2); // netherPage.addAchievement(Achievements.ENTER_WARRENS, 0, 5); - netherPage.addAchievement(Achievements.MOST_WANTED, 0, -1); - netherPage.addAchievement(Achievements.FAST_TRAVEL, 0, 1); - netherPage.addAchievement(Achievements.GET_STEEL_BLAST_FURNACE, 0, 4); - netherPage.addAchievement(Achievements.OBTAIN_STEEL, -2, 4); + + netherPage.addAchievement(Achievements.ENTER_NETHER, 0, 0); + netherPage.addAchievement(Achievements.MOST_WANTED, -2, 1); + netherPage.addAchievement(Achievements.FAST_TRAVEL, 2, 1); + netherPage.addAchievement(Achievements.DISCOVER_SULFURPOOLS, 4, 0); + netherPage.addAchievement(Achievements.COLLECT_ACID, 5, 2); + netherPage.addAchievement(Achievements.CONVERT_ACID_TO_SULFUR, 7, 2); + netherPage.addAchievement(Achievements.CRAFT_ACID_COBBLE_TO_STONE, 6, 1); + netherPage.addAchievement(Achievements.CONVERT_ACID_TO_STONE, 6, 3); + netherPage.addAchievement(Achievements.ACID_BATH, 7, -1); + netherPage.addAchievement(Achievements.LIGHT_SIGN, 2, -1); + netherPage.addAchievement(Achievements.DISCOVER_VOLCANICISLANDS, -4, 0); + netherPage.addAchievement(Achievements.FLING_VENT, -4, 2); + netherPage.addAchievement(Achievements.WHAT_WAS_THAT, -6, 1); + netherPage.addAchievement(Achievements.COLLECT_PUMICE, -6, -1); + netherPage.addAchievement(Achievements.GET_NETHERCOAL, -1, -2); + netherPage.addAchievement(Achievements.GET_STEEL_BLAST_FURNACE, -3, -3); + netherPage.addAchievement(Achievements.OBTAIN_STEEL, -5, -3); + netherPage.addAchievement(Achievements.CRAFT_STEEL_BUCKET, -6, -4); + netherPage.addAchievement(Achievements.DISCOVER_CRYSTALFOREST, 0, 4); + netherPage.addAchievement(Achievements.COLLECT_RUBYGLASS, 2, 4); + netherPage.addAchievement(Achievements.CRAFT_CONDUIT, 3, 3); + netherPage.addAchievement(Achievements.SWIM_NETHER, -2, 3); + netherPage.addAchievement(Achievements.DISCOVER_OLDWORLD, 0, -4); + netherPage.addAchievement(Achievements.TRIGGER_EMBER, -2, -5); + netherPage.addAchievement(Achievements.HIT_FIREBALL, 2, -3); + netherPage.addAchievement(Achievements.SLEEP_NETHER, 4, -4); + + + + + } static { init(); diff --git a/game/client/src/main/java/net/minecraft/client/gui/achievements/pages/AchievementPageNether.java b/game/client/src/main/java/net/minecraft/client/gui/achievements/pages/AchievementPageNether.java index 3353e308d..96da2d4e7 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/achievements/pages/AchievementPageNether.java +++ b/game/client/src/main/java/net/minecraft/client/gui/achievements/pages/AchievementPageNether.java @@ -51,7 +51,7 @@ public class AchievementPageNether extends AchievementPage { final int offsetY = tileY + random.nextInt(3) - random.nextInt(3); int r = random.nextInt(50); - IconCoordinate texture = getTextureFromBlock(Blocks.COBBLE_NETHERRACK); + IconCoordinate texture = random.nextBoolean() ? getTextureFromBlock(Blocks.NETHERRACK) : getTextureFromBlock(Blocks.COBBLE_NETHERRACK); if (offsetY >= bedrockLayer) { texture = getTextureFromBlock(Blocks.BEDROCK); } else if (r < 10) { @@ -86,6 +86,15 @@ public class AchievementPageNether extends AchievementPage { cave--; } + } else if (layerCache.id == 1) { + // Remove covered icons so the deeper layer only shows through carved caves + IconCoordinate[] foreground = screen.getLayer(0).getData(); + IconCoordinate[] background = layerCache.getData(); + for (int i = 0; i < background.length; i++) { + if (foreground[i] != null){ + background[i] = null; + } + } } } @@ -96,7 +105,7 @@ public class AchievementPageNether extends AchievementPage { @Override public int backgroundLayers() { - return 1; + return 2; } @Override diff --git a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenFlagEditor.java b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenFlagEditor.java index 9bef9bdc4..1f646c930 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenFlagEditor.java +++ b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenFlagEditor.java @@ -225,7 +225,7 @@ public class ScreenFlagEditor this.colorLabels[this.selectedColor].setTextColor(0xFF404040); this.colorLabels[this.selectedColor].setShadow(false); this.selectedColor = button.id - 100; - this.colorLabels[this.selectedColor].setTextColor(0xFFFFFFF); + this.colorLabels[this.selectedColor].setTextColor(0xFFFFFFFF); this.colorLabels[this.selectedColor].setShadow(true); } } 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 6274f1555..e0b69fe01 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 @@ -191,12 +191,12 @@ public class RecipePageCrafting for (SlotGuidebook slot : this.slots) { if (getIsMouseOverSlot(slot, x, y, mouseX, mouseY)) mouseOverSlot = slot; GLRenderer.setColor4f(1.0F, 1.0F, 1.0F, 1.0F); - if (mouseOverSlot != null && mouseOverSlot.hasItem()) { - boolean showDescription = DescriptionPromptEnum.showDescription(); - String str = this.tooltipElement.getTooltipText(mouseOverSlot.getItemStack(), showDescription, mouseOverSlot); - if (!str.isEmpty()) { - this.tooltipElement.render(str, mouseX, mouseY, 8, -8); - } + } + if (mouseOverSlot != null && mouseOverSlot.hasItem()) { + boolean showDescription = DescriptionPromptEnum.showDescription(); + String str = this.tooltipElement.getTooltipText(mouseOverSlot.getItemStack(), showDescription, mouseOverSlot); + if (!str.isEmpty()) { + this.tooltipElement.render(str, mouseX, mouseY, 8, -8); } } } diff --git a/game/client/src/main/java/net/minecraft/client/gui/guidebook/mobs/GuidebookPageMob.java b/game/client/src/main/java/net/minecraft/client/gui/guidebook/mobs/GuidebookPageMob.java index 1d6fae08b..12ac8a0f4 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/guidebook/mobs/GuidebookPageMob.java +++ b/game/client/src/main/java/net/minecraft/client/gui/guidebook/mobs/GuidebookPageMob.java @@ -280,12 +280,12 @@ public class GuidebookPageMob for (SlotGuidebook slot : this.slots) { if (getIsMouseOverSlot(slot, x, y, mouseX, mouseY)) mouseOverSlot = slot; GLRenderer.setColor4f(1.0F, 1.0F, 1.0F, 1.0F); - if (mouseOverSlot != null && mouseOverSlot.hasItem()) { - boolean showDescription = DescriptionPromptEnum.showDescription(); - String str = tooltipElement.getTooltipText(mouseOverSlot.getItemStack(), showDescription, mouseOverSlot); - if (!str.isEmpty()) { - tooltipElement.render(str, mouseX, mouseY, 8, -8); - } + } + if (mouseOverSlot != null && mouseOverSlot.hasItem()) { + boolean showDescription = DescriptionPromptEnum.showDescription(); + String str = tooltipElement.getTooltipText(mouseOverSlot.getItemStack(), showDescription, mouseOverSlot); + if (!str.isEmpty()) { + tooltipElement.render(str, mouseX, mouseY, 8, -8); } } diff --git a/game/client/src/main/java/net/minecraft/client/gui/guidebook/smelting/RecipePageBlastSmelting.java b/game/client/src/main/java/net/minecraft/client/gui/guidebook/smelting/RecipePageBlastSmelting.java index f9b070c9d..a29be00b7 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/guidebook/smelting/RecipePageBlastSmelting.java +++ b/game/client/src/main/java/net/minecraft/client/gui/guidebook/smelting/RecipePageBlastSmelting.java @@ -149,12 +149,12 @@ public class RecipePageBlastSmelting for (SlotGuidebook slot : this.slots) { if (getIsMouseOverSlot(slot, x, y, mouseX, mouseY)) mouseOverSlot = slot; GLRenderer.setColor4f(1.0F, 1.0F, 1.0F, 1.0F); - if (mouseOverSlot != null && mouseOverSlot.hasItem()) { - boolean showDescription = DescriptionPromptEnum.showDescription(); - String str = this.tooltipElement.getTooltipText(mouseOverSlot.getItemStack(), showDescription, mouseOverSlot); - if (!str.isEmpty()) { - this.tooltipElement.render(str, mouseX, mouseY, 8, -8); - } + } + if (mouseOverSlot != null && mouseOverSlot.hasItem()) { + boolean showDescription = DescriptionPromptEnum.showDescription(); + String str = this.tooltipElement.getTooltipText(mouseOverSlot.getItemStack(), showDescription, mouseOverSlot); + if (!str.isEmpty()) { + this.tooltipElement.render(str, mouseX, mouseY, 8, -8); } } } diff --git a/game/client/src/main/java/net/minecraft/client/gui/guidebook/smelting/RecipePageSmelting.java b/game/client/src/main/java/net/minecraft/client/gui/guidebook/smelting/RecipePageSmelting.java index 73c390edb..a5758ed29 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/guidebook/smelting/RecipePageSmelting.java +++ b/game/client/src/main/java/net/minecraft/client/gui/guidebook/smelting/RecipePageSmelting.java @@ -144,12 +144,12 @@ public class RecipePageSmelting for (SlotGuidebook slot : this.slots) { if (getIsMouseOverSlot(slot, x, y, mouseX, mouseY)) mouseOverSlot = slot; GLRenderer.setColor4f(1.0F, 1.0F, 1.0F, 1.0F); - if (mouseOverSlot != null && mouseOverSlot.hasItem()) { - boolean showDescription = DescriptionPromptEnum.showDescription(); - String str = this.tooltipElement.getTooltipText(mouseOverSlot.getItemStack(), showDescription, mouseOverSlot); - if (!str.isEmpty()) { - this.tooltipElement.render(str, mouseX, mouseY, 8, -8); - } + } + if (mouseOverSlot != null && mouseOverSlot.hasItem()) { + boolean showDescription = DescriptionPromptEnum.showDescription(); + String str = this.tooltipElement.getTooltipText(mouseOverSlot.getItemStack(), showDescription, mouseOverSlot); + if (!str.isEmpty()) { + this.tooltipElement.render(str, mouseX, mouseY, 8, -8); } } } diff --git a/game/client/src/main/java/net/minecraft/client/gui/guidebook/trommeling/RecipePageTrommel.java b/game/client/src/main/java/net/minecraft/client/gui/guidebook/trommeling/RecipePageTrommel.java index e32768445..b34d35a68 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/guidebook/trommeling/RecipePageTrommel.java +++ b/game/client/src/main/java/net/minecraft/client/gui/guidebook/trommeling/RecipePageTrommel.java @@ -191,12 +191,12 @@ public class RecipePageTrommel for (SlotGuidebook slot : this.slots) { if (getIsMouseOverSlot(slot, x, y, mouseX, mouseY)) mouseOverSlot = slot; GLRenderer.setColor4f(1.0F, 1.0F, 1.0F, 1.0F); - if (mouseOverSlot != null && mouseOverSlot.hasItem()) { - boolean showDescription = DescriptionPromptEnum.showDescription(); - String str = this.tooltipElement.getTooltipText(mouseOverSlot.getItemStack(), showDescription, mouseOverSlot); - if (!str.isEmpty()) { - this.tooltipElement.render(str, mouseX, mouseY, 8, -8); - } + } + if (mouseOverSlot != null && mouseOverSlot.hasItem()) { + boolean showDescription = DescriptionPromptEnum.showDescription(); + String str = this.tooltipElement.getTooltipText(mouseOverSlot.getItemStack(), showDescription, mouseOverSlot); + if (!str.isEmpty()) { + this.tooltipElement.render(str, mouseX, mouseY, 8, -8); } } } diff --git a/game/client/src/main/java/net/minecraft/client/gui/modelviewer/ScreenModelViewer.java b/game/client/src/main/java/net/minecraft/client/gui/modelviewer/ScreenModelViewer.java index f93d80aea..05f044b47 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/modelviewer/ScreenModelViewer.java +++ b/game/client/src/main/java/net/minecraft/client/gui/modelviewer/ScreenModelViewer.java @@ -86,7 +86,7 @@ public class ScreenModelViewer extends ScreenPhotoMode implements ListenerTextFi this.mc.currentWorld.entities.clear(); this.mc.currentWorld.tileEntityList.clear(); this.mc.thePlayer.absMoveTo(0.5, 64.5, 0.5, 180, 0); - this.mc.thePlayer.noPhysics = true; + this.mc.thePlayer.setNoclip(true); this.mc.textureManager.refreshTexturesAndDisplayErrors(); this.mc.textureManager.updateDynamicTextures(false); diff --git a/game/client/src/main/java/net/minecraft/client/gui/modelviewer/categories/ViewerCategoryEntity.java b/game/client/src/main/java/net/minecraft/client/gui/modelviewer/categories/ViewerCategoryEntity.java index ad43af76d..60f1217b3 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/modelviewer/categories/ViewerCategoryEntity.java +++ b/game/client/src/main/java/net/minecraft/client/gui/modelviewer/categories/ViewerCategoryEntity.java @@ -195,7 +195,7 @@ public class ViewerCategoryEntity extends ModelViewerCategory { DataDumper.main(new String[0]); - FileOpener.open(new File(mc.getMinecraftDir(), "infodump")); + FileOpener.open(new File(mc.getMinecraftDir(), DumpFolder.NAME)); Global.accessor = mc; })) + .withComponent(new ShortcutComponent("gui.options.page.debug.button.dump.wiki", () -> { + try { + WikiDumper.dump(mc.getMinecraftDir()); + FileOpener.open(new File(mc.getMinecraftDir(), DumpFolder.NAME)); + } catch (IOException e) { + LogUtils.getLogger().error("Failed to write wiki dump", e); + } + })) + .withComponent(new ShortcutComponent("gui.options.page.debug.button.dump.invsprite", () -> { + if (mc.currentWorld == null) return; // Biome/season tints only resolve inside a world + try { + FileOpener.open(InvSpriteDumper.dump(mc.getMinecraftDir())); + } catch (IOException e) { + LogUtils.getLogger().error("Failed to write inventory sprite dump", e); + } + }) { + private boolean hovered = false; + + @Override + public void render(@NotNull ScreenOptions screenOptions, final int x, final int y, final int width, final int relativeMouseX, final int relativeMouseY) { + this.button.enabled = mc.currentWorld != null; + this.hovered = relativeMouseX >= 0 && relativeMouseX <= width && relativeMouseY >= 0 && relativeMouseY <= getHeight(); + super.render(screenOptions, x, y, width, relativeMouseX, relativeMouseY); + } + + @Override + public @Nullable String getTooltipTranslationKey() { + return (this.hovered && mc.currentWorld == null) ? "gui.options.page.debug.button.dump.invsprite.tooltip" : null; + } + }) + .withComponent(new ShortcutComponent("gui.options.page.debug.button.dump.invsprite_apng", () -> { + if (mc.currentWorld == null) return; // Biome/season tints only resolve inside a world. + try { + FileOpener.open(InvSpriteAPNGDumper.dump(mc.getMinecraftDir())); + } catch (IOException e) { + LogUtils.getLogger().error("Failed to write animated inventory sprite dump", e); + } + }) { + private boolean hovered = false; + + @Override + public void render(@NotNull ScreenOptions screenOptions, final int x, final int y, final int width, final int relativeMouseX, final int relativeMouseY) { + this.button.enabled = mc.currentWorld != null; + this.hovered = relativeMouseX >= 0 && relativeMouseX <= width && relativeMouseY >= 0 && relativeMouseY <= getHeight(); + super.render(screenOptions, x, y, width, relativeMouseX, relativeMouseY); + } + + @Override + public @Nullable String getTooltipTranslationKey() { + return (this.hovered && mc.currentWorld == null) ? "gui.options.page.debug.button.dump.invsprite.tooltip" : null; + } + }) .withComponent(new ShortcutComponent("gui.options.page.debug.button.open.achievements", () -> mc.displayScreen(new ScreenAchievements(mc.currentScreen, mc.statsCounter)))) ) .withComponent(new OptionsCategory("gui.options.page.debug.category.test") diff --git a/game/client/src/main/java/net/minecraft/client/gui/popup/ListComponent.java b/game/client/src/main/java/net/minecraft/client/gui/popup/ListComponent.java index 9cecac3bd..ad2554f33 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/popup/ListComponent.java +++ b/game/client/src/main/java/net/minecraft/client/gui/popup/ListComponent.java @@ -1,9 +1,11 @@ package net.minecraft.client.gui.popup; +import net.minecraft.client.gui.TextFieldElement; import net.minecraft.client.render.Scissor; import net.minecraft.client.render.renderer.GLRenderer; import net.minecraft.client.render.renderer.Shaders; import net.minecraft.client.render.tessellator.TessellatorGeneral; +import net.minecraft.core.lang.I18n; import net.minecraft.core.sound.SoundCategory; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -17,15 +19,27 @@ import net.minecraft.core.util.helper.MathHelper; public class ListComponent extends InteractivePopupComponent { private final @NotNull Minecraft minecraft = Minecraft.getMinecraft(); + private int selectedItem; private float scrollAmount = 0; - private final @NotNull ButtonElement @NotNull [] buttons; + + private final @NotNull ButtonElement @NotNull [] unfilteredButtons; + private final @NotNull String @Nullable [] unfilteredButtonTooltips; + + // These hold the buttons after the filter is applied. You SHOULD use these. + private ButtonElement[] filteredButtons = null; + private int[] filteredButtonsIndexes = null; + + // permanent buffer for the search so we avoid allocating. Do NOT use. + private final @NotNull ButtonElement @NotNull [] temporaryFilterButtonBuffer; + private final int @NotNull [] temporaryFilterIndexBuffer; + private final int height; private int lastX = 0; private int lastY = 0; private final boolean closeOnItemSelected; private final @NotNull TooltipElement tooltip; - private final @NotNull String @Nullable [] buttonTooltips; + private int scrollbarX; private int scrollbarY; @@ -35,6 +49,8 @@ public class ListComponent private int clickY; private float previousScrollAmount = 0.0f; + private TextFieldElement searchBar = null; + public ListComponent( final int width, final int height, @@ -47,25 +63,67 @@ public class ListComponent this.height = height; this.closeOnItemSelected = closeOnItemSelected; + assert this.minecraft != null; + this.tooltip = new TooltipElement(this.minecraft); + this.searchBar = new TextFieldElement(this.getParent(), this.minecraft.font, 0, 0, this.getWidth(), 20, "", I18n.getInstance().translateKey("gui.inventory.creative.label.search")); this.selectedItem = selectedItem; - this.buttonTooltips = buttonTooltips; + this.unfilteredButtonTooltips = buttonTooltips; - this.buttons = new ButtonElement[buttonLabels.length]; - for (int i = 0; i < this.buttons.length; i++) { - this.buttons[i] = new ButtonElement(i, 0, 0, width - 2 - 6, 20, buttonLabels[i]); + this.unfilteredButtons = new ButtonElement[buttonLabels.length]; + for (int i = 0; i < this.unfilteredButtons.length; i++) { + this.unfilteredButtons[i] = new ButtonElement(i, 0, 0, width - 2 - 6, 20, buttonLabels[i]); if (i == selectedItem) { - this.buttons[i].enabled = false; + this.unfilteredButtons[i].enabled = false; } } // Scroll so selected item is in view final float ratio = (float) (this.getScrollableHeight() + this.height) / this.getScrollableHeight(); - float sc = (selectedItem / (float) (this.buttons.length - 4)) * ratio; + float sc = (selectedItem / (float) (this.unfilteredButtons.length - 4)) * ratio; sc -= ((float) this.height / this.getScrollableHeight()); this.scroll(sc); + + this.temporaryFilterButtonBuffer = new ButtonElement[this.unfilteredButtons.length]; + this.temporaryFilterIndexBuffer = new int[this.unfilteredButtons.length]; + + updateButtonFilter(); + this.searchBar.setTextChangeListener(textFieldElement -> updateButtonFilter()); + } + + // I doubt this is a very good filter allocation wise. But this isn't performance critical anyway. + // - kheppy + private boolean filterString(String filter, String input) { + return input.toLowerCase().contains(filter.toLowerCase()); + } + + public void updateButtonFilter() { + int count = 0; + + for (int i = 0; i < this.unfilteredButtons.length; i++) { + var btn = this.unfilteredButtons[i]; + + if (filterString(this.searchBar.getText(), btn.displayString)) { + temporaryFilterButtonBuffer[count] = btn; + temporaryFilterIndexBuffer[count] = i; + count += 1; + } + } + + if (this.filteredButtons == null || this.filteredButtons.length != count) { + this.scrollAmount = 0; + this.filteredButtons = new ButtonElement[count]; + this.filteredButtonsIndexes = new int[count]; + } + + System.arraycopy(temporaryFilterButtonBuffer, 0, this.filteredButtons, 0, count); + System.arraycopy(temporaryFilterIndexBuffer, 0, this.filteredButtonsIndexes, 0, count); + } + + private int getSearchBarOffset() { + return this.searchBar.height + 5; } private boolean canScroll() { @@ -95,32 +153,39 @@ public class ListComponent this.lastX = x; this.lastY = y; + this.searchBar.xPosition = x; + this.searchBar.yPosition = y; + this.searchBar.drawTextBox(); + this.searchBar.updateCursor(Minecraft.getMinecraft(), mouseX, mouseY); + // Do scroll if (this.canScroll() && this.mouseInRegion(x, y, mouseX, mouseY)) { final float wheel = Mouse.getDWheel(); if (wheel != 0.0f) this.scroll(wheel / -12.0f); } + GLRenderer.pushFrame(); GLRenderer.setShader(Shaders.COLOR); - this.drawBackground(x, y); - this.drawScrollbar(x, y, mouseY); + this.drawBackground(x, y + getSearchBarOffset()); + this.drawScrollbar(x, y + getSearchBarOffset(), mouseY); GLRenderer.popFrame(); - Scissor.enable(x + 1, y + 1, this.getWidth() - 2, this.getHeight() - 2); + Scissor.enable(x + 1, y + getSearchBarOffset() + 1, this.getWidth() - 2, this.getHeight() - 2 - getSearchBarOffset()); // Draw buttons - for (int i = 0; i < this.buttons.length; i++) { + for (int i = 0; i < this.filteredButtons.length; ++i) { if (this.canScroll()) { - this.buttons[i].width = this.getWidth() - 2 - 6; + this.filteredButtons[i].width = this.getWidth() - 2 - 6; } else { - this.buttons[i].width = this.getWidth() - 2; + this.filteredButtons[i].width = this.getWidth() - 2; } - final ButtonElement button = this.buttons[i]; + ButtonElement button = this.filteredButtons[i]; button.xPosition = x + 1; - button.yPosition = (y + 1) + (i * 20) - this.getScrollPixels(); + button.yPosition = (y + getSearchBarOffset() + 1) + i * 20 - this.getScrollPixels(); + if (this.mouseInRegion(x, y, mouseX, mouseY)) { button.drawButton(this.minecraft, mouseX, mouseY); } else { @@ -130,13 +195,17 @@ public class ListComponent Scissor.disable(); - if (this.buttonTooltips != null && this.mouseInRegion(x, y, mouseX, mouseY)) { - for (int i = 0; i < this.buttons.length; i++) { - final ButtonElement button = this.buttons[i]; + if (this.unfilteredButtonTooltips != null && this.mouseInRegion(x, y, mouseX, mouseY)) { + + for(int i = 0; i < this.filteredButtons.length; ++i) { + ButtonElement button = this.filteredButtons[i]; + if (button.isHovered(mouseX, mouseY)) { - this.tooltip.render(this.buttonTooltips[i], mouseX, mouseY, 0, 0); + final int buttonIndex = this.filteredButtonsIndexes[i]; + this.tooltip.render(this.unfilteredButtonTooltips[buttonIndex], mouseX, mouseY, 0, 0); break; } + } } } @@ -146,17 +215,19 @@ public class ListComponent } private int getScrollPixels() { - return (int) (this.scrollAmount * (this.getScrollableHeight() - (this.height - 2))); + return (int) (this.scrollAmount * (this.getScrollableHeight() - (this.height - 2 - this.getSearchBarOffset()))); } private int getScrollableHeight() { - return 20 * this.buttons.length; + return 20 * (this.filteredButtons != null ? this.filteredButtons.length : this.unfilteredButtons.length); } @Override public void onClick(final int x, final int y, final int button) { super.onClick(x, y, button); + this.searchBar.mouseClicked(x, y, button); + final int scrollableHeight = this.getScrollableHeight(); final int displayRegionHeight = this.height - 2; final boolean canScroll = scrollableHeight > displayRegionHeight; @@ -169,9 +240,9 @@ public class ListComponent } if (this.mouseInRegion(this.lastX, this.lastY, x, y)) { - for (final ButtonElement b : this.buttons) { + for (final ButtonElement b : this.filteredButtons) { if (b.mouseClicked(this.minecraft, x, y)) { - for (final ButtonElement b2 : this.buttons) { + for (final ButtonElement b2 : this.filteredButtons) { b2.enabled = true; } @@ -191,6 +262,18 @@ public class ListComponent } } + @Override + public void onKeyDown(int keyCode, char c) { + super.onKeyDown(keyCode, c); + this.searchBar.textboxKeyTyped(c, keyCode); + } + + @Override + public void tick() { + super.tick(); + searchBar.updateCursorCounter(); + } + @Override public void mouseMovedOrUp(final int x, final int y, final int button) { super.mouseMovedOrUp(x, y, button); @@ -205,15 +288,15 @@ public class ListComponent final @NotNull TessellatorGeneral tessellator = GLRenderer.getTessellator(); tessellator.startDrawingQuads(); tessellator.setColorOpaque1i(0xA0A0A0); - tessellator.addVertex(x, y + this.height, 0.0D); - tessellator.addVertex(x + this.getWidth(), y + this.height, 0.0D); + tessellator.addVertex(x, y + this.height - this.getSearchBarOffset(), 0.0D); + tessellator.addVertex(x + this.getWidth(), y + this.height - this.getSearchBarOffset(), 0.0D); tessellator.addVertex(x + this.getWidth(), y, 0.0D); tessellator.addVertex(x, y, 0.0D); tessellator.draw(); tessellator.startDrawingQuads(); tessellator.setColorOpaque1i(0x000000); - tessellator.addVertex(x + 1, y + this.height - 1, 0.0D); - tessellator.addVertex(x + this.getWidth() - 1, y + this.height - 1, 0.0D); + tessellator.addVertex(x + 1, y + this.height - this.getSearchBarOffset() - 1, 0.0D); + tessellator.addVertex(x + this.getWidth() - 1, y + this.height - this.getSearchBarOffset() - 1, 0.0D); tessellator.addVertex(x + this.getWidth() - 1, y + 1, 0.0D); tessellator.addVertex(x + 1, y + 1, 0.0D); tessellator.draw(); @@ -221,7 +304,7 @@ public class ListComponent private void drawScrollbar(final int x, final int y, final int mouseY) { final int scrollableHeight = this.getScrollableHeight(); - final int displayRegionHeight = this.height - 2; + final int displayRegionHeight = this.height - 2 - this.getSearchBarOffset(); if (!this.canScroll()) { return; diff --git a/game/client/src/main/java/net/minecraft/client/input/PlayerInput.java b/game/client/src/main/java/net/minecraft/client/input/PlayerInput.java index 830b7e6a7..5546ff18b 100644 --- a/game/client/src/main/java/net/minecraft/client/input/PlayerInput.java +++ b/game/client/src/main/java/net/minecraft/client/input/PlayerInput.java @@ -38,7 +38,7 @@ public class PlayerInput { } if(GameSettings.KEY_SNEAK.isKeyOrMouse(keyCode, mouseCode)) { - if(GameSettings.SNEAK_TOGGLE.value && !this.mc.thePlayer.noPhysics) { + if(GameSettings.SNEAK_TOGGLE.value && !this.mc.thePlayer.hasNoPhysics()) { if(pressed) { this.sneak = !this.sneak; } @@ -99,7 +99,7 @@ public class PlayerInput { this.moveForward = -this.mc.controllerInput.joyLeft.getY(); this.moveStrafe = -this.mc.controllerInput.joyLeft.getX(); this.jump = this.mc.controllerInput.buttonA.isPressed(); - if (this.mc.thePlayer.noPhysics) { + if (this.mc.thePlayer.hasNoPhysics()) { this.sneak = this.mc.controllerInput.joyRight.getButton().isPressed(); } else { if(this.mc.controllerInput.joyRight.getButton().pressedThisFrame()) { @@ -117,7 +117,7 @@ public class PlayerInput { this.moveForward = MathHelper.clamp(this.moveForward, -1, 1); this.moveStrafe = MathHelper.clamp(this.moveStrafe, -1, 1); - if(entityplayer.isSneaking() && !entityplayer.noPhysics) { + if(entityplayer.isSneaking() && !entityplayer.hasNoPhysics()) { this.moveStrafe *= 0.3f; this.moveForward *= 0.3f; } diff --git a/game/client/src/main/java/net/minecraft/client/net/handler/PacketHandlerClient.java b/game/client/src/main/java/net/minecraft/client/net/handler/PacketHandlerClient.java index aec3987af..47d2bc3f8 100644 --- a/game/client/src/main/java/net/minecraft/client/net/handler/PacketHandlerClient.java +++ b/game/client/src/main/java/net/minecraft/client/net/handler/PacketHandlerClient.java @@ -1022,6 +1022,10 @@ public class PacketHandlerClient extends PacketHandler { return; } if (stat.clientside) return; // Don't allow server to give client side only stats + if (packetStatistic.valueChange < 0 && stat.isAchievement()) { + this.mc.thePlayer.revokeAchievement(stat); + return; + } this.mc.thePlayer.addStat(stat, packetStatistic.valueChange); } diff --git a/game/client/src/main/java/net/minecraft/client/render/ItemRenderer.java b/game/client/src/main/java/net/minecraft/client/render/ItemRenderer.java index ba93e9343..4fdeb33d1 100644 --- a/game/client/src/main/java/net/minecraft/client/render/ItemRenderer.java +++ b/game/client/src/main/java/net/minecraft/client/render/ItemRenderer.java @@ -147,7 +147,7 @@ public class ItemRenderer { renderFireInFirstPerson(partialTicks); } - if (this.mc.thePlayer.isInWall() && !this.mc.thePlayer.noPhysics) { + if (this.mc.thePlayer.isInWall() && !this.mc.thePlayer.hasNoPhysics()) { TilePos blockPos = this.mc.activeCamera.getTilePos(partialTicks); TextureRegistry.worldAtlas.bind(); 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 3ae20b3f5..3fe26f5b6 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 @@ -1115,7 +1115,7 @@ public final class RenderGlobal final int pendingChunkSize = this.dirtyChunks.size(); - float nearCheck = camera instanceof EntityCamera entityCamera && entityCamera.mob.noPhysics ? 16 : 2048; + float nearCheck = camera instanceof EntityCamera entityCamera && entityCamera.mob.hasNoPhysics() ? 16 : 2048; for (int i = 0; i < pendingChunkSize; i++) { final ChunkRenderer renderer = this.dirtyChunks.get(i); if (renderer.distanceToCameraSquared(camera) > nearCheck) { 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 e28df35d0..c2507eac2 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 @@ -62,13 +62,13 @@ public class ItemDragHandler extends Gui { this.dragOverBuffer.clear(); if (Math.max(this.lastX, this.lastY) == Integer.MAX_VALUE) { final var slot = this.container.getSlotAtPosition(mouseX, mouseY); - if (slot != null) dragOverBuffer.add(slot); + 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; - if (this.dragOverBuffer.size() == 0) this.onDragOutsideOfSlots(); + if (this.dragOverBuffer.isEmpty()) this.onDragOutsideOfSlots(); else for (final var slot : this.dragOverBuffer) onDragOverSlot(slot); if (this.draggedSlots.size() <= 1) return; @@ -85,15 +85,16 @@ public class ItemDragHandler extends Gui { }else { this.renderItemStackCount = this.draggingItemStack.stackSize / this.draggedSlots.size(); } - + + int depositedCount = 0; for(int i = 0; i < this.draggedSlots.size(); i++) { - drawSlotOverlayWhileDragging(this.draggedSlots.get(i)); + depositedCount += drawSlotOverlayWhileDragging(this.draggedSlots.get(i)); } if(isCreativeDragging) { this.renderItemStack.stackSize = 1; }else { - this.renderItemStack.stackSize = this.draggingItemStack.stackSize - (this.draggedSlots.size() * this.renderItemStackCount); + this.renderItemStack.stackSize = this.draggingItemStack.stackSize - depositedCount; } } } @@ -117,7 +118,7 @@ public class ItemDragHandler extends Gui { protected void onDragOutsideOfSlots() {} - private void drawSlotOverlayWhileDragging(Slot slot) { + private int drawSlotOverlayWhileDragging(Slot slot) { if(slot == null) throw new NullPointerException("Slot is null"); if(this.draggingItemStack == null) throw new NullPointerException("Stack is null!"); @@ -135,7 +136,9 @@ public class ItemDragHandler extends Gui { if(stack != null) { this.renderItemStack.stackSize = Math.min(this.renderItemStack.getMaxStackSize(), this.renderItemStack.stackSize + stack.stackSize); } + this.renderItemStack.stackSize = Math.min(this.renderItemStack.stackSize, slot.getMaxStackSize()); this.itemElement.render(this.renderItemStack, slot.x, slot.y); + return this.renderItemStack.stackSize - (stack != null ? stack.stackSize : 0); } public void mouseEvent(final int x, final int y, int button, boolean pressed) { diff --git a/game/client/src/main/resources/credits.txt b/game/client/src/main/resources/credits.txt index 2c4c38d9e..f8d77498e 100644 --- a/game/client/src/main/resources/credits.txt +++ b/game/client/src/main/resources/credits.txt @@ -65,6 +65,7 @@ kheprep --Pumpkin pie textures --Pumpkin crop bush texture --Assistance with bug fixes +--Popup component search bar Luke (@loquatdev) --Assistance with extended controller support LukeisStuff diff --git a/game/core/src/main/java/net/minecraft/core/achievement/Achievements.java b/game/core/src/main/java/net/minecraft/core/achievement/Achievements.java index 83e08154c..79eaa680a 100644 --- a/game/core/src/main/java/net/minecraft/core/achievement/Achievements.java +++ b/game/core/src/main/java/net/minecraft/core/achievement/Achievements.java @@ -31,7 +31,7 @@ public abstract class Achievements .registerAchievement(); public static Achievement DOWN_THE_DRAIN = new Achievement(NamespaceID.fromPool("minecraft", "down_the_drain"), "downTheDrain", Blocks.MESH, ACQUIRE_IRON) - .registerAchievement(); + .registerAchievement(); public static Achievement GET_DIAMONDS = new Achievement(NamespaceID.fromPool("minecraft", "get_diamonds"), "getDiamonds", Items.DIAMOND, ACQUIRE_IRON) .setType(Achievement.TYPE_SPECIAL) @@ -118,35 +118,95 @@ public abstract class Achievements // Nether achievements public static Achievement ENTER_NETHER = - new Achievement(NamespaceID.fromPool("minecraft", "enter_nether"), "enterNether", Blocks.OBSIDIAN, null) + new Achievement(NamespaceID.fromPool("minecraft", "enter_nether"), "enterNether", Blocks.NETHERRACK, null) .setType(Achievement.TYPE_SPECIAL) .registerAchievement(); public static Achievement GET_NETHERCOAL = new Achievement(NamespaceID.fromPool("minecraft", "get_nethercoal"), "getNethercoal", Items.NETHERCOAL, ENTER_NETHER) .setType(Achievement.TYPE_NORMAL) .registerAchievement(); + public static Achievement DISCOVER_SULFURPOOLS = + new Achievement(NamespaceID.fromPool("minecraft", "discover_sulfurpools"), "discoverSulfurPools", Blocks.BRIMTHAW, ENTER_NETHER) + .setType(Achievement.TYPE_SPECIAL) + .registerAchievement(); + public static Achievement DISCOVER_VOLCANICISLANDS = + new Achievement(NamespaceID.fromPool("minecraft", "discover_volcanicislands"), "discoverVolcanicIslands", Blocks.MAGMA, ENTER_NETHER) + .setType(Achievement.TYPE_SPECIAL) + .registerAchievement(); + public static Achievement FLING_VENT = + new Achievement(NamespaceID.fromPool("minecraft", "flingVent"), "flingVent", Items.GUNPOWDER, DISCOVER_VOLCANICISLANDS) + .setType(Achievement.TYPE_NORMAL) + .registerAchievement(); + public static Achievement COLLECT_PUMICE = + new Achievement(NamespaceID.fromPool("minecraft", "collect_pumice"), "collectPumice", Blocks.PUMICE_WET, DISCOVER_VOLCANICISLANDS) + .setType(Achievement.TYPE_NORMAL) + .registerAchievement(); + public static Achievement WHAT_WAS_THAT = + new Achievement(NamespaceID.fromPool("minecraft", "explode_stacked_vent"), "explodeStackedVent", Blocks.THERMAL_VENT, DISCOVER_VOLCANICISLANDS) + .setType(Achievement.TYPE_NORMAL) + .registerAchievement(); + public static Achievement DISCOVER_CRYSTALFOREST = + new Achievement(NamespaceID.fromPool("minecraft", "discover_crystalforest"), "discoverCrystalForest", Blocks.RUBYGLASS_COLUMN, ENTER_NETHER) + .setType(Achievement.TYPE_SPECIAL) + .registerAchievement(); + public static Achievement COLLECT_RUBYGLASS = + new Achievement(NamespaceID.fromPool("minecraft", "collect_rubyglass"), "collectRubyglass", Items.RUBYGLASS, DISCOVER_CRYSTALFOREST) + .setType(Achievement.TYPE_NORMAL) + .registerAchievement(); + public static Achievement CRAFT_CONDUIT = + new Achievement(NamespaceID.fromPool("minecraft", "craft_conduit"), "craftConduit", Blocks.CONDUIT, COLLECT_RUBYGLASS) + .setType(Achievement.TYPE_NORMAL) + .registerAchievement(); + public static Achievement DISCOVER_OLDWORLD = + new Achievement(NamespaceID.fromPool("minecraft", "discover_oldworld"), "discoverOldWorld", Blocks.BLOCK_ASH, ENTER_NETHER) + .setType(Achievement.TYPE_SPECIAL) + .registerAchievement(); + public static Achievement TRIGGER_EMBER = + new Achievement(NamespaceID.fromPool("minecraft", "trigger_ember"), "triggerEmber", Blocks.EMBER, null) + .setType(Achievement.TYPE_SECRET) + .registerAchievement(); + public static Achievement COLLECT_ACID = + new Achievement(NamespaceID.fromPool("minecraft", "collect_acid"), "collectAcid", ItemBucket.createItemStack(Items.BUCKET_IRON, ItemBucket.STATE_ACID, 1), DISCOVER_SULFURPOOLS) + .setType(Achievement.TYPE_NORMAL) + .registerAchievement(); + public static Achievement CONVERT_ACID_TO_SULFUR = + new Achievement(NamespaceID.fromPool("minecraft", "acid_sulfur_conversion"), "acidSulfurConversion", Blocks.SULFUR, COLLECT_ACID) + .setType(Achievement.TYPE_NORMAL) + .registerAchievement(); + public static Achievement CONVERT_ACID_TO_STONE = + new Achievement(NamespaceID.fromPool("minecraft", "acid_stone_generation"), "acidStoneGeneration", Blocks.STONE, COLLECT_ACID) + .setType(Achievement.TYPE_NORMAL) + .registerAchievement(); + public static Achievement CRAFT_ACID_COBBLE_TO_STONE = + new Achievement(NamespaceID.fromPool("minecraft", "acid_cobble_conversion"), "acidCobbleConversion", Blocks.COBBLE_STONE, COLLECT_ACID) + .setType(Achievement.TYPE_NORMAL) + .registerAchievement(); + public static Achievement ACID_BATH = + new Achievement(NamespaceID.fromPool("minecraft", "acid_bath"), "acidBath", ItemBucket.createItemStack(Items.BUCKET_STEEL, ItemBucket.STATE_ACID, 3), null) + .setType(Achievement.TYPE_SECRET) + .registerAchievement(); public static Achievement LIGHT_SIGN = new Achievement(NamespaceID.fromPool("minecraft", "light_sign"), "lightSign", Items.DUST_GLOWSTONE, ENTER_NETHER) .setType(Achievement.TYPE_NORMAL) .registerAchievement(); public static Achievement SWIM_NETHER = - new Achievement(NamespaceID.fromPool("minecraft", "swim_nether"), "swimNether", ItemBucket.createItemStack(Items.BUCKET_IRON, ItemBucket.STATE_WATER, 1), ENTER_NETHER) - .setType(Achievement.TYPE_SPECIAL) + new Achievement(NamespaceID.fromPool("minecraft", "swim_nether"), "swimNether", Blocks.RUBYGLASS_CRYSTAL, ENTER_NETHER) + .setType(Achievement.TYPE_NORMAL) .registerAchievement(); public static Achievement HIT_FIREBALL = new Achievement(NamespaceID.fromPool("minecraft", "hit_fireball"), "hitFireball", Items.AMMO_FIREBALL, ENTER_NETHER) .setType(Achievement.TYPE_SPECIAL) .registerAchievement(); public static Achievement SLEEP_NETHER = - new Achievement(NamespaceID.fromPool("minecraft", "sleep_nether"), "sleepNether", Items.BED, ENTER_NETHER) - .setType(Achievement.TYPE_SPECIAL) + new Achievement(NamespaceID.fromPool("minecraft", "sleep_nether"), "sleepNether", Items.BED, null) + .setType(Achievement.TYPE_SECRET) .registerAchievement(); public static Achievement MOST_WANTED = new Achievement(NamespaceID.fromPool("minecraft", "most_wanted"), "mostWanted", Items.TOOL_SWORD_GOLD, ENTER_NETHER) .setType(Achievement.TYPE_NORMAL) .registerAchievement(); - public static Achievement FAST_TRAVEL = // TODO - new Achievement(NamespaceID.fromPool("minecraft", "fast_travel"), "fastTravel", Blocks.GRASS, ENTER_NETHER) + public static Achievement FAST_TRAVEL = + new Achievement(NamespaceID.fromPool("minecraft", "fast_travel"), "fastTravel", Blocks.OBSIDIAN, ENTER_NETHER) .setType(Achievement.TYPE_NORMAL) .registerAchievement(); public static final Achievement GET_STEEL_BLAST_FURNACE = @@ -156,6 +216,9 @@ public abstract class Achievements public static final Achievement OBTAIN_STEEL = new Achievement(NamespaceID.fromPool("minecraft", "obtain_steel"), "obtainSteel", Items.INGOT_STEEL, GET_STEEL_BLAST_FURNACE) .registerAchievement(); + public static final Achievement CRAFT_STEEL_BUCKET = + new Achievement(NamespaceID.fromPool("minecraft", "craft_steel_bucket"), "craftSteelBucket", Items.BUCKET_STEEL, OBTAIN_STEEL) + .registerAchievement(); public static void init(){}; } diff --git a/game/core/src/main/java/net/minecraft/core/achievement/stat/StatsCounter.java b/game/core/src/main/java/net/minecraft/core/achievement/stat/StatsCounter.java index 1b9a6ece7..2f903e673 100644 --- a/game/core/src/main/java/net/minecraft/core/achievement/stat/StatsCounter.java +++ b/game/core/src/main/java/net/minecraft/core/achievement/stat/StatsCounter.java @@ -50,9 +50,26 @@ public class StatsCounter { modified = true; } + public void remove(Stat stat) { + if (stat == null) { + return; + } + if (!sessionStats.containsKey(stat) && !savedStats.containsKey(stat)) { + return; + } + sessionStats.removeInt(stat); + savedStats.removeInt(stat); + modified = true; + } + private void addToMap(Object2IntMap map, Stat stat, int amount) { int currentValue = map.getOrDefault(stat, 0); - map.put(stat, currentValue + amount); + int newValue = currentValue + amount; + if (newValue <= 0) { + map.removeInt(stat); + } else { + map.put(stat, newValue); + } } @Unmodifiable diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicEmber.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicEmber.java index e32631874..525836a0b 100644 --- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicEmber.java +++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicEmber.java @@ -1,19 +1,17 @@ package net.minecraft.core.block; import net.minecraft.core.block.material.Material; +import net.minecraft.core.achievement.Achievements; import net.minecraft.core.entity.Entity; import net.minecraft.core.entity.animal.MobWolf; import net.minecraft.core.entity.player.Player; import net.minecraft.core.util.helper.DamageType; -import net.minecraft.core.util.helper.MathHelper; -import net.minecraft.core.util.phys.HitResult; import net.minecraft.core.world.World; import net.minecraft.core.world.WorldSource; import net.minecraft.core.world.pos.TilePos; import net.minecraft.core.world.pos.TilePosc; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.joml.Vector3d; import org.joml.primitives.AABBd; import org.joml.primitives.AABBdc; @@ -26,19 +24,15 @@ public class BlockLogicEmber extends BlockLogic { @Override public void onEntityCollision(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Entity entity) { - if(entity instanceof Player || entity instanceof MobWolf){ - this.ignite(world, tilePos, entity); - } - } - - @Override - public void onEntityWalkedOn(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Entity entity) { - if(entity instanceof Player || entity instanceof MobWolf){ // We do a little trolling here... + if(!world.isClientSide && (entity instanceof Player || entity instanceof MobWolf)){ this.ignite(world, tilePos, entity); } } public void ignite(@NotNull World world, @NotNull TilePosc tilePos, Entity entity) { + if (!world.isClientSide && entity instanceof Player player) { + player.addStat(Achievements.TRIGGER_EMBER, 1); + } if (entity != null) { entity.hurt(null, 8, DamageType.BLAST); entity.remainingFireTicks = 100; @@ -85,4 +79,10 @@ public class BlockLogicEmber extends BlockLogic { world.spawnParticle("largesmoke", xPos, yPos + 0.30, zPos, 0.0F, 0.015F, 0.0F, 0, false); } } + + @Override + public @Nullable AABBdc getCollisionAABB(@NotNull WorldSource source, @NotNull TilePosc tilePos) { + double f = 0.25/16d; // TODO making the hitbox slightly shorter so `onEntityCollision` triggers consistently, need a method that actually reliably is called when walked on or touched `onEntityWalkedOn` is only applied when its the block responsible for footstep noises and `onEntityCollision` only happens consistently when intersecting the bounds of a block + return new AABBd(tilePos.x(), tilePos.y(), tilePos.z(), tilePos.x() + 1, tilePos.y() + 1 - f, tilePos.z() + 1); + } } \ No newline at end of file diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java index f969d4b79..e7428b752 100644 --- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java +++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java @@ -120,7 +120,7 @@ public abstract class BlockLogicFluid extends BlockLogic { @Override public void onEntityInside(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Entity entity, @NotNull Vector3d velocityDirection) { - if (entity.noPhysics) return; + if (entity.hasNoPhysics()) return; velocityDirection.add(getFlow(world, tilePos)); } diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java index 3acad5f13..29190bc4f 100644 --- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java +++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java @@ -3,24 +3,24 @@ package net.minecraft.core.block; import net.minecraft.core.block.tag.BlockTags; import net.minecraft.core.enums.EnumDropCause; import net.minecraft.core.block.material.Material; +import net.minecraft.core.block.material.Materials; import net.minecraft.core.world.World; import net.minecraft.core.world.pos.TilePos; import net.minecraft.core.world.pos.TilePosc; import org.jetbrains.annotations.NotNull; +import it.unimi.dsi.fastutil.ints.IntArrayList; + +import java.util.Arrays; import java.util.Random; public class BlockLogicFluidFlowing extends BlockLogicFluid { int maxCount; - boolean @NotNull [] result; - int @NotNull [] distance; public final @NotNull Block blockStill; public BlockLogicFluidFlowing(@NotNull Block block, @NotNull Material material, final @NotNull Fluid fluid, @NotNull Block blockStill) { super(block, material, fluid); this.maxCount = 0; - this.result = new boolean[4]; - this.distance = new int[4]; this.blockStill = blockStill; } @@ -69,7 +69,7 @@ public class BlockLogicFluidFlowing extends BlockLogicFluid { } else { world.setBlockDataNotify(tilePos, localFlowDecay); world.scheduleBlockUpdate(tilePos, this.block, tickDelay(world, tilePos)); - world.notifyBlocksOfNeighborChange(tilePos, this.block); + // world.notifyBlocksOfNeighborChange(tilePos, this.block); } } else { setFluidStill(world, tilePos); @@ -78,39 +78,30 @@ public class BlockLogicFluidFlowing extends BlockLogicFluid { setFluidStill(world, tilePos); } if (canSpreadTo(world, tilePos.down(queryPos))) { - Block b = world.getBlockType(tilePos.down(queryPos)); + final TilePosc spreadPos = tilePos.down(queryPos); + Block b = world.getBlockType(spreadPos); if (b != null) { - b.dropWithCause(world, EnumDropCause.WORLD, tilePos.down(queryPos), world.getBlockData(tilePos.down(queryPos)), null, null); + b.dropWithCause(world, EnumDropCause.WORLD, spreadPos, world.getBlockData(spreadPos), null, null); } if (localFlowDecay >= 8) { - world.setBlockTypeDataNotify(tilePos.down(queryPos), this.block, localFlowDecay); + world.setBlockTypeDataNotify(spreadPos, this.block, localFlowDecay); } else { - world.setBlockTypeDataNotify(tilePos.down(queryPos), this.block, localFlowDecay + 8); + world.setBlockTypeDataNotify(spreadPos, this.block, localFlowDecay + 8); } } else if (localFlowDecay >= 0 && (localFlowDecay == 0 || isFluidBlocking(world, tilePos.down(queryPos)))) { - boolean[] spreadFlags = getSpread(world, tilePos); - int k1 = localFlowDecay + flowDecayMod; - if (localFlowDecay >= 8) { - k1 = 1; - } - if (k1 >= 8) { - return; - } - if (spreadFlags[0]) { - flowIntoBlock(world, tilePos.west(queryPos), k1); - } - if (spreadFlags[1]) { - flowIntoBlock(world, tilePos.east(queryPos), k1); - } - if (spreadFlags[2]) { - flowIntoBlock(world, tilePos.north(queryPos), k1); - } - if (spreadFlags[3]) { - flowIntoBlock(world, tilePos.south(queryPos), k1); - } + final int meta = (localFlowDecay < MAX_SPREAD) + ? localFlowDecay + flowDecayMod + : 1; + final int spreadDistance = (MAX_SPREAD - localFlowDecay) / flowDecayMod - 1; + if (spreadDistance <= 0) return; + + final byte spreadFlags = getSpreadDirections(world, tilePos, spreadDistance); + if ((spreadFlags & MASK_W) != 0) flowIntoBlock(world, tilePos.west(queryPos), meta); + if ((spreadFlags & MASK_E) != 0) flowIntoBlock(world, tilePos.east(queryPos), meta); + if ((spreadFlags & MASK_N) != 0) flowIntoBlock(world, tilePos.north(queryPos), meta); + if ((spreadFlags & MASK_S) != 0) flowIntoBlock(world, tilePos.south(queryPos), meta); } } - private void flowIntoBlock(@NotNull World world, @NotNull TilePos tilePos, int meta) { if (canSpreadTo(world, tilePos)) { this.fluid.onFlowIntoBlock(this, world, tilePos, meta); @@ -118,79 +109,188 @@ public class BlockLogicFluidFlowing extends BlockLogicFluid { } } - private int getSlopeDistance(@NotNull World world, @NotNull TilePosc tilePos, int l, int i1) { - int j1 = 1000; - TilePos queryPos = new TilePos(tilePos); - for (int d = 0; d < 4; d++) { - if (d == 0 && i1 == 1 || d == 1 && i1 == 0 || d == 2 && i1 == 3 || d == 3 && i1 == 2) { - continue; - } - int checkX = tilePos.x(); - int checkY = tilePos.y(); - int checkZ = tilePos.z(); - switch (d) { - case 0 -> checkX--; - case 1 -> checkX++; - case 2 -> checkZ--; - case 3 -> checkZ++; - } - queryPos.set(checkX, checkY, checkZ); - if (isFluidBlocking(world, queryPos) || world.getBlockMaterial(queryPos) == this.material && world.getBlockData(queryPos) == 0) { - continue; - } - if (!isFluidBlocking(world, queryPos.set(checkX, checkY - 1, checkZ))) { - return l; - } - if (l >= this.fluid.getMaxHorizontalSpread(this, world, tilePos)) { - continue; - } - int k2 = getSlopeDistance(world, queryPos.set(checkX, checkY, checkZ), l + 1, d); - if (k2 < j1) { - j1 = k2; - } - } + private static final int MAX_SPREAD = 8; + private static final int POS_OBSTRUCTED = Integer.MAX_VALUE, POS_EMPTY = Integer.MAX_VALUE - 1; + + private static final byte MASK_W = 0b0001, MASK_E = 0b0010, MASK_N = 0b0100, MASK_S = 0b1000; + private static final int INDEX_W = pos2index(-1, 0), INDEX_E = pos2index(+1, 0), INDEX_N = pos2index(0, -1), INDEX_S = pos2index(0, +1); - return j1; + private static final int[] MAP_1 = new int[256]; + private static final IntArrayList BUF_1 = new IntArrayList(); + private static final IntArrayList BUF_2 = new IntArrayList(); + + private static final int pos2index(final int x, final int z) { + assert Math.abs(x) < 8 && Math.abs(z) < 8; + return ((x & 0xF)) | ((z & 0xF) << 4); } - private boolean[] getSpread(@NotNull World world, @NotNull TilePosc tilePos) { - TilePos queryPos = new TilePos(tilePos); - for (int d = 0; d < 4; d++) { - this.distance[d] = 1000; - int checkX = tilePos.x(); - int checkY = tilePos.y(); - int checkZ = tilePos.z(); - switch (d) { - case 0 -> checkX--; - case 1 -> checkX++; - case 2 -> checkZ--; - case 3 -> checkZ++; - } - queryPos.set(checkX, checkY, checkZ); - if (isFluidBlocking(world, queryPos) || world.getBlockMaterial(queryPos) == this.material && world.getBlockData(queryPos) == 0) { - continue; + private static int x, z; + private static final void index2pos(final int i) { + x = (i) & 0xF; + z = (i>>4) & 0xF; + if (x >= 8) x |= -8; + if (z >= 8) z |= -8; + } + + private byte getSpreadDirections(@NotNull World world, @NotNull TilePosc tilePos, int spreadDistance) { + assert spreadDistance >= 0 && spreadDistance < MAX_SPREAD; + + final int ox = tilePos.x(); + final int oz = tilePos.z(); + final int oy = tilePos.y(); + + BUF_1.clear(); + BUF_2.clear(); + final var holeBuf = BUF_1; + final var obstacleBuf = BUF_2; + final var distanceMap = MAP_1; + Arrays.fill(distanceMap, POS_EMPTY); + + final var queryPos = new TilePos(); + byte neighboringObstaclesMask = 0b0000; + + int layer; + for (layer = 1; layer <= spreadDistance; layer++) { + final int obstaclesBeforeLayer = obstacleBuf.size(); + + for (int x = -layer; x <= layer; x++) { + final int rz = layer - Math.abs(x); + for (int z = -rz;; z = rz) { + queryPos.set(ox + x, oy, oz + z); + final int i = pos2index(x, z); + + check: { + final boolean isObstructed; isObstructed: { + final Block b = world.getBlockType(queryPos); + if (b == Blocks.AIR) { + isObstructed = false; break isObstructed; + } + final boolean isFluidBlocking = + !(b.getLogic() instanceof BlockLogicFluid) && + !b.hasTag(BlockTags.BROKEN_BY_FLUIDS) && + !b.hasTag(BlockTags.PLACE_OVERWRITES); + if (isFluidBlocking) { + isObstructed = true; break isObstructed; + } + + final boolean isMatchingSource = + b.getMaterial() == this.material && + world.getBlockData(queryPos) == 0; + isObstructed = isMatchingSource; + } + if (isObstructed) { + obstacleBuf.add(i); + distanceMap[i] = POS_OBSTRUCTED; + if (layer != 1) break check; + + if (x != 0) { + assert Math.abs(x) == 1 && z == 0; + neighboringObstaclesMask |= (x == -1) ? MASK_W : MASK_E; + } else { + assert Math.abs(z) == 1 && x == 0; + neighboringObstaclesMask |= (z == -1) ? MASK_N : MASK_S; + } + break check; + } + + final boolean isHole; isHole: { + final Block b = world.getBlockType(queryPos.down()); + if (b == Blocks.AIR) { + isHole = true; break isHole; + } + final boolean isFluidBlocking = + !(b.getLogic() instanceof BlockLogicFluid) && + !b.hasTag(BlockTags.BROKEN_BY_FLUIDS) && + !b.hasTag(BlockTags.PLACE_OVERWRITES); + isHole = !isFluidBlocking; + } + if (isHole) { + holeBuf.add(i); + distanceMap[i] = 0; + break check; + } + } + + // iterate once if rz == 0, twice otherwise + if (z == rz) break; + } } - if (!isFluidBlocking(world, queryPos.set(checkX, checkY - 1, checkZ))) { - this.distance[d] = 0; - } else { - this.distance[d] = getSlopeDistance(world, queryPos.set(checkX, checkY, checkZ), 1, d); + + final int blocksThisLayer = 4*layer; + final int holesThisLayer = holeBuf.size(); + + // no obstacles nor holes were found before this layer, therefore + // 1) holes this layer are the closest holes, + // 2) distance to holes is simply manhattan distance. + if (obstaclesBeforeLayer == 0 && holesThisLayer != 0) { + if (holesThisLayer == blocksThisLayer) return 0b1111; + byte mask = 0b0000; + for (int _i = 0; _i < holeBuf.size(); _i++) { + /*x, z =*/ index2pos(holeBuf.getInt(_i)); + if (x != 0) mask |= (x < 0) ? MASK_W : MASK_E; + if (z != 0) mask |= (z < 0) ? MASK_N : MASK_S; + } + return mask; } + + // every block in this layer are obstacles, + // therefore no point in continuing the search + if (obstacleBuf.size() - obstaclesBeforeLayer == blocksThisLayer) break; } - int i1 = this.distance[0]; - for (int k1 = 1; k1 < 4; k1++) { - if (this.distance[k1] < i1) { - i1 = this.distance[k1]; + // there exist no reachable holes, therefore we only care about adjacent obstacles + if (holeBuf.size() == 0) return (byte)~neighboringObstaclesMask; + + // there exist some holes that are potentially obstructed. + // calculate the distance map with BFS from holes + final var updateBuf = holeBuf; + for (int _i = 0; _i < updateBuf.size(); _i++) { + final int i = updateBuf.getInt(_i); + final int d = distanceMap[i]; + if (d == POS_OBSTRUCTED) continue; + /*x, z =*/ index2pos(i); + + for (int q = 0; q < 4; q++) { + int x2 = x, z2 = z; + switch (q) { + case 0 -> { x2 -= 1; } + case 1 -> { x2 += 1; } + case 2 -> { z2 -= 1; } + case 3 -> { z2 += 1; } + } + final int od = Math.abs(x2) + Math.abs(z2); + + // we do not update distances outside the current searched diamond, + // nor do we update distances across the origin (this makes the distance values + // across the origin incorrect, but does not change the comparison results). + if (od >= layer || od == 0) continue; + + final int i2 = pos2index(x2, z2); + final int d2 = distanceMap[i2]; + + if (d2 == POS_OBSTRUCTED) continue; + if (d2 > d + 1) { + distanceMap[i2] = d + 1; + updateBuf.push(i2); + } } } - for (int l1 = 0; l1 < 4; l1++) { - this.result[l1] = this.distance[l1] == i1; - } + final int dW = distanceMap[INDEX_W]; + final int dE = distanceMap[INDEX_E]; + final int dN = distanceMap[INDEX_N]; + final int dS = distanceMap[INDEX_S]; - return this.result; - } + final int dm = Math.min(Math.min(dW, dE), Math.min(dN, dS)); + byte mask = 0; + if (dW == dm) mask |= 0b0001; + if (dE == dm) mask |= 0b0010; + if (dN == dm) mask |= 0b0100; + if (dS == dm) mask |= 0b1000; + return mask; + } + private boolean isFluidBlocking(@NotNull World world, @NotNull TilePos tilePos) { Block b = world.getBlockType(tilePos); return !(b.getLogic() instanceof BlockLogicFluid) && !b.hasTag(BlockTags.BROKEN_BY_FLUIDS) && !b.hasTag(BlockTags.PLACE_OVERWRITES); diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicNodeRubyglass.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicNodeRubyglass.java index 52c12fe06..009ce2575 100644 --- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicNodeRubyglass.java +++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicNodeRubyglass.java @@ -1,8 +1,8 @@ package net.minecraft.core.block; import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; -import net.minecraft.core.block.entity.TileEntity; import net.minecraft.core.enums.EnumDropCause; +import net.minecraft.core.block.entity.TileEntity; import net.minecraft.core.item.ItemStack; import net.minecraft.core.item.Items; import net.minecraft.core.world.World; diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRotatable.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRotatable.java index 8e8075dc0..f8ce3cecf 100644 --- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRotatable.java +++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRotatable.java @@ -9,7 +9,7 @@ import net.minecraft.core.world.pos.TilePos; import net.minecraft.core.world.pos.TilePosc; import org.jetbrains.annotations.NotNull; -public abstract class BlockLogicRotatable extends BlockLogic { +public class BlockLogicRotatable extends BlockLogic { public static final int MASK_DIRECTION = 0b0000_0111; public BlockLogicRotatable(@NotNull Block block, @NotNull Material material) { diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassCrystal.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassCrystal.java index 4ce22dac6..41ff6a93b 100644 --- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassCrystal.java +++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRubyglassCrystal.java @@ -44,11 +44,6 @@ public class BlockLogicRubyglassCrystal extends BlockLogicTransparent { if (data == 0) world.scheduleBlockUpdate(tilePos, this.block, tickDelay()); } - @Override - public void onEntityWalkedOn(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Entity entity) { - this.onEntityCollision(world, tilePos, entity); - } - @Override public float getAmbientOcclusionStrength(@NotNull WorldSource source, @NotNull TilePosc tilePos) { return 0.0F; @@ -93,7 +88,7 @@ public class BlockLogicRubyglassCrystal extends BlockLogicTransparent { @Override public @Nullable AABBdc getCollisionAABB(@NotNull WorldSource source, @NotNull TilePosc tilePos) { - double f = 0.25/16d; // TODO making the hitbox slightly shorter so `onEntityWalkedOn` triggers consistently, need a method that actually reliably is called when walked on or touched `onEntityWalkedOn` is only applied when its the block responsible for footstep noises and `onEntityWalkedOn` only happens consistently when intersecting the bounds of a block + double f = 0.25/16d; // TODO making the hitbox slightly shorter so `onEntityCollision` triggers consistently, need a method that actually reliably is called when walked on or touched `onEntityWalkedOn` is only applied when its the block responsible for footstep noises and `onEntityCollision` only happens consistently when intersecting the bounds of a block return new AABBd(tilePos.x(), tilePos.y(), tilePos.z(), tilePos.x() + 1, tilePos.y() + 1 - f, tilePos.z() + 1); } diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSoulSand.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSoulSand.java index b93cf2b38..c4b158c72 100644 --- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSoulSand.java +++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSoulSand.java @@ -1,5 +1,6 @@ package net.minecraft.core.block; +import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; import net.minecraft.core.block.material.Materials; import net.minecraft.core.entity.Entity; import net.minecraft.core.world.World; @@ -12,12 +13,20 @@ import org.joml.primitives.AABBdc; public class BlockLogicSoulSand extends BlockLogic { + public static final Int2IntArrayMap pocketVariantMap = new Int2IntArrayMap(); public BlockLogicSoulSand(Block block) { super(block, Materials.SOUL_SAND); } + static { + pocketVariantMap.put(Blocks.GLOOMSTONE.id(), Blocks.SOULSAND.id()); + pocketVariantMap.put(Blocks.COBBLE_GLOOMSTONE.id(), Blocks.SOULSAND.id()); + pocketVariantMap.put(Blocks.BLOCK_ASH.id(), Blocks.SOULSAND.id()); + pocketVariantMap.put(Blocks.SLATE.id(), Blocks.SOULSAND.id()); + } + @Override public @Nullable AABBdc getCollisionAABB(@NotNull WorldSource source, @NotNull TilePosc tilePos) { double f = 2/16d; diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSulfur.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSulfur.java index 4570f258a..b904c1c85 100644 --- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSulfur.java +++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSulfur.java @@ -1,7 +1,7 @@ package net.minecraft.core.block; +import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; import net.minecraft.core.block.entity.TileEntity; -import net.minecraft.core.block.material.Material; import net.minecraft.core.block.material.Materials; import net.minecraft.core.entity.Mob; import net.minecraft.core.entity.player.Player; @@ -10,22 +10,22 @@ import net.minecraft.core.item.Item; import net.minecraft.core.item.ItemStack; import net.minecraft.core.item.Items; import net.minecraft.core.sound.SoundCategory; -import net.minecraft.core.util.helper.Direction; import net.minecraft.core.util.helper.Side; -import net.minecraft.core.world.LevelListener; import net.minecraft.core.world.World; -import net.minecraft.core.world.pos.TilePos; import net.minecraft.core.world.pos.TilePosc; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Random; - public class BlockLogicSulfur extends BlockLogic { + public static final Int2IntArrayMap pocketVariantMap = new Int2IntArrayMap(); public BlockLogicSulfur(@NotNull Block block) { super(block, Materials.SOUL_SAND); - block.setTicking(true); + } + + static { + pocketVariantMap.put(Blocks.BRIMSTONE.id(), Blocks.SULFUR.id()); + pocketVariantMap.put(Blocks.BRIMSAND.id(), Blocks.SULFUR.id()); } @Override @@ -36,11 +36,6 @@ public class BlockLogicSulfur extends BlockLogic { }; } - @Override - public int tickDelay() { - return 20; - } - @Override public void onNeighborChanged(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Block block) { world.scheduleBlockUpdate(tilePos, this.block, this.tickDelay() + world.rand.nextInt(5) - world.rand.nextInt(5)); @@ -51,32 +46,6 @@ public class BlockLogicSulfur extends BlockLogic { world.scheduleBlockUpdate(tilePos, this.block, this.tickDelay() + world.rand.nextInt(5) - world.rand.nextInt(5)); } - public boolean canMelt(@NotNull World world, @NotNull TilePosc tilePos) { - int acidCount = 0; - TilePos queryPos = new TilePos(); - - for (Direction dir : Direction.ID_MAP) { - Block block = world.getBlockType(tilePos.add(dir, queryPos)); - Material adjacentMaterial = block.getMaterial(); - if (adjacentMaterial == Materials.LAVA) return false; - if (adjacentMaterial == Materials.WATER) return false; - if (adjacentMaterial == Materials.ACID) { - acidCount++; - } - } - - return acidCount >= 3; - } - - @Override - public void updateTick(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Random rand, boolean isRandomTick) { - if (this.canMelt(world, tilePos)) { - world.playBlockEvent(tilePos, LevelListener.EVENT_ACID_SPREAD, 0); - world.setBlockTypeNotify(tilePos, Blocks.FLUID_ACID_FLOWING); - } - - } - @Override public void onPlacedByMob(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final @NotNull Mob mob, final double xHit, final double yHit) { world.playSoundEffect( @@ -97,14 +66,14 @@ public class BlockLogicSulfur extends BlockLogic { super.onDestroyedByPlayer(world, tilePos, side, data, player, item); world.playSoundEffect( - player, - SoundCategory.WORLD_SOUNDS, - tilePos.x() + 0.5, - tilePos.y() + 0.5, - tilePos.z() + 0.5, - "step.stone", - 1.0F, - 0.8F + player, + SoundCategory.WORLD_SOUNDS, + tilePos.x() + 0.5, + tilePos.y() + 0.5, + tilePos.z() + 0.5, + "step.stone", + 1.0F, + 0.8F ); } } diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java index ce20f709a..04f08add0 100644 --- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java +++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java @@ -1,8 +1,13 @@ package net.minecraft.core.block; +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +import net.minecraft.core.achievement.Achievements; import net.minecraft.core.block.material.Materials; import net.minecraft.core.entity.Entity; +import net.minecraft.core.entity.Mob; import net.minecraft.core.entity.player.Player; +import net.minecraft.core.util.helper.Side; +import net.minecraft.core.world.Dimension; import net.minecraft.core.world.World; import net.minecraft.core.world.particle.ParticleEmitterRange; import net.minecraft.core.world.pos.TilePos; @@ -19,6 +24,15 @@ public class BlockLogicThermalVent extends BlockLogic { public static int DORMANT_STATE = 0; public static int VENTING_STATE = 4; + private static final Long2IntOpenHashMap STACKED_VENT_OWNERS = new Long2IntOpenHashMap(); + + private static long packStackedVentKey(final @NotNull World world, final @NotNull TilePosc tilePos) { + return ((long) world.getDimension().id << 60) + | ((long) (tilePos.x() & 0x3FFFFF) << 40) + | ((long) (tilePos.z() & 0x3FFFFF) << 20) + | (long) (tilePos.y() & 0xFFFFF); + } + public BlockLogicThermalVent(final @NotNull Block block) { super(block, Materials.BASALT); block.setTicking(true); @@ -134,17 +148,48 @@ public class BlockLogicThermalVent extends BlockLogic { if (!world.isClientSide) { + final long ventKey = packStackedVentKey(world, tilePos); + final int ownerId = STACKED_VENT_OWNERS.containsKey(ventKey) ? STACKED_VENT_OWNERS.remove(ventKey) : -1; alertNearbyPlayers(world, tilePos); final List entities = world.getEntitiesWithinAABB(Entity.class, new AABBd(tilePos.x() - 1, tilePos.y(), tilePos.z() - 1, tilePos.x() + 2, tilePos.y() + 7, tilePos.z() + 2)); for (final @NotNull Entity entity : entities) { entity.fling(0, 2, 0, 1f); + if (entity instanceof Player player) { + player.addStat(Achievements.FLING_VENT, 1); + } + } + + if (ownerId != -1) { + for (final Player player : world.players) { + if (player.id == ownerId) { + player.addStat(Achievements.WHAT_WAS_THAT, 1); + break; + } + } } world.setBlockData(tilePos, DORMANT_STATE); } } + @Override + public void onPlacedByMob(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final @NotNull Mob mob, final double xHit, final double yHit) { + if (!world.isClientSide && mob instanceof Player player + && world.getDimension() == Dimension.OVERWORLD + && hasVentBelow(world, tilePos)) { + STACKED_VENT_OWNERS.put(packStackedVentKey(world, tilePos), player.id); + } + } + + @Override + public void onRemoved(final @NotNull World world, final @NotNull TilePosc tilePos, final int data) { + super.onRemoved(world, tilePos, data); + if (!world.isClientSide) { + STACKED_VENT_OWNERS.remove(packStackedVentKey(world, tilePos)); + } + } + @Override public void onPlacedByWorld(final @NotNull World world, final @NotNull TilePosc tilePos) { super.onPlacedByWorld(world, tilePos); diff --git a/game/core/src/main/java/net/minecraft/core/block/FluidHardening.java b/game/core/src/main/java/net/minecraft/core/block/FluidHardening.java index fef77ac99..2e6e82020 100644 --- a/game/core/src/main/java/net/minecraft/core/block/FluidHardening.java +++ b/game/core/src/main/java/net/minecraft/core/block/FluidHardening.java @@ -1,14 +1,19 @@ package net.minecraft.core.block; +import net.minecraft.core.achievement.Achievements; import net.minecraft.core.block.material.Material; import net.minecraft.core.block.material.Materials; -import net.minecraft.core.sound.SoundCategory; +import net.minecraft.core.entity.player.Player; import net.minecraft.core.world.World; import net.minecraft.core.world.pos.TilePosc; import net.minecraft.core.world.type.tag.WorldTypeTags; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public final class FluidHardening { + private static final int RECENT_PLACEMENT_TICKS = 240; + private static final double RECENT_PLACEMENT_RADIUS_SQ = 16.0 * 16.0; + private FluidHardening() { } @@ -59,6 +64,7 @@ public final class FluidHardening { world.setBlockTypeNotify(tilePos, data == 0 ? Blocks.OBSIDIAN : Blocks.GLOOMSTONE); } else { world.setBlockTypeNotify(tilePos, Blocks.LIMESTONE); + notifyAcidStoneCreated(world, tilePos, Blocks.LIMESTONE); } BlockLogicFluid.fizz(world, tilePos); return true; @@ -89,10 +95,13 @@ public final class FluidHardening { world.setBlockTypeNotify(tilePos, Blocks.OBSIDIAN); } else if (data <= 2) { world.setBlockTypeNotify(tilePos, Blocks.GRANITE); + notifyAcidStoneCreated(world, tilePos, Blocks.GRANITE); } else if (data <= 4) { world.setBlockTypeNotify(tilePos, Blocks.STONE); + notifyAcidStoneCreated(world, tilePos, Blocks.STONE); } else { world.setBlockTypeNotify(tilePos, Blocks.BASALT); + notifyAcidStoneCreated(world, tilePos, Blocks.BASALT); } BlockLogicFluid.fizz(world, tilePos); return true; @@ -101,10 +110,26 @@ public final class FluidHardening { private static boolean hardenAcidWithLava(final @NotNull World world, final @NotNull TilePosc tilePos) { final int data = world.getBlockData(tilePos) & 0x0F; if (world.getWorldType().hasTag(WorldTypeTags.NETHER)) { - world.setBlockTypeNotify(tilePos, data == 0 ? Blocks.BRIMTHAW : Blocks.BRIMSTONE); - fizzAcidNether(world, tilePos); - } else { - hardenAcidOverworld(world, tilePos, data); + if(data == 0) { + world.setBlockTypeNotify(tilePos, Blocks.BRIMTHAW); + } else { + world.setBlockTypeNotify(tilePos, Blocks.BRIMSTONE); + } + } + else { + if (data == 0) { + world.setBlockTypeNotify(tilePos, Blocks.BRIMTHAW); + } else if (data <= 2) { + world.setBlockTypeNotify(tilePos, Blocks.GRANITE); + notifyAcidStoneCreated(world, tilePos, Blocks.GRANITE); + } else if (data <= 4) { + world.setBlockTypeNotify(tilePos, Blocks.STONE); + notifyAcidStoneCreated(world, tilePos, Blocks.STONE); + } else { + world.setBlockTypeNotify(tilePos, Blocks.BASALT); + notifyAcidStoneCreated(world, tilePos, Blocks.BASALT); + } + BlockLogicFluid.fizz(world, tilePos); } return true; } @@ -112,31 +137,65 @@ public final class FluidHardening { private static boolean hardenAcidWithWater(final @NotNull World world, final @NotNull TilePosc tilePos) { final int data = world.getBlockData(tilePos) & 0x0F; if (world.getWorldType().hasTag(WorldTypeTags.NETHER)) { - world.setBlockTypeNotify(tilePos, data == 0 ? Blocks.BRIMTHAW : Blocks.BRIMSTONE); - fizzAcidNether(world, tilePos); - } else { - hardenAcidOverworld(world, tilePos, data); + if(data == 0) { + world.setBlockTypeNotify(tilePos, Blocks.BRIMTHAW); + } else { + world.setBlockTypeNotify(tilePos, Blocks.BRIMSTONE); + } + } + else { + if (data == 0) { + world.setBlockTypeNotify(tilePos, Blocks.SULFUR); + notifyAcidConvertedToSulfur(world, tilePos); + } else if (data <= 2) { + world.setBlockTypeNotify(tilePos, Blocks.GRANITE); + notifyAcidStoneCreated(world, tilePos, Blocks.GRANITE); + } else if (data <= 4) { + world.setBlockTypeNotify(tilePos, Blocks.STONE); + notifyAcidStoneCreated(world, tilePos, Blocks.STONE); + } else { + world.setBlockTypeNotify(tilePos, Blocks.BASALT); + notifyAcidStoneCreated(world, tilePos, Blocks.BASALT); + } + BlockLogicFluid.fizz(world, tilePos); } return true; } - private static void hardenAcidOverworld(final @NotNull World world, final @NotNull TilePosc tilePos, final int data) { - if (data == 0) { - world.setBlockTypeNotify(tilePos, Blocks.SULFUR); - } else if (data <= 2) { - world.setBlockTypeNotify(tilePos, Blocks.GRANITE); - } else if (data <= 4) { - world.setBlockTypeNotify(tilePos, Blocks.STONE); - } else { - world.setBlockTypeNotify(tilePos, Blocks.BASALT); + private static void notifyAcidStoneCreated(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Block block) { + if (world.isClientSide || !Block.hasLogicClass(block, BlockLogicStone.class)) { + return; + } + final Player credit = findRecentPlacer(world, tilePos, true); + if (credit != null) { + credit.addStat(Achievements.CONVERT_ACID_TO_STONE, 1); + } + } + + private static void notifyAcidConvertedToSulfur(final @NotNull World world, final @NotNull TilePosc tilePos) { + if (world.isClientSide) { + return; + } + Player credit = findRecentPlacer(world, tilePos, true); + if (credit == null) { + credit = findRecentPlacer(world, tilePos, false); + } + if (credit != null) { + credit.addStat(Achievements.CONVERT_ACID_TO_SULFUR, 1); } - BlockLogicFluid.fizz(world, tilePos); } - private static void fizzAcidNether(final @NotNull World world, final @NotNull TilePosc tilePos) { - world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, (float) tilePos.x() + 0.5F, (float) tilePos.y() + 0.5F, (float) tilePos.z() + 0.5F, "random.fizz", 0.5F, 2.6F + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.8F); - for (int i = 0; i < 8; ++i) { - world.spawnParticle("largesmoke", (double) tilePos.x() + Math.random(), (double) tilePos.y() + 1.01, (double) tilePos.z() + Math.random(), 0.0F, 0.0F, 0.0F, 0, false); + private static @Nullable Player findRecentPlacer(final @NotNull World world, final @NotNull TilePosc tilePos, final boolean acid) { + final long now = world.getTotalWorldTime(); + final int dimensionId = world.getDimension().id; + for (final Player player : world.players) { + final Player.FluidPlacement placement = acid ? player.lastAcidPlacement : player.lastWaterPlacement; + if (placement == null) continue; + if (placement.dimensionId() != dimensionId) continue; + if (now - placement.tick() > RECENT_PLACEMENT_TICKS) continue; + if (tilePos.distanceSquared(placement.x(), placement.y(), placement.z()) > RECENT_PLACEMENT_RADIUS_SQ) continue; + return player; } + return null; } } diff --git a/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonBase.java b/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonBase.java index 087ecd16f..189a41fec 100644 --- a/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonBase.java +++ b/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonBase.java @@ -323,7 +323,7 @@ public class BlockLogicPistonBase extends BlockLogic { // note that flingEntity is performed before flingBlock, therefore no need to // check for flingBlock for (final var entity : entities) { - if (entity.noPhysics) continue; + if (entity.hasNoPhysics()) continue; entity.fling(vx, vy, vz, 1f); } } diff --git a/game/core/src/main/java/net/minecraft/core/block/piston/TileEntityMovingPistonBlock.java b/game/core/src/main/java/net/minecraft/core/block/piston/TileEntityMovingPistonBlock.java index 158c6cec8..e011cab22 100644 --- a/game/core/src/main/java/net/minecraft/core/block/piston/TileEntityMovingPistonBlock.java +++ b/game/core/src/main/java/net/minecraft/core/block/piston/TileEntityMovingPistonBlock.java @@ -157,7 +157,7 @@ public class TileEntityMovingPistonBlock extends TileEntity implements IMovingBl final var entities = this.worldObj.getEntitiesWithinAABBExcludingEntity(null, this.sweptCollision); for (final var e : entities) { - if (e.noPhysics) continue; + if (e.hasNoPhysics()) continue; e.move(dir.x, dir.y, dir.z); } diff --git a/game/core/src/main/java/net/minecraft/core/data/registry/recipe/RecipeRegistry.java b/game/core/src/main/java/net/minecraft/core/data/registry/recipe/RecipeRegistry.java index 0c44fe617..620ab5f79 100644 --- a/game/core/src/main/java/net/minecraft/core/data/registry/recipe/RecipeRegistry.java +++ b/game/core/src/main/java/net/minecraft/core/data/registry/recipe/RecipeRegistry.java @@ -10,6 +10,7 @@ import net.minecraft.core.data.registry.recipe.entry.*; import net.minecraft.core.item.Item; import net.minecraft.core.item.ItemStack; import net.minecraft.core.player.inventory.container.ContainerCrafting; +import org.jetbrains.annotations.Nullable; import java.util.*; @@ -165,13 +166,19 @@ public class RecipeRegistry extends Registry { } public ItemStack findMatchingRecipe(ContainerCrafting inventorycrafting) + { + RecipeEntryCrafting recipe = findMatchingCraftingRecipe(inventorycrafting); + return recipe != null ? recipe.getCraftingResult(inventorycrafting) : null; + } + + public @Nullable RecipeEntryCrafting findMatchingCraftingRecipe(ContainerCrafting inventorycrafting) { for(int i = 0; i < getAllCraftingRecipes().size(); i++) { RecipeEntryCrafting recipe = getAllCraftingRecipes().get(i); if(recipe.matches(inventorycrafting)) { - return recipe.getCraftingResult(inventorycrafting); + return recipe; } } @@ -180,16 +187,8 @@ public class RecipeRegistry extends Registry { public ItemStack[] onCraftResult(ContainerCrafting inventorycrafting) { - for(int i = 0; i < getAllCraftingRecipes().size(); i++) - { - RecipeEntryCrafting recipe = getAllCraftingRecipes().get(i); - if(recipe.matches(inventorycrafting)) - { - return recipe.onCraftResult(inventorycrafting); - } - } - - return null; + RecipeEntryCrafting recipe = findMatchingCraftingRecipe(inventorycrafting); + return recipe != null ? recipe.onCraftResult(inventorycrafting) : null; } public void addRecipe(String recipeKey, ItemStack itemstack, boolean consumeContainerItem, Object... aobj) { 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 c37a5bb63..d23c84461 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 @@ -49,6 +49,7 @@ public abstract class Entity public static final int FLAG_SNEAKING = 1; public static final int FLAG_RIDING = 2; public static final int FLAG_SPRINTING = 3; + public static final int FLAG_NO_PHYSICS = 4; // Misc/IDK public boolean slide = true; @@ -96,7 +97,6 @@ public abstract class Entity private final @NotNull AABBd workingBox4 = new AABBd(); // Movement/Physics - public boolean noPhysics = false; public float pushthrough = 0.0F; public float walkDistO = 0.0F; public float walkDist = 0.0F; @@ -432,7 +432,7 @@ public abstract class Entity } public void move(double xd, double yd, double zd) { - if (this.noPhysics) { + if (hasNoPhysics()) { this.onGround = false; this.bb.translate(xd, yd, zd); this.x = (this.bb.minX + this.bb.maxX) / 2.0; @@ -743,7 +743,7 @@ public abstract class Entity } protected void checkFallDamage(final double yd, final boolean onGround) { - if (this.vehicle != null || this.noPhysics) { + if (this.vehicle != null || hasNoPhysics()) { this.fallDistance = 0; } else if (onGround) { if (this.fallDistance > 0.0F) { @@ -1091,7 +1091,7 @@ public abstract class Entity } public void push(@NotNull Entity entity) { - if (entity.passenger == this || entity.vehicle == this || entity.noPhysics || !entity.isPickable() || !this.collidesWith(entity) || !entity.collidesWith(this)) { + if (entity.passenger == this || entity.vehicle == this || entity.hasNoPhysics() || !entity.isPickable() || !this.collidesWith(entity) || !entity.collidesWith(this)) { return; } @@ -1465,6 +1465,14 @@ public abstract class Entity setSharedFlag(FLAG_SNEAKING, flag); } + public boolean hasNoPhysics() { + return getSharedFlag(FLAG_NO_PHYSICS); + } + + public void setNoPhysics(boolean flag) { + setSharedFlag(FLAG_NO_PHYSICS, flag); + } + protected boolean getSharedFlag(int i) { return (this.entityData.getByte(0) & 1 << i) != 0; } 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 41355d1ae..67cee9925 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 @@ -221,6 +221,15 @@ public class EntityItem extends Entity } if(this.item.itemID == Items.NETHERCOAL.id) { player.triggerAchievement(Achievements.GET_NETHERCOAL); + } + if(this.item.itemID == Items.INGOT_STEEL.id) { + player.triggerAchievement(Achievements.OBTAIN_STEEL); + } + if(this.item.itemID == Items.RUBYGLASS.id) { + player.triggerAchievement(Achievements.COLLECT_RUBYGLASS); + } + if(this.item.itemID == Blocks.PUMICE_WET.id()) { + player.triggerAchievement(Achievements.COLLECT_PUMICE); } if(this.item.itemID == Blocks.ICE.id()) { player.triggerAchievement(Achievements.CRUSH_BLOCKS); diff --git a/game/core/src/main/java/net/minecraft/core/entity/EntityItemHoming.java b/game/core/src/main/java/net/minecraft/core/entity/EntityItemHoming.java index 4cd2339ae..cdcc39bc0 100644 --- a/game/core/src/main/java/net/minecraft/core/entity/EntityItemHoming.java +++ b/game/core/src/main/java/net/minecraft/core/entity/EntityItemHoming.java @@ -51,7 +51,7 @@ public class EntityItemHoming extends EntityItem { } if (this.homingTicks > 10) { - this.noPhysics = true; + setNoPhysics(true); } } } diff --git a/game/core/src/main/java/net/minecraft/core/entity/Mob.java b/game/core/src/main/java/net/minecraft/core/entity/Mob.java index 8b3ded235..153d56e63 100644 --- a/game/core/src/main/java/net/minecraft/core/entity/Mob.java +++ b/game/core/src/main/java/net/minecraft/core/entity/Mob.java @@ -286,7 +286,7 @@ public abstract class Mob extends Entity { this.livingSoundTime = -getAmbientSoundInterval(); playLivingSound(); } - if (isAlive() && isInWall() && !this.noPhysics) { + if (isAlive() && isInWall() && !hasNoPhysics()) { hurt(null, 1, null); } if (this.fireImmune || this.world.isClientSide) { @@ -693,7 +693,7 @@ public abstract class Mob extends Entity { this.muteStepSounds = false; this.isSkating = false; - if (this.noPhysics) { + if (hasNoPhysics()) { int vertical = 0; if (isSneaking()) vertical--; if (this.isJumping) vertical++; @@ -992,7 +992,7 @@ public abstract class Mob extends Entity { } protected void jump() { - if (this.noPhysics) { + if (hasNoPhysics()) { return; } this.yd = this.jumpHeight; diff --git a/game/core/src/main/java/net/minecraft/core/entity/animal/MobCow.java b/game/core/src/main/java/net/minecraft/core/entity/animal/MobCow.java index 12e6eb0e4..f87d16447 100644 --- a/game/core/src/main/java/net/minecraft/core/entity/animal/MobCow.java +++ b/game/core/src/main/java/net/minecraft/core/entity/animal/MobCow.java @@ -78,17 +78,18 @@ public class MobCow return 0.4F; } - @Override - public boolean interact(@NotNull Player player) { - ItemStack itemstack = player.inventory.getCurrentItem(); - - if (itemstack != null && itemstack.getItem() instanceof ItemBucket) { - ItemBucket.useBucket(itemstack, player, this.world, ItemBucket.STATE_MILK); - return true; - } - - return super.interact(player); - } + // // upstream design makes implementing it here not ideal + // @Override + // public boolean interact(@NotNull Player player) { + // ItemStack itemstack = player.inventory.getCurrentItem(); + + // if (itemstack != null && itemstack.getItem() instanceof ItemBucket) { + // ItemBucket.useBucket(itemstack, player, this.world, ItemBucket.STATE_MILK); + // return true; + // } + + // return super.interact(player); + // } @Override public boolean isFavouriteItem(ItemStack itemStack) diff --git a/game/core/src/main/java/net/minecraft/core/entity/monster/MobMonsterArmored.java b/game/core/src/main/java/net/minecraft/core/entity/monster/MobMonsterArmored.java index 5fbd9c81f..c939dfa40 100644 --- a/game/core/src/main/java/net/minecraft/core/entity/monster/MobMonsterArmored.java +++ b/game/core/src/main/java/net/minecraft/core/entity/monster/MobMonsterArmored.java @@ -23,7 +23,7 @@ public abstract class MobMonsterArmored protected static final int DATA_ITEM_HELD = 24; protected static final int DATA_ARMOR_START = 25; - protected static final int FLAG_LEFT_HANDED = 4; + protected static final int FLAG_LEFT_HANDED = 5; public MobMonsterArmored(final @NotNull World world) { super(world); 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 f145f1694..6edabf547 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 @@ -3,7 +3,6 @@ package net.minecraft.core.entity.player; import com.mojang.nbt.tags.*; import net.minecraft.core.block.BlockLogicBed; import net.minecraft.core.block.entity.*; -import net.minecraft.core.block.material.Materials; import net.minecraft.core.data.registry.Registries; import net.minecraft.core.entity.*; import net.minecraft.core.entity.animal.MobPig; @@ -55,6 +54,9 @@ import net.minecraft.core.world.Dimension; import net.minecraft.core.world.ICarriable; import net.minecraft.core.world.SpawnerMobs; import net.minecraft.core.world.World; +import net.minecraft.core.world.biome.Biome; +import net.minecraft.core.world.biome.BiomeTags; +import net.minecraft.core.world.biome.Biomes; import net.minecraft.core.world.chunk.ChunkCoordinates; import net.minecraft.core.world.chunk.provider.ChunkProvider; import net.minecraft.core.world.pos.TilePos; @@ -92,6 +94,7 @@ public abstract class Player public @NotNull Gamemode gamemode = Gamemodes.SURVIVAL; // public byte userType; public int score; + private int acidStandingTicks; public List messageHistory = new ArrayList<>(); public float cameraVelocityOld; public float cameraVelocity; @@ -114,6 +117,9 @@ public abstract class Player private @Nullable TilePos playerSpawnPoint; private @Nullable TilePos lastDeathPoint; private @Nullable TilePos startMinecartRidingPoint; + public record FluidPlacement(int dimensionId, int x, int y, int z, long tick) {} + public @Nullable FluidPlacement lastAcidPlacement; + public @Nullable FluidPlacement lastWaterPlacement; public int timeUntilPortal; protected boolean inPortal; public int portalID; @@ -281,7 +287,7 @@ public abstract class Player final boolean wSneak = wantSneak(); - if (wSneak || this.noPhysics) { + if (wSneak || hasNoPhysics()) { this.sneaking = wSneak; } else { if (boundsClear(getBoundsForState(MOTION_STATE_DEFAULT))) { @@ -313,6 +319,23 @@ public abstract class Player } } super.tick(); + if (!this.world.isClientSide + && this.world.getDimension() == Dimension.NETHER + && this.tickCount % 100 == 0) { + final Biome biome = this.world.getBlockBiome(new TilePos(this)); + if (biome.hasTag(BiomeTags.HAS_SULFUR_POOLS)) { + addStat(Achievements.DISCOVER_SULFURPOOLS, 1); + } + if (biome == Biomes.NETHER_VOLCANIC_ISLANDS) { + addStat(Achievements.DISCOVER_VOLCANICISLANDS, 1); + } + if (biome.hasTag(BiomeTags.HAS_CRYSTAL_VEINS)) { + addStat(Achievements.DISCOVER_CRYSTALFOREST, 1); + } + if (biome.hasTag(BiomeTags.HAS_SURFACE_ASH)) { + addStat(Achievements.DISCOVER_OLDWORLD, 1); + } + } if (this.heldObject != null) { this.heldObject.heldTick(this.world, this); } @@ -454,9 +477,20 @@ public abstract class Player this.acidExitDelayLeft = 0; this.acidPostExitPeak = 0; this.acidPostExitDrainTicksLeft = 0; + this.acidStandingTicks = 0; return; } super.acidTick(); + if (!this.world.isClientSide) { + if (isInAcid()) { + this.acidStandingTicks++; + if (this.acidStandingTicks > 200) { + triggerAchievement(Achievements.ACID_BATH); + } + } else { + this.acidStandingTicks = 0; + } + } } public boolean shouldShowAcidVisualEffects() { @@ -794,7 +828,7 @@ public abstract class Player @Override public void push(@NotNull Entity entity) { - if (this.noPhysics || !getGamemode().canInteract()) return; + if (hasNoPhysics() || !getGamemode().canInteract()) return; super.push(entity); } @@ -905,7 +939,7 @@ public abstract class Player } else { this.setGamemodeOnLogin(Gamemodes.SURVIVAL); } - this.noPhysics = tag.getBoolean("Noclip") && this.gamemode.hasPlayerFlight(); + setNoPhysics(tag.getBoolean("Noclip") && this.gamemode.hasPlayerFlight()); this.sneaking = tag.getBoolean("Sneaking"); if (this.sleeping) { this.bedTilePos = new TilePos(this); @@ -933,7 +967,7 @@ public abstract class Player tag.putBoolean("Sleeping", this.sleeping); tag.putShort("SleepTimer", (short) this.sleepTimer); tag.putString("Gamemode", this.gamemode.getId()); - tag.putBoolean("Noclip", this.noPhysics && this.gamemode.hasPlayerFlight()); + tag.putBoolean("Noclip", hasNoPhysics() && this.gamemode.hasPlayerFlight()); tag.putBoolean("Sneaking", this.sneaking); if (this.playerSpawnPoint != null) { tag.putInt("SpawnX", this.playerSpawnPoint.x); @@ -1472,6 +1506,9 @@ public abstract class Player addStat(statbase, 1); } + public void revokeAchievement(Stat statbase) { + } + public void addStat(@Nullable Stat stat, int i) { } @@ -1640,7 +1677,7 @@ public abstract class Player } this.inventoryMenu = newContainer; if (!gamemode.hasPlayerFlight()) { - this.noPhysics = false; + setNoPhysics(false); } this.fireImmune = gamemode.hasFireImmunity(); @@ -1652,8 +1689,8 @@ public abstract class Player } public void setNoclip(final boolean noclip) { - this.noPhysics = noclip; - if (this.noPhysics) { + setNoPhysics(noclip); + if (hasNoPhysics()) { this.yd += 0.079f; } } diff --git a/game/core/src/main/java/net/minecraft/core/entity/projectile/ProjectileArrowGolden.java b/game/core/src/main/java/net/minecraft/core/entity/projectile/ProjectileArrowGolden.java index 4ef9a4701..a0ca22147 100644 --- a/game/core/src/main/java/net/minecraft/core/entity/projectile/ProjectileArrowGolden.java +++ b/game/core/src/main/java/net/minecraft/core/entity/projectile/ProjectileArrowGolden.java @@ -12,21 +12,21 @@ public class ProjectileArrowGolden extends ProjectileArrow public ProjectileArrowGolden(World world) { super(world, TYPE_GOLDEN); - this.noPhysics = true; + setNoPhysics(true); stack = new ItemStack(Items.AMMO_ARROW_GOLD); } public ProjectileArrowGolden(World world, double x, double y, double z) { super(world, x, y, z, TYPE_GOLDEN); - this.noPhysics = true; + setNoPhysics(true); stack = new ItemStack(Items.AMMO_ARROW_GOLD); } public ProjectileArrowGolden(World world, Mob owner, boolean doesArrowBelongToPlayer) { super(world, owner, doesArrowBelongToPlayer, TYPE_GOLDEN); - this.noPhysics = true; + setNoPhysics(true); stack = new ItemStack(Items.AMMO_ARROW_GOLD); } diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java b/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java index 19fb9d14e..198181725 100644 --- a/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java +++ b/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java @@ -1,6 +1,7 @@ package net.minecraft.core.item; import com.mojang.nbt.tags.CompoundTag; +import net.minecraft.core.achievement.Achievements; import net.minecraft.core.block.Block; import net.minecraft.core.block.BlockLogicFluid; import net.minecraft.core.block.Blocks; @@ -30,6 +31,8 @@ import org.joml.primitives.AABBd; import java.util.*; import java.util.function.BiConsumer; +// TODO: the abstractions here might be too overcomplicatted +// ik reworking things might be a pain for modders but this really is not designed in a good way public abstract class ItemBucket extends ItemFood implements IItemContainer { /** @@ -244,14 +247,6 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer { // return STATE_EMPTY.equals(currentStateId) || getCharges(selfStack) < this.maxCharges; } - @Override - public boolean interactsWithEntity(@NotNull ItemStack selfStack, @NotNull Entity entity) { - if (!super.interactsWithEntity(selfStack, entity)) return false; - final NamespaceID currentStateId = getState(selfStack); - final BucketState currentState = getBucketState(currentStateId); - return currentState.canPlace() || !currentState.isEdible(); - } - @Override public @Nullable ItemStack onUse(@NotNull ItemStack selfStack, @NotNull World world, @NotNull Player player) { final NamespaceID currentStateId = getState(selfStack); @@ -303,19 +298,11 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer { @Override public boolean useOnEntity(@NotNull ItemStack selfStack, @NotNull Player player, @NotNull Mob mob) { - final NamespaceID currentStateId = getState(selfStack); - final BucketState currentState = getBucketState(currentStateId); - - if (currentState.isEdible() && !currentState.canPlace()) return false; - - final int oldSize = selfStack.stackSize; - var newStack = handleEntityClick(selfStack, mob.world, player, mob); - if (newStack.stackSize <= 0) newStack = null; - - final boolean isDifferent = oldSize != newStack.stackSize || newStack != selfStack; - if (isDifferent) player.inventory.setItem(player.inventory.getCurrentSlot(), newStack); + if (mob instanceof MobCow) { + return useBucket(selfStack, player, player.world, STATE_MILK); + } - return isDifferent; + return false; } // @Override @@ -392,6 +379,9 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer { if (STATE_EMPTY.equals(currentState)) { setState(itemStack, targetState); setCharges(itemStack, 1); + if (STATE_ACID.equals(targetState) && !world.isClientSide) { + player.addStat(Achievements.COLLECT_ACID, 1); + } } else { setCharges(itemStack, getCharges(itemStack) + 1); } @@ -422,6 +412,16 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer { final Block replacedBlock = world.getBlockType(tilePos); final int replacedData = world.getBlockData(tilePos); + if (!world.isClientSide) { + if (STATE_ACID.equals(stateId)) { + player.lastAcidPlacement = new Player.FluidPlacement( + world.getDimension().id, tilePos.x(), tilePos.y(), tilePos.z(), world.getTotalWorldTime()); + } else if (STATE_WATER.equals(stateId)) { + player.lastWaterPlacement = new Player.FluidPlacement( + world.getDimension().id, tilePos.x(), tilePos.y(), tilePos.z(), world.getTotalWorldTime()); + } + } + if (!world.setBlockTypeNotify(tilePos, bucketState.fluidBlock())) return result; replacedBlock.dropWithCause(world, EnumDropCause.WORLD, tilePos, replacedData, null, null); @@ -448,9 +448,9 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer { return itemStack; } - protected ItemStack handleEntityClick(@NotNull ItemStack itemStack, @NotNull World world, @NotNull Player player, @NotNull Entity mob) { - return handleAirClick(itemStack, world, player); - } + // protected ItemStack handleEntityClick(@NotNull ItemStack itemStack, @NotNull World world, @NotNull Player player, @NotNull Entity mob) { + // return handleAirClick(itemStack, world, player); + // } protected ItemStack handleEdibleConsumption(@NotNull ItemStack itemStack, @NotNull World world, @NotNull Player player) { NamespaceID state = getState(itemStack); @@ -472,22 +472,23 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer { return itemStack; } - public static void useBucket(ItemStack itemStack, Player player, World world, NamespaceID targetState) { + public static boolean useBucket(ItemStack itemStack, Player player, World world, NamespaceID targetState) { boolean isStacked = itemStack.stackSize > 1; NamespaceID currentState = getState(itemStack); int limit = (itemStack.getItem() instanceof ItemBucket bucket) ? bucket.maxCharges : 1; - if (STATE_EMPTY.equals(currentState) || (currentState.equals(targetState) && getCharges(itemStack) < limit)) { - if (!isStacked) { - if (STATE_EMPTY.equals(currentState)) { - setState(itemStack, targetState); - } - setCharges(itemStack, getCharges(itemStack) + 1); - } else { - splitBucket(itemStack, player, targetState, world); + final boolean canUse = STATE_EMPTY.equals(currentState) || (currentState.equals(targetState) && getCharges(itemStack) < limit); + if (!canUse) return false; + if (!isStacked) { + if (STATE_EMPTY.equals(currentState)) { + setState(itemStack, targetState); } + setCharges(itemStack, getCharges(itemStack) + 1); + } else { + splitBucket(itemStack, player, targetState, world); } + return true; } protected static ItemStack splitBucket(@NotNull ItemStack itemStack, @NotNull Player player, NamespaceID state, @NotNull World world) { 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 99c349963..cc2f87441 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 @@ -4,20 +4,20 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.builder.ArgumentBuilderLiteral; import com.mojang.brigadier.builder.ArgumentBuilderRequired; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import net.minecraft.core.achievement.Achievement; import net.minecraft.core.achievement.Achievements; -import net.minecraft.core.entity.Entity; -import net.minecraft.core.entity.Mob; 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.arguments.ArgumentTypeAchievement; -import net.minecraft.core.net.command.arguments.ArgumentTypeEntity; -import net.minecraft.core.net.command.helpers.EntitySelector; +import net.minecraft.core.net.command.exceptions.CommandExceptions; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; public class CommandAchievement implements CommandManager.CommandRegistry { @@ -27,70 +27,130 @@ public class CommandAchievement implements CommandManager.CommandRegistry { dispatcher.register(((ArgumentBuilderLiteral.literal("achievement")) .requires(CommandSource::hasAdmin) .then((ArgumentBuilderLiteral.literal("grant")) - .then(ArgumentBuilderRequired.argument("entities", ArgumentTypeEntity.usernames()) - .then(ArgumentBuilderRequired.argument("achievement", ArgumentTypeAchievement.achievement()) - .executes(c -> { - CommandSource source = c.getSource(); - List entities = c.getArgument("entities", EntitySelector.class).get(source); - Achievement achievement = c.getArgument("achievement", Achievement.class); - - if (entities.size() == 1 && ((Player) entities.get(0)).getStat(achievement) != 0) { - throw PLAYER_ALREADY_HAS_ACHIEVEMENT.create(); - } - - List achievements = new ArrayList<>(); - achievements.add(achievement); - - while (achievements.get(achievements.size() - 1).parent != null) { - achievements.add(achievements.get(achievements.size() - 1).parent); - } - for (int i = 0; i < achievements.size(); i++) { - for (Entity entity : entities) { - ((Player) entity).triggerAchievement(achievements.get(achievements.size() - 1 - i)); - } - } - - sendContextualMessage(source, entities, achievement); - - return Command.SINGLE_SUCCESS; - })) - .then(ArgumentBuilderLiteral.literal("*") - .executes(c -> { - CommandSource source = c.getSource(); - List entities = c.getArgument("entities", EntitySelector.class).get(source); - - for (Achievement achievement : Achievements.achievementList) { - List achievements = new ArrayList<>(); - achievements.add(achievement); - while (achievements.get(achievements.size() - 1).parent != null) { - achievements.add(achievements.get(achievements.size() - 1).parent); - } - for (int i = 0; i < achievements.size(); i++) { - for (Entity entity : entities) { - ((Player) entity).triggerAchievement(achievements.get(achievements.size() - 1 - i)); - } - } - } - - sendWildcardContextualMessage(source, entities); - - return Command.SINGLE_SUCCESS; - })))))); + .then(ArgumentBuilderRequired.argument("achievement", ArgumentTypeAchievement.achievement()) + .executes(c -> { + CommandSource source = c.getSource(); + Player player = requireSender(source); + Achievement achievement = c.getArgument("achievement", Achievement.class); + + if (player.getStat(achievement) != 0) { + throw PLAYER_ALREADY_HAS_ACHIEVEMENT.create(); + } + + grantAchievements(player, collectWithAncestors(achievement)); + source.sendTranslatableMessage("command.commands.achievement.grant.success_single_entity", achievement.getStatName().trim(), player.getDisplayName()); + + return Command.SINGLE_SUCCESS; + })) + .then(ArgumentBuilderLiteral.literal("*") + .executes(c -> { + CommandSource source = c.getSource(); + Player player = requireSender(source); + + for (Achievement achievement : Achievements.achievementList) { + grantAchievements(player, collectWithAncestors(achievement)); + } + + source.sendTranslatableMessage("command.commands.achievement.grant.all.success_single_entity", player.getDisplayName()); + + return Command.SINGLE_SUCCESS; + }))) + .then((ArgumentBuilderLiteral.literal("revoke")) + .then(ArgumentBuilderRequired.argument("achievement", ArgumentTypeAchievement.achievement()) + .executes(c -> { + CommandSource source = c.getSource(); + Player player = requireSender(source); + 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()); + + return Command.SINGLE_SUCCESS; + })) + .then(ArgumentBuilderLiteral.literal("*") + .executes(c -> { + CommandSource source = c.getSource(); + Player player = requireSender(source); + + revokeAchievements(player, collectAllAchievementsByDepthDesc()); + source.sendTranslatableMessage("command.commands.achievement.revoke.all.success_single_entity", player.getDisplayName()); + + return Command.SINGLE_SUCCESS; + }))))); + } + + private static @NotNull Player requireSender(CommandSource source) throws CommandSyntaxException { + Player player = source.getSender(); + if (player == null) { + throw CommandExceptions.notInWorld().create(); + } + return player; + } + + private static void grantAchievements(Player player, List achievements) { + for (int i = 0; i < achievements.size(); i++) { + Achievement achievement = achievements.get(achievements.size() - 1 - i); + player.triggerAchievement(achievement); + } } - private static void sendContextualMessage(CommandSource source, List entities, Achievement achievement) { - if (entities.size() == 1) { - source.sendTranslatableMessage("command.commands.achievement.grant.success_single_entity", achievement.getStatName().trim(), ((Mob) entities.get(0)).getDisplayName()); - } else { - source.sendTranslatableMessage("command.commands.achievement.grant.success_multiple_entities", achievement.getStatName(), entities.size()); + private static void revokeAchievements(Player player, List achievements) { + for (Achievement achievement : achievements) { + player.revokeAchievement(achievement); + } + } + + private static List collectWithAncestors(Achievement achievement) { + List achievements = new ArrayList<>(); + achievements.add(achievement); + + while (achievements.get(achievements.size() - 1).parent != null) { + achievements.add(achievements.get(achievements.size() - 1).parent); + } + + return achievements; + } + + private static List collectWithDescendants(Achievement achievement) { + List achievements = new ArrayList<>(); + + for (Achievement candidate : Achievements.achievementList) { + if (isDescendantOf(candidate, achievement)) { + achievements.add(candidate); + } + } + + achievements.sort(Comparator.comparingInt(CommandAchievement::getDepth).reversed()); + return achievements; + } + + private static List collectAllAchievementsByDepthDesc() { + List achievements = new ArrayList<>(Achievements.achievementList); + achievements.sort(Comparator.comparingInt(CommandAchievement::getDepth).reversed()); + return achievements; + } + + private static boolean isDescendantOf(Achievement achievement, Achievement ancestor) { + if (achievement == ancestor) { + return true; + } + Achievement parent = achievement.parent; + while (parent != null) { + if (parent == ancestor) { + return true; + } + parent = parent.parent; } + return false; } - private static void sendWildcardContextualMessage(CommandSource source, List entities) { - if (entities.size() == 1) { - source.sendTranslatableMessage("command.commands.achievement.grant.all.success_single_entity", ((Mob) entities.get(0)).getDisplayName()); - } else { - source.sendTranslatableMessage("command.commands.achievement.grant.all.success_multiple_entities", entities.size()); + private static int getDepth(Achievement achievement) { + int depth = 0; + Achievement parent = achievement.parent; + while (parent != null) { + depth++; + parent = parent.parent; } + return depth; } } diff --git a/game/core/src/main/java/net/minecraft/core/net/packet/PacketStatistic.java b/game/core/src/main/java/net/minecraft/core/net/packet/PacketStatistic.java index a7e0005fd..7c8d649e5 100644 --- a/game/core/src/main/java/net/minecraft/core/net/packet/PacketStatistic.java +++ b/game/core/src/main/java/net/minecraft/core/net/packet/PacketStatistic.java @@ -1,7 +1,6 @@ package net.minecraft.core.net.packet; import net.minecraft.core.net.handler.PacketHandler; -import net.minecraft.core.util.HardIllegalArgumentException; import net.minecraft.core.util.collection.NamespaceID; import java.io.*; diff --git a/game/core/src/main/java/net/minecraft/core/net/packet/PacketUpdatePlayerState.java b/game/core/src/main/java/net/minecraft/core/net/packet/PacketUpdatePlayerState.java index 596b23723..ea700c1ed 100644 --- a/game/core/src/main/java/net/minecraft/core/net/packet/PacketUpdatePlayerState.java +++ b/game/core/src/main/java/net/minecraft/core/net/packet/PacketUpdatePlayerState.java @@ -9,6 +9,8 @@ public class PacketUpdatePlayerState extends Packet public static final int STATE_SNEAK = 1; public static final int STATE_UN_SNEAK = 2; public static final int STATE_LEAVE_BED = 3; + public static final int STATE_NO_CLIP = 4; + public static final int STATE_DO_CLIP = 5; public int state; public PacketUpdatePlayerState() diff --git a/game/core/src/main/java/net/minecraft/core/player/inventory/InventorySorter.java b/game/core/src/main/java/net/minecraft/core/player/inventory/InventorySorter.java index d2b418fa6..6c14a87ce 100644 --- a/game/core/src/main/java/net/minecraft/core/player/inventory/InventorySorter.java +++ b/game/core/src/main/java/net/minecraft/core/player/inventory/InventorySorter.java @@ -1,8 +1,11 @@ package net.minecraft.core.player.inventory; +import it.unimi.dsi.fastutil.ints.IntSet; import net.minecraft.core.item.ItemStack; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; public abstract class InventorySorter { @@ -11,28 +14,21 @@ public abstract class InventorySorter { } public static void sortInventory(final ItemStack[] inventory, final int startIndex, final int endIndex) { + sortInventory(inventory, null, startIndex, endIndex); + } + + public static void sortInventory(final ItemStack[] inventory, final @Nullable IntSet lockedSlots, final int startIndex, final int endIndex) { final List items = new ArrayList<>(); for (int i = startIndex; i <= endIndex; i++) { - if (inventory[i] == null) { + if (inventory[i] == null || (lockedSlots != null && lockedSlots.contains(i))) { continue; } items.add(inventory[i]); inventory[i] = null; } - items.sort((a, b) -> { - final int aId = a.itemID; - final int bId = b.itemID; - - if (aId == bId) { - return 0; - } - if (aId < bId) { - return -1; - } - return 1; - }); + items.sort(Comparator.comparingInt(a -> a.itemID)); // Merge stacks for (int i = 0; i < items.size(); i++) { @@ -80,8 +76,16 @@ public abstract class InventorySorter { return 1; }); - for (int i = 0; i < items.size(); i++) { - inventory[startIndex + i] = items.get(i); + int invIndex = startIndex; + for (int i = 0; i < items.size();) { + if (lockedSlots != null && lockedSlots.contains(invIndex)) { + invIndex++; + continue; + } + inventory[invIndex] = items.get(i); + + invIndex++; + i++; } } } diff --git a/game/core/src/main/java/net/minecraft/core/player/inventory/container/ContainerInventory.java b/game/core/src/main/java/net/minecraft/core/player/inventory/container/ContainerInventory.java index ebfa5e378..df3040bd7 100644 --- a/game/core/src/main/java/net/minecraft/core/player/inventory/container/ContainerInventory.java +++ b/game/core/src/main/java/net/minecraft/core/player/inventory/container/ContainerInventory.java @@ -4,6 +4,8 @@ package net.minecraft.core.player.inventory.container; import com.mojang.nbt.tags.CompoundTag; import com.mojang.nbt.tags.ListTag; +import it.unimi.dsi.fastutil.ints.IntArraySet; +import it.unimi.dsi.fastutil.ints.IntSet; import net.minecraft.core.block.Blocks; import net.minecraft.core.block.tag.BlockTags; import net.minecraft.core.entity.player.Player; @@ -422,7 +424,10 @@ public class ContainerInventory @Override public void sort() { - InventorySorter.sortInventory(this.mainInventory, HOTBAR_SIZE, MAIN_INVENTORY_SIZE - 1); + class Cache {private static final IntSet intSet = new IntArraySet();} + Cache.intSet.clear(); + if (currentSlotLocked()) Cache.intSet.add(this.currentSlot); + InventorySorter.sortInventory(this.mainInventory, Cache.intSet, HOTBAR_SIZE, MAIN_INVENTORY_SIZE - 1); } public void transferAllContents(final @NotNull ContainerInventory inventory) { diff --git a/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuAbstract.java b/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuAbstract.java index 21e256655..f558bd76f 100644 --- a/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuAbstract.java +++ b/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuAbstract.java @@ -710,12 +710,12 @@ public abstract class MenuAbstract final var srcStack = slot.getItemStack(); if (srcStack == null) { final var stack = draggingItemStack.copy(); - stack.stackSize = stack.getMaxStackSize(); + stack.stackSize = Math.min(stack.getMaxStackSize(), slot.getMaxStackSize()); slot.set(stack); slot.setChanged(); } else { if (!srcStack.canStackWith(draggingItemStack)) continue; - srcStack.stackSize = srcStack.getMaxStackSize(); + srcStack.stackSize = Math.min(srcStack.getMaxStackSize(), slot.getMaxStackSize()); slot.setChanged(); } } @@ -747,7 +747,7 @@ public abstract class MenuAbstract int amount = itemsPerSlot; amount = Math.min(amount, slot.getMaxStackSize()); if(stackInSlot != null) { - amount = Math.min(amount, stackInSlot.getMaxStackSize() - stackInSlot.stackSize); + amount = Math.min(amount, Math.min(stackInSlot.getMaxStackSize(), slot.getMaxStackSize()) - stackInSlot.stackSize); } amount = Math.min(amount, draggingItemStack.stackSize); if(amount <= 0) { diff --git a/game/core/src/main/java/net/minecraft/core/player/inventory/slot/SlotResult.java b/game/core/src/main/java/net/minecraft/core/player/inventory/slot/SlotResult.java index 3e57f99a4..6bd157f89 100644 --- a/game/core/src/main/java/net/minecraft/core/player/inventory/slot/SlotResult.java +++ b/game/core/src/main/java/net/minecraft/core/player/inventory/slot/SlotResult.java @@ -3,6 +3,7 @@ package net.minecraft.core.player.inventory.slot; import net.minecraft.core.achievement.Achievements; import net.minecraft.core.block.Blocks; import net.minecraft.core.data.registry.Registries; +import net.minecraft.core.data.registry.recipe.entry.RecipeEntryCrafting; import net.minecraft.core.entity.player.Player; import net.minecraft.core.item.*; import net.minecraft.core.item.tool.ItemToolHoe; @@ -10,6 +11,7 @@ import net.minecraft.core.item.tool.ItemToolPickaxe; import net.minecraft.core.item.tool.ItemToolSword; import net.minecraft.core.player.inventory.container.Container; import net.minecraft.core.player.inventory.container.ContainerCrafting; +import org.jetbrains.annotations.Nullable; public class SlotResult extends Slot { @@ -60,7 +62,16 @@ public class SlotResult extends Slot || itemStack.itemID == Items.ARMOR_LEGGINGS_CHAINMAIL.id) { thePlayer.addStat(Achievements.REPAIR_ARMOR, 1); } + if(item.id == Items.INGOT_STEEL.id) thePlayer.addStat(Achievements.OBTAIN_STEEL, 1); + if(item.id == Items.BUCKET_STEEL.id) thePlayer.addStat(Achievements.CRAFT_STEEL_BUCKET, 1); + if(item.id == Blocks.CONDUIT.id()) thePlayer.addStat(Achievements.CRAFT_CONDUIT, 1); + if (!thePlayer.world.isClientSide) { + final @Nullable RecipeEntryCrafting recipe = Registries.RECIPES.findMatchingCraftingRecipe((ContainerCrafting) craftSlots); + if (recipe != null && recipe.toString().startsWith("minecraft:workbench/acid_conversion_")) { + thePlayer.addStat(Achievements.CRAFT_ACID_COBBLE_TO_STONE, 1); + } + } Registries.RECIPES.onCraftResult((ContainerCrafting) craftSlots); } diff --git a/game/core/src/main/java/net/minecraft/core/world/Explosion.java b/game/core/src/main/java/net/minecraft/core/world/Explosion.java index 9fd51bf2c..7a0caad5e 100644 --- a/game/core/src/main/java/net/minecraft/core/world/Explosion.java +++ b/game/core/src/main/java/net/minecraft/core/world/Explosion.java @@ -25,7 +25,7 @@ public class Explosion { protected final @NotNull World world; public final @Nullable Entity exploder; public boolean isFlaming; - protected Random ExplosionRNG; + protected Random explosionRNG; public double explosionX; public double explosionY; public double explosionZ; @@ -35,7 +35,7 @@ public class Explosion { public Explosion(@NotNull World world, @Nullable Entity exploder, double x, double y, double z, float explosionSize) { this.isFlaming = false; - this.ExplosionRNG = new Random(); + this.explosionRNG = new Random(); this.destroyedBlockPositions = new HashSet<>(); this.world = world; this.exploder = exploder; @@ -175,7 +175,7 @@ public class Explosion { float proc = 1 - (((IArmorWearing) entity).getTotalProtectionAmount(DamageType.BLAST) / 2f); flingForce *= proc; } - if (!entity.noPhysics) { + if (!entity.hasNoPhysics()) { entity.fling(xComp * flingForce, yComp * flingForce, zComp * flingForce, 1); } } @@ -191,7 +191,7 @@ public class Explosion { int y1 = chunkposition.y; int z1 = chunkposition.z; - if (this.world.getBlockId(x1, y1, z1) == Blocks.AIR.id() && Blocks.solid[this.world.getBlockId(x1, y1 - 1, z1)] && this.ExplosionRNG.nextInt(3) == 0) { + if (this.world.getBlockId(x1, y1, z1) == Blocks.AIR.id() && Blocks.solid[this.world.getBlockId(x1, y1 - 1, z1)] && this.explosionRNG.nextInt(3) == 0) { this.world.setBlockWithNotify(x1, y1, z1, Blocks.FIRE.id()); } } diff --git a/game/core/src/main/java/net/minecraft/core/world/ExplosionCannonball.java b/game/core/src/main/java/net/minecraft/core/world/ExplosionCannonball.java index a00844f49..c9f9b2674 100644 --- a/game/core/src/main/java/net/minecraft/core/world/ExplosionCannonball.java +++ b/game/core/src/main/java/net/minecraft/core/world/ExplosionCannonball.java @@ -53,7 +53,7 @@ public class ExplosionCannonball extends Explosion { float proc = 1 - entArmored.getTotalProtectionAmount(DamageType.BLAST) / 2f; flingForce *= proc; } - if (!entity.noPhysics) { + if (!entity.hasNoPhysics()) { entity.fling(xComp * flingForce, yComp * flingForce, zComp * flingForce, 1); } } @@ -69,9 +69,9 @@ public class ExplosionCannonball extends Explosion { Vector3d d = new Vector3d(0, 0, 1); for (int i = 0; i < 64; i++) { d.set(0, 0, 0.25); - d.rotateY(ExplosionRNG.nextDouble() * Math.PI_TIMES_2); - d.rotateX(ExplosionRNG.nextDouble() * Math.PI_TIMES_2); - d.mul(1 - ExplosionRNG.nextDouble() * 0.25d); + d.rotateY(explosionRNG.nextDouble() * Math.PI_TIMES_2); + d.rotateX(explosionRNG.nextDouble() * Math.PI_TIMES_2); + d.mul(1 - explosionRNG.nextDouble() * 0.25d); this.world.spawnParticle("explode", this.explosionX + d.x(), this.explosionY + d.y(), this.explosionZ + d.z(), d.x() * 2, d.y() * 2, d.z() * 2, 0, false); this.world.spawnParticle("smoke", this.explosionX + d.x(), this.explosionY + d.y(), this.explosionZ + d.z(), d.x() * 2, d.y() * 2, d.z() * 2, 0, false); } diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/provider/BiomeProviderNether.java b/game/core/src/main/java/net/minecraft/core/world/biome/provider/BiomeProviderNether.java index 199476ffc..3f5b4bce2 100644 --- a/game/core/src/main/java/net/minecraft/core/world/biome/provider/BiomeProviderNether.java +++ b/game/core/src/main/java/net/minecraft/core/world/biome/provider/BiomeProviderNether.java @@ -176,14 +176,12 @@ public final class BiomeProviderNether extends BiomeProvider { 0.5, 1.0, 0.0, 1.0)); - // Mid/Warm Biome brm.addRange(Biomes.NETHER_SHELF, new BiomeRange( 0.70, 0.75, 0.0, 1.0, 0.5, 1.0, 0.0, 1.0)); - // Mid/Cool Biomes brm.addRange(Biomes.NETHER_CRYSTAL_FOREST, new BiomeRange( 0.45, 0.70, 0.1, 1.0, diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/LavaFloeLargeFeature.java b/game/core/src/main/java/net/minecraft/core/world/generate/LavaFloeLargeFeature.java index 0358fc8aa..0108944dd 100644 --- a/game/core/src/main/java/net/minecraft/core/world/generate/LavaFloeLargeFeature.java +++ b/game/core/src/main/java/net/minecraft/core/world/generate/LavaFloeLargeFeature.java @@ -74,7 +74,8 @@ public class LavaFloeLargeFeature if(random.nextInt(20) == 0) { this.trySetBlock(result, x, y, z, Blocks.PUMICE_WET.id()); } else { - this.trySetBlock(result, x, y, z, Blocks.COBBLE_NETHERRACK.id()); + int floeBlockId = random.nextInt(2) == 0 ? Blocks.NETHERRACK.id() : Blocks.COBBLE_NETHERRACK.id(); + this.trySetBlock(result, x, y, z, floeBlockId); } } for (int y = oceanY + (int) (height - 2); y < oceanY + (height + 1); y++) { diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/RubyglassFloeLargeFeature.java b/game/core/src/main/java/net/minecraft/core/world/generate/RubyglassFloeLargeFeature.java index 39cb00389..9d4f333b3 100644 --- a/game/core/src/main/java/net/minecraft/core/world/generate/RubyglassFloeLargeFeature.java +++ b/game/core/src/main/java/net/minecraft/core/world/generate/RubyglassFloeLargeFeature.java @@ -63,7 +63,8 @@ public class RubyglassFloeLargeFeature extends LargeFeature { } for (int y = oceanY; y < oceanY + depth; ++y) { - this.trySetBlock(result, x, y, z, Blocks.COBBLE_NETHERRACK.id()); + int floeBlockId = random.nextInt(2) == 0 ? Blocks.NETHERRACK.id() : Blocks.COBBLE_NETHERRACK.id(); + this.trySetBlock(result, x, y, z, floeBlockId); } } } diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkDecoratorNether.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkDecoratorNether.java index efa7e670d..b1d2967af 100644 --- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkDecoratorNether.java +++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkDecoratorNether.java @@ -3,6 +3,8 @@ package net.minecraft.core.world.generate.chunk.perlin.nether; import net.minecraft.core.block.Block; import net.minecraft.core.block.BlockLogicOreNetherCoal; import net.minecraft.core.block.BlockLogicNodeRubyglass; +import net.minecraft.core.block.BlockLogicSoulSand; +import net.minecraft.core.block.BlockLogicSulfur; import net.minecraft.core.block.Blocks; import net.minecraft.core.world.World; import net.minecraft.core.world.biome.Biome; @@ -113,6 +115,28 @@ public class ChunkDecoratorNether .withPositionSelector(PositionSelectors.HeightRangeUniform) .withPlacementMethod(new PlacementMethod.TriesPerChunk(1000))); + this.register("minecraft:decoration/nether/default/sulfur_pocket", (new ChunkDecorationBuilder(new WorldFeatureOre(BlockLogicSulfur.pocketVariantMap, 24))) + .withBiomeMask(new Biome[]{Biomes.NETHER_SULFUR_POOLS}) + .withPositionSelector(PositionSelectors.HeightRangeUniform) + .withPlacementMethod((feature, world, chunk, random) -> { + int rangeY = world.getWorldType().getMaxY(world) - world.getWorldType().getMinY(world) + 1; + int tries = (int) (20 * (rangeY / 128f)); + for (int i = 0; i < tries; i++) { + feature.placeFeature(world, chunk, random); + } + })); + + this.register("minecraft:decoration/nether/default/soulsand_pocket", (new ChunkDecorationBuilder(new WorldFeatureOre(BlockLogicSoulSand.pocketVariantMap, 32))) + .withBiomeMask(new Biome[]{Biomes.NETHER_OLD_WORLD, Biomes.NETHER_OLD_WORLD_DESERT}) + .withPositionSelector(PositionSelectors.HeightRangeUniform) + .withPlacementMethod((feature, world, chunk, random) -> { + int rangeY = world.getWorldType().getMaxY(world) - world.getWorldType().getMinY(world) + 1; + int tries = (int) (20 * (rangeY / 128f)); + for (int i = 0; i < tries; i++) { + feature.placeFeature(world, chunk, random); + } + })); + // Water Features this.register("minecraft:decoration/nether/default/water_spring", (new ChunkDecorationBuilder(new WorldFeatureNetherSpring(Blocks.FLUID_WATER_FLOWING.id()))) .withBiomeMask(new Biome[]{Biomes.NETHER_CRYSTAL_FOREST, Biomes.NETHER_CRYSTAL_PLAINS}) diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/SurfaceGeneratorNether.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/SurfaceGeneratorNether.java index 367b6cf12..b75207a26 100644 --- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/SurfaceGeneratorNether.java +++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/SurfaceGeneratorNether.java @@ -32,6 +32,7 @@ public class SurfaceGeneratorNether implements SurfaceGenerator { private final short cobbleNetherrackId = (short) Blocks.COBBLE_NETHERRACK.id(); private final short cobbleNetherrackCrystallineId = (short) Blocks.COBBLE_NETHERRACK_CRYSTALLINE.id(); private final short cobbleGloomstoneId = (short) Blocks.COBBLE_GLOOMSTONE.id(); + private final short gloomstoneId = (short) Blocks.GLOOMSTONE.id(); private final short glowstoneId = (short) Blocks.GLOWSTONE.id(); private final short ashId = (short) Blocks.BLOCK_ASH.id(); private final short slateId = (short) Blocks.SLATE.id(); @@ -139,7 +140,7 @@ public class SurfaceGeneratorNether implements SurfaceGenerator { } else if (biome == Biomes.NETHER_SULFUR_POOLS) { fillerBlock = sulfurId; } else if (biome == Biomes.NETHER_OLD_WORLD || biome == Biomes.NETHER_OLD_WORLD_DESERT) { - fillerBlock = cobbleGloomstoneId; + fillerBlock = (rand.nextInt(2) == 0) ? gloomstoneId : cobbleGloomstoneId; } else { fillerBlock = netherrackId; } @@ -164,16 +165,16 @@ public class SurfaceGeneratorNether implements SurfaceGenerator { currentLayerDepth = soilThickness + 2; if (y >= absoluteOceanY - 1) { - result.setBlock(x, y, z, this.cragOrShelfFloorMixIfCobble(biome, topBlock, rand)); + result.setBlock(x, y, z, this.mixStoneVariant(biome, topBlock, rand)); } else { // if the block is below ocean level, set it to the chosen filler block - result.setBlock(x, y, z, this.cragOrShelfFloorMixIfCobble(biome, fillerBlock, rand)); + result.setBlock(x, y, z, this.mixStoneVariant(biome, fillerBlock, rand)); } continue; } if (currentLayerDepth > 0) { - result.setBlock(x, y, z, this.cragOrShelfFloorMixIfCobble(biome, fillerBlock, rand)); + result.setBlock(x, y, z, this.mixStoneVariant(biome, fillerBlock, rand)); currentLayerDepth--; } @@ -184,9 +185,10 @@ public class SurfaceGeneratorNether implements SurfaceGenerator { if (this.generateStoneVariants && currentLayerDepth <= 0) { int stoneBlockId = cachedBiomeStoneBlockId; - // Crag/shelf is the only biome with a randomized variant per block. if (biome == Biomes.NETHER_CRAG || biome == Biomes.NETHER_SHELF) { stoneBlockId = (rand.nextInt(2) == 0) ? netherrackId : cobbleNetherrackId; + } else if (biome == Biomes.NETHER_OLD_WORLD || biome == Biomes.NETHER_OLD_WORLD_DESERT) { + stoneBlockId = (rand.nextInt(2) == 0) ? gloomstoneId : cobbleGloomstoneId; } result.setBlock(x, y, z, stoneBlockId); } @@ -195,11 +197,15 @@ public class SurfaceGeneratorNether implements SurfaceGenerator { } } - private short cragOrShelfFloorMixIfCobble(final @NotNull Biome biome, final short blockId, final @NotNull Random rand) { - if ((biome != Biomes.NETHER_CRAG && biome != Biomes.NETHER_SHELF) || blockId != this.cobbleNetherrackId) { - return blockId; + private short mixStoneVariant(final @NotNull Biome biome, final short blockId, final @NotNull Random rand) { + if ((biome == Biomes.NETHER_CRAG || biome == Biomes.NETHER_SHELF || biome == Biomes.NETHER_CRYSTAL_PLAINS || biome == Biomes.NETHER_CRYSTAL_FOREST) && blockId == this.cobbleNetherrackId) { + return rand.nextInt(2) == 0 ? this.netherrackId : this.cobbleNetherrackId; } - return (short) (rand.nextInt(2) == 0 ? this.netherrackId : this.cobbleNetherrackId); + if ((biome == Biomes.NETHER_OLD_WORLD || biome == Biomes.NETHER_OLD_WORLD_DESERT) + && (blockId == this.cobbleGloomstoneId || blockId == this.gloomstoneId)) { + return rand.nextInt(2) == 0 ? this.gloomstoneId : this.cobbleGloomstoneId; + } + return blockId; } private int computeBiomeStoneBlock(Biome biome, int worldFillBlock) { diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/TerrainGeneratorNether.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/TerrainGeneratorNether.java index 79450252d..0d0b35cb0 100644 --- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/TerrainGeneratorNether.java +++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/TerrainGeneratorNether.java @@ -24,6 +24,7 @@ public class TerrainGeneratorNether extends TerrainGeneratorLerp { private final int fillerBlockId; private final int lavaId; private final int cobbleNetherrackId; + private final int netherrackId; private final int obsidianId; private final ThreadLocal oceanFluidByXZ = ThreadLocal.withInitial(() -> new int[256]); @@ -45,6 +46,7 @@ public class TerrainGeneratorNether extends TerrainGeneratorLerp { this.waterId = Blocks.FLUID_WATER_STILL.id(); this.lavaId = Blocks.FLUID_LAVA_STILL.id(); this.cobbleNetherrackId = Blocks.COBBLE_NETHERRACK.id(); + this.netherrackId = Blocks.NETHERRACK.id(); this.obsidianId = Blocks.OBSIDIAN.id(); this.oceanY = world.getWorldType().getOceanY(); @@ -98,6 +100,10 @@ public class TerrainGeneratorNether extends TerrainGeneratorLerp { return this.rubyglassId; } + if (fluidId == this.cobbleNetherrackId) { + return this.rand.nextInt(2) == 0 ? this.netherrackId : this.cobbleNetherrackId; + } + return fluidId; } else { return this.airId; diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeaturePillar.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeaturePillar.java index df53c5ce8..f66d83ca7 100644 --- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeaturePillar.java +++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeaturePillar.java @@ -151,7 +151,10 @@ public class WorldFeaturePillar int xW = x0 + MathHelper.floor(t[0]); int yW = y0 + MathHelper.floor(t[1]); int zW = z0 + MathHelper.floor(t[2]); - world.setBlockWithNotify(xW, yW, zW, blockId); + int placed = (blockId == Blocks.COBBLE_NETHERRACK.id() && random.nextInt(2) == 0) + ? Blocks.NETHERRACK.id() + : blockId; + world.setBlockWithNotify(xW, yW, zW, placed); } } } diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRoofSpire.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRoofSpire.java index d60ce0ab1..82229f5e4 100644 --- a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRoofSpire.java +++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureRoofSpire.java @@ -23,6 +23,8 @@ public class WorldFeatureRoofSpire extends WorldFeature { pos.up(); } + final boolean randomizeCobbleNetherrack = boulderBlock == Blocks.COBBLE_NETHERRACK; + if (this.hasSpace(world, pos.x, pos.y, pos.z)) { Vector3i highestOffset = new Vector3i(0, 0, 0); TilePos centerPos = new TilePos(pos.x, pos.y, pos.z); @@ -41,7 +43,7 @@ public class WorldFeatureRoofSpire extends WorldFeature { Block currentBlock = world.getBlockType(queryPos); if (currentBlock != Blocks.BEDROCK) { - world.setBlockType(queryPos, boulderBlock); + world.setBlockType(queryPos, pickSpireBlock(randomizeCobbleNetherrack, random)); } } } @@ -49,13 +51,20 @@ public class WorldFeatureRoofSpire extends WorldFeature { for (int y1 = 0; y1 < 2; ++y1) { centerPos.add(highestOffset.x, -(highestOffset.y + y1), highestOffset.z, queryPos); - world.setBlockType(queryPos, boulderBlock); + world.setBlockType(queryPos, pickSpireBlock(randomizeCobbleNetherrack, random)); } } return true; } + private Block pickSpireBlock(boolean randomizeCobbleNetherrack, Random random) { + if (randomizeCobbleNetherrack && random.nextInt(2) == 0) { + return Blocks.NETHERRACK; + } + return boulderBlock; + } + private boolean hasSpace(World world, int xc, int y, int zc) { TilePos queryPos = new TilePos(); diff --git a/game/core/src/main/resources/assets/minecraft/lang/en_US/command.lang b/game/core/src/main/resources/assets/minecraft/lang/en_US/command.lang index 9c29fc03c..a22884b0d 100644 --- a/game/core/src/main/resources/assets/minecraft/lang/en_US/command.lang +++ b/game/core/src/main/resources/assets/minecraft/lang/en_US/command.lang @@ -67,6 +67,11 @@ command.commands.achievement.grant.success_multiple_entities=Granted achievement command.commands.achievement.grant.success_single_entity=Granted achievement §5[%s]§r to %s command.commands.achievement.grant.all.success_multiple_entities=Granted all achievements to %s entities command.commands.achievement.grant.all.success_single_entity=Granted all achievements to %s +command.commands.achievement.revoke.exception_does_not_have_achievement=Player does not have achievement +command.commands.achievement.revoke.success_multiple_entities=Revoked achievement §5[%s]§r from %s entities +command.commands.achievement.revoke.success_single_entity=Revoked achievement §5[%s]§r from %s +command.commands.achievement.revoke.all.success_multiple_entities=Revoked all achievements from %s entities +command.commands.achievement.revoke.all.success_single_entity=Revoked all achievements from %s command.commands.seed.success=Seed: §5[%s]§r command.commands.seed.copied_to_clipboard=Copied to clipboard! command.commands.clear.success_single_item_single_entity=%s item slot cleared 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 8d0e43643..de2a55a7a 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 @@ -7,6 +7,8 @@ gui.achievements.button.done=Done gui.achievements.label.title=Achievements gui.achievements.label.taken=Taken! gui.achievements.label.requires=Requires '%1$s' +gui.achievements.label.secret=??? +gui.achievements.label.secret.desc=??? gui.achievements.label.completion=%1$s Complete! gui.achievements.page.vanilla.name=Vanilla @@ -273,6 +275,10 @@ gui.options.page.debug.category.test=Tests gui.options.page.debug.button.open.model_viewer=Open Model Viewer gui.options.page.debug.button.open.biome_finder=Open Seed Viewer gui.options.page.debug.button.open.datadumper=Run Data Dumper +gui.options.page.debug.button.dump.wiki=Dump Formatted Wiki Data +gui.options.page.debug.button.dump.invsprite=Dump InvSprites PNG +gui.options.page.debug.button.dump.invsprite_apng=Dump InvSprites Animated PNG +gui.options.page.debug.button.dump.invsprite.tooltip=Load a world first: \n Biome and season colors only render in-world. gui.options.page.debug.button.open.sound_test=Open Sound Test gui.options.page.debug.button.open.texture_viewer=Open Texture Viewer gui.options.page.debug.button.open.font_tester=Open Font Test diff --git a/game/core/src/main/resources/assets/minecraft/lang/en_US/stats.lang b/game/core/src/main/resources/assets/minecraft/lang/en_US/stats.lang index 0a3bad62e..ce960cede 100644 --- a/game/core/src/main/resources/assets/minecraft/lang/en_US/stats.lang +++ b/game/core/src/main/resources/assets/minecraft/lang/en_US/stats.lang @@ -124,4 +124,36 @@ achievement.fastTravel.desc=Use the Nether to travel more than 8000 blocks in th achievement.blastFurnace=Blast Processing achievement.blastFurnace.desc=Construct a blast furnace out of eight iron bars and nether coal. achievement.obtainSteel=What a Steel! -achievement.obtainSteel.desc=Combine coal and iron to acquire steel, a strong alloy. \ No newline at end of file +achievement.obtainSteel.desc=Combine coal and iron to acquire steel, a strong alloy. +achievement.craftSteelBucket=Heavy Haul +achievement.craftSteelBucket.desc=Craft a steel bucket. +achievement.discoverSulfurPools=Something Smells... +achievement.discoverSulfurPools.desc=Discover the Sulfur Pools. +achievement.discoverVolcanicIslands=Having a Blast +achievement.discoverVolcanicIslands.desc=Discover the Volcanic Islands. +achievement.flingVent=Blasting off Again! +achievement.flingVent.desc=Stand atop a thermal vent as it goes off. +achievement.collectPumice=Hidden Pores +achievement.collectPumice.desc=Collect a block of molten pumice. +achievement.explodeStackedVent=What was that? +achievement.explodeStackedVent.desc=Place a thermal vent on top of another in the overworld and wait for it to erupt. +achievement.discoverCrystalForest=BzZzZzt! +achievement.discoverCrystalForest.desc=Discover the Crystal Forest. +achievement.collectRubyglass=Crystalline Harvest +achievement.collectRubyglass.desc=Collect rubyglass from a crystal node. +achievement.craftConduit=Go with the Flow +achievement.craftConduit.desc=Craft a conduit. +achievement.discoverOldWorld=Uncanny... +achievement.discoverOldWorld.desc=Discover the Old World. +achievement.triggerEmber=Suprise! +achievement.triggerEmber.desc=Trigger an ember block to exlode by walking ontop of it. +achievement.collectAcid=Acquire Acid +achievement.collectAcid.desc=Collect acid in a bucket. +achievement.acidSulfurConversion=Caustic Cooling +achievement.acidSulfurConversion.desc=Convert acid to sulfur with water in the overworld. +achievement.acidStoneGeneration=Smooth Operator +achievement.acidStoneGeneration.desc=Use acid to harden fluids into smooth stone. +achievement.acidCobbleConversion=Reconstitution +achievement.acidCobbleConversion.desc=Mix acid with cobble or sand in the workbench. +achievement.acidBath=Garra Acida +achievement.acidBath.desc=Sit in acid for more than 10 seconds. \ No newline at end of file diff --git a/game/core/src/main/resources/assets/minecraft/lang/en_US/tile.lang b/game/core/src/main/resources/assets/minecraft/lang/en_US/tile.lang index a92cd646b..b13a636c7 100644 --- a/game/core/src/main/resources/assets/minecraft/lang/en_US/tile.lang +++ b/game/core/src/main/resources/assets/minecraft/lang/en_US/tile.lang @@ -1549,6 +1549,22 @@ tile.statue.pigman.lower.name=Pigman Statue (Lower) tile.statue.pigman.lower.desc=How? tile.statue.pigman.upper.name=Pigman Statue (Upper) tile.statue.pigman.upper.desc=How? +tile.statue.slate.lower.name=Slate Statue (Lower) +tile.statue.slate.lower.desc=How? +tile.statue.slate.upper.name=Slate Statue (Upper) +tile.statue.slate.upper.desc=How? +tile.statue.permafrost.lower.name=Permafrost Statue (Lower) +tile.statue.permafrost.lower.desc=How? +tile.statue.permafrost.upper.name=Permafrost Statue (Upper) +tile.statue.permafrost.upper.desc=How? +tile.statue.netherrack.lower.name=Netherrack Statue (Lower) +tile.statue.netherrack.lower.desc=How? +tile.statue.netherrack.upper.name=Netherrack Statue (Upper) +tile.statue.netherrack.upper.desc=How? +tile.statue.gloomstone.lower.name=Gloomstone Statue (Lower) +tile.statue.gloomstone.lower.desc=How? +tile.statue.gloomstone.upper.name=Gloomstone Statue (Upper) +tile.statue.gloomstone.upper.desc=How? tile.matcher.name=Matcher tile.matcher.desc=Matches the block in front to the block behind. 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 b9979de3a..a9dd6427b 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 @@ -81,7 +81,7 @@ public class PlayerServer extends Player implements ContainerListener { public void moveEntityWithHeading(float moveStrafing, float moveForward) { super.moveEntityWithHeading(moveStrafing, moveForward); - if (noPhysics) + if (hasNoPhysics()) { yd = 0.0D; onGround = true; @@ -108,7 +108,7 @@ public class PlayerServer extends Player implements ContainerListener { inventoryMenu.addSlotListener(this); if (!gamemode.hasPlayerFlight()) { - noPhysics = false; + setNoPhysics(false); } fireImmune = gamemode.hasFireImmunity(); @@ -117,7 +117,7 @@ public class PlayerServer extends Player implements ContainerListener { @Override public @Nullable AABBdc getCollisionAABB() { - if (noPhysics) + if (hasNoPhysics()) { return null; } @@ -183,6 +183,17 @@ public class PlayerServer extends Player implements ContainerListener { playerController.tick(); ticksOfInvuln--; containerMenu.broadcastChanges(); + + ItemStack heldItem = inventory.getHeldItemStack(); + if (!ItemStack.areItemStacksEqual(heldItem, lastHeldItem)) + { + if (playerNetServerHandler != null) + { + playerNetServerHandler.sendPacket(new PacketContainerSetSlot(-1, -1, heldItem)); + } + lastHeldItem = heldItem == null ? null : heldItem.copy(); + } + for (int i = 0; i < 5; i++) { ItemStack itemstack = getEquipmentInSlot(i); @@ -288,7 +299,7 @@ public class PlayerServer extends Player implements ContainerListener { } } - if (inPortal && !noPhysics) + if (inPortal && !hasNoPhysics()) { Dimension targetDim = ((BlockLogicPortal) Blocks.blocksList[portalID].getLogic()).targetDimension; boolean netherAllowed = mcServer.propertyManager.getBooleanProperty("allow-nether", true); @@ -677,6 +688,14 @@ public class PlayerServer extends Player implements ContainerListener { yRot = f3; } + @Override + public void revokeAchievement(Stat stat) { + if (stat == null) { + return; + } + playerNetServerHandler.sendPacket(new PacketStatistic(stat.statId, -1)); + } + @Override public void addStat(@Nullable Stat stat, int i) { @@ -686,12 +705,21 @@ public class PlayerServer extends Player implements ContainerListener { } if (!stat.clientside) { - for (; i > 100; i -= 100) { // send value change in groups of 100 - playerNetServerHandler.sendPacket(new PacketStatistic(stat.statId, 100)); - } - if (i > 0) { - playerNetServerHandler.sendPacket(new PacketStatistic(stat.statId, i)); + for (; i > 100; i -= 100) { + playerNetServerHandler.sendPacket(new PacketStatistic(stat.statId, 100)); + } + if (i > 0) { + playerNetServerHandler.sendPacket(new PacketStatistic(stat.statId, i)); + } + } else if (i < 0) { + int remaining = -i; + for (; remaining > 100; remaining -= 100) { + playerNetServerHandler.sendPacket(new PacketStatistic(stat.statId, -100)); + } + if (remaining > 0) { + playerNetServerHandler.sendPacket(new PacketStatistic(stat.statId, -remaining)); + } } } } @@ -788,6 +816,7 @@ public class PlayerServer extends Player implements ContainerListener { private ItemStack[] playerInventory = { null, null, null, null, null }; + private ItemStack lastHeldItem; private int currentWindowId; public boolean isChangingQuantityOnly; } 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 b4b3edf6c..2bb059989 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 @@ -545,16 +545,27 @@ public class PacketHandlerServer extends PacketHandler @Override public void handleUpdatePlayerState(@NotNull PacketUpdatePlayerState packetUpdatePlayerState) { if (!this.playerEntity.isAlive()) return; - if (packetUpdatePlayerState.state == PacketUpdatePlayerState.STATE_SNEAK) { - this.playerEntity.setSneaking(true); - if (this.playerEntity.vehicle != null) { - this.playerEntity.vehicle.ejectRider(); + switch (packetUpdatePlayerState.state) { + case PacketUpdatePlayerState.STATE_SNEAK -> { + this.playerEntity.setSneaking(true); + if (this.playerEntity.vehicle != null) { + this.playerEntity.vehicle.ejectRider(); + } + } + case PacketUpdatePlayerState.STATE_UN_SNEAK -> this.playerEntity.setSneaking(false); + case PacketUpdatePlayerState.STATE_LEAVE_BED -> { + this.playerEntity.wakeUpPlayer(false, true); + this.hasMoved = false; + } + case PacketUpdatePlayerState.STATE_NO_CLIP -> { + this.playerEntity.setNoclip(true); + if (!this.playerEntity.gamemode.hasPlayerFlight()) { + this.playerEntity.setNoclip(false); + } + } + case PacketUpdatePlayerState.STATE_DO_CLIP -> { + this.playerEntity.setNoclip(false); } - } else if (packetUpdatePlayerState.state == PacketUpdatePlayerState.STATE_UN_SNEAK) { - this.playerEntity.setSneaking(false); - } else if (packetUpdatePlayerState.state == PacketUpdatePlayerState.STATE_LEAVE_BED) { - this.playerEntity.wakeUpPlayer(false, true); - this.hasMoved = false; } } 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 ca95a3382..4d7899c9d 100644 --- a/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java +++ b/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java @@ -485,7 +485,8 @@ class WorkbenchGenerator { .addInput(Items.SULFUR) .addInput(Items.CLAY) .addInput(new ItemStack(Items.DYE, 1, DyeColor.WHITE.itemMeta)) - .create("dirt", new ItemStack(Blocks.DIRT, 2)); + .addInput(Items.AMMO_PEBBLE) + .create("dirt", new ItemStack(Blocks.DIRT, 4)); }