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