diff --git a/build.gradle b/build.gradle index aa496fde24..ff7fb723ca 100644 --- a/build.gradle +++ b/build.gradle @@ -98,7 +98,6 @@ neoForge { data { data() - // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources. programArguments.addAll('--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath()) } @@ -125,7 +124,8 @@ repositories { maven { url = "https://maven.saps.dev/releases" } // FTB Mods maven { url = "https://maven.architectury.dev" } // Arch API maven { url = "https://raw.githubusercontent.com/Fuzss/modresources/main/maven" } // NeoForge config api port, needed by ponder - maven { url = "https://jm.gserv.me/repository/maven-public" // JourneyMap + maven { + url = "https://jm.gserv.me/repository/maven-public" // JourneyMap content { includeGroup "info.journeymap" includeGroup "mysticdrew" @@ -167,7 +167,8 @@ dependencies { if (cc_tweaked_enable.toBoolean()) { compileOnly("cc.tweaked:cc-tweaked-${cc_tweaked_minecraft_version}-core-api:${cc_tweaked_version}") compileOnly("cc.tweaked:cc-tweaked-${cc_tweaked_minecraft_version}-forge-api:${cc_tweaked_version}") - runtimeOnly("cc.tweaked:cc-tweaked-${cc_tweaked_minecraft_version}-forge:${cc_tweaked_version}") + // TODO - Uncomment when cc tweaked supports the api refactors + //runtimeOnly("cc.tweaked:cc-tweaked-${cc_tweaked_minecraft_version}-forge:${cc_tweaked_version}") } runtimeOnly("dev.architectury:architectury-neoforge:13.0.8") @@ -221,7 +222,7 @@ jar { manifest.attributes([ "MixinConfigs": "create.mixins.json", - "Git-Hash": gitHash + "Git-Hash" : gitHash ]) } @@ -294,7 +295,7 @@ String calculateGitHash() { commandLine("git", "rev-parse", "HEAD") } return output.standardOutput.asText.get().trim() - } catch(Throwable ignored) { + } catch (Throwable ignored) { return "unknown" } } @@ -308,7 +309,7 @@ boolean hasUnstaged() { if (!result.isEmpty()) println("Found stageable results:\n ${result}\n") return !result.isEmpty() - } catch(Throwable ignored) { + } catch (Throwable ignored) { return false } } diff --git a/src/generated/resources/.cache/dc4224e5ed0ee367217e022442da0b7476174a87 b/src/generated/resources/.cache/dc4224e5ed0ee367217e022442da0b7476174a87 index a2d55dcaaa..51d7811da1 100644 --- a/src/generated/resources/.cache/dc4224e5ed0ee367217e022442da0b7476174a87 +++ b/src/generated/resources/.cache/dc4224e5ed0ee367217e022442da0b7476174a87 @@ -1,4 +1,4 @@ -// 1.20.1 2025-02-20T18:50:30.47170729 Registrate Provider for create [Recipes, Advancements, Loot Tables, Tags (blocks), Tags (items), Tags (fluids), Tags (entity_types), Blockstates, Item models, Lang (en_us/en_ud)] +// 1.20.1 2025-02-21T09:54:58.646012686 Registrate Provider for create [Recipes, Advancements, Loot Tables, Tags (blocks), Tags (items), Tags (fluids), Tags (entity_types), Blockstates, Item models, Lang (en_us/en_ud)] 60bbdf92d2ac9824ea6144955c74043a6005f79d assets/create/blockstates/acacia_window.json 6a67703c2697d81b7dc83e9d72a66f9c9ff08383 assets/create/blockstates/acacia_window_pane.json c3ae87b62e81d8e9476eccd793bb1548d74c66a1 assets/create/blockstates/adjustable_chain_gearshift.json @@ -4728,6 +4728,7 @@ f43cac8216e2a9347e48cf93a43de95dd810ca20 data/create/tags/items/contraption_cont d371dfd35e49a7bef19f59c03e7f4ae20992f03d data/create/tags/items/create_ingots.json 910d0f5ccbc4c84b224eca1f1588b1695f41447b data/create/tags/items/crushed_raw_materials.json 0fa526e7e742573b603ad26b09526cf724efa1dc data/create/tags/items/deployable_drink.json +35133e95f1c8fdd7a1c21afcc231fc0bffefb9a8 data/create/tags/items/dispense_behavior_wrap_blacklist.json ebd7b09daf2f64c0c04d821696b0e145683d8693 data/create/tags/items/dyed_table_cloths.json d2bb65d893d71d2d9871f81f430c233a93adb4bb data/create/tags/items/invalid_for_track_paving.json 1cebeb92bd514b75d54ac6d5708047801f0501c5 data/create/tags/items/modded_stripped_logs.json diff --git a/src/generated/resources/.cache/f5350c6189acfbe5425c2432489f46008c69099a b/src/generated/resources/.cache/f5350c6189acfbe5425c2432489f46008c69099a new file mode 100644 index 0000000000..a185aa953b --- /dev/null +++ b/src/generated/resources/.cache/f5350c6189acfbe5425c2432489f46008c69099a @@ -0,0 +1,3 @@ +// 1.20.1 2025-02-20T19:36:44.18737762 Create's Mounted Item Storage Type Tags +c65f95f356db09e468847e5799a2cdd8e1417cac data/create/tags/create/mounted_item_storage_type/fuel_blacklist.json +fdadceec842a4cd12dd95f7e271645a52829ec6e data/create/tags/create/mounted_item_storage_type/internal.json diff --git a/src/generated/resources/data/create/tags/create/mounted_item_storage_type/fuel_blacklist.json b/src/generated/resources/data/create/tags/create/mounted_item_storage_type/fuel_blacklist.json new file mode 100644 index 0000000000..99eb273fe1 --- /dev/null +++ b/src/generated/resources/data/create/tags/create/mounted_item_storage_type/fuel_blacklist.json @@ -0,0 +1,5 @@ +{ + "values": [ + "create:vault" + ] +} \ No newline at end of file diff --git a/src/generated/resources/data/create/tags/create/mounted_item_storage_type/internal.json b/src/generated/resources/data/create/tags/create/mounted_item_storage_type/internal.json new file mode 100644 index 0000000000..f91fe16551 --- /dev/null +++ b/src/generated/resources/data/create/tags/create/mounted_item_storage_type/internal.json @@ -0,0 +1,5 @@ +{ + "values": [ + "create:dispenser" + ] +} \ No newline at end of file diff --git a/src/generated/resources/data/create/tags/item/dispense_behavior_wrap_blacklist.json b/src/generated/resources/data/create/tags/item/dispense_behavior_wrap_blacklist.json new file mode 100644 index 0000000000..f72d209df7 --- /dev/null +++ b/src/generated/resources/data/create/tags/item/dispense_behavior_wrap_blacklist.json @@ -0,0 +1,3 @@ +{ + "values": [] +} \ No newline at end of file diff --git a/src/main/java/com/simibubi/create/AllMountedDispenseItemBehaviors.java b/src/main/java/com/simibubi/create/AllMountedDispenseItemBehaviors.java new file mode 100644 index 0000000000..2514c77557 --- /dev/null +++ b/src/main/java/com/simibubi/create/AllMountedDispenseItemBehaviors.java @@ -0,0 +1,191 @@ +package com.simibubi.create; + +import org.jetbrains.annotations.Nullable; + +import com.simibubi.create.api.contraption.dispenser.DefaultMountedDispenseBehavior; +import com.simibubi.create.api.contraption.dispenser.MountedDispenseBehavior; +import com.simibubi.create.api.contraption.dispenser.MountedProjectileDispenseBehavior; +import com.simibubi.create.api.contraption.dispenser.OptionalMountedDispenseBehavior; +import com.simibubi.create.content.contraptions.behaviour.MovementContext; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.FluidTags; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.MobSpawnType; +import net.minecraft.world.entity.item.PrimedTnt; +import net.minecraft.world.entity.projectile.FireworkRocketEntity; +import net.minecraft.world.entity.projectile.Projectile; +import net.minecraft.world.entity.projectile.SmallFireball; +import net.minecraft.world.entity.projectile.ThrownPotion; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.SpawnEggItem; +import net.minecraft.world.item.alchemy.PotionContents; +import net.minecraft.world.item.alchemy.Potions; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.BeehiveBlock; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.BucketPickup; +import net.minecraft.world.level.block.LevelEvent; +import net.minecraft.world.level.block.entity.BeehiveBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; + +public class AllMountedDispenseItemBehaviors { + private static final MountedDispenseBehavior SPAWN_EGG = new DefaultMountedDispenseBehavior() { + @Override + protected ItemStack execute(ItemStack stack, MovementContext context, BlockPos pos, Vec3 facing) { + if (!(stack.getItem() instanceof SpawnEggItem egg)) + return super.execute(stack, context, pos, facing); + + if (context.world instanceof ServerLevel serverLevel) { + EntityType type = egg.getType(stack); + BlockPos offset = BlockPos.containing(facing.x + .7, facing.y + .7, facing.z + .7); + Entity entity = type.spawn(serverLevel, stack, null, pos.offset(offset), MobSpawnType.DISPENSER, facing.y < .5, false); + if (entity != null) { + entity.setDeltaMovement(context.motion.scale(2)); + } + } + + stack.shrink(1); + return stack; + } + }; + private static final MountedDispenseBehavior TNT = new DefaultMountedDispenseBehavior() { + @Override + protected ItemStack execute(ItemStack stack, MovementContext context, BlockPos pos, Vec3 facing) { + double x = pos.getX() + facing.x * .7 + .5; + double y = pos.getY() + facing.y * .7 + .5; + double z = pos.getZ() + facing.z * .7 + .5; + PrimedTnt tnt = new PrimedTnt(context.world, x, y, z, null); + tnt.push(context.motion.x, context.motion.y, context.motion.z); + context.world.addFreshEntity(tnt); + context.world.playSound(null, tnt.getX(), tnt.getY(), tnt.getZ(), SoundEvents.TNT_PRIMED, SoundSource.BLOCKS, 1, 1); + stack.shrink(1); + return stack; + } + }; + private static final MountedDispenseBehavior FIREWORK = new DefaultMountedDispenseBehavior() { + @Override + protected ItemStack execute(ItemStack stack, MovementContext context, BlockPos pos, Vec3 facing) { + double x = pos.getX() + facing.x * .7 + .5; + double y = pos.getY() + facing.y * .7 + .5; + double z = pos.getZ() + facing.z * .7 + .5; + FireworkRocketEntity firework = new FireworkRocketEntity(context.world, stack, x, y, z, true); + firework.shoot(facing.x, facing.y, facing.z, 0.5F, 1.0F); + context.world.addFreshEntity(firework); + stack.shrink(1); + return stack; + } + + @Override + protected void playSound(LevelAccessor level, BlockPos pos) { + level.levelEvent(LevelEvent.SOUND_FIREWORK_SHOOT, pos, 0); + } + }; + private static final MountedDispenseBehavior FIRE_CHARGE = new DefaultMountedDispenseBehavior() { + @Override + protected ItemStack execute(ItemStack stack, MovementContext context, BlockPos pos, Vec3 facing) { + RandomSource random = context.world.random; + double x = pos.getX() + facing.x * .7 + .5; + double y = pos.getY() + facing.y * .7 + .5; + double z = pos.getZ() + facing.z * .7 + .5; + SmallFireball fireball = new SmallFireball( + context.world, + x, y, z, + new Vec3(random.nextGaussian() * 0.05 + facing.x + context.motion.x, + random.nextGaussian() * 0.05 + facing.y + context.motion.y, + random.nextGaussian() * 0.05 + facing.z + context.motion.z).normalize() + ); + fireball.setItem(stack); // copies the stack + context.world.addFreshEntity(fireball); + stack.shrink(1); + return stack; + } + + @Override + protected void playSound(LevelAccessor level, BlockPos pos) { + level.levelEvent(LevelEvent.SOUND_BLAZE_FIREBALL, pos, 0); + } + }; + private static final MountedDispenseBehavior BUCKET = new DefaultMountedDispenseBehavior() { + @Override + protected ItemStack execute(ItemStack stack, MovementContext context, BlockPos pos, Vec3 facing) { + BlockPos interactionPos = pos.relative(MountedDispenseBehavior.getClosestFacingDirection(facing)); + BlockState state = context.world.getBlockState(interactionPos); + if (!(state.getBlock() instanceof BucketPickup bucketPickup)) { + return super.execute(stack, context, pos, facing); + } + + ItemStack bucket = bucketPickup.pickupBlock(null, context.world, interactionPos, state); + MountedDispenseBehavior.placeItemInInventory(bucket, context, pos); + stack.shrink(1); + return stack; + } + }; + private static final MountedDispenseBehavior POTIONS = new MountedProjectileDispenseBehavior() { + @Override + protected Projectile getProjectile(Level level, double x, double y, double z, ItemStack stack, Direction facing) { + ThrownPotion potion = new ThrownPotion(level, x, y, z); + potion.setItem(stack); // copies item + return potion; + } + + @Override + protected float getUncertainty() { + return super.getUncertainty() * 0.5f; + } + + @Override + protected float getPower() { + return super.getPower() * 1.25f; + } + }; + private static final MountedDispenseBehavior BOTTLE = new OptionalMountedDispenseBehavior() { + @Override + @Nullable + protected ItemStack doExecute(ItemStack stack, MovementContext context, BlockPos pos, Vec3 facing) { + BlockPos interactionPos = pos.relative(MountedDispenseBehavior.getClosestFacingDirection(facing)); + BlockState state = context.world.getBlockState(interactionPos); + Block block = state.getBlock(); + + if (block instanceof BeehiveBlock hive && state.is(BlockTags.BEEHIVES) && state.getValue(BeehiveBlock.HONEY_LEVEL) >= 5) { + hive.releaseBeesAndResetHoneyLevel(context.world, state, interactionPos, null, BeehiveBlockEntity.BeeReleaseStatus.BEE_RELEASED); + MountedDispenseBehavior.placeItemInInventory(new ItemStack(Items.HONEY_BOTTLE), context, pos); + stack.shrink(1); + return stack; + } else if (context.world.getFluidState(interactionPos).is(FluidTags.WATER)) { + ItemStack waterBottle = PotionContents.createItemStack(Items.POTION, Potions.WATER); + MountedDispenseBehavior.placeItemInInventory(waterBottle, context, pos); + stack.shrink(1); + return stack; + } else { + return null; + } + } + }; + + public static void registerDefaults() { + for (SpawnEggItem egg : SpawnEggItem.eggs()) { + MountedDispenseBehavior.REGISTRY.register(egg, SPAWN_EGG); + } + + MountedDispenseBehavior.REGISTRY.register(Items.TNT, TNT); + MountedDispenseBehavior.REGISTRY.register(Items.FIREWORK_ROCKET, FIREWORK); + MountedDispenseBehavior.REGISTRY.register(Items.FIRE_CHARGE, FIRE_CHARGE); + MountedDispenseBehavior.REGISTRY.register(Items.BUCKET, BUCKET); + MountedDispenseBehavior.REGISTRY.register(Items.GLASS_BOTTLE, BOTTLE); + + // potions can't be automatically converted since they use a weird wrapper thing + MountedDispenseBehavior.REGISTRY.register(Items.SPLASH_POTION, POTIONS); + MountedDispenseBehavior.REGISTRY.register(Items.LINGERING_POTION, POTIONS); + } +} diff --git a/src/main/java/com/simibubi/create/AllMovementBehaviours.java b/src/main/java/com/simibubi/create/AllMovementBehaviours.java index d10f60a0d4..d218ff7eeb 100644 --- a/src/main/java/com/simibubi/create/AllMovementBehaviours.java +++ b/src/main/java/com/simibubi/create/AllMovementBehaviours.java @@ -13,8 +13,6 @@ public class AllMovementBehaviours { MovementBehaviour.REGISTRY.register(Blocks.BELL, new BellMovementBehaviour()); MovementBehaviour.REGISTRY.register(Blocks.CAMPFIRE, new CampfireMovementBehaviour()); MovementBehaviour.REGISTRY.register(Blocks.SOUL_CAMPFIRE, new CampfireMovementBehaviour()); - - DispenserMovementBehaviour.gatherMovedDispenseItemBehaviours(); MovementBehaviour.REGISTRY.register(Blocks.DISPENSER, new DispenserMovementBehaviour()); MovementBehaviour.REGISTRY.register(Blocks.DROPPER, new DropperMovementBehaviour()); } diff --git a/src/main/java/com/simibubi/create/AllTags.java b/src/main/java/com/simibubi/create/AllTags.java index f89eecc6b2..aeb89a43c1 100644 --- a/src/main/java/com/simibubi/create/AllTags.java +++ b/src/main/java/com/simibubi/create/AllTags.java @@ -7,6 +7,8 @@ import static com.simibubi.create.AllTags.NameSpace.QUARK; import static com.simibubi.create.AllTags.NameSpace.TIC; import com.simibubi.create.api.contraption.ContraptionType; +import com.simibubi.create.api.contraption.storage.item.MountedItemStorage; +import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType; import com.simibubi.create.api.registry.CreateRegistries; import net.createmod.catnip.lang.Lang; @@ -191,6 +193,7 @@ public class AllTags { TRACKS, UPRIGHT_ON_BELT, VALVE_HANDLES, + DISPENSE_BEHAVIOR_WRAP_BLACKLIST, PLATES(COMMON), OBSIDIAN_DUST(COMMON, "dusts/obsidian"), @@ -415,6 +418,31 @@ public class AllTags { } } + public enum AllMountedItemStorageTypeTags { + INTERNAL, + FUEL_BLACKLIST; + + public final TagKey> tag; + public final boolean alwaysDatagen; + + AllMountedItemStorageTypeTags() { + ResourceLocation tagId = Create.asResource(Lang.asId(this.name())); + this.tag = TagKey.create(CreateRegistries.MOUNTED_ITEM_STORAGE_TYPE, tagId); + this.alwaysDatagen = true; + } + + public boolean matches(MountedItemStorage storage) { + return this.matches(storage.type); + } + + public boolean matches(MountedItemStorageType type) { + return type.is(this.tag); + } + + private static void init() { + } + } + public static void init() { AllBlockTags.init(); AllItemTags.init(); @@ -422,5 +450,6 @@ public class AllTags { AllEntityTags.init(); AllRecipeSerializerTags.init(); AllContraptionTypeTags.init(); + AllMountedItemStorageTypeTags.init(); } } diff --git a/src/main/java/com/simibubi/create/Create.java b/src/main/java/com/simibubi/create/Create.java index 72e05a4fff..86fa45b65f 100644 --- a/src/main/java/com/simibubi/create/Create.java +++ b/src/main/java/com/simibubi/create/Create.java @@ -177,6 +177,7 @@ public class Create { AllInteractionBehaviours.registerDefaults(); AllContraptionMovementSettings.registerDefaults(); AllOpenPipeEffectHandlers.registerDefaults(); + AllMountedDispenseItemBehaviors.registerDefaults(); // -- }); } diff --git a/src/main/java/com/simibubi/create/api/contraption/dispenser/DefaultMountedDispenseBehavior.java b/src/main/java/com/simibubi/create/api/contraption/dispenser/DefaultMountedDispenseBehavior.java new file mode 100644 index 0000000000..5f8469cd39 --- /dev/null +++ b/src/main/java/com/simibubi/create/api/contraption/dispenser/DefaultMountedDispenseBehavior.java @@ -0,0 +1,88 @@ +package com.simibubi.create.api.contraption.dispenser; + +import com.simibubi.create.content.contraptions.behaviour.MovementContext; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.dispenser.DefaultDispenseItemBehavior; +import net.minecraft.world.Container; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.LevelEvent; +import net.minecraft.world.level.block.entity.HopperBlockEntity; +import net.minecraft.world.phys.Vec3; + +/** + * A parallel to {@link DefaultDispenseItemBehavior}, providing a common, default, extendable dispense implementation. + */ +public class DefaultMountedDispenseBehavior implements MountedDispenseBehavior { + /** + * A reusable instance of the default behavior. + */ + public static final MountedDispenseBehavior INSTANCE = new DefaultMountedDispenseBehavior(); + + @Override + public ItemStack dispense(ItemStack stack, MovementContext context, BlockPos pos) { + Vec3 normal = MountedDispenseBehavior.getDispenserNormal(context); + + Direction closestToFacing = MountedDispenseBehavior.getClosestFacingDirection(normal); + Container inventory = HopperBlockEntity.getContainerAt(context.world, pos.relative(closestToFacing)); + if (inventory == null) { + ItemStack remainder = this.execute(stack, context, pos, normal); + this.playSound(context.world, pos); + this.playAnimation(context.world, pos, closestToFacing); + return remainder; + } else { + ItemStack toInsert = stack.copyWithCount(1); + ItemStack remainder = HopperBlockEntity.addItem(null, inventory, toInsert, closestToFacing.getOpposite()); + if (remainder.isEmpty()) { + stack.shrink(1); + } + } + return stack; + } + + /** + * Dispense the given item. Sounds and particles are already handled. + * @return the remaining items after dispensing one + */ + protected ItemStack execute(ItemStack stack, MovementContext context, BlockPos pos, Vec3 facing) { + ItemStack toDispense = stack.split(1); + spawnItem(context.world, toDispense, 6, facing, pos, context); + return stack; + } + + protected void playSound(LevelAccessor level, BlockPos pos) { + level.levelEvent(LevelEvent.SOUND_DISPENSER_DISPENSE, pos, 0); + } + + protected void playAnimation(LevelAccessor level, BlockPos pos, Vec3 facing) { + this.playAnimation(level, pos, MountedDispenseBehavior.getClosestFacingDirection(facing)); + } + + protected void playAnimation(LevelAccessor level, BlockPos pos, Direction direction) { + level.levelEvent(LevelEvent.PARTICLES_SHOOT_SMOKE, pos, direction.get3DDataValue()); + } + + public static void spawnItem(Level level, ItemStack stack, int speed, Vec3 facing, BlockPos pos, MovementContext context) { + double x = pos.getX() + facing.x + .5; + double y = pos.getY() + facing.y + .5; + double z = pos.getZ() + facing.z + .5; + if (MountedDispenseBehavior.getClosestFacingDirection(facing).getAxis() == Direction.Axis.Y) { + y = y - 0.125; + } else { + y = y - 0.15625; + } + + ItemEntity entity = new ItemEntity(level, x, y, z, stack); + double d3 = level.random.nextDouble() * 0.1 + 0.2; + entity.setDeltaMovement( + level.random.nextGaussian() * 0.0075 * speed + facing.x() * d3 + context.motion.x, + level.random.nextGaussian() * 0.0075 * speed + facing.y() * d3 + context.motion.y, + level.random.nextGaussian() * 0.0075 * speed + facing.z() * d3 + context.motion.z + ); + level.addFreshEntity(entity); + } +} diff --git a/src/main/java/com/simibubi/create/api/contraption/dispenser/MountedDispenseBehavior.java b/src/main/java/com/simibubi/create/api/contraption/dispenser/MountedDispenseBehavior.java new file mode 100644 index 0000000000..5d3da14425 --- /dev/null +++ b/src/main/java/com/simibubi/create/api/contraption/dispenser/MountedDispenseBehavior.java @@ -0,0 +1,79 @@ +package com.simibubi.create.api.contraption.dispenser; + +import com.simibubi.create.api.registry.SimpleRegistry; +import com.simibubi.create.content.contraptions.behaviour.MovementContext; +import com.simibubi.create.impl.contraption.dispenser.DispenserBehaviorConverter; + +import net.minecraft.Util; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.dispenser.DispenseItemBehavior; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.DispenserBlock; +import net.minecraft.world.phys.Vec3; + +import net.neoforged.neoforge.items.ItemHandlerHelper; +import net.neoforged.neoforge.items.wrapper.CombinedInvWrapper; + +/** + * A parallel to {@link DispenseItemBehavior}, for use by mounted dispensers. + * Create will attempt to wrap existing {@link DispenseItemBehavior}s, but this interface can be used to provide better or fixed behavior. + * @see DefaultMountedDispenseBehavior + * @see MountedProjectileDispenseBehavior + * @see OptionalMountedDispenseBehavior + */ +@FunctionalInterface +public interface MountedDispenseBehavior { + SimpleRegistry REGISTRY = Util.make(() -> { + SimpleRegistry registry = SimpleRegistry.create(); + registry.registerProvider(DispenserBehaviorConverter.INSTANCE); + return registry; + }); + + /** + * Dispense the given stack into the world. + * @param stack the stack to dispense. Safe to modify, behaviors are given a copy + * @param context the MovementContext of the dispenser + * @param pos the BlockPos being visited by the dispenser + * @return the remaining stack after dispensing one item + */ + ItemStack dispense(ItemStack stack, MovementContext context, BlockPos pos); + + // utilities for implementations + + static Vec3 getDispenserNormal(MovementContext ctx) { + Direction facing = ctx.state.getValue(DispenserBlock.FACING); + Vec3 normal = Vec3.atLowerCornerOf(facing.getNormal()); + return ctx.rotation.apply(normal).normalize(); + } + + static Direction getClosestFacingDirection(Vec3 facing) { + return Direction.getNearest(facing.x, facing.y, facing.z); + } + + /** + * Attempt to place an item back into the inventory. This is used in the case of item overflow, such as a stack + * of buckets becoming two separate stacks when one is filled with water. + *

+ * First tries to insert directly into the dispenser inventory. If that fails, it then tries the contraption's + * whole inventory. If that still fails, the stack is dispensed into the world with the default behavior. + * @param stack the stack to store in the inventory + * @param context the MovementContext given to the behavior + * @param pos the position given to the behavior + */ + static void placeItemInInventory(ItemStack stack, MovementContext context, BlockPos pos) { + ItemStack toInsert = stack.copy(); + // try inserting into own inventory first + ItemStack remainder = ItemHandlerHelper.insertItem(context.getItemStorage(), toInsert, false); + if (!remainder.isEmpty()) { + // next, try the whole contraption inventory + CombinedInvWrapper contraption = context.contraption.getStorage().getAllItems(); + ItemStack newRemainder = ItemHandlerHelper.insertItem(contraption, remainder, false); + if (!newRemainder.isEmpty()) { + // if there's *still* something left, dispense into world + DefaultMountedDispenseBehavior.INSTANCE.dispense(remainder, context, pos); + } + } + } +} diff --git a/src/main/java/com/simibubi/create/api/contraption/dispenser/MountedProjectileDispenseBehavior.java b/src/main/java/com/simibubi/create/api/contraption/dispenser/MountedProjectileDispenseBehavior.java new file mode 100644 index 0000000000..240602ea92 --- /dev/null +++ b/src/main/java/com/simibubi/create/api/contraption/dispenser/MountedProjectileDispenseBehavior.java @@ -0,0 +1,76 @@ +package com.simibubi.create.api.contraption.dispenser; + +import javax.annotation.Nullable; + +import com.simibubi.create.content.contraptions.behaviour.MovementContext; +import com.simibubi.create.foundation.mixin.accessor.ProjectileDispenseBehaviorAccessor; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.dispenser.ProjectileDispenseBehavior; +import net.minecraft.world.entity.projectile.Projectile; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.LevelEvent; +import net.minecraft.world.phys.Vec3; + +/** + * A parallel to {@link ProjectileDispenseBehavior}, providing a base implementation for projectile-shooting behaviors. + */ +public abstract class MountedProjectileDispenseBehavior extends DefaultMountedDispenseBehavior { + @Override + protected ItemStack execute(ItemStack stack, MovementContext context, BlockPos pos, Vec3 facing) { + double x = pos.getX() + facing.x * .7 + .5; + double y = pos.getY() + facing.y * .7 + .5; + double z = pos.getZ() + facing.z * .7 + .5; + Projectile projectile = this.getProjectile(context.world, x, y, z, stack.copy(), MountedDispenseBehavior.getClosestFacingDirection(facing)); + if (projectile == null) + return stack; + + Vec3 motion = facing.scale(this.getPower()).add(context.motion); + projectile.shoot(motion.x, motion.y, motion.z, (float) motion.length(), this.getUncertainty()); + context.world.addFreshEntity(projectile); + stack.shrink(1); + return stack; + } + + @Override + protected void playSound(LevelAccessor level, BlockPos pos) { + level.levelEvent(LevelEvent.SOUND_DISPENSER_PROJECTILE_LAUNCH, pos, 0); + } + + @Nullable + protected abstract Projectile getProjectile(Level level, double x, double y, double z, ItemStack stack, Direction facing); + + protected float getUncertainty() { + return 6; + } + + protected float getPower() { + return 1.1f; + } + + /** + * Create a mounted behavior wrapper from a vanilla projectile dispense behavior. + */ + public static MountedDispenseBehavior of(ProjectileDispenseBehavior vanillaBehaviour) { + ProjectileDispenseBehaviorAccessor accessor = (ProjectileDispenseBehaviorAccessor) vanillaBehaviour; + return new MountedProjectileDispenseBehavior() { + @Override + protected Projectile getProjectile(Level level, double x, double y, double z, ItemStack stack, Direction facing) { + return accessor.create$getProjectileItem().asProjectile(level, new Vec3(x, y, z), stack, facing); + } + + @Override + protected float getUncertainty() { + return accessor.create$getDispenseConfig().uncertainty(); + } + + @Override + protected float getPower() { + return accessor.create$getDispenseConfig().power(); + } + }; + } +} diff --git a/src/main/java/com/simibubi/create/api/contraption/dispenser/OptionalMountedDispenseBehavior.java b/src/main/java/com/simibubi/create/api/contraption/dispenser/OptionalMountedDispenseBehavior.java new file mode 100644 index 0000000000..4be21e7222 --- /dev/null +++ b/src/main/java/com/simibubi/create/api/contraption/dispenser/OptionalMountedDispenseBehavior.java @@ -0,0 +1,43 @@ +package com.simibubi.create.api.contraption.dispenser; + +import org.jetbrains.annotations.Nullable; + +import com.simibubi.create.content.contraptions.behaviour.MovementContext; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.LevelEvent; +import net.minecraft.world.phys.Vec3; + +/** + * A mounted dispenser behavior that might fail, playing the empty sound if it does. + */ +public class OptionalMountedDispenseBehavior extends DefaultMountedDispenseBehavior { + private boolean success; + + @Override + protected final ItemStack execute(ItemStack stack, MovementContext context, BlockPos pos, Vec3 facing) { + ItemStack remainder = this.doExecute(stack, context, pos, facing); + this.success = remainder != null; + return remainder == null ? stack : remainder; + } + + @Override + protected void playSound(LevelAccessor level, BlockPos pos) { + if (this.success) { + super.playSound(level, pos); + } else { + level.levelEvent(LevelEvent.SOUND_DISPENSER_FAIL, pos, 0); + } + } + + /** + * Dispense the given item. + * @return the remaining items after dispensing one, or null if it failed + */ + @Nullable + protected ItemStack doExecute(ItemStack stack, MovementContext context, BlockPos pos, Vec3 facing) { + return super.execute(stack, context, pos, facing); + } +} diff --git a/src/main/java/com/simibubi/create/api/contraption/storage/item/MountedItemStorage.java b/src/main/java/com/simibubi/create/api/contraption/storage/item/MountedItemStorage.java index e215b00e27..51f1437eaf 100644 --- a/src/main/java/com/simibubi/create/api/contraption/storage/item/MountedItemStorage.java +++ b/src/main/java/com/simibubi/create/api/contraption/storage/item/MountedItemStorage.java @@ -8,11 +8,8 @@ import java.util.function.Predicate; import org.jetbrains.annotations.Nullable; import com.mojang.serialization.Codec; -import com.simibubi.create.api.behaviour.movement.MovementBehaviour; import com.simibubi.create.api.contraption.storage.item.menu.MountedStorageMenus; import com.simibubi.create.content.contraptions.Contraption; -import com.simibubi.create.content.contraptions.MountedStorageManager; -import com.simibubi.create.content.contraptions.behaviour.MovementContext; import com.simibubi.create.foundation.utility.CreateLang; import net.minecraft.core.BlockPos; @@ -59,26 +56,6 @@ public abstract class MountedItemStorage implements IItemHandlerModifiable { */ public abstract void unmount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be); - /** - * Internal mounted storages are not exposed to the larger contraption inventory. - * They are only for internal use, such as access from a {@link MovementBehaviour}. - * Internal storages are still accessible through {@link MovementContext#getItemStorage()} - * as well as {@link MountedStorageManager#getAllItemStorages()}. - * A storage being internal implies that it does not provide fuel either. - * This is only called once on assembly. - */ - public boolean isInternal() { - return false; - } - - /** - * Contraptions may search storage for fuel, such as for powering furnace minecarts - * and trains. Return false if this storage should - */ - public boolean providesFuel() { - return true; - } - /** * Handle a player clicking on this mounted storage. This is always called on the server. * The default implementation will try to open a generic GUI for standard inventories. diff --git a/src/main/java/com/simibubi/create/api/contraption/storage/item/MountedItemStorageType.java b/src/main/java/com/simibubi/create/api/contraption/storage/item/MountedItemStorageType.java index 846d66146d..9bbf297a33 100644 --- a/src/main/java/com/simibubi/create/api/contraption/storage/item/MountedItemStorageType.java +++ b/src/main/java/com/simibubi/create/api/contraption/storage/item/MountedItemStorageType.java @@ -14,6 +14,8 @@ import com.tterrag.registrate.util.nullness.NonNullUnaryOperator; import net.minecraft.Util; import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.tags.TagKey; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; @@ -28,9 +30,15 @@ public abstract class MountedItemStorageType { }); public final MapCodec codec; + public final Holder> holder; protected MountedItemStorageType(MapCodec codec) { this.codec = codec; + this.holder = CreateBuiltInRegistries.MOUNTED_ITEM_STORAGE_TYPE.createIntrusiveHolder(this); + } + + public final boolean is(TagKey> tag) { + return this.holder.is(tag); } @Nullable diff --git a/src/main/java/com/simibubi/create/api/registry/CreateBuiltInRegistries.java b/src/main/java/com/simibubi/create/api/registry/CreateBuiltInRegistries.java index d4524da4c9..036c8afe21 100644 --- a/src/main/java/com/simibubi/create/api/registry/CreateBuiltInRegistries.java +++ b/src/main/java/com/simibubi/create/api/registry/CreateBuiltInRegistries.java @@ -1,12 +1,8 @@ package com.simibubi.create.api.registry; -import java.lang.reflect.Field; import java.util.HashSet; -import java.util.Optional; import java.util.Set; -import org.jetbrains.annotations.ApiStatus; - import com.mojang.serialization.Lifecycle; import com.simibubi.create.api.behaviour.display.DisplaySource; import com.simibubi.create.api.behaviour.display.DisplayTarget; @@ -19,10 +15,8 @@ import com.simibubi.create.content.logistics.item.filter.attribute.ItemAttribute import com.simibubi.create.content.logistics.packagePort.PackagePortTargetType; import net.minecraft.core.MappedRegistry; -import net.minecraft.core.RegistrationInfo; import net.minecraft.core.Registry; import net.minecraft.core.WritableRegistry; -import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceKey; import net.neoforged.bus.api.SubscribeEvent; @@ -37,8 +31,6 @@ import net.neoforged.neoforge.registries.NewRegistryEvent; */ @EventBusSubscriber(bus = Bus.MOD) public class CreateBuiltInRegistries { - private static final WritableRegistry> ROOT_REGISTRY = getRootRegistry(); - public static final Set> REGISTRIES = new HashSet<>(); public static final Registry ARM_INTERACTION_POINT_TYPE = simple(CreateRegistries.ARM_INTERACTION_POINT_TYPE); @@ -46,47 +38,24 @@ public class CreateBuiltInRegistries { public static final Registry ITEM_ATTRIBUTE_TYPE = simple(CreateRegistries.ITEM_ATTRIBUTE_TYPE); public static final Registry DISPLAY_SOURCE = simple(CreateRegistries.DISPLAY_SOURCE); public static final Registry DISPLAY_TARGET = simple(CreateRegistries.DISPLAY_TARGET); - public static final Registry> MOUNTED_ITEM_STORAGE_TYPE = simple(CreateRegistries.MOUNTED_ITEM_STORAGE_TYPE); + public static final Registry> MOUNTED_ITEM_STORAGE_TYPE = withIntrusiveHolders(CreateRegistries.MOUNTED_ITEM_STORAGE_TYPE); public static final Registry> MOUNTED_FLUID_STORAGE_TYPE = simple(CreateRegistries.MOUNTED_FLUID_STORAGE_TYPE); public static final Registry CONTRAPTION_TYPE = withIntrusiveHolders(CreateRegistries.CONTRAPTION_TYPE); public static final Registry PACKAGE_PORT_TARGET_TYPE = simple(CreateRegistries.PACKAGE_PORT_TARGET_TYPE); private static Registry simple(ResourceKey> key) { - return register(key, new MappedRegistry<>(key, Lifecycle.stable(), false)); + return register(new MappedRegistry<>(key, Lifecycle.stable(), false)); } private static Registry withIntrusiveHolders(ResourceKey> key) { - return register(key, new MappedRegistry<>(key, Lifecycle.stable(), true)); + return register(new MappedRegistry<>(key, Lifecycle.stable(), true)); } - @SuppressWarnings("unchecked") - private static Registry register(ResourceKey> key, WritableRegistry registry) { - ROOT_REGISTRY.register( - (ResourceKey>) (Object) key, registry, new RegistrationInfo(Optional.empty(), Lifecycle.stable()) - ); + private static Registry register(WritableRegistry registry) { REGISTRIES.add(registry); return registry; } - @SuppressWarnings("unchecked") - private static WritableRegistry> getRootRegistry() { - // an accessor can't be used here because BuiltInRegistries is loaded too early during datagen. - try { - Field field = BuiltInRegistries.class.getDeclaredField("WRITABLE_REGISTRY"); - field.setAccessible(true); - return (WritableRegistry>) field.get(null); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException("Create: Failed to get root registry", e); - } - } - - // TODO Remove - @ApiStatus.Internal - public static void init() { - // make sure the class is loaded. - // this method is called at the tail of BuiltInRegistries, injected by a coremod. See it for details. - } - @SubscribeEvent public static void onNewRegistryEvent(NewRegistryEvent event) { for (Registry registry : REGISTRIES) diff --git a/src/main/java/com/simibubi/create/api/registry/registrate/SimpleBuilder.java b/src/main/java/com/simibubi/create/api/registry/registrate/SimpleBuilder.java index 215d985648..81e6e9fbc2 100644 --- a/src/main/java/com/simibubi/create/api/registry/registrate/SimpleBuilder.java +++ b/src/main/java/com/simibubi/create/api/registry/registrate/SimpleBuilder.java @@ -2,6 +2,7 @@ package com.simibubi.create.api.registry.registrate; import java.util.function.BiConsumer; import java.util.function.Function; +import java.util.function.Supplier; import org.jetbrains.annotations.Nullable; @@ -22,21 +23,21 @@ import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.material.Fluid; public class SimpleBuilder extends AbstractBuilder> { - private final T value; + private final Supplier value; private SimpleRegistryAccess byBlock; private SimpleRegistryAccess, R> byBlockEntity; private SimpleRegistryAccess, R> byEntity; private SimpleRegistryAccess byFluid; - public SimpleBuilder(AbstractRegistrate owner, P parent, String name, BuilderCallback callback, ResourceKey> registryKey, T value) { + public SimpleBuilder(AbstractRegistrate owner, P parent, String name, BuilderCallback callback, ResourceKey> registryKey, Supplier value) { super(owner, parent, name, callback, registryKey); this.value = value; } @Override protected T createEntry() { - return this.value; + return this.value.get(); } // for setup @@ -91,49 +92,49 @@ public class SimpleBuilder extends AbstractBuilder associate(Block block) { assertPresent(this.byBlock, "Block"); - this.byBlock.adder.accept(block, this.value); + this.onRegister(value -> this.byBlock.adder.accept(block, value)); return this; } public SimpleBuilder associateBlockTag(TagKey tag) { assertPresent(this.byBlock, "Block"); - this.byBlock.tagAdder.accept(tag, this.value); + this.onRegister(value -> this.byBlock.tagAdder.accept(tag, value)); return this; } public SimpleBuilder associate(BlockEntityType type) { assertPresent(this.byBlockEntity, "BlockEntityType"); - this.byBlockEntity.adder.accept(type, this.value); + this.onRegister(value -> this.byBlockEntity.adder.accept(type, value)); return this; } public SimpleBuilder associateBeTag(TagKey> tag) { assertPresent(this.byBlockEntity, "BlockEntityType"); - this.byBlockEntity.tagAdder.accept(tag, this.value); + this.onRegister(value -> this.byBlockEntity.tagAdder.accept(tag, value)); return this; } public SimpleBuilder associate(EntityType type) { assertPresent(this.byEntity, "EntityType"); - this.byEntity.adder.accept(type, this.value); + this.onRegister(value -> this.byEntity.adder.accept(type, value)); return this; } public SimpleBuilder associateEntityTag(TagKey> tag) { assertPresent(this.byEntity, "EntityType"); - this.byEntity.tagAdder.accept(tag, this.value); + this.onRegister(value -> this.byEntity.tagAdder.accept(tag, value)); return this; } public SimpleBuilder associate(Fluid fluid) { assertPresent(this.byFluid, "Fluid"); - this.byFluid.adder.accept(fluid, this.value); + this.onRegister(value -> this.byFluid.adder.accept(fluid, value)); return this; } public SimpleBuilder associateFluidTag(TagKey tag) { assertPresent(this.byFluid, "Fluid"); - this.byFluid.tagAdder.accept(tag, this.value); + this.onRegister(value -> this.byFluid.tagAdder.accept(tag, value)); return this; } diff --git a/src/main/java/com/simibubi/create/content/contraptions/MountedStorageManager.java b/src/main/java/com/simibubi/create/content/contraptions/MountedStorageManager.java index deaaa36e3e..a2a8a85676 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/MountedStorageManager.java +++ b/src/main/java/com/simibubi/create/content/contraptions/MountedStorageManager.java @@ -9,6 +9,8 @@ import java.util.Map; import java.util.Set; import java.util.function.Predicate; +import com.simibubi.create.AllTags.AllMountedItemStorageTypeTags; + import net.createmod.catnip.codecs.CatnipCodecUtils; import org.jetbrains.annotations.Nullable; @@ -96,16 +98,12 @@ public class MountedStorageManager { this.allItemStorages = ImmutableMap.copyOf(this.itemsBuilder); - this.items = new MountedItemStorageWrapper(subMap( - this.allItemStorages, storage -> !storage.isInternal() - )); + this.items = new MountedItemStorageWrapper(subMap(this.allItemStorages, this::isExposed)); this.allItems = this.items; this.itemsBuilder = null; - ImmutableMap fuelMap = subMap( - this.allItemStorages, storage -> !storage.isInternal() && storage.providesFuel() - ); + ImmutableMap fuelMap = subMap(this.allItemStorages, this::canUseForFuel); this.fuelItems = fuelMap.isEmpty() ? null : new MountedItemStorageWrapper(fuelMap); ImmutableMap fluids = ImmutableMap.copyOf(this.fluidsBuilder); @@ -118,6 +116,14 @@ public class MountedStorageManager { this.syncedFluidsBuilder = null; } + private boolean isExposed(MountedItemStorage storage) { + return !AllMountedItemStorageTypeTags.INTERNAL.matches(storage); + } + + private boolean canUseForFuel(MountedItemStorage storage) { + return this.isExposed(storage) && !AllMountedItemStorageTypeTags.FUEL_BLACKLIST.matches(storage); + } + private boolean isInitialized() { return this.itemsBuilder == null; } @@ -364,10 +370,7 @@ public class MountedStorageManager { } /** - * Gets a map of all MountedItemStorages in the contraption, irrelevant of them - * being internal or providing fuel. - * @see MountedItemStorage#isInternal() - * @see MountedItemStorage#providesFuel() + * Gets a map of all MountedItemStorages in the contraption, irrelevant of them being internal or providing fuel. */ public ImmutableMap getAllItemStorages() { this.assertInitialized(); diff --git a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/DispenseItemLocation.java b/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/DispenseItemLocation.java deleted file mode 100644 index c9134e0b3f..0000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/DispenseItemLocation.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.simibubi.create.content.contraptions.behaviour.dispenser; - -public class DispenseItemLocation { - private final boolean internal; - private final int slot; - - public static final DispenseItemLocation NONE = new DispenseItemLocation(false, -1); - - public DispenseItemLocation(boolean internal, int slot) { - this.internal = internal; - this.slot = slot; - } - - public boolean isInternal() { - return internal; - } - - public int getSlot() { - return slot; - } - - public boolean isEmpty() { - return slot < 0; - } -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/DispenserMovementBehaviour.java b/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/DispenserMovementBehaviour.java index ecc652421c..86468d4cd5 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/DispenserMovementBehaviour.java +++ b/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/DispenserMovementBehaviour.java @@ -1,114 +1,16 @@ package com.simibubi.create.content.contraptions.behaviour.dispenser; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - +import com.simibubi.create.api.contraption.dispenser.DefaultMountedDispenseBehavior; +import com.simibubi.create.api.contraption.dispenser.MountedDispenseBehavior; import com.simibubi.create.content.contraptions.behaviour.MovementContext; -import com.simibubi.create.foundation.mixin.accessor.DispenserBlockAccessor; import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.dispenser.BlockSource; -import net.minecraft.core.dispenser.DefaultDispenseItemBehavior; -import net.minecraft.core.dispenser.DispenseItemBehavior; -import net.minecraft.core.dispenser.ProjectileDispenseBehavior; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.DispenserBlock; -import net.minecraft.world.level.block.entity.DispenserBlockEntity; -import net.minecraft.world.phys.Vec3; public class DispenserMovementBehaviour extends DropperMovementBehaviour { - private static final Map movedDispenseItemBehaviors = new HashMap<>(); - private static final Set blacklist = new HashSet<>(); - - private static boolean spawnEggsRegistered = false; - - public static void gatherMovedDispenseItemBehaviours() { - IMovedDispenseItemBehaviour.init(); - } - - public static void registerMovedDispenseItemBehaviour(Item item, - IMovedDispenseItemBehaviour movedDispenseItemBehaviour) { - movedDispenseItemBehaviors.put(item, movedDispenseItemBehaviour); - } - - public static DispenseItemBehavior getDispenseMethod(Level level, ItemStack itemstack) { - return ((DispenserBlockAccessor) Blocks.DISPENSER).create$callGetDispenseMethod(level, itemstack); - } - @Override - protected IMovedDispenseItemBehaviour getDispenseBehavior(MovementContext context, BlockPos pos, ItemStack stack) { - if (!spawnEggsRegistered) { - spawnEggsRegistered = true; - IMovedDispenseItemBehaviour.initSpawnEggs(); - } - - Item item = stack.getItem(); - // return registered/cached behavior if present - if (movedDispenseItemBehaviors.containsKey(item)) { - return movedDispenseItemBehaviors.get(item); - } - - // if there isn't one, try to create one from a vanilla behavior - if (blacklist.contains(item)) { - // unless it's been blacklisted, which means a behavior was created already and errored - return MovedDefaultDispenseItemBehaviour.INSTANCE; - } - - DispenseItemBehavior behavior = getDispenseMethod(context.world, stack); - // no behavior or default, use the moved default - if (behavior == null || behavior.getClass() == DefaultDispenseItemBehavior.class) - return MovedDefaultDispenseItemBehaviour.INSTANCE; - - // projectile-specific behaviors are pretty straightforward to convert - if (behavior instanceof ProjectileDispenseBehavior projectile) { - IMovedDispenseItemBehaviour movedBehaviour = MovedProjectileDispenserBehaviour.of(projectile); - // cache it for later - registerMovedDispenseItemBehaviour(item, movedBehaviour); - return movedBehaviour; - } - - MinecraftServer server = context.world.getServer(); - ServerLevel serverLevel = server != null ? server.getLevel(context.world.dimension()) : null; - - DispenserBlockEntity blockEntity = null; - if (context.world.getBlockEntity(pos) instanceof DispenserBlockEntity dispenserBlockEntity) - blockEntity = dispenserBlockEntity; - - // other behaviors are more convoluted due to BlockSource providing a BlockEntity. - Vec3 normal = getRotatedFacingNormal(context); - Direction nearestFacing = Direction.getNearest(normal.x, normal.y, normal.z); - BlockSource source = new BlockSource(serverLevel, pos, context.state, blockEntity); - IMovedDispenseItemBehaviour movedBehavior = new FallbackMovedDispenseBehavior(item, behavior, source); - registerMovedDispenseItemBehaviour(item, movedBehavior); - return movedBehavior; - } - - private static Vec3 getRotatedFacingNormal(MovementContext ctx) { - Direction facing = ctx.state.getValue(DispenserBlock.FACING); - Vec3 normal = Vec3.atLowerCornerOf(facing.getNormal()); - return ctx.rotation.apply(normal); - } - - private record FallbackMovedDispenseBehavior(Item item, DispenseItemBehavior wrapped, BlockSource source) implements IMovedDispenseItemBehaviour { - @Override - public ItemStack dispense(ItemStack stack, MovementContext context, BlockPos pos) { - ItemStack backup = stack.copy(); - try { - return this.wrapped.dispense(this.source, stack); - } catch (NullPointerException ignored) { - // error due to lack of a BlockEntity. Un-register self to avoid continuing to fail - movedDispenseItemBehaviors.remove(this.item); - blacklist.add(this.item); - return backup; - } - } + protected MountedDispenseBehavior getDispenseBehavior(MovementContext context, BlockPos pos, ItemStack stack) { + MountedDispenseBehavior behavior = MountedDispenseBehavior.REGISTRY.get(stack.getItem()); + return behavior != null ? behavior : DefaultMountedDispenseBehavior.INSTANCE; } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/DropperMovementBehaviour.java b/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/DropperMovementBehaviour.java index c67ef67c9c..e27583c05c 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/DropperMovementBehaviour.java +++ b/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/DropperMovementBehaviour.java @@ -5,6 +5,8 @@ import java.util.function.Predicate; import org.jetbrains.annotations.Nullable; import com.simibubi.create.api.behaviour.movement.MovementBehaviour; +import com.simibubi.create.api.contraption.dispenser.DefaultMountedDispenseBehavior; +import com.simibubi.create.api.contraption.dispenser.MountedDispenseBehavior; import com.simibubi.create.api.contraption.storage.item.MountedItemStorage; import com.simibubi.create.content.contraptions.behaviour.MovementContext; import com.simibubi.create.foundation.item.ItemHelper; @@ -38,13 +40,13 @@ public class DropperMovementBehaviour implements MovementBehaviour { // copy because dispense behaviors will modify it directly ItemStack stack = storage.getStackInSlot(slot).copy(); - IMovedDispenseItemBehaviour behavior = getDispenseBehavior(context, pos, stack); + MountedDispenseBehavior behavior = getDispenseBehavior(context, pos, stack); ItemStack remainder = behavior.dispense(stack, context, pos); storage.setStackInSlot(slot, remainder); } - protected IMovedDispenseItemBehaviour getDispenseBehavior(MovementContext context, BlockPos pos, ItemStack stack) { - return MovedDefaultDispenseItemBehaviour.INSTANCE; + protected MountedDispenseBehavior getDispenseBehavior(MovementContext context, BlockPos pos, ItemStack stack) { + return DefaultMountedDispenseBehavior.INSTANCE; } /** @@ -59,8 +61,8 @@ public class DropperMovementBehaviour implements MovementBehaviour { if (stack.getCount() == 1 && stack.getMaxStackSize() != 1) { stack = tryTopOff(stack, contraptionInventory); - if (stack == null) { - continue; + if (stack != null) { + storage.setStackInSlot(i, stack); } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/IMovedDispenseItemBehaviour.java b/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/IMovedDispenseItemBehaviour.java deleted file mode 100644 index 758ddfa024..0000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/IMovedDispenseItemBehaviour.java +++ /dev/null @@ -1,191 +0,0 @@ -package com.simibubi.create.content.contraptions.behaviour.dispenser; - -import com.simibubi.create.content.contraptions.behaviour.MovementContext; - -import net.minecraft.Util; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.sounds.SoundSource; -import net.minecraft.tags.BlockTags; -import net.minecraft.tags.FluidTags; -import net.minecraft.util.RandomSource; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.MobSpawnType; -import net.minecraft.world.entity.item.PrimedTnt; -import net.minecraft.world.entity.projectile.FireworkRocketEntity; -import net.minecraft.world.entity.projectile.Projectile; -import net.minecraft.world.entity.projectile.SmallFireball; -import net.minecraft.world.entity.projectile.ThrownPotion; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; -import net.minecraft.world.item.SpawnEggItem; -import net.minecraft.world.item.alchemy.PotionContents; -import net.minecraft.world.item.alchemy.Potions; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.block.BeehiveBlock; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.BucketPickup; -import net.minecraft.world.level.block.entity.BeehiveBlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.Vec3; - -public interface IMovedDispenseItemBehaviour { - - static void initSpawnEggs() { - final IMovedDispenseItemBehaviour spawnEggDispenseBehaviour = new MovedDefaultDispenseItemBehaviour() { - @Override - protected ItemStack dispenseStack(ItemStack itemStack, MovementContext context, BlockPos pos, Vec3 facing) { - if (!(itemStack.getItem() instanceof SpawnEggItem)) - return super.dispenseStack(itemStack, context, pos, facing); - if (context.world instanceof ServerLevel) { - EntityType entityType = ((SpawnEggItem) itemStack.getItem()).getType(itemStack); - Entity spawnedEntity = entityType.spawn((ServerLevel) context.world, itemStack, null, - pos.offset(BlockPos.containing(facing.x + .7, facing.y + .7, facing.z + .7)), MobSpawnType.DISPENSER, facing.y < .5, - false); - if (spawnedEntity != null) - spawnedEntity.setDeltaMovement(context.motion.scale(2)); - } - itemStack.shrink(1); - return itemStack; - } - }; - - for (SpawnEggItem spawneggitem : SpawnEggItem.eggs()) - DispenserMovementBehaviour.registerMovedDispenseItemBehaviour(spawneggitem, spawnEggDispenseBehaviour); - } - - static void init() { - MovedProjectileDispenserBehaviour movedPotionDispenseItemBehaviour = new MovedProjectileDispenserBehaviour() { - @Override - protected Projectile getProjectileEntity(Level world, double x, double y, double z, ItemStack itemStack, Direction facing) { - return Util.make(new ThrownPotion(world, x, y, z), (thrownPotion) -> thrownPotion.setItem(itemStack)); - } - - protected float getProjectileInaccuracy() { - return super.getProjectileInaccuracy() * 0.5F; - } - - protected float getProjectileVelocity() { - return super.getProjectileVelocity() * .5F; - } - }; - - DispenserMovementBehaviour.registerMovedDispenseItemBehaviour(Items.SPLASH_POTION, - movedPotionDispenseItemBehaviour); - DispenserMovementBehaviour.registerMovedDispenseItemBehaviour(Items.LINGERING_POTION, - movedPotionDispenseItemBehaviour); - - DispenserMovementBehaviour.registerMovedDispenseItemBehaviour(Items.TNT, - new MovedDefaultDispenseItemBehaviour() { - @Override - protected ItemStack dispenseStack(ItemStack itemStack, MovementContext context, BlockPos pos, - Vec3 facing) { - double x = pos.getX() + facing.x * .7 + .5; - double y = pos.getY() + facing.y * .7 + .5; - double z = pos.getZ() + facing.z * .7 + .5; - PrimedTnt tntentity = new PrimedTnt(context.world, x, y, z, null); - tntentity.push(context.motion.x, context.motion.y, context.motion.z); - context.world.addFreshEntity(tntentity); - context.world.playSound(null, tntentity.getX(), tntentity.getY(), tntentity.getZ(), - SoundEvents.TNT_PRIMED, SoundSource.BLOCKS, 1.0F, 1.0F); - itemStack.shrink(1); - return itemStack; - } - }); - - DispenserMovementBehaviour.registerMovedDispenseItemBehaviour(Items.FIREWORK_ROCKET, - new MovedDefaultDispenseItemBehaviour() { - @Override - protected ItemStack dispenseStack(ItemStack itemStack, MovementContext context, BlockPos pos, - Vec3 facing) { - double x = pos.getX() + facing.x * .7 + .5; - double y = pos.getY() + facing.y * .7 + .5; - double z = pos.getZ() + facing.z * .7 + .5; - FireworkRocketEntity fireworkrocketentity = - new FireworkRocketEntity(context.world, itemStack, x, y, z, true); - fireworkrocketentity.shoot(facing.x, facing.y, facing.z, 0.5F, 1.0F); - context.world.addFreshEntity(fireworkrocketentity); - itemStack.shrink(1); - return itemStack; - } - - @Override - protected void playDispenseSound(LevelAccessor world, BlockPos pos) { - world.levelEvent(1004, pos, 0); - } - }); - - DispenserMovementBehaviour.registerMovedDispenseItemBehaviour(Items.FIRE_CHARGE, - new MovedDefaultDispenseItemBehaviour() { - @Override - protected void playDispenseSound(LevelAccessor world, BlockPos pos) { - world.levelEvent(1018, pos, 0); - } - - @Override - protected ItemStack dispenseStack(ItemStack itemStack, MovementContext context, BlockPos pos, - Vec3 facing) { - RandomSource random = context.world.random; - double x = pos.getX() + facing.x * .7 + .5; - double y = pos.getY() + facing.y * .7 + .5; - double z = pos.getZ() + facing.z * .7 + .5; - context.world.addFreshEntity(Util.make( - new SmallFireball(context.world, x, y, z, - new Vec3(random.nextGaussian() * 0.05D + facing.x + context.motion.x, - random.nextGaussian() * 0.05D + facing.y + context.motion.y, - random.nextGaussian() * 0.05D + facing.z + context.motion.z).normalize()), - (smallFireball) -> smallFireball.setItem(itemStack))); - itemStack.shrink(1); - return itemStack; - } - }); - - DispenserMovementBehaviour.registerMovedDispenseItemBehaviour(Items.GLASS_BOTTLE, - new MovedOptionalDispenseBehaviour() { - @Override - protected ItemStack dispenseStack(ItemStack itemStack, MovementContext context, BlockPos pos, - Vec3 facing) { - this.successful = false; - BlockPos interactAt = pos.relative(getClosestFacingDirection(facing)); - BlockState state = context.world.getBlockState(interactAt); - Block block = state.getBlock(); - - if (state.is(BlockTags.BEEHIVES) && state.getValue(BeehiveBlock.HONEY_LEVEL) >= 5) { - ((BeehiveBlock) block).releaseBeesAndResetHoneyLevel(context.world, state, interactAt, null, - BeehiveBlockEntity.BeeReleaseStatus.BEE_RELEASED); - this.successful = true; - return placeItemInInventory(itemStack, new ItemStack(Items.HONEY_BOTTLE), context, pos, facing); - } else if (context.world.getFluidState(interactAt) - .is(FluidTags.WATER)) { - this.successful = true; - return placeItemInInventory(itemStack, - PotionContents.createItemStack(Items.POTION, Potions.WATER), context, pos, facing); - } else { - return super.dispenseStack(itemStack, context, pos, facing); - } - } - }); - - DispenserMovementBehaviour.registerMovedDispenseItemBehaviour(Items.BUCKET, - new MovedDefaultDispenseItemBehaviour() { - @Override - protected ItemStack dispenseStack(ItemStack itemStack, MovementContext context, BlockPos pos, - Vec3 facing) { - BlockPos interactAt = pos.relative(getClosestFacingDirection(facing)); - BlockState state = context.world.getBlockState(interactAt); - Block block = state.getBlock(); - if (block instanceof BucketPickup) { - ItemStack bucket = ((BucketPickup) block).pickupBlock(null, context.world, interactAt, state); - return placeItemInInventory(itemStack, bucket, context, pos, facing); - } - return super.dispenseStack(itemStack, context, pos, facing); - } - }); - } - - ItemStack dispense(ItemStack itemStack, MovementContext context, BlockPos pos); -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/MovedDefaultDispenseItemBehaviour.java b/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/MovedDefaultDispenseItemBehaviour.java deleted file mode 100644 index 9fd15f4440..0000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/MovedDefaultDispenseItemBehaviour.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.simibubi.create.content.contraptions.behaviour.dispenser; - -import com.simibubi.create.content.contraptions.behaviour.MovementContext; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.world.Container; -import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.block.DispenserBlock; -import net.minecraft.world.level.block.entity.HopperBlockEntity; -import net.minecraft.world.phys.Vec3; - -import net.neoforged.neoforge.items.ItemHandlerHelper; -import net.neoforged.neoforge.items.wrapper.CombinedInvWrapper; - -public class MovedDefaultDispenseItemBehaviour implements IMovedDispenseItemBehaviour { - public static final MovedDefaultDispenseItemBehaviour INSTANCE = new MovedDefaultDispenseItemBehaviour(); - - public static void doDispense(Level p_82486_0_, ItemStack p_82486_1_, int p_82486_2_, Vec3 facing, - BlockPos p_82486_4_, MovementContext context) { - double d0 = p_82486_4_.getX() + facing.x + .5; - double d1 = p_82486_4_.getY() + facing.y + .5; - double d2 = p_82486_4_.getZ() + facing.z + .5; - if (Direction.getNearest(facing.x, facing.y, facing.z) - .getAxis() == Direction.Axis.Y) { - d1 = d1 - 0.125D; - } else { - d1 = d1 - 0.15625D; - } - - ItemEntity itementity = new ItemEntity(p_82486_0_, d0, d1, d2, p_82486_1_); - double d3 = p_82486_0_.random.nextDouble() * 0.1D + 0.2D; - itementity.setDeltaMovement( - p_82486_0_.random.nextGaussian() * (double) 0.0075F * (double) p_82486_2_ + facing.x() * d3 - + context.motion.x, - p_82486_0_.random.nextGaussian() * (double) 0.0075F * (double) p_82486_2_ + facing.y() * d3 - + context.motion.y, - p_82486_0_.random.nextGaussian() * (double) 0.0075F * (double) p_82486_2_ + facing.z() * d3 - + context.motion.z); - p_82486_0_.addFreshEntity(itementity); - } - - @Override - public ItemStack dispense(ItemStack itemStack, MovementContext context, BlockPos pos) { - Vec3 facingVec = Vec3.atLowerCornerOf(context.state.getValue(DispenserBlock.FACING) - .getNormal()); - facingVec = context.rotation.apply(facingVec); - facingVec.normalize(); - - Direction closestToFacing = getClosestFacingDirection(facingVec); - Container inventory = HopperBlockEntity.getContainerAt(context.world, pos.relative(closestToFacing)); - if (inventory == null) { - this.playDispenseSound(context.world, pos); - this.spawnDispenseParticles(context.world, pos, closestToFacing); - return this.dispenseStack(itemStack, context, pos, facingVec); - } else { - if (HopperBlockEntity.addItem(null, inventory, itemStack.copy() - .split(1), closestToFacing.getOpposite()) - .isEmpty()) - itemStack.shrink(1); - return itemStack; - } - } - - /** - * Dispense the specified stack, play the dispense sound and spawn particles. - */ - protected ItemStack dispenseStack(ItemStack itemStack, MovementContext context, BlockPos pos, Vec3 facing) { - ItemStack itemstack = itemStack.split(1); - doDispense(context.world, itemstack, 6, facing, pos, context); - return itemStack; - } - - /** - * Play the dispense sound from the specified block. - */ - protected void playDispenseSound(LevelAccessor world, BlockPos pos) { - world.levelEvent(1000, pos, 0); - } - - /** - * Order clients to display dispense particles from the specified block and - * facing. - */ - protected void spawnDispenseParticles(LevelAccessor world, BlockPos pos, Vec3 facing) { - spawnDispenseParticles(world, pos, getClosestFacingDirection(facing)); - } - - protected void spawnDispenseParticles(LevelAccessor world, BlockPos pos, Direction direction) { - world.levelEvent(2000, pos, direction.get3DDataValue()); - } - - protected Direction getClosestFacingDirection(Vec3 exactFacing) { - return Direction.getNearest(exactFacing.x, exactFacing.y, exactFacing.z); - } - - protected ItemStack placeItemInInventory(ItemStack consumedFrom, ItemStack output, MovementContext context, - BlockPos pos, Vec3 facing) { - consumedFrom.shrink(1); - - ItemStack toInsert = output.copy(); - // try inserting into own inventory first - ItemStack remainder = ItemHandlerHelper.insertItem(context.getItemStorage(), toInsert, false); - if (!remainder.isEmpty()) { - // next, try the whole contraption inventory - // note that this contains the dispenser inventory. That's fine. - CombinedInvWrapper contraption = context.contraption.getStorage().getAllItems(); - ItemStack newRemainder = ItemHandlerHelper.insertItem(contraption, remainder, false); - if (!newRemainder.isEmpty()) { - // if there's *still* something left, dispense into world - INSTANCE.dispenseStack(remainder, context, pos, facing); - } - } - - return consumedFrom; - } -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/MovedOptionalDispenseBehaviour.java b/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/MovedOptionalDispenseBehaviour.java deleted file mode 100644 index 3e9e77ed13..0000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/MovedOptionalDispenseBehaviour.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.simibubi.create.content.contraptions.behaviour.dispenser; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.LevelAccessor; - -public class MovedOptionalDispenseBehaviour extends MovedDefaultDispenseItemBehaviour { - protected boolean successful = true; - - @Override - protected void playDispenseSound(LevelAccessor world, BlockPos pos) { - world.levelEvent(this.successful ? 1000 : 1001, pos, 0); - } -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/MovedProjectileDispenserBehaviour.java b/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/MovedProjectileDispenserBehaviour.java deleted file mode 100644 index 247aafa9f4..0000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/MovedProjectileDispenserBehaviour.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.simibubi.create.content.contraptions.behaviour.dispenser; - -import javax.annotation.Nullable; - -import com.simibubi.create.content.contraptions.behaviour.MovementContext; -import com.simibubi.create.foundation.mixin.accessor.ProjectileDispenseBehaviorAccessor; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.dispenser.ProjectileDispenseBehavior; -import net.minecraft.world.entity.projectile.Projectile; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.phys.Vec3; - -public abstract class MovedProjectileDispenserBehaviour extends MovedDefaultDispenseItemBehaviour { - - @Override - protected ItemStack dispenseStack(ItemStack itemStack, MovementContext context, BlockPos pos, Vec3 facing) { - double x = pos.getX() + facing.x * .7 + .5; - double y = pos.getY() + facing.y * .7 + .5; - double z = pos.getZ() + facing.z * .7 + .5; - Projectile projectile = this.getProjectileEntity(context.world, x, y, z, itemStack.copy(), getClosestFacingDirection(facing)); - if (projectile == null) - return itemStack; - Vec3 effectiveMovementVec = facing.scale(getProjectileVelocity()).add(context.motion); - projectile.shoot(effectiveMovementVec.x, effectiveMovementVec.y, effectiveMovementVec.z, (float) effectiveMovementVec.length(), this.getProjectileInaccuracy()); - context.world.addFreshEntity(projectile); - itemStack.shrink(1); - return itemStack; - } - - @Override - protected void playDispenseSound(LevelAccessor world, BlockPos pos) { - world.levelEvent(1002, pos, 0); - } - - @Nullable - protected abstract Projectile getProjectileEntity(Level world, double x, double y, double z, ItemStack itemStack, Direction facing); - - protected float getProjectileInaccuracy() { - return 6.0F; - } - - protected float getProjectileVelocity() { - return 1.1F; - } - - public static MovedProjectileDispenserBehaviour of(ProjectileDispenseBehavior vanillaBehaviour) { - ProjectileDispenseBehaviorAccessor accessor = (ProjectileDispenseBehaviorAccessor) vanillaBehaviour; - return new MovedProjectileDispenserBehaviour() { - @Override - protected Projectile getProjectileEntity(Level world, double x, double y, double z, ItemStack itemStack, Direction facing) { - return accessor.create$getProjectileItem().asProjectile(world, new SimplePos(x, y, z), itemStack, facing); - } - - @Override - protected float getProjectileInaccuracy() { - return accessor.create$getDispenseConfig().uncertainty(); - } - - @Override - protected float getProjectileVelocity() { - return accessor.create$getDispenseConfig().power(); - } - }; - } -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/SimplePos.java b/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/SimplePos.java deleted file mode 100644 index 9b8c22acca..0000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/SimplePos.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.simibubi.create.content.contraptions.behaviour.dispenser; - -import net.minecraft.core.Position; - -public class SimplePos implements Position { - private final double x; - private final double y; - private final double z; - - public SimplePos(double x, double y, double z) { - this.x = x; - this.y = y; - this.z = z; - } - - @Override - public double x() { - return x; - } - - @Override - public double y() { - return y; - } - - @Override - public double z() { - return z; - } -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/storage/DispenserMountedStorage.java b/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/storage/DispenserMountedStorage.java index 79e1883fb1..4aa137c09d 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/storage/DispenserMountedStorage.java +++ b/src/main/java/com/simibubi/create/content/contraptions/behaviour/dispenser/storage/DispenserMountedStorage.java @@ -31,11 +31,6 @@ public class DispenserMountedStorage extends SimpleMountedStorage { this(AllMountedStorageTypes.DISPENSER.get(), handler); } - @Override - public boolean isInternal() { - return true; - } - @Override @Nullable protected MenuProvider createMenuProvider(Component name, IItemHandlerModifiable handler, diff --git a/src/main/java/com/simibubi/create/content/equipment/armor/TrimmableArmorModelGenerator.java b/src/main/java/com/simibubi/create/content/equipment/armor/TrimmableArmorModelGenerator.java index 08009da960..2fd25c93c7 100644 --- a/src/main/java/com/simibubi/create/content/equipment/armor/TrimmableArmorModelGenerator.java +++ b/src/main/java/com/simibubi/create/content/equipment/armor/TrimmableArmorModelGenerator.java @@ -5,6 +5,7 @@ import java.lang.invoke.VarHandle; import java.util.Map; import com.simibubi.create.Create; +import com.simibubi.create.foundation.mixin.accessor.ItemModelGeneratorsAccessor; import com.tterrag.registrate.providers.DataGenContext; import com.tterrag.registrate.providers.RegistrateItemModelProvider; @@ -33,25 +34,25 @@ public class TrimmableArmorModelGenerator { public static void generate(DataGenContext c, RegistrateItemModelProvider p) { T item = c.get(); ItemModelBuilder builder = p.generated(c); - for (ItemModelGenerators.TrimModelData data : ItemModelGenerators.GENERATED_TRIM_MODELS) { + for (ItemModelGenerators.TrimModelData data : ItemModelGeneratorsAccessor.create$getGENERATED_TRIM_MODELS()) { ResourceLocation modelLoc = ModelLocationUtils.getModelLocation(item); ResourceLocation textureLoc = TextureMapping.getItemTexture(item); String trimId = data.name(item.getMaterial()); ResourceLocation trimModelLoc = modelLoc.withSuffix("_" + trimId + "_trim"); ResourceLocation trimLoc = - ResourceLocation.withDefaultNamespace("trims/items/" + item.getType().getName() + "_trim_" + trimId); + ResourceLocation.withDefaultNamespace("trims/items/" + item.getType().getName() + "_trim_" + trimId); String parent = "item/generated"; if (item.getMaterial() == AllArmorMaterials.CARDBOARD) { trimLoc = Create.asResource("trims/items/card_" + item.getType().getName() + "_trim_" + trimId); } ItemModelBuilder itemModel = p.withExistingParent(trimModelLoc.getPath(), parent) - .texture("layer0", textureLoc); + .texture("layer0", textureLoc); Map textures = (Map) TEXTURES_HANDLE.get(itemModel); textures.put("layer1", trimLoc.toString()); builder.override() - .predicate(ItemModelGenerators.TRIM_TYPE_PREDICATE_ID, data.itemModelIndex()) - .model(itemModel) - .end(); + .predicate(ItemModelGenerators.TRIM_TYPE_PREDICATE_ID, data.itemModelIndex()) + .model(itemModel) + .end(); } } } diff --git a/src/main/java/com/simibubi/create/content/logistics/vault/ItemVaultMountedStorage.java b/src/main/java/com/simibubi/create/content/logistics/vault/ItemVaultMountedStorage.java index 9d53c34344..bef8ad5862 100644 --- a/src/main/java/com/simibubi/create/content/logistics/vault/ItemVaultMountedStorage.java +++ b/src/main/java/com/simibubi/create/content/logistics/vault/ItemVaultMountedStorage.java @@ -46,11 +46,6 @@ public class ItemVaultMountedStorage extends WrapperMountedItemStorage { public > SimpleBuilder, T, CreateRegistrate> mountedItemStorage(String name, Supplier supplier) { return this.entry(name, callback -> new SimpleBuilder<>( - this, this, name, callback, CreateRegistries.MOUNTED_ITEM_STORAGE_TYPE, supplier.get() + this, this, name, callback, CreateRegistries.MOUNTED_ITEM_STORAGE_TYPE, supplier ).byBlock(MountedItemStorageType.REGISTRY)); } public > SimpleBuilder, T, CreateRegistrate> mountedFluidStorage(String name, Supplier supplier) { return this.entry(name, callback -> new SimpleBuilder<>( - this, this, name, callback, CreateRegistries.MOUNTED_FLUID_STORAGE_TYPE, supplier.get() + this, this, name, callback, CreateRegistries.MOUNTED_FLUID_STORAGE_TYPE, supplier ).byBlock(MountedFluidStorageType.REGISTRY)); } public SimpleBuilder displaySource(String name, Supplier supplier) { return this.entry(name, callback -> new SimpleBuilder<>( - this, this, name, callback, CreateRegistries.DISPLAY_SOURCE, supplier.get() + this, this, name, callback, CreateRegistries.DISPLAY_SOURCE, supplier ).byBlock(DisplaySource.BY_BLOCK).byBlockEntity(DisplaySource.BY_BLOCK_ENTITY)); } public SimpleBuilder displayTarget(String name, Supplier supplier) { return this.entry(name, callback -> new SimpleBuilder<>( - this, this, name, callback, CreateRegistries.DISPLAY_TARGET, supplier.get() + this, this, name, callback, CreateRegistries.DISPLAY_TARGET, supplier ).byBlock(DisplayTarget.BY_BLOCK).byBlockEntity(DisplayTarget.BY_BLOCK_ENTITY)); } diff --git a/src/main/java/com/simibubi/create/foundation/mixin/accessor/BuiltInRegistriesAccessor.java b/src/main/java/com/simibubi/create/foundation/mixin/accessor/BuiltInRegistriesAccessor.java new file mode 100644 index 0000000000..9646101b52 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/mixin/accessor/BuiltInRegistriesAccessor.java @@ -0,0 +1,15 @@ +package com.simibubi.create.foundation.mixin.accessor; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.core.WritableRegistry; +import net.minecraft.core.registries.BuiltInRegistries; + +@Mixin(BuiltInRegistries.class) +public interface BuiltInRegistriesAccessor { + @Accessor("WRITABLE_REGISTRY") + static WritableRegistry> create$getWRITABLE_REGISTRY() { + throw new AssertionError(); + } +} diff --git a/src/main/java/com/simibubi/create/foundation/mixin/accessor/ItemModelGeneratorsAccessor.java b/src/main/java/com/simibubi/create/foundation/mixin/accessor/ItemModelGeneratorsAccessor.java new file mode 100644 index 0000000000..fb82aa9ec7 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/mixin/accessor/ItemModelGeneratorsAccessor.java @@ -0,0 +1,17 @@ +package com.simibubi.create.foundation.mixin.accessor; + +import java.util.List; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.data.models.ItemModelGenerators; +import net.minecraft.data.models.ItemModelGenerators.TrimModelData; + +@Mixin(ItemModelGenerators.class) +public interface ItemModelGeneratorsAccessor { + @Accessor("GENERATED_TRIM_MODELS") + static List create$getGENERATED_TRIM_MODELS() { + throw new AssertionError(); + } +} diff --git a/src/main/java/com/simibubi/create/impl/contraption/dispenser/DispenserBehaviorConverter.java b/src/main/java/com/simibubi/create/impl/contraption/dispenser/DispenserBehaviorConverter.java new file mode 100644 index 0000000000..7e00d8b10a --- /dev/null +++ b/src/main/java/com/simibubi/create/impl/contraption/dispenser/DispenserBehaviorConverter.java @@ -0,0 +1,120 @@ +package com.simibubi.create.impl.contraption.dispenser; + +import org.jetbrains.annotations.Nullable; + +import com.simibubi.create.AllTags.AllItemTags; +import com.simibubi.create.Create; +import com.simibubi.create.api.contraption.dispenser.DefaultMountedDispenseBehavior; +import com.simibubi.create.api.contraption.dispenser.MountedDispenseBehavior; +import com.simibubi.create.api.contraption.dispenser.MountedProjectileDispenseBehavior; +import com.simibubi.create.api.registry.SimpleRegistry; +import com.simibubi.create.content.contraptions.behaviour.MovementContext; +import com.simibubi.create.foundation.mixin.accessor.DispenserBlockAccessor; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.dispenser.BlockSource; +import net.minecraft.core.dispenser.DefaultDispenseItemBehavior; +import net.minecraft.core.dispenser.DispenseItemBehavior; +import net.minecraft.core.dispenser.ProjectileDispenseBehavior; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.phys.Vec3; + +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.TagsUpdatedEvent; +import net.neoforged.neoforge.server.ServerLifecycleHooks; + +public enum DispenserBehaviorConverter implements SimpleRegistry.Provider { + INSTANCE; + + @Override + @Nullable + public MountedDispenseBehavior get(Item item) { + DispenseItemBehavior vanilla = getDispenseMethod(new ItemStack(item)); + if (vanilla == null) + return null; + + // when the default, return null. The default will be used anyway, avoid caching it for no reason. + if (vanilla.getClass() == DefaultDispenseItemBehavior.class) + return null; + + // if the item is explicitly blocked from having its behavior wrapped, ignore it + if (AllItemTags.DISPENSE_BEHAVIOR_WRAP_BLACKLIST.matches(item)) + return null; + + if (vanilla instanceof ProjectileDispenseBehavior projectile) { + return MountedProjectileDispenseBehavior.of(projectile); + } + + // other behaviors are more dangerous due to BlockSource providing a BlockEntity, which contraptions can't do. + // wrap in a fallback that will watch for errors. + return new FallbackBehavior(item, vanilla); + } + + @Override + public void onRegister(Runnable invalidate) { + // invalidate if the blacklist tag might've changed + NeoForge.EVENT_BUS.addListener((TagsUpdatedEvent event) -> { + if (event.shouldUpdateStaticData()) { + invalidate.run(); + } + }); + } + + @Nullable + private static DispenseItemBehavior getDispenseMethod(ItemStack stack) { + MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); + if (server == null) + return null; + + return ((DispenserBlockAccessor) Blocks.DISPENSER).create$callGetDispenseMethod(server.getLevel(Level.OVERWORLD), stack); + } + + private static final class FallbackBehavior extends DefaultMountedDispenseBehavior { + private final Item item; + private final DispenseItemBehavior wrapped; + private boolean hasErrored; + + private FallbackBehavior(Item item, DispenseItemBehavior wrapped) { + this.item = item; + this.wrapped = wrapped; + } + + @Override + protected ItemStack execute(ItemStack stack, MovementContext context, BlockPos pos, Vec3 facing) { + if (this.hasErrored) + return stack; + + MinecraftServer server = context.world.getServer(); + ServerLevel serverLevel = server != null ? server.getLevel(context.world.dimension()) : null; + + Direction nearestFacing = MountedDispenseBehavior.getClosestFacingDirection(facing); + BlockState state = context.state; + if (state.hasProperty(BlockStateProperties.FACING)) + state = state.setValue(BlockStateProperties.FACING, nearestFacing); + + BlockSource source = new BlockSource(serverLevel, pos, state, null); + + try { + // use a copy in case of implosion after modifying it + return this.wrapped.dispense(source, stack.copy()); + } catch (NullPointerException e) { + // likely due to the lack of a BlockEntity + ResourceLocation itemId = BuiltInRegistries.ITEM.getKey(this.item); + String message = "Error dispensing item '" + itemId + "' from contraption, not doing that anymore"; + Create.LOGGER.error(message, e); + this.hasErrored = true; + return stack; + } + } + } +} diff --git a/src/main/java/com/simibubi/create/infrastructure/data/CreateDatagen.java b/src/main/java/com/simibubi/create/infrastructure/data/CreateDatagen.java index 5407b20379..efd901584b 100644 --- a/src/main/java/com/simibubi/create/infrastructure/data/CreateDatagen.java +++ b/src/main/java/com/simibubi/create/infrastructure/data/CreateDatagen.java @@ -47,6 +47,7 @@ public class CreateDatagen { generator.addProvider(event.includeServer(), new CreateRecipeSerializerTagsProvider(output, lookupProvider, existingFileHelper)); generator.addProvider(event.includeServer(), new CreateContraptionTypeTagsProvider(output, lookupProvider, existingFileHelper)); + generator.addProvider(event.includeServer(), new CreateMountedItemStorageTypeTagsProvider(output, lookupProvider, existingFileHelper)); generator.addProvider(event.includeServer(), new DamageTypeTagGen(output, lookupProvider, existingFileHelper)); generator.addProvider(event.includeServer(), new AllAdvancements(output, lookupProvider)); generator.addProvider(event.includeServer(), new StandardRecipeGen(output, lookupProvider)); diff --git a/src/main/java/com/simibubi/create/infrastructure/data/CreateMountedItemStorageTypeTagsProvider.java b/src/main/java/com/simibubi/create/infrastructure/data/CreateMountedItemStorageTypeTagsProvider.java new file mode 100644 index 0000000000..7b7ff72b8c --- /dev/null +++ b/src/main/java/com/simibubi/create/infrastructure/data/CreateMountedItemStorageTypeTagsProvider.java @@ -0,0 +1,47 @@ +package com.simibubi.create.infrastructure.data; + +import java.util.concurrent.CompletableFuture; + +import org.jetbrains.annotations.Nullable; + +import com.simibubi.create.AllMountedStorageTypes; +import com.simibubi.create.AllTags.AllMountedItemStorageTypeTags; +import com.simibubi.create.Create; +import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType; +import com.simibubi.create.api.registry.CreateRegistries; + +import net.minecraft.core.HolderLookup.Provider; +import net.minecraft.data.PackOutput; +import net.minecraft.data.tags.TagsProvider; +import net.minecraft.tags.TagEntry; + +import net.neoforged.neoforge.common.data.ExistingFileHelper; + +public class CreateMountedItemStorageTypeTagsProvider extends TagsProvider> { + public CreateMountedItemStorageTypeTagsProvider(PackOutput output, CompletableFuture lookupProvider, @Nullable ExistingFileHelper existingFileHelper) { + super(output, CreateRegistries.MOUNTED_ITEM_STORAGE_TYPE, lookupProvider, Create.ID, existingFileHelper); + } + + @Override + protected void addTags(Provider pProvider) { + tag(AllMountedItemStorageTypeTags.INTERNAL.tag).add( + TagEntry.element(AllMountedStorageTypes.DISPENSER.getId()) + ); + tag(AllMountedItemStorageTypeTags.FUEL_BLACKLIST.tag).add( + TagEntry.element(AllMountedStorageTypes.VAULT.getId()) + ); + + // VALIDATE + + for (AllMountedItemStorageTypeTags tag : AllMountedItemStorageTypeTags.values()) { + if (tag.alwaysDatagen) { + getOrCreateRawBuilder(tag.tag); + } + } + } + + @Override + public String getName() { + return "Create's Mounted Item Storage Type Tags"; + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index e7dfec0890..d8e93bd05e 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -53,6 +53,3 @@ protected-f net.minecraft.world.entity.decoration.HangingEntity recalculateBound public net.minecraft.world.item.alchemy.PotionBrewing$Mix public net.minecraft.data.models.ItemModelGenerators$TrimModelData - -# Using a static accessor here doesn't work, no idea why but will have to debug it later -public net.minecraft.data.models.ItemModelGenerators f_265952_ # GENERATED_TRIM_MODELS diff --git a/src/main/resources/META-INF/coremods.json b/src/main/resources/META-INF/coremods.json deleted file mode 100644 index 0b7ada160d..0000000000 --- a/src/main/resources/META-INF/coremods.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "better_registry": "coremods/better_registry.js" -} diff --git a/src/main/resources/coremods/better_registry.js b/src/main/resources/coremods/better_registry.js deleted file mode 100644 index 778712c3ee..0000000000 --- a/src/main/resources/coremods/better_registry.js +++ /dev/null @@ -1,28 +0,0 @@ -var ASMAPI = Java.type('net.minecraftforge.coremod.api.ASMAPI') - -// this is terrible, but Forge has forced our hands. -// for some reason, Forge loads half the game with Bootstrap.bootStrap *before* loading mods during datagen. -// this is not the case in other entrypoints. -// this makes mixins to some important places, like BuiltInRegistries, impossible since mixin isn't -// initialized when the class is loaded. -function initializeCoreMod() { - return { - "registrycoremod": { - 'target': { - 'type': 'METHOD', - 'class': 'net.minecraft.core.registries.BuiltInRegistries', - 'methodName': '', - 'methodDesc': '()V' - }, - 'transformer': function (method) { - var CreateBuiltInRegistries = "com/simibubi/create/api/registry/CreateBuiltInRegistries"; - - var insn = ASMAPI.buildMethodCall(CreateBuiltInRegistries, "init", "()V", ASMAPI.MethodType.STATIC) - - method.instructions.insertBefore(method.instructions.getLast(), insn) - - return method; - } - } - } -} diff --git a/src/main/resources/create.mixins.json b/src/main/resources/create.mixins.json index 6dd35b0ec0..a0bbb7bc0f 100644 --- a/src/main/resources/create.mixins.json +++ b/src/main/resources/create.mixins.json @@ -25,11 +25,13 @@ "accessor.AbstractRegistrateAccessor", "accessor.BlockBehaviourAccessor", "accessor.BlockLootSubProviderAccessor", + "accessor.BuiltInRegistriesAccessor", "accessor.DispenserBlockAccessor", "accessor.FallingBlockEntityAccessor", "accessor.FlowingFluidAccessor", "accessor.FluidInteractionRegistryAccessor", "accessor.GameTestHelperAccessor", + "accessor.ItemModelGeneratorsAccessor", "accessor.ItemStackHandlerAccessor", "accessor.LivingEntityAccessor", "accessor.MappedRegistryAccessor",