Alchemical hydra script
Code:
/**
* The Npc extension for the hydras.
*
*/
class Hydra : Npc {
/**
* The amount of attacks left until it changes.
*/
var recordedAttaacks: Int = 3
/**
* The hydra's current attack.
*/
var currentAttack: HydraAttacks = HydraAttacks.MAGIC
/**
* The moment of the last hydra's poison pool attack.
*/
var lastPoisonPool: Long = System.currentTimeMillis()
constructor(id: Int, world: World?, tile: Tile?) : super(id, world, tile)
override fun takeHit(hit: Hit?) {
super.takeHit(hit)
if (System.currentTimeMillis() - lastPoisonPool > 60000L) {
lastPoisonPool = System.currentTimeMillis()
}
}
}
/**
* The combat script for the hydra.
*/
object HydraCombatScript {
@JvmField
val script: Function1<Script, Unit> = s@ @Suspendable {
val npc = it.npc()
if (npc !is Hydra) {
return@s
}
var target = EntityCombat.getTarget(it) ?: return@s
while (!npc.locked() && EntityCombat.targetOk(npc, target) && PlayerCombat.canAttack(npc, target)) {
if (EntityCombat.canAttackDistant(npc, target, true, 5) && EntityCombat.attackTimerReady(npc)) {
if (System.currentTimeMillis() - npc.lastPoisonPool >= 30000L) {
sendPoisonAttack(npc, target)
npc.lastPoisonPool = System.currentTimeMillis()
} else {
regularAttack(npc, target)
}
target.putattrib(AttributeKey.LAST_DAMAGER, npc)
target.putattrib(AttributeKey.LAST_WAS_ATTACKED_TIME, System.currentTimeMillis())
npc.putattrib(AttributeKey.LAST_TARGET, target)
EntityCombat.putCombatDelay(npc, npc.combatInfo().attackspeed)
}
EntityCombat.postHitLogic(npc)
it.delay(1)
target = EntityCombat.refreshTarget(it) ?: return@s
}
}
/**
* Sends the hydra's ranged or magical attack.
*/
@Suspendable
fun regularAttack(hydra: Hydra, target: Entity) {
hydra.world().executeScript @Suspendable {
hydra.animate(hydraAttacks[Misc.random(hydraAttacks.size - 1)])
hydra.recordedAttaacks--
if (hydra.recordedAttaacks == 0) {
hydra.currentAttack = if (hydra.currentAttack == HydraAttacks.MAGIC) HydraAttacks.RANGED else HydraAttacks.MAGIC
hydra.recordedAttaacks = 3
}
sendProjectile(hydra, target, if (hydra.currentAttack == HydraAttacks.MAGIC) 1663 else 1662)
it.delay(if (hydra.currentAttack == HydraAttacks.MAGIC) Misc.getMagicDelay(hydra.tile(), target.tile()) else Misc.getRangeDelay(hydra.tile(), target.tile()))
target.hit(hydra, Misc.random(hydra.combatInfo().maxhit), 0, Hit.Type.REGULAR).combatStyle(if (hydra.currentAttack == HydraAttacks.MAGIC) CombatStyle.MAGIC else CombatStyle.RANGE)
}
}
/**
* Sends the poison pool attack.
*/
@Suspendable
fun sendPoisonAttack(hydra: Npc, target: Entity) {
hydra.world().executeScript @Suspendable {
val poolAmount = Misc.random(1, 2)
val pools = ArrayList<Tile>()
pools.add(target.tile())
for (i in 1..poolAmount) {
pools.add(target.tile().transform(Misc.random(-3, 3), Misc.random(-3, 3), 0))
}
hydra.animate(hydraAttacks[Misc.random(hydraAttacks.size - 1)])
for (pool in pools) {
fireProjectileToLocation(hydra, pool, 1644, 50, 90, 0, 5, 55, 0)
}
it.delay(3)
for (pool in pools) {
hydra.world().tileGraphic(1645, pool, 0, 0)
}
it.delay(1)
for (pool in pools) {
val graphicId = getPoolGraphic(hydra, pool)
hydra.world().tileGraphic(graphicId, pool, 0, 0)
}
for (i in 0..10) {
for (pool in pools) {
if (pool == target.tile()) {
target.hit(hydra, Misc.random(12), 0, Hit.Type.POISON)
}
}
it.delay(1)
}
}
}
/**
* Fires a projectile from the hydra to a tile.
*/
private fun fireProjectileToLocation(hydra: Npc, tile: Tile, projectile: Int, delay: Int, projectileSpeed: Int, angle: Int, stepness: Int, startHeight: Int, endHeight: Int) {
val center = Misc.getCenterLocation(hydra)
val dir = Direction.of(tile.x - center.x, tile.z - center.z)
val from = center.transform(dir.x * 2, dir.y * 2)
hydra.world().spawnProjectile(from, tile, projectile, startHeight, endHeight, delay, projectileSpeed, angle, stepness)
}
/**
* Sends the projectile from the hydra to the target.
*/
private fun sendProjectile(npc: Npc, target: Entity, projectile: Int) {
val center = Misc.getCenterLocation(npc)
val dir = Direction.of(target.tile().x - center.x, target.tile().z - center.z)
val speed = Misc.getSpeedModifier(npc.tile(), target.tile())
val from = center.transform(dir.x, dir.y)
npc.world().spawnProjectile(from, target.tile(), projectile, 35, 30, 30, speed, 10, 10)
}
}
/**
* The hydra's attack animations.
*/
private val hydraAttacks = arrayOf(8261, 8262, 8263)
Code:
/**
* The alchemical hydra SCRIPT.
*
*/
class AlchemicalHydra : Npc {
/**
* The amount of attacks left until it changes.
*/
var recordedAttaacks: Int = 3
/**
* The hydra's current phase.
*/
var currentPhase: HydraPhase = HydraPhase.GREEN
/**
* The hydra's current attack.
*/
var currentAttack: HydraAttacks = HydraAttacks.MAGIC
/**
* The moment of the last hydra's fire wall attack.
*/
var lastFirewall: Long = 0L
/**
* The moment of the last hydra's poison pool attack.
*/
var lastPoisonPool: Long = 0L
/**
* The moment of the last hydra's lightning attack.
*/
var lastLightning: Long = 0L
/**
* The hydra's instance base location
*/
val baseLocation: Tile
/**
* If the hydra's shield is dropped.
*/
var shieldDropped: Boolean = false
/**
* The delay for the vent to show up
*/
var ventsDelay: Int = 15
/**
* The owner of this instanced hydra
*/
var owner: Player
/**
* Constructor for the hydra.
*/
constructor(world: World?, tile: Tile, owner: Player) : super(8615, world, tile) {
putattrib(AttributeKey.MAX_DISTANCE_FROM_SPAWN, 30)
respawns(false)
baseLocation = tile.transform(-hydraSpawnLoc.x, -hydraSpawnLoc.z, 0)
this.owner = owner
animateVents(-1)
}
override fun cycle() {
super.cycle()
if (locked()) {
return
}
ventsDelay--
if(ventsDelay == 5) {
animateVents(8279)
}
if (ventsDelay == 3 && !isEnraged() && !shieldDropped) {
val baseTile = tile().transform(-baseLocation.x, -baseLocation.z)
if (isWithinRedVent(baseTile, size())) {
if (currentPhase == HydraPhase.GREEN) {
sync().shout("Roaaaaaaaaaaar!")
shieldDropped = true
} else if (currentPhase == HydraPhase.RED) {
owner.message("The chemicals are absorbed by the Alchemical Hydra; empowering it further!")
}
} else if (isWithinBlueVent(baseTile, size())) {
if (currentPhase == HydraPhase.RED) {
sync().shout("Roaaaaaaaaaaar!")
shieldDropped = true
} else if (currentPhase == HydraPhase.BLUE) {
owner.message("The chemicals are absorbed by the Alchemical Hydra; empowering it further!")
}
} else if (isWithinGreenVent(baseTile, size())) {
if (currentPhase == HydraPhase.BLUE) {
sync().shout("Roaaaaaaaaaaar!")
shieldDropped = true
} else if (currentPhase == HydraPhase.GREEN) {
owner.message("The chemicals are absorbed by the Alchemical Hydra; empowering it further!")
}
}
if (shieldDropped) {
owner.message("The chemicals neutralise the Alchemical Hydra's defence!")
}
}
if (ventsDelay == 0) {
animateVents(8280)
ventsDelay = 20
}
val healthAmount = hp() * 1.0 / (maxHp() * 1.0)
if (healthAmount <= 0.75 && healthAmount > 0.50 && currentPhase === HydraPhase.GREEN) {
changePhase()
}
if (healthAmount <= 0.50 && healthAmount > 0.25 && currentPhase === HydraPhase.BLUE) {
changePhase()
}
if (healthAmount <= 0.25 && currentPhase === HydraPhase.RED) {
changePhase()
}
}
fun isEnraged(): Boolean {
return currentPhase == HydraPhase.ENRAGED
}
/**
* Returns the hydra's current attack anim based on its recorded attacks
*/
fun getAttackAnim(): Int {
return currentPhase.attackAnims[if (recordedAttaacks == 0) recordedAttaacks else recordedAttaacks - 1]
}
/**
* Returns the hydra's next attack.
*/
fun getNextAttack(): HydraAttacks {
if (recordedAttaacks > 0) {
return currentAttack
}
val possibleAttacks = ArrayList<HydraAttacks>()
for (value in HydraAttacks.values()) {
if (value.phaseRequired == null || value.phaseRequired == currentPhase) {
if (value == HydraAttacks.FIRE_WALL) {
if ((System.currentTimeMillis() - lastFirewall > fireWallDelay)) {
possibleAttacks.add(value)
}
} else if (value == HydraAttacks.POISON) {
if ((System.currentTimeMillis() - lastPoisonPool > poisonPoolDelay)) {
possibleAttacks.add(value)
}
} else if (value == HydraAttacks.LIGHTNING) {
if ((System.currentTimeMillis() - lastLightning > lightningDelay)) {
possibleAttacks.add(value)
}
} else {
possibleAttacks.add(value)
}
}
}
recordedAttaacks = if (isEnraged()) 1 else 3
var attack = possibleAttacks[Misc.random(possibleAttacks.size - 1)]
if (attack == HydraAttacks.POISON) {
lastPoisonPool = System.currentTimeMillis()
} else if (attack == HydraAttacks.FIRE_WALL) {
lastFirewall = System.currentTimeMillis()
} else if (attack == HydraAttacks.LIGHTNING) {
lastLightning = System.currentTimeMillis()
} else if (attack == HydraAttacks.RANGED && currentAttack == HydraAttacks.RANGED) {
attack = HydraAttacks.MAGIC
} else if (attack == HydraAttacks.MAGIC && currentAttack == HydraAttacks.MAGIC) {
attack = HydraAttacks.RANGED
}
return attack
}
/**
* Changes the hydra's phase to the next phase
*/
fun changePhase() {
currentPhase.switchPhase(this)
currentPhase = HydraPhase.values()[currentPhase.ordinal + 1]
if (currentPhase == HydraPhase.RED) {
lastFirewall = 10000L
}
shieldDropped = false
}
/**
* Animates the vent within the instance area.
*/
fun animateVents(animId: Int) {
for (vent in vents) {
val instanceVent = world().objById(vent.id(), baseLocation.transform(vent.tile()))
owner.write(SetMapBase(owner, instanceVent.tile()))
owner.write(AnimateObject(instanceVent, animId))
}
}
}
/**
* The script for the hydra's attack.
*/
object AlchemicalHydraAttackScript {
@JvmField
val script: Function1<Script, Unit> = s@ @Suspendable {
val npc = it.npc()
if (npc !is AlchemicalHydra) {
return@s
}
var target = EntityCombat.getTarget(it) ?: return@s
while (!npc.locked() && EntityCombat.targetOk(npc, target) && PlayerCombat.canAttack(npc, target)) {
if (EntityCombat.canAttackDistant(npc, target, true, 5) && EntityCombat.attackTimerReady(npc)) {
npc.recordedAttaacks--
var nextAttackType = npc.getNextAttack()
if (nextAttackType == HydraAttacks.RANGED || nextAttackType == HydraAttacks.MAGIC) {
npc.currentAttack = nextAttackType
}
nextAttackType.executeAttack(npc, target as Player)
EntityCombat.putCombatDelay(npc, 5)
}
EntityCombat.postHitLogic(npc)
it.delay(1)
target = EntityCombat.refreshTarget(it) ?: return@s
}
}
}
Code:
[/**
* The attacks that the hydra can perform.
*
*/
enum class HydraAttacks(val phaseRequired: HydraPhase?) {
/**
* The default magical attack.
*/
MAGIC(null) {
@Suspendable
override fun executeAttack(hydra: AlchemicalHydra, target: Entity) {
hydra.world().executeScript @Suspendable {
hydra.animate(hydra.getAttackAnim())
fireProjectileToEntity(hydra, target, 1663, 50)
it.delay(Misc.getMagicDelay(hydra.tile(), target.tile()))
target.hit(hydra, hydra.world().random(if (hydra.isEnraged()) 26 else 17), 0).combatStyle(CombatStyle.MAGIC)
}
}
},
/**
* The default ranged attack.
*/
RANGED(null) {
@Suspendable
override fun executeAttack(hydra: AlchemicalHydra, target: Entity) {
hydra.world().executeScript @Suspendable {
hydra.animate(hydra.getAttackAnim())
fireProjectileToEntity(hydra, target, 1662, 50)
fireProjectileToEntity(hydra, target, 1662, 60)
it.delay(Misc.getRangeDelay(hydra.tile(), target.tile()))
target.hit(hydra, hydra.world().random(if (hydra.isEnraged()) 26 else 17), 0).combatStyle(CombatStyle.RANGE)
}
}
},
/**
* The poison pool for the first phase.
*/
POISON(HydraPhase.GREEN) {
@Suspendable
override fun executeAttack(hydra: AlchemicalHydra, target: Entity) {
hydra.world().executeScript @Suspendable {
val poolAmount = Misc.random(4, 5)
val pools = getPoolTiles(hydra.baseLocation, target.tile(), poolAmount)
hydra.animate(HydraPhase.GREEN.attackAnims[Misc.random(HydraPhase.GREEN.attackAnims.size - 1)])
for (pool in pools) {
fireProjectileToLocation(hydra, pool, 1644, 50, 90, 0, 5, 55, 0)
}
it.delay(3)
for (pool in pools) {
hydra.world().tileGraphic(1645, pool, 0, 0)
}
it.delay(1)
for (pool in pools) {
val graphicId = getPoolGraphic(hydra, pool)
hydra.world().tileGraphic(graphicId, pool, 0, 0)
}
for (i in 0..10) {
for (pool in pools) {
if (pool == target.tile()) {
target.hit(hydra, Misc.random(12), 0, Hit.Type.POISON)
}
}
it.delay(1)
}
}
}
},
/**
* The lightning strike for the second phase.
*/
LIGHTNING(HydraPhase.BLUE) {
@Suspendable
override fun executeAttack(hydra: AlchemicalHydra, target: Entity) {
hydra.world().executeScript @Suspendable {
hydra.animate(HydraPhase.BLUE.attackAnims[Misc.random(HydraPhase.BLUE.attackAnims.size - 1)])
val base = hydra.baseLocation
val central = base.transform(centralLightningSpot)
var spots = ArrayList<Tile>(lightningSpots)
fireProjectileToLocation(hydra, central, 1664, 50, 80, 0, 0, 0, 0)
it.delay(2)
hydra.world().tileGraphic(1664, central, 0, 0)
it.delay(1)
for (spot in spots) {
fireProjectileToLocation(hydra.world(), central, base.transform(spot), 1665, 0, 40, 0, 5, 55, 0)
}
it.delay(1)
for (i in 0..10) {
for (spot in spots) {
hydra.world().tileGraphic(1666, base.transform(spot), 0, 0)
}
val newSpots = ArrayList<Tile>()
for (spot in ArrayList<Tile>(spots)) {
if (base.transform(spot) == target.tile()) {
target.hit(hydra, Misc.random(20), 0, Hit.Type.REGULAR)
if (target is Player) {
target.message("<col=ff0000>The electricity temporarily paralyzes you!")
target.stun(8, false, false)
}
} else {
val newSpot = getMoveLocation(base, spot, target.tile())
newSpots.add(newSpot)
}
}
spots = newSpots
it.delay(1)
}
}
}
},
/**
* The wall of fire for the third phase.
*/
FIRE_WALL(HydraPhase.RED) {
@Suspendable
override fun executeAttack(hydra: AlchemicalHydra, target: Entity) {
hydra.world().executeScript @Suspendable {
hydra.lockNoAttack()
hydra.face(null)
val base = hydra.baseLocation
hydra.walkTo(base.transform(hydraSpawnLoc), PathQueue.StepType.FORCED_WALK)
it.delay(1)
while (hydra.tile() != base.transform(hydraSpawnLoc)) {
it.delay(1)
}
it.delay(1)
val fireWallSpots = getWallFireSpots(target.tile(), base)
val spotOffsets = fireWallSpots.spots
val spots = ArrayList<Tile>()
for (i in 0..1) {
val faceSpot = base.transform((spotOffsets[i * 2].x + spotOffsets[i * 2 + 1].x) / 2, (spotOffsets[i * 2].z + spotOffsets[i * 2 + 1].z) / 2)
hydra.faceTile(faceSpot)
hydra.animate(hydra.getAttackAnim())
for (spot in fireWallSpots.projectileSpots) {
for (x in fireWallSpots.projectileSpots[i * 2].x..fireWallSpots.projectileSpots[i * 2 + 1].x) {
for (z in fireWallSpots.projectileSpots[i * 2].z..fireWallSpots.projectileSpots[i * 2 + 1].z) {
fireProjectileToLocation(hydra, base.transform(Tile(x, z, 0)), 1667, 45, 55, 15, 15, 55, 0)
}
}
}
it.delay(1)
for (spot in spots) {
if (target.tile() == spot) {
target.hit(hydra, Misc.random(20), 0, Hit.Type.REGULAR)
}
}
it.delay(1)
for (spot in spots) {
if (target.tile() == spot) {
target.hit(hydra, Misc.random(20), 0, Hit.Type.REGULAR)
}
}
fireWallSpots.spawnFireWall(hydra, base, i, spots)
it.delay(1)
for (spot in spots) {
if (target.tile() == spot) {
target.hit(hydra, Misc.random(20), 0, Hit.Type.REGULAR)
}
}
}
it.delay(1)
for (spot in spots) {
if (target.tile() == spot) {
target.hit(hydra, Misc.random(20), 0, Hit.Type.REGULAR)
}
}
hydra.face(target)
var startMovingFire = Tile(Misc.random(spotOffsets[0].x, spotOffsets[1].x), Misc.random(spotOffsets[0].z, spotOffsets[1].z), 0)
hydra.animate(hydra.getAttackAnim())
fireProjectileToLocation(hydra, base.transform(startMovingFire), 1667, 45, 50, 0, 5, 55, 0)
it.delay(1)
for (spot in spots) {
if (target.tile() == spot) {
target.hit(hydra, Misc.random(20), 0, Hit.Type.REGULAR)
}
}
it.delay(1)
spots.add(base.transform(startMovingFire))
for (i in 0..fireWallDuration) {
for (spot in spots) {
if (target.tile() == spot) {
target.hit(hydra, Misc.random(20), 0, Hit.Type.REGULAR)
}
}
if (i < 16) {
if (base.transform(startMovingFire) == target.tile()) {
target.hit(hydra, Misc.random(20), 0, Hit.Type.REGULAR)
} else {
startMovingFire = getMoveLocation(base, startMovingFire, target.tile())
spots.add(startMovingFire)
hydra.world().tileGraphic(1668, base.transform(startMovingFire), 0, 0)
}
} else if (i == 16) {
hydra.unlock()
}
it.delay(1)
}
}
}
},
/**
* The poison pool for the final and enraged version.
*/
ENRAGED_POISON(HydraPhase.ENRAGED) {
@Suspendable
override fun executeAttack(hydra: AlchemicalHydra, target: Entity) {
hydra.world().executeScript @Suspendable {
val poolAmount = if (Misc.randomFloat() < 0.6) 1 else Misc.random(4, 5)
val pools = getPoolTiles(hydra.baseLocation, target.tile(), poolAmount)
hydra.animate(HydraPhase.ENRAGED.attackAnims[Misc.random(HydraPhase.ENRAGED.attackAnims.size - 1)])
for (pool in pools) {
fireProjectileToLocation(hydra, pool, 1644, 50, 90, 0, 5, 55, 0)
}
it.delay(3)
for (pool in pools) {
hydra.world().tileGraphic(1645, pool, 0, 0)
}
it.delay(1)
for (pool in pools) {
val graphicId = getPoolGraphic(hydra, pool)
hydra.world().tileGraphic(graphicId, pool, 0, 0)
}
for (i in 0..10) {
for (pool in pools) {
if (pool == target.tile()) {
target.hit(hydra, Misc.random(12), 0, Hit.Type.POISON)
}
}
it.delay(1)
}
}
}
},
;
/**
* Executes the hydra's attack.
*/
@Suspendable
abstract fun executeAttack(hydra: AlchemicalHydra, target: Entity)
/**
* Fires a projectile from the hydra to the target.
*/
internal fun fireProjectileToEntity(hydra: AlchemicalHydra, target: Entity, projectile: Int, delay: Int) {
val center = Misc.getCenterLocation(hydra)
val dir = Direction.of(target.tile().x - center.x, target.tile().z - center.z)
val from = center.transform(dir.x * 2, dir.y * 2)
val speed = Misc.getSpeedModifier(from, target.tile())
hydra.world().spawnProjectile(from, target, projectile, 55, 25, delay, speed, 15, 15)
}
/**
* Fires a projectile from the hydra to a tile.
*/
internal fun fireProjectileToLocation(hydra: AlchemicalHydra, tile: Tile, projectile: Int, delay: Int, projectileSpeed: Int, angle: Int, stepness: Int, startHeight: Int, endHeight: Int) {
val center = Misc.getCenterLocation(hydra)
val dir = Direction.of(tile.x - center.x, tile.z - center.z)
val from = center.transform(dir.x * 2, dir.y * 2)
hydra.world().spawnProjectile(from, tile, projectile, startHeight, endHeight, delay, projectileSpeed, angle, stepness)
}
/**
* Fires a projectile from a tile to another.
*/
internal fun fireProjectileToLocation(world: World, from: Tile, to: Tile, projectile: Int, delay: Int, projectileSpeed: Int, angle: Int, stepness: Int, startHeight: Int, endHeight: Int) {
world.spawnProjectile(from, to, projectile, startHeight, endHeight, delay, projectileSpeed, angle, stepness)
}
}
Code:
/**
* The alchemical hydra SCRIPT.
*
*/
/**
* The delay between the fire wall attacks.
*/
val fireWallDelay = 45000L
/**
* The delay between the poison pool attacks.
*/
val poisonPoolDelay = 15000L
/**
* The delay between the lightning attacks.
*/
val lightningDelay = 25000L
/**
* The duration for the fire wall effect.
*/
val fireWallDuration = 43
/**
* The base location for the instances.
*/
val baseLocation = Tile(1328, 10248, 0)
/**
* The hydra's spawn location.
*/
val hydraSpawnLoc = Tile(36, 17, 0)
/**
* The blue vent central spot.
*/
val blueVentLoc = Tile(34, 24, 0)
/**
* The green vent central spot.
*/
val greenVentLoc = Tile(43, 24, 0)
/**
* The red vent central spot.
*/
val redVentLoc = Tile(43, 15, 0)
/**
* The blue vent object.
*/
val blueVent = MapObj(blueVentLoc, 34570, 10, 0, false)
/**
* The red vent object.
*/
val redVent = MapObj(redVentLoc, 34568, 10, 0, false)
/**
* The green vent object.
*/
val greenVent = MapObj(greenVentLoc, 34569, 10, 0, false)
/**
* The list of vents.
*/
val vents = arrayListOf(blueVent, redVent, greenVent)
/**
* The spot the hydra will send the first projectile for lightning.
*/
val centralLightningSpot = Tile(39, 14, 0)
/**
* The forbidden spots for the attacks.
*/
val forbiddenSpots = arrayListOf(blueVentLoc, greenVentLoc, redVentLoc, Tile(48, 29, 0), Tile(49, 30, 0), Tile(49, 29, 0), Tile(49, 30, 0), Tile(28, 29, 0), Tile(28, 30, 0), Tile(29, 29, 0), Tile(29, 30, 0), Tile(48, 9, 0), Tile(49, 9, 0), Tile(48, 10, 0), Tile(49, 10, 0))
/**
* The spots the lightning spawns
*/
val lightningSpots = arrayListOf(Tile(44, 15, 0), Tile(34, 15, 0), Tile(44, 24, 0), Tile(33, 24, 0))
/**
* Returns if the location is within the instance's chamber.
*/
fun isWithinChamber(baseLocation: Tile, location: Tile): Boolean {
val xOffset = location.x - baseLocation.x
val zOffset = location.z - baseLocation.z
return xOffset in 28..49 && zOffset in 9..30 && !isForbiddenSpot(Tile(xOffset, zOffset, location.level))
}
/**
* Returns if the tile is a forbidden one.
*/
fun isForbiddenSpot(tile: Tile): Boolean {
for (forbiddenSpot in forbiddenSpots) {
if (forbiddenSpot == tile) {
return true
}
}
return false
}
/**
* Returns if the tile is within the blue vent.
*/
fun isWithinBlueVent(tile: Tile, size: Int): Boolean {
return Misc.isWithinDiagonalDistance(tile, blueVentLoc, size, 1, 0)
}
/**
* Returns if the tile is within the gree vent.
*/
fun isWithinGreenVent(tile: Tile, size: Int): Boolean {
return Misc.isWithinDiagonalDistance(tile, greenVentLoc, size, 1, 0)
}
/**
* Returns if the tile is within the red vent.
*/
fun isWithinRedVent(tile: Tile, size: Int): Boolean {
return Misc.isWithinDiagonalDistance(tile, redVentLoc, size, 1, 0)
}
/**
* Returns the pool graphic for the poison pool attack.
*/
fun getPoolGraphic(hydra: Npc, targetTile: Tile): Int {
val center = Misc.getCenterLocation(hydra)
val dir = Direction.of(targetTile.x - center.x, targetTile.z - center.z)
for (pool in PoisonPools.values()) {
if (pool.offsetX == dir.x && pool.offsetZ == dir.y) {
return pool.graphicId
}
}
return 1654
}
/**
* Returns the list of pool tiles.
*/
fun getPoolTiles(baseLocation: Tile, tile: Tile, poolAmount: Int): ArrayList<Tile> {
val pools = ArrayList<Tile>()
for (i in 0..poolAmount) {
var randTile: Tile
for (x in 0..100) {
randTile = tile.transform(Misc.random(-3, 3), Misc.random(-3, 3), 0)
var matches = false
for (pool in pools) {
if (pool == randTile) {
matches = true
break
}
}
if (!matches && isWithinChamber(baseLocation, tile)) {
pools.add(randTile)
break
}
}
}
return pools
}
/**
* Returns the location the tile has to move to reach the target.
*/
fun getMoveLocation(baseLocation: Tile, currentLoc: Tile, target: Tile): Tile {
val dir = Direction.of(target.x - (baseLocation.x + currentLoc.x), target.z - (baseLocation.z + currentLoc.z))
return currentLoc.transform(dir.x, dir.y, 0)
}
/**
* The poison pools graphics based on the position offset.
*
*/
enum class PoisonPools(val offsetX: Int, val offsetZ: Int, val graphicId: Int) {
NORTH(0, 1, 1659),
NORTH_EAST(1, 1, 1660),
EAST(1, 0, 1661),
SOUTH_EAST(1, -1, 1654),
SOUTH(0, -1, 1655),
SOUTH_WEST(-1, -1, 1656),
WEST(-1, 0, 1657),
NORTH_WEST(-1, 1, 1658);
}
/**
* The fire wall spots.
*
*/
enum class FireWallSpots(val requiredArea: Area, val projectileSpots: ArrayList<Tile>, val spots: ArrayList<Tile>) {
SOUTH_WEST(Area(28, 9, 38, 19), arrayListOf(Tile(35, 18, 0), Tile(35, 21, 0), Tile(37, 16, 0), Tile(40, 16, 0)), arrayListOf(Tile(28, 18, 0), Tile(35, 21, 0), Tile(37, 9, 0), Tile(40, 16, 0))),
NORTH_WEST(Area(28, 20, 38, 30), arrayListOf(Tile(35, 18, 0), Tile(35, 21, 0), Tile(37, 23, 0), Tile(40, 23, 0)), arrayListOf(Tile(28, 18, 0), Tile(35, 21, 0), Tile(37, 23, 0), Tile(40, 30, 0))),
NORTH_EAST(Area(39, 20, 49, 30), arrayListOf(Tile(42, 18, 0), Tile(42, 21, 0), Tile(37, 23, 0), Tile(40, 23, 0)), arrayListOf(Tile(42, 18, 0), Tile(49, 21, 0), Tile(37, 23, 0), Tile(40, 30, 0))),
SOUTH_EAST(Area(39, 9, 49, 20), arrayListOf(Tile(42, 18, 0), Tile(42, 21, 0), Tile(37, 16, 0), Tile(40, 16, 0)), arrayListOf(Tile(42, 18, 0), Tile(49, 21, 0), Tile(37, 9, 0), Tile(40, 16, 0)));
/**
* Spawns the fire wall based on the fire wall spot.
*/
fun spawnFireWall(hydra: AlchemicalHydra, base: Tile, index: Int, spots: ArrayList<Tile>) {
when (this) {
SOUTH_WEST -> {
spawnWall(hydra, base, index, if (index == 0) Direction.West else Direction.South, spots)
}
NORTH_WEST -> {
spawnWall(hydra, base, index, if (index == 0) Direction.West else Direction.North, spots)
}
NORTH_EAST -> {
spawnWall(hydra, base, index, if (index == 0) Direction.East else Direction.North, spots)
}
SOUTH_EAST -> {
spawnWall(hydra, base, index, if (index == 0) Direction.East else Direction.South, spots)
}
}
}
/**
* Spawns the wall based on the direction and the spots.
*/
private fun spawnWall(hydra: AlchemicalHydra, base: Tile, index: Int, direction: Direction, hydraSpots: ArrayList<Tile>) {
var delay = 0
when (direction) {
Direction.South -> {
for (z in spots[index * 2 + 1].z downTo spots[index * 2].z) {
for (x in spots[index * 2].x..spots[index * 2 + 1].x) {
val tile = Tile(base.transform(x, z, 0))
spawn(hydra, tile, delay, hydraSpots)
}
delay += 10
}
}
Direction.North -> {
for (z in spots[index * 2].z..spots[index * 2 + 1].z) {
for (x in spots[index * 2].x..spots[index * 2 + 1].x) {
val tile = Tile(base.transform(x, z, 0))
spawn(hydra, tile, delay, hydraSpots)
}
delay += 10
}
}
Direction.East -> {
for (x in spots[index * 2].x..spots[index * 2 + 1].x) {
for (z in spots[index * 2].z..spots[index * 2 + 1].z) {
val tile = Tile(base.transform(x, z, 0))
spawn(hydra, tile, delay, hydraSpots)
}
delay += 10
}
}
Direction.West -> {
for (x in spots[index * 2 + 1].x downTo spots[index * 2].x) {
for (z in spots[index * 2].z..spots[index * 2 + 1].z) {
val tile = Tile(base.transform(x, z, 0))
spawn(hydra, tile, delay, hydraSpots)
}
delay += 10
}
}
}
}
/**
* Spawns the graphics.
*/
private fun spawn(hydra: AlchemicalHydra, tile: Tile, delay: Int, spots: ArrayList<Tile>) {
hydra.world().tileGraphic(1668, tile, 0, delay)
spots.add(tile)
}
}
/**
* Returns the spots where the fire wall will be sent.
*/
fun getWallFireSpots(playerLoc: Tile, base: Tile): FireWallSpots {
for (value in FireWallSpots.values()) {
if (value.requiredArea.contains(playerLoc.transform(-base.x, -base.z))) {
return value
}
}
return FireWallSpots.SOUTH_WEST
}
Code:
/**
* The hydra phases.
*
*/
enum class HydraPhase {
/**
* The first phase with poison special.
*/
GREEN(8616, 8615, 8237, arrayOf(8234, 8235, 8236)) {
@Suspendable
override fun switchPhase(hydra: AlchemicalHydra) {
hydra.world().executeScript @Suspendable {
hydra.lock()
hydra.sync().transmog(phaseSwitchID)
hydra.animate(phaseSwitchAnimID)
it.delay(3)
hydra.animate(phaseSwitchAnimID + 1)
hydra.sync().transmog(BLUE.phaseID)
it.delay(2)
hydra.unlock()
}
}
},
/**
* The second phase with lightning special.
*/
BLUE(8617, 8619, 8244, arrayOf(8241, 8242, 8243)) {
@Suspendable
override fun switchPhase(hydra: AlchemicalHydra) {
hydra.world().executeScript @Suspendable {
hydra.lock()
hydra.sync().transmog(phaseSwitchID)
hydra.animate(phaseSwitchAnimID)
it.delay(2)
hydra.animate(phaseSwitchAnimID + 1)
hydra.sync().transmog(RED.phaseID)
it.delay(2)
hydra.unlock()
}
}
},
/**
* The third phase with fire special.
*/
RED(8618, 8620, 8251, arrayOf(8248, 8249, 8250)) {
@Suspendable
override fun switchPhase(hydra: AlchemicalHydra) {
hydra.world().executeScript @Suspendable {
hydra.lock()
hydra.sync().transmog(phaseSwitchID)
hydra.animate(phaseSwitchAnimID)
it.delay(3)
hydra.animate(phaseSwitchAnimID + 1)
hydra.sync().transmog(ENRAGED.phaseID)
it.delay(2)
hydra.unlock()
}
}
},
/**
* The final phase, stronger and with poison special
*/
ENRAGED(8622, 8621, 8262, arrayOf(8255, 8256)) {
@Suspendable
override fun switchPhase(hydra: AlchemicalHydra) {
}
},
;
/**
* The NPC ID for the phase switching moment.
*/
val phaseSwitchID: Int
/**
* The NPC ID for the phase already switched.
*/
val phaseID: Int
/**
* The animation of switching phases.
*/
val phaseSwitchAnimID: Int
/**
* The attack anims for range and magic for this phase.
*/
val attackAnims: Array<Int>
/**
* Constructor for the phase.
*/
constructor(phaseSwitchID: Int, phaseID: Int, phaseSwitchAnimID: Int, attackAnims: Array<Int>) {
this.phaseSwitchID = phaseSwitchID
this.phaseID = phaseID
this.phaseSwitchAnimID = phaseSwitchAnimID
this.attackAnims = attackAnims
}
/**
* Called upon phase switch.
*/
@Suspendable
abstract fun switchPhase(hydra: AlchemicalHydra)
}