diff --git a/build.gradle b/build.gradle index 40374a5f7..34023b791 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,7 @@ targetCompatibility = JavaVersion.VERSION_21 repositories { maven { url 'https://masa.dy.fi/maven' } maven { url 'https://maven.terraformersmc.com/releases/' } + maven { url 'https://jitpack.io' } } dependencies { @@ -16,8 +17,8 @@ dependencies { modImplementation "net.fabricmc:fabric-loader:${project.fabric_loader_version}" implementation "com.google.code.findbugs:jsr305:3.0.2" - modImplementation "fi.dy.masa.malilib:malilib-fabric-${project.minecraft_version_out}:${project.malilib_version}" - //modImplementation "com.github.sakura-ryoko:malilib:${project.malilib_id}" + //modImplementation "fi.dy.masa.malilib:malilib-fabric-${project.minecraft_version_out}:${project.malilib_version}" + modImplementation "com.github.sakura-ryoko:malilib:${project.malilib_id}" // Fabric API. This is technically optional, but you probably want it anyway. //include(modApi(fabricApi.module("fabric-api-base", project.fabric_api_version))) diff --git a/gradle.properties b/gradle.properties index 6080acbcd..2091053a4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,10 +9,11 @@ author = masa mod_file_name = tweakeroo-fabric # Current mod version -mod_version = 0.21.1-sakura.1 +mod_version = 0.21.51-sakura.1 # Required malilib version -malilib_version = 0.20.0 +malilib_version = 0.21.1-sakura.3 +malilib_id = 5eeca46223 # Minecraft, Fabric Loader and API and mappings versions minecraft_version_out = 1.21 diff --git a/src/main/java/fi/dy/masa/tweakeroo/InitHandler.java b/src/main/java/fi/dy/masa/tweakeroo/InitHandler.java index 0d902bfdc..3ecbad9f8 100644 --- a/src/main/java/fi/dy/masa/tweakeroo/InitHandler.java +++ b/src/main/java/fi/dy/masa/tweakeroo/InitHandler.java @@ -2,15 +2,14 @@ import net.minecraft.client.MinecraftClient; import fi.dy.masa.malilib.config.ConfigManager; -import fi.dy.masa.malilib.event.InputEventHandler; -import fi.dy.masa.malilib.event.RenderEventHandler; -import fi.dy.masa.malilib.event.TickHandler; -import fi.dy.masa.malilib.event.WorldLoadHandler; +import fi.dy.masa.malilib.event.*; import fi.dy.masa.malilib.interfaces.IInitializationHandler; import fi.dy.masa.malilib.interfaces.IRenderer; import fi.dy.masa.malilib.interfaces.IWorldLoadListener; import fi.dy.masa.tweakeroo.config.Callbacks; import fi.dy.masa.tweakeroo.config.Configs; +import fi.dy.masa.tweakeroo.data.DataManager; +import fi.dy.masa.tweakeroo.data.ServerDataSyncer; import fi.dy.masa.tweakeroo.event.ClientTickHandler; import fi.dy.masa.tweakeroo.event.InputHandler; import fi.dy.masa.tweakeroo.event.RenderHandler; @@ -22,6 +21,7 @@ public class InitHandler implements IInitializationHandler public void registerModHandlers() { ConfigManager.getInstance().registerConfigHandler(Reference.MOD_ID, new Configs()); + ServerDataSyncer.getInstance().onGameInit(); InputEventHandler.getKeybindManager().registerKeybindProvider(InputHandler.getInstance()); InputEventHandler.getInputManager().registerKeyboardInputHandler(InputHandler.getInstance()); @@ -38,6 +38,12 @@ public void registerModHandlers() WorldLoadHandler.getInstance().registerWorldLoadPreHandler(worldListener); WorldLoadHandler.getInstance().registerWorldLoadPostHandler(worldListener); + + ServerHandler.getInstance().registerServerHandler(DataManager.getInstance()); + + TickHandler.getInstance().registerClientTickHandler(new ClientTickHandler()); + TickHandler.getInstance().registerClientTickHandler(ServerDataSyncer.getInstance()); + Callbacks.init(MinecraftClient.getInstance()); } } diff --git a/src/main/java/fi/dy/masa/tweakeroo/Reference.java b/src/main/java/fi/dy/masa/tweakeroo/Reference.java index a28135683..0d42ab028 100644 --- a/src/main/java/fi/dy/masa/tweakeroo/Reference.java +++ b/src/main/java/fi/dy/masa/tweakeroo/Reference.java @@ -1,5 +1,7 @@ package fi.dy.masa.tweakeroo; +import net.minecraft.MinecraftVersion; + import fi.dy.masa.malilib.util.StringUtils; public class Reference @@ -7,4 +9,7 @@ public class Reference public static final String MOD_ID = "tweakeroo"; public static final String MOD_NAME = "Tweakeroo"; public static final String MOD_VERSION = StringUtils.getModVersionString(MOD_ID); + public static final String MC_VERSION = MinecraftVersion.CURRENT.getName(); + public static final String MOD_TYPE = "fabric"; + public static final String MOD_STRING = MOD_ID+"-"+MOD_TYPE+"-"+MC_VERSION+"-"+MOD_VERSION; } diff --git a/src/main/java/fi/dy/masa/tweakeroo/Tweakeroo.java b/src/main/java/fi/dy/masa/tweakeroo/Tweakeroo.java index 929d8f6a0..454b2cc64 100644 --- a/src/main/java/fi/dy/masa/tweakeroo/Tweakeroo.java +++ b/src/main/java/fi/dy/masa/tweakeroo/Tweakeroo.java @@ -4,6 +4,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import fi.dy.masa.malilib.event.InitializationHandler; +import fi.dy.masa.tweakeroo.config.Configs; public class Tweakeroo implements ModInitializer { @@ -18,13 +19,11 @@ public void onInitialize() InitializationHandler.getInstance().registerInitializationHandler(new InitHandler()); } - /* - public static void debugLog(String msg, Object... args) + public static void printDebug(String msg, Object... args) { if (Configs.Generic.DEBUG_LOGGING.getBooleanValue()) { Tweakeroo.logger.info(msg, args); } } - */ } diff --git a/src/main/java/fi/dy/masa/tweakeroo/config/Configs.java b/src/main/java/fi/dy/masa/tweakeroo/config/Configs.java index ceb3bc95e..ff8ae4d13 100644 --- a/src/main/java/fi/dy/masa/tweakeroo/config/Configs.java +++ b/src/main/java/fi/dy/masa/tweakeroo/config/Configs.java @@ -45,11 +45,13 @@ public static class Generic public static final ConfigOptionList BLOCK_TYPE_BREAK_RESTRICTION_WARN = new ConfigOptionList ("blockTypeBreakRestrictionWarn", MessageOutputType.MESSAGE, "tweakeroo.config.generic.comment.blockTypeBreakRestrictionWarn").translatedName("tweakeroo.config.generic.name.blockTypeBreakRestrictionWarn"); public static final ConfigInteger BREAKING_GRID_SIZE = new ConfigInteger ("breakingGridSize", 3, 1, 1000, "tweakeroo.config.generic.comment.breakingGridSize").translatedName("tweakeroo.config.generic.name.breakingGridSize"); public static final ConfigOptionList BREAKING_RESTRICTION_MODE = new ConfigOptionList ("breakingRestrictionMode", PlacementRestrictionMode.LINE, "tweakeroo.config.generic.comment.breakingRestrictionMode").translatedName("tweakeroo.config.generic.name.breakingRestrictionMode"); + public static final ConfigBoolean BUNDLE_DISPLAY_BACKGROUND_COLOR = new ConfigBoolean ("bundleDisplayBgColor", true, "tweakeroo.config.generic.comment.bundleDisplayBgColor").translatedName("tweakeroo.config.generic.name.bundleDisplayBgColor"); + public static final ConfigBoolean BUNDLE_DISPLAY_REQUIRE_SHIFT = new ConfigBoolean ("bundleDisplayRequireShift", true, "tweakeroo.config.generic.comment.bundleDisplayRequireShift").translatedName("tweakeroo.config.generic.name.bundleDisplayRequireShift"); public static final ConfigColor CHAT_BACKGROUND_COLOR = new ConfigColor ("chatBackgroundColor", "#80000000", "tweakeroo.config.generic.comment.chatBackgroundColor").translatedName("tweakeroo.config.generic.name.chatBackgroundColor"); public static final ConfigString CHAT_TIME_FORMAT = new ConfigString ("chatTimeFormat", "[HH:mm:ss]", "tweakeroo.config.generic.comment.chatTimeFormat").translatedName("tweakeroo.config.generic.name.chatTimeFormat"); public static final ConfigBoolean CLIENT_PLACEMENT_ROTATION = new ConfigBoolean ("clientPlacementRotation", true, "tweakeroo.config.generic.comment.clientPlacementRotation").translatedName("tweakeroo.config.generic.name.clientPlacementRotation"); public static final ConfigInteger CUSTOM_INVENTORY_GUI_SCALE = new ConfigInteger ("customInventoryGuiScale", 2, 1, 10, "tweakeroo.config.generic.comment.customInventoryGuiScale").translatedName("tweakeroo.config.generic.name.customInventoryGuiScale"); - //public static final ConfigBoolean DEBUG_LOGGING = new ConfigBoolean ("debugLogging", false, "tweakeroo.config.generic.comment.debugLogging").translatedName("tweakeroo.config.generic.name.debugLogging"); + public static final ConfigBoolean DEBUG_LOGGING = new ConfigBoolean ("debugLogging", false, "tweakeroo.config.generic.comment.debugLogging").translatedName("tweakeroo.config.generic.name.debugLogging"); public static final ConfigOptionList ELYTRA_CAMERA_INDICATOR = new ConfigOptionList ("elytraCameraIndicator", ActiveMode.WITH_KEY, "tweakeroo.config.generic.comment.elytraCameraIndicator").translatedName("tweakeroo.config.generic.name.elytraCameraIndicator"); public static final ConfigDouble ENTITY_REACH_DISTANCE = new ConfigDouble ("entityReachDistance", 3.0, 1, 64, "tweakeroo.config.generic.comment.entityReachDistance").translatedName("tweakeroo.config.generic.name.entityReachDistance"); public static final ConfigOptionList ENTITY_TYPE_ATTACK_RESTRICTION_WARN = new ConfigOptionList ("entityTypeAttackRestrictionWarn", MessageOutputType.MESSAGE, "tweakeroo.config.generic.comment.entityTypeAttackRestrictionWarn").translatedName("tweakeroo.config.generic.name.entityTypeAttackRestrictionWarn"); @@ -101,6 +103,7 @@ public static class Generic public static final ConfigInteger RENDER_LIMIT_ITEM = new ConfigInteger ("renderLimitItem", -1, -1, 10000, "tweakeroo.config.generic.comment.renderLimitItem").translatedName("tweakeroo.config.generic.name.renderLimitItem"); public static final ConfigInteger RENDER_LIMIT_XP_ORB = new ConfigInteger ("renderLimitXPOrb", -1, -1, 10000, "tweakeroo.config.generic.comment.renderLimitXPOrb").translatedName("tweakeroo.config.generic.name.renderLimitXPOrb"); public static final ConfigInteger SCULK_SENSOR_PULSE_LENGTH = new ConfigInteger ("sculkSensorPulseLength", 40, 0, 10000, "tweakeroo.config.generic.comment.sculkSensorPulseLength").translatedName("tweakeroo.config.generic.name.sculkSensorPulseLength"); + public static final ConfigInteger SERVER_NBT_REQUEST_RATE = new ConfigInteger ("serverNbtRequestRate", 2, "tweakeroo.config.generic.comment.serverNbtRequestRate").translatedName("tweakeroo.config.generic.name.serverNbtRequestRate"); public static final ConfigBoolean SHULKER_DISPLAY_BACKGROUND_COLOR = new ConfigBoolean ("shulkerDisplayBgColor", true, "tweakeroo.config.generic.comment.shulkerDisplayBgColor").translatedName("tweakeroo.config.generic.name.shulkerDisplayBgColor"); public static final ConfigBoolean SHULKER_DISPLAY_REQUIRE_SHIFT = new ConfigBoolean ("shulkerDisplayRequireShift", true, "tweakeroo.config.generic.comment.shulkerDisplayRequireShift").translatedName("tweakeroo.config.generic.name.shulkerDisplayRequireShift"); public static final ConfigBoolean SLOT_SYNC_WORKAROUND = new ConfigBoolean ("slotSyncWorkaround", true, "tweakeroo.config.generic.comment.slotSyncWorkaround").translatedName("tweakeroo.config.generic.name.slotSyncWorkaround"); @@ -126,8 +129,10 @@ public static class Generic public static final ImmutableList OPTIONS = ImmutableList.of( ACCURATE_PLACEMENT_PROTOCOL_MODE, ACCURATE_PLACEMENT_PROTOCOL, + BUNDLE_DISPLAY_BACKGROUND_COLOR, + BUNDLE_DISPLAY_REQUIRE_SHIFT, CLIENT_PLACEMENT_ROTATION, - //DEBUG_LOGGING, + DEBUG_LOGGING, FAST_LEFT_CLICK_ALLOW_TOOLS, FAST_PLACEMENT_REMEMBER_ALWAYS, FREE_CAMERA_PLAYER_INPUTS, @@ -201,6 +206,7 @@ public static class Generic RENDER_LIMIT_ITEM, RENDER_LIMIT_XP_ORB, SCULK_SENSOR_PULSE_LENGTH, + SERVER_NBT_REQUEST_RATE, SNAP_AIM_PITCH_STEP, SNAP_AIM_THRESHOLD_PITCH, SNAP_AIM_THRESHOLD_YAW, @@ -248,7 +254,7 @@ public static class Lists public static final ConfigOptionList FAST_RIGHT_CLICK_ITEM_LIST_TYPE = new ConfigOptionList("fastRightClickListType", ListType.NONE, "tweakeroo.config.lists.comment.fastRightClickListType").translatedName("tweakeroo.config.lists.name.fastRightClickListType"); public static final ConfigStringList FAST_RIGHT_CLICK_ITEM_BLACKLIST = new ConfigStringList("fastRightClickBlackList", ImmutableList.of("minecraft:firework_rocket"), "tweakeroo.config.lists.comment.fastRightClickBlackList").translatedName("tweakeroo.config.lists.name.fastRightClickBlackList"); public static final ConfigStringList FAST_RIGHT_CLICK_ITEM_WHITELIST = new ConfigStringList("fastRightClickWhiteList", ImmutableList.of("minecraft:bucket", "minecraft:water_bucket", "minecraft:lava_bucket", "minecraft:glass_bottle"), "tweakeroo.config.lists.comment.fastRightClickWhiteList").translatedName("tweakeroo.config.lists.name.fastRightClickWhiteList"); - public static final ConfigStringList FLAT_WORLD_PRESETS = new ConfigStringList("flatWorldPresets", ImmutableList.of("White Glass;1*minecraft:white_stained_glass;minecraft:plains;;minecraft:white_stained_glass", "Glass;1*minecraft:glass;minecraft:plains;;minecraft:glass"), "tweakeroo.config.lists.comment.flatWorldPresets").translatedName("tweakeroo.config.lists.name.flatWorldPresets"); + //public static final ConfigStringList FLAT_WORLD_PRESETS = new ConfigStringList("flatWorldPresets", ImmutableList.of("White Glass;1*minecraft:white_stained_glass;minecraft:plains;;minecraft:white_stained_glass", "Glass;1*minecraft:glass;minecraft:plains;;minecraft:glass"), "tweakeroo.config.lists.comment.flatWorldPresets").translatedName("tweakeroo.config.lists.name.flatWorldPresets"); public static final ConfigOptionList HAND_RESTOCK_LIST_TYPE = new ConfigOptionList("handRestockListType", ListType.NONE, "tweakeroo.config.lists.comment.handRestockListType").translatedName("tweakeroo.config.lists.name.handRestockListType"); public static final ConfigStringList HAND_RESTOCK_BLACKLIST = new ConfigStringList("handRestockBlackList", ImmutableList.of("minecraft:bucket", "minecraft:lava_bucket", "minecraft:water_bucket"), "tweakeroo.config.lists.comment.handRestockBlackList").translatedName("tweakeroo.config.lists.name.handRestockBlackList"); public static final ConfigStringList HAND_RESTOCK_WHITELIST = new ConfigStringList("handRestockWhiteList", ImmutableList.of(), "tweakeroo.config.lists.comment.handRestockWhiteList").translatedName("tweakeroo.config.lists.name.handRestockWhiteList"); @@ -278,7 +284,7 @@ public static class Lists FAST_RIGHT_CLICK_BLOCK_WHITELIST, FAST_RIGHT_CLICK_ITEM_BLACKLIST, FAST_RIGHT_CLICK_ITEM_WHITELIST, - FLAT_WORLD_PRESETS, + //FLAT_WORLD_PRESETS, HAND_RESTOCK_LIST_TYPE, HAND_RESTOCK_BLACKLIST, HAND_RESTOCK_WHITELIST, diff --git a/src/main/java/fi/dy/masa/tweakeroo/config/FeatureToggle.java b/src/main/java/fi/dy/masa/tweakeroo/config/FeatureToggle.java index b51d4f327..dc6054d68 100644 --- a/src/main/java/fi/dy/masa/tweakeroo/config/FeatureToggle.java +++ b/src/main/java/fi/dy/masa/tweakeroo/config/FeatureToggle.java @@ -28,6 +28,7 @@ public enum FeatureToggle implements IHotkeyTogglable, IConfigNotifiable HANDLER = ServuxTweaksHandler.getInstance(); + private final static MinecraftClient mc = MinecraftClient.getInstance(); + private int uptimeTicks = 0; + private boolean servuxServer = false; + private boolean hasInValidServux = false; + private String servuxVersion; + + // Data Cache + private final ConcurrentHashMap>> blockEntityCache = new ConcurrentHashMap<>(); + private final ConcurrentHashMap>> entityCache = new ConcurrentHashMap<>(); + private final long cacheTimeout = 4; + private long serverTickTime = 0; + // Requests to be executed + private final Set pendingBlockEntitiesQueue = new LinkedHashSet<>(); + private final Set pendingEntitiesQueue = new LinkedHashSet<>(); + // To save vanilla query packet transaction + private final Map> transactionToBlockPosOrEntityId = new HashMap<>(); + private ClientWorld clientWorld; + + @Nullable + public World getWorld() { - INSTANCE = null; + return WorldUtils.getBestWorld(mc); } - /** - * key: BlockPos - * value: data, timestamp - */ - private final Map> blockCache = new HashMap<>(); - private final Map> entityCache = new HashMap<>(); - /** - * this map is not important, you can clear it anytime you want, but a bit more packets will be sent to the server - */ - private final Map, CompletableFuture<@Nullable NbtCompound>> pendingQueriesByObject = new HashMap<>(); - private final Map> pendingQueriesById = new HashMap<>(); - private final ClientWorld clientWorld; - /** - * if the client can query the server for block/entity data? empty if not yet known. - */ - private Optional yesIAmOp = Optional.empty(); - - public ServerDataSyncer(ClientWorld world) + private ClientWorld getClientWorld() { - this.clientWorld = Objects.requireNonNull(world); + if (this.clientWorld == null) + { + clientWorld = mc.world; + } + + return clientWorld; } - private @Nullable BlockEntity getCache(BlockPos pos) + public ServerDataSyncer() { } + + @Override + public void onClientTick(MinecraftClient mc) { - var data = blockCache.get(pos); + this.uptimeTicks++; + if (System.currentTimeMillis() - this.serverTickTime > 50) + { + // In this block, we do something every server tick + if (FeatureToggle.TWEAK_SERVER_DATA_SYNC.getBooleanValue() == false) + { + this.serverTickTime = System.currentTimeMillis(); + if (DataManager.getInstance().hasIntegratedServer() == false && this.hasServuxServer()) + { + this.servuxServer = false; + HANDLER.unregisterPlayReceiver(); + } + return; + } + else if (DataManager.getInstance().hasIntegratedServer() == false && + this.hasServuxServer() == false && + this.hasInValidServux == false && + this.getWorld() != null) + { + // Make sure we're Play Registered, and request Metadata + HANDLER.registerPlayReceiver(ServuxTweaksPacket.Payload.ID, HANDLER::receivePlayPayload); + this.requestMetadata(); + } - if (yesIAmOp.isPresent() && !yesIAmOp.get()) return null; + // Expire cached NBT + this.tickCache(); - if (data != null && System.currentTimeMillis() - data.getRight() <= 1000) - { - if (System.currentTimeMillis() - data.getRight() > 500) + // 5 queries / server tick + for (int i = 0; i < Configs.Generic.SERVER_NBT_REQUEST_RATE.getIntegerValue(); i++) { - syncBlockEntity(clientWorld, pos); + if (!this.pendingBlockEntitiesQueue.isEmpty()) + { + var iter = this.pendingBlockEntitiesQueue.iterator(); + BlockPos pos = iter.next(); + iter.remove(); + if (this.hasServuxServer()) + { + requestServuxBlockEntityData(pos); + } + else + { + requestQueryBlockEntity(pos); + } + } + if (!this.pendingEntitiesQueue.isEmpty()) + { + var iter = this.pendingEntitiesQueue.iterator(); + int entityId = iter.next(); + iter.remove(); + if (this.hasServuxServer()) + { + requestServuxEntityData(entityId); + } + else + { + requestQueryEntityData(entityId); + } + } } + this.serverTickTime = System.currentTimeMillis(); + } + } - return data.getLeft(); + public Identifier getNetworkChannel() + { + return ServuxTweaksHandler.CHANNEL_ID; + } + + private ClientPlayNetworkHandler getVanillaHandler() + { + if (mc.player != null) + { + return mc.player.networkHandler; } return null; } - private @Nullable Entity getCache(int networkId) + public IPluginClientPlayHandler getNetworkHandler() + { + return HANDLER; + } + + public void reset(boolean isLogout) + { + if (isLogout) + { + Tweakeroo.printDebug("ServerDataSyncer#reset() - log-out"); + HANDLER.reset(this.getNetworkChannel()); + HANDLER.resetFailures(this.getNetworkChannel()); + this.servuxServer = false; + this.hasInValidServux = false; + } + else + { + Tweakeroo.printDebug("ServerDataSyncer#reset() - dimension change or log-in"); + this.serverTickTime = System.currentTimeMillis() - (this.cacheTimeout + 5) * 1000L; + this.tickCache(); + this.serverTickTime = System.currentTimeMillis(); + this.clientWorld = mc.world; + } + // Clear data + this.blockEntityCache.clear(); + this.entityCache.clear(); + this.pendingBlockEntitiesQueue.clear(); + this.pendingEntitiesQueue.clear(); + } + + private void tickCache() { - var data = entityCache.get(networkId); + long nowTime = System.currentTimeMillis(); + long blockTimeout = (this.cacheTimeout) * 1000L; + long entityTimeout = (this.cacheTimeout / 2) * 1000L; + + synchronized (this.blockEntityCache) + { + for (BlockPos pos : this.blockEntityCache.keySet()) + { + Pair> pair = this.blockEntityCache.get(pos); - if (yesIAmOp.isPresent() && !yesIAmOp.get()) return null; + if (nowTime - pair.getLeft() > blockTimeout || pair.getLeft() - nowTime > 0) + { + //Tweakeroo.printDebug("entityCache: be at pos [{}] has timed out", pos.toShortString()); + this.blockEntityCache.remove(pos); + } + } + } - if (data != null && System.currentTimeMillis() - data.getRight() <= 1000) + synchronized (this.entityCache) { - if (System.currentTimeMillis() - data.getRight() > 500) + for (Integer entityId : this.entityCache.keySet()) { - syncEntity(networkId); + Pair> pair = this.entityCache.get(entityId); + + if (nowTime - pair.getLeft() > entityTimeout || pair.getLeft() - nowTime > 0) + { + //Tweakeroo.printDebug("entityCache: entity Id [{}] has timed out", entityId); + this.entityCache.remove(entityId); + } } + } + } - return data.getLeft(); + public @Nullable NbtCompound getFromBlockEntityCacheNbt(BlockPos pos) + { + if (this.blockEntityCache.containsKey(pos)) + { + return this.blockEntityCache.get(pos).getRight().getRight(); } return null; } - public void handleQueryResponse(int transactionId, NbtCompound nbt) + public @Nullable BlockEntity getFromBlockEntityCache(BlockPos pos) { - CompletableFuture<@Nullable NbtCompound> future = pendingQueriesById.remove(transactionId); - if (future != null) + if (this.blockEntityCache.containsKey(pos)) { - future.complete(nbt); - yesIAmOp = Optional.of(true); + return this.blockEntityCache.get(pos).getRight().getLeft(); } - if (blockCache.size() > 30) + return null; + } + + public @Nullable NbtCompound getFromEntityCacheNbt(int entityId) + { + if (this.entityCache.containsKey(entityId)) { - blockCache.entrySet().removeIf(entry -> System.currentTimeMillis() - entry.getValue().getRight() > 1000); - pendingQueriesByObject.clear(); + return this.entityCache.get(entityId).getRight().getRight(); } - if (entityCache.size() > 30) + + return null; + } + + public @Nullable Entity getFromEntityCache(int entityId) + { + if (this.entityCache.containsKey(entityId)) { - entityCache.entrySet().removeIf(entry -> System.currentTimeMillis() - entry.getValue().getRight() > 1000); - pendingQueriesByObject.clear(); + return this.entityCache.get(entityId).getRight().getLeft(); } + + return null; } - public Inventory getBlockInventory(World world, BlockPos pos) + public void setIsServuxServer() { - if (FeatureToggle.TWEAK_SERVER_DATA_SYNC.getBooleanValue() == false) + this.servuxServer = true; + this.hasInValidServux = false; + } + + public boolean hasServuxServer() + { + return this.servuxServer; + } + + public void setServuxVersion(String ver) + { + if (ver != null && ver.isEmpty() == false) { - return null; + this.servuxVersion = ver; + Tweakeroo.printDebug("tweaksDataChannel: joining Servux version {}", ver); } - else if (yesIAmOp.isPresent() && !yesIAmOp.get()) + else { - return null; + this.servuxVersion = "unknown"; } + } - if (!world.isChunkLoaded(pos)) return null; - if (getCache(pos) instanceof Inventory inv) - { - BlockState state = world.getBlockState(pos); - if (state.getBlock() instanceof ChestBlock && inv instanceof ChestBlockEntity) - { - ChestType type = state.get(ChestBlock.CHEST_TYPE); + public String getServuxVersion() + { + return this.servuxVersion; + } - if (type != ChestType.SINGLE) - { - BlockPos posAdj = pos.offset(ChestBlock.getFacing(state)); - if (!world.isChunkLoaded(posAdj)) return null; - BlockState stateAdj = world.getBlockState(posAdj); + public int getPendingBlockEntitiesCount() + { + return this.pendingBlockEntitiesQueue.size(); + } - var dataAdj = getCache(posAdj); - if (dataAdj == null) - { - syncBlockEntity(world, posAdj); - } + public int getPendingEntitiesCount() + { + return this.pendingEntitiesQueue.size(); + } - if (stateAdj.getBlock() == state.getBlock() && dataAdj instanceof ChestBlockEntity inv2 && stateAdj.get(ChestBlock.CHEST_TYPE) != ChestType.SINGLE && stateAdj.get(ChestBlock.FACING) == state.get(ChestBlock.FACING)) - { - Inventory invRight = type == ChestType.RIGHT ? inv : inv2; - Inventory invLeft = type == ChestType.RIGHT ? inv2 : inv; - inv = new DoubleInventory(invRight, invLeft); - } - } - } - else if (state.getBlock() instanceof CrafterBlock && inv instanceof CrafterBlockEntity ce) - { - return InventoryUtils.getAsInventory(ce.getHeldStacks()); - } + public int getBlockEntityCacheCount() + { + return this.blockEntityCache.size(); + } + + public int getEntityCacheCount() + { + return this.entityCache.size(); + } + + public void onGameInit() + { + ClientPlayHandler.getInstance().registerClientPlayHandler(HANDLER); + HANDLER.registerPlayPayload(ServuxTweaksPacket.Payload.ID, ServuxTweaksPacket.Payload.CODEC, IPluginClientPlayHandler.BOTH_CLIENT); + } - return inv; + public void onWorldPre() + { + if (DataManager.getInstance().hasIntegratedServer() == false) + { + HANDLER.registerPlayReceiver(ServuxTweaksPacket.Payload.ID, HANDLER::receivePlayPayload); } + } + + public void onWorldJoin() + { + // NO-OP + } + + public void requestMetadata() + { + if (DataManager.getInstance().hasIntegratedServer() == false && + FeatureToggle.TWEAK_SERVER_DATA_SYNC.getBooleanValue()) + { + NbtCompound nbt = new NbtCompound(); + nbt.putString("version", Reference.MOD_STRING); - syncBlockEntity(world, pos); + HANDLER.encodeClientData(ServuxTweaksPacket.MetadataRequest(nbt)); + } + } - BlockState state = world.getBlockState(pos); - if (state.getBlock() instanceof ChestBlock) + public boolean receiveServuxMetadata(NbtCompound data) + { + if (DataManager.getInstance().hasIntegratedServer() == false) { - ChestType type = state.get(ChestBlock.CHEST_TYPE); + Tweakeroo.printDebug("ServerDataSyncer#receiveServuxMetadata(): received METADATA from Servux"); - if (type != ChestType.SINGLE) + if (FeatureToggle.TWEAK_SERVER_DATA_SYNC.getBooleanValue()) { - BlockPos posAdj = pos.offset(ChestBlock.getFacing(state)); - if (world.isChunkLoaded(posAdj)) + if (data.getInt("version") != ServuxTweaksPacket.PROTOCOL_VERSION) { - BlockState stateAdj = world.getBlockState(posAdj); - if (stateAdj.getBlock() instanceof ChestBlock) - { - syncBlockEntity(world, posAdj); - } + Tweakeroo.logger.warn("tweaksDataChannel: Mis-matched protocol version!"); } + + DataManager.getInstance().setHasServuxServer(true); + this.setServuxVersion(data.getString("servux")); + this.setIsServuxServer(); + + return true; } } - else if (state.getBlock() instanceof CrafterBlock) + + return false; + } + + public void onPacketFailure() + { + DataManager.getInstance().setHasServuxServer(false); + this.servuxServer = false; + this.hasInValidServux = true; + } + + public @Nullable Pair requestBlockEntity(World world, BlockPos pos) + { + if (this.blockEntityCache.containsKey(pos)) + { + return this.blockEntityCache.get(pos).getRight(); + } + else if (world.getBlockState(pos).getBlock() instanceof BlockEntityProvider) { - syncBlockEntity(world, pos); + if (DataManager.getInstance().hasIntegratedServer() == false && + FeatureToggle.TWEAK_SERVER_DATA_SYNC.getBooleanValue()) + { + this.pendingBlockEntitiesQueue.add(pos); + } + + BlockEntity be = world.getWorldChunk(pos).getBlockEntity(pos); + + if (be != null) + { + NbtCompound nbt = be.createNbtWithIdentifyingData(world.getRegistryManager()); + Pair pair = Pair.of(be, nbt); + + this.blockEntityCache.put(pos, Pair.of(System.currentTimeMillis(), pair)); + + return pair; + } } return null; } - public CompletableFuture syncBlockEntity(World world, BlockPos pos) + public @Nullable Pair requestEntity(int entityId) { - if (yesIAmOp.isPresent() && !yesIAmOp.get()) + if (this.entityCache.containsKey(entityId)) { - return CompletableFuture.completedFuture(null); + return this.entityCache.get(entityId).getRight(); + } + if (DataManager.getInstance().hasIntegratedServer() == false && + FeatureToggle.TWEAK_SERVER_DATA_SYNC.getBooleanValue()) + { + this.pendingEntitiesQueue.add(entityId); } - if (MinecraftClient.getInstance().isIntegratedServerRunning()) + if (this.getWorld() != null) { - BlockEntity blockEntity = MinecraftClient.getInstance().getServer().getWorld(world.getRegistryKey()).getWorldChunk(pos).getBlockEntity(pos, WorldChunk.CreationType.CHECK); - if (blockEntity != null) + Entity entity = this.getWorld().getEntityById(entityId); + NbtCompound nbt = new NbtCompound(); + + if (entity != null && entity.saveSelfNbt(nbt)) { - blockCache.put(pos, new Pair<>(blockEntity, System.currentTimeMillis())); - return CompletableFuture.completedFuture(blockEntity.createNbt(world.getRegistryManager())); + Pair pair = Pair.of(entity, nbt); + this.entityCache.put(entityId, Pair.of(System.currentTimeMillis(), pair)); + return pair; } } - Either posEither = Either.left(pos); - if (MinecraftClient.getInstance().getNetworkHandler() != null) + + return null; + } + + @Nullable + public Inventory getBlockInventory(World world, BlockPos pos, boolean useNbt) + { + if (this.blockEntityCache.containsKey(pos)) { - if (pendingQueriesByObject.containsKey(posEither)) + Inventory inv = null; + + if (useNbt) { - return pendingQueriesByObject.get(posEither); + inv = InventoryUtils.getNbtInventory(this.blockEntityCache.get(pos).getRight().getRight(), -1, world.getRegistryManager()); } + else + { + BlockEntity be = this.blockEntityCache.get(pos).getRight().getLeft(); - CompletableFuture future = new CompletableFuture<>(); - DataQueryHandler handler = MinecraftClient.getInstance().getNetworkHandler().getDataQueryHandler(); - handler.queryBlockNbt(pos, it -> { - }); - pendingQueriesByObject.put(posEither, future); - pendingQueriesById.put(((IMixinDataQueryHandler) handler).currentTransactionId(), future); - future.thenAccept(nbt -> { - pendingQueriesByObject.remove(posEither); - if (!clientWorld.isChunkLoaded(pos) || nbt == null) return; - BlockState state = clientWorld.getBlockState(pos); - - if (state.getBlock() instanceof BlockEntityProvider provider) + if (be instanceof Inventory inv1) { - var be = provider.createBlockEntity(pos, state); - if (be != null) + if (be instanceof ChestBlockEntity) + { + BlockState state = world.getBlockState(pos); + ChestType type = state.get(ChestBlock.CHEST_TYPE); + + if (type != ChestType.SINGLE) + { + BlockPos posAdj = pos.offset(ChestBlock.getFacing(state)); + if (!world.isChunkLoaded(posAdj)) return null; + BlockState stateAdj = world.getBlockState(posAdj); + + var dataAdj = this.getFromBlockEntityCache(posAdj); + + if (dataAdj == null) + { + this.requestBlockEntity(world, posAdj); + } + + if (stateAdj.getBlock() == state.getBlock() && + dataAdj instanceof ChestBlockEntity inv2 && + stateAdj.get(ChestBlock.CHEST_TYPE) != ChestType.SINGLE && + stateAdj.get(ChestBlock.FACING) == state.get(ChestBlock.FACING)) + { + Inventory invRight = type == ChestType.RIGHT ? inv1 : inv2; + Inventory invLeft = type == ChestType.RIGHT ? inv2 : inv1; + + inv = new DoubleInventory(invRight, invLeft); + } + } + else + { + inv = inv1; + } + } + else { - be.read(nbt, clientWorld.getRegistryManager()); - blockCache.put(pos, new Pair<>(be, System.currentTimeMillis())); + inv = inv1; } } - }); - if (yesIAmOp.isEmpty()) - { - yesIAmOp = Optional.of(false); } - return future; + if (inv != null) + { + return inv; + } } - else + + if (FeatureToggle.TWEAK_SERVER_DATA_SYNC.getBooleanValue()) { - throw new IllegalStateException("Not connected to a server"); + this.requestBlockEntity(world, pos); } + + return null; } - public CompletableFuture syncEntity(int networkId) + @Nullable + public Inventory getEntityInventory(int entityId, boolean useNbt) { - if (yesIAmOp.isPresent() && !yesIAmOp.get()) + if (this.entityCache.containsKey(entityId) && this.getWorld() != null) { - return CompletableFuture.completedFuture(null); - } + Inventory inv = null; - Either idEither = Either.right(networkId); - if (MinecraftClient.getInstance().getNetworkHandler() != null) - { - if (pendingQueriesByObject.containsKey(idEither)) + if (useNbt) { - return pendingQueriesByObject.get(idEither); + inv = InventoryUtils.getNbtInventory(this.entityCache.get(entityId).getRight().getRight(), -1, this.getWorld().getRegistryManager()); } + else + { + Entity entity = this.entityCache.get(entityId).getRight().getLeft(); - CompletableFuture future = new CompletableFuture<>(); - DataQueryHandler handler = MinecraftClient.getInstance().getNetworkHandler().getDataQueryHandler(); - handler.queryEntityNbt(networkId, it -> { - }); - pendingQueriesByObject.put(idEither, future); - pendingQueriesById.put(((IMixinDataQueryHandler) handler).currentTransactionId(), future); - future.thenAccept(nbt -> { - pendingQueriesByObject.remove(idEither); - if (nbt == null) return; - if (clientWorld.getEntityById(networkId) != null) + if (entity instanceof Inventory) { - Entity entity = clientWorld.getEntityById(networkId).getType().create(clientWorld); - if (entity != null) - { - entity.readNbt(nbt); - entityCache.put(networkId, new Pair<>(entity, System.currentTimeMillis())); - } + inv = (Inventory) entity; } - }); - if (yesIAmOp.isEmpty()) + else if (entity instanceof PlayerEntity player) + { + inv = new SimpleInventory(player.getInventory().main.toArray(new ItemStack[36])); + } + else if (entity instanceof VillagerEntity) + { + inv = ((VillagerEntity) entity).getInventory(); + } + else if (entity instanceof AbstractHorseEntity) + { + inv = ((IMixinAbstractHorseEntity) entity).tweakeroo_getHorseInventory(); + } + else if (entity instanceof PiglinEntity) + { + inv = ((IMixinPiglinEntity) entity).tweakeroo_inventory(); + } + } + + if (inv != null) { - yesIAmOp = Optional.of(false); + return inv; } + } + + if (FeatureToggle.TWEAK_SERVER_DATA_SYNC.getBooleanValue()) + { + this.requestEntity(entityId); + } + + return null; + } - return future; + private void requestQueryBlockEntity(BlockPos pos) + { + if (FeatureToggle.TWEAK_SERVER_DATA_SYNC_BACKUP.getBooleanValue() == false) + { + return; } - else + + ClientPlayNetworkHandler handler = this.getVanillaHandler(); + + if (handler != null) + { + handler.getDataQueryHandler().queryBlockNbt(pos, nbtCompound -> + { + handleBlockEntityData(pos, nbtCompound, null); + }); + this.transactionToBlockPosOrEntityId.put(((IMixinDataQueryHandler) handler.getDataQueryHandler()).currentTransactionId(), Either.left(pos)); + } + } + + private void requestQueryEntityData(int entityId) + { + if (FeatureToggle.TWEAK_SERVER_DATA_SYNC_BACKUP.getBooleanValue() == false) { - throw new IllegalStateException("Not connected to a server"); + return; + } + + ClientPlayNetworkHandler handler = this.getVanillaHandler(); + + if (handler != null) + { + handler.getDataQueryHandler().queryEntityNbt(entityId, nbtCompound -> + { + handleEntityData(entityId, nbtCompound); + }); + this.transactionToBlockPosOrEntityId.put(((IMixinDataQueryHandler) handler.getDataQueryHandler()).currentTransactionId(), Either.right(entityId)); } } - public @Nullable Entity getServerEntity(Entity entity) + private void requestServuxBlockEntityData(BlockPos pos) { - if (FeatureToggle.TWEAK_SERVER_DATA_SYNC.getBooleanValue() == false) + if (FeatureToggle.TWEAK_SERVER_DATA_SYNC.getBooleanValue()) { - return null; + HANDLER.encodeClientData(ServuxTweaksPacket.BlockEntityRequest(pos)); } - else if (yesIAmOp.isPresent() && !yesIAmOp.get()) + } + + private void requestServuxEntityData(int entityId) + { + if (FeatureToggle.TWEAK_SERVER_DATA_SYNC.getBooleanValue()) { - return null; + HANDLER.encodeClientData(ServuxTweaksPacket.EntityRequest(entityId)); } + } + + @Nullable + public BlockEntity handleBlockEntityData(BlockPos pos, NbtCompound nbt, @Nullable Identifier type) + { + this.pendingBlockEntitiesQueue.remove(pos); + if (nbt == null || this.getClientWorld() == null) return null; - Entity serverEntity = getCache(entity.getId()); - if (serverEntity == null) + BlockEntity blockEntity = this.getClientWorld().getBlockEntity(pos); + + if (blockEntity != null && (type == null || type.equals(BlockEntityType.getId(blockEntity.getType())))) { - syncEntity(entity.getId()); - return null; + if (nbt.contains(NbtKeys.ID, Constants.NBT.TAG_STRING) == false) + { + Identifier id = BlockEntityType.getId(blockEntity.getType()); + + if (id != null) + { + nbt.putString(NbtKeys.ID, id.toString()); + } + } + synchronized (this.blockEntityCache) + { + if (this.blockEntityCache.containsKey(pos)) + { + this.blockEntityCache.replace(pos, Pair.of(System.currentTimeMillis(), Pair.of(blockEntity, nbt))); + } + else + { + this.blockEntityCache.put(pos, Pair.of(System.currentTimeMillis(), Pair.of(blockEntity, nbt))); + } + } + + blockEntity.read(nbt, this.getClientWorld().getRegistryManager()); + return blockEntity; } - return serverEntity; + Optional>> opt = Registries.BLOCK_ENTITY_TYPE.getEntry(type); + + if (opt.isPresent()) + { + BlockEntityType beType = opt.get().value(); + + if (beType.supports(this.getClientWorld().getBlockState(pos))) + { + BlockEntity blockEntity2 = beType.instantiate(pos, this.getClientWorld().getBlockState(pos)); + + if (blockEntity2 != null) + { + if (nbt.contains(NbtKeys.ID, Constants.NBT.TAG_STRING) == false) + { + Identifier id = BlockEntityType.getId(beType); + + if (id != null) + { + nbt.putString(NbtKeys.ID, id.toString()); + } + } + synchronized (this.blockEntityCache) + { + if (this.blockEntityCache.containsKey(pos)) + { + this.blockEntityCache.replace(pos, Pair.of(System.currentTimeMillis(), Pair.of(blockEntity2, nbt))); + } + else + { + this.blockEntityCache.put(pos, Pair.of(System.currentTimeMillis(), Pair.of(blockEntity2, nbt))); + } + } + + return blockEntity2; + } + } + } + + return null; + } + + @Nullable + public Entity handleEntityData(int entityId, NbtCompound nbt) + { + this.pendingEntitiesQueue.remove(entityId); + if (nbt == null || this.getClientWorld() == null) return null; + Entity entity = this.getClientWorld().getEntityById(entityId); + + if (entity != null) + { + if (nbt.contains(NbtKeys.ID, Constants.NBT.TAG_STRING) == false) + { + Identifier id = EntityType.getId(entity.getType()); + + if (id != null) + { + nbt.putString(NbtKeys.ID, id.toString()); + } + } + synchronized (this.entityCache) + { + if (this.entityCache.containsKey(entityId)) + { + this.entityCache.replace(entityId, Pair.of(System.currentTimeMillis(), Pair.of(entity, nbt))); + } + else + { + this.entityCache.put(entityId, Pair.of(System.currentTimeMillis(), Pair.of(entity, nbt))); + } + } + } + return entity; + } + + public void handleBulkEntityData(int transactionId, NbtCompound nbt) + { + // todo } - public void recheckOpStatus() + public void handleVanillaQueryNbt(int transactionId, NbtCompound nbt) { - yesIAmOp = Optional.empty(); - pendingQueriesByObject.clear(); - pendingQueriesById.clear(); + Either either = this.transactionToBlockPosOrEntityId.remove(transactionId); + if (either != null) + { + either.ifLeft(pos -> handleBlockEntityData(pos, nbt, null)) + .ifRight(entityId -> handleEntityData(entityId, nbt)); + } } } diff --git a/src/main/java/fi/dy/masa/tweakeroo/event/RenderHandler.java b/src/main/java/fi/dy/masa/tweakeroo/event/RenderHandler.java index 204930689..a14330bc1 100644 --- a/src/main/java/fi/dy/masa/tweakeroo/event/RenderHandler.java +++ b/src/main/java/fi/dy/masa/tweakeroo/event/RenderHandler.java @@ -15,6 +15,7 @@ import fi.dy.masa.malilib.gui.GuiBase; import fi.dy.masa.malilib.interfaces.IRenderer; +import fi.dy.masa.malilib.render.InventoryOverlay; import fi.dy.masa.malilib.util.ActiveMode; import fi.dy.masa.malilib.util.Color4f; import fi.dy.masa.malilib.util.InventoryUtils; @@ -22,6 +23,7 @@ import fi.dy.masa.tweakeroo.config.FeatureToggle; import fi.dy.masa.tweakeroo.config.Hotkeys; import fi.dy.masa.tweakeroo.renderer.RenderUtils; +import fi.dy.masa.tweakeroo.util.RayTraceUtils; public class RenderHandler implements IRenderer { @@ -44,7 +46,14 @@ else if (FeatureToggle.TWEAK_HOTBAR_SCROLL.getBooleanValue() && if (FeatureToggle.TWEAK_INVENTORY_PREVIEW.getBooleanValue() && Hotkeys.INVENTORY_PREVIEW.getKeybind().isKeybindHeld()) { - RenderUtils.renderInventoryOverlay(mc, drawContext); + InventoryOverlay.Context context = RayTraceUtils.getTargetInventory(mc); + + if (context != null) + { + RenderUtils.renderInventoryOverlay(context, drawContext); + } + + //RenderUtils.renderInventoryOverlay(mc, drawContext); } if (FeatureToggle.TWEAK_PLAYER_INVENTORY_PEEK.getBooleanValue() && @@ -90,20 +99,28 @@ else if (stack.getComponents().contains(DataComponentTypes.CONTAINER) && Invento fi.dy.masa.malilib.render.RenderUtils.renderShulkerBoxPreview(stack, x, y, Configs.Generic.SHULKER_DISPLAY_BACKGROUND_COLOR.getBooleanValue(), drawContext); } } + else if (stack.getComponents().contains(DataComponentTypes.BUNDLE_CONTENTS) && InventoryUtils.bundleHasItems(stack)) + { + if (FeatureToggle.TWEAK_BUNDLE_DISPLAY.getBooleanValue() && + (Configs.Generic.BUNDLE_DISPLAY_REQUIRE_SHIFT.getBooleanValue() == false || GuiBase.isShiftDown())) + { + fi.dy.masa.malilib.render.RenderUtils.renderBundlePreview(stack, x, y, Configs.Generic.BUNDLE_DISPLAY_BACKGROUND_COLOR.getBooleanValue(), drawContext); + } + } } @Override - public void onRenderWorldLast(Matrix4f matrix4f, Matrix4f projMatrix) + public void onRenderWorldLast(Matrix4f posMatrix, Matrix4f projMatrix) { MinecraftClient mc = MinecraftClient.getInstance(); if (mc.player != null) { - this.renderOverlays(matrix4f, mc); + this.renderOverlays(posMatrix, mc); } } - private void renderOverlays(Matrix4f matrix4f, MinecraftClient mc) + private void renderOverlays(Matrix4f posMatrix, MinecraftClient mc) { Entity entity = mc.getCameraEntity(); @@ -130,7 +147,7 @@ private void renderOverlays(Matrix4f matrix4f, MinecraftClient mc) hitResult.getSide(), hitResult.getPos(), color, - matrix4f, + posMatrix, mc); RenderSystem.enableDepthTest(); diff --git a/src/main/java/fi/dy/masa/tweakeroo/event/WorldLoadListener.java b/src/main/java/fi/dy/masa/tweakeroo/event/WorldLoadListener.java index f468918f7..a74421d6f 100644 --- a/src/main/java/fi/dy/masa/tweakeroo/event/WorldLoadListener.java +++ b/src/main/java/fi/dy/masa/tweakeroo/event/WorldLoadListener.java @@ -5,6 +5,7 @@ import net.minecraft.client.world.ClientWorld; import fi.dy.masa.malilib.interfaces.IWorldLoadListener; import fi.dy.masa.tweakeroo.config.FeatureToggle; +import fi.dy.masa.tweakeroo.data.DataManager; import fi.dy.masa.tweakeroo.data.ServerDataSyncer; public class WorldLoadListener implements IWorldLoadListener @@ -14,11 +15,19 @@ public void onWorldLoadPre(@Nullable ClientWorld worldBefore, @Nullable ClientWo { // Always disable the Free Camera mode when leaving the world or switching dimensions FeatureToggle.TWEAK_FREE_CAMERA.setBooleanValue(false); + + if (worldAfter != null) + { + ServerDataSyncer.getInstance().onWorldPre(); + } } @Override public void onWorldLoadPost(@Nullable ClientWorld worldBefore, @Nullable ClientWorld worldAfter, MinecraftClient mc) { + DataManager.getInstance().reset(worldAfter == null); + ServerDataSyncer.getInstance().reset(worldAfter == null); + if (worldBefore == null) { if (FeatureToggle.TWEAK_GAMMA_OVERRIDE.getBooleanValue()) @@ -28,6 +37,10 @@ public void onWorldLoadPost(@Nullable ClientWorld worldBefore, @Nullable ClientW } } - ServerDataSyncer.resetInstance(); + // Logging in to a world or changing dimensions or respawning + if (worldAfter != null) + { + ServerDataSyncer.getInstance().onWorldJoin(); + } } } \ No newline at end of file diff --git a/src/main/java/fi/dy/masa/tweakeroo/mixin/IMixinDataQueryHandler.java b/src/main/java/fi/dy/masa/tweakeroo/mixin/IMixinDataQueryHandler.java index 41c6e2f05..0c9f17809 100644 --- a/src/main/java/fi/dy/masa/tweakeroo/mixin/IMixinDataQueryHandler.java +++ b/src/main/java/fi/dy/masa/tweakeroo/mixin/IMixinDataQueryHandler.java @@ -5,7 +5,8 @@ import org.spongepowered.asm.mixin.gen.Accessor; @Mixin(DataQueryHandler.class) -public interface IMixinDataQueryHandler { +public interface IMixinDataQueryHandler +{ @Accessor("expectedTransactionId") int currentTransactionId(); } diff --git a/src/main/java/fi/dy/masa/tweakeroo/mixin/IMixinPiglinEntity.java b/src/main/java/fi/dy/masa/tweakeroo/mixin/IMixinPiglinEntity.java new file mode 100644 index 000000000..68bdd8b6d --- /dev/null +++ b/src/main/java/fi/dy/masa/tweakeroo/mixin/IMixinPiglinEntity.java @@ -0,0 +1,13 @@ +package fi.dy.masa.tweakeroo.mixin; + +import net.minecraft.entity.mob.PiglinEntity; +import net.minecraft.inventory.SimpleInventory; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(PiglinEntity.class) +public interface IMixinPiglinEntity +{ + @Accessor("inventory") + SimpleInventory tweakeroo_inventory(); +} \ No newline at end of file diff --git a/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinBundleItem.java b/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinBundleItem.java new file mode 100644 index 000000000..6e5c0a2f3 --- /dev/null +++ b/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinBundleItem.java @@ -0,0 +1,35 @@ +package fi.dy.masa.tweakeroo.mixin; + +import java.util.Optional; + +import net.minecraft.item.BundleItem; +import net.minecraft.item.ItemStack; +import net.minecraft.item.tooltip.TooltipData; +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.CallbackInfoReturnable; + +import fi.dy.masa.malilib.gui.GuiBase; +import fi.dy.masa.tweakeroo.config.Configs; +import fi.dy.masa.tweakeroo.config.FeatureToggle; + +@Mixin(BundleItem.class) +public class MixinBundleItem +{ + @Inject(method = "getTooltipData", at = @At("HEAD"), cancellable = true) + private void tweakeroo_getTooltipData(ItemStack stack, CallbackInfoReturnable> cir) + { + if (FeatureToggle.TWEAK_BUNDLE_DISPLAY.getBooleanValue() && + Configs.Generic.BUNDLE_DISPLAY_REQUIRE_SHIFT.getBooleanValue() && + GuiBase.isShiftDown()) + { + cir.setReturnValue(Optional.empty()); + } + else if (FeatureToggle.TWEAK_BUNDLE_DISPLAY.getBooleanValue() && + !Configs.Generic.BUNDLE_DISPLAY_REQUIRE_SHIFT.getBooleanValue()) + { + cir.setReturnValue(Optional.empty()); + } + } +} diff --git a/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinClientPlayNetworkHandler.java b/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinClientPlayNetworkHandler.java index eadcab43b..02fcc764f 100644 --- a/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinClientPlayNetworkHandler.java +++ b/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinClientPlayNetworkHandler.java @@ -1,31 +1,26 @@ package fi.dy.masa.tweakeroo.mixin; -import com.llamalad7.mixinextras.sugar.Local; -import fi.dy.masa.tweakeroo.util.InventoryUtils; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientCommonNetworkHandler; import net.minecraft.client.network.ClientConnectionState; -import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.network.ClientConnection; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.network.packet.s2c.play.DeathMessageS2CPacket; import net.minecraft.network.packet.s2c.play.EntityStatusS2CPacket; +import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket; import net.minecraft.util.Hand; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.network.packet.CustomPayload; -import net.minecraft.network.packet.s2c.play.DeathMessageS2CPacket; -import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket; + import fi.dy.masa.tweakeroo.config.FeatureToggle; import fi.dy.masa.tweakeroo.data.DataManager; -import fi.dy.masa.tweakeroo.data.ServerDataSyncer; import fi.dy.masa.tweakeroo.tweaks.PlacementTweaks; import fi.dy.masa.tweakeroo.util.MiscUtils; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(ClientPlayNetworkHandler.class) public abstract class MixinClientPlayNetworkHandler extends ClientCommonNetworkHandler @@ -48,7 +43,7 @@ private void onHandleSetSlot(ScreenHandlerSlotUpdateS2CPacket packet, CallbackIn } @Inject(method = "onDeathMessage", at = @At(value = "INVOKE", // onCombatEvent - target = "Lnet/minecraft/client/MinecraftClient;setScreen(Lnet/minecraft/client/gui/screen/Screen;)V")) + target = "Lnet/minecraft/client/MinecraftClient;setScreen(Lnet/minecraft/client/gui/screen/Screen;)V")) private void onPlayerDeath(DeathMessageS2CPacket packetIn, CallbackInfo ci) { MinecraftClient mc = MinecraftClient.getInstance(); @@ -59,19 +54,6 @@ private void onPlayerDeath(DeathMessageS2CPacket packetIn, CallbackInfo ci) } } - @Inject( - method = "onCommandTree", - at = @At("RETURN") - ) - private void onCommandTree(CallbackInfo ci) - { - if (FeatureToggle.TWEAK_SERVER_DATA_SYNC.getBooleanValue()) - { - // when the player becomes OP, the server sends the command tree to the client - ServerDataSyncer.getInstance().recheckOpStatus(); - } - } - @Inject(method = "onCustomPayload", at = @At("HEAD")) private void tweakeroo_onCustomPayload(CustomPayload payload, CallbackInfo ci) { @@ -84,9 +66,10 @@ else if (payload.getId().id().equals(DataManager.SERVUX_ENTITY_DATA)) DataManager.getInstance().setHasServuxServer(true); } } + @Inject( - method = "onEntityStatus", - at = @At(value = "INVOKE", ordinal = 0, target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;getActiveTotemOfUndying(Lnet/minecraft/entity/player/PlayerEntity;)Lnet/minecraft/item/ItemStack;") + method = "onEntityStatus", + at = @At(value = "INVOKE", ordinal = 0, target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;getActiveTotemOfUndying(Lnet/minecraft/entity/player/PlayerEntity;)Lnet/minecraft/item/ItemStack;") ) private void onPlayerUseTotemOfUndying(EntityStatusS2CPacket packet, CallbackInfo ci) { diff --git a/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinDataQueryHandler.java b/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinDataQueryHandler.java index 41cc4b597..b7caac3c3 100644 --- a/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinDataQueryHandler.java +++ b/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinDataQueryHandler.java @@ -25,7 +25,7 @@ private void queryResponse(int transactionId, NbtCompound nbt, CallbackInfoRetur { if (FeatureToggle.TWEAK_SERVER_DATA_SYNC.getBooleanValue()) { - ServerDataSyncer.getInstance().handleQueryResponse(transactionId, nbt); + ServerDataSyncer.getInstance().handleVanillaQueryNbt(transactionId, nbt); } } } diff --git a/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinHopperBlockEntity.java b/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinHopperBlockEntity.java new file mode 100644 index 000000000..fd03b1fa8 --- /dev/null +++ b/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinHopperBlockEntity.java @@ -0,0 +1,49 @@ +package fi.dy.masa.tweakeroo.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; + +import net.minecraft.block.entity.HopperBlockEntity; +import net.minecraft.item.ItemStack; +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.CallbackInfoReturnable; + +import fi.dy.masa.tweakeroo.util.MiscUtils; + +/** + * ... by KikuGie + * Priority 999 if installed with stackable-shulkers-fix + */ +@Mixin(value = HopperBlockEntity.class, priority = 999) +public class MixinHopperBlockEntity +{ + @WrapOperation( + method = "isFull", + at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;getMaxCount()I") + ) + private int modifyShulkerMaxCount(ItemStack instance, Operation original) + { + return MiscUtils.isShulkerBox(instance) ? instance.getCount() : original.call(instance); + } + + @WrapOperation( + method = "isInventoryFull", + at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;getMaxCount()I") + ) + private static int modifyShulkerMaxCountStatic(ItemStack instance, Operation original) + { + return MiscUtils.isShulkerBox(instance) ? 1 : original.call(instance); + } + + @Inject( + method = "canMergeItems", + at = @At("HEAD"), + cancellable = true + ) + private static void cancelItemMerging(ItemStack first, ItemStack second, CallbackInfoReturnable cir) + { + if (MiscUtils.isShulkerBox(first) || MiscUtils.isShulkerBox(second)) cir.setReturnValue(false); + } +} \ No newline at end of file diff --git a/src/main/java/fi/dy/masa/tweakeroo/network/ServuxTweaksHandler.java b/src/main/java/fi/dy/masa/tweakeroo/network/ServuxTweaksHandler.java new file mode 100644 index 000000000..b2a6b688c --- /dev/null +++ b/src/main/java/fi/dy/masa/tweakeroo/network/ServuxTweaksHandler.java @@ -0,0 +1,177 @@ +package fi.dy.masa.tweakeroo.network; + +import io.netty.buffer.Unpooled; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtSizeTracker; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.util.Identifier; +import net.minecraft.util.Util; +import net.minecraft.util.math.random.Random; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; + +import fi.dy.masa.malilib.network.IClientPayloadData; +import fi.dy.masa.malilib.network.IPluginClientPlayHandler; +import fi.dy.masa.malilib.network.PacketSplitter; +import fi.dy.masa.tweakeroo.Tweakeroo; +import fi.dy.masa.tweakeroo.data.ServerDataSyncer; + +@Environment(EnvType.CLIENT) +public abstract class ServuxTweaksHandler implements IPluginClientPlayHandler +{ + private static final ServuxTweaksHandler INSTANCE = new ServuxTweaksHandler<>() { + @Override + public void receive(ServuxTweaksPacket.Payload payload, ClientPlayNetworking.Context context) + { + ServuxTweaksHandler.INSTANCE.receivePlayPayload(payload, context); + } + }; + public static ServuxTweaksHandler getInstance() { return INSTANCE; } + + public static final Identifier CHANNEL_ID = Identifier.of("servux", "tweaks"); + + private boolean servuxRegistered; + private boolean payloadRegistered = false; + private int failures = 0; + private static final int MAX_FAILURES = 4; + private long readingSessionKey = -1; + + + @Override + public Identifier getPayloadChannel() { return CHANNEL_ID; } + + @Override + public boolean isPlayRegistered(Identifier channel) + { + if (channel.equals(CHANNEL_ID)) + { + return payloadRegistered; + } + + return false; + } + + @Override + public void setPlayRegistered(Identifier channel) + { + if (channel.equals(CHANNEL_ID)) + { + this.payloadRegistered = true; + } + } + + @Override + public

void decodeClientData(Identifier channel, P data) + { + ServuxTweaksPacket packet = (ServuxTweaksPacket) data; + + if (!channel.equals(CHANNEL_ID)) + { + return; + } + switch (packet.getType()) + { + case PACKET_S2C_METADATA -> + { + if (ServerDataSyncer.getInstance().receiveServuxMetadata(packet.getCompound())) + { + this.servuxRegistered = true; + } + } + case PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE -> ServerDataSyncer.getInstance().handleBlockEntityData(packet.getPos(), packet.getCompound(), null); + case PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE -> ServerDataSyncer.getInstance().handleEntityData(packet.getEntityId(), packet.getCompound()); + case PACKET_S2C_NBT_RESPONSE_DATA -> + { + if (this.readingSessionKey == -1) + { + this.readingSessionKey = Random.create(Util.getMeasuringTimeMs()).nextLong(); + } + + //Tweakeroo.printDebug("ServuxEntitiesHandler#decodeClientData(): received Entity Data Packet Slice of size {} (in bytes) // reading session key [{}]", packet.getTotalSize(), this.readingSessionKey); + PacketByteBuf fullPacket = PacketSplitter.receive(this, this.readingSessionKey, packet.getBuffer()); + + if (fullPacket != null) + { + try + { + this.readingSessionKey = -1; + ServerDataSyncer.getInstance().handleBulkEntityData(fullPacket.readVarInt(), (NbtCompound) fullPacket.readNbt(NbtSizeTracker.ofUnlimitedBytes())); + } + catch (Exception e) + { + Tweakeroo.logger.error("ServuxEntitiesHandler#decodeClientData(): Entity Data: error reading fullBuffer [{}]", e.getLocalizedMessage()); + } + } + } + default -> Tweakeroo.logger.warn("ServuxEntitiesHandler#decodeClientData(): received unhandled packetType {} of size {} bytes.", packet.getPacketType(), packet.getTotalSize()); + } + } + + @Override + public void reset(Identifier channel) + { + if (channel.equals(CHANNEL_ID) && this.servuxRegistered) + { + this.servuxRegistered = false; + this.failures = 0; + this.readingSessionKey = -1; + } + } + + public void resetFailures(Identifier channel) + { + if (channel.equals(CHANNEL_ID) && this.failures > 0) + { + this.failures = 0; + } + } + + @Override + public void receivePlayPayload(T payload, ClientPlayNetworking.Context ctx) + { + if (payload.getId().id().equals(CHANNEL_ID)) + { + ServuxTweaksHandler.INSTANCE.decodeClientData(CHANNEL_ID, ((ServuxTweaksPacket.Payload) payload).data()); + } + } + + @Override + public void encodeWithSplitter(PacketByteBuf buffer, ClientPlayNetworkHandler handler) + { + // Send each PacketSplitter buffer slice + ServuxTweaksHandler.INSTANCE.sendPlayPayload(new ServuxTweaksPacket.Payload(ServuxTweaksPacket.ResponseS2CData(buffer))); + } + + @Override + public

void encodeClientData(P data) + { + ServuxTweaksPacket packet = (ServuxTweaksPacket) data; + + if (packet.getType().equals(ServuxTweaksPacket.Type.PACKET_C2S_NBT_RESPONSE_START)) + { + PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer()); + buffer.writeVarInt(packet.getTransactionId()); + buffer.writeNbt(packet.getCompound()); + PacketSplitter.send(this, buffer, MinecraftClient.getInstance().getNetworkHandler()); + } + else if (!ServuxTweaksHandler.INSTANCE.sendPlayPayload(new ServuxTweaksPacket.Payload(packet))) + { + if (this.failures > MAX_FAILURES) + { + Tweakeroo.printDebug("encodeClientData(): encountered [{}] sendPayload failures, cancelling any Servux join attempt(s)", MAX_FAILURES); + this.servuxRegistered = false; + ServuxTweaksHandler.INSTANCE.unregisterPlayReceiver(); + ServerDataSyncer.getInstance().onPacketFailure(); + } + else + { + this.failures++; + } + } + } +} diff --git a/src/main/java/fi/dy/masa/tweakeroo/network/ServuxTweaksPacket.java b/src/main/java/fi/dy/masa/tweakeroo/network/ServuxTweaksPacket.java new file mode 100644 index 000000000..d3b15300c --- /dev/null +++ b/src/main/java/fi/dy/masa/tweakeroo/network/ServuxTweaksPacket.java @@ -0,0 +1,480 @@ +package fi.dy.masa.tweakeroo.network; + +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import io.netty.buffer.Unpooled; + +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtSizeTracker; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; + +import fi.dy.masa.malilib.network.IClientPayloadData; +import fi.dy.masa.tweakeroo.Tweakeroo; + +public class ServuxTweaksPacket implements IClientPayloadData +{ + private Type packetType; + private int transactionId; + private int entityId; + private BlockPos pos; + private NbtCompound nbt; + private PacketByteBuf buffer; + private List requestingChunks; + public static final int PROTOCOL_VERSION = 1; + + private ServuxTweaksPacket(Type type) + { + this.packetType = type; + this.transactionId = -1; + this.entityId = -1; + this.pos = BlockPos.ORIGIN; + this.nbt = new NbtCompound(); + this.clearPacket(); + } + + public static ServuxTweaksPacket MetadataRequest(@Nullable NbtCompound nbt) + { + var packet = new ServuxTweaksPacket(Type.PACKET_C2S_METADATA_REQUEST); + if (nbt != null) + { + packet.nbt.copyFrom(nbt); + } + return packet; + } + + public static ServuxTweaksPacket MetadataResponse(@Nullable NbtCompound nbt) + { + var packet = new ServuxTweaksPacket(Type.PACKET_S2C_METADATA); + if (nbt != null) + { + packet.nbt.copyFrom(nbt); + } + return packet; + } + + // Entity simple response + public static ServuxTweaksPacket SimpleEntityResponse(int entityId, @Nullable NbtCompound nbt) + { + var packet = new ServuxTweaksPacket(Type.PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE); + if (nbt != null) + { + packet.nbt.copyFrom(nbt); + } + packet.entityId = entityId; + return packet; + } + + public static ServuxTweaksPacket SimpleBlockResponse(BlockPos pos, @Nullable NbtCompound nbt) + { + var packet = new ServuxTweaksPacket(Type.PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE); + if (nbt != null) + { + packet.nbt.copyFrom(nbt); + } + packet.pos = pos.toImmutable(); + return packet; + } + + public static ServuxTweaksPacket BlockEntityRequest(BlockPos pos) + { + var packet = new ServuxTweaksPacket(Type.PACKET_C2S_BLOCK_ENTITY_REQUEST); + packet.pos = pos.toImmutable(); + return packet; + } + + public static ServuxTweaksPacket EntityRequest(int entityId) + { + var packet = new ServuxTweaksPacket(Type.PACKET_C2S_ENTITY_REQUEST); + packet.entityId = entityId; + return packet; + } + + // Nbt Packet, using Packet Splitter + public static ServuxTweaksPacket ResponseS2CStart(@Nonnull NbtCompound nbt) + { + var packet = new ServuxTweaksPacket(Type.PACKET_S2C_NBT_RESPONSE_START); + packet.nbt.copyFrom(nbt); + return packet; + } + + public static ServuxTweaksPacket ResponseS2CData(@Nonnull PacketByteBuf buffer) + { + var packet = new ServuxTweaksPacket(Type.PACKET_S2C_NBT_RESPONSE_DATA); + packet.buffer = buffer; + packet.nbt = new NbtCompound(); + return packet; + } + + public static ServuxTweaksPacket ResponseC2SStart(@Nonnull NbtCompound nbt) + { + var packet = new ServuxTweaksPacket(Type.PACKET_C2S_NBT_RESPONSE_START); + packet.nbt.copyFrom(nbt); + return packet; + } + + public static ServuxTweaksPacket ResponseC2SData(@Nonnull PacketByteBuf buffer) + { + var packet = new ServuxTweaksPacket(Type.PACKET_C2S_NBT_RESPONSE_DATA); + packet.buffer = buffer; + packet.nbt = new NbtCompound(); + return packet; + } + + private void clearPacket() + { + if (this.buffer != null) + { + this.buffer.clear(); + this.buffer = new PacketByteBuf(Unpooled.buffer()); + } + } + + @Override + public int getVersion() + { + return PROTOCOL_VERSION; + } + + @Override + public int getPacketType() + { + return this.packetType.get(); + } + + @Override + public int getTotalSize() + { + int total = 2; + + if (this.nbt != null && !this.nbt.isEmpty()) + { + total += this.nbt.getSizeInBytes(); + } + if (this.buffer != null) + { + total += this.buffer.readableBytes(); + } + + return total; + } + + public Type getType() + { + return this.packetType; + } + + public void setTransactionId(int id) + { + this.transactionId = id; + } + + public int getTransactionId() + { + return this.transactionId; + } + + public int getEntityId() { return this.entityId; } + + public BlockPos getPos() { return this.pos; } + + public NbtCompound getCompound() + { + return this.nbt; + } + + public PacketByteBuf getBuffer() + { + return this.buffer; + } + + public boolean hasBuffer() { return this.buffer != null && this.buffer.isReadable(); } + + public boolean hasNbt() { return this.nbt != null && !this.nbt.isEmpty(); } + + @Override + public boolean isEmpty() + { + return !this.hasBuffer() && !this.hasNbt(); + } + + @Override + public void toPacket(PacketByteBuf output) + { + output.writeVarInt(this.packetType.get()); + + switch (this.packetType) + { + case PACKET_C2S_BLOCK_ENTITY_REQUEST -> + { + // Write BE Request + try + { + output.writeVarInt(this.transactionId); + output.writeBlockPos(this.pos); + } + catch (Exception e) + { + Tweakeroo.logger.error("ServuxTweaksPacket#toPacket: error writing Block Entity Request to packet: [{}]", e.getLocalizedMessage()); + } + } + case PACKET_C2S_ENTITY_REQUEST -> + { + // Write Entity Request + try + { + output.writeVarInt(this.transactionId); + output.writeVarInt(this.entityId); + } + catch (Exception e) + { + Tweakeroo.logger.error("ServuxTweaksPacket#toPacket: error writing Entity Request to packet: [{}]", e.getLocalizedMessage()); + } + } + case PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE -> + { + try + { + output.writeBlockPos(this.pos); + output.writeNbt(this.nbt); + } + catch (Exception e) + { + Tweakeroo.logger.error("ServuxTweaksPacket#toPacket: error writing Block Entity Response to packet: [{}]", e.getLocalizedMessage()); + } + } + case PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE -> + { + try + { + output.writeVarInt(this.entityId); + output.writeNbt(this.nbt); + } + catch (Exception e) + { + Tweakeroo.logger.error("ServuxTweaksPacket#toPacket: error writing Entity Response to packet: [{}]", e.getLocalizedMessage()); + } + } + case PACKET_S2C_NBT_RESPONSE_DATA, PACKET_C2S_NBT_RESPONSE_DATA -> + { + // Write Packet Buffer (Slice) + try + { + output.writeBytes(this.buffer.readBytes(this.buffer.readableBytes())); + } + catch (Exception e) + { + Tweakeroo.logger.error("ServuxTweaksPacket#toPacket: error writing buffer data to packet: [{}]", e.getLocalizedMessage()); + } + } + case PACKET_C2S_METADATA_REQUEST, PACKET_S2C_METADATA -> + { + // Write NBT + try + { + output.writeNbt(this.nbt); + } + catch (Exception e) + { + Tweakeroo.logger.error("ServuxTweaksPacket#toPacket: error writing NBT to packet: [{}]", e.getLocalizedMessage()); + } + } + default -> Tweakeroo.logger.error("ServuxTweaksPacket#toPacket: Unknown packet type!"); + } + } + + @Nullable + public static ServuxTweaksPacket fromPacket(PacketByteBuf input) + { + int i = input.readVarInt(); + Type type = getType(i); + + if (type == null) + { + // Invalid Type + Tweakeroo.logger.warn("ServuxTweaksPacket#fromPacket: invalid packet type received"); + return null; + } + switch (type) + { + case PACKET_C2S_BLOCK_ENTITY_REQUEST -> + { + // Read Packet Buffer + try + { + input.readVarInt(); // todo: old code compat + return ServuxTweaksPacket.BlockEntityRequest(input.readBlockPos()); + } + catch (Exception e) + { + Tweakeroo.logger.error("ServuxTweaksPacket#fromPacket: error reading Block Entity Request from packet: [{}]", e.getLocalizedMessage()); + } + } + case PACKET_C2S_ENTITY_REQUEST -> + { + // Read Packet Buffer + try + { + input.readVarInt(); // todo: old code compat + return ServuxTweaksPacket.EntityRequest(input.readVarInt()); + } + catch (Exception e) + { + Tweakeroo.logger.error("ServuxTweaksPacket#fromPacket: error reading Entity Request from packet: [{}]", e.getLocalizedMessage()); + } + } + case PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE -> + { + try + { + return ServuxTweaksPacket.SimpleBlockResponse(input.readBlockPos(), (NbtCompound) input.readNbt(NbtSizeTracker.ofUnlimitedBytes())); + } + catch (Exception e) + { + Tweakeroo.logger.error("ServuxTweaksPacket#fromPacket: error reading Block Entity Response from packet: [{}]", e.getLocalizedMessage()); + } + } + case PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE -> + { + try + { + return ServuxTweaksPacket.SimpleEntityResponse(input.readVarInt(), (NbtCompound) input.readNbt(NbtSizeTracker.ofUnlimitedBytes())); + } + catch (Exception e) + { + Tweakeroo.logger.error("ServuxTweaksPacket#fromPacket: error reading Entity Response from packet: [{}]", e.getLocalizedMessage()); + } + } + case PACKET_S2C_NBT_RESPONSE_DATA -> + { + // Read Packet Buffer Slice + try + { + return ServuxTweaksPacket.ResponseS2CData(new PacketByteBuf(input.readBytes(input.readableBytes()))); + } + catch (Exception e) + { + Tweakeroo.logger.error("ServuxTweaksPacket#fromPacket: error reading S2C Bulk Response Buffer from packet: [{}]", e.getLocalizedMessage()); + } + } + case PACKET_C2S_NBT_RESPONSE_DATA -> + { + // Read Packet Buffer Slice + try + { + return ServuxTweaksPacket.ResponseC2SData(new PacketByteBuf(input.readBytes(input.readableBytes()))); + } + catch (Exception e) + { + Tweakeroo.logger.error("ServuxTweaksPacket#fromPacket: error reading C2S Bulk Response Buffer from packet: [{}]", e.getLocalizedMessage()); + } + } + case PACKET_C2S_METADATA_REQUEST -> + { + // Read Nbt + try + { + return ServuxTweaksPacket.MetadataRequest(input.readNbt()); + } + catch (Exception e) + { + Tweakeroo.logger.error("ServuxTweaksPacket#fromPacket: error reading Metadata Request from packet: [{}]", e.getLocalizedMessage()); + } + } + case PACKET_S2C_METADATA -> + { + // Read Nbt + try + { + return ServuxTweaksPacket.MetadataResponse(input.readNbt()); + } + catch (Exception e) + { + Tweakeroo.logger.error("ServuxTweaksPacket#fromPacket: error reading Metadata Response from packet: [{}]", e.getLocalizedMessage()); + } + } + default -> Tweakeroo.logger.error("ServuxTweaksPacket#fromPacket: Unknown packet type!"); + } + + return null; + } + + @Override + public void clear() + { + if (this.nbt != null && !this.nbt.isEmpty()) + { + this.nbt = new NbtCompound(); + } + this.clearPacket(); + this.transactionId = -1; + this.entityId = -1; + this.pos = BlockPos.ORIGIN; + this.packetType = null; + } + + @Nullable + public static Type getType(int input) + { + for (Type type : Type.values()) + { + if (type.get() == input) + { + return type; + } + } + + return null; + } + + public enum Type + { + PACKET_S2C_METADATA(1), + PACKET_C2S_METADATA_REQUEST(2), + PACKET_C2S_BLOCK_ENTITY_REQUEST(3), + PACKET_C2S_ENTITY_REQUEST(4), + PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE(5), + PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE(6), + // For Packet Splitter (Oversize Packets, S2C) + PACKET_S2C_NBT_RESPONSE_START(10), + PACKET_S2C_NBT_RESPONSE_DATA(11), + // For Packet Splitter (Oversize Packets, C2S) + PACKET_C2S_NBT_RESPONSE_START(12), + PACKET_C2S_NBT_RESPONSE_DATA(13); + + private final int type; + + Type(int type) + { + this.type = type; + } + + int get() { return this.type; } + } + + public record Payload(ServuxTweaksPacket data) implements CustomPayload + { + public static final Id ID = new Id<>(ServuxTweaksHandler.CHANNEL_ID); + public static final PacketCodec CODEC = CustomPayload.codecOf(Payload::write, Payload::new); + + public Payload(PacketByteBuf input) + { + this(fromPacket(input)); + } + + private void write(PacketByteBuf output) + { + data.toPacket(output); + } + + @Override + public Id getId() + { + return ID; + } + } +} diff --git a/src/main/java/fi/dy/masa/tweakeroo/renderer/RenderUtils.java b/src/main/java/fi/dy/masa/tweakeroo/renderer/RenderUtils.java index 6db0345a4..6656691e6 100644 --- a/src/main/java/fi/dy/masa/tweakeroo/renderer/RenderUtils.java +++ b/src/main/java/fi/dy/masa/tweakeroo/renderer/RenderUtils.java @@ -1,12 +1,14 @@ package fi.dy.masa.tweakeroo.renderer; +import java.util.HashSet; import java.util.Set; import org.joml.Matrix4f; import org.joml.Matrix4fStack; + import com.mojang.blaze3d.systems.RenderSystem; -import net.minecraft.block.Block; -import net.minecraft.block.BlockEntityProvider; import net.minecraft.block.ShulkerBoxBlock; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.CrafterBlockEntity; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; @@ -25,23 +27,15 @@ import net.minecraft.inventory.Inventory; import net.minecraft.inventory.SimpleInventory; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; import net.minecraft.registry.entry.RegistryEntry; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.hit.BlockHitResult; -import net.minecraft.util.hit.EntityHitResult; -import net.minecraft.util.hit.HitResult; -import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; -import net.minecraft.world.World; + import fi.dy.masa.malilib.render.InventoryOverlay; -import fi.dy.masa.malilib.util.EntityUtils; +import fi.dy.masa.malilib.util.BlockUtils; import fi.dy.masa.malilib.util.GuiUtils; import fi.dy.masa.tweakeroo.config.Configs; -import fi.dy.masa.tweakeroo.config.FeatureToggle; -import fi.dy.masa.tweakeroo.data.ServerDataSyncer; -import fi.dy.masa.tweakeroo.mixin.IMixinAbstractHorseEntity; import fi.dy.masa.tweakeroo.util.MiscUtils; -import fi.dy.masa.tweakeroo.util.RayTraceUtils; import fi.dy.masa.tweakeroo.util.SnapAimMode; public class RenderUtils @@ -122,6 +116,7 @@ public static void renderHotbarSwapOverlay(MinecraftClient mc, DrawContext drawC } } + /* public static void renderInventoryOverlay(MinecraftClient mc, DrawContext drawContext) { World world = fi.dy.masa.malilib.util.WorldUtils.getBestWorld(mc); @@ -285,6 +280,151 @@ else if (entity instanceof AbstractHorseEntity) fi.dy.masa.malilib.render.InventoryOverlay.renderEquipmentStacks(entityLivingBase, x, y, mc, drawContext); } } + */ + + public static void renderInventoryOverlay(InventoryOverlay.Context context, DrawContext drawContext) + { + MinecraftClient mc = MinecraftClient.getInstance(); + LivingEntity entityLivingBase = null; + BlockEntity be = null; + Inventory inv = null; + NbtCompound nbt = new NbtCompound(); + + if (context.be() != null) + { + be = context.be(); + } + else if (context.entity() != null) + { + if (context.entity() instanceof LivingEntity) + { + entityLivingBase = context.entity(); + } + } + if (context.inv() != null) + { + inv = context.inv(); + } + if (context.nbt() != null) + { + nbt.copyFrom(context.nbt()); + } + + //Tweakeroo.logger.error("render: ctx-type [{}], inv [{}], raw Nbt [{}]", context.type().toString(), inv != null ? inv.size() : "null", nbt.isEmpty() ? "empty" : nbt.toString()); + + final boolean isWolf = (entityLivingBase instanceof WolfEntity); + final int xCenter = GuiUtils.getScaledWindowWidth() / 2; + final int yCenter = GuiUtils.getScaledWindowHeight() / 2; + int x = xCenter - 52 / 2; + int y = yCenter - 92; + if (inv != null && inv.size() > 0) + { + final boolean isHorse = (entityLivingBase instanceof AbstractHorseEntity); + final int totalSlots = isHorse ? inv.size() - 1 : inv.size(); + final int firstSlot = isHorse ? 1 : 0; + + InventoryOverlay.InventoryRenderType type = (entityLivingBase instanceof VillagerEntity) ? InventoryOverlay.InventoryRenderType.VILLAGER : InventoryOverlay.getBestInventoryType(inv, nbt, context); + final InventoryOverlay.InventoryProperties props = InventoryOverlay.getInventoryPropsTemp(type, totalSlots); + final int rows = (int) Math.ceil((double) totalSlots / props.slotsPerRow); + Set lockedSlots = new HashSet<>(); + int xInv = xCenter - (props.width / 2); + int yInv = yCenter - props.height - 6; + + if (rows > 6) + { + yInv -= (rows - 6) * 18; + y -= (rows - 6) * 18; + } + if (entityLivingBase != null) + { + x = xCenter - 55; + xInv = xCenter + 2; + yInv = Math.min(yInv, yCenter - 92); + } + + if (be != null && type == InventoryOverlay.InventoryRenderType.CRAFTER) + { + if (be instanceof CrafterBlockEntity cbe) + { + lockedSlots = BlockUtils.getDisabledSlots(cbe); + } + else if (context.nbt() != null) + { + lockedSlots = BlockUtils.getDisabledSlotsFromNbt(context.nbt()); + } + } + + //Tweakeroo.logger.warn("renderInventoryOverlay: type [{}] // Nbt Type [{}]", type.toString(), context.nbt() != null ? InventoryOverlay.getInventoryType(context.nbt()) : "INVALID"); + + if (context.be() != null && context.be().getCachedState().getBlock() instanceof ShulkerBoxBlock sbb) + { + fi.dy.masa.malilib.render.RenderUtils.setShulkerboxBackgroundTintColor(sbb, Configs.Generic.SHULKER_DISPLAY_BACKGROUND_COLOR.getBooleanValue()); + } + + if (isHorse) + { + Inventory horseInv = new SimpleInventory(2); + ItemStack horseArmor = (((AbstractHorseEntity) entityLivingBase).getBodyArmor()); + horseInv.setStack(0, horseArmor != null && !horseArmor.isEmpty() ? horseArmor : ItemStack.EMPTY); + horseInv.setStack(1, inv.getStack(0)); + + InventoryOverlay.renderInventoryBackground(type, xInv, yInv, 1, 2, mc); + /* + if (type == InventoryOverlay.InventoryRenderType.LLAMA) + { + InventoryOverlay.renderLlamaArmorBackgroundSlots(horseInv, xInv + props.slotOffsetX, yInv + props.slotOffsetY, drawContext); + } + else + { + InventoryOverlay.renderHorseArmorBackgroundSlots(horseInv, xInv + props.slotOffsetX, yInv + props.slotOffsetY, drawContext); + } + */ + InventoryOverlay.renderInventoryStacks(type, horseInv, xInv + props.slotOffsetX, yInv + props.slotOffsetY, 1, 0, 2, mc, drawContext); + xInv += 32 + 4; + } + + if (totalSlots > 0) + { + InventoryOverlay.renderInventoryBackground(type, xInv, yInv, props.slotsPerRow, totalSlots, mc); + /* + if (type == InventoryOverlay.InventoryRenderType.BREWING_STAND) + { + InventoryOverlay.renderBrewerBackgroundSlots(inv, xInv, yInv, drawContext); + } + */ + InventoryOverlay.renderInventoryStacks(type, inv, xInv + props.slotOffsetX, yInv + props.slotOffsetY, props.slotsPerRow, firstSlot, totalSlots, lockedSlots, mc, drawContext); + } + } + + if (isWolf) + { + InventoryOverlay.InventoryRenderType type = InventoryOverlay.InventoryRenderType.HORSE; + final InventoryOverlay.InventoryProperties props = InventoryOverlay.getInventoryPropsTemp(type, 2); + final int rows = (int) Math.ceil((double) 2 / props.slotsPerRow); + int xInv; + int yInv = yCenter - props.height - 6; + if (rows > 6) + { + yInv -= (rows - 6) * 18; + y -= (rows - 6) * 18; + } + x = xCenter - 55; + xInv = xCenter + 2; + yInv = Math.min(yInv, yCenter - 92); + Inventory wolfInv = new SimpleInventory(2); + ItemStack wolfArmor = ((WolfEntity) entityLivingBase).getBodyArmor(); + wolfInv.setStack(0, wolfArmor != null && !wolfArmor.isEmpty() ? wolfArmor : ItemStack.EMPTY); + InventoryOverlay.renderInventoryBackground(type, xInv, yInv, 1, 2, mc); + //InventoryOverlay.renderWolfArmorBackgroundSlots(wolfInv, xInv + props.slotOffsetX, yInv + props.slotOffsetY, drawContext); + InventoryOverlay.renderInventoryStacks(type, wolfInv, xInv + props.slotOffsetX, yInv + props.slotOffsetY, 1, 0, 2, mc, drawContext); + } + + if (entityLivingBase != null) + { + InventoryOverlay.renderEquipmentOverlayBackground(x, y, entityLivingBase, drawContext); + InventoryOverlay.renderEquipmentStacks(entityLivingBase, x, y, mc, drawContext); + } + } public static void renderPlayerInventoryOverlay(MinecraftClient mc, DrawContext drawContext) { diff --git a/src/main/java/fi/dy/masa/tweakeroo/tweaks/PlacementTweaks.java b/src/main/java/fi/dy/masa/tweakeroo/tweaks/PlacementTweaks.java index c784d6a25..d20a4114a 100644 --- a/src/main/java/fi/dy/masa/tweakeroo/tweaks/PlacementTweaks.java +++ b/src/main/java/fi/dy/masa/tweakeroo/tweaks/PlacementTweaks.java @@ -1142,6 +1142,7 @@ private static BlockPos getPlacementPositionForTargetedPosition(World world, Blo return pos.offset(side); } + @SuppressWarnings({"deprecation"}) private static boolean canPlaceBlockIntoPosition(World world, BlockPos pos, ItemPlacementContext useContext) { BlockState state = world.getBlockState(pos); diff --git a/src/main/java/fi/dy/masa/tweakeroo/util/MiscUtils.java b/src/main/java/fi/dy/masa/tweakeroo/util/MiscUtils.java index 1d5e40842..8fad11794 100644 --- a/src/main/java/fi/dy/masa/tweakeroo/util/MiscUtils.java +++ b/src/main/java/fi/dy/masa/tweakeroo/util/MiscUtils.java @@ -5,30 +5,43 @@ import java.awt.image.BufferedImage; import java.io.File; import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.function.Predicate; +import java.util.regex.Matcher; import java.util.regex.Pattern; + +import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.MapColor; +import net.minecraft.block.ShulkerBoxBlock; import net.minecraft.block.entity.CommandBlockBlockEntity; import net.minecraft.block.entity.SignBlockEntity; import net.minecraft.block.entity.SignText; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.ingame.AbstractSignEditScreen; +import net.minecraft.client.gui.screen.world.CustomizeFlatLevelScreen; import net.minecraft.client.input.Input; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.option.GameOptions; +import net.minecraft.client.world.GeneratorOptionsHolder; +import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.MapIdComponent; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.passive.TameableEntity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.BlockItem; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; import net.minecraft.item.map.MapState; +import net.minecraft.registry.*; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.resource.featuretoggle.FeatureSet; +import net.minecraft.structure.StructureSet; import net.minecraft.text.*; import net.minecraft.util.ActionResult; import net.minecraft.util.Hand; +import net.minecraft.util.Identifier; import net.minecraft.util.Util; import net.minecraft.util.hit.EntityHitResult; import net.minecraft.util.math.BlockPos; @@ -36,6 +49,12 @@ import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.biome.BiomeKeys; +import net.minecraft.world.gen.chunk.FlatChunkGeneratorConfig; +import net.minecraft.world.gen.chunk.FlatChunkGeneratorLayer; +import net.minecraft.world.gen.feature.PlacedFeature; + import fi.dy.masa.malilib.gui.GuiBase; import fi.dy.masa.malilib.gui.Message; import fi.dy.masa.malilib.util.FileUtils; @@ -47,11 +66,9 @@ import fi.dy.masa.tweakeroo.config.Configs; import fi.dy.masa.tweakeroo.config.FeatureToggle; import fi.dy.masa.tweakeroo.config.Hotkeys; -import fi.dy.masa.tweakeroo.mixin.IMixinAxeItem; -import fi.dy.masa.tweakeroo.mixin.IMixinClientWorld; -import fi.dy.masa.tweakeroo.mixin.IMixinCommandBlockExecutor; -import fi.dy.masa.tweakeroo.mixin.IMixinShovelItem; +import fi.dy.masa.tweakeroo.mixin.*; import fi.dy.masa.tweakeroo.renderer.RenderUtils; +import fi.dy.masa.tweakeroo.tweaks.MiscTweaks; public class MiscUtils { @@ -668,6 +685,101 @@ private static void writeMapAsImage(File fileOut, MapState state) } } + public static boolean isShulkerBox(ItemStack stack) + { + return stack.getItem() instanceof BlockItem blockItem && blockItem.getBlock() instanceof ShulkerBoxBlock; + } + + public static boolean hasCustomMaxStackSize(ItemStack stack) + { + int defaultStackSize = stack.getDefaultComponents().getOrDefault(DataComponentTypes.MAX_STACK_SIZE, 1); + int currentStackSize = stack.getOrDefault(DataComponentTypes.MAX_STACK_SIZE, 1); + return defaultStackSize != currentStackSize; + } + + public static boolean registerPresetFromString(CustomizeFlatLevelScreen screen, String str) + { + Matcher matcher = MiscUtils.PATTERN_WORLD_PRESET.matcher(str); + + if (matcher.matches()) + { + // TODO --> I added some code here, and added the IMixinCustomizeFlatLevelScreen + GeneratorOptionsHolder generatorOptionsHolder = ((IMixinCustomizeFlatLevelScreen) screen).tweakeroo_getCreateWorldParent().getWorldCreator().getGeneratorOptionsHolder(); + DynamicRegistryManager.Immutable registryManager = generatorOptionsHolder.getCombinedRegistryManager(); + FeatureSet featureSet = generatorOptionsHolder.dataConfiguration().enabledFeatures(); + RegistryEntryLookup biomeLookup = registryManager.get(RegistryKeys.BIOME).getReadOnlyWrapper(); + RegistryEntryLookup structureLookup = registryManager.get(RegistryKeys.STRUCTURE_SET).getReadOnlyWrapper();; + RegistryEntryLookup featuresLookup = registryManager.get(RegistryKeys.PLACED_FEATURE).getReadOnlyWrapper();; + RegistryEntryLookup blockLookup = registryManager.get(RegistryKeys.BLOCK).getReadOnlyWrapper(); + FlatChunkGeneratorConfig defaultConfig = FlatChunkGeneratorConfig.getDefaultConfig(biomeLookup, structureLookup, featuresLookup); + FlatChunkGeneratorConfig currentConfig = screen.getConfig(); + RegistryEntry.Reference referenceEntry = biomeLookup.getOrThrow(BiomeKeys.PLAINS); + RegistryEntry.Reference biomeEntry = referenceEntry; + + String name = matcher.group("name"); + String blocksString = matcher.group("blocks"); + String biomeName = matcher.group("biome"); + // TODO add back the features + String iconItemName = matcher.group("icon"); + + try + { + Optional> optBiome = Optional.ofNullable(Identifier.tryParse(biomeName)).map((biomeId) -> + RegistryKey.of(RegistryKeys.BIOME, biomeId)); + + biomeEntry = optBiome.flatMap(biomeLookup::getOptional).orElse(referenceEntry); + } + catch (Exception ignore) {} + + if (biomeEntry == null) + { + Tweakeroo.logger.error("Invalid biome while parsing flat world string: '{}'", biomeName); + return false; + } + + Item item = null; + + try + { + Optional> opt = Registries.ITEM.getEntry(Identifier.of(iconItemName)); + if (opt.isPresent()) + { + item = opt.get().value(); + } + } + catch (Exception ignore) {} + + if (item == null) + { + Tweakeroo.logger.error("Invalid item for icon while parsing flat world string: '{}'", iconItemName); + return false; + } + + List layers = MiscTweaks.parseBlockString(blocksString); + + if (layers == null) + { + Tweakeroo.logger.error("Failed to get the layers for the flat world preset"); + return false; + } + + FlatChunkGeneratorConfig newConfig = defaultConfig.with(layers, defaultConfig.getStructureOverrides(), biomeEntry); + + //new PresetsScreen.SuperflatPresetsListWidget.SuperflatPresetEntry(null); + //addPreset(Text.translatable(name), item, biome, ImmutableSet.of(), false, false, layers); + + screen.setConfig(newConfig); + + return true; + } + else + { + Tweakeroo.logger.error("Flat world preset string did not match the regex"); + } + + return false; + } + public static class PostKeyAction { private int lastIntValue; diff --git a/src/main/java/fi/dy/masa/tweakeroo/util/RayTraceUtils.java b/src/main/java/fi/dy/masa/tweakeroo/util/RayTraceUtils.java index f3a906b00..3b9d1b8ad 100644 --- a/src/main/java/fi/dy/masa/tweakeroo/util/RayTraceUtils.java +++ b/src/main/java/fi/dy/masa/tweakeroo/util/RayTraceUtils.java @@ -3,7 +3,25 @@ import java.util.List; import java.util.Optional; import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import com.llamalad7.mixinextras.lib.apache.commons.tuple.Pair; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockEntityProvider; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.client.MinecraftClient; import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.mob.PiglinEntity; +import net.minecraft.entity.passive.AbstractHorseEntity; +import net.minecraft.entity.passive.VillagerEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.Inventory; +import net.minecraft.inventory.SimpleInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.server.world.ServerWorld; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.EntityHitResult; import net.minecraft.util.hit.HitResult; @@ -13,12 +31,22 @@ import net.minecraft.world.RaycastContext; import net.minecraft.world.World; +import fi.dy.masa.malilib.render.InventoryOverlay; +import fi.dy.masa.malilib.util.Constants; +import fi.dy.masa.malilib.util.EntityUtils; +import fi.dy.masa.malilib.util.NbtKeys; +import fi.dy.masa.malilib.util.WorldUtils; +import fi.dy.masa.tweakeroo.data.ServerDataSyncer; +import fi.dy.masa.tweakeroo.mixin.IMixinAbstractHorseEntity; +import fi.dy.masa.tweakeroo.mixin.IMixinPiglinEntity; + public class RayTraceUtils { @Nonnull public static HitResult getRayTraceFromEntity(World worldIn, Entity entityIn, boolean useLiquids) { - double reach = 5.0d; + //double reach = 5.0d; + double reach = entityIn instanceof PlayerEntity pe ? pe.getBlockInteractionRange() + 1.0d : 5.0d; return getRayTraceFromEntity(worldIn, entityIn, useLiquids, reach); } @@ -71,4 +99,236 @@ public static HitResult getRayTraceFromEntity(World worldIn, Entity entityIn, bo return result; } + + public static @Nullable InventoryOverlay.Context getTargetInventory(MinecraftClient mc) + { + World world = WorldUtils.getBestWorld(mc); + Entity cameraEntity = EntityUtils.getCameraEntity(); + + if (mc.player == null || world == null) + { + return null; + } + + if (cameraEntity == mc.player && world instanceof ServerWorld) + { + // We need to get the player from the server world (if available, ie. in single player), + // so that the player itself won't be included in the ray trace + Entity serverPlayer = world.getPlayerByUuid(mc.player.getUuid()); + + if (serverPlayer != null) + { + cameraEntity = serverPlayer; + } + } + + HitResult trace = getRayTraceFromEntity(world, cameraEntity, false); + NbtCompound nbt = new NbtCompound(); + + if (trace.getType() == HitResult.Type.BLOCK) + { + BlockPos pos = ((BlockHitResult) trace).getBlockPos(); + BlockState state = world.getBlockState(pos); + Block blockTmp = state.getBlock(); + BlockEntity be = null; + + //Tweakeroo.logger.warn("getTarget():1: pos [{}], state [{}]", pos.toShortString(), state.toString()); + + if (blockTmp instanceof BlockEntityProvider) + { + if (world instanceof ServerWorld) + { + be = world.getWorldChunk(pos).getBlockEntity(pos); + + if (be != null) + { + nbt = be.createNbtWithIdentifyingData(world.getRegistryManager()); + } + } + else + { + Pair pair = ServerDataSyncer.getInstance().requestBlockEntity(world, pos); + + if (pair != null) + { + nbt = pair.getRight(); + } + } + + //Tweakeroo.logger.warn("getTarget():2: pos [{}], be [{}], nbt [{}]", pos.toShortString(), be != null, nbt != null); + + return getTargetInventoryFromBlock(world, pos, be, nbt); + } + + return null; + } + else if (trace.getType() == HitResult.Type.ENTITY) + { + Entity entity = ((EntityHitResult) trace).getEntity(); + + if (world instanceof ServerWorld) + { + if (entity.saveSelfNbt(nbt)) + { + return getTargetInventoryFromEntity(world.getEntityById(entity.getId()), nbt); + } + } + else + { + Pair pair = ServerDataSyncer.getInstance().requestEntity(entity.getId()); + + if (pair != null) + { + return getTargetInventoryFromEntity(world.getEntityById(pair.getLeft().getId()), pair.getRight()); + } + } + } + + return null; + } + + public static @Nullable InventoryOverlay.Context getTargetInventoryFromBlock(World world, BlockPos pos, @Nullable BlockEntity be, NbtCompound nbt) + { + Inventory inv; + + if (be != null) + { + if (nbt.isEmpty()) + { + nbt = be.createNbtWithIdentifyingData(world.getRegistryManager()); + } + inv = fi.dy.masa.malilib.util.InventoryUtils.getInventory(world, pos); + } + else + { + if (nbt.isEmpty()) + { + Pair pair = ServerDataSyncer.getInstance().requestBlockEntity(world, pos); + + if (pair != null) + { + nbt = pair.getRight(); + } + } + + inv = ServerDataSyncer.getInstance().getBlockInventory(world, pos, false); + } + + if (nbt != null && !nbt.isEmpty()) + { + Inventory inv2 = fi.dy.masa.malilib.util.InventoryUtils.getNbtInventory(nbt, inv != null ? inv.size() : -1, world.getRegistryManager()); + + if (inv == null) + { + inv = inv2; + } + } + + //Tweakeroo.logger.warn("getTarget():3: pos [{}], inv [{}], be [{}], nbt [{}]", pos.toShortString(), inv != null, be != null, nbt != null ? nbt.getString("id") : new NbtCompound()); + + if (inv == null || nbt == null) + { + return null; + } + + return new InventoryOverlay.Context(InventoryOverlay.getBestInventoryType(inv, nbt), inv, be != null ? be : world.getBlockEntity(pos), null, nbt); + } + + public static @Nullable InventoryOverlay.Context getTargetInventoryFromEntity(Entity entity, NbtCompound nbt) + { + Inventory inv = null; + LivingEntity entityLivingBase = null; + + if (entity instanceof LivingEntity) + { + entityLivingBase = (LivingEntity) entity; + } + + if (entity instanceof Inventory) + { + inv = (Inventory) entity; + } + else if (entity instanceof PlayerEntity player) + { + inv = new SimpleInventory(player.getInventory().main.toArray(new ItemStack[36])); + } + else if (entity instanceof VillagerEntity) + { + inv = ((VillagerEntity) entity).getInventory(); + } + else if (entity instanceof AbstractHorseEntity) + { + inv = ((IMixinAbstractHorseEntity) entity).tweakeroo_getHorseInventory(); + } + else if (entity instanceof PiglinEntity) + { + inv = ((IMixinPiglinEntity) entity).tweakeroo_inventory(); + } + if (!nbt.isEmpty()) + { + Inventory inv2; + + //Tweakeroo.logger.warn("getTargetInventoryFromEntity(): rawNbt: [{}]", nbt.toString()); + + // Fix for empty horse inv + if (inv != null && + //inv.size() == 1 && + nbt.contains(NbtKeys.ITEMS) && + nbt.getList(NbtKeys.ITEMS, Constants.NBT.TAG_COMPOUND).size() > 1) + //!DataManager.getInstance().hasIntegratedServer()) + { + if (entity instanceof AbstractHorseEntity) + { + inv2 = fi.dy.masa.malilib.util.InventoryUtils.getNbtInventoryHorseFix(nbt, -1, entity.getRegistryManager()); + } + else + { + inv2 = fi.dy.masa.malilib.util.InventoryUtils.getNbtInventory(nbt, -1, entity.getRegistryManager()); + } + inv = null; + } + // Fix for saddled horse, no inv + else if (inv != null && + //inv.size() == 1 && + nbt.contains(NbtKeys.SADDLE)) + //!DataManager.getInstance().hasIntegratedServer()) + { + inv2 = fi.dy.masa.malilib.util.InventoryUtils.getNbtInventoryHorseFix(nbt, -1, entity.getRegistryManager()); + inv = null; + } + // Fix for empty Villager/Piglin inv + else if (inv != null && inv.size() == 8 && + nbt.contains(NbtKeys.INVENTORY) && + !nbt.getList(NbtKeys.INVENTORY, Constants.NBT.TAG_COMPOUND).isEmpty()) + //!DataManager.getInstance().hasIntegratedServer()) + { + inv2 = fi.dy.masa.malilib.util.InventoryUtils.getNbtInventory(nbt, 8, entity.getRegistryManager()); + inv = null; + } + else + { + inv2 = fi.dy.masa.malilib.util.InventoryUtils.getNbtInventory(nbt, inv != null ? inv.size() : -1, entity.getRegistryManager()); + + if (inv2 != null) + { + inv = null; + } + } + + //Tweakeroo.logger.error("getTargetInventoryFromEntity(): inv.size [{}], inv2.size [{}]", inv != null ? inv.size() : "null", inv2 != null ? inv2.size() : "null"); + + if (inv2 != null) + { + inv = inv2; + } + } + + if (inv == null && entityLivingBase == null) + { + return null; + } + + return new InventoryOverlay.Context(inv != null ? InventoryOverlay.getBestInventoryType(inv, nbt) : InventoryOverlay.getInventoryType(nbt), + inv, null, entityLivingBase, nbt); + } } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 1199f072d..e80de043b 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -34,6 +34,6 @@ "depends": { "minecraft": ">=1.21 <=1.21.1", - "malilib": ">=0.20.0" + "malilib": ">=0.21.1-sakura.3" } } diff --git a/src/main/resources/mixins.tweakeroo.json b/src/main/resources/mixins.tweakeroo.json index cd656306c..6adfabe79 100644 --- a/src/main/resources/mixins.tweakeroo.json +++ b/src/main/resources/mixins.tweakeroo.json @@ -1,86 +1,91 @@ { - "required": true, - "package": "fi.dy.masa.tweakeroo.mixin", - "minVersion": "0.8", - "compatibilityLevel": "JAVA_17", - "client": [ - "IMixinAbstractBlock", - "IMixinAbstractHorseEntity", - "IMixinAxeItem", - "IMixinChunkLightProvider", - "IMixinClientWorld", - "IMixinCommandBlockExecutor", - "IMixinCustomizeFlatLevelScreen", - "IMixinDataQueryHandler", - "IMixinShovelItem", - "IMixinSimpleOption", - "MixinAbstractClientPlayerEntity", - "MixinAbstractInventoryScreen", - "MixinAbstractSignEditScreen", - "MixinBackgroundRenderer", - "MixinBatEntity", - "MixinBeaconBlockEntityRenderer", - "MixinBlockEntityRenderDispatcher", - "MixinBlockItem", - "MixinBossBarHud", - "MixinBuiltChunk", - "MixinChatHud", - "MixinChatScreen", - "MixinChunkBuilder_BuiltChunk", - "MixinClientBossBar", - "MixinClientCommandSource", - "MixinClientCommonNetworkHandler", - "MixinClientPlayerEntity", - "MixinClientPlayerInteractionManager", - "MixinClientPlayNetworkHandler", - "MixinClientWorld", - "MixinClientWorld_Properties", - "MixinCloneCommand", - "MixinCommandBlockScreen", - "MixinCreativeInventoryScreen", - "MixinDataQueryHandler", - "MixinDimensionEffects_Nether", - "MixinEntity", - "MixinEntityRenderDispatcher", - "MixinExplosion", - "MixinFillCommand", - "MixinGameRenderer", - "MixinGameRenderer_ViewBob", - "MixinHeldItemRenderer", - "MixinInGameHud", - "MixinItem", - "MixinItemGroup", - "MixinItemStack", - "MixinKeyboardInput", - "MixinLightingProvider", - "MixinLivingEntity", - "MixinMinecraftClient", - "MixinMobSpawnerBlockEntityRenderer", - "MixinMobSpawnerLogic", - "MixinMouse", - "MixinNetherPortalBlock", - "MixinObserverBlock", - "MixinParticleManager", - "MixinPlayerAbilities", - "MixinPlayerEntity", - "MixinRavagerEntity", - "MixinScaffoldingBlock", - "MixinSculkSensor", - "MixinServerChunkLoadingManager", - "MixinServerPlayNetworkHandler", - "MixinShulkerBoxBlock", - "MixinSignBlockEntity", - "MixinSlimeBlock", - "MixinStructureBlockBlockEntity", - "MixinTeleportSpectatorMenu", - "MixinTradeOffer", - "MixinUpdateStructureBlockC2SPacket", - "MixinWindow", - "MixinWorld", - "MixinWorldRenderer" - ], - "mixinPriority": 990, - "injectors": { - "defaultRequire": 0 - } + "required": true, + "package": "fi.dy.masa.tweakeroo.mixin", + "minVersion": "0.8", + "compatibilityLevel": "JAVA_17", + "client": [ + "IMixinAbstractBlock", + "IMixinAbstractHorseEntity", + "IMixinAxeItem", + "IMixinChunkLightProvider", + "IMixinClientWorld", + "IMixinCommandBlockExecutor", + "IMixinCustomizeFlatLevelScreen", + "IMixinDataQueryHandler", + "IMixinPiglinEntity", + "IMixinShovelItem", + "IMixinSimpleOption", + "MixinAbstractClientPlayerEntity", + "MixinAbstractInventoryScreen", + "MixinAbstractSignEditScreen", + "MixinBackgroundRenderer", + "MixinBatEntity", + "MixinBeaconBlockEntityRenderer", + "MixinBlockEntityRenderDispatcher", + "MixinBlockItem", + "MixinBossBarHud", + "MixinBuiltChunk", + "MixinBundleItem", + "MixinChatHud", + "MixinChatScreen", + "MixinChunkBuilder_BuiltChunk", + "MixinClientBossBar", + "MixinClientCommandSource", + "MixinClientCommonNetworkHandler", + "MixinClientPlayerEntity", + "MixinClientPlayerInteractionManager", + "MixinClientPlayNetworkHandler", + "MixinClientWorld", + "MixinClientWorld_Properties", + "MixinCloneCommand", + "MixinCommandBlockScreen", + "MixinCreativeInventoryScreen", + "MixinDataQueryHandler", + "MixinDimensionEffects_Nether", + "MixinEntity", + "MixinEntityRenderDispatcher", + "MixinExplosion", + "MixinFillCommand", + "MixinGameRenderer", + "MixinGameRenderer_ViewBob", + "MixinHeldItemRenderer", + "MixinHopperBlockEntity", + "MixinInGameHud", + "MixinItem", + "MixinItemGroup", + "MixinItemStack", + "MixinKeyboardInput", + "MixinLightingProvider", + "MixinLivingEntity", + "MixinMinecraftClient", + "MixinMobSpawnerBlockEntityRenderer", + "MixinMobSpawnerLogic", + "MixinMouse", + "MixinNetherPortalBlock", + "MixinObserverBlock", + "MixinParticleManager", + "MixinPlayerAbilities", + "MixinPlayerEntity", + "MixinRavagerEntity", + "MixinScaffoldingBlock", + "MixinSculkSensor", + "MixinServerChunkLoadingManager", + "MixinServerPlayNetworkHandler", + "MixinShulkerBoxBlock", + "MixinSignBlockEntity", + "MixinSlimeBlock", + "MixinStructureBlockBlockEntity", + "MixinTeleportSpectatorMenu", + "MixinTradeOffer", + "MixinUpdateStructureBlockC2SPacket", + "MixinWindow", + "MixinWorld", + "MixinWorldRenderer" + ], + "mixinPriority": 990, + "injectors": { + "defaultRequire": 0 + }, + "mixins": [ + ] }