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

Initial implementation of Entity Interaction Events #183

Draft
wants to merge 21 commits into
base: 1.19
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
16 changes: 16 additions & 0 deletions library/entity/interaction/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
plugins {
id("qsl.module")
}

qslModule {
name = "Quilt Entity Interaction API"
moduleName = "interaction"
id = "quilt_entity_interaction"
description = "An API to support entity interaction events."
library = "entity"
moduleDependencies {
core {
api("qsl_base")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright 2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.quiltmc.qsl.entity.interaction.api;

import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.item.ItemStack;
import org.jetbrains.annotations.NotNull;

/**
* An encapsulation of several arguments relevant to a damage context with a
* mutable damage value and cancellation that is shared across all listeners.
*/
public class DamageContext {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Annotating members with @Nullable and @NotNull would be really helpful!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imo, nothing in it should ever be null. Should I just annotate everything with @notNull?


private final @NotNull LivingEntity attacker;
private final @NotNull ItemStack stack;
private final @NotNull Entity target;
private final @NotNull DamageSource source;
private float damage;
private boolean canceled = false;

public DamageContext(@NotNull LivingEntity attacker, @NotNull ItemStack stack, @NotNull Entity target, @NotNull DamageSource source, float damage) {
this.attacker = attacker;
this.stack = stack;
this.target = target;
this.source = source;
this.damage = damage;
}

/**
* Gets the attacking {@link LivingEntity} in the damage context.
* @return the attacking {@link LivingEntity}
*/
public @NotNull LivingEntity getAttacker() {
return this.attacker;
}

/**
* Gets the {@link ItemStack} in the attacker's main-hand.
* @return the {@link ItemStack} used by the attacker
*/
public @NotNull ItemStack getWeapon() {
return this.stack;
}

/**
* Gets the targeted {@link Entity} in the damage context.
* @return the targeted {@link Entity}
*/
public @NotNull Entity getTarget() {
return this.target;
}

/**
* Gets the {@link DamageSource} used in the damage context.
* @return the damage's {@link DamageSource}
*/
public @NotNull DamageSource getDamageSource() {
return source;
}

/**
* Gets the damage amount in the damage context.
* @return the damage amount
*/
public float getDamage() {
return damage;
}

/**
* Sets the damage amount in the damage context.
* @param damage the desired damage value
*/
public void setDamage(float damage) {
this.damage = damage;
}

/**
* Cancels the damage event.
*/
public void cancel() {
this.canceled = true;
}

/**
* Returns whether the event has been canceled or not.
* @return {@code true} if the event has been canceled, otherwise {@code false}
*/
public boolean isCanceled() {
return this.canceled;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright 2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.quiltmc.qsl.entity.interaction.api;

import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.item.ItemStack;
import org.quiltmc.qsl.base.api.event.Event;

/**
* Contains events that invoke when a {@link LivingEntity} attacks another.
*/
public class LivingEntityAttackEvents {

/**
* A callback that is invoked when a {@link LivingEntity} attacks another
* <strong>before</strong> the damage is dealt.
* <p>
* This event is cancellable by calling {@link DamageContext#cancel()}.
* <p>
* Implementations should not assume the hit will go through. Ideally, this
* event is used for conditionally cancelling or altering the damage value.
*/
public static final Event<Before> BEFORE = Event.create(Before.class,
callbacks -> context -> {
for (var callback : callbacks) {
callback.beforeDamage(context);

if (context.isCanceled()) return;
}
});

/**
* A callback that is invoked when a {@link LivingEntity} attacks another
* <strong>after</strong> the damage is dealt.
*/
public static final Event<After> AFTER = Event.create(After.class,
callbacks -> (attacker, stack, target, source, damage) -> {
for (var callback : callbacks) {
callback.afterDamage(attacker, stack, target, source, damage);
}
});



@FunctionalInterface
public interface Before {
/**
* Invoked <strong>before</strong> a {@link LivingEntity} damages another.
*
* @param context the {@link DamageContext} containing all the relevant arguments
*/
void beforeDamage(DamageContext context);
}

@FunctionalInterface
public interface After {
/**
* Invoked <strong>after</strong> a {@link LivingEntity} damages another.
*
* @param attacker the attacking entity
* @param stack the {@link ItemStack} in the attacker's main-hand
* @param target the target {@link LivingEntity}
* @param source the {@link DamageSource} of the attack
* @param damage the damage that was dealt
*/
void afterDamage(LivingEntity attacker, ItemStack stack, LivingEntity target, DamageSource source, float damage);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there should be two DamageContexts. A MutableDamageContext for the before event, and an ImmutableDamageContext (names very much up for debate) for the after event. Then these interfaces can even be merged into one.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So two more classes extending a abstract DamageContext? I think that can work.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The separation of the Before and After functional interfaces I believe are more for clarification purposes however. The documentation could become a bit confusing if they are merged.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming back to this, I think a separate MODIFY_DAMAGE event would be better. That way you have the ability to cancel it, modify it, or react to it in clear, separate events.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this would create a lot of duplicate code on the user's part. I think logic that determines cancellation or modifying damage would align a lot of the time and I'd rather not force users to duplicate code to perform these tasks.

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2016, 2017, 2018, 2019 FabricMC
* Copyright 2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.quiltmc.qsl.entity.interaction.api.player;

import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.World;
import org.quiltmc.qsl.base.api.event.Event;

/**
* A callback that is invoked if a player attacks (left-clicks) a block.
* <p>
* Is hooked before the spectator check, so make sure to check the player's game mode!
Quplet marked this conversation as resolved.
Show resolved Hide resolved
* <ul>
* <li>{@link ActionResult#SUCCESS} cancels further processing and, on the client, sends a packet to the server.</li>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linking the class of the packet would be useful! (also in all the other places that have similar docs)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Easy enough to do, but I'm not so sure why that would be helpful. All the packet really does is tell the logical server to process the event on their side.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just lets devs jump around the codebase a little easier, in case they want to look at how the interaction is processed

* <li>{@link ActionResult#PASS} falls back to further processing.</li>
* <li>{@link ActionResult#FAIL} cancels further processing and does not send a packet to the server.</li>
* </ul>
*/
public interface AttackBlockCallback {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is based on the QM name, but I don't like calling it "attacking" a block, because it's really the beginning of a player breaking (destroying) a block.

Would this maybe make more sense if it was part of BreakBlockEvents?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they have different purposes... its kinda hard to describe. The action of left clicking vs the event of breaking.
Break block events to me are designed to be acted upon the block's breaking more than the player's action while this is more the player's action than the block breaking, if that makes any sense.

I'm fine with moving it over tho if people agree it fits better with the block breaking events.


Event<AttackBlockCallback> EVENT = Event.create(AttackBlockCallback.class,
callbacks -> (player, world, stack, pos, direction) -> {
for (AttackBlockCallback callback : callbacks) {
ActionResult result = callback.onAttackBlock(player, world,stack, pos, direction);

if (result != ActionResult.PASS) return result;
}
return ActionResult.PASS;
});

/**
* Invoked if a player attacks (left-clicks) a block.
*
* @param player the player attacking the block
* @param world the world the event is occurring in
* @param pos the block's position
* @param direction the side of the block hit
* @return {@link ActionResult#SUCCESS} to cancel processing and send a packet to the server,
* {@link ActionResult#PASS} to fall back to further processing,
* {@link ActionResult#FAIL} to cancel further processing
*/
ActionResult onAttackBlock(PlayerEntity player, World world, ItemStack stack, BlockPos pos, Direction direction);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright 2016, 2017, 2018, 2019 FabricMC
* Copyright 2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.quiltmc.qsl.entity.interaction.api.player;

import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
import net.minecraft.world.World;
import org.quiltmc.qsl.base.api.event.Event;

public class AttackEntityEvents {

/**
* A callback that is invoked <strong>before</strong> a {@link PlayerEntity} attacks (left-clicks) an {@link Entity}.
* <p>
* This is invoked prior to the Spectator check, so make sure you check the game mode!
* Implementations should not assume the action has been completed.
* <p>
* Upon return:
* <ul>
* <li>{@link ActionResult#SUCCESS} cancels further processing and, on the client, sends a packet to the server.</li>
* <li>{@link ActionResult#PASS} falls back to further processing.</li>
* <li>{@link ActionResult#FAIL} cancels further processing and does not send a packet to the server.</li>
* </ul>
*/
public static final Event<Before> BEFORE = Event.create(Before.class,
callbacks -> (player, world, stack, entity) -> {
for (var callback : callbacks) {
ActionResult result = callback.beforeAttackEntity(player, world, stack, entity);

if (result != ActionResult.PASS) return result;
}
return ActionResult.PASS;
});

/**
* A callback that is invoked <strong>after</strong> a {@link PlayerEntity} attacks (left-clicks) an {@link Entity}.
*/
public static final Event<After> AFTER = Event.create(After.class,
callbacks -> (player, world, stack, entity) -> {
for (var callback : callbacks) {
callback.afterAttackEntity(player, world, stack, entity);
}
});

public interface Before {
/**
* Invoked <strong>before</strong> a {@link PlayerEntity} attacks (left-clicks) an {@link Entity}.
*
* @param player the interacting {@link PlayerEntity}
* @param world the {@link World} the event occurs in
* @param entity the hit {@link Entity}
* @return {@link ActionResult#SUCCESS} to cancel processing and send packet to the server,
* {@link ActionResult#PASS} to fall back to further processing,
* {@link ActionResult#FAIL} to cancel further processing
*/
ActionResult beforeAttackEntity(PlayerEntity player, World world, ItemStack stack, Entity entity);
}

public interface After {
/**
* Invoked <strong>after</strong> a {@link PlayerEntity} attacks (left-clicks) an {@link Entity}.
*
* @param player the interacting {@link PlayerEntity}
* @param world the {@link World} the event occurs in
* @param entity the hit {@link Entity}
*/
void afterAttackEntity(PlayerEntity player, World world, ItemStack stack, Entity entity);
}
}
Loading