Jump to content
  • 0

Phantoms On L2Wolrd/ages Pack...


Question

Posted (edited)

They are not spawning on their own and they won't start spawning on themselves. Any idea how to fix that, or if there is a working phantoms shere somewhere on the web could you pls provide me with a link?

 

I think there is an issue in the following code... Because I believe that somewhere in the code the minimum spawns has been set, and I cant change it in the configs so I have to change it from the code itself. The question, however, remains... How do you find where its written?

	public static class PhantomSpawner implements Runnable
	{
		private final int _numSpawns;
		private final long _delayInMilis;
		private final boolean _generateNewPhantoms;
		private int _curSpawns = 0;
		private Location _loc = null;
		private ScheduledFuture<?> _task = null;
		
		public PhantomSpawner()
		{
			_numSpawns = Config.PHANTOM_SPAWN_MAX;
			_delayInMilis = Config.PHANTOM_SPAWN_DELAY;
			_generateNewPhantoms = true;
		}
		
		public PhantomSpawner(int numSpawns)
		{
			_numSpawns = numSpawns;
			_delayInMilis = Config.PHANTOM_SPAWN_DELAY;
			_generateNewPhantoms = true;
		}
		
		public PhantomSpawner(int numSpawns, long delayInMilis)
		{
			_numSpawns = numSpawns;
			_delayInMilis = delayInMilis;
			_generateNewPhantoms = true;
		}
		
		public PhantomSpawner(int numSpawns, long delayInMilis, boolean generateNewPhantoms)
		{
			_numSpawns = numSpawns;
			_delayInMilis = delayInMilis;
			_generateNewPhantoms = generateNewPhantoms;
		}
		
		public PhantomSpawner setLocation(Location loc)
		{
			_loc = loc;
			return this;
		}
		
		@Override
		public void run()
		{
			if (_numSpawns == 0)
			{
				return;
			}
			
			_task = ThreadPoolManager.getInstance().scheduleAtFixedRate(() ->
			{
				if (_curSpawns < _numSpawns)
				{
					// Do not spawn more than max phantoms.
					if (L2ObjectsStorage.getAllPlayersStream().filter(Player::isPhantom).count() >= Config.PHANTOM_MAX_PLAYERS)
					{
						return;
					}
					
					int objId = getUnspawnedPhantomObjId();
					if (objId < 0)
					{
						if (_generateNewPhantoms)
						{
							try
							{
								Player phantom = createNewPhantom();
								if (phantom != null)
								{
									objId = phantom.getObjectId();
									_log.info("Spawning phantom " + phantom + " through spawner. Cur/Max " + _curSpawns + "/" + _numSpawns);
								}
							}
							catch (Exception e)
							{
								_log.error("ERROR: Spawning phantom  through spawner. Cur/Max " + _curSpawns + "/" + _numSpawns, e);
							}
						}
						else
						{
							return;
						}
					}
					
					ThreadPoolManager.getInstance().execute(new PhantomSpawn(objId).setLocation(_loc));
					_curSpawns++;
				}
			}, 0, _delayInMilis);
		}
Edited by bru7al

4 answers to this question

Recommended Posts

  • 0
Posted (edited)

Nothing is magical, you have to run() the task somehow, somewhere - basically wherever you initialized an PhantomSpawner (which should be, btw, a Manager otherwise you can create as much as classes you want which can be tragic).

Edited by Tryskell
  • 0
Posted

Nothing is magical, you have to run() the task somehow, somewhere - basically wherever you initialized an PhantomSpawner (which should be, btw, a Manager otherwise you can create as much as classes you want which can be tragic).

execute(new PhantomSpawn  does trigger the threadpool e.t.c but code is fucked up.

  • 0
Posted (edited)

execute(new PhantomSpawn  does trigger the threadpool e.t.c but code is fucked up.

 

Once again, if the first run is never reached, the other internal can't be reached.

 

And PhantomSpawn !=  PhantomSpawner. You must have a .run(), somewhere, for the PhantomSpawner instance. It's exactly like any Manager, you have to getInstance() to initialize the class and the content, otherwise it never loads.

 

If it's a code issue, there isn't enough code to answer.

Edited by Tryskell
  • 0
Posted

Once again, if the first run is never reached, the other internal can't be reached.

 

And PhantomSpawn !=  PhantomSpawner. You must have a .run(), somewhere, for the PhantomSpawner instance. It's exactly like any Manager, you have to getInstance() to initialize the class and the content, otherwise it never loads.

 

If it's a code issue, there isn't enough code to answer.

I bet it is a code issue. I will provide the codes below, so if anyone has gone through this and has a quick fix, please do tell! :))

 

This is the Spawner:

package wp.gameserver.model;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ScheduledFuture;

import org.apache.log4j.Logger;

import com.google.common.io.Files;

import wp.commons.util.Rnd;
import wp.gameserver.Config;
import wp.gameserver.ThreadPoolManager;
import wp.gameserver.ai.PhantomPlayerAI;
import wp.gameserver.dao.CharacterDAO;
import wp.gameserver.dao.PhantomsDAO;
import wp.gameserver.data.xml.holder.PhantomTemplateData;
import wp.gameserver.data.xml.holder.SkillAcquireHolder;
import wp.gameserver.instancemanager.CursedWeaponsManager;
import wp.gameserver.model.base.AcquireType;
import wp.gameserver.model.base.ClassId;
import wp.gameserver.model.base.Experience;
import wp.gameserver.model.base.InvisibleType;
import wp.gameserver.model.base.RestartType;
import wp.gameserver.model.entity.L2Event;
import wp.gameserver.model.items.ItemInstance;
import wp.gameserver.model.phantom.PhantomTemplate;
import wp.gameserver.tables.SkillTable;
import wp.gameserver.templates.item.CreateItem;
import wp.gameserver.templates.item.ItemTemplate;
import wp.gameserver.utils.Location;
import wp.gameserver.utils.Util;

public class PhantomPlayers
{
	private static final Logger _log = Logger.getLogger(PhantomPlayers.class.getName());
	private static List<Integer> _phantoms;
	private static List<String> _phantomNames;
	private static List<PhantomSpawner> _phantomSpawners;
	
	public static void init()
	{
		_log.info("Loading phantom players...");
		
		try
		{
			File file = Config.findNonCustomResource("config/phantom/player_names.txt");
			_phantomNames = Files.readLines(file, StandardCharsets.UTF_8);
		}
		catch (IOException e)
		{
			_log.warn("PhantomPlayers: Unable to load phantom player names.", e);
			_phantomNames = Collections.emptyList();
		}
		
		_phantoms = new ArrayList<>(PhantomsDAO.getInstance().selectAll().keySet());
		_phantomSpawners = new ArrayList<>();
		PhantomSpawner spawner = new PhantomSpawner();
		ThreadPoolManager.getInstance().execute(spawner);
		_phantomSpawners.add(spawner);
		
		_log.info("Loaded " + _phantoms.size() + " phantom players from database with a maximum of " + Config.PHANTOM_MAX_PLAYERS + " phantoms.");
		_log.info("Loaded " + _phantomNames.size() + " possible phantom names.");
		_log.info("Scheduled spawner with " + (Config.PHANTOM_SPAWN_DELAY / 1000) + " seconds delay.");
	}
	
	public static Player createNewPhantom()
	{
		if (_phantomNames == null)
		{
			return null;
		}
		
		for (int i = 0; i < 25; i++) // 25 tries to make a phantom player. Enough tries to avoid name duplicate and other stuff.
		{
			String name = Rnd.get(_phantomNames);
			Player player = createNewPhantom(name);
			if (player != null)
			{
				return player;
			}
		}
		
		return null;
	}
	
	public static Player createNewPhantom(String name)
	{
		return createNewPhantom(name, PhantomTemplateData.getInstance().getRandomTemplate());
	}
	
	public static Player createNewPhantom(String name, PhantomTemplate template)
	{
		boolean female = Rnd.nextBoolean();
		int hairStyle = Rnd.get(3);
		int hairColor = Rnd.get(3);
		int face = Rnd.get(3);
		return createNewPhantom(name, template, female, hairStyle, hairColor, face);
	}
	
	public static Player createNewPhantom(String name, PhantomTemplate template, boolean female, int hairStyle, int hairColor, int face)
	{
		try
		{
			if (!Util.isMatchingRegexp(name, Config.CNAME_TEMPLATE))
			{
				return null;
			}
			if ((CharacterDAO.getInstance().getObjectIdByName(name) > 0) || Util.contains(Config.FORBIDDEN_CHAR_NAMES, name))
			{
				return null;
			}
			
			if (template == null)
			{
				return null;
			}
			
			final ClassId classId = ClassId.VALUES[template.getClassId()];
			Player newChar = Player.create(classId.getId(), female ? 1 : 0, Config.PHANTOM_ACCOUNT, name, hairStyle, hairColor, face);
			if (newChar == null)
			{
				return null;
			}
			
			newChar.setCreateTime(System.currentTimeMillis());
			try
			{
				switch (classId.getLevel())
				{
					case 2:
						newChar.addExpAndSp(Experience.getExpForLevel(20), 835863);
						break;
					case 3:
						newChar.addExpAndSp(Experience.getExpForLevel(40), 15422930);
						break;
					case 4:
						newChar.addExpAndSp(Experience.getExpForLevel(76), 931275829);
						break;
				}
			}
			catch (ArrayIndexOutOfBoundsException | NullPointerException e)
			{
				_log.warn("PhantomPlayers: Failed to set appropreate level for classId " + classId, e);
			}
			
			Player.restoreCharSubClasses(newChar);
			
			if (Config.STARTING_ADENA > 0)
			{
				newChar.addAdena(Config.STARTING_ADENA);
			}
			
			if (Config.STARTING_LVL > newChar.getLevel())
			{
				newChar.addExpAndSp(Experience.LEVEL[Config.STARTING_LVL] - newChar.getExp(), 0, 0, 0, false, false);
			}
			
			if (Config.SPAWN_CHAR)
			{
				newChar.teleToLocation(Config.SPAWN_X, Config.SPAWN_Y, Config.SPAWN_Z);
			}
			else
			{
				newChar.setLoc(Rnd.get(newChar.getTemplate().getSpawnLocs()));
			}
			
			if (Config.CHAR_TITLE)
			{
				newChar.setTitle(Config.ADD_CHAR_TITLE);
			}
			else
			{
				newChar.setTitle("");
			}
			
			for (CreateItem i : newChar.getTemplate().getItems())
			{
				ItemInstance item = new ItemInstance(i.getItemId());
				newChar.getInventory().addItem(item);
				
				if (i.isEquipable() && item.isEquipable() && ((newChar.getActiveWeaponItem() == null) || (item.getTemplate().getType2() != ItemTemplate.TYPE2_WEAPON)))
				{
					newChar.getInventory().equipItem(item);
				}
			}
			
			if (Config.ALLOW_START_ITEMS)
			{
				if (classId.isMage())
				{
					for (int i = 0; i < Config.START_ITEMS_MAGE.length; i++)
					{
						ItemInstance item = new ItemInstance(Config.START_ITEMS_MAGE[i]);
						item.setCount(Config.START_ITEMS_MAGE_COUNT[i]);
						newChar.getInventory().addItem(item);
					}
					
					if (Config.BIND_NEWBIE_START_ITEMS_TO_CHAR)
					{
						for (int i = 0; i < Config.START_ITEMS_MAGE_BIND_TO_CHAR.length; i++)
						{
							ItemInstance item = new ItemInstance(Config.START_ITEMS_MAGE_BIND_TO_CHAR[i]);
							item.setCount(Config.START_ITEMS_MAGE_COUNT_BIND_TO_CHAR[i]);
							item.setCustomFlags(ItemInstance.FLAG_NO_CRYSTALLIZE | ItemInstance.FLAG_NO_TRADE | ItemInstance.FLAG_NO_TRANSFER | ItemInstance.FLAG_NO_DROP | ItemInstance.FLAG_NO_SELL);
							newChar.getInventory().addItem(item);
						}
					}
				}
				else
				{
					for (int i = 0; i < Config.START_ITEMS_FITHER.length; i++)
					{
						ItemInstance item = new ItemInstance(Config.START_ITEMS_FITHER[i]);
						item.setCount(Config.START_ITEMS_FITHER_COUNT[i]);
						newChar.getInventory().addItem(item);
					}
					
					if (Config.BIND_NEWBIE_START_ITEMS_TO_CHAR)
					{
						for (int i = 0; i < Config.START_ITEMS_FITHER_BIND_TO_CHAR.length; i++)
						{
							ItemInstance item = new ItemInstance(Config.START_ITEMS_FITHER_BIND_TO_CHAR[i]);
							item.setCount(Config.START_ITEMS_FITHER_COUNT_BIND_TO_CHAR[i]);
							item.setCustomFlags(ItemInstance.FLAG_NO_CRYSTALLIZE | ItemInstance.FLAG_NO_TRADE | ItemInstance.FLAG_NO_TRANSFER | ItemInstance.FLAG_NO_DROP | ItemInstance.FLAG_NO_SELL);
							newChar.getInventory().addItem(item);
						}
					}
				}
			}
			
			for (SkillLearn skill : SkillAcquireHolder.getInstance().getAvailableSkills(newChar, AcquireType.NORMAL))
			{
				newChar.addSkill(SkillTable.getInstance().getInfo(skill.getId(), skill.getLevel()), true);
			}
			
			newChar.setCurrentHpMp(newChar.getMaxHp(), newChar.getMaxMp());
			newChar.setCurrentCp(0); // retail
			newChar.setOnlineStatus(false);
			
			newChar.store(false);
			newChar.getInventory().store();
			newChar.deleteMe();
			
			PhantomsDAO.getInstance().insert(newChar.getObjectId(), template);
			
			_phantoms.add(newChar.getObjectId());
			return newChar;
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
		return null;
	}
	
	public static PhantomSpawner spawnPhantoms(int numSpawns, long delayInMilis, boolean generateNew, Location loc)
	{
		PhantomSpawner spawner = new PhantomSpawner(numSpawns, delayInMilis, generateNew).setLocation(loc);
		ThreadPoolManager.getInstance().execute(spawner);
		_phantomSpawners.add(spawner);
		return spawner;
	}
	
	/**
	 * Gets the next unspawned phantom.
	 * @return random free (unspawned) phantom object id or -1 if all are taken.
	 */
	private static int getUnspawnedPhantomObjId()
	{
		List<Integer> _unspawnedPhantoms = new ArrayList<>();
		_unspawnedPhantoms.addAll(_phantoms);
		
		for (Player player : L2ObjectsStorage.getAllPlayersForIterate())
		{
			if (_unspawnedPhantoms.contains(Integer.valueOf(player.getObjectId())))
			{
				_unspawnedPhantoms.remove(Integer.valueOf(player.getObjectId()));
			}
		}
		
		if (!_unspawnedPhantoms.isEmpty())
		{
			return Rnd.get(_unspawnedPhantoms);
		}
		
		return -1;
	}
	
	public static class PhantomSpawn implements Runnable
	{
		private final int _objId;
		private Location _loc;
		
		public PhantomSpawn()
		{
			_objId = getUnspawnedPhantomObjId();
		}
		
		public PhantomSpawn(int objId)
		{
			_objId = objId;
			_loc = null;
		}
		
		public PhantomSpawn setLocation(Location loc)
		{
			_loc = new Location(loc.getX() + Rnd.get(200), loc.getY() + Rnd.get(200), loc.getZ());
			return this;
		}
		
		@Override
		public void run()
		{
			
			Player player = World.getPlayer(_objId);
			if (player == null)
			{
				player = Player.restore(_objId);
			}
			if (player == null)
			{
				return;
			}
			
			player.setOfflineMode(false);
			player.setIsOnline(true);
			player.updateOnlineStatus();
			
			player.setOnlineStatus(true);
			player.setInvisibleType(InvisibleType.NONE);
			player.setNonAggroTime(Long.MAX_VALUE);
			player.spawnMe();
			
			player.setHero(Config.NEW_CHAR_IS_HERO);
			player.setNoble(Config.NEW_CHAR_IS_NOBLE);
			
			player.getListeners().onEnter();
			
			// Backup to set default name color every time on login.
			if ((player.getNameColor() != 0xFFFFFF) && ((player.getKarma() == 0) || (player.getRecomHave() == 0)) && !player.isGM())
			{
				player.setNameColor(0xFFFFFF);
			}
			
			if ((player.getTitleColor() != Player.DEFAULT_TITLE_COLOR) && !player.isGM())
			{
				player.setTitleColor(Player.DEFAULT_TITLE_COLOR);
			}
			
			// Restore after nocarrier title, title color.
			if (player.getVar("NoCarrierTitle") != null)
			{
				player.setTitle(player.getVar("NoCarrierTitle"));
				
				if (player.getVar("NoCarrierTitleColor") != null)
				{
					player.setTitleColor(Integer.parseInt(player.getVar("NoCarrierTitleColor")));
				}
				
				player.broadcastCharInfo();
				
				player.unsetVar("NoCarrierTitle");
				player.unsetVar("NoCarrierTitleColor");
			}
			
			if (player.isCursedWeaponEquipped())
			{
				CursedWeaponsManager.getInstance().showUsageTime(player, player.getCursedWeaponEquippedId());
			}
			
			player.setCurrentHpMp(player.getMaxHp(), player.getMaxMp());
			player.setCurrentCp(player.getMaxCp());
			
			player.setIsPhantom(true);
			
			if (player.getAI().isPhantomPlayerAI())
			{
				((PhantomPlayerAI) player.getAI()).startAITask();
			}
			
			if (L2Event.isParticipant(player))
			{
				L2Event.restorePlayerEventStatus(player);
			}
			
			player.setRunning();
			player.standUp();
			player.startTimers();
			
			player.broadcastCharInfo();
			player.setHeading(Rnd.get(65535));
			if (player.isDead())
			{
				player.teleToLocation(Location.getRestartLocation(player, RestartType.TO_VILLAGE));
				player.doRevive(100);
			}
			else
			{
				player.teleToLocation(_loc == null ? player.getLoc() : _loc);
			}
		}
	}
	
	/**
	 * Checks if the given player is in the world, then logs out when he is out of combat.
	 */
	private static class PhantomDespawn implements Runnable
	{
		private final int _objId;
		private final boolean _force;
		
		public PhantomDespawn(int objId, boolean force)
		{
			_objId = objId;
			_force = force;
		}
		
		@Override
		public void run()
		{
			Player phantom = L2ObjectsStorage.getPlayer(_objId);
			if (phantom == null)
			{
				return;
			}
			
			if (!_force)
			{
				// Continue when phantom is out of combat.
				if (phantom.isInCombat())
				{
					ThreadPoolManager.getInstance().schedule(this, 1000);
					return;
				}
				
				// When phantom is out of combat, stop moving.
				if (phantom.isMoving)
				{
					phantom.stopMove();
				}
			}
			
			phantom.getAI().stopAITask();
			phantom.kick();
		}
	}
	
	public static class PhantomSpawner implements Runnable
	{
		private final int _numSpawns;
		private final long _delayInMilis;
		private final boolean _generateNewPhantoms;
		private int _curSpawns = 0;
		private Location _loc = null;
		private ScheduledFuture<?> _task = null;
		
		public PhantomSpawner()
		{
			_numSpawns = Config.PHANTOM_SPAWN_MAX;
			_delayInMilis = Config.PHANTOM_SPAWN_DELAY;
			_generateNewPhantoms = true;
		}
		
		public PhantomSpawner(int numSpawns)
		{
			_numSpawns = numSpawns;
			_delayInMilis = Config.PHANTOM_SPAWN_DELAY;
			_generateNewPhantoms = true;
		}
		
		public PhantomSpawner(int numSpawns, long delayInMilis)
		{
			_numSpawns = numSpawns;
			_delayInMilis = delayInMilis;
			_generateNewPhantoms = true;
		}
		
		public PhantomSpawner(int numSpawns, long delayInMilis, boolean generateNewPhantoms)
		{
			_numSpawns = numSpawns;
			_delayInMilis = delayInMilis;
			_generateNewPhantoms = generateNewPhantoms;
		}
		
		public PhantomSpawner setLocation(Location loc)
		{
			_loc = loc;
			return this;
		}
		
		@Override
		public void run()
		{
			if (_numSpawns == 0)
			{
				return;
			}
			
			_task = ThreadPoolManager.getInstance().scheduleAtFixedRate(() ->
			{
				if (_curSpawns < _numSpawns)
				{
					// Do not spawn more than max phantoms.
					if (L2ObjectsStorage.getAllPlayersStream().filter(Player::isPhantom).count() >= Config.PHANTOM_MAX_PLAYERS)
					{
						return;
					}
					
					int objId = getUnspawnedPhantomObjId();
					if (objId < 0)
					{
						if (_generateNewPhantoms)
						{
							try
							{
								Player phantom = createNewPhantom();
								if (phantom != null)
								{
									objId = phantom.getObjectId();
									_log.info("Spawning phantom " + phantom + " through spawner. Cur/Max " + _curSpawns + "/" + _numSpawns);
								}
							}
							catch (Exception e)
							{
								_log.error("ERROR: Spawning phantom  through spawner. Cur/Max " + _curSpawns + "/" + _numSpawns, e);
							}
						}
						else
						{
							return;
						}
					}
					
					ThreadPoolManager.getInstance().execute(new PhantomSpawn(objId).setLocation(_loc));
					_curSpawns++;
				}
			}, 0, _delayInMilis);
		}
		
		public void cancel()
		{
			if (_task != null)
			{
				_task.cancel(true);
				System.out.println("Canceling phantom scheduler");
			}
		}
	}
	
	public static void stopSpawners()
	{
		if (_phantomSpawners != null)
		{
			for (PhantomSpawner thread : _phantomSpawners)
			{
				if (thread != null)
				{
					thread.cancel();
				}
			}
		}
	}
	
	public static void terminatePhantoms(boolean force)
	{
		stopSpawners();
		
		for (int objId : _phantoms)
		{
			new PhantomDespawn(objId, force).run();
		}
	}
	
	public static void terminatePhantom(int objId, boolean disableFromReenter)
	{
		if (disableFromReenter && (_phantoms != null))
		{
			_phantoms.remove(Integer.valueOf(objId));
		}
		
		new PhantomDespawn(objId, true).run();
	}
}

 

This is the AI, which does not start hitting:

package wp.gameserver.ai;

import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.NavigableSet;
import java.util.Objects;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ScheduledFuture;
import java.util.stream.Collectors;

import wp.commons.util.Rnd;
import wp.gameserver.Config;
import wp.gameserver.ThreadPoolManager;
import wp.gameserver.ai.DefaultAI.Task;
import wp.gameserver.ai.DefaultAI.TaskType;
import wp.gameserver.dao.CharacterDAO;
import wp.gameserver.dao.PhantomsDAO;
import wp.gameserver.data.xml.holder.PhantomTemplateData;
import wp.gameserver.geodata.GeoEngine;
import wp.gameserver.handler.bbs.CommunityBoardManager;
import wp.gameserver.instancemanager.ReflectionManager;
import wp.gameserver.listener.actor.OnKillListener;
import wp.gameserver.listener.actor.ai.OnAiEventListener;
import wp.gameserver.model.Creature;
import wp.gameserver.model.Effect;
import wp.gameserver.model.L2Object;
import wp.gameserver.model.PhantomPlayers;
import wp.gameserver.model.Player;
import wp.gameserver.model.Skill;
import wp.gameserver.model.Skill.SkillTargetType;
import wp.gameserver.model.Skill.SkillType;
import wp.gameserver.model.World;
import wp.gameserver.model.Zone;
import wp.gameserver.model.base.ClassId;
import wp.gameserver.model.base.Experience;
import wp.gameserver.model.base.RestartType;
import wp.gameserver.model.instances.ChestInstance;
import wp.gameserver.model.items.ItemInstance;
import wp.gameserver.model.phantom.PhantomItem;
import wp.gameserver.model.phantom.PhantomItemAction;
import wp.gameserver.model.phantom.PhantomSkill;
import wp.gameserver.model.phantom.PhantomTemplate;
import wp.gameserver.model.phantom.PhantomZone;
import wp.gameserver.model.stats.Env;
import wp.gameserver.model.stats.Stats;
import wp.gameserver.model.stats.conditions.Condition;
import wp.gameserver.model.stats.funcs.FuncAdd;
import wp.gameserver.network.clientpackets.EnterWorld;
import wp.gameserver.network.serverpackets.Say2;
import wp.gameserver.network.serverpackets.components.ChatType;
import wp.gameserver.network.serverpackets.components.IStaticPacket;
import wp.gameserver.tables.AdminTable;
import wp.gameserver.tables.SkillTable;
import wp.gameserver.taskmanager.AiTaskManager;
import wp.gameserver.templates.item.EtcItemTemplate.EtcItemType;
import wp.gameserver.templates.item.ItemTemplate.Grade;
import wp.gameserver.utils.Location;

/**
 * @author Nik
 */
@SuppressWarnings("unused")
public class PhantomPlayerAI extends PlayerAI implements OnAiEventListener, OnKillListener
{// TODO: Refuse trade and party and clan and shits.
	public static final int TASK_DEFAULT_WEIGHT = 10000;
	private static final int WAIT_TIMER_ID = 0;
	private static final int BUFF_TIMER_ID = 1;
	
	public static final Comparator<Creature> lvlDiff = Comparator.comparingInt(Creature::getLevel);
	public final Comparator<Creature> distanceComparator = (c1, c2) -> (Integer.compare((int) getActor().getDistance(c1), (int) getActor().getDistance(c2)));
	public final Comparator<Creature> targetComparator = distanceComparator.thenComparing(lvlDiff);
	
	protected long AI_TASK_ATTACK_DELAY = Config.AI_TASK_ATTACK_DELAY;
	protected long AI_TASK_ACTIVE_DELAY = Config.AI_TASK_ACTIVE_DELAY;
	protected long AI_TASK_DELAY_CURRENT = AI_TASK_ACTIVE_DELAY;
	
	protected ScheduledFuture<?> _aiTask;
	protected ScheduledFuture<?> _roamingTask;
	private boolean _isWaiting = false;
	private final int _thinkActiveExceptions = 0;
	protected String _mood = "";
	protected final NavigableSet<Task> _tasks = new ConcurrentSkipListSet<>(TaskComparator.getInstance());
	
	private long _buffTimeLastCached = 0;
	private int _buffTimeCache = -1;
	private final long _lastSelfBuffCheck = 0;
	private long _lastMoveAround = 0;
	private long _lastAiResponse = 0;
	private long _lastWantToFarm = 0;
	private final long _lastFarmStarted = System.currentTimeMillis();
	
	protected PhantomTemplate _template;
	
	// Roaming in town shits.
	private boolean _offlineShopsChecked = false;
	private int _warehouseChecked = 0;
	private int _shopChecked = 0;
	private int _npcChecked = 0;
	
	int maxChecksWh = Config.PHANTOM_ROAMING_MAX_WH_CHECKS; // Dwarfs check warehouse more often
	int maxChecksShop = Config.PHANTOM_ROAMING_MAX_SHOP_CHECKS; // Dwarfs check shops more often
	
	public PhantomPlayerAI(Player actor)
	{
		super(actor);
	}
	
	private static class TaskComparator implements Comparator<Task>
	{
		private static final Comparator<Task> _instance = new TaskComparator();
		
		public static final Comparator<Task> getInstance()
		{
			return _instance;
		}
		
		@Override
		public int compare(Task o1, Task o2)
		{
			if ((o1 == null) || (o2 == null))
			{
				return 0;
			}
			return o2.weight - o1.weight;
		}
	}
	
	public void addTaskCast(Creature target, Skill skill)
	{
		addTaskCast(target, skill, null, false, false);
	}
	
	public void addTaskCast(Creature target, Skill skill, Condition cond)
	{
		addTaskCast(target, skill, cond, false, false);
	}
	
	public void addTaskCast(Creature target, Skill skill, Condition cond, boolean forceUse)
	{
		addTaskCast(target, skill, cond, forceUse, false);
	}
	
	public void addTaskCast(Creature target, Skill skill, Condition cond, boolean forceUse, boolean dontMove)
	{
		if (skill == null)
		{
			return;
		}
		
		Task task = new Task();
		task.type = TaskType.CAST;
		task.target = target.getRef();
		task.skill = skill;
		task.cond = cond;
		task.forceUse = forceUse;
		task.dontMove = dontMove;
		_tasks.add(task);
	}
	
	public void addTaskBuff(Creature target, Skill skill)
	{
		addTaskBuff(target, skill, null);
	}
	
	public void addTaskBuff(Creature target, Skill skill, Condition cond)
	{
		if (skill == null)
		{
			return;
		}
		
		Task task = new Task();
		task.type = TaskType.BUFF;
		task.target = target.getRef();
		task.skill = skill;
		task.cond = cond;
		_tasks.add(task);
	}
	
	public void addTaskAttack(Creature target)
	{
		addTaskAttack(target, null, false, false);
	}
	
	public void addTaskAttack(Creature target, Condition cond)
	{
		addTaskAttack(target, cond, false, false);
	}
	
	public void addTaskAttack(Creature target, Condition cond, boolean forceUse)
	{
		addTaskAttack(target, cond, forceUse, false);
	}
	
	public void addTaskAttack(Creature target, Condition cond, boolean forceUse, boolean dontMove)
	{
		if (target == null)
		{
			return;
		}
		
		Task task = new Task();
		task.type = TaskType.ATTACK;
		task.target = target.getRef();
		task.cond = cond;
		task.forceUse = forceUse;
		task.dontMove = dontMove;
		_tasks.add(task);
	}
	
	public void addTaskMove(Location loc, int offset)
	{
		addTaskMove(loc, offset, true, null);
	}
	
	public void addTaskMove(Location loc, int offset, boolean pathfind)
	{
		addTaskMove(loc, offset, pathfind, null);
	}
	
	public void addTaskMove(Location loc, int offset, boolean pathfind, Condition cond)
	{
		Task task = new Task();
		task.type = TaskType.MOVE;
		task.loc = loc;
		task.pathfind = pathfind;
		task.locationOffset = offset;
		_tasks.add(task);
	}
	
	public void addTaskInteract(Creature target)
	{
		Task task = new Task();
		task.type = TaskType.INTERACT;
		task.target = target.getRef();
		task.pathfind = true;
		_tasks.add(task);
	}
	
	protected boolean maybeNextTask(Task currentTask)
	{
		// Next job
		_tasks.remove(currentTask);
		// If there are no more jobs - define new
		if (_tasks.size() == 0)
		{
			return true;
		}
		return false;
	}
	
	/**
	 * @return true : clear all tasks
	 */
	protected void doTask()
	{
		Player actor = getActor();
		
		if (_tasks.isEmpty())
		{
			return;
		}
		
		Task currentTask = _tasks.pollFirst();
		if (currentTask == null)
		{
			return;
		}
		
		if (actor.isDead() || actor.isAttackingNow() || actor.isCastingNow())
		{
			return;
		}
		
		if (currentTask.cond != null)
		{
			if (currentTask.target == null)
			{
				return;
			}
			
			final Env env = Env.valueOf(actor, currentTask.target.get(), currentTask.skill);
			if (!currentTask.cond.test(env))
			{
				env.recycle();
				return;
			}
			env.recycle();
		}
		
		switch (currentTask.type)
		{
			case MOVE:
				setNextAction(nextAction.MOVE, currentTask.loc, currentTask.locationOffset, currentTask.pathfind, currentTask.dontMove);
				getActor().setRunning();
				setNextIntention();
				break;
			case INTERACT:
				setNextAction(nextAction.INTERACT, currentTask.target.get(), null, currentTask.forceUse, currentTask.dontMove);
				getActor().setRunning();
				setNextIntention();
				break;
			case ATTACK:
				setNextAction(nextAction.ATTACK, currentTask.target.get(), null, false, currentTask.dontMove);
				setNextIntention();
				break;
			case CAST:
				setNextAction(nextAction.CAST, currentTask.skill, currentTask.target.get(), currentTask.forceUse, currentTask.dontMove);
				setNextIntention();
				break;
		}
	}
	
	@Override
	public synchronized void startAITask()
	{
		if (_aiTask == null)
		{
			System.out.println("TESTTTTTTTTTTTTT");
			
			AI_TASK_DELAY_CURRENT = AI_TASK_ACTIVE_DELAY;
			_aiTask = AiTaskManager.getInstance().scheduleAtFixedRate(this, 0L, AI_TASK_DELAY_CURRENT);
			final int classId = PhantomsDAO.getInstance().select(getActor().getObjectId());
			_template = PhantomTemplateData.getInstance().getTemplate(classId);
			
			if (_template == null)
			{
				_log.warn("startAITask called on phantom with no template " + getActor());
				PhantomPlayers.terminatePhantom(getActor().getObjectId(), true);
				return;
			}
			
			getActor().addListener(this);
			getActor().addStatFunc(new FuncAdd(Stats.MAX_NO_PENALTY_LOAD, 0x40, this, Integer.MAX_VALUE)); // I have reached a new level of laziness - avoid caring about weight penalty :D
			waitTime(4000); // Wait t least 4secs until active, to not look suspicious.
		}
	}
	
	protected synchronized void switchAITask(long NEW_DELAY)
	{
		if (_aiTask == null)
		{
			return;
		}
		
		if (AI_TASK_DELAY_CURRENT != NEW_DELAY)
		{
			_aiTask.cancel(false);
			AI_TASK_DELAY_CURRENT = NEW_DELAY;
			_aiTask = AiTaskManager.getInstance().scheduleAtFixedRate(this, 0L, AI_TASK_DELAY_CURRENT);
			final int classId = PhantomsDAO.getInstance().select(getActor().getObjectId());
			_template = PhantomTemplateData.getInstance().getTemplate(classId);
			if (_template == null)
			{
				_log.warn("switchAITask called on phantom with no template " + getActor());
				PhantomPlayers.terminatePhantom(getActor().getObjectId(), true);
				return;
			}
			getActor().addListener(this);
			getActor().addStatFunc(new FuncAdd(Stats.MAX_NO_PENALTY_LOAD, 0x40, this, Integer.MAX_VALUE)); // I have reached a new level of laziness - avoid caring about weight penalty :D
		}
	}
	
	@Override
	public final synchronized void stopAITask()
	{
		if (_aiTask != null)
		{
			_aiTask.cancel(false);
			_aiTask = null;
			_template = null;
			getActor().removeListener(this);
			getActor().removeStatsOwner(this); // Remove the added weight bonus
		}
	}
	
	@Override
	public boolean isActive()
	{
		return _aiTask != null;
	}
	
	@Override
	public void runImpl()
	{
		if (_aiTask == null)
		{
			return;
		}
		
		onEvtThink();
		_lastAiResponse = System.currentTimeMillis();
		// say(this.getIntention().toString());
	}
	
	public void waitTime(long timeInMilis)
	{
		_isWaiting = true;
		addTimer(WAIT_TIMER_ID, timeInMilis);
	}
	
	public void setMood(String mood)
	{
		say("Changing my mood [" + _mood + "] -> [" + mood + "]");
		_mood = mood;
	}
	
	public String getMood()
	{
		return _mood;
	}
	
	@Override
	protected void onIntentionActive()
	{
		clearNextAction();
		changeIntention(CtrlIntention.AI_INTENTION_ACTIVE, null, null);
	}
	
	@Override
	protected void onIntentionIdle()
	{
		clearNextAction();
		changeIntention(CtrlIntention.AI_INTENTION_ACTIVE, null, null);
	}
	
	/**
	 * @return true : block thinkAttack() execution <br>
	 *         false : if the AI should continue and execute thinkAttack()
	 */
	@Override
	protected boolean thinkActive()
	{
		return false;
	}
	
	private void thinkCanFarm()
	{
		int playersAround = 0;
		for (Player player : World.getAroundPlayers(getActor()))
		{
			if (getActor().getDistance(player) < Config.PHANTOM_MAX_PLAYERS_IN_FARMZONE_RANGE)
			{
				playersAround++;
			}
		}
		
		if (playersAround > Config.PHANTOM_MAX_PLAYERS_IN_FARMZONE)
		{
			say("There is more then " + Config.PHANTOM_MAX_PLAYERS_IN_FARMZONE + " players around me going to town and gonna try again after " + (Config.PHANTOM_RETRY_TO_FARM_DELAY / 1000) + " seconds..");
			waitTime(Config.PHANTOM_RETRY_TO_FARM_DELAY);
			getActor().doCast(findSoeSkill(), getActor(), false);
		}
	}
	
	private void thinkMoveAround()
	{
		final Player actor = getActor();
		System.out.println("TTTTTTTTTTTTTTTTTTTTTTTTTTTTT");
		if (!actor.isMoving && ((System.currentTimeMillis() - _lastMoveAround) > Config.PHANTOM_MOVE_AROUND_DELAY))
		{
			System.out.println("2222222222222222222222222");
			say("No target? lets move around!");
			final int radius = Config.PHANTOM_MOVE_AROUND_RANGE;
			int x = actor.getX();
			int y = actor.getY();
			
			x = Rnd.nextInt(radius * 2);
			y = Rnd.get(x, radius * 2);
			y = (int) Math.sqrt((y * y) - (x * x));
			x += actor.getX() - radius;
			y += actor.getY() - radius;
			final Location loc = GeoEngine.moveCheck(actor.getX(), actor.getY(), actor.getZ(), x, y, actor.getGeoIndex());
			addTaskMove(loc, 0, true);
			_lastMoveAround = System.currentTimeMillis();
			actor.setTarget(null);
			setAttackTarget(null);
		}
	}
	
	@Override
	protected void thinkAttack(boolean checkRange)
	{
		if (_template == null)
		{
			_log.warn("thinkAttack called on phantom with no template " + getActor());
			PhantomPlayers.terminatePhantom(getActor().getObjectId(), true);
			return;
		}
		
		// Set last attack time.
		getActor().setLastAttackPacket();
		
		// Activate shots if not activated.
		if (getActor().getAutoSoulShot().isEmpty())
		{
			EnterWorld.verifyAndLoadShots(getActor());
		}
		
		// Get a target to cast
		Creature target = getAttackTarget();
		if (target == null)
		{
			target = getActor().getTarget(Creature.class);
		}
		
		// No valid target found? Do nothing
		if (target != null)
		{
			boolean shouldCastSkill = target.isPlayer(); // In pvp always cast skills.
			switch (_template.getType())
			{
				case WARRIOR:
					shouldCastSkill = Rnd.chance(100);
					break;
				case DAGGER:
					shouldCastSkill = Rnd.chance(60);
					break;
				case ARCHER:
					shouldCastSkill = Rnd.chance(50);
					break;
				case MAGE:
					shouldCastSkill = true;
					break;
			}
			
			// Check to cast skill
			if (shouldCastSkill)
			{
				for (PhantomSkill skill : _template.getSkills().values())
				{
					if (GeoEngine.canSeeTarget(getActor(), target, false) && skill.canUseSkill(getActor(), target, false, false))
					{
						// Check if the skill is going to be casted or not.
						if (cast(skill.getSkill(getActor()), target, false, false))
						{
							say("Casting " + skill.getSkill(getActor()).getName());
							return;
						}
					}
				}
				
				// couldn't cast?
				thinkMoveAround();
			}
		}
		
		super.thinkAttack(checkRange);
	}
	
	@Override
	protected void thinkCast(boolean checkRange)
	{
		// Mage cant see target, let him press attack so he can let geodata lead him to the mob.
		if (!GeoEngine.canSeeTarget(getActor(), getAttackTarget(), false))
		{
			// Catch new target.
			setAttackTarget(null);
			getActor().setTarget(null);
			setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
			return;
		}
		
		super.thinkCast(checkRange);
	}
	
	private Skill findSoeSkill()
	{
		Skill soe = SkillTable.getInstance().getInfo(2013, 1);
		if (!Config.PHANTOM_SCROLLS_OF_ESCAPE.isEmpty())
		{
			final int skillId = Rnd.get(Config.PHANTOM_SCROLLS_OF_ESCAPE);
			final Skill sk = SkillTable.getInstance().getInfo(skillId, 1);
			if (sk != null)
			{
				return sk;
			}
		}
		return soe;
	}
	
	private void createNewItems()
	{
		if (_template == null)
		{
			_log.warn("createNewItems called on phantom with no template " + getActor());
			PhantomPlayers.terminatePhantom(getActor().getObjectId(), true);
			return;
		}
		
		final Player actor = getActor();
		
		// Cleanup
		say("Cleaning up some inventory junk");
		for (ItemInstance item : actor.getInventory().getItems())
		{
			if (item.isEquipped())
			{
				continue;
			}
			
			if (item.isAccessory() || item.isArmor() || item.isWeapon())
			{
				boolean noGradeJunk = (item.getCrystalType() == Grade.NONE) && (actor.getLevel() > 20);
				boolean dGradeJunk = (item.getCrystalType() == Grade.D) && (actor.getLevel() > 40);
				boolean cGradeJunk = (item.getCrystalType() == Grade.C) && (actor.getLevel() > 52);
				boolean bGradeJunk = (item.getCrystalType() == Grade.B) && (actor.getLevel() > 61);
				boolean aGradeJunk = (item.getCrystalType() == Grade.A) && (actor.getLevel() > 76);
				if (noGradeJunk && dGradeJunk && cGradeJunk && bGradeJunk && aGradeJunk)
				{
					say("Destroying " + item);
					actor.getInventory().destroyItem(item);
				}
				continue;
			}
			else if (item.isArrow() || item.isAdena() || (item.getItemType() == EtcItemType.SHOT) || (item.getItemType() == EtcItemType.POTION))
			{
				continue;
			}
			say("Destroying " + item);
			actor.getInventory().destroyItem(item);
		}
		
		for (PhantomItem item : _template.getItems().get(PhantomItemAction.EQUIP))
		{
			if (!item.checkCondition(getActor()))
			{
				continue;
			}
			else if (item.isEquipped(getActor()))
			{
				continue;
			}
			
			item.createItemIfNotInInventory(getActor(), false);
			item.equip(getActor(), false);
			if (item.isWeapon())
			{
				item.equipThisWeaponAndActivateShots(getActor(), false);
			}
		}
	}
	
	protected Creature getTarget()
	{
		final LinkedList<Creature> lowPriorityTargets = new LinkedList<>();
		final List<Creature> list = World.getAroundCharacters(getActor()).stream().filter(Objects::nonNull).filter(t -> !t.isRaid() && !t.isDead() && !t.isMinion() && !t.isInvisible() && !t.isInvul() && t.isMonster() && !(t instanceof ChestInstance)).sorted(targetComparator).collect(Collectors.toList());
		
		// Find a valid target to attack.
		OUT: for (Creature tgt : list)
		{
			for (Player player : tgt.getAroundPlayers())
			{
				if (player.isGM() || player.isInvisible())
				{
					continue;
				}
				
				if ((player.getTarget() == tgt) && (tgt.getCurrentHpPercents() < 99))
				{
					continue OUT;
				}
			}
			
			if ((System.currentTimeMillis() - tgt.getLastAttackedTime()) < 5000) // Target attacked in the last 5 secs.
			{
				if ((tgt.getAggressionTarget() != null) && (tgt.getAggressionTarget() != getActor()))
				{
					continue;
				}
			}
			if (getActor().checkTarget(tgt))
			{
				if ((getActor().getLevel() - tgt.getLevel()) > 5)
				{
					lowPriorityTargets.add(tgt);
				}
				else
				{
					return tgt;
				}
			}
			else
			{
				if (Location.getDistance(getActor(), tgt) >= 2000)
				{
					lowPriorityTargets.add(tgt);
				}
			}
		}
		
		lowPriorityTargets.sort(targetComparator);
		return lowPriorityTargets.isEmpty() ? null : lowPriorityTargets.getFirst();
	}
	
	/**
	 * @return : A visible random location nearby. Null if it fails to find such.
	 */
	protected Location getRandomLocation(int minRange, int maxRange, int maximumAttemptsToFindSuchLocation)
	{
		int x = getActor().getX();
		int y = getActor().getY();
		int z = getActor().getZ();
		int geoIndex = getActor().getGeoIndex();
		
		for (int i = 0; i < maximumAttemptsToFindSuchLocation; i++)
		{
			Location tmp = getActor().getLoc().coordsRandomize(minRange, maxRange);
			if (GeoEngine.canMoveToCoord(x, y, z, tmp.x, tmp.y, tmp.z, geoIndex))
			{
				return tmp;
			}
		}
		
		return null;
	}
	
	public void runAway(Location fromLoc, int timeInMilis)
	{
		int posX = getActor().getX();
		int posY = getActor().getY();
		int posZ = getActor().getZ();
		
		int old_posX = posX;
		int old_posY = posY;
		int old_posZ = posZ;
		
		int signx = posX < fromLoc.getX() ? -1 : 1;
		int signy = posY < fromLoc.getY() ? -1 : 1;
		
		int range = (int) (((0.71 * timeInMilis) / 1000) * getActor().getMoveSpeed());
		
		posX += signx * range;
		posY += signy * range;
		posZ = GeoEngine.getHeight(posX, posY, posZ, getActor().getGeoIndex());
		
		if (GeoEngine.canMoveToCoord(old_posX, old_posY, old_posZ, posX, posY, posZ, getActor().getGeoIndex()))
		{
			addTaskMove(new Location(posX, posY, posZ), 0);
		}
	}
	
	public boolean hasBetterFarmZone()
	{
		if (_template == null)
		{
			_log.warn("hasBetterFarmZone called on phantom with no template " + getActor());
			PhantomPlayers.terminatePhantom(getActor().getObjectId(), true);
			return false;
		}
		
		List<PhantomZone> availableZones = _template.getAvailableZones(getActor());
		if ((availableZones == null) || availableZones.isEmpty())
		{
			return false;
		}
		
		for (PhantomZone zone : availableZones)
		{
			// Already in good enough zone.
			if (getActor().getDistance(zone.getRandomTeleportLoc()) <= 8_000)
			{
				return false;
			}
		}
		
		return true;
	}
	
	private boolean isInTown()
	{
		if (!getActor().isInPeaceZone())
		{
			return false;
		}
		
		for (Zone zone : getActor().getZones())
		{
			if (zone.getName().contains("talking_island_town_peace_zone") || zone.getName().contains("darkelf_town_peace_zone") || zone.getName().contains("elf_town_peace") || zone.getName().contains("guldiocastle_town_peace") || zone.getName().contains("gludin_town_peace") || zone.getName().contains("dion_town_peace") || zone.getName().contains("floran_town_peace") || zone.getName().contains("giran_town_peace") || zone.getName().contains("orc_town_peace") || zone.getName().contains("dwarf_town_peace") || zone.getName().contains("oren_town_peace") || zone.getName().contains("hunter_town_peace") || zone.getName().contains("aden_town_peace") || zone.getName().contains("speaking_port_peace") || zone.getName().contains("gludin_port") || zone.getName().contains("giran_port") || zone.getName().contains("heiness_peace") || zone.getName().contains("godad_peace") || zone.getName().contains("rune_peace") || zone.getName().contains("gludio_airship_peace") || zone.getName().contains("schuttgart_town_peace") || zone.getName().contains("kamael_village_town_peace") || zone.getName().contains("keucereus_alliance_base_town_peace") || zone.getName().contains("giran_harbor_peace_alt") || zone.getName().contains("parnassus_peace"))
			{
				return true;
			}
		}
		
		return false;
	}
	
	public void buffFromNpcBuffer(int curTries)
	{
		if (!Config.ENABLE_SCHEME_BUFFER || !isInTown())
		{
			return;
		}
		
		if (_template == null)
		{
			_log.warn("buffFromNpcBuffer called on phantom with no template " + getActor());
			PhantomPlayers.terminatePhantom(getActor().getObjectId(), true);
			return;
		}
		
		// Fail buff, schedule another try in 30 sec.A
		if ((getActor().getPvpFlag() > 0) || getActor().isInCombat())
		{
			addTimer(BUFF_TIMER_ID, curTries + 1, 30000);
		}
		else
		{
			switch (_template.getType())
			{
				
				case DAGGER:
				case ARCHER:
					CommunityBoardManager.getInstance().executeBypass(getActor(), "_bbsbuffer getBuffs;Rogue");
					break;
				case MAGE:
					CommunityBoardManager.getInstance().executeBypass(getActor(), "_bbsbuffer getBuffs;Wizzard");
					break;
				default: // WARRIOR
					CommunityBoardManager.getInstance().executeBypass(getActor(), "_bbsbuffer getBuffs;Fighter");
			}
			
			CommunityBoardManager.getInstance().executeBypass(getActor(), "_bbsbuffer getBuffs;noblesse");
			CommunityBoardManager.getInstance().executeBypass(getActor(), "_bbsbuffer getBuffs;heal");
			
			if ((getActor().getEffectList().getAllFirstEffects().length < Config.ALT_BUFF_LIMIT) || (getRemaningBuffTime(false) < 7000)) // Just a check to see if there are any buffs
			{
				say("I tried to buff but it didn't buff me right :( I will try " + (5 - curTries) + " more times, next one in 1 min");
				addTimer(BUFF_TIMER_ID, curTries + 1, 60000);
			}
			else
			{
				say("I have buffed.");
			}
		}
	}
	
	private int getRemaningBuffTime(boolean useCache)
	{
		// If this is called in the last 1 minute, it will use cache instead.
		if (useCache && ((System.currentTimeMillis() - _buffTimeLastCached) <= 60000) && (_buffTimeCache >= 0))
		{
			return _buffTimeCache;
		}
		
		int buffTimeLeft = 0;
		int buffsCount = 0;
		for (Effect e : getActor().getEffectList().getAllEffects())
		{
			if ((e != null) && (e.getSkill().getSkillType() == SkillType.BUFF) && (e.getSkill().getTargetType() != SkillTargetType.TARGET_SELF))
			{
				buffTimeLeft += e.getTimeLeft();
				buffsCount++;
			}
		}
		
		_buffTimeLastCached = System.currentTimeMillis();
		
		// Whoops, almost forgot... I was going to create a black hole if I didnt do that :D
		if (buffsCount == 0)
		{
			_buffTimeCache = 0;
			return 0;
		}
		
		return _buffTimeCache = (buffTimeLeft / buffsCount);
	}
	
	protected void upgradeClass()
	{
		if (_template == null)
		{
			_log.warn("upgradeClass called on phantom with no template " + getActor());
			PhantomPlayers.terminatePhantom(getActor().getObjectId(), true);
			return;
		}
		
		if ((getActor().getClassId().getLevel() < 4) && // Not 3rd class
			(((getActor().getLevel() >= 20) && (getActor().getClassId().getLevel() == 1)) || ((getActor().getLevel() >= 40) && (getActor().getClassId().getLevel() == 2)) || ((getActor().getLevel() >= 76) && (getActor().getClassId().getLevel() == 3))))
		{
			
			ClassId nextClass = _template.getNextClass(getActor());
			if (nextClass != null)
			{
				say("My class choice is from " + getActor().getClassId() + " to " + nextClass);
				getActor().setClassId(nextClass.getId(), false, false);
			}
		}
	}
	
	/**
	 * @return true : if the packet should be forwarded to the client (active players can be set to phantoms too)
	 */
	public boolean onPacketRecieved(IStaticPacket p)
	{
		return false;
	}
	
	@Override
	public Player getActor()
	{
		return super.getActor();
	}
	
	@Override
	public boolean isPhantomPlayerAI()
	{
		return true;
	}
	
	public long getLastAiResponse()
	{
		return _lastAiResponse;
	}
	
	private void say(String text)
	{
		for (Player gm : AdminTable.getAllGms(false))
		{
			if ((gm != null) && !gm.isBlockAll() && gm.isInRange(getActor(), 500))
			{
				gm.sendPacket(new Say2(getActor().getObjectId(), ChatType.ALL, getActor().getName(), text));
			}
		}
	}
	
	@Override
	public void onAiEvent(Creature actor, CtrlEvent evt, Object[] args)
	{
		switch (evt)
		{
			case EVT_DEAD:
			{
				final Player player = actor.getPlayer();
				int ressurectionDelay = Rnd.get(2, 10) * 1000; // This is here because I dont want instant revive. Lets make it look more player-like
				_lastWantToFarm = System.currentTimeMillis();
				
				say("QQ im dead :( Going to village in " + (ressurectionDelay / 1000) + " seconds.");
				
				ThreadPoolManager.getInstance().schedule(() ->
				{
					Location loc = Location.getRestartLocation(player.getPlayer(), RestartType.TO_VILLAGE);
					// Reflection ref = player.getReflection();
					//
					// if (ref == ReflectionManager.DEFAULT)
					// {
					// for (GlobalEvent e : player.getEvents())
					// {
					// loc = e.getRestartLoc(player.getPlayer(), RestartType.TO_VILLAGE);
					// }
					// }
					//
					// if (loc == null)
					// {
					// loc = Location.getRestartLocation(player.getPlayer(), RestartType.TO_VILLAGE);
					// }
					//
					
					if (loc != null)
					{
						player.setPendingRevive(true);
						player.teleToLocation(loc, ReflectionManager.DEFAULT);
					}
					
					createNewItems();
				}, ressurectionDelay);
				break;
			}
			case EVT_ATTACKED:
			{
				Creature attacker = (Creature) args[0];
				int damage = (int) args[1];
				double dmgPer = damage / actor.getCurrentHp();
				if (attacker.isPlayer())
				{
					// Do not touch people in party.
					if (attacker.getPlayer().isInParty())
					{
						return;
					}
					
					if (actor != getActor())
					{
						return;
					}
					
					int ggp = attacker.getPlayer().getGearScore();
					if ((ggp <= actor.getPlayer().getGearScore()) && (dmgPer <= 0.2D)) // If damage is less than 20% of total HP
					{
						// If casts skill with remaning cast of 3s or more, abort.
						if (actor.getCastingTime() > 3000)
						{
							actor.abortCast(false, false);
						}
						
						actor.setTarget(attacker);
						if (actor.isMoving)
						{
							thinkActive();
						}
						_isWaiting = false;
					}
				}
				else
				{
					// If casts skill with remaning cast of 3s or more, abort.
					if (actor.getCastingTime() > 3000)
					{
						actor.abortCast(false, false);
					}
					
					if (getAttackTarget() == null)
					{
						setAttackTarget(attacker);
					}
					if (getAttackTarget() == attacker)
					{
						actor.setTarget(attacker);
					}
					Creature target = getAttackTarget();
					if ((target == null) || (target == attacker) || !getActor().checkTarget(target))
					{
						setIntention(CtrlIntention.AI_INTENTION_ATTACK, attacker);
					}
					if (actor.isMoving)
					{
						thinkActive();
					}
					_isWaiting = false;
				}
				break;
			}
			case EVT_FORGET_OBJECT:
			{
				L2Object object = (L2Object) args[0];
				if (object.isPlayer() && object.isInvisible())
				{
					waitTime(10000);
				}
				break;
			}
			case EVT_SEE_SPELL:
			{
				break;
			}
			case EVT_TELEPORTED:
			{
				final Player player = actor.getPlayer();
				if (player == null)
				{
					return;
				}
				
				if (!player.isPhantom() || !player.getAI().isPhantomPlayerAI())
				{
					return;
				}
				
				PhantomPlayerAI ai = ((PhantomPlayerAI) player.getAI());
				
				if (ai.isInTown())
				{
					ai.say("Lawl, a town. Time to buff, equip and change class if needed.");
					ai.buffFromNpcBuffer(0);
					
					ai.upgradeClass();
					
					ai.say("Meh in town, will roam around.");
					ai.setMood(Rnd.get(new String[]
					{
						"free roam",
						"check npcs",
						"check shop",
						"check warehouse"
					}));
					ai.createNewItems();
				}
				else
				{
					// TODO THIS IS FARM
					waitTime(Rnd.get(2500, 6000));
					setMood("");
					_offlineShopsChecked = false;
					_warehouseChecked = 0;
					_shopChecked = 0;
					_npcChecked = 0;
				}
				break;
			}
			case EVT_TIMER:
			{
				int timerId = (int) args[0];
				switch (timerId)
				{
					case WAIT_TIMER_ID:
						_isWaiting = false;
						break;
					case BUFF_TIMER_ID:
						int tries = (int) args[1];
						if (tries < 5)
						{
							buffFromNpcBuffer(tries);
						}
						break;
				}
				break;
			}
		}
		
	}
	
	@Override
	public void onKill(Creature actor, Creature victim)
	{
		waitTime(Rnd.get(7, 15) * 100); // Wait 750-1500ms after kill, so it doesnt look like botting
		
		if (Rnd.chance(20)) // 20% chance to change loc a bit when killing
		{
			Location randomLocation = getRandomLocation(50, 120, 200);
			if (randomLocation != null)
			{
				addTaskMove(randomLocation, 50, true);
			}
		}
		else if (Rnd.chance(2)) // 2% chance to go a bit afk while farming
		{
			Location randomLocation = getRandomLocation(50, 200, 200);
			if (randomLocation != null)
			{
				addTaskMove(randomLocation, 50, true);
			}
			
			int time = Rnd.get(4, 10) * 1000;
			waitTime(time);
		}
	}
	
	@Override
	public boolean ignorePetOrSummon()
	{
		return true;
	}
	
	@Override
	public void onLevelChange(int oldLvl, int level)
	{
		// When phantom reach max level (85) terminate him.
		if (level == Experience.getMaxLevel())
		{
			final int objectId = getActor().getObjectId();
			PhantomPlayers.terminatePhantom(objectId, true);
			PhantomsDAO.getInstance().delete(objectId);
			CharacterDAO.getInstance().deleteCharByObjId(objectId);
		}
	}
}

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Answer this question...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



  • Posts

    • L2Lusty 50x Essence High Version Open day 27/06  12:00 GMT London WebSite: https://l2lusty.com/ General Information If you are looking for a High Five retail server, this project is not for you, our gameplay and farming system is based on the Essence version. If you are looking for something new and different to play, this server is for you. Our server is using its own version that mixes High Five and the latest Essence version. With this, we have a good part of the items from the Lineage2 Essence version, with classes and bosses from High Five. An auto-balance system in onlympiad games, which will bring greater equality in combats. We added the Chaos Zone and Peace Zone events, for a better LCoin farm, these events are 24h and can be accessed with the commands .chaos or .peace. All essence items are purchased in our alt+b and cost an average of 300k LCoins. LCoins are obtained by killing any mob, with better drops in the Chaos zone and the Peace Zone. Server Version High Five + Essence Game Play Experience 50x Spoil 8x Drop 8x Adena 8x Normal Enchant 50% (+3 to +12) (40% 12 to 20) Blessed Enchant 50% (+3 to +12) (40% 12 to 20) Safe Enchant +3 Max Enchant +20 Max Windows / IP 6 Accounts Anti-Bot system ON Champions System ON TerritoryWar Saturday 20:00 Siege Every Sunday Olympiads 18:00 / 23:50 Oly End Days 1, 11 and 22 Max Register 1 For IP Minimum Players for Start 4 Players Subclass FREE MAX LVL 85 SHOP GRADE-S Party Diff 30 Level 55/85   Essence Items Price   Items Lvl 1 300k / 1.200kk LCoins Upgrade to Lvl 2 2 items Lvl 1 + 2b Adenas Upgrade to Lvl 3 2 items Lvl 2 + 2b Adenas Upgrade to Lvl 4 2 items Lvl 3 + 2b Adenas Upgrade Chance 25%   Exchange Items   As in other MMORPGs, we have a system of exchanging items for better items.       GrandBoss All Grand Boss Time Fixed Raids Status 50% Change Drop Queen Ant / Core / Orfen / Baium Queen ant Level: 80 Every day 18:30 / Drop Jewel 40% Core Level: 80 Every day 18:40 / Drop Jewel 90% Orfem Level: 80 Every day 18:50 / Drop Jewel 90% Baium Level: 80 Every Friday 18:00 / Drop Jewel 100% Beleth Every Friday 22:00 / Drop Jewel 100% Valakas Every Saturday 18:00 / Drop Jewel 100% Antharas Every Sunday 17:00 / Drop Jewel 100% Max Character in Zone Boss 1 For IP   Instances Party All Intances 5 Players Raids Status 50% Zaken Day 61 5 Players / Jewel Chance 10% Zaken Day Hard 83 5 Players / Jewel Chance Normal / 10% / Blessed 1.9% Zaken Nightmare 61 5 Players / Jewel Chance 90% Frintezza 5 Players / Jewel Chance 40% Freya Normal 5 Players / Jewel Chance 40% Freya Hard 5 Players / Jewel Chance 40% Tiat 5 Players / Weapon Chance 10%   Instances Solo   All Intances Drop S84 Up Crystal Level 10 / 17 Baylor Solo Drop Moirai Set / Vesper Weapons Darion Solo Weapon Chance 1% Tiat Solo Weapon Chance 1% Frintezza Solo Jewel Chance 2% Freya Solo Jewel Chance 2% Zaken Solo Jewel Chance 2% Core Solo Jewel Chance 2% Orfen Solo Jewel Chance 2% Beleth Solo Jewel Chance 1%   Special Events Event Boss Lindvior Every day 16:40 - 22:40 Event Pig Invazion Every day 15:40 - 23:40 Event Dragon Invazion Every day 18:40 - 01:40 Event City War Every day 19:40 - 02:40   Commands .ach .achievements Opens the achievements interface .buffshield (anti-buff) .away .back (stay away / back) .menu .cfg (Character control panel) .combine .talisman (Combine all Talismans) .dressme (Visual Armor Added +1000 HP) .party .invite .partylist Our custom party creation system .offline (off line shop) .repair (repair character) .siege (See the siege time or register your clan) .stats (character status)   .offbuff (Open store buffs) .autofarm (Open Auto Farm Interface) .report (Report a suspicious bot player) .buffshop (Invoke a summon to sell your buffs.) .vote (Opens our vote system.) .aa (Exchange your seal stones for AA automatically.) .oly (Shows all players who are first in the oly ranking.) .status (See a player's status, he has to be in your target) .seeres (Look at a player's resistance, he has to be in his target) .equip (See all of a player's items, they have to be in their target) .regoly (Register with oly wherever you are.) .bagclean Delete all items from your inventory, use it knowing that you will lose everything that is not equipped!   WebSite: https://l2lusty.com/
    • Our sales are ongoing. Bump. 22 June 2025 Telegram: ContactDiscordAccS
  • Topics

×
×
  • Create New...

AdBlock Extension Detected!

Our website is made possible by displaying online advertisements to our members.

Please disable AdBlock browser extension first, to be able to use our community.

I've Disabled AdBlock