only when certain conditions are met. These are EXTREMELY TOUGH MONSTERS and it would be in your best interest to be extremely geared before challenging these bosses. ->Recommended Stats: ---->TANKS: 70+ HP, 98% Damage Reduction ---->Damage Dealers: 100+ Raw DPS, 50+ HP, 85% Damage Reduction ---->Recommended Party Size: 4 Members ---->Do not expect to be able to escape from these fights easily. It's kill, or be killed. Elites do not despawn. ->You can hunt for an Elite Monster by finding a 'Hunter's Compass'. These can be found as drops from Zombie Leaders rarely. ->Fixed a bug where Greed would end up having a negative % chance of breaking. ->Fixed a bug where 'Headshot!' and the "!" pre-emptive strike indicators were not appearing properly. ->Fix Rabbit's Foot not working for Malleable Bases. If you had a Malleable Base before this patch, you may type '/fix' while holding the bad bases to have the timer reset and continue. ->Fixed a bug where Explosions were not properly dealing damage to players. ->Fixed a bug where Explosions were ignoring Damage Reduction. ->Fixed a bug where monster damage was ignoring Absorption health. ->Name tags now cancel the naming process and are refunded if you try to name it something that would turn it into a stronger monster. (Example: Deadly Zombie) ->After death, players respawn with 10 seconds of invulnerability. ->Increased health pool of lower tier Zombie Leaders. ->Death Mark and AoE damage now appears in '/dps'. ->Greed knock off chance reduced dramatically. ->Ranger's Base arrow damage increased from x2 -> x4 again. ->Sniping mode now lowers Dodge Chance by 10% per Slowness stack, but gives you a Resistance buff equal to your Slowness level, and 10% Crit Chance per slowness stack. ->Enabling '/dps' now shows more colors when the number appears on-screen. Yellow = Critical Strike, Blue = Pre-emptive Strike, Red = Headshot, Aqua = Normal. ->Monsters now take damage from all sources of player damage. This means multiple players can release their damage onto one monster at once, enabling parties to damage monsters more effectively.dev
parent
db04feaa3b
commit
fafb4652a4
Binary file not shown.
@ -0,0 +1,426 @@ |
||||
package sig.plugin.TwosideKeeper; |
||||
|
||||
import java.text.DecimalFormat; |
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
|
||||
import org.bukkit.Bukkit; |
||||
import org.bukkit.ChatColor; |
||||
import org.bukkit.Color; |
||||
import org.bukkit.Location; |
||||
import org.bukkit.Material; |
||||
import org.bukkit.Particle; |
||||
import org.bukkit.Sound; |
||||
import org.bukkit.attribute.Attribute; |
||||
import org.bukkit.block.Block; |
||||
import org.bukkit.entity.AreaEffectCloud; |
||||
import org.bukkit.entity.Creeper; |
||||
import org.bukkit.entity.EntityType; |
||||
import org.bukkit.entity.FallingBlock; |
||||
import org.bukkit.entity.LivingEntity; |
||||
import org.bukkit.entity.Monster; |
||||
import org.bukkit.entity.Player; |
||||
import org.bukkit.metadata.FixedMetadataValue; |
||||
import org.bukkit.potion.PotionData; |
||||
import org.bukkit.potion.PotionEffect; |
||||
import org.bukkit.potion.PotionEffectType; |
||||
import org.bukkit.potion.PotionType; |
||||
import org.bukkit.util.Vector; |
||||
import org.inventivetalent.glow.GlowAPI; |
||||
|
||||
import sig.plugin.TwosideKeeper.HelperStructures.MonsterDifficulty; |
||||
import sig.plugin.TwosideKeeper.HelperStructures.Common.GenericFunctions; |
||||
|
||||
public class EliteMonster { |
||||
static int REFRESH_BUFFS = 20*30; |
||||
static int RESTORE_HEALTH = 20*10; |
||||
static float DEFAULT_MOVE_SPD = 0.4f; |
||||
static float FAST_MOVE_SPD = 0.65f; |
||||
static long BURST_TIME = 20*3; |
||||
static float BURST_LIMIT = 10f; |
||||
static int WEAKNESS_DURATION = 20*10; |
||||
static int POISON_DURATION = 20*10; |
||||
static int LEAP_COOLDOWN = 20*40; |
||||
static int ENRAGE_COOLDOWN = 20*60; |
||||
static int STORINGENERGY_COOLDOWN = 20*50; |
||||
static int GLOW_TIME = 20*1; |
||||
|
||||
Monster m; |
||||
long last_rebuff_time=0; |
||||
long last_regen_time=0; |
||||
long last_burstcheck_time=0; |
||||
long last_applyglow_time=0; |
||||
double hp_before_burstcheck=0; |
||||
double last_leap_time=0; |
||||
double last_enrage_time=0; |
||||
double last_storingenergy_time=0; |
||||
double last_storingenergy_health=0; |
||||
double storingenergy_hit=0; |
||||
boolean leaping=false; |
||||
boolean chasing=false; |
||||
boolean enraged=false; |
||||
boolean storingenergy=false; |
||||
Location target_leap_loc = null; |
||||
HashMap<Block,Material> storedblocks = new HashMap<Block,Material>(); |
||||
|
||||
List<Player> targetlist = new ArrayList<Player>(); |
||||
//Contains all functionality specific to Elite Monsters.
|
||||
//These are checked every 5 ticks, so have very high control over the monster itself.
|
||||
EliteMonster(Monster m) { |
||||
this.m=m; |
||||
m.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED).setBaseValue(DEFAULT_MOVE_SPD); |
||||
this.hp_before_burstcheck=m.getHealth(); |
||||
} |
||||
|
||||
public void runTick() { |
||||
//This monster constantly gives itself its buffs as it may lose some (Debilitation mode).
|
||||
dontDrown(); |
||||
if (m.isValid() && targetlist.size()>0) { |
||||
rebuff(); |
||||
regenerateHealth(); |
||||
moveFasterToTarget(); |
||||
weakenTeam(); |
||||
retargetInAir(); |
||||
destroyLiquids(2); |
||||
reapplyGlow(); |
||||
} |
||||
} |
||||
|
||||
private void dontDrown() { |
||||
m.setRemainingAir(m.getMaximumAir()); |
||||
} |
||||
|
||||
private void reapplyGlow() { |
||||
if (last_applyglow_time+GLOW_TIME<=TwosideKeeper.getServerTickTime()) { |
||||
GlowAPI.Color col = GlowAPI.Color.DARK_PURPLE; |
||||
if (m.hasPotionEffect(PotionEffectType.INCREASE_DAMAGE)) { |
||||
col = GlowAPI.Color.YELLOW; |
||||
} |
||||
if (storingenergy) { |
||||
col = GlowAPI.Color.GREEN; |
||||
} |
||||
GenericFunctions.setGlowing(m, col); |
||||
} |
||||
} |
||||
|
||||
private void destroyLiquids(int radius) { |
||||
for (int x=-radius;x<=radius;x++) { |
||||
for (int y=-radius;y<=radius;y++) { |
||||
for (int z=-radius;z<=radius;z++) { |
||||
Block b = m.getLocation().add(0,-0.9,0).getBlock().getRelative(x,y,z); |
||||
if (b.isLiquid()) { |
||||
b.setType(Material.AIR); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void retargetInAir() { |
||||
Player p = ChooseRandomTarget(); |
||||
if (p!=null) { |
||||
if (Math.random()<=0.2 && !p.isOnGround()) { |
||||
//p.addPotionEffect(new PotionEffect(PotionEffectType.LEVITATION,20*5,-31));
|
||||
p.addPotionEffect(new PotionEffect(PotionEffectType.JUMP,20*5,-1)); |
||||
m.setTarget(p); |
||||
p.setFlying(false); |
||||
p.setVelocity(new Vector(0,-1,0)); |
||||
p.removePotionEffect(PotionEffectType.LEVITATION); |
||||
p.addPotionEffect(new PotionEffect(PotionEffectType.CONFUSION,(int)(20*2.25),0)); |
||||
p.playSound(p.getLocation(), Sound.BLOCK_ANVIL_FALL, 0.4f, 0.8f); |
||||
p.playSound(p.getLocation(), Sound.ENTITY_MAGMACUBE_SQUISH, 1.0f, 1.0f); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void weakenTeam() { |
||||
if (last_burstcheck_time+BURST_TIME<=TwosideKeeper.getServerTickTime()) { |
||||
if (hp_before_burstcheck-BURST_LIMIT>m.getHealth()) { |
||||
//Apply a Weakness debuff aura based on how much stronger the team is.
|
||||
int weaknesslv = Math.min(8,(int)((hp_before_burstcheck-BURST_LIMIT)/BURST_LIMIT)); |
||||
createWeaknessCloud(m.getLocation(),weaknesslv); |
||||
} |
||||
last_burstcheck_time=TwosideKeeper.getServerTickTime(); |
||||
hp_before_burstcheck=m.getHealth(); |
||||
} |
||||
} |
||||
|
||||
private void createWeaknessCloud(Location loc, int weaknesslv) { |
||||
AreaEffectCloud lp = (AreaEffectCloud)loc.getWorld().spawnEntity(loc, EntityType.AREA_EFFECT_CLOUD); |
||||
lp.setColor(Color.BLACK); |
||||
DecimalFormat df = new DecimalFormat("0.00"); |
||||
lp.setCustomName("WEAK "+weaknesslv+" "+WEAKNESS_DURATION); |
||||
lp.setRadius(2f); |
||||
lp.setRadiusPerTick(0.5f/20); |
||||
lp.setDuration(20*5); |
||||
lp.setReapplicationDelay(5); |
||||
lp.setBasePotionData(new PotionData(PotionType.POISON)); |
||||
lp.setParticle(Particle.SPELL); |
||||
loc.getWorld().playSound(loc, Sound.ENTITY_HOSTILE_SPLASH, 1.0f, 1.0f); |
||||
} |
||||
|
||||
private void regenerateHealth() { |
||||
if (m.getHealth()<m.getMaxHealth() && last_regen_time+RESTORE_HEALTH<=TwosideKeeper.getServerTickTime()) { |
||||
m.setHealth(Math.min(m.getHealth()+1,m.getMaxHealth())); |
||||
} |
||||
} |
||||
|
||||
private void moveFasterToTarget() { |
||||
if (m.isInsideVehicle()) { |
||||
m.eject(); |
||||
} |
||||
LivingEntity l = m.getTarget(); |
||||
if (l!=null) { |
||||
if (l.isDead()) { |
||||
targetlist.remove(l); |
||||
if (targetlist.size()>0) { |
||||
m.setTarget(ChooseRandomTarget()); |
||||
} else { |
||||
m.setTarget(null); |
||||
} |
||||
} |
||||
if (!storingenergy) { |
||||
if (l.getLocation().distanceSquared(m.getLocation())>100 && !leaping) { |
||||
l.getWorld().playSound(l.getLocation(), Sound.ENTITY_CAT_HISS, 1.0f, 1.0f); |
||||
chasing=true; |
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(TwosideKeeper.plugin, new Runnable() { |
||||
public void run() { |
||||
m.teleport(l.getLocation().add(Math.random(),Math.random(),Math.random())); |
||||
l.addPotionEffect(new PotionEffect(PotionEffectType.SLOW,20*5,7)); |
||||
l.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS,20*5,7)); |
||||
chasing=false; |
||||
} |
||||
},20*2); |
||||
} else if (l.getLocation().distanceSquared(m.getLocation())>4) { |
||||
m.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED).setBaseValue(FAST_MOVE_SPD); |
||||
} else { |
||||
m.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED).setBaseValue(DEFAULT_MOVE_SPD); |
||||
} |
||||
} |
||||
|
||||
if (l.getLocation().getY()>m.getLocation().getY()+1) { |
||||
//Jump up to compensate. Move towards the player too.
|
||||
m.setVelocity((m.getLocation().getDirection()).add(new Vector(0,0.2*(l.getLocation().getY()-m.getLocation().getY()),0))); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void rebuff() { |
||||
if (last_rebuff_time+REFRESH_BUFFS<=TwosideKeeper.getServerTickTime()) { |
||||
last_rebuff_time=TwosideKeeper.getServerTickTime(); |
||||
m.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE,Integer.MAX_VALUE,8),true); |
||||
m.addPotionEffect(new PotionEffect(PotionEffectType.FIRE_RESISTANCE,Integer.MAX_VALUE,8),true); |
||||
//m.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION,Integer.MAX_VALUE,2),true);
|
||||
} |
||||
} |
||||
|
||||
//Triggers when this mob is hit.
|
||||
public void runHitEvent(LivingEntity damager) { |
||||
if (!targetlist.contains(damager) && (damager instanceof Player)) { |
||||
targetlist.add((Player)damager); |
||||
} |
||||
last_regen_time=TwosideKeeper.getServerTickTime(); |
||||
double randomrate = 0d; |
||||
if (!chasing && NewCombat.getPercentHealthRemaining(m)<=50) { |
||||
if (last_leap_time+LEAP_COOLDOWN<=TwosideKeeper.getServerTickTime()) { |
||||
performLeap(); |
||||
} |
||||
} |
||||
if (NewCombat.getPercentHealthRemaining(m)<=25) { |
||||
if (!leaping && !chasing && |
||||
last_storingenergy_time+STORINGENERGY_COOLDOWN<=TwosideKeeper.getServerTickTime()) { |
||||
last_storingenergy_time=TwosideKeeper.getServerTickTime(); |
||||
storingenergy=true; |
||||
for (int i=0;i<targetlist.size();i++) { |
||||
targetlist.get(i).sendMessage(ChatColor.GOLD+"The "+m.getCustomName()+ChatColor.GOLD+" is absorbing energy!"); |
||||
} |
||||
m.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED).setBaseValue(0f); |
||||
last_storingenergy_health=m.getHealth(); |
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(TwosideKeeper.plugin, new Runnable() { |
||||
public void run() { |
||||
Player target = ChooseRandomTarget(); |
||||
if (target!=null) { |
||||
if (last_storingenergy_health-m.getHealth()>0) { |
||||
storingenergy_hit=(last_storingenergy_health-m.getHealth())*90d; |
||||
for (int i=0;i<targetlist.size();i++) { |
||||
targetlist.get(i).sendMessage(ChatColor.GOLD+"The "+m.getCustomName()+ChatColor.GOLD+"'s next hit is stronger!"); |
||||
targetlist.get(i).sendMessage(ChatColor.DARK_RED+""+ChatColor.ITALIC+" \"DIE "+target.getName()+ChatColor.DARK_RED+"! DIEE!\""); |
||||
} |
||||
m.setTarget(target); |
||||
storingenergy=false; |
||||
} |
||||
} |
||||
} |
||||
},5*20); |
||||
} |
||||
} |
||||
if (NewCombat.getPercentHealthRemaining(m)<=10) { |
||||
if (last_enrage_time+ENRAGE_COOLDOWN<=TwosideKeeper.getServerTickTime()) { |
||||
last_enrage_time=TwosideKeeper.getServerTickTime(); |
||||
for (int i=0;i<targetlist.size();i++) { |
||||
targetlist.get(i).sendMessage(ChatColor.BOLD+""+ChatColor.YELLOW+"WARNING!"+ChatColor.RESET+ChatColor.GREEN+"The "+m.getCustomName()+ChatColor.GREEN+" is going into a tantrum!"); |
||||
} |
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(TwosideKeeper.plugin, new Runnable() { |
||||
public void run() { |
||||
if (!m.isDead()) { |
||||
for (int i=0;i<targetlist.size();i++) { |
||||
targetlist.get(i).sendMessage(ChatColor.RED+"The "+m.getCustomName()+ChatColor.RED+" becomes much stronger!"); |
||||
} |
||||
enraged=true; |
||||
if (m.hasPotionEffect(PotionEffectType.INCREASE_DAMAGE)) { |
||||
m.addPotionEffect(new PotionEffect(PotionEffectType.INCREASE_DAMAGE,ENRAGE_COOLDOWN*2,GenericFunctions.getPotionEffectLevel(PotionEffectType.INCREASE_DAMAGE, m)+15)); |
||||
} else { |
||||
m.addPotionEffect(new PotionEffect(PotionEffectType.INCREASE_DAMAGE,ENRAGE_COOLDOWN*2,14)); |
||||
} |
||||
} |
||||
}},20*20); |
||||
} |
||||
} |
||||
if (NewCombat.getPercentHealthRemaining(m)<=75 && |
||||
NewCombat.getPercentHealthRemaining(m)>50) { |
||||
randomrate = 1/16d; |
||||
} else |
||||
if (NewCombat.getPercentHealthRemaining(m)<=50 && |
||||
NewCombat.getPercentHealthRemaining(m)>25) { |
||||
randomrate = 1/8d; |
||||
} else |
||||
{ |
||||
randomrate = 1/4d; |
||||
} |
||||
if (Math.random()<=randomrate) { |
||||
EntityType choice = null; |
||||
switch ((int)(Math.random()*4)) { |
||||
case 0 :{ |
||||
choice = EntityType.ZOMBIE; |
||||
}break; |
||||
case 1 :{ |
||||
choice = EntityType.SKELETON; |
||||
}break; |
||||
case 2 :{ |
||||
choice = EntityType.CREEPER; |
||||
}break; |
||||
case 3 :{ |
||||
choice = EntityType.ENDERMAN; |
||||
}break; |
||||
default:{ |
||||
choice = EntityType.ZOMBIE; |
||||
} |
||||
} |
||||
Monster nm = (Monster)m.getWorld().spawnEntity(getNearbyFreeLocation(m.getLocation()),choice); |
||||
Player target = targetlist.get((int)(Math.random() * targetlist.size())); |
||||
nm.setTarget(target); |
||||
MonsterController.convertMonster(nm, MonsterDifficulty.HELLFIRE); |
||||
} |
||||
if (NewCombat.getPercentHealthRemaining(m)<10) { |
||||
Player target = targetlist.get((int)(Math.random() * targetlist.size())); |
||||
Creeper nm = (Creeper)m.getWorld().spawnEntity(target.getLocation().add(0,30,0),EntityType.CREEPER); |
||||
if (Math.random()<=0.5) { |
||||
nm.setPowered(true); |
||||
} |
||||
nm.setTarget(target); |
||||
MonsterController.convertMonster(nm, MonsterDifficulty.HELLFIRE); |
||||
} |
||||
} |
||||
|
||||
private void performLeap() { |
||||
last_leap_time = TwosideKeeper.getServerTickTime(); |
||||
int radius = (int)(6*(NewCombat.getPercentHealthMissing(m)/100d)); |
||||
//Choose a target randomly.
|
||||
Player target = ChooseRandomTarget(); |
||||
m.setTarget(target); |
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(TwosideKeeper.plugin, new Runnable() { |
||||
public void run() { |
||||
m.addPotionEffect(new PotionEffect(PotionEffectType.LEVITATION,Integer.MAX_VALUE,60)); |
||||
} |
||||
},8); |
||||
target_leap_loc = target.getLocation(); |
||||
m.addPotionEffect(new PotionEffect(PotionEffectType.LEVITATION,Integer.MAX_VALUE,20)); |
||||
for (int x=-radius;x<radius+1;x++) { |
||||
for (int z=-radius;z<radius+1;z++) { |
||||
Block b = target.getLocation().add(x,-0.9,z).getBlock(); |
||||
if (b.getType()!=Material.AIR && aPlugin.API.isDestroyable(b)) { |
||||
storedblocks.put(b, b.getType()); |
||||
b.setType(Material.STAINED_GLASS); |
||||
b.setData((byte)4); |
||||
} |
||||
} |
||||
} |
||||
leaping=true; |
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(TwosideKeeper.plugin, new Runnable() { |
||||
public void run() { |
||||
restoreBlocks(); |
||||
m.teleport(target_leap_loc); |
||||
leaping=false; |
||||
m.removePotionEffect(PotionEffectType.LEVITATION); |
||||
} |
||||
|
||||
private void restoreBlocks() { |
||||
for (Block b : storedblocks.keySet()) { |
||||
FallingBlock fb = (FallingBlock)b.getLocation().getWorld().spawnFallingBlock(b.getLocation(), storedblocks.get(b), b.getData()); |
||||
fb.setMetadata("FAKE", new FixedMetadataValue(TwosideKeeper.plugin,true)); |
||||
fb.setVelocity(new Vector(0,Math.random()*1.7,0)); |
||||
//b.setType(storedblocks.get(b));
|
||||
b.setType(Material.AIR); |
||||
aPlugin.API.sendSoundlessExplosion(target_leap_loc, 4); |
||||
b.getLocation().getWorld().playSound(b.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 0.7f, 1.2f); |
||||
} |
||||
storedblocks.clear(); |
||||
GenericFunctions.DealDamageToNearbyPlayers(target_leap_loc, 160, radius, true, 2); |
||||
} |
||||
},(int)(((20*4)*(NewCombat.getPercentHealthRemaining(m)/100d))+10)); |
||||
} |
||||
|
||||
private Player ChooseRandomTarget() { |
||||
if (targetlist.size()>0) { |
||||
return targetlist.get((int)(Math.random() * targetlist.size())); |
||||
} else { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
private Location getNearbyFreeLocation(Location l) { |
||||
int tries = 0; |
||||
while (tries<10) { |
||||
Location testloc = l.add((Math.random()*3)-(Math.random()*6),Math.random()*5,Math.random()*3-(Math.random()*6)); |
||||
Block testblock = testloc.getBlock(); |
||||
if (testblock.getType()==Material.AIR && testblock.getRelative(0, 1, 0).getType()==Material.AIR) { |
||||
return testloc; |
||||
} |
||||
} |
||||
return l; |
||||
} |
||||
|
||||
//Triggers when this mob hits something.
|
||||
public void hitEvent(LivingEntity ent) { |
||||
if (!targetlist.contains(ent) && (ent instanceof Player)) { |
||||
targetlist.add((Player)ent); |
||||
} |
||||
if (ent.hasPotionEffect(PotionEffectType.POISON)) { |
||||
int poisonlv = GenericFunctions.getPotionEffectLevel(PotionEffectType.POISON, ent); |
||||
ent.addPotionEffect(new PotionEffect(PotionEffectType.POISON,POISON_DURATION,poisonlv+1),true); |
||||
ent.addPotionEffect(new PotionEffect(PotionEffectType.SLOW_DIGGING,POISON_DURATION,poisonlv+1)); |
||||
} else { |
||||
ent.addPotionEffect(new PotionEffect(PotionEffectType.POISON,POISON_DURATION,0)); |
||||
ent.addPotionEffect(new PotionEffect(PotionEffectType.SLOW_DIGGING,POISON_DURATION,0)); |
||||
} |
||||
if (ent instanceof Player) { |
||||
Player p = (Player)ent; |
||||
if (storingenergy_hit>0) { |
||||
p.playSound(p.getLocation(), Sound.ENTITY_ZOMBIE_BREAK_DOOR_WOOD, 1.0f, 1.0f); |
||||
p.addPotionEffect(new PotionEffect(PotionEffectType.CONFUSION,20*4,0)); |
||||
TwosideKeeper.log("Got hit for "+storingenergy_hit+" damage!", 2); |
||||
GenericFunctions.DealDamageToMob(NewCombat.CalculateDamageReduction(storingenergy_hit,p,m),p,m); |
||||
storingenergy_hit=0; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public Monster getMonster() { |
||||
return m; |
||||
} |
||||
|
||||
public List<Player> getTargetList() { |
||||
return targetlist; |
||||
} |
||||
} |
Loading…
Reference in new issue