summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/wtf/kity/uncrackable/Uncrackable.java8
-rw-r--r--src/main/java/wtf/kity/uncrackable/mixin/BlockBehaviourMixin.java35
-rw-r--r--src/main/java/wtf/kity/uncrackable/mixin/DragonEggBlockMixin.java64
-rw-r--r--src/main/java/wtf/kity/uncrackable/mixin/FallingBlockEntityMixin.java112
-rw-r--r--src/main/resources/uncrackable.mixins.json16
-rw-r--r--src/main/templates/META-INF/neoforge.mods.toml110
6 files changed, 345 insertions, 0 deletions
diff --git a/src/main/java/wtf/kity/uncrackable/Uncrackable.java b/src/main/java/wtf/kity/uncrackable/Uncrackable.java
new file mode 100644
index 0000000..8735efa
--- /dev/null
+++ b/src/main/java/wtf/kity/uncrackable/Uncrackable.java
@@ -0,0 +1,8 @@
+package wtf.kity.uncrackable;
+
+import net.neoforged.fml.common.Mod;
+
+@Mod(Uncrackable.MODID)
+public class Uncrackable {
+    public static final String MODID = "uncrackable";
+}
diff --git a/src/main/java/wtf/kity/uncrackable/mixin/BlockBehaviourMixin.java b/src/main/java/wtf/kity/uncrackable/mixin/BlockBehaviourMixin.java
new file mode 100644
index 0000000..506b0c9
--- /dev/null
+++ b/src/main/java/wtf/kity/uncrackable/mixin/BlockBehaviourMixin.java
@@ -0,0 +1,35 @@
+package wtf.kity.uncrackable.mixin;
+
+import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.entity.item.FallingBlockEntity;
+import net.minecraft.world.level.BlockGetter;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.BlockBehaviour;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.material.PushReaction;
+import net.minecraft.world.phys.shapes.CollisionContext;
+import net.minecraft.world.phys.shapes.EntityCollisionContext;
+import net.minecraft.world.phys.shapes.Shapes;
+import net.minecraft.world.phys.shapes.VoxelShape;
+import org.spongepowered.asm.mixin.Mixin;
+
+@Mixin(BlockBehaviour.class)
+public class BlockBehaviourMixin {
+    /// If checking collision for a dragon egg, non-piston-destructible blocks should act
+    /// as full blocks so the egg lands on top of them.
+    @WrapMethod(method = "getCollisionShape")
+    VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context, Operation<VoxelShape> original) {
+        VoxelShape shape = original.call(state, level, pos, context);
+        if (!shape.isEmpty()
+                && context instanceof EntityCollisionContext entityContext
+                && entityContext.getEntity() instanceof FallingBlockEntity fallingBlock
+                && fallingBlock.getBlockState().is(Blocks.DRAGON_EGG)
+                && state.getPistonPushReaction() != PushReaction.DESTROY
+        ) {
+            return Shapes.block();
+        }
+        return shape;
+    }
+}
diff --git a/src/main/java/wtf/kity/uncrackable/mixin/DragonEggBlockMixin.java b/src/main/java/wtf/kity/uncrackable/mixin/DragonEggBlockMixin.java
new file mode 100644
index 0000000..7690985
--- /dev/null
+++ b/src/main/java/wtf/kity/uncrackable/mixin/DragonEggBlockMixin.java
@@ -0,0 +1,64 @@
+package wtf.kity.uncrackable.mixin;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.particles.ParticleTypes;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvents;
+import net.minecraft.sounds.SoundSource;
+import net.minecraft.util.Mth;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.DragonEggBlock;
+import net.minecraft.world.level.block.state.BlockBehaviour;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.border.WorldBorder;
+import net.minecraft.world.level.material.PushReaction;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Overwrite;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.ModifyVariable;
+
+@Mixin(DragonEggBlock.class)
+public class DragonEggBlockMixin {
+    @ModifyVariable(method = "<init>", at = @At("HEAD"), ordinal = 0, argsOnly = true)
+    private static BlockBehaviour.Properties init(BlockBehaviour.Properties properties) {
+        return properties
+                .pushReaction(PushReaction.NORMAL)
+                .strength(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY);
+    }
+
+    /**
+     * @author ashkitten
+     * @reason fixing MC-2157
+     */
+    @Overwrite
+    private void teleport(BlockState state, Level level, BlockPos pos) {
+        if (level instanceof ServerLevel serverLevel) {
+            WorldBorder worldborder = level.getWorldBorder();
+
+            for (int i = 0; i < 1000; i++) {
+                BlockPos blockpos = pos.offset(
+                    level.random.nextInt(16) - level.random.nextInt(16),
+                    level.random.nextInt(8) - level.random.nextInt(8),
+                    level.random.nextInt(16) - level.random.nextInt(16)
+                );
+                if (level.getBlockState(blockpos).isAir() && worldborder.isWithinBounds(blockpos)) {
+                    // slightly different behavior than vanilla, since we send 32x4 particles evenly distributed
+                    // instead of 1x128 randomly distributed, but it actually points in the right fucking direction lol
+                    for (int j = 0; j < 32; j++) {
+                        double d0 = j / 32.0;
+                        double d1 = Mth.lerp(d0, blockpos.getX(), pos.getX()) + (level.random.nextDouble() - 0.5) + 0.5;
+                        double d2 = Mth.lerp(d0, blockpos.getY(), pos.getY()) + level.random.nextDouble() - 0.5;
+                        double d3 = Mth.lerp(d0, blockpos.getZ(), pos.getZ()) + (level.random.nextDouble() - 0.5) + 0.5;
+                        serverLevel.sendParticles(ParticleTypes.PORTAL, d1, d2, d3, 4,  0.1, 0.1, 0.1, 0.0);
+                    }
+
+                    level.setBlock(blockpos, state, 2);
+                    level.removeBlock(pos, false);
+                    level.playSound(null, pos, SoundEvents.ENDERMAN_TELEPORT, SoundSource.BLOCKS);
+
+                    return;
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/wtf/kity/uncrackable/mixin/FallingBlockEntityMixin.java b/src/main/java/wtf/kity/uncrackable/mixin/FallingBlockEntityMixin.java
new file mode 100644
index 0000000..500f970
--- /dev/null
+++ b/src/main/java/wtf/kity/uncrackable/mixin/FallingBlockEntityMixin.java
@@ -0,0 +1,112 @@
+package wtf.kity.uncrackable.mixin;
+
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import com.llamalad7.mixinextras.sugar.Local;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.item.FallingBlockEntity;
+import net.minecraft.world.item.context.BlockPlaceContext;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+import org.objectweb.asm.Opcodes;
+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.CallbackInfo;
+
+@Mixin(FallingBlockEntity.class)
+public abstract class FallingBlockEntityMixin extends Entity {
+    public FallingBlockEntityMixin(EntityType<?> entityType, Level level) {
+        super(entityType, level);
+    }
+
+    /// If the egg falls into the void, teleport back up to build limit.
+    @Inject(method = "tick", at = @At("HEAD"))
+    private void beforeTick(CallbackInfo ci) {
+        Vec3 pos = this.position();
+        if (pos.y < this.level().getMinBuildHeight()) {
+            this.teleportTo(pos.x, this.level().getMaxBuildHeight(), pos.z);
+        }
+    }
+
+    /// Egg should never time out. Skip add-assign operation entirely for the egg.
+    @WrapOperation(
+            method = "tick",
+            at = @At(
+                value = "FIELD",
+                target = "Lnet/minecraft/world/entity/item/FallingBlockEntity;time:I",
+                opcode = Opcodes.PUTFIELD
+            )
+    )
+    private void setTime(FallingBlockEntity instance, int value, Operation<Void> original) {
+        if (!instance.getBlockState().is(Blocks.DRAGON_EGG)) {
+            original.call(instance, value);
+        }
+    }
+
+    /// Egg should not drop as an item if it lands above build height.
+    @WrapOperation(
+            method = "tick",
+            at = @At(
+                    value = "FIELD",
+                    target = "Lnet/minecraft/world/entity/item/FallingBlockEntity;dropItem:Z",
+                    opcode = Opcodes.GETFIELD
+            )
+    )
+    private boolean getDropItem(FallingBlockEntity instance, Operation<Boolean> original) {
+        if (instance.getBlockState().is(Blocks.DRAGON_EGG)) {
+            return false;
+        }
+        return original.call(instance);
+    }
+
+    /// If it lands inside a block, destroy the block as if by piston push.
+    /// We override block shapes for non-piston-destructible blocks in`BlockBehaviorMixin`
+    /// so normally this only applies to piston-destructible blocks, unless a block is pushed
+    /// into the egg while it's an entity.
+    @WrapOperation(
+            method = "tick",
+            at = @At(
+                    value = "INVOKE",
+                    target = "Lnet/minecraft/world/level/block/state/BlockState;canBeReplaced(Lnet/minecraft/world/item/context/BlockPlaceContext;)Z"
+            )
+    )
+    private boolean canBeReplaced(
+            BlockState instance,
+            BlockPlaceContext blockPlaceContext,
+            Operation<Boolean> original,
+            @Local BlockPos blockPos
+    ) {
+        if (original.call(instance, blockPlaceContext)) {
+            return true;
+        }
+
+        if (instance.is(Blocks.DRAGON_EGG)) {
+            BlockEntity blockentity = instance.hasBlockEntity() ? this.level().getBlockEntity(blockPos) : null;
+            Block.dropResources(instance, this.level(), blockPos, blockentity);
+            instance.onDestroyedByPushReaction(this.level(), blockPos, Direction.DOWN, this.level().getFluidState(blockPos));
+            return true;
+        }
+
+        return false;
+    }
+
+    /// The egg should never drop as an item, so we always pretend that the ground beneath it is landable.
+    @WrapOperation(
+            method = "tick",
+            at = @At(
+                    value = "INVOKE",
+                    target = "Lnet/minecraft/world/level/block/FallingBlock;isFree(Lnet/minecraft/world/level/block/state/BlockState;)Z"
+            )
+    )
+    private boolean isFree(BlockState state, Operation<Boolean> original, @Local Block block) {
+        return original.call(state) && block != Blocks.DRAGON_EGG;
+    }
+}
diff --git a/src/main/resources/uncrackable.mixins.json b/src/main/resources/uncrackable.mixins.json
new file mode 100644
index 0000000..d31eb1b
--- /dev/null
+++ b/src/main/resources/uncrackable.mixins.json
@@ -0,0 +1,16 @@
+{
+  "required": true,
+  "package": "wtf.kity.uncrackable.mixin",
+  "compatibilityLevel": "JAVA_21",
+  "mixins": [
+    "BlockBehaviourMixin",
+    "DragonEggBlockMixin",
+    "FallingBlockEntityMixin"
+  ],
+  "injectors": {
+    "defaultRequire": 1
+  },
+  "overwrites": {
+    "requireAnnotations": true
+  }
+}
\ No newline at end of file
diff --git a/src/main/templates/META-INF/neoforge.mods.toml b/src/main/templates/META-INF/neoforge.mods.toml
new file mode 100644
index 0000000..d2b603e
--- /dev/null
+++ b/src/main/templates/META-INF/neoforge.mods.toml
@@ -0,0 +1,110 @@
+# This is an example neoforge.mods.toml file. It contains the data relating to the loading mods.
+# There are several mandatory fields (#mandatory), and many more that are optional (#optional).
+# The overall format is standard TOML format, v0.5.0.
+# Note that there are a couple of TOML lists in this file.
+# Find more information on toml format here:  https://github.com/toml-lang/toml
+
+# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml
+modLoader="javafml" #mandatory
+
+# A version range to match for said mod loader - for regular FML @Mod it will be the FML version. This is currently 2.
+loaderVersion="${loader_version_range}" #mandatory
+
+# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
+# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
+license="${mod_license}"
+
+# A URL to refer people to when problems occur with this mod
+issueTrackerURL="https://github.com/ashkitten/Uncrackable/issues" #optional
+
+# A list of mods - how many allowed here is determined by the individual mod loader
+[[mods]] #mandatory
+
+# The modid of the mod
+modId="${mod_id}" #mandatory
+
+# The version number of the mod
+version="${mod_version}" #mandatory
+
+# A display name for the mod
+displayName="${mod_name}" #mandatory
+
+# A URL to query for updates for this mod. See the JSON update specification https://docs.neoforged.net/docs/misc/updatechecker/
+#updateJSONURL="https://change.me.example.invalid/updates.json" #optional
+
+# A URL for the "homepage" for this mod, displayed in the mod UI
+displayURL="https://github.com/ashkitten/Uncrackable/" #optional
+
+# A file name (in the root of the mod JAR) containing a logo for display
+#logoFile="examplemod.png" #optional
+
+# A text field displayed in the mod UI
+#credits="" #optional
+
+# The authors of the mod, displayed in the mod UI (optional)
+authors="ashkitten"
+
+# The description text for the mod (multi line!) (#mandatory)
+description='''
+Makes the dragon egg indestructible by any means, and adds minor feedback improvements to the teleportation mechanic.
+
+Functional changes:
+- Pushable by pistons
+- Indestructible by mining or explosions
+- Lands on top of slabs and other pushable non-full blocks
+- Destroys blocks when landing inside (for non-full blocks like torches, or if a block gets pushed inside while falling)
+- Doesn't time out and drop as an item after 30 seconds like other falling blocks
+- Remains a falling block if landing above build height
+- Wraps around to build height when falling into the void
+
+Feedback improvements:
+- Teleportation particles correctly point to the new location
+- Teleportation sound effect added
+
+This mod is only required on the server side, though the client will generate its own teleportation trail without it.
+'''
+
+# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded.
+[[mixins]]
+config="${mod_id}.mixins.json"
+
+# The [[accessTransformers]] block allows you to declare where your AT file is.
+# If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg
+#[[accessTransformers]]
+#file="META-INF/accesstransformer.cfg"
+
+# The coremods config file path is not configurable and is always loaded from META-INF/coremods.json
+
+# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional.
+[[dependencies.${mod_id}]] #optional
+    # the modid of the dependency
+    modId="neoforge" #mandatory
+    # The type of the dependency. Can be one of "required", "optional", "incompatible" or "discouraged" (case insensitive).
+    # 'required' requires the mod to exist, 'optional' does not
+    # 'incompatible' will prevent the game from loading when the mod exists, and 'discouraged' will show a warning
+    type="required" #mandatory
+    # Optional field describing why the dependency is required or why it is incompatible
+    # reason="..."
+    # The version range of the dependency
+    versionRange="[${neo_version},)" #mandatory
+    # An ordering relationship for the dependency.
+    # BEFORE - This mod is loaded BEFORE the dependency
+    # AFTER - This mod is loaded AFTER the dependency
+    ordering="NONE"
+    # Side this dependency is applied on - BOTH, CLIENT, or SERVER
+    side="BOTH"
+
+# Here's another dependency
+[[dependencies.${mod_id}]]
+    modId="minecraft"
+    type="required"
+    # This version range declares a minimum of the current minecraft version up to but not including the next major version
+    versionRange="${minecraft_version_range}"
+    ordering="NONE"
+    side="BOTH"
+
+# Features are specific properties of the game environment, that you may want to declare you require. This example declares
+# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't
+# stop your mod loading on the server for example.
+#[features.${mod_id}]
+#openGLVersion="[3.2,)"