Created
February 20, 2018 23:11
-
-
Save Exerosis/a4e41f6562fdc8777e7fdefe9bd7727a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package me.exerosis.example; | |
import org.bukkit.ChatColor; | |
import org.bukkit.Location; | |
import org.bukkit.World; | |
import org.bukkit.attribute.Attribute; | |
import org.bukkit.attribute.AttributeInstance; | |
import org.bukkit.attribute.AttributeModifier; | |
import org.bukkit.boss.BossBar; | |
import org.bukkit.entity.LivingEntity; | |
import org.bukkit.entity.Zombie; | |
import org.bukkit.event.EventHandler; | |
import org.bukkit.event.Listener; | |
import org.bukkit.event.entity.EntityDamageEvent; | |
import org.bukkit.event.entity.EntityDeathEvent; | |
import org.bukkit.event.player.PlayerJoinEvent; | |
import org.bukkit.inventory.ItemStack; | |
import org.bukkit.plugin.Plugin; | |
import org.bukkit.scheduler.BukkitRunnable; | |
import org.bukkit.util.Vector; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.function.Function; | |
import java.util.stream.Stream; | |
import static java.lang.Math.max; | |
import static java.lang.Math.sqrt; | |
import static java.util.concurrent.ThreadLocalRandom.current; | |
import static java.util.stream.Stream.concat; | |
import static java.util.stream.Stream.of; | |
import static org.bukkit.Bukkit.*; | |
import static org.bukkit.Material.IRON_SWORD; | |
import static org.bukkit.Material.NETHER_STAR; | |
import static org.bukkit.Particle.BLOCK_DUST; | |
import static org.bukkit.Sound.ENTITY_WITHER_DEATH; | |
import static org.bukkit.attribute.Attribute.*; | |
import static org.bukkit.attribute.AttributeModifier.Operation.ADD_SCALAR; | |
import static org.bukkit.boss.BarColor.RED; | |
import static org.bukkit.boss.BarStyle.SOLID; | |
import static org.bukkit.craftbukkit.v1_12_R1.inventory.CraftItemStack.asBukkitCopy; | |
import static org.bukkit.craftbukkit.v1_12_R1.inventory.CraftItemStack.asNMSCopy; | |
import static org.bukkit.entity.EntityType.ZOMBIE; | |
import static org.bukkit.event.EventPriority.MONITOR; | |
class BossZombie implements Listener { | |
private final Plugin plugin; | |
private final Zombie zombie; | |
private final BossBar bar; | |
public BossZombie(Plugin plugin, Location location) { | |
this.plugin = plugin; | |
getPluginManager().registerEvents(this, plugin); | |
World world = location.getWorld(); | |
//Ok first thing to do is spawn our boss into the world. | |
zombie = (Zombie) world.spawnEntity(location, ZOMBIE); | |
//Next up lets give it a scary name. | |
zombie.setCustomName(ChatColor.RED + "World Eater >:)"); | |
zombie.setCustomNameVisible(true); | |
//Then lets make it follow you to the ends of the earth. | |
scaleAttribute(zombie, GENERIC_FOLLOW_RANGE, "Scale Range", 10); | |
//We should create a boss bar so players can easily tell how much HP it has. | |
bar = createBossBar(zombie.getCustomName(), RED, SOLID); | |
getOnlinePlayers().forEach(bar::addPlayer); | |
//Now we need to make it a little tougher by modifying it's attributes. | |
//First lets increase it's health to 4 times a normal zombie | |
//then make sure he has full health. | |
AttributeInstance attribute = zombie.getAttribute(GENERIC_MAX_HEALTH); | |
attribute.addModifier(new AttributeModifier("Quad HP", 4, ADD_SCALAR)); | |
zombie.setHealth(attribute.getValue()); | |
//Next lets increase it's speed by 50% | |
scaleAttribute(zombie, GENERIC_MOVEMENT_SPEED, "Double Speed", 2); | |
//And it's DPS by 100% | |
scaleAttribute(zombie, GENERIC_ATTACK_DAMAGE, "Double Atk Damage", 2); | |
scaleAttribute(zombie, GENERIC_ATTACK_SPEED, "Double Atk Speed", 2); | |
//Now mainly for aesthetics lets give it a sword that does no damage. | |
//It makes the math easier when we want to edit the damage. | |
net.minecraft.server.v1_12_R1.ItemStack stack = asNMSCopy(new ItemStack(IRON_SWORD)); | |
if (stack.hasTag()) | |
stack.getTag().setInt("generic.attackDamage", 0); | |
zombie.getEquipment().setItemInMainHand(asBukkitCopy(stack)); | |
} | |
public class BossBabyZombie { | |
private final Zombie baby; | |
public BossBabyZombie() { | |
//We start by spawning the baby right on top of it's parent. | |
Location location = zombie.getEyeLocation(); | |
baby = (Zombie) location.getWorld().spawnEntity(location, ZOMBIE); | |
//Now we need to find it a good landing zone. | |
int x = location.getBlockY() + current().nextInt(-4, 4); | |
int z = location.getBlockY() + current().nextInt(-4, 4); | |
int y = location.getWorld().getHighestBlockYAt(x, z); | |
//Then we need to calculate a trajectory that will get it there with a nice ark. | |
baby.setVelocity(calculateVelocity(location.toVector(), new Vector(x, y, z), 2.5)); | |
//Boost em up a little | |
scaleAttribute(baby, GENERIC_MOVEMENT_SPEED, "Quad Speed", 4); | |
scaleAttribute(baby, GENERIC_ATTACK_DAMAGE, "Double Atk Damage", 2); | |
scaleAttribute(baby, GENERIC_FOLLOW_RANGE, "Scale Range", 10); | |
//Make sure they don't go running off. | |
baby.setTarget(zombie.getTarget()); | |
//Not entirely sure how this will look, but I figure we should give them a little trail. | |
new BukkitRunnable() { | |
@Override | |
public void run() { | |
baby.getWorld().spawnParticle(BLOCK_DUST, baby.getLocation(), 4, 1, 1, 1); | |
if (baby.isOnGround()) | |
cancel(); | |
} | |
}.runTaskTimer(plugin, 2, 2); | |
} | |
} | |
@EventHandler | |
void onJoin(PlayerJoinEvent event) { | |
//Show players the bar as they join. | |
bar.addPlayer(event.getPlayer()); | |
} | |
@EventHandler | |
void onDeath(EntityDeathEvent event) { | |
if (!event.getEntity().equals(zombie)) | |
return; | |
//Reward the player for a great kill! | |
event.setDroppedExp(10_000); | |
event.getDrops().add(new ItemStack(NETHER_STAR)); | |
//And play a crazy death sound. | |
zombie.getLocation().getWorld().playSound(zombie.getLocation(), ENTITY_WITHER_DEATH, 1, 1); | |
//Maybe even some blood particles if u want. | |
} | |
@EventHandler(priority = MONITOR, ignoreCancelled = true) | |
void onDamage(EntityDamageEvent event) { | |
if (!event.getEntity().equals(zombie)) | |
return; | |
//Get our zombies health as a percent of the max. | |
double health = asPercentage(zombie, $ -> zombie.getHealth() - event.getFinalDamage()); | |
//Check if our zombie is at less than 25% health. | |
if (health < 0.25) { | |
//Now we kick things up a notch by spitting out a bunch of little baby zombies. | |
new Runnable() { | |
List<Zombie> babies = new ArrayList<>(); | |
@Override | |
public void run() { | |
if (zombie.isDead()) | |
return; | |
//Update the boss bar to reflect the health of the babies. | |
BossBabyZombie baby = new BossBabyZombie(); | |
babies.add(baby.baby); | |
//Another confusing bit of code | |
//All it does is get the combine percent health of all the mobs. | |
double percentHealth = allZombies() | |
.map(zombie -> zombie.getAttribute(GENERIC_MAX_HEALTH).getValue()) | |
.reduce((a, b) -> a + b) | |
.orElse(0.0) | |
/ | |
allZombies() | |
.map(Zombie::getHealth) | |
.reduce((a, b) -> a + b) | |
.orElse(0.0); | |
bar.setProgress(percentHealth); | |
//Run this again later, but we want to have somewhat random intervals. | |
getScheduler().runTaskLater(plugin, this, current().nextInt(2 * 20)); | |
} | |
private Stream<Zombie> allZombies() { | |
return concat(of(zombie), babies.stream()); | |
} | |
}.run(); | |
} else | |
bar.setProgress(health); //Update the boss bar. | |
} | |
//This bit is a little confusing, but that's only because I'm being lazy ;( | |
private static double asPercentage(LivingEntity entity, Function<LivingEntity, Double> getHealth) { | |
return getHealth.apply(entity) / entity.getAttribute(GENERIC_MAX_HEALTH).getValue(); | |
} | |
//We could even make this public and stick it into an EntityUtil | |
//if we wanted to do this kind of thing a lot. | |
public static void scaleAttribute(LivingEntity entity, Attribute attribute, String name, double scalar) { | |
entity.getAttribute(attribute).addModifier(new AttributeModifier(name, scalar, ADD_SCALAR)); | |
} | |
//Mathy bits. | |
public static Vector calculateVelocity(Vector from, Vector to, double gain) { | |
double gravity = 0.667; | |
Vector subtract = to.subtract(from); | |
double vertical = subtract.getY(); | |
double horizontal = from.distance(to); | |
double maxGain = vertical + max(0, gain); | |
double a = -horizontal * horizontal / (4 * maxGain); | |
double slope = -horizontal / (2 * a) - sqrt(horizontal * horizontal - 4 * a * -vertical) / (2 * a); | |
double y = sqrt(maxGain * gravity); | |
return subtract.multiply(y / slope) | |
.multiply(1 / Math.sqrt(subtract.setY(0).lengthSquared())) | |
.setY(y); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment