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

Adding more events to silk-core & silk-paper #62

Draft
wants to merge 7 commits into
base: main
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 net.silkmc.silk.core.mixin.entity;

import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.behavior.BehaviorUtils;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import net.silkmc.silk.core.event.EntityEvents;
import net.silkmc.silk.core.event.EventScopeProperty;
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(BehaviorUtils.class)
public class MixinBehaviorUtils {

@Inject(
method = "throwItem(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/phys/Vec3;F)V",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/Level;addFreshEntity(Lnet/minecraft/world/entity/Entity;)Z",
shift = At.Shift.BEFORE
),
cancellable = true
)
private static void throwItem(LivingEntity livingEntity, ItemStack itemStack, Vec3 vec3, Vec3 vec32, float f, CallbackInfo ci, @Local ItemEntity itemEntity) {
final var event = new EntityEvents.EntityDropItemEvent(
livingEntity,
itemEntity,
new EventScopeProperty<>(false)
);

EntityEvents.INSTANCE.getDropItem().invoke(event);

if (event.isCancelled().get()) {
ci.cancel();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package net.silkmc.silk.core.mixin.entity;

import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.ItemStack;
import net.silkmc.silk.core.event.EntityEvents;
import net.silkmc.silk.core.event.EventScopeProperty;
import org.spongepowered.asm.mixin.Mixin;
Expand Down Expand Up @@ -33,4 +36,29 @@ private void onIsInvulnerableTo(DamageSource damageSource,
cir.setReturnValue(event.isInvulnerable().get());
}
}

@Inject(
method = "spawnAtLocation(Lnet/minecraft/world/item/ItemStack;F)Lnet/minecraft/world/entity/item/ItemEntity;",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/Level;addFreshEntity(Lnet/minecraft/world/entity/Entity;)Z",
shift = At.Shift.BEFORE
),
cancellable = true
)
private void onSpawnAtLocation(ItemStack itemStack, float f, CallbackInfoReturnable<ItemEntity> cir, @Local ItemEntity itemEntity) {
if (cir.getReturnValue() == null) return;

final var event = new EntityEvents.EntityDropItemEvent(
(Entity) (Object) this,
itemEntity,
new EventScopeProperty<>(false)
);

EntityEvents.INSTANCE.getDropItem().invoke(event);

if (event.isCancelled().get()) {
cir.setReturnValue(null);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
package net.silkmc.silk.core.mixin.entity;

import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.silkmc.silk.core.event.EntityEvents;
import net.silkmc.silk.core.event.EventScopeProperty;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

@Mixin(LivingEntity.class)
public class MixinLivingEntity {
public abstract class MixinLivingEntity {
@Shadow public abstract ItemStack eat(Level level, ItemStack itemStack);

@Unique
private Boolean isDroppingLoot = false;

@Inject(
method = "actuallyHurt",
Expand All @@ -21,4 +33,78 @@ private void onActuallyHurt(DamageSource damageSource,
EntityEvents.INSTANCE.getDamageLivingEntity()
.invoke(new EntityEvents.EntityDamageEvent((LivingEntity) (Object) this, amount, damageSource));
}

@Inject(
method = "createWitherRose",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/Level;addFreshEntity(Lnet/minecraft/world/entity/Entity;)Z",
shift = At.Shift.BEFORE
),
cancellable = true
)
private void onCreateWitherRose(LivingEntity livingEntity, CallbackInfo ci, @Local ItemEntity itemEntity) {
// Death event - start
if (isDroppingLoot) {
ci.cancel();
return;
}
// Death event - end

// Drop item event - start
final var event = new EntityEvents.EntityDropItemEvent(
livingEntity,
itemEntity,
new EventScopeProperty<>(false)
);

EntityEvents.INSTANCE.getDropItem().invoke(event);

if (event.isCancelled().get()) {
ci.cancel();
}
// Drop item event - end
}

@Inject(
method = "die",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/damagesource/DamageSource;getEntity()Lnet/minecraft/world/entity/Entity;",
shift = At.Shift.BEFORE
),
cancellable = true
)
private void onDie(DamageSource source, CallbackInfo ci) {
var entity = (LivingEntity) (Object) this;
final var event = new EntityEvents.EntityDeathEvent(
entity,
source,
new EventScopeProperty<>(true),
new EventScopeProperty<>(false)
);

EntityEvents.INSTANCE.getDeath().invoke(event);

if (event.isCancelled().get()) {
entity.setHealth(1F);
ci.cancel();
return;
}

isDroppingLoot = event.isDroppingLoot().get();
}

@Inject(
method = "dropAllDeathLoot",
at = @At("HEAD"),
cancellable = true
)
private void onDropAllDeathLoot(ServerLevel serverLevel, DamageSource damageSource, CallbackInfo ci) {
// Death event - start
if (isDroppingLoot) {
ci.cancel();
}
// Death event - end
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package net.silkmc.silk.core.mixin.entity;

import com.llamalad7.mixinextras.sugar.Local;
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.damagesource.DamageSource;
import net.silkmc.silk.core.event.EventScopeProperty;
import net.silkmc.silk.core.event.PlayerEvents;
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(ServerPlayer.class)
public class MixinServerPlayer {

@Inject(
method = "die",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/server/network/ServerGamePacketListenerImpl;send(Lnet/minecraft/network/protocol/Packet;Lnet/minecraft/network/PacketSendListener;)V",
shift = At.Shift.BEFORE
)
)
private void onDie(DamageSource damageSource, CallbackInfo ci, @Local LocalRef<Component> component) {
var event = new PlayerEvents.PlayerDeathMessageEvent(
(ServerPlayer) (Object) this,
new EventScopeProperty<>(component.get())
);

PlayerEvents.INSTANCE.getDeathMessage().invoke(event);

component.set(event.getDeathMessage().get());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package net.silkmc.silk.core.mixin.entity;

import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerPlayerGameMode;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.silkmc.silk.core.event.EventScopeProperty;
import net.silkmc.silk.core.event.PlayerEvents;
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.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Mixin(ServerPlayerGameMode.class)
public class MixinServerPlayerGameMode {

@Shadow
@Final
protected ServerPlayer player;

@Inject(
method = "destroyBlock",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/block/Block;playerWillDestroy(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/entity/player/Player;)Lnet/minecraft/world/level/block/state/BlockState;",
shift = At.Shift.BEFORE
),
cancellable = true
)
private void onBlockDestroy(BlockPos blockPos, CallbackInfoReturnable<Boolean> cir, @Local BlockEntity entity, @Local BlockState state) {
var event = new PlayerEvents.PlayerBlockBreakEvent(player, blockPos, state, entity, new EventScopeProperty<>(false));

PlayerEvents.INSTANCE.getBlockBreak().invoke(event);

if (event.isCancelled().get()) {
cir.setReturnValue(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package net.silkmc.silk.core.event
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.ItemStack
import net.silkmc.silk.core.annotations.ExperimentalSilkApi

@ExperimentalSilkApi
Expand Down Expand Up @@ -42,4 +44,35 @@ object EntityEvents {
* event, since Minecraft performs its checks more than one time
*/
val checkInvulnerability = Event.onlySync<EntityCheckInvulnerabilityEvent>()

open class EntityDropItemEvent(
entity: Entity,
val item: ItemEntity,
val isCancelled: EventScopeProperty<Boolean>
) : EntityEvent<Entity>(entity)

/**
* Called when an entity is about to drop an item.
* This event allows listeners to modify the item that is being dropped or cancel the drop.
*
* Note: this event is not called for items that are dropped as a result of the entity dying
*
* TODO: detect player dropping on single player worlds
*/
val dropItem = Event.onlySync<EntityDropItemEvent>()

open class EntityDeathEvent(
entity: LivingEntity,
val source: DamageSource,
val isDroppingLoot: EventScopeProperty<Boolean>,
val isCancelled: EventScopeProperty<Boolean>
) : EntityEvent<LivingEntity>(entity)

/**
* Called when a [LivingEntity] is about to die.
* This event allows listeners to prevent loot dropping or cancel the death.
*
* Note: player deaths cannot be canceled using this event
*/
val death = Event.onlySync<EntityDeathEvent>()
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package net.silkmc.silk.core.event

import com.mojang.authlib.GameProfile
import net.minecraft.core.BlockPos
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.silkmc.silk.core.annotations.ExperimentalSilkApi

@ExperimentalSilkApi
Expand Down Expand Up @@ -60,4 +64,27 @@ object PlayerEvents {
* @see preQuit
*/
val quitDuringConfiguration = Event.syncAsync<PlayerQuitDuringLoginEvent>()

open class PlayerDeathMessageEvent(
player: ServerPlayer,
val deathMessage: EventScopeProperty<Component>,
): PlayerEvent<ServerPlayer>(player)

/**
* Called when a player dies and sends a death message. This event will not trigger if death messages are disabled.
*/
val deathMessage = Event.syncAsync<PlayerDeathMessageEvent>()

open class PlayerBlockBreakEvent(
player: Player,
val blockPos: BlockPos,
val blockState: BlockState,
val blockEntity: BlockEntity?,
val isCancelled: EventScopeProperty<Boolean>,
): PlayerEvent<Player>(player)

/**
* Called when a player breaks a block. This event is triggered before the block is broken.
*/
val blockBreak = Event.syncAsync<PlayerBlockBreakEvent>()
}
3 changes: 3 additions & 0 deletions silk-core/src/main/resources/silk-core.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
"compatibilityLevel": "JAVA_21",
"mixins": [
"block.AbstractBlockAccessor",
"entity.MixinBehaviorUtils",
"entity.MixinEntity",
"entity.MixinLivingEntity",
"entity.MixinServerPlayer",
"entity.MixinServerPlayerGameMode",
"server.MixinMinecraftServer",
"server.MixinPlayerList",
"server.MixinServerConfigurationPacketListenerImpl",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package net.silkmc.silk.paper.conversions

import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer
import net.minecraft.core.RegistryAccess
import net.minecraft.network.chat.Component
import net.minecraft.server.MinecraftServer
import net.minecraft.server.dedicated.DedicatedServer
import net.minecraft.server.level.ServerEntity
import net.minecraft.server.level.ServerPlayer
import net.silkmc.silk.core.text.serializeToPrettyJson
import org.bukkit.Server
import org.bukkit.craftbukkit.CraftServer
import org.bukkit.craftbukkit.entity.CraftEntity
Expand All @@ -29,3 +33,9 @@ val Player.mcPlayer: ServerPlayer
*/
val Entity.mcEntity: net.minecraft.world.entity.Entity
get() = (this as CraftEntity).handle

/**
* Converts a [Component] to an adventure [net.kyori.adventure.text.Component].
*/
val Component.adventureComponent: net.kyori.adventure.text.Component
get() = GsonComponentSerializer.gson().deserialize(Component.Serializer.toJson(this, RegistryAccess.EMPTY))
Loading