diff --git a/api-internal/src/main/java/at/pavlov/internal/projectile/definition/CustomProjectileDefinition.java b/api-internal/src/main/java/at/pavlov/internal/projectile/definition/CustomProjectileDefinition.java index 6fee1ad2..71f16475 100644 --- a/api-internal/src/main/java/at/pavlov/internal/projectile/definition/CustomProjectileDefinition.java +++ b/api-internal/src/main/java/at/pavlov/internal/projectile/definition/CustomProjectileDefinition.java @@ -4,8 +4,11 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Map; + @Getter @Builder @AllArgsConstructor @@ -29,28 +32,5 @@ public class CustomProjectileDefinition implements ProjectilePhysics { private final @Nullable Key material; private final @Nullable Integer customModelData; - /* - @Override - public double getGravity() { - return fromKey().getGravity(); - } - - @Override - public double getDrag() { - return fromKey().getDrag(); - } - - @Override - public double getWaterDrag() { - return fromKey().getWaterDrag(); - } - - private @NotNull ProjectilePhysics fromKey() { - DefaultProjectileDefinition value = Registries.DEFAULT_PROJECTILE_DEFINITION_REGISTRY.of(entityKey); - if (value == null) { - return ProjectilePhysics.DEFAULT; - } - - return value; - }*/ + private final @NotNull Map attributes; } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/listener/EntityListener.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/listener/EntityListener.java index f5bad471..6c92c78b 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/listener/EntityListener.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/listener/EntityListener.java @@ -5,8 +5,10 @@ import at.pavlov.cannons.multiversion.EventResolver; import at.pavlov.cannons.projectile.ProjectileManager; import at.pavlov.cannons.utils.EventUtils; +import org.bukkit.entity.LivingEntity; 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.EntityDeathEvent; import org.bukkit.event.entity.EntityExplodeEvent; @@ -40,7 +42,7 @@ public void onProjectileHitEntity(ProjectileHitEvent event) { * @param event */ @EventHandler(ignoreCancelled = true) - public void EntityExplode(EntityExplodeEvent event) { + public void onEntityExplode(EntityExplodeEvent event) { plugin.logDebug("Explode event listener called"); if (!EventResolver.isValidExplosion(event)) { @@ -49,4 +51,14 @@ public void EntityExplode(EntityExplodeEvent event) { EventUtils.handleExplosion(event.blockList()); } + + /** + * If living entity projectile gets shot down and killed, make it detonate mid air or wherever it is + * @param event + */ + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onEntityDeath(EntityDeathEvent event) { + LivingEntity entity = event.getEntity(); + ProjectileManager.getInstance().detonateProjectile(entity); + } } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/ProjectileManager.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/ProjectileManager.java index e3bafef4..7fa83e6f 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/ProjectileManager.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/ProjectileManager.java @@ -4,30 +4,27 @@ import at.pavlov.cannons.CreateExplosion; import at.pavlov.cannons.Enum.ProjectileCause; import at.pavlov.cannons.dao.AsyncTaskManager; +import at.pavlov.internal.Key; import at.pavlov.internal.key.registries.Registries; import at.pavlov.internal.projectile.definition.CustomProjectileDefinition; import at.pavlov.internal.projectile.definition.ProjectilePhysics; import com.google.common.base.Preconditions; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.entity.AbstractArrow; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.ItemDisplay; -import org.bukkit.entity.Player; -import org.bukkit.entity.ThrowableProjectile; -import org.bukkit.entity.WitherSkull; +import org.bukkit.*; +import org.bukkit.attribute.Attributable; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.entity.*; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; import org.bukkit.util.Vector; import org.jetbrains.annotations.NotNull; +import java.nio.charset.StandardCharsets; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -public class ProjectileManager -{ +public class ProjectileManager { private static ProjectileManager instance = null; private final Cannons plugin; @@ -46,6 +43,7 @@ public static ProjectileManager getInstance() { /** * ProjectileManager + * * @param plugin - Cannons instance */ private ProjectileManager(Cannons plugin) { @@ -58,8 +56,8 @@ public Entity spawnProjectile(Projectile projectile, @NotNull UUID shooter, org. //set yaw, pitch for fireballs double v = velocity.length(); - spawnLoc.setPitch((float) (Math.acos(velocity.getY()/v)*180.0/Math.PI - 90)); - spawnLoc.setYaw((float) (Math.atan2(velocity.getZ(),velocity.getX())*180.0/Math.PI - 90)); + spawnLoc.setPitch((float) (Math.acos(velocity.getY() / v) * 180.0 / Math.PI - 90)); + spawnLoc.setYaw((float) (Math.atan2(velocity.getZ(), velocity.getX()) * 180.0 / Math.PI - 90)); Entity projectileEntity = spawnProjectile(projectile, spawnLoc, velocity, world); @@ -78,17 +76,22 @@ public Entity spawnProjectile(Projectile projectile, @NotNull UUID shooter, org. return projectileEntity; } + private static final NamespacedKey MOB_TYPE_KEY = new NamespacedKey(Cannons.getPlugin(), "mob_type"); private @NotNull Entity spawnProjectile(Projectile projectile, Location spawnLoc, Vector velocity, World world) { Entity entity = world.spawnEntity(spawnLoc, projectile.getProjectileEntity()); //calculate firing vector entity.setVelocity(velocity); - CustomProjectileDefinition definition = Registries.CUSTOM_PROJECTILE_DEFINITION.of(projectile.getProjectileDefinitionKey()); + Key customEntityKey = projectile.getProjectileDefinitionKey(); + CustomProjectileDefinition definition = Registries.CUSTOM_PROJECTILE_DEFINITION.of(customEntityKey); if (definition == null) { return entity; } + // allow people to use custom textures for entities + entity.getPersistentDataContainer().set(MOB_TYPE_KEY, PersistentDataType.STRING, customEntityKey.full()); + entity.setVisualFire(definition.isOnFire()); entity.setGlowing(definition.isGlowing()); @@ -101,6 +104,18 @@ public Entity spawnProjectile(Projectile projectile, @NotNull UUID shooter, org. entity.setGravity(false); } + if (entity instanceof LivingEntity livingEntity) { + livingEntity.setAI(false); + } + + if (entity instanceof Attributable attributable) { + handleAttributable(attributable, definition, entity); + } + + if (entity instanceof Display display) { + display.setTeleportDuration(1); + } + if (entity instanceof WitherSkull witherSkull) { witherSkull.setCharged(definition.isCharged()); } else if (entity instanceof AbstractArrow arrow) { @@ -138,9 +153,36 @@ public Entity spawnProjectile(Projectile projectile, @NotNull UUID shooter, org. return entity; } + private static void handleAttributable(Attributable attributable, CustomProjectileDefinition definition, Entity entity) { + UUID uuid = UUID.nameUUIDFromBytes("cannon:attribute".getBytes(StandardCharsets.UTF_8)); + for (var entry : definition.getAttributes().entrySet()) { + String attrKey = entry.getKey(); + Attribute attribute = Registry.ATTRIBUTE.get(NamespacedKey.minecraft(attrKey)); + + if (attribute == null) { + Cannons.getPlugin().logSevere("Attribute [" + attrKey + "] doesn't exist"); + continue; + } + + var attributeInstance = attributable.getAttribute(attribute); + if (attributeInstance == null) { + Cannons.getPlugin().logSevere("Attribute [" + attrKey + "] not found for entity " + entity.getType()); + continue; + } + + // todo: this AttributeModifier constructor is marked for removal, might want to replace with a more robust implementation + attributeInstance.addModifier( + new AttributeModifier( + uuid, "attribute_definition", entry.getValue(), AttributeModifier.Operation.ADD_NUMBER + ) + ); + } + } + /** * detonate a timefused projectile mid air + * * @param cannonball - the cannonball to detonate */ private void detonateTimefuse(final FlyingProjectile cannonball) { @@ -164,23 +206,19 @@ private void detonateTimefuse(final FlyingProjectile cannonball) { projectile_entity.remove(); } flyingProjectilesMap.remove(cannonball.getUID()); - }, (long) (cannonball.getProjectile().getTimefuse()*20)); + }, (long) (cannonball.getProjectile().getTimefuse() * 20)); } /** * detonates the given projectile entity + * * @param projectile - the projectile with this entity */ - public void detonateProjectile(Entity projectile) - { - if(projectile == null || !(projectile instanceof org.bukkit.entity.Projectile)) - return; - + public void detonateProjectile(Entity projectile) { FlyingProjectile fproj = flyingProjectilesMap.get(projectile.getUniqueId()); - if (fproj!=null) - { - CreateExplosion.getInstance().detonate(fproj, (org.bukkit.entity.Projectile) projectile); + if (fproj != null) { + CreateExplosion.getInstance().detonate(fproj, projectile); projectile.remove(); flyingProjectilesMap.remove(fproj.getUID()); } @@ -188,11 +226,12 @@ public void detonateProjectile(Entity projectile) /** * detonates the given projectile entity + * * @param cannonball - the projectile with this entity - * @param target the entity hit by the projectile + * @param target the entity hit by the projectile */ public void directHitProjectile(Entity cannonball, Entity target) { - if(cannonball == null || target == null) return; + if (cannonball == null || target == null) return; FlyingProjectile fproj = flyingProjectilesMap.get(cannonball.getUniqueId()); if (fproj == null) { @@ -211,11 +250,11 @@ public void directHitProjectile(Entity cannonball, Entity target) { /** * returns true if the given entity is a cannonball projectile + * * @param projectile flying projectile * @return true if cannonball projectile */ - public boolean isFlyingProjectile(Entity projectile) - { + public boolean isFlyingProjectile(Entity projectile) { FlyingProjectile fproj = flyingProjectilesMap.get(projectile.getUniqueId()); return fproj != null; } @@ -223,21 +262,21 @@ public boolean isFlyingProjectile(Entity projectile) /** * returns the list of all flying projectiles + * * @return - the list of all flying projectiles */ - public ConcurrentHashMap getFlyingProjectiles() - { + public ConcurrentHashMap getFlyingProjectiles() { return flyingProjectilesMap; } /** * returns the projectile of which the player is passenger * if the player is attached to a projectile he will follow its movement + * * @param player is the passenger * @return the projectile or null */ - public FlyingProjectile getAttachedProjectile(Player player) - { + public FlyingProjectile getAttachedProjectile(Player player) { if (player == null) { return null; } diff --git a/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/definitions/ProjectileDefinitionLoader.java b/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/definitions/ProjectileDefinitionLoader.java index 0bbc1008..9f76a4ba 100644 --- a/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/definitions/ProjectileDefinitionLoader.java +++ b/cannons-bukkit/src/main/java/at/pavlov/cannons/projectile/definitions/ProjectileDefinitionLoader.java @@ -8,8 +8,10 @@ import at.pavlov.internal.projectile.definition.ProjectilePhysics; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; import java.io.File; +import java.util.HashMap; public class ProjectileDefinitionLoader { public static void reload() { @@ -43,7 +45,7 @@ private static void loadEntry(YamlConfiguration cfg, String node) { dpd = ProjectilePhysics.DEFAULT; } - CustomProjectileDefinition definition = CustomProjectileDefinition.builder() + CustomProjectileDefinition.CustomProjectileDefinitionBuilder builder = CustomProjectileDefinition.builder() .key(key) .entityKey(entityKey) .constantAcceleration(section.getObject("constantAcceleration", Double.class, dpd.getConstantAcceleration())) @@ -55,9 +57,18 @@ private static void loadEntry(YamlConfiguration cfg, String node) { .charged(section.getBoolean("charged")) .critical(section.getBoolean("critical")) .material(Key.from(section.getString("material", "SNOWBALL"))) - .customModelData(section.getObject("customModelData", Integer.class, null)) - .build(); + .customModelData(section.getObject("customModelData", Integer.class, null)); - Registries.CUSTOM_PROJECTILE_DEFINITION.register(definition); + var attributeSection = section.getConfigurationSection("attributes"); + HashMap attributeMap = new HashMap<>(); + if (attributeSection != null) { + for (var attrName : attributeSection.getKeys(false)) { + var value = attributeSection.getDouble(attrName); + attributeMap.put(attrName, value); + } + } + + builder = builder.attributes(attributeMap); + Registries.CUSTOM_PROJECTILE_DEFINITION.register(builder.build()); } } diff --git a/cannons-bukkit/src/main/resources/projectile_definitions.yml b/cannons-bukkit/src/main/resources/projectile_definitions.yml index 43607025..e72bacd9 100644 --- a/cannons-bukkit/src/main/resources/projectile_definitions.yml +++ b/cannons-bukkit/src/main/resources/projectile_definitions.yml @@ -18,6 +18,10 @@ # charged: false # works only for wither skulls, makes it charged # critical: false # works for arrows and tridents, makes the try visible # +# # available for all entities that are Attributable, generally all living entities have this +# attributes: +# scale: 2.0 +# # # this stuff is for throwable projectiles, such as snowballs, and item displays # material: SNOWBALL # you can set any material https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html # customModelData: 123 # must be an integer, texturepack magic number goes here