Jump to content

Question

Posted (edited)

Hi there. How to inject this movement contoller into aCis latest source?
 

package ru.catssoftware.gameserver.model.actor;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import ru.catssoftware.gameserver.ThreadPoolManager;
import ru.catssoftware.gameserver.ai.CtrlEvent;
import ru.catssoftware.gameserver.ai.CtrlIntention;
import ru.catssoftware.gameserver.ai.ParallelManager;
import ru.catssoftware.gameserver.geodata.GeoData;
import ru.catssoftware.gameserver.geodata.MathUtil;
import ru.catssoftware.gameserver.geodata.pathfinding.AbstractNodeLoc;
import ru.catssoftware.gameserver.geodata.pathfinding.PathFinding;
import ru.catssoftware.gameserver.idfactory.IdFactory;
import ru.catssoftware.gameserver.model.L2Character;
import ru.catssoftware.gameserver.model.L2ItemInstance;
import ru.catssoftware.gameserver.model.Location;
import ru.catssoftware.gameserver.model.MoveModel;
import ru.catssoftware.gameserver.network.serverpackets.MoveToLocation;
import ru.catssoftware.gameserver.util.Util;

import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class MovementController
{
	private final static int MAX_DISTANCE = 5000;
	private final static int PAWN_OFFSET = 55;

	private final ReentrantLock lock = new ReentrantLock(true);
	private final L2Character actor;
	private ScheduledFuture<?> watchTask;
	private ScheduledFuture<?> pawnWatcher;

	private final AtomicReference<MoveModel> moveModel = new AtomicReference<>();
	private List<AbstractNodeLoc> path;
	private Location originalEndPoint;

	@Getter private L2Character pawnTarget;
	private int pawnOffset;

	private long initTime;

	public MovementController(L2Character actor)
	{
		this.actor = actor;
	}

	public boolean isMoving()
	{
		return moveModel.get() != null;
	}

	public boolean isPawnMoving()
	{
		return pawnTarget != null || (pawnWatcher != null && !pawnWatcher.isDone());
	}

	public Location getDestiny()
	{
		MoveModel model = moveModel.get();
		if(model == null)
			return actor.getLoc();
		return model.getEndMovePoint();
	}

	public void stopMove()
	{
		stopWatcher();
		stopPawnWatcher();
		moveModel.set(null);
		originalEndPoint = null;
		pawnTarget = null;
		path = null;
	}

	public void movePawn(L2Character pawn, int offset)
	{
		if (true)
		{
			move(pawn.getX(), pawn.getY(), pawn.getZ(), offset, true, -1);
			return;
		}
		Location current = actor.getLoc();
		Location dest = pawn.getLoc();

		if (offset == 0)
			pawnOffset = PAWN_OFFSET;
		else
			pawnOffset = offset;

		double distance = current.getDistance(dest);

		//debug("movePawn: " + distance + " <= " + (pawnOffset) + " = " + (distance <= pawnOffset));

		if(distance <= pawnOffset)
		{
			startPawnWatcher(pawn);
			ParallelManager.getInstance().notify(actor.getAI(), CtrlEvent.EVT_ARRIVED);
			return;
		}

		if(distance > MAX_DISTANCE)
		{
			stopMove();
			ParallelManager.getInstance().intention(actor.getAI(), CtrlIntention.AI_INTENTION_ACTIVE);
			return;
		}

		initTime = System.currentTimeMillis();

		movePawn0(pawn, current, dest);
	}

	private void movePawn0(L2Character pawn, Location current, Location dest) {
		lock.lock();
		try
		{
			stopMove();

			dest = normalizeByOffset(dest, pawnOffset);
			dest = checkWay(current, dest);

			actor.getListeners().onMove(actor, dest);

			MoveModel model = new MoveModel();
			model.setWithoutGeodata(false);
			model.setStartMovePoint(current);
			model.setEndMovePoint(dest);
			model.setDistance(current.getDistance(dest));
			moveModel.set(model);

			validateHeading(current, dest);
			actor.broadcastPacket(new MoveToLocation(actor, dest));

			pawnTarget = pawn;
			startWatcher(model);
			startPawnWatcher(pawn);
		}
		finally
		{
			lock.unlock();
		}
	}

	private void stopWatcher() {
		if(watchTask != null && !watchTask.isDone())
			watchTask.cancel(false);
	}

	private void startWatcher(MoveModel model)
	{
		if(model != null)
			model.setPrevUpdateTime(System.currentTimeMillis());

		watchTask = ThreadPoolManager.getInstance().scheduleMove(new RunnableWatch(), getTime(actor));
	}

	private void watch()
	{
		if (actor.isMovementDisabled())
		{
			stopMove();
			return;
		}

		if (initTime + 120000 < System.currentTimeMillis())
		{
			stopMove();
			return;
		}

		MoveModel moveModel = this.moveModel.get();

		if (moveModel == null)
		{
			stopWatcher();
			stopMove();
			return;
		}

		long timeNow = System.currentTimeMillis();

		double moveSpeed = actor.getStat().getMoveSpeed();
		double complete = (moveSpeed / 1000d) * (timeNow - moveModel.getPrevUpdateTime());
		double divider = (complete + moveModel.getCompleteDistance()) / moveModel.getDistance();
		moveModel.setPrevUpdateTime(timeNow);
		moveModel.setCompleteDistance(complete + moveModel.getCompleteDistance());

		if (divider >= 1)
		{
			actor.setXYZ(moveModel.getEndMovePoint());
			completeMove(moveModel);
			return;
		}

		int dx = moveModel.getEndMovePoint().getX() - moveModel.getStartMovePoint().getX();
		int dy = moveModel.getEndMovePoint().getY() - moveModel.getStartMovePoint().getY();
		int dz = moveModel.getEndMovePoint().getZ() - moveModel.getStartMovePoint().getZ();

		Location start = moveModel.getStartMovePoint();
		int x = start.getX() + (int) Math.round(divider * dx);
		int y = start.getY() + (int) Math.round(divider * dy);
		int z;
		if (GeoData.getInstance().isEnabled() && moveModel.isWithoutGeodata())
			z = start.getZ() + (int) Math.round(divider * dz);
		else
			z = GeoData.getInstance().getMoveHeight(x, y, actor.getZ());

		if (!this.moveModel.compareAndSet(moveModel, moveModel))
			return;

		dropItem(x, y, z, 57);

		actor.setXYZ(x, y, z);

		startWatcher(moveModel);
		actor.revalidateZoneMT(false);
	}


	private void startPawnWatcher(final L2Character pawn)
	{
		stopPawnWatcher();
		pawnWatcher = ThreadPoolManager.getInstance().scheduleMove(new RunnableWatchPawn(pawn), getTime(pawn) << 1);
	}

	private void stopPawnWatcher()
	{
		if(pawnWatcher != null && !pawnWatcher.isDone())
			pawnWatcher.cancel(false);
	}

	private void watchPawn(L2Character pawn)
	{
		if (actor.isMovementDisabled())
		{
			stopMove();
			return;
		}

		if(isMoving())
		{
			startPawnWatcher(pawn);
			return;
		}

		//if (initTime + 120000 < System.currentTimeMillis())
			//log.info("{}: watchPawn({}) very long ~ {} ms. {} -> {}", actor, pawn, System.currentTimeMillis() - initTime, actor.getLoc(), getDestiny());

		Location current = actor.getLoc();
		Location dest = pawn.getLoc();

		double distance = current.getDistance(dest);
		//debug("watchPawn: " + distance + " <= " + (pawnOffset) + " = " + (distance <= pawnOffset));
		if(distance <= pawnOffset)
		{
			startPawnWatcher(pawn);
			return;
		}

		if(distance > MAX_DISTANCE)
		{
			ParallelManager.getInstance().intention(actor.getAI(), CtrlIntention.AI_INTENTION_ACTIVE);
			return;
		}

		movePawn0(pawn, current, dest);
	}

	public void moveWoGeodata(int x, int y, int z, int offset, boolean broadcast) {
		lock.lock();
		try
		{
			stopMove();

			Location dest;

			if(offset != 0)
				dest = normalizeByOffset(new Location(x, y, z), offset);
			else
				dest = new Location(x, y, z);

			Location current = actor.getLoc();

			actor.getListeners().onMove(actor, dest);

			MoveModel model = new MoveModel();
			model.setWithoutGeodata(false);
			model.setStartMovePoint(current);
			model.setEndMovePoint(dest);
			model.setDistance(current.getDistance(dest));
			model.setWithoutGeodata(true);
			moveModel.set(model);
			if(broadcast)
				actor.broadcastPacket(new MoveToLocation(actor, dest));

			initTime = System.currentTimeMillis();

			startWatcher(model);
		} finally {
			lock.unlock();
		}
	}

	public void moveWoPathfind(int x, int y, int z, int offset, boolean broadcast, int doorid) {
		lock.lock();
		try {
			stopMove();

			Location dest;
			if(offset != 0)
				dest = normalizeByOffset(new Location(x, y, z), offset);
			else
				dest = new Location(x, y, z);

			Location current = actor.getLoc();
			double distance2 = current.getDistance2D(dest);
			if(actor.hasDebuger())
				actor.say("Distance: {}", distance2);

			Location geoDest = cutByGeodata(dest, doorid);

			double destgeoDistance = dest.getDistance2D(geoDest);
			if(destgeoDistance > MathUtil.LossyPointsOnConvert)
				dest = geoDest;

			actor.getListeners().onMove(actor, dest);

			MoveModel model = new MoveModel();
			model.setWithoutGeodata(false);
			model.setStartMovePoint(current);
			model.setEndMovePoint(dest);
			model.setDistance(current.getDistance(dest));
			moveModel.set(model);
			if(broadcast) {
				validateHeading(current, dest);
				actor.broadcastPacket(new MoveToLocation(actor, dest));
			}


			initTime = System.currentTimeMillis();
			startWatcher(model);
		} finally {
			lock.unlock();
		}
	}

	public void move(int x, int y, int z, int offset, boolean broadcast, int doorid)
	{
		move(x, y, z, offset, broadcast, doorid, true);
	}

	public void move(int x, int y, int z, int offset, boolean broadcast, int doorid, boolean first)
	{
		lock.lock();
		try
		{
			stopMove();

			Location dest;
			if(offset != 0)
				dest = normalizeByOffset(new Location(x, y, z), offset);
			else
				dest = new Location(x, y, z);

			Location current = actor.getLoc();
			double distance2 = current.getDistance2D(dest);

			if(distance2 > MAX_DISTANCE)
			{
				originalEndPoint = dest;
				dest = normalizeByDistance(dest, MAX_DISTANCE);
			}

			dest = checkWay(current, dest);

			actor.getListeners().onMove(actor, dest);

			MoveModel model = new MoveModel();
			model.setWithoutGeodata(false);
			model.setStartMovePoint(current);
			model.setEndMovePoint(dest);
			model.setDistance(current.getDistance(dest));
			moveModel.set(model);

			validateHeading(current, dest);
			if(broadcast)
				actor.broadcastPacket(new MoveToLocation(actor, dest));

			if (first)
				initTime = System.currentTimeMillis();

			startWatcher(model);
		}
		finally
		{
			lock.unlock();
		}
	}

	private Location normalizeByDistance(Location dest, double maxDistance)
	{
		Location current = actor.getLoc();
		double distance = current.getDistance2D(dest);
		if(distance <= maxDistance)
			return dest;

		double divider = maxDistance / distance;

		int x = current.getX() + (int)Math.round(divider * (dest.getX() - current.getX()));
		int y = current.getY() + (int)Math.round(divider * (dest.getY() - current.getY()));
		if(GeoData.getInstance().isEnabled() && !GeoData.getInstance().hasGeo(x, y))
			return null;

		int z;
		if(GeoData.getInstance().isEnabled())
			z = GeoData.getInstance().getMoveHeight(x, y, dest.getZ());
		else
			z = dest.getZ();
		return new Location(x, y, z);
	}

	private Location normalizeByOffset(Location dest, int offset)
	{
		Location current = actor.getLoc();

		double distance = current.getDistance(dest);

		double cos = (dest.getX() - current.getX()) / distance;
		double sin = (dest.getY() - current.getY()) / distance;

		int x = current.getX() + (int)((distance - offset + 10) * cos);
		int y = current.getY() + (int)((distance - offset + 10) * sin);

		if(GeoData.getInstance().isEnabled() && !GeoData.getInstance().hasGeo(x, y))
			return null;

		int z;
		if(GeoData.getInstance().isEnabled())
			z = GeoData.getInstance().getMoveHeight(x, y, dest.getZ());
		else
			z = dest.getZ();
		return new Location(x, y, z);
	}

	private Location cutByGeodata(Location dest, int doorid)
	{
		if(!GeoData.getInstance().isEnabled())
			return dest;

		if (actor.getLoc() == null)
			log.error("cutByGeodata({}): {} have loc is null.", dest, actor);
		if (dest == null)
			log.error("cutByGeodata(NULL) have dest is null for actor: {}", actor);


		return GeoData.getInstance().moveCheck(actor.getLoc(), dest, actor.getInstanceId(), doorid);
	}

	/**
	 * Метод проверяет маршрут персонажа, при необходимости - обрезает или создает путь.
	 * @param current
	 * @param dest
	 * @return
	 */
	private Location checkWay(Location current, Location dest)
	{
		Location geoLockBlocked = cutByGeodata(dest, -1);

		dropItem(geoLockBlocked.getX(), geoLockBlocked.getY(), geoLockBlocked.getZ(), 65);

		//debug(String.format("checkWay: [%s] | %s -> %s", current, dest, geoLockBlocked));

		// если расстояние от блокирущего блока до целевой точки больше размера гео-блока необходимо искать путь.
		if(dest.getDistance2D(geoLockBlocked) != 0)
		{
			path = PathFinding.getInstance().findPath(current.getX(), current.getY(), current.getZ(), dest.getX(), dest.getY(), dest.getZ(), actor.getInstanceId(), actor.isPlayer());
			if(path != null && !path.isEmpty())
			{
				dest = Location.fromNodeLoc(path.remove(0));
				//debug(String.format("checkWay(%d,%d,%d) path size: %d | next: (%s) path: %s", dest.getX(), dest.getY(), dest.getZ(), path.size(), dest, path));
				dropItem(dest.getX(), dest.getY(), dest.getZ(), 1062);
			}
			else
			{
				dest = normalizeByOffset(geoLockBlocked, MathUtil.LossyPointsOnConvert * 2);
				//debug(String.format("move(%d,%d,%d) path is empty", dest.getX(), dest.getY(), dest.getZ()));
			}
		}

		if (actor.hasDebuger())
		{
			double a = current.getDistance2D(dest);
			double c = current.getDistance(dest);

			//debug(String.format("Path[%s]|[%s]", current, dest));
			//debug(String.format("-> a/c: sin: %.2f, cos: %.2f, asin: %.2f, acos: %.2f",  Math.toDegrees(Math.sin(a/c)), Math.toDegrees(Math.cos(a/c)), Math.toDegrees(Math.asin(a/c)), Math.toDegrees(Math.acos(a/c))));
			//debug(String.format("-> c/a: sin: %.2f, cos: %.2f, asin: %.2f, acos: %.2f",  Math.toDegrees(Math.sin(c/a)), Math.toDegrees(Math.cos(c/a)), Math.toDegrees(Math.asin(c/a)), Math.toDegrees(Math.acos(c/a))));
		}

		return dest;
	}

	private boolean validateHeading(Location current, Location dest) {
		int heading = Util.calculateNormalHeading(current.getX(), current.getY(), dest.getX(), dest.getY());
		if(MathUtil.isInRange(actor.getHeading() - 10, actor.getHeading() + 10, heading))
			return false;
		actor.setHeading(heading);
		return true;
	}

	private void completeMove(MoveModel oldModel)
	{
		if(!moveModel.compareAndSet(oldModel, null))
			return;

		if(path == null || path.isEmpty())
		{
			Location originalEndPoint = this.originalEndPoint;
			L2Character pawnTarget = this.pawnTarget;

			stopMove();

			if(originalEndPoint != null) //путь был обрезан из-за длины. продолжаем движение
			{
				move(originalEndPoint.getX(), originalEndPoint.getY(), originalEndPoint.getZ(), 0, true, -1, false);
				return;
			}

			if(pawnTarget != null && pawnTarget.getDistance(actor.getLoc()) > pawnOffset)
			{
				ParallelManager.getInstance().notify(actor.getAI(), CtrlEvent.EVT_ARRIVED_BLOCKED, actor.getLoc());
				return;
			}

			ParallelManager.getInstance().notify(actor.getAI(), CtrlEvent.EVT_ARRIVED);
			return;
		}

		lock.lock();
		try
		{
			Location sp = actor.getLoc();
			Location ep = Location.fromNodeLoc(path.remove(0));
			double dist = sp.getDistance(ep);

			if (actor.hasDebuger())
			{
				double a = sp.getDistance2D(ep);
				double c = sp.getDistance(ep);

				//debug(String.format("Path[%s]|[%s]", sp, ep));
				//debug(String.format("-> a/c: sin: %.2f, cos: %.2f, asin: %.2f, acos: %.2f",  Math.toDegrees(Math.sin(a/c)), Math.toDegrees(Math.cos(a/c)), Math.toDegrees(Math.asin(a/c)), Math.toDegrees(Math.acos(a/c))));
				//debug(String.format("-> c/a: sin: %.2f, cos: %.2f, asin: %.2f, acos: %.2f",  Math.toDegrees(Math.sin(c/a)), Math.toDegrees(Math.cos(c/a)), Math.toDegrees(Math.asin(c/a)), Math.toDegrees(Math.acos(c/a))));
			}
			/*if()
			{
				ParallelManager.getInstance().notify(actor.getAI(), CtrlEvent.EVT_ARRIVED_BLOCKED, actor.getLoc());
				return;
			}*/

			MoveModel model = new MoveModel();
			model.setStartMovePoint(sp);
			model.setEndMovePoint(ep);
			model.setDistance(dist);
			model.setWithoutGeodata(false);
			if(!moveModel.compareAndSet(null, model))
				return;

			validateHeading(sp, ep);
			actor.broadcastPacket(new MoveToLocation(actor, ep));
			startWatcher(model);
		}
		finally
		{
			lock.unlock();
		}
	}


	private void dropItem(int x, int y, int z, int itemId)
	{
		dropItem(x, y, z, itemId, 1);
	}

	private void dropItem(int x, int y, int z, int itemId, int count)
	{
		if(actor.hasDebuger())
		{
			final L2ItemInstance item = new L2ItemInstance(IdFactory.getInstance().getNextId(), itemId);
			item.setCount(count);
			item.dropMe(null, x, y, z);
			ThreadPoolManager.getInstance().schedule(new Runnable()
			{
				@Override
				public void run()
				{
					item.decayMe();
				}
			}, 300000);
		}
	}

	private long getTime(L2Character actor)
	{
		return Math.round((MathUtil.LossyPointsOnConvert * 1000d) / actor.getStat().getMoveSpeed());
	}

	private void debug(String text)
	{
		if (actor.hasDebuger() && actor.isPlayer())
			actor.debug(text);
	}

	private class RunnableWatch implements Runnable
	{
		@Override
		public void run()
		{
			watch();
		}
	}

	private class RunnableWatchPawn implements Runnable
	{
		private final L2Character pawn;

		private RunnableWatchPawn(L2Character pawn)
		{
			this.pawn = pawn;
		}

		@Override
		public void run()
		{
			watchPawn(pawn);
		}
	}
}

Edited by Lancer

0 answers to this question

Recommended Posts

There have been no answers to this question yet

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now


  • Posts

    • CLEAR BRIEF – ZERO SURPRISES The client requested an Illinois Driver License with custom data. Task – scannable front/back and photos suitable for upload. He provided data and portrait. Timeline agreed – 30+ minutes. Then everything followed engineering logic: – built the document for the specific state – checked structure and fields – prepared both sides – provided additional dark-background photos upon request No changing requirements. No “let’s redo it differently”. When the brief is clear – the case is controllable. When the process is disciplined – the result is predictable. Want the same smooth process without chaos and endless revisions? Message us – we’ll structure the task before it even starts. › TG: https://t.me/mustang_service ( https:// t.me/ mustang_service ) › Channel: https://t.me/+JPpJCETg-xM1NjNl ( https:// t.me/ +JPpJCETg-xM1NjNl ) #MustangService #verification #documents #case #antifraud #rendering
    • i know some things from java and eclipse about coding but for sure i take one programmer to make the hard work. thank you very much for your replying 🙂
    • Forum Post:   🛡️ L2Genesis Closed Beta — Test With Us, Launch With Exclusive Rewards Join the community: https://discord.gg/mcuHsQzNCm Website: https://l2genesis.com/ Join Beta: https://cbt.l2genesis.com/     Hey everyone, We're L2Genesis — an Interlude Classic server being built with one core belief: every player matters. Not just the top clans, not just the donators — everyone. We've spent months developing, refining, and listening to community feedback. Now we're opening up our Closed Beta and we need your help to make sure this server launches rock-solid. Registration is open for one week only — after that, the doors close. Every tester who puts in the work walks into launch day with exclusive rewards that won't be available again.     Why Genesis? We're not rushing to open doors and hope for the best. We're building a community-first server where player feedback directly shapes the final product. No pay-to-win, no shortcuts — just a clean Interlude experience with thoughtful quality-of-life improvements. If you've been burned by servers that promise the world and deliver a cash shop, this one's for you. What you can expect to see on our server: - x4 rate - Player buff trade shop - Crystallization shop - Arena mode for FUN PvP     Closed Beta Rewards Rewards are tied to real participation — no freebies for just showing up. 🥉 Tier 1 The test server runs at x100 rates with a gear shop, so you won't be grinding for days just to start testing. Requirements: Register, level to S-grade, complete 3rd profession, and join one of the organized beta clans.   Launch Rewards: - Exclusive "Genesis Start" Discord rank - Beta Box — unique hat (won't be available after launch) + big-head potions, fireworks & more   Optional: Participate in the Bug Hunt for additional rewards (details below). You will have one week from CBT server launch to complete Tier 1 🥇 Tier 2 Requirements: Complete Tier 1 + participate in at least 3 events like Clan Wars, Siege and others, they will be stated during CBT in Discord. Launch Rewards: - Everything from Tier 1 - 1 Month of VIP status — quality-of-life perks handed to you for free on day one     🐛 Bug Hunt — Bonus Rewards Throughout the beta we're running a Bug Hunt — find and report bugs to earn 1 Genesis Coin (donation currency) for every confirmed bug. We'll share full details and focus areas once you're in.     Get Involved This is your chance to shape a server before it launches, not complain about it after. The testers who show up now are the ones who'll feel the difference on day one — and they'll have the exclusive rewards to prove it. Sign Up for beta test and drop in to Discord, and let's build something worth logging into. Even if you're not ready to test but you're a veteran L2 player — join the community anyway. Your experience and perspective are worth a lot to us. See you in Aden.  
    • l2jlucera the source code is not public and is not for sale. If you're going to use L2jMobius or L2jAcis, you need to know how to program or have a basic understanding, and you can ask for help from a bot, which usually won't be 100% helpful. Or you can pay a programmer to do the work for you.  
    • You need for sure some knowledge to make a good start and dont get scammed.
  • Topics

×
×
  • Create New...

Important Information

This community uses essential cookies to function properly. Non-essential cookies and third-party services are used only with your consent. Read our Privacy Policy and We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue..