Inspired by 2020's April Fools' 20w14infinite Snapshot, this mod brings endless randomly generated dimensions into Minecraft.
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}