mirror of
https://github.com/GTNewHorizons/StructureLib.git
synced 2026-11-13 00:37:55 +00:00
Extract fluid containers from the same inventories as items
This commit is contained in:
1 parent
98101c00aa
commit
51a85dfa1b
3 files changed
+177
-37
No files matched your search
@@ -1,5 +1,7 @@
|
||||
package com.gtnewhorizon.structurelib.fluid;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
@@ -11,6 +13,10 @@ import net.minecraftforge.fluids.FluidStack;
|
||||
import net.minecraftforge.fluids.IFluidContainerItem;
|
||||
|
||||
import com.gtnewhorizon.structurelib.SortedRegistry;
|
||||
import com.gtnewhorizon.structurelib.util.InventoryIterable;
|
||||
import com.gtnewhorizon.structurelib.util.InventoryUtility;
|
||||
import com.gtnewhorizon.structurelib.util.InventoryUtility.InventoryProvider;
|
||||
import com.gtnewhorizon.structurelib.util.InventoryUtility.ItemStackExtractor;
|
||||
|
||||
/**
|
||||
* The registry of every way StructureLib knows how to pull fluid out of an item, plus the inventory walk that uses
|
||||
@@ -49,11 +55,16 @@ public class FluidContainerExtractors {
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract fluid from the main inventory of given player.
|
||||
* Extract fluid from every inventory associated with the given player.
|
||||
* <p>
|
||||
* Armor slots are excluded. Containers nested inside another item, e.g. inside a backpack, are not looked into. A
|
||||
* mod that wants to support those can register its own extractor, or hand its own fluid source to autoplace through
|
||||
* its autoplace environment.
|
||||
* This walks exactly the same inventories as item autoplace does, see
|
||||
* {@link com.gtnewhorizon.structurelib.util.InventoryUtility}: the main inventory first, then whatever else is
|
||||
* registered, which currently means the ender chest when a mod asked for it and every inventory a mod registered
|
||||
* for a backpack. Inside every item that holds an inventory the walk goes one level deep, so a bucket a player left
|
||||
* in a backpack is found, and a mod that already lets autoplace take items out of its backpacks does not have to do
|
||||
* anything for the fluid containers inside them.
|
||||
* <p>
|
||||
* Armor slots are excluded, as there is no fluid container that goes into one.
|
||||
*
|
||||
* @param player where to take fluid from
|
||||
* @param resource what fluid to take, and how much of it at most
|
||||
@@ -61,22 +72,49 @@ public class FluidContainerExtractors {
|
||||
* @return what has actually been taken. Never null, and never holding more than requested.
|
||||
*/
|
||||
public static FluidStack takeFromPlayer(EntityPlayerMP player, FluidStack resource, boolean simulate) {
|
||||
// Only the main inventory, as there is no fluid container that goes into an armor slot.
|
||||
FluidStack taken = takeFromInventory(
|
||||
player.inventory,
|
||||
resource,
|
||||
simulate,
|
||||
player,
|
||||
player.inventory.mainInventory.length);
|
||||
// A fake player, e.g. one a structure preview tool uses, has no connection to sync the inventory over.
|
||||
if (!simulate && taken.amount > 0 && player.playerNetServerHandler != null) {
|
||||
player.inventoryContainer.detectAndSendChanges();
|
||||
if (player == null) throw new IllegalArgumentException();
|
||||
if (resource == null || resource.getFluid() == null) throw new IllegalArgumentException();
|
||||
int remaining = resource.amount;
|
||||
for (InventoryProvider<?> provider : InventoryUtility.getInventoryProviders(player)) {
|
||||
if (remaining <= 0) break;
|
||||
remaining -= takeFromProvider(player, provider, new FluidStack(resource, remaining), simulate);
|
||||
}
|
||||
return new FluidStack(resource, resource.amount - remaining);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract fluid from one registered inventory of a player, and let its provider know it changed.
|
||||
* <p>
|
||||
* The wildcard of {@link InventoryProvider} is captured by this method's type parameter, which is what allows the
|
||||
* inventory to be handed back to {@code markDirty}.
|
||||
*/
|
||||
private static <R extends Iterable<ItemStack>> int takeFromProvider(EntityPlayerMP player,
|
||||
InventoryProvider<R> provider, FluidStack resource, boolean simulate) {
|
||||
R inventory = provider.getInventory(player);
|
||||
if (inventory == null) return 0;
|
||||
// Only an inventory that is backed by an IInventory can have a slot written back, which a container such as a
|
||||
// bucket needs when it is emptied into the world. A provider that hands out its stacks some other way is
|
||||
// skipped rather than drained in place, as draining without replacing would leave the player a filled
|
||||
// container that is already spent.
|
||||
if (!(inventory instanceof InventoryIterable<?>iterable)) return 0;
|
||||
int taken = takeFromInventory(iterable.getInventory(), resource, simulate, player, iterable.getMaxSlot(), true);
|
||||
if (taken > 0 && !simulate && hasConnection(player)) provider.markDirty(inventory);
|
||||
return taken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the inventory of given player has a client to be kept in sync with. A fake player, e.g. the one a
|
||||
* structure preview tool uses, has no connection, and an inventory it changed never has to be sent anywhere.
|
||||
*/
|
||||
private static boolean hasConnection(EntityPlayerMP player) {
|
||||
return player.playerNetServerHandler != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract fluid from given inventory.
|
||||
* <p>
|
||||
* Containers held inside an item of this inventory, e.g. in a backpack, are not looked into here. Take from a
|
||||
* player through {@link #takeFromPlayer(EntityPlayerMP, FluidStack, boolean)} to look into those as well.
|
||||
*
|
||||
* @param inv where to take fluid from
|
||||
* @param resource what fluid to take, and how much of it at most
|
||||
@@ -84,38 +122,94 @@ public class FluidContainerExtractors {
|
||||
* @return what has actually been taken. Never null, and never holding more than requested.
|
||||
*/
|
||||
public static FluidStack takeFromInventory(IInventory inv, FluidStack resource, boolean simulate) {
|
||||
return takeFromInventory(inv, resource, simulate, null, -1);
|
||||
}
|
||||
|
||||
private static FluidStack takeFromInventory(IInventory inv, FluidStack resource, boolean simulate,
|
||||
@Nullable EntityPlayerMP player, int maxSlot) {
|
||||
if (inv == null) throw new IllegalArgumentException();
|
||||
if (resource == null || resource.getFluid() == null) throw new IllegalArgumentException();
|
||||
return new FluidStack(resource, takeFromInventory(inv, resource, simulate, null, -1, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk the slots of one inventory and take as much of the requested fluid as they hold.
|
||||
*
|
||||
* @param maxSlot slot to stop at, exclusive. -1 for every slot
|
||||
* @param recursive whether to look inside the items of this inventory, e.g. into a backpack
|
||||
* @return how much has been taken, never more than requested
|
||||
*/
|
||||
private static int takeFromInventory(IInventory inv, FluidStack resource, boolean simulate,
|
||||
@Nullable EntityPlayerMP player, int maxSlot, boolean recursive) {
|
||||
int slots = maxSlot < 0 ? inv.getSizeInventory() : Math.min(maxSlot, inv.getSizeInventory());
|
||||
int remaining = resource.amount;
|
||||
for (int slot = 0; slot < slots && remaining > 0; slot++) {
|
||||
ItemStack stack = inv.getStackInSlot(slot);
|
||||
if (stack == null || stack.getItem() == null || stack.stackSize <= 0) continue;
|
||||
for (FluidContainerExtractor extractor : EXTRACTORS.getPlayerOrdering(player)) {
|
||||
if (!extractor.isValidSource(stack)) continue;
|
||||
FluidStack request = new FluidStack(resource, remaining);
|
||||
boolean converts = extractor.convertsContainer(stack, request);
|
||||
ItemStack replacement = converts ? extractor.getReplacement(stack) : null;
|
||||
// A container that turns into another item can only be replaced when it is the only item in its slot.
|
||||
if (replacement != null && stack.stackSize != 1) continue;
|
||||
int drained = extractor.drain(stack, request, simulate);
|
||||
if (drained <= 0) continue;
|
||||
remaining -= Math.min(remaining, drained);
|
||||
if (!simulate && converts) {
|
||||
// A null replacement means the whole slot is spent, e.g. the drops of a fluid that was paid in
|
||||
// full.
|
||||
inv.setInventorySlotContents(slot, replacement);
|
||||
inv.markDirty();
|
||||
}
|
||||
break;
|
||||
int taken = takeFromStack(stack, inv, slot, resource, remaining, simulate, player);
|
||||
if (taken > 0) {
|
||||
remaining -= taken;
|
||||
continue;
|
||||
}
|
||||
if (recursive && player != null) {
|
||||
remaining -= takeFromNested(player, stack, new FluidStack(resource, remaining), simulate);
|
||||
}
|
||||
}
|
||||
return new FluidStack(resource, resource.amount - remaining);
|
||||
return resource.amount - remaining;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take fluid out of a single slot, using the first extractor that recognises what is in it.
|
||||
*
|
||||
* @return how much has been taken out of this slot, never more than requested
|
||||
*/
|
||||
private static int takeFromStack(ItemStack stack, IInventory owner, int slot, FluidStack resource, int maxAmount,
|
||||
boolean simulate, @Nullable EntityPlayerMP player) {
|
||||
for (FluidContainerExtractor extractor : EXTRACTORS.getPlayerOrdering(player)) {
|
||||
if (!extractor.isValidSource(stack)) continue;
|
||||
FluidStack request = new FluidStack(resource, maxAmount);
|
||||
boolean converts = extractor.convertsContainer(stack, request);
|
||||
ItemStack replacement = converts ? extractor.getReplacement(stack) : null;
|
||||
// A container that turns into another item can only be replaced when it is the only item in its slot.
|
||||
if (replacement != null && stack.stackSize != 1) continue;
|
||||
int drained = extractor.drain(stack, request, simulate);
|
||||
if (drained <= 0) continue;
|
||||
if (!simulate && converts) {
|
||||
// A null replacement means the whole slot is spent, e.g. the drops of a fluid that was paid in full.
|
||||
owner.setInventorySlotContents(slot, replacement);
|
||||
owner.markDirty();
|
||||
}
|
||||
return Math.min(drained, maxAmount);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look inside the given stack for an inventory, e.g. a backpack, and take fluid out of the containers it holds.
|
||||
* <p>
|
||||
* Which stacks hold an inventory is decided by {@link InventoryUtility}, which is the same registry item autoplace
|
||||
* uses, so a mod that already lets autoplace take items out of its backpacks gets the fluid containers inside them
|
||||
* for free and never has to register anything for fluid. Only one level is looked into, exactly like the item walk
|
||||
* does.
|
||||
*
|
||||
* @return how much has been taken, never more than requested
|
||||
*/
|
||||
private static int takeFromNested(EntityPlayerMP player, ItemStack stack, FluidStack resource, boolean simulate) {
|
||||
int taken = 0;
|
||||
for (Iterator<? extends ItemStackExtractor> iter = InventoryUtility.getStackExtractors(player); iter
|
||||
.hasNext();) {
|
||||
ItemStackExtractor extractor = iter.next();
|
||||
if (!extractor.isValidSource(stack, player)) continue;
|
||||
IInventory nested = extractor.getInventory(stack, player);
|
||||
if (nested == null) continue;
|
||||
int fromNested = takeFromInventory(
|
||||
nested,
|
||||
new FluidStack(resource, resource.amount - taken),
|
||||
simulate,
|
||||
player,
|
||||
-1,
|
||||
false);
|
||||
if (fromNested <= 0) continue;
|
||||
if (!simulate) nested.markDirty();
|
||||
taken += fromNested;
|
||||
break;
|
||||
}
|
||||
return taken;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,16 @@ public class InventoryIterable<Inv extends IInventory> implements Iterable<ItemS
|
||||
return inv;
|
||||
}
|
||||
|
||||
/**
|
||||
* The slot this iteration stops at, exclusive, or -1 when it runs to the end of the inventory.
|
||||
* <p>
|
||||
* Callers that have to write into the inventory instead of iterating over it, e.g. because they replace a stack
|
||||
* rather than removing it, need this to respect the same range the iterator uses.
|
||||
*/
|
||||
public int getMaxSlot() {
|
||||
return maxSlot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ItemStack> iterator() {
|
||||
return new Iterator<ItemStack>() {
|
||||
|
||||
@@ -97,6 +97,19 @@ public class InventoryUtility {
|
||||
return stackExtractors.getPlayerOrdering(player).iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Every inventory registered for the given player, in the order the player configured.
|
||||
* <p>
|
||||
* This is the set of inventories {@link #takeFromInventory(EntityPlayerMP, ItemStack, boolean)} walks, exposed so
|
||||
* that a walk that is not item based, e.g. StructureLib taking fluid out of a container a player carries, can cover
|
||||
* the same inventories without having to register anything of its own.
|
||||
*
|
||||
* @param player the player whose inventories to walk
|
||||
*/
|
||||
public static Iterable<InventoryProvider<?>> getInventoryProviders(EntityPlayerMP player) {
|
||||
return inventoryProviders.getPlayerOrdering(player);
|
||||
}
|
||||
|
||||
public static <Inv extends IInventory> InventoryProvider<InventoryIterable<Inv>> newInventoryProvider(
|
||||
Function<EntityPlayerMP, ? extends Inv> extractor) {
|
||||
return new InventoryProvider<InventoryIterable<Inv>>() {
|
||||
@@ -122,6 +135,12 @@ public class InventoryUtility {
|
||||
return type == APIType.MAIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public IInventory getInventory(ItemStack source, @Nullable EntityPlayerMP player) {
|
||||
return extractor.apply(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int takeFromStack(Predicate<ItemStack> predicate, boolean simulate, int count,
|
||||
ItemStackCounter store, ItemStack stack, ItemStack filter, EntityPlayerMP player) {
|
||||
@@ -301,6 +320,23 @@ public class InventoryUtility {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The inventory held by the given item stack, if it holds one.
|
||||
* <p>
|
||||
* An extractor for an item that contains an inventory is registered once for item extraction, and can hand that
|
||||
* same inventory out here, so a walk that is not item based, e.g. StructureLib looking for a fluid container a
|
||||
* player put in their backpack, reaches the backpack contents as well. Returning null is fine and means this
|
||||
* extractor cannot hand out the inventory itself.
|
||||
*
|
||||
* @param source item stack that might hold an inventory
|
||||
* @param player the player the stack belongs to, or null when it does not come from a player
|
||||
* @return the inventory inside the given stack, or null
|
||||
*/
|
||||
@Nullable
|
||||
default IInventory getInventory(ItemStack source, @Nullable EntityPlayerMP player) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a particular type of item. The extractor can choose to not return all items contained within this
|
||||
* item as long as it makes sense, but the author should inform the player of this.
|
||||
|
||||
Reference in new issue
Block a user