Jump to content
  • 0

Bug when attacking too many monsters


dextroy

Question

Hello.

 

I have a very particular issue and i don't know if there's a fix or if it's a client limitation.

 

I'm making an interlude multiskill server using a L2JFrozen base, and everything is pretty much set already.

 

As it's a multiskill, a stronger player should be able to fight many monsters at once. However, if i'm using a fighter class with Polearm when killing more than 8-10 monsters at once, the following bug happens:

 

The monster hp bar freezes (it doesn't go down visually), the character keeps attacking the monsters for a few seconds until the hp drain stops, the attack animation continues for some time as if the character is still fighintg all the monsters. but the hp drops to 0 and stays like that with the animation ongoing for a few more seconds until the player eventually dies and the monsters stop attacking all at once.

 

What i'm assuming is that the first monster of the aggroed pack dies (or something like that), but since the hp bar and the animation gets stuck as there's too much going on in the battle at once (too many hits, soulshots splashes, etc.), there's no time to switch to attack another monster and the result is the character dying.

 

Has anyone ever seen this? Is there a serverside or clientside fix? I have a good setup and a good Graphics Card so it's not a computer issue.

 

 

Link to comment
Share on other sites

Recommended Posts

  • 0
6 hours ago, dextroy said:

Hello.

 

I have a very particular issue and i don't know if there's a fix or if it's a client limitation.

 

I'm making an interlude multiskill server using a L2JFrozen base, and everything is pretty much set already.

 

As it's a multiskill, a stronger player should be able to fight many monsters at once. However, if i'm using a fighter class with Polearm when killing more than 8-10 monsters at once, the following bug happens:

 

The monster hp bar freezes (it doesn't go down visually), the character keeps attacking the monsters for a few seconds until the hp drain stops, the attack animation continues for some time as if the character is still fighintg all the monsters. but the hp drops to 0 and stays like that with the animation ongoing for a few more seconds until the player eventually dies and the monsters stop attacking all at once.

 

What i'm assuming is that the first monster of the aggroed pack dies (or something like that), but since the hp bar and the animation gets stuck as there's too much going on in the battle at once (too many hits, soulshots splashes, etc.), there's no time to switch to attack another monster and the result is the character dying.

 

Has anyone ever seen this? Is there a serverside or clientside fix? I have a good setup and a good Graphics Card so it's not a computer issue.

 

 

what base of frozen you use? and can you upload video?

Link to comment
Share on other sites

  • 0
9 hours ago, dextroy said:

Hello.

 

I have a very particular issue and i don't know if there's a fix or if it's a client limitation.

 

I'm making an interlude multiskill server using a L2JFrozen base, and everything is pretty much set already.

 

As it's a multiskill, a stronger player should be able to fight many monsters at once. However, if i'm using a fighter class with Polearm when killing more than 8-10 monsters at once, the following bug happens:

 

The monster hp bar freezes (it doesn't go down visually), the character keeps attacking the monsters for a few seconds until the hp drain stops, the attack animation continues for some time as if the character is still fighintg all the monsters. but the hp drops to 0 and stays like that with the animation ongoing for a few more seconds until the player eventually dies and the monsters stop attacking all at once.

 

What i'm assuming is that the first monster of the aggroed pack dies (or something like that), but since the hp bar and the animation gets stuck as there's too much going on in the battle at once (too many hits, soulshots splashes, etc.), there's no time to switch to attack another monster and the result is the character dying.

 

Has anyone ever seen this? Is there a serverside or clientside fix? I have a good setup and a good Graphics Card so it's not a computer issue.

 

 

From who you bought it? 

Link to comment
Share on other sites

  • 0

Sorry for the delay. Replying to everyone:

 

For the 1st question: My frozen version is a clean 1132. I can try to record a video if necessary.

For the 2nd, i didn't buy it from anyone i just got a public version of it on a forum and worked from there. I don't know how to make a java code entirely but i can understand bits and pieces and changed the things i wanted myself with the source.

 

@Tryskell As for the isDead check, on which one of the AI event? Where and how would i make this check? Sorry if it sounds as a stupid question, but as i mentioned, i understand bits and pieces but i'm not a programmer myself. Here's all the AI java codes:

image.png.47eb7de9189fd5a22ea0dd480e24d1db.png

 

Would it be the AttackableAI? If so here's the code from L2AttackableAI.java below:

 

/*
 * L2jFrozen Project - www.l2jfrozen.com 
 * 
 * This program 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 2, or (at your option)
 * any later version.
 *
 * This program 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * http://www.gnu.org/copyleft/gpl.html
 */
package com.l2jfrozen.gameserver.ai;

import static com.l2jfrozen.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
import static com.l2jfrozen.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static com.l2jfrozen.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;

import java.util.concurrent.Future;

import com.l2jfrozen.Config;
import com.l2jfrozen.gameserver.controllers.GameTimeController;
import com.l2jfrozen.gameserver.datatables.sql.TerritoryTable;
import com.l2jfrozen.gameserver.geo.GeoData;
import com.l2jfrozen.gameserver.managers.DimensionalRiftManager;
import com.l2jfrozen.gameserver.model.L2Attackable;
import com.l2jfrozen.gameserver.model.L2Character;
import com.l2jfrozen.gameserver.model.L2Effect;
import com.l2jfrozen.gameserver.model.L2Object;
import com.l2jfrozen.gameserver.model.L2Skill;
import com.l2jfrozen.gameserver.model.L2Summon;
import com.l2jfrozen.gameserver.model.actor.instance.L2ChestInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2DoorInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2FestivalMonsterInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2FolkInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2FriendlyMobInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2GuardInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2MinionInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2MonsterInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2NpcInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2PcInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2PenaltyMonsterInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2PlayableInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2RaidBossInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2RiftInvaderInstance;
import com.l2jfrozen.gameserver.model.actor.position.L2CharPosition;
import com.l2jfrozen.gameserver.model.quest.Quest;
import com.l2jfrozen.gameserver.templates.L2Weapon;
import com.l2jfrozen.gameserver.templates.L2WeaponType;
import com.l2jfrozen.gameserver.thread.ThreadPoolManager;
import com.l2jfrozen.util.random.Rnd;

/**
 * This class manages AI of L2Attackable.<BR>
 * <BR>
 */
public class L2AttackableAI extends L2CharacterAI implements Runnable
{
	
	// protected static final Logger LOGGER = Logger.getLogger(L2AttackableAI.class);
	
	private static final int RANDOM_WALK_RATE = 30; // confirmed
	// private static final int MAX_DRIFT_RANGE = 300;
	private static final int MAX_ATTACK_TIMEOUT = 300; // int ticks, i.e. 30 seconds
	
	/** The L2Attackable AI task executed every 1s (call onEvtThink method) */
	private Future<?> _aiTask;
	
	/** The delay after wich the attacked is stopped */
	private int _attackTimeout;
	
	/** The L2Attackable aggro counter */
	private int _globalAggro;
	
	/** The flag used to indicate that a thinking action is in progress */
	private boolean _thinking; // to prevent recursive thinking
	
	/**
	 * Constructor of L2AttackableAI.<BR>
	 * <BR>
	 * @param accessor The AI accessor of the L2Character
	 */
	public L2AttackableAI(final L2Character.AIAccessor accessor)
	{
		super(accessor);
		_attackTimeout = Integer.MAX_VALUE;
		_globalAggro = -10; // 10 seconds timeout of ATTACK after respawn
	}
	
	@Override
	public void run()
	{
		// Launch actions corresponding to the Event Think
		onEvtThink();
	}
	
	/**
	 * Return True if the target is autoattackable (depends on the actor type).<BR>
	 * <BR>
	 * <B><U> Actor is a L2GuardInstance</U> :</B><BR>
	 * <BR>
	 * <li>The target isn't a Folk or a Door</li> <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li> <li>The target is in the actor Aggro range and is at the same height</li> <li>The L2PcInstance target has karma (=PK)</li> <li>The L2MonsterInstance
	 * target is aggressive</li><BR>
	 * <BR>
	 * <B><U> Actor is a L2SiegeGuardInstance</U> :</B><BR>
	 * <BR>
	 * <li>The target isn't a Folk or a Door</li> <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li> <li>The target is in the actor Aggro range and is at the same height</li> <li>A siege is in progress</li> <li>The L2PcInstance target isn't a Defender</li>
	 * <BR>
	 * <BR>
	 * <B><U> Actor is a L2FriendlyMobInstance</U> :</B><BR>
	 * <BR>
	 * <li>The target isn't a Folk, a Door or another L2NpcInstance</li> <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li> <li>The target is in the actor Aggro range and is at the same height</li> <li>The L2PcInstance target has karma (=PK)</li><BR>
	 * <BR>
	 * <B><U> Actor is a L2MonsterInstance</U> :</B><BR>
	 * <BR>
	 * <li>The target isn't a Folk, a Door or another L2NpcInstance</li> <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li> <li>The target is in the actor Aggro range and is at the same height</li> <li>The actor is Aggressive</li><BR>
	 * <BR>
	 * @param target The targeted L2Object
	 * @return
	 */
	private boolean autoAttackCondition(final L2Character target)
	{
		if (target == null || !(_actor instanceof L2Attackable))
			return false;
		
		final L2Attackable me = (L2Attackable) _actor;
		
		// Check if the target isn't invulnerable
		if (target.isInvul())
		{
			// However EffectInvincible requires to check GMs specially
			if (target instanceof L2PcInstance && ((L2PcInstance) target).isGM())
				return false;
			
			if (target instanceof L2Summon && ((L2Summon) target).getOwner().isGM())
				return false;
		}
		
		// Check if the target isn't a Folk or a Door
		if (target instanceof L2FolkInstance || target instanceof L2DoorInstance)
			return false;
		
		// Check if the target isn't dead, is in the Aggro range and is at the same height
		if (target.isAlikeDead() || !me.isInsideRadius(target, me.getAggroRange(), false, false) || Math.abs(_actor.getZ() - target.getZ()) > 300)
			return false;
		
		// Check if the target is a L2PcInstance
		if (target instanceof L2PcInstance)
		{
			// Don't take the aggro if the GM has the access level below or equal to GM_DONT_TAKE_AGGRO
			if (((L2PcInstance) target).isGM() && ((L2PcInstance) target).getAccessLevel().canTakeAggro())
				return false;
			
			// Check if the AI isn't a Raid Boss and the target isn't in silent move mode
			if (!(me instanceof L2RaidBossInstance) && ((L2PcInstance) target).isSilentMoving())
				return false;
			
			// if in offline mode
			if (((L2PcInstance) target).isInOfflineMode())
				return false;
			
			// Check if player is an ally //TODO! [Nemesiss] it should be rather boolean or smth like that
			// Comparing String isnt good idea!
			if (me.getFactionId() != null && me.getFactionId().equals("varka") && ((L2PcInstance) target).isAlliedWithVarka())
				return false;
			
			if (me.getFactionId() != null && me.getFactionId().equals("ketra") && ((L2PcInstance) target).isAlliedWithKetra())
				return false;
			
			// check if the target is within the grace period for JUST getting up from fake death
			if (((L2PcInstance) target).isRecentFakeDeath())
				return false;
			
			// check player is in away mod
			if (((L2PcInstance) target).isAway() && !Config.AWAY_PLAYER_TAKE_AGGRO)
				return false;
			
			if (target.isInParty() && target.getParty().isInDimensionalRift())
			{
				final byte riftType = target.getParty().getDimensionalRift().getType();
				final byte riftRoom = target.getParty().getDimensionalRift().getCurrentRoom();
				
				if (me instanceof L2RiftInvaderInstance && !DimensionalRiftManager.getInstance().getRoom(riftType, riftRoom).checkIfInZone(me.getX(), me.getY(), me.getZ()))
					return false;
			}
		}
		
		// Check if the target is a L2Summon
		if (target instanceof L2Summon)
		{
			final L2PcInstance owner = ((L2Summon) target).getOwner();
			if (owner != null)
			{
				// Don't take the aggro if the GM has the access level below or equal to GM_DONT_TAKE_AGGRO
				if (owner.isGM() && (owner.isInvul() || !owner.getAccessLevel().canTakeAggro()))
					return false;
				// Check if player is an ally (comparing mem addr)
				if (me.getFactionId() != null && me.getFactionId() == "varka" && owner.isAlliedWithVarka())
					return false;
				if (me.getFactionId() != null && me.getFactionId() == "ketra" && owner.isAlliedWithKetra())
					return false;
			}
		}
		
		// Check if the actor is a L2GuardInstance
		if (_actor instanceof L2GuardInstance)
		{
			
			// Check if the L2PcInstance target has karma (=PK)
			if (target instanceof L2PcInstance && ((L2PcInstance) target).getKarma() > 0)
				// Los Check
				return GeoData.getInstance().canSeeTarget(me, target);
			
			// if (target instanceof L2Summon)
			// return ((L2Summon)target).getKarma() > 0;
			// Check if the L2MonsterInstance target is aggressive
			if (target instanceof L2MonsterInstance)
				return ((L2MonsterInstance) target).isAggressive() && GeoData.getInstance().canSeeTarget(me, target);
			
			return false;
		}
		else if (_actor instanceof L2FriendlyMobInstance)
		{
			// the actor is a L2FriendlyMobInstance
			
			// Check if the target isn't another L2NpcInstance
			if (target instanceof L2NpcInstance)
				return false;
			
			// Check if the L2PcInstance target has karma (=PK)
			if (target instanceof L2PcInstance && ((L2PcInstance) target).getKarma() > 0)
				// Los Check
				return GeoData.getInstance().canSeeTarget(me, target);
			return false;
		}
		else
		{
			// The actor is a L2MonsterInstance
			
			// Check if the target isn't another L2NpcInstance
			if (target instanceof L2NpcInstance)
				return false;
			
			// depending on config, do not allow mobs to attack _new_ players in peacezones,
			// unless they are already following those players from outside the peacezone.
			if (L2Character.isInsidePeaceZone(me, target))
				return false;
			
			// Check if the actor is Aggressive
			return me.isAggressive() && GeoData.getInstance().canSeeTarget(me, target);
		}
	}
	
	public synchronized void startAITask()
	{
		// If not idle - create an AI task (schedule onEvtThink repeatedly)
		
		if (_aiTask == null)
		{
			_aiTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(this, 1000, 1000);
		}
		
	}
	
	public synchronized void stopAITask()
	{
		
		if (_aiTask != null)
		{
			_aiTask.cancel(false);
			_aiTask = null;
		}
		
	}
	
	@Override
	protected void onEvtDead()
	{
		stopAITask();
		super.onEvtDead();
	}
	
	/**
	 * Set the Intention of this L2CharacterAI and create an AI Task executed every 1s (call onEvtThink method) for this L2Attackable.<BR>
	 * <BR>
	 * <FONT COLOR=#FF0000><B> <U>Caution</U> : If actor _knowPlayer isn't EMPTY, AI_INTENTION_IDLE will be change in AI_INTENTION_ACTIVE</B></FONT><BR>
	 * <BR>
	 * @param intention The new Intention to set to the AI
	 * @param arg0 The first parameter of the Intention
	 * @param arg1 The second parameter of the Intention
	 */
	@Override
	public void changeIntention(CtrlIntention intention, final Object arg0, final Object arg1)
	{
		if (intention == AI_INTENTION_IDLE || intention == AI_INTENTION_ACTIVE)
		{
			// Check if actor is not dead
			if (!_actor.isAlikeDead())
			{
				L2Attackable npc = (L2Attackable) _actor;
				
				// If its _knownPlayer isn't empty set the Intention to AI_INTENTION_ACTIVE
				if (npc.getKnownList().getKnownPlayers().size() > 0)
				{
					intention = AI_INTENTION_ACTIVE;
				}
				
				npc = null;
			}
			
			if (intention == AI_INTENTION_IDLE)
			{
				// Set the Intention of this L2AttackableAI to AI_INTENTION_IDLE
				super.changeIntention(AI_INTENTION_IDLE, null, null);
				
				// Stop AI task and detach AI from NPC
				stopAITask();
				
				// Cancel the AI
				_accessor.detachAI();
				
				return;
			}
		}
		
		// Set the Intention of this L2AttackableAI to intention
		super.changeIntention(intention, arg0, arg1);
		
		// If not idle - create an AI task (schedule onEvtThink repeatedly)
		startAITask();
	}
	
	/**
	 * Manage the Attack Intention : Stop current Attack (if necessary), Calculate attack timeout, Start a new Attack and Launch Think Event.<BR>
	 * <BR>
	 * @param target The L2Character to attack
	 */
	@Override
	protected void onIntentionAttack(final L2Character target)
	{
		// Calculate the attack timeout
		_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
		
		// Manage the Attack Intention : Stop current Attack (if necessary), Start a new Attack and Launch Think Event
		super.onIntentionAttack(target);
	}
	
	/**
	 * Manage AI standard thinks of a L2Attackable (called by onEvtThink).<BR>
	 * <BR>
	 * <B><U> Actions</U> :</B><BR>
	 * <BR>
	 * <li>Update every 1s the _globalAggro counter to come close to 0</li> <li>If the actor is Aggressive and can attack, add all autoAttackable L2Character in its Aggro Range to its _aggroList, chose a target and order to attack it</li> <li>If the actor is a L2GuardInstance that can't attack,
	 * order to it to return to its home location</li> <li>If the actor is a L2MonsterInstance that can't attack, order to it to random walk (1/100)</li><BR>
	 * <BR>
	 */
	private void thinkActive()
	{
		L2Attackable npc = (L2Attackable) _actor;
		
		// Update every 1s the _globalAggro counter to come close to 0
		if (_globalAggro != 0)
		{
			if (_globalAggro < 0)
			{
				_globalAggro++;
			}
			else
			{
				_globalAggro--;
			}
		}
		
		// Add all autoAttackable L2Character in L2Attackable Aggro Range to its _aggroList with 0 damage and 1 hate
		// A L2Attackable isn't aggressive during 10s after its spawn because _globalAggro is set to -10
		if (_globalAggro >= 0)
		{
			// Get all visible objects inside its Aggro Range
			// L2Object[] objects = L2World.getInstance().getVisibleObjects(_actor, ((L2NpcInstance)_actor).getAggroRange());
			// Go through visible objects
			for (final L2Object obj : npc.getKnownList().getKnownObjects().values())
			{
				if (obj == null || !(obj instanceof L2Character))
				{
					continue;
				}
				
				L2Character target = (L2Character) obj;
				
				/*
				 * Check to see if this is a festival mob spawn. If it is, then check to see if the aggro trigger is a festival participant...if so, move to attack it.
				 */
				if (_actor instanceof L2FestivalMonsterInstance && obj instanceof L2PcInstance)
				{
					L2PcInstance targetPlayer = (L2PcInstance) obj;
					if (!targetPlayer.isFestivalParticipant())
					{
						continue;
					}
					
					targetPlayer = null;
				}
				
				if (obj instanceof L2PcInstance || obj instanceof L2Summon)
				{
					if (!target.isAlikeDead() && !npc.isInsideRadius(obj, npc.getAggroRange(), true, false))
					{
						final L2PcInstance targetPlayer = obj instanceof L2PcInstance ? (L2PcInstance) obj : ((L2Summon) obj).getOwner();
						
						for (final Quest quest : npc.getTemplate().getEventQuests(Quest.QuestEventType.ON_AGGRO_RANGE_ENTER))
						{
							quest.notifyAggroRangeEnter(npc, targetPlayer, obj instanceof L2Summon);
						}
					}
				}
				
				// For each L2Character check if the target is autoattackable
				if (autoAttackCondition(target)) // check aggression
				{
					// Get the hate level of the L2Attackable against this L2Character target contained in _aggroList
					final int hating = npc.getHating(target);
					
					// Add the attacker to the L2Attackable _aggroList with 0 damage and 1 hate
					if (hating == 0)
					{
						npc.addDamageHate(target, 0, 1);
					}
				}
				
				target = null;
			}
			
			// Chose a target from its aggroList
			L2Character hated;
			
			// Force mobs to attak anybody if confused
			if (_actor.isConfused())
			{
				hated = getAttackTarget();
			}
			else
			{
				hated = npc.getMostHated();
			}
			
			// Order to the L2Attackable to attack the target
			if (hated != null)
			{
				// Get the hate level of the L2Attackable against this L2Character target contained in _aggroList
				final int aggro = npc.getHating(hated);
				if (aggro + _globalAggro > 0)
				{
					// Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
					if (!_actor.isRunning())
					{
						_actor.setRunning();
					}
					
					// Set the AI Intention to AI_INTENTION_ATTACK
					setIntention(CtrlIntention.AI_INTENTION_ATTACK, hated);
				}
				
				return;
			}
		}
		
		// Check if the actor is a L2GuardInstance
		if (_actor instanceof L2GuardInstance)
		{
			// Order to the L2GuardInstance to return to its home location because there's no target to attack
			((L2GuardInstance) _actor).returnHome();
		}
		
		// If this is a festival monster, then it remains in the same location.
		if (_actor instanceof L2FestivalMonsterInstance)
			return;
		
		// Check if the mob should not return to spawn point
		if (!npc.canReturnToSpawnPoint())
			return;
		
		// Minions following leader
		if (_actor instanceof L2MinionInstance && ((L2MinionInstance) _actor).getLeader() != null)
		{
			int offset;
			
			// for Raids - need correction
			if (_actor.isRaid())
			{
				offset = 500;
			}
			else
			{
				// for normal minions - need correction :)
				offset = 200;
			}
			
			if (((L2MinionInstance) _actor).getLeader().isRunning())
			{
				_actor.setRunning();
			}
			else
			{
				_actor.setWalking();
			}
			
			if (_actor.getPlanDistanceSq(((L2MinionInstance) _actor).getLeader()) > offset * offset)
			{
				int x1, y1, z1;
				
				x1 = ((L2MinionInstance) _actor).getLeader().getX() + Rnd.nextInt((offset - 30) * 2) - (offset - 30);
				y1 = ((L2MinionInstance) _actor).getLeader().getY() + Rnd.nextInt((offset - 30) * 2) - (offset - 30);
				z1 = ((L2MinionInstance) _actor).getLeader().getZ();
				// Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet CharMoveToLocation (broadcast)
				moveTo(x1, y1, z1);
				return;
			}
		}
		// Order to the L2MonsterInstance to random walk (1/100)
		else if (!(npc instanceof L2ChestInstance) && npc.getSpawn() != null && Rnd.nextInt(RANDOM_WALK_RATE) == 0)
		{
			int x1, y1, z1;
			
			// If NPC with random coord in territory
			if (npc.getSpawn().getLocx() == 0 && npc.getSpawn().getLocy() == 0)
			{
				// If NPC with random fixed coord, don't move
				if (TerritoryTable.getInstance().getProcMax(npc.getSpawn().getLocation()) > 0)
					return;
				
				// Calculate a destination point in the spawn area
				final int p[] = TerritoryTable.getInstance().getRandomPoint(npc.getSpawn().getLocation());
				x1 = p[0];
				y1 = p[1];
				z1 = p[2];
				
				// Calculate the distance between the current position of the L2Character and the target (x,y)
				final double distance2 = _actor.getPlanDistanceSq(x1, y1);
				
				if (distance2 > Config.MAX_DRIFT_RANGE * Config.MAX_DRIFT_RANGE)
				{
					npc.setisReturningToSpawnPoint(true);
					final float delay = (float) Math.sqrt(distance2) / Config.MAX_DRIFT_RANGE;
					x1 = _actor.getX() + (int) ((x1 - _actor.getX()) / delay);
					y1 = _actor.getY() + (int) ((y1 - _actor.getY()) / delay);
				}
				else
				{
					npc.setisReturningToSpawnPoint(false);
				}
				
			}
			else
			{
				if (Config.MONSTER_RETURN_DELAY > 0 && npc instanceof L2MonsterInstance && !npc.isAlikeDead() && !npc.isDead() && npc.getSpawn() != null && !npc.isInsideRadius(npc.getSpawn().getLocx(), npc.getSpawn().getLocy(), Config.MAX_DRIFT_RANGE, false))
				{
					((L2MonsterInstance) _actor).returnHome();
				}
				
				// If NPC with fixed coord
				x1 = npc.getSpawn().getLocx() + Rnd.nextInt(Config.MAX_DRIFT_RANGE * 2) - Config.MAX_DRIFT_RANGE;
				y1 = npc.getSpawn().getLocy() + Rnd.nextInt(Config.MAX_DRIFT_RANGE * 2) - Config.MAX_DRIFT_RANGE;
				z1 = npc.getZ();
			}
			
			// LOGGER.config("Curent pos ("+getX()+", "+getY()+"), moving to ("+x1+", "+y1+").");
			// Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet CharMoveToLocation (broadcast)
			moveTo(x1, y1, z1);
		}
		
		npc = null;
		
		return;
		
	}
	
	/**
	 * Manage AI attack thinks of a L2Attackable (called by onEvtThink).<BR>
	 * <BR>
	 * <B><U> Actions</U> :</B><BR>
	 * <BR>
	 * <li>Update the attack timeout if actor is running</li> <li>If target is dead or timeout is expired, stop this attack and set the Intention to AI_INTENTION_ACTIVE</li> <li>Call all L2Object of its Faction inside the Faction Range</li> <li>Chose a target and order to attack it with magic skill
	 * or physical attack</li><BR>
	 * <BR>
	 * TODO: Manage casting rules to healer mobs (like Ant Nurses)
	 */
	private void thinkAttack()
	{
		if ((_actor == null) || _actor.isCastingNow())
		{
			return;
		}
		if (_attackTimeout < GameTimeController.getGameTicks())
		{
			// Check if the actor is running
			if (_actor.isRunning())
			{
				// Set the actor movement type to walk and send Server->Client packet ChangeMoveType to all others L2PcInstance
				_actor.setWalking();
				
				// Calculate a new attack timeout
				_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
			}
		}
		
		final L2Character originalAttackTarget = getAttackTarget();
		// Check if target is dead or if timeout is expired to stop this attack
		if (originalAttackTarget == null || originalAttackTarget.isAlikeDead() || (originalAttackTarget instanceof L2PcInstance && (((L2PcInstance) originalAttackTarget).isInOfflineMode() || ((L2PcInstance) originalAttackTarget).isOnline() == 0)) || _attackTimeout < GameTimeController.getGameTicks())
		{
			// Stop hating this target after the attack timeout or if target is dead
			if (originalAttackTarget != null)
				((L2Attackable) _actor).stopHating(originalAttackTarget);
			
			// Set the AI Intention to AI_INTENTION_ACTIVE
			setIntention(AI_INTENTION_ACTIVE);
			
			_actor.setWalking();
			return;
		}
		
		/*
		 * // Handle all L2Object of its Faction inside the Faction Range if (_actor instanceof L2NpcInstance && ((L2NpcInstance) _actor).getFactionId() != null) { String faction_id = ((L2NpcInstance) _actor).getFactionId(); // Go through all L2Object that belong to its faction Collection<L2Object>
		 * objs = _actor.getKnownList().getKnownObjects().values(); //synchronized (_actor.getKnownList().getKnownObjects()) try { for (L2Object obj : objs) { if (obj instanceof L2NpcInstance) { L2NpcInstance npc = (L2NpcInstance) obj; //Handle SevenSigns mob Factions String npcfaction =
		 * npc.getFactionId(); boolean sevenSignFaction = false; // TODO: Unhardcode this by AI scripts (DrHouse) //Catacomb mobs should assist lilim and nephilim other than dungeon if ("c_dungeon_clan".equals(faction_id) && ("c_dungeon_lilim".equals(npcfaction) ||
		 * "c_dungeon_nephi".equals(npcfaction))) sevenSignFaction = true; //Lilim mobs should assist other Lilim and catacomb mobs else if ("c_dungeon_lilim".equals(faction_id) && "c_dungeon_clan".equals(npcfaction)) sevenSignFaction = true; //Nephilim mobs should assist other Nephilim and catacomb
		 * mobs else if ("c_dungeon_nephi".equals(faction_id) && "c_dungeon_clan".equals(npcfaction)) sevenSignFaction = true; if (!faction_id.equals(npc.getFactionId()) && !sevenSignFaction) continue; // Check if the L2Object is inside the Faction Range of // the actor if
		 * (_actor.isInsideRadius(npc, npc.getFactionRange() + npc.getTemplate().collisionRadius, true, false) && npc.getAI() != null) { if (Math.abs(originalAttackTarget.getZ() - npc.getZ()) < 600 && _actor.getAttackByList().contains(originalAttackTarget) && (npc.getAI()._intention ==
		 * CtrlIntention.AI_INTENTION_IDLE || npc.getAI()._intention == CtrlIntention.AI_INTENTION_ACTIVE) && GeoData.getInstance().canSeeTarget(_actor, npc)) { if ((originalAttackTarget instanceof L2PcInstance) || (originalAttackTarget instanceof L2Summon)) { if
		 * (npc.getTemplate().getEventQuests(Quest.QuestEventType.ON_FACTION_CALL) != null) { L2PcInstance player = (originalAttackTarget instanceof L2PcInstance) ? (L2PcInstance) originalAttackTarget : ((L2Summon) originalAttackTarget).getOwner(); for (Quest quest :
		 * npc.getTemplate().getEventQuests(Quest.QuestEventType.ON_FACTION_CALL)) quest.notifyFactionCall(npc, (L2NpcInstance) _actor, player, (originalAttackTarget instanceof L2Summon)); } } else if (npc instanceof L2Attackable && getAttackTarget() != null && npc.getAI()._intention !=
		 * CtrlIntention.AI_INTENTION_ATTACK) { ((L2Attackable) npc).addDamageHate(getAttackTarget(), 0, ((L2Attackable) _actor).getHating(getAttackTarget())); npc.getAI().setIntention(CtrlIntention.AI_INTENTION_ATTACK, getAttackTarget()); } } } } } } catch (NullPointerException e) { LOGGER.warn(
		 * "L2AttackableAI: thinkAttack() faction call failed: " + e.getMessage(), e); } }
		 */
		
		// Call all L2Object of its Faction inside the Faction Range
		if (((L2NpcInstance) _actor).getFactionId() != null)
		{
			// Go through all L2Object that belong to its faction
			for (final L2Object obj : _actor.getKnownList().getKnownObjects().values())
			{
				if (obj instanceof L2NpcInstance)
				{
					L2NpcInstance npc = (L2NpcInstance) obj;
					String faction_id = ((L2NpcInstance) _actor).getFactionId();
					
					if (!faction_id.equalsIgnoreCase(npc.getFactionId()) || npc.getFactionRange() == 0)
					{
						faction_id = null;
						continue;
					}
					
					// Check if the L2Object is inside the Faction Range of the actor
					if (_actor.getAttackByList() != null && _actor.isInsideRadius(npc, npc.getFactionRange(), true, false) && npc.getAI() != null && _actor.getAttackByList().contains(originalAttackTarget))
					{
						if (npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE || npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_ACTIVE)
						{
							
							if (GeoData.getInstance().canSeeTarget(_actor, npc) && Math.abs(originalAttackTarget.getZ() - npc.getZ()) < 600)
							{
								
								if (originalAttackTarget instanceof L2PcInstance && originalAttackTarget.isInParty() && originalAttackTarget.getParty().isInDimensionalRift())
								{
									final byte riftType = originalAttackTarget.getParty().getDimensionalRift().getType();
									final byte riftRoom = originalAttackTarget.getParty().getDimensionalRift().getCurrentRoom();
									
									if (_actor instanceof L2RiftInvaderInstance && !DimensionalRiftManager.getInstance().getRoom(riftType, riftRoom).checkIfInZone(npc.getX(), npc.getY(), npc.getZ()))
									{
										continue;
									}
								}
								// Notify the L2Object AI with EVT_AGGRESSION
								npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, originalAttackTarget, 1);
								
							}
							
						}
						
						if (GeoData.getInstance().canSeeTarget(_actor, npc) && Math.abs(originalAttackTarget.getZ() - npc.getZ()) < 500)
						{
							
							if (originalAttackTarget instanceof L2PcInstance || originalAttackTarget instanceof L2Summon)
							{
								final L2PcInstance player = originalAttackTarget instanceof L2PcInstance ? (L2PcInstance) originalAttackTarget : ((L2Summon) originalAttackTarget).getOwner();
								for (final Quest quest : npc.getTemplate().getEventQuests(Quest.QuestEventType.ON_FACTION_CALL))
								{
									quest.notifyFactionCall(npc, (L2NpcInstance) _actor, player, (originalAttackTarget instanceof L2Summon));
								}
							}
							
						}
						
					}
					
					npc = null;
				}
			}
		}
		
		if (_actor.isAttackingDisabled())
			return;
		
		// Get all information needed to chose between physical or magical attack
		L2Skill[] skills = null;
		double dist2 = 0;
		int range = 0;
		
		try
		{
			_actor.setTarget(originalAttackTarget);
			skills = _actor.getAllSkills();
			dist2 = _actor.getPlanDistanceSq(originalAttackTarget.getX(), originalAttackTarget.getY());
			range = _actor.getPhysicalAttackRange() + _actor.getTemplate().collisionRadius + originalAttackTarget.getTemplate().collisionRadius;
		}
		catch (final NullPointerException e)
		{
			// LOGGER.warn("AttackableAI: Attack target is NULL.");
			if (Config.ENABLE_ALL_EXCEPTIONS)
				e.printStackTrace();
			
			setIntention(AI_INTENTION_ACTIVE);
			return;
		}
		
		L2Weapon weapon = _actor.getActiveWeaponItem();
		final int collision = _actor.getTemplate().collisionRadius;
		final int combinedCollision = collision + originalAttackTarget.getTemplate().collisionRadius;
		
		// ------------------------------------------------------
		// In case many mobs are trying to hit from same place, move a bit,
		// circling around the target
		// Note from Gnacik:
		// On l2js because of that sometimes mobs don't attack player only running
		// around player without any sense, so decrease chance for now
		if (!_actor.isMovementDisabled() && Rnd.nextInt(100) <= 3)
		{
			for (final L2Object nearby : _actor.getKnownList().getKnownObjects().values())
			{
				if (nearby instanceof L2Attackable && _actor.isInsideRadius(nearby, collision, false, false) && nearby != originalAttackTarget)
				{
					int newX = combinedCollision + Rnd.get(40);
					if (Rnd.nextBoolean())
						newX = originalAttackTarget.getX() + newX;
					else
						newX = originalAttackTarget.getX() - newX;
					int newY = combinedCollision + Rnd.get(40);
					if (Rnd.nextBoolean())
						newY = originalAttackTarget.getY() + newY;
					else
						newY = originalAttackTarget.getY() - newY;
					
					if (!_actor.isInsideRadius(newX, newY, collision, false))
					{
						final int newZ = _actor.getZ() + 30;
						if (Config.GEODATA == 0 || GeoData.getInstance().canMoveFromToTarget(_actor.getX(), _actor.getY(), _actor.getZ(), newX, newY, newZ))
							moveTo(newX, newY, newZ);
					}
					return;
				}
			}
		}
		
		if (weapon != null && weapon.getItemType() == L2WeaponType.BOW)
		{
			// Micht: kepping this one otherwise we should do 2 sqrt
			final double distance2 = _actor.getPlanDistanceSq(originalAttackTarget.getX(), originalAttackTarget.getY());
			if (Math.sqrt(distance2) <= 60 + combinedCollision)
			{
				// double distance2 = _actor.getPlanDistanceSq(originalAttackTarget.getX(), originalAttackTarget.getY());
				// if(distance2 <= 10000)
				// {
				final int chance = 5;
				if (chance >= Rnd.get(100))
				{
					int posX = _actor.getX();
					int posY = _actor.getY();
					final int posZ = _actor.getZ();
					final double distance = Math.sqrt(distance2); // This way, we only do the sqrt if we need it
					
					int signx = -1;
					int signy = -1;
					if (_actor.getX() > originalAttackTarget.getX())
					{
						signx = 1;
					}
					if (_actor.getY() > originalAttackTarget.getY())
					{
						signy = 1;
					}
					
					posX += Math.round((float) (signx * (range / 2 + Rnd.get(range)) - distance));
					posY += Math.round((float) (signy * (range / 2 + Rnd.get(range)) - distance));
					setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new L2CharPosition(posX, posY, posZ, 0));
					return;
				}
			}
		}
		weapon = null;
		
		// Force mobs to attack anybody if confused
		L2Character hated;
		if (_actor.isConfused())
		{
			hated = originalAttackTarget;
		}
		else
		{
			hated = ((L2Attackable) _actor).getMostHated();
		}
		
		if (hated == null)
		{
			setIntention(AI_INTENTION_ACTIVE);
			return;
		}
		
		if (hated != originalAttackTarget)
		{
			setAttackTarget(hated);
		}
		// We should calculate new distance cuz mob can have changed the target
		dist2 = _actor.getPlanDistanceSq(hated.getX(), hated.getY());
		
		if (hated.isMoving())
		{
			range += 50;
		}
		
		// Check if the actor isn't far from target
		if (dist2 > range * range)
		{
			// check for long ranged skills and heal/buff skills
			if (!_actor.isMuted() && (!Config.ALT_GAME_MOB_ATTACK_AI || _actor instanceof L2MonsterInstance && Rnd.nextInt(100) <= 5))
			{
				for (final L2Skill sk : skills)
				{
					final int castRange = sk.getCastRange();
					
					boolean _inRange = false;
					if (dist2 >= castRange * castRange / 9.0 && dist2 <= castRange * castRange && castRange > 70)
					{
						_inRange = true;
					}
					
					if ((sk.getSkillType() == L2Skill.SkillType.BUFF || sk.getSkillType() == L2Skill.SkillType.HEAL || _inRange) && !_actor.isSkillDisabled(sk) && _actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk) && !sk.isPassive() && Rnd.nextInt(100) <= 5)
					{
						
						if (sk.getSkillType() == L2Skill.SkillType.BUFF || sk.getSkillType() == L2Skill.SkillType.HEAL)
						{
							boolean useSkillSelf = true;
							
							if (sk.getSkillType() == L2Skill.SkillType.HEAL && _actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5))
							{
								useSkillSelf = false;
								break;
							}
							
							if (sk.getSkillType() == L2Skill.SkillType.BUFF)
							{
								L2Effect[] effects = _actor.getAllEffects();
								
								for (int i = 0; effects != null && i < effects.length; i++)
								{
									final L2Effect effect = effects[i];
									
									if (effect.getSkill() == sk)
									{
										useSkillSelf = false;
										break;
									}
								}
								
								effects = null;
							}
							if (useSkillSelf)
							{
								_actor.setTarget(_actor);
							}
						}
						
						L2Object OldTarget = _actor.getTarget();
						
						clientStopMoving(null);
						
						_accessor.doCast(sk);
						_actor.setTarget(OldTarget);
						OldTarget = null;
						
						return;
					}
				}
			}
			
			// Move the actor to Pawn server side AND client side by sending Server->Client packet MoveToPawn (broadcast)
			if (hated.isMoving())
			{
				range -= 100;
			}
			if (range < 5)
			{
				range = 5;
			}
			
			moveToPawn(originalAttackTarget, range);
			
			return;
		}
		// Else, if this is close enough to attack
		_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
		
		// check for close combat skills && heal/buff skills
		if (!_actor.isMuted() /* && _rnd.nextInt(100) <= 5 */)
		{
			boolean useSkillSelf = true;
			
			for (final L2Skill sk : skills)
			{
				if (/* sk.getCastRange() >= dist && sk.getCastRange() <= 70 && */!sk.isPassive() && _actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk) && !_actor.isSkillDisabled(sk) && (Rnd.nextInt(100) <= 8 || _actor instanceof L2PenaltyMonsterInstance && Rnd.nextInt(100) <= 20))
				{
					if (sk.getSkillType() == L2Skill.SkillType.BUFF || sk.getSkillType() == L2Skill.SkillType.HEAL)
					{
						useSkillSelf = true;
						
						if (sk.getSkillType() == L2Skill.SkillType.HEAL && _actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5))
						{
							useSkillSelf = false;
							break;
						}
						
						if (sk.getSkillType() == L2Skill.SkillType.BUFF)
						{
							L2Effect[] effects = _actor.getAllEffects();
							
							for (int i = 0; effects != null && i < effects.length; i++)
							{
								final L2Effect effect = effects[i];
								
								if (effect.getSkill() == sk)
								{
									useSkillSelf = false;
									break;
								}
							}
							
							effects = null;
						}
						if (useSkillSelf)
						{
							_actor.setTarget(_actor);
						}
					}
					// GeoData Los Check here
					if (!useSkillSelf && !GeoData.getInstance().canSeeTarget(_actor, _actor.getTarget()))
						return;
					
					L2Object OldTarget = _actor.getTarget();
					
					clientStopMoving(null);
					_accessor.doCast(sk);
					_actor.setTarget(OldTarget);
					OldTarget = null;
					
					return;
				}
			}
		}
		
		// Finally, physical attacks
		clientStopMoving(null);
		_accessor.doAttack(hated);
		skills = null;
		hated = null;
	}
	
	/**
	 * Manage AI thinking actions of a L2Attackable.<BR>
	 * <BR>
	 */
	@Override
	protected void onEvtThink()
	{
		// Check if the actor can't use skills and if a thinking action isn't already in progress
		if (_thinking || _actor.isAllSkillsDisabled())
			return;
		
		// Start thinking action
		_thinking = true;
		
		try
		{
			// Manage AI thinks of a L2Attackable
			if (getIntention() == AI_INTENTION_ACTIVE)
			{
				thinkActive();
			}
			else if (getIntention() == AI_INTENTION_ATTACK)
			{
				thinkAttack();
			}
		}
		finally
		{
			// Stop thinking action
			_thinking = false;
		}
	}
	
	/**
	 * Launch actions corresponding to the Event Attacked.<BR>
	 * <BR>
	 * <B><U> Actions</U> :</B><BR>
	 * <BR>
	 * <li>Init the attack : Calculate the attack timeout, Set the _globalAggro to 0, Add the attacker to the actor _aggroList</li> <li>Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance</li> <li>Set the Intention to AI_INTENTION_ATTACK</li>
	 * <BR>
	 * <BR>
	 * @param attacker The L2Character that attacks the actor
	 */
	@Override
	protected void onEvtAttacked(final L2Character attacker)
	{
		// Calculate the attack timeout
		_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
		
		// Set the _globalAggro to 0 to permit attack even just after spawn
		if (_globalAggro < 0)
		{
			_globalAggro = 0;
		}
		
		// Add the attacker to the _aggroList of the actor
		((L2Attackable) _actor).addDamageHate(attacker, 0, 1);
		
		// Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
		if (!_actor.isRunning())
		{
			_actor.setRunning();
		}
		
		if (!((_actor instanceof L2NpcInstance && !(_actor instanceof L2Attackable)) && !(_actor instanceof L2PlayableInstance)))
		{
			
			// Set the Intention to AI_INTENTION_ATTACK
			if (getIntention() != AI_INTENTION_ATTACK)
			{
				setIntention(CtrlIntention.AI_INTENTION_ATTACK, attacker);
			}
			else if (((L2Attackable) _actor).getMostHated() != getAttackTarget())
			{
				setIntention(CtrlIntention.AI_INTENTION_ATTACK, attacker);
			}
		}
		
		super.onEvtAttacked(attacker);
	}
	
	/**
	 * Launch actions corresponding to the Event Aggression.<BR>
	 * <BR>
	 * <B><U> Actions</U> :</B><BR>
	 * <BR>
	 * <li>Add the target to the actor _aggroList or update hate if already present</li> <li>Set the actor Intention to AI_INTENTION_ATTACK (if actor is L2GuardInstance check if it isn't too far from its home location)</li><BR>
	 * <BR>
	 * @param target the L2Character that attacks
	 * @param aggro The value of hate to add to the actor against the target
	 */
	@Override
	protected void onEvtAggression(final L2Character target, final int aggro)
	{
		L2Attackable me = (L2Attackable) _actor;
		
		// To avoid lag issue
		if (me.isDead())
			return;
		
		if (target != null)
		{
			// Add the target to the actor _aggroList or update hate if already
			// present
			me.addDamageHate(target, 0, aggro);
			
			// Set the actor AI Intention to AI_INTENTION_ATTACK
			if (getIntention() != CtrlIntention.AI_INTENTION_ATTACK)
			{
				// Set the L2Character movement type to run and send
				// Server->Client packet ChangeMoveType to all others
				// L2PcInstance
				if (!_actor.isRunning())
				{
					_actor.setRunning();
				}
				
				setIntention(CtrlIntention.AI_INTENTION_ATTACK, target);
			}
		}
		me = null;
	}
	
	@Override
	protected void onIntentionActive()
	{
		// Cancel attack timeout
		_attackTimeout = Integer.MAX_VALUE;
		super.onIntentionActive();
	}
	
	public void setGlobalAggro(final int value)
	{
		_globalAggro = value;
	}
}

 

@xRelic

 

Here's a vid of the issue:

 

As you can see the monster i initially attacked probably died, then my hp starts dropping (because the real attack probably ended), but the animation continues.

Link to comment
Share on other sites

  • 0
	@Override
	protected void onEvtAggression(final L2Character target, final int aggro)
	{
		L2Attackable me = (L2Attackable) _actor;
		
		// To avoid lag issue
		if (me.isDead())
			return;

 

Seems you already got what I had in mind.

 

If you type in chat during that lag, or move, is it properly working ? If not, it's probably packet queue flood.

 

Which is strange is even death effect is delayed, but death itself isn't (and you seem dead, since HPs never go back by either HP regen or vampire). I never saw that.

 

Also, if you got a second client (run in on a second computer or use a nosleep mode), what actually see the second client ? Can it move/speak/attack/whatever else properly ?

Edited by Tryskell
Link to comment
Share on other sites

  • 0
44 minutes ago, Tryskell said:
	@Override
	protected void onEvtAggression(final L2Character target, final int aggro)
	{
		L2Attackable me = (L2Attackable) _actor;
		
		// To avoid lag issue
		if (me.isDead())
			return;

 

Seems you already got what I had in mind.

 

If you type in chat during that lag, or move, is it properly working ? If not, it's probably packet queue flood.

 

Which is strange is even death effect is delayed, but death itself isn't (and you seem dead, since HPs never go back by either HP regen or vampire). I never saw that.

 

Also, if you got a second client (run in on a second computer or use a nosleep mode), what actually see the second client ? Can it move/speak/attack/whatever else properly ?

 

When i type that code on my L2Attackable.java i get errors from 

 

onEvtAggression

image.png.d7b8be5946b6e3812c0229be8f39ccbc.png

and _actor

image.thumb.png.b0555a3518532865e0d337c3ae53e67c.png

 

which i'm assuming are not defined on some other file? How would i fix that? (That falls under programming i don't know much of, sorry).

 

I'll test chat and what i see on a 2nd client as you said to see what happens and i'll come back here and edit with the results from that. I know that when this is happening i can't click on other monsters properly, it lags for a while until it changes, so it could be a packet flood.

 

Edit:

It lags to switch target, lags the chat, can't do much while it's on the state with fast response.

As for the 2nd client, on the 2nd client i can see the 1st client character stops attacking when the targetted monster actually dies, but the animation keeps going on the 1st client for a while longer. It takes longer when there are more monsters, so yeah, packet queue flood seems like a good assumption.

 

2nd edit:

If i click to walk during that lag moment, i can see the character moving on the 2nd client instantly, but it stays on the fighting state on the 1st client lagged.

Edited by dextroy
Link to comment
Share on other sites

  • 0

Do the second client lags as the first one ?

 

You can use L2PHX to see running packets.

 

The code I posted is simply what you got in your own file. I simply shown that isDead() check was set here, there is even the reason in comment.

Link to comment
Share on other sites

  • 0
1 hour ago, Tryskell said:

Do the second client lags as the first one ?

 

You can use L2PHX to see running packets.

 

The code I posted is simply what you got in your own file. I simply shown that isDead() check was set here, there is even the reason in comment.

 

Oh, i misread and opened the wrong file the 2nd time i was checking here, lol, i opened my L2Attackable from model and not AI when checking it for the 2nd time, sorry haha. Yeah, i got the isDead() line on the L2AttackableAI.java already. Same as i posted, so i'm assuming it's not that.

 

And nope, no lag on the 2nd client, everything seems smooth. I click to walk on the 1st client, it doesn't move there, but it moves instantly on the spectator character on the 2nd client.

 

I've never used L2PHX so i'll have to get some info on how to see the packets there, i don't even have a clue how to start there, i'm still a rookie with programming stuff, i'm more of an enthusiast so please have some patience with me.

 

On a second note, I was thinking if there would be some way to possibly reduce packets that are not really necessary when using Polearm. I'm assuming using Polearm with fast atk speed generates multiple system logs of the attack, which floods the client with multiple packets and results in heavy lag.

 

You think that if i find those message packets and remove them for polearm users could work? Or maybe put a ms interval (Idk is that's possible)? I don't know how the code would be, but a logic like this:

if (player.isUsingPolearm())
	return;
else
	player.sendPacket(new SystemMessage(SystemMessageId.HIT));

 

On the L2Attackable.java (model) there's this line for OverHit, so it gave me that idea i just mentioned:

if (attacker instanceof L2PcInstance)
	{
	final L2PcInstance player = (L2PcInstance) attacker;
	if (isOverhit() && attacker == getOverhitAttacker())
	{
	player.sendPacket(new SystemMessage(SystemMessageId.OVER_HIT));
	exp += calculateOverhitExp(exp);
	}

 

Would you know a way to limit these packets under these circumstances?

Link to comment
Share on other sites

  • 0

There is no reason to get such lags, even on IL, with so low amount of characters involved. It's definitively a server issue, not a client limitation.

 

Since 2nd character isn't affected, I would say the first character packet queue is simply filled, packets are lost since queue is full, and when character dies there isn't anymore packets to send so queue can be finally cleaned and everything popups magically.

 

A polearm attack normally uses only one Attack packet, filled with an array of Hits. If L2JFrozen is badly coded, than subsystems (startAttackStance, etc) can eventually be called on every character hit, launching the method multiple times (up to 20 targets on IL ?), but that has to be debugged. You could have more clue about what exactly happens with one pole hit launching L2PHX and seeing packets.

 

There is some easy network configs to edit in order to improve packet send (you can see some tests on my own video here, answer in 1:13). In the video, the drop of ClientStats didn't help at all, the MAX_SEND_PER_PASS did everything.

 

The default value was edited in 2017 on my pack.

 

 

Edited by Tryskell
  • Thanks 1
  • Upvote 1
Link to comment
Share on other sites

  • 0

client handles all packets in the order they got in, spam wont change that and queue doesn't have full state, you will just crash client at some point but no packets will be dropped before that. So if on video hp was updated to 0 but no death and sysmsg/attacks continued, its server side issues

Link to comment
Share on other sites

  • 0
4 hours ago, Tryskell said:

There is no reason to get such lags, even on IL, with so low amount of characters involved. It's definitively a server issue, not a client limitation.

 

Since 2nd character isn't affected, I would say the first character packet queue is simply filled, packets are lost since queue is full, and when character dies there isn't anymore packets to send so queue can be finally cleaned and everything popups magically.

 

A polearm attack normally uses only one Attack packet, filled with an array of Hits. If L2JFrozen is badly coded, than subsystems (startAttackStance, etc) can eventually be called on every character hit, launching the method multiple times (up to 20 targets on IL ?), but that has to be debugged. You could have more clue about what exactly happens with one pole hit launching L2PHX and seeing packets.

 

There is some easy network configs to edit in order to improve packet send (you can see some tests on my own video here, answer in 1:13). In the video, the drop of ClientStats didn't help at all, the MAX_SEND_PER_PASS did everything.

 

The default value was edited in 2017 on my pack.

 

 

Hmm, i'm not sure i understood everything, but i did a search on the datapack and found this line on NetcoreConfig.java

image.png.fca6db1a1bb3af601b2da0752c1dcac7.png

 

From what i saw on your vid you changed from 12 to 80, right? So, do i change those send/read lines to read more packets? Or would it be a more complicated solution? I will test changing those 2 to 80 as you suggested and get back here with the results soon.

 

As for L2PHX i don't know about it, but i can try and see what log it generates then post it here. I'll do those 2 tests as soon as i get free and get back to you.

 

@AlmostGood

Yeah, from the discussions with Tryskell i could figure it's something server sided, i'm just trying to find where exactly can i fix it so that it can read the packets properly.

 

@BruT

Not any particular big mod changes like adding an enterily new system that could compromise. There's some TVT and some other event stuff that came with l2jfrozen rev, but i got a clean datapack to work with (i got it over a year ago, worked a while, picked up again now), so i don't use those custom stuff, if there are such mods as you mentioned, probably came in the rev i got but it's not something that i use.

 

But as for some changes, yeah, i did a few changes in the datapack to modify/add features that i wanted in the server, but none that would compromise the packets queue as far as i'm concerned, since most of the changes were simple if/elses in between the codes. As i mentioned before, i'm not really a programmer, i'm more of a enthusiast that likes to test things out.

 

But since i did make alot of changes to make the server as i envisioned it, i don't want to get a new datapack or change it, i'm trying at least a work around that lag issue which is the only thing that actually compromises the gameplay for Fighter classes at the moment.

 

Edit:

@Tryskell changing the MAX_SEND_PER_PASS did the trick!!

The animations are working smoothly with 80/80, i tested it up to 100 and it also went well!

 

I left it currently as:

public int MMO_MAX_SEND_PER_PASS = 100; // default 12
public int MMO_MAX_READ_PER_PASS = 100; // default 12

 

Just so i know, are there any risks related to leaving those 2 higher? Should i set the Read back to 12? I just tried changing both to see if it would work, and apparently it did. I just don't know if it represents any risks to the server leaving them so high.

 

Edited by dextroy
Link to comment
Share on other sites

  • 0

If it did the trick, then it was indeed, your client packet process queue which was saturated.

 

I personally left it to 80 after seeing some strange artifacts with higher values. They are set on 20/80/80/20.

 

Btw, you only repelled the problem. The problem will probably reappear if, let's say you got multiple ppl doing what one people was actually doing. Imho, you should still check exactly if something can be fixed on polearm uses (packets sent wise).

Edited by Tryskell
Link to comment
Share on other sites

  • 0

im using frozen older rev. and there no problem like this. Tried with more than 2k mobs in one place everything work fine.

 

Try write to chat something when monster hp bar freezes and if message late that mean ur pc instance busy with something else, i think, maybe one of ur mod. or something like that 😄

and its happens with all mobs? tried other not dinosaurs?

Link to comment
Share on other sites

  • 0
54 minutes ago, Tryskell said:

If it did the trick, then it was indeed, your client packet process queue which was saturated.

 

I personally left it to 80 after seeing some strange artifacts with higher values. They are set on 20/80/80/20.

 

Btw, you only repelled the problem. The problem will probably reappear if, let's say you got multiple ppl doing what one people was actually doing. Imho, you should still check exactly if something can be fixed on polearm uses (packets sent wise).

I know, but i made my server as a hobby of mine, so i don't really have goals to make  it professional or huge, that's why i didn't want to check aCIS or L2-Off 'cause they're often bit more complex. I had l2j C3-C5 servers long time ago. I got back to it last year and i just wanted to make a decent stable with the unique features i wanted on other servers and never could find.

 

And in order to check all packets exclusive to Polearm users would probably require alot of time studying the datapack and java knowledge that i don't really have atm. So for now, i'd say that works fine for me.

 

Thanks alot! I really wouldn't have figured this one out alone.

 

@wongerlt

I have no problem seeing or walking in places with lots of monsters, i can even just let them attack me and could walk and heal with no lag, the issue only happened when attacking with a polearm multiple of them. And nah, it was not exclusive to the dinos, any place with lots of monster to aggro (Antharas Lair, Cruma Tower, etc) and attacking them with a polearm would result in those multiple packets that lagged the client. But the suggestion Tryskell gave me did the trick!

Link to comment
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now



×
×
  • Create New...