diff --git a/MouseTweaks/Config.java b/MouseTweaks/Config.java index 8b5452a..e22e7a6 100644 --- a/MouseTweaks/Config.java +++ b/MouseTweaks/Config.java @@ -11,58 +11,55 @@ import java.util.Iterator; public class Config { private String fileName; private HashMap Properties = new HashMap(); - + public Config(String fileName) { this.fileName = fileName; } - + public boolean readConfig() { Properties.clear(); try { File config = new File(fileName); - if (!config.exists()) - return false; - + if (!config.exists()) return false; + FileReader fr = new FileReader(config); BufferedReader br = new BufferedReader(fr); String strLine = ""; - + while ((strLine = br.readLine()) != null) { String[] strKeyValue = strLine.split("="); - + int l = strKeyValue.length; - + if (l < 2) { continue; } - + strKeyValue[0] = strKeyValue[0].trim(); strKeyValue[1] = strKeyValue[1].trim(); - + if (Properties.containsKey(strKeyValue[0])) { - Logger.Log("Duplicate property \"" + strKeyValue[0] - + "\" in the config file, using the last value present"); + Logger.Log("Duplicate property \"" + strKeyValue[0] + "\" in the config file, using the last value present"); } - + Properties.put(strKeyValue[0], Integer.parseInt(strKeyValue[1])); } - + br.close(); fr.close(); - + int size = Properties.size(); - if (size == 0) - return false; - + if (size == 0) return false; + return true; } catch (Exception e) { e.printStackTrace(); } - + return false; } - + public void saveConfig() { try { File config = new File(fileName); @@ -70,20 +67,19 @@ public class Config { config.getParentFile().mkdirs(); config.createNewFile(); } - + int size = Properties.size(); - if (size == 0) - return; - + if (size == 0) return; + FileWriter fw = new FileWriter(config); BufferedWriter bw = new BufferedWriter(fw); - + Iterator i = Properties.keySet().iterator(); while (i.hasNext()) { String key = (String) i.next(); bw.write(key + " = " + Properties.get(key) + "\n"); } - + bw.close(); fw.close(); } catch (Exception e) { @@ -91,14 +87,23 @@ public class Config { e.printStackTrace(); } } - + + public int getOrCreatePropertyValue(String name, int defaultValue) { + if (Properties.containsKey(name)) + return (Integer) Properties.get(name); + else { + setPropertyValue(name, defaultValue); + return defaultValue; + } + } + public int getPropertyValue(String name) { if (Properties.containsKey(name)) return (Integer) Properties.get(name); else return 0; } - + public void setPropertyValue(String name, int value) { Properties.put(name, value); } diff --git a/MouseTweaks/Constants.java b/MouseTweaks/Constants.java index ffa534e..49c4c62 100644 --- a/MouseTweaks/Constants.java +++ b/MouseTweaks/Constants.java @@ -6,12 +6,14 @@ public class Constants { public static final String NAME = "Mouse Tweaks"; // Mod version - public static final String VERSION = "2.2 (for Minecraft 1.6.2)"; + public static final String VERSION = "2.2b (for Minecraft 1.6.2)"; // Obfuscated names public static final String ISMOUSEOVERSLOT_NAME = "a"; public static final String FIELDE_NAME = "G"; public static final String FIELDq_NAME = "s"; public static final String MCPROFILER_NAME = "C"; - + + // Inventory-related stuff + public static final int INVENTORY_SIZE = 36; // Size of the player inventory } diff --git a/MouseTweaks/DeobfuscationLayer.java b/MouseTweaks/DeobfuscationLayer.java index 3a3e471..f02e580 100644 --- a/MouseTweaks/DeobfuscationLayer.java +++ b/MouseTweaks/DeobfuscationLayer.java @@ -2,8 +2,9 @@ package MouseTweaks; import java.util.List; -import net.minecraft.src.Minecraft; import net.minecraft.src.Container; +import net.minecraft.src.ContainerPlayer; +import net.minecraft.src.ContainerWorkbench; import net.minecraft.src.EntityClientPlayerMP; import net.minecraft.src.GameSettings; import net.minecraft.src.GuiContainer; @@ -11,6 +12,7 @@ import net.minecraft.src.GuiContainerCreative; import net.minecraft.src.GuiScreen; import net.minecraft.src.InventoryPlayer; import net.minecraft.src.ItemStack; +import net.minecraft.src.Minecraft; import net.minecraft.src.PlayerControllerMP; import net.minecraft.src.ScaledResolution; import net.minecraft.src.Slot; @@ -18,61 +20,66 @@ import net.minecraft.src.Slot; import org.lwjgl.input.Mouse; public class DeobfuscationLayer { - + protected static Minecraft mc; - + // protected MouseTweaksModCompatibility MTMC; - + protected static GuiScreen getCurrentScreen() { return mc.currentScreen; } - + protected static boolean isGuiContainer(GuiScreen guiScreen) { return (guiScreen != null) && (guiScreen instanceof GuiContainer); } - + protected static boolean isValidGuiContainer(GuiScreen guiScreen) { return (guiScreen != null) && !(guiScreen.getClass().getSimpleName().contains("CJB_GuiCrafting")) && !(guiScreen.getClass().equals(GuiContainerCreative.class)); } - + + protected static boolean isCraftingOutputSlot(Container container, Slot slot) { + return ((container instanceof ContainerWorkbench) && (getSlotNumber(slot) == 0)) + || ((container instanceof ContainerPlayer) && (getSlotNumber(slot) == 0)); + } + protected static GuiContainer asGuiContainer(GuiScreen guiScreen) { return (GuiContainer) guiScreen; } - + protected static Container getContainer(GuiContainer guiContainer) { return guiContainer.inventorySlots; } - + protected static List getSlots(Container container) { return container.inventorySlots; } - + protected static Slot getSlot(Container container, int index) { return (Slot) (getSlots(container).get(index)); } - + protected static ItemStack getSlotStack(Slot slot) { return slot.getStack(); } - + protected static int getItemStackID(ItemStack itemStack) { return itemStack.itemID; } - + protected static boolean hasSubtypesItemStack(ItemStack itemStack) { return itemStack.getHasSubtypes(); } - + protected static int getItemStackItemDamage(ItemStack itemStack) { return itemStack.getItemDamage(); } - + protected static int getWindowId(Container container) { return container.windowId; } - + protected static void windowClick(int windowId, int slotNumber, int mouseButton, int shiftPressed) { // if (slotNumber != -1) { @@ -80,70 +87,86 @@ public class DeobfuscationLayer { getThePlayer()); // } } - + protected static EntityClientPlayerMP getThePlayer() { return mc.thePlayer; } - + protected static InventoryPlayer getInventoryPlayer() { return getThePlayer().inventory; } - + protected static GameSettings getGameSettings() { return mc.gameSettings; } - + protected static int getDisplayWidth() { return mc.displayWidth; } - + protected static int getDisplayHeight() { return mc.displayHeight; } - + protected static ItemStack getStackOnMouse() { return getInventoryPlayer().getItemStack(); } - + protected static PlayerControllerMP getPlayerController() { return mc.playerController; } - + protected static int getSlotNumber(Slot slot) { return slot.slotNumber; } - + + protected static int getItemStackSize(ItemStack itemStack) { + return itemStack.stackSize; + } + + protected static int getMaxItemStackSize(ItemStack itemStack) { + return itemStack.getMaxStackSize(); + } + + protected static ItemStack copyItemStack(ItemStack itemStack) { + return itemStack.copy(); + } + + protected static boolean areStacksCompatible(ItemStack itemStack1, ItemStack itemStack2) { + return (getItemStackID(itemStack1) == getItemStackID(itemStack2)) && (!hasSubtypesItemStack(itemStack1) || (getItemStackItemDamage(itemStack1) == getItemStackItemDamage(itemStack2))); + } + protected static boolean isMouseOverSlot(GuiContainer guiContainer, Slot slot) { boolean returnValue = false; returnValue = (Boolean) Reflection.guiContainerClass.invokeMethod(guiContainer, "isMouseOverSlot", slot, getRequiredMouseX(), getRequiredMouseY()); - + return returnValue; } - + protected static void disableDefaultRMBDrag(GuiContainer guiContainer) { Reflection.guiContainerClass.setFieldValue(guiContainer, "field_94068_E", true); Reflection.guiContainerClass.setFieldValue(guiContainer, "field_94076_q", false); } - + protected static int getRequiredMouseX() { ScaledResolution var8 = new ScaledResolution(getGameSettings(), getDisplayWidth(), getDisplayHeight()); int var9 = var8.getScaledWidth(); int var11 = (Mouse.getX() * var9) / getDisplayWidth(); - + return var11; } - + protected static int getRequiredMouseY() { ScaledResolution var8 = new ScaledResolution(getGameSettings(), getDisplayWidth(), getDisplayHeight()); int var10 = var8.getScaledHeight(); int var13 = var10 - ((Mouse.getY() * var10) / getDisplayHeight()) - 1; - + return var13; } - + protected static void sendClientMessage(String message) { getThePlayer().sendChatMessage(message); } diff --git a/MouseTweaks/Main.java b/MouseTweaks/Main.java index 3a47df6..21890d9 100644 --- a/MouseTweaks/Main.java +++ b/MouseTweaks/Main.java @@ -2,18 +2,20 @@ package MouseTweaks; import java.io.File; -import net.minecraft.src.Minecraft; +import net.minecraft.src.Container; import net.minecraft.src.GuiContainer; import net.minecraft.src.GuiScreen; import net.minecraft.src.ItemStack; +import net.minecraft.src.Minecraft; import net.minecraft.src.Slot; import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; public class Main extends DeobfuscationLayer { - + private static GuiContainer guiContainer; + private static Container container; private static Slot currentSlot = null; private static boolean mouseOverSomeSlot = false; private static boolean firstSlotClicked = true; @@ -23,94 +25,115 @@ public class Main extends DeobfuscationLayer { private static boolean readConfig = false; private static boolean initialised = false; private static boolean disabled = false; - + public static boolean liteLoader = false; public static boolean minecraftForge = false; public static boolean optifine = false; public static boolean modLoader = false; public static boolean useModLoader = false; - + public static boolean DisableRMBTweak = false; - + public static int RMBTweak = 0; public static int LMBTweakWithItem = 0; public static int LMBTweakWithoutItem = 0; - + public static int WheelTweak = 0; + public static int ForceModLoader = 0; + public static Config mainConfig; - + public static boolean initialise() { if (disabled) return false; - + if (initialised) return true; - + initialised = true; - + mc = Minecraft.getMinecraft(); - + mainConfig = new Config(mc.mcDataDir + File.separator + "config" + File.separator + "MouseTweaks.cfg"); readConfigFile(); - - liteLoader = Reflection.reflectLiteLoader(); + + modLoader = Reflection.doesClassExist("net.minecraft.src.ModLoader"); minecraftForge = Reflection .doesClassExist("net.minecraftforge.client.MinecraftForgeClient"); - optifine = Reflection.reflectOptifine(); - modLoader = Reflection.doesClassExist("net.minecraft.src.ModLoader"); - + + if (ForceModLoader == 1) { + if (modLoader) { + useModLoader = true; + Logger.Log("ForceModLoader is set to 1, using ModLoader's onTick for mod operation..."); + } else { + Logger.Log("ForceModLoader is set to 1, but ModLoader's main class is not present, quitting..."); + disabled = true; + return false; + } + } else { + liteLoader = Reflection.reflectLiteLoader(); + optifine = Reflection.reflectOptifine(); + } + if (minecraftForge) { Logger.Log("Minecraft Forge is installed"); } else { Logger.Log("Minecraft Forge is not installed"); } - if (!liteLoader) { + if (!liteLoader && (ForceModLoader == 0)) { useModLoader = !Reflection.reflectMinecraft(); if (!useModLoader) { - if (!Reflection.replaceProfiler()) { - if (modLoader) { - useModLoader = true; - Logger.Log("Using ModLoader for mod operation"); - } else { - Logger.Log("Failed to replace Minecraft profiler, quitting"); - - disabled = true; - return false; - } - } + if (!Reflection.replaceProfiler()) { + if (modLoader) { + useModLoader = true; + Logger.Log("Using ModLoader for mod operation"); + } else { + Logger.Log("Failed to replace Minecraft profiler, quitting"); + + disabled = true; + return false; + } + } } else { Logger.Log("Using ModLoader for mod operation"); } } - + boolean returnValue = Reflection.reflectGuiContainer(); - + if (returnValue) { - Logger.Log(Constants.NAME + " version " + Constants.VERSION + " has been initialised"); + Logger.Log(Constants.NAME + " version " + Constants.VERSION + + " has been initialised"); } else { disabled = true; } - + return returnValue; } - + public static void readConfigFile() { if (!mainConfig.readConfig()) { mainConfig.setPropertyValue("RMBTweak", 1); mainConfig.setPropertyValue("LMBTweakWithItem", 1); mainConfig.setPropertyValue("LMBTweakWithoutItem", 1); - + mainConfig.setPropertyValue("WheelTweak", 1); + mainConfig.setPropertyValue("ForceModLoader", 0); + mainConfig.saveConfig(); - + Logger.Log("Mouse Tweaks config file was created."); } - - RMBTweak = mainConfig.getPropertyValue("RMBTweak"); - LMBTweakWithItem = mainConfig.getPropertyValue("LMBTweakWithItem"); - LMBTweakWithoutItem = mainConfig.getPropertyValue("LMBTweakWithoutItem"); + + RMBTweak = mainConfig.getOrCreatePropertyValue("RMBTweak", 1); + LMBTweakWithItem = mainConfig.getOrCreatePropertyValue("LMBTweakWithItem", 1); + LMBTweakWithoutItem = mainConfig.getOrCreatePropertyValue("LMBTweakWithoutItem", 1); + WheelTweak = mainConfig.getOrCreatePropertyValue("WheelTweak", 1); + ForceModLoader = mainConfig.getOrCreatePropertyValue("ForceModLoader", 0); + + mainConfig.saveConfig(); } - + public static void onUpdateInGame() { GuiScreen currentScreen = getCurrentScreen(); if (currentScreen == null) { @@ -119,6 +142,7 @@ public class Main extends DeobfuscationLayer { currentSlot = null; firstSlotClicked = true; oldStackOnMouse = null; + container = null; guiContainer = null; mouseOverSomeSlot = false; readConfig = true; @@ -126,7 +150,7 @@ public class Main extends DeobfuscationLayer { onUpdateInGUI(currentScreen); } } - + public static void onUpdateInGUI(GuiScreen currentScreen) { if (readConfig) { readConfig = false; @@ -136,11 +160,11 @@ public class Main extends DeobfuscationLayer { if ((DisableRMBTweak || (RMBTweak == 0)) && (LMBTweakWithoutItem == 0) && (LMBTweakWithItem == 0)) return; - + if (isGuiContainer(currentScreen) && isValidGuiContainer(currentScreen)) { guiContainer = asGuiContainer(currentScreen); } - + if (guiContainer != null) { /* * if (guiContainer != oldGuiContainer) { oldGuiContainer = @@ -149,56 +173,66 @@ public class Main extends DeobfuscationLayer { * append("Slots: ").append(guiContainer.inventorySlots * .inventorySlots.size()).toString()); } */ - + + int wheel = (WheelTweak == 1) ? Mouse.getDWheel() / 120 : 0; + + container = getContainer(guiContainer); + int containerSize = getSlots(container).size(); + if (!mouseOverSomeSlot) { currentSlot = null; } - + mouseOverSomeSlot = false; - + ItemStack stackOnMouse = getStackOnMouse(); - + if (!Mouse.isButtonDown(0) && !Mouse.isButtonDown(1)) { firstSlot = null; firstSlotClicked = false; } - - if ((oldStackOnMouse == null) && (stackOnMouse != null) && Mouse.isButtonDown(1)) { + + if ((oldStackOnMouse == null) && (stackOnMouse != null) + && Mouse.isButtonDown(1)) { firstSlotClicked = true; } - + oldStackOnMouse = stackOnMouse; - - for (int counter = 0; counter < getSlots(getContainer(guiContainer)).size(); counter++) { - Slot slot = getSlot(getContainer(guiContainer), counter); + + for (int counter = 0; counter < containerSize; counter++) { + Slot slot = getSlot(container, counter); if (isMouseOverSlot(guiContainer, slot)) { mouseOverSomeSlot = true; - + if (currentSlot != slot) { if (firstSlot == null) { firstSlot = currentSlot; } - + + // Logger.Log(new + // StringBuilder().append("Current slot number: ").append(slot.slotNumber).toString()); + currentSlot = slot; ItemStack originalStack = getSlotStack(slot); - + boolean shiftIsDown = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT); - - if (Mouse.isButtonDown(1) && !DisableRMBTweak && (RMBTweak == 1)) { + + if (Mouse.isButtonDown(1) && !DisableRMBTweak + && (RMBTweak == 1)) { disableDefaultRMBDrag(guiContainer); if ((firstSlot != null) && !firstSlotClicked && (getSlotNumber(firstSlot) <= getSlots( - getContainer(guiContainer)).size())) { - windowClick(getWindowId(getContainer(guiContainer)), + container).size())) { + windowClick(getWindowId(container), getSlotNumber(firstSlot), 1, 0); firstSlotClicked = true; } if (stackOnMouse != null) { if ((originalStack == null) - || ((getItemStackID(originalStack) == getItemStackID(stackOnMouse)) && (!hasSubtypesItemStack(originalStack) || (getItemStackItemDamage(originalStack) == getItemStackItemDamage(stackOnMouse))))) { - windowClick(getWindowId(getContainer(guiContainer)), + || areStacksCompatible(originalStack, stackOnMouse)) { + windowClick(getWindowId(container), getSlotNumber(slot), 1, 0); } } @@ -206,18 +240,17 @@ public class Main extends DeobfuscationLayer { if (LMBTweakWithItem == 1) { if (stackOnMouse != null) { if ((originalStack != null) - && ((getItemStackID(originalStack) == getItemStackID(stackOnMouse)) && (!hasSubtypesItemStack(originalStack) || (getItemStackItemDamage(originalStack) == getItemStackItemDamage(stackOnMouse))))) { + && areStacksCompatible(originalStack, stackOnMouse)) { if (shiftIsDown) { - windowClick(getWindowId(getContainer(guiContainer)), + windowClick(getWindowId(container), getSlotNumber(slot), 0, 1); } else { - if ((stackOnMouse.stackSize + originalStack.stackSize) <= stackOnMouse - .getMaxStackSize()) { + if ((getItemStackSize(stackOnMouse) + getItemStackSize(originalStack)) <= getMaxItemStackSize(stackOnMouse)) { windowClick( - getWindowId(getContainer(guiContainer)), + getWindowId(container), getSlotNumber(slot), 0, 0); windowClick( - getWindowId(getContainer(guiContainer)), + getWindowId(container), getSlotNumber(slot), 0, 0); } } @@ -225,27 +258,156 @@ public class Main extends DeobfuscationLayer { } else if (LMBTweakWithoutItem == 1) { if (originalStack != null) { if (shiftIsDown) { - windowClick(getWindowId(getContainer(guiContainer)), + windowClick(getWindowId(container), getSlotNumber(slot), 0, 1); } } } } - } + } } - + + if (wheel != 0) + { + int numItemsToMove = Math.abs(wheel); + + if (containerSize > Constants.INVENTORY_SIZE) + { + ItemStack originalStack = getSlotStack(slot); + if ((originalStack != null) + && ((stackOnMouse == null) || ((isCraftingOutputSlot(container, slot)) ? areStacksCompatible(originalStack, stackOnMouse) : !areStacksCompatible(originalStack, stackOnMouse)))) { + do { + Slot applicableSlot = null; + + int slotCounter = 0; + int countUntil = containerSize + - Constants.INVENTORY_SIZE; + if (counter < countUntil) { + slotCounter = countUntil; + countUntil = containerSize; + } + for (int i = slotCounter; i < countUntil; i++) + { + Slot sl = getSlot(container, i); + ItemStack stackSl = getSlotStack(sl); + + if (stackSl == null) { + if ((applicableSlot == null) + && (wheel < 0) + && sl.isItemValid(originalStack)) + { + applicableSlot = sl; + } + } else if ((getItemStackID(originalStack) == getItemStackID(stackSl)) + && (!hasSubtypesItemStack(originalStack) || (getItemStackItemDamage(originalStack) == getItemStackItemDamage(stackSl)))) { + if ((wheel < 0) + && (stackSl.stackSize < stackSl.getMaxStackSize())) + { + applicableSlot = sl; + break; + } else if (wheel > 0) + { + applicableSlot = sl; + break; + } + } + } + + if (isCraftingOutputSlot(container, slot)) { + if (wheel < 0) { + boolean mouseWasEmpty = stackOnMouse == null; + + for (int i = 0; i < numItemsToMove; i++) { + windowClick(getWindowId(container), getSlotNumber(slot), 0, 0); + } + + if ((applicableSlot != null) + && mouseWasEmpty) { + windowClick(getWindowId(container), getSlotNumber(applicableSlot), 0, 0); + } + } + + break; + } + + if (applicableSlot != null) + { + Slot slotTo = (wheel < 0) ? applicableSlot : slot; + Slot slotFrom = (wheel < 0) ? slot : applicableSlot; + ItemStack stackTo = (getSlotStack(slotTo) != null) ? copyItemStack(getSlotStack(slotTo)) : null; + ItemStack stackFrom = copyItemStack(getSlotStack(slotFrom)); + + if (wheel < 0) { + if ((stackTo != null) + && ((getMaxItemStackSize(stackTo) - getItemStackSize(stackTo)) <= numItemsToMove)) { + windowClick(getWindowId(container), getSlotNumber(slotFrom), 0, 0); + windowClick(getWindowId(container), getSlotNumber(slotTo), 0, 0); + windowClick(getWindowId(container), getSlotNumber(slotFrom), 0, 0); + + numItemsToMove -= getMaxItemStackSize(stackTo) + - getItemStackSize(stackTo); + } else { + windowClick(getWindowId(container), getSlotNumber(slotFrom), 0, 0); + + if (getItemStackSize(stackFrom) <= numItemsToMove) { + windowClick(getWindowId(container), getSlotNumber(slotTo), 0, 0); + } else { + for (int i = 0; i < numItemsToMove; i++) { + windowClick(getWindowId(container), getSlotNumber(slotTo), 1, 0); + } + } + + windowClick(getWindowId(container), getSlotNumber(slotFrom), 0, 0); + + numItemsToMove = 0; + } + } else { + if ((getMaxItemStackSize(stackTo) - getItemStackSize(stackTo)) <= numItemsToMove) { + windowClick(getWindowId(container), getSlotNumber(slotFrom), 0, 0); + windowClick(getWindowId(container), getSlotNumber(slotTo), 0, 0); + windowClick(getWindowId(container), getSlotNumber(slotFrom), 0, 0); + } else { + windowClick(getWindowId(container), getSlotNumber(slotFrom), 0, 0); + + if (stackFrom.stackSize <= numItemsToMove) { + windowClick(getWindowId(container), getSlotNumber(slotTo), 0, 0); + numItemsToMove -= getMaxItemStackSize(stackFrom); + } else { + for (int i = 0; i < numItemsToMove; i++) { + windowClick(getWindowId(container), getSlotNumber(slotTo), 1, 0); + } + + numItemsToMove = 0; + } + + windowClick(getWindowId(container), getSlotNumber(slotFrom), 0, 0); + } + + if (getMaxItemStackSize(stackTo) == getMaxItemStackSize(stackTo)) { + numItemsToMove = 0; + } + } + } else { + break; + } + } while (numItemsToMove != 0); + } + } + } + break; } } } - + + container = null; guiContainer = null; firstSlot = null; - + /* * if (MTMC.isValidModInventory(currentScreen)) { * MTMC.handleOnTickInModInventory(currentScreen); } */ } - + } diff --git a/mcmod.info b/mcmod.info index f41cf77..53de03b 100644 --- a/mcmod.info +++ b/mcmod.info @@ -3,15 +3,14 @@ "modid": "mod_MouseTweaks", "name": "Mouse Tweaks", "description": "Have you ever been tired of right clicking while crafting furnaces, chests or whatever? My client-side mod solves this problem! Just hold your right mouse button in one inventory slot and drag your mouse toward another. Also works with left mouse button! Credits: LiteLoader and some mods' source code, which I used as a reference to how to do certain stuff when I was unsure", - "version": "2.2", + "version": "2.2b", "mcversion": "1.6.2", "url": "http://www.minecraftforum.net/topic/1518185-", "updateUrl": "", "authors": [ "YaLTeR" ], "credits": "In the description.", "logoFile": "/mousetweaks_logo.png", - "screenshots": [ - ], + "screenshots": [ ], "parent":"", "dependencies": [ ] }