Jump to content
Returning Members: Password Reset Required ×

Recommended Posts

Posted

Hi all,

So I've been working on a 5065 server and I've hit a brick wall regarding the visibility of players.

I've found a bug in Redux where players will sporadically go invisible when jumping onto each others screens at specific times/angles. I've found that this bug is present on an awful lot of public sources/bases (and live servers as well), and I'm having a hard time figuring out why...

I tried following the wiki to the letter for screen and visibility, but I just haven't been able to nail down the cause or indeed, fix it. It also happens in the albetros source for example (so any sources based on these that people try and sell, have this bug).

Has anyone come across this and or can point me in the right direction of how to fix it?

Thanks I appreciate it.

Posted

What you need to know is:

* If the distance between you and the target is <=18 and he is not added to your screen, you spawn him with the MsgPlayer

* If the target distance is >18, you remove him from your screen but DO NOT send the MsgActon RemoveEntity packet, the client will do it alone

* You just remove the character from your client screen if he is leaving the map (teleport, disconnect etc)

* If you're Moving (MsgWalk or MsgAction:Jump) and the user is in your screen and spawned, you redirect your jump packet to your targets so they know you're moving

What the code from my repository does apart from that? Maybe you already notiiced, but when a player enter the screen it will teleport to the target location and that happens because the MsgPlayer will spawn him at the target location already, this will cause that little jump in the same coordinate. Maybe there is an easier solution, but my code calculates the line with Bresenham (Why not DDA? DDA is slower, so for that action I chose Bresenham) and creates a MsgPlayer with the first coordinate in the screen from that line, so when you send the Jump packet after that it wiill jump from the border of the screen at least.

Posted

What you need to know is:

* If the distance between you and the target is <=18 and he is not added to your screen, you spawn him with the MsgPlayer

* If the target distance is >18, you remove him from your screen but DO NOT send the MsgActon RemoveEntity packet, the client will do it alone

* You just remove the character from your client screen if he is leaving the map (teleport, disconnect etc)

* If you're Moving (MsgWalk or MsgAction:Jump) and the user is in your screen and spawned, you redirect your jump packet to your targets so they know you're moving

What the code from my repository does apart from that? Maybe you already notiiced, but when a player enter the screen it will teleport to the target location and that happens because the MsgPlayer will spawn him at the target location already, this will cause that little jump in the same coordinate. Maybe there is an easier solution, but my code calculates the line with Bresenham (Why not DDA? DDA is slower, so for that action I chose Bresenham) and creates a MsgPlayer with the first coordinate in the screen from that line, so when you send the Jump packet after that it wiill jump from the border of the screen at least.

Thanks that's really really helpful.

My problem is (I think) - that because the client automatically despawns the entity when it leaves the screen, there's this very specific moment where if player A is chasing player B, if they jump at the same time, player A goes invisible to player B, but the visibilitydictionary is not updated because technically, player A can still see player B....

Therefore Player A is not considered a newObject, and therefore not spawned back into the screen of Player B when it arrives... Does that make sense?

I can't figure out how to reliably capture Player A and force it to be considered a newObject at that very specific moment, so that it's respawned onto the screen of Player B... :/

Posted

Map actions need to be synchronized. Who jumped first? Will it be removed from screen? Are you broadcasting your players movements?

If PlayerA leave PlayerB screen by jumping, you remove both from each other visibility dictionary, but you won't "despawn" them from the screen, the client will do it automatically.

If you're handling map actions asynchronously without any synchronization, then you'll face this kind of problem. First you need to handle PlayerA movement and then you handle PlayerB movement.

First update surroundings and don't broadcast the Walk or Jump packet, send them individually. Whos entering the screen needs to receive your movement packets and who's entering your screen must receive it too.

If you broadcast the packet before updating your surroundings, whos in your screen will receive it, but who's entering your screen will not. And if you broadcast them twice, you can guess what will happen.

Take a look at this:

public async Task UpdateAsync(IPacket msg = null)
{
bool isJmpMsg = false;
ushort oldX = 0;
ushort oldY = 0;
var dda = new List<Point>();
if (msg is MsgAction jump && jump.Action == ActionType.MapJump)
{
	isJmpMsg = true;

	oldX = jump.ArgumentX;
	oldY = jump.ArgumentY;

	dda.AddRange(Bresenham.Calculate(oldX, oldY, role.X, role.Y));
}
else
{
	jump = null;
}

List<Role> targets = role.Map.Query9BlocksByPos(role.X, role.Y);
targets.AddRange(Roles.Values);
foreach (Role target in targets.Select(x => x).Distinct())
{
	bool skipMessage = false;
	if (target.Identity == role.Identity)
	{
		continue;
	}

	ushort newOldX = oldX;
	ushort newOldY = oldY;
	var isExit = false;
	var targetUser = target as Character;
	if (Calculations.GetDistance(role.X, role.Y, target.X, target.Y) <= VIEW_SIZE) // if the target is in my view range
	{
		if (Add(target)) // I try to add him to my screen, if I succeed, this means that he is a new entity in my screen, so I'll exchange spawn packets
		{
			targetUser?.Screen?.Add(role); // the target must receive me in his screen too
			if (!isJmpMsg)
			{
				skipMessage = true;
			}

			if (role is Character user)
			{
				await target.SendSpawnToAsync(user);
			}

			if (targetUser != null && isJmpMsg)
			{
				for (var i = 0; i < dda.Count; i++)
				{
					if (targetUser.GetDistance(dda[i].X, dda[i].Y) <= VIEW_SIZE)
					{
						newOldX = (ushort)dda[i].X;
						newOldY = (ushort)dda[i].Y;
						break;
					}
				}

				await role.SendSpawnToAsync(targetUser, newOldX, newOldY);
			}
			else if (targetUser != null)
			{
				await role.SendSpawnToAsync(targetUser);
			}
		}
	}
	else // if he is out of range, I will remove us from each other screen, but won't send the remove packet...
	{
		isExit = true;
		await RemoveAsync(target.Identity);
		if (targetUser?.Screen != null)
		{
			await targetUser.Screen.RemoveAsync(role.Identity);
		}
	}

	if (msg != null && targetUser != null) // in both cases, if the target is a player he must receive my movement packet
	{
		if (isJmpMsg && !isExit)
		{
			await targetUser.SendAsync(new MsgAction
			{
				Action = jump.Action,
				Argument = jump.Argument,
				X = newOldX,
				Y = newOldY,
				Command = jump.Command,
				Data = jump.Data,
				Direction = jump.Direction,
				Identity = jump.Identity,
				Map = jump.Map,
				MapColor = jump.MapColor,
				Timestamp = jump.Timestamp,
				Sprint = jump.Sprint
			});
		}
		else if (!skipMessage)
		{
			await targetUser.SendAsync(msg);
		}
	}
}
}

Posted

Map actions need to be synchronized. Who jumped first? Will it be removed from screen? Are you broadcasting your players movements?

If PlayerA leave PlayerB screen by jumping, you remove both from each other visibility dictionary, but you won't "despawn" them from the screen, the client will do it automatically.

If you're handling map actions asynchronously without any synchronization, then you'll face this kind of problem. First you need to handle PlayerA movement and then you handle PlayerB movement.

First update surroundings and don't broadcast the Walk or Jump packet, send them individually. Whos entering the screen needs to receive your movement packets and who's entering your screen must receive it too.

If you broadcast the packet before updating your surroundings, whos in your screen will receive it, but who's entering your screen will not. And if you broadcast them twice, you can guess what will happen.

Take a look at this:

public async Task UpdateAsync(IPacket msg = null)
{
bool isJmpMsg = false;
ushort oldX = 0;
ushort oldY = 0;
var dda = new List<Point>();
if (msg is MsgAction jump && jump.Action == ActionType.MapJump)
{
	isJmpMsg = true;

	oldX = jump.ArgumentX;
	oldY = jump.ArgumentY;

	dda.AddRange(Bresenham.Calculate(oldX, oldY, role.X, role.Y));
}
else
{
	jump = null;
}

List<Role> targets = role.Map.Query9BlocksByPos(role.X, role.Y);
targets.AddRange(Roles.Values);
foreach (Role target in targets.Select(x => x).Distinct())
{
	bool skipMessage = false;
	if (target.Identity == role.Identity)
	{
		continue;
	}

	ushort newOldX = oldX;
	ushort newOldY = oldY;
	var isExit = false;
	var targetUser = target as Character;
	if (Calculations.GetDistance(role.X, role.Y, target.X, target.Y) <= VIEW_SIZE) // if the target is in my view range
	{
		if (Add(target)) // I try to add him to my screen, if I succeed, this means that he is a new entity in my screen, so I'll exchange spawn packets
		{
			targetUser?.Screen?.Add(role); // the target must receive me in his screen too
			if (!isJmpMsg)
			{
				skipMessage = true;
			}

			if (role is Character user)
			{
				await target.SendSpawnToAsync(user);
			}

			if (targetUser != null && isJmpMsg)
			{
				for (var i = 0; i < dda.Count; i++)
				{
					if (targetUser.GetDistance(dda[i].X, dda[i].Y) <= VIEW_SIZE)
					{
						newOldX = (ushort)dda[i].X;
						newOldY = (ushort)dda[i].Y;
						break;
					}
				}

				await role.SendSpawnToAsync(targetUser, newOldX, newOldY);
			}
			else if (targetUser != null)
			{
				await role.SendSpawnToAsync(targetUser);
			}
		}
	}
	else // if he is out of range, I will remove us from each other screen, but won't send the remove packet...
	{
		isExit = true;
		await RemoveAsync(target.Identity);
		if (targetUser?.Screen != null)
		{
			await targetUser.Screen.RemoveAsync(role.Identity);
		}
	}

	if (msg != null && targetUser != null) // in both cases, if the target is a player he must receive my movement packet
	{
		if (isJmpMsg && !isExit)
		{
			await targetUser.SendAsync(new MsgAction
			{
				Action = jump.Action,
				Argument = jump.Argument,
				X = newOldX,
				Y = newOldY,
				Command = jump.Command,
				Data = jump.Data,
				Direction = jump.Direction,
				Identity = jump.Identity,
				Map = jump.Map,
				MapColor = jump.MapColor,
				Timestamp = jump.Timestamp,
				Sprint = jump.Sprint
			});
		}
		else if (!skipMessage)
		{
			await targetUser.SendAsync(msg);
		}
	}
}
}

Pintinho?! Haha. Thanks, again, that's very helpful :D.

I'll try some more things later and take a look at that snippet.

Posted

So, if we say Konichu is chasing you, then this can happen when you spawn Konichu outside of your client's screen distance with MsgPlayer. That could be a race condition where Konichu's spawn is sent to your client before it's received a response from your own jump (the client isn't updated, but the screen on the game server is). It can also happen if you're simply trying to spawn a player outside of screen distance (but I'm guessing you've triple checked those by now).

For the race condition issue, I'd recommend looking at how you're processing movement overall and how you're sending responses. Movement should generally be single-threaded per map / map bubble (most actions should be in my opinion), and the next movement shouldn't be processed until the messages for the spawns and movement response are in queue to be sent back to the client. Since messages queued and encrypted are in order, that'll ensure that the spawns and movement response messages are processed first before any follow up movements based on that server state.

Make sure you have MsgAction type 102 (or whatever QUERY_PLAYER is) implemented on your server, too. That'll at least let you heal when these occurrences happen (if you can't fix it right now). Best of luck with this.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  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.

×
×
  • Create New...