Skip to content

Commit

Permalink
Add entity ingredient logic to JEI
Browse files Browse the repository at this point in the history
Means anyone using entity ingredient gets it for free
  • Loading branch information
KnightMiner committed Apr 8, 2024
1 parent 0358e1d commit eed14b3
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 0 deletions.
9 changes: 9 additions & 0 deletions src/main/java/slimeknights/mantle/plugin/jei/JEIPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
import mezz.jei.api.JeiPlugin;
import mezz.jei.api.gui.handlers.IGuiContainerHandler;
import mezz.jei.api.registration.IGuiHandlerRegistration;
import mezz.jei.api.registration.IModIngredientRegistration;
import mezz.jei.api.registration.IVanillaCategoryExtensionRegistration;
import net.minecraft.client.renderer.Rect2i;
import net.minecraft.resources.ResourceLocation;
import slimeknights.mantle.Mantle;
import slimeknights.mantle.client.screen.MultiModuleScreen;
import slimeknights.mantle.inventory.MultiModuleContainerMenu;
import slimeknights.mantle.plugin.jei.entity.EntityIngredientHelper;
import slimeknights.mantle.plugin.jei.entity.EntityIngredientRenderer;
import slimeknights.mantle.recipe.crafting.ShapedRetexturedRecipe;

import java.util.Collections;
import java.util.List;

@JeiPlugin
Expand All @@ -21,6 +25,11 @@ public ResourceLocation getPluginUid() {
return Mantle.getResource("jei");
}

@Override
public void registerIngredients(IModIngredientRegistration registration) {
registration.register(MantleJEIConstants.ENTITY_TYPE, Collections.emptyList(), new EntityIngredientHelper(), new EntityIngredientRenderer(16));
}

@Override
public void registerVanillaCategoryExtensions(IVanillaCategoryExtensionRegistration registry) {
registry.getCraftingCategory().addCategoryExtension(ShapedRetexturedRecipe.class, RetexturableRecipeExtension::new);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package slimeknights.mantle.plugin.jei;

import mezz.jei.api.ingredients.IIngredientType;
import slimeknights.mantle.recipe.ingredient.EntityIngredient.EntityInput;

public class MantleJEIConstants {
/** Ingredient for an entity */
public static final IIngredientType<EntityInput> ENTITY_TYPE = () -> EntityInput.class;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package slimeknights.mantle.plugin.jei.entity;

import mezz.jei.api.ingredients.IIngredientHelper;
import mezz.jei.api.ingredients.IIngredientType;
import mezz.jei.api.ingredients.subtypes.UidContext;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation;
import slimeknights.mantle.plugin.jei.MantleJEIConstants;
import slimeknights.mantle.recipe.ingredient.EntityIngredient;

import javax.annotation.Nullable;

/** Handler for working with entity types as ingredients */
public class EntityIngredientHelper implements IIngredientHelper<EntityIngredient.EntityInput> {
@Override
public IIngredientType<EntityIngredient.EntityInput> getIngredientType() {
return MantleJEIConstants.ENTITY_TYPE;
}

@Override
public String getDisplayName(EntityIngredient.EntityInput type) {
return type.type().getDescription().getString();
}

@Override
public String getUniqueId(EntityIngredient.EntityInput type, UidContext context) {
return getResourceLocation(type).toString();
}

@Override
public ResourceLocation getResourceLocation(EntityIngredient.EntityInput type) {
return Registry.ENTITY_TYPE.getKey(type.type());
}

@Override
public EntityIngredient.EntityInput copyIngredient(EntityIngredient.EntityInput type) {
return type;
}

@Override
public String getErrorInfo(@Nullable EntityIngredient.EntityInput type) {
if (type == null) {
return "null";
}
return getResourceLocation(type).toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package slimeknights.mantle.plugin.jei.entity;

import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import lombok.RequiredArgsConstructor;
import mezz.jei.api.ingredients.IIngredientRenderer;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.InventoryScreen;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.core.Registry;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import slimeknights.mantle.Mantle;
import slimeknights.mantle.recipe.ingredient.EntityIngredient;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* Renderer for entity type ingredients
*/
@RequiredArgsConstructor
public class EntityIngredientRenderer implements IIngredientRenderer<EntityIngredient.EntityInput> {
private static final ResourceLocation MISSING = Mantle.getResource("textures/item/missingno.png");
/** Entity types that will not render, as they either errored or are the wrong type */
private static final Set<EntityType<?>> IGNORED_ENTITIES = new HashSet<>();

/** Square size of the renderer in pixels */
private final int size;

/** Cache of entities for each entity type */
private final Map<EntityType<?>,Entity> ENTITY_MAP = new HashMap<>();

@Override
public int getWidth() {
return size;
}

@Override
public int getHeight() {
return size;
}

@Override
public void render(PoseStack matrixStack, @Nullable EntityIngredient.EntityInput input) {
if (input != null) {
Level world = Minecraft.getInstance().level;
EntityType<?> type = input.type();
if (world != null && !IGNORED_ENTITIES.contains(type)) {
Entity entity;
// players cannot be created using the type, but we can use the client player
// side effect is it renders armor/items
if (type == EntityType.PLAYER) {
entity = Minecraft.getInstance().player;
} else {
// entity is created with the client world, but the entity map is thrown away when JEI restarts so they should be okay I think
entity = ENTITY_MAP.computeIfAbsent(type, t -> t.create(world));
}
// only can draw living entities, plus non-living ones don't get recipes anyways
if (entity instanceof LivingEntity livingEntity) {
// scale down large mobs, but don't scale up small ones
int scale = size / 2;
float height = entity.getBbHeight();
float width = entity.getBbWidth();
if (height > 2 || width > 2) {
scale = (int)(size / Math.max(height, width));
}
// catch exceptions drawing the entity to be safe, any caught exceptions blacklist the entity
try {
PoseStack modelView = RenderSystem.getModelViewStack();
modelView.pushPose();
modelView.mulPoseMatrix(matrixStack.last().pose());
InventoryScreen.renderEntityInInventory(size / 2, size, scale, 0, 10, livingEntity);
modelView.popPose();
RenderSystem.applyModelViewMatrix();
return;
} catch (Exception e) {
Mantle.logger.error("Error drawing entity " + Registry.ENTITY_TYPE.getKey(type), e);
IGNORED_ENTITIES.add(type);
ENTITY_MAP.remove(type);
}
} else {
// not living, so might as well skip next time
IGNORED_ENTITIES.add(type);
ENTITY_MAP.remove(type);
}
}

// fallback, draw a pink and black "spawn egg"
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.setShaderTexture(0, MISSING);
RenderSystem.setShaderColor(1, 1, 1, 1);
int offset = (size - 16) / 2;
Screen.blit(matrixStack, offset, offset, 0, 0, 16, 16, 16, 16);
}
}

@Override
public List<Component> getTooltip(EntityIngredient.EntityInput type, TooltipFlag flag) {
List<Component> tooltip = new ArrayList<>();
tooltip.add(type.type().getDescription());
if (flag.isAdvanced()) {
tooltip.add((Component.literal(Registry.ENTITY_TYPE.getKey(type.type()).toString())).withStyle(ChatFormatting.DARK_GRAY));
}
return tooltip;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package slimeknights.mantle.plugin.jei.entity;

import net.minecraft.MethodsReturnNonnullByDefault;

import javax.annotation.ParametersAreNonnullByDefault;
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraftforge.common.ForgeSpawnEggItem;
import slimeknights.mantle.data.loadable.IAmLoadable;
import slimeknights.mantle.data.loadable.Loadable;
import slimeknights.mantle.data.loadable.Loadables;
import slimeknights.mantle.data.loadable.mapping.EitherLoadable;
import slimeknights.mantle.data.loadable.record.RecordLoadable;
import slimeknights.mantle.util.RegistryHelper;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;

Expand Down Expand Up @@ -121,6 +126,30 @@ public static EntityIngredient read(FriendlyByteBuf buffer) {
}


/* JEI */

private List<EntityInput> display;
private List<ItemStack> eggs;

/** Gets the list of eggs matching this ingredient, used for display in JEI as it cannot do entity type */
public List<EntityInput> getDisplay() {
if (display == null) {
display = EntityInput.wrap(getTypes());
}
return display;
}

/** Gets the list of eggs matching this ingredient, used for focus links in JEI */
public List<ItemStack> getEggs() {
if (eggs == null) {
// use getDisplay to guarantee order is the same, just in case
eggs = getDisplay().stream().map(type -> new ItemStack(Objects.requireNonNullElse(ForgeSpawnEggItem.fromEntityType(type.type), Items.AIR))).toList();
}
return eggs;
}



/* Impls */

/** Ingredient that matches any entity from a set */
Expand Down Expand Up @@ -200,4 +229,12 @@ public Set<EntityType<?>> getTypes() {
return allTypes;
}
}

/** Simple wrapper around entity type for usage in JEI */
public record EntityInput(EntityType<?> type) {
/** Wraps the given list into a list of entity inputs */
public static List<EntityInput> wrap(Collection<EntityType<?>> types) {
return types.stream().map(EntityInput::new).toList();
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit eed14b3

Please sign in to comment.