-
-
Notifications
You must be signed in to change notification settings - Fork 85
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
base: 1.19
Are you sure you want to change the base?
Changes from all commits
5f89cdb
f049b20
4673b65
2afbca2
08f1a12
f172a97
3eccaf4
37f755f
2284a90
9168151
a4d1824
fa5dbf1
68c5fff
8f45bff
1264907
fa68216
8e0b114
7ba2570
8b23b17
98e4734
9a57c84
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 { | ||
|
||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if there should be two There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So two more classes extending a abstract There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The separation of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Coming back to this, I think a separate There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. 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); | ||
} | ||
} |
There was a problem hiding this comment.
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!There was a problem hiding this comment.
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
?