RoughlyEnoughItemsCore.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. /*
  2. * Copyright (c) 2018, 2019, 2020 shedaniel
  3. * Licensed under the MIT License (the "License").
  4. */
  5. package me.shedaniel.rei;
  6. import com.google.common.collect.Lists;
  7. import com.google.common.collect.Maps;
  8. import me.shedaniel.cloth.hooks.ClothClientHooks;
  9. import me.shedaniel.rei.api.*;
  10. import me.shedaniel.rei.api.plugins.REIPluginV0;
  11. import me.shedaniel.rei.gui.ContainerScreenOverlay;
  12. import me.shedaniel.rei.impl.*;
  13. import me.shedaniel.rei.listeners.RecipeBookButtonWidgetHooks;
  14. import me.shedaniel.rei.listeners.RecipeBookGuiHooks;
  15. import me.shedaniel.rei.tests.plugin.REITestPlugin;
  16. import net.fabricmc.api.ClientModInitializer;
  17. import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
  18. import net.fabricmc.loader.api.FabricLoader;
  19. import net.fabricmc.loader.api.ModContainer;
  20. import net.minecraft.client.MinecraftClient;
  21. import net.minecraft.client.gui.Element;
  22. import net.minecraft.client.gui.screen.Screen;
  23. import net.minecraft.client.gui.screen.ingame.ContainerScreen;
  24. import net.minecraft.client.gui.screen.ingame.CraftingTableScreen;
  25. import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen;
  26. import net.minecraft.client.gui.screen.ingame.InventoryScreen;
  27. import net.minecraft.client.gui.screen.recipebook.RecipeBookGhostSlots;
  28. import net.minecraft.client.gui.screen.recipebook.RecipeBookProvider;
  29. import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget;
  30. import net.minecraft.client.gui.widget.TextFieldWidget;
  31. import net.minecraft.client.gui.widget.TexturedButtonWidget;
  32. import net.minecraft.client.resource.language.I18n;
  33. import net.minecraft.container.CraftingTableContainer;
  34. import net.minecraft.container.Slot;
  35. import net.minecraft.item.ItemStack;
  36. import net.minecraft.item.Items;
  37. import net.minecraft.recipe.Ingredient;
  38. import net.minecraft.recipe.RecipeManager;
  39. import net.minecraft.text.LiteralText;
  40. import net.minecraft.util.ActionResult;
  41. import net.minecraft.util.Identifier;
  42. import org.apache.logging.log4j.LogManager;
  43. import org.apache.logging.log4j.Logger;
  44. import org.jetbrains.annotations.ApiStatus;
  45. import java.util.LinkedList;
  46. import java.util.List;
  47. import java.util.Map;
  48. import java.util.Optional;
  49. import java.util.concurrent.CompletableFuture;
  50. import java.util.concurrent.ExecutorService;
  51. import java.util.concurrent.Executors;
  52. import java.util.concurrent.atomic.AtomicLong;
  53. @ApiStatus.Internal
  54. public class RoughlyEnoughItemsCore implements ClientModInitializer {
  55. @ApiStatus.Internal public static final Logger LOGGER = LogManager.getFormatterLogger("REI");
  56. private static final RecipeHelper RECIPE_HELPER = new RecipeHelperImpl();
  57. private static final EntryRegistry ENTRY_REGISTRY = new EntryRegistryImpl();
  58. private static final DisplayHelper DISPLAY_HELPER = new DisplayHelperImpl();
  59. private static final Map<Identifier, REIPluginEntry> plugins = Maps.newHashMap();
  60. private static final ExecutorService SYNC_RECIPES = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "REI-SyncRecipes"));
  61. private static ConfigManager configManager;
  62. @ApiStatus.Internal
  63. public static RecipeHelper getRecipeHelper() {
  64. return RECIPE_HELPER;
  65. }
  66. @ApiStatus.Internal
  67. public static ConfigManager getConfigManager() {
  68. return configManager;
  69. }
  70. @ApiStatus.Internal
  71. public static EntryRegistry getEntryRegistry() {
  72. return ENTRY_REGISTRY;
  73. }
  74. @ApiStatus.Internal
  75. public static DisplayHelper getDisplayHelper() {
  76. return DISPLAY_HELPER;
  77. }
  78. /**
  79. * Registers a REI plugin
  80. *
  81. * @param plugin the plugin instance
  82. * @return the plugin itself
  83. */
  84. @ApiStatus.Internal
  85. public static REIPluginEntry registerPlugin(REIPluginEntry plugin) {
  86. plugins.put(plugin.getPluginIdentifier(), plugin);
  87. RoughlyEnoughItemsCore.LOGGER.debug("[REI] Registered plugin %s from %s", plugin.getPluginIdentifier().toString(), plugin.getClass().getSimpleName());
  88. return plugin;
  89. }
  90. public static List<REIPluginEntry> getPlugins() {
  91. return new LinkedList<>(plugins.values());
  92. }
  93. public static Optional<Identifier> getPluginIdentifier(REIPluginEntry plugin) {
  94. for (Identifier identifier : plugins.keySet())
  95. if (identifier != null && plugins.get(identifier).equals(plugin))
  96. return Optional.of(identifier);
  97. return Optional.empty();
  98. }
  99. public static boolean hasPermissionToUsePackets() {
  100. try {
  101. MinecraftClient.getInstance().getNetworkHandler().getCommandSource().hasPermissionLevel(0);
  102. return hasOperatorPermission() && canUsePackets();
  103. } catch (NullPointerException e) {
  104. return true;
  105. }
  106. }
  107. public static boolean hasOperatorPermission() {
  108. try {
  109. return MinecraftClient.getInstance().getNetworkHandler().getCommandSource().hasPermissionLevel(1);
  110. } catch (NullPointerException e) {
  111. return true;
  112. }
  113. }
  114. public static boolean canUsePackets() {
  115. return ClientSidePacketRegistry.INSTANCE.canServerReceive(RoughlyEnoughItemsNetwork.CREATE_ITEMS_PACKET) && ClientSidePacketRegistry.INSTANCE.canServerReceive(RoughlyEnoughItemsNetwork.DELETE_ITEMS_PACKET);
  116. }
  117. @ApiStatus.Internal
  118. public static void syncRecipes(AtomicLong lastSync) {
  119. if (lastSync != null) {
  120. if (lastSync.get() > 0 && System.currentTimeMillis() - lastSync.get() <= 5000) {
  121. RoughlyEnoughItemsCore.LOGGER.warn("[REI] Suppressing Sync Recipes!");
  122. return;
  123. }
  124. lastSync.set(System.currentTimeMillis());
  125. }
  126. RecipeManager recipeManager = MinecraftClient.getInstance().getNetworkHandler().getRecipeManager();
  127. if (ConfigObject.getInstance().doesRegisterRecipesInAnotherThread()) {
  128. CompletableFuture.runAsync(() -> ((RecipeHelperImpl) RecipeHelper.getInstance()).recipesLoaded(recipeManager), SYNC_RECIPES);
  129. } else {
  130. ((RecipeHelperImpl) RecipeHelper.getInstance()).recipesLoaded(recipeManager);
  131. }
  132. }
  133. @Override
  134. public void onInitializeClient() {
  135. configManager = new ConfigManagerImpl();
  136. registerClothEvents();
  137. discoverPluginEntries();
  138. for (ModContainer modContainer : FabricLoader.getInstance().getAllMods()) {
  139. //noinspection deprecation
  140. if (modContainer.getMetadata().containsCustomElement("roughlyenoughitems:plugins"))
  141. RoughlyEnoughItemsCore.LOGGER.error("[REI] REI plugin from " + modContainer.getMetadata().getId() + " is not loaded because it is too old!");
  142. }
  143. ClientSidePacketRegistry.INSTANCE.register(RoughlyEnoughItemsNetwork.CREATE_ITEMS_MESSAGE_PACKET, (packetContext, packetByteBuf) -> {
  144. ItemStack stack = packetByteBuf.readItemStack();
  145. String player = packetByteBuf.readString(32767);
  146. packetContext.getPlayer().addChatMessage(new LiteralText(I18n.translate("text.rei.cheat_items").replaceAll("\\{item_name}", SearchArgument.tryGetItemStackName(stack.copy())).replaceAll("\\{item_count}", stack.copy().getCount() + "").replaceAll("\\{player_name}", player)), false);
  147. });
  148. ClientSidePacketRegistry.INSTANCE.register(RoughlyEnoughItemsNetwork.NOT_ENOUGH_ITEMS_PACKET, (packetContext, packetByteBuf) -> {
  149. Screen currentScreen = MinecraftClient.getInstance().currentScreen;
  150. if (currentScreen instanceof CraftingTableScreen) {
  151. RecipeBookWidget recipeBookGui = ((RecipeBookProvider) currentScreen).getRecipeBookWidget();
  152. RecipeBookGhostSlots ghostSlots = ((RecipeBookGuiHooks) recipeBookGui).rei_getGhostSlots();
  153. ghostSlots.reset();
  154. List<List<ItemStack>> input = Lists.newArrayList();
  155. int mapSize = packetByteBuf.readInt();
  156. for (int i = 0; i < mapSize; i++) {
  157. List<ItemStack> list = Lists.newArrayList();
  158. int count = packetByteBuf.readInt();
  159. for (int j = 0; j < count; j++) {
  160. list.add(packetByteBuf.readItemStack());
  161. }
  162. input.add(list);
  163. }
  164. ghostSlots.addSlot(Ingredient.ofItems(Items.STONE), 381203812, 12738291);
  165. CraftingTableContainer container = ((CraftingTableScreen) currentScreen).getContainer();
  166. for (int i = 0; i < input.size(); i++) {
  167. List<ItemStack> stacks = input.get(i);
  168. if (!stacks.isEmpty()) {
  169. Slot slot = container.getSlot(i + container.getCraftingResultSlotIndex() + 1);
  170. ghostSlots.addSlot(Ingredient.ofStacks(stacks.toArray(new ItemStack[0])), slot.xPosition, slot.yPosition);
  171. }
  172. }
  173. }
  174. });
  175. }
  176. private void discoverPluginEntries() {
  177. for (REIPluginEntry reiPlugin : FabricLoader.getInstance().getEntrypoints("rei_plugins", REIPluginEntry.class)) {
  178. try {
  179. if (!REIPluginV0.class.isAssignableFrom(reiPlugin.getClass()))
  180. throw new IllegalArgumentException("REI plugin is too old!");
  181. registerPlugin(reiPlugin);
  182. } catch (Exception e) {
  183. e.printStackTrace();
  184. RoughlyEnoughItemsCore.LOGGER.error("[REI] Can't load REI plugins from %s: %s", reiPlugin.getClass(), e.getLocalizedMessage());
  185. }
  186. }
  187. for (REIPluginV0 reiPlugin : FabricLoader.getInstance().getEntrypoints("rei_plugins_v0", REIPluginV0.class)) {
  188. try {
  189. registerPlugin(reiPlugin);
  190. } catch (Exception e) {
  191. e.printStackTrace();
  192. RoughlyEnoughItemsCore.LOGGER.error("[REI] Can't load REI plugins from %s: %s", reiPlugin.getClass(), e.getLocalizedMessage());
  193. }
  194. }
  195. // Test Only
  196. loadTestPlugins();
  197. }
  198. @ApiStatus.Internal
  199. public static boolean isDebugModeEnabled() {
  200. return System.getProperty("rei.test", "false").equals("true");
  201. }
  202. private void loadTestPlugins() {
  203. if (isDebugModeEnabled()) {
  204. registerPlugin(new REITestPlugin());
  205. }
  206. }
  207. private void registerClothEvents() {
  208. final Identifier recipeButtonTex = new Identifier("textures/gui/recipe_button.png");
  209. AtomicLong lastSync = new AtomicLong(-1);
  210. ClothClientHooks.SYNC_RECIPES.register((minecraftClient, recipeManager, synchronizeRecipesS2CPacket) -> syncRecipes(lastSync));
  211. ClothClientHooks.SCREEN_ADD_BUTTON.register((minecraftClient, screen, abstractButtonWidget) -> {
  212. if (ConfigObject.getInstance().doesDisableRecipeBook() && screen instanceof ContainerScreen && abstractButtonWidget instanceof TexturedButtonWidget)
  213. if (((RecipeBookButtonWidgetHooks) abstractButtonWidget).rei_getTexture().equals(recipeButtonTex))
  214. return ActionResult.FAIL;
  215. return ActionResult.PASS;
  216. });
  217. ClothClientHooks.SCREEN_INIT_POST.register((minecraftClient, screen, screenHooks) -> {
  218. if (screen instanceof ContainerScreen) {
  219. if (screen instanceof InventoryScreen && minecraftClient.interactionManager.hasCreativeInventory())
  220. return;
  221. ScreenHelper.setLastContainerScreen((ContainerScreen<?>) screen);
  222. boolean alreadyAdded = false;
  223. for (Element element : Lists.newArrayList(screenHooks.cloth_getInputListeners()))
  224. if (ContainerScreenOverlay.class.isAssignableFrom(element.getClass()))
  225. if (alreadyAdded)
  226. screenHooks.cloth_getInputListeners().remove(element);
  227. else
  228. alreadyAdded = true;
  229. if (!alreadyAdded)
  230. screenHooks.cloth_getInputListeners().add(ScreenHelper.getLastOverlay(true, false));
  231. }
  232. });
  233. ClothClientHooks.SCREEN_RENDER_POST.register((minecraftClient, screen, i, i1, v) -> {
  234. if (screen instanceof ContainerScreen)
  235. ScreenHelper.getLastOverlay().render(i, i1, v);
  236. });
  237. ClothClientHooks.SCREEN_MOUSE_DRAGGED.register((minecraftClient, screen, v, v1, i, v2, v3) -> {
  238. if (screen instanceof ContainerScreen)
  239. if (ScreenHelper.isOverlayVisible() && ScreenHelper.getLastOverlay().mouseDragged(v, v1, i, v2, v3))
  240. return ActionResult.SUCCESS;
  241. return ActionResult.PASS;
  242. });
  243. ClothClientHooks.SCREEN_MOUSE_CLICKED.register((minecraftClient, screen, v, v1, i) -> {
  244. if (screen instanceof CreativeInventoryScreen)
  245. if (ScreenHelper.isOverlayVisible() && ScreenHelper.getLastOverlay().mouseClicked(v, v1, i)) {
  246. screen.setFocused(ScreenHelper.getLastOverlay());
  247. if (i == 0)
  248. screen.setDragging(true);
  249. return ActionResult.SUCCESS;
  250. }
  251. return ActionResult.PASS;
  252. });
  253. ClothClientHooks.SCREEN_MOUSE_SCROLLED.register((minecraftClient, screen, v, v1, v2) -> {
  254. if (screen instanceof ContainerScreen)
  255. if (ScreenHelper.isOverlayVisible() && ScreenHelper.getLastOverlay().mouseScrolled(v, v1, v2))
  256. return ActionResult.SUCCESS;
  257. return ActionResult.PASS;
  258. });
  259. ClothClientHooks.SCREEN_CHAR_TYPED.register((minecraftClient, screen, character, keyCode) -> {
  260. if (screen instanceof ContainerScreen)
  261. if (ScreenHelper.getLastOverlay().charTyped(character, keyCode))
  262. return ActionResult.SUCCESS;
  263. return ActionResult.PASS;
  264. });
  265. ClothClientHooks.SCREEN_LATE_RENDER.register((minecraftClient, screen, i, i1, v) -> {
  266. if (!ScreenHelper.isOverlayVisible())
  267. return;
  268. if (screen instanceof ContainerScreen)
  269. ScreenHelper.getLastOverlay().lateRender(i, i1, v);
  270. });
  271. ClothClientHooks.SCREEN_KEY_PRESSED.register((minecraftClient, screen, i, i1, i2) -> {
  272. if (screen.getFocused() != null && screen.getFocused() instanceof TextFieldWidget || (screen.getFocused() instanceof RecipeBookWidget && ((RecipeBookGuiHooks) screen.getFocused()).rei_getSearchField() != null && ((RecipeBookGuiHooks) screen.getFocused()).rei_getSearchField().isFocused()))
  273. return ActionResult.PASS;
  274. if (screen instanceof ContainerScreen)
  275. if (ScreenHelper.getLastOverlay().keyPressed(i, i1, i2))
  276. return ActionResult.SUCCESS;
  277. if (screen instanceof ContainerScreen && ConfigObject.getInstance().doesDisableRecipeBook() && ConfigObject.getInstance().doesFixTabCloseContainer())
  278. if (i == 258 && minecraftClient.options.keyInventory.matchesKey(i, i1)) {
  279. minecraftClient.player.closeContainer();
  280. return ActionResult.SUCCESS;
  281. }
  282. return ActionResult.PASS;
  283. });
  284. }
  285. }