Inspired by 2020's April Fools' 20w14infinite Snapshot, this mod brings endless randomly generated dimensions into Minecraft.

debug errors & better warp

Lera 27141fea d039d12f

+261 -167
+27
common/src/main/java/net/lerariemann/infinity/access/Timebombable.java
··· 1 1 package net.lerariemann.infinity.access; 2 2 3 + import net.lerariemann.infinity.util.InfinityMethods; 4 + import net.lerariemann.infinity.util.WarpLogic; 5 + import net.lerariemann.infinity.var.ModCriteria; 6 + import net.minecraft.entity.damage.DamageSource; 7 + import net.minecraft.entity.damage.DamageType; 8 + import net.minecraft.registry.Registry; 9 + import net.minecraft.registry.RegistryKeys; 10 + import net.minecraft.registry.entry.RegistryEntry; 11 + import net.minecraft.server.network.ServerPlayerEntity; 12 + 3 13 public interface Timebombable { 4 14 int cooldownTicks = 6000; 5 15 ··· 8 18 9 19 boolean infinity$isTimebombed(); 10 20 int infinity$getTimebombProgress(); 21 + 22 + default void tickTimebombProgress(ServerPlayerEntity player) { 23 + int i = infinity$getTimebombProgress(); 24 + if (i > 3540) { 25 + WarpLogic.respawnAlive(player); 26 + } 27 + else if (i > 3500) { 28 + ModCriteria.WHO_REMAINS.get().trigger(player); 29 + } 30 + else if (i > 200) { 31 + if (i%4 == 0) { 32 + Registry<DamageType> r = player.getServerWorld().getServer().getRegistryManager().get(RegistryKeys.DAMAGE_TYPE); 33 + RegistryEntry<DamageType> entry = r.getEntry(r.get(InfinityMethods.getId("world_ceased"))); 34 + player.damage(new DamageSource(entry), i > 400 ? 2.0f : 1.0f); 35 + } 36 + } 37 + } 11 38 }
+50 -30
common/src/main/java/net/lerariemann/infinity/block/custom/InfinityPortalBlock.java
··· 13 13 import net.lerariemann.infinity.entity.ModEntities; 14 14 import net.lerariemann.infinity.entity.custom.ChaosPawn; 15 15 import net.lerariemann.infinity.item.ModItems; 16 - import net.lerariemann.infinity.util.WarpLogic; 17 16 import net.lerariemann.infinity.var.ModPoi; 18 17 import net.minecraft.block.*; 19 18 import net.minecraft.block.entity.BlockEntity; ··· 27 26 import net.minecraft.particle.DustParticleEffect; 28 27 import net.minecraft.particle.ParticleEffect; 29 28 import net.minecraft.particle.ParticleTypes; 30 - import net.minecraft.registry.Registries; 31 29 import net.minecraft.registry.RegistryKey; 32 30 import net.minecraft.registry.RegistryKeys; 33 31 import net.minecraft.server.MinecraftServer; 32 + import net.minecraft.server.network.ServerPlayerEntity; 34 33 import net.minecraft.server.world.ServerWorld; 35 34 import net.minecraft.sound.SoundCategory; 36 35 import net.minecraft.sound.SoundEvents; 37 36 import net.minecraft.state.StateManager; 38 37 import net.minecraft.state.property.BooleanProperty; 39 38 import net.minecraft.state.property.Properties; 40 - import net.minecraft.util.ActionResult; 41 - import net.minecraft.util.Hand; 42 - import net.minecraft.util.Identifier; 43 - import net.minecraft.util.WorldSavePath; 39 + import net.minecraft.text.MutableText; 40 + import net.minecraft.text.Text; 41 + import net.minecraft.util.*; 44 42 import net.minecraft.util.hit.BlockHitResult; 45 43 import net.minecraft.util.math.BlockPos; 46 44 import net.minecraft.util.math.Direction; ··· 51 49 import net.minecraft.world.poi.PointOfInterest; 52 50 import net.minecraft.world.poi.PointOfInterestStorage; 53 51 import net.minecraft.world.poi.PointOfInterestTypes; 54 - import org.apache.logging.log4j.LogManager; 55 52 import org.jetbrains.annotations.Nullable; 56 53 import org.joml.Vector3f; 57 54 ··· 89 86 BlockEntity blockEntity = world.getBlockEntity(pos); 90 87 if (blockEntity instanceof InfinityPortalBlockEntity npbe) { 91 88 /* If the portal is open already, nothing should happen. */ 92 - if (npbe.getOpen() && world_exists(s, npbe.getDimension())) 89 + if (npbe.isOpen() && world_exists(s, npbe.getDimension())) 93 90 return ActionResult.SUCCESS; 94 91 95 92 /* If the portal key is blank, open the portal on any right-click. */ 96 93 RandomProvider prov = InfinityMod.provider; 97 - if (prov.portalKey.isBlank()) { 94 + Optional<Item> key = prov.getPortalKeyAsItem(); 95 + if (key.isEmpty()) { 98 96 PortalCreationLogic.openWithStatIncrease(player, s, world, pos); 99 97 } 100 98 101 99 /* Otherwise check if we're using the correct key. If so, open. */ 102 100 else { 103 101 ItemStack usedKey = player.getStackInHand(Hand.MAIN_HAND); 104 - Item correctKey = Registries.ITEM.get(Identifier.of(prov.portalKey)); 105 - if (usedKey.isOf(correctKey)) { 102 + if (usedKey.isOf(key.get())) { 106 103 if (!player.getAbilities().creativeMode && prov.rule("consumePortalKey")) { 107 104 usedKey.decrement(1); // Consume the key if needed 108 105 } ··· 174 171 } 175 172 176 173 public static ComponentMap getKeyComponents(Identifier dim) { 177 - Integer keycolor = WarpLogic.getKeyColorFromId(dim); 174 + Integer keycolor = InfinityMethods.getKeyColorFromId(dim); 178 175 return (ComponentMap.builder() 179 176 .add(ModItemFunctions.KEY_DESTINATION.get(), dim) 180 177 .add(ModItemFunctions.COLOR.get(), keycolor)).build(); ··· 192 189 ModItemFunctions.checkCollisionRecipes(world, e, ModItemFunctions.PORTAL_CRAFTING_TYPE.get(), 193 190 item -> getKeyComponents(item, npbe.getDimension())); 194 191 if (entity instanceof PlayerEntity player 195 - && InfinityMod.provider.portalKey.isBlank()) { 192 + && InfinityMod.provider.isPortalKeyBlank()) { 196 193 ServerWorld world1 = server.getWorld(RegistryKey.of(RegistryKeys.WORLD, npbe.getDimension())); 197 - if ((world1 == null) || !npbe.getOpen()) 194 + if ((world1 == null) || !npbe.isOpen()) 198 195 PortalCreationLogic.openWithStatIncrease(player, server, world, pos); 199 196 else { 200 197 Timebombable tw = (Timebombable)world1; ··· 238 235 */ 239 236 @Nullable @Override 240 237 public TeleportTarget createTeleportTarget(ServerWorld worldFrom, Entity entity, BlockPos posFrom) { 241 - try { 242 - if (worldFrom.getBlockEntity(posFrom) instanceof InfinityPortalBlockEntity ipbe) { 243 - Identifier id = ipbe.getDimension(); 244 - RegistryKey<World> keyTo = RegistryKey.of(RegistryKeys.WORLD, id); 245 - ServerWorld worldTo = worldFrom.getServer().getWorld(keyTo); 238 + if (worldFrom.getBlockEntity(posFrom) instanceof InfinityPortalBlockEntity ipbe) { 239 + Identifier id = ipbe.getDimension(); 240 + RegistryKey<World> keyTo = RegistryKey.of(RegistryKeys.WORLD, id); 241 + ServerWorld worldTo = worldFrom.getServer().getWorld(keyTo); 246 242 247 - if (WarpLogic.dimExists(worldTo) && ipbe.getOpen()) { 248 - BlockPos targetPos = ipbe.getOtherSidePos(); 249 - if (isValidDestinationStrong(worldFrom, worldTo, targetPos)) 250 - return getExistingTarget(worldTo, targetPos, entity); 251 - return findNewTeleportTarget(worldFrom, posFrom, worldTo, entity); 252 - } 243 + if (InfinityMethods.dimExists(worldTo) && ipbe.isOpen()) { 244 + BlockPos targetPos = ipbe.getOtherSidePos(); 245 + if (isValidDestinationStrong(worldFrom, worldTo, targetPos)) 246 + return getExistingTarget(worldTo, targetPos, entity); 247 + return findNewTeleportTarget(worldFrom, posFrom, worldTo, entity); 248 + } 249 + 250 + //below this point is error handling. note that not all such errors are bugs 251 + if (worldTo == null && ipbe.isOpen()) { //an open portal with no destination? close that 252 + PortalCreationLogic.modifyPortalRecursive(worldFrom, posFrom, 253 + new PortalCreationLogic.PortalModifier(e -> e.setOpen(false))); 254 + } 255 + if (entity instanceof ServerPlayerEntity player) { 256 + sendErrors(player, worldTo, ipbe); 253 257 } 254 258 } 255 - catch (Exception ignored) {} 259 + else if (entity instanceof ServerPlayerEntity player) 260 + InfinityMethods.sendUnexpectedError(player, "portal"); 256 261 return emptyTarget(entity); //if anything goes wrong, don't teleport anywhere 257 262 } 258 263 264 + static void sendErrors(ServerPlayerEntity player, ServerWorld worldTo, InfinityPortalBlockEntity ipbe) { 265 + if (worldTo != null && ((Timebombable)worldTo).infinity$isTimebombed()) 266 + player.sendMessage(Text.translatable("error.infinity.portal.deleted")); 267 + else if (!ipbe.isOpen()) 268 + InfinityMod.provider.getPortalKeyAsItem().ifPresent(item -> player.sendMessage( 269 + Text.translatable("error.infinity.portal.closed", 270 + ((MutableText)item.getName()).formatted(Formatting.AQUA)))); 271 + else if (worldTo == null) 272 + player.sendMessage(Text.translatable("error.infinity.portal.null")); 273 + else InfinityMethods.sendUnexpectedError(player, "portal"); 274 + } 275 + 259 276 static Direction.Axis getAxisOrDefault(BlockState state) { 260 277 if (state.getProperties().contains(Properties.HORIZONTAL_AXIS)) 261 278 return state.get(Properties.HORIZONTAL_AXIS); ··· 286 303 * Filter for portal blocks except infinity portals of other destinations. 287 304 */ 288 305 public static boolean isValidDestinationWeak(ServerWorld worldFrom, ServerWorld worldTo, BlockPos posTo) { 289 - if (posTo == null || !WarpLogic.dimExists(worldTo)) return false; 306 + if (posTo == null || !InfinityMethods.dimExists(worldTo)) return false; 290 307 return (worldTo.getBlockState(posTo).isOf(Blocks.NETHER_PORTAL)) 291 308 || (worldTo.getBlockEntity(posTo) instanceof InfinityPortalBlockEntity ipbe 292 309 && ipbe.getDimension().toString().equals(worldFrom.getRegistryKey().getValue().toString())); ··· 296 313 * Filter for infinity portals of the correct destination. 297 314 */ 298 315 public static boolean isValidDestinationStrong(ServerWorld worldFrom, ServerWorld worldTo, BlockPos posTo) { 299 - if (posTo == null || !WarpLogic.dimExists(worldTo)) return false; 316 + if (posTo == null || !InfinityMethods.dimExists(worldTo)) return false; 300 317 return (worldTo.getBlockEntity(posTo) instanceof InfinityPortalBlockEntity ipbe 301 318 && ipbe.getDimension().toString().equals(worldFrom.getRegistryKey().getValue().toString())); 302 319 } ··· 348 365 Direction.Axis axis = worldFrom.getBlockState(posFrom).getOrEmpty(AXIS).orElse(Direction.Axis.X); 349 366 Optional<BlockLocating.Rectangle> optional2 = worldTo.getPortalForcer().createPortal(originOfTesting, axis); 350 367 if (optional2.isEmpty()) { 351 - LogManager.getLogger().error("Unable to create a portal, likely target out of worldborder"); 368 + if (teleportingEntity instanceof ServerPlayerEntity player) { 369 + player.sendMessage(Text.translatable("error.infinity.portal.cannot_create")); 370 + } 352 371 return emptyTarget(teleportingEntity); 353 372 } 354 373 rectangleTo = optional2.get(); ··· 369 388 Identifier idFrom = worldFrom.getRegistryKey().getValue(); 370 389 371 390 if (worldTo.getBlockEntity(posTo) instanceof InfinityPortalBlockEntity ipbe) { 372 - if (ipbe.getDimension() != idFrom || isValidDestinationStrong(worldTo, worldFrom, ipbe.getOtherSidePos())) return; //don't resync what's already synced 391 + if (ipbe.getDimension() != idFrom) return; 392 + if (isValidDestinationStrong(worldTo, worldFrom, ipbe.getOtherSidePos())) return; //don't resync what's already synced 373 393 } 374 394 else { 375 395 otherSideModifier = PortalCreationLogic.forInitialSetupping(worldTo, posTo, idFrom, true); //make it an infinity portal while you're at it
+4 -4
common/src/main/java/net/lerariemann/infinity/block/entity/InfinityPortalBlockEntity.java
··· 1 1 package net.lerariemann.infinity.block.entity; 2 2 3 + import net.lerariemann.infinity.options.PortalColorApplier; 3 4 import net.lerariemann.infinity.util.InfinityMethods; 4 - import net.lerariemann.infinity.util.WarpLogic; 5 5 import net.minecraft.block.BlockState; 6 6 import net.minecraft.block.entity.BlockEntity; 7 7 import net.minecraft.nbt.NbtCompound; ··· 63 63 public int getPortalColor() { 64 64 return this.portalColor; 65 65 } 66 - public boolean getOpen() { 66 + public boolean isOpen() { 67 67 return this.isOpen; 68 68 } 69 69 @Nullable ··· 114 114 this.dimension = Identifier.of(tag.getString("Dimension")); 115 115 this.portalColor = tag.contains("Color", NbtElement.INT_TYPE) ? 116 116 tag.getInt("Color") : 117 - (world != null ? WarpLogic.getPortalColorApplier(dimension, world.getServer()) : 118 - WarpLogic.getPortalColorApplier(dimension, new NbtCompound())).apply(pos); 117 + (world != null ? PortalColorApplier.of(dimension, world.getServer()) : 118 + PortalColorApplier.of(dimension, new NbtCompound())).apply(pos); 119 119 } 120 120 else { 121 121 setDimension(InfinityMethods.getRandomSeed(new Random())); //random by default
+1 -2
common/src/main/java/net/lerariemann/infinity/dimensions/RandomDimension.java
··· 5 5 import net.lerariemann.infinity.util.CommonIO; 6 6 import net.lerariemann.infinity.util.InfinityMethods; 7 7 import net.lerariemann.infinity.util.RandomProvider; 8 - import net.lerariemann.infinity.util.WarpLogic; 9 8 import net.minecraft.nbt.*; 10 9 import net.minecraft.registry.Registry; 11 10 import net.minecraft.registry.RegistryKey; ··· 50 49 this.server = server; 51 50 PROVIDER = InfinityMod.provider; 52 51 identifier = id; 53 - numericId = WarpLogic.getNumericFromId(identifier); 52 + numericId = InfinityMethods.getNumericFromId(identifier); 54 53 random = new Random(numericId); 55 54 initializeStorage(); 56 55 /* Code for easter dimensions */
+1 -1
common/src/main/java/net/lerariemann/infinity/iridescence/IridescentEffect.java
··· 54 54 if (Iridescence.shouldWarp(duration, amplifier)) { 55 55 player.setInvulnerable(true); 56 56 Identifier id = Iridescence.getIdForWarp(player); 57 - WarpLogic.warpWithTimer(player, id, 10, false); 57 + WarpLogic.requestWarpWithTimer(player, id, 10, false); 58 58 } 59 59 if (Iridescence.shouldReturn(duration, amplifier)) { 60 60 player.setInvulnerable(false);
+9 -49
common/src/main/java/net/lerariemann/infinity/mixin/ServerPlayerEntityMixin.java
··· 9 9 import net.lerariemann.infinity.util.InfinityMethods; 10 10 import net.lerariemann.infinity.util.PortalCreationLogic; 11 11 import net.lerariemann.infinity.util.WarpLogic; 12 - import net.lerariemann.infinity.var.ModCriteria; 13 12 import net.lerariemann.infinity.var.ModPayloads; 14 - import net.minecraft.block.BlockState; 15 13 import net.minecraft.block.Blocks; 16 14 import net.minecraft.entity.Entity; 17 15 import net.minecraft.entity.damage.DamageSource; 18 - import net.minecraft.entity.damage.DamageType; 19 - import net.minecraft.entity.effect.StatusEffectInstance; 20 16 import net.minecraft.entity.player.PlayerEntity; 21 17 import net.minecraft.network.packet.s2c.play.PositionFlag; 22 - import net.minecraft.registry.Registry; 23 18 import net.minecraft.registry.RegistryKey; 24 - import net.minecraft.registry.RegistryKeys; 25 - import net.minecraft.registry.entry.RegistryEntry; 26 - import net.minecraft.server.MinecraftServer; 27 19 import net.minecraft.server.network.ServerPlayerEntity; 28 20 import net.minecraft.server.world.ServerWorld; 29 21 import net.minecraft.util.Identifier; 30 22 import net.minecraft.util.math.BlockPos; 31 - import net.minecraft.util.math.MathHelper; 32 23 import net.minecraft.util.math.Vec3d; 33 24 import net.minecraft.world.GameMode; 34 25 import net.minecraft.world.TeleportTarget; 35 26 import net.minecraft.world.World; 36 - import net.minecraft.world.dimension.DimensionType; 37 27 import org.jetbrains.annotations.Nullable; 38 28 import org.spongepowered.asm.mixin.Mixin; 39 29 import org.spongepowered.asm.mixin.Shadow; ··· 43 33 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 44 34 import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 45 35 46 - import java.util.HashSet; 47 36 import java.util.Optional; 48 37 import java.util.Set; 49 38 ··· 56 45 @Shadow public abstract ServerWorld getServerWorld(); 57 46 58 47 @Shadow public abstract boolean teleport(ServerWorld world, double destX, double destY, double destZ, Set<PositionFlag> flags, float yaw, float pitch); 59 - 60 - @Shadow public abstract Entity getCameraEntity(); 61 48 62 49 @Shadow public abstract boolean damage(DamageSource source, float amount); 63 50 ··· 94 81 @Inject(method = "teleportTo", 95 82 at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;getPlayerManager()Lnet/minecraft/server/PlayerManager;")) 96 83 private void injected3(TeleportTarget teleportTarget, CallbackInfoReturnable<Entity> cir) { 97 - InfinityMethods.sendS2CPayload(((ServerPlayerEntity)(Object)this), ModPayloads.setShaderFromWorld(teleportTarget.world())); 98 - InfinityMethods.sendS2CPayload(((ServerPlayerEntity)(Object)this), ModPayloads.StarsRePayLoad.INSTANCE); 84 + ServerPlayerEntity player = (ServerPlayerEntity)(Object)this; 85 + InfinityMethods.sendS2CPayload(player, ModPayloads.setShaderFromWorld(teleportTarget.world())); 86 + InfinityMethods.sendS2CPayload(player, ModPayloads.StarsRePayLoad.INSTANCE); 99 87 } 100 88 101 89 @Inject(method = "tick", at = @At("TAIL")) 102 90 private void onTick(CallbackInfo ci) { 103 - /* Handle infinity options */ 104 - InfinityOptions opt = InfinityOptions.access(getWorld()); 105 - if (!opt.effect.isEmpty()) { 106 - if (getWorld().getTime() % opt.effect.cooldown() == 0) { 107 - addStatusEffect(new StatusEffectInstance(opt.effect.id(), opt.effect.duration(), opt.effect.amplifier())); 108 - } 109 - } 91 + ServerPlayerEntity player = (ServerPlayerEntity)(Object)this; 110 92 93 + /* Handle infinity options */ 94 + InfinityOptions.access(getWorld()).effect.tryGiveEffect(player); 111 95 /* Handle the warp command */ 112 - if (--this.infinity$ticksUntilWarp == 0L) { 113 - MinecraftServer s = this.getServerWorld().getServer(); 114 - ServerWorld w = s.getWorld(RegistryKey.of(RegistryKeys.WORLD, this.infinity$idForWarp)); 115 - if (w==null) return; 116 - double d = DimensionType.getCoordinateScaleFactor(this.getServerWorld().getDimension(), w.getDimension()); 117 - Entity self = getCameraEntity(); 118 - double y = MathHelper.clamp(self.getY(), w.getBottomY(), w.getTopY()); 119 - BlockPos blockPos2 = WarpLogic.getPosForWarp(w.getWorldBorder().clamp(self.getX() * d, y, self.getZ() * d), w); 120 - BlockState state = w.getBlockState(blockPos2.down()); 121 - if (state.isAir() || state.isOf(Blocks.LAVA)) w.setBlockState(blockPos2.down(), Blocks.OBSIDIAN.getDefaultState()); 122 - this.teleport(w, blockPos2.getX() + 0.5, blockPos2.getY(), blockPos2.getZ() + 0.5, new HashSet<>(), self.getYaw(), self.getPitch()); 123 - } 124 - 96 + if (--this.infinity$ticksUntilWarp == 0L) 97 + WarpLogic.performWarp(player, infinity$idForWarp); 125 98 /* Handle effects from dimension deletion */ 126 - int i = ((Timebombable)(getServerWorld())).infinity$getTimebombProgress(); 127 - if (i > 3540) { 128 - WarpLogic.respawnAlive((ServerPlayerEntity)(Object)this); 129 - } 130 - else if (i > 3500) { 131 - ModCriteria.WHO_REMAINS.get().trigger((ServerPlayerEntity)(Object)this); 132 - } 133 - else if (i > 200) { 134 - if (i%4 == 0) { 135 - Registry<DamageType> r = getServerWorld().getServer().getRegistryManager().get(RegistryKeys.DAMAGE_TYPE); 136 - RegistryEntry<DamageType> entry = r.getEntry(r.get(InfinityMethods.getId("world_ceased"))); 137 - damage(new DamageSource(entry), i > 400 ? 2.0f : 1.0f); 138 - } 139 - } 99 + ((Timebombable)getServerWorld()).tickTimebombProgress(player); 140 100 } 141 101 142 102 @Inject(method = "changeGameMode", at = @At("RETURN"))
+8
common/src/main/java/net/lerariemann/infinity/options/EffectGiver.java
··· 2 2 3 3 import net.minecraft.entity.effect.InstantStatusEffect; 4 4 import net.minecraft.entity.effect.StatusEffect; 5 + import net.minecraft.entity.effect.StatusEffectInstance; 5 6 import net.minecraft.nbt.NbtCompound; 6 7 import net.minecraft.registry.Registries; 7 8 import net.minecraft.registry.RegistryKey; 8 9 import net.minecraft.registry.RegistryKeys; 9 10 import net.minecraft.registry.entry.RegistryEntry; 11 + import net.minecraft.server.network.ServerPlayerEntity; 10 12 import net.minecraft.util.Identifier; 11 13 12 14 public record EffectGiver(RegistryEntry<StatusEffect> id, int duration, int amplifier, int cooldown) { ··· 22 24 InfinityOptions.test(data, "amplifier", 0), 23 25 Math.min(InfinityOptions.test(data, "cooldown", 100), 100)); 24 26 return new EffectGiver(null, 0,0,200); 27 + } 28 + 29 + public void tryGiveEffect(ServerPlayerEntity player) { 30 + if (!isEmpty() && player.getWorld().getTime() % cooldown == 0) { 31 + player.addStatusEffect(new StatusEffectInstance(id, duration, amplifier)); 32 + } 25 33 } 26 34 27 35 public boolean isEmpty() {
+14 -3
common/src/main/java/net/lerariemann/infinity/options/PortalColorApplier.java
··· 1 1 package net.lerariemann.infinity.options; 2 2 3 + import net.lerariemann.infinity.InfinityMod; 3 4 import net.lerariemann.infinity.util.InfinityMethods; 4 5 import net.minecraft.nbt.NbtCompound; 5 6 import net.minecraft.nbt.NbtElement; 6 7 import net.minecraft.nbt.NbtList; 8 + import net.minecraft.server.MinecraftServer; 9 + import net.minecraft.util.Identifier; 7 10 import net.minecraft.util.math.BlockPos; 8 11 9 12 import java.awt.Color; 10 13 import java.util.Random; 11 14 12 15 public interface PortalColorApplier { 13 - static PortalColorApplier extract(NbtCompound data, int def) { 14 - if (!data.contains("portal_color")) return new PortalColorApplier.Simple(def); 16 + static PortalColorApplier of(Identifier id, MinecraftServer server) { 17 + return of(id, InfinityOptions.readData(server, id)); 18 + } 19 + static PortalColorApplier of(Identifier id, NbtCompound defaultData) { 20 + NbtCompound data = InfinityMod.provider.easterizer.optionmap.get(id.getPath()); 21 + if (data == null) data = defaultData; 22 + return of(data, (int)InfinityMethods.getNumericFromId(id)); 23 + } 24 + static PortalColorApplier of(NbtCompound data, int defaultColor) { 25 + if (!data.contains("portal_color")) return new PortalColorApplier.Simple(defaultColor); 15 26 if (data.contains("portal_color", NbtElement.INT_TYPE)) return new PortalColorApplier.Simple(data.getInt("portal_color")); 16 27 NbtCompound applierData = data.getCompound("portal_color"); 17 28 return switch (applierData.getString("type")) { ··· 19 30 case "checker" -> new PortalColorApplier.Checker(applierData.getList("values", NbtElement.INT_TYPE)); 20 31 case "random_hue" -> new PortalColorApplier.RandomHue(applierData); 21 32 case "random" -> PortalColorApplier.RandomColor.INSTANCE; 22 - default -> new PortalColorApplier.Simple(def); 33 + default -> new PortalColorApplier.Simple(defaultColor); 23 34 }; 24 35 } 25 36
+56
common/src/main/java/net/lerariemann/infinity/util/InfinityMethods.java
··· 1 1 package net.lerariemann.infinity.util; 2 2 3 + import com.google.common.hash.HashCode; 4 + import com.google.common.hash.Hashing; 3 5 import dev.architectury.platform.Platform; 4 6 import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; 5 7 import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; 6 8 import net.lerariemann.infinity.InfinityMod; 9 + import net.lerariemann.infinity.access.Timebombable; 7 10 import net.lerariemann.infinity.block.entity.BiomeBottleBlockEntity; 8 11 import net.lerariemann.infinity.block.entity.InfinityPortalBlockEntity; 9 12 import net.lerariemann.infinity.item.function.ModItemFunctions; ··· 13 16 import net.minecraft.network.packet.CustomPayload; 14 17 import net.minecraft.registry.RegistryKey; 15 18 import net.minecraft.server.network.ServerPlayerEntity; 19 + import net.minecraft.server.world.ServerWorld; 20 + import net.minecraft.text.Text; 16 21 import net.minecraft.util.Identifier; 17 22 import net.minecraft.util.math.BlockPos; 18 23 import net.minecraft.util.math.ColorHelper; 19 24 import net.minecraft.util.math.random.Random; 20 25 import net.minecraft.world.BlockRenderView; 21 26 import net.minecraft.world.World; 27 + 28 + import java.nio.charset.StandardCharsets; 22 29 23 30 import static net.lerariemann.infinity.InfinityModClient.sampler; 24 31 ··· 79 86 return (res >= 0) ? res : b + res; 80 87 } 81 88 89 + static void sendUnexpectedError(ServerPlayerEntity player, String type) { 90 + player.sendMessage(Text.translatable("error.infinity." + type + ".unexpected")); 91 + } 92 + 93 + /** 94 + * Convert a provided string into a dimension ID. 95 + * This also checks if it matches an Easter Egg dimension. 96 + */ 97 + static Identifier getIdentifier(String text) { 98 + if (text.equals("abatised redivides")) return World.END.getValue(); 99 + if (text.isEmpty()) return InfinityMethods.getId("missingno"); 100 + if (InfinityMod.provider.easterizer.isEaster(text, InfinityMod.provider) && !text.equals("missingno")) return InfinityMethods.getId(text); 101 + return InfinityMethods.getDimId(getDimensionSeed(text)); 102 + } 103 + 104 + /** 105 + * Check if a dimension exists and has not been timebombed. 106 + */ 107 + static boolean dimExists(ServerWorld world) { 108 + return (world != null && !((Timebombable)world).infinity$isTimebombed()); 109 + } 110 + 111 + /** 112 + * Hashes text into dimension ID. 113 + */ 114 + static long getDimensionSeed(String text) { 115 + HashCode f = Hashing.sha256().hashString(text + InfinityMod.provider.salt, StandardCharsets.UTF_8); 116 + return InfinityMethods.longArithmeticEnabled() ? f.asLong() & Long.MAX_VALUE : f.asInt() & Integer.MAX_VALUE; 117 + } 118 + 119 + static long getNumericFromId(Identifier id) { 120 + String dimensionName = id.getPath(); 121 + String numericId = dimensionName.substring(dimensionName.lastIndexOf("_") + 1); 122 + long i; 123 + try { 124 + i = Long.parseLong(numericId); 125 + } catch (Exception e) { 126 + /* Simply hash the name if it isn't of "generated_..." format. */ 127 + i = getDimensionSeed(numericId); 128 + } 129 + return i; 130 + } 131 + 82 132 /** 83 133 * Converts a coordinate value to a "random" color. 84 134 */ ··· 140 190 } 141 191 } 142 192 return 0xFFFFFF; 193 + } 194 + 195 + static int getKeyColorFromId(Identifier id) { 196 + if(id.getNamespace().equals(InfinityMod.MOD_ID) && id.getPath().contains("generated_")) 197 + return ColorHelper.Argb.fullAlpha((int) getNumericFromId(id) & 0xFFFFFF); 198 + return 0; 143 199 } 144 200 145 201 static long getRandomSeed(java.util.Random random) {
+10 -5
common/src/main/java/net/lerariemann/infinity/util/PortalCreationLogic.java
··· 34 34 import net.minecraft.server.world.ServerWorld; 35 35 import net.minecraft.sound.SoundCategory; 36 36 import net.minecraft.sound.SoundEvents; 37 + import net.minecraft.text.Text; 37 38 import net.minecraft.util.Identifier; 38 39 import net.minecraft.util.math.BlockPos; 39 40 import net.minecraft.util.math.Direction; ··· 73 74 } 74 75 if (writableComponent != null || writtenComponent != null || printedComponent != null) { 75 76 String content = parseComponents(writableComponent, writtenComponent, printedComponent); 76 - Identifier id = WarpLogic.getIdentifier(content); 77 + Identifier id = InfinityMethods.getIdentifier(content); 77 78 boolean bl = modifyOnInitialCollision(id, world, pos); 78 79 if (bl) entity.remove(Entity.RemovalReason.CHANGED_DIMENSION); 79 80 } ··· 114 115 PlayerEntity nearestPlayer = world.getClosestPlayer(pos.getX(), pos.getY(), pos.getZ(), 5, false); 115 116 116 117 if (((MinecraftServerAccess)server).infinity$needsInvocation()) { 117 - WarpLogic.onInvocationNeedDetected(nearestPlayer); 118 + onInvocationNeedDetected(nearestPlayer); 118 119 return false; 119 120 } 120 121 ··· 128 129 } 129 130 130 131 /* If the portal key is blank, open the portal immediately. */ 131 - else if (InfinityMod.provider.portalKey.isBlank()) { 132 + else if (InfinityMod.provider.isPortalKeyBlank()) { 132 133 openWithStatIncrease(nearestPlayer, server, world, pos); 133 134 } 134 135 return true; ··· 137 138 /* Calls to open the portal and attributes the relevant statistics to a player provided. */ 138 139 static void openWithStatIncrease(PlayerEntity player, MinecraftServer s, ServerWorld world, BlockPos pos) { 139 140 if (((MinecraftServerAccess)s).infinity$needsInvocation()) { 140 - WarpLogic.onInvocationNeedDetected(player); 141 + onInvocationNeedDetected(player); 141 142 return; 142 143 } 143 144 boolean isDimensionNew = open(s, world, pos); ··· 148 149 } 149 150 player.increaseStat(ModStats.PORTALS_OPENED_STAT, 1); 150 151 } 152 + } 153 + 154 + static void onInvocationNeedDetected(PlayerEntity player) { 155 + if (player != null) player.sendMessage(Text.translatable("error.infinity.invocation_needed")); 151 156 } 152 157 153 158 /** ··· 209 214 } 210 215 211 216 static PortalModifierUnion forInitialSetupping(ServerWorld world, BlockPos pos, Identifier id, boolean open) { 212 - PortalColorApplier applier = WarpLogic.getPortalColorApplier(id, world.getServer()); 217 + PortalColorApplier applier = PortalColorApplier.of(id, world.getServer()); 213 218 return new PortalModifierUnion() 214 219 .addSetupper(infPortalSetupper(world, pos)) 215 220 .addModifier(nbpe -> nbpe.setDimension(id))
+9
common/src/main/java/net/lerariemann/infinity/util/RandomProvider.java
··· 1 1 package net.lerariemann.infinity.util; 2 2 3 3 import net.lerariemann.infinity.InfinityMod; 4 + import net.minecraft.item.Item; 4 5 import net.minecraft.nbt.*; 5 6 import net.minecraft.registry.DynamicRegistryManager; 6 7 import net.minecraft.registry.Registries; ··· 82 83 rootChances.put(s, rootchances.getCompound(c).getDouble(s)); 83 84 } 84 85 } 86 + } 87 + 88 + public Optional<Item> getPortalKeyAsItem() { 89 + if (portalKey.isBlank()) return Optional.empty(); 90 + return Registries.ITEM.getOrEmpty(Identifier.of(portalKey)); 91 + } 92 + public boolean isPortalKeyBlank() { 93 + return getPortalKeyAsItem().isEmpty(); 85 94 } 86 95 87 96 public NbtCompound notRandomTree(String tree, String block) {
+63 -71
common/src/main/java/net/lerariemann/infinity/util/WarpLogic.java
··· 1 1 package net.lerariemann.infinity.util; 2 - import com.google.common.hash.HashCode; 3 - import com.google.common.hash.Hashing; 2 + 4 3 import com.mojang.brigadier.context.CommandContext; 5 - import net.lerariemann.infinity.InfinityMod; 6 4 import net.lerariemann.infinity.access.MinecraftServerAccess; 7 5 import net.lerariemann.infinity.access.ServerPlayerEntityAccess; 8 - import net.lerariemann.infinity.access.Timebombable; 9 - import net.lerariemann.infinity.options.InfinityOptions; 10 - import net.lerariemann.infinity.options.PortalColorApplier; 11 6 import net.lerariemann.infinity.var.ModStats; 12 - import net.minecraft.entity.player.PlayerEntity; 13 - import net.minecraft.nbt.NbtCompound; 7 + import net.minecraft.block.Block; 8 + import net.minecraft.block.BlockState; 9 + import net.minecraft.block.Blocks; 10 + import net.minecraft.fluid.FluidState; 11 + import net.minecraft.fluid.Fluids; 12 + import net.minecraft.registry.RegistryKey; 13 + import net.minecraft.registry.RegistryKeys; 14 + import net.minecraft.registry.tag.FluidTags; 14 15 import net.minecraft.server.MinecraftServer; 15 16 import net.minecraft.server.command.ServerCommandSource; 16 17 import net.minecraft.server.network.ServerPlayerEntity; 17 18 import net.minecraft.server.world.ServerWorld; 18 - import net.minecraft.text.Text; 19 19 import net.minecraft.util.Identifier; 20 20 import net.minecraft.util.math.BlockPos; 21 - import net.minecraft.util.math.ColorHelper; 21 + import net.minecraft.util.math.MathHelper; 22 22 import net.minecraft.world.BlockView; 23 23 import net.minecraft.world.TeleportTarget; 24 24 import net.minecraft.world.World; 25 + import net.minecraft.world.dimension.DimensionType; 26 + import org.jetbrains.annotations.NotNull; 25 27 import org.jetbrains.annotations.Nullable; 26 28 27 - import java.nio.charset.StandardCharsets; 28 - 29 + import java.util.HashSet; 29 30 30 31 public interface WarpLogic { 31 32 /** 32 33 * Handles the /warpid command, converting their numeric ID to an Identifier 33 34 * and passing the data along to /warp. 34 35 */ 35 - static void warpId(CommandContext<ServerCommandSource> context, long value) { 36 - warp(context, InfinityMethods.getDimId(value)); 36 + static void requestWarpById(CommandContext<ServerCommandSource> context, long value) { 37 + requestWarp(context, InfinityMethods.getDimId(value)); 37 38 } 38 39 39 40 /** 40 41 * Handles the /warp command, warping the player to their specified dimension. 41 42 * The player-provided text should have already been converted to an Identifier. 42 43 */ 43 - static void warp(CommandContext<ServerCommandSource> context, Identifier value) { 44 - warpWithTimer(context.getSource().getPlayer(), value, 20, true); 44 + static void requestWarp(CommandContext<ServerCommandSource> context, Identifier value) { 45 + requestWarpWithTimer(context.getSource().getPlayer(), value, 2, true); 45 46 } 46 47 47 48 /** 48 49 * Handles moving a specified player to a specified dimension. 49 50 * This is used by both the warp commands and Iridescence. 50 51 */ 51 - static void warpWithTimer(@Nullable ServerPlayerEntity player, Identifier value, int ticks, boolean increaseStats) { 52 + static void requestWarpWithTimer(@Nullable ServerPlayerEntity player, Identifier value, int ticks, boolean increaseStats) { 52 53 if (player == null) return; 53 54 MinecraftServer s = player.getServer(); 54 55 if (s == null) return; 55 56 if (((MinecraftServerAccess)s).infinity$needsInvocation()) { 56 - onInvocationNeedDetected(player); 57 + PortalCreationLogic.onInvocationNeedDetected(player); 57 58 return; 58 59 } 59 60 boolean isThisANewDimension = PortalCreationLogic.addInfinityDimension(s, value); ··· 71 72 } 72 73 /* will implement a proper end-of-time dimension later */ 73 74 static void sendToMissingno(ServerPlayerEntity player) { 74 - warpWithTimer(player, InfinityMethods.getId("missingno"), 2, false); 75 + requestWarpWithTimer(player, InfinityMethods.getId("missingno"), 2, false); 75 76 } 76 77 77 - static void onInvocationNeedDetected(PlayerEntity player) { 78 - if (player != null) player.sendMessage(Text.translatable("error.infinity.invocation_needed")); 79 - } 78 + static void performWarp(ServerPlayerEntity player, Identifier idForWarp) { 79 + MinecraftServer s = player.getServerWorld().getServer(); 80 + RegistryKey<World> key = RegistryKey.of(RegistryKeys.WORLD, idForWarp); 81 + ServerWorld w = s.getWorld(key); 82 + if (w==null) { 83 + InfinityMethods.sendUnexpectedError(player, "warp"); 84 + return; 85 + } 86 + 87 + double d = DimensionType.getCoordinateScaleFactor(player.getServerWorld().getDimension(), w.getDimension()); 88 + double y = MathHelper.clamp(player.getY(), w.getBottomY(), w.getTopY()); 89 + 90 + BlockPos blockPos2 = getPosForWarp(w.getWorldBorder().clamp(player.getX() * d, y, player.getZ() * d), w); 80 91 81 - static PortalColorApplier getPortalColorApplier(Identifier id, MinecraftServer server) { 82 - return getPortalColorApplier(id, InfinityOptions.readData(server, id)); 92 + ensureSafety(w, blockPos2.down(), Blocks.OBSIDIAN); 93 + ensureSafety(w, blockPos2, Blocks.AIR); 94 + ensureSafety(w, blockPos2.up(), Blocks.AIR); 95 + player.teleport(w, blockPos2.getX() + 0.5, blockPos2.getY(), blockPos2.getZ() + 0.5, 96 + new HashSet<>(), player.getYaw(), player.getPitch()); 83 97 } 84 - static PortalColorApplier getPortalColorApplier(Identifier id, NbtCompound def) { 85 - NbtCompound c = InfinityMod.provider.easterizer.optionmap.get(id.getPath()); 86 - if (c == null) c = def; 87 - return PortalColorApplier.extract(c, (int)WarpLogic.getNumericFromId(id)); 98 + 99 + static void ensureSafety(ServerWorld world, BlockPos pos, Block fallback) { 100 + BlockState fb = fallback.getDefaultState(); 101 + if (!isSafe(world.getBlockState(pos), fb)) 102 + world.setBlockState(pos, fb); 88 103 } 89 104 90 - static long getNumericFromId(Identifier id) { 91 - String dimensionName = id.getPath(); 92 - String numericId = dimensionName.substring(dimensionName.lastIndexOf("_") + 1); 93 - long i; 94 - try { 95 - i = Long.parseLong(numericId); 96 - } catch (Exception e) { 97 - /* Simply hash the name if it isn't of "generated_..." format. */ 98 - i = WarpLogic.getDimensionSeed(numericId); 99 - } 100 - return i; 105 + static boolean isSafe(BlockState state, BlockState fallback) { 106 + if (state.isAir()) return fallback.isAir(); 107 + FluidState fluidState = state.getFluidState(); 108 + return (fluidState.isOf(Fluids.EMPTY) || fluidState.isIn(FluidTags.WATER)); 101 109 } 102 110 103 - static int getKeyColorFromId(Identifier id) { 104 - if(id.getNamespace().equals(InfinityMod.MOD_ID) && id.getPath().contains("generated_")) 105 - return ColorHelper.Argb.fullAlpha((int) getNumericFromId(id) & 0xFFFFFF); 106 - return 0; 111 + static BlockPos getPosForWarp(@NotNull BlockPos orig, @NotNull ServerWorld world) { 112 + BlockPos pos = scanColumn(orig, world); 113 + BlockPos iter = orig; 114 + int counter = 0; 115 + while (pos == null) { 116 + if (++counter > 8) pos = orig; 117 + else { 118 + iter = iter.add(world.random.nextInt(8), 0, world.random.nextInt(8)); 119 + pos = scanColumn(iter, world); 120 + } 121 + } 122 + return pos; 107 123 } 108 124 109 - static BlockPos getPosForWarp(BlockPos orig, ServerWorld world) { 125 + @Nullable 126 + static BlockPos scanColumn(BlockPos orig, ServerWorld world) { 110 127 int x = orig.getX(); 111 128 int y1 = orig.getY(); 112 129 int z = orig.getZ(); ··· 118 135 y2+=1; 119 136 if (isPosViable(x, y2, z, world)) return new BlockPos(x, y2, z); 120 137 } 121 - return orig; 138 + return null; 122 139 } 123 140 124 141 static boolean isPosViable(int x, int y, int z, BlockView w) { 142 + if (w.isOutOfHeightLimit(y)) return false; 125 143 boolean bl = w.getBlockState(new BlockPos(x, y+1, z)).isAir(); 126 144 boolean bl2 = w.getBlockState(new BlockPos(x, y, z)).isAir(); 127 145 boolean bl3 = w.getBlockState(new BlockPos(x, y-1, z)).isAir(); 128 146 return (!bl3 && bl2 && bl); 129 - } 130 - 131 - /** 132 - * Convert a provided string into a dimension ID. 133 - * This also checks if it matches an Easter Egg dimension. 134 - */ 135 - static Identifier getIdentifier(String text) { 136 - if (text.equals("abatised redivides")) return World.END.getValue(); 137 - if (text.isEmpty()) return InfinityMethods.getId("missingno"); 138 - if (InfinityMod.provider.easterizer.isEaster(text, InfinityMod.provider) && !text.equals("missingno")) return InfinityMethods.getId(text); 139 - return InfinityMethods.getDimId(getDimensionSeed(text)); 140 - } 141 - 142 - /** 143 - * Check if a dimension exists and has not been timebombed. 144 - */ 145 - static boolean dimExists(ServerWorld world) { 146 - return (world != null && !((Timebombable)world).infinity$isTimebombed()); 147 - } 148 - 149 - /** 150 - * Hashes text into dimension ID. 151 - */ 152 - static long getDimensionSeed(String text) { 153 - HashCode f = Hashing.sha256().hashString(text + InfinityMod.provider.salt, StandardCharsets.UTF_8); 154 - return InfinityMethods.longArithmeticEnabled() ? f.asLong() & Long.MAX_VALUE : f.asInt() & Integer.MAX_VALUE; 155 147 } 156 148 }
+3 -2
common/src/main/java/net/lerariemann/infinity/var/ModCommands.java
··· 3 3 import com.mojang.brigadier.arguments.IntegerArgumentType; 4 4 import com.mojang.brigadier.arguments.StringArgumentType; 5 5 import dev.architectury.event.events.common.CommandRegistrationEvent; 6 + import net.lerariemann.infinity.util.InfinityMethods; 6 7 import net.lerariemann.infinity.util.WarpLogic; 7 8 8 9 import static net.minecraft.server.command.CommandManager.argument; ··· 14 15 .requires(source -> source.hasPermissionLevel(2)) 15 16 .then(argument("id", IntegerArgumentType.integer()).executes(context -> { 16 17 final int value = IntegerArgumentType.getInteger(context, "id"); 17 - WarpLogic.warpId(context, value); 18 + WarpLogic.requestWarpById(context, value); 18 19 return 1; 19 20 })))); 20 21 CommandRegistrationEvent.EVENT.register((dispatcher, registryAccess, environment) -> dispatcher.register(literal("warp") 21 22 .requires(source -> source.hasPermissionLevel(2)) 22 23 .then(argument("text", StringArgumentType.string()).executes(context -> { 23 24 final String text = StringArgumentType.getString(context, "text"); 24 - WarpLogic.warp(context, WarpLogic.getIdentifier(text)); 25 + WarpLogic.requestWarp(context, InfinityMethods.getIdentifier(text)); 25 26 return 1; 26 27 })))); 27 28
+6
common/src/main/resources/assets/infinity/lang/en_us.json
··· 73 73 "entity.infinity.chaos_creeper.description": "These hostile mobs change the biome around them when they explode. They can be washed with a water bucket to reset the stored biome.", 74 74 "entity.infinity.chaos_pawn.description": "These neutral mobs can be found in some dimensions and sometimes spawn randomly in Infinity Portals. They drop a random item when killed.", 75 75 "error.infinity.invocation_needed": "This multiverse still needs invocation. This is a bug, please contact Lera", 76 + "error.infinity.portal.null": "This dimension does not exist. This is a bug, please contact Lera", 77 + "error.infinity.portal.deleted": "This dimension ceased to exist", 78 + "error.infinity.portal.closed": "This portal is closed; use a key (%s) to open it", 79 + "error.infinity.portal.cannot_create": "Unable to create an exit portal, likely target out of world border", 80 + "error.infinity.portal.unexpected": "An unexpected teleportation error has occurred, please contact Lera", 81 + "error.infinity.warp.unexpected": "An unexpected warp error has occurred, please contact Lera", 76 82 "fluid.infinity.iridescence": "Iridescence", 77 83 "item.infinity.chaos_skeleton_spawn_egg": "Chaos Skeleton Spawn Egg", 78 84 "item.infinity.chaos_slime_spawn_egg": "Chaos Slime Spawn Egg",