diff --git a/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/level/ServerExplosionMixin_Forge.java b/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/level/ServerExplosionMixin_Forge.java new file mode 100644 index 00000000000..b2b2936d6ca --- /dev/null +++ b/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/level/ServerExplosionMixin_Forge.java @@ -0,0 +1,114 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.forge.mixin.core.world.level; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ServerExplosion; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.api.event.Cause; +import org.spongepowered.api.event.SpongeEventFactory; +import org.spongepowered.api.event.world.ExplosionEvent; +import org.spongepowered.api.world.explosion.Explosion; +import org.spongepowered.api.world.server.ServerLocation; +import org.spongepowered.api.world.server.ServerWorld; +import org.spongepowered.asm.mixin.Final; +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.Redirect; +import org.spongepowered.common.SpongeCommon; +import org.spongepowered.common.event.ShouldFire; +import org.spongepowered.common.event.tracking.PhaseTracker; +import org.spongepowered.common.util.VecHelper; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@Mixin(ServerExplosion.class) +public abstract class ServerExplosionMixin_Forge { + + // @formatter:off + @Shadow @Final private ServerLevel level; + + @Shadow protected abstract boolean shadow$interactsWithBlocks(); + // @formatter:on + + /** + * These two fields are used in the mixin below, but require a redirect specific for SpongeVanilla + * and SpongeForge as NeoForge makes a change to the method being used here. + * + * @see org.spongepowered.common.mixin.core.world.level.ServerExplosionMixin + */ + private boolean impl$shouldDamageEntities; + private double impl$knockbackMultiplier; + private List impl$affectedBlocks; + + @Redirect(method = "hurtEntities", at = @At(value = "INVOKE", + target = "Lnet/minecraft/server/level/ServerLevel;getEntities(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/AABB;)Ljava/util/List;")) + private List forge$throwEventToFilterEntitiesOnHurt(final ServerLevel instance, final Entity sourceEntity, final AABB aabb) { + final List entities; + if (this.impl$shouldDamageEntities) { + // filter out invulnerable entities before event + entities = instance.getEntities(sourceEntity, aabb).stream() + .filter(e -> !e.ignoreExplosion((net.minecraft.world.level.Explosion) this)) + .toList(); + } else { + entities = Collections.emptyList(); + } + + if (ShouldFire.EXPLOSION_EVENT_DETONATE) { + final var apiWorld = (ServerWorld) this.level; + final var apiEntities = entities.stream().map(org.spongepowered.api.entity.Entity.class::cast).toList(); + final var apiBlockPositions = this.impl$affectedBlocks.stream().map(bp -> ServerLocation.of(apiWorld, VecHelper.toVector3i(bp))).toList(); + final Cause cause = PhaseTracker.getCauseStackManager().currentCause(); + final ExplosionEvent.Detonate event = SpongeEventFactory.createExplosionEventDetonate(cause, apiBlockPositions, apiEntities, (Explosion) this, apiWorld); + if (SpongeCommon.post(event)) { + this.impl$affectedBlocks.clear(); // no blocks affected + return Collections.emptyList(); // no entities affected + } + if (this.shadow$interactsWithBlocks()) { + this.impl$affectedBlocks = event.affectedLocations().stream().map(VecHelper::toBlockPos).collect(Collectors.toList()); + } + if (this.impl$shouldDamageEntities) { + return event.entities().stream().map(Entity.class::cast).toList(); + } + } + return entities; + } + + + @Redirect(method = "hurtEntities", at = @At(value = "NEW", target = "(DDD)Lnet/minecraft/world/phys/Vec3;")) + private Vec3 forge$useKnockbackMultiplier(final double $$0, final double $$1, final double $$2) { + // Honor our knockback value from event + return new Vec3($$0 * this.impl$knockbackMultiplier, + $$1 * this.impl$knockbackMultiplier, + $$2 * this.impl$knockbackMultiplier); + } + +} diff --git a/forge/src/mixins/resources/mixins.spongeforge.core.json b/forge/src/mixins/resources/mixins.spongeforge.core.json index ae82ddbcc44..82aa52ae4c8 100644 --- a/forge/src/mixins/resources/mixins.spongeforge.core.json +++ b/forge/src/mixins/resources/mixins.spongeforge.core.json @@ -1,43 +1,44 @@ { - "required": true, - "parent": "mixins.sponge.parent.json", - "package": "org.spongepowered.forge.mixin.core", - "priority": 1301, - "mixins": [ - "api.event.EventMixin_Forge", - "api.event.block.ChangeBlockEvent_AllMixin_Forge", - "api.event.entity.ChangeEntityWorldEvent_PreMixin_Forge", - "api.event.entity.ChangeEventWorldEvent_PostMixin_Forge", - "commands.CommandsMixin_Forge", - "minecraftforge.MinecraftForgeMixin_Forge", - "minecraftforge.event.entity.EntityTravelToDimensionEventMixin_Forge", - "minecraftforge.event.entity.player.PlayerEvent_PlayerChangedDimensionEventMixin_Forge", - "minecraftforge.event.world.BlockEvent_BreakEventMixin_Forge", - "minecraftforge.event.world.BlockEventMixin_Forge", - "minecraftforge.internal.BrandingControlMixin_Forge", - "minecraftforge.registries.ForgeRegistryMixin_Forge", - "minecraftforge.registries.RegistryManagerMixin_Forge", - "network.ConnectionMixin_Forge", - "network.protocol.common.ClientboundCustomPayloadPacketMixin_Forge", - "network.protocol.common.ServerboundCustomPayloadPacketMixin_Forge", - "server.BootstrapMixin_Forge", - "server.MinecraftServerMixin_Forge", - "server.commands.SpreadPlayersCommandMixin_Forge", - "server.level.ServerPlayerMixin_Forge", - "server.network.ServerGamePacketListenerImplMixin_Forge", - "world.entity.LivingEntityMixin_Forge", - "world.entity.LivingEntityMixin_Forge_Attack_Impl", - "world.entity.item.ItemEntityMixin_Forge", - "world.entity.player.PlayerMixin_Forge_Attack_Impl", - "world.entity.vehicle.BoatMixin_Forge", - "world.level.block.FireBlockMixin_Forge", - "world.level.block.entity.AbstractFurnaceBlockEntityMixin_Forge" - ], - "client": [ - "client.MinecraftMixin_Forge", - "client.main.MainMixin_Forge" - ], - "server": [ - "server.MainMixin_Forge" - ] + "required": true, + "parent": "mixins.sponge.parent.json", + "package": "org.spongepowered.forge.mixin.core", + "priority": 1301, + "mixins": [ + "api.event.EventMixin_Forge", + "api.event.block.ChangeBlockEvent_AllMixin_Forge", + "api.event.entity.ChangeEntityWorldEvent_PreMixin_Forge", + "api.event.entity.ChangeEventWorldEvent_PostMixin_Forge", + "commands.CommandsMixin_Forge", + "minecraftforge.MinecraftForgeMixin_Forge", + "minecraftforge.event.entity.EntityTravelToDimensionEventMixin_Forge", + "minecraftforge.event.entity.player.PlayerEvent_PlayerChangedDimensionEventMixin_Forge", + "minecraftforge.event.world.BlockEvent_BreakEventMixin_Forge", + "minecraftforge.event.world.BlockEventMixin_Forge", + "minecraftforge.internal.BrandingControlMixin_Forge", + "minecraftforge.registries.ForgeRegistryMixin_Forge", + "minecraftforge.registries.RegistryManagerMixin_Forge", + "network.ConnectionMixin_Forge", + "network.protocol.common.ClientboundCustomPayloadPacketMixin_Forge", + "network.protocol.common.ServerboundCustomPayloadPacketMixin_Forge", + "server.BootstrapMixin_Forge", + "server.MinecraftServerMixin_Forge", + "server.commands.SpreadPlayersCommandMixin_Forge", + "server.level.ServerPlayerMixin_Forge", + "server.network.ServerGamePacketListenerImplMixin_Forge", + "world.entity.LivingEntityMixin_Forge", + "world.entity.LivingEntityMixin_Forge_Attack_Impl", + "world.entity.item.ItemEntityMixin_Forge", + "world.entity.player.PlayerMixin_Forge_Attack_Impl", + "world.entity.vehicle.BoatMixin_Forge", + "world.level.ServerExplosionMixin_Forge", + "world.level.block.FireBlockMixin_Forge", + "world.level.block.entity.AbstractFurnaceBlockEntityMixin_Forge" + ], + "client": [ + "client.MinecraftMixin_Forge", + "client.main.MainMixin_Forge" + ], + "server": [ + "server.MainMixin_Forge" + ] } \ No newline at end of file diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 2cf4b8bf80a..e174819790a 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -3911,6 +3911,14 @@ + + + + + + + + @@ -3988,6 +3996,20 @@ + + + + + + + + + + + + + + @@ -4028,6 +4050,22 @@ + + + + + + + + + + + + + + + + @@ -4044,6 +4082,14 @@ + + + + + + + + @@ -4060,6 +4106,14 @@ + + + + + + + + diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index 96e95d67d69..73d56f909dc 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -352,6 +352,9 @@ tasks { from(commonProject.sourceSets.named("applaunch").map { it.output }) from(forgeAppLaunch.output) + // We need to exclude this as NeoForge ships jackson-core as a library + // and we would be violating the packages + dependencyFilter.exclude(dependencyFilter.dependency("com.fasterxml.jackson.core:jackson-core")) // Make sure to relocate access widener so that we don't conflict with other coremods relocate("net.fabricmc.accesswidener", "org.spongepowered.neoforge.libs.accesswidener") diff --git a/neoforge/gradle.properties b/neoforge/gradle.properties index 42d6f02f9fc..834881bed03 100644 --- a/neoforge/gradle.properties +++ b/neoforge/gradle.properties @@ -2,7 +2,7 @@ name=SpongeNeo implementation=NeoForge description=The SpongeAPI implementation for NeoForge -neoForgeVersion=21.3.0-beta +neoForgeVersion=21.3.35-beta loom.platform=neoforge fabric.loom.dontRemap=true mixinConfigs=mixins.spongeneo.accessors.json,mixins.spongeneo.api.json,mixins.spongeneo.inventory.json,mixins.spongeneo.core.json,mixins.spongeneo.tracker.json \ No newline at end of file diff --git a/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/level/ServerExplosionMixin_NeoForge.java b/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/level/ServerExplosionMixin_NeoForge.java new file mode 100644 index 00000000000..43e3768598b --- /dev/null +++ b/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/level/ServerExplosionMixin_NeoForge.java @@ -0,0 +1,115 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.neoforge.mixin.core.world.level; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ServerExplosion; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.api.event.Cause; +import org.spongepowered.api.event.SpongeEventFactory; +import org.spongepowered.api.event.world.ExplosionEvent; +import org.spongepowered.api.world.explosion.Explosion; +import org.spongepowered.api.world.server.ServerLocation; +import org.spongepowered.api.world.server.ServerWorld; +import org.spongepowered.asm.mixin.Final; +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.Redirect; +import org.spongepowered.common.SpongeCommon; +import org.spongepowered.common.event.ShouldFire; +import org.spongepowered.common.event.tracking.PhaseTracker; +import org.spongepowered.common.util.VecHelper; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@Mixin(ServerExplosion.class) +public abstract class ServerExplosionMixin_NeoForge { + + + // @formatter:off + @Shadow @Final private ServerLevel level; + + @Shadow protected abstract boolean shadow$interactsWithBlocks(); + // @formatter:on + + /** + * These two fields are used in the mixin below, but require a redirect specific for SpongeVanilla + * and SpongeForge as NeoForge makes a change to the method being used here. + * + * @see org.spongepowered.common.mixin.core.world.level.ServerExplosionMixin + */ + private boolean impl$shouldDamageEntities; + private double impl$knockbackMultiplier; + private List impl$affectedBlocks; + + @Redirect(method = "hurtEntities(Ljava/util/List;)V", at = @At(value = "INVOKE", + target = "net/minecraft/server/level/ServerLevel.getEntities(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/AABB;)Ljava/util/List;")) + private List neo$throwEventForHurtEntities(final ServerLevel instance, final Entity sourceEntity, final AABB aabb) { + final List entities; + if (this.impl$shouldDamageEntities) { + // filter out invulnerable entities before event + entities = instance.getEntities(sourceEntity, aabb).stream() + .filter(e -> !e.ignoreExplosion((net.minecraft.world.level.Explosion) this)) + .toList(); + } else { + entities = Collections.emptyList(); + } + + if (ShouldFire.EXPLOSION_EVENT_DETONATE) { + final var apiWorld = (ServerWorld) this.level; + final var apiEntities = entities.stream().map(org.spongepowered.api.entity.Entity.class::cast).toList(); + final var apiBlockPositions = this.impl$affectedBlocks.stream().map(bp -> ServerLocation.of(apiWorld, VecHelper.toVector3i(bp))).toList(); + final Cause cause = PhaseTracker.getCauseStackManager().currentCause(); + final ExplosionEvent.Detonate event = SpongeEventFactory.createExplosionEventDetonate(cause, apiBlockPositions, apiEntities, (Explosion) this, apiWorld); + if (SpongeCommon.post(event)) { + this.impl$affectedBlocks.clear(); // no blocks affected + return Collections.emptyList(); // no entities affected + } + if (this.shadow$interactsWithBlocks()) { + this.impl$affectedBlocks = event.affectedLocations().stream().map(VecHelper::toBlockPos).collect(Collectors.toList()); + } + if (this.impl$shouldDamageEntities) { + return event.entities().stream().map(Entity.class::cast).toList(); + } + } + return entities; + } + + + @Redirect(method = "hurtEntities(Ljava/util/List;)V", at = @At(value = "NEW", target = "(DDD)Lnet/minecraft/world/phys/Vec3;")) + private Vec3 neo$useKnockbackMultiplier(final double $$0, final double $$1, final double $$2) { + // Honor our knockback value from event + return new Vec3($$0 * this.impl$knockbackMultiplier, + $$1 * this.impl$knockbackMultiplier, + $$2 * this.impl$knockbackMultiplier); + } + +} diff --git a/neoforge/src/mixins/resources/mixins.spongeneo.core.json b/neoforge/src/mixins/resources/mixins.spongeneo.core.json index 40f3495d5e5..82d10e7dbf2 100644 --- a/neoforge/src/mixins/resources/mixins.spongeneo.core.json +++ b/neoforge/src/mixins/resources/mixins.spongeneo.core.json @@ -1,44 +1,45 @@ { - "required": true, - "parent": "mixins.sponge.parent.json", - "package": "org.spongepowered.neoforge.mixin.core", - "priority": 1301, - "mixins": [ - "api.event.EventMixin_Neo", - "api.event.block.ChangeBlockEvent_AllMixin_Neo", - "api.event.entity.ChangeEntityWorldEvent_PreMixin_Neo", - "api.event.entity.ChangeEventWorldEvent_PostMixin_Neo", - "commands.CommandsMixin_Neo", - "neoforge.NeoForgeMixin_Neo", - "neoforge.event.entity.EntityTravelToDimensionEventMixin_Neo", - "neoforge.event.entity.player.PlayerEvent_PlayerChangedDimensionEventMixin_Neo", - "neoforge.event.world.BlockEvent_BreakEventMixin_Neo", - "neoforge.event.world.BlockEventMixin_Neo", - "neoforge.internal.BrandingControlMixin_Neo", - "neoforge.registries.RegistryManagerMixin_Neo", - "network.ConnectionMixin_Neo", - "server.BootstrapMixin_Neo", - "server.MinecraftServerMixin_Neo", - "server.commands.SpreadPlayersCommandMixin_Neo", - "server.level.ServerEntityMixin_Neo", - "server.level.ServerPlayerMixin_Neo", - "server.network.ServerGamePacketListenerImplMixin_Neo", - "world.entity.EntityMixin_Neo", - "world.entity.LivingEntityMixin_Neo", - "world.entity.LivingEntityMixin_Neo_Attack_Impl", - "world.entity.animal.SnowGolemMixin_Neo", - "world.entity.item.ItemEntityMixin_Neo", - "world.entity.player.PlayerMixin_Neo_Attack_Impl", - "world.entity.projectile.FishingHookMixin_Neo", - "world.entity.vehicle.AbstractBoatMixin_Neo", - "world.level.block.FireBlockMixin_Neo", - "world.level.block.entity.AbstractFurnaceBlockEntityMixin_Neo" - ], - "client": [ - "client.MinecraftMixin_Neo", - "client.main.MainMixin_Neo" - ], - "server": [ - "server.MainMixin_Neo" + "required": true, + "parent": "mixins.sponge.parent.json", + "package": "org.spongepowered.neoforge.mixin.core", + "priority": 1301, + "mixins": [ + "api.event.EventMixin_Neo", + "api.event.block.ChangeBlockEvent_AllMixin_Neo", + "api.event.entity.ChangeEntityWorldEvent_PreMixin_Neo", + "api.event.entity.ChangeEventWorldEvent_PostMixin_Neo", + "commands.CommandsMixin_Neo", + "neoforge.NeoForgeMixin_Neo", + "neoforge.event.entity.EntityTravelToDimensionEventMixin_Neo", + "neoforge.event.entity.player.PlayerEvent_PlayerChangedDimensionEventMixin_Neo", + "neoforge.event.world.BlockEvent_BreakEventMixin_Neo", + "neoforge.event.world.BlockEventMixin_Neo", + "neoforge.internal.BrandingControlMixin_Neo", + "neoforge.registries.RegistryManagerMixin_Neo", + "network.ConnectionMixin_Neo", + "server.BootstrapMixin_Neo", + "server.MinecraftServerMixin_Neo", + "server.commands.SpreadPlayersCommandMixin_Neo", + "server.level.ServerEntityMixin_Neo", + "server.level.ServerPlayerMixin_Neo", + "server.network.ServerGamePacketListenerImplMixin_Neo", + "world.entity.EntityMixin_Neo", + "world.entity.LivingEntityMixin_Neo", + "world.entity.LivingEntityMixin_Neo_Attack_Impl", + "world.entity.animal.SnowGolemMixin_Neo", + "world.entity.item.ItemEntityMixin_Neo", + "world.entity.player.PlayerMixin_Neo_Attack_Impl", + "world.entity.projectile.FishingHookMixin_Neo", + "world.entity.vehicle.AbstractBoatMixin_Neo", + "world.level.ServerExplosionMixin_NeoForge", + "world.level.block.FireBlockMixin_Neo", + "world.level.block.entity.AbstractFurnaceBlockEntityMixin_Neo" + ], + "client": [ + "client.MinecraftMixin_Neo", + "client.main.MainMixin_Neo" + ], + "server": [ + "server.MainMixin_Neo" ] } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/level/ServerExplosionMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/level/ServerExplosionMixin.java index 87cc5812fae..9572b7d8276 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/level/ServerExplosionMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/level/ServerExplosionMixin.java @@ -31,14 +31,7 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.ExplosionDamageCalculator; import net.minecraft.world.level.ServerExplosion; -import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; -import org.spongepowered.api.event.Cause; -import org.spongepowered.api.event.SpongeEventFactory; -import org.spongepowered.api.event.world.ExplosionEvent; -import org.spongepowered.api.world.explosion.Explosion; -import org.spongepowered.api.world.server.ServerLocation; -import org.spongepowered.api.world.server.ServerWorld; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -46,17 +39,12 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.common.SpongeCommon; import org.spongepowered.common.bridge.world.level.ExplosionBridge; import org.spongepowered.common.event.ShouldFire; -import org.spongepowered.common.event.tracking.PhaseTracker; -import org.spongepowered.common.util.VecHelper; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.StringJoiner; -import java.util.stream.Collectors; @Mixin(net.minecraft.world.level.ServerExplosion.class) public abstract class ServerExplosionMixin implements ExplosionBridge { @@ -84,6 +72,7 @@ public abstract class ServerExplosionMixin implements ExplosionBridge { private int impl$resolution; private float impl$randomness; private double impl$knockbackMultiplier; + private List impl$affectedBlocks; @Inject(method = "", at = @At("RETURN")) private void impl$onConstructed(final ServerLevel $$0, final Entity $$1, final DamageSource $$2, @@ -95,7 +84,6 @@ public abstract class ServerExplosionMixin implements ExplosionBridge { this.impl$knockbackMultiplier = 1.0; } - private List impl$affectedBlocks; @Redirect(method = "explode", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/ServerExplosion;calculateExplodedPositions()Ljava/util/List;")) @@ -109,48 +97,6 @@ public abstract class ServerExplosionMixin implements ExplosionBridge { return this.impl$affectedBlocks; } - - @Redirect(method = "hurtEntities", at = @At(value = "INVOKE", - target = "Lnet/minecraft/server/level/ServerLevel;getEntities(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/AABB;)Ljava/util/List;")) - private List impl$onGetHurtEntities(final ServerLevel instance, final Entity sourceEntity, final AABB aabb) { - final List entities; - if (this.impl$shouldDamageEntities) { - // filter out invulnerable entities before event - entities = instance.getEntities(sourceEntity, aabb).stream() - .filter(e -> !e.ignoreExplosion((net.minecraft.world.level.Explosion) this)) - .toList(); - } else { - entities = Collections.emptyList(); - } - - if (ShouldFire.EXPLOSION_EVENT_DETONATE) { - final var apiWorld = (ServerWorld) this.level; - final var apiEntities = entities.stream().map(org.spongepowered.api.entity.Entity.class::cast).toList(); - final var apiBlockPositions = this.impl$affectedBlocks.stream().map(bp -> ServerLocation.of(apiWorld, VecHelper.toVector3i(bp))).toList(); - final Cause cause = PhaseTracker.getCauseStackManager().currentCause(); - final ExplosionEvent.Detonate event = SpongeEventFactory.createExplosionEventDetonate(cause, apiBlockPositions, apiEntities, (Explosion) this, apiWorld); - if (SpongeCommon.post(event)) { - this.impl$affectedBlocks.clear(); // no blocks affected - return Collections.emptyList(); // no entities affected - } - if (this.shadow$interactsWithBlocks()) { - this.impl$affectedBlocks = event.affectedLocations().stream().map(VecHelper::toBlockPos).collect(Collectors.toList()); - } - if (this.impl$shouldDamageEntities) { - return event.entities().stream().map(Entity.class::cast).toList(); - } - } - return entities; - } - - @Redirect(method = "hurtEntities", at = @At(value = "NEW", target = "(DDD)Lnet/minecraft/world/phys/Vec3;")) - private Vec3 impl$onAddKnockback(final double $$0, final double $$1, final double $$2) { - // Honor our knockback value from event - return new Vec3($$0 * this.impl$knockbackMultiplier, - $$1 * this.impl$knockbackMultiplier, - $$2 * this.impl$knockbackMultiplier); - } - @Redirect(method = "explode", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/ServerExplosion;interactWithBlocks(Ljava/util/List;)V")) private void impl$onInteractWithBlocks(final ServerExplosion instance, final List $$0) { diff --git a/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/level/ServerExplosionMixin_Vanilla.java b/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/level/ServerExplosionMixin_Vanilla.java new file mode 100644 index 00000000000..a7118055ba4 --- /dev/null +++ b/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/level/ServerExplosionMixin_Vanilla.java @@ -0,0 +1,115 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.vanilla.mixin.core.world.level; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ServerExplosion; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.api.event.Cause; +import org.spongepowered.api.event.SpongeEventFactory; +import org.spongepowered.api.event.world.ExplosionEvent; +import org.spongepowered.api.world.explosion.Explosion; +import org.spongepowered.api.world.server.ServerLocation; +import org.spongepowered.api.world.server.ServerWorld; +import org.spongepowered.asm.mixin.Final; +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.Redirect; +import org.spongepowered.common.SpongeCommon; +import org.spongepowered.common.event.ShouldFire; +import org.spongepowered.common.event.tracking.PhaseTracker; +import org.spongepowered.common.util.VecHelper; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@Mixin(ServerExplosion.class) +public abstract class ServerExplosionMixin_Vanilla { + + + // @formatter:off + @Shadow @Final private ServerLevel level; + + @Shadow protected abstract boolean shadow$interactsWithBlocks(); + // @formatter:on + + /** + * These two fields are used in the mixin below, but require a redirect specific for SpongeVanilla + * and SpongeForge as NeoForge makes a change to the method being used here. + * + * @see org.spongepowered.common.mixin.core.world.level.ServerExplosionMixin + */ + private boolean impl$shouldDamageEntities; + private double impl$knockbackMultiplier; + + private List impl$affectedBlocks; + + @Redirect(method = "hurtEntities", at = @At(value = "INVOKE", + target = "Lnet/minecraft/server/level/ServerLevel;getEntities(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/AABB;)Ljava/util/List;")) + private List vanilla$throwEventToFilterEntitiesOnHurt(final ServerLevel instance, final Entity sourceEntity, final AABB aabb) { + final List entities; + if (this.impl$shouldDamageEntities) { + // filter out invulnerable entities before event + entities = instance.getEntities(sourceEntity, aabb).stream() + .filter(e -> !e.ignoreExplosion((net.minecraft.world.level.Explosion) this)) + .toList(); + } else { + entities = Collections.emptyList(); + } + + if (ShouldFire.EXPLOSION_EVENT_DETONATE) { + final var apiWorld = (ServerWorld) this.level; + final var apiEntities = entities.stream().map(org.spongepowered.api.entity.Entity.class::cast).toList(); + final var apiBlockPositions = this.impl$affectedBlocks.stream().map(bp -> ServerLocation.of(apiWorld, VecHelper.toVector3i(bp))).toList(); + final Cause cause = PhaseTracker.getCauseStackManager().currentCause(); + final ExplosionEvent.Detonate event = SpongeEventFactory.createExplosionEventDetonate(cause, apiBlockPositions, apiEntities, (Explosion) this, apiWorld); + if (SpongeCommon.post(event)) { + this.impl$affectedBlocks.clear(); // no blocks affected + return Collections.emptyList(); // no entities affected + } + if (this.shadow$interactsWithBlocks()) { + this.impl$affectedBlocks = event.affectedLocations().stream().map(VecHelper::toBlockPos).collect(Collectors.toList()); + } + if (this.impl$shouldDamageEntities) { + return event.entities().stream().map(Entity.class::cast).toList(); + } + } + return entities; + } + + @Redirect(method = "hurtEntities", at = @At(value = "NEW", target = "(DDD)Lnet/minecraft/world/phys/Vec3;")) + private Vec3 vanilla$useKnockbackMultiplier(final double $$0, final double $$1, final double $$2) { + // Honor our knockback value from event + return new Vec3($$0 * this.impl$knockbackMultiplier, + $$1 * this.impl$knockbackMultiplier, + $$2 * this.impl$knockbackMultiplier); + } + +} diff --git a/vanilla/src/mixins/resources/mixins.spongevanilla.core.json b/vanilla/src/mixins/resources/mixins.spongevanilla.core.json index dd3af254426..f5a6e04c79b 100644 --- a/vanilla/src/mixins/resources/mixins.spongevanilla.core.json +++ b/vanilla/src/mixins/resources/mixins.spongevanilla.core.json @@ -36,6 +36,7 @@ "world.entity.item.ItemEntityMixin_Vanilla", "world.entity.player.PlayerMixin_Vanilla_Attack_Impl", "world.entity.vehicle.AbstractBoatMixin_Vanilla", + "world.level.ServerExplosionMixin_Vanilla", "world.level.block.FireBlockMixin_Vanilla", "world.level.block.entity.AbstractFurnaceBlockEntityMixin_Vanilla", "world.level.storage.LevelStorageSourceMixin_Vanilla"