Add physics simulation to client
This allows the player to fall to the ground when they spawn.
This commit is contained in:
parent
240ca3c972
commit
605cbbc2a0
@ -3,6 +3,7 @@ using TrueCraft.API.Networking;
|
|||||||
using TrueCraft.Core.Networking.Packets;
|
using TrueCraft.Core.Networking.Packets;
|
||||||
using TrueCraft.Core.Networking;
|
using TrueCraft.Core.Networking;
|
||||||
using TrueCraft.Client.Linux.Events;
|
using TrueCraft.Client.Linux.Events;
|
||||||
|
using TrueCraft.API;
|
||||||
|
|
||||||
namespace TrueCraft.Client.Linux.Handlers
|
namespace TrueCraft.Client.Linux.Handlers
|
||||||
{
|
{
|
||||||
@ -12,6 +13,7 @@ namespace TrueCraft.Client.Linux.Handlers
|
|||||||
{
|
{
|
||||||
client.RegisterPacketHandler(new HandshakeResponsePacket().ID, HandleHandshake);
|
client.RegisterPacketHandler(new HandshakeResponsePacket().ID, HandleHandshake);
|
||||||
client.RegisterPacketHandler(new ChatMessagePacket().ID, HandleChatMessage);
|
client.RegisterPacketHandler(new ChatMessagePacket().ID, HandleChatMessage);
|
||||||
|
client.RegisterPacketHandler(new SetPlayerPositionPacket().ID, HandlePositionAndLook);
|
||||||
|
|
||||||
client.RegisterPacketHandler(new ChunkPreamblePacket().ID, ChunkHandler.HandleChunkPreamble);
|
client.RegisterPacketHandler(new ChunkPreamblePacket().ID, ChunkHandler.HandleChunkPreamble);
|
||||||
client.RegisterPacketHandler(new ChunkDataPacket().ID, ChunkHandler.HandleChunkData);
|
client.RegisterPacketHandler(new ChunkDataPacket().ID, ChunkHandler.HandleChunkData);
|
||||||
@ -29,5 +31,12 @@ namespace TrueCraft.Client.Linux.Handlers
|
|||||||
Console.WriteLine("Got handshake with {0}", packet.ConnectionHash); // TODO: Authenticate?
|
Console.WriteLine("Got handshake with {0}", packet.ConnectionHash); // TODO: Authenticate?
|
||||||
client.QueuePacket(new LoginRequestPacket(PacketReader.Version, "TestUser"));
|
client.QueuePacket(new LoginRequestPacket(PacketReader.Version, "TestUser"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void HandlePositionAndLook(IPacket _packet, MultiplayerClient client)
|
||||||
|
{
|
||||||
|
var packet = (SetPlayerPositionPacket)_packet;
|
||||||
|
client._Position = new Vector3(packet.X, packet.Y, packet.Z);
|
||||||
|
// TODO: Pitch and yaw
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,22 +7,23 @@ using System.Threading;
|
|||||||
using TrueCraft.Core.Networking;
|
using TrueCraft.Core.Networking;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using TrueCraft.Core.Networking.Packets;
|
using TrueCraft.Core.Networking.Packets;
|
||||||
|
|
||||||
// TODO: Make IMultiplayerClient and so on
|
|
||||||
using TrueCraft.Client.Linux.Events;
|
using TrueCraft.Client.Linux.Events;
|
||||||
|
using TrueCraft.Core.Logic;
|
||||||
|
using TrueCraft.API.Entities;
|
||||||
|
using TrueCraft.API;
|
||||||
|
|
||||||
namespace TrueCraft.Client.Linux
|
namespace TrueCraft.Client.Linux
|
||||||
{
|
{
|
||||||
public delegate void PacketHandler(IPacket packet, MultiplayerClient client);
|
public delegate void PacketHandler(IPacket packet, MultiplayerClient client);
|
||||||
|
|
||||||
public class MultiplayerClient
|
public class MultiplayerClient : IAABBEntity // TODO: Make IMultiplayerClient and so on
|
||||||
{
|
{
|
||||||
public event EventHandler<ChatMessageEventArgs> ChatMessage;
|
public event EventHandler<ChatMessageEventArgs> ChatMessage;
|
||||||
public event EventHandler<ChunkEventArgs> ChunkLoaded;
|
public event EventHandler<ChunkEventArgs> ChunkLoaded;
|
||||||
public event EventHandler<ChunkEventArgs> ChunkUnloaded;
|
public event EventHandler<ChunkEventArgs> ChunkUnloaded;
|
||||||
|
|
||||||
public ReadOnlyWorld World { get; private set; }
|
public ReadOnlyWorld World { get; private set; }
|
||||||
|
public PhysicsEngine Physics { get; set; }
|
||||||
|
|
||||||
private TcpClient Client { get; set; }
|
private TcpClient Client { get; set; }
|
||||||
private IMinecraftStream Stream { get; set; }
|
private IMinecraftStream Stream { get; set; }
|
||||||
@ -41,6 +42,10 @@ namespace TrueCraft.Client.Linux
|
|||||||
PacketHandlers = new PacketHandler[0x100];
|
PacketHandlers = new PacketHandler[0x100];
|
||||||
Handlers.PacketHandlers.RegisterHandlers(this);
|
Handlers.PacketHandlers.RegisterHandlers(this);
|
||||||
World = new ReadOnlyWorld();
|
World = new ReadOnlyWorld();
|
||||||
|
var repo = new BlockRepository();
|
||||||
|
repo.DiscoverBlockProviders();
|
||||||
|
World.World.BlockRepository = repo;
|
||||||
|
Physics = new PhysicsEngine(World, repo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterPacketHandler(byte packetId, PacketHandler handler)
|
public void RegisterPacketHandler(byte packetId, PacketHandler handler)
|
||||||
@ -53,6 +58,14 @@ namespace TrueCraft.Client.Linux
|
|||||||
Client.BeginConnect(endPoint.Address, endPoint.Port, ConnectionComplete, null);
|
Client.BeginConnect(endPoint.Address, endPoint.Port, ConnectionComplete, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Disconnect()
|
||||||
|
{
|
||||||
|
NetworkWorker.Abort();
|
||||||
|
new DisconnectPacket("Disconnecting").WritePacket(Stream);
|
||||||
|
Stream.BaseStream.Flush();
|
||||||
|
Client.Close();
|
||||||
|
}
|
||||||
|
|
||||||
public void QueuePacket(IPacket packet)
|
public void QueuePacket(IPacket packet)
|
||||||
{
|
{
|
||||||
PacketQueue.Enqueue(packet);
|
PacketQueue.Enqueue(packet);
|
||||||
@ -63,6 +76,7 @@ namespace TrueCraft.Client.Linux
|
|||||||
Client.EndConnect(result);
|
Client.EndConnect(result);
|
||||||
Stream = new MinecraftStream(new BufferedStream(Client.GetStream()));
|
Stream = new MinecraftStream(new BufferedStream(Client.GetStream()));
|
||||||
NetworkWorker.Start();
|
NetworkWorker.Start();
|
||||||
|
Physics.AddEntity(this);
|
||||||
QueuePacket(new HandshakePacket("TestUser")); // TODO: Get username from somewhere else
|
QueuePacket(new HandshakePacket("TestUser")); // TODO: Get username from somewhere else
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,5 +121,86 @@ namespace TrueCraft.Client.Linux
|
|||||||
{
|
{
|
||||||
if (ChunkUnloaded != null) ChunkUnloaded(this, e);
|
if (ChunkUnloaded != null) ChunkUnloaded(this, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region IAABBEntity implementation
|
||||||
|
|
||||||
|
public const double Width = 0.6;
|
||||||
|
public const double Height = 1.62;
|
||||||
|
public const double Depth = 0.6;
|
||||||
|
|
||||||
|
public void TerrainCollision(Vector3 collisionPoint, Vector3 collisionDirection)
|
||||||
|
{
|
||||||
|
// This space intentionally left blank
|
||||||
|
}
|
||||||
|
|
||||||
|
public BoundingBox BoundingBox
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new BoundingBox(Position, Position + Size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Size Size
|
||||||
|
{
|
||||||
|
get { return new Size(Width, Height, Depth); }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IPhysicsEntity implementation
|
||||||
|
|
||||||
|
public bool BeginUpdate()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndUpdate(Vector3 newPosition)
|
||||||
|
{
|
||||||
|
Position = newPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Vector3 _Position;
|
||||||
|
public Vector3 Position
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _Position;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_Position != value)
|
||||||
|
QueuePacket(new PlayerPositionAndLookPacket(value.X, value.Y, value.Y + Height, value.Z, 0, 0, false));
|
||||||
|
_Position = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 Velocity { get; set; }
|
||||||
|
|
||||||
|
public float AccelerationDueToGravity
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return 0.08f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Drag
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return 0.02f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float TerminalVelocity
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return 3.92f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
405
TrueCraft.Client.Linux/PhysicsEngine.cs
Normal file
405
TrueCraft.Client.Linux/PhysicsEngine.cs
Normal file
@ -0,0 +1,405 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using TrueCraft.Core.World;
|
||||||
|
using TrueCraft.API.Entities;
|
||||||
|
using TrueCraft.API.World;
|
||||||
|
using TrueCraft.API;
|
||||||
|
using TrueCraft.Client.Linux;
|
||||||
|
|
||||||
|
namespace TrueCraft
|
||||||
|
{
|
||||||
|
// This is a stripped down version of the physics engine that the server uses
|
||||||
|
// The only thing we use it for is our own movement
|
||||||
|
public class PhysicsEngine
|
||||||
|
{
|
||||||
|
public PhysicsEngine(ReadOnlyWorld world, IBlockPhysicsProvider physicsProvider)
|
||||||
|
{
|
||||||
|
World = world;
|
||||||
|
Entities = new List<IPhysicsEntity>();
|
||||||
|
EntityLock = new object();
|
||||||
|
LastUpdate = DateTime.MinValue;
|
||||||
|
BlockPhysicsProvider = physicsProvider;
|
||||||
|
MillisecondsBetweenUpdates = 1000 / 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int MillisecondsBetweenUpdates { get; set; }
|
||||||
|
public ReadOnlyWorld World { get; set; }
|
||||||
|
public IBlockPhysicsProvider BlockPhysicsProvider { get; set; }
|
||||||
|
public List<IPhysicsEntity> Entities { get; set; }
|
||||||
|
private object EntityLock { get; set; }
|
||||||
|
private DateTime LastUpdate { get; set; }
|
||||||
|
|
||||||
|
public void AddEntity(IPhysicsEntity entity)
|
||||||
|
{
|
||||||
|
if (Entities.Contains(entity))
|
||||||
|
return;
|
||||||
|
lock (EntityLock)
|
||||||
|
Entities.Add(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveEntity(IPhysicsEntity entity)
|
||||||
|
{
|
||||||
|
if (!Entities.Contains(entity))
|
||||||
|
return;
|
||||||
|
lock (EntityLock)
|
||||||
|
Entities.Remove(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BoundingBox TempBoundingBox;
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
double multipler = (DateTime.Now - LastUpdate).TotalMilliseconds / MillisecondsBetweenUpdates;
|
||||||
|
if (LastUpdate == DateTime.MinValue)
|
||||||
|
multipler = 1;
|
||||||
|
if (multipler > 5) multipler = 5;
|
||||||
|
if (multipler < 0.1) multipler = 0.1;
|
||||||
|
lock (EntityLock)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Entities.Count; i++)
|
||||||
|
{
|
||||||
|
var entity = Entities[i];
|
||||||
|
if (entity.BeginUpdate())
|
||||||
|
{
|
||||||
|
entity.Velocity -= new Vector3(0, entity.AccelerationDueToGravity * multipler, 0);
|
||||||
|
entity.Velocity.Clamp(entity.TerminalVelocity);
|
||||||
|
if (entity is IAABBEntity)
|
||||||
|
CheckWithTerrain((IAABBEntity)entity, World);
|
||||||
|
entity.EndUpdate(entity.Position + entity.Velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LastUpdate = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckWithTerrain(IAABBEntity entity, ReadOnlyWorld world)
|
||||||
|
{
|
||||||
|
Vector3 collisionPoint, collisionDirection;
|
||||||
|
if (entity.Position.Y > 0 && entity.Position.Y <= 127) // Don't do checks outside the map
|
||||||
|
{
|
||||||
|
bool fireEvent = entity.Velocity != Vector3.Zero;
|
||||||
|
// Do terrain collisions
|
||||||
|
if (AdjustVelocityX(entity, world, out collisionPoint, out collisionDirection))
|
||||||
|
{
|
||||||
|
if (fireEvent)
|
||||||
|
entity.TerrainCollision(collisionPoint, collisionDirection);
|
||||||
|
}
|
||||||
|
if (AdjustVelocityY(entity, world, out collisionPoint, out collisionDirection))
|
||||||
|
{
|
||||||
|
entity.Velocity *= new Vector3(0.1, 1, 0.1); // TODO: More sophisticated friction
|
||||||
|
if (fireEvent)
|
||||||
|
entity.TerrainCollision(collisionPoint, collisionDirection);
|
||||||
|
}
|
||||||
|
if (AdjustVelocityZ(entity, world, out collisionPoint, out collisionDirection))
|
||||||
|
{
|
||||||
|
if (fireEvent)
|
||||||
|
entity.TerrainCollision(collisionPoint, collisionDirection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: There's a lot of code replication here, perhaps it can be consolidated
|
||||||
|
/// <summary>
|
||||||
|
/// Performs terrain collision tests and adjusts the X-axis velocity accordingly
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the entity collides with the terrain</returns>
|
||||||
|
private bool AdjustVelocityX(IAABBEntity entity, ReadOnlyWorld world, out Vector3 collision, out Vector3 collisionDirection)
|
||||||
|
{
|
||||||
|
collision = Vector3.Zero;
|
||||||
|
collisionDirection = Vector3.Zero;
|
||||||
|
if (entity.Velocity.X == 0)
|
||||||
|
return false;
|
||||||
|
// Do some enviornment guessing to improve speed
|
||||||
|
int minY = (int)entity.Position.Y - (entity.Position.Y < 0 ? 1 : 0);
|
||||||
|
int maxY = (int)(entity.Position.Y + entity.Size.Width) - (entity.Position.Y < 0 ? 1 : 0);
|
||||||
|
int minZ = (int)entity.Position.Z - (entity.Position.Z < 0 ? 1 : 0);
|
||||||
|
int maxZ = (int)(entity.Position.Z + entity.Size.Depth) - (entity.Position.Z < 0 ? 1 : 0);
|
||||||
|
int minX, maxX;
|
||||||
|
|
||||||
|
// Expand bounding box to include area to be tested
|
||||||
|
if (entity.Velocity.X < 0)
|
||||||
|
{
|
||||||
|
TempBoundingBox = new BoundingBox(
|
||||||
|
new Vector3(entity.BoundingBox.Min.X + entity.Velocity.X, entity.BoundingBox.Min.Y, entity.BoundingBox.Min.Z),
|
||||||
|
entity.BoundingBox.Max);
|
||||||
|
|
||||||
|
maxX = (int)(TempBoundingBox.Max.X);
|
||||||
|
minX = (int)(TempBoundingBox.Min.X + entity.Velocity.X) - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TempBoundingBox = new BoundingBox(
|
||||||
|
entity.BoundingBox.Min,
|
||||||
|
new Vector3(entity.BoundingBox.Max.X + entity.Velocity.X, entity.BoundingBox.Max.Y, entity.BoundingBox.Max.Z));
|
||||||
|
minX = (int)(entity.BoundingBox.Min.X);
|
||||||
|
maxX = (int)(entity.BoundingBox.Max.X + entity.Velocity.X) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do terrain checks
|
||||||
|
double? collisionPoint = null;
|
||||||
|
BoundingBox blockBox;
|
||||||
|
for (int x = minX; x <= maxX; x++)
|
||||||
|
{
|
||||||
|
for (int y = minY; y <= maxY; y++)
|
||||||
|
{
|
||||||
|
for (int z = minZ; z <= maxZ; z++)
|
||||||
|
{
|
||||||
|
var position = new Coordinates3D(x, y, z);
|
||||||
|
var boundingBox = BlockPhysicsProvider.GetBoundingBox(world.World, position);
|
||||||
|
if (boundingBox == null)
|
||||||
|
continue;
|
||||||
|
blockBox = boundingBox.Value.OffsetBy(position + new Vector3(0.5));
|
||||||
|
if (TempBoundingBox.Intersects(blockBox))
|
||||||
|
{
|
||||||
|
if (entity.Velocity.X < 0)
|
||||||
|
{
|
||||||
|
if (!collisionPoint.HasValue)
|
||||||
|
collisionPoint = blockBox.Max.X;
|
||||||
|
else if (collisionPoint.Value < blockBox.Max.X)
|
||||||
|
collisionPoint = blockBox.Max.X;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!collisionPoint.HasValue)
|
||||||
|
collisionPoint = blockBox.Min.X;
|
||||||
|
else if (collisionPoint.Value > blockBox.Min.X)
|
||||||
|
collisionPoint = blockBox.Min.X;
|
||||||
|
}
|
||||||
|
collision = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collisionPoint != null)
|
||||||
|
{
|
||||||
|
if (entity.Velocity.X < 0)
|
||||||
|
{
|
||||||
|
entity.Velocity = new Vector3(
|
||||||
|
entity.Velocity.X - (TempBoundingBox.Min.X - collisionPoint.Value),
|
||||||
|
entity.Velocity.Y,
|
||||||
|
entity.Velocity.Z);
|
||||||
|
collisionDirection = Vector3.Left;
|
||||||
|
}
|
||||||
|
else if (entity.Velocity.X > 0)
|
||||||
|
{
|
||||||
|
entity.Velocity = new Vector3(
|
||||||
|
entity.Velocity.X - (TempBoundingBox.Max.X - collisionPoint.Value),
|
||||||
|
entity.Velocity.Y,
|
||||||
|
entity.Velocity.Z);
|
||||||
|
collisionDirection = Vector3.Right;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs terrain collision tests and adjusts the Y-axis velocity accordingly
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the entity collides with the terrain</returns>
|
||||||
|
private bool AdjustVelocityY(IAABBEntity entity, ReadOnlyWorld world, out Vector3 collision, out Vector3 collisionDirection)
|
||||||
|
{
|
||||||
|
collision = Vector3.Zero;
|
||||||
|
collisionDirection = Vector3.Zero;
|
||||||
|
if (entity.Velocity.Y == 0)
|
||||||
|
return false;
|
||||||
|
// Do some enviornment guessing to improve speed
|
||||||
|
int minX = (int)entity.Position.X - (entity.Position.X < 0 ? 1 : 0);
|
||||||
|
int maxX = (int)(entity.Position.X + entity.Size.Width) - (entity.Position.X < 0 ? 1 : 0);
|
||||||
|
int minZ = (int)entity.Position.Z - (entity.Position.Z < 0 ? 1 : 0);
|
||||||
|
int maxZ = (int)(entity.Position.Z + entity.Size.Depth) - (entity.Position.Z < 0 ? 1 : 0);
|
||||||
|
int minY, maxY;
|
||||||
|
|
||||||
|
// Expand bounding box to include area to be tested
|
||||||
|
if (entity.Velocity.Y < 0)
|
||||||
|
{
|
||||||
|
TempBoundingBox = new BoundingBox(
|
||||||
|
new Vector3(entity.BoundingBox.Min.X, entity.BoundingBox.Min.Y + entity.Velocity.Y, entity.BoundingBox.Min.Z),
|
||||||
|
entity.BoundingBox.Max);
|
||||||
|
|
||||||
|
maxY = (int)(TempBoundingBox.Max.Y);
|
||||||
|
minY = (int)(TempBoundingBox.Min.Y + entity.Velocity.Y) - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TempBoundingBox = new BoundingBox(
|
||||||
|
entity.BoundingBox.Min,
|
||||||
|
new Vector3(entity.BoundingBox.Max.X, entity.BoundingBox.Max.Y + entity.Velocity.Y, entity.BoundingBox.Max.Z));
|
||||||
|
minY = (int)(entity.BoundingBox.Min.Y);
|
||||||
|
maxY = (int)(entity.BoundingBox.Max.Y + entity.Velocity.Y) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp Y into map boundaries
|
||||||
|
if (minY < 0)
|
||||||
|
minY = 0;
|
||||||
|
if (minY >= TrueCraft.Core.World.World.Height)
|
||||||
|
minY = TrueCraft.Core.World.World.Height - 1;
|
||||||
|
if (maxY < 0)
|
||||||
|
maxY = 0;
|
||||||
|
if (maxY >= TrueCraft.Core.World.World.Height)
|
||||||
|
maxY = TrueCraft.Core.World.World.Height - 1;
|
||||||
|
|
||||||
|
// Do terrain checks
|
||||||
|
double? collisionPoint = null;
|
||||||
|
BoundingBox blockBox;
|
||||||
|
for (int x = minX; x <= maxX; x++)
|
||||||
|
{
|
||||||
|
for (int y = minY; y <= maxY; y++)
|
||||||
|
{
|
||||||
|
for (int z = minZ; z <= maxZ; z++)
|
||||||
|
{
|
||||||
|
var position = new Coordinates3D(x, y, z);
|
||||||
|
if (!World.IsValidPosition(position))
|
||||||
|
continue;
|
||||||
|
var boundingBox = BlockPhysicsProvider.GetBoundingBox(world.World, position);
|
||||||
|
if (boundingBox == null)
|
||||||
|
continue;
|
||||||
|
blockBox = boundingBox.Value.OffsetBy(position + new Vector3(0.5));
|
||||||
|
if (TempBoundingBox.Intersects(blockBox))
|
||||||
|
{
|
||||||
|
if (entity.Velocity.Y < 0)
|
||||||
|
{
|
||||||
|
if (!collisionPoint.HasValue)
|
||||||
|
collisionPoint = blockBox.Max.Y;
|
||||||
|
else if (collisionPoint.Value < blockBox.Max.Y)
|
||||||
|
collisionPoint = blockBox.Max.Y;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!collisionPoint.HasValue)
|
||||||
|
collisionPoint = blockBox.Min.Y;
|
||||||
|
else if (collisionPoint.Value > blockBox.Min.Y)
|
||||||
|
collisionPoint = blockBox.Min.Y;
|
||||||
|
}
|
||||||
|
collision = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collisionPoint != null)
|
||||||
|
{
|
||||||
|
if (entity.Velocity.Y < 0)
|
||||||
|
{
|
||||||
|
// TODO: Do block event
|
||||||
|
//var block = world.GetBlock(collision);
|
||||||
|
//block.OnBlockWalkedOn(world, collision, this);
|
||||||
|
entity.Velocity = new Vector3(entity.Velocity.X,
|
||||||
|
entity.Velocity.Y + (collisionPoint.Value - TempBoundingBox.Min.Y),
|
||||||
|
entity.Velocity.Z);
|
||||||
|
collisionDirection = Vector3.Down;
|
||||||
|
}
|
||||||
|
else if (entity.Velocity.Y > 0)
|
||||||
|
{
|
||||||
|
entity.Velocity = new Vector3(entity.Velocity.X,
|
||||||
|
entity.Velocity.Y - (TempBoundingBox.Max.Y - collisionPoint.Value),
|
||||||
|
entity.Velocity.Z);
|
||||||
|
collisionDirection = Vector3.Up;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs terrain collision tests and adjusts the Z-axis velocity accordingly
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the entity collides with the terrain</returns>
|
||||||
|
private bool AdjustVelocityZ(IAABBEntity entity, ReadOnlyWorld world, out Vector3 collision, out Vector3 collisionDirection)
|
||||||
|
{
|
||||||
|
collision = Vector3.Zero;
|
||||||
|
collisionDirection = Vector3.Zero;
|
||||||
|
if (entity.Velocity.Z == 0)
|
||||||
|
return false;
|
||||||
|
// Do some enviornment guessing to improve speed
|
||||||
|
int minX = (int)entity.Position.X - (entity.Position.X < 0 ? 1 : 0);
|
||||||
|
int maxX = (int)(entity.Position.X + entity.Size.Depth) - (entity.Position.X < 0 ? 1 : 0);
|
||||||
|
int minY = (int)entity.Position.Y - (entity.Position.Y < 0 ? 1 : 0);
|
||||||
|
int maxY = (int)(entity.Position.Y + entity.Size.Width) - (entity.Position.Y < 0 ? 1 : 0);
|
||||||
|
int minZ, maxZ;
|
||||||
|
|
||||||
|
// Expand bounding box to include area to be tested
|
||||||
|
if (entity.Velocity.Z < 0)
|
||||||
|
{
|
||||||
|
TempBoundingBox = new BoundingBox(
|
||||||
|
new Vector3(entity.BoundingBox.Min.X, entity.BoundingBox.Min.Y, entity.BoundingBox.Min.Z + entity.Velocity.Z),
|
||||||
|
entity.BoundingBox.Max);
|
||||||
|
|
||||||
|
maxZ = (int)(TempBoundingBox.Max.Z);
|
||||||
|
minZ = (int)(TempBoundingBox.Min.Z + entity.Velocity.Z) - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TempBoundingBox = new BoundingBox(
|
||||||
|
entity.BoundingBox.Min,
|
||||||
|
new Vector3(entity.BoundingBox.Max.X, entity.BoundingBox.Max.Y, entity.BoundingBox.Max.Z + entity.Velocity.Z)
|
||||||
|
);
|
||||||
|
minZ = (int)(entity.BoundingBox.Min.Z);
|
||||||
|
maxZ = (int)(entity.BoundingBox.Max.Z + entity.Velocity.Z) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do terrain checks
|
||||||
|
double? collisionPoint = null;
|
||||||
|
BoundingBox blockBox;
|
||||||
|
for (int x = minX; x <= maxX; x++)
|
||||||
|
{
|
||||||
|
for (int y = minY; y <= maxY; y++)
|
||||||
|
{
|
||||||
|
for (int z = minZ; z <= maxZ; z++)
|
||||||
|
{
|
||||||
|
var position = new Coordinates3D(x, y, z);
|
||||||
|
var boundingBox = BlockPhysicsProvider.GetBoundingBox(world.World, position);
|
||||||
|
if (boundingBox == null)
|
||||||
|
continue;
|
||||||
|
blockBox = boundingBox.Value.OffsetBy(position + new Vector3(0.5));
|
||||||
|
if (TempBoundingBox.Intersects(blockBox))
|
||||||
|
{
|
||||||
|
if (entity.Velocity.Z < 0)
|
||||||
|
{
|
||||||
|
if (!collisionPoint.HasValue)
|
||||||
|
collisionPoint = blockBox.Max.Z;
|
||||||
|
else if (collisionPoint.Value < blockBox.Max.Z)
|
||||||
|
collisionPoint = blockBox.Max.Z;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!collisionPoint.HasValue)
|
||||||
|
collisionPoint = blockBox.Min.Z;
|
||||||
|
else if (collisionPoint.Value > blockBox.Min.Z)
|
||||||
|
collisionPoint = blockBox.Min.Z;
|
||||||
|
}
|
||||||
|
collision = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collisionPoint != null)
|
||||||
|
{
|
||||||
|
if (entity.Velocity.Z < 0)
|
||||||
|
{
|
||||||
|
entity.Velocity = new Vector3(
|
||||||
|
entity.Velocity.X,
|
||||||
|
entity.Velocity.Y,
|
||||||
|
entity.Velocity.Z - (TempBoundingBox.Min.Z - collisionPoint.Value));
|
||||||
|
collisionDirection = Vector3.Backwards;
|
||||||
|
}
|
||||||
|
else if (entity.Velocity.Z > 0)
|
||||||
|
{
|
||||||
|
entity.Velocity = new Vector3(
|
||||||
|
entity.Velocity.X,
|
||||||
|
entity.Velocity.Y,
|
||||||
|
entity.Velocity.Z - (TempBoundingBox.Max.Z - collisionPoint.Value));
|
||||||
|
collisionDirection = Vector3.Forwards;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ namespace TrueCraft.Client.Linux
|
|||||||
var client = new MultiplayerClient();
|
var client = new MultiplayerClient();
|
||||||
var game = new TrueCraftGame(client, ParseEndPoint(args[0]));
|
var game = new TrueCraftGame(client, ParseEndPoint(args[0]));
|
||||||
game.Run();
|
game.Run();
|
||||||
|
client.Disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IPEndPoint ParseEndPoint(string arg)
|
private static IPEndPoint ParseEndPoint(string arg)
|
||||||
|
@ -47,9 +47,16 @@ namespace TrueCraft.Client.Linux
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal IChunk FindChunk(Coordinates2D coordinates)
|
internal IChunk FindChunk(Coordinates2D coordinates)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return World.FindChunk(new Coordinates3D(coordinates.X, 0, coordinates.Z));
|
return World.FindChunk(new Coordinates3D(coordinates.X, 0, coordinates.Z));
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ReadOnlyChunk GetChunk(Coordinates2D coordinates)
|
public ReadOnlyChunk GetChunk(Coordinates2D coordinates)
|
||||||
{
|
{
|
||||||
@ -66,6 +73,11 @@ namespace TrueCraft.Client.Linux
|
|||||||
if (UnloadChunks)
|
if (UnloadChunks)
|
||||||
World.UnloadChunk(coordinates);
|
World.UnloadChunk(coordinates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsValidPosition(Coordinates3D coords)
|
||||||
|
{
|
||||||
|
return World.IsValidPosition(coords);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ReadOnlyChunk
|
public class ReadOnlyChunk
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
<OutputPath>bin\Client</OutputPath>
|
<OutputPath>bin\Client</OutputPath>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<Commandlineparameters>127.0.0.1:25565</Commandlineparameters>
|
<Commandlineparameters>127.0.0.1:25565</Commandlineparameters>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Server|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Server|AnyCPU' ">
|
||||||
<Optimize>false</Optimize>
|
<Optimize>false</Optimize>
|
||||||
@ -71,6 +72,7 @@
|
|||||||
<Compile Include="ReadOnlyWorld.cs" />
|
<Compile Include="ReadOnlyWorld.cs" />
|
||||||
<Compile Include="Events\ChunkEventArgs.cs" />
|
<Compile Include="Events\ChunkEventArgs.cs" />
|
||||||
<Compile Include="ChunkConverter.cs" />
|
<Compile Include="ChunkConverter.cs" />
|
||||||
|
<Compile Include="PhysicsEngine.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using TrueCraft.Client.Linux.Interface;
|
using TrueCraft.Client.Linux.Interface;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using TrueCraft.API;
|
||||||
|
|
||||||
namespace TrueCraft.Client.Linux
|
namespace TrueCraft.Client.Linux
|
||||||
{
|
{
|
||||||
@ -18,6 +19,7 @@ namespace TrueCraft.Client.Linux
|
|||||||
private SpriteBatch SpriteBatch { get; set; }
|
private SpriteBatch SpriteBatch { get; set; }
|
||||||
private IPEndPoint EndPoint { get; set; }
|
private IPEndPoint EndPoint { get; set; }
|
||||||
private ChunkConverter ChunkConverter { get; set; }
|
private ChunkConverter ChunkConverter { get; set; }
|
||||||
|
private DateTime NextPhysicsUpdate { get; set; }
|
||||||
|
|
||||||
public TrueCraftGame(MultiplayerClient client, IPEndPoint endPoint)
|
public TrueCraftGame(MultiplayerClient client, IPEndPoint endPoint)
|
||||||
{
|
{
|
||||||
@ -31,6 +33,7 @@ namespace TrueCraft.Client.Linux
|
|||||||
EndPoint = endPoint;
|
EndPoint = endPoint;
|
||||||
ChunkConverter = new ChunkConverter();
|
ChunkConverter = new ChunkConverter();
|
||||||
Client.ChunkLoaded += (sender, e) => ChunkConverter.QueueChunk(e.Chunk);
|
Client.ChunkLoaded += (sender, e) => ChunkConverter.QueueChunk(e.Chunk);
|
||||||
|
NextPhysicsUpdate = DateTime.MinValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Initialize()
|
protected override void Initialize()
|
||||||
@ -53,14 +56,44 @@ namespace TrueCraft.Client.Linux
|
|||||||
base.LoadContent();
|
base.LoadContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnExiting(object sender, EventArgs args)
|
||||||
|
{
|
||||||
|
ChunkConverter.Stop();
|
||||||
|
base.OnExiting(sender, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void UpdateKeyboard(GameTime gameTime, KeyboardState state)
|
||||||
|
{
|
||||||
|
if (state.IsKeyDown(Keys.Escape))
|
||||||
|
Exit();
|
||||||
|
// TODO: Handle rotation
|
||||||
|
// TODO: Rebindable keys
|
||||||
|
// TODO: Horizontal terrain collisions
|
||||||
|
TrueCraft.API.Vector3 delta = TrueCraft.API.Vector3.Zero;
|
||||||
|
if (state.IsKeyDown(Keys.Left) || state.IsKeyDown(Keys.A))
|
||||||
|
delta = TrueCraft.API.Vector3.Left;
|
||||||
|
if (state.IsKeyDown(Keys.Right) || state.IsKeyDown(Keys.D))
|
||||||
|
delta = TrueCraft.API.Vector3.Right;
|
||||||
|
if (state.IsKeyDown(Keys.Up) || state.IsKeyDown(Keys.W))
|
||||||
|
delta = TrueCraft.API.Vector3.Forwards;
|
||||||
|
if (state.IsKeyDown(Keys.Down) || state.IsKeyDown(Keys.S))
|
||||||
|
delta = TrueCraft.API.Vector3.Backwards;
|
||||||
|
Client.Position += delta * (gameTime.ElapsedGameTime.TotalSeconds * 4.3717); // Note: 4.3717 is the speed of a Minecraft player in m/s
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update(GameTime gameTime)
|
protected override void Update(GameTime gameTime)
|
||||||
{
|
{
|
||||||
if (Keyboard.GetState().IsKeyDown(Keys.Escape))
|
|
||||||
Exit();
|
|
||||||
foreach (var i in Interfaces)
|
foreach (var i in Interfaces)
|
||||||
{
|
{
|
||||||
i.Update(gameTime);
|
i.Update(gameTime);
|
||||||
}
|
}
|
||||||
|
if (NextPhysicsUpdate < DateTime.Now)
|
||||||
|
{
|
||||||
|
if (Client.World.FindChunk(new Coordinates2D((int)Client.Position.X, (int)Client.Position.Z)) != null)
|
||||||
|
Client.Physics.Update();
|
||||||
|
NextPhysicsUpdate = DateTime.Now.AddMilliseconds(1000 / 20);
|
||||||
|
}
|
||||||
|
UpdateKeyboard(gameTime, Keyboard.GetState());
|
||||||
base.Update(gameTime);
|
base.Update(gameTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ using TrueCraft.API.Entities;
|
|||||||
using TrueCraft.API;
|
using TrueCraft.API;
|
||||||
using TrueCraft.API.World;
|
using TrueCraft.API.World;
|
||||||
|
|
||||||
namespace TrueCraft
|
namespace TrueCraft.Core.Logic
|
||||||
{
|
{
|
||||||
public class BlockRepository : IBlockRepository, IBlockPhysicsProvider
|
public class BlockRepository : IBlockRepository, IBlockPhysicsProvider
|
||||||
{
|
{
|
||||||
@ -22,7 +22,7 @@ namespace TrueCraft
|
|||||||
BlockProviders[provider.ID] = provider;
|
BlockProviders[provider.ID] = provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void DiscoverBlockProviders()
|
public void DiscoverBlockProviders()
|
||||||
{
|
{
|
||||||
var providerTypes = new List<Type>();
|
var providerTypes = new List<Type>();
|
||||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
@ -320,6 +320,7 @@
|
|||||||
<Compile Include="Logic\Blocks\WallSignBlock.cs" />
|
<Compile Include="Logic\Blocks\WallSignBlock.cs" />
|
||||||
<Compile Include="Logic\Blocks\UprightSignBlock.cs" />
|
<Compile Include="Logic\Blocks\UprightSignBlock.cs" />
|
||||||
<Compile Include="TerrainGen\Decorators\LiquidDecorator.cs" />
|
<Compile Include="TerrainGen\Decorators\LiquidDecorator.cs" />
|
||||||
|
<Compile Include="Logic\BlockRepository.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -14,6 +14,7 @@ using TrueCraft.Core.World;
|
|||||||
using TrueCraft.API;
|
using TrueCraft.API;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using TrueCraft.API.Logging;
|
using TrueCraft.API.Logging;
|
||||||
|
using TrueCraft.Core.Logic;
|
||||||
|
|
||||||
namespace TrueCraft
|
namespace TrueCraft
|
||||||
{
|
{
|
||||||
|
@ -13,6 +13,7 @@ using TrueCraft.API;
|
|||||||
using TrueCraft.Core.Logging;
|
using TrueCraft.Core.Logging;
|
||||||
using TrueCraft.API.Logic;
|
using TrueCraft.API.Logic;
|
||||||
using TrueCraft.Exceptions;
|
using TrueCraft.Exceptions;
|
||||||
|
using TrueCraft.Core.Logic;
|
||||||
|
|
||||||
namespace TrueCraft
|
namespace TrueCraft
|
||||||
{
|
{
|
||||||
|
@ -69,12 +69,11 @@
|
|||||||
<Compile Include="Handlers\EntityHandlers.cs" />
|
<Compile Include="Handlers\EntityHandlers.cs" />
|
||||||
<Compile Include="Handlers\InteractionHandlers.cs" />
|
<Compile Include="Handlers\InteractionHandlers.cs" />
|
||||||
<Compile Include="Commands\DebugCommands.cs" />
|
<Compile Include="Commands\DebugCommands.cs" />
|
||||||
<Compile Include="BlockRepository.cs" />
|
|
||||||
<Compile Include="Exceptions\PlayerDisconnectException.cs" />
|
<Compile Include="Exceptions\PlayerDisconnectException.cs" />
|
||||||
<Compile Include="PhysicsEngine.cs" />
|
|
||||||
<Compile Include="ItemRepository.cs" />
|
<Compile Include="ItemRepository.cs" />
|
||||||
<Compile Include="CraftingRepository.cs" />
|
<Compile Include="CraftingRepository.cs" />
|
||||||
<Compile Include="Configuration.cs" />
|
<Compile Include="Configuration.cs" />
|
||||||
|
<Compile Include="PhysicsEngine.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
Reference in New Issue
Block a user