Skip to content

Improve Moving Platforms #204

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Feb 20, 2021
Merged
97 changes: 97 additions & 0 deletions Uchu.StandardScripts/AvantGardens/BusDoor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System.Collections.Generic;
using System.Numerics;
using System.Threading.Tasks;
using Uchu.Physics;
using Uchu.World;
using Uchu.World.Scripting.Native;

namespace Uchu.StandardScripts.AvantGardens
{
[ZoneSpecific(1100)]
public class BusDoor : NativeScript
{
private List<Player> _playersInRadius = new List<Player>();
private bool _doorOpen = false;

public override Task LoadAsync()
{
var gameObjects = HasLuaScript("l_ag_bus_door.lua");

foreach (var gameObject in gameObjects)
{
if (!gameObject.TryGetComponent<MovingPlatformComponent>(out var movingPlatformComponent)) continue;
movingPlatformComponent.Stop();

var physics = gameObject.AddComponent<PhysicsComponent>();

var physicsObject = CylinderBody.Create(
gameObject.Zone.Simulation,
gameObject.Transform.Position,
gameObject.Transform.Rotation,
new Vector2(85, 190));

physics.SetPhysics(physicsObject);

// Set up players entering and leaving.
Listen(physics.OnEnter, (other) =>
{
if (!(other.GameObject is Player player)) return;
if (_playersInRadius.Contains(player)) return;
_playersInRadius.Add(player);
UpdateDoor(gameObject);
});

Listen(physics.OnLeave, (other) =>
{
if (!(other.GameObject is Player player)) return;
if (!_playersInRadius.Contains(player)) return;
_playersInRadius.Remove(player);
UpdateDoor(gameObject);
});

// Set up player disconnecting so that players disconnecting near the door don't keep it open.
Listen(Zone.OnPlayerLoad, (player) =>
{
Listen(player.OnDestroyed, () =>
{
if (!_playersInRadius.Contains(player)) return;
_playersInRadius.Remove(player);
UpdateDoor(gameObject);
});
});
}

return Task.CompletedTask;
}

private void UpdateDoor(GameObject door)
{
// Return if the door is moving.
// If the intended open state changed, it will be updated when the move ends.
if (!door.TryGetComponent<MovingPlatformComponent>(out var movingPlatformComponent)) return;
if (movingPlatformComponent.State != PlatformState.Idle) return;

// Return if the door is already open to reduce updates.
var doorShouldBeOpen = _playersInRadius.Count > 0;
if (doorShouldBeOpen == _doorOpen) return;
_doorOpen = doorShouldBeOpen;

// Open or close the door, then update again in case the intended state changed mid-movement.
if (_doorOpen)
{
movingPlatformComponent.MoveTo(1, () =>
{
UpdateDoor(door);
});
}
else
{
movingPlatformComponent.MoveTo(0, () =>
{
door.PlayFX("busDust", "create", 642);
UpdateDoor(door);
});
}
}
}
}
19 changes: 19 additions & 0 deletions Uchu.World/Handlers/GameMessages/GeneralHandler.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
Expand Down Expand Up @@ -126,5 +127,23 @@ public async Task NotifyServerLevelProcessingCompleteHandler(NotifyServerLevelPr
player.Zone.BroadcastChatMessage($"{character.Name} has reached Level {character.Level}!");
}
}

[PacketHandler]
public void RequestPlatformResyncHandler(RequestPlatformResyncMessage message, Player player)
{
if (message.Associate.TryGetComponent<MovingPlatformComponent>(out var movingPlatformComponent))
{
player.Message(new PlatformResyncMessage()
{
State = movingPlatformComponent.State,
IdleTimeElapsed = movingPlatformComponent.IdleTimeElapsed,
PercentBetweenPoints = movingPlatformComponent.PercentBetweenPoints,
Index = (int) movingPlatformComponent.CurrentWaypointIndex,
NextIndex = (int) movingPlatformComponent.NextWaypointIndex,
UnexpectedLocation = movingPlatformComponent.TargetPosition,
UnexpectedRotation = movingPlatformComponent.TargetRotation,
});
}
}
}
}
2 changes: 1 addition & 1 deletion Uchu.World/Handlers/PositionUpdateHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public async Task PositionHandler(PositionUpdatePacket packet, IRakConnection co

physics.AngularVelocity = packet.AngularVelocity;

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

physics.PlatformPosition = packet.PlatformPosition;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using System.Timers;
using InfectedRose.Luz;
using RakDotNet.IO;
using Uchu.Core;
using Timer = System.Timers.Timer;

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

/// <summary>
/// Current stopwatch for delta times.
/// </summary>
private Stopwatch Stopwatch { get; set; } = new Stopwatch();

public LuzMovingPlatformPath Path { get; set; }

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

public uint NextWaypointIndex { get; set; }

public float IdleTimeElapsed { get; set; }
/// <summary>
/// Time spent idle
/// </summary>
public float IdleTimeElapsed =>
State == PlatformState.Idle
? (float) (Stopwatch.ElapsedMilliseconds / 1000.0)
: 0;

/// <summary>
/// Percent of the platform's moving progress between the current waypoints
/// </summary>
public float PercentBetweenPoints => State != PlatformState.Idle
? (float) ((Stopwatch.ElapsedMilliseconds / 1000.0) / _currentDuration)
: 0;

public override ComponentId Id => ComponentId.MovingPlatformComponent;

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

private double _currentDuration;

protected MovingPlatformComponent()
{
Listen(OnStart, () =>
Expand All @@ -65,7 +86,7 @@ protected MovingPlatformComponent()
Path = Zone.ZoneInfo.LuzFile.PathData.FirstOrDefault(p =>
p is LuzMovingPlatformPath && p.PathName == PathName) as LuzMovingPlatformPath;

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

State = PlatformState.Idle;

Task.Run(WaitPoint);
Task.Run(() => WaitPoint());

if (GameObject.TryGetComponent<SimplePhysicsComponent>(out var simplePhysicsComponent))
{
simplePhysicsComponent.HasPosition = false;
}

if (!GameObject.TryGetComponent<QuickBuildComponent>(out var quickBuildComponent)) return;
Listen(quickBuildComponent.OnStateChange, (state) =>
{
if (state != RebuildState.Completed) return;

// The waypoint must be set to the previous from the start since WaitPoint increments it.
this.Stop();
CurrentWaypointIndex = PathStart == 0 ? (uint) (Path.Waypoints.Length - 1) : PathStart - 1;
Task.Run(() =>
{
// TODO: A wait is required at the beginning, otherwise the platform moves right as the player is unfrozen from the building complete animation.
WaitPoint(1);
});
});
});
}

public override void Construct(BitWriter writer)
public override void Construct(BitWriter writer)
{
Serialize(writer);
Serialize(writer, true);
}

public override void Serialize(BitWriter writer)
{
Serialize(writer, false);
}

public void Serialize(BitWriter writer,bool includePath)
{
writer.WriteBit(true);

var hasPath = PathName != null;
var hasPath = includePath && PathName != null;

writer.WriteBit(hasPath);

Expand Down Expand Up @@ -148,61 +194,101 @@ public override void Serialize(BitWriter writer)
}
}

public void Stop()
{
Timer.Stop();
State = PlatformState.Idle;
}

public void MoveTo(uint position,Action moveCompleteCallback = default)
{
// Update Object in world.
CurrentWaypointIndex = position;
NextWaypointIndex = NextIndex;
_currentDuration = (WayPoint.Position - NextWayPoint.Position).Length() / WayPoint.Speed;
Stopwatch.Restart();
State = PlatformState.Move;
TargetPosition = WayPoint.Position;
TargetRotation = WayPoint.Rotation;

GameObject.Serialize(GameObject);

// Start Waiting after completing path.
Timer = new Timer
{
AutoReset = false,
Interval = _currentDuration * 1000
};
Timer.Elapsed += (sender, args) =>
{
Timer.Stop();

// Update Object in world.
CurrentWaypointIndex = NextIndex;
PathName = null;
State = PlatformState.Idle;
TargetPosition = WayPoint.Position;
TargetRotation = WayPoint.Rotation;
NextWaypointIndex = NextIndex;

GameObject.Serialize(GameObject);
};
if (moveCompleteCallback != default)
{
Timer.Elapsed += (sender, args) => moveCompleteCallback();
}

Timer.Start();
}

private void MovePlatform()
{
/*
* Update Object in world.
*/
PathName = Path.PathName;
// Update Object in world.
_currentDuration = (WayPoint.Position - NextWayPoint.Position).Length() / WayPoint.Speed;
Stopwatch.Restart();
State = PlatformState.Move;
TargetPosition = WayPoint.Position;
TargetRotation = WayPoint.Rotation;
NextWaypointIndex = NextIndex;

GameObject.Serialize(GameObject);

/*
* Start Waiting after completing path.
*/
// Start Waiting after completing path.
Timer = new Timer
{
AutoReset = false,
Interval = WayPoint.Speed * 1000
Interval = _currentDuration * 1000
};

Timer.Elapsed += (sender, args) => { WaitPoint(); };

Task.Run(() => Timer.Start());
Timer.Start();
}

private void WaitPoint()
private void WaitPoint(int extraWaitTime = 0)
{
// Move to next path index.
CurrentWaypointIndex = NextIndex;

/*
* Update Object in world.
*/
PathName = null;
Stopwatch.Restart();
_currentDuration = WayPoint.Wait;

// Update Object in world.
State = PlatformState.Idle;
TargetPosition = WayPoint.Position;
TargetRotation = WayPoint.Rotation;
NextWaypointIndex = NextIndex;

GameObject.Serialize(GameObject);

/*
* Start Waiting after waiting.
*/
// Start Waiting after waiting.
Timer = new Timer
{
AutoReset = false,
Interval = WayPoint.Wait * 1000
Interval = (WayPoint.Wait * 1000) + extraWaitTime
};

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

Task.Run(() => Timer.Start());
Timer.Start();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Uchu.World
{
public class RequestPlatformResyncMessage : ClientGameMessage
{
public override GameMessageId GameMessageId => GameMessageId.RequestPlatformResync;
}
}
Loading