diff --git a/StaffMod/src/main/java/opekope2/avm_staff/api/entity/IImpactTnt.java b/StaffMod/src/main/java/opekope2/avm_staff/api/entity/IImpactTnt.java
new file mode 100644
index 000000000..b842cfc7a
--- /dev/null
+++ b/StaffMod/src/main/java/opekope2/avm_staff/api/entity/IImpactTnt.java
@@ -0,0 +1,51 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.api.entity;
+
+import net.minecraft.entity.TntEntity;
+import net.minecraft.entity.data.DataTracker;
+import net.minecraft.entity.data.TrackedData;
+import net.minecraft.entity.data.TrackedDataHandlerRegistry;
+
+/**
+ * A TNT, which can be configured to explode when collides (with blocks or other entities).
+ */
+public interface IImpactTnt {
+ /**
+ * Returns if the current TNT explodes, when it collides.
+ */
+ boolean explodesOnImpact();
+
+ /**
+ * Configures the current TNT to explode or not, when it collides.
+ *
+ * @param explode Whether to explode on collision
+ */
+ void explodeOnImpact(boolean explode);
+
+ /**
+ * NBT Key for TNT's explodes on impact property.
+ */
+ String EXPLODES_ON_IMPACT_NBT_KEY = "ExplodesOnImpact";
+
+ /**
+ * Data tracker for TNT's explodes on impact property.
+ */
+ TrackedData EXPLODES_ON_IMPACT = DataTracker.registerData(TntEntity.class, TrackedDataHandlerRegistry.BOOLEAN);
+}
diff --git a/StaffMod/src/main/java/opekope2/avm_staff/mixin/TntEntityMixin.java b/StaffMod/src/main/java/opekope2/avm_staff/mixin/TntEntityMixin.java
new file mode 100644
index 000000000..0ee9cf783
--- /dev/null
+++ b/StaffMod/src/main/java/opekope2/avm_staff/mixin/TntEntityMixin.java
@@ -0,0 +1,111 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.mixin;
+
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.EntityType;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.entity.TntEntity;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.nbt.NbtElement;
+import net.minecraft.world.World;
+import opekope2.avm_staff.api.entity.IImpactTnt;
+import org.jetbrains.annotations.Nullable;
+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.CallbackInfo;
+
+import java.util.List;
+
+@Mixin(TntEntity.class)
+public abstract class TntEntityMixin extends Entity implements IImpactTnt {
+ private TntEntityMixin(EntityType> type, World world) {
+ super(type, world);
+ }
+
+ @Shadow
+ protected abstract void explode();
+
+ @Shadow
+ @Nullable
+ public abstract LivingEntity getOwner();
+
+ @Inject(method = "initDataTracker", at = @At("TAIL"))
+ private void initDataTracker(CallbackInfo ci) {
+ dataTracker.startTracking(EXPLODES_ON_IMPACT, false);
+ }
+
+ @Inject(method = "writeCustomDataToNbt", at = @At("TAIL"))
+ private void writeCustomDataToNbt(NbtCompound nbt, CallbackInfo ci) {
+ nbt.putBoolean(EXPLODES_ON_IMPACT_NBT_KEY, explodesOnImpact());
+ }
+
+ @Inject(method = "readCustomDataFromNbt", at = @At("TAIL"))
+ private void readCustomDataFromNbt(NbtCompound nbt, CallbackInfo ci) {
+ if (nbt.contains(EXPLODES_ON_IMPACT_NBT_KEY, NbtElement.BYTE_TYPE)) {
+ explodeOnImpact(nbt.getBoolean(EXPLODES_ON_IMPACT_NBT_KEY));
+ }
+ }
+
+ @Inject(
+ method = "tick",
+ at = @At(
+ value = "INVOKE",
+ target = "Lnet/minecraft/entity/TntEntity;move(Lnet/minecraft/entity/MovementType;Lnet/minecraft/util/math/Vec3d;)V",
+ shift = At.Shift.AFTER
+ )
+ )
+ private void explodeOnImpact(CallbackInfo ci) {
+ if (!explodesOnImpact()) return;
+
+ boolean explode = horizontalCollision || verticalCollision;
+ if (!explode) {
+ Entity owner = getOwner();
+ List collisions = getWorld().getOtherEntities(this, getBoundingBox(), entity -> entity != owner);
+ explode = !collisions.isEmpty();
+
+ for (Entity collider : collisions) {
+ if (collider instanceof TntEntity tnt && ((IImpactTnt) tnt).explodesOnImpact()) {
+ // Force explode other TNT, because the current TNT gets discarded before the other TNT gets processed
+ tnt.setFuse(0);
+ }
+ }
+ }
+
+ if (!explode) return;
+
+ if (!getWorld().isClient) {
+ // Server sends EntitiesDestroyS2CPacket to client, because TntEntity.getOwner() isn't available on the client.
+ discard();
+ explode();
+ }
+ }
+
+ @Override
+ public boolean explodesOnImpact() {
+ return dataTracker.get(EXPLODES_ON_IMPACT);
+ }
+
+ @Override
+ public void explodeOnImpact(boolean explode) {
+ dataTracker.set(EXPLODES_ON_IMPACT, explode);
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff_item_handler/TntHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff_item_handler/TntHandler.kt
new file mode 100644
index 000000000..184c7e734
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff_item_handler/TntHandler.kt
@@ -0,0 +1,54 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.internal.staff_item_handler
+
+import net.minecraft.entity.LivingEntity
+import net.minecraft.entity.TntEntity
+import net.minecraft.item.BlockItem
+import net.minecraft.item.ItemStack
+import net.minecraft.item.Items
+import net.minecraft.sound.SoundCategory
+import net.minecraft.sound.SoundEvents
+import net.minecraft.util.ActionResult
+import net.minecraft.util.Hand
+import net.minecraft.world.World
+import net.minecraft.world.event.GameEvent
+import opekope2.avm_staff.api.item.StaffItemHandler
+import opekope2.avm_staff.api.item.renderer.InsideStaffBlockStateRenderer
+import opekope2.avm_staff.api.entity.IImpactTnt
+
+class TntHandler : StaffItemHandler() {
+ override val staffItemRenderer = InsideStaffBlockStateRenderer.forBlockItem(Items.TNT as BlockItem)
+
+ override fun attack(staffStack: ItemStack, world: World, attacker: LivingEntity, hand: Hand): ActionResult {
+ if (world.isClient) return ActionResult.SUCCESS
+
+ world.spawnEntity(
+ TntEntity(world, attacker.x, attacker.eyeY, attacker.z, attacker).apply {
+ velocity = attacker.rotationVector.normalize()
+ @Suppress("KotlinConstantConditions") // IImpactTnt is ducked into TntEntity
+ (this as IImpactTnt).explodeOnImpact(true)
+ world.playSound(null, x, y, z, SoundEvents.ENTITY_TNT_PRIMED, SoundCategory.BLOCKS, 1.0f, 1.0f)
+ world.emitGameEvent(attacker, GameEvent.PRIME_FUSE, pos)
+ }
+ )
+
+ return ActionResult.SUCCESS
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff_item_handler/VanillaStaffItemHandlers.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff_item_handler/VanillaStaffItemHandlers.kt
index e321e8f6b..1be3c14cc 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff_item_handler/VanillaStaffItemHandlers.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff_item_handler/VanillaStaffItemHandlers.kt
@@ -56,6 +56,8 @@ fun register() {
SNOW_BLOCK.registerHandler(SnowBlockHandler())
+ TNT.registerHandler(TntHandler())
+
WHITE_WOOL.registerHandler(WoolHandler(WHITE_WOOL as BlockItem, WHITE_CARPET as BlockItem))
ORANGE_WOOL.registerHandler(WoolHandler(ORANGE_WOOL as BlockItem, ORANGE_CARPET as BlockItem))
MAGENTA_WOOL.registerHandler(WoolHandler(MAGENTA_WOOL as BlockItem, MAGENTA_CARPET as BlockItem))
diff --git a/StaffMod/src/main/resources/avm_staff.mixins.json b/StaffMod/src/main/resources/avm_staff.mixins.json
index 732abeacb..b9bf177d4 100644
--- a/StaffMod/src/main/resources/avm_staff.mixins.json
+++ b/StaffMod/src/main/resources/avm_staff.mixins.json
@@ -11,6 +11,7 @@
],
"mixins": [
"IAbstractFurnaceBlockEntityMixin",
- "LivingEntityMixin"
+ "LivingEntityMixin",
+ "TntEntityMixin"
]
}
diff --git a/gradle.properties b/gradle.properties
index 850b6e99e..e4e430b6d 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -9,7 +9,7 @@ loader_version=0.15.3
java_version=17
##########################################################################
# Mod Properties
-mod_version=0.9.0-beta
+mod_version=0.10.0-beta
maven_group=opekope2.avm_staff
##########################################################################
# Kotlin Dependencies