RoughlyEnoughItemsCore.java 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. /*
  2. * This file is licensed under the MIT License, part of Roughly Enough Items.
  3. * Copyright (c) 2018, 2019, 2020 shedaniel
  4. *
  5. * Permission is hereby granted, free of charge, to any person obtaining a copy
  6. * of this software and associated documentation files (the "Software"), to deal
  7. * in the Software without restriction, including without limitation the rights
  8. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. * copies of the Software, and to permit persons to whom the Software is
  10. * furnished to do so, subject to the following conditions:
  11. *
  12. * The above copyright notice and this permission notice shall be included in all
  13. * copies or substantial portions of the Software.
  14. *
  15. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21. * SOFTWARE.
  22. */
  23. package me.shedaniel.rei;
  24. import com.google.common.collect.Lists;
  25. import com.google.common.collect.Maps;
  26. import me.shedaniel.cloth.hooks.ClothClientHooks;
  27. import me.shedaniel.math.api.Executor;
  28. import me.shedaniel.rei.api.*;
  29. import me.shedaniel.rei.api.plugins.REIPluginV0;
  30. import me.shedaniel.rei.gui.ContainerScreenOverlay;
  31. import me.shedaniel.rei.impl.*;
  32. import me.shedaniel.rei.tests.plugin.REITestPlugin;
  33. import net.fabricmc.api.ClientModInitializer;
  34. import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
  35. import net.fabricmc.loader.api.FabricLoader;
  36. import net.fabricmc.loader.api.ModContainer;
  37. import net.minecraft.client.MinecraftClient;
  38. import net.minecraft.client.gui.Element;
  39. import net.minecraft.client.gui.screen.Screen;
  40. import net.minecraft.client.gui.screen.ingame.CraftingScreen;
  41. import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen;
  42. import net.minecraft.client.gui.screen.ingame.HandledScreen;
  43. import net.minecraft.client.gui.screen.ingame.InventoryScreen;
  44. import net.minecraft.client.gui.screen.recipebook.RecipeBookGhostSlots;
  45. import net.minecraft.client.gui.screen.recipebook.RecipeBookProvider;
  46. import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget;
  47. import net.minecraft.client.gui.widget.TextFieldWidget;
  48. import net.minecraft.client.gui.widget.TexturedButtonWidget;
  49. import net.minecraft.client.resource.language.I18n;
  50. import net.minecraft.item.ItemStack;
  51. import net.minecraft.item.Items;
  52. import net.minecraft.recipe.Ingredient;
  53. import net.minecraft.recipe.RecipeManager;
  54. import net.minecraft.screen.CraftingScreenHandler;
  55. import net.minecraft.screen.slot.Slot;
  56. import net.minecraft.text.LiteralText;
  57. import net.minecraft.util.ActionResult;
  58. import net.minecraft.util.Identifier;
  59. import org.apache.logging.log4j.LogManager;
  60. import org.apache.logging.log4j.Logger;
  61. import org.jetbrains.annotations.ApiStatus;
  62. import java.io.File;
  63. import java.io.IOException;
  64. import java.lang.reflect.Field;
  65. import java.util.*;
  66. import java.util.concurrent.CompletableFuture;
  67. import java.util.concurrent.ExecutorService;
  68. import java.util.concurrent.Executors;
  69. @ApiStatus.Internal
  70. public class RoughlyEnoughItemsCore implements ClientModInitializer {
  71. @ApiStatus.Internal public static final Logger LOGGER = LogManager.getFormatterLogger("REI");
  72. private static final RecipeHelper RECIPE_HELPER = new RecipeHelperImpl();
  73. private static final EntryRegistry ENTRY_REGISTRY = new EntryRegistryImpl();
  74. private static final DisplayHelper DISPLAY_HELPER = new DisplayHelperImpl();
  75. private static final Map<Identifier, REIPluginEntry> plugins = Maps.newHashMap();
  76. private static final ExecutorService SYNC_RECIPES = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "REI-SyncRecipes"));
  77. private static ConfigManager configManager;
  78. @ApiStatus.Experimental
  79. public static boolean isLeftModePressed = false;
  80. @ApiStatus.Internal
  81. public static RecipeHelper getRecipeHelper() {
  82. return RECIPE_HELPER;
  83. }
  84. @ApiStatus.Internal
  85. public static ConfigManager getConfigManager() {
  86. return configManager;
  87. }
  88. @ApiStatus.Internal
  89. public static EntryRegistry getEntryRegistry() {
  90. return ENTRY_REGISTRY;
  91. }
  92. @ApiStatus.Internal
  93. public static DisplayHelper getDisplayHelper() {
  94. return DISPLAY_HELPER;
  95. }
  96. /**
  97. * Registers a REI plugin
  98. *
  99. * @param plugin the plugin instance
  100. * @return the plugin itself
  101. */
  102. @ApiStatus.Internal
  103. public static REIPluginEntry registerPlugin(REIPluginEntry plugin) {
  104. plugins.put(plugin.getPluginIdentifier(), plugin);
  105. RoughlyEnoughItemsCore.LOGGER.debug("Registered plugin %s from %s", plugin.getPluginIdentifier().toString(), plugin.getClass().getSimpleName());
  106. return plugin;
  107. }
  108. public static List<REIPluginEntry> getPlugins() {
  109. return new LinkedList<>(plugins.values());
  110. }
  111. public static Optional<Identifier> getPluginIdentifier(REIPluginEntry plugin) {
  112. for (Identifier identifier : plugins.keySet())
  113. if (identifier != null && plugins.get(identifier).equals(plugin))
  114. return Optional.of(identifier);
  115. return Optional.empty();
  116. }
  117. public static boolean hasPermissionToUsePackets() {
  118. try {
  119. MinecraftClient.getInstance().getNetworkHandler().getCommandSource().hasPermissionLevel(0);
  120. return hasOperatorPermission() && canUsePackets();
  121. } catch (NullPointerException e) {
  122. return true;
  123. }
  124. }
  125. public static boolean hasOperatorPermission() {
  126. try {
  127. return MinecraftClient.getInstance().getNetworkHandler().getCommandSource().hasPermissionLevel(1);
  128. } catch (NullPointerException e) {
  129. return true;
  130. }
  131. }
  132. public static boolean canUsePackets() {
  133. return ClientSidePacketRegistry.INSTANCE.canServerReceive(RoughlyEnoughItemsNetwork.CREATE_ITEMS_PACKET) && ClientSidePacketRegistry.INSTANCE.canServerReceive(RoughlyEnoughItemsNetwork.DELETE_ITEMS_PACKET);
  134. }
  135. @ApiStatus.Internal
  136. public static void syncRecipes(long[] lastSync) {
  137. if (lastSync != null) {
  138. if (lastSync[0] > 0 && System.currentTimeMillis() - lastSync[0] <= 5000) {
  139. RoughlyEnoughItemsCore.LOGGER.warn("Suppressing Sync Recipes!");
  140. return;
  141. }
  142. lastSync[0] = System.currentTimeMillis();
  143. }
  144. RecipeManager recipeManager = MinecraftClient.getInstance().getNetworkHandler().getRecipeManager();
  145. if (ConfigObject.getInstance().doesRegisterRecipesInAnotherThread()) {
  146. CompletableFuture.runAsync(() -> ((RecipeHelperImpl) RecipeHelper.getInstance()).recipesLoaded(recipeManager), SYNC_RECIPES);
  147. } else {
  148. ((RecipeHelperImpl) RecipeHelper.getInstance()).recipesLoaded(recipeManager);
  149. }
  150. }
  151. @ApiStatus.Internal
  152. public static boolean isDebugModeEnabled() {
  153. return System.getProperty("rei.test", "false").equals("true");
  154. }
  155. @SuppressWarnings("deprecation")
  156. @Override
  157. public void onInitializeClient() {
  158. configManager = new ConfigManagerImpl();
  159. detectFabricLoader();
  160. registerClothEvents();
  161. discoverPluginEntries();
  162. for (ModContainer modContainer : FabricLoader.getInstance().getAllMods()) {
  163. if (modContainer.getMetadata().containsCustomElement("roughlyenoughitems:plugins"))
  164. RoughlyEnoughItemsCore.LOGGER.error("REI plugin from " + modContainer.getMetadata().getId() + " is not loaded because it is too old!");
  165. }
  166. boolean networkingLoaded = FabricLoader.getInstance().isModLoaded("fabric-networking-v0");
  167. if (!networkingLoaded) {
  168. RoughlyEnoughItemsState.error("Fabric API is not installed!", "https://www.curseforge.com/minecraft/mc-mods/fabric-api/files/all");
  169. return;
  170. }
  171. Executor.run(() -> () -> {
  172. ClientSidePacketRegistry.INSTANCE.register(RoughlyEnoughItemsNetwork.CREATE_ITEMS_MESSAGE_PACKET, (packetContext, packetByteBuf) -> {
  173. ItemStack stack = packetByteBuf.readItemStack();
  174. String player = packetByteBuf.readString(32767);
  175. packetContext.getPlayer().sendMessage(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);
  176. });
  177. ClientSidePacketRegistry.INSTANCE.register(RoughlyEnoughItemsNetwork.NOT_ENOUGH_ITEMS_PACKET, (packetContext, packetByteBuf) -> {
  178. Screen currentScreen = MinecraftClient.getInstance().currentScreen;
  179. if (currentScreen instanceof CraftingScreen) {
  180. RecipeBookWidget recipeBookGui = ((RecipeBookProvider) currentScreen).getRecipeBookWidget();
  181. RecipeBookGhostSlots ghostSlots = recipeBookGui.ghostSlots;
  182. ghostSlots.reset();
  183. List<List<ItemStack>> input = Lists.newArrayList();
  184. int mapSize = packetByteBuf.readInt();
  185. for (int i = 0; i < mapSize; i++) {
  186. List<ItemStack> list = Lists.newArrayList();
  187. int count = packetByteBuf.readInt();
  188. for (int j = 0; j < count; j++) {
  189. list.add(packetByteBuf.readItemStack());
  190. }
  191. input.add(list);
  192. }
  193. ghostSlots.addSlot(Ingredient.ofItems(Items.STONE), 381203812, 12738291);
  194. CraftingScreenHandler screenHandler = ((CraftingScreen) currentScreen).getScreenHandler();
  195. for (int i = 0; i < input.size(); i++) {
  196. List<ItemStack> stacks = input.get(i);
  197. if (!stacks.isEmpty()) {
  198. Slot slot = screenHandler.getSlot(i + screenHandler.getCraftingResultSlotIndex() + 1);
  199. ghostSlots.addSlot(Ingredient.ofStacks(stacks.toArray(new ItemStack[0])), slot.x, slot.y);
  200. }
  201. }
  202. }
  203. });
  204. });
  205. }
  206. private void detectFabricLoader() {
  207. Executor.run(() -> () -> {
  208. try {
  209. FabricLoader instance = FabricLoader.getInstance();
  210. for (Field field : instance.getClass().getDeclaredFields()) {
  211. if (Logger.class.isAssignableFrom(field.getType())) {
  212. field.setAccessible(true);
  213. Logger logger = (Logger) field.get(instance);
  214. if (logger.getName().toLowerCase(Locale.ROOT).contains("subsystem")) {
  215. if (!new File(instance.getConfigDirectory(), "roughlyenoughitems/.ignoresubsystem").exists()) {
  216. RoughlyEnoughItemsState.warn("Subsystem is detected (probably though Aristois), please contact support from them if anything happens.");
  217. RoughlyEnoughItemsState.onContinue(() -> {
  218. try {
  219. new File(instance.getConfigDirectory(), "roughlyenoughitems").mkdirs();
  220. new File(instance.getConfigDirectory(), "roughlyenoughitems/.ignoresubsystem").createNewFile();
  221. } catch (IOException e) {
  222. e.printStackTrace();
  223. }
  224. });
  225. }
  226. }
  227. }
  228. }
  229. } catch (Throwable ignored) {
  230. }
  231. });
  232. }
  233. private void discoverPluginEntries() {
  234. for (REIPluginEntry reiPlugin : FabricLoader.getInstance().getEntrypoints("rei_plugins", REIPluginEntry.class)) {
  235. try {
  236. if (!REIPluginV0.class.isAssignableFrom(reiPlugin.getClass()))
  237. throw new IllegalArgumentException("REI plugin is too old!");
  238. registerPlugin(reiPlugin);
  239. } catch (Exception e) {
  240. e.printStackTrace();
  241. RoughlyEnoughItemsCore.LOGGER.error("Can't load REI plugins from %s: %s", reiPlugin.getClass(), e.getLocalizedMessage());
  242. }
  243. }
  244. for (REIPluginV0 reiPlugin : FabricLoader.getInstance().getEntrypoints("rei_plugins_v0", REIPluginV0.class)) {
  245. try {
  246. registerPlugin(reiPlugin);
  247. } catch (Exception e) {
  248. e.printStackTrace();
  249. RoughlyEnoughItemsCore.LOGGER.error("Can't load REI plugins from %s: %s", reiPlugin.getClass(), e.getLocalizedMessage());
  250. }
  251. }
  252. // Test Only
  253. loadTestPlugins();
  254. }
  255. private void loadTestPlugins() {
  256. if (isDebugModeEnabled()) {
  257. registerPlugin(new REITestPlugin());
  258. }
  259. }
  260. private boolean shouldReturn(Class<?> screen) {
  261. try {
  262. for (OverlayDecider decider : DisplayHelper.getInstance().getAllOverlayDeciders()) {
  263. if (!decider.isHandingScreen(screen))
  264. continue;
  265. ActionResult result = decider.shouldScreenBeOverlayed(screen);
  266. if (result != ActionResult.PASS)
  267. return result == ActionResult.FAIL || REIHelper.getInstance().getPreviousHandledScreen() == null;
  268. }
  269. } catch (ConcurrentModificationException ignored) {
  270. }
  271. return true;
  272. }
  273. private void registerClothEvents() {
  274. final Identifier recipeButtonTex = new Identifier("textures/gui/recipe_button.png");
  275. long[] lastSync = {-1};
  276. ClothClientHooks.SYNC_RECIPES.register((minecraftClient, recipeManager, synchronizeRecipesS2CPacket) -> syncRecipes(lastSync));
  277. ClothClientHooks.SCREEN_ADD_BUTTON.register((minecraftClient, screen, abstractButtonWidget) -> {
  278. if (ConfigObject.getInstance().doesDisableRecipeBook() && screen instanceof HandledScreen && abstractButtonWidget instanceof TexturedButtonWidget)
  279. if (((TexturedButtonWidget) abstractButtonWidget).texture.equals(recipeButtonTex))
  280. return ActionResult.FAIL;
  281. return ActionResult.PASS;
  282. });
  283. ClothClientHooks.SCREEN_INIT_POST.register((minecraftClient, screen, screenHooks) -> {
  284. if (screen instanceof InventoryScreen && minecraftClient.interactionManager.hasCreativeInventory())
  285. return;
  286. if (shouldReturn(screen.getClass()))
  287. return;
  288. if (screen instanceof HandledScreen)
  289. ScreenHelper.setLastHandledScreen((HandledScreen<?>) screen);
  290. boolean alreadyAdded = false;
  291. for (Element element : Lists.newArrayList(screenHooks.cloth_getChildren()))
  292. if (ContainerScreenOverlay.class.isAssignableFrom(element.getClass()))
  293. if (alreadyAdded)
  294. screenHooks.cloth_getChildren().remove(element);
  295. else
  296. alreadyAdded = true;
  297. if (!alreadyAdded)
  298. screenHooks.cloth_getChildren().add(ScreenHelper.getLastOverlay(true, false));
  299. });
  300. ClothClientHooks.SCREEN_RENDER_POST.register((minecraftClient, screen, i, i1, v) -> {
  301. if (shouldReturn(screen.getClass()))
  302. return;
  303. ScreenHelper.getLastOverlay().render(i, i1, v);
  304. });
  305. ClothClientHooks.SCREEN_MOUSE_DRAGGED.register((minecraftClient, screen, v, v1, i, v2, v3) -> {
  306. if (shouldReturn(screen.getClass()))
  307. return ActionResult.PASS;
  308. if (ScreenHelper.isOverlayVisible() && ScreenHelper.getLastOverlay().mouseDragged(v, v1, i, v2, v3))
  309. return ActionResult.SUCCESS;
  310. return ActionResult.PASS;
  311. });
  312. ClothClientHooks.SCREEN_MOUSE_CLICKED.register((minecraftClient, screen, v, v1, i) -> {
  313. isLeftModePressed = true;
  314. if (screen instanceof CreativeInventoryScreen)
  315. if (ScreenHelper.isOverlayVisible() && ScreenHelper.getLastOverlay().mouseClicked(v, v1, i)) {
  316. screen.setFocused(ScreenHelper.getLastOverlay());
  317. if (i == 0)
  318. screen.setDragging(true);
  319. return ActionResult.SUCCESS;
  320. }
  321. return ActionResult.PASS;
  322. });
  323. ClothClientHooks.SCREEN_MOUSE_RELEASED.register((minecraftClient, screen, v, v1, i) -> {
  324. isLeftModePressed = false;
  325. return ActionResult.PASS;
  326. });
  327. ClothClientHooks.SCREEN_MOUSE_SCROLLED.register((minecraftClient, screen, v, v1, v2) -> {
  328. if (shouldReturn(screen.getClass()))
  329. return ActionResult.PASS;
  330. if (ScreenHelper.isOverlayVisible() && ScreenHelper.getLastOverlay().mouseScrolled(v, v1, v2))
  331. return ActionResult.SUCCESS;
  332. return ActionResult.PASS;
  333. });
  334. ClothClientHooks.SCREEN_CHAR_TYPED.register((minecraftClient, screen, character, keyCode) -> {
  335. if (shouldReturn(screen.getClass()))
  336. return ActionResult.PASS;
  337. if (ScreenHelper.getLastOverlay().charTyped(character, keyCode))
  338. return ActionResult.SUCCESS;
  339. return ActionResult.PASS;
  340. });
  341. ClothClientHooks.SCREEN_LATE_RENDER.register((minecraftClient, screen, i, i1, v) -> {
  342. if (!ScreenHelper.isOverlayVisible())
  343. return;
  344. if (shouldReturn(screen.getClass()))
  345. return;
  346. ScreenHelper.getLastOverlay().lateRender(i, i1, v);
  347. });
  348. ClothClientHooks.SCREEN_KEY_PRESSED.register((minecraftClient, screen, i, i1, i2) -> {
  349. if (screen.getFocused() != null && screen.getFocused() instanceof TextFieldWidget || (screen.getFocused() instanceof RecipeBookWidget && ((RecipeBookWidget) screen.getFocused()).searchField != null && ((RecipeBookWidget) screen.getFocused()).searchField.isFocused()))
  350. return ActionResult.PASS;
  351. if (shouldReturn(screen.getClass()))
  352. return ActionResult.PASS;
  353. if (ScreenHelper.getLastOverlay().keyPressed(i, i1, i2))
  354. return ActionResult.SUCCESS;
  355. if (screen instanceof HandledScreen && ConfigObject.getInstance().doesDisableRecipeBook() && ConfigObject.getInstance().doesFixTabCloseContainer())
  356. if (i == 258 && minecraftClient.options.keyInventory.matchesKey(i, i1)) {
  357. minecraftClient.player.closeHandledScreen();
  358. return ActionResult.SUCCESS;
  359. }
  360. return ActionResult.PASS;
  361. });
  362. }
  363. }