Skip to content

Commit 55efc28

Browse files
Merge pull request #204 from UchuServer/enhancement/improve-platforms
Improve Moving Platforms
2 parents 434463a + a8afa81 commit 55efc28

File tree

6 files changed

+292
-29
lines changed

6 files changed

+292
-29
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using System.Collections.Generic;
2+
using System.Numerics;
3+
using System.Threading.Tasks;
4+
using Uchu.Physics;
5+
using Uchu.World;
6+
using Uchu.World.Scripting.Native;
7+
8+
namespace Uchu.StandardScripts.AvantGardens
9+
{
10+
[ZoneSpecific(1100)]
11+
public class BusDoor : NativeScript
12+
{
13+
private List<Player> _playersInRadius = new List<Player>();
14+
private bool _doorOpen = false;
15+
16+
public override Task LoadAsync()
17+
{
18+
var gameObjects = HasLuaScript("l_ag_bus_door.lua");
19+
20+
foreach (var gameObject in gameObjects)
21+
{
22+
if (!gameObject.TryGetComponent<MovingPlatformComponent>(out var movingPlatformComponent)) continue;
23+
movingPlatformComponent.Stop();
24+
25+
var physics = gameObject.AddComponent<PhysicsComponent>();
26+
27+
var physicsObject = CylinderBody.Create(
28+
gameObject.Zone.Simulation,
29+
gameObject.Transform.Position,
30+
gameObject.Transform.Rotation,
31+
new Vector2(85, 190));
32+
33+
physics.SetPhysics(physicsObject);
34+
35+
// Set up players entering and leaving.
36+
Listen(physics.OnEnter, (other) =>
37+
{
38+
if (!(other.GameObject is Player player)) return;
39+
if (_playersInRadius.Contains(player)) return;
40+
_playersInRadius.Add(player);
41+
UpdateDoor(gameObject);
42+
});
43+
44+
Listen(physics.OnLeave, (other) =>
45+
{
46+
if (!(other.GameObject is Player player)) return;
47+
if (!_playersInRadius.Contains(player)) return;
48+
_playersInRadius.Remove(player);
49+
UpdateDoor(gameObject);
50+
});
51+
52+
// Set up player disconnecting so that players disconnecting near the door don't keep it open.
53+
Listen(Zone.OnPlayerLoad, (player) =>
54+
{
55+
Listen(player.OnDestroyed, () =>
56+
{
57+
if (!_playersInRadius.Contains(player)) return;
58+
_playersInRadius.Remove(player);
59+
UpdateDoor(gameObject);
60+
});
61+
});
62+
}
63+
64+
return Task.CompletedTask;
65+
}
66+
67+
private void UpdateDoor(GameObject door)
68+
{
69+
// Return if the door is moving.
70+
// If the intended open state changed, it will be updated when the move ends.
71+
if (!door.TryGetComponent<MovingPlatformComponent>(out var movingPlatformComponent)) return;
72+
if (movingPlatformComponent.State != PlatformState.Idle) return;
73+
74+
// Return if the door is already open to reduce updates.
75+
var doorShouldBeOpen = _playersInRadius.Count > 0;
76+
if (doorShouldBeOpen == _doorOpen) return;
77+
_doorOpen = doorShouldBeOpen;
78+
79+
// Open or close the door, then update again in case the intended state changed mid-movement.
80+
if (_doorOpen)
81+
{
82+
movingPlatformComponent.MoveTo(1, () =>
83+
{
84+
UpdateDoor(door);
85+
});
86+
}
87+
else
88+
{
89+
movingPlatformComponent.MoveTo(0, () =>
90+
{
91+
door.PlayFX("busDust", "create", 642);
92+
UpdateDoor(door);
93+
});
94+
}
95+
}
96+
}
97+
}

Uchu.World/Handlers/GameMessages/GeneralHandler.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Linq;
23
using System.Threading.Tasks;
34
using Microsoft.EntityFrameworkCore;
@@ -147,5 +148,23 @@ public async Task NotifyServerLevelProcessingCompleteHandler(NotifyServerLevelPr
147148
player.Zone.BroadcastChatMessage($"{character.Name} has reached Level {character.Level}!");
148149
}
149150
}
151+
152+
[PacketHandler]
153+
public void RequestPlatformResyncHandler(RequestPlatformResyncMessage message, Player player)
154+
{
155+
if (message.Associate.TryGetComponent<MovingPlatformComponent>(out var movingPlatformComponent))
156+
{
157+
player.Message(new PlatformResyncMessage()
158+
{
159+
State = movingPlatformComponent.State,
160+
IdleTimeElapsed = movingPlatformComponent.IdleTimeElapsed,
161+
PercentBetweenPoints = movingPlatformComponent.PercentBetweenPoints,
162+
Index = (int) movingPlatformComponent.CurrentWaypointIndex,
163+
NextIndex = (int) movingPlatformComponent.NextWaypointIndex,
164+
UnexpectedLocation = movingPlatformComponent.TargetPosition,
165+
UnexpectedRotation = movingPlatformComponent.TargetRotation,
166+
});
167+
}
168+
}
150169
}
151170
}

Uchu.World/Handlers/PositionUpdateHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public async Task PositionHandler(PositionUpdatePacket packet, IRakConnection co
3232

3333
physics.AngularVelocity = packet.AngularVelocity;
3434

35-
physics.Platform = default; //player.Zone.GameObjects.FirstOrDefault(g => g.ObjectId == packet.PlatformObjectId);
35+
physics.Platform = player.Zone.GameObjects.FirstOrDefault(g => g.Id == packet.PlatformObjectId);
3636

3737
physics.PlatformPosition = packet.PlatformPosition;
3838

Uchu.World/Objects/Components/ReplicaComponents/MovingPlatformComponent.cs

Lines changed: 114 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
using System;
2+
using System.Diagnostics;
23
using System.Linq;
34
using System.Numerics;
45
using System.Threading.Tasks;
5-
using System.Timers;
66
using InfectedRose.Luz;
77
using RakDotNet.IO;
88
using Uchu.Core;
9+
using Timer = System.Timers.Timer;
910

1011
namespace Uchu.World
1112
{
@@ -16,6 +17,11 @@ public class MovingPlatformComponent : ReplicaComponent
1617
/// </summary>
1718
private Timer Timer { get; set; }
1819

20+
/// <summary>
21+
/// Current stopwatch for delta times.
22+
/// </summary>
23+
private Stopwatch Stopwatch { get; set; } = new Stopwatch();
24+
1925
public LuzMovingPlatformPath Path { get; set; }
2026

2127
public string PathName { get; set; }
@@ -34,7 +40,20 @@ public class MovingPlatformComponent : ReplicaComponent
3440

3541
public uint NextWaypointIndex { get; set; }
3642

37-
public float IdleTimeElapsed { get; set; }
43+
/// <summary>
44+
/// Time spent idle
45+
/// </summary>
46+
public float IdleTimeElapsed =>
47+
State == PlatformState.Idle
48+
? (float) (Stopwatch.ElapsedMilliseconds / 1000.0)
49+
: 0;
50+
51+
/// <summary>
52+
/// Percent of the platform's moving progress between the current waypoints
53+
/// </summary>
54+
public float PercentBetweenPoints => State != PlatformState.Idle
55+
? (float) ((Stopwatch.ElapsedMilliseconds / 1000.0) / _currentDuration)
56+
: 0;
3857

3958
public override ComponentId Id => ComponentId.MovingPlatformComponent;
4059

@@ -53,6 +72,8 @@ public class MovingPlatformComponent : ReplicaComponent
5372
/// </summary>
5473
public LuzMovingPlatformWaypoint NextWayPoint => Path.Waypoints[NextIndex] as LuzMovingPlatformWaypoint;
5574

75+
private double _currentDuration;
76+
5677
protected MovingPlatformComponent()
5778
{
5879
Listen(OnStart, () =>
@@ -65,7 +86,7 @@ protected MovingPlatformComponent()
6586
Path = Zone.ZoneInfo.LuzFile.PathData.FirstOrDefault(p =>
6687
p is LuzMovingPlatformPath && p.PathName == PathName) as LuzMovingPlatformPath;
6788

68-
Type = GameObject.Settings.TryGetValue("platformIsMover", out var isMover) && (bool) isMover
89+
Type = !GameObject.Settings.TryGetValue("platformIsMover", out var isMover) || (bool) isMover
6990
? PlatformType.Mover
7091
: GameObject.Settings.TryGetValue("platformIsSimpleMover", out var isSimpleMover) &&
7192
(bool) isSimpleMover
@@ -76,20 +97,45 @@ protected MovingPlatformComponent()
7697

7798
State = PlatformState.Idle;
7899

79-
Task.Run(WaitPoint);
100+
Task.Run(() => WaitPoint());
101+
102+
if (GameObject.TryGetComponent<SimplePhysicsComponent>(out var simplePhysicsComponent))
103+
{
104+
simplePhysicsComponent.HasPosition = false;
105+
}
106+
107+
if (!GameObject.TryGetComponent<QuickBuildComponent>(out var quickBuildComponent)) return;
108+
Listen(quickBuildComponent.OnStateChange, (state) =>
109+
{
110+
if (state != RebuildState.Completed) return;
111+
112+
// The waypoint must be set to the previous from the start since WaitPoint increments it.
113+
this.Stop();
114+
CurrentWaypointIndex = PathStart == 0 ? (uint) (Path.Waypoints.Length - 1) : PathStart - 1;
115+
Task.Run(() =>
116+
{
117+
// TODO: A wait is required at the beginning, otherwise the platform moves right as the player is unfrozen from the building complete animation.
118+
WaitPoint(1);
119+
});
120+
});
80121
});
81122
}
82123

83-
public override void Construct(BitWriter writer)
124+
public override void Construct(BitWriter writer)
84125
{
85-
Serialize(writer);
126+
Serialize(writer, true);
86127
}
87128

88129
public override void Serialize(BitWriter writer)
130+
{
131+
Serialize(writer, false);
132+
}
133+
134+
public void Serialize(BitWriter writer,bool includePath)
89135
{
90136
writer.WriteBit(true);
91137

92-
var hasPath = PathName != null;
138+
var hasPath = includePath && PathName != null;
93139

94140
writer.WriteBit(hasPath);
95141

@@ -148,61 +194,101 @@ public override void Serialize(BitWriter writer)
148194
}
149195
}
150196

197+
public void Stop()
198+
{
199+
Timer.Stop();
200+
State = PlatformState.Idle;
201+
}
202+
203+
public void MoveTo(uint position,Action moveCompleteCallback = default)
204+
{
205+
// Update Object in world.
206+
CurrentWaypointIndex = position;
207+
NextWaypointIndex = NextIndex;
208+
_currentDuration = (WayPoint.Position - NextWayPoint.Position).Length() / WayPoint.Speed;
209+
Stopwatch.Restart();
210+
State = PlatformState.Move;
211+
TargetPosition = WayPoint.Position;
212+
TargetRotation = WayPoint.Rotation;
213+
214+
GameObject.Serialize(GameObject);
215+
216+
// Start Waiting after completing path.
217+
Timer = new Timer
218+
{
219+
AutoReset = false,
220+
Interval = _currentDuration * 1000
221+
};
222+
Timer.Elapsed += (sender, args) =>
223+
{
224+
Timer.Stop();
225+
226+
// Update Object in world.
227+
CurrentWaypointIndex = NextIndex;
228+
PathName = null;
229+
State = PlatformState.Idle;
230+
TargetPosition = WayPoint.Position;
231+
TargetRotation = WayPoint.Rotation;
232+
NextWaypointIndex = NextIndex;
233+
234+
GameObject.Serialize(GameObject);
235+
};
236+
if (moveCompleteCallback != default)
237+
{
238+
Timer.Elapsed += (sender, args) => moveCompleteCallback();
239+
}
240+
241+
Timer.Start();
242+
}
243+
151244
private void MovePlatform()
152245
{
153-
/*
154-
* Update Object in world.
155-
*/
156-
PathName = Path.PathName;
246+
// Update Object in world.
247+
_currentDuration = (WayPoint.Position - NextWayPoint.Position).Length() / WayPoint.Speed;
248+
Stopwatch.Restart();
157249
State = PlatformState.Move;
158250
TargetPosition = WayPoint.Position;
159251
TargetRotation = WayPoint.Rotation;
160252
NextWaypointIndex = NextIndex;
161253

162254
GameObject.Serialize(GameObject);
163255

164-
/*
165-
* Start Waiting after completing path.
166-
*/
256+
// Start Waiting after completing path.
167257
Timer = new Timer
168258
{
169259
AutoReset = false,
170-
Interval = WayPoint.Speed * 1000
260+
Interval = _currentDuration * 1000
171261
};
172-
173262
Timer.Elapsed += (sender, args) => { WaitPoint(); };
174263

175-
Task.Run(() => Timer.Start());
264+
Timer.Start();
176265
}
177266

178-
private void WaitPoint()
267+
private void WaitPoint(int extraWaitTime = 0)
179268
{
180269
// Move to next path index.
181270
CurrentWaypointIndex = NextIndex;
182-
183-
/*
184-
* Update Object in world.
185-
*/
186-
PathName = null;
271+
Stopwatch.Restart();
272+
_currentDuration = WayPoint.Wait;
273+
274+
// Update Object in world.
187275
State = PlatformState.Idle;
188276
TargetPosition = WayPoint.Position;
189277
TargetRotation = WayPoint.Rotation;
190278
NextWaypointIndex = NextIndex;
191279

192280
GameObject.Serialize(GameObject);
193281

194-
/*
195-
* Start Waiting after waiting.
196-
*/
282+
// Start Waiting after waiting.
197283
Timer = new Timer
198284
{
199285
AutoReset = false,
200-
Interval = WayPoint.Wait * 1000
286+
Interval = (WayPoint.Wait * 1000) + extraWaitTime
201287
};
202288

203289
Timer.Elapsed += (sender, args) => { MovePlatform(); };
204290

205-
Task.Run(() => Timer.Start());
291+
Timer.Start();
206292
}
207293
}
208294
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Uchu.World
2+
{
3+
public class RequestPlatformResyncMessage : ClientGameMessage
4+
{
5+
public override GameMessageId GameMessageId => GameMessageId.RequestPlatformResync;
6+
}
7+
}

0 commit comments

Comments
 (0)