Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions xplat/src/main/java/dev/emi/emi/api/EmiApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import dev.emi.emi.EmiPort;
import dev.emi.emi.VanillaPlugin;
import dev.emi.emi.api.recipe.EmiRecipe;
import dev.emi.emi.api.recipe.EmiRecipeCategory;
Expand All @@ -25,6 +26,7 @@
import dev.emi.emi.recipe.EmiTagRecipe;
import dev.emi.emi.registry.EmiRecipes;
import dev.emi.emi.registry.EmiStackList;
import dev.emi.emi.registry.EmiStackPullers;
import dev.emi.emi.runtime.EmiFavorite;
import dev.emi.emi.runtime.EmiHistory;
import dev.emi.emi.runtime.EmiSidebars;
Expand All @@ -35,6 +37,12 @@
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.gui.screen.ingame.InventoryScreen;
import net.minecraft.client.network.ClientPlayerInteractionManager;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.slot.Slot;
import net.minecraft.screen.slot.SlotActionType;

public class EmiApi {
private static final MinecraftClient client = MinecraftClient.getInstance();
Expand Down Expand Up @@ -155,6 +163,88 @@ public static void displayUses(EmiIngredient stack) {
}
}

/**
* Looks inside the currently open container and attempts to pull a matching item
* into the player's inventory.
* @param stack The item to search for and pull
*/
public static void pullItem(EmiIngredient stack) {
if (stack.isEmpty() || !(stack instanceof EmiFavorite)) return;

long toPull = stack.getAmount();
if (stack instanceof EmiFavorite.Synthetic synthetic) {
toPull = synthetic.amount;
}

MinecraftClient client = MinecraftClient.getInstance();
ClientPlayerInteractionManager manager = client.interactionManager;
PlayerEntity player = client.player;
ScreenHandler screenHandler = player.currentScreenHandler;

List<EmiStack> searchStacks = stack.getEmiStacks();

// Attempt to pull using any registered custom pullers, and stop on a successful pull
if (EmiStackPullers.attemptPull(screenHandler, searchStacks, toPull)) return;

for (EmiStack searchStack : searchStacks) {

// Sweep through all the non-player inventory slots
for (Slot inventorySlot : screenHandler.slots) {
if (inventorySlot.inventory instanceof PlayerInventory || !inventorySlot.hasStack() || !inventorySlot.canTakeItems(player)) continue;

EmiStack fromStack = EmiStack.of(inventorySlot.getStack());
if (!searchStack.isEqual(fromStack)) continue;

long remaining = fromStack.getAmount();

// And attempt to smoosh it into the player inventory
for (Slot playerSlot : getQuickMoveDestinationSlots(screenHandler.slots, fromStack)) {
if (playerSlot.hasStack() && !EmiStack.of(playerSlot.getStack()).isEqual(searchStack, EmiPort.compareStrict())) continue;

EmiStack playerStack = EmiStack.of(playerSlot.getStack());

long maxTransfer = fromStack.getItemStack().getMaxCount() - playerStack.getAmount();
long amountToTransfer = Math.min(maxTransfer, toPull);

manager.clickSlot(screenHandler.syncId, inventorySlot.id, 0, SlotActionType.PICKUP, player);

if (remaining <= amountToTransfer) {
manager.clickSlot(screenHandler.syncId, playerSlot.id, 0, SlotActionType.PICKUP, player);
toPull -= remaining;
remaining = 0;
} else {
while (amountToTransfer > 0) {
manager.clickSlot(screenHandler.syncId, playerSlot.id, 1, SlotActionType.PICKUP, player);
toPull--;

amountToTransfer--;
remaining--;
}

// put that thing back where it came from, or so help me...!
manager.clickSlot(screenHandler.syncId, inventorySlot.id, 0, SlotActionType.PICKUP, player);
}

if (toPull <= 0) return;
if (remaining <= 0) break;
}
}
}
}

private static List<Slot> getQuickMoveDestinationSlots(List<Slot> slots, EmiStack stackToMove) {
List<Slot> destinationSlots = Lists.newArrayList();
for (Slot candidateSlot : slots) {
if (candidateSlot.inventory instanceof PlayerInventory && candidateSlot.canInsert(stackToMove.getItemStack())) {
destinationSlots.add(candidateSlot);
}
}

// Sort such that we fill existing stacks first where possible
destinationSlots.sort((a, b) -> Boolean.compare(b.hasStack(), a.hasStack()));
return destinationSlots;
}

public static void viewRecipeTree() {
if (client.currentScreen == null) {
client.setScreen(new InventoryScreen(client.player));
Expand Down
12 changes: 12 additions & 0 deletions xplat/src/main/java/dev/emi/emi/api/EmiRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,18 @@ default void removeEmiStacks(EmiStack stack) {
*/
void addGenericStackProvider(EmiStackProvider<Screen> provider);

/**
* Adds an EmiStackPuller to screens of a given class.
* Stack pullers take items from the currently open container and move them into the player's inventory.
*/
<T extends ScreenHandler> void addStackPuller(Class<T> clazz, EmiStackPuller<T> puller);

/**
* Adds an EmiStackProvider to every screen.
* Stack pullers take items from the currently open container and move them into the player's inventory.
*/
void addGenericStackPuller(EmiStackPuller<ScreenHandler> puller);

/**
* Adds a default compraison method for a stack key.
* @param key A stack key such as an item or fluid.
Expand Down
18 changes: 18 additions & 0 deletions xplat/src/main/java/dev/emi/emi/api/EmiStackPuller.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dev.emi.emi.api;

import java.util.List;

import dev.emi.emi.api.stack.EmiStack;
import net.minecraft.screen.ScreenHandler;

public interface EmiStackPuller<T extends ScreenHandler> {

/**
* Pull stacks matching the given stack into the player inventory until toPull is reached or items run out
* @param stacks All valid items for pulling, for tag favorites this will contain every item in the tag
* @param toPull The desired number of items to pull into the players inventory
* @return whether or not the pull has been successfully handled by this, `true` stops execution of further handlers
*/
public boolean pullStack(T screenHandler, List<EmiStack> ingredients, long toPull);

}
6 changes: 5 additions & 1 deletion xplat/src/main/java/dev/emi/emi/config/EmiConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ public class EmiConfig {
@Comment("Whether to render the header buttons and page count for the left sidebar")
@ConfigValue("ui.left-sidebar-header")
public static HeaderType leftSidebarHeader = HeaderType.VISIBLE;

@ConfigGroupEnd
@Comment("Which theme to use for the left sidebar")
@ConfigValue("ui.left-sidebar-theme")
Expand Down Expand Up @@ -378,6 +378,10 @@ public class EmiConfig {
@ConfigValue("binds.favorite")
public static EmiBind favorite = new EmiBind("key.emi.favorite", GLFW.GLFW_KEY_A);

@Comment("Pull the stack from any currently open inventory that contains it.")
@ConfigValue("binds.pull-item")
public static EmiBind pullItem = new EmiBind("key.emi.pull_item", GLFW.GLFW_KEY_V);

@Comment("Set the default recipe for a given stack in the output of a recipe to that recipe.")
@ConfigValue("binds.default-stack")
public static EmiBind defaultStack = new EmiBind("key.emi.default_stack",
Expand Down
13 changes: 12 additions & 1 deletion xplat/src/main/java/dev/emi/emi/registry/EmiRegistryImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import dev.emi.emi.api.EmiExclusionArea;
import dev.emi.emi.api.EmiRegistry;
import dev.emi.emi.api.EmiStackProvider;
import dev.emi.emi.api.EmiStackPuller;
import dev.emi.emi.api.recipe.EmiRecipe;
import dev.emi.emi.api.recipe.EmiRecipeCategory;
import dev.emi.emi.api.recipe.EmiRecipeDecorator;
Expand Down Expand Up @@ -131,7 +132,17 @@ public <T extends Screen> void addStackProvider(Class<T> clazz, EmiStackProvider
public void addGenericStackProvider(EmiStackProvider<Screen> provider) {
EmiStackProviders.generic.add(provider);
}


@Override
public <T extends ScreenHandler> void addStackPuller(Class<T> clazz, EmiStackPuller<T> puller) {
EmiStackPullers.fromClass.put(clazz, puller);
}

@Override
public void addGenericStackPuller(EmiStackPuller<ScreenHandler> puller) {
EmiStackPullers.generic.add(puller);
}

@Override
public <T extends ScreenHandler> void addRecipeHandler(ScreenHandlerType<T> type, EmiRecipeHandler<T> handler) {
EmiRecipeFiller.handlers.computeIfAbsent(type, (c) -> Lists.newArrayList()).add(handler);
Expand Down
37 changes: 37 additions & 0 deletions xplat/src/main/java/dev/emi/emi/registry/EmiStackPullers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package dev.emi.emi.registry;

import java.util.Map;
import java.util.List;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import dev.emi.emi.api.EmiStackPuller;
import dev.emi.emi.api.stack.EmiStack;
import net.minecraft.screen.ScreenHandler;

public class EmiStackPullers {

public static Map<Class<? extends ScreenHandler>, EmiStackPuller<?>> fromClass = Maps.newHashMap();
public static List<EmiStackPuller<?>> generic = Lists.newArrayList();

public static void clear() {
fromClass.clear();
generic.clear();
}

@SuppressWarnings({ "rawtypes", "unchecked" })
public static boolean attemptPull(ScreenHandler screenHandler, List<EmiStack> stacks, long toPull) {
if (fromClass.containsKey(screenHandler.getClass())) {
EmiStackPuller puller = fromClass.get(screenHandler.getClass());
if (puller.pullStack(screenHandler, stacks, toPull)) return true;
}

for (EmiStackPuller puller : generic) {
if (puller.pullStack(screenHandler, stacks, toPull)) return true;
}

return false;
}

}
2 changes: 2 additions & 0 deletions xplat/src/main/java/dev/emi/emi/runtime/EmiReloadManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import dev.emi.emi.registry.EmiRegistryImpl;
import dev.emi.emi.registry.EmiStackList;
import dev.emi.emi.registry.EmiStackProviders;
import dev.emi.emi.registry.EmiStackPullers;
import dev.emi.emi.registry.EmiTags;
import dev.emi.emi.screen.EmiScreenManager;
import dev.emi.emi.search.EmiSearch;
Expand Down Expand Up @@ -130,6 +131,7 @@ public void run() {
EmiExclusionAreas.clear();
EmiDragDropHandlers.clear();
EmiStackProviders.clear();
EmiStackPullers.clear();
EmiRecipeFiller.clear();
EmiHidden.clear();
EmiTags.ADAPTERS_BY_CLASS.map().clear();
Expand Down
3 changes: 3 additions & 0 deletions xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -1241,6 +1241,9 @@ public static boolean stackInteraction(EmiStackInteraction stack, Function<EmiBi
} else if (function.apply(EmiConfig.viewUses)) {
EmiApi.displayUses(ingredient);
return true;
} else if (function.apply(EmiConfig.pullItem)) {
EmiApi.pullItem(ingredient);
return true;
} else if (function.apply(EmiConfig.favorite)) {
EmiFavorites.addFavorite(ingredient, stack.getRecipeContext());
repopulatePanels(SidebarType.FAVORITES);
Expand Down
1 change: 1 addition & 0 deletions xplat/src/main/resources/assets/emi/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"key.emi.clear_search": "Clear Search",
"key.emi.view_recipes": "View Recipes",
"key.emi.view_uses": "View Uses",
"key.emi.pull_item": "Pull Item",
"key.emi.favorite": "Favorite",
"key.emi.set_default": "Set Default",
"key.emi.default_stack": "Default Stack",
Expand Down