How I Synchronized Character Coordinates on the Server in Lineage 2 using Node.js / Habr

Hello there,

I’m currently working on a Node.js server emulator for Lineage 2 Chronicle 1: Harbingers of War.

I’ve encountered an issue with aligning the character’s coordinates between the server and the client. When you command a movement in the game, there’s a seamless transition with an animation. However, the server manages movement based on a timer, resulting in less fluid motion.

C(client) – transitions smoothly from one point to another. S(server) – increments coordinates periodically.
C(client) – transitions smoothly from one point to another. S(server) – increments coordinates periodically.

For illustration, I took a Java-based build l2j-lisvus. There are many builds, but they all fork from the l2jserver project https://l2jserver.com/, inheriting various features, including character movement.

In l2j-lisvus and all l2jserver assemblies, character movement on the server proceeds through a timer with uniform increments.

The problem arises when an action, such as striking an NPC, is needed after reaching a destination.

For short distances, the issue is imperceptible. The foot lands precisely on the coin.
For short distances, the issue is imperceptible. The foot lands precisely on the coin.
At longer distances, the attack action starts before the character reaches the target.
At longer distances, the attack action starts before the character reaches the target.
Maximizing speed (900) makes the discrepancy evident. This is due to the difference between running and walking speed.
Maximizing speed (900) makes the discrepancy evident. This is due to the difference between running and walking speed.

How character movement on the server works.

The foundation is the basic characteristics of the character. Running speed is 126.

126 units per second internally.
126 units per second internally.

This schema represents coordinate incrementation every 1000ms by 126 units. Based on the schema above, an example code snippet for character actions upon reaching the destination:

// No coordinate increment. Simply calculating when the character will reach the final coordinates.
const distance = 1500;
const playerSpeed = 126;
const ticks = distance / playerSpeed; // 11.90
const time = ticks * 1000; // 11900ms

setTimeout(() => {
    // character action post-run
}, time);
At short distances.
At short distances.
At long distances.
At long distances.
Deviation at short distances.
Deviation at short distances.
Deviation at long distances.
Deviation at long distances.

The green zone indicates where the foot should have landed without discrepancies.

Character speed increase over time.

126 is the base speed. As the character progresses, movement speed increases, leading to larger discrepancies. However, before devising a formula, it’s essential to confirm the theory that walking speed affects the variance.

Characteristic data is transmitted from the server to the client.

A packet UserInfo.java line 83.

writeD(player.runSpeed);
writeD(player.walkSpeed);

Baseline values:

runSpeed: 126

walkSpeed: 88

Setting walkSpeed to 126. If the walking speed equals the running speed, discrepancies should vanish.

The character's foot reaches the correct endpoint.
The character’s foot reaches the correct endpoint.

The character is synchronized and begins the attack timely. Now, it’s crucial to understand how walking speed influences discrepancies between the client and server.

So, how far does a character get before starting to run?

It’s essential to capture the moment when walking transitions to running. To do this, we’ll send client data where the walking speed is higher than the running speed. This discrepancy will reveal the transition and allow for calculating the distance covered while walking.

runSpeed: 10

walkSpeed: 600

Walking is faster than running.
Walking is faster than running.

At a stride of 600, the character manages to cover 250 before starting to run.

600 / 250 = 2.4

700 / 291 = 2.4

800 / 333 = 2.4

From this, it’s concluded that the character covers a distance 2.4 times less than its walking speed before beginning to run.

Thus, at a walking speed of 88, the character will cover 36 units.

88 / 2.4 = 36

The first division represents the start of movement (walking), followed by running.
The first division represents the start of movement (walking), followed by running.

Solution

A formula for calculating time:

distance_covered_at_start = walking_speed / 2.4

(((distance_between_npc_and_player – distance_covered_at_start) / running_speed) * 1000ms) + time_walked

For example, with a distance of 1500.

Out of which we covered 36 initially.

1500 – 36 = 1464 running distance.

Running speed 126 per second.

1464 / 126 = 11.61 (number of segments covered per second).

11.61 * 1000 = 11610ms of running.

Add the walking time to 11610

Walking speed 88 per second.

1000 / 88 = 11.36ms per 1 unit

36 units * 11.36ms = 408ms

11610 + 408 = 12018ms

12018ms is the precise duration from start to finish.

Comparing it to the old time of 11900ms. A difference of 118ms.

setTimeout(() => {
    player.attack(npc);
}, 12018);
Running speed at 126.
Running speed at 126.
Running speed at 900.
Running speed at 900.
Foot position at running speed 126.
Foot position at running speed 126.
Foot position at running speed 900.
Foot position at running speed 900.

As showcased above, there’s no difference in foot positioning at various speeds, indicating the solution is effective.

Project link: lineage2js

Source

Read also