1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-01-26 08:56:54 +00:00

Improve our JEI integration a little bit

- Turtle and pocket computers provide a "creator mod id" based on their
   upgrade(s).
   We track which mod was active when the upgrade was registered, and
   use that to determine the owner. Technically we could use the
   RegistryLocation ID, but this is not always correct (such as
   Plethora's vanilla modules).
 - We show all upgraded turtles/pocket computers in JEI now, rather than
   just CC ones.
 - We provide a custom IRecipeRegistryPlugin for upgrades, which
   provides custom usage/recipes for any upgrade or upgraded item. We
   also hide our generated turtle/pocket computer recipes in order to
   prevent duplicates.
This commit is contained in:
SquidDev 2019-03-16 01:49:02 +00:00
parent 54b9966feb
commit 41429bdc0b
9 changed files with 495 additions and 17 deletions

View File

@ -67,11 +67,11 @@ configurations {
}
dependencies {
deobfProvided "mezz.jei:jei_1.12.2:4.8.5.159:api"
deobfProvided "mezz.jei:jei_1.12.2:4.15.0.269:api"
deobfProvided "pl.asie:Charset-Lib:0.5.4.6"
deobfProvided "MCMultiPart2:MCMultiPart:2.5.3"
runtime "mezz.jei:jei_1.12.2:4.8.5.159"
runtime "mezz.jei:jei_1.12.2:4.15.0.269"
shade 'org.squiddev:Cobalt:0.5.0-SNAPSHOT'

View File

@ -54,6 +54,9 @@ public interface IPocketUpgrade
* pocket computer which holds this upgrade. This item stack is also used to determine the upgrade given by
* {@code pocket.equip()}/{@code pocket.unequip()}.
*
* Ideally this should be constant over a session. It is recommended that you cache
* the item too, in order to prevent constructing it every time the method is called.
*
* @return The item stack used for crafting. This can be {@link ItemStack#EMPTY} if crafting is disabled.
*/
@Nonnull

View File

@ -79,6 +79,9 @@ public interface ITurtleUpgrade
* with to create a turtle which holds this upgrade. This item stack is also used
* to determine the upgrade given by {@code turtle.equip()}
*
* Ideally this should be constant over a session. It is recommended that you cache
* the item too, in order to prevent constructing it every time the method is called.
*
* @return The item stack to craft with, or {@link ItemStack#EMPTY} if it cannot be crafted.
*/
@Nonnull

View File

@ -11,16 +11,17 @@ import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.shared.util.InventoryUtil;
import net.minecraft.item.ItemStack;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.ModContainer;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import java.util.*;
public final class PocketUpgrades
{
private static final Map<String, IPocketUpgrade> upgrades = new HashMap<>();
private static final IdentityHashMap<IPocketUpgrade, String> upgradeOwners = new IdentityHashMap<>();
public static void register( @Nonnull IPocketUpgrade upgrade )
{
@ -34,8 +35,10 @@ public final class PocketUpgrades
}
upgrades.put( id, upgrade );
}
ModContainer mc = Loader.instance().activeModContainer();
if( mc != null && mc.getModId() != null ) upgradeOwners.put( upgrade, mc.getModId() );
}
public static IPocketUpgrade get( String id )
{
@ -61,6 +64,12 @@ public final class PocketUpgrades
return null;
}
@Nullable
public static String getOwner( IPocketUpgrade upgrade )
{
return upgradeOwners.get( upgrade );
}
public static Iterable<IPocketUpgrade> getVanillaUpgrades()
{
List<IPocketUpgrade> vanilla = new ArrayList<>();
@ -69,4 +78,9 @@ public final class PocketUpgrades
vanilla.add( ComputerCraft.PocketUpgrades.speaker );
return vanilla;
}
public static Iterable<IPocketUpgrade> getUpgrades()
{
return Collections.unmodifiableCollection( upgrades.values() );
}
}

View File

@ -14,17 +14,18 @@ import dan200.computercraft.shared.util.InventoryUtil;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.item.ItemStack;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.ModContainer;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import java.util.*;
public final class TurtleUpgrades
{
private static final Map<String, ITurtleUpgrade> upgrades = new HashMap<>();
private static final Int2ObjectMap<ITurtleUpgrade> legacyUpgrades = new Int2ObjectOpenHashMap<>();
private static final IdentityHashMap<ITurtleUpgrade, String> upgradeOwners = new IdentityHashMap<>();
public static void register( @Nonnull ITurtleUpgrade upgrade )
{
@ -41,7 +42,7 @@ public final class TurtleUpgrades
registerInternal( upgrade );
}
public static void registerInternal( ITurtleUpgrade upgrade )
static void registerInternal( ITurtleUpgrade upgrade )
{
Preconditions.checkNotNull( upgrade, "upgrade cannot be null" );
@ -77,6 +78,9 @@ public final class TurtleUpgrades
// Register
if( legacyId >= 0 ) legacyUpgrades.put( legacyId, upgrade );
upgrades.put( id, upgrade );
ModContainer mc = Loader.instance().activeModContainer();
if( mc != null && mc.getModId() != null ) upgradeOwners.put( upgrade, mc.getModId() );
}
private static String getMessage( ITurtleUpgrade upgrade, String rest )
@ -125,6 +129,17 @@ public final class TurtleUpgrades
return vanilla;
}
@Nullable
public static String getOwner( @Nonnull ITurtleUpgrade upgrade )
{
return upgradeOwners.get( upgrade );
}
public static Iterable<ITurtleUpgrade> getUpgrades()
{
return Collections.unmodifiableCollection( upgrades.values() );
}
public static boolean suitableForFamily( ComputerFamily family, ITurtleUpgrade upgrade )
{
return true;

View File

@ -1,24 +1,46 @@
package dan200.computercraft.shared.integration;
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.integration.jei;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.PocketUpgrades;
import dan200.computercraft.shared.TurtleUpgrades;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.media.items.ItemDiskLegacy;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.pocket.items.PocketComputerItemFactory;
import dan200.computercraft.shared.turtle.items.ITurtleItem;
import mezz.jei.api.IModPlugin;
import mezz.jei.api.IModRegistry;
import mezz.jei.api.ISubtypeRegistry;
import dan200.computercraft.shared.turtle.items.TurtleItemFactory;
import mezz.jei.api.*;
import mezz.jei.api.ISubtypeRegistry.ISubtypeInterpreter;
import mezz.jei.api.JEIPlugin;
import mezz.jei.api.ingredients.IIngredientRegistry;
import mezz.jei.api.ingredients.VanillaTypes;
import mezz.jei.api.recipe.IRecipeCategory;
import mezz.jei.api.recipe.IRecipeWrapper;
import mezz.jei.api.recipe.VanillaRecipeCategoryUid;
import mezz.jei.api.recipe.wrapper.ICraftingRecipeWrapper;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.oredict.OreDictionary;
import java.util.ArrayList;
import java.util.List;
import static dan200.computercraft.shared.integration.jei.RecipeResolver.MAIN_FAMILIES;
@JEIPlugin
public class JEIComputerCraft implements IModPlugin
{
private IIngredientRegistry ingredients;
@Override
public void registerItemSubtypes( ISubtypeRegistry subtypeRegistry )
{
@ -35,9 +57,53 @@ public class JEIComputerCraft implements IModPlugin
@Override
public void register( IModRegistry registry )
{
ingredients = registry.getIngredientRegistry();
// Hide treasure disks from the ingredient list
registry.getJeiHelpers().getIngredientBlacklist()
.addIngredientToBlacklist( new ItemStack( ComputerCraft.Items.treasureDisk, OreDictionary.WILDCARD_VALUE ) );
registry.addRecipeRegistryPlugin( new RecipeResolver() );
}
@Override
public void onRuntimeAvailable( IJeiRuntime runtime )
{
IRecipeRegistry registry = runtime.getRecipeRegistry();
// Register all turtles/pocket computers (not just vanilla upgrades) as upgrades on JEI.
List<ItemStack> upgradeItems = new ArrayList<>();
for( ComputerFamily family : MAIN_FAMILIES )
{
for( ITurtleUpgrade upgrade : TurtleUpgrades.getUpgrades() )
{
if( !TurtleUpgrades.suitableForFamily( family, upgrade ) ) continue;
upgradeItems.add( TurtleItemFactory.create( -1, null, -1, family, null, upgrade, 0, null ) );
}
for( IPocketUpgrade upgrade : PocketUpgrades.getUpgrades() )
{
upgradeItems.add( PocketComputerItemFactory.create( -1, null, -1, family, upgrade ) );
}
}
ingredients.addIngredientsAtRuntime( VanillaTypes.ITEM, upgradeItems );
// Hide all upgrade recipes
IRecipeCategory<? extends IRecipeWrapper> category = (IRecipeCategory<? extends IRecipeWrapper>) registry.getRecipeCategory( VanillaRecipeCategoryUid.CRAFTING );
if( category != null )
{
for( IRecipeWrapper wrapper : registry.getRecipeWrappers( category ) )
{
if( !(wrapper instanceof ICraftingRecipeWrapper) ) continue;
ResourceLocation id = ((ICraftingRecipeWrapper) wrapper).getRegistryName();
if( id != null && id.getNamespace().equals( ComputerCraft.MOD_ID )
&& (id.getPath().startsWith( "generated/turtle_" ) || id.getPath().startsWith( "generated/pocket_" )) )
{
registry.hideRecipe( wrapper, VanillaRecipeCategoryUid.CRAFTING );
}
}
}
}
/**

View File

@ -0,0 +1,335 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.integration.jei;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.PocketUpgrades;
import dan200.computercraft.shared.TurtleUpgrades;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.pocket.items.PocketComputerItemFactory;
import dan200.computercraft.shared.turtle.items.ITurtleItem;
import dan200.computercraft.shared.turtle.items.TurtleItemFactory;
import dan200.computercraft.shared.util.InventoryUtil;
import mezz.jei.api.ingredients.IIngredients;
import mezz.jei.api.ingredients.VanillaTypes;
import mezz.jei.api.recipe.*;
import mezz.jei.api.recipe.wrapper.IShapedCraftingRecipeWrapper;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
import static java.util.Arrays.asList;
class RecipeResolver implements IRecipeRegistryPlugin
{
static final ComputerFamily[] MAIN_FAMILIES = new ComputerFamily[] { ComputerFamily.Normal, ComputerFamily.Advanced };
private final Map<Item, List<ITurtleUpgrade>> turtleUpgrades = new HashMap<>();
private final Map<Item, List<IPocketUpgrade>> pocketUpgrades = new HashMap<>();
private boolean initialised = false;
/**
* Build a cache of items which are used for turtle and pocket computer upgrades.
*/
private void setupCache()
{
if( initialised ) return;
initialised = true;
for( ITurtleUpgrade upgrade : TurtleUpgrades.getUpgrades() )
{
ItemStack stack = upgrade.getCraftingItem();
if( stack.isEmpty() ) continue;
turtleUpgrades.computeIfAbsent( stack.getItem(), k -> new ArrayList<>( 1 ) ).add( upgrade );
}
for( IPocketUpgrade upgrade : PocketUpgrades.getUpgrades() )
{
ItemStack stack = upgrade.getCraftingItem();
if( stack.isEmpty() ) continue;
pocketUpgrades.computeIfAbsent( stack.getItem(), k -> new ArrayList<>( 1 ) ).add( upgrade );
}
}
@Nullable
private ITurtleUpgrade getTurtleUpgrade( @Nonnull ItemStack stack )
{
if( stack.isEmpty() ) return null;
setupCache();
List<ITurtleUpgrade> upgrades = turtleUpgrades.get( stack.getItem() );
if( upgrades == null ) return null;
for( ITurtleUpgrade upgrade : upgrades )
{
ItemStack craftingStack = upgrade.getCraftingItem();
if( !craftingStack.isEmpty() && InventoryUtil.areItemsSimilar( stack, craftingStack ) ) return upgrade;
}
return null;
}
@Nullable
private IPocketUpgrade getPocketUpgrade( @Nonnull ItemStack stack )
{
if( stack.isEmpty() ) return null;
setupCache();
List<IPocketUpgrade> upgrades = pocketUpgrades.get( stack.getItem() );
if( upgrades == null ) return null;
for( IPocketUpgrade upgrade : upgrades )
{
ItemStack craftingStack = upgrade.getCraftingItem();
if( !craftingStack.isEmpty() && InventoryUtil.areItemsSimilar( stack, craftingStack ) ) return upgrade;
}
return null;
}
@Nonnull
@Override
public <V> List<String> getRecipeCategoryUids( @Nonnull IFocus<V> focus )
{
V value = focus.getValue();
if( !(value instanceof ItemStack) ) return Collections.emptyList();
ItemStack stack = (ItemStack) value;
switch( focus.getMode() )
{
case INPUT:
return stack.getItem() instanceof ITurtleItem || stack.getItem() instanceof ItemPocketComputer ||
getTurtleUpgrade( stack ) != null || getPocketUpgrade( stack ) != null
? Collections.singletonList( VanillaRecipeCategoryUid.CRAFTING )
: Collections.emptyList();
case OUTPUT:
return stack.getItem() instanceof ITurtleItem || stack.getItem() instanceof ItemPocketComputer
? Collections.singletonList( VanillaRecipeCategoryUid.CRAFTING )
: Collections.emptyList();
default:
return Collections.emptyList();
}
}
@Nonnull
@Override
public <T extends IRecipeWrapper, V> List<T> getRecipeWrappers( @Nonnull IRecipeCategory<T> recipeCategory, @Nonnull IFocus<V> focus )
{
if( !(focus.getValue() instanceof ItemStack) || !recipeCategory.getUid().equals( VanillaRecipeCategoryUid.CRAFTING ) )
{
return Collections.emptyList();
}
ItemStack stack = (ItemStack) focus.getValue();
switch( focus.getMode() )
{
case INPUT:
return cast( findRecipesWithInput( stack ) );
case OUTPUT:
return cast( findRecipesWithOutput( stack ) );
default:
return Collections.emptyList();
}
}
@Nonnull
@Override
public <T extends IRecipeWrapper> List<T> getRecipeWrappers( @Nonnull IRecipeCategory<T> recipeCategory )
{
return Collections.emptyList();
}
@Nonnull
private List<Shaped> findRecipesWithInput( @Nonnull ItemStack stack )
{
if( stack.getItem() instanceof ITurtleItem )
{
// Suggest possible upgrades which can be applied to this turtle
ITurtleItem item = (ITurtleItem) stack.getItem();
ITurtleUpgrade left = item.getUpgrade( stack, TurtleSide.Left );
ITurtleUpgrade right = item.getUpgrade( stack, TurtleSide.Right );
if( left != null && right != null ) return Collections.emptyList();
List<Shaped> recipes = new ArrayList<>();
for( ITurtleUpgrade upgrade : TurtleUpgrades.getUpgrades() )
{
if( left == null )
{
recipes.add( horizontal( asList( stack, upgrade.getCraftingItem() ), turtleWith( stack, upgrade, right ) ) );
}
if( right == null )
{
recipes.add( horizontal( asList( stack, upgrade.getCraftingItem() ), turtleWith( stack, left, upgrade ) ) );
}
}
return cast( recipes );
}
else if( stack.getItem() instanceof ItemPocketComputer )
{
// Suggest possible upgrades which can be applied to this turtle
ItemPocketComputer item = (ItemPocketComputer) stack.getItem();
IPocketUpgrade back = item.getUpgrade( stack );
if( back != null ) return Collections.emptyList();
List<Shaped> recipes = new ArrayList<>();
for( IPocketUpgrade upgrade : PocketUpgrades.getUpgrades() )
{
recipes.add( vertical( asList( stack, upgrade.getCraftingItem() ), pocketWith( stack, upgrade ) ) );
}
return recipes;
}
else
{
// Find places this may be used as an upgrade.
ITurtleUpgrade turtle = getTurtleUpgrade( stack );
IPocketUpgrade pocket = getPocketUpgrade( stack );
if( turtle == null && pocket == null ) return Collections.emptyList();
List<Shaped> recipes = new ArrayList<>( 1 );
for( ComputerFamily family : MAIN_FAMILIES )
{
if( turtle != null && TurtleUpgrades.suitableForFamily( family, turtle ) )
{
recipes.add( horizontal(
asList( stack, TurtleItemFactory.create( -1, null, -1, family, null, null, 0, null ) ),
TurtleItemFactory.create( -1, null, -1, family, turtle, null, 0, null )
) );
}
if( pocket != null )
{
recipes.add( vertical(
asList( stack, PocketComputerItemFactory.create( -1, null, -1, family, null ) ),
PocketComputerItemFactory.create( -1, null, -1, family, pocket )
) );
}
}
return recipes;
}
}
@Nonnull
private List<Shaped> findRecipesWithOutput( @Nonnull ItemStack stack )
{
// Find which upgrade this item currently has, an so how we could build it.
if( stack.getItem() instanceof ITurtleItem )
{
ITurtleItem item = (ITurtleItem) stack.getItem();
List<IRecipeWrapper> recipes = new ArrayList<>( 0 );
ITurtleUpgrade left = item.getUpgrade( stack, TurtleSide.Left );
ITurtleUpgrade right = item.getUpgrade( stack, TurtleSide.Right );
if( left != null )
{
recipes.add( horizontal( asList( left.getCraftingItem(), turtleWith( stack, null, right ) ), stack ) );
}
if( right != null )
{
recipes.add( horizontal( asList( turtleWith( stack, left, null ), right.getCraftingItem() ), stack ) );
}
return cast( recipes );
}
else if( stack.getItem() instanceof ItemPocketComputer )
{
ItemPocketComputer item = (ItemPocketComputer) stack.getItem();
List<IRecipeWrapper> recipes = new ArrayList<>( 0 );
IPocketUpgrade back = item.getUpgrade( stack );
if( back != null )
{
recipes.add( vertical( asList( back.getCraftingItem(), pocketWith( stack, null ) ), stack ) );
}
return cast( recipes );
}
else
{
return Collections.emptyList();
}
}
@SuppressWarnings( "unchecked" )
private static <T extends IRecipeWrapper, U extends IRecipeWrapper> List<T> cast( List<U> from )
{
return (List) from;
}
private static ItemStack turtleWith( ItemStack stack, ITurtleUpgrade left, ITurtleUpgrade right )
{
ITurtleItem item = (ITurtleItem) stack.getItem();
return TurtleItemFactory.create(
item.getComputerID( stack ), item.getLabel( stack ), item.getColour( stack ), item.getFamily( stack ),
left, right, item.getFuelLevel( stack ), item.getOverlay( stack )
);
}
private static ItemStack pocketWith( ItemStack stack, IPocketUpgrade back )
{
ItemPocketComputer item = (ItemPocketComputer) stack.getItem();
return PocketComputerItemFactory.create(
item.getComputerID( stack ), item.getLabel( stack ), item.getColour( stack ), item.getFamily( stack ),
back
);
}
private static Shaped vertical( List<ItemStack> input, ItemStack result )
{
return new Shaped( 1, input.size(), input, result );
}
private static Shaped horizontal( List<ItemStack> input, ItemStack result )
{
return new Shaped( input.size(), 1, input, result );
}
static class Shaped implements IShapedCraftingRecipeWrapper
{
private final int width;
private final int height;
private final List<ItemStack> input;
private final ItemStack output;
Shaped( int width, int height, List<ItemStack> input, ItemStack output )
{
this.width = width;
this.height = height;
this.input = input;
this.output = output;
}
@Override
public int getWidth()
{
return width;
}
@Override
public int getHeight()
{
return height;
}
@Override
public void getIngredients( @Nonnull IIngredients ingredients )
{
ingredients.setInputs( VanillaTypes.ITEM, input );
ingredients.setOutput( VanillaTypes.ITEM, output );
}
}
}

View File

@ -38,6 +38,7 @@ import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
public class ItemPocketComputer extends Item implements IComputerItem, IMedia, IColouredItem
@ -242,6 +243,22 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
}
}
@Nullable
@Override
public String getCreatorModId( ItemStack stack )
{
IPocketUpgrade upgrade = getUpgrade( stack );
if( upgrade != null )
{
// If we're a non-vanilla, non-CC upgrade then return whichever mod this upgrade
// belongs to.
String mod = PocketUpgrades.getOwner( upgrade );
if( mod != null && !mod.equals( ComputerCraft.MOD_ID ) ) return mod;
}
return super.getCreatorModId( stack );
}
private PocketServerComputer createServerComputer( final World world, IInventory inventory, Entity entity, @Nonnull ItemStack stack )
{
if( world.isRemote )

View File

@ -6,6 +6,7 @@
package dan200.computercraft.shared.turtle.items;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.TurtleUpgrades;
@ -172,6 +173,30 @@ public abstract class ItemTurtleBase extends ItemComputerBase implements ITurtle
}
}
@Nullable
@Override
public String getCreatorModId( ItemStack stack )
{
// Determine our "creator mod" from the upgrades. We attempt to find the first non-vanilla/non-CC
// upgrade (starting from the left).
ITurtleUpgrade left = getUpgrade( stack, TurtleSide.Left );
if( left != null )
{
String mod = TurtleUpgrades.getOwner( left );
if( mod != null && !mod.equals( ComputerCraft.MOD_ID ) ) return mod;
}
ITurtleUpgrade right = getUpgrade( stack, TurtleSide.Right );
if( right != null )
{
String mod = TurtleUpgrades.getOwner( right );
if( mod != null && !mod.equals( ComputerCraft.MOD_ID ) ) return mod;
}
return super.getCreatorModId( stack );
}
@Override
public ItemStack withFamily( @Nonnull ItemStack stack, @Nonnull ComputerFamily family )
{