/*
* Copyright (C) 2004-2013 L2J DataPack
*
* This file is part of L2J DataPack.
*
* L2J DataPack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J DataPack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package ai.individual;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_FOLLOW;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import javolution.util.FastList;
import ai.npc.AbstractNpcAI;
import com.l2jserver.Config;
import com.l2jserver.gameserver.GeoData;
import com.l2jserver.gameserver.ThreadPoolManager;
import com.l2jserver.gameserver.enums.MountType;
import com.l2jserver.gameserver.instancemanager.GrandBossManager;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.Location;
import com.l2jserver.gameserver.model.StatsSet;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.actor.L2Npc;
import com.l2jserver.gameserver.model.actor.instance.L2DecoyInstance;
import com.l2jserver.gameserver.model.actor.instance.L2GrandBossInstance;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.model.holders.SkillHolder;
import com.l2jserver.gameserver.model.quest.QuestTimer;
import com.l2jserver.gameserver.model.skills.L2Skill;
import com.l2jserver.gameserver.model.zone.type.L2BossZone;
import com.l2jserver.gameserver.network.serverpackets.Earthquake;
import com.l2jserver.gameserver.network.serverpackets.MoveToPawn;
import com.l2jserver.gameserver.network.serverpackets.PlaySound;
import com.l2jserver.gameserver.util.Util;
/**
* Baium's AI.
* @author Fulminus
*/
public class Baium extends AbstractNpcAI
{
// NPCs
private static final int STONE_BAIUM = 29025;
private static final int ANGELIC_VORTEX = 31862;
private static final int LIVE_BAIUM = 29020;
private static final int ARCHANGEL = 29021;
private static final int TELEPORT_CUBIC = 31842;
// Item
private static final int BLOODED_FABRIC = 4295;
// Baium status tracking
private static final byte ASLEEP = 0; // baium is in the stone version, waiting to be woken up. Entry is unlocked
private static final byte AWAKE = 1; // baium is awake and fighting. Entry is locked.
private static final byte DEAD = 2; // baium has been killed and has not yet spawned. Entry is locked
// Fixed archangel spawn location
private static final Location[] ANGEL_LOCATION =
{
new Location(114239, 17168, 10080, 63544),
new Location(115780, 15564, 10080, 13620),
new Location(114880, 16236, 10080, 5400),
new Location(115168, 17200, 10080, 0),
new Location(115792, 16608, 10080, 0)
};
private static final Location[] TELEPORT_CUBIC_LOCATION = new Location[]
{
new Location(108784, 16000, -4928),
new Location(113824, 10448, -5164),
new Location(115488, 22096, -5168),
};
private static final Location BAIUM_DESPAWN = new Location(116033, 17447, 10104);
private static final Location BAIUM_ENTER = new Location(113100, 14500, 10077);
// Skills
private static final SkillHolder GENERAL_ATTACK = new SkillHolder(4127, 1);
private static final SkillHolder WIND_OF_FORCE = new SkillHolder(4128, 1);
private static final SkillHolder EARTHQUAKE = new SkillHolder(4129, 1);
private static final SkillHolder STRIKING_OF_THUNDERBOLT = new SkillHolder(4130, 1);
private static final SkillHolder STUN = new SkillHolder(4131, 1);
private static final SkillHolder BAIUM_HEAL = new SkillHolder(4135, 1);
private static final SkillHolder HINDER_STRIDER = new SkillHolder(4258, 1);
// private static final SkillHolder PRESENT_FROM_BAIUM = new SkillHolder(4136, 1);
private long _LastAttackVsBaiumTime = 0;
protected final List _Minions = new ArrayList<>(5);
private L2BossZone _Zone;
private L2Character _target;
private SkillHolder _skill;
private Baium(String name, String descr)
{
super(name, descr);
registerMobs(LIVE_BAIUM);
// Quest NPC starter initialization
addStartNpc(STONE_BAIUM, ANGELIC_VORTEX, TELEPORT_CUBIC);
addTalkId(STONE_BAIUM, ANGELIC_VORTEX, TELEPORT_CUBIC);
_Zone = GrandBossManager.getInstance().getZone(113100, 14500, 10077);
StatsSet info = GrandBossManager.getInstance().getStatsSet(LIVE_BAIUM);
int status = GrandBossManager.getInstance().getBossStatus(LIVE_BAIUM);
if (status == DEAD)
{
// load the unlock date and time for baium from DB
long temp = (info.getLong("respawn_time") - System.currentTimeMillis());
if (temp > 0)
{
// the unlock time has not yet expired. Mark Baium as currently locked (dead). Setup a timer
// to fire at the correct time (calculate the time between now and the unlock time,
// setup a timer to fire after that many msec)
startQuestTimer("baium_unlock", temp, null, null);
}
else
{
// the time has already expired while the server was offline. Delete the saved time and
// immediately spawn the stone-baium. Also the state need not be changed from ASLEEP
addSpawn(STONE_BAIUM, 116033, 17447, 10107, -25348, false, 0);
GrandBossManager.getInstance().setBossStatus(LIVE_BAIUM, ASLEEP);
}
}
else if (status == AWAKE)
{
int loc_x = info.getInt("loc_x");
int loc_y = info.getInt("loc_y");
int loc_z = info.getInt("loc_z");
int heading = info.getInt("heading");
final int hp = info.getInt("currentHP");
final int mp = info.getInt("currentMP");
L2GrandBossInstance baium = (L2GrandBossInstance) addSpawn(LIVE_BAIUM, loc_x, loc_y, loc_z, heading, false, 0);
GrandBossManager.getInstance().addBoss(baium);
final L2Npc _baium = baium;
ThreadPoolManager.getInstance().scheduleGeneral(new Runnable()
{
@Override
public void run()
{
try
{
_baium.setCurrentHpMp(hp, mp);
_baium.setIsInvul(true);
_baium.setIsImmobilized(true);
_baium.setRunning();
_baium.broadcastSocialAction(2);
startQuestTimer("baium_wakeup", 15000, _baium, null);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}, 100L);
}
else
{
addSpawn(STONE_BAIUM, 116033, 17447, 10107, -25348, false, 0);
}
}
@Override
public String onAdvEvent(String event, L2Npc npc, L2PcInstance player)
{
switch (event)
{
case "baium_unlock":
{
GrandBossManager.getInstance().setBossStatus(LIVE_BAIUM, ASLEEP);
addSpawn(STONE_BAIUM, 116033, 17447, 10107, -25348, false, 0);
break;
}
case "skill_range":
{
if (npc != null)
{
callSkillAI(npc);
}
break;
}
case "clean_player":
{
_target = getRandomTarget(npc);
break;
}
case "baium_wakeup":
{
if ((npc != null) && (npc.getId() == LIVE_BAIUM))
{
npc.broadcastSocialAction(1);
npc.broadcastPacket(new Earthquake(npc.getX(), npc.getY(), npc.getZ(), 40, 5));
npc.broadcastPacket(new PlaySound(1, "BS02_A", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ()));
// start monitoring baium's inactivity
_LastAttackVsBaiumTime = System.currentTimeMillis();
startQuestTimer("baium_despawn", 60000, npc, null, true);
startQuestTimer("skill_range", 500, npc, null, true);
final L2Npc baium = npc;
ThreadPoolManager.getInstance().scheduleGeneral(new Runnable()
{
@Override
public void run()
{
try
{
baium.setIsInvul(false);
baium.setIsImmobilized(false);
for (L2Npc minion : _Minions)
{
minion.setShowSummonAnimation(false);
}
}
catch (Exception e)
{
_log.log(Level.WARNING, "", e);
}
}
}, 11100L);
// TODO: Player that wake up Baium take damage.
for (Location loc : ANGEL_LOCATION)
{
L2Npc angel = addSpawn(ARCHANGEL, loc, false, 0, true);
angel.setIsInvul(true);
_Minions.add(angel);
}
}
// despawn the live baium after 30 minutes of inactivity
// also check if the players are cheating, having pulled Baium outside his zone...
break;
}
case "baium_despawn":
{
if ((npc != null) && (npc.getId() == LIVE_BAIUM))
{
// just in case the zone reference has been lost (somehow...), restore the reference
if (_Zone == null)
{
_Zone = GrandBossManager.getInstance().getZone(113100, 14500, 10077);
}
if ((_LastAttackVsBaiumTime + 1800000) < System.currentTimeMillis())
{
npc.deleteMe(); // despawn the live-baium
for (L2Npc minion : _Minions)
{
if (minion != null)
{
minion.getSpawn().stopRespawn();
minion.deleteMe();
}
}
_Minions.clear();
addSpawn(STONE_BAIUM, 116033, 17447, 10107, -25348, false, 0); // spawn stone-baium
GrandBossManager.getInstance().setBossStatus(LIVE_BAIUM, ASLEEP); // mark that Baium is not awake any more
_Zone.oustAllPlayers();
cancelQuestTimer("baium_despawn", npc, null);
}
else if (((_LastAttackVsBaiumTime + 300000) < System.currentTimeMillis()) && (npc.getCurrentHp() < ((npc.getMaxHp() * 3) / 4.0)))
{
npc.setIsCastingNow(false); // just in case
npc.setTarget(npc);
if (npc.isPhysicalMuted())
{
return super.onAdvEvent(event, npc, player);
}
npc.doCast(BAIUM_HEAL.getSkill());
npc.setIsCastingNow(true);
}
else if (!_Zone.isInsideZone(npc))
{
npc.teleToLocation(BAIUM_DESPAWN);
}
}
break;
}
}
return super.onAdvEvent(event, npc, player);
}
@Override
public String onTalk(L2Npc npc, L2PcInstance player)
{
String htmltext = "";
if (_Zone == null)
{
_Zone = GrandBossManager.getInstance().getZone(113100, 14500, 10077);
}
if (_Zone == null)
{
return "Angelic Vortex:
You may not enter while admin disabled this zone";
}
switch (npc.getId())
{
case STONE_BAIUM:
{
if (GrandBossManager.getInstance().getBossStatus(LIVE_BAIUM) == ASLEEP)
{
if (_Zone.isPlayerAllowed(player))
{
// once Baium is awaken, no more people may enter until he dies, the server reboots, or
// 30 minutes pass with no attacks made against Baium.
GrandBossManager.getInstance().setBossStatus(LIVE_BAIUM, AWAKE);
npc.deleteMe();
L2GrandBossInstance baium = (L2GrandBossInstance) addSpawn(LIVE_BAIUM, npc, true);
GrandBossManager.getInstance().addBoss(baium);
final L2Npc _baium = baium;
ThreadPoolManager.getInstance().scheduleGeneral(new Runnable()
{
@Override
public void run()
{
try
{
_baium.setIsInvul(true);
_baium.setRunning();
_baium.broadcastSocialAction(2);
startQuestTimer("baium_wakeup", 15000, _baium, null);
_baium.setShowSummonAnimation(false);
}
catch (Throwable e)
{
_log.log(Level.WARNING, "", e);
}
}
}, 100L);
}
else
{
htmltext = "Conditions are not right to wake up Baium";
}
}
break;
}
case ANGELIC_VORTEX:
{
if (player.isFlying())
{
// print "Player "+player.getName()+" attempted to enter Baium's lair while flying!";
return "Angelic Vortex:
You may not enter while flying a wyvern";
}
if ((GrandBossManager.getInstance().getBossStatus(LIVE_BAIUM) == ASLEEP) && hasQuestItems(player, BLOODED_FABRIC))
{
takeItems(player, BLOODED_FABRIC, 1);
// allow entry for the player for the next 30 secs (more than enough time for the TP to happen)
// Note: this just means 30secs to get in, no limits on how long it takes before we get out.
_Zone.allowPlayerEntry(player, 30);
player.teleToLocation(BAIUM_ENTER);
}
else
{
npc.showChatWindow(player, 1);
}
break;
}
case TELEPORT_CUBIC:
{
final Location loc = TELEPORT_CUBIC_LOCATION[getRandom(TELEPORT_CUBIC_LOCATION.length)];
player.teleToLocation(loc.getX() + getRandom(100), loc.getY() + getRandom(100), loc.getZ());
break;
}
}
return htmltext;
}
@Override
public String onSpellFinished(L2Npc npc, L2PcInstance player, L2Skill skill)
{
if (npc.isInvul())
{
npc.getAI().setIntention(AI_INTENTION_IDLE);
return null;
}
callSkillAI(npc);
return super.onSpellFinished(npc, player, skill);
}
@Override
public String onSpawn(L2Npc npc)
{
npc.disableCoreAI(true);
return super.onSpawn(npc);
}
@Override
public String onAttack(L2Npc npc, L2PcInstance attacker, int damage, boolean isSummon)
{
if (!_Zone.isInsideZone(attacker))
{
attacker.reduceCurrentHp(attacker.getCurrentHp(), attacker, false, false, null);
return super.onAttack(npc, attacker, damage, isSummon);
}
if (npc.isInvul())
{
npc.getAI().setIntention(AI_INTENTION_IDLE);
return super.onAttack(npc, attacker, damage, isSummon);
}
if (attacker.getMountType() == MountType.STRIDER)
{
if (!attacker.isAffectedBySkill(HINDER_STRIDER.getSkillId()))
{
npc.setTarget(attacker);
if (npc.isMuted())
{
return super.onAttack(npc, attacker, damage, isSummon);
}
npc.doCast(HINDER_STRIDER.getSkill());
}
}
// update a variable with the last action against baium
_LastAttackVsBaiumTime = System.currentTimeMillis();
callSkillAI(npc);
return super.onAttack(npc, attacker, damage, isSummon);
}
@Override
public String onKill(L2Npc npc, L2PcInstance killer, boolean isSummon)
{
cancelQuestTimer("baium_despawn", npc, null);
npc.broadcastPacket(new PlaySound(1, "BS01_D", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ()));
// spawn the "Teleportation Cubic" for 15 minutes (to allow players to exit the lair)
addSpawn(TELEPORT_CUBIC, 115017, 15549, 10090, 0, false, 900000);
// Calculate Min and Max respawn times randomly.
long respawnTime = Config.BAIUM_SPAWN_INTERVAL + getRandom(-Config.BAIUM_SPAWN_RANDOM, Config.BAIUM_SPAWN_RANDOM);
respawnTime *= 3600000;
GrandBossManager.getInstance().setBossStatus(LIVE_BAIUM, DEAD);
startQuestTimer("baium_unlock", respawnTime, null, null);
// also save the respawn time so that the info is maintained past reboots
StatsSet info = GrandBossManager.getInstance().getStatsSet(LIVE_BAIUM);
info.set("respawn_time", (System.currentTimeMillis()) + respawnTime);
GrandBossManager.getInstance().setStatsSet(LIVE_BAIUM, info);
for (L2Npc minion : _Minions)
{
if (minion != null)
{
minion.getSpawn().stopRespawn();
minion.deleteMe();
}
}
_Minions.clear();
final QuestTimer timer = getQuestTimer("skill_range", npc, null);
if (timer != null)
{
timer.cancelAndRemove();
}
return super.onKill(npc, killer, isSummon);
}
public L2Character getRandomTarget(L2Npc npc)
{
FastList result = FastList.newInstance();
Collection objs = npc.getKnownList().getKnownObjects().values();
{
for (L2Object obj : objs)
{
if (obj.isPlayable() || (obj instanceof L2DecoyInstance))
{
if (obj.isPlayer())
{
if (obj.getActingPlayer().getAppearance().getInvisible())
{
continue;
}
}
if (((((L2Character) obj).getZ() < (npc.getZ() - 100)) && (((L2Character) obj).getZ() > (npc.getZ() + 100))) || !(GeoData.getInstance().canSeeTarget(((L2Character) obj).getX(), ((L2Character) obj).getY(), ((L2Character) obj).getZ(), npc.getX(), npc.getY(), npc.getZ())))
{
continue;
}
}
if (obj.isPlayable() || (obj instanceof L2DecoyInstance))
{
if (Util.checkIfInRange(9000, npc, obj, true) && !((L2Character) obj).isDead())
{
result.add((L2Character) obj);
}
}
}
}
if (result.isEmpty())
{
for (L2Npc minion : _Minions)
{
if (minion != null)
{
result.add(minion);
}
}
}
if (result.isEmpty())
{
FastList.recycle(result);
return null;
}
Object[] characters = result.toArray();
QuestTimer timer = getQuestTimer("clean_player", npc, null);
if (timer != null)
{
timer.cancelAndRemove();
}
startQuestTimer("clean_player", 20000, npc, null);
L2Character target = (L2Character) characters[getRandom(characters.length)];
FastList.recycle(result);
return target;
}
public synchronized void callSkillAI(L2Npc npc)
{
if (npc.isInvul() || npc.isCastingNow())
{
return;
}
if ((_target == null) || _target.isDead() || !(_Zone.isInsideZone(_target)))
{
_target = getRandomTarget(npc);
if (_target != null)
{
_skill = getRandomSkill(npc);
}
}
L2Character target = _target;
SkillHolder skill = _skill;
if (skill == null)
{
skill = (getRandomSkill(npc));
}
if (npc.isPhysicalMuted())
{
return;
}
if ((target == null) || target.isDead() || !(_Zone.isInsideZone(target)))
{
npc.setIsCastingNow(false);
return;
}
if (Util.checkIfInRange(skill.getSkill().getCastRange(), npc, target, true))
{
npc.getAI().setIntention(AI_INTENTION_IDLE);
npc.setTarget(target);
npc.setIsCastingNow(true);
_target = null;
_skill = null;
if (getDist(skill.getSkill().getCastRange()) > 0)
{
npc.broadcastPacket(new MoveToPawn(npc, target, getDist(skill.getSkill().getCastRange())));
}
try
{
Thread.sleep(1000);
npc.stopMove(null);
npc.doCast(skill.getSkill());
}
catch (Exception e)
{
e.printStackTrace();
}
}
else
{
npc.getAI().setIntention(AI_INTENTION_FOLLOW, target, null);
npc.setIsCastingNow(false);
}
}
public SkillHolder getRandomSkill(L2Npc npc)
{
SkillHolder skill;
if (npc.getCurrentHp() > ((npc.getMaxHp() * 3) / 4.0))
{
if (getRandom(100) < 10)
{
skill = WIND_OF_FORCE;
}
else if (getRandom(100) < 10)
{
skill = EARTHQUAKE;
}
else
{
skill = GENERAL_ATTACK;
}
}
else if (npc.getCurrentHp() > ((npc.getMaxHp() * 2) / 4.0))
{
if (getRandom(100) < 10)
{
skill = STUN;
}
else if (getRandom(100) < 10)
{
skill = WIND_OF_FORCE;
}
else if (getRandom(100) < 10)
{
skill = EARTHQUAKE;
}
else
{
skill = GENERAL_ATTACK;
}
}
else if (npc.getCurrentHp() > (npc.getMaxHp() / 4.0))
{
if (getRandom(100) < 10)
{
skill = STRIKING_OF_THUNDERBOLT;
}
else if (getRandom(100) < 10)
{
skill = STUN;
}
else if (getRandom(100) < 10)
{
skill = WIND_OF_FORCE;
}
else if (getRandom(100) < 10)
{
skill = EARTHQUAKE;
}
else
{
skill = GENERAL_ATTACK;
}
}
else if (getRandom(100) < 10)
{
skill = STRIKING_OF_THUNDERBOLT;
}
else if (getRandom(100) < 10)
{
skill = STUN;
}
else if (getRandom(100) < 10)
{
skill = WIND_OF_FORCE;
}
else if (getRandom(100) < 10)
{
skill = EARTHQUAKE;
}
else
{
skill = GENERAL_ATTACK;
}
return skill;
}
@Override
public String onSkillSee(L2Npc npc, L2PcInstance caster, L2Skill skill, L2Object[] targets, boolean isSummon)
{
if (npc.isInvul())
{
npc.getAI().setIntention(AI_INTENTION_IDLE);
return null;
}
npc.setTarget(caster);
return super.onSkillSee(npc, caster, skill, targets, isSummon);
}
public int getDist(int range)
{
int dist = 0;
switch (range)
{
case -1:
break;
case 100:
dist = 85;
break;
default:
dist = range - 85;
break;
}
return dist;
}
public static void main(String[] args)
{
new Baium(Baium.class.getSimpleName(), "ai");
}
}