diff options
Diffstat (limited to 'src/main/java/com')
3 files changed, 338 insertions, 210 deletions
diff --git a/src/main/java/com/anthonyhilyard/iceberg/mixin/ItemMixin.java b/src/main/java/com/anthonyhilyard/iceberg/mixin/ItemMixin.java new file mode 100644 index 0000000..75b8ec3 --- /dev/null +++ b/src/main/java/com/anthonyhilyard/iceberg/mixin/ItemMixin.java @@ -0,0 +1,31 @@ +package com.anthonyhilyard.iceberg.mixin; + +import com.anthonyhilyard.iceberg.util.EntityCollector; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; + +@Mixin(value = Item.class, priority = 100) +public class ItemMixin +{ + @Inject(method = "getPlayerPOVHitResult", at = @At(value = "HEAD"), cancellable = true) + private static void icebergGetPlayerPOVHitResult(Level level, Player player, ClipContext.Fluid clipContext, CallbackInfoReturnable<HitResult> info) + { + // If the level is an entity collector, always return a valid hit result. + if (level instanceof EntityCollector) + { + info.setReturnValue(new BlockHitResult(Vec3.ZERO, Direction.DOWN, BlockPos.ZERO, false)); + } + } +} diff --git a/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java b/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java index 44c7a61..70a3481 100644 --- a/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java +++ b/src/main/java/com/anthonyhilyard/iceberg/renderer/CustomItemRenderer.java @@ -1,19 +1,15 @@ package com.anthonyhilyard.iceberg.renderer; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Predicate; -import java.util.function.Supplier; -import com.anthonyhilyard.iceberg.Loader; +import com.anthonyhilyard.iceberg.util.EntityCollector; import com.google.common.collect.Maps; -import org.apache.commons.lang3.exception.ExceptionUtils; import org.joml.Matrix4f; import org.joml.Quaternionf; import org.joml.Vector3f; @@ -66,18 +62,12 @@ import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.animal.horse.Horse; import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.entity.vehicle.AbstractMinecart; -import net.minecraft.world.entity.vehicle.Boat; -import net.minecraft.world.entity.vehicle.ChestBoat; import net.minecraft.world.inventory.InventoryMenu; import net.minecraft.world.item.BlockItem; -import net.minecraft.world.item.BoatItem; import net.minecraft.world.item.HorseArmorItem; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; -import net.minecraft.world.item.MinecartItem; -import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.HalfTransparentBlock; @@ -86,13 +76,10 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; -import net.minecraft.world.phys.BlockHitResult; -import net.minecraft.world.phys.HitResult; -import net.minecraft.world.phys.Vec3; /** - * An extended ItemRenderer with extended functionality, such as allowing items to be rendered to a RenderTarget + * An extended ItemRenderer with extra functionality, such as allowing items to be rendered to a RenderTarget * before drawing to screen for alpha support, and allowing handheld item models to be rendered into the gui. */ public class CustomItemRenderer extends ItemRenderer @@ -102,16 +89,16 @@ public class CustomItemRenderer extends ItemRenderer private static RenderTarget iconFrameBuffer = null; private static ArmorStand armorStand = null; - private static AbstractMinecart minecart = null; - private static Boat boat = null; private static Horse horse = null; + private static Entity entity = null; private static Pair<Item, CompoundTag> cachedArmorStandItem = null; - private static Item cachedBoatItem = null; private static Pair<Item, CompoundTag> cachedHorseArmorItem = null; + private static Item cachedEntityItem = null; private static Map<Item, ModelBounds> modelBoundsCache = Maps.newHashMap(); private static final List<Direction> quadDirections; - static { + static + { quadDirections = new ArrayList<>(Arrays.asList(Direction.values())); quadDirections.add(null); } @@ -235,7 +222,14 @@ public class CustomItemRenderer extends ItemRenderer if (blockModel != modelManager.getMissingModel()) { // First try rendering via the BlockEntityWithoutLevelRenderer. - blockEntityRenderer.renderByItem(itemStack, transformType, poseStack, bufferSource, packedLight, packedOverlay); + try + { + blockEntityRenderer.renderByItem(itemStack, transformType, poseStack, bufferSource, packedLight, packedOverlay); + } + catch (Exception e) + { + // Do nothing if an exception occurs. + } } else { @@ -279,27 +273,19 @@ public class CustomItemRenderer extends ItemRenderer } } - // Now try rendering entity models for items that spawn minecarts or boats. - if (bufferSourceReady.test(bufferSource) && itemStack.getItem() instanceof MinecartItem minecartItem) + // Now try rendering entity models for items that spawn entities. + if (bufferSourceReady.test(bufferSource) && EntityCollector.itemCreatesEntity(itemStack.getItem(), Entity.class)) { - if (updateMinecart(minecartItem)) + if (updateEntity(itemStack.getItem())) { - renderEntityModel(minecart, poseStack, bufferSource, packedLight); - } - } - - if (bufferSourceReady.test(bufferSource) && itemCreatesBoat(itemStack.getItem())) - { - if (updateBoat(itemStack.getItem())) - { - renderEntityModel(boat, poseStack, bufferSource, packedLight); + renderEntityModel(entity, poseStack, bufferSource, packedLight); } } // If this is horse armor, render it here. if (bufferSourceReady.test(bufferSource) && itemStack.getItem() instanceof HorseArmorItem) { - if (updateHorse(itemStack)) + if (updateHorseArmor(itemStack)) { renderEntityModel(horse, poseStack, bufferSource, packedLight); } @@ -333,7 +319,7 @@ public class CustomItemRenderer extends ItemRenderer { isBlockItem = true; } - else if (itemStack.getItem() instanceof MinecartItem || itemCreatesBoat(itemStack.getItem())) + else if (EntityCollector.itemCreatesEntity(itemStack.getItem(), Entity.class)) { spawnsEntity = true; } @@ -516,194 +502,33 @@ public class CustomItemRenderer extends ItemRenderer return true; } - private boolean updateMinecart(MinecartItem item) - { - // We don't support modded minecarts right now. - if (!item.getClass().equals(MinecartItem.class)) - { - minecart = null; - } - else - { - if (minecart == null || minecart.getMinecartType() != item.type) - { - Minecraft minecraft = Minecraft.getInstance(); - minecart = AbstractMinecart.createMinecart(minecraft.level, 0, 0, 0, item.type); - } - } - - // If somehow the minecart is still null, then we can't render anything. - return minecart != null; - } - - private boolean itemCreatesBoat(Item item) - { - // Vanilla and modded subclass BoatItems obviously create boats. - if (item instanceof BoatItem) - { - return true; - } - - // More complex modded items may create a boat subclass entity, so check for that next. - // To do so, we'll look for a method that returns a subclass of Boat. - try - { - Method[] methods = item.getClass().getDeclaredMethods(); - for (Method method : methods) - { - if (Boat.class.isAssignableFrom(method.getReturnType()) && !method.getReturnType().equals(Boat.class)) - { - return true; - } - } - } - catch (Exception e) {} - - // This doesn't appear to be a boat-creating item. - return false; - } - - /// Attempts to create a modded boat entity from a modded boat item using reflection. - /// Returns null if we failed to create the entity. - private Boat createModdedBoat(Item item) + private Entity getEntityFromItem(Item item) { - Boat moddedBoat = null; - - // Ensure this is actually a modded boat item before continuing. - //if (itemCreatesBoat(item)) + Entity collectedEntity = null; + List<Entity> collectedEntities = EntityCollector.collectEntitiesFromItem(item); + if (!collectedEntities.isEmpty()) { - Minecraft minecraft = Minecraft.getInstance(); - - // Since this boat is modded, first we will look for a method in the BoatItem subclass that returns an instance of a Boat subclass. - // If we find it, we will use it to create the boat entity. - try - { - Method[] methods = item.getClass().getDeclaredMethods(); - for (Method method : methods) - { - // If the signature matches, call it. - if (Boat.class.isAssignableFrom(method.getReturnType()) && !method.getReturnType().equals(Boat.class)) - { - // First supported signature: createBoat(Level level, HitResult hit) - if (method.getParameterCount() == 2 && - method.getParameterTypes()[0].equals(Level.class) && - method.getParameterTypes()[1].equals(HitResult.class)) - - { - method.setAccessible(true); - moddedBoat = (Boat) method.invoke(item, minecraft.level, new BlockHitResult(Vec3.ZERO, Direction.DOWN, BlockPos.ZERO, false)); - break; - } - // Second supported signature: createBoat(Level level, Vec3 pos, float yaw) - else if (method.getParameterCount() == 3 && - method.getParameterTypes()[0].equals(Level.class) && - method.getParameterTypes()[1].equals(Vec3.class) && - method.getParameterTypes()[2].equals(float.class)) - { - method.setAccessible(true); - moddedBoat = (Boat) method.invoke(item, minecraft.level, Vec3.ZERO, 30.0f); - break; - } - } - } - - // Now, if we've created an entity we need to set the "wood type", so we will first look for an enum field on this item that matches - // the parameter type of a method on the boat subclass, then call that method. - if (moddedBoat != null) - { - Field[] fields = item.getClass().getDeclaredFields(); - Method[] boatMethods = moddedBoat.getClass().getDeclaredMethods(); - boolean enumFound = false; - - for (Field field : fields) - { - if (field.getType().isEnum()) - { - // Check if this enum field is the same type as the first parameter of a method on the boat subclass. - for (Method method : boatMethods) - { - if (method.getParameterCount() == 1 && method.getParameterTypes()[0].equals(field.getType())) - { - method.setAccessible(true); - field.setAccessible(true); - method.invoke(moddedBoat, field.get(item)); - enumFound = true; - break; - } - } - } - } - - // If we didn't find any enum fields to try, now try with suppliers. - if (!enumFound) - { - for (Field field : fields) - { - if (field.getType().isAssignableFrom(Supplier.class)) - { - field.setAccessible(true); - Object fieldValue = ((Supplier<?>)field.get(item)).get(); - - // Check if this enum field is the same type as the first parameter of a method on the boat subclass. - for (Method method : boatMethods) - { - if (method.getParameterCount() == 1 && method.getParameterTypes()[0].equals(fieldValue.getClass())) - { - method.setAccessible(true); - method.invoke(moddedBoat, fieldValue); - break; - } - } - } - } - } - } - } - catch (Exception e) - { - // If we fail to create the boat entity by looking for a "getBoat"-esque method, we can't render this modded boat. - // This will fallback to just rendering the item icon. - moddedBoat = null; - Loader.LOGGER.info(ExceptionUtils.getStackTrace(e)); - } + // Just return the first entity collected. + // TODO: Should all entities be considered for weird items that spawn multiple? + collectedEntity = collectedEntities.get(0); } - return moddedBoat; + return collectedEntity; } - private boolean updateBoat(Item item) + private boolean updateEntity(Item item) { - boolean moddedBoat = false; - - // Check if this is a modded boat item, that is if it is a subclass of BoatItem (or not a BoatItem at all). - if (!item.getClass().equals(BoatItem.class)) + if (entity == null || cachedEntityItem != item) { - // This is a modded boat item, so we need to create the boat entity in a different way. - moddedBoat = true; - } - - if (boat == null || cachedBoatItem != item) - { - Minecraft minecraft = Minecraft.getInstance(); - - if (moddedBoat) - { - boat = createModdedBoat(item); - } - else if (item instanceof BoatItem boatItem) - { - boat = boatItem.hasChest ? new ChestBoat(minecraft.level, 0, 0, 0) : new Boat(minecraft.level, 0, 0, 0); - boat.setVariant(boatItem.type); - } - - cachedBoatItem = item; + entity = getEntityFromItem(item); + cachedEntityItem = item; } - // If somehow the boat is still null, then we can't render anything. - return boat != null; + // If somehow the entity is still null, then we can't render anything. + return entity != null; } - private boolean updateHorse(ItemStack horseArmorItem) + private boolean updateHorseArmor(ItemStack horseArmorItem) { // If this isn't a horse armor item, we can't render anything. if (!(horseArmorItem.getItem() instanceof HorseArmorItem)) diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/EntityCollector.java b/src/main/java/com/anthonyhilyard/iceberg/util/EntityCollector.java new file mode 100644 index 0000000..1524a88 --- /dev/null +++ b/src/main/java/com/anthonyhilyard/iceberg/util/EntityCollector.java @@ -0,0 +1,272 @@ +package com.anthonyhilyard.iceberg.util; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Consumer; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.mojang.authlib.GameProfile; + +import net.minecraft.client.Minecraft; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Holder; +import net.minecraft.core.RegistryAccess; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundSource; +import net.minecraft.util.AbortableIterationConsumer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.SpawnEggItem; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.item.crafting.RecipeManager; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkSource; +import net.minecraft.world.level.entity.EntityTypeTest; +import net.minecraft.world.level.entity.LevelEntityGetter; +import net.minecraft.world.level.gameevent.GameEvent; +import net.minecraft.world.level.gameevent.GameEvent.Context; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.saveddata.maps.MapItemSavedData; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.scores.Scoreboard; +import net.minecraft.world.ticks.LevelTickAccess; + +public class EntityCollector extends Level +{ + private final Level wrappedLevel; + private final List<Entity> collectedEntities = Lists.newArrayList(); + private BlockState blockState = Blocks.AIR.defaultBlockState(); + + private static final Map<Level, EntityCollector> wrappedLevelsMap = Maps.newHashMap(); + private static final Map<ItemClassPair, Boolean> itemCreatesEntityResultCache = Maps.newHashMap(); + + private record ItemClassPair(Item item, Class<?> targetClass) {} + + protected EntityCollector(Level wrapped) + { + super(null, null, wrapped.dimensionTypeRegistration(), wrapped.getProfilerSupplier(), false, wrapped.isDebug(), 0, 0); + wrappedLevel = wrapped; + } + + public static EntityCollector of(Level wrappedLevel) + { + if (!wrappedLevelsMap.containsKey(wrappedLevel)) + { + wrappedLevelsMap.put(wrappedLevel, new EntityCollector(wrappedLevel)); + } + + return wrappedLevelsMap.get(wrappedLevel); + } + + public static List<Entity> collectEntitiesFromItem(Item item) + { + Minecraft minecraft = Minecraft.getInstance(); + List<Entity> entities = Lists.newArrayList(); + + try + { + Player dummyPlayer = new Player(minecraft.player.level, BlockPos.ZERO, 0.0f, new GameProfile(null, "_dummy")) { + @Override public boolean isSpectator() { return false; } + @Override public boolean isCreative() { return false; } + }; + + dummyPlayer.setItemInHand(InteractionHand.MAIN_HAND, new ItemStack(item)); + + EntityCollector levelWrapper = EntityCollector.of(dummyPlayer.level); + + if (item instanceof SpawnEggItem spawnEggItem) + { + entities.add(spawnEggItem.getType(new CompoundTag()).create(levelWrapper)); + } + else + { + item.use(levelWrapper, dummyPlayer, InteractionHand.MAIN_HAND); + } + + entities.addAll(levelWrapper.getCollectedEntities()); + + // If we didn't spawn any entities, try again but this time on a simulated rail for minecart-like items. + if (entities.isEmpty()) + { + levelWrapper.setBlockState(Blocks.RAIL.defaultBlockState()); + item.useOn(new UseOnContext(levelWrapper, dummyPlayer, InteractionHand.MAIN_HAND, dummyPlayer.getItemInHand(InteractionHand.MAIN_HAND), new BlockHitResult(Vec3.ZERO, Direction.DOWN, BlockPos.ZERO, false))); + levelWrapper.setBlockState(Blocks.AIR.defaultBlockState()); + + entities.addAll(levelWrapper.getCollectedEntities()); + } + } + catch (Exception e) + { + // Ignore any errors. + } + + return entities; + } + + public static <T extends Entity> boolean itemCreatesEntity(Item item, Class<T> targetClass) + { + ItemClassPair key = new ItemClassPair(item, targetClass); + boolean result = false; + if (!itemCreatesEntityResultCache.containsKey(key)) + { + // Return true if any collected entities from this item are a subclass of the given type. + for (Entity entity : collectEntitiesFromItem(item)) + { + if (targetClass.isInstance(entity)) + { + result = true; + break; + } + } + + itemCreatesEntityResultCache.put(key, result); + } + + return itemCreatesEntityResultCache.get(key); + } + + public List<Entity> getCollectedEntities() + { + // Clear the collected entities after this method is called. + List<Entity> entities = Lists.newArrayList(); + entities.addAll(collectedEntities); + collectedEntities.clear(); + return entities; + } + + public void setBlockState(BlockState blockState) + { + this.blockState = blockState; + } + + @Override + public BlockState getBlockState(BlockPos blockPos) + { + return blockState; + } + + @Override + public boolean noCollision(Entity entity, AABB boundingBox) + { + return true; + } + + @Override + public boolean addFreshEntity(Entity entity) + { + collectedEntities.add(entity); + return false; + } + + @Override + public LevelTickAccess<Block> getBlockTicks() { return wrappedLevel.getBlockTicks(); } + + @Override + public LevelTickAccess<Fluid> getFluidTicks() { return wrappedLevel.getFluidTicks(); } + + @Override + public ChunkSource getChunkSource() { return wrappedLevel.getChunkSource(); } + + + @Override + public void levelEvent(Player p_46771_, int p_46772_, BlockPos p_46773_, int p_46774_) { /* No events. */ } + + @Override + public void gameEvent(GameEvent p_220404_, Vec3 p_220405_, Context p_220406_) { /* No events. */ } + + + @Override + public List<? extends Player> players() { return wrappedLevel.players(); } + + + @Override + public Holder<Biome> getUncachedNoiseBiome(int p_204159_, int p_204160_, int p_204161_) { return wrappedLevel.getUncachedNoiseBiome(p_204159_, p_204160_, p_204161_); } + + + @Override + public RegistryAccess registryAccess() { return wrappedLevel.registryAccess(); } + + + @Override + public FeatureFlagSet enabledFeatures() { return wrappedLevel.enabledFeatures(); } + + @Override + public float getShade(Direction p_45522_, boolean p_45523_) { return wrappedLevel.getShade(p_45522_, p_45523_); } + + + @Override + public void sendBlockUpdated(BlockPos p_46612_, BlockState p_46613_, BlockState p_46614_, int p_46615_) { /* No block updates. */ } + + @Override + public void playSeededSound(Player p_262953_, double p_263004_, double p_263398_, double p_263376_, Holder<SoundEvent> p_263359_, SoundSource p_263020_, float p_263055_, float p_262914_, long p_262991_) { /* No sounds. */ } + + @Override + public void playSeededSound(Player p_220372_, Entity p_220373_, Holder<SoundEvent> p_263500_, SoundSource p_220375_, float p_220376_, float p_220377_, long p_220378_) { /* No sounds. */ } + + + @Override + public String gatherChunkSourceStats() { return wrappedLevel.gatherChunkSourceStats(); } + + @Override + public Entity getEntity(int p_46492_) { return null; } + + + @Override + public MapItemSavedData getMapData(String p_46650_) { return wrappedLevel.getMapData(p_46650_); } + + @Override + public void setMapData(String p_151533_, MapItemSavedData p_151534_) { /* No map data updates. */ } + + @Override + public int getFreeMapId() { return wrappedLevel.getFreeMapId(); } + + @Override + public void destroyBlockProgress(int p_46506_, BlockPos p_46507_, int p_46508_) { /* No block updates. */ } + + + @Override + public Scoreboard getScoreboard() { return wrappedLevel.getScoreboard(); } + + @Override + public RecipeManager getRecipeManager() { return wrappedLevel.getRecipeManager(); } + + @Override + public LevelEntityGetter<Entity> getEntities() + { + return new LevelEntityGetter<Entity>() { + + @Override + public Entity get(int p_156931_) { return null; } + + @Override + public Entity get(UUID p_156939_) { return null; } + + @Override + public Iterable<Entity> getAll() { return List.of(); } + + @Override + public <U extends Entity> void get(EntityTypeTest<Entity, U> p_156935_, AbortableIterationConsumer<U> p_261602_) {} + + @Override + public void get(AABB p_156937_, Consumer<Entity> p_156938_) {} + + @Override + public <U extends Entity> void get(EntityTypeTest<Entity, U> p_156932_, AABB p_156933_, AbortableIterationConsumer<U> p_261542_) {} + }; + + } +} |