Upgrade BackTankUtil to handle multiple (backtank) air sources (#4777)

- Addons can now register backtank-esque air sources placed in curios slots
- Diving helmets now support multiple (backtank) air sources
This commit is contained in:
Michael C 2023-07-03 09:31:05 -04:00 committed by GitHub
parent e42fba6341
commit 440d7e0e39
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 119 additions and 44 deletions

View file

@ -1,8 +1,12 @@
package com.simibubi.create.compat.curios;
import com.simibubi.create.AllItems;
import com.simibubi.create.content.equipment.armor.BacktankUtil;
import com.simibubi.create.content.equipment.goggles.GogglesItem;
import com.simibubi.create.AllTags;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.DistExecutor;
@ -12,29 +16,61 @@ import net.minecraftforge.fml.event.lifecycle.InterModEnqueueEvent;
import top.theillusivec4.curios.api.CuriosCapability;
import top.theillusivec4.curios.api.SlotTypeMessage;
import top.theillusivec4.curios.api.SlotTypePreset;
import top.theillusivec4.curios.api.type.capability.ICuriosItemHandler;
import top.theillusivec4.curios.api.type.inventory.ICurioStacksHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class Curios {
/**
* Resolves the Stacks Handler Map given an Entity.
* It is recommended to then use a `.map(curiosMap -> curiosMap.get({key})`,
* which can be null and would therefore be caught by the Optional::map function.
*
* @param entity The entity which possibly has a Curio Inventory capability
* @return An optional of the Stacks Handler Map
*/
private static Optional<Map<String, ICurioStacksHandler>> resolveCuriosMap(LivingEntity entity) {
return entity.getCapability(CuriosCapability.INVENTORY).map(ICuriosItemHandler::getCurios);
}
public static void init(IEventBus modEventBus, IEventBus forgeEventBus) {
modEventBus.addListener(Curios::onInterModEnqueue);
modEventBus.addListener(Curios::onClientSetup);
GogglesItem.addIsWearingPredicate(player -> player.getCapability(CuriosCapability.INVENTORY)
.map(handler -> {
ICurioStacksHandler stacksHandler = handler.getCurios()
.get("head");
if (stacksHandler == null)
return false;
GogglesItem.addIsWearingPredicate(player -> resolveCuriosMap(player)
.map(curiosMap -> curiosMap.get("head"))
.map(stacksHandler -> {
// Check all the Head slots for Goggles existing
int slots = stacksHandler.getSlots();
for (int slot = 0; slot < slots; slot++)
if (AllItems.GOGGLES.isIn(stacksHandler.getStacks()
.getStackInSlot(slot)))
if (AllItems.GOGGLES.isIn(stacksHandler.getStacks().getStackInSlot(slot)))
return true;
return false;
})
.orElse(false));
BacktankUtil.addBacktankSupplier(entity -> resolveCuriosMap(entity)
.map(curiosMap -> {
List<ItemStack> stacks = new ArrayList<>();
for (ICurioStacksHandler stacksHandler : curiosMap.values()) {
// Search all the curio slots for pressurized air sources, and add them to the list
int slots = stacksHandler.getSlots();
for (int slot = 0; slot < slots; slot++) {
final ItemStack itemStack = stacksHandler.getStacks().getStackInSlot(slot);
if (AllTags.AllItemTags.PRESSURIZED_AIR_SOURCES.matches(itemStack))
stacks.add(itemStack);
}
}
return stacks;
}).orElse(new ArrayList<>()));
DistExecutor.unsafeRunWhenOn(Dist.CLIENT,
() -> () -> modEventBus.addListener(CuriosRenderers::onLayerRegister));
}

View file

@ -15,7 +15,6 @@ import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket;
import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket;
import net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
@ -23,13 +22,39 @@ import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.DistExecutor;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class BacktankUtil {
public static ItemStack get(LivingEntity entity) {
for (ItemStack itemStack : entity.getArmorSlots())
if (AllTags.AllItemTags.PRESSURIZED_AIR_SOURCES.matches(itemStack))
return itemStack;
return ItemStack.EMPTY;
private static final List<Function<LivingEntity, List<ItemStack>>> BACKTANK_SUPPLIERS = new ArrayList<>();
static {
addBacktankSupplier(entity -> {
List<ItemStack> stacks = new ArrayList<>();
for (ItemStack itemStack : entity.getArmorSlots())
if (AllTags.AllItemTags.PRESSURIZED_AIR_SOURCES.matches(itemStack))
stacks.add(itemStack);
return stacks;
});
}
public static List<ItemStack> getAllWithAir(LivingEntity entity) {
List<ItemStack> all = new ArrayList<>();
for (Function<LivingEntity, List<ItemStack>> supplier : BACKTANK_SUPPLIERS) {
List<ItemStack> result = supplier.apply(entity);
for (ItemStack stack : result)
if (hasAirRemaining(stack))
all.add(stack);
}
// Sort with ascending order (we want to prioritize the most empty so things actually run out)
all.sort((a, b) -> Float.compare(getAir(a), getAir(b)));
return all;
}
public static boolean hasAirRemaining(ItemStack backtank) {
@ -45,7 +70,7 @@ public class BacktankUtil {
CompoundTag tag = backtank.getOrCreateTag();
int maxAir = maxAir(backtank);
float air = getAir(backtank);
float newAir = air - i;
float newAir = Math.max(air - i, 0);
tag.putFloat("Air", Math.min(newAir, maxAir));
backtank.setTag(tag);
@ -92,13 +117,11 @@ public class BacktankUtil {
return true;
if (entity instanceof Player && ((Player) entity).isCreative())
return true;
ItemStack backtank = get(entity);
if (backtank.isEmpty())
return false;
if (!hasAirRemaining(backtank))
List<ItemStack> backtanks = getAllWithAir(entity);
if (backtanks.isEmpty())
return false;
float cost = ((float) maxAirWithoutEnchants()) / usesPerTank;
consumeAir(entity, backtank, cost);
consumeAir(entity, backtanks.get(0), cost);
return true;
}
@ -110,8 +133,8 @@ public class BacktankUtil {
Player player = DistExecutor.unsafeCallWhenOn(Dist.CLIENT, () -> () -> Minecraft.getInstance().player);
if (player == null)
return false;
ItemStack backtank = get(player);
if (backtank.isEmpty() || !hasAirRemaining(backtank))
List<ItemStack> backtanks = getAllWithAir(player);
if (backtanks.isEmpty())
return stack.isDamaged();
return true;
}
@ -122,11 +145,21 @@ public class BacktankUtil {
Player player = DistExecutor.unsafeCallWhenOn(Dist.CLIENT, () -> () -> Minecraft.getInstance().player);
if (player == null)
return 13;
ItemStack backtank = get(player);
if (backtank.isEmpty() || !hasAirRemaining(backtank))
List<ItemStack> backtanks = getAllWithAir(player);
if (backtanks.isEmpty())
return Math.round(13.0F - (float) stack.getDamageValue() / stack.getMaxDamage() * 13.0F);
return backtank.getItem()
.getBarWidth(backtank);
if (backtanks.size() == 1)
return backtanks.get(0).getItem().getBarWidth(backtanks.get(0));
// If there is more than one backtank, average the bar widths.
int sumBarWidth = backtanks.stream()
.map(backtank -> backtank.getItem().getBarWidth(backtank))
.reduce(0 , Integer::sum);
return Math.round((float) sumBarWidth / backtanks.size());
}
public static int getBarColor(ItemStack stack, int usesPerTank) {
@ -135,12 +168,17 @@ public class BacktankUtil {
Player player = DistExecutor.unsafeCallWhenOn(Dist.CLIENT, () -> () -> Minecraft.getInstance().player);
if (player == null)
return 0;
ItemStack backtank = get(player);
if (backtank.isEmpty() || !hasAirRemaining(backtank))
return Mth.hsvToRgb(Math.max(0.0F, 1.0F - (float) stack.getDamageValue() / stack.getMaxDamage()) / 3.0F,
1.0F, 1.0F);
return backtank.getItem()
.getBarColor(backtank);
List<ItemStack> backtanks = getAllWithAir(player);
// Just return the "first" backtank for the bar color since that's the one we are consuming from
return backtanks.get(0).getItem().getBarColor(backtanks.get(0));
}
/**
* Use this method to add custom entry points to the backtank item stack supplier, e.g. getting them from custom
* slots or items.
*/
public static void addBacktankSupplier(Function<LivingEntity, List<ItemStack>> supplier) {
BACKTANK_SUPPLIERS.add(supplier);
}
}

View file

@ -20,6 +20,8 @@ import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
import java.util.List;
@EventBusSubscriber
public class DivingHelmetItem extends BaseArmorItem {
public static final EquipmentSlot SLOT = EquipmentSlot.HEAD;
@ -75,17 +77,14 @@ public class DivingHelmetItem extends BaseArmorItem {
if (entity instanceof Player && ((Player) entity).isCreative())
return;
ItemStack backtank = BacktankUtil.get(entity);
if (backtank.isEmpty())
return;
if (!BacktankUtil.hasAirRemaining(backtank))
List<ItemStack> backtanks = BacktankUtil.getAllWithAir(entity);
if (backtanks.isEmpty())
return;
if (lavaDiving) {
if (entity instanceof ServerPlayer sp)
AllAdvancements.DIVING_SUIT_LAVA.awardTo(sp);
if (!backtank.getItem()
.isFireResistant())
if (backtanks.stream().noneMatch(backtank -> backtank.getItem().isFireResistant()))
return;
}
@ -94,12 +93,12 @@ public class DivingHelmetItem extends BaseArmorItem {
if (world.isClientSide)
entity.getPersistentData()
.putInt("VisualBacktankAir", (int) BacktankUtil.getAir(backtank));
.putInt("VisualBacktankAir", Math.round(backtanks.stream().map(BacktankUtil::getAir).reduce(0f, Float::sum)));
if (!second)
return;
BacktankUtil.consumeAir(entity, backtank, 1);
BacktankUtil.consumeAir(entity, backtanks.get(0), 1);
if (lavaDiving)
return;

View file

@ -16,6 +16,8 @@ import net.minecraft.world.level.GameType;
import net.minecraftforge.client.gui.ForgeIngameGui;
import net.minecraftforge.client.gui.IIngameOverlay;
import java.util.List;
public class RemainingAirOverlay implements IIngameOverlay {
public static final RemainingAirOverlay INSTANCE = new RemainingAirOverlay();
@ -59,9 +61,9 @@ public class RemainingAirOverlay implements IIngameOverlay {
}
public static ItemStack getDisplayedBacktank(LocalPlayer player) {
ItemStack backtank = BacktankUtil.get(player);
if (!backtank.isEmpty()) {
return backtank;
List<ItemStack> backtanks = BacktankUtil.getAllWithAir(player);
if (!backtanks.isEmpty()) {
return backtanks.get(0);
}
return AllItems.COPPER_BACKTANK.asStack();
}