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