Skip to content
Merged
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
Expand Up @@ -147,6 +147,7 @@
import com.leonardobishop.quests.bukkit.util.CompatUtils;
import com.leonardobishop.quests.bukkit.util.FormatUtils;
import com.leonardobishop.quests.bukkit.util.LogHistory;
import com.leonardobishop.quests.bukkit.util.Projectile2ItemCache;
import com.leonardobishop.quests.common.config.ConfigProblem;
import com.leonardobishop.quests.common.config.ConfigProblemDescriptions;
import com.leonardobishop.quests.common.config.QuestsConfig;
Expand Down Expand Up @@ -226,6 +227,7 @@ public class BukkitQuestsPlugin extends JavaPlugin implements Quests {
private QuestsBossBar bossBarHandle;
private QuestsActionBar actionBarHandle;
private VersionSpecificHandler versionSpecificHandler;
private Projectile2ItemCache projectile2ItemCache;

private LogHistory logHistory;
private WrappedTask questAutoSaveTask;
Expand Down Expand Up @@ -370,6 +372,10 @@ public void onEnable() {
};
}

// Instantiate Projectile to ItemStack cache
this.projectile2ItemCache = new Projectile2ItemCache();
this.projectile2ItemCache.registerEvents(this);

// Set item getter to be used by Quests config
this.questsConfig.setItemGetter(this.itemGetter);

Expand Down Expand Up @@ -972,6 +978,10 @@ public VersionSpecificHandler getVersionSpecificHandler() {
return versionSpecificHandler;
}

public Projectile2ItemCache getProjectile2ItemCache() {
return projectile2ItemCache;
}

public QuestItemRegistry getQuestItemRegistry() {
return questItemRegistry;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,13 @@ public interface VersionSpecificHandler {
/**
* {@link DamageSource}s were introduced in {@code 1.20.4}.
*/
@SuppressWarnings("UnstableApiUsage")
@Nullable Player getDamager(@Nullable EntityDamageEvent lastDamageCause);

/**
* {@link DamageSource}s were introduced in {@code 1.20.4}.
*/
@Nullable Entity getDirectSource(@Nullable EntityDamageEvent lastDamageCause);

/**
* {@link Tag#CANDLE_CAKES} was introduced in {@code 1.17}.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ public String getSmithMode(SmithItemEvent event) {
}
}

@SuppressWarnings("UnstableApiUsage")
@Override
public @Nullable Player getDamager(@Nullable EntityDamageEvent event) {
if (!DAMAGE_SOURCE_API) {
Expand All @@ -74,4 +73,18 @@ public String getSmithMode(SmithItemEvent event) {

return null;
}

@Override
public @Nullable Entity getDirectSource(@Nullable EntityDamageEvent event) {
if (!DAMAGE_SOURCE_API) {
return super.getDamager(event);
}

if (event == null) {
return null;
}

DamageSource source = event.getDamageSource();
return source.getDirectEntity();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,11 @@ public List<Entity> getPassengers(Entity entity) {
return null;
}

@Override
public @Nullable Entity getDirectSource(@Nullable EntityDamageEvent lastDamageCause) {
return null;
}

@Override
public boolean isCake(Material type) {
return type == Material.CAKE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ public void onReady() {

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityDamage(EntityDamageEvent event) {
Entity entity = event.getEntity();
Player player = plugin.getVersionSpecificHandler().getDamager(event);

if (player == null || player.hasMetadata("NPC")) {
return;
}
Expand All @@ -57,10 +55,15 @@ public void onEntityDamage(EntityDamageEvent event) {
return;
}

Entity entity = event.getEntity();
if (!(entity instanceof Damageable damageable)) {
return;
}

Entity directSource = plugin.getVersionSpecificHandler().getDirectSource(event);
ItemStack bowItem = directSource != null ? plugin.getProjectile2ItemCache().getItem(directSource) : null;
ItemStack item = bowItem != null ? bowItem : plugin.getVersionSpecificHandler().getItemInMainHand(player);

// Clamp entity damage as getDamage() returns Float.MAX_VALUE for killing a parrot with a cookie
// https://github.com/LMBishop/Quests/issues/753
double finalDamage = event.getFinalDamage();
Expand All @@ -87,7 +90,6 @@ public void onEntityDamage(EntityDamageEvent event) {
}

if (task.hasConfigKey("item")) {
ItemStack item = plugin.getVersionSpecificHandler().getItemInMainHand(player);
if (item == null) {
super.debug("Specific item is required, player has no item in hand; continuing...", quest.getId(), task.getId(), player.getUniqueId());
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ private void handle(Player player, LivingEntity entity, int eventAmount) {
return;
}

EntityDamageEvent lastDamageCause = entity.getLastDamageCause();
Entity directSource = plugin.getVersionSpecificHandler().getDirectSource(lastDamageCause);
ItemStack bowItem = directSource != null ? plugin.getProjectile2ItemCache().getItem(directSource) : null;
ItemStack item = bowItem != null ? bowItem : plugin.getVersionSpecificHandler().getItemInMainHand(player);

//noinspection deprecation
String customName = entity.getCustomName();

Expand Down Expand Up @@ -144,7 +149,6 @@ private void handle(Player player, LivingEntity entity, int eventAmount) {
}

if (task.hasConfigKey("item")) {
ItemStack item = plugin.getVersionSpecificHandler().getItemInMainHand(player);
if (item == null) {
super.debug("Specific item is required, player has no item in hand; continuing...", quest.getId(), task.getId(), player.getUniqueId());
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ public void onEntityDamage(EntityDamageEvent event) {
}

private void handle(final EntityDamageEvent event, final String mobName, final double level) {
Entity entity = event.getEntity();
Player player = plugin.getVersionSpecificHandler().getDamager(event);

if (player == null || player.hasMetadata("NPC")) {
Expand All @@ -112,10 +111,15 @@ private void handle(final EntityDamageEvent event, final String mobName, final d
return;
}

Entity entity = event.getEntity();
if (!(entity instanceof Damageable damageable)) {
return;
}

Entity directSource = plugin.getVersionSpecificHandler().getDirectSource(event);
ItemStack bowItem = directSource != null ? plugin.getProjectile2ItemCache().getItem(directSource) : null;
ItemStack item = bowItem != null ? bowItem : plugin.getVersionSpecificHandler().getItemInMainHand(player);

// Clamp entity damage as getDamage() returns Float.MAX_VALUE for killing a parrot with a cookie
// https://github.com/LMBishop/Quests/issues/753
double finalDamage = event.getFinalDamage();
Expand Down Expand Up @@ -147,7 +151,6 @@ private void handle(final EntityDamageEvent event, final String mobName, final d
}

if (task.hasConfigKey("item")) {
ItemStack item = plugin.getVersionSpecificHandler().getItemInMainHand(player);
if (item == null) {
super.debug("Specific item is required, player has no item in hand; continuing...", quest.getId(), task.getId(), player.getUniqueId());
continue;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.leonardobishop.quests.bukkit.util;

import com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent;
import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityShootBowEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.PluginManager;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

import java.util.Map;
import java.util.WeakHashMap;

/**
* Provides a cache that links projectiles to the item used to fire them.
*
* <p>This cache exists because damage-related events do not expose
* information about the item from which a projectile was fired.
* By capturing this association at the time the projectile is created,
* the item can later be retrieved when handling damage events.</p>
*/
@NullMarked
public final class Projectile2ItemCache implements Listener {

private final Map<Entity, @Nullable ItemStack> backingMap;

public Projectile2ItemCache() {
this.backingMap = WeakHashMap.newWeakHashMap(1024);
}

public void registerEvents(final BukkitQuestsPlugin plugin) {
final PluginManager pluginManager = plugin.getServer().getPluginManager();

pluginManager.registerEvents(this, plugin);

try {
Class.forName("com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent");
pluginManager.registerEvents(new PlayerLaunchProjectileListener(), plugin);
} catch (final ClassNotFoundException e) {
// not supported on Spigot
}
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityShootBow(final EntityShootBowEvent event) {
final LivingEntity shooter = event.getEntity();
final Entity projectile = event.getProjectile();
final ItemStack bow = event.getBow();

// Currently there are no advantages of caching projectiles for non-player arrows.
// It would be needed to cache these if we needed a task to take damage from mobs.
if (shooter instanceof Player) {
this.backingMap.put(projectile, bow);
}
}

public final class PlayerLaunchProjectileListener implements Listener {

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerLaunchProjectile(final PlayerLaunchProjectileEvent event) {
final Projectile projectile = event.getProjectile();
final ItemStack item = event.getItemStack();

// TODO: doesn't really work for tridents
// https://github.com/LMBishop/Quests/pull/833
Projectile2ItemCache.this.backingMap.put(projectile, item);
Comment thread
Krakenied marked this conversation as resolved.
}
}

public @Nullable ItemStack getItem(final Entity projectile) {
return this.backingMap.get(projectile);
}
}
3 changes: 0 additions & 3 deletions docs/task-types/dealdamage-(task-type).md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ Deal a certain amount of damage.
| `exact-match` | Whether the item should exactly match what is defined. | Boolean | No | true | \- |
| `worlds` | Worlds which should count towards the progress. | List of world names | No | \- | \- |

{: .caution }
It's not possible to use item option for projectile weapons (like bow or other projectile shooters). Currently, the API is insufficient to implement such a feature without any unwanted side effects.

## Examples

Deal 100 HP of damage:
Expand Down