Inspired by 2020's April Fools' 20w14infinite Snapshot, this mod brings endless randomly generated dimensions into Minecraft.
at master 330 lines 16 kB view raw
1package net.lerariemann.infinity.item; 2 3import net.lerariemann.infinity.block.entity.InfinityPortalBlockEntity; 4import net.lerariemann.infinity.registry.core.ModBlocks; 5import net.lerariemann.infinity.registry.core.ModComponentTypes; 6import net.lerariemann.infinity.util.VersionMethods; 7import net.lerariemann.infinity.util.ClientMethods; 8import net.lerariemann.infinity.util.InfinityMethods; 9import net.lerariemann.infinity.util.teleport.InfinityPortal; 10import net.minecraft.BlockUtil; 11import net.minecraft.ChatFormatting; 12import net.minecraft.core.BlockPos; 13import net.minecraft.core.Direction; 14import net.minecraft.network.chat.Component; 15import net.minecraft.network.chat.MutableComponent; 16import net.minecraft.resources.ResourceLocation; 17import net.minecraft.sounds.SoundEvents; 18import net.minecraft.sounds.SoundSource; 19import net.minecraft.world.InteractionHand; 20import net.minecraft.world.InteractionResult; 21//? if >1.21.4 { 22import net.minecraft.world.item.component.TooltipDisplay; 23import java.util.function.Consumer; 24//?} else { 25/*import net.minecraft.world.InteractionResultHolder; 26*///?} 27import net.minecraft.world.entity.player.Player; 28import net.minecraft.world.item.Item; 29import net.minecraft.world.item.ItemStack; 30import net.minecraft.world.item.TooltipFlag; 31import net.minecraft.world.item.context.UseOnContext; 32import net.minecraft.world.level.Level; 33import net.minecraft.world.level.block.Blocks; 34import net.minecraft.world.level.block.NetherPortalBlock; 35import net.minecraft.world.level.block.state.BlockState; 36import net.minecraft.world.level.block.state.properties.BlockStateProperties; 37import org.jspecify.annotations.Nullable; 38import java.util.*; 39import java.util.function.Consumer; 40 41public class F4Item extends Item implements PortalDataHolder.Destinable { 42 static final BlockState OBSIDIAN = Blocks.OBSIDIAN.defaultBlockState(); 43 44 public F4Item(Properties settings) { 45 super(settings); 46 } 47 48 public static MutableComponent getDimensionTooltip(@Nullable ResourceLocation dimension) { 49 if (dimension == null) return Component.translatable(Blocks.NETHER_PORTAL.getDescriptionId()); 50 String name = dimension.toString(); 51 MutableComponent text = InfinityMethods.getDimensionNameAsText(dimension); 52 if (name.contains("infinity:generated_") || name.equals(InfinityMethods.ofRandomDim)) 53 return text; 54 return Component.translatable("tooltip.infinity.f4", text); 55 } 56 57 public static int getCharge(ItemStack f4) { 58 return VersionMethods.getOrDefaultInt(f4, ModComponentTypes.CHARGE, 0); 59 } 60 61 @Override 62 public ItemStack getStack() { 63 return getDefaultInstance(); 64 } 65 66 @Override 67 public void appendHoverText(ItemStack stack, 68 //? if >1.21 { 69 TooltipContext 70 //?} else { 71 /*Level 72 *///?} 73 context, 74 //? if >1.21.2 { 75 TooltipDisplay tooltipDisplay, Consumer<Component> tooltipAdder 76 //?} else { 77 /*List<Component> tooltip 78 *///?} 79 , TooltipFlag type) { 80 //? if >1.21.2 { 81 super.appendHoverText(stack, context, tooltipDisplay, tooltipAdder, type); 82 //?} else { 83 /*super.appendHoverText(stack, context, tooltip, type); 84 Consumer<Component> tooltipAdder = tooltip::add; 85 *///?} 86 ResourceLocation dimension = VersionMethods.getDimensionIdentifier(stack); 87 MutableComponent mutableText = getDimensionTooltip(dimension).withStyle(ChatFormatting.GRAY); 88 tooltipAdder.accept(mutableText); 89 MutableComponent mutableText2 = Component.translatable("tooltip.infinity.f4.charges", getCharge(stack)).withStyle(ChatFormatting.GRAY); 90 tooltipAdder.accept(mutableText2); 91 } 92 93 //? if >1.21 { 94 @Nullable 95 //?} 96 public static ItemStack placePortal(Level world, Player player, ItemStack stack, BlockPos lowerCenter, 97 int size_x, int size_y) { 98 Direction.Axis dir2 = player.getDirection().getClockWise(Direction.Axis.Y).getAxis(); 99 100 if (!VersionMethods.allowNether(world.getServer())) { 101 return null; 102 } 103 104 int charges = getCharge(stack); 105 int useCharges = player.isCreative() ? 0 : 2*(2 + size_x + size_y); 106 if (charges < useCharges) { 107 if (!world.isClientSide()) { 108 //? if >1.21.4 { 109 player.displayClientMessage(Component.translatable("error.infinity.f4.no_charges", useCharges), false); 110 //?} else { 111 /*player.sendSystemMessage(Component.translatable("error.infinity.f4.no_charges", useCharges)); 112 *///?} 113 } 114 return null; 115 } 116 BlockPos lowerLeft = lowerCenter.relative(dir2, -(size_x/2)); 117 ResourceLocation id = VersionMethods.getDimensionIdentifier(stack); 118 boolean doNotRenderPortal = (world.isClientSide() && (id == null || !id.getPath().contains("generated_"))); 119 if (PortalDataHolder.isDestinationRandom(id)) 120 id = InfinityMethods.getRandomId(world.random); 121 int obsNotReplaced = 0; 122 123 //placing the portal 124 for (int x = -1; x <= size_x; x++) { 125 if (!world.setBlockAndUpdate(lowerLeft.relative(dir2, x).above(size_y), OBSIDIAN)) obsNotReplaced++; 126 if (!world.setBlockAndUpdate(lowerLeft.relative(dir2, x).below(), OBSIDIAN)) obsNotReplaced++; 127 } 128 for (int y = 0; y < size_y; y++) { 129 if (!world.setBlockAndUpdate(lowerLeft.relative(dir2, -1).relative(Direction.UP, y), OBSIDIAN)) obsNotReplaced++; 130 if (!world.setBlockAndUpdate(lowerLeft.relative(dir2, size_x).relative(Direction.UP, y), OBSIDIAN)) obsNotReplaced++; 131 if (!doNotRenderPortal) for (int x = 0; x < size_x; x++) { 132 BlockPos pos = lowerLeft.relative(dir2, x).relative(Direction.UP, y); 133 world.setBlockAndUpdate(pos, 134 ((id == null) ? Blocks.NETHER_PORTAL : ModBlocks.PORTAL.get()) 135 .defaultBlockState().setValue(NetherPortalBlock.AXIS, dir2)); 136 if (id != null && world.getBlockEntity(pos) instanceof InfinityPortalBlockEntity ipbe) { 137 ipbe.setData(world.getServer(), id); 138 } 139 } 140 } 141 useCharges -= obsNotReplaced; 142 143 world.playSound(player, player.blockPosition(), SoundEvents.BELL_BLOCK, SoundSource.BLOCKS, 1, 0.75f); 144 return VersionMethods.apply(stack, ModComponentTypes.CHARGE, charges-useCharges); 145 146 } 147 148 @Override 149 public 150 //? if <1.21.5 { 151 /*InteractionResultHolder<ItemStack> 152 *///?} else { 153 InteractionResult 154 //?} 155 use(Level world, Player player, InteractionHand hand) { 156 if (world.isClientSide()) 157 ClientMethods.setF4Screen(player); 158 return VersionMethods.success(player.getItemInHand(hand)); 159 } 160 161 public static 162 //? if <1.21.5 { 163 /*InteractionResultHolder<ItemStack> 164 *///?} else { 165 InteractionResult 166 //?} 167 deploy(Level world, Player player, InteractionHand hand) { 168 Direction dir = player.getDirection(); 169 Direction.Axis dir2 = dir.getClockWise(Direction.Axis.Y).getAxis(); 170 BlockPos lowerCenter = player.blockPosition().relative(dir, 4); 171 ItemStack stack = player.getItemInHand(hand); 172 173 int size_x = VersionMethods.getOrDefaultInt(stack, ModComponentTypes.SIZE_X, 3); 174 int size_y = VersionMethods.getOrDefaultInt(stack, ModComponentTypes.SIZE_Y, 3); 175 if (size_y % 2 == 0) { 176 double d = dir2.equals(Direction.Axis.X) ? player.position().x : player.position().z; 177 if (d % 1 > 0.5) { //player on the positive side of the block 178 lowerCenter = lowerCenter.relative(dir2, 1); 179 } 180 } 181 182 int lowerY = lowerCenter.getY(); 183 if (world.isOutsideBuildHeight(lowerY)) return VersionMethods.pass(stack); 184 185 //finding a place position 186 int i; 187 boolean positionFound = true; 188 for (i = 0; i <= 8 && !world.isOutsideBuildHeight(lowerY + i + size_y); i++) { 189 positionFound = true; 190 for (int j = 0; j <= size_y+1 && positionFound; j++) for (int k = -1; k <= size_x; k++) { 191 BlockState bs = world.getBlockState(lowerCenter.above(i+j-1).relative(dir2, k - (size_x /2))); 192 if (bs.canBeReplaced()) continue; 193 if (bs.is(Blocks.OBSIDIAN)) { 194 if (j == 0 || j == size_y+1 || k == -1 || k == size_x) continue; 195 i += j-1; 196 } 197 else i += j; 198 positionFound = false; 199 break; 200 } 201 if (positionFound) break; 202 } 203 if (!positionFound) return VersionMethods.pass(stack); 204 205 ItemStack newStack = placePortal(world, player, stack.copy(), lowerCenter.above(i), size_x, size_y); 206 if (newStack == null) return VersionMethods.pass(stack); 207 return VersionMethods.consume(player.isCreative() ? stack : newStack); 208 } 209 210 public static boolean isPortal(BlockState state) { 211 return state.is(Blocks.NETHER_PORTAL) || state.is(ModBlocks.PORTAL.get()); 212 } 213 214 @Override 215 public InteractionResult useOn(UseOnContext context) { 216 Player player = context.getPlayer(); 217 if (player == null) return InteractionResult.FAIL; 218 ItemStack stack = context.getItemInHand(); 219 Level world = context.getLevel(); 220 BlockPos pos = context.getClickedPos(); 221 BlockState bs = world.getBlockState(pos); 222 if (bs.canBeReplaced()) { 223 pos = pos.below(); 224 bs = world.getBlockState(pos); 225 } 226 InteractionHand hand = context.getHand(); 227 228 if (isPortal(bs)) { 229 ItemStack newStack = useOnPortalBlock(world, pos, stack.copy()); 230 if (!player.isCreative()) player.setItemInHand(hand, newStack); 231 return InteractionResult.CONSUME; 232 } 233 pos = pos.above(bs.is(Blocks.OBSIDIAN) ? 1 : 2); 234 235 Direction.Axis dir2 = 236 player.getDirection().getAxis().equals(Direction.Axis.X) ? Direction.Axis.Z : Direction.Axis.X; 237 238 int size_x = VersionMethods.getOrDefaultInt(stack, ModComponentTypes.SIZE_X, 3); 239 int size_y = VersionMethods.getOrDefaultInt(stack, ModComponentTypes.SIZE_Y, 3); 240 241 //validating the place position 242 for (int j = -1; j <= size_y; j++) for (int k = -1; k <= size_x; k++) { 243 bs = world.getBlockState(pos.above(j).relative(dir2, k - (size_x /2))); 244 if (bs.is(Blocks.OBSIDIAN) && (j == -1 || j == size_y || k == -1 || k == size_x)) continue; 245 if (!bs.canBeReplaced()) return InteractionResult.FAIL; 246 } 247 248 ItemStack newStack = placePortal(world, player, context.getItemInHand().copy(), pos, 249 size_x, size_y); 250 if (newStack == null) { 251 return InteractionResult.FAIL; 252 } 253 if (!player.isCreative()) player.setItemInHand(hand, newStack); 254 return InteractionResult.CONSUME; 255 } 256 257 public static boolean checkIfValidAxis(Direction.Axis axisFound, Direction.Axis axisBeingChecked, Direction.@Nullable Axis forceAxis) { 258 if (forceAxis == null) { 259 return axisBeingChecked.isVertical() || axisFound.equals(axisBeingChecked); 260 } 261 return axisFound.equals(forceAxis); 262 } 263 264 public static boolean checkNeighbors(Level world, BlockPos bp, 265 Collection<Direction> primaryOffsets, //possible directions in which blocks of other portals may be 266 Collection<Direction> secondaryOffsets, //possible secondary directions from a neighboring obsidian in case we found a corner 267 int max, Direction.@Nullable Axis forcePrimaryAxis) { 268 int i = 0; 269 boolean checkCorners = !secondaryOffsets.isEmpty(); 270 for (Direction dir : primaryOffsets) { 271 BlockState bs = world.getBlockState(bp.relative(dir)); 272 Direction.Axis axis = dir.getAxis(); 273 if (isPortal(bs)) { 274 Direction.Axis axisFound = bs.getValue(BlockStateProperties.HORIZONTAL_AXIS); 275 if (checkIfValidAxis(axisFound, axis, forcePrimaryAxis)) 276 if (++i > max) return true; 277 } 278 if (checkCorners && bs.is(Blocks.OBSIDIAN)) { 279 Direction.Axis forceAxis = axis.isHorizontal() ? axis : null; 280 return checkNeighbors(world, bp.relative(dir), secondaryOffsets, Set.of(), 0, forceAxis); 281 } 282 } 283 return false; 284 } 285 286 public static void checkObsidianRemovalSides(Level world, BlockPos bp, 287 Set<BlockPos> toRemove, 288 Set<BlockPos> toLeave, 289 Direction direction) { 290 if (world.getBlockState(bp).is(Blocks.OBSIDIAN)) { 291 boolean bl = direction.getAxis().isVertical(); 292 Set<Direction> primaryOffsets = bl ? Set.of(direction) : Set.of(direction, 293 direction.getClockWise(Direction.Axis.Y), 294 direction.getCounterClockWise(Direction.Axis.Y)); 295 Set<Direction> secondaryOffsets = bl ? Set.of(Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST) : 296 Set.of(Direction.UP, Direction.DOWN); 297 if (checkNeighbors(world, bp, primaryOffsets, secondaryOffsets, 0, null)) { 298 toLeave.add(bp); 299 return; 300 } 301 toRemove.add(bp); 302 } 303 } 304 305 public static ItemStack useOnPortalBlock(Level world, BlockPos origin, ItemStack stack) { 306 Direction.Axis axis = world.getBlockState(origin).getValue(NetherPortalBlock.AXIS); 307 Direction positive = axis.equals(Direction.Axis.X) ? Direction.EAST : Direction.SOUTH; 308 BlockUtil.FoundRectangle portal = InfinityPortal.getRect(world, origin); 309 Set<BlockPos> toRemove = new HashSet<>(); 310 Set<BlockPos> toLeave = new HashSet<>(); 311 for (int i = -1; i <= portal.axis1Size; i++) { 312 checkObsidianRemovalSides(world, portal.minCorner.relative(axis, i).above(-1), toRemove, toLeave, Direction.DOWN); 313 checkObsidianRemovalSides(world, portal.minCorner.relative(axis, i).above(portal.axis2Size), toRemove, toLeave, Direction.UP); 314 } 315 for (int j = -1; j <= portal.axis2Size; j++) { 316 checkObsidianRemovalSides(world, portal.minCorner.relative(axis, -1).above(j), toRemove, toLeave, positive.getOpposite()); 317 checkObsidianRemovalSides(world, portal.minCorner.relative(axis, portal.axis1Size).above(j), toRemove, toLeave, positive); 318 } 319 int obsidian = 0; 320 for (BlockPos bp : toRemove) if (!toLeave.contains(bp)) { //double check since we're checking the corners twice 321 world.setBlock(bp, Blocks.AIR.defaultBlockState(), 3, 0); 322 obsidian++; 323 } 324 for (int i = 0; i < portal.axis1Size; i++) for (int j = 0; j < portal.axis2Size; j++) 325 world.setBlock(portal.minCorner.relative(axis, i).above(j), Blocks.AIR.defaultBlockState(), 3, 0); 326 world.playSound(null, origin, SoundEvents.GLASS_BREAK, SoundSource.BLOCKS, 1, 0.75f); 327 return VersionMethods.apply(stack, ModComponentTypes.CHARGE, getCharge(stack)+obsidian); 328 329 } 330}