From 4e1b33c0eacf5c809fd55091724d0aadccaaf438 Mon Sep 17 00:00:00 2001 From: simibubi <31564874+simibubi@users.noreply.github.com> Date: Sun, 17 Nov 2019 21:25:59 +0100 Subject: [PATCH] Connecting the Quads - Added models and a block interface for connected textures - Added Framed glass as an example block --- .../java/com/simibubi/create/AllBlocks.java | 4 +- .../com/simibubi/create/CreateClient.java | 28 +++- .../create/foundation/block/CTModel.java | 109 ++++++++++++++ .../block/CTModelTextureHandler.java | 46 ++++++ .../block/IHaveConnectedTextures.java | 133 ++++++++++++++++++ .../contraptions/CachedBufferReloader.java | 2 + .../create/modules/palettes/CTGlassBlock.java | 37 +++++ .../create/blockstates/framed_glass.json | 5 + .../assets/create/models/block/ctblock.json | 17 +++ .../models/block/palettes/framed_glass.json | 6 + .../create/models/item/framed_glass.json | 3 + .../textures/block/connected/framed_glass.png | Bin 0 -> 3185 bytes .../create/textures/block/framed_glass.png | Bin 0 -> 478 bytes 13 files changed, 386 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/simibubi/create/foundation/block/CTModel.java create mode 100644 src/main/java/com/simibubi/create/foundation/block/CTModelTextureHandler.java create mode 100644 src/main/java/com/simibubi/create/foundation/block/IHaveConnectedTextures.java create mode 100644 src/main/java/com/simibubi/create/modules/palettes/CTGlassBlock.java create mode 100644 src/main/resources/assets/create/blockstates/framed_glass.json create mode 100644 src/main/resources/assets/create/models/block/ctblock.json create mode 100644 src/main/resources/assets/create/models/block/palettes/framed_glass.json create mode 100644 src/main/resources/assets/create/models/item/framed_glass.json create mode 100644 src/main/resources/assets/create/textures/block/connected/framed_glass.png create mode 100644 src/main/resources/assets/create/textures/block/framed_glass.png diff --git a/src/main/java/com/simibubi/create/AllBlocks.java b/src/main/java/com/simibubi/create/AllBlocks.java index 35de22404..6ef95116a 100644 --- a/src/main/java/com/simibubi/create/AllBlocks.java +++ b/src/main/java/com/simibubi/create/AllBlocks.java @@ -61,6 +61,7 @@ import com.simibubi.create.modules.logistics.management.base.LogisticalControlle import com.simibubi.create.modules.logistics.management.index.LogisticalIndexBlock; import com.simibubi.create.modules.logistics.transport.villager.LogisticiansTableBlock; import com.simibubi.create.modules.logistics.transport.villager.PackageFunnelBlock; +import com.simibubi.create.modules.palettes.CTGlassBlock; import com.simibubi.create.modules.palettes.GlassPaneBlock; import com.simibubi.create.modules.schematics.block.CreativeCrateBlock; import com.simibubi.create.modules.schematics.block.SchematicTableBlock; @@ -173,6 +174,7 @@ public enum AllBlocks { __PALETTES__(), TILED_GLASS(new GlassBlock(Properties.from(Blocks.GLASS))), TILED_GLASS_PANE(new GlassPaneBlock(Properties.from(Blocks.GLASS))), + FRAMED_GLASS(new CTGlassBlock(true)), ANDESITE_BRICKS(new Block(Properties.from(Blocks.ANDESITE))), DIORITE_BRICKS(new Block(Properties.from(Blocks.DIORITE))), @@ -264,7 +266,7 @@ public enum AllBlocks { blockItem = new MechanicalMixerBlockItem(standardItemProperties); else blockItem = new BlockItem(blockIn, standardItemProperties); - + registry.register(blockItem.setRegistryName(blockIn.getRegistryName())); } diff --git a/src/main/java/com/simibubi/create/CreateClient.java b/src/main/java/com/simibubi/create/CreateClient.java index 9ab7b1a15..a1205bb29 100644 --- a/src/main/java/com/simibubi/create/CreateClient.java +++ b/src/main/java/com/simibubi/create/CreateClient.java @@ -3,6 +3,9 @@ package com.simibubi.create; import java.util.Map; import java.util.function.Function; +import com.simibubi.create.foundation.block.CTModel; +import com.simibubi.create.foundation.block.IHaveConnectedTextures; +import com.simibubi.create.foundation.utility.Lang; import com.simibubi.create.modules.contraptions.CachedBufferReloader; import com.simibubi.create.modules.contraptions.WrenchModel; import com.simibubi.create.modules.contraptions.receivers.EncasedFanParticleHandler; @@ -27,6 +30,7 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.client.event.ModelBakeEvent; import net.minecraftforge.client.event.ModelRegistryEvent; +import net.minecraftforge.client.event.TextureStitchEvent; import net.minecraftforge.client.model.ModelLoader; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.fml.DistExecutor; @@ -41,7 +45,7 @@ public class CreateClient { public static SchematicAndQuillHandler schematicAndQuillHandler; public static EncasedFanParticleHandler fanParticles; public static int renderTicks; - + public static ModConfig config; public static void addListeners(IEventBus modEventBus) { @@ -50,6 +54,7 @@ public class CreateClient { modEventBus.addListener(CreateClient::createConfigs); modEventBus.addListener(CreateClient::onModelBake); modEventBus.addListener(CreateClient::onModelRegistry); + modEventBus.addListener(CreateClient::onTextureStitch); }); } @@ -86,16 +91,33 @@ public class CreateClient { schematicHologram.tick(); } + @OnlyIn(Dist.CLIENT) + public static void onTextureStitch(TextureStitchEvent.Pre event) { + if (!event.getMap().getBasePath().equals("textures")) + return; + for (AllBlocks allBlocks : AllBlocks.values()) { + if (!(allBlocks.get() instanceof IHaveConnectedTextures)) + continue; + event.addSprite(new ResourceLocation(Create.ID, "block/connected/" + Lang.asId(allBlocks.name()))); + } + } + @OnlyIn(Dist.CLIENT) public static void onModelBake(ModelBakeEvent event) { Map modelRegistry = event.getModelRegistry(); + for (AllBlocks allBlocks : AllBlocks.values()) { + if (!(allBlocks.get() instanceof IHaveConnectedTextures)) + continue; + swapModels(modelRegistry, getBlockModelLocation(allBlocks, ""), + t -> new CTModel(t, Lang.asId(allBlocks.name()))); + } + swapModels(modelRegistry, getItemModelLocation(AllItems.SYMMETRY_WAND), t -> new SymmetryWandModel(t).loadPartials(event)); swapModels(modelRegistry, getItemModelLocation(AllItems.PLACEMENT_HANDGUN), t -> new BuilderGunModel(t).loadPartials(event)); - swapModels(modelRegistry, getItemModelLocation(AllItems.WRENCH), - t -> new WrenchModel(t).loadPartials(event)); + swapModels(modelRegistry, getItemModelLocation(AllItems.WRENCH), t -> new WrenchModel(t).loadPartials(event)); swapModels(modelRegistry, getItemModelLocation(AllItems.DEFORESTER), t -> new DeforesterModel(t).loadPartials(event)); swapModels(modelRegistry, diff --git a/src/main/java/com/simibubi/create/foundation/block/CTModel.java b/src/main/java/com/simibubi/create/foundation/block/CTModel.java new file mode 100644 index 000000000..599148ff0 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/block/CTModel.java @@ -0,0 +1,109 @@ +package com.simibubi.create.foundation.block; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +import com.simibubi.create.foundation.block.CTModelTextureHandler.TextureEntry; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.client.renderer.model.BakedQuad; +import net.minecraft.client.renderer.model.IBakedModel; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IEnviromentBlockReader; +import net.minecraftforge.client.model.BakedModelWrapper; +import net.minecraftforge.client.model.data.EmptyModelData; +import net.minecraftforge.client.model.data.IModelData; +import net.minecraftforge.client.model.data.ModelDataMap; +import net.minecraftforge.client.model.data.ModelProperty; + +public class CTModel extends BakedModelWrapper { + + private static ModelProperty CT_PROPERTY = new ModelProperty<>(); + private TextureEntry texture; + + private class CTData { + int[] textures; + + public CTData() { + textures = new int[6]; + Arrays.fill(textures, -1); + } + + void put(Direction face, int texture) { + textures[face.getIndex()] = texture; + } + + int get(Direction face) { + return textures[face.getIndex()]; + } + } + + public CTModel(IBakedModel originalModel, String blockId) { + super(originalModel); + texture = CTModelTextureHandler.get(blockId); + } + + @Override + public IModelData getModelData(IEnviromentBlockReader world, BlockPos pos, BlockState state, IModelData tileData) { + if (!(state.getBlock() instanceof IHaveConnectedTextures)) + return EmptyModelData.INSTANCE; + CTData data = new CTData(); + IHaveConnectedTextures texDef = (IHaveConnectedTextures) state.getBlock(); + for (Direction face : Direction.values()) { + if (!Block.shouldSideBeRendered(state, world, pos, face)) + continue; + data.put(face, texDef.getTextureIndex(world, pos, state, face)); + } + return new ModelDataMap.Builder().withInitial(CT_PROPERTY, data).build(); + } + + @Override + public List getQuads(BlockState state, Direction side, Random rand, IModelData extraData) { + List quads = new ArrayList<>(super.getQuads(state, side, rand, extraData)); + if (!extraData.hasProperty(CT_PROPERTY)) + return quads; + IHaveConnectedTextures texDef = (IHaveConnectedTextures) state.getBlock(); + CTData data = extraData.getData(CT_PROPERTY); + + for (int i = 0; i < quads.size(); i++) { + BakedQuad quad = quads.get(i); + if (!texDef.appliesTo(quad)) + continue; + int index = data.get(quad.getFace()); + if (index == -1) + return quads; + + float textureSize = 16f / 128f / 8f; + float uShift = (index % 8) * textureSize; + float vShift = (index / 8) * textureSize * 2; + + uShift = texture.connectedTextures.getInterpolatedU((index % 8) * 2) - texture.originalTexture.getMinU(); + vShift = texture.connectedTextures.getInterpolatedV((index / 8) * 2) - texture.originalTexture.getMinV(); + + BakedQuad newQuad = new BakedQuad(Arrays.copyOf(quad.getVertexData(), quad.getVertexData().length), + quad.getTintIndex(), quad.getFace(), quad.getSprite(), quad.shouldApplyDiffuseLighting(), + quad.getFormat()); + VertexFormat format = quad.getFormat(); + int[] vertexData = newQuad.getVertexData(); + for (int vertex = 0; vertex < vertexData.length; vertex += format.getIntegerSize()) { + int uvOffset = format.getUvOffsetById(0) / 4; + int uIndex = vertex + uvOffset; + int vIndex = vertex + uvOffset + 1; + float u = Float.intBitsToFloat(vertexData[uIndex]); + float v = Float.intBitsToFloat(vertexData[vIndex]); + u += uShift; + v += vShift; + vertexData[uIndex] = Float.floatToIntBits(u); + vertexData[vIndex] = Float.floatToIntBits(v); + } + quads.set(i, newQuad); + } + return quads; + } + +} diff --git a/src/main/java/com/simibubi/create/foundation/block/CTModelTextureHandler.java b/src/main/java/com/simibubi/create/foundation/block/CTModelTextureHandler.java new file mode 100644 index 000000000..838f8a913 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/block/CTModelTextureHandler.java @@ -0,0 +1,46 @@ +package com.simibubi.create.foundation.block; + +import java.util.HashMap; +import java.util.Map; + +import com.simibubi.create.Create; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.AtlasTexture; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.util.ResourceLocation; + +public class CTModelTextureHandler { + + static class TextureEntry { + ResourceLocation originalTextureLocation; + ResourceLocation connectedTextureLocation; + TextureAtlasSprite originalTexture; + TextureAtlasSprite connectedTextures; + + void loadTextures() { + AtlasTexture textureMap = Minecraft.getInstance().getTextureMap(); + originalTexture = textureMap.getSprite(originalTextureLocation); + connectedTextures = textureMap.getSprite(connectedTextureLocation); + } + } + + static Map textures = new HashMap<>(); + + public static TextureEntry get(String blockId) { + if (textures.containsKey(blockId)) + return textures.get(blockId); + + TextureEntry entry = new TextureEntry(); + entry.originalTextureLocation = new ResourceLocation(Create.ID, "block/" + blockId); + entry.connectedTextureLocation = new ResourceLocation(Create.ID, "block/connected/" + blockId); + entry.loadTextures(); + textures.put(blockId, entry); + return entry; + } + + public static void reloadUVs() { + textures.values().forEach(TextureEntry::loadTextures); + } + +} diff --git a/src/main/java/com/simibubi/create/foundation/block/IHaveConnectedTextures.java b/src/main/java/com/simibubi/create/foundation/block/IHaveConnectedTextures.java new file mode 100644 index 000000000..534accb8b --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/block/IHaveConnectedTextures.java @@ -0,0 +1,133 @@ +package com.simibubi.create.foundation.block; + +import java.util.function.BiPredicate; + +import net.minecraft.block.BlockState; +import net.minecraft.client.renderer.model.BakedQuad; +import net.minecraft.util.Direction; +import net.minecraft.util.Direction.Axis; +import net.minecraft.util.Direction.AxisDirection; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IEnviromentBlockReader; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +public interface IHaveConnectedTextures { + + class CTContext { + boolean up, down, left, right; + boolean topLeft, topRight, bottomLeft, bottomRight; + } + + @OnlyIn(Dist.CLIENT) + public boolean appliesTo(BakedQuad quad); + + default boolean connectsTo(BlockState state, BlockState other, IEnviromentBlockReader reader, BlockPos pos, + BlockPos otherPos, Direction face) { + + BlockPos blockingPos = otherPos.offset(face); + if ((face.getAxis().getCoordinate(pos.getX(), pos.getY(), pos.getZ()) == face.getAxis() + .getCoordinate(otherPos.getX(), otherPos.getY(), otherPos.getZ())) + && connectsTo(state, reader.getBlockState(blockingPos), reader, pos, blockingPos, face)) + return false; + + return state.getBlock() == other.getBlock(); + } + + default int getTextureIndex(IEnviromentBlockReader reader, BlockPos pos, BlockState state, Direction face) { + return getTextureIndexForContext(reader, pos, state, face, buildContext(reader, pos, state, face)); + } + + default CTContext buildContext(IEnviromentBlockReader reader, BlockPos pos, BlockState state, Direction face) { + Axis axis = face.getAxis(); + boolean positive = face.getAxisDirection() == AxisDirection.POSITIVE; + Direction h = axis == Axis.X ? Direction.SOUTH : Direction.WEST; + Direction v = axis.isHorizontal() ? Direction.UP : Direction.NORTH; + h = positive ? h.getOpposite() : h; + if (face == Direction.DOWN) { + v = v.getOpposite(); + h = h.getOpposite(); + } + + final Direction horizontal = h; + final Direction vertical = v; + + BiPredicate connection = (x, y) -> { + BlockPos p = pos.offset(horizontal, x).offset(vertical, y); + return connectsTo(state, reader.getBlockState(p), reader, pos, p, face); + }; + + CTContext context = new CTContext(); + context.up = connection.test(0, 1); + context.down = connection.test(0, -1); + context.left = connection.test(-1, 0); + context.right = connection.test(1, 0); + context.topLeft = connection.test(-1, 1); + context.topRight = connection.test(1, 1); + context.bottomLeft = connection.test(-1, -1); + context.bottomRight = connection.test(1, -1); + return context; + } + + default int getTextureIndexForContext(IEnviromentBlockReader reader, BlockPos pos, BlockState state, Direction face, + CTContext c) { + int tileX = 0, tileY = 0; + int borders = (!c.up ? 1 : 0) + (!c.down ? 1 : 0) + (!c.left ? 1 : 0) + (!c.right ? 1 : 0); + + if (c.up) + tileX++; + if (c.down) + tileX += 2; + if (c.left) + tileY++; + if (c.right) + tileY += 2; + + if (borders == 0) { + if (c.topRight) + tileX++; + if (c.topLeft) + tileX += 2; + if (c.bottomRight) + tileY += 2; + if (c.bottomLeft) + tileY++; + } + + if (borders == 1) { + if (!c.right) { + if (c.topLeft || c.bottomLeft) { + tileY = 4; + tileX = -1 + (c.bottomLeft ? 1 : 0) + (c.topLeft ? 1 : 0) * 2; + } + } + if (!c.left) { + if (c.topRight || c.bottomRight) { + tileY = 5; + tileX = -1 + (c.bottomRight ? 1 : 0) + (c.topRight ? 1 : 0) * 2; + } + } + if (!c.down) { + if (c.topLeft || c.topRight) { + tileY = 6; + tileX = -1 + (c.topLeft ? 1 : 0) + (c.topRight ? 1 : 0) * 2; + } + } + if (!c.up) { + if (c.bottomLeft || c.bottomRight) { + tileY = 7; + tileX = -1 + (c.bottomLeft ? 1 : 0) + (c.bottomRight ? 1 : 0) * 2; + } + } + } + + if (borders == 2) { + if ((c.up && c.left && c.topLeft) || (c.down && c.left && c.bottomLeft) || (c.up && c.right && c.topRight) + || (c.down && c.right && c.bottomRight)) + tileX += 3; + } + + return tileX + 8 * tileY; + } + +} diff --git a/src/main/java/com/simibubi/create/modules/contraptions/CachedBufferReloader.java b/src/main/java/com/simibubi/create/modules/contraptions/CachedBufferReloader.java index 862496802..6a900e39e 100644 --- a/src/main/java/com/simibubi/create/modules/contraptions/CachedBufferReloader.java +++ b/src/main/java/com/simibubi/create/modules/contraptions/CachedBufferReloader.java @@ -1,5 +1,6 @@ package com.simibubi.create.modules.contraptions; +import com.simibubi.create.foundation.block.CTModelTextureHandler; import com.simibubi.create.foundation.utility.ColoredIndicatorRenderer; import com.simibubi.create.modules.contraptions.base.KineticTileEntityRenderer; import com.simibubi.create.modules.contraptions.receivers.constructs.ContraptionRenderer; @@ -22,6 +23,7 @@ public class CachedBufferReloader extends ReloadListener { ContraptionRenderer.invalidateCache(); MechanicalBearingTileEntityRenderer.invalidateCache(); ColoredIndicatorRenderer.invalidateCache(); + CTModelTextureHandler.reloadUVs(); } diff --git a/src/main/java/com/simibubi/create/modules/palettes/CTGlassBlock.java b/src/main/java/com/simibubi/create/modules/palettes/CTGlassBlock.java new file mode 100644 index 000000000..536ea4751 --- /dev/null +++ b/src/main/java/com/simibubi/create/modules/palettes/CTGlassBlock.java @@ -0,0 +1,37 @@ +package com.simibubi.create.modules.palettes; + +import java.util.function.Supplier; + +import com.simibubi.create.Create; +import com.simibubi.create.foundation.block.IHaveConnectedTextures; + +import net.minecraft.block.Blocks; +import net.minecraft.block.GlassBlock; +import net.minecraft.client.renderer.model.BakedQuad; +import net.minecraft.util.BlockRenderLayer; +import net.minecraft.util.ResourceLocation; + +public class CTGlassBlock extends GlassBlock implements IHaveConnectedTextures { + + private Supplier textureToReplace; + private boolean hasAlpha; + + public CTGlassBlock(boolean hasAlpha) { + super(Properties.from(Blocks.GLASS)); + textureToReplace = () -> { + return new ResourceLocation(Create.ID, "block/" + getRegistryName().getPath()); + }; + this.hasAlpha = hasAlpha; + } + + @Override + public boolean appliesTo(BakedQuad quad) { + return quad.getSprite().getName().equals(textureToReplace.get()); + } + + @Override + public BlockRenderLayer getRenderLayer() { + return hasAlpha ? BlockRenderLayer.TRANSLUCENT : super.getRenderLayer(); + } + +} diff --git a/src/main/resources/assets/create/blockstates/framed_glass.json b/src/main/resources/assets/create/blockstates/framed_glass.json new file mode 100644 index 000000000..cf1f52360 --- /dev/null +++ b/src/main/resources/assets/create/blockstates/framed_glass.json @@ -0,0 +1,5 @@ +{ + "variants": { + "": { "model": "create:block/palettes/framed_glass" } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/block/ctblock.json b/src/main/resources/assets/create/models/block/ctblock.json new file mode 100644 index 000000000..7607a59ae --- /dev/null +++ b/src/main/resources/assets/create/models/block/ctblock.json @@ -0,0 +1,17 @@ +{ + "parent": "block/block", + "elements": [ + { + "from": [0, 0, 0], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [0, 0, 2, 2], "texture": "#sheet", "cullface": "north"}, + "east": {"uv": [0, 0, 2, 2], "texture": "#sheet", "cullface": "east"}, + "south": {"uv": [0, 0, 2, 2], "texture": "#sheet", "cullface": "south"}, + "west": {"uv": [0, 0, 2, 2], "texture": "#sheet", "cullface": "west"}, + "up": {"uv": [0, 0, 2, 2], "texture": "#sheet", "cullface": "up"}, + "down": {"uv": [0, 0, 2, 2], "texture": "#sheet", "cullface": "down"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/create/models/block/palettes/framed_glass.json b/src/main/resources/assets/create/models/block/palettes/framed_glass.json new file mode 100644 index 000000000..47c73aae8 --- /dev/null +++ b/src/main/resources/assets/create/models/block/palettes/framed_glass.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "create:block/framed_glass" + } +} diff --git a/src/main/resources/assets/create/models/item/framed_glass.json b/src/main/resources/assets/create/models/item/framed_glass.json new file mode 100644 index 000000000..a8e4e28b8 --- /dev/null +++ b/src/main/resources/assets/create/models/item/framed_glass.json @@ -0,0 +1,3 @@ +{ + "parent": "create:block/palettes/framed_glass" +} \ No newline at end of file diff --git a/src/main/resources/assets/create/textures/block/connected/framed_glass.png b/src/main/resources/assets/create/textures/block/connected/framed_glass.png new file mode 100644 index 0000000000000000000000000000000000000000..240f72174fca236cb7368d64477e322ca4b67cd5 GIT binary patch literal 3185 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 TmdV=wn*cc5yW2IL3r_ntKQ&8z literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/create/textures/block/framed_glass.png b/src/main/resources/assets/create/textures/block/framed_glass.png new file mode 100644 index 0000000000000000000000000000000000000000..9c22d68c8f2ed89a445c3b2af1bcbd0362754c02 GIT binary patch literal 478 zcmV<40U`d0P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0dq-2K~y+Tos&yT z!$1(nXR{4%YU?XhM5zTW>Osh`WsAAt<@+t zIa3MJmdaMCRLGCTUTIZsE~Mn+Yg0x*5<9!TlRHHrQ9rr?g5o44KOH|+gn@N*{m&n2 zCuKx~q_`g)sJj8h-zEGPN`NJdqXD9y-pH7J3dYqSRL6jdM-A`>kumi`I*i{QT(`1}9>Us@KR UZkRz^J^%m!07*qoM6N<$f)^3Zi2wiq literal 0 HcmV?d00001