Modularise LocalPlayer.Physics into separate PhysicsComponent.

This commit is contained in:
UnknownShadow200 2016-03-31 21:25:25 +11:00
parent dbc2311435
commit 634b3213c3
14 changed files with 637 additions and 618 deletions

View File

@ -137,10 +137,10 @@ namespace ClassicalSharp.Gui {
bool speeding, halfSpeeding, noclip, fly;
int lastZoomFov;
void UpdateHackState( bool force ) {
LocalPlayer p = game.LocalPlayer;
if( force || p.speeding != speeding || p.halfSpeeding != halfSpeeding || p.noClip != noclip ||
p.flying != fly || game.ZoomFieldOfView != lastZoomFov ) {
speeding = p.speeding; halfSpeeding = p.halfSpeeding; noclip = p.noClip; fly = p.flying;
HacksComponent hacks = game.LocalPlayer.Hacks;
if( force || hacks.Speeding != speeding || hacks.HalfSpeeding != halfSpeeding || hacks.Noclip != noclip ||
hacks.Flying != fly || game.ZoomFieldOfView != lastZoomFov ) {
speeding = hacks.Speeding; halfSpeeding = hacks.HalfSpeeding; noclip = hacks.Noclip; fly = hacks.Flying;
lastZoomFov = game.ZoomFieldOfView;
int index = 0;
text.Clear();

View File

@ -31,7 +31,7 @@ namespace ClassicalSharp.Gui {
Make2( -1, 0, "Jump height", OnWidgetClick,
g => g.LocalPlayer.JumpHeight.ToString( "F3" ),
(g, v) => g.LocalPlayer.CalculateJumpVelocity( Single.Parse( v ) ) ),
(g, v) => g.LocalPlayer.physics.CalculateJumpVelocity( Single.Parse( v ) ) ),
MakeBool( -1, 50, "Double jump", OptionsKey.DoubleJump,
OnWidgetClick, g => g.LocalPlayer.Hacks.DoubleJump,

View File

@ -150,13 +150,13 @@
<Compile Include="Entities\Components\AnimatedComponent.cs" />
<Compile Include="Entities\Components\HacksComponent.cs" />
<Compile Include="Entities\Components\InterpolatedComponent.cs" />
<Compile Include="Entities\Components\CollisionsComponent.cs" />
<Compile Include="Entities\Components\PhysicsComponent.cs" />
<Compile Include="Entities\Components\ShadowComponent.cs" />
<Compile Include="Entities\Entity.Bounds.cs" />
<Compile Include="Entities\Entity.cs" />
<Compile Include="Entities\EntityList.cs" />
<Compile Include="Entities\LocalPlayer.cs" />
<Compile Include="Entities\LocalPlayer.Physics.cs" />
<Compile Include="Entities\LocationUpdate.cs" />
<Compile Include="Entities\NetPlayer.cs" />
<Compile Include="Events\EntityEvents.cs" />
@ -170,7 +170,7 @@
<Compile Include="Generator\NotchyGenerator.Utils.cs" />
<Compile Include="GraphicsAPI\OpenGLESApi.cs" />
<Compile Include="Map\Formats\MapCw.Nbt.cs" />
<Compile Include="Math\RayCaster.cs" />
<Compile Include="Math\RayTracer.cs" />
<Compile Include="Model\CustomModel.cs" />
<Compile Include="Model\HumanModels.cs" />
<Compile Include="Network\NetworkProcessor.CPECustom.cs" />

View File

@ -0,0 +1,331 @@
// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT
using System;
using OpenTK;
namespace ClassicalSharp.Entities {
/// <summary> Entity component that performs collision detection. </summary>
public sealed class CollisionsComponent {
Game game;
Entity entity;
BlockInfo info;
public CollisionsComponent( Game game, Entity entity ) {
this.game = game;
this.entity = entity;
info = game.BlockInfo;
}
internal bool hitYMax, collideX, collideY, collideZ;
/// <summary> Constant offset used to avoid floating point roundoff errors. </summary>
public const float Adjustment = 0.001f;
public byte GetPhysicsBlockId( int x, int y, int z ) {
if( x < 0 || x >= game.World.Width || z < 0 ||
z >= game.World.Length || y < 0 ) return (byte)Block.Bedrock;
if( y >= game.World.Height ) return (byte)Block.Air;
return game.World.GetBlock( x, y, z );
}
bool GetBoundingBox( byte block, int x, int y, int z, ref BoundingBox box ) {
if( info.Collide[block] != CollideType.Solid ) return false;
Add( x, y, z, ref info.MinBB[block], ref box.Min );
Add( x, y, z, ref info.MaxBB[block], ref box.Max );
return true;
}
static void Add( int x, int y, int z, ref Vector3 offset, ref Vector3 target ) {
target.X = x + offset.X;
target.Y = y + offset.Y;
target.Z = z + offset.Z;
}
// TODO: test for corner cases, and refactor this.
internal void MoveAndWallSlide() {
if( entity.Velocity == Vector3.Zero ) return;
Vector3 size = entity.CollisionSize;
BoundingBox entityBB, entityExtentBB;
int count = 0;
FindReachableBlocks( ref count, ref size, out entityBB, out entityExtentBB );
CollideWithReachableBlocks( count, ref size, ref entityBB, ref entityExtentBB );
}
void FindReachableBlocks( ref int count, ref Vector3 size,
out BoundingBox entityBB, out BoundingBox entityExtentBB ) {
Vector3 vel = entity.Velocity;
Vector3 pos = entity.Position;
entityBB = new BoundingBox(
pos.X - size.X / 2, pos.Y, pos.Z - size.Z / 2,
pos.X + size.X / 2, pos.Y + size.Y, pos.Z + size.Z / 2
);
// Exact maximum extent the entity can reach, and the equivalent map coordinates.
entityExtentBB = new BoundingBox(
vel.X < 0 ? entityBB.Min.X + vel.X : entityBB.Min.X,
vel.Y < 0 ? entityBB.Min.Y + vel.Y : entityBB.Min.Y,
vel.Z < 0 ? entityBB.Min.Z + vel.Z : entityBB.Min.Z,
vel.X > 0 ? entityBB.Max.X + vel.X : entityBB.Max.X,
vel.Y > 0 ? entityBB.Max.Y + vel.Y : entityBB.Max.Y,
vel.Z > 0 ? entityBB.Max.Z + vel.Z : entityBB.Max.Z
);
Vector3I min = Vector3I.Floor( entityExtentBB.Min );
Vector3I max = Vector3I.Floor( entityExtentBB.Max );
int elements = (max.X + 1 - min.X) * (max.Y + 1 - min.Y) * (max.Z + 1 - min.Z);
if( elements > stateCache.Length ) {
stateCache = new State[elements];
}
BoundingBox blockBB = default( BoundingBox );
// Order loops so that we minimise cache misses
for( int y = min.Y; y <= max.Y; y++ )
for( int z = min.Z; z <= max.Z; z++ )
for( int x = min.X; x <= max.X; x++ )
{
byte blockId = GetPhysicsBlockId( x, y, z );
if( !GetBoundingBox( blockId, x, y, z, ref blockBB ) ) continue;
if( !entityExtentBB.Intersects( blockBB ) ) continue; // necessary for non whole blocks. (slabs)
float tx = 0, ty = 0, tz = 0;
CalcTime( ref vel, ref entityBB, ref blockBB, out tx, out ty, out tz );
if( tx > 1 || ty > 1 || tz > 1 ) continue;
float tSquared = tx * tx + ty * ty + tz * tz;
stateCache[count++] = new State( x, y, z, blockId, tSquared );
}
}
void CollideWithReachableBlocks( int count, ref Vector3 size,
ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) {
bool wasOn = entity.onGround;
entity.onGround = false;
if( count > 0 )
QuickSort( stateCache, 0, count - 1 );
collideX = false; collideY = false; collideZ = false;
BoundingBox blockBB = default(BoundingBox);
for( int i = 0; i < count; i++ ) {
State state = stateCache[i];
Vector3 blockPos = new Vector3( state.X >> 3, state.Y >> 3, state.Z >> 3 );
int block = (state.X & 0x7) | (state.Y & 0x7) << 3 | (state.Z & 0x7) << 6;
blockBB.Min = blockPos + info.MinBB[block];
blockBB.Max = blockPos + info.MaxBB[block];
if( !entityExtentBB.Intersects( blockBB ) ) continue;
float tx = 0, ty = 0, tz = 0;
CalcTime( ref entity.Velocity, ref entityBB, ref blockBB, out tx, out ty, out tz );
if( tx > 1 || ty > 1 || tz > 1 )
Utils.LogDebug( "t > 1 in physics calculation.. this shouldn't have happened." );
BoundingBox finalBB = entityBB.Offset( entity.Velocity * new Vector3( tx, ty, tz ) );
// if we have hit the bottom of a block, we need to change the axis we test first.
if( hitYMax ) {
if( finalBB.Min.Y + Adjustment >= blockBB.Max.Y )
ClipYMax( ref blockBB, ref entityBB, ref entityExtentBB, ref size );
else if( finalBB.Max.Y - Adjustment <= blockBB.Min.Y )
ClipYMin( ref blockBB, ref entityBB, ref entityExtentBB, ref size );
else if( finalBB.Min.X + Adjustment >= blockBB.Max.X )
ClipXMax( ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size );
else if( finalBB.Max.X - Adjustment <= blockBB.Min.X )
ClipXMin( ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size );
else if( finalBB.Min.Z + Adjustment >= blockBB.Max.Z )
ClipZMax( ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size );
else if( finalBB.Max.Z - Adjustment <= blockBB.Min.Z )
ClipZMin( ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size );
continue;
}
// if flying or falling, test the horizontal axes first.
if( finalBB.Min.X + Adjustment >= blockBB.Max.X )
ClipXMax( ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size );
else if( finalBB.Max.X - Adjustment <= blockBB.Min.X )
ClipXMin( ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size );
else if( finalBB.Min.Z + Adjustment >= blockBB.Max.Z )
ClipZMax( ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size );
else if( finalBB.Max.Z - Adjustment <= blockBB.Min.Z )
ClipZMin( ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size );
else if( finalBB.Min.Y + Adjustment >= blockBB.Max.Y )
ClipYMax( ref blockBB, ref entityBB, ref entityExtentBB, ref size );
else if( finalBB.Max.Y - Adjustment <= blockBB.Min.Y )
ClipYMin( ref blockBB, ref entityBB, ref entityExtentBB, ref size );
}
}
void ClipXMin( ref BoundingBox blockBB, ref BoundingBox entityBB, bool wasOn,
BoundingBox finalBB, ref BoundingBox entityExtentBB, ref Vector3 size ) {
if( !wasOn || !DidSlide( blockBB, ref size, finalBB, ref entityBB, ref entityExtentBB ) ) {
entity.Position.X = blockBB.Min.X - size.X / 2 - Adjustment;
ClipX( ref size, ref entityBB, ref entityExtentBB );
}
}
void ClipXMax( ref BoundingBox blockBB, ref BoundingBox entityBB, bool wasOn,
BoundingBox finalBB, ref BoundingBox entityExtentBB, ref Vector3 size ) {
if( !wasOn || !DidSlide( blockBB, ref size, finalBB, ref entityBB, ref entityExtentBB ) ) {
entity.Position.X = blockBB.Max.X + size.X / 2 + Adjustment;
ClipX( ref size, ref entityBB, ref entityExtentBB );
}
}
void ClipZMax( ref BoundingBox blockBB, ref BoundingBox entityBB, bool wasOn,
BoundingBox finalBB, ref BoundingBox entityExtentBB, ref Vector3 size ) {
if( !wasOn || !DidSlide( blockBB, ref size, finalBB, ref entityBB, ref entityExtentBB ) ) {
entity.Position.Z = blockBB.Max.Z + size.Z / 2 + Adjustment;
ClipZ( ref size, ref entityBB, ref entityExtentBB );
}
}
void ClipZMin( ref BoundingBox blockBB, ref BoundingBox entityBB, bool wasOn,
BoundingBox finalBB, ref BoundingBox extentBB, ref Vector3 size ) {
if( !wasOn || !DidSlide( blockBB, ref size, finalBB, ref entityBB, ref extentBB ) ) {
entity.Position.Z = blockBB.Min.Z - size.Z / 2 - Adjustment;
ClipZ( ref size, ref entityBB, ref extentBB );
}
}
void ClipYMin( ref BoundingBox blockBB, ref BoundingBox entityBB,
ref BoundingBox extentBB, ref Vector3 size ) {
entity.Position.Y = blockBB.Min.Y - size.Y - Adjustment;
ClipY( ref size, ref entityBB, ref extentBB );
hitYMax = false;
}
void ClipYMax( ref BoundingBox blockBB, ref BoundingBox entityBB,
ref BoundingBox extentBB, ref Vector3 size ) {
entity.Position.Y = blockBB.Max.Y + Adjustment;
entity.onGround = true;
ClipY( ref size, ref entityBB, ref extentBB );
hitYMax = true;
}
bool DidSlide( BoundingBox blockBB, ref Vector3 size, BoundingBox finalBB,
ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) {
float yDist = blockBB.Max.Y - entityBB.Min.Y;
if( yDist > 0 && yDist <= entity.StepSize + 0.01f ) {
float blockXMin = blockBB.Min.X, blockZMin = blockBB.Min.Z;
blockBB.Min.X = Math.Max( blockBB.Min.X, blockBB.Max.X - size.X / 2 );
blockBB.Max.X = Math.Min( blockBB.Max.X, blockXMin + size.X / 2 );
blockBB.Min.Z = Math.Max( blockBB.Min.Z, blockBB.Max.Z - size.Z / 2 );
blockBB.Max.Z = Math.Min( blockBB.Max.Z, blockZMin + size.Z / 2 );
BoundingBox adjBB = finalBB;
adjBB.Min.X = Math.Min( finalBB.Min.X, blockBB.Min.X + Adjustment );
adjBB.Max.X = Math.Max( finalBB.Max.X, blockBB.Max.X - Adjustment );
adjBB.Min.Y = blockBB.Max.Y + Adjustment;
adjBB.Max.Y = adjBB.Min.Y + size.Y;
adjBB.Min.Z = Math.Min( finalBB.Min.Z, blockBB.Min.Z + Adjustment );
adjBB.Max.Z = Math.Max( finalBB.Max.Z, blockBB.Max.Z - Adjustment );
if( !CanSlideThrough( ref adjBB ) )
return false;
entity.Position.Y = blockBB.Max.Y + Adjustment;
entity.onGround = true;
ClipY( ref size, ref entityBB, ref entityExtentBB );
return true;
}
return false;
}
bool CanSlideThrough( ref BoundingBox adjFinalBB ) {
Vector3I bbMin = Vector3I.Floor( adjFinalBB.Min );
Vector3I bbMax = Vector3I.Floor( adjFinalBB.Max );
for( int y = bbMin.Y; y <= bbMax.Y; y++ )
for( int z = bbMin.Z; z <= bbMax.Z; z++ )
for( int x = bbMin.X; x <= bbMax.X; x++ )
{
byte block = GetPhysicsBlockId( x, y, z );
Vector3 min = new Vector3( x, y, z ) + info.MinBB[block];
Vector3 max = new Vector3( x, y, z ) + info.MaxBB[block];
BoundingBox blockBB = new BoundingBox( min, max );
if( !blockBB.Intersects( adjFinalBB ) )
continue;
if( info.Collide[GetPhysicsBlockId( x, y, z )] == CollideType.Solid )
return false;
}
return true;
}
void ClipX( ref Vector3 size, ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) {
entity.Velocity.X = 0;
entityBB.Min.X = entityExtentBB.Min.X = entity.Position.X - size.X / 2;
entityBB.Max.X = entityExtentBB.Max.X = entity.Position.X + size.X / 2;
collideX = true;
}
void ClipY( ref Vector3 size, ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) {
entity.Velocity.Y = 0;
entityBB.Min.Y = entityExtentBB.Min.Y = entity.Position.Y;
entityBB.Max.Y = entityExtentBB.Max.Y = entity.Position.Y + size.Y;
collideY = true;
}
void ClipZ( ref Vector3 size, ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) {
entity.Velocity.Z = 0;
entityBB.Min.Z = entityExtentBB.Min.Z = entity.Position.Z - size.Z / 2;
entityBB.Max.Z = entityExtentBB.Max.Z = entity.Position.Z + size.Z / 2;
collideZ = true;
}
static void CalcTime( ref Vector3 vel, ref BoundingBox entityBB, ref BoundingBox blockBB,
out float tx, out float ty, out float tz ) {
float dx = vel.X > 0 ? blockBB.Min.X - entityBB.Max.X : entityBB.Min.X - blockBB.Max.X;
float dy = vel.Y > 0 ? blockBB.Min.Y - entityBB.Max.Y : entityBB.Min.Y - blockBB.Max.Y;
float dz = vel.Z > 0 ? blockBB.Min.Z - entityBB.Max.Z : entityBB.Min.Z - blockBB.Max.Z;
tx = vel.X == 0 ? float.PositiveInfinity : Math.Abs( dx / vel.X );
ty = vel.Y == 0 ? float.PositiveInfinity : Math.Abs( dy / vel.Y );
tz = vel.Z == 0 ? float.PositiveInfinity : Math.Abs( dz / vel.Z );
if( entityBB.XIntersects( blockBB ) ) tx = 0;
if( entityBB.YIntersects( blockBB ) ) ty = 0;
if( entityBB.ZIntersects( blockBB ) ) tz = 0;
}
struct State {
public int X, Y, Z;
public float tSquared;
public State( int x, int y, int z, byte block, float tSquared ) {
X = x << 3; Y = y << 3; Z = z << 3;
X |= (block & 0x07);
Y |= (block & 0x38) >> 3;
Z |= (block & 0xC0) >> 6;
this.tSquared = tSquared;
}
}
static State[] stateCache = new State[0];
static void QuickSort( State[] keys, int left, int right ) {
while( left < right ) {
int i = left, j = right;
float pivot = keys[(i + j) / 2].tSquared;
// partition the list
while( i <= j ) {
while( pivot > keys[i].tSquared ) i++;
while( pivot < keys[j].tSquared ) j--;
if( i <= j ) {
State key = keys[i]; keys[i] = keys[j]; keys[j] = key;
i++; j--;
}
}
// recurse into the smaller subset
if( j - left <= right - i ) {
if( left < j )
QuickSort( keys, left, j );
left = i;
} else {
if( i < right )
QuickSort( keys, i, right );
right = j;
}
}
}
}
}

View File

@ -28,42 +28,44 @@ namespace ClassicalSharp.Entities {
/// <summary> Whether the player has allowed hacks usage as an option.
/// Note that all 'can use X' set by the server override this. </summary>
public bool Enabled = true;
/// <summary> Whether the player is allowed to use any type of hacks. </summary>
public bool CanAnyHacks = true;
/// <summary> Whether the player is allowed to use the types of cameras that use third person. </summary>
public bool CanUseThirdPersonCamera = true;
/// <summary> Whether the player is allowed to increase its speed beyond the normal walking speed. </summary>
public bool CanSpeed = true;
/// <summary> Whether the player is allowed to fly in the world. </summary>
public bool CanFly = true;
/// <summary> Whether the player is allowed to teleport to their respawn coordinates. </summary>
public bool CanRespawn = true;
/// <summary> Whether the player is allowed to pass through all blocks. </summary>
public bool CanNoclip = true;
/// <summary> Whether the player is allowed to use pushback block placing. </summary>
public bool CanPushbackBlocks = true;
/// <summary> Whether the player is allowed to see all entity name tags. </summary>
public bool CanSeeAllNames = true;
/// <summary> Whether the player is allowed to double jump. </summary>
public bool CanDoubleJump = true;
/// <summary> Maximum speed the entity can move at horizontally when CanSpeed is false. </summary>
public float MaxSpeedMultiplier = 1;
/// <summary> Whether the player should slide after letting go of movement buttons in noclip. </summary>
public bool NoclipSlide = true;
/// <summary> Whether the player has allowed the usage of fast double jumping abilities. </summary>
public bool DoubleJump = false;
/// <summary> Whether the player is allowed to double jump. </summary>
public bool CanDoubleJump = true;
/// <summary> Maximum speed the entity can move at horizontally when CanSpeed is false. </summary>
public float MaxSpeedMultiplier = 1;
/// <summary> Whether the player currently has noclip on. </summary>
public bool Noclip;
/// <summary> Whether the player currently has fly mode active. </summary>
public bool Flying;
/// <summary> Whether the player is currently flying upwards. </summary>
public bool FlyingUp;
/// <summary> Whether the player is currently flying downwards. </summary>
public bool FlyingDown;
/// <summary> Whether the player is currently walking at base speed * speed multiplier. </summary>
public bool Speeding;
/// <summary> Whether the player is currently walking at base speed * 0.5 * speed multiplier. </summary>
public bool HalfSpeeding;
/// <summary> Parses hack flags specified in the motd and/or name of the server. </summary>
/// <remarks> Recognises +/-hax, +/-fly, +/-noclip, +/-speed, +/-respawn, +/-ophax, and horspeed=xyz </remarks>
@ -124,5 +126,16 @@ namespace ClassicalSharp.Entities {
inv.CanPlace[(int)Block.StillLava] = value == 0x64;
CanSeeAllNames = value == 0x64;
}
/// <summary> Disables any hacks if their respective CanHackX value is set to false. </summary>
public void CheckHacksConsistency() {
if( !CanFly || !Enabled ) { Flying = false; FlyingDown = false; FlyingUp = false; }
if( !CanNoclip || !Enabled ) Noclip = false;
if( !CanSpeed || !Enabled ) { Speeding = false; HalfSpeeding = false; }
CanDoubleJump = CanAnyHacks && Enabled && CanSpeed;
if( !CanUseThirdPersonCamera || !Enabled )
game.CycleCamera();
}
}
}

View File

@ -7,325 +7,257 @@ namespace ClassicalSharp.Entities {
/// <summary> Entity component that performs collision detection. </summary>
public sealed class PhysicsComponent {
Game game;
bool useLiquidGravity = false; // used by BlockDefinitions.
bool canLiquidJump = true;
internal bool firstJump, secondJump, jumping;
Entity entity;
Game game;
BlockInfo info;
internal float jumpVel = 0.42f, serverJumpVel = 0.42f;
internal HacksComponent hacks;
internal CollisionsComponent collisions;
public PhysicsComponent( Game game, Entity entity ) {
this.game = game;
this.entity = entity;
info = game.BlockInfo;
}
internal bool hitYMax, collideX, collideY, collideZ;
public void UpdateVelocityState( float xMoving, float zMoving ) {
if( !hacks.NoclipSlide && (hacks.Noclip && xMoving == 0 && zMoving == 0) )
entity.Velocity = Vector3.Zero;
if( hacks.Flying || hacks.Noclip ) {
entity.Velocity.Y = 0; // eliminate the effect of gravity
int dir = (hacks.FlyingUp || jumping) ? 1 : (hacks.FlyingDown ? -1 : 0);
/// <summary> Constant offset used to avoid floating point roundoff errors. </summary>
public const float Adjustment = 0.001f;
public byte GetPhysicsBlockId( int x, int y, int z ) {
if( x < 0 || x >= game.World.Width || z < 0 ||
z >= game.World.Length || y < 0 ) return (byte)Block.Bedrock;
if( y >= game.World.Height ) return (byte)Block.Air;
return game.World.GetBlock( x, y, z );
}
bool GetBoundingBox( byte block, int x, int y, int z, ref BoundingBox box ) {
if( info.Collide[block] != CollideType.Solid ) return false;
Add( x, y, z, ref info.MinBB[block], ref box.Min );
Add( x, y, z, ref info.MaxBB[block], ref box.Max );
return true;
}
static void Add( int x, int y, int z, ref Vector3 offset, ref Vector3 target ) {
target.X = x + offset.X;
target.Y = y + offset.Y;
target.Z = z + offset.Z;
}
// TODO: test for corner cases, and refactor this.
internal void MoveAndWallSlide() {
if( entity.Velocity == Vector3.Zero ) return;
Vector3 size = entity.CollisionSize;
BoundingBox entityBB, entityExtentBB;
int count = 0;
FindReachableBlocks( ref count, ref size, out entityBB, out entityExtentBB );
CollideWithReachableBlocks( count, ref size, ref entityBB, ref entityExtentBB );
}
void FindReachableBlocks( ref int count, ref Vector3 size,
out BoundingBox entityBB, out BoundingBox entityExtentBB ) {
Vector3 vel = entity.Velocity;
Vector3 pos = entity.Position;
entityBB = new BoundingBox(
pos.X - size.X / 2, pos.Y, pos.Z - size.Z / 2,
pos.X + size.X / 2, pos.Y + size.Y, pos.Z + size.Z / 2
);
// Exact maximum extent the entity can reach, and the equivalent map coordinates.
entityExtentBB = new BoundingBox(
vel.X < 0 ? entityBB.Min.X + vel.X : entityBB.Min.X,
vel.Y < 0 ? entityBB.Min.Y + vel.Y : entityBB.Min.Y,
vel.Z < 0 ? entityBB.Min.Z + vel.Z : entityBB.Min.Z,
vel.X > 0 ? entityBB.Max.X + vel.X : entityBB.Max.X,
vel.Y > 0 ? entityBB.Max.Y + vel.Y : entityBB.Max.Y,
vel.Z > 0 ? entityBB.Max.Z + vel.Z : entityBB.Max.Z
);
Vector3I min = Vector3I.Floor( entityExtentBB.Min );
Vector3I max = Vector3I.Floor( entityExtentBB.Max );
int elements = (max.X + 1 - min.X) * (max.Y + 1 - min.Y) * (max.Z + 1 - min.Z);
if( elements > stateCache.Length ) {
stateCache = new State[elements];
entity.Velocity.Y += 0.12f * dir;
if( hacks.Speeding && hacks.CanSpeed ) entity.Velocity.Y += 0.12f * dir;
if( hacks.HalfSpeeding && hacks.CanSpeed ) entity.Velocity.Y += 0.06f * dir;
} else if( jumping && entity.TouchesAnyRope() && entity.Velocity.Y > 0.02f ) {
entity.Velocity.Y = 0.02f;
}
BoundingBox blockBB = default( BoundingBox );
// Order loops so that we minimise cache misses
for( int y = min.Y; y <= max.Y; y++ )
for( int z = min.Z; z <= max.Z; z++ )
for( int x = min.X; x <= max.X; x++ )
{
byte blockId = GetPhysicsBlockId( x, y, z );
if( !GetBoundingBox( blockId, x, y, z, ref blockBB ) ) continue;
if( !entityExtentBB.Intersects( blockBB ) ) continue; // necessary for non whole blocks. (slabs)
float tx = 0, ty = 0, tz = 0;
CalcTime( ref vel, ref entityBB, ref blockBB, out tx, out ty, out tz );
if( tx > 1 || ty > 1 || tz > 1 ) continue;
float tSquared = tx * tx + ty * ty + tz * tz;
stateCache[count++] = new State( x, y, z, blockId, tSquared );
if( !jumping ) {
canLiquidJump = false; return;
}
}
void CollideWithReachableBlocks( int count, ref Vector3 size,
ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) {
bool wasOn = entity.onGround;
entity.onGround = false;
if( count > 0 )
QuickSort( stateCache, 0, count - 1 );
collideX = false; collideY = false; collideZ = false;
BoundingBox blockBB = default(BoundingBox);
bool touchWater = entity.TouchesAnyWater();
bool touchLava = entity.TouchesAnyLava();
if( touchWater || touchLava ) {
BoundingBox bounds = entity.CollisionBounds;
int feetY = Utils.Floor( bounds.Min.Y ), bodyY = feetY + 1;
int headY = Utils.Floor( bounds.Max.Y );
if( bodyY > headY ) bodyY = headY;
for( int i = 0; i < count; i++ ) {
State state = stateCache[i];
Vector3 blockPos = new Vector3( state.X >> 3, state.Y >> 3, state.Z >> 3 );
int block = (state.X & 0x7) | (state.Y & 0x7) << 3 | (state.Z & 0x7) << 6;
blockBB.Min = blockPos + info.MinBB[block];
blockBB.Max = blockPos + info.MaxBB[block];
if( !entityExtentBB.Intersects( blockBB ) ) continue;
bounds.Max.Y = bounds.Min.Y = feetY;
bool liquidFeet = entity.TouchesAny( bounds, StandardLiquid );
bounds.Min.Y = Math.Min( bodyY, headY );
bounds.Max.Y = Math.Max( bodyY, headY );
bool liquidRest = entity.TouchesAny( bounds, StandardLiquid );
float tx = 0, ty = 0, tz = 0;
CalcTime( ref entity.Velocity, ref entityBB, ref blockBB, out tx, out ty, out tz );
if( tx > 1 || ty > 1 || tz > 1 )
Utils.LogDebug( "t > 1 in physics calculation.. this shouldn't have happened." );
BoundingBox finalBB = entityBB.Offset( entity.Velocity * new Vector3( tx, ty, tz ) );
// if we have hit the bottom of a block, we need to change the axis we test first.
if( hitYMax ) {
if( finalBB.Min.Y + Adjustment >= blockBB.Max.Y )
ClipYMax( ref blockBB, ref entityBB, ref entityExtentBB, ref size );
else if( finalBB.Max.Y - Adjustment <= blockBB.Min.Y )
ClipYMin( ref blockBB, ref entityBB, ref entityExtentBB, ref size );
else if( finalBB.Min.X + Adjustment >= blockBB.Max.X )
ClipXMax( ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size );
else if( finalBB.Max.X - Adjustment <= blockBB.Min.X )
ClipXMin( ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size );
else if( finalBB.Min.Z + Adjustment >= blockBB.Max.Z )
ClipZMax( ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size );
else if( finalBB.Max.Z - Adjustment <= blockBB.Min.Z )
ClipZMin( ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size );
continue;
bool pastJumpPoint = liquidFeet && !liquidRest && (entity.Position.Y % 1 >= 0.4);
if( !pastJumpPoint ) {
canLiquidJump = true;
entity.Velocity.Y += 0.04f;
if( hacks.Speeding && hacks.CanSpeed ) entity.Velocity.Y += 0.04f;
if( hacks.HalfSpeeding && hacks.CanSpeed ) entity.Velocity.Y += 0.02f;
} else if( pastJumpPoint ) {
// either A) jump bob in water B) climb up solid on side
if( canLiquidJump || (collisions.collideX || collisions.collideZ) )
entity.Velocity.Y += touchLava ? 0.20f : 0.10f;
canLiquidJump = false;
}
// if flying or falling, test the horizontal axes first.
if( finalBB.Min.X + Adjustment >= blockBB.Max.X )
ClipXMax( ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size );
else if( finalBB.Max.X - Adjustment <= blockBB.Min.X )
ClipXMin( ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size );
else if( finalBB.Min.Z + Adjustment >= blockBB.Max.Z )
ClipZMax( ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size );
else if( finalBB.Max.Z - Adjustment <= blockBB.Min.Z )
ClipZMin( ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size );
else if( finalBB.Min.Y + Adjustment >= blockBB.Max.Y )
ClipYMax( ref blockBB, ref entityBB, ref entityExtentBB, ref size );
else if( finalBB.Max.Y - Adjustment <= blockBB.Min.Y )
ClipYMin( ref blockBB, ref entityBB, ref entityExtentBB, ref size );
} else if( useLiquidGravity ) {
entity.Velocity.Y += 0.04f;
if( hacks.Speeding && hacks.CanSpeed ) entity.Velocity.Y += 0.04f;
if( hacks.HalfSpeeding && hacks.CanSpeed ) entity.Velocity.Y += 0.02f;
canLiquidJump = false;
} else if( entity.TouchesAnyRope() ) {
entity.Velocity.Y += (hacks.Speeding && hacks.CanSpeed) ? 0.15f : 0.10f;
canLiquidJump = false;
} else if( entity.onGround ) {
DoNormalJump();
}
}
void ClipXMin( ref BoundingBox blockBB, ref BoundingBox entityBB, bool wasOn,
BoundingBox finalBB, ref BoundingBox entityExtentBB, ref Vector3 size ) {
if( !wasOn || !DidSlide( blockBB, ref size, finalBB, ref entityBB, ref entityExtentBB ) ) {
entity.Position.X = blockBB.Min.X - size.X / 2 - Adjustment;
ClipX( ref size, ref entityBB, ref entityExtentBB );
public void DoNormalJump() {
entity.Velocity.Y = jumpVel;
if( hacks.Speeding && hacks.CanSpeed ) entity.Velocity.Y += jumpVel;
if( hacks.HalfSpeeding && hacks.CanSpeed ) entity.Velocity.Y += jumpVel / 2;
canLiquidJump = false;
}
bool StandardLiquid( byte block ) {
return info.Collide[block] == CollideType.SwimThrough;
}
static Vector3 waterDrag = new Vector3( 0.8f, 0.8f, 0.8f ),
lavaDrag = new Vector3( 0.5f, 0.5f, 0.5f ),
ropeDrag = new Vector3( 0.5f, 0.85f, 0.5f ),
normalDrag = new Vector3( 0.91f, 0.98f, 0.91f ),
airDrag = new Vector3( 0.6f, 1f, 0.6f );
const float liquidGrav = 0.02f, ropeGrav = 0.034f, normalGrav = 0.08f;
public void PhysicsTick( float xMoving, float zMoving ) {
if( hacks.Noclip ) entity.onGround = false;
float multiply = GetBaseMultiply( true );
float yMultiply = GetBaseMultiply( hacks.CanSpeed );
float modifier = LowestSpeedModifier();
float yMul = Math.Max( 1f, yMultiply / 5 ) * modifier;
float horMul = multiply * modifier;
if( !(hacks.Flying || hacks.Noclip) ) {
if( secondJump ) { horMul *= 93f; yMul *= 10f; }
else if( firstJump ) { horMul *= 46.5f; yMul *= 7.5f; }
}
}
void ClipXMax( ref BoundingBox blockBB, ref BoundingBox entityBB, bool wasOn,
BoundingBox finalBB, ref BoundingBox entityExtentBB, ref Vector3 size ) {
if( !wasOn || !DidSlide( blockBB, ref size, finalBB, ref entityBB, ref entityExtentBB ) ) {
entity.Position.X = blockBB.Max.X + size.X / 2 + Adjustment;
ClipX( ref size, ref entityBB, ref entityExtentBB );
if( entity.TouchesAnyWater() && !hacks.Flying && !hacks.Noclip ) {
MoveNormal( xMoving, zMoving, 0.02f * horMul, waterDrag, liquidGrav, yMul );
} else if( entity.TouchesAnyLava() && !hacks.Flying && !hacks.Noclip ) {
MoveNormal( xMoving, zMoving, 0.02f * horMul, lavaDrag, liquidGrav, yMul );
} else if( entity.TouchesAnyRope() && !hacks.Flying && !hacks.Noclip ) {
MoveNormal( xMoving, zMoving, 0.02f * 1.7f, ropeDrag, ropeGrav, yMul );
} else {
float factor = !(hacks.Flying || hacks.Noclip) && entity.onGround ? 0.1f : 0.02f;
float gravity = useLiquidGravity ? liquidGrav : normalGrav;
if( hacks.Flying || hacks.Noclip )
MoveFlying( xMoving, zMoving, factor * horMul, normalDrag, gravity, yMul );
else
MoveNormal( xMoving, zMoving, factor * horMul, normalDrag, gravity, yMul );
if( entity.BlockUnderFeet == Block.Ice && !(hacks.Flying || hacks.Noclip) ) {
// limit components to +-0.25f by rescaling vector to [-0.25, 0.25]
if( Math.Abs( entity.Velocity.X ) > 0.25f || Math.Abs( entity.Velocity.Z ) > 0.25f ) {
float scale = Math.Min(
Math.Abs( 0.25f / entity.Velocity.X ), Math.Abs( 0.25f / entity.Velocity.Z ) );
entity.Velocity.X *= scale;
entity.Velocity.Z *= scale;
}
} else if( entity.onGround || hacks.Flying ) {
entity.Velocity *= airDrag; // air drag or ground friction
}
}
if( entity.onGround ) { firstJump = false; secondJump = false; }
}
void ClipZMax( ref BoundingBox blockBB, ref BoundingBox entityBB, bool wasOn,
BoundingBox finalBB, ref BoundingBox entityExtentBB, ref Vector3 size ) {
if( !wasOn || !DidSlide( blockBB, ref size, finalBB, ref entityBB, ref entityExtentBB ) ) {
entity.Position.Z = blockBB.Max.Z + size.Z / 2 + Adjustment;
ClipZ( ref size, ref entityBB, ref entityExtentBB );
void AdjHeadingVelocity( float x, float z, float factor ) {
float dist = (float)Math.Sqrt( x * x + z * z );
if( dist < 0.00001f ) return;
if( dist < 1 ) dist = 1;
float multiply = factor / dist;
entity.Velocity += Utils.RotateY( x * multiply, 0, z * multiply, entity.HeadYawRadians );
}
void MoveFlying( float xMoving, float zMoving, float factor, Vector3 drag, float gravity, float yMul ) {
AdjHeadingVelocity( zMoving, xMoving, factor );
float yVel = (float)Math.Sqrt( entity.Velocity.X * entity.Velocity.X + entity.Velocity.Z * entity.Velocity.Z );
// make vertical speed the same as vertical speed.
if( (xMoving != 0 || zMoving != 0) && yVel > 0.001f ) {
entity.Velocity.Y = 0;
yMul = 1;
if( hacks.FlyingUp || jumping ) entity.Velocity.Y += yVel;
if( hacks.FlyingDown ) entity.Velocity.Y -= yVel;
}
Move( xMoving, zMoving, factor, drag, gravity, yMul );
}
void ClipZMin( ref BoundingBox blockBB, ref BoundingBox entityBB, bool wasOn,
BoundingBox finalBB, ref BoundingBox extentBB, ref Vector3 size ) {
if( !wasOn || !DidSlide( blockBB, ref size, finalBB, ref entityBB, ref extentBB ) ) {
entity.Position.Z = blockBB.Min.Z - size.Z / 2 - Adjustment;
ClipZ( ref size, ref entityBB, ref extentBB );
void MoveNormal( float xMoving, float zMoving, float factor, Vector3 drag, float gravity, float yMul ) {
AdjHeadingVelocity( zMoving, xMoving, factor );
Move( xMoving, zMoving, factor, drag, gravity, yMul );
}
void Move( float xMoving, float zMoving, float factor, Vector3 drag, float gravity, float yMul ) {
entity.Velocity.Y *= yMul;
if( !hacks.Noclip )
collisions.MoveAndWallSlide();
entity.Position += entity.Velocity;
entity.Velocity.Y /= yMul;
entity.Velocity *= drag;
entity.Velocity.Y -= gravity;
}
float GetBaseMultiply( bool canSpeed ) {
float multiply = 0;
if( hacks.Flying || hacks.Noclip ) {
if( hacks.Speeding && canSpeed ) multiply += hacks.SpeedMultiplier * 8;
if( hacks.HalfSpeeding && canSpeed ) multiply += hacks.SpeedMultiplier * 8 / 2;
if( multiply == 0 ) multiply = 8f;
} else {
if( hacks.Speeding && canSpeed ) multiply += hacks.SpeedMultiplier;
if( hacks.HalfSpeeding && canSpeed ) multiply += hacks.SpeedMultiplier / 2;
if( multiply == 0 ) multiply = 1;
}
return hacks.CanSpeed ? multiply : Math.Min( multiply, hacks.MaxSpeedMultiplier );
}
void ClipYMin( ref BoundingBox blockBB, ref BoundingBox entityBB,
ref BoundingBox extentBB, ref Vector3 size ) {
entity.Position.Y = blockBB.Min.Y - size.Y - Adjustment;
ClipY( ref size, ref entityBB, ref extentBB );
hitYMax = false;
const float inf = float.PositiveInfinity;
float LowestSpeedModifier() {
BoundingBox bounds = entity.CollisionBounds;
useLiquidGravity = false;
float baseModifier = LowestModifier( bounds, false );
bounds.Min.Y -= 0.5f/16f; // also check block standing on
float solidModifier = LowestModifier( bounds, true );
if( baseModifier == inf && solidModifier == inf ) return 1;
return baseModifier == inf ? solidModifier : baseModifier;
}
void ClipYMax( ref BoundingBox blockBB, ref BoundingBox entityBB,
ref BoundingBox extentBB, ref Vector3 size ) {
entity.Position.Y = blockBB.Max.Y + Adjustment;
entity.onGround = true;
ClipY( ref size, ref entityBB, ref extentBB );
hitYMax = true;
}
bool DidSlide( BoundingBox blockBB, ref Vector3 size, BoundingBox finalBB,
ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) {
float yDist = blockBB.Max.Y - entityBB.Min.Y;
if( yDist > 0 && yDist <= entity.StepSize + 0.01f ) {
float blockXMin = blockBB.Min.X, blockZMin = blockBB.Min.Z;
blockBB.Min.X = Math.Max( blockBB.Min.X, blockBB.Max.X - size.X / 2 );
blockBB.Max.X = Math.Min( blockBB.Max.X, blockXMin + size.X / 2 );
blockBB.Min.Z = Math.Max( blockBB.Min.Z, blockBB.Max.Z - size.Z / 2 );
blockBB.Max.Z = Math.Min( blockBB.Max.Z, blockZMin + size.Z / 2 );
BoundingBox adjBB = finalBB;
adjBB.Min.X = Math.Min( finalBB.Min.X, blockBB.Min.X + Adjustment );
adjBB.Max.X = Math.Max( finalBB.Max.X, blockBB.Max.X - Adjustment );
adjBB.Min.Y = blockBB.Max.Y + Adjustment;
adjBB.Max.Y = adjBB.Min.Y + size.Y;
adjBB.Min.Z = Math.Min( finalBB.Min.Z, blockBB.Min.Z + Adjustment );
adjBB.Max.Z = Math.Max( finalBB.Max.Z, blockBB.Max.Z - Adjustment );
if( !CanSlideThrough( ref adjBB ) )
return false;
entity.Position.Y = blockBB.Max.Y + Adjustment;
entity.onGround = true;
ClipY( ref size, ref entityBB, ref entityExtentBB );
return true;
}
return false;
}
bool CanSlideThrough( ref BoundingBox adjFinalBB ) {
Vector3I bbMin = Vector3I.Floor( adjFinalBB.Min );
Vector3I bbMax = Vector3I.Floor( adjFinalBB.Max );
float LowestModifier( BoundingBox bounds, bool checkSolid ) {
Vector3I bbMin = Vector3I.Floor( bounds.Min );
Vector3I bbMax = Vector3I.Floor( bounds.Max );
float modifier = inf;
for( int y = bbMin.Y; y <= bbMax.Y; y++ )
for( int z = bbMin.Z; z <= bbMax.Z; z++ )
for( int x = bbMin.X; x <= bbMax.X; x++ )
{
byte block = GetPhysicsBlockId( x, y, z );
byte block = game.World.SafeGetBlock( x, y, z );
if( block == 0 ) continue;
CollideType type = info.Collide[block];
if( type == CollideType.Solid && !checkSolid )
continue;
Vector3 min = new Vector3( x, y, z ) + info.MinBB[block];
Vector3 max = new Vector3( x, y, z ) + info.MaxBB[block];
BoundingBox blockBB = new BoundingBox( min, max );
if( !blockBB.Intersects( adjFinalBB ) )
continue;
if( info.Collide[GetPhysicsBlockId( x, y, z )] == CollideType.Solid )
return false;
if( !blockBB.Intersects( bounds ) ) continue;
modifier = Math.Min( modifier, info.SpeedMultiplier[block] );
if( block >= BlockInfo.CpeBlocksCount && type == CollideType.SwimThrough )
useLiquidGravity = true;
}
return true;
return modifier;
}
void ClipX( ref Vector3 size, ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) {
entity.Velocity.X = 0;
entityBB.Min.X = entityExtentBB.Min.X = entity.Position.X - size.X / 2;
entityBB.Max.X = entityExtentBB.Max.X = entity.Position.X + size.X / 2;
collideX = true;
/// <summary> Calculates the jump velocity required such that when a client presses
/// the jump binding they will be able to jump up to the given height. </summary>
internal void CalculateJumpVelocity( float jumpHeight ) {
jumpVel = 0;
if( jumpHeight >= 256 ) jumpVel = 10.0f;
if( jumpHeight >= 512 ) jumpVel = 16.5f;
if( jumpHeight >= 768 ) jumpVel = 22.5f;
while( GetMaxHeight( jumpVel ) <= jumpHeight )
jumpVel += 0.001f;
}
void ClipY( ref Vector3 size, ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) {
entity.Velocity.Y = 0;
entityBB.Min.Y = entityExtentBB.Min.Y = entity.Position.Y;
entityBB.Max.Y = entityExtentBB.Max.Y = entity.Position.Y + size.Y;
collideY = true;
public static double GetMaxHeight( float u ) {
// equation below comes from solving diff(x(t, u))= 0
// We only work in discrete timesteps, so test both rounded up and down.
double t = 49.49831645 * Math.Log( 0.247483075 * u + 0.9899323 );
return Math.Max( YPosAt( (int)t, u ), YPosAt( (int)t + 1, u ) );
}
void ClipZ( ref Vector3 size, ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) {
entity.Velocity.Z = 0;
entityBB.Min.Z = entityExtentBB.Min.Z = entity.Position.Z - size.Z / 2;
entityBB.Max.Z = entityExtentBB.Max.Z = entity.Position.Z + size.Z / 2;
collideZ = true;
}
static void CalcTime( ref Vector3 vel, ref BoundingBox entityBB, ref BoundingBox blockBB,
out float tx, out float ty, out float tz ) {
float dx = vel.X > 0 ? blockBB.Min.X - entityBB.Max.X : entityBB.Min.X - blockBB.Max.X;
float dy = vel.Y > 0 ? blockBB.Min.Y - entityBB.Max.Y : entityBB.Min.Y - blockBB.Max.Y;
float dz = vel.Z > 0 ? blockBB.Min.Z - entityBB.Max.Z : entityBB.Min.Z - blockBB.Max.Z;
tx = vel.X == 0 ? float.PositiveInfinity : Math.Abs( dx / vel.X );
ty = vel.Y == 0 ? float.PositiveInfinity : Math.Abs( dy / vel.Y );
tz = vel.Z == 0 ? float.PositiveInfinity : Math.Abs( dz / vel.Z );
if( entityBB.XIntersects( blockBB ) ) tx = 0;
if( entityBB.YIntersects( blockBB ) ) ty = 0;
if( entityBB.ZIntersects( blockBB ) ) tz = 0;
}
struct State {
public int X, Y, Z;
public float tSquared;
public State( int x, int y, int z, byte block, float tSquared ) {
X = x << 3; Y = y << 3; Z = z << 3;
X |= (block & 0x07);
Y |= (block & 0x38) >> 3;
Z |= (block & 0xC0) >> 6;
this.tSquared = tSquared;
}
}
static State[] stateCache = new State[0];
static void QuickSort( State[] keys, int left, int right ) {
while( left < right ) {
int i = left, j = right;
float pivot = keys[(i + j) / 2].tSquared;
// partition the list
while( i <= j ) {
while( pivot > keys[i].tSquared ) i++;
while( pivot < keys[j].tSquared ) j--;
if( i <= j ) {
State key = keys[i]; keys[i] = keys[j]; keys[j] = key;
i++; j--;
}
}
// recurse into the smaller subset
if( j - left <= right - i ) {
if( left < j )
QuickSort( keys, left, j );
left = i;
} else {
if( i < right )
QuickSort( keys, i, right );
right = j;
}
}
static double YPosAt( int t, float u ) {
// v(t, u) = (4 + u) * (0.98^t) - 4, where u = initial velocity
// x(t, u) = Σv(t, u) from 0 to t (since we work in discrete timesteps)
// plugging into Wolfram Alpha gives 1 equation as
// (0.98^t) * (-49u - 196) - 4t + 50u + 196
double a = Math.Exp( -0.0202027 * t ); //~0.98^t
return a * ( -49 * u - 196 ) - 4 * t + 50 * u + 196;
}
}
}

View File

@ -60,7 +60,7 @@ namespace ClassicalSharp.Entities {
/// <summary> Determines whether any of the blocks that intersect the
/// bounding box of this entity are lava or still lava. </summary>
protected bool TouchesAnyLava() {
public bool TouchesAnyLava() {
BoundingBox bounds = CollisionBounds.Expand( liqExpand );
AdjustLiquidTestBounds( ref bounds );
return TouchesAny( bounds,
@ -69,7 +69,7 @@ namespace ClassicalSharp.Entities {
/// <summary> Determines whether any of the blocks that intersect the
/// bounding box of this entity are rope. </summary>
protected bool TouchesAnyRope() {
public bool TouchesAnyRope() {
BoundingBox bounds = CollisionBounds;
bounds.Max.Y += 0.5f/16f;
return TouchesAny( bounds, b => b == (byte)Block.Rope );
@ -77,7 +77,7 @@ namespace ClassicalSharp.Entities {
/// <summary> Determines whether any of the blocks that intersect the
/// bounding box of this entity are water or still water. </summary>
protected bool TouchesAnyWater() {
public bool TouchesAnyWater() {
BoundingBox bounds = CollisionBounds.Expand( liqExpand );
AdjustLiquidTestBounds( ref bounds );
return TouchesAny( bounds,

View File

@ -1,251 +0,0 @@
// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT
using System;
using System.Drawing;
using ClassicalSharp.Renderers;
using OpenTK;
using OpenTK.Input;
namespace ClassicalSharp.Entities {
public partial class LocalPlayer : Player {
bool useLiquidGravity = false; // used by BlockDefinitions.
bool canLiquidJump = true;
bool firstJump = false, secondJump = false;
void UpdateVelocityState( float xMoving, float zMoving ) {
if( !Hacks.NoclipSlide && (noClip && xMoving == 0 && zMoving == 0) )
Velocity = Vector3.Zero;
if( flying || noClip ) {
Velocity.Y = 0; // eliminate the effect of gravity
int dir = (flyingUp || jumping) ? 1 : (flyingDown ? -1 : 0);
Velocity.Y += 0.12f * dir;
if( speeding && Hacks.CanSpeed ) Velocity.Y += 0.12f * dir;
if( halfSpeeding && Hacks.CanSpeed ) Velocity.Y += 0.06f * dir;
} else if( jumping && TouchesAnyRope() && Velocity.Y > 0.02f ) {
Velocity.Y = 0.02f;
}
if( !jumping ) {
canLiquidJump = false; return;
}
bool touchWater = TouchesAnyWater();
bool touchLava = TouchesAnyLava();
if( touchWater || touchLava ) {
BoundingBox bounds = CollisionBounds;
int feetY = Utils.Floor( bounds.Min.Y ), bodyY = feetY + 1;
int headY = Utils.Floor( bounds.Max.Y );
if( bodyY > headY ) bodyY = headY;
bounds.Max.Y = bounds.Min.Y = feetY;
bool liquidFeet = TouchesAny( bounds, StandardLiquid );
bounds.Min.Y = Math.Min( bodyY, headY );
bounds.Max.Y = Math.Max( bodyY, headY );
bool liquidRest = TouchesAny( bounds, StandardLiquid );
bool pastJumpPoint = liquidFeet && !liquidRest && (Position.Y % 1 >= 0.4);
if( !pastJumpPoint ) {
canLiquidJump = true;
Velocity.Y += 0.04f;
if( speeding && Hacks.CanSpeed ) Velocity.Y += 0.04f;
if( halfSpeeding && Hacks.CanSpeed ) Velocity.Y += 0.02f;
} else if( pastJumpPoint ) {
// either A) jump bob in water B) climb up solid on side
if( canLiquidJump || (physics.collideX || physics.collideZ) )
Velocity.Y += touchLava ? 0.20f : 0.10f;
canLiquidJump = false;
}
} else if( useLiquidGravity ) {
Velocity.Y += 0.04f;
if( speeding && Hacks.CanSpeed ) Velocity.Y += 0.04f;
if( halfSpeeding && Hacks.CanSpeed ) Velocity.Y += 0.02f;
canLiquidJump = false;
} else if( TouchesAnyRope() ) {
Velocity.Y += (speeding && Hacks.CanSpeed) ? 0.15f : 0.10f;
canLiquidJump = false;
} else if( onGround ) {
DoNormalJump();
}
}
void DoNormalJump() {
Velocity.Y = jumpVel;
if( speeding && Hacks.CanSpeed ) Velocity.Y += jumpVel;
if( halfSpeeding && Hacks.CanSpeed ) Velocity.Y += jumpVel / 2;
canLiquidJump = false;
}
bool StandardLiquid( byte block ) {
return info.Collide[block] == CollideType.SwimThrough;
}
static Vector3 waterDrag = new Vector3( 0.8f, 0.8f, 0.8f ),
lavaDrag = new Vector3( 0.5f, 0.5f, 0.5f ),
ropeDrag = new Vector3( 0.5f, 0.85f, 0.5f ),
normalDrag = new Vector3( 0.91f, 0.98f, 0.91f ),
airDrag = new Vector3( 0.6f, 1f, 0.6f );
const float liquidGrav = 0.02f, ropeGrav = 0.034f, normalGrav = 0.08f;
void PhysicsTick( float xMoving, float zMoving ) {
if( noClip ) onGround = false;
float multiply = GetBaseMultiply( true );
float yMultiply = GetBaseMultiply( Hacks.CanSpeed );
float modifier = LowestSpeedModifier();
float yMul = Math.Max( 1f, yMultiply / 5 ) * modifier;
float horMul = multiply * modifier;
if( !(flying || noClip) ) {
if( secondJump ) { horMul *= 93f; yMul *= 10f; }
else if( firstJump ) { horMul *= 46.5f; yMul *= 7.5f; }
}
if( TouchesAnyWater() && !flying && !noClip ) {
MoveNormal( xMoving, zMoving, 0.02f * horMul, waterDrag, liquidGrav, yMul );
} else if( TouchesAnyLava() && !flying && !noClip ) {
MoveNormal( xMoving, zMoving, 0.02f * horMul, lavaDrag, liquidGrav, yMul );
} else if( TouchesAnyRope() && !flying && !noClip ) {
MoveNormal( xMoving, zMoving, 0.02f * 1.7f, ropeDrag, ropeGrav, yMul );
} else {
float factor = !(flying || noClip) && onGround ? 0.1f : 0.02f;
float gravity = useLiquidGravity ? liquidGrav : normalGrav;
if( flying || noClip )
MoveFlying( xMoving, zMoving, factor * horMul, normalDrag, gravity, yMul );
else
MoveNormal( xMoving, zMoving, factor * horMul, normalDrag, gravity, yMul );
if( BlockUnderFeet == Block.Ice && !(flying || noClip) ) {
// limit components to +-0.25f by rescaling vector to [-0.25, 0.25]
if( Math.Abs( Velocity.X ) > 0.25f || Math.Abs( Velocity.Z ) > 0.25f ) {
float scale = Math.Min(
Math.Abs( 0.25f / Velocity.X ), Math.Abs( 0.25f / Velocity.Z ) );
Velocity.X *= scale;
Velocity.Z *= scale;
}
} else if( onGround || flying ) {
Velocity *= airDrag; // air drag or ground friction
}
}
}
void AdjHeadingVelocity( float x, float z, float factor ) {
float dist = (float)Math.Sqrt( x * x + z * z );
if( dist < 0.00001f ) return;
if( dist < 1 ) dist = 1;
float multiply = factor / dist;
Velocity += Utils.RotateY( x * multiply, 0, z * multiply, HeadYawRadians );
}
void MoveFlying( float xMoving, float zMoving, float factor, Vector3 drag, float gravity, float yMul ) {
AdjHeadingVelocity( zMoving, xMoving, factor );
float yVel = (float)Math.Sqrt( Velocity.X * Velocity.X + Velocity.Z * Velocity.Z );
// make vertical speed the same as vertical speed.
if( (xMoving != 0 || zMoving != 0) && yVel > 0.001f ) {
Velocity.Y = 0;
yMul = 1;
if( flyingUp || jumping ) Velocity.Y += yVel;
if( flyingDown ) Velocity.Y -= yVel;
}
Move( xMoving, zMoving, factor, drag, gravity, yMul );
}
void MoveNormal( float xMoving, float zMoving, float factor, Vector3 drag, float gravity, float yMul ) {
AdjHeadingVelocity( zMoving, xMoving, factor );
Move( xMoving, zMoving, factor, drag, gravity, yMul );
}
void Move( float xMoving, float zMoving, float factor, Vector3 drag, float gravity, float yMul ) {
Velocity.Y *= yMul;
if( !noClip )
physics.MoveAndWallSlide();
Position += Velocity;
Velocity.Y /= yMul;
Velocity *= drag;
Velocity.Y -= gravity;
}
float GetBaseMultiply( bool canSpeed ) {
float multiply = 0;
if( flying || noClip ) {
if( speeding && canSpeed ) multiply += Hacks.SpeedMultiplier * 8;
if( halfSpeeding && canSpeed ) multiply += Hacks.SpeedMultiplier * 8 / 2;
if( multiply == 0 ) multiply = 8f;
} else {
if( speeding && canSpeed ) multiply += Hacks.SpeedMultiplier;
if( halfSpeeding && canSpeed ) multiply += Hacks.SpeedMultiplier / 2;
if( multiply == 0 ) multiply = 1;
}
return Hacks.CanSpeed ? multiply : Math.Min( multiply, Hacks.MaxSpeedMultiplier );
}
const float inf = float.PositiveInfinity;
float LowestSpeedModifier() {
BoundingBox bounds = CollisionBounds;
useLiquidGravity = false;
float baseModifier = LowestModifier( bounds, false );
bounds.Min.Y -= 0.5f/16f; // also check block standing on
float solidModifier = LowestModifier( bounds, true );
if( baseModifier == inf && solidModifier == inf ) return 1;
return baseModifier == inf ? solidModifier : baseModifier;
}
float LowestModifier( BoundingBox bounds, bool checkSolid ) {
Vector3I bbMin = Vector3I.Floor( bounds.Min );
Vector3I bbMax = Vector3I.Floor( bounds.Max );
float modifier = inf;
for( int y = bbMin.Y; y <= bbMax.Y; y++ )
for( int z = bbMin.Z; z <= bbMax.Z; z++ )
for( int x = bbMin.X; x <= bbMax.X; x++ )
{
byte block = game.World.SafeGetBlock( x, y, z );
if( block == 0 ) continue;
CollideType type = info.Collide[block];
if( type == CollideType.Solid && !checkSolid )
continue;
Vector3 min = new Vector3( x, y, z ) + info.MinBB[block];
Vector3 max = new Vector3( x, y, z ) + info.MaxBB[block];
BoundingBox blockBB = new BoundingBox( min, max );
if( !blockBB.Intersects( bounds ) ) continue;
modifier = Math.Min( modifier, info.SpeedMultiplier[block] );
if( block >= BlockInfo.CpeBlocksCount && type == CollideType.SwimThrough )
useLiquidGravity = true;
}
return modifier;
}
/// <summary> Calculates the jump velocity required such that when a client presses
/// the jump binding they will be able to jump up to the given height. </summary>
internal void CalculateJumpVelocity( float jumpHeight ) {
jumpVel = 0;
if( jumpHeight >= 256 ) jumpVel = 10.0f;
if( jumpHeight >= 512 ) jumpVel = 16.5f;
if( jumpHeight >= 768 ) jumpVel = 22.5f;
while( GetMaxHeight( jumpVel ) <= jumpHeight )
jumpVel += 0.001f;
}
static double GetMaxHeight( float u ) {
// equation below comes from solving diff(x(t, u))= 0
// We only work in discrete timesteps, so test both rounded up and down.
double t = 49.49831645 * Math.Log( 0.247483075 * u + 0.9899323 );
return Math.Max( YPosAt( (int)t, u ), YPosAt( (int)t + 1, u ) );
}
static double YPosAt( int t, float u ) {
// v(t, u) = (4 + u) * (0.98^t) - 4, where u = initial velocity
// x(t, u) = Σv(t, u) from 0 to t (since we work in discrete timesteps)
// plugging into Wolfram Alpha gives 1 equation as
// (0.98^t) * (-49u - 196) - 4t + 50u + 196
double a = Math.Exp( -0.0202027 * t ); //~0.98^t
return a * ( -49 * u - 196 ) - 4 * t + 50 * u + 196;
}
}
}

View File

@ -18,23 +18,26 @@ namespace ClassicalSharp.Entities {
/// reach to and interact/modify blocks in. </summary>
public float ReachDistance = 5f;
internal float jumpVel = 0.42f, serverJumpVel = 0.42f;
/// <summary> Returns the height that the client can currently jump up to.<br/>
/// Note that when speeding is enabled the client is able to jump much further. </summary>
public float JumpHeight {
get { return (float)GetMaxHeight( jumpVel ); }
get { return (float)PhysicsComponent.GetMaxHeight( physics.jumpVel ); }
}
internal float curWalkTime, curSwing;
internal PhysicsComponent physics;
internal CollisionsComponent collisions;
public HacksComponent Hacks;
internal PhysicsComponent physics;
public LocalPlayer( Game game ) : base( game ) {
DisplayName = game.Username;
SkinName = game.Username;
SkinIdentifier = "skin_255";
physics = new PhysicsComponent( game, this );
collisions = new CollisionsComponent( game, this );
Hacks = new HacksComponent( game, this );
physics = new PhysicsComponent( game, this );
physics.hacks = Hacks;
physics.collisions = collisions;
Hacks.SpeedMultiplier = Options.GetFloat( OptionsKey.Speed, 0.1f, 50, 10 );
Hacks.PushbackPlacing = !game.ClassicMode && Options.GetBool( OptionsKey.PushbackPlacing, false );
@ -56,9 +59,8 @@ namespace ClassicalSharp.Entities {
bool wasOnGround = onGround;
HandleInput( ref xMoving, ref zMoving );
UpdateVelocityState( xMoving, zMoving );
PhysicsTick( xMoving, zMoving );
if( onGround ) { firstJump = false; secondJump = false; }
physics.UpdateVelocityState( xMoving, zMoving );
physics.PhysicsTick( xMoving, zMoving );
nextPos = Position;
Position = lastPos;
@ -146,34 +148,26 @@ namespace ClassicalSharp.Entities {
void HandleInput( ref float xMoving, ref float zMoving ) {
if( game.ScreenLockedInput ) {
jumping = speeding = flyingUp = flyingDown = false;
physics.jumping = Hacks.Speeding = Hacks.FlyingUp = Hacks.FlyingDown = false;
} else {
if( game.IsKeyDown( KeyBinding.Forward ) ) xMoving -= 0.98f;
if( game.IsKeyDown( KeyBinding.Back ) ) xMoving += 0.98f;
if( game.IsKeyDown( KeyBinding.Left ) ) zMoving -= 0.98f;
if( game.IsKeyDown( KeyBinding.Right ) ) zMoving += 0.98f;
jumping = game.IsKeyDown( KeyBinding.Jump );
speeding = Hacks.Enabled && game.IsKeyDown( KeyBinding.Speed );
halfSpeeding = Hacks.Enabled && game.IsKeyDown( KeyBinding.HalfSpeed );
flyingUp = game.IsKeyDown( KeyBinding.FlyUp );
flyingDown = game.IsKeyDown( KeyBinding.FlyDown );
physics.jumping = game.IsKeyDown( KeyBinding.Jump );
Hacks.Speeding = Hacks.Enabled && game.IsKeyDown( KeyBinding.Speed );
Hacks.HalfSpeeding = Hacks.Enabled && game.IsKeyDown( KeyBinding.HalfSpeed );
Hacks.FlyingUp = game.IsKeyDown( KeyBinding.FlyUp );
Hacks.FlyingDown = game.IsKeyDown( KeyBinding.FlyDown );
}
}
internal bool jumping, speeding, halfSpeeding, flying, noClip, flyingDown, flyingUp;
/// <summary> Disables any hacks if their respective CanHackX value is set to false. </summary>
public void CheckHacksConsistency() {
if( !Hacks.CanFly || !Hacks.Enabled ) { flying = false; flyingDown = false; flyingUp = false; }
if( !Hacks.CanNoclip || !Hacks.Enabled ) noClip = false;
if( !Hacks.CanSpeed || !Hacks.Enabled ) { speeding = false; halfSpeeding = false; }
Hacks.CanDoubleJump = Hacks.CanAnyHacks && Hacks.Enabled && Hacks.CanSpeed;
if( !Hacks.CanUseThirdPersonCamera || !Hacks.Enabled )
game.CycleCamera();
Hacks.CheckHacksConsistency();
if( !Hacks.Enabled || !Hacks.CanAnyHacks || !Hacks.CanSpeed )
jumpVel = serverJumpVel;
physics.jumpVel = physics.serverJumpVel;
}
internal Vector3 lastPos, nextPos;
@ -248,17 +242,17 @@ namespace ClassicalSharp.Entities {
SpawnYaw = HeadYawDegrees;
SpawnPitch = PitchDegrees;
} else if( key == keys[KeyBinding.Fly] && Hacks.CanFly && Hacks.Enabled ) {
flying = !flying;
Hacks.Flying = !Hacks.Flying;
} else if( key == keys[KeyBinding.NoClip] && Hacks.CanNoclip && Hacks.Enabled ) {
if( noClip ) Velocity.Y = 0;
noClip = !noClip;
} else if( key == keys[KeyBinding.Jump] && !onGround && !(flying || noClip) ) {
if( !firstJump && Hacks.CanDoubleJump && Hacks.DoubleJump ) {
DoNormalJump();
firstJump = true;
} else if( !secondJump && Hacks.CanDoubleJump && Hacks.DoubleJump ) {
DoNormalJump();
secondJump = true;
if( Hacks.Noclip ) Velocity.Y = 0;
Hacks.Noclip = !Hacks.Noclip;
} else if( key == keys[KeyBinding.Jump] && !onGround && !(Hacks.Flying || Hacks.Noclip) ) {
if( !physics.firstJump && Hacks.CanDoubleJump && Hacks.DoubleJump ) {
physics.DoNormalJump();
physics.firstJump = true;
} else if( !physics.secondJump && Hacks.CanDoubleJump && Hacks.DoubleJump ) {
physics.DoNormalJump();
physics.secondJump = true;
}
} else {
return false;
@ -282,10 +276,9 @@ namespace ClassicalSharp.Entities {
for ( int xx = minX; xx <= maxX; xx++ )
{
Vector3I coords = new Vector3I( P.X + xx, y + yy, P.Z + zz );
byte block = physics.GetPhysicsBlockId( coords.X, coords.Y, coords.Z );
byte block = collisions.GetPhysicsBlockId( coords.X, coords.Y, coords.Z );
Vector3 min = info.MinBB[block] + (Vector3)coords;
Vector3 max = info.MaxBB[block] + (Vector3)coords;
Console.WriteLine( min + "_" + max );
if( !bb.Intersects( new BoundingBox( min, max ) ) ) continue;
anyHit |= info.Collide[block] == CollideType.Solid;
}

View File

@ -123,7 +123,7 @@ namespace ClassicalSharp {
pos + game.BlockInfo.MaxBB[newBlock] );
BoundingBox localBB = game.LocalPlayer.CollisionBounds;
if( game.LocalPlayer.noClip || !localBB.Intersects( blockBB ) ) return true;
if( game.LocalPlayer.Hacks.Noclip || !localBB.Intersects( blockBB ) ) return true;
HacksComponent hacks = game.LocalPlayer.Hacks;
if( hacks.CanPushbackBlocks && hacks.PushbackPlacing && hacks.Enabled )
return PushbackPlace( selected, blockBB );
@ -163,7 +163,8 @@ namespace ClassicalSharp {
if( !validPos ) return false;
game.LocalPlayer.Position = newP;
if( !game.LocalPlayer.noClip && game.LocalPlayer.TouchesAny( CannotPassThrough ) ) {
if( !game.LocalPlayer.Hacks.Noclip
&& game.LocalPlayer.TouchesAny( CannotPassThrough ) ) {
game.LocalPlayer.Position = oldP;
return false;
}

View File

@ -7,20 +7,20 @@ namespace ClassicalSharp {
public static class Picking {
static RayCaster ray = new RayCaster();
static RayTracer tracer = new RayTracer();
/// <summary> Determines the picked block based on the given origin and direction vector.<br/>
/// Marks pickedPos as invalid if a block could not be found due to going outside map boundaries
/// or not being able to find a suitable candiate within the given reach distance. </summary>
public static void CalculatePickedBlock( Game game, Vector3 origin, Vector3 dir, float reach, PickedPos pickedPos ) {
ray.SetRayData( origin, dir );
tracer.SetRayData( origin, dir );
World map = game.World;
BlockInfo info = game.BlockInfo;
float reachSquared = reach * reach;
int iterations = 0;
while( iterations < 10000 ) {
int x = ray.X, y = ray.Y, z = ray.Z;
int x = tracer.X, y = tracer.Y, z = tracer.Z;
byte block = GetBlock( map, x, y, z, origin );
Vector3 min = new Vector3( x, y, z ) + info.MinBB[block];
Vector3 max = new Vector3( x, y, z ) + info.MaxBB[block];
@ -43,7 +43,7 @@ namespace ClassicalSharp {
return;
}
}
ray.Step();
tracer.Step();
iterations++;
}
throw new InvalidOperationException( "did over 10000 iterations in CalculatePickedBlock(). " +
@ -56,14 +56,14 @@ namespace ClassicalSharp {
pickedPos.IntersectPoint = origin + dir * reach;
return;
}
ray.SetRayData( origin, dir );
tracer.SetRayData( origin, dir );
World map = game.World;
BlockInfo info = game.BlockInfo;
float reachSquared = reach * reach;
int iterations = 0;
while( iterations < 10000 ) {
int x = ray.X, y = ray.Y, z = ray.Z;
int x = tracer.X, y = tracer.Y, z = tracer.Z;
byte block = GetBlock( map, x, y, z, origin );
Vector3 min = new Vector3( x, y, z ) + info.MinBB[block];
Vector3 max = new Vector3( x, y, z ) + info.MaxBB[block];
@ -102,7 +102,7 @@ namespace ClassicalSharp {
return;
}
}
ray.Step();
tracer.Step();
iterations++;
}
throw new InvalidOperationException( "did over 10000 iterations in ClipCameraPos(). " +

View File

@ -12,7 +12,7 @@ namespace ClassicalSharp {
// John Amanatides, Andrew Woo
// http://www.cse.yorku.ca/~amana/research/grid.pdf
// http://www.devmaster.net/articles/raytracing_series/A%20faster%20voxel%20traversal%20algorithm%20for%20ray%20tracing.pdf
public sealed class RayCaster {
public sealed class RayTracer {
public int X, Y, Z;
Vector3I step, cellBoundary;

View File

@ -348,9 +348,9 @@ namespace ClassicalSharp.Net {
p.CheckHacksConsistency();
float jumpHeight = reader.ReadInt16() / 32f;
if( jumpHeight < 0 ) p.jumpVel = 0.42f;
else p.CalculateJumpVelocity( jumpHeight );
p.serverJumpVel = p.jumpVel;
if( jumpHeight < 0 ) p.physics.jumpVel = 0.42f;
else p.physics.CalculateJumpVelocity( jumpHeight );
p.physics.serverJumpVel = p.physics.jumpVel;
game.Events.RaiseHackPermissionsChanged();
}

View File

@ -66,6 +66,7 @@ namespace ClassicalSharp.Net {
if( (DateTime.UtcNow - lastPacket).TotalSeconds >= 20 )
CheckDisconnection( delta );
if( Disconnected ) return;
LocalPlayer player = game.LocalPlayer;
try {
reader.ReadPendingData();
@ -83,8 +84,8 @@ namespace ClassicalSharp.Net {
// Workaround for older D3 servers which wrote one byte too many for HackControl packets.
if( opcode == 0xFF && lastOpcode == PacketId.CpeHackControl ) {
reader.Skip( 1 );
game.LocalPlayer.jumpVel = 0.42f; // assume default jump height
game.LocalPlayer.serverJumpVel = game.LocalPlayer.jumpVel;
player.physics.jumpVel = 0.42f; // assume default jump height
player.physics.serverJumpVel = player.physics.jumpVel;
continue;
}
@ -98,9 +99,8 @@ namespace ClassicalSharp.Net {
if( (reader.size - reader.index) < packetSizes[opcode] ) break;
ReadPacket( opcode );
}
reader.RemoveProcessed();
Player player = game.LocalPlayer;
reader.RemoveProcessed();
if( receivedFirstPosition ) {
SendPosition( player.Position, player.HeadYawDegrees, player.PitchDegrees );
}