diff --git a/TwosideKeeper.jar b/TwosideKeeper.jar index 9c364d9..398521c 100644 Binary files a/TwosideKeeper.jar and b/TwosideKeeper.jar differ diff --git a/src/sig/plugin/TwosideKeeper/ChargeZombie.java b/src/sig/plugin/TwosideKeeper/ChargeZombie.java index 471c836..197ccab 100644 --- a/src/sig/plugin/TwosideKeeper/ChargeZombie.java +++ b/src/sig/plugin/TwosideKeeper/ChargeZombie.java @@ -38,8 +38,34 @@ public class ChargeZombie { Math.abs(z)m.getLocation().getY()) || !m.getLocation().add(x,y,z).getBlock().getType().isSolid()) { //Player is higher than zombie. Don't break blocks in front of it. Climb up them. Unless it's lava. - if (!(y<0 && (m.getTarget().getLocation().getY()>m.getLocation().getY()-1))) { //Player is lower than zombie. Break blocks below it to get to the player. + if (m.getTarget()!=null && m.getTarget().isValid()) { + if (!(y==0 && m.getTarget().getLocation().getY()>m.getLocation().getY()) || !m.getLocation().add(x,y,z).getBlock().getType().isSolid()) { //Player is higher than zombie. Don't break blocks in front of it. Climb up them. Unless it's lava. + if (!(y<0 && (m.getTarget().getLocation().getY()>m.getLocation().getY()-1))) { //Player is lower than zombie. Break blocks below it to get to the player. + boolean brokeliquid = false; + //Break it. + if (ChanceToBreak(m.getLocation().add(x,y,z).getBlock())) { + if (m.getLocation().add(x,y,z).getBlock().getType()==Material.WATER || + m.getLocation().add(x,y,z).getBlock().getType()==Material.STATIONARY_WATER || + m.getLocation().add(x,y,z).getBlock().getType()==Material.LAVA || + m.getLocation().add(x,y,z).getBlock().getType()==Material.STATIONARY_LAVA) { + brokeliquid=true; + if (m.getLocation().add(x,y,z).getBlock().getType()==Material.STATIONARY_LAVA) { + m.getLocation().add(x,y,z).getBlock().setType(Material.OBSIDIAN); + SoundUtils.playGlobalSound(m.getLocation().add(x,y,z),Sound.BLOCK_FIRE_EXTINGUISH, 1f, 1f); + } + } + if (!brokeliquid) { + SoundUtils.playGlobalSound(m.getLocation().add(x,y,z),Sound.BLOCK_STONE_BREAK, 1.0f, 1.0f); + } + m.getLocation().add(x,y,z).getBlock().breakNaturally(); + aPlugin.API.sendBlockBreakPacket(m.getLocation().add(x,y,z).getBlock(), -1); + } else { + aPlugin.API.sendBlockBreakPacket(m.getLocation().add(x,y,z).getBlock(), (int)(Math.random()*6)+3); + } + } + } + } else { + if (y>=0) { boolean brokeliquid = false; //Break it. if (ChanceToBreak(m.getLocation().add(x,y,z).getBlock())) { diff --git a/src/sig/plugin/TwosideKeeper/CustomDamage.java b/src/sig/plugin/TwosideKeeper/CustomDamage.java index f2b7507..8664280 100644 --- a/src/sig/plugin/TwosideKeeper/CustomDamage.java +++ b/src/sig/plugin/TwosideKeeper/CustomDamage.java @@ -76,9 +76,12 @@ import sig.plugin.TwosideKeeper.HelperStructures.Utils.EntityUtils; import sig.plugin.TwosideKeeper.HelperStructures.Utils.IndicatorType; import sig.plugin.TwosideKeeper.HelperStructures.Utils.SoundUtils; import sig.plugin.TwosideKeeper.HolidayEvents.Christmas; +import sig.plugin.TwosideKeeper.Monster.DarkSpider; +import sig.plugin.TwosideKeeper.Monster.DarkSpiderMinion; import sig.plugin.TwosideKeeper.Monster.Dummy; import sig.plugin.TwosideKeeper.Monster.HellfireGhast; import sig.plugin.TwosideKeeper.Monster.HellfireSpider; +import sig.plugin.TwosideKeeper.Monster.Knight; public class CustomDamage { @@ -1559,6 +1562,10 @@ public class CustomDamage { wi.runHitEvent(p, dmg); } } + if (TwosideKeeper.custommonsters.containsKey(target.getUniqueId())) { + CustomMonster cm = TwosideKeeper.custommonsters.get(target.getUniqueId()); + cm.onHitEvent(p, dmg); + } if (target instanceof Villager) { Villager v = (Villager)target; /*for (UUID id : TwosideKeeper.custommonsters.keySet()) { @@ -1791,8 +1798,28 @@ public class CustomDamage { addHellfireGhastToList(m); addBlazeToList(m); addWitherToList(m); + addKnighttoList(m); + removeStraySpiderMinions(m); } + private static void removeStraySpiderMinions(LivingEntity m) { + if (!TwosideKeeper.custommonsters.containsKey(m.getUniqueId()) && + DarkSpiderMinion.isDarkSpiderMinion(m)) { + m.remove(); + } + } + + private static void addKnighttoList(LivingEntity m) { + if (!TwosideKeeper.custommonsters.containsKey(m.getUniqueId()) && + (Knight.isKnight(m) || + (m instanceof Skeleton && + Knight.randomlyConvertAsKnight(m)))) { + TwosideKeeper.custommonsters.put(m.getUniqueId(),new Knight(m)); + TwosideKeeper.log("Spawned a new "+LivingEntityStructure.getCustomLivingEntityName(m), 0); + TwosideKeeper.LAST_SPECIAL_SPAWN=TwosideKeeper.getServerTickTime(); + } + } + private static void addWitherToList(LivingEntity m) { if (!TwosideKeeper.custommonsters.containsKey(m.getUniqueId()) && m instanceof Wither) { @@ -2298,6 +2325,12 @@ public class CustomDamage { } les.checkedforcubes=true; } + if (Knight.isKnight(target)) { + dmgreductiondiv += Knight.getDamageReduction(); + } else + if (DarkSpider.isDarkSpider(target)){ + dmgreductiondiv += DarkSpider.getDamageReduction(); + } } for (int i=0;i DealDamageToNearbyPlayers(Location l, double basedmg, int range, boolean knockup, boolean dodgeable, double knockupamt, Entity damager, String reason, boolean truedmg) { + return DealDamageToNearbyPlayers(l,basedmg,range,knockup,dodgeable,knockupamt,damager,reason,truedmg,false); + } + + public static List DealDamageToNearbyPlayers(Location l, double basedmg, int range, boolean knockup, boolean dodgeable, double knockupamt, Entity damager, String reason, boolean truedmg, boolean truepctdmg) { List players = getNearbyPlayers(l,range); //We cleared the non-living entities, deal damage to the rest. for (Player p : players) { @@ -3962,12 +3967,16 @@ public class GenericFunctions { /*if (knockup && p.getHealth()>0) { //Prevent knockups if we die to the attack. p.setVelocity(new Vector(0,knockupamt,0)); }*/ - if (CustomDamage.ApplyDamage(basedmg, damager, p, null, reason, (truedmg)?CustomDamage.TRUEDMG:CustomDamage.NONE)) { + if (truepctdmg) { + basedmg = p.getMaxHealth()*basedmg; + } + if (CustomDamage.ApplyDamage(basedmg, damager, p, null, reason, (truedmg|truepctdmg)?(CustomDamage.TRUEDMG|CustomDamage.IGNORE_DAMAGE_TICK|(dodgeable?CustomDamage.NONE:CustomDamage.IGNOREDODGE)):CustomDamage.NONE)) { if (knockup && p.getHealth()>0) { //Prevent knockups if we die to the attack. p.setVelocity(new Vector(0,knockupamt,0)); } } } + return players; } /** @@ -4670,7 +4679,6 @@ public class GenericFunctions { set.add(Material.STATIONARY_WATER); Block b = player.getTargetBlock(set, 100); if (b!=null && b.getType()!=Material.AIR) { - SoundUtils.playGlobalSound(player.getLocation(), Sound.BLOCK_NOTE_BASS, 1.0f, 1.0f); Vector dir = player.getLocation().getDirection(); //player.teleport(); Location blockcenter = b.getLocation().add(0.5,0.5,0.5); @@ -4707,6 +4715,7 @@ public class GenericFunctions { blockcenter.getWorld().spawnParticle(Particle.NOTE, teleportloc, 5); teleportloc.setDirection(dir); player.teleport(teleportloc); + SoundUtils.playGlobalSound(teleportloc, Sound.BLOCK_NOTE_BASS, 1.0f, 1.0f); PlayerStructure pd = PlayerStructure.GetPlayerStructure(player); pd.lastusedassassinate=TwosideKeeper.getServerTickTime(); if (name!=Material.SKULL_ITEM || pd.lastlifesavertime+GetModifiedCooldown(TwosideKeeper.LIFESAVER_COOLDOWN,player)death_time) { + return false; + } + return true; + } + + protected void createParticles() { + //loc.getWorld().spawnParticle(Particle.END_ROD, loc.clone().add(0,-SLASH_SIZE/2,0), 4); + for (int i=0;i0) {GenericFunctions.DealDamageToNearbyPlayers(loc, dmg.getTruePctDmgComponent(), SLASH_SIZE, false, true, 0, l, "Dark Slash", false, true);} + if (dmg.getTrueDmgComponent()>0) {GenericFunctions.DealDamageToNearbyPlayers(loc, dmg.getTrueDmgComponent(), SLASH_SIZE, false, true, 0, l, "Dark Slash", true, false);} + } +} diff --git a/src/sig/plugin/TwosideKeeper/HelperStructures/Effects/TemporaryBlock.java b/src/sig/plugin/TwosideKeeper/HelperStructures/Effects/TemporaryBlock.java index 181eb99..57c1a1c 100644 --- a/src/sig/plugin/TwosideKeeper/HelperStructures/Effects/TemporaryBlock.java +++ b/src/sig/plugin/TwosideKeeper/HelperStructures/Effects/TemporaryBlock.java @@ -195,10 +195,9 @@ public class TemporaryBlock { public static boolean isTemporaryBlock(Block b) { return TwosideKeeper.temporaryblocks.containsKey(TemporaryBlock.getLocationKey(b)); } - @Deprecated public static boolean isStandingOnSpecialBlock(Location l, String specialKey) { //return TwosideKeeper.temporaryblocks.containsKey(TemporaryBlock.getLocationKey(b)); - Block b = l.getBlock(); + Block b = l.getBlock().getRelative(0, -1, 0); //TwosideKeeper.log(b.toString(), 0); if (b!=null) { return TwosideKeeper.temporaryblocks.containsKey(TemporaryBlock.getLocationKey(b,specialKey)); @@ -206,7 +205,6 @@ public class TemporaryBlock { return false; } } - @Deprecated public static boolean isInRangeOfSpecialBlock(Location l, double range, String specialKey) { Block b = l.getBlock(); //TwosideKeeper.log(b.toString(), 0); diff --git a/src/sig/plugin/TwosideKeeper/HelperStructures/Effects/WindSlash.java b/src/sig/plugin/TwosideKeeper/HelperStructures/Effects/WindSlash.java index 0f79db2..731f0e7 100644 --- a/src/sig/plugin/TwosideKeeper/HelperStructures/Effects/WindSlash.java +++ b/src/sig/plugin/TwosideKeeper/HelperStructures/Effects/WindSlash.java @@ -3,6 +3,7 @@ package sig.plugin.TwosideKeeper.HelperStructures.Effects; import org.bukkit.Location; import org.bukkit.Particle; import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.util.Vector; @@ -11,11 +12,13 @@ import sig.plugin.TwosideKeeper.TwosideKeeper; import sig.plugin.TwosideKeeper.HelperStructures.Common.GenericFunctions; import sig.plugin.TwosideKeeper.HelperStructures.Utils.BlockUtils; import sig.plugin.TwosideKeeper.HelperStructures.Utils.SoundUtils; +import sig.plugin.TwosideKeeper.HelperStructures.Utils.Classes.MixedDamage; public class WindSlash { Location loc; Player sourcep; - double dmg; + LivingEntity l; + MixedDamage dmg; long lasteffect; long death_time; final static int EFFECT_DENSITY = 20; @@ -27,6 +30,15 @@ public class WindSlash { public WindSlash(Location loc, Player p, double dmg, int tick_duration) { this.loc=loc.clone().add(0,p.getEyeHeight(),0); this.sourcep=p; + this.dmg=MixedDamage.v(dmg); + this.death_time = TwosideKeeper.getServerTickTime()+tick_duration; + this.lasteffect=TwosideKeeper.getServerTickTime(); + SoundUtils.playGlobalSound(loc,Sound.BLOCK_PORTAL_TRIGGER, 0.2f, 2.0f); + } + + public WindSlash(Location loc, LivingEntity l, MixedDamage dmg, int tick_duration) { + this.loc=loc.clone().add(0,l.getEyeHeight(),0); + this.l=l; this.dmg=dmg; this.death_time = TwosideKeeper.getServerTickTime()+tick_duration; this.lasteffect=TwosideKeeper.getServerTickTime(); @@ -46,10 +58,10 @@ public class WindSlash { } private void damageNearbyTargets() { - GenericFunctions.DealDamageToNearbyMobs(loc, dmg, SLASH_SIZE, false, 0, sourcep, sourcep.getEquipment().getItemInMainHand(), false, "Wind Slash"); + GenericFunctions.DealDamageToNearbyMobs(loc, dmg.getDmgComponent(), SLASH_SIZE, false, 0, sourcep, sourcep.getEquipment().getItemInMainHand(), false, "Wind Slash"); } - private boolean moveWindSlash() { + protected boolean moveWindSlash() { Location origloc = loc.clone(); Vector move = origloc.getDirection().setY(origloc.getDirection().getY()/1.4).multiply(SPEED_MULT); float dist = SPEED_MULT; @@ -64,7 +76,7 @@ public class WindSlash { //TwosideKeeper.log("New Location: "+loc, 0); } - private void createParticles() { + protected void createParticles() { loc.getWorld().spawnParticle(Particle.SWEEP_ATTACK, loc.clone().add(0,-SLASH_SIZE/2,0), 2); for (int i=0;i players, int r, int g, int b) { + ParticleEffect.valueOf(name).display(r/255, g / 255, b / 255, 1, 0, location, players); + } + public void send(Location location, int Distance, int r, int g, int b) { + ParticleEffect.valueOf(name).display(r/255, g / 255, b / 255, 1, 0, location, Distance); + } + +} \ No newline at end of file diff --git a/src/sig/plugin/TwosideKeeper/HelperStructures/Utils/Classes/MixedDamage.java b/src/sig/plugin/TwosideKeeper/HelperStructures/Utils/Classes/MixedDamage.java new file mode 100644 index 0000000..91a8702 --- /dev/null +++ b/src/sig/plugin/TwosideKeeper/HelperStructures/Utils/Classes/MixedDamage.java @@ -0,0 +1,48 @@ +package sig.plugin.TwosideKeeper.HelperStructures.Utils.Classes; + +public class MixedDamage { + double dmgcomponent; + double truepctdmgcomponent; + double truedmgcomponent; + + public MixedDamage(double dmg) { + this.dmgcomponent=dmg; + this.truepctdmgcomponent=0; + this.truedmgcomponent=0; + } + public MixedDamage(double dmg,double truepctdmg) { + this.dmgcomponent=dmg; + this.truepctdmgcomponent=truepctdmg; + this.truedmgcomponent=0; + } + public MixedDamage(double dmg,double truepctdmg,double truedmg) { + this.dmgcomponent=dmg; + this.truepctdmgcomponent=truepctdmg; + this.truedmgcomponent=truedmg; + } + public static MixedDamage v(double dmg) { + return new MixedDamage(dmg); + } + public static MixedDamage v(double dmg,double truepctdmg) { + return new MixedDamage(dmg,truepctdmg); + } + public static MixedDamage v(double dmg,double truepctdmg,double truedmg) { + return new MixedDamage(dmg,truepctdmg,truedmg); + } + public double getDmgComponent() { + return dmgcomponent; + } + public double getTruePctDmgComponent() { + return truepctdmgcomponent; + } + public double getTrueDmgComponent() { + return truedmgcomponent; + } + + public boolean hasTruePctDmgComponent() { + return truepctdmgcomponent>0; + } + public boolean hasTrueDmgComponent() { + return truedmgcomponent>0; + } +} diff --git a/src/sig/plugin/TwosideKeeper/HelperStructures/Utils/Classes/ParticleEffect.java b/src/sig/plugin/TwosideKeeper/HelperStructures/Utils/Classes/ParticleEffect.java new file mode 100644 index 0000000..45f4821 --- /dev/null +++ b/src/sig/plugin/TwosideKeeper/HelperStructures/Utils/Classes/ParticleEffect.java @@ -0,0 +1,1606 @@ +package sig.plugin.TwosideKeeper.HelperStructures.Utils.Classes; + +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import sig.plugin.TwosideKeeper.HelperStructures.Utils.Classes.ReflectionUtils.PackageType; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * ParticleEffect Library + *

+ * This library was created by @DarkBlade12 and allows you to display all Minecraft particle effects on a Bukkit server + *

+ * You are welcome to use it, modify it and redistribute it under the following conditions: + *

    + *
  • Don't claim this class as your own + *
  • Don't remove this disclaimer + *
+ *

+ * Special thanks: + *

    + *
  • @microgeek (original idea, names and packet parameters) + *
  • @ShadyPotato (1.8 names, ids and packet parameters) + *
  • @RingOfStorms (particle behavior) + *
  • @Cybermaxke (particle behavior) + *
  • @JamieSinn (hosting a jenkins server and documentation for particleeffect) + *
+ *

+ * It would be nice if you provide credit to me if you use this class in a published project + * + * @author DarkBlade12 + * @version 1.7 + */ +public enum ParticleEffect { + /** + * A particle effect which is displayed by exploding tnt and creepers: + *

    + *
  • It looks like a white cloud + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + EXPLOSION_NORMAL("explode", 0, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by exploding ghast fireballs and wither skulls: + *
    + *
  • It looks like a gray ball which is fading away + *
  • The speed value slightly influences the size of this particle effect + *
+ */ + EXPLOSION_LARGE("largeexplode", 1, -1), + /** + * A particle effect which is displayed by exploding tnt and creepers: + *
    + *
  • It looks like a crowd of gray balls which are fading away + *
  • The speed value has no influence on this particle effect + *
+ */ + EXPLOSION_HUGE("hugeexplosion", 2, -1), + /** + * A particle effect which is displayed by launching fireworks: + *
    + *
  • It looks like a white star which is sparkling + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + FIREWORKS_SPARK("fireworksSpark", 3, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by swimming entities and arrows in water: + *
    + *
  • It looks like a bubble + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + WATER_BUBBLE("bubble", 4, -1, ParticleProperty.DIRECTIONAL, ParticleProperty.REQUIRES_WATER), + /** + * A particle effect which is displayed by swimming entities and shaking wolves: + *
    + *
  • It looks like a blue drop + *
  • The speed value has no influence on this particle effect + *
+ */ + WATER_SPLASH("splash", 5, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed on water when fishing: + *
    + *
  • It looks like a blue droplet + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + WATER_WAKE("wake", 6, 7, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by water: + *
    + *
  • It looks like a tiny blue square + *
  • The speed value has no influence on this particle effect + *
+ */ + SUSPENDED("suspended", 7, -1, ParticleProperty.REQUIRES_WATER), + /** + * A particle effect which is displayed by air when close to bedrock and the in the void: + *
    + *
  • It looks like a tiny gray square + *
  • The speed value has no influence on this particle effect + *
+ */ + SUSPENDED_DEPTH("depthSuspend", 8, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed when landing a critical hit and by arrows: + *
    + *
  • It looks like a light brown cross + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + CRIT("crit", 9, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed when landing a hit with an enchanted weapon: + *
    + *
  • It looks like a cyan star + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + CRIT_MAGIC("magicCrit", 10, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by primed tnt, torches, droppers, dispensers, end portals, brewing stands and monster spawners: + *
    + *
  • It looks like a little gray cloud + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + SMOKE_NORMAL("smoke", 11, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by fire, minecarts with furnace and blazes: + *
    + *
  • It looks like a large gray cloud + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + SMOKE_LARGE("largesmoke", 12, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed when splash potions or bottles o' enchanting hit something: + *
    + *
  • It looks like a white swirl + *
  • The speed value causes the particle to only move upwards when set to 0 + *
  • Only the motion on the y-axis can be controlled, the motion on the x- and z-axis are multiplied by 0.1 when setting the values to 0 + *
+ */ + SPELL("spell", 13, -1), + /** + * A particle effect which is displayed when instant splash potions hit something: + *
    + *
  • It looks like a white cross + *
  • The speed value causes the particle to only move upwards when set to 0 + *
  • Only the motion on the y-axis can be controlled, the motion on the x- and z-axis are multiplied by 0.1 when setting the values to 0 + *
+ */ + SPELL_INSTANT("instantSpell", 14, -1), + /** + * A particle effect which is displayed by entities with active potion effects: + *
    + *
  • It looks like a colored swirl + *
  • The speed value causes the particle to be colored black when set to 0 + *
  • The particle color gets lighter when increasing the speed and darker when decreasing the speed + *
+ */ + SPELL_MOB("mobSpell", 15, -1, ParticleProperty.COLORABLE), + /** + * A particle effect which is displayed by entities with active potion effects applied through a beacon: + *
    + *
  • It looks like a transparent colored swirl + *
  • The speed value causes the particle to be always colored black when set to 0 + *
  • The particle color gets lighter when increasing the speed and darker when decreasing the speed + *
+ */ + SPELL_MOB_AMBIENT("mobSpellAmbient", 16, -1, ParticleProperty.COLORABLE), + /** + * A particle effect which is displayed by witches: + *
    + *
  • It looks like a purple cross + *
  • The speed value causes the particle to only move upwards when set to 0 + *
  • Only the motion on the y-axis can be controlled, the motion on the x- and z-axis are multiplied by 0.1 when setting the values to 0 + *
+ */ + SPELL_WITCH("witchMagic", 17, -1), + /** + * A particle effect which is displayed by blocks beneath a water source: + *
    + *
  • It looks like a blue drip + *
  • The speed value has no influence on this particle effect + *
+ */ + DRIP_WATER("dripWater", 18, -1), + /** + * A particle effect which is displayed by blocks beneath a lava source: + *
    + *
  • It looks like an orange drip + *
  • The speed value has no influence on this particle effect + *
+ */ + DRIP_LAVA("dripLava", 19, -1), + /** + * A particle effect which is displayed when attacking a villager in a village: + *
    + *
  • It looks like a cracked gray heart + *
  • The speed value has no influence on this particle effect + *
+ */ + VILLAGER_ANGRY("angryVillager", 20, -1), + /** + * A particle effect which is displayed when using bone meal and trading with a villager in a village: + *
    + *
  • It looks like a green star + *
  • The speed value has no influence on this particle effect + *
+ */ + VILLAGER_HAPPY("happyVillager", 21, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by mycelium: + *
    + *
  • It looks like a tiny gray square + *
  • The speed value has no influence on this particle effect + *
+ */ + TOWN_AURA("townaura", 22, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by note blocks: + *
    + *
  • It looks like a colored note + *
  • The speed value causes the particle to be colored green when set to 0 + *
+ */ + NOTE("note", 23, -1, ParticleProperty.COLORABLE), + /** + * A particle effect which is displayed by nether portals, endermen, ender pearls, eyes of ender, ender chests and dragon eggs: + *
    + *
  • It looks like a purple cloud + *
  • The speed value influences the spread of this particle effect + *
+ */ + PORTAL("portal", 24, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by enchantment tables which are nearby bookshelves: + *
    + *
  • It looks like a cryptic white letter + *
  • The speed value influences the spread of this particle effect + *
+ */ + ENCHANTMENT_TABLE("enchantmenttable", 25, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by torches, active furnaces, magma cubes and monster spawners: + *
    + *
  • It looks like a tiny flame + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + FLAME("flame", 26, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by lava: + *
    + *
  • It looks like a spark + *
  • The speed value has no influence on this particle effect + *
+ */ + LAVA("lava", 27, -1), + /** + * A particle effect which is currently unused: + *
    + *
  • It looks like a transparent gray square + *
  • The speed value has no influence on this particle effect + *
+ */ + FOOTSTEP("footstep", 28, -1), + /** + * A particle effect which is displayed when a mob dies: + *
    + *
  • It looks like a large white cloud + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + CLOUD("cloud", 29, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by redstone ore, powered redstone, redstone torches and redstone repeaters: + *
    + *
  • It looks like a tiny colored cloud + *
  • The speed value causes the particle to be colored red when set to 0 + *
+ */ + REDSTONE("reddust", 30, -1, ParticleProperty.COLORABLE), + /** + * A particle effect which is displayed when snowballs hit a block: + *
    + *
  • It looks like a little piece with the snowball texture + *
  • The speed value has no influence on this particle effect + *
+ */ + SNOWBALL("snowballpoof", 31, -1), + /** + * A particle effect which is currently unused: + *
    + *
  • It looks like a tiny white cloud + *
  • The speed value influences the velocity at which the particle flies off + *
+ */ + SNOW_SHOVEL("snowshovel", 32, -1, ParticleProperty.DIRECTIONAL), + /** + * A particle effect which is displayed by slimes: + *
    + *
  • It looks like a tiny part of the slimeball icon + *
  • The speed value has no influence on this particle effect + *
+ */ + SLIME("slime", 33, -1), + /** + * A particle effect which is displayed when breeding and taming animals: + *
    + *
  • It looks like a red heart + *
  • The speed value has no influence on this particle effect + *
+ */ + HEART("heart", 34, -1), + /** + * A particle effect which is displayed by barriers: + *
    + *
  • It looks like a red box with a slash through it + *
  • The speed value has no influence on this particle effect + *
+ */ + BARRIER("barrier", 35, 8), + /** + * A particle effect which is displayed when breaking a tool or eggs hit a block: + *
    + *
  • It looks like a little piece with an item texture + *
+ */ + ITEM_CRACK("iconcrack", 36, -1, ParticleProperty.DIRECTIONAL, ParticleProperty.REQUIRES_DATA), + /** + * A particle effect which is displayed when breaking blocks or sprinting: + *
    + *
  • It looks like a little piece with a block texture + *
  • The speed value has no influence on this particle effect + *
+ */ + BLOCK_CRACK("blockcrack", 37, -1, ParticleProperty.REQUIRES_DATA), + /** + * A particle effect which is displayed when falling: + *
    + *
  • It looks like a little piece with a block texture + *
+ */ + BLOCK_DUST("blockdust", 38, 7, ParticleProperty.DIRECTIONAL, ParticleProperty.REQUIRES_DATA), + /** + * A particle effect which is displayed when rain hits the ground: + *
    + *
  • It looks like a blue droplet + *
  • The speed value has no influence on this particle effect + *
+ */ + WATER_DROP("droplet", 39, 8), + /** + * A particle effect which is currently unused: + *
    + *
  • It has no visual effect + *
+ */ + ITEM_TAKE("take", 40, 8), + /** + * A particle effect which is displayed by elder guardians: + *
    + *
  • It looks like the shape of the elder guardian + *
  • The speed value has no influence on this particle effect + *
  • The offset values have no influence on this particle effect + *
+ */ + MOB_APPEARANCE("mobappearance", 41, 8); + + private static final Map NAME_MAP = new HashMap(); + private static final Map ID_MAP = new HashMap(); + private final String name; + private final int id; + private final int requiredVersion; + private final List properties; + + // Initialize map for quick name and id lookup + static { + for (ParticleEffect effect : values()) { + NAME_MAP.put(effect.name, effect); + ID_MAP.put(effect.id, effect); + } + } + + /** + * Construct a new particle effect + * + * @param name Name of this particle effect + * @param id Id of this particle effect + * @param requiredVersion Version which is required (1.x) + * @param properties Properties of this particle effect + */ + private ParticleEffect(String name, int id, int requiredVersion, ParticleProperty... properties) { + this.name = name; + this.id = id; + this.requiredVersion = requiredVersion; + this.properties = Arrays.asList(properties); + } + + /** + * Returns the name of this particle effect + * + * @return The name + */ + public String getName() { + return name; + } + + /** + * Returns the id of this particle effect + * + * @return The id + */ + public int getId() { + return id; + } + + /** + * Returns the required version for this particle effect (1.x) + * + * @return The required version + */ + public int getRequiredVersion() { + return requiredVersion; + } + + /** + * Determine if this particle effect has a specific property + * + * @return Whether it has the property or not + */ + public boolean hasProperty(ParticleProperty property) { + return properties.contains(property); + } + + /** + * Determine if this particle effect is supported by your current server version + * + * @return Whether the particle effect is supported or not + */ + public boolean isSupported() { + if (requiredVersion == -1) { + return true; + } + return ParticlePacket.getVersion() >= requiredVersion; + } + + /** + * Returns the particle effect with the given name + * + * @param name Name of the particle effect + * @return The particle effect + */ + public static ParticleEffect fromName(String name) { + for (Entry entry : NAME_MAP.entrySet()) { + if (!entry.getKey().equalsIgnoreCase(name)) { + continue; + } + return entry.getValue(); + } + return null; + } + + /** + * Returns the particle effect with the given id + * + * @param id Id of the particle effect + * @return The particle effect + */ + public static ParticleEffect fromId(int id) { + for (Entry entry : ID_MAP.entrySet()) { + if (entry.getKey() != id) { + continue; + } + return entry.getValue(); + } + return null; + } + + /** + * Determine if water is at a certain location + * + * @param location Location to check + * @return Whether water is at this location or not + */ + private static boolean isWater(Location location) { + Material material = location.getBlock().getType(); + return material == Material.WATER || material == Material.STATIONARY_WATER; + } + + /** + * Determine if the distance between @param location and one of the players exceeds 256 + * + * @param location Location to check + * @return Whether the distance exceeds 256 or not + */ + private static boolean isLongDistance(Location location, List players) { + String world = location.getWorld().getName(); + for (Player player : players) { + Location playerLocation = player.getLocation(); + if (!world.equals(playerLocation.getWorld().getName()) || playerLocation.distanceSquared(location) < 65536) { + continue; + } + return true; + } + return false; + } + + /** + * Determine if the data type for a particle effect is correct + * + * @param effect Particle effect + * @param data Particle data + * @return Whether the data type is correct or not + */ + private static boolean isDataCorrect(ParticleEffect effect, ParticleData data) { + return ((effect == BLOCK_CRACK || effect == BLOCK_DUST) && data instanceof BlockData) || (effect == ITEM_CRACK && data instanceof ItemData); + } + + /** + * Determine if the color type for a particle effect is correct + * + * @param effect Particle effect + * @param color Particle color + * @return Whether the color type is correct or not + */ + private static boolean isColorCorrect(ParticleEffect effect, ParticleColor color) { + return ((effect == SPELL_MOB || effect == SPELL_MOB_AMBIENT || effect == REDSTONE) && color instanceof OrdinaryColor) || (effect == NOTE && color instanceof NoteColor); + } + + /** + * Displays a particle effect which is only visible for all players within a certain range in the world of @param center + * + * @param offsetX Maximum distance particles can fly away from the center on the x-axis + * @param offsetY Maximum distance particles can fly away from the center on the y-axis + * @param offsetZ Maximum distance particles can fly away from the center on the z-axis + * @param speed Display speed of the particles + * @param amount Amount of particles + * @param center Center location of the effect + * @param range Range of the visibility + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect requires additional data + * @throws IllegalArgumentException If the particle effect requires water and none is at the center location + * @see ParticlePacket + * @see ParticlePacket#sendTo(Location, double) + */ + public void display(float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, double range) throws ParticleVersionException, ParticleDataException, IllegalArgumentException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (hasProperty(ParticleProperty.REQUIRES_DATA)) { + throw new ParticleDataException("This particle effect requires additional data"); + } + if (hasProperty(ParticleProperty.REQUIRES_WATER) && !isWater(center)) { + throw new IllegalArgumentException("There is no water at the center location"); + } + new ParticlePacket(this, offsetX, offsetY, offsetZ, speed, amount, range > 256, null).sendTo(center, range); + } + + /** + * Displays a particle effect which is only visible for the specified players + * + * @param offsetX Maximum distance particles can fly away from the center on the x-axis + * @param offsetY Maximum distance particles can fly away from the center on the y-axis + * @param offsetZ Maximum distance particles can fly away from the center on the z-axis + * @param speed Display speed of the particles + * @param amount Amount of particles + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect requires additional data + * @throws IllegalArgumentException If the particle effect requires water and none is at the center location + * @see ParticlePacket + * @see ParticlePacket#sendTo(Location, List) + */ + public void display(float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, List players) throws ParticleVersionException, ParticleDataException, IllegalArgumentException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (hasProperty(ParticleProperty.REQUIRES_DATA)) { + throw new ParticleDataException("This particle effect requires additional data"); + } + if (hasProperty(ParticleProperty.REQUIRES_WATER) && !isWater(center)) { + throw new IllegalArgumentException("There is no water at the center location"); + } + new ParticlePacket(this, offsetX, offsetY, offsetZ, speed, amount, isLongDistance(center, players), null).sendTo(center, players); + } + + /** + * Displays a particle effect which is only visible for the specified players + * + * @param offsetX Maximum distance particles can fly away from the center on the x-axis + * @param offsetY Maximum distance particles can fly away from the center on the y-axis + * @param offsetZ Maximum distance particles can fly away from the center on the z-axis + * @param speed Display speed of the particles + * @param amount Amount of particles + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect requires additional data + * @throws IllegalArgumentException If the particle effect requires water and none is at the center location + * @see #display(float, float, float, float, int, Location, List) + */ + public void display(float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, Player... players) throws ParticleVersionException, ParticleDataException, IllegalArgumentException { + display(offsetX, offsetY, offsetZ, speed, amount, center, Arrays.asList(players)); + } + + /** + * Displays a single particle which flies into a determined direction and is only visible for all players within a certain range in the world of @param center + * + * @param direction Direction of the particle + * @param speed Display speed of the particle + * @param center Center location of the effect + * @param range Range of the visibility + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect requires additional data + * @throws IllegalArgumentException If the particle effect is not directional or if it requires water and none is at the center location + * @see ParticlePacket#ParticlePacket(ParticleEffect, Vector, float, boolean, ParticleData) + * @see ParticlePacket#sendTo(Location, double) + */ + public void display(Vector direction, float speed, Location center, double range) throws ParticleVersionException, ParticleDataException, IllegalArgumentException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (hasProperty(ParticleProperty.REQUIRES_DATA)) { + throw new ParticleDataException("This particle effect requires additional data"); + } + if (!hasProperty(ParticleProperty.DIRECTIONAL)) { + throw new IllegalArgumentException("This particle effect is not directional"); + } + if (hasProperty(ParticleProperty.REQUIRES_WATER) && !isWater(center)) { + throw new IllegalArgumentException("There is no water at the center location"); + } + new ParticlePacket(this, direction, speed, range > 256, null).sendTo(center, range); + } + + /** + * Displays a single particle which flies into a determined direction and is only visible for the specified players + * + * @param direction Direction of the particle + * @param speed Display speed of the particle + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect requires additional data + * @throws IllegalArgumentException If the particle effect is not directional or if it requires water and none is at the center location + * @see ParticlePacket#ParticlePacket(ParticleEffect, Vector, float, boolean, ParticleData) + * @see ParticlePacket#sendTo(Location, List) + */ + public void display(Vector direction, float speed, Location center, List players) throws ParticleVersionException, ParticleDataException, IllegalArgumentException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (hasProperty(ParticleProperty.REQUIRES_DATA)) { + throw new ParticleDataException("This particle effect requires additional data"); + } + if (!hasProperty(ParticleProperty.DIRECTIONAL)) { + throw new IllegalArgumentException("This particle effect is not directional"); + } + if (hasProperty(ParticleProperty.REQUIRES_WATER) && !isWater(center)) { + throw new IllegalArgumentException("There is no water at the center location"); + } + new ParticlePacket(this, direction, speed, isLongDistance(center, players), null).sendTo(center, players); + } + + /** + * Displays a single particle which flies into a determined direction and is only visible for the specified players + * + * @param direction Direction of the particle + * @param speed Display speed of the particle + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect requires additional data + * @throws IllegalArgumentException If the particle effect is not directional or if it requires water and none is at the center location + * @see #display(Vector, float, Location, List) + */ + public void display(Vector direction, float speed, Location center, Player... players) throws ParticleVersionException, ParticleDataException, IllegalArgumentException { + display(direction, speed, center, Arrays.asList(players)); + } + + /** + * Displays a single particle which is colored and only visible for all players within a certain range in the world of @param center + * + * @param color Color of the particle + * @param center Center location of the effect + * @param range Range of the visibility + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleColorException If the particle effect is not colorable or the color type is incorrect + * @see ParticlePacket#ParticlePacket(ParticleEffect, ParticleColor, boolean) + * @see ParticlePacket#sendTo(Location, double) + */ + public void display(ParticleColor color, Location center, double range) throws ParticleVersionException, ParticleColorException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (!hasProperty(ParticleProperty.COLORABLE)) { + throw new ParticleColorException("This particle effect is not colorable"); + } + if (!isColorCorrect(this, color)) { + throw new ParticleColorException("The particle color type is incorrect"); + } + new ParticlePacket(this, color, range > 256).sendTo(center, range); + } + + /** + * Displays a single particle which is colored and only visible for the specified players + * + * @param color Color of the particle + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleColorException If the particle effect is not colorable or the color type is incorrect + * @see ParticlePacket#ParticlePacket(ParticleEffect, ParticleColor, boolean) + * @see ParticlePacket#sendTo(Location, List) + */ + public void display(ParticleColor color, Location center, List players) throws ParticleVersionException, ParticleColorException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (!hasProperty(ParticleProperty.COLORABLE)) { + throw new ParticleColorException("This particle effect is not colorable"); + } + if (!isColorCorrect(this, color)) { + throw new ParticleColorException("The particle color type is incorrect"); + } + new ParticlePacket(this, color, isLongDistance(center, players)).sendTo(center, players); + } + + /** + * Displays a single particle which is colored and only visible for the specified players + * + * @param color Color of the particle + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleColorException If the particle effect is not colorable or the color type is incorrect + * @see #display(ParticleColor, Location, List) + */ + public void display(ParticleColor color, Location center, Player... players) throws ParticleVersionException, ParticleColorException { + display(color, center, Arrays.asList(players)); + } + + /** + * Displays a particle effect which requires additional data and is only visible for all players within a certain range in the world of @param center + * + * @param data Data of the effect + * @param offsetX Maximum distance particles can fly away from the center on the x-axis + * @param offsetY Maximum distance particles can fly away from the center on the y-axis + * @param offsetZ Maximum distance particles can fly away from the center on the z-axis + * @param speed Display speed of the particles + * @param amount Amount of particles + * @param center Center location of the effect + * @param range Range of the visibility + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect + * @see ParticlePacket + * @see ParticlePacket#sendTo(Location, double) + */ + public void display(ParticleData data, float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, double range) throws ParticleVersionException, ParticleDataException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (!hasProperty(ParticleProperty.REQUIRES_DATA)) { + throw new ParticleDataException("This particle effect does not require additional data"); + } + if (!isDataCorrect(this, data)) { + throw new ParticleDataException("The particle data type is incorrect"); + } + new ParticlePacket(this, offsetX, offsetY, offsetZ, speed, amount, range > 256, data).sendTo(center, range); + } + + /** + * Displays a particle effect which requires additional data and is only visible for the specified players + * + * @param data Data of the effect + * @param offsetX Maximum distance particles can fly away from the center on the x-axis + * @param offsetY Maximum distance particles can fly away from the center on the y-axis + * @param offsetZ Maximum distance particles can fly away from the center on the z-axis + * @param speed Display speed of the particles + * @param amount Amount of particles + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect + * @see ParticlePacket + * @see ParticlePacket#sendTo(Location, List) + */ + public void display(ParticleData data, float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, List players) throws ParticleVersionException, ParticleDataException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (!hasProperty(ParticleProperty.REQUIRES_DATA)) { + throw new ParticleDataException("This particle effect does not require additional data"); + } + if (!isDataCorrect(this, data)) { + throw new ParticleDataException("The particle data type is incorrect"); + } + new ParticlePacket(this, offsetX, offsetY, offsetZ, speed, amount, isLongDistance(center, players), data).sendTo(center, players); + } + + /** + * Displays a particle effect which requires additional data and is only visible for the specified players + * + * @param data Data of the effect + * @param offsetX Maximum distance particles can fly away from the center on the x-axis + * @param offsetY Maximum distance particles can fly away from the center on the y-axis + * @param offsetZ Maximum distance particles can fly away from the center on the z-axis + * @param speed Display speed of the particles + * @param amount Amount of particles + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect + * @see #display(ParticleData, float, float, float, float, int, Location, List) + */ + public void display(ParticleData data, float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, Player... players) throws ParticleVersionException, ParticleDataException { + display(data, offsetX, offsetY, offsetZ, speed, amount, center, Arrays.asList(players)); + } + + /** + * Displays a single particle which requires additional data that flies into a determined direction and is only visible for all players within a certain range in the world of @param center + * + * @param data Data of the effect + * @param direction Direction of the particle + * @param speed Display speed of the particles + * @param center Center location of the effect + * @param range Range of the visibility + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect + * @see ParticlePacket + * @see ParticlePacket#sendTo(Location, double) + */ + public void display(ParticleData data, Vector direction, float speed, Location center, double range) throws ParticleVersionException, ParticleDataException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (!hasProperty(ParticleProperty.REQUIRES_DATA)) { + throw new ParticleDataException("This particle effect does not require additional data"); + } + if (!isDataCorrect(this, data)) { + throw new ParticleDataException("The particle data type is incorrect"); + } + new ParticlePacket(this, direction, speed, range > 256, data).sendTo(center, range); + } + + /** + * Displays a single particle which requires additional data that flies into a determined direction and is only visible for the specified players + * + * @param data Data of the effect + * @param direction Direction of the particle + * @param speed Display speed of the particles + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect + * @see ParticlePacket + * @see ParticlePacket#sendTo(Location, List) + */ + public void display(ParticleData data, Vector direction, float speed, Location center, List players) throws ParticleVersionException, ParticleDataException { + if (!isSupported()) { + throw new ParticleVersionException("This particle effect is not supported by your server version"); + } + if (!hasProperty(ParticleProperty.REQUIRES_DATA)) { + throw new ParticleDataException("This particle effect does not require additional data"); + } + if (!isDataCorrect(this, data)) { + throw new ParticleDataException("The particle data type is incorrect"); + } + new ParticlePacket(this, direction, speed, isLongDistance(center, players), data).sendTo(center, players); + } + + /** + * Displays a single particle which requires additional data that flies into a determined direction and is only visible for the specified players + * + * @param data Data of the effect + * @param direction Direction of the particle + * @param speed Display speed of the particles + * @param center Center location of the effect + * @param players Receivers of the effect + * @throws ParticleVersionException If the particle effect is not supported by the server version + * @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect + * @see #display(ParticleData, Vector, float, Location, List) + */ + public void display(ParticleData data, Vector direction, float speed, Location center, Player... players) throws ParticleVersionException, ParticleDataException { + display(data, direction, speed, center, Arrays.asList(players)); + } + + /** + * Represents the property of a particle effect + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.7 + */ + public static enum ParticleProperty { + /** + * The particle effect requires water to be displayed + */ + REQUIRES_WATER, + /** + * The particle effect requires block or item data to be displayed + */ + REQUIRES_DATA, + /** + * The particle effect uses the offsets as direction values + */ + DIRECTIONAL, + /** + * The particle effect uses the offsets as color values + */ + COLORABLE; + } + + /** + * Represents the particle data for effects like {@link ParticleEffect#ITEM_CRACK}, {@link ParticleEffect#BLOCK_CRACK} and {@link ParticleEffect#BLOCK_DUST} + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.6 + */ + public static abstract class ParticleData { + private final Material material; + private final byte data; + private final int[] packetData; + + /** + * Construct a new particle data + * + * @param material Material of the item/block + * @param data Data value of the item/block + */ + @SuppressWarnings("deprecation") + public ParticleData(Material material, byte data) { + this.material = material; + this.data = data; + this.packetData = new int[] { material.getId(), data }; + } + + /** + * Returns the material of this data + * + * @return The material + */ + public Material getMaterial() { + return material; + } + + /** + * Returns the data value of this data + * + * @return The data value + */ + public byte getData() { + return data; + } + + /** + * Returns the data as an int array for packet construction + * + * @return The data for the packet + */ + public int[] getPacketData() { + return packetData; + } + + /** + * Returns the data as a string for pre 1.8 versions + * + * @return The data string for the packet + */ + public String getPacketDataString() { + return "_" + packetData[0] + "_" + packetData[1]; + } + } + + /** + * Represents the item data for the {@link ParticleEffect#ITEM_CRACK} effect + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.6 + */ + public static final class ItemData extends ParticleData { + /** + * Construct a new item data + * + * @param material Material of the item + * @param data Data value of the item + * @see ParticleData#ParticleData(Material, byte) + */ + public ItemData(Material material, byte data) { + super(material, data); + } + } + + /** + * Represents the block data for the {@link ParticleEffect#BLOCK_CRACK} and {@link ParticleEffect#BLOCK_DUST} effects + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.6 + */ + public static final class BlockData extends ParticleData { + /** + * Construct a new block data + * + * @param material Material of the block + * @param data Data value of the block + * @throws IllegalArgumentException If the material is not a block + * @see ParticleData#ParticleData(Material, byte) + */ + public BlockData(Material material, byte data) throws IllegalArgumentException { + super(material, data); + if (!material.isBlock()) { + throw new IllegalArgumentException("The material is not a block"); + } + } + } + + /** + * Represents the color for effects like {@link ParticleEffect#SPELL_MOB}, {@link ParticleEffect#SPELL_MOB_AMBIENT}, {@link ParticleEffect#REDSTONE} and {@link ParticleEffect#NOTE} + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.7 + */ + public static abstract class ParticleColor { + /** + * Returns the value for the offsetX field + * + * @return The offsetX value + */ + public abstract float getValueX(); + + /** + * Returns the value for the offsetY field + * + * @return The offsetY value + */ + public abstract float getValueY(); + + /** + * Returns the value for the offsetZ field + * + * @return The offsetZ value + */ + public abstract float getValueZ(); + } + + /** + * Represents the color for effects like {@link ParticleEffect#SPELL_MOB}, {@link ParticleEffect#SPELL_MOB_AMBIENT} and {@link ParticleEffect#NOTE} + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.7 + */ + public static final class OrdinaryColor extends ParticleColor { + private final int red; + private final int green; + private final int blue; + + /** + * Construct a new ordinary color + * + * @param red Red value of the RGB format + * @param green Green value of the RGB format + * @param blue Blue value of the RGB format + * @throws IllegalArgumentException If one of the values is lower than 0 or higher than 255 + */ + public OrdinaryColor(int red, int green, int blue) throws IllegalArgumentException { + if (red < 0) { + throw new IllegalArgumentException("The red value is lower than 0"); + } + if (red > 255) { + throw new IllegalArgumentException("The red value is higher than 255"); + } + this.red = red; + if (green < 0) { + throw new IllegalArgumentException("The green value is lower than 0"); + } + if (green > 255) { + throw new IllegalArgumentException("The green value is higher than 255"); + } + this.green = green; + if (blue < 0) { + throw new IllegalArgumentException("The blue value is lower than 0"); + } + if (blue > 255) { + throw new IllegalArgumentException("The blue value is higher than 255"); + } + this.blue = blue; + } + + /** + * Construct a new ordinary color + * + * @param color Bukkit color + */ + public OrdinaryColor(Color color) { + this(color.getRed(), color.getGreen(), color.getBlue()); + } + + /** + * Returns the red value of the RGB format + * + * @return The red value + */ + public int getRed() { + return red; + } + + /** + * Returns the green value of the RGB format + * + * @return The green value + */ + public int getGreen() { + return green; + } + + /** + * Returns the blue value of the RGB format + * + * @return The blue value + */ + public int getBlue() { + return blue; + } + + /** + * Returns the red value divided by 255 + * + * @return The offsetX value + */ + @Override + public float getValueX() { + return (float) red / 255F; + } + + /** + * Returns the green value divided by 255 + * + * @return The offsetY value + */ + @Override + public float getValueY() { + return (float) green / 255F; + } + + /** + * Returns the blue value divided by 255 + * + * @return The offsetZ value + */ + @Override + public float getValueZ() { + return (float) blue / 255F; + } + } + + /** + * Represents the color for the {@link ParticleEffect#NOTE} effect + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.7 + */ + public static final class NoteColor extends ParticleColor { + private final int note; + + /** + * Construct a new note color + * + * @param note Note id which determines color + * @throws IllegalArgumentException If the note value is lower than 0 or higher than 24 + */ + public NoteColor(int note) throws IllegalArgumentException { + if (note < 0) { + throw new IllegalArgumentException("The note value is lower than 0"); + } + if (note > 24) { + throw new IllegalArgumentException("The note value is higher than 24"); + } + this.note = note; + } + + /** + * Returns the note value divided by 24 + * + * @return The offsetX value + */ + @Override + public float getValueX() { + return (float) note / 24F; + } + + /** + * Returns zero because the offsetY value is unused + * + * @return zero + */ + @Override + public float getValueY() { + return 0; + } + + /** + * Returns zero because the offsetZ value is unused + * + * @return zero + */ + @Override + public float getValueZ() { + return 0; + } + + } + + /** + * Represents a runtime exception that is thrown either if the displayed particle effect requires data and has none or vice-versa or if the data type is incorrect + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.6 + */ + private static final class ParticleDataException extends RuntimeException { + private static final long serialVersionUID = 3203085387160737484L; + + /** + * Construct a new particle data exception + * + * @param message Message that will be logged + */ + public ParticleDataException(String message) { + super(message); + } + } + + /** + * Represents a runtime exception that is thrown either if the displayed particle effect is not colorable or if the particle color type is incorrect + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.7 + */ + private static final class ParticleColorException extends RuntimeException { + private static final long serialVersionUID = 3203085387160737484L; + + /** + * Construct a new particle color exception + * + * @param message Message that will be logged + */ + public ParticleColorException(String message) { + super(message); + } + } + + /** + * Represents a runtime exception that is thrown if the displayed particle effect requires a newer version + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.6 + */ + private static final class ParticleVersionException extends RuntimeException { + private static final long serialVersionUID = 3203085387160737484L; + + /** + * Construct a new particle version exception + * + * @param message Message that will be logged + */ + public ParticleVersionException(String message) { + super(message); + } + } + + /** + * Represents a particle effect packet with all attributes which is used for sending packets to the players + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.5 + */ + public static final class ParticlePacket { + private static int version; + private static Class enumParticle; + private static Constructor packetConstructor; + private static Method getHandle; + private static Field playerConnection; + private static Method sendPacket; + private static boolean initialized; + private final ParticleEffect effect; + private float offsetX; + private final float offsetY; + private final float offsetZ; + private final float speed; + private final int amount; + private final boolean longDistance; + private final ParticleData data; + private Object packet; + + /** + * Construct a new particle packet + * + * @param effect Particle effect + * @param offsetX Maximum distance particles can fly away from the center on the x-axis + * @param offsetY Maximum distance particles can fly away from the center on the y-axis + * @param offsetZ Maximum distance particles can fly away from the center on the z-axis + * @param speed Display speed of the particles + * @param amount Amount of particles + * @param longDistance Indicates whether the maximum distance is increased from 256 to 65536 + * @param data Data of the effect + * @throws IllegalArgumentException If the speed or amount is lower than 0 + * @see #initialize() + */ + public ParticlePacket(ParticleEffect effect, float offsetX, float offsetY, float offsetZ, float speed, int amount, boolean longDistance, ParticleData data) throws IllegalArgumentException { + initialize(); + if (speed < 0) { + throw new IllegalArgumentException("The speed is lower than 0"); + } + if (amount < 0) { + throw new IllegalArgumentException("The amount is lower than 0"); + } + this.effect = effect; + this.offsetX = offsetX; + this.offsetY = offsetY; + this.offsetZ = offsetZ; + this.speed = speed; + this.amount = amount; + this.longDistance = longDistance; + this.data = data; + } + + /** + * Construct a new particle packet of a single particle flying into a determined direction + * + * @param effect Particle effect + * @param direction Direction of the particle + * @param speed Display speed of the particle + * @param longDistance Indicates whether the maximum distance is increased from 256 to 65536 + * @param data Data of the effect + * @throws IllegalArgumentException If the speed is lower than 0 + * @see #ParticleEffect(ParticleEffect, float, float, float, float, int, boolean, ParticleData) + */ + public ParticlePacket(ParticleEffect effect, Vector direction, float speed, boolean longDistance, ParticleData data) throws IllegalArgumentException { + this(effect, (float) direction.getX(), (float) direction.getY(), (float) direction.getZ(), speed, 0, longDistance, data); + } + + /** + * Construct a new particle packet of a single colored particle + * + * @param effect Particle effect + * @param color Color of the particle + * @param longDistance Indicates whether the maximum distance is increased from 256 to 65536 + * @see #ParticleEffect(ParticleEffect, float, float, float, float, int, boolean, ParticleData) + */ + public ParticlePacket(ParticleEffect effect, ParticleColor color, boolean longDistance) { + this(effect, color.getValueX(), color.getValueY(), color.getValueZ(), 1, 0, longDistance, null); + if (effect == ParticleEffect.REDSTONE && color instanceof OrdinaryColor && ((OrdinaryColor) color).getRed() == 0) { + offsetX = Float.MIN_NORMAL; + } + } + + /** + * Initializes {@link #packetConstructor}, {@link #getHandle}, {@link #playerConnection} and {@link #sendPacket} and sets {@link #initialized} to true if it succeeds + *

+ * Note: These fields only have to be initialized once, so it will return if {@link #initialized} is already set to true + * + * @throws VersionIncompatibleException if your bukkit version is not supported by this library + */ + public static void initialize() throws VersionIncompatibleException { + if (initialized) { + return; + } + try { + version = Integer.parseInt(Character.toString(PackageType.getServerVersion().charAt(3))); + if (version > 7) { + enumParticle = PackageType.MINECRAFT_SERVER.getClass("EnumParticle"); + } + Class packetClass = PackageType.MINECRAFT_SERVER.getClass(version < 7 ? "Packet63WorldParticles" : "PacketPlayOutWorldParticles"); + packetConstructor = ReflectionUtils.getConstructor(packetClass); + getHandle = ReflectionUtils.getMethod("CraftPlayer", PackageType.CRAFTBUKKIT_ENTITY, "getHandle"); + playerConnection = ReflectionUtils.getField("EntityPlayer", PackageType.MINECRAFT_SERVER, false, "playerConnection"); + sendPacket = ReflectionUtils.getMethod(playerConnection.getType(), "sendPacket", PackageType.MINECRAFT_SERVER.getClass("Packet")); + } catch (Exception exception) { + throw new VersionIncompatibleException("Your current bukkit version seems to be incompatible with this library", exception); + } + initialized = true; + } + + /** + * Returns the version of your server (1.x) + * + * @return The version number + */ + public static int getVersion() { + if (!initialized) { + initialize(); + } + return version; + } + + /** + * Determine if {@link #packetConstructor}, {@link #getHandle}, {@link #playerConnection} and {@link #sendPacket} are initialized + * + * @return Whether these fields are initialized or not + * @see #initialize() + */ + public static boolean isInitialized() { + return initialized; + } + + /** + * Initializes {@link #packet} with all set values + * + * @param center Center location of the effect + * @throws PacketInstantiationException If instantion fails due to an unknown error + */ + private void initializePacket(Location center) throws PacketInstantiationException { + if (packet != null) { + return; + } + try { + packet = packetConstructor.newInstance(); + if (version < 8) { + String name = effect.getName(); + if (data != null) { + name += data.getPacketDataString(); + } + ReflectionUtils.setValue(packet, true, "a", name); + } else { + ReflectionUtils.setValue(packet, true, "a", enumParticle.getEnumConstants()[effect.getId()]); + ReflectionUtils.setValue(packet, true, "j", longDistance); + if (data != null) { + int[] packetData = data.getPacketData(); + ReflectionUtils.setValue(packet, true, "k", effect == ParticleEffect.ITEM_CRACK ? packetData : new int[] { packetData[0] | (packetData[1] << 12) }); + } + } + ReflectionUtils.setValue(packet, true, "b", (float) center.getX()); + ReflectionUtils.setValue(packet, true, "c", (float) center.getY()); + ReflectionUtils.setValue(packet, true, "d", (float) center.getZ()); + ReflectionUtils.setValue(packet, true, "e", offsetX); + ReflectionUtils.setValue(packet, true, "f", offsetY); + ReflectionUtils.setValue(packet, true, "g", offsetZ); + ReflectionUtils.setValue(packet, true, "h", speed); + ReflectionUtils.setValue(packet, true, "i", amount); + } catch (Exception exception) { + throw new PacketInstantiationException("Packet instantiation failed", exception); + } + } + + /** + * Sends the packet to a single player and caches it + * + * @param center Center location of the effect + * @param player Receiver of the packet + * @throws PacketInstantiationException If instantion fails due to an unknown error + * @throws PacketSendingException If sending fails due to an unknown error + * @see #initializePacket(Location) + */ + public void sendTo(Location center, Player player) throws PacketInstantiationException, PacketSendingException { + initializePacket(center); + try { + sendPacket.invoke(playerConnection.get(getHandle.invoke(player)), packet); + } catch (Exception exception) { + throw new PacketSendingException("Failed to send the packet to player '" + player.getName() + "'", exception); + } + } + + /** + * Sends the packet to all players in the list + * + * @param center Center location of the effect + * @param players Receivers of the packet + * @throws IllegalArgumentException If the player list is empty + * @see #sendTo(Location center, Player player) + */ + public void sendTo(Location center, List players) throws IllegalArgumentException { + if (players.isEmpty()) { + throw new IllegalArgumentException("The player list is empty"); + } + for (Player player : players) { + sendTo(center, player); + } + } + + /** + * Sends the packet to all players in a certain range + * + * @param center Center location of the effect + * @param range Range in which players will receive the packet (Maximum range for particles is usually 16, but it can differ for some types) + * @throws IllegalArgumentException If the range is lower than 1 + * @see #sendTo(Location center, Player player) + */ + public void sendTo(Location center, double range) throws IllegalArgumentException { + if (range < 1) { + throw new IllegalArgumentException("The range is lower than 1"); + } + String worldName = center.getWorld().getName(); + double squared = range * range; + for (Player player : Bukkit.getOnlinePlayers()) { + if (!player.getWorld().getName().equals(worldName) || player.getLocation().distanceSquared(center) > squared) { + continue; + } + sendTo(center, player); + } + } + + /** + * Represents a runtime exception that is thrown if a bukkit version is not compatible with this library + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.5 + */ + private static final class VersionIncompatibleException extends RuntimeException { + private static final long serialVersionUID = 3203085387160737484L; + + /** + * Construct a new version incompatible exception + * + * @param message Message that will be logged + * @param cause Cause of the exception + */ + public VersionIncompatibleException(String message, Throwable cause) { + super(message, cause); + } + } + + /** + * Represents a runtime exception that is thrown if packet instantiation fails + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.4 + */ + private static final class PacketInstantiationException extends RuntimeException { + private static final long serialVersionUID = 3203085387160737484L; + + /** + * Construct a new packet instantiation exception + * + * @param message Message that will be logged + * @param cause Cause of the exception + */ + public PacketInstantiationException(String message, Throwable cause) { + super(message, cause); + } + } + + /** + * Represents a runtime exception that is thrown if packet sending fails + *

+ * This class is part of the ParticleEffect Library and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.4 + */ + private static final class PacketSendingException extends RuntimeException { + private static final long serialVersionUID = 3203085387160737484L; + + /** + * Construct a new packet sending exception + * + * @param message Message that will be logged + * @param cause Cause of the exception + */ + public PacketSendingException(String message, Throwable cause) { + super(message, cause); + } + } + } +} \ No newline at end of file diff --git a/src/sig/plugin/TwosideKeeper/HelperStructures/Utils/Classes/ReflectionUtils.java b/src/sig/plugin/TwosideKeeper/HelperStructures/Utils/Classes/ReflectionUtils.java new file mode 100644 index 0000000..81b76e1 --- /dev/null +++ b/src/sig/plugin/TwosideKeeper/HelperStructures/Utils/Classes/ReflectionUtils.java @@ -0,0 +1,605 @@ +package sig.plugin.TwosideKeeper.HelperStructures.Utils.Classes; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.Bukkit; + +/** + * ReflectionUtils + *

+ * This class provides useful methods which makes dealing with reflection much easier, especially when working with Bukkit + *

+ * You are welcome to use it, modify it and redistribute it under the following conditions: + *

    + *
  • Don't claim this class as your own + *
  • Don't remove this disclaimer + *
+ *

+ * It would be nice if you provide credit to me if you use this class in a published project + * + * @author DarkBlade12 + * @version 1.1 + */ +public final class ReflectionUtils { + // Prevent accidental construction + private ReflectionUtils() {} + + /** + * Returns the constructor of a given class with the given parameter types + * + * @param clazz Target class + * @param parameterTypes Parameter types of the desired constructor + * @return The constructor of the target class with the specified parameter types + * @throws NoSuchMethodException If the desired constructor with the specified parameter types cannot be found + * @see DataType + * @see DataType#getPrimitive(Class[]) + * @see DataType#compare(Class[], Class[]) + */ + public static Constructor getConstructor(Class clazz, Class... parameterTypes) throws NoSuchMethodException { + Class[] primitiveTypes = DataType.getPrimitive(parameterTypes); + for (Constructor constructor : clazz.getConstructors()) { + if (!DataType.compare(DataType.getPrimitive(constructor.getParameterTypes()), primitiveTypes)) { + continue; + } + return constructor; + } + throw new NoSuchMethodException("There is no such constructor in this class with the specified parameter types"); + } + + /** + * Returns the constructor of a desired class with the given parameter types + * + * @param className Name of the desired target class + * @param packageType Package where the desired target class is located + * @param parameterTypes Parameter types of the desired constructor + * @return The constructor of the desired target class with the specified parameter types + * @throws NoSuchMethodException If the desired constructor with the specified parameter types cannot be found + * @throws ClassNotFoundException ClassNotFoundException If the desired target class with the specified name and package cannot be found + * @see #getClass(String, PackageType) + * @see #getConstructor(Class, Class...) + */ + public static Constructor getConstructor(String className, PackageType packageType, Class... parameterTypes) throws NoSuchMethodException, ClassNotFoundException { + return getConstructor(packageType.getClass(className), parameterTypes); + } + + /** + * Returns an instance of a class with the given arguments + * + * @param clazz Target class + * @param arguments Arguments which are used to construct an object of the target class + * @return The instance of the target class with the specified arguments + * @throws InstantiationException If you cannot create an instance of the target class due to certain circumstances + * @throws IllegalAccessException If the desired constructor cannot be accessed due to certain circumstances + * @throws IllegalArgumentException If the types of the arguments do not match the parameter types of the constructor (this should not occur since it searches for a constructor with the types of the arguments) + * @throws InvocationTargetException If the desired constructor cannot be invoked + * @throws NoSuchMethodException If the desired constructor with the specified arguments cannot be found + */ + public static Object instantiateObject(Class clazz, Object... arguments) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return getConstructor(clazz, DataType.getPrimitive(arguments)).newInstance(arguments); + } + + /** + * Returns an instance of a desired class with the given arguments + * + * @param className Name of the desired target class + * @param packageType Package where the desired target class is located + * @param arguments Arguments which are used to construct an object of the desired target class + * @return The instance of the desired target class with the specified arguments + * @throws InstantiationException If you cannot create an instance of the desired target class due to certain circumstances + * @throws IllegalAccessException If the desired constructor cannot be accessed due to certain circumstances + * @throws IllegalArgumentException If the types of the arguments do not match the parameter types of the constructor (this should not occur since it searches for a constructor with the types of the arguments) + * @throws InvocationTargetException If the desired constructor cannot be invoked + * @throws NoSuchMethodException If the desired constructor with the specified arguments cannot be found + * @throws ClassNotFoundException If the desired target class with the specified name and package cannot be found + * @see #getClass(String, PackageType) + * @see #instantiateObject(Class, Object...) + */ + public static Object instantiateObject(String className, PackageType packageType, Object... arguments) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { + return instantiateObject(packageType.getClass(className), arguments); + } + + /** + * Returns a method of a class with the given parameter types + * + * @param clazz Target class + * @param methodName Name of the desired method + * @param parameterTypes Parameter types of the desired method + * @return The method of the target class with the specified name and parameter types + * @throws NoSuchMethodException If the desired method of the target class with the specified name and parameter types cannot be found + * @see DataType#getPrimitive(Class[]) + * @see DataType#compare(Class[], Class[]) + */ + public static Method getMethod(Class clazz, String methodName, Class... parameterTypes) throws NoSuchMethodException { + Class[] primitiveTypes = DataType.getPrimitive(parameterTypes); + for (Method method : clazz.getMethods()) { + if (!method.getName().equals(methodName) || !DataType.compare(DataType.getPrimitive(method.getParameterTypes()), primitiveTypes)) { + continue; + } + return method; + } + throw new NoSuchMethodException("There is no such method in this class with the specified name and parameter types"); + } + + /** + * Returns a method of a desired class with the given parameter types + * + * @param className Name of the desired target class + * @param packageType Package where the desired target class is located + * @param methodName Name of the desired method + * @param parameterTypes Parameter types of the desired method + * @return The method of the desired target class with the specified name and parameter types + * @throws NoSuchMethodException If the desired method of the desired target class with the specified name and parameter types cannot be found + * @throws ClassNotFoundException If the desired target class with the specified name and package cannot be found + * @see #getClass(String, PackageType) + * @see #getMethod(Class, String, Class...) + */ + public static Method getMethod(String className, PackageType packageType, String methodName, Class... parameterTypes) throws NoSuchMethodException, ClassNotFoundException { + return getMethod(packageType.getClass(className), methodName, parameterTypes); + } + + /** + * Invokes a method on an object with the given arguments + * + * @param instance Target object + * @param methodName Name of the desired method + * @param arguments Arguments which are used to invoke the desired method + * @return The result of invoking the desired method on the target object + * @throws IllegalAccessException If the desired method cannot be accessed due to certain circumstances + * @throws IllegalArgumentException If the types of the arguments do not match the parameter types of the method (this should not occur since it searches for a method with the types of the arguments) + * @throws InvocationTargetException If the desired method cannot be invoked on the target object + * @throws NoSuchMethodException If the desired method of the class of the target object with the specified name and arguments cannot be found + * @see #getMethod(Class, String, Class...) + * @see DataType#getPrimitive(Object[]) + */ + public static Object invokeMethod(Object instance, String methodName, Object... arguments) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return getMethod(instance.getClass(), methodName, DataType.getPrimitive(arguments)).invoke(instance, arguments); + } + + /** + * Invokes a method of the target class on an object with the given arguments + * + * @param instance Target object + * @param clazz Target class + * @param methodName Name of the desired method + * @param arguments Arguments which are used to invoke the desired method + * @return The result of invoking the desired method on the target object + * @throws IllegalAccessException If the desired method cannot be accessed due to certain circumstances + * @throws IllegalArgumentException If the types of the arguments do not match the parameter types of the method (this should not occur since it searches for a method with the types of the arguments) + * @throws InvocationTargetException If the desired method cannot be invoked on the target object + * @throws NoSuchMethodException If the desired method of the target class with the specified name and arguments cannot be found + * @see #getMethod(Class, String, Class...) + * @see DataType#getPrimitive(Object[]) + */ + public static Object invokeMethod(Object instance, Class clazz, String methodName, Object... arguments) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return getMethod(clazz, methodName, DataType.getPrimitive(arguments)).invoke(instance, arguments); + } + + /** + * Invokes a method of a desired class on an object with the given arguments + * + * @param instance Target object + * @param className Name of the desired target class + * @param packageType Package where the desired target class is located + * @param methodName Name of the desired method + * @param arguments Arguments which are used to invoke the desired method + * @return The result of invoking the desired method on the target object + * @throws IllegalAccessException If the desired method cannot be accessed due to certain circumstances + * @throws IllegalArgumentException If the types of the arguments do not match the parameter types of the method (this should not occur since it searches for a method with the types of the arguments) + * @throws InvocationTargetException If the desired method cannot be invoked on the target object + * @throws NoSuchMethodException If the desired method of the desired target class with the specified name and arguments cannot be found + * @throws ClassNotFoundException If the desired target class with the specified name and package cannot be found + * @see #getClass(String, PackageType) + * @see #invokeMethod(Object, Class, String, Object...) + */ + public static Object invokeMethod(Object instance, String className, PackageType packageType, String methodName, Object... arguments) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { + return invokeMethod(instance, packageType.getClass(className), methodName, arguments); + } + + /** + * Returns a field of the target class with the given name + * + * @param clazz Target class + * @param declared Whether the desired field is declared or not + * @param fieldName Name of the desired field + * @return The field of the target class with the specified name + * @throws NoSuchFieldException If the desired field of the given class cannot be found + * @throws SecurityException If the desired field cannot be made accessible + */ + public static Field getField(Class clazz, boolean declared, String fieldName) throws NoSuchFieldException, SecurityException { + Field field = declared ? clazz.getDeclaredField(fieldName) : clazz.getField(fieldName); + field.setAccessible(true); + return field; + } + + /** + * Returns a field of a desired class with the given name + * + * @param className Name of the desired target class + * @param packageType Package where the desired target class is located + * @param declared Whether the desired field is declared or not + * @param fieldName Name of the desired field + * @return The field of the desired target class with the specified name + * @throws NoSuchFieldException If the desired field of the desired class cannot be found + * @throws SecurityException If the desired field cannot be made accessible + * @throws ClassNotFoundException If the desired target class with the specified name and package cannot be found + * @see #getField(Class, boolean, String) + */ + public static Field getField(String className, PackageType packageType, boolean declared, String fieldName) throws NoSuchFieldException, SecurityException, ClassNotFoundException { + return getField(packageType.getClass(className), declared, fieldName); + } + + /** + * Returns the value of a field of the given class of an object + * + * @param instance Target object + * @param clazz Target class + * @param declared Whether the desired field is declared or not + * @param fieldName Name of the desired field + * @return The value of field of the target object + * @throws IllegalArgumentException If the target object does not feature the desired field + * @throws IllegalAccessException If the desired field cannot be accessed + * @throws NoSuchFieldException If the desired field of the target class cannot be found + * @throws SecurityException If the desired field cannot be made accessible + * @see #getField(Class, boolean, String) + */ + public static Object getValue(Object instance, Class clazz, boolean declared, String fieldName) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { + return getField(clazz, declared, fieldName).get(instance); + } + + /** + * Returns the value of a field of a desired class of an object + * + * @param instance Target object + * @param className Name of the desired target class + * @param packageType Package where the desired target class is located + * @param declared Whether the desired field is declared or not + * @param fieldName Name of the desired field + * @return The value of field of the target object + * @throws IllegalArgumentException If the target object does not feature the desired field + * @throws IllegalAccessException If the desired field cannot be accessed + * @throws NoSuchFieldException If the desired field of the desired class cannot be found + * @throws SecurityException If the desired field cannot be made accessible + * @throws ClassNotFoundException If the desired target class with the specified name and package cannot be found + * @see #getValue(Object, Class, boolean, String) + */ + public static Object getValue(Object instance, String className, PackageType packageType, boolean declared, String fieldName) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, ClassNotFoundException { + return getValue(instance, packageType.getClass(className), declared, fieldName); + } + + /** + * Returns the value of a field with the given name of an object + * + * @param instance Target object + * @param declared Whether the desired field is declared or not + * @param fieldName Name of the desired field + * @return The value of field of the target object + * @throws IllegalArgumentException If the target object does not feature the desired field (should not occur since it searches for a field with the given name in the class of the object) + * @throws IllegalAccessException If the desired field cannot be accessed + * @throws NoSuchFieldException If the desired field of the target object cannot be found + * @throws SecurityException If the desired field cannot be made accessible + * @see #getValue(Object, Class, boolean, String) + */ + public static Object getValue(Object instance, boolean declared, String fieldName) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { + return getValue(instance, instance.getClass(), declared, fieldName); + } + + /** + * Sets the value of a field of the given class of an object + * + * @param instance Target object + * @param clazz Target class + * @param declared Whether the desired field is declared or not + * @param fieldName Name of the desired field + * @param value New value + * @throws IllegalArgumentException If the type of the value does not match the type of the desired field + * @throws IllegalAccessException If the desired field cannot be accessed + * @throws NoSuchFieldException If the desired field of the target class cannot be found + * @throws SecurityException If the desired field cannot be made accessible + * @see #getField(Class, boolean, String) + */ + public static void setValue(Object instance, Class clazz, boolean declared, String fieldName, Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { + getField(clazz, declared, fieldName).set(instance, value); + } + + /** + * Sets the value of a field of a desired class of an object + * + * @param instance Target object + * @param className Name of the desired target class + * @param packageType Package where the desired target class is located + * @param declared Whether the desired field is declared or not + * @param fieldName Name of the desired field + * @param value New value + * @throws IllegalArgumentException If the type of the value does not match the type of the desired field + * @throws IllegalAccessException If the desired field cannot be accessed + * @throws NoSuchFieldException If the desired field of the desired class cannot be found + * @throws SecurityException If the desired field cannot be made accessible + * @throws ClassNotFoundException If the desired target class with the specified name and package cannot be found + * @see #setValue(Object, Class, boolean, String, Object) + */ + public static void setValue(Object instance, String className, PackageType packageType, boolean declared, String fieldName, Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, ClassNotFoundException { + setValue(instance, packageType.getClass(className), declared, fieldName, value); + } + + /** + * Sets the value of a field with the given name of an object + * + * @param instance Target object + * @param declared Whether the desired field is declared or not + * @param fieldName Name of the desired field + * @param value New value + * @throws IllegalArgumentException If the type of the value does not match the type of the desired field + * @throws IllegalAccessException If the desired field cannot be accessed + * @throws NoSuchFieldException If the desired field of the target object cannot be found + * @throws SecurityException If the desired field cannot be made accessible + * @see #setValue(Object, Class, boolean, String, Object) + */ + public static void setValue(Object instance, boolean declared, String fieldName, Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { + setValue(instance, instance.getClass(), declared, fieldName, value); + } + + /** + * Represents an enumeration of dynamic packages of NMS and CraftBukkit + *

+ * This class is part of the ReflectionUtils and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.0 + */ + public enum PackageType { + MINECRAFT_SERVER("net.minecraft.server." + getServerVersion()), + CRAFTBUKKIT("org.bukkit.craftbukkit." + getServerVersion()), + CRAFTBUKKIT_BLOCK(CRAFTBUKKIT, "block"), + CRAFTBUKKIT_CHUNKIO(CRAFTBUKKIT, "chunkio"), + CRAFTBUKKIT_COMMAND(CRAFTBUKKIT, "command"), + CRAFTBUKKIT_CONVERSATIONS(CRAFTBUKKIT, "conversations"), + CRAFTBUKKIT_ENCHANTMENS(CRAFTBUKKIT, "enchantments"), + CRAFTBUKKIT_ENTITY(CRAFTBUKKIT, "entity"), + CRAFTBUKKIT_EVENT(CRAFTBUKKIT, "event"), + CRAFTBUKKIT_GENERATOR(CRAFTBUKKIT, "generator"), + CRAFTBUKKIT_HELP(CRAFTBUKKIT, "help"), + CRAFTBUKKIT_INVENTORY(CRAFTBUKKIT, "inventory"), + CRAFTBUKKIT_MAP(CRAFTBUKKIT, "map"), + CRAFTBUKKIT_METADATA(CRAFTBUKKIT, "metadata"), + CRAFTBUKKIT_POTION(CRAFTBUKKIT, "potion"), + CRAFTBUKKIT_PROJECTILES(CRAFTBUKKIT, "projectiles"), + CRAFTBUKKIT_SCHEDULER(CRAFTBUKKIT, "scheduler"), + CRAFTBUKKIT_SCOREBOARD(CRAFTBUKKIT, "scoreboard"), + CRAFTBUKKIT_UPDATER(CRAFTBUKKIT, "updater"), + CRAFTBUKKIT_UTIL(CRAFTBUKKIT, "util"); + + private final String path; + + /** + * Construct a new package type + * + * @param path Path of the package + */ + private PackageType(String path) { + this.path = path; + } + + /** + * Construct a new package type + * + * @param parent Parent package of the package + * @param path Path of the package + */ + private PackageType(PackageType parent, String path) { + this(parent + "." + path); + } + + /** + * Returns the path of this package type + * + * @return The path + */ + public String getPath() { + return path; + } + + /** + * Returns the class with the given name + * + * @param className Name of the desired class + * @return The class with the specified name + * @throws ClassNotFoundException If the desired class with the specified name and package cannot be found + */ + public Class getClass(String className) throws ClassNotFoundException { + return Class.forName(this + "." + className); + } + + // Override for convenience + @Override + public String toString() { + return path; + } + + /** + * Returns the version of your server + * + * @return The server version + */ + public static String getServerVersion() { + return Bukkit.getServer().getClass().getPackage().getName().substring(23); + } + } + + /** + * Represents an enumeration of Java data types with corresponding classes + *

+ * This class is part of the ReflectionUtils and follows the same usage conditions + * + * @author DarkBlade12 + * @since 1.0 + */ + public enum DataType { + BYTE(byte.class, Byte.class), + SHORT(short.class, Short.class), + INTEGER(int.class, Integer.class), + LONG(long.class, Long.class), + CHARACTER(char.class, Character.class), + FLOAT(float.class, Float.class), + DOUBLE(double.class, Double.class), + BOOLEAN(boolean.class, Boolean.class); + + private static final Map, DataType> CLASS_MAP = new HashMap, DataType>(); + private final Class primitive; + private final Class reference; + + // Initialize map for quick class lookup + static { + for (DataType type : values()) { + CLASS_MAP.put(type.primitive, type); + CLASS_MAP.put(type.reference, type); + } + } + + /** + * Construct a new data type + * + * @param primitive Primitive class of this data type + * @param reference Reference class of this data type + */ + private DataType(Class primitive, Class reference) { + this.primitive = primitive; + this.reference = reference; + } + + /** + * Returns the primitive class of this data type + * + * @return The primitive class + */ + public Class getPrimitive() { + return primitive; + } + + /** + * Returns the reference class of this data type + * + * @return The reference class + */ + public Class getReference() { + return reference; + } + + /** + * Returns the data type with the given primitive/reference class + * + * @param clazz Primitive/Reference class of the data type + * @return The data type + */ + public static DataType fromClass(Class clazz) { + return CLASS_MAP.get(clazz); + } + + /** + * Returns the primitive class of the data type with the given reference class + * + * @param clazz Reference class of the data type + * @return The primitive class + */ + public static Class getPrimitive(Class clazz) { + DataType type = fromClass(clazz); + return type == null ? clazz : type.getPrimitive(); + } + + /** + * Returns the reference class of the data type with the given primitive class + * + * @param clazz Primitive class of the data type + * @return The reference class + */ + public static Class getReference(Class clazz) { + DataType type = fromClass(clazz); + return type == null ? clazz : type.getReference(); + } + + /** + * Returns the primitive class array of the given class array + * + * @param classes Given class array + * @return The primitive class array + */ + public static Class[] getPrimitive(Class[] classes) { + int length = classes == null ? 0 : classes.length; + Class[] types = new Class[length]; + for (int index = 0; index < length; index++) { + types[index] = getPrimitive(classes[index]); + } + return types; + } + + /** + * Returns the reference class array of the given class array + * + * @param classes Given class array + * @return The reference class array + */ + public static Class[] getReference(Class[] classes) { + int length = classes == null ? 0 : classes.length; + Class[] types = new Class[length]; + for (int index = 0; index < length; index++) { + types[index] = getReference(classes[index]); + } + return types; + } + + /** + * Returns the primitive class array of the given object array + * + * @param object Given object array + * @return The primitive class array + */ + public static Class[] getPrimitive(Object[] objects) { + int length = objects == null ? 0 : objects.length; + Class[] types = new Class[length]; + for (int index = 0; index < length; index++) { + types[index] = getPrimitive(objects[index].getClass()); + } + return types; + } + + /** + * Returns the reference class array of the given object array + * + * @param object Given object array + * @return The reference class array + */ + public static Class[] getReference(Object[] objects) { + int length = objects == null ? 0 : objects.length; + Class[] types = new Class[length]; + for (int index = 0; index < length; index++) { + types[index] = getReference(objects[index].getClass()); + } + return types; + } + + /** + * Compares two class arrays on equivalence + * + * @param primary Primary class array + * @param secondary Class array which is compared to the primary array + * @return Whether these arrays are equal or not + */ + public static boolean compare(Class[] primary, Class[] secondary) { + if (primary == null || secondary == null || primary.length != secondary.length) { + return false; + } + for (int index = 0; index < primary.length; index++) { + Class primaryClass = primary[index]; + Class secondaryClass = secondary[index]; + if (primaryClass.equals(secondaryClass) || primaryClass.isAssignableFrom(secondaryClass)) { + continue; + } + return false; + } + return true; + } + } +} \ No newline at end of file diff --git a/src/sig/plugin/TwosideKeeper/HelperStructures/Utils/EntityUtils.java b/src/sig/plugin/TwosideKeeper/HelperStructures/Utils/EntityUtils.java index 7d134e4..1349871 100644 --- a/src/sig/plugin/TwosideKeeper/HelperStructures/Utils/EntityUtils.java +++ b/src/sig/plugin/TwosideKeeper/HelperStructures/Utils/EntityUtils.java @@ -16,6 +16,8 @@ import org.bukkit.entity.EntityType; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; +import org.bukkit.block.BlockFace; import sig.plugin.TwosideKeeper.Buff; import sig.plugin.TwosideKeeper.LivingEntityStructure; @@ -27,6 +29,9 @@ import sig.plugin.TwosideKeeper.HelperStructures.LivingEntityDifficulty; import sig.plugin.TwosideKeeper.HelperStructures.Common.GenericFunctions; public class EntityUtils { + final public static BlockFace[] faces = new BlockFace[]{BlockFace.EAST,BlockFace.SOUTH_EAST,BlockFace.SOUTH, + BlockFace.SOUTH_WEST,BlockFace.WEST,BlockFace.NORTH_WEST,BlockFace.NORTH,BlockFace.NORTH_EAST}; + public static int CountNearbyEntityType(EntityType type, Entity ent, double range) { List ents = ent.getNearbyEntities(range, range, range); int count=0; @@ -180,4 +185,22 @@ public class EntityUtils { GenericFunctions.sendActionBarMessage((Player)l, ""); } } + + public static BlockFace getFacingDirection(LivingEntity l) { + Vector direction = l.getLocation().getDirection(); + double rad = Math.atan2(direction.getZ(), direction.getX()); + double dir = Math.toDegrees(rad); + if (dir<0) { + dir=360-Math.abs(dir); + //-90 180 + 90 = 270 + // -0 180 + //-180 360 + } + //TwosideKeeper.log(Double.toString(dir), 0); + //+Z: 90 degrees (South) + //+X: 0 degrees (East) + //-Z: -90 degrees (North) + //-X: -180/180 degrees (West) + return faces[(int)((dir+22.5)/45)%faces.length]; + } } diff --git a/src/sig/plugin/TwosideKeeper/HelperStructures/Utils/MovementUtils.java b/src/sig/plugin/TwosideKeeper/HelperStructures/Utils/MovementUtils.java index 721d4fd..9915f4b 100644 --- a/src/sig/plugin/TwosideKeeper/HelperStructures/Utils/MovementUtils.java +++ b/src/sig/plugin/TwosideKeeper/HelperStructures/Utils/MovementUtils.java @@ -1,6 +1,7 @@ package sig.plugin.TwosideKeeper.HelperStructures.Utils; import org.bukkit.Location; +import org.bukkit.block.BlockFace; import org.bukkit.util.Vector; public class MovementUtils { @@ -47,4 +48,36 @@ public class MovementUtils { //TwosideKeeper.log("New angle is "+angle, 0); return new Vector(-velx,-vely,-velz); } + + /** + * Returns an array with a size of two elements: + * One pointing 45 degrees to the right of the specified direction. + * One pointing 45 degrees to the left of the specified direction. + */ + public static BlockFace[] get45DegreeDirections(BlockFace dir) { + int slotfound = 0; + for (int i=0;i0 && difficulty_modifier.length()>0) { - sb.append(" "); - } - sb.append(difficulty_modifier); - if (sb.length()>0 && base_name.length()>0) { - sb.append(" "); - } - sb.append(base_name); - if (sb.length()>0 && suffix.length()>0) { + if (prefix.length()==0) { + if (sb.length()>0 && difficulty_modifier.length()>0) { + sb.append(" "); + } + sb.append(difficulty_modifier); + if (sb.length()>0 && base_name.length()>0) { + sb.append(" "); + } + sb.append(base_name); + if (sb.length()>0 && suffix.length()>0) { + sb.append(" "); + } + } else { sb.append(" "); } sb.append(suffix); @@ -208,7 +213,11 @@ public class LivingEntityStructure { } else if (GenericFunctions.isIsolatedTarget(m, p)) { setGlow(p,GlowAPI.Color.WHITE); - } else { + } + if (Knight.isKnight(m)) { + setGlow(p,GlowAPI.Color.AQUA); + } + else { //No glow. //setGlow(p,null); if (glowcolorlist.containsKey(p.getUniqueId())) { @@ -219,7 +228,7 @@ public class LivingEntityStructure { if (!GlowAPI.isGlowing(m, p) && glowcolorlist.containsKey(p.getUniqueId())) { GlowAPI.setGlowing(m, glowcolorlist.get(p.getUniqueId()), p); } else - if (GlowAPI.isGlowing(m, p) && !glowcolorlist.get(p.getUniqueId()).equals(GlowAPI.getGlowColor(m, p))) { + if (GlowAPI.isGlowing(m, p) && (p==null || !glowcolorlist.get(p.getUniqueId()).equals(GlowAPI.getGlowColor(m, p)))) { GlowAPI.setGlowing(m, glowcolorlist.get(p.getUniqueId()), p); } } diff --git a/src/sig/plugin/TwosideKeeper/Monster/DarkSpider.java b/src/sig/plugin/TwosideKeeper/Monster/DarkSpider.java new file mode 100644 index 0000000..6d84001 --- /dev/null +++ b/src/sig/plugin/TwosideKeeper/Monster/DarkSpider.java @@ -0,0 +1,204 @@ +package sig.plugin.TwosideKeeper.Monster; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Color; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.CaveSpider; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Spider; +import org.bukkit.potion.PotionEffectType; + +import sig.plugin.TwosideKeeper.Buff; +import sig.plugin.TwosideKeeper.CustomDamage; +import sig.plugin.TwosideKeeper.CustomMonster; +import sig.plugin.TwosideKeeper.LivingEntityStructure; +import sig.plugin.TwosideKeeper.MonsterController; +import sig.plugin.TwosideKeeper.TwosideKeeper; +import sig.plugin.TwosideKeeper.Events.EntityChannelCastEvent; +import sig.plugin.TwosideKeeper.HelperStructures.Channel; +import sig.plugin.TwosideKeeper.HelperStructures.LivingEntityDifficulty; +import sig.plugin.TwosideKeeper.HelperStructures.Spell; +import sig.plugin.TwosideKeeper.HelperStructures.Common.GenericFunctions; +import sig.plugin.TwosideKeeper.HelperStructures.Utils.SoundUtils; +import sig.plugin.TwosideKeeper.HelperStructures.Utils.Classes.MixedDamage; + +public class DarkSpider extends CustomMonster{ + + Knight linked_knight; + + final static int[] SILENCE_DURATIONS = new int[]{200,100,60}; + final static int[] MINION_HEALTH = new int[]{900,3000,18000}; + List temp_spiders = new ArrayList(); + final Spell SPIDERSUMMON = new Spell("Summon Spider",new int[]{100,60,40},new int[]{600,500,400}); + final Spell ULTRABURST = new Spell("UltraBurst",new int[]{80,80,80},new int[]{1200,900,800}, new MixedDamage[]{MixedDamage.v(200),MixedDamage.v(1000),MixedDamage.v(50,0.95)}); + + int randomness = 16; + + public DarkSpider(LivingEntity m) { + super(m); + LivingEntityStructure les = LivingEntityStructure.GetLivingEntityStructure(m); + MonsterController.convertLivingEntity(m, LivingEntityDifficulty.NORMAL); + les.setCustomLivingEntityName(m, ChatColor.DARK_RED+"Dark Spider"); + m.setMaxHealth(100000); + m.setHealth(m.getMaxHealth()); + m.setAI(true); + } + + public void runTick() { + if (canCastSpells()) { //SPELL CASTS HERE. + castSpiderSummon(); + } + } + + private void castSpiderSummon() { + CastSpell(SPIDERSUMMON); + } + + public void runChannelCastEvent(EntityChannelCastEvent ev) { + switch (ev.getAbilityName()) { + case "Summon Spider":{ + //Create another Spider. + DarkSpiderMinion dsm = InitializeDarkSpiderMinion(linked_knight); + dsm.linked_knight = linked_knight; + SPIDERSUMMON.setLastCastedTime(TwosideKeeper.getServerTickTime()); + }break; + case "UltraBurst":{ + playUltraBurst(); + ULTRABURST.setLastCastedTime(TwosideKeeper.getServerTickTime()); + }break; + } + } + + private void playUltraBurst() { + SoundUtils.playGlobalSound(m.getLocation(), Sound.ENTITY_TNT_PRIMED, 1.0f, 1.0f); + for (int i=0;i<3;i++) { + Bukkit.getScheduler().runTaskLater(TwosideKeeper.plugin, ()->{ + SoundUtils.playGlobalSound(m.getLocation(), Sound.ENTITY_TNT_PRIMED, 1.0f, 1.2f);}, (i+1)*10); + } + Bukkit.getScheduler().runTaskLater(TwosideKeeper.plugin, ()->{ + aPlugin.API.sendSoundlessExplosion(m.getLocation(), 6); + m.getWorld().spawnParticle(Particle.LAVA, m.getLocation(), 30); + m.getWorld().spawnParticle(Particle.EXPLOSION_HUGE, m.getLocation(), 2); + SoundUtils.playGlobalSound(m.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 1.0f, 0.7f); + + DealSpellDamageToNearbyPlayers(ULTRABURST,50,true,false,5); + m.remove(); + }, 40); + } + + private void DealSpellDamageToNearbyPlayers(Spell spell, int range, boolean knockup, boolean dodgeable, double knockupamt) { + List players = GenericFunctions.DealDamageToNearbyPlayers(m.getLocation(), spell.getDamageValues()[getDifficultySlot()].getDmgComponent(), range, knockup, dodgeable, knockupamt, m, spell.getName(), false); + if (spell.getDamageValues()[getDifficultySlot()].hasTruePctDmgComponent()) {GenericFunctions.DealDamageToNearbyPlayers(m.getLocation(), spell.getDamageValues()[getDifficultySlot()].getTruePctDmgComponent(), range, knockup, dodgeable, knockupamt, m, spell.getName(), false,true);} + if (spell.getDamageValues()[getDifficultySlot()].hasTrueDmgComponent()) {GenericFunctions.DealDamageToNearbyPlayers(m.getLocation(), spell.getDamageValues()[getDifficultySlot()].getTrueDmgComponent(), range, knockup, dodgeable, knockupamt, m, spell.getName(), true);} + for (Player p : players) { + GenericFunctions.addStackingPotionEffect(p, PotionEffectType.CONFUSION, 20*6, 1); + } + } + + private DarkSpiderMinion InitializeDarkSpiderMinion(Knight knight) { + LivingEntity knight_ent = knight.GetMonster(); + CaveSpider s = (CaveSpider)knight_ent.getWorld().spawnEntity(m.getLocation(), EntityType.CAVE_SPIDER); + DarkSpiderMinion dsm = new DarkSpiderMinion(s); + TwosideKeeper.custommonsters.put(s.getUniqueId(), dsm); + s.setMaxHealth(MINION_HEALTH[getDifficultySlot()]); + s.setHealth(s.getMaxHealth()); + temp_spiders.add(s); + return dsm; + } + + protected void CastSpell(Spell spell) { + if (hasValidKnight() && + cooldownIsAvailable(spell.getLastCastedTime(),spell)) { + Channel.createNewChannel(m, spell.getName(), spell.getCastTimes()[getDifficultySlot()]); + } + } + + private boolean cooldownIsAvailable(long spell_timer, Spell spell) { + return spell_timer+spell.getCooldowns()[getDifficultySlot()]<=TwosideKeeper.getServerTickTime(); + } + + public static double getDamageReduction() { + return 1.0; + } + + public static Spider InitializeDarkSpider(LivingEntity sourceKnight) { + Spider s = (Spider)sourceKnight.getWorld().spawnEntity(sourceKnight.getLocation(), EntityType.SPIDER); + DarkSpider ds = new DarkSpider(s); + TwosideKeeper.custommonsters.put(s.getUniqueId(), ds); + return s; + } + + public static boolean isDarkSpider(LivingEntity m) { + return (m instanceof Spider) && + m.getMaxHealth()==100000 && + MonsterController.getLivingEntityDifficulty(m)==LivingEntityDifficulty.NORMAL; + } + + public LivingEntityDifficulty getDifficulty() { + if (hasValidKnight()) { + return MonsterController.getLivingEntityDifficulty(linked_knight.GetMonster()); + } else { + return LivingEntityDifficulty.T1_MINIBOSS; + } + } + + private boolean hasValidKnight() { + return linked_knight!=null && linked_knight.GetMonster()!=null && linked_knight.GetMonster().isValid(); + } + + public int getDifficultySlot() { + switch (getDifficulty()) { + case T1_MINIBOSS:{ + return 0; + } + case T2_MINIBOSS:{ + return 1; + } + case T3_MINIBOSS:{ + return 2; + } + default:{ + TwosideKeeper.log("WARNING! Could not get proper difficulty slot for Difficulty "+getDifficulty()+". Defaulting to slot 0.", 1); + return 0; + } + } + } + + public boolean canCastSpells() { + return Math.random()<=1/16d && !Buff.hasBuff(m, "SILENCE") && hasValidKnight() && linked_knight.startedfight && !Channel.isChanneling(m); + } + + public void onHitEvent(LivingEntity damager, double damage) { + Buff.addBuff( + m, "SILENCE", new Buff( + "Silence",SILENCE_DURATIONS[getDifficultySlot()], + 0,Color.BLUE,ChatColor.WHITE+"…",false)); + if (Channel.isChanneling(m)) { + Channel.stopChanneling(m); + } + if (hasValidKnight() && + (damager instanceof Player)) { + linked_knight.addParticipant((Player)damager); + } + } + + public void cleanup() { + for (LivingEntity l : temp_spiders) { + if (l!=null && l.isValid()) { + if (TwosideKeeper.custommonsters.containsKey(l.getUniqueId())) { + CustomMonster cm = TwosideKeeper.custommonsters.get(l.getUniqueId()); + cm.cleanup(); + } + l.remove(); + } + } + m.remove(); + } +} diff --git a/src/sig/plugin/TwosideKeeper/Monster/DarkSpiderMinion.java b/src/sig/plugin/TwosideKeeper/Monster/DarkSpiderMinion.java new file mode 100644 index 0000000..53d5193 --- /dev/null +++ b/src/sig/plugin/TwosideKeeper/Monster/DarkSpiderMinion.java @@ -0,0 +1,38 @@ +package sig.plugin.TwosideKeeper.Monster; + +import org.bukkit.ChatColor; +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.CaveSpider; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Spider; + +import sig.plugin.TwosideKeeper.CustomMonster; +import sig.plugin.TwosideKeeper.LivingEntityStructure; +import sig.plugin.TwosideKeeper.MonsterController; +import sig.plugin.TwosideKeeper.HelperStructures.LivingEntityDifficulty; +import sig.plugin.TwosideKeeper.HelperStructures.Spell; + +public class DarkSpiderMinion extends DarkSpider{ + + public DarkSpiderMinion(LivingEntity m) { + super(m); + LivingEntityStructure les = LivingEntityStructure.GetLivingEntityStructure(m); + les.setCustomLivingEntityName(m, ChatColor.DARK_PURPLE+"Dark Spider Minion"); + m.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED).setBaseValue(0.5f); + } + + public void runTick() { + super.runTick(); + if (canCastSpells()) { + CastSpell(ULTRABURST); + } + } + + public static boolean isDarkSpiderMinion(LivingEntity m) { + return (m instanceof CaveSpider) && + (m.getMaxHealth()==MINION_HEALTH[0] || + m.getMaxHealth()==MINION_HEALTH[1] || + m.getMaxHealth()==MINION_HEALTH[2]) && + MonsterController.getLivingEntityDifficulty(m)==LivingEntityDifficulty.NORMAL; + } +} diff --git a/src/sig/plugin/TwosideKeeper/Monster/Knight.java b/src/sig/plugin/TwosideKeeper/Monster/Knight.java new file mode 100644 index 0000000..adae081 --- /dev/null +++ b/src/sig/plugin/TwosideKeeper/Monster/Knight.java @@ -0,0 +1,603 @@ +package sig.plugin.TwosideKeeper.Monster; + +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.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.BlockFace; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarFlag; +import org.bukkit.boss.BarStyle; +import org.bukkit.boss.BossBar; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Monster; +import org.bukkit.entity.Player; +import org.bukkit.entity.Skeleton; +import org.bukkit.entity.Skeleton.SkeletonType; +import org.inventivetalent.glow.GlowAPI.Color; +import org.bukkit.entity.Spider; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; + +import sig.plugin.TwosideKeeper.Buff; +import sig.plugin.TwosideKeeper.ChargeZombie; +import sig.plugin.TwosideKeeper.CustomMonster; +import sig.plugin.TwosideKeeper.LivingEntityStructure; +import sig.plugin.TwosideKeeper.MonsterController; +import sig.plugin.TwosideKeeper.TwosideKeeper; +import sig.plugin.TwosideKeeper.Events.EntityChannelCastEvent; +import sig.plugin.TwosideKeeper.HelperStructures.Channel; +import sig.plugin.TwosideKeeper.HelperStructures.LivingEntityDifficulty; +import sig.plugin.TwosideKeeper.HelperStructures.Spell; +import sig.plugin.TwosideKeeper.HelperStructures.Common.GenericFunctions; +import sig.plugin.TwosideKeeper.HelperStructures.Effects.DarkSlash; +import sig.plugin.TwosideKeeper.HelperStructures.Utils.BlockUtils; +import sig.plugin.TwosideKeeper.HelperStructures.Utils.EntityUtils; +import sig.plugin.TwosideKeeper.HelperStructures.Utils.ItemUtils; +import sig.plugin.TwosideKeeper.HelperStructures.Utils.MovementUtils; +import sig.plugin.TwosideKeeper.HelperStructures.Utils.SoundUtils; +import sig.plugin.TwosideKeeper.HelperStructures.Utils.Classes.MixedDamage; + +public class Knight extends CustomMonster{ + + DarkSpider spider_pet; + protected String arrow = "->"; + protected List participantlist = new ArrayList(); + protected HashMap dpslist = new HashMap(); + BossBar healthbar; + long lasthit; + boolean startedfight=false; + boolean isFlying=false; + private long stuckTimer=0; + int scroll=0; + private Location lastLoc = null; + + final static int[] ASSASSINATE_COOLDOWN = new int[]{320,280,240}; + long lastusedassassinate = TwosideKeeper.getServerTickTime(); + final Spell DARKSLASH = new Spell("Dark Slash",new int[]{60,40,40},new int[]{400,300,200},new MixedDamage[]{MixedDamage.v(150),MixedDamage.v(300),MixedDamage.v(300,0.1)}); + final Spell LINEDRIVE = new Spell("Line Drive",new int[]{20,10,10},new int[]{800,700,600},new MixedDamage[]{MixedDamage.v(200),MixedDamage.v(400),MixedDamage.v(400, 0.2)}); + + int randomness = 20; + + + public Knight(LivingEntity m) { + super(m); + LivingEntityStructure les = LivingEntityStructure.GetLivingEntityStructure(m); + les.setCustomLivingEntityName(m, ChatColor.GOLD+"Knight"); + LivingEntityDifficulty led = MonsterController.getLivingEntityDifficulty(m); + switch (led) { + case T1_MINIBOSS:{ + m.setMaxHealth(18000); + }break; + case T2_MINIBOSS:{ + m.setMaxHealth(47000); + }break; + case T3_MINIBOSS:{ + m.setMaxHealth(116000); + }break; + } + m.setHealth(m.getMaxHealth()); + relinkToSpider(); + m.setAI(false); + createBossHealthbar(); + //GenericFunctions.setGlowing(m, Color.AQUA); + setupDarkSword(); + } + + public void runTick() { + updateHealthbarForNearbyPlayers(); + relinkToSpider(); + displayDarkSwordParticles(); + updateTargetIfLost(); + regenerateHealthAndResetBossIfIdle(); + keepHealthbarUpdated(); + keepSpiderPetNearby(); + unstuckIfStuck(); + preventTargetFromBeingTheSameAsSpider(); + increaseBarTextScroll(); + performSpells(); + } + + public void runChannelCastEvent(EntityChannelCastEvent ev) { + switch (ev.getAbilityName()) { + case "Dark Slash":{ + TwosideKeeper.windslashes.add( + new DarkSlash(m.getLocation(),m,DARKSLASH.getDamageValues()[getDifficultySlot()],20*20) + ); + BlockFace[] dirs = MovementUtils.get45DegreeDirections(EntityUtils.getFacingDirection(m)); + for (BlockFace face : dirs) { + TwosideKeeper.windslashes.add( + new DarkSlash(m.getLocation().add( + new Vector(face.getModX(),face.getModY(),face.getModZ()).multiply(3) + ),m,DARKSLASH.getDamageValues()[getDifficultySlot()],20*20) + ); + } + DARKSLASH.setLastCastedTime(TwosideKeeper.getServerTickTime()); + }break; + case "Line Drive":{ + m.setVelocity(new Vector(0,0.6,0)); + Bukkit.getScheduler().runTaskLater(TwosideKeeper.plugin, ()->{ + m.setVelocity(m.getLocation().getDirection().multiply(8)); + int range = 8; + double xspd = m.getLocation().getDirection().getX()*2; + double zspd = m.getLocation().getDirection().getZ()*2; + for (int i=0;i{ + + },2); + } + }, 4); + }break; + } + } + + protected void CastSpell(Spell spell) { + if (cooldownIsAvailable(spell.getLastCastedTime(),spell)) { + //Face target. + FaceTarget(m); + Channel.createNewChannel(m, spell.getName(), spell.getCastTimes()[getDifficultySlot()]); + } + } + + private void FaceTarget(LivingEntity m) { + if (((Monster)m).getTarget()!=null) { + Location loc = m.getLocation(); + loc.setDirection(MovementUtils.pointTowardsLocation(loc, ((Monster)m).getTarget().getLocation())); + m.teleport(loc); + } + } + + private boolean cooldownIsAvailable(long spell_timer, Spell spell) { + return spell_timer+spell.getCooldowns()[getDifficultySlot()]<=TwosideKeeper.getServerTickTime(); + } + + private void performSpells() { + final Runnable[] actions = new Runnable[]{ + ()->{performAssassinate();}, + ()->{CastSpell(DARKSLASH);}, + ()->{changeAggroToRandomNewTarget();CastSpell(LINEDRIVE);}}; + if (canCastSpells()) { + for (Runnable r : actions) { + if (Math.random()<=1d/actions.length) { + Bukkit.getScheduler().runTask(TwosideKeeper.plugin, r); + break; + } + } + } + } + + private void performAssassinate() { + if (lastusedassassinate+ASSASSINATE_COOLDOWN[getDifficultySlot()]<=TwosideKeeper.getServerTickTime()) { + lastusedassassinate=TwosideKeeper.getServerTickTime(); + Player p = setAggroOnRandomTarget(); + Location teleloc = p.getLocation().add(p.getLocation().getDirection().multiply(-1d)); + if (teleloc.getBlock().getType().isSolid() || + teleloc.getBlock().getRelative(0,1,0).getType().isSolid()) { + teleloc = p.getLocation(); + } + m.teleport(teleloc); + SoundUtils.playGlobalSound(m.getLocation(), Sound.BLOCK_NOTE_SNARE, 1.0f, 1.0f); + } + } + + private Player setAggroOnRandomTarget() { + Player p = pickRandomTarget(); + setAggro((Monster)m,p); + return p; + } + + public LivingEntityDifficulty getDifficulty() { + return MonsterController.getLivingEntityDifficulty(m); + } + + public int getDifficultySlot() { + switch (getDifficulty()) { + case T1_MINIBOSS:{ + return 0; + } + case T2_MINIBOSS:{ + return 1; + } + case T3_MINIBOSS:{ + return 2; + } + default:{ + TwosideKeeper.log("WARNING! Could not get proper difficulty slot for Difficulty "+getDifficulty()+". Defaulting to slot 0.", 1); + return 0; + } + } + } + + private void changeAggroToRandomNewTarget() { + if (Math.random()<=0.5) { + Monster me = (Monster)m; + Player newtarget = pickRandomTarget(); + setAggro(me, newtarget); + } + } + + private void setAggro(Monster me, Player newtarget) { + if (newtarget!=null) { + me.setTarget(newtarget); + LivingEntityStructure les = LivingEntityStructure.GetLivingEntityStructure(m); + les.SetTarget(me.getTarget()); + } + } + + private Player pickRandomTarget() { + if (participantlist.size()>0) { + for (Player p : participantlist) { + if (Math.random()<=1d/participantlist.size()) { + return p; + } + } + return participantlist.get(0); + } else { + return null; + } + } + + private boolean canCastSpells() { + return Math.random()<=1/20d && !Buff.hasBuff(m, "SILENCE") && startedfight && !Channel.isChanneling(m); + } + + private void preventTargetFromBeingTheSameAsSpider() { + if (isValidSpiderPet()) { + Monster me = (Monster)m; + Monster spider = (Monster)spider_pet.GetMonster(); + if (spider.getTarget()!=null && me.getTarget()!=null && + spider.getTarget().equals(me.getTarget())) { + if (Math.random()<=0.5) { + Location newloc = spider.getLocation().add(Math.random()*15-5,0,0); + if (!newloc.getBlock().getType().isSolid() && + !newloc.getBlock().getRelative(0,1,0).getType().isSolid()) { + //SoundUtils.playGlobalSound(spider.getLocation(), Sound.ENTITY_ENDERMEN_TELEPORT, 1.0f, 1.0f); + spider.teleport(newloc); + spider.setTarget(null); + } + } else { + Location newloc = spider.getLocation().add(Math.random()*10-5,0,0); + if (!newloc.getBlock().getType().isSolid() && + !newloc.getBlock().getRelative(0,1,0).getType().isSolid()) { + //SoundUtils.playGlobalSound(spider.getLocation(), Sound.ENTITY_ENDERMEN_TELEPORT, 1.0f, 1.0f); + spider.teleport(newloc); + spider.setTarget(null); + } + } + } + } + } + + private boolean isValidSpiderPet() { + return spider_pet!=null && spider_pet.GetMonster()!=null && + spider_pet.GetMonster().isValid(); + } + + private void unstuckIfStuck() { + if (!startedfight) { + ChargeZombie.BreakBlocksAroundArea((Monster)m, 1); + } else + if (startedfight) { + lastLoc = m.getLocation().clone(); + if (lastLoc!=null && lastLoc.distance(m.getLocation())<=0.4) { + stuckTimer++; + //TwosideKeeper.log("Stuck. "+stuckTimer, 0); + ChargeZombie.BreakBlocksAroundArea((Monster)m, 1); + } else { + stuckTimer=0; + } + if (!Channel.isChanneling(m) && stuckTimer>5) { + //Teleport randomly. + double numb = Math.random(); + if (numb<=0.33) { + SoundUtils.playGlobalSound(m.getLocation(), Sound.ENTITY_ENDERMEN_TELEPORT, 0.4f, 0.95f); + m.teleport(m.getLocation().add(Math.random()*10-5,0,0)); + } else + if (numb<=0.5) { + SoundUtils.playGlobalSound(m.getLocation(), Sound.ENTITY_ENDERMEN_TELEPORT, 0.4f, 0.95f); + m.teleport(m.getLocation().add(0,0,Math.random()*10-5)); + } + stuckTimer=0; + } + } + } + + private void keepSpiderPetNearby() { + if (isValidSpiderPet()) { + LivingEntityStructure les = LivingEntityStructure.GetLivingEntityStructure(spider_pet.GetMonster()); + les.SetTarget(m); + } + } + + private void keepHealthbarUpdated() { + healthbar.setProgress(m.getHealth()/m.getMaxHealth()); + Monster me = (Monster)m; + healthbar.setTitle(GenericFunctions.getDisplayName(m) + ((me.getTarget()!=null && (me.getTarget() instanceof Player))?(ChatColor.DARK_AQUA+" "+arrow+" "+ChatColor.YELLOW+((Player)me.getTarget()).getName()):"")); + if (isValidSpiderPet()) { + spider_pet.GetMonster().setHealth(spider_pet.GetMonster().getMaxHealth()); + } + } + + private void regenerateHealthAndResetBossIfIdle() { + if (lasthit+20*15<=TwosideKeeper.getServerTickTime()) { + GenericFunctions.HealEntity(m, m.getMaxHealth()*0.01); + if (startedfight) { + healthbar.setColor(BarColor.GREEN); + } + } else { + if (startedfight) { + healthbar.setColor(BarColor.BLUE); + } + } + if (participantlist.size()==0 && startedfight) { + startedfight=false; + m.setAI(false); + m.setHealth(m.getMaxHealth()); + announceFailedTakedown(); + } + } + + private void updateTargetIfLost() { + Monster mm = (Monster)m; + LivingEntityStructure les = LivingEntityStructure.GetLivingEntityStructure(m); + if (mm.getTarget()==null || !mm.getTarget().isValid() || + les.GetTarget()==null || !mm.getTarget().isValid() || + (!isFlying && (mm.getTarget().getLocation().distanceSquared(mm.getLocation())>2500 || + les.GetTarget().getLocation().distanceSquared(mm.getLocation())>2500 + ))) { + //See if there's another participant in the list. Choose randomly. + while (participantlist.size()>0) { + Player p = participantlist.get((int)(Math.random()*participantlist.size())); + if (p!=null && p.isValid() && + (isFlying || p.getLocation().distanceSquared(mm.getLocation())<=2500)) { + mm.setTarget(p); + les.SetTarget(p); + break; + } else { + participantlist.remove(p); + } + } + if (participantlist.size()==0 && startedfight) { + //This fight has failed. + announceFailedTakedown(); + startedfight=false; + } + } + } + + public void announceFailedTakedown() { + if (dpslist.size()>0 && !m.isDead()) { + Bukkit.getServer().broadcastMessage(GenericFunctions.getDisplayName(m)+" Takedown Failed..."); + Bukkit.getServer().broadcastMessage(ChatColor.YELLOW+"DPS Breakdown:"); + Bukkit.getServer().broadcastMessage(generateDPSReport()); + aPlugin.API.discordSendRaw(GenericFunctions.getDisplayName(m)+" Takedown Failed...\n\n"+ChatColor.YELLOW+"DPS Breakdown:"+"\n```\n"+generateDPSReport()+"\n```"); + dpslist.clear(); + PerformSpiderCleanup(); + healthbar.setColor(BarColor.WHITE); + } + } + + private void PerformSpiderCleanup() { + if (spider_pet!=null && spider_pet.GetMonster()!=null) { + spider_pet.GetMonster().remove(); + spider_pet.cleanup(); + } + } + + public String generateDPSReport() { + //Sorts a list of players by DPS contribution. + List sorted_dmg = new ArrayList(); + List sorted_pl = new ArrayList(); + double totaldmg = 0; + for (String pl : dpslist.keySet()) { + double dmg = dpslist.get(pl); + int slot = 0; + totaldmg+=dmg; + for (int i=0;isorted_dmg.get(i)) { + break; + } else { + slot++; + } + } + sorted_pl.add(slot,pl); + sorted_dmg.add(slot,dmg); + } + StringBuilder finalstr = new StringBuilder(); + DecimalFormat df = new DecimalFormat("0.00"); + for (int i=0;i2500) { + healthbar.removePlayer(p); + } + } + for (Entity e : m.getNearbyEntities(50, 50, 50)) { + if (e instanceof Player) { + Player p = (Player)e; + healthbar.addPlayer(p); + } + } + } + + private void SetupSpiderPet(LivingEntity m) { + if (!TwosideKeeper.custommonsters.containsKey(m)) { + TwosideKeeper.custommonsters.put(m.getUniqueId(),new DarkSpider((LivingEntity)m)); + } + spider_pet=(DarkSpider)TwosideKeeper.custommonsters.get(m.getUniqueId()); + spider_pet.GetMonster().setAI(false); + spider_pet.linked_knight=this; + } + + public static boolean randomlyConvertAsKnight(LivingEntity m) { + return randomlyConvertAsKnight(m,false); + } + + public static boolean randomlyConvertAsKnight(LivingEntity m, boolean force) { + if ((TwosideKeeper.MINIBOSSES_ACTIVATED && + TwosideKeeper.LAST_SPECIAL_SPAWN+(6000/Math.max(Bukkit.getOnlinePlayers().size(),1))<=TwosideKeeper.getServerTickTime() && + Math.random()<=0.01) || force) { + Skeleton s = (Skeleton)m; + s.setSkeletonType(SkeletonType.WITHER); + Spider ss = DarkSpider.InitializeDarkSpider(m); + //ss.setPassenger(s); + //Determine distance from Twoside for Difficulty. + double chancer = TwosideKeeper.TWOSIDE_LOCATION.distanceSquared(m.getLocation()); + if (Math.random()*chancer<4000000) { + MonsterController.convertLivingEntity(m, LivingEntityDifficulty.T1_MINIBOSS); + } else + if (Math.random()*chancer<25000000) { + MonsterController.convertLivingEntity(m, LivingEntityDifficulty.T2_MINIBOSS); + } else { + MonsterController.convertLivingEntity(m, LivingEntityDifficulty.T3_MINIBOSS); + } + return true; + } + return false; + } + + public static boolean isKnight(LivingEntity m) { + return m instanceof Skeleton && + ((Skeleton)m).getSkeletonType()==SkeletonType.WITHER && + ( + MonsterController.getLivingEntityDifficulty(m)==LivingEntityDifficulty.T1_MINIBOSS || + MonsterController.getLivingEntityDifficulty(m)==LivingEntityDifficulty.T2_MINIBOSS || + MonsterController.getLivingEntityDifficulty(m)==LivingEntityDifficulty.T3_MINIBOSS + ); + } + + public static double getDamageReduction() { + return 0.2; + } + + public void cleanup() { + healthbar.removeAll(); + if (startedfight) { + announceFailedTakedown(); + startedfight=false; + } + PerformSpiderCleanup(); + } + + protected void increaseBarTextScroll() { + scroll++; + switch (scroll%22) { + case 11:{ + arrow=" -"; + }break; + case 12:{ + arrow=" "; + }break; + case 13:{ + arrow="> "; + }break; + case 14:{ + arrow="->"; + }break; + } + } +} diff --git a/src/sig/plugin/TwosideKeeper/MonsterController.java b/src/sig/plugin/TwosideKeeper/MonsterController.java index d109c7b..7055977 100644 --- a/src/sig/plugin/TwosideKeeper/MonsterController.java +++ b/src/sig/plugin/TwosideKeeper/MonsterController.java @@ -36,6 +36,7 @@ import sig.plugin.TwosideKeeper.HelperStructures.Loot; import sig.plugin.TwosideKeeper.HelperStructures.MonsterDifficulty; import sig.plugin.TwosideKeeper.HelperStructures.MonsterType; import sig.plugin.TwosideKeeper.HelperStructures.Common.GenericFunctions; +import sig.plugin.TwosideKeeper.Monster.Knight; public class MonsterController { /** @@ -842,24 +843,13 @@ public class MonsterController { }*/ LivingEntityStructure les = LivingEntityStructure.GetLivingEntityStructure(m); String difficulty_modifier = les.difficulty_modifier; - if (difficulty_modifier.contains("Dangerous")) { - return LivingEntityDifficulty.DANGEROUS; - } else - if (difficulty_modifier.contains("Deadly")) { - return LivingEntityDifficulty.DEADLY; - } else - if (difficulty_modifier.contains("Hellfire")) { - return LivingEntityDifficulty.HELLFIRE; - } else - if (difficulty_modifier.contains("Elite")) { - return LivingEntityDifficulty.ELITE; - } else - if (difficulty_modifier.contains("End ")) { - return LivingEntityDifficulty.END; - } else - { - return LivingEntityDifficulty.NORMAL; + for (LivingEntityDifficulty led : LivingEntityDifficulty.values()) { + if (led!=LivingEntityDifficulty.NORMAL && + difficulty_modifier.equalsIgnoreCase(led.getDifficultyString())) { + return led; + } } + return LivingEntityDifficulty.NORMAL; } @Deprecated @@ -1070,31 +1060,8 @@ public class MonsterController { ms.UpdateGlow(); m.getAttribute(Attribute.GENERIC_FOLLOW_RANGE).setBaseValue(72.0); }break; - default: { - if (isAllowedToEquipItems(m)) { - m.getEquipment().clear(); - RandomizeEquipment(m,0); - } else { - m.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE,Integer.MAX_VALUE,0)); - } - SetupCustomName(led,m); - if(isZombieLeader(m)) - { - m.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE,Integer.MAX_VALUE,8)); - m.setMaxHealth(400); - m.setHealth(m.getMaxHealth()); - m.setCustomName("Zombie Leader"); - LivingEntityStructure ms = LivingEntityStructure.GetLivingEntityStructure(m); - ms.SetLeader(true); - ms.UpdateGlow(); - TwosideKeeper.log("->Setting an entity with Difficulty "+led.name()+" w/"+m.getHealth()+"/"+m.getMaxHealth()+" HP to a Leader.",5); - } else { - m.setMaxHealth(m.getMaxHealth()*1.0); - m.setHealth(m.getMaxHealth()); - } - m.getAttribute(Attribute.GENERIC_FOLLOW_RANGE).setBaseValue(24.0); - }break; case END:{ + SetupCustomName(led,m); //m.setCustomName(ChatColor.DARK_AQUA+"Dangerous Mob"); //m.setCustomNameVisible(true); if (m.getType()!=EntityType.ENDERMAN) { @@ -1121,6 +1088,35 @@ public class MonsterController { } m.getAttribute(Attribute.GENERIC_FOLLOW_RANGE).setBaseValue(64.0); }break; + case T1_MINIBOSS: + case T2_MINIBOSS: + case T3_MINIBOSS:{ + SetupCustomName(led,m); + }break; + default: { + if (isAllowedToEquipItems(m)) { + m.getEquipment().clear(); + RandomizeEquipment(m,0); + } else { + m.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE,Integer.MAX_VALUE,0)); + } + SetupCustomName(led,m); + if(isZombieLeader(m)) + { + m.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE,Integer.MAX_VALUE,8)); + m.setMaxHealth(400); + m.setHealth(m.getMaxHealth()); + m.setCustomName("Zombie Leader"); + LivingEntityStructure ms = LivingEntityStructure.GetLivingEntityStructure(m); + ms.SetLeader(true); + ms.UpdateGlow(); + TwosideKeeper.log("->Setting an entity with Difficulty "+led.name()+" w/"+m.getHealth()+"/"+m.getMaxHealth()+" HP to a Leader.",5); + } else { + m.setMaxHealth(m.getMaxHealth()*1.0); + m.setHealth(m.getMaxHealth()); + } + m.getAttribute(Attribute.GENERIC_FOLLOW_RANGE).setBaseValue(24.0); + }break; } removeZombieLeaderAttribute(m); return m; diff --git a/src/sig/plugin/TwosideKeeper/TwosideKeeper.java b/src/sig/plugin/TwosideKeeper/TwosideKeeper.java index 6dc0ba0..2f845b9 100644 --- a/src/sig/plugin/TwosideKeeper/TwosideKeeper.java +++ b/src/sig/plugin/TwosideKeeper/TwosideKeeper.java @@ -19,6 +19,7 @@ import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Chunk; import org.bukkit.Color; +import org.bukkit.Effect; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; @@ -28,6 +29,7 @@ import org.bukkit.Statistic; import org.bukkit.WorldCreator; import org.bukkit.attribute.Attribute; import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; import org.bukkit.block.Chest; import org.bukkit.block.DoubleChest; @@ -149,6 +151,7 @@ import org.bukkit.event.player.PlayerToggleSprintEvent; import org.bukkit.event.server.ServerCommandEvent; import org.bukkit.event.server.ServerListPingEvent; import org.bukkit.event.vehicle.VehicleDestroyEvent; +import org.bukkit.event.vehicle.VehicleEnterEvent; import org.bukkit.event.vehicle.VehicleExitEvent; import org.bukkit.event.weather.LightningStrikeEvent; import org.bukkit.event.world.ChunkLoadEvent; @@ -233,6 +236,7 @@ import sig.plugin.TwosideKeeper.HelperStructures.Common.ItemContainer; import sig.plugin.TwosideKeeper.HelperStructures.Common.JobRecipe; import sig.plugin.TwosideKeeper.HelperStructures.Common.RecipeCategory; import sig.plugin.TwosideKeeper.HelperStructures.Common.RecipeLinker; +import sig.plugin.TwosideKeeper.HelperStructures.Effects.DarkSlash; import sig.plugin.TwosideKeeper.HelperStructures.Effects.EarthWaveTask; import sig.plugin.TwosideKeeper.HelperStructures.Effects.LavaPlume; import sig.plugin.TwosideKeeper.HelperStructures.Effects.ReplaceBlockTask; @@ -249,9 +253,12 @@ import sig.plugin.TwosideKeeper.HelperStructures.Utils.InventoryUtils; import sig.plugin.TwosideKeeper.HelperStructures.Utils.ItemCubeUtils; import sig.plugin.TwosideKeeper.HelperStructures.Utils.ItemUtils; import sig.plugin.TwosideKeeper.HelperStructures.Utils.MessageUtils; +import sig.plugin.TwosideKeeper.HelperStructures.Utils.MovementUtils; import sig.plugin.TwosideKeeper.HelperStructures.Utils.PlayerUtils; import sig.plugin.TwosideKeeper.HelperStructures.Utils.SoundUtils; import sig.plugin.TwosideKeeper.HelperStructures.Utils.TimeUtils; +import sig.plugin.TwosideKeeper.HelperStructures.Utils.Classes.ColoredParticle; +import sig.plugin.TwosideKeeper.HelperStructures.Utils.Classes.MixedDamage; import sig.plugin.TwosideKeeper.HelperStructures.Utils.Classes.SoundData; import sig.plugin.TwosideKeeper.HolidayEvents.Christmas; import sig.plugin.TwosideKeeper.HolidayEvents.TreeBuilder; @@ -260,6 +267,7 @@ import sig.plugin.TwosideKeeper.Logging.LootLogger; import sig.plugin.TwosideKeeper.Logging.MysteriousEssenceLogger; import sig.plugin.TwosideKeeper.Monster.Dummy; import sig.plugin.TwosideKeeper.Monster.HellfireGhast; +import sig.plugin.TwosideKeeper.Monster.Knight; import sig.plugin.TwosideKeeper.Monster.MonsterTemplate; @@ -312,6 +320,7 @@ public class TwosideKeeper extends JavaPlugin implements Listener { public static Set notWorldShop = new HashSet(); public static List suppressed_entities = new ArrayList(); public static List lavaplume_list = new ArrayList(); + public static long LAST_SPECIAL_SPAWN = 0; public static CustomItem HUNTERS_COMPASS; public static CustomItem UPGRADE_SHARD; @@ -540,6 +549,7 @@ public class TwosideKeeper extends JavaPlugin implements Listener { public final static boolean CHRISTMASLINGERINGEVENT_ACTIVATED=false; public final static boolean ELITEGUARDIANS_ACTIVATED=false; + public final static boolean MINIBOSSES_ACTIVATED=false; public final static boolean NEWARTIFACTABILITIES_ACTIVATED=false; public static final Set LIVING_ENTITY_TYPES = ImmutableSet.of( @@ -784,6 +794,7 @@ public class TwosideKeeper extends JavaPlugin implements Listener { sig.plugin.TwosideKeeper.Monster.Wither w = (sig.plugin.TwosideKeeper.Monster.Wither)cs; w.Cleanup(); } + cs.cleanup(); ScheduleRemoval(custommonsters,cs.m.getUniqueId()); } else { cs.runTick(); @@ -893,6 +904,7 @@ public class TwosideKeeper extends JavaPlugin implements Listener { TwosideKeeper.log("WARNING! Structure Handling took longer than 1 tick! "+((int)(System.nanoTime()-totaltime)/1000000d)+"ms", 0); } TwosideKeeper.HeartbeatLogger.AddEntry(ChatColor.LIGHT_PURPLE+"Total Structure Handling", (int)(System.nanoTime()-totaltime));totaltime=System.nanoTime(); + } private void UpdateLavaBlock(Block lavamod) { @@ -1989,9 +2001,38 @@ public class TwosideKeeper extends JavaPlugin implements Listener { } } }break; + case "KNIGHT":{ + LivingEntity m = MonsterController.convertLivingEntity((Skeleton)p.getWorld().spawnEntity(p.getLocation(),EntityType.SKELETON), + LivingEntityDifficulty.T1_MINIBOSS); + Knight.randomlyConvertAsKnight(m,true); + TwosideKeeper.custommonsters.put(m.getUniqueId(),new Knight(m)); + }break; + case "DAMAGETEST":{ + LivingEntity m = MonsterController.convertLivingEntity((Skeleton)p.getWorld().spawnEntity(p.getLocation(),EntityType.SKELETON), + LivingEntityDifficulty.T1_MINIBOSS); + GenericFunctions.DealDamageToNearbyPlayers(p.getLocation(), Double.parseDouble(args[1]), 50, true, false, 3, m, "Explosion", false, true); + m.remove(); + }break; + case "FACINGDIRECTION":{ + TwosideKeeper.log(EntityUtils.getFacingDirection(p).name(),0); + }break; + case "DARKSLASH":{ + BlockFace[] dirs = MovementUtils.get90DegreeDirections(EntityUtils.getFacingDirection(p)); + TwosideKeeper.windslashes.add( + new DarkSlash(p.getLocation(),p,MixedDamage.v(0),20*20) + ); + for (BlockFace face : dirs) { + //TwosideKeeper.log("Vector is "+(new Vector(face.getModX(),face.getModY(),face.getModZ())), 0); + TwosideKeeper.windslashes.add( + new DarkSlash(p.getLocation().add( + new Vector(face.getModX(),face.getModY(),face.getModZ()).multiply(8) + ),p,MixedDamage.v(0),20*20) + ); + } + }break; } } - LivingEntity m = MonsterController.convertMonster((Monster)p.getWorld().spawnEntity(p.getLocation(),EntityType.ZOMBIE), MonsterDifficulty.ELITE); + //LivingEntity m = MonsterController.convertMonster((Monster)p.getWorld().spawnEntity(p.getLocation(),EntityType.ZOMBIE), MonsterDifficulty.ELITE); /* StackTraceElement[] stacktrace = new Throwable().getStackTrace(); StringBuilder stack = new StringBuilder("Mini stack tracer:"); @@ -4533,6 +4574,11 @@ public class TwosideKeeper extends JavaPlugin implements Listener { } }break; } + UUID key = ev.getLivingEntity().getUniqueId(); + if (custommonsters.containsKey(key)) { + CustomMonster cm = custommonsters.get(key); + cm.runChannelCastEvent(ev); + } } @EventHandler(priority=EventPriority.LOW,ignoreCancelled = true) @@ -4705,7 +4751,9 @@ public class TwosideKeeper extends JavaPlugin implements Listener { } case "Explosion": case "BLOCK_EXPLOSION": - case "MELTING":{ + case "MELTING": + case "UltraBurst": + { return Pronouns.ChoosePronoun(5); } case "Leap": { @@ -4745,6 +4793,9 @@ public class TwosideKeeper extends JavaPlugin implements Listener { case "Orni": { return "was killed by merely existing."; } + case "Dark Slash":{ + return "was sliced into darkness."; + } default:{ return "has died by "+pd.lasthitdesc; } @@ -6277,7 +6328,7 @@ public class TwosideKeeper extends JavaPlugin implements Listener { for (Entity e : ev.getChunk().getEntities()) { if (e instanceof LivingEntity) { LivingEntity l = (LivingEntity)e; - if (l!=null && l.isValid()) { + if (l!=null && l.isValid() && (!(l instanceof Player))) { LivingEntityStructure les = LivingEntityStructure.GetLivingEntityStructure(l); l.setCustomName(les.getUnloadedName()); } @@ -6320,6 +6371,7 @@ public class TwosideKeeper extends JavaPlugin implements Listener { } } } + CustomDamage.addToCustomStructures(m); } } @@ -6424,7 +6476,8 @@ public class TwosideKeeper extends JavaPlugin implements Listener { } if (ev.getEntity() instanceof LivingEntity) { LivingEntity m = ev.getEntity(); - LivingEntityStructure.GetLivingEntityStructure(m); + //LivingEntityStructure.GetLivingEntityStructure(m); + CustomDamage.addToCustomStructures(m); } } @@ -9455,6 +9508,14 @@ public class TwosideKeeper extends JavaPlugin implements Listener { } } } + + @EventHandler(priority=EventPriority.LOW,ignoreCancelled = true) + public void MinecartExitEvent(VehicleEnterEvent ev) { + //Attempt to update the entity a few ticks later. + Bukkit.getScheduler().runTaskLater(plugin, ()->{ + ev.getEntered().teleport(ev.getVehicle()); + }, 5); + } @EventHandler(priority=EventPriority.LOW,ignoreCancelled = true) public void MinecartExitEvent(VehicleExitEvent ev) { @@ -9564,6 +9625,7 @@ public class TwosideKeeper extends JavaPlugin implements Listener { getConfig().set("LAST_ELITE_SPAWN", LAST_ELITE_SPAWN); getConfig().set("LAST_DEAL", LAST_DEAL); getConfig().set("WEATHER_WATCH_USERS", weather_watch_users); + getConfig().set("LAST_SPECIAL_SPAWN", LAST_SPECIAL_SPAWN); if (ELITE_LOCATION!=null) { getConfig().set("ELITE_LOCATION_X", ELITE_LOCATION.getBlockX()); getConfig().set("ELITE_LOCATION_Z", ELITE_LOCATION.getBlockZ()); @@ -9629,6 +9691,7 @@ public class TwosideKeeper extends JavaPlugin implements Listener { getConfig().addDefault("WORLD_SHOP_MULT", worldShopPriceMult); getConfig().addDefault("LAST_DEAL", TimeUtils.GetCurrentDayOfWeek()); getConfig().addDefault("WEATHER_WATCH_USERS", weather_watch_users); + getConfig().addDefault("LAST_SPECIAL_SPAWN", LAST_SPECIAL_SPAWN); getConfig().options().copyDefaults(true); saveConfig(); SERVERTICK = getConfig().getLong("SERVERTICK"); @@ -9667,6 +9730,7 @@ public class TwosideKeeper extends JavaPlugin implements Listener { worldShopPriceMult = getConfig().getDouble("WORLD_SHOP_MULT"); LAST_DEAL = getConfig().getInt("LAST_DEAL"); weather_watch_users = (List)getConfig().getList("WEATHER_WATCH_USERS"); + LAST_SPECIAL_SPAWN = getConfig().getLong("LAST_SPECIAL_SPAWN"); if (getConfig().contains("ELITE_LOCATION_X")) { int x = getConfig().getInt("ELITE_LOCATION_X"); int z = getConfig().getInt("ELITE_LOCATION_Z"); diff --git a/src/sig/plugin/TwosideKeeper/runServerTick.java b/src/sig/plugin/TwosideKeeper/runServerTick.java index 468b278..ea480d7 100644 --- a/src/sig/plugin/TwosideKeeper/runServerTick.java +++ b/src/sig/plugin/TwosideKeeper/runServerTick.java @@ -1,6 +1,10 @@ package sig.plugin.TwosideKeeper; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + import sig.plugin.TwosideKeeper.HelperStructures.Common.BlockModifyQueue; +import sig.plugin.TwosideKeeper.HelperStructures.Utils.Classes.ColoredParticle; public class runServerTick implements Runnable{ final int queuespd = 3; @@ -14,6 +18,15 @@ public class runServerTick implements Runnable{ } } runServerHeartbeat.resetDamageQueue(); + /*if (Bukkit.getPlayer("sigonasr2")!=null) { + Player p = Bukkit.getPlayer("sigonasr2"); + + for (int i=0;i<200;i++) { + ColoredParticle.RED_DUST.send(p.getEyeLocation().add( + p.getLocation().getDirection()).add(0,-0.05*i,0) + , 20, 0, 0, 0); + } + }*/ } }