diff --git a/src/main/java/dev/amble/lib/boat/ABoatItem.java b/src/main/java/dev/amble/lib/boat/ABoatItem.java new file mode 100644 index 0000000..7b7a777 --- /dev/null +++ b/src/main/java/dev/amble/lib/boat/ABoatItem.java @@ -0,0 +1,83 @@ +package dev.amble.lib.boat; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.vehicle.BoatEntity; +import net.minecraft.entity.vehicle.ChestBoatEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.predicate.entity.EntityPredicates; +import net.minecraft.stat.Stats; +import net.minecraft.util.Hand; +import net.minecraft.util.TypedActionResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.RaycastContext; +import net.minecraft.world.World; +import net.minecraft.world.event.GameEvent; + +import java.util.List; +import java.util.function.Predicate; + +public class ABoatItem extends Item { + + private static final Predicate RIDERS = EntityPredicates.EXCEPT_SPECTATOR.and(Entity::canHit); + private final ABoatType type; + private final boolean chest; + + public ABoatItem(boolean chest, ABoatType type, Item.Settings settings) { + super(settings); + this.chest = chest; + this.type = type; + } + + public TypedActionResult use(World world, PlayerEntity user, Hand hand) { + ItemStack itemStack = user.getStackInHand(hand); + HitResult hitResult = raycast(world, user, RaycastContext.FluidHandling.ANY); + + if (hitResult.getType() == HitResult.Type.MISS) + return TypedActionResult.pass(itemStack); + + Vec3d vec3d = user.getRotationVec(1.0F); + + List list = world.getOtherEntities(user, user.getBoundingBox().stretch(vec3d.multiply(5.0F)).expand(1.0F), RIDERS); + + if (!list.isEmpty()) { + Vec3d vec3d2 = user.getEyePos(); + + for(Entity entity : list) { + Box box = entity.getBoundingBox().expand(entity.getTargetingMargin()); + if (box.contains(vec3d2)) { + return TypedActionResult.pass(itemStack); + } + } + } + + if (hitResult.getType() != HitResult.Type.BLOCK) + return TypedActionResult.pass(itemStack); + + BoatEntity boatEntity = this.createEntity(world, hitResult); + boatEntity.setVariant(this.type.get()); + boatEntity.setYaw(user.getYaw()); + + if (!world.isSpaceEmpty(boatEntity, boatEntity.getBoundingBox())) { + return TypedActionResult.fail(itemStack); + } else { + if (!world.isClient()) { + world.spawnEntity(boatEntity); + world.emitGameEvent(user, GameEvent.ENTITY_PLACE, hitResult.getPos()); + + if (!user.getAbilities().creativeMode) + itemStack.decrement(1); + } + + user.incrementStat(Stats.USED.getOrCreateStat(this)); + return TypedActionResult.success(itemStack, world.isClient()); + } + } + + private BoatEntity createEntity(World world, HitResult hitResult) { + return this.chest ? new ChestBoatEntity(world, hitResult.getPos().x, hitResult.getPos().y, hitResult.getPos().z) : new BoatEntity(world, hitResult.getPos().x, hitResult.getPos().y, hitResult.getPos().z); + } +} \ No newline at end of file diff --git a/src/main/java/dev/amble/lib/boat/ABoatType.java b/src/main/java/dev/amble/lib/boat/ABoatType.java new file mode 100644 index 0000000..357f124 --- /dev/null +++ b/src/main/java/dev/amble/lib/boat/ABoatType.java @@ -0,0 +1,7 @@ +package dev.amble.lib.boat; + +import net.minecraft.entity.vehicle.BoatEntity; + +public interface ABoatType { + BoatEntity.Type get(); +} \ No newline at end of file diff --git a/src/main/java/dev/amble/lib/boat/BoatTypeContainer.java b/src/main/java/dev/amble/lib/boat/BoatTypeContainer.java new file mode 100644 index 0000000..8de2438 --- /dev/null +++ b/src/main/java/dev/amble/lib/boat/BoatTypeContainer.java @@ -0,0 +1,71 @@ +package dev.amble.lib.boat; + +import dev.amble.lib.container.RegistryContainer; +import net.minecraft.block.Block; +import net.minecraft.entity.vehicle.BoatEntity; +import net.minecraft.item.Item; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Field; + +public abstract class BoatTypeContainer implements RegistryContainer { + @Override + public @Nullable Registry getRegistry() { + return null; + } + + protected static ABoatType register(Item item, Item chest, Block block) { + return new Holder(new Pending(item, chest, block)); + } + + protected static ABoatType registerNormal(Item item, Block block) { + return register(item, null, block); + } + + protected static ABoatType registerChest(Item item, Block block) { + return register(null, item, block); + } + + record Pending(Item item, Item chest, Block block) implements ABoatType { + + @Override + public BoatEntity.Type get() { + throw new IllegalStateException("This boat type was not registered yet!"); + } + + public ABoatType register(Identifier id) { + return BoatTypeRegistry.register(id, this.item, this.chest, this.block); + } + } + + public static final class Holder implements ABoatType { + + ABoatType child; + + Holder(ABoatType child) { + this.child = child; + } + + @Override + public BoatEntity.Type get() { + return child.get(); + } + } + + public Class getTargetClass() { + return Holder.class; + } + + @Override + public void postProcessField(Identifier identifier, Holder value, Field field) { + // Promotion + value.child = ((Pending) value.child).register(identifier); + } + + @Override + public void finish() { + BoatTypeRegistry.apply(); + } +} \ No newline at end of file diff --git a/src/main/java/dev/amble/lib/boat/BoatTypeRegistry.java b/src/main/java/dev/amble/lib/boat/BoatTypeRegistry.java new file mode 100644 index 0000000..6bf07bc --- /dev/null +++ b/src/main/java/dev/amble/lib/boat/BoatTypeRegistry.java @@ -0,0 +1,80 @@ + +package dev.amble.lib.boat; + +import dev.amble.lib.util.LazyObject; +import net.minecraft.block.Block; +import net.minecraft.entity.vehicle.BoatEntity; +import net.minecraft.item.Item; +import net.minecraft.util.Identifier; + +import java.util.ArrayList; +import java.util.List; + +public class BoatTypeRegistry { + + private static Item[] TYPE2ITEM; + private static Item[] CTYPE2ITEM; + + private static final BoatItemConsumer CONSUMER = new BoatItemConsumer() { + + @Override + public void init(int size) { + TYPE2ITEM = new Item[size]; + CTYPE2ITEM = new Item[size]; + } + + @Override + public void accept(BoatEntity.Type type, Item normal, Item chest) { + TYPE2ITEM[type.ordinal()] = normal; + CTYPE2ITEM[type.ordinal()] = chest; + } + }; + + private static final List entries = new ArrayList<>(); + + public static LazyBoatType register(Identifier id, Item item, Item chest, Block block) { + Entry entry = new Entry(id, item, chest, block); + entries.add(entry); + + return entry.lazyGetter(); + } + + public static Item getItem(BoatEntity.Type type, boolean chest) { + return (chest ? CTYPE2ITEM : TYPE2ITEM)[type.ordinal()]; + } + + public static void apply() { + ((CustomBoatTypes) (Object) BoatEntity.Type.OAK).amble$recalc(entries, CONSUMER); + } + + public interface BoatItemConsumer { + void init(int size); + void accept(BoatEntity.Type type, Item normal, Item chest); + } + + public interface CustomBoatTypes { + void amble$recalc(List entries, BoatItemConsumer consumer); + } + + public record Entry(Identifier id, Item item, Item chest, Block block) { + + public String getPath() { + return id.getNamespace() + "/" + id.getPath(); + } + + public String getEnumName() { + return id.getNamespace().toUpperCase() + "_" + id.getPath().replace('/', '_').toUpperCase(); + } + + public LazyBoatType lazyGetter() { + return new LazyBoatType(this); + } + } + + public static class LazyBoatType extends LazyObject implements ABoatType { + + public LazyBoatType(Entry entry) { + super(() -> BoatEntity.Type.getType(entry.getPath()), BoatEntity.Type.OAK); + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/amble/lib/container/RegistryContainer.java b/src/main/java/dev/amble/lib/container/RegistryContainer.java index 21eac5b..e49c02d 100644 --- a/src/main/java/dev/amble/lib/container/RegistryContainer.java +++ b/src/main/java/dev/amble/lib/container/RegistryContainer.java @@ -8,6 +8,7 @@ import net.minecraft.util.Identifier; import dev.amble.lib.AmbleKit; +import org.jetbrains.annotations.Nullable; public interface RegistryContainer { @@ -16,6 +17,7 @@ public interface RegistryContainer { /** * @return The registry the fields of this class should be registered into */ + @Nullable Registry getRegistry(); /** @@ -28,12 +30,16 @@ public interface RegistryContainer { */ default void postProcessField(Identifier identifier, T value, Field field) {} - static void register(Class> clazz, String namespace) { + default boolean preProcessField(Field field) { + return true; + } + + static void register(Class> clazz, String namespace) { try { RegistryContainer container = clazz.getDeclaredConstructor().newInstance(); Field[] fields = clazz.getDeclaredFields(); - container.start(fields.length); + if (!container.start(fields.length)) return; for (Field field : fields) { if (!Modifier.isStatic(field.getModifiers())) @@ -42,6 +48,8 @@ static void register(Class> clazz, String nam if (!container.getTargetClass().isAssignableFrom(field.getType())) continue; + if (!container.preProcessField(field)) return; + // trust me bro T v = (T) field.get(null); @@ -55,7 +63,9 @@ static void register(Class> clazz, String nam Identifier id = new Identifier(namespace, name); - Registry.register(container.getRegistry(), id, v); + if (container.getRegistry() != null) { + Registry.register(container.getRegistry(), id, v); + } container.postProcessField(id, v, field); } @@ -70,7 +80,7 @@ static Class conform(Class input) { return (Class) input; } - default void start(int fields) { } + default boolean start(int fields) { return true; } default void finish() { } } \ No newline at end of file diff --git a/src/main/java/dev/amble/lib/container/impl/BlockContainer.java b/src/main/java/dev/amble/lib/container/impl/BlockContainer.java index e0ec3ac..c19ad80 100644 --- a/src/main/java/dev/amble/lib/container/impl/BlockContainer.java +++ b/src/main/java/dev/amble/lib/container/impl/BlockContainer.java @@ -24,8 +24,9 @@ public abstract class BlockContainer implements RegistryContainer { private List items; @Override - public void start(int fields) { + public boolean start(int fields) { this.items = new ArrayList<>(fields); + return true; } @Override diff --git a/src/main/java/dev/amble/lib/mixin/BoatEntityMixin.java b/src/main/java/dev/amble/lib/mixin/BoatEntityMixin.java new file mode 100644 index 0000000..0d8434e --- /dev/null +++ b/src/main/java/dev/amble/lib/mixin/BoatEntityMixin.java @@ -0,0 +1,25 @@ +package dev.amble.lib.mixin; + +import dev.amble.lib.boat.BoatTypeRegistry; +import net.minecraft.entity.vehicle.BoatEntity; +import net.minecraft.item.Item; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(value = BoatEntity.class, priority = 999) +public abstract class BoatEntityMixin { + + @Shadow + public abstract BoatEntity.Type getVariant(); + + @Inject(method = "asItem", at = @At("HEAD"), cancellable = true) + public void asItem(CallbackInfoReturnable cir) { + Item item = BoatTypeRegistry.getItem(this.getVariant(), false); + + if (item != null) + cir.setReturnValue(item); + } +} \ No newline at end of file diff --git a/src/main/java/dev/amble/lib/mixin/BoatEntityTypeMixin.java b/src/main/java/dev/amble/lib/mixin/BoatEntityTypeMixin.java new file mode 100644 index 0000000..ff8adca --- /dev/null +++ b/src/main/java/dev/amble/lib/mixin/BoatEntityTypeMixin.java @@ -0,0 +1,68 @@ +package dev.amble.lib.mixin; + +import dev.amble.lib.boat.BoatTypeRegistry; +import net.minecraft.block.Block; +import net.minecraft.entity.vehicle.BoatEntity; +import net.minecraft.item.Items; +import net.minecraft.util.StringIdentifiable; +import net.minecraft.util.function.ValueLists; +import org.spongepowered.asm.mixin.*; +import org.spongepowered.asm.mixin.gen.Invoker; + +import java.util.List; +import java.util.function.IntFunction; + +@Mixin(BoatEntity.Type.class) +public class BoatEntityTypeMixin implements BoatTypeRegistry.CustomBoatTypes { + + @Mutable + @Shadow + @Final + private static BoatEntity.Type[] field_7724; + @Mutable @Shadow @Final public static StringIdentifiable.Codec CODEC; + @Mutable @Shadow @Final private static IntFunction BY_ID; + + @Invoker("") + public static BoatEntity.Type amblekit$init(String internalName, int ordinal, Block baseBlock, String name) { + throw new AssertionError(); + } + + @Unique + private static BoatEntity.Type amblekit$init(BoatTypeRegistry.Entry entry, int offset) { + return amblekit$init(entry.getEnumName(), + field_7724.length + offset, + entry.block(), entry.getPath() + ); + } + + @Override + public void amble$recalc(List entries, BoatTypeRegistry.BoatItemConsumer consumer) { + int l = field_7724.length; + + BoatEntity.Type[] variants = new BoatEntity.Type[l + entries.size()]; + System.arraycopy(field_7724, 0, variants, 0, l); + + consumer.init(variants.length); + consumer.accept(BoatEntity.Type.OAK, Items.OAK_BOAT, Items.OAK_CHEST_BOAT); + consumer.accept(BoatEntity.Type.SPRUCE, Items.SPRUCE_BOAT, Items.SPRUCE_CHEST_BOAT); + consumer.accept(BoatEntity.Type.BIRCH, Items.BIRCH_BOAT, Items.BIRCH_CHEST_BOAT); + consumer.accept(BoatEntity.Type.JUNGLE, Items.JUNGLE_BOAT, Items.JUNGLE_CHEST_BOAT); + consumer.accept(BoatEntity.Type.ACACIA, Items.ACACIA_BOAT, Items.ACACIA_CHEST_BOAT); + consumer.accept(BoatEntity.Type.CHERRY, Items.CHERRY_BOAT, Items.CHERRY_CHEST_BOAT); + consumer.accept(BoatEntity.Type.DARK_OAK, Items.DARK_OAK_BOAT, Items.DARK_OAK_CHEST_BOAT); + consumer.accept(BoatEntity.Type.MANGROVE, Items.MANGROVE_BOAT, Items.MANGROVE_CHEST_BOAT); + consumer.accept(BoatEntity.Type.BAMBOO, Items.BAMBOO_RAFT, Items.BAMBOO_CHEST_RAFT); + + for (int i = 0; i < entries.size(); i++) { + BoatTypeRegistry.Entry entry = entries.get(i); + BoatEntity.Type type = variants[l + i] = amblekit$init(entry, i); + + consumer.accept(type, entry.item(), entry.chest()); + } + + field_7724 = variants; + + CODEC = StringIdentifiable.createCodec(BoatEntity.Type::values); + BY_ID = ValueLists.createIdToValueFunction(Enum::ordinal, variants, ValueLists.OutOfBoundsHandling.ZERO); + } +} \ No newline at end of file diff --git a/src/main/java/dev/amble/lib/mixin/ChestBoatEntityMixin.java b/src/main/java/dev/amble/lib/mixin/ChestBoatEntityMixin.java new file mode 100644 index 0000000..efbb3f0 --- /dev/null +++ b/src/main/java/dev/amble/lib/mixin/ChestBoatEntityMixin.java @@ -0,0 +1,28 @@ +package dev.amble.lib.mixin; + +import dev.amble.lib.boat.BoatTypeRegistry; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.vehicle.BoatEntity; +import net.minecraft.entity.vehicle.ChestBoatEntity; +import net.minecraft.item.Item; +import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(value = ChestBoatEntity.class, priority = 999) +public abstract class ChestBoatEntityMixin extends BoatEntity { + + public ChestBoatEntityMixin(EntityType entityType, World world) { + super(entityType, world); + } + + @Inject(method = "asItem", at = @At("HEAD"), cancellable = true) + public void asItem(CallbackInfoReturnable cir) { + Item item = BoatTypeRegistry.getItem(this.getVariant(), true); + + if (item != null) + cir.setReturnValue(item); + } +} \ No newline at end of file diff --git a/src/main/resources/amblekit.mixins.json b/src/main/resources/amblekit.mixins.json index fc47264..2181312 100644 --- a/src/main/resources/amblekit.mixins.json +++ b/src/main/resources/amblekit.mixins.json @@ -6,6 +6,9 @@ "mixins": [ "AbstractBlockAccessor", "BlockMixin", + "BoatEntityMixin", + "BoatEntityTypeMixin", + "ChestBoatEntityMixin", "EndermanEntityMixin", "ItemMixin", "PlayerEntityMixin",