Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add leash events #51

Draft
wants to merge 1 commit into
base: 1.21
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package xyz.nucleoid.stimuli.event.entity;

import net.minecraft.entity.Entity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import org.jetbrains.annotations.Nullable;
import xyz.nucleoid.stimuli.event.StimulusEvent;

public class EntityLeashEvent {
/**
* Called when a leash is attached to either a holding {@link Entity} or a {@link BlockPos}.
*
* <p>A leash holder can be either a player or an existing leash knot.
* A leash position may or may not contain a leash knot yet.
*
* <p>Upon return:
* <ul>
* <li>{@link ActionResult#SUCCESS} cancels further handlers and allows the leash to be attached.
* <li>{@link ActionResult#FAIL} cancels further handlers and prevents the leash from being attached.
* <li>{@link ActionResult#PASS} moves on to the next listener.</ul>
*/
public static final StimulusEvent<Attach> ATTACH = StimulusEvent.create(Attach.class, ctx -> (entity, leashHolder, leashPos, player, hand) -> {
try {
for (var listener : ctx.getListeners()) {
var result = listener.onAttachLeash(entity, leashHolder, leashPos, player, hand);
if (result != ActionResult.PASS) {
return result;
}
}
} catch (Throwable t) {
ctx.handleException(t);
}
return ActionResult.PASS;
});

public interface Attach {
ActionResult onAttachLeash(Entity entity, @Nullable Entity leashHolder, @Nullable BlockPos leashPos, ServerPlayerEntity player, @Nullable Hand hand);
}
}
56 changes: 56 additions & 0 deletions src/main/java/xyz/nucleoid/stimuli/mixin/entity/EntityMixin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package xyz.nucleoid.stimuli.mixin.entity;

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.entity.Entity;
import net.minecraft.entity.Leashable;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import xyz.nucleoid.stimuli.Stimuli;
import xyz.nucleoid.stimuli.event.entity.EntityLeashEvent;

@Mixin(Entity.class)
public class EntityMixin {
@WrapOperation(
method = "interact",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/entity/Leashable;canLeashAttachTo()Z"
)
)
private static boolean onLeashAttach(Leashable leashable, Operation<Boolean> original, @Local(argsOnly = true) PlayerEntity player, @Local(argsOnly = true) Hand hand) {
if (!original.call(leashable)) {
return false;
}

// Leashable instance is the same as this entity
var entity = (Entity) leashable;

if (!entity.getWorld().isClient()) {
var serverPlayer = (ServerPlayerEntity) player;

var events = Stimuli.select();

try (var invokers = events.forEntity(serverPlayer)) {
var result = invokers.get(EntityLeashEvent.ATTACH).onAttachLeash(entity, serverPlayer, null, serverPlayer, hand);
if (result == ActionResult.FAIL) {
var stack = player.getStackInHand(hand);

// notify the client that this action did not go through
int slot = hand == Hand.MAIN_HAND ? serverPlayer.getInventory().selectedSlot : 40;
serverPlayer.networkHandler.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(ScreenHandlerSlotUpdateS2CPacket.UPDATE_PLAYER_INVENTORY_SYNC_ID, 0, slot, stack));

return false;
}
}
}

return true;
}
}
51 changes: 51 additions & 0 deletions src/main/java/xyz/nucleoid/stimuli/mixin/entity/LeadItemMixin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package xyz.nucleoid.stimuli.mixin.entity;

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.entity.Entity;
import net.minecraft.entity.Leashable;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.LeadItem;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.ActionResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import xyz.nucleoid.stimuli.Stimuli;
import xyz.nucleoid.stimuli.event.entity.EntityLeashEvent;

import java.util.List;
import java.util.function.Predicate;

@Mixin(LeadItem.class)
public class LeadItemMixin {
@WrapOperation(
method = "attachHeldMobsToBlock",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/item/LeadItem;collectLeashablesAround(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Ljava/util/function/Predicate;)Ljava/util/List;"
)
)
private static List<Leashable> onLeashAttachToBlock(World world, BlockPos pos, Predicate<Leashable> predicate, Operation<List<Leashable>> original, @Local(argsOnly = true) PlayerEntity player) {
return original.call(world, pos, predicate).stream()
.filter(leashable -> {
// Leashable is a filtered Entity instance
var entity = (Entity) leashable;
var serverPlayer = (ServerPlayerEntity) player;

var events = Stimuli.select();

try (var invokers = events.forEntity(serverPlayer)) {
var result = invokers.get(EntityLeashEvent.ATTACH).onAttachLeash(entity, null, pos, serverPlayer, null);
if (result == ActionResult.FAIL) {
return false;
}
}

return true;
})
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package xyz.nucleoid.stimuli.mixin.entity;

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import com.llamalad7.mixinextras.sugar.Share;
import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef;
import net.minecraft.entity.Entity;
import net.minecraft.entity.Leashable;
import net.minecraft.entity.decoration.LeashKnotEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import xyz.nucleoid.stimuli.Stimuli;
import xyz.nucleoid.stimuli.event.entity.EntityLeashEvent;

@Mixin(LeashKnotEntity.class)
public class LeashKnotEntityMixin {
@WrapOperation(
method = "interact",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/entity/Leashable;getLeashHolder()Lnet/minecraft/entity/Entity;",
ordinal = 0
)
)
private Entity onLeashAttachToKnot(Leashable leashable, Operation<Entity> original, @Local(argsOnly = true) PlayerEntity player, @Local(argsOnly = true) Hand hand, @Share("attached") LocalBooleanRef attached) {
// Leashable is a filtered Entity instance
var entity = (Entity) leashable;

var leashHolder = (Entity) (Object) this;
var currentHolder = original.call(leashable);

// Vanilla reattaches leashes already attached to this knot, so these cases must be filtered out
if (leashHolder != currentHolder) {
var serverPlayer = (ServerPlayerEntity) player;

var events = Stimuli.select();

try (var invokers = events.forEntity(serverPlayer)) {
var result = invokers.get(EntityLeashEvent.ATTACH).onAttachLeash(entity, leashHolder, null, serverPlayer, hand);
if (result == ActionResult.FAIL) {
return null;
}
}
}

return currentHolder;
}
}
3 changes: 3 additions & 0 deletions src/main/resources/stimuli.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
"block.FarmlandBlockMixin",
"block.FlowerPotBlockMixin",
"block.TurtleEggBlockMixin",
"entity.EntityMixin",
"entity.LeadItemMixin",
"entity.LeashKnotEntityMixin",
"entity.LivingEntityMixin",
"entity.ShearableEntityMixin",
"entity.ShearsDispenserBehaviorMixin",
Expand Down
27 changes: 27 additions & 0 deletions src/testmod/java/xyz/nucleoid/stimuli/test/StimuliInitializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.slf4j.Logger;
import xyz.nucleoid.stimuli.Stimuli;
import xyz.nucleoid.stimuli.event.block.FlowerPotModifyEvent;
import xyz.nucleoid.stimuli.event.entity.EntityLeashEvent;
import xyz.nucleoid.stimuli.event.entity.EntityShearEvent;
import xyz.nucleoid.stimuli.event.projectile.ArrowFireEvent;
import xyz.nucleoid.stimuli.event.world.ExplosionDetonatedEvent;
Expand Down Expand Up @@ -58,5 +59,31 @@ public void onInitialize() {
Stimuli.global().listen(ExplosionDetonatedEvent.EVENT, (explosion, particles) -> {
server.sendMessage(Text.literal("ExplosionDetonatedEvent: " + explosion.getDestructionType().name()));
});
Stimuli.global().listen(EntityLeashEvent.ATTACH, (entity, leashHolder, leashPos, player, hand) -> {
var message = Text.literal("EntityLeashEvent.Attach: ")
.append(entity.getName());

if (leashHolder != null) {
message
.append(" to ")
.append(leashHolder.getName());
}

if (hand != null) {
message.append(" with " + hand);
}

if (leashPos != null) {
message.append(" at " + leashPos);
}

if (player == null) {
server.sendMessage(message);
} else {
player.sendMessage(message);
}

return result;
});
}
}