diff --git a/README.md b/README.md index b7eb547..f2946ca 100644 --- a/README.md +++ b/README.md @@ -455,3 +455,11 @@ Karena project ini menargetkan Java 25, compile final sebaiknya dilakukan menggu ## License Lobby Systems is **proprietary software — All Rights Reserved**. Public access to this repository is for viewing, auditing, and reference only. Using, running, compiling, modifying, redistributing, or re-uploading the mod requires prior written permission from DartoKloning. See [`LICENSE`](LICENSE) for the complete terms. + + +## v1.6.1 + +- Fixed a newly-created Lobby Systems visual portal being able to use vanilla Nether routing on its first entry. +- Managed `minecraft:nether_portal` surfaces are now explicitly intercepted so Lobby Systems exclusively owns their destination. +- Reduced the creator-side post-create trigger suppression from the old 100-tick window to the configured portal check interval. +- Added the Lobby Systems mod icon to Fabric metadata. diff --git a/gradle.properties b/gradle.properties index 3263439..ce97e51 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,6 +5,6 @@ minecraft_version=26.2 loader_version=0.19.3 loom_version=1.17-SNAPSHOT fabric_api_version=0.156.0+26.2 -mod_version=1.6.0 +mod_version=1.6.1 maven_group=id.dartokloning archives_base_name=lobby-systems diff --git a/src/main/java/id/dartokloning/lobbysystems/LobbySystemsMod.java b/src/main/java/id/dartokloning/lobbysystems/LobbySystemsMod.java index 9fbaa60..118ecd0 100644 --- a/src/main/java/id/dartokloning/lobbysystems/LobbySystemsMod.java +++ b/src/main/java/id/dartokloning/lobbysystems/LobbySystemsMod.java @@ -49,6 +49,17 @@ public final class LobbySystemsMod implements ModInitializer { private static final StarterKitManager STARTER_KITS = new StarterKitManager(); private static int saveTicker; + /** + * Used by the NetherPortalBlock mixin to distinguish Lobby Systems visual + * portals from ordinary vanilla Nether portals. + */ + public static boolean isManagedPortalBlock(Level level, BlockPos pos) { + if (level == null || pos == null) return false; + return PORTALS.isManagedVisualBlock( + level.dimension().identifier().toString(), + pos.getX(), pos.getY(), pos.getZ()); + } + @Override public void onInitialize() { LobbyLog.startup(); @@ -885,7 +896,11 @@ public final class LobbySystemsMod implements ModInitializer { return 0; } - PORTALS.suppress(player.getUUID(), Math.max(CONFIG.get().portalCooldownTicks, 100)); + // Only guard the command tick itself. Older builds suppressed the + // creator for at least 100 ticks, which left a window where the custom + // portal trigger was disabled while vanilla Nether routing was still + // active. The NetherPortalBlock mixin now owns that vanilla suppression. + PORTALS.suppress(player.getUUID(), Math.max(1, CONFIG.get().portalCheckIntervalTicks)); player.sendSystemMessage(Component.literal( "§aPortal §f" + name + "§a dibuat. §7" + portal.sourceDimension + " §8→ §f" + targetId + "\n§dVisual portal aktif §7(" + visualBlocks + " blok, axis=" + visualAxis + ")." diff --git a/src/main/java/id/dartokloning/lobbysystems/PortalManager.java b/src/main/java/id/dartokloning/lobbysystems/PortalManager.java index fdf1dd9..c4cbd33 100644 --- a/src/main/java/id/dartokloning/lobbysystems/PortalManager.java +++ b/src/main/java/id/dartokloning/lobbysystems/PortalManager.java @@ -85,6 +85,30 @@ public final class PortalManager { return portals.size(); } + /** + * Returns true when the given block position belongs to a Lobby Systems + * portal that uses vanilla nether_portal only as its visual surface. + * + * Vanilla portal routing must be suppressed for these blocks; Lobby Systems + * is the sole owner of their destination. + */ + public boolean isManagedVisualBlock(String dimension, int x, int y, int z) { + List> candidates = byDimension.get(dimension); + if (candidates == null || candidates.isEmpty()) return false; + + // Test the center of the block against the inclusive WorldEdit region. + double cx = x + 0.5D; + double cy = y + 0.5D; + double cz = z + 0.5D; + for (Map.Entry entry : candidates) { + PortalDefinition portal = entry.getValue(); + if (portal.hasNetherPortalVisual() && portal.contains(cx, cy, cz)) { + return true; + } + } + return false; + } + public void tick(MinecraftServer server, int intervalTicks, int cooldownTicks, PortalTrigger trigger) { tick++; cooldownUntil.entrySet().removeIf(e -> e.getValue() <= tick); diff --git a/src/main/java/id/dartokloning/lobbysystems/mixin/NetherPortalBlockMixin.java b/src/main/java/id/dartokloning/lobbysystems/mixin/NetherPortalBlockMixin.java new file mode 100644 index 0000000..f10f8bc --- /dev/null +++ b/src/main/java/id/dartokloning/lobbysystems/mixin/NetherPortalBlockMixin.java @@ -0,0 +1,52 @@ +package id.dartokloning.lobbysystems.mixin; + +import id.dartokloning.lobbysystems.LobbySystemsMod; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.InsideBlockEffectApplier; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.NetherPortalBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.portal.TeleportTransition; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** + * Makes vanilla nether_portal blocks inside Lobby Systems portal regions purely + * visual. Ordinary Nether portals outside managed regions keep vanilla behavior. + */ +@Mixin(NetherPortalBlock.class) +public abstract class NetherPortalBlockMixin { + + @Inject(method = "entityInside", at = @At("HEAD"), cancellable = true) + private void dartokloning$cancelVanillaPortalEntry( + BlockState state, + Level level, + BlockPos pos, + Entity entity, + InsideBlockEffectApplier effectApplier, + boolean isPrecise, + CallbackInfo ci) { + if (!level.isClientSide() && LobbySystemsMod.isManagedPortalBlock(level, pos)) { + ci.cancel(); + } + } + + // Defense in depth: if another mod or a stale portal processor reaches the + // destination lookup anyway, managed Lobby Systems portal blocks still never + // resolve through vanilla Nether routing. + @Inject(method = "getPortalDestination", at = @At("HEAD"), cancellable = true) + private void dartokloning$cancelVanillaDestination( + ServerLevel currentLevel, + Entity entity, + BlockPos portalEntryPos, + CallbackInfoReturnable cir) { + if (LobbySystemsMod.isManagedPortalBlock(currentLevel, portalEntryPos)) { + cir.setReturnValue(null); + } + } +} diff --git a/src/main/resources/assets/dartokloning/icon.png b/src/main/resources/assets/dartokloning/icon.png new file mode 100644 index 0000000..22d7d3c Binary files /dev/null and b/src/main/resources/assets/dartokloning/icon.png differ diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 442570b..aee71d7 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -28,5 +28,6 @@ ], "breaks": { "world-gamerules": "*" - } + }, + "icon": "assets/dartokloning/icon.png" } diff --git a/src/main/resources/lobby-systems.mixins.json b/src/main/resources/lobby-systems.mixins.json index e71987b..2df6411 100644 --- a/src/main/resources/lobby-systems.mixins.json +++ b/src/main/resources/lobby-systems.mixins.json @@ -4,7 +4,8 @@ "compatibilityLevel": "JAVA_25", "mixins": [ "ServerLevelGameRulesMixin", - "PlayerListMixin" + "PlayerListMixin", + "NetherPortalBlockMixin" ], "injectors": { "defaultRequire": 1