diff --git a/src/main/generated/assets/galacticraft/blockstates/web_string.json b/src/main/generated/assets/galacticraft/blockstates/web_string.json new file mode 100644 index 000000000..ddc2ac7c7 --- /dev/null +++ b/src/main/generated/assets/galacticraft/blockstates/web_string.json @@ -0,0 +1,36 @@ +{ + "multipart": [ + { + "apply": { + "model": "galacticraft:block/web_string_top" + }, + "when": { + "web_string_part": "top" + } + }, + { + "apply": { + "model": "galacticraft:block/web_string_middle" + }, + "when": { + "web_string_part": "middle" + } + }, + { + "apply": { + "model": "galacticraft:block/web_string_bottom" + }, + "when": { + "web_string_part": "bottom" + } + }, + { + "apply": { + "model": "galacticraft:block/web_string_top_bottom" + }, + "when": { + "web_string_part": "top_bottom" + } + } + ] +} \ No newline at end of file diff --git a/src/main/generated/assets/galacticraft/blockstates/web_torch.json b/src/main/generated/assets/galacticraft/blockstates/web_torch.json new file mode 100644 index 000000000..167377bff --- /dev/null +++ b/src/main/generated/assets/galacticraft/blockstates/web_torch.json @@ -0,0 +1,20 @@ +{ + "multipart": [ + { + "apply": { + "model": "galacticraft:block/web_torch" + }, + "when": { + "top": "false" + } + }, + { + "apply": { + "model": "galacticraft:block/web_torch_top" + }, + "when": { + "top": "true" + } + } + ] +} \ No newline at end of file diff --git a/src/main/generated/assets/galacticraft/lang/en_us.json b/src/main/generated/assets/galacticraft/lang/en_us.json index 949e23913..ca59a0c0e 100644 --- a/src/main/generated/assets/galacticraft/lang/en_us.json +++ b/src/main/generated/assets/galacticraft/lang/en_us.json @@ -381,6 +381,8 @@ "block.galacticraft.venus_tin_ore": "Venus Tin Ore", "block.galacticraft.volcanic_rock": "Volcanic Rock", "block.galacticraft.walkway": "Walkway", + "block.galacticraft.web_string": "Web String", + "block.galacticraft.web_torch": "Web Torch", "block.galacticraft.white_candle_moon_cheese_wheel": "Moon Cheese Wheel with White Candle", "block.galacticraft.white_glass_fluid_pipe": "White Stained Glass Fluid Pipe", "block.galacticraft.wire_walkway": "Wire Walkway", diff --git a/src/main/generated/assets/galacticraft/models/item/web_string.json b/src/main/generated/assets/galacticraft/models/item/web_string.json new file mode 100644 index 000000000..0e9037f3c --- /dev/null +++ b/src/main/generated/assets/galacticraft/models/item/web_string.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "galacticraft:block/web_string_middle" + } +} \ No newline at end of file diff --git a/src/main/generated/assets/galacticraft/models/item/web_torch.json b/src/main/generated/assets/galacticraft/models/item/web_torch.json new file mode 100644 index 000000000..452cea97f --- /dev/null +++ b/src/main/generated/assets/galacticraft/models/item/web_torch.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "galacticraft:block/web_torch" + } +} \ No newline at end of file diff --git a/src/main/generated/data/galacticraft/advancement/recipes/decorations/web_torch.json b/src/main/generated/data/galacticraft/advancement/recipes/decorations/web_torch.json new file mode 100644 index 000000000..4afd5353d --- /dev/null +++ b/src/main/generated/data/galacticraft/advancement/recipes/decorations/web_torch.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_the_recipe": { + "conditions": { + "recipe": "galacticraft:web_torch" + }, + "trigger": "minecraft:recipe_unlocked" + }, + "has_web_string": { + "conditions": { + "items": [ + { + "items": "galacticraft:web_string" + } + ] + }, + "trigger": "minecraft:inventory_changed" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_web_string" + ] + ], + "rewards": { + "recipes": [ + "galacticraft:web_torch" + ] + } +} \ No newline at end of file diff --git a/src/main/generated/data/galacticraft/loot_table/blocks/web_string.json b/src/main/generated/data/galacticraft/loot_table/blocks/web_string.json new file mode 100644 index 000000000..3c24fa5c3 --- /dev/null +++ b/src/main/generated/data/galacticraft/loot_table/blocks/web_string.json @@ -0,0 +1,20 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "galacticraft:web_string" + } + ], + "rolls": 1.0 + } + ] +} \ No newline at end of file diff --git a/src/main/generated/data/galacticraft/loot_table/blocks/web_torch.json b/src/main/generated/data/galacticraft/loot_table/blocks/web_torch.json new file mode 100644 index 000000000..0d202d2b4 --- /dev/null +++ b/src/main/generated/data/galacticraft/loot_table/blocks/web_torch.json @@ -0,0 +1,20 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "galacticraft:web_torch" + } + ], + "rolls": 1.0 + } + ] +} \ No newline at end of file diff --git a/src/main/generated/data/galacticraft/recipe/web_torch.json b/src/main/generated/data/galacticraft/recipe/web_torch.json new file mode 100644 index 000000000..0d08eaaa1 --- /dev/null +++ b/src/main/generated/data/galacticraft/recipe/web_torch.json @@ -0,0 +1,16 @@ +{ + "type": "minecraft:crafting_shapeless", + "category": "misc", + "ingredients": [ + { + "item": "galacticraft:web_string" + }, + { + "item": "galacticraft:glowstone_torch" + } + ], + "result": { + "count": 1, + "id": "galacticraft:web_torch" + } +} \ No newline at end of file diff --git a/src/main/java/dev/galacticraft/mod/Constant.java b/src/main/java/dev/galacticraft/mod/Constant.java index 45e468ad1..1079df33c 100644 --- a/src/main/java/dev/galacticraft/mod/Constant.java +++ b/src/main/java/dev/galacticraft/mod/Constant.java @@ -234,6 +234,8 @@ interface Block { String WALKWAY = "walkway"; String WIRE_WALKWAY = "wire_walkway"; String FLUID_PIPE_WALKWAY = "fluid_pipe_walkway"; + String WEB_TORCH = "web_torch"; + String WEB_STRING = "web_string"; // Environment String GLOWSTONE_TORCH = "glowstone_torch"; @@ -247,7 +249,6 @@ interface Block { String UNLIT_SOUL_LANTERN = "unlit_soul_lantern"; String CAVERNOUS_VINES = "cavernous_vines"; String CAVERNOUS_VINES_PLANT = "cavernous_vines_plant"; - String WEB_TORCH = "web_torch"; String FALLEN_METEOR = "fallen_meteor"; String SLIMELING_EGG = "slimeling_egg"; String CREEPER_EGG = "creeper_egg"; diff --git a/src/main/java/dev/galacticraft/mod/GalacticraftClient.java b/src/main/java/dev/galacticraft/mod/GalacticraftClient.java index fe4c7638a..14eb34462 100644 --- a/src/main/java/dev/galacticraft/mod/GalacticraftClient.java +++ b/src/main/java/dev/galacticraft/mod/GalacticraftClient.java @@ -175,6 +175,8 @@ public void onInitializeClient() { BlockRenderLayerMap.INSTANCE.putBlock(GCBlocks.GLOWSTONE_LANTERN, RenderType.cutout()); BlockRenderLayerMap.INSTANCE.putBlock(GCBlocks.UNLIT_LANTERN, RenderType.cutout()); BlockRenderLayerMap.INSTANCE.putBlock(GCBlocks.UNLIT_SOUL_LANTERN, RenderType.cutout()); + BlockRenderLayerMap.INSTANCE.putBlock(GCBlocks.WEB_TORCH, RenderType.cutout()); + BlockRenderLayerMap.INSTANCE.putBlock(GCBlocks.WEB_STRING, RenderType.cutout()); BlockRenderLayerMap.INSTANCE.putBlock(GCBlocks.CAVERNOUS_VINES, RenderType.cutout()); BlockRenderLayerMap.INSTANCE.putBlock(GCBlocks.CAVERNOUS_VINES_PLANT, RenderType.cutout()); BlockRenderLayerMap.INSTANCE.putBlock(GCBlocks.OLIVINE_CLUSTER, RenderType.cutout()); diff --git a/src/main/java/dev/galacticraft/mod/content/GCBlocks.java b/src/main/java/dev/galacticraft/mod/content/GCBlocks.java index 856c9406c..eb0d98034 100644 --- a/src/main/java/dev/galacticraft/mod/content/GCBlocks.java +++ b/src/main/java/dev/galacticraft/mod/content/GCBlocks.java @@ -366,6 +366,10 @@ public class GCBlocks { public static final Block UNLIT_WALL_TORCH = BLOCKS.register(Constant.Block.UNLIT_WALL_TORCH, new UnlitWallTorchBlock(Blocks.WALL_TORCH, BlockBehaviour.Properties.ofFullCopy(UNLIT_TORCH).dropsLike(UNLIT_TORCH))); public static final Block UNLIT_SOUL_WALL_TORCH = BLOCKS.register(Constant.Block.UNLIT_SOUL_WALL_TORCH, new UnlitWallTorchBlock(Blocks.SOUL_WALL_TORCH, BlockBehaviour.Properties.ofFullCopy(UNLIT_SOUL_TORCH).dropsLike(UNLIT_SOUL_TORCH))); + // WEB TORCH & WEB STRING + public static final Block WEB_TORCH = BLOCKS.registerWithItem(Constant.Block.WEB_TORCH, new WebTorchBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOL).sound(SoundType.COBWEB).noCollission().strength(3.0F).pushReaction(PushReaction.DESTROY).lightLevel(state -> 10))); + public static final Block WEB_STRING = BLOCKS.registerWithItem(Constant.Block.WEB_STRING, new WebStringBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOL).sound(SoundType.COBWEB).noCollission().strength(3.0F).pushReaction(PushReaction.DESTROY))); + // LANTERNS - Don't use registerWithItem in order for the torches to be before the lanterns public static final Block GLOWSTONE_LANTERN = BLOCKS.register(Constant.Block.GLOWSTONE_LANTERN, new GlowstoneLanternBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.LANTERN))); public static final Block UNLIT_LANTERN = BLOCKS.register(Constant.Block.UNLIT_LANTERN, new UnlitLanternBlock(Blocks.LANTERN, BlockBehaviour.Properties.ofFullCopy(Blocks.LANTERN).lightLevel(state -> 0))); @@ -392,7 +396,7 @@ private static boolean never(BlockState blockState, BlockGetter blockGetter, Blo return false; } - private static ToIntFunction litBlockEmission(int i) { - return blockState -> blockState.getValue(BlockStateProperties.LIT) ? i : 0; + private static ToIntFunction litBlockEmission(int intensity) { + return blockState -> blockState.getValue(BlockStateProperties.LIT) ? intensity : 0; } } \ No newline at end of file diff --git a/src/main/java/dev/galacticraft/mod/content/block/special/GCWebBlock.java b/src/main/java/dev/galacticraft/mod/content/block/special/GCWebBlock.java new file mode 100644 index 000000000..f138ee027 --- /dev/null +++ b/src/main/java/dev/galacticraft/mod/content/block/special/GCWebBlock.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019-2025 Team Galacticraft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package dev.galacticraft.mod.content.block.special; + +import com.mojang.serialization.MapCodec; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.WebBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +/// This class acts as a superclass for the WebTorch and WebString classes, providing common code for handling of +/// the intersection between the player and the web string and web torches. This is so the player slows down, similar +/// to the way that a player slows down in a normal web block. It differs to the MC WebBlock in that the collision +/// box is based on the voxel shape as opposed to a full bounding box for a block. It still inherits the behaviour +/// from the MC WebBlock though. + +public class GCWebBlock extends WebBlock { + public static final MapCodec CODEC = simpleCodec(GCWebBlock::new); + + @Override + public MapCodec codec() { + return CODEC; + } + + public GCWebBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + protected VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { + return getShape(state); + } + + // Must override this method or an exception will be generated! + protected VoxelShape getShape(BlockState state) { + return null; + } + + protected AABB getShapeInWorldCoordinates(BlockState state, BlockPos pos) { + return getShape(state).bounds().move(pos); + } + + @Override + protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entityGoalInfo) { + AABB entityBB = entityGoalInfo.getBoundingBox(); + AABB shapeBB = getShapeInWorldCoordinates(state, pos); + if (shapeBB.intersects(entityBB)) { + super.entityInside(state, level, pos, entityGoalInfo); + } + } +} diff --git a/src/main/java/dev/galacticraft/mod/content/block/special/WebStringBlock.java b/src/main/java/dev/galacticraft/mod/content/block/special/WebStringBlock.java new file mode 100644 index 000000000..e4e2c2ae0 --- /dev/null +++ b/src/main/java/dev/galacticraft/mod/content/block/special/WebStringBlock.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2019-2025 Team Galacticraft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package dev.galacticraft.mod.content.block.special; + +import com.mojang.serialization.MapCodec; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.sounds.SoundSource; +import net.minecraft.util.StringRepresentable; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.ItemInteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.WebBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.level.gameevent.GameEvent; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.shapes.VoxelShape; +import org.jetbrains.annotations.Nullable; + +import static dev.galacticraft.mod.content.GCBlocks.WEB_STRING; +import static dev.galacticraft.mod.content.GCBlocks.WEB_TORCH; +import static dev.galacticraft.mod.content.item.GCItems.GLOWSTONE_TORCH; + +public class WebStringBlock extends GCWebBlock { + + public static final MapCodec CODEC = simpleCodec(WebStringBlock::new); + + public static final EnumProperty WEB_STRING_PART = WebStringPart.WEB_STRING_PART; + + public WebStringBlock(BlockBehaviour.Properties properties) { + super(properties); + this.registerDefaultState(this.stateDefinition.any().setValue(WEB_STRING_PART, WebStringPart.MIDDLE)); + } + + protected static final VoxelShape FULL_VOXEL = Block.box(5.0, 0.0, 5.0, 11.0, 16.0, 11.0); + protected static final VoxelShape BOTTOM_VOXEL = Block.box(5.0, 3.0, 5.0, 11.0, 16.0, 11.0); + + @Override + protected VoxelShape getShape(BlockState state) { + if (state.getValue(WEB_STRING_PART) == WebStringPart.BOTTOM) { + return BOTTOM_VOXEL; + } else { + return FULL_VOXEL; + } + } + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) { + BlockPos pos = ctx.getClickedPos().above(); + BlockState state = ctx.getLevel().getBlockState(pos); + if (state.is(WEB_STRING)) { + return defaultBlockState().setValue(WEB_STRING_PART, WebStringPart.BOTTOM); + } + return defaultBlockState().setValue(WEB_STRING_PART, WebStringPart.TOP_BOTTOM); + } + + @Nullable + private ItemInteractionResult checkCanBlockBePlaced(Level level, BlockPos checkPos, Player player, ItemStack itemStack, BlockState newState) { + BlockState checkState = level.getBlockState(checkPos); + if (checkState.canBeReplaced() && newState.canSurvive(level, checkPos)) { + level.setBlockAndUpdate(checkPos, newState); + level.playSound(null, checkPos, newState.getSoundType().getPlaceSound(), SoundSource.BLOCKS, (newState.getSoundType().getVolume() + 1.0F) / 2.0F, newState.getSoundType().getPitch() * 0.8F); + level.gameEvent(GameEvent.BLOCK_PLACE, checkPos, GameEvent.Context.of(player, newState)); + + if (!player.getAbilities().instabuild) { + itemStack.shrink(1); + } + return ItemInteractionResult.SUCCESS; + } else if (!checkState.is(this)) { + return ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION; + } + return null; + } + + private ItemInteractionResult convertToWebTorch(ItemStack stack, BlockState state, Level level, BlockPos pos, Player player) { + BlockState newState = WEB_TORCH.defaultBlockState().setValue(WebTorchBlock.TOP, + state.getValue(WEB_STRING_PART) == WebStringPart.TOP + || state.getValue(WEB_STRING_PART) == WebStringPart.TOP_BOTTOM); + level.setBlockAndUpdate(pos, newState); + level.playSound(null, pos, newState.getSoundType().getPlaceSound(), SoundSource.BLOCKS, + (newState.getSoundType().getVolume() + 1.0F) / 2.0F, newState.getSoundType().getPitch() * 0.8F); + level.gameEvent(GameEvent.BLOCK_PLACE, pos, GameEvent.Context.of(player, newState)); + if (!player.getAbilities().instabuild) { + stack.shrink(1); + } + return ItemInteractionResult.SUCCESS; + } + + private ItemInteractionResult extendBlockDown(ItemStack stack, Level level, BlockPos pos, Player player, BlockState newState) { + // Find a pos directly below the existing block (this) but above world bottom where the new block can be placed. + for (BlockPos checkPos = new BlockPos.MutableBlockPos(pos.getX(), pos.getY(), pos.getZ()); checkPos.getY() > level.getMinBuildHeight(); checkPos = checkPos.offset(0, -1, 0)) { + ItemInteractionResult result = checkCanBlockBePlaced(level, checkPos, player, stack, newState); + if (result != null) return result; + } + return null; + } + + /// This method is called when the player tries to use something on a Web String block. + /// The default behaviour is extended by checking if that item is a glowstone torch, which + /// automatically hands the torch in the web. Web strings below the torch are broken. + @Override + protected ItemInteractionResult useItemOn(ItemStack stack, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + ItemInteractionResult result = null; + // If using a glowstone torch on a web string, then it gets hung in the web string becoming a web torch block. + if (stack.is(GLOWSTONE_TORCH)) { + result = convertToWebTorch(stack, state, level, pos, player); + } else if (stack.is(WEB_STRING.asItem())) { // If using a Web string on the web string, then extend the whole column of web strings down if we can. + result = extendBlockDown(stack, level, pos, player, defaultBlockState().setValue(WEB_STRING_PART, WebStringPart.BOTTOM)); + } else if (stack.is(WEB_TORCH.asItem())) { // If using a web torch on the web string, then extend the whole column of web strings down if we can. + result = extendBlockDown(stack, level, pos, player, WEB_TORCH.defaultBlockState()); + } + + if (result == null) { + result = super.useItemOn(stack, state, level, pos, player, hand, hit); + } + return result; + } + + /// Manages the appearance of the web strings via the web string part property. Effectively top means it's attached + /// to something, bottom means it is the end dangling down, and middle means it is neither the top nor the bottom. + /// Top_Bottom means it is both the top and the only bit. The method also handles breaking of the web string + /// if the block above is broken. + @Override + protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) { + // If above changed and it isn't a web string block (and in theory not a solid block) then it breaks. + if (direction == Direction.UP && !neighborState.is(WEB_STRING)) { + return Blocks.AIR.defaultBlockState(); + } else if (direction == Direction.DOWN) { // If block below changed + WebStringPart webStringState = state.getValue(WEB_STRING_PART); + if (neighborState.is(WEB_STRING) || neighborState.is(WEB_TORCH)) { // If below is a web string or a web torch + if (webStringState == WebStringPart.TOP_BOTTOM || webStringState == WebStringPart.TOP) { // If it was at the top + return state.setValue(WEB_STRING_PART, WebStringPart.TOP); // Then still top + } else { + return state.setValue(WEB_STRING_PART, WebStringPart.MIDDLE); // Otherwise middle + } + } else { + if (webStringState == WebStringPart.TOP) { // If it was at the top + return state.setValue(WEB_STRING_PART, WebStringPart.TOP_BOTTOM); // Then top and bottom + } else { + return state.setValue(WEB_STRING_PART, WebStringPart.BOTTOM); // Otherwise bottom + } + } + } + // No change + return state; + } + + @Override + protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) { + BlockState checkState = level.getBlockState(pos); + return this.canAttachTo(level, pos.above(), Direction.DOWN) && (checkState.getFluidState().is(Fluids.EMPTY) || !checkState.is(Blocks.WATER)); + } + + private boolean canAttachTo(BlockGetter world, BlockPos pos, Direction side) { + BlockState blockState = world.getBlockState(pos); + return blockState.isFaceSturdy(world, pos, side) || blockState.is(WEB_STRING); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder compositeStateBuilder) { + compositeStateBuilder.add(WEB_STRING_PART); + } + + public enum WebStringPart implements StringRepresentable { + TOP("top"), + MIDDLE("middle"), + BOTTOM("bottom"), + TOP_BOTTOM("top_bottom"); + + private final String name; + + private WebStringPart(final String name) { + this.name = name; + } + + public String toString() { + return this.getSerializedName(); + } + + @Override + public String getSerializedName() { + return this.name; + } + + public static final EnumProperty WEB_STRING_PART = EnumProperty.create("web_string_part", WebStringPart.class); + } +} \ No newline at end of file diff --git a/src/main/java/dev/galacticraft/mod/content/block/special/WebTorchBlock.java b/src/main/java/dev/galacticraft/mod/content/block/special/WebTorchBlock.java new file mode 100644 index 000000000..96ad4b4af --- /dev/null +++ b/src/main/java/dev/galacticraft/mod/content/block/special/WebTorchBlock.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2019-2025 Team Galacticraft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package dev.galacticraft.mod.content.block.special; + +import com.mojang.serialization.MapCodec; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.WebBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.phys.shapes.VoxelShape; + +import static dev.galacticraft.mod.content.GCBlocks.WEB_STRING; + +public class WebTorchBlock extends GCWebBlock { + + public static final MapCodec CODEC = simpleCodec(WebTorchBlock::new); + + public static final BooleanProperty TOP = BooleanProperty.create("top"); + + public WebTorchBlock(BlockBehaviour.Properties properties) { + super(properties); + this.registerDefaultState(this.stateDefinition.any().setValue(TOP, false)); + } + + protected static final VoxelShape TORCH_VOXEL = Block.box(5.0, 3.0, 5.0, 11.0, 16.0, 11.0); + + @Override + protected VoxelShape getShape(BlockState state) { + return TORCH_VOXEL; + } + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) { + BlockPos pos = ctx.getClickedPos(); + BlockState state = ctx.getLevel().getBlockState(pos); + if (!state.is(WEB_STRING)) { + return defaultBlockState().setValue(TOP, true); + } + return defaultBlockState().setValue(TOP, false); + } + + // Manages the updates to the web torch to determine if this block is attached to a solid face or a web string. + @Override + protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) { + // If above changed and it isn't a web torch block (and in theory not a solid block) then it breaks. + if (direction == Direction.UP && !neighborState.is(WEB_STRING)) { + return Blocks.AIR.defaultBlockState(); + } + // No change + return state; + } + + @Override + protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) { + BlockState checkState = level.getBlockState(pos); + return this.canAttachTo(level, pos.above(), Direction.DOWN) && (checkState.getFluidState().is(Fluids.EMPTY) || !checkState.is(Blocks.WATER)); + } + + private boolean canAttachTo(BlockGetter world, BlockPos pos, Direction side) { + BlockState blockState = world.getBlockState(pos); + return blockState.isFaceSturdy(world, pos, side) || blockState.is(WEB_STRING); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder compositeStateBuilder) { + compositeStateBuilder.add(TOP); + } +} \ No newline at end of file diff --git a/src/main/java/dev/galacticraft/mod/content/item/GCCreativeModeTabs.java b/src/main/java/dev/galacticraft/mod/content/item/GCCreativeModeTabs.java index 83a21bccd..6755f6277 100644 --- a/src/main/java/dev/galacticraft/mod/content/item/GCCreativeModeTabs.java +++ b/src/main/java/dev/galacticraft/mod/content/item/GCCreativeModeTabs.java @@ -210,6 +210,10 @@ public class GCCreativeModeTabs { output.accept(GCItems.UNLIT_LANTERN); output.accept(GCItems.UNLIT_SOUL_LANTERN); + // WEB TORCH/STRING + output.accept(WEB_TORCH); + output.accept(WEB_STRING); + // MISC DECOR output.accept(WALKWAY); output.accept(WIRE_WALKWAY); diff --git a/src/main/java/dev/galacticraft/mod/data/GCBlockLootTableProvider.java b/src/main/java/dev/galacticraft/mod/data/GCBlockLootTableProvider.java index 038eff19d..1a4dbd44b 100644 --- a/src/main/java/dev/galacticraft/mod/data/GCBlockLootTableProvider.java +++ b/src/main/java/dev/galacticraft/mod/data/GCBlockLootTableProvider.java @@ -73,6 +73,9 @@ public void generate() { this.dropSelf(GCBlocks.UNLIT_LANTERN); this.dropSelf(GCBlocks.UNLIT_SOUL_LANTERN); + this.dropSelf(GCBlocks.WEB_TORCH); + this.dropSelf(GCBlocks.WEB_STRING); + for (DecorationSet decorationSet : GCBlocks.BLOCKS.getDecorations()) { this.dropSelf(decorationSet.block()); this.dropSelf(decorationSet.stairs()); diff --git a/src/main/java/dev/galacticraft/mod/data/model/GCModelProvider.java b/src/main/java/dev/galacticraft/mod/data/model/GCModelProvider.java index cbcd2e57e..6e9dfc352 100644 --- a/src/main/java/dev/galacticraft/mod/data/model/GCModelProvider.java +++ b/src/main/java/dev/galacticraft/mod/data/model/GCModelProvider.java @@ -38,6 +38,8 @@ import dev.galacticraft.mod.content.block.machine.ResourceStorageBlock; import dev.galacticraft.mod.content.block.special.ParachestBlock; import dev.galacticraft.mod.content.block.special.RocketWorkbench; +import dev.galacticraft.mod.content.block.special.WebStringBlock; +import dev.galacticraft.mod.content.block.special.WebTorchBlock; import dev.galacticraft.mod.content.block.special.launchpad.AbstractLaunchPad; import dev.galacticraft.mod.content.item.GCItems; import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; @@ -109,6 +111,10 @@ public void generateBlockStateModels(BlockModelGenerators generator) { generator.createLantern(GCBlocks.UNLIT_LANTERN); generator.createLantern(GCBlocks.UNLIT_SOUL_LANTERN); + // WEB TORCH/STRING + createWebTorch(generator, GCBlocks.WEB_TORCH); + createWebString(generator, GCBlocks.WEB_STRING); + // MOON NATURAL this.createMoonTurf(generator); generator.createTrivialCube(GCBlocks.MOON_DIRT); @@ -470,6 +476,30 @@ private static void createActiveMachine(BlockModelGenerators generator, Block bl ))); } + private static void createWebString(BlockModelGenerators generator, Block webString) { + ResourceLocation topModel = ModelLocationUtils.getModelLocation(GCBlocks.WEB_STRING, "_top"); + ResourceLocation middleModel = ModelLocationUtils.getModelLocation(GCBlocks.WEB_STRING, "_middle"); + ResourceLocation bottomModel = ModelLocationUtils.getModelLocation(GCBlocks.WEB_STRING, "_bottom"); + ResourceLocation topBottomModel = ModelLocationUtils.getModelLocation(GCBlocks.WEB_STRING, "_top_bottom"); + generator.createSimpleFlatItemModel(webString, "_middle"); + MultiPartGenerator blockState = MultiPartGenerator.multiPart(webString) + .with(Condition.condition().term(WebStringBlock.WebStringPart.WEB_STRING_PART, WebStringBlock.WebStringPart.TOP), Variant.variant().with(VariantProperties.MODEL, topModel)) + .with(Condition.condition().term(WebStringBlock.WebStringPart.WEB_STRING_PART, WebStringBlock.WebStringPart.MIDDLE), Variant.variant().with(VariantProperties.MODEL, middleModel)) + .with(Condition.condition().term(WebStringBlock.WebStringPart.WEB_STRING_PART, WebStringBlock.WebStringPart.BOTTOM), Variant.variant().with(VariantProperties.MODEL, bottomModel)) + .with(Condition.condition().term(WebStringBlock.WebStringPart.WEB_STRING_PART, WebStringBlock.WebStringPart.TOP_BOTTOM), Variant.variant().with(VariantProperties.MODEL, topBottomModel)); + generator.blockStateOutput.accept(blockState); + } + + private static void createWebTorch(BlockModelGenerators generator, Block webTorch) { + ResourceLocation normalModel = ModelLocationUtils.getModelLocation(GCBlocks.WEB_TORCH); + ResourceLocation topModel = ModelLocationUtils.getModelLocation(GCBlocks.WEB_TORCH, "_top"); + generator.createSimpleFlatItemModel(webTorch); + MultiPartGenerator blockState = MultiPartGenerator.multiPart(webTorch) + .with(Condition.condition().term(WebTorchBlock.TOP, false), Variant.variant().with(VariantProperties.MODEL, normalModel)) + .with(Condition.condition().term(WebTorchBlock.TOP, true), Variant.variant().with(VariantProperties.MODEL, topModel)); + generator.blockStateOutput.accept(blockState); + } + private static void createWalkway(BlockModelGenerators generator, Block walkway, ResourceLocation pipeModel, ResourceLocation centerModel) { ResourceLocation walkwayPlatform = Constant.id("block/walkway"); MultiPartGenerator blockState = MultiPartGenerator.multiPart(walkway) diff --git a/src/main/java/dev/galacticraft/mod/data/recipes/GCDecorationRecipeProvider.java b/src/main/java/dev/galacticraft/mod/data/recipes/GCDecorationRecipeProvider.java index 20dba3c28..d02c3977c 100644 --- a/src/main/java/dev/galacticraft/mod/data/recipes/GCDecorationRecipeProvider.java +++ b/src/main/java/dev/galacticraft/mod/data/recipes/GCDecorationRecipeProvider.java @@ -34,6 +34,7 @@ import net.minecraft.data.recipes.RecipeCategory; import net.minecraft.data.recipes.RecipeOutput; import net.minecraft.data.recipes.ShapedRecipeBuilder; +import net.minecraft.data.recipes.ShapelessRecipeBuilder; import net.minecraft.data.recipes.SimpleCookingRecipeBuilder; import net.minecraft.world.item.Items; import net.minecraft.world.item.crafting.Ingredient; @@ -87,6 +88,12 @@ public void buildRecipes(RecipeOutput output) { .unlockedBy(getHasName(Items.GLOWSTONE_DUST), has(ConventionalItemTags.GLOWSTONE_DUSTS)) .save(output); + ShapelessRecipeBuilder.shapeless(RecipeCategory.DECORATIONS, GCBlocks.WEB_TORCH, 1) + .requires(GCBlocks.WEB_STRING) + .requires(GCItems.GLOWSTONE_TORCH) + .unlockedBy(getHasName(GCBlocks.WEB_STRING), has(GCBlocks.WEB_STRING)) + .save(output); + ShapedRecipeBuilder.shaped(RecipeCategory.DECORATIONS, GCBlocks.GLOWSTONE_LANTERN) .define('G', GCItems.GLOWSTONE_TORCH) .define('I', ConventionalItemTags.IRON_NUGGETS) diff --git a/src/main/resources/assets/galacticraft/models/block/web_string_bottom.json b/src/main/resources/assets/galacticraft/models/block/web_string_bottom.json new file mode 100644 index 000000000..6bebefb59 --- /dev/null +++ b/src/main/resources/assets/galacticraft/models/block/web_string_bottom.json @@ -0,0 +1,29 @@ +{ + "ambientocclusion": false, + "textures": { + "cross": "galacticraft:block/web_string_bottom", + "particle": "galacticraft:block/web_string_bottom" + }, + "elements": [ + { + "from": [ 5, 3, 8 ], + "to": [ 11, 16, 8 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "north": { "uv": [ 5, 0, 11, 13 ], "texture": "cross" }, + "south": { "uv": [ 5, 0, 11, 13 ], "texture": "cross" } + } + }, + { + "from": [ 8, 3, 5 ], + "to": [ 8, 16, 11 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "west": { "uv": [ 5, 0, 11, 13 ], "texture": "cross" }, + "east": { "uv": [ 5, 0, 11, 13 ], "texture": "cross" } + } + } + ] +} diff --git a/src/main/resources/assets/galacticraft/models/block/web_string_middle.json b/src/main/resources/assets/galacticraft/models/block/web_string_middle.json new file mode 100644 index 000000000..7677dfc5c --- /dev/null +++ b/src/main/resources/assets/galacticraft/models/block/web_string_middle.json @@ -0,0 +1,29 @@ +{ + "ambientocclusion": false, + "textures": { + "cross": "galacticraft:block/web_string_middle", + "particle": "galacticraft:block/web_string_middle" + }, + "elements": [ + { + "from": [ 5, 0, 8 ], + "to": [ 11, 16, 8 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "north": { "uv": [ 5, 0, 11, 16 ], "texture": "cross" }, + "south": { "uv": [ 5, 0, 11, 16 ], "texture": "cross" } + } + }, + { + "from": [ 8, 0, 5 ], + "to": [ 8, 16, 11 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "west": { "uv": [ 5, 0, 11, 16 ], "texture": "cross" }, + "east": { "uv": [ 5, 0, 11, 16 ], "texture": "cross" } + } + } + ] +} diff --git a/src/main/resources/assets/galacticraft/models/block/web_string_top.json b/src/main/resources/assets/galacticraft/models/block/web_string_top.json new file mode 100644 index 000000000..82d1d5b18 --- /dev/null +++ b/src/main/resources/assets/galacticraft/models/block/web_string_top.json @@ -0,0 +1,49 @@ +{ + "ambientocclusion": false, + "textures": { + "cross": "galacticraft:block/web_string_top", + "particle": "galacticraft:block/web_string_top" + }, + "elements": [ + { + "from": [ 3, 14, 8 ], + "to": [ 13, 16, 8 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "north": { "uv": [ 3, 0, 13, 2 ], "texture": "cross" }, + "south": { "uv": [ 3, 0, 13, 2 ], "texture": "cross" } + } + }, + { + "from": [ 8, 14, 3 ], + "to": [ 8, 16, 13 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "west": { "uv": [ 3, 0, 13, 2 ], "texture": "cross" }, + "east": { "uv": [ 3, 0, 13, 2 ], "texture": "cross" } + } + }, + { + "from": [ 5, 0, 8 ], + "to": [ 11, 14, 8 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "north": { "uv": [ 5, 2, 11, 16 ], "texture": "cross" }, + "south": { "uv": [ 5, 2, 11, 16 ], "texture": "cross" } + } + }, + { + "from": [ 8, 0, 5 ], + "to": [ 8, 14, 11 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "west": { "uv": [ 5, 2, 11, 16 ], "texture": "cross" }, + "east": { "uv": [ 5, 2, 11, 16 ], "texture": "cross" } + } + } + ] +} diff --git a/src/main/resources/assets/galacticraft/models/block/web_string_top_bottom.json b/src/main/resources/assets/galacticraft/models/block/web_string_top_bottom.json new file mode 100644 index 000000000..0659d61f6 --- /dev/null +++ b/src/main/resources/assets/galacticraft/models/block/web_string_top_bottom.json @@ -0,0 +1,49 @@ +{ + "ambientocclusion": false, + "textures": { + "cross": "galacticraft:block/web_string_top_bottom", + "particle": "galacticraft:block/web_string_top_bottom" + }, + "elements": [ + { + "from": [ 3, 14, 8 ], + "to": [ 13, 16, 8 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "north": { "uv": [ 3, 0, 13, 2 ], "texture": "cross" }, + "south": { "uv": [ 3, 0, 13, 2 ], "texture": "cross" } + } + }, + { + "from": [ 8, 14, 3 ], + "to": [ 8, 16, 13 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "west": { "uv": [ 3, 0, 13, 2 ], "texture": "cross" }, + "east": { "uv": [ 3, 0, 13, 2 ], "texture": "cross" } + } + }, + { + "from": [ 5, 3, 8 ], + "to": [ 11, 14, 8 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "north": { "uv": [ 5, 2, 11, 13 ], "texture": "cross" }, + "south": { "uv": [ 5, 2, 11, 13 ], "texture": "cross" } + } + }, + { + "from": [ 8, 3, 5 ], + "to": [ 8, 14, 11 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "west": { "uv": [ 5, 2, 11, 13 ], "texture": "cross" }, + "east": { "uv": [ 5, 2, 11, 13 ], "texture": "cross" } + } + } + ] +} diff --git a/src/main/resources/assets/galacticraft/models/block/web_torch.json b/src/main/resources/assets/galacticraft/models/block/web_torch.json new file mode 100644 index 000000000..99d83741d --- /dev/null +++ b/src/main/resources/assets/galacticraft/models/block/web_torch.json @@ -0,0 +1,29 @@ +{ + "ambientocclusion": false, + "textures": { + "cross": "galacticraft:block/web_torch", + "particle": "galacticraft:block/web_torch" + }, + "elements": [ + { + "from": [ 5, 3, 8 ], + "to": [ 11, 16, 8 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "north": { "uv": [ 5, 0, 11, 13 ], "texture": "cross" }, + "south": { "uv": [ 5, 0, 11, 13 ], "texture": "cross" } + } + }, + { + "from": [ 8, 3, 5 ], + "to": [ 8, 16, 11 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "west": { "uv": [ 5, 0, 11, 13 ], "texture": "cross" }, + "east": { "uv": [ 5, 0, 11, 13 ], "texture": "cross" } + } + } + ] +} diff --git a/src/main/resources/assets/galacticraft/models/block/web_torch_top.json b/src/main/resources/assets/galacticraft/models/block/web_torch_top.json new file mode 100644 index 000000000..1cb8b270a --- /dev/null +++ b/src/main/resources/assets/galacticraft/models/block/web_torch_top.json @@ -0,0 +1,49 @@ +{ + "ambientocclusion": false, + "textures": { + "cross": "galacticraft:block/web_torch_top", + "particle": "galacticraft:block/web_torch_top" + }, + "elements": [ + { + "from": [ 3, 14, 8 ], + "to": [ 13, 16, 8 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "north": { "uv": [ 3, 0, 13, 2 ], "texture": "cross" }, + "south": { "uv": [ 3, 0, 13, 2 ], "texture": "cross" } + } + }, + { + "from": [ 8, 14, 3 ], + "to": [ 8, 16, 13 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "west": { "uv": [ 3, 0, 13, 2 ], "texture": "cross" }, + "east": { "uv": [ 3, 0, 13, 2 ], "texture": "cross" } + } + }, + { + "from": [ 5, 3, 8 ], + "to": [ 11, 14, 8 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "north": { "uv": [ 5, 2, 11, 13 ], "texture": "cross" }, + "south": { "uv": [ 5, 2, 11, 13 ], "texture": "cross" } + } + }, + { + "from": [ 8, 3, 5 ], + "to": [ 8, 14, 11 ], + "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": false }, + "shade": false, + "faces": { + "west": { "uv": [ 5, 2, 11, 13 ], "texture": "cross" }, + "east": { "uv": [ 5, 2, 11, 13 ], "texture": "cross" } + } + } + ] +} diff --git a/src/main/resources/assets/galacticraft/textures/block/web_string_bottom.png b/src/main/resources/assets/galacticraft/textures/block/web_string_bottom.png new file mode 100644 index 000000000..765d682d3 Binary files /dev/null and b/src/main/resources/assets/galacticraft/textures/block/web_string_bottom.png differ diff --git a/src/main/resources/assets/galacticraft/textures/block/web_string_middle.png b/src/main/resources/assets/galacticraft/textures/block/web_string_middle.png new file mode 100644 index 000000000..7cee28e5e Binary files /dev/null and b/src/main/resources/assets/galacticraft/textures/block/web_string_middle.png differ diff --git a/src/main/resources/assets/galacticraft/textures/block/web_string_top.png b/src/main/resources/assets/galacticraft/textures/block/web_string_top.png new file mode 100644 index 000000000..2b9193804 Binary files /dev/null and b/src/main/resources/assets/galacticraft/textures/block/web_string_top.png differ diff --git a/src/main/resources/assets/galacticraft/textures/block/web_string_top_bottom.png b/src/main/resources/assets/galacticraft/textures/block/web_string_top_bottom.png new file mode 100644 index 000000000..cfaffe32b Binary files /dev/null and b/src/main/resources/assets/galacticraft/textures/block/web_string_top_bottom.png differ diff --git a/src/main/resources/assets/galacticraft/textures/block/web_torch.png b/src/main/resources/assets/galacticraft/textures/block/web_torch.png new file mode 100644 index 000000000..d9a0e8b8b Binary files /dev/null and b/src/main/resources/assets/galacticraft/textures/block/web_torch.png differ diff --git a/src/main/resources/assets/galacticraft/textures/block/web_torch_top.png b/src/main/resources/assets/galacticraft/textures/block/web_torch_top.png new file mode 100644 index 000000000..464598a64 Binary files /dev/null and b/src/main/resources/assets/galacticraft/textures/block/web_torch_top.png differ