Merge branch 'mc1.20.1/feature-dev' into mc1.21.1/dev

This commit is contained in:
IThundxr 2025-02-21 19:14:50 -05:00
commit 994f0afd3e
Failed to generate hash of commit
40 changed files with 794 additions and 698 deletions

View file

@ -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
}
}

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,5 @@
{
"values": [
"create:vault"
]
}

View file

@ -0,0 +1,5 @@
{
"values": [
"create:dispenser"
]
}

View file

@ -0,0 +1,3 @@
{
"values": []
}

View file

@ -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);
}
}

View file

@ -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());
}

View file

@ -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<MountedItemStorageType<?>> 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();
}
}

View file

@ -177,6 +177,7 @@ public class Create {
AllInteractionBehaviours.registerDefaults();
AllContraptionMovementSettings.registerDefaults();
AllOpenPipeEffectHandlers.registerDefaults();
AllMountedDispenseItemBehaviors.registerDefaults();
// --
});
}

View file

@ -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);
}
}

View file

@ -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<Item, MountedDispenseBehavior> REGISTRY = Util.make(() -> {
SimpleRegistry<Item, MountedDispenseBehavior> 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.
* <p>
* 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);
}
}
}
}

View file

@ -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();
}
};
}
}

View file

@ -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);
}
}

View file

@ -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.

View file

@ -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<T extends MountedItemStorage> {
});
public final MapCodec<? extends T> codec;
public final Holder<MountedItemStorageType<?>> holder;
protected MountedItemStorageType(MapCodec<? extends T> codec) {
this.codec = codec;
this.holder = CreateBuiltInRegistries.MOUNTED_ITEM_STORAGE_TYPE.createIntrusiveHolder(this);
}
public final boolean is(TagKey<MountedItemStorageType<?>> tag) {
return this.holder.is(tag);
}
@Nullable

View file

@ -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<WritableRegistry<?>> ROOT_REGISTRY = getRootRegistry();
public static final Set<Registry<?>> REGISTRIES = new HashSet<>();
public static final Registry<ArmInteractionPointType> ARM_INTERACTION_POINT_TYPE = simple(CreateRegistries.ARM_INTERACTION_POINT_TYPE);
@ -46,47 +38,24 @@ public class CreateBuiltInRegistries {
public static final Registry<ItemAttributeType> ITEM_ATTRIBUTE_TYPE = simple(CreateRegistries.ITEM_ATTRIBUTE_TYPE);
public static final Registry<DisplaySource> DISPLAY_SOURCE = simple(CreateRegistries.DISPLAY_SOURCE);
public static final Registry<DisplayTarget> DISPLAY_TARGET = simple(CreateRegistries.DISPLAY_TARGET);
public static final Registry<MountedItemStorageType<?>> MOUNTED_ITEM_STORAGE_TYPE = simple(CreateRegistries.MOUNTED_ITEM_STORAGE_TYPE);
public static final Registry<MountedItemStorageType<?>> MOUNTED_ITEM_STORAGE_TYPE = withIntrusiveHolders(CreateRegistries.MOUNTED_ITEM_STORAGE_TYPE);
public static final Registry<MountedFluidStorageType<?>> MOUNTED_FLUID_STORAGE_TYPE = simple(CreateRegistries.MOUNTED_FLUID_STORAGE_TYPE);
public static final Registry<ContraptionType> CONTRAPTION_TYPE = withIntrusiveHolders(CreateRegistries.CONTRAPTION_TYPE);
public static final Registry<PackagePortTargetType> PACKAGE_PORT_TARGET_TYPE = simple(CreateRegistries.PACKAGE_PORT_TARGET_TYPE);
private static <T> Registry<T> simple(ResourceKey<Registry<T>> key) {
return register(key, new MappedRegistry<>(key, Lifecycle.stable(), false));
return register(new MappedRegistry<>(key, Lifecycle.stable(), false));
}
private static <T> Registry<T> withIntrusiveHolders(ResourceKey<Registry<T>> key) {
return register(key, new MappedRegistry<>(key, Lifecycle.stable(), true));
return register(new MappedRegistry<>(key, Lifecycle.stable(), true));
}
@SuppressWarnings("unchecked")
private static <T> Registry<T> register(ResourceKey<Registry<T>> key, WritableRegistry<T> registry) {
ROOT_REGISTRY.register(
(ResourceKey<WritableRegistry<?>>) (Object) key, registry, new RegistrationInfo(Optional.empty(), Lifecycle.stable())
);
private static <T> Registry<T> register(WritableRegistry<T> registry) {
REGISTRIES.add(registry);
return registry;
}
@SuppressWarnings("unchecked")
private static WritableRegistry<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<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)

View file

@ -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<R, T extends R, P> extends AbstractBuilder<R, T, P, SimpleBuilder<R, T, P>> {
private final T value;
private final Supplier<T> value;
private SimpleRegistryAccess<Block, R> byBlock;
private SimpleRegistryAccess<BlockEntityType<?>, R> byBlockEntity;
private SimpleRegistryAccess<EntityType<?>, R> byEntity;
private SimpleRegistryAccess<Fluid, R> byFluid;
public SimpleBuilder(AbstractRegistrate<?> owner, P parent, String name, BuilderCallback callback, ResourceKey<Registry<R>> registryKey, T value) {
public SimpleBuilder(AbstractRegistrate<?> owner, P parent, String name, BuilderCallback callback, ResourceKey<Registry<R>> registryKey, Supplier<T> 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<R, T extends R, P> extends AbstractBuilder<R, T, P, S
public SimpleBuilder<R, T, P> 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<R, T, P> associateBlockTag(TagKey<Block> 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<R, T, P> 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<R, T, P> associateBeTag(TagKey<BlockEntityType<?>> 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<R, T, P> 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<R, T, P> associateEntityTag(TagKey<EntityType<?>> 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<R, T, P> 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<R, T, P> associateFluidTag(TagKey<Fluid> tag) {
assertPresent(this.byFluid, "Fluid");
this.byFluid.tagAdder.accept(tag, this.value);
this.onRegister(value -> this.byFluid.tagAdder.accept(tag, value));
return this;
}

View file

@ -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<BlockPos, MountedItemStorage> fuelMap = subMap(
this.allItemStorages, storage -> !storage.isInternal() && storage.providesFuel()
);
ImmutableMap<BlockPos, MountedItemStorage> fuelMap = subMap(this.allItemStorages, this::canUseForFuel);
this.fuelItems = fuelMap.isEmpty() ? null : new MountedItemStorageWrapper(fuelMap);
ImmutableMap<BlockPos, MountedFluidStorage> 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<BlockPos, MountedItemStorage> getAllItemStorages() {
this.assertInitialized();

View file

@ -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;
}
}

View file

@ -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<Item, IMovedDispenseItemBehaviour> movedDispenseItemBehaviors = new HashMap<>();
private static final Set<Item> 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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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();
}
};
}
}

View file

@ -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;
}
}

View file

@ -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,

View file

@ -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 <T extends ArmorItem> void generate(DataGenContext<Item, T> 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<String, String> textures = (Map<String, String>) 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();
}
}
}

View file

@ -46,11 +46,6 @@ public class ItemVaultMountedStorage extends WrapperMountedItemStorage<ItemStack
return false;
}
@Override
public boolean providesFuel() {
return false;
}
public static ItemVaultMountedStorage fromVault(ItemVaultBlockEntity vault) {
// Vault inventories have a world-affecting onContentsChanged, copy to a safe one
return new ItemVaultMountedStorage(copyToItemStackHandler(vault.getInventoryOfBlock()));

View file

@ -156,25 +156,25 @@ public class CreateRegistrate extends AbstractRegistrate<CreateRegistrate> {
public <T extends MountedItemStorageType<?>> SimpleBuilder<MountedItemStorageType<?>, T, CreateRegistrate> mountedItemStorage(String name, Supplier<T> 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 <T extends MountedFluidStorageType<?>> SimpleBuilder<MountedFluidStorageType<?>, T, CreateRegistrate> mountedFluidStorage(String name, Supplier<T> 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 <T extends DisplaySource> SimpleBuilder<DisplaySource, T, CreateRegistrate> displaySource(String name, Supplier<T> 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 <T extends DisplayTarget> SimpleBuilder<DisplayTarget, T, CreateRegistrate> displayTarget(String name, Supplier<T> 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));
}

View file

@ -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<WritableRegistry<?>> create$getWRITABLE_REGISTRY() {
throw new AssertionError();
}
}

View file

@ -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<TrimModelData> create$getGENERATED_TRIM_MODELS() {
throw new AssertionError();
}
}

View file

@ -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<Item, MountedDispenseBehavior> {
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;
}
}
}
}

View file

@ -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));

View file

@ -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<MountedItemStorageType<?>> {
public CreateMountedItemStorageTypeTagsProvider(PackOutput output, CompletableFuture<Provider> 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";
}
}

View file

@ -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

View file

@ -1,3 +0,0 @@
{
"better_registry": "coremods/better_registry.js"
}

View file

@ -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': '<clinit>',
'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;
}
}
}
}

View file

@ -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",