#include "Entity.h"
#include "ExtMath.h"
#include "World.h"
#include "Block.h"
#include "Event.h"
#include "Game.h"
#include "Camera.h"
#include "Platform.h"
#include "Funcs.h"
#include "Graphics.h"
#include "Lighting.h"
#include "Http.h"
#include "Chat.h"
#include "Model.h"
#include "Input.h"
#include "InputHandler.h"
#include "Gui.h"
#include "Stream.h"
#include "Bitmap.h"
#include "Logger.h"
#include "Options.h"
#include "Errors.h"
#include "Utils.h"
#include "EntityRenderers.h"
#include "Protocol.h"

const char* const NameMode_Names[NAME_MODE_COUNT]   = { "None", "Hovered", "All", "AllHovered", "AllUnscaled" };
const char* const ShadowMode_Names[SHADOW_MODE_COUNT] = { "None", "SnapToBlock", "Circle", "CircleAll" };


/*########################################################################################################################*
*---------------------------------------------------------Entity----------------------------------------------------------*
*#########################################################################################################################*/
static PackedCol Entity_GetColor(struct Entity* e) {
	Vec3 eyePos = Entity_GetEyePosition(e);
	IVec3 pos; IVec3_Floor(&pos, &eyePos);
	return Lighting.Color(pos.x, pos.y, pos.z);
}

void Entity_Init(struct Entity* e) {
	static const cc_string model = String_FromConst("humanoid");
	Vec3_Set(e->ModelScale, 1,1,1);
	e->Flags      = ENTITY_FLAG_HAS_MODELVB;
	e->uScale     = 1.0f;
	e->vScale     = 1.0f;
	e->_skinReqID = 0;
	e->SkinRaw[0] = '\0';
	e->NameRaw[0] = '\0';
	Entity_SetModel(e, &model);
}

void Entity_SetName(struct Entity* e, const cc_string* name) {
	EntityNames_Delete(e);
	String_CopyToRawArray(e->NameRaw, name);
}

Vec3 Entity_GetEyePosition(struct Entity* e) {
	Vec3 pos = e->Position; pos.y += Entity_GetEyeHeight(e); return pos;
}

float Entity_GetEyeHeight(struct Entity* e) {
	return e->Model->GetEyeY(e) * e->ModelScale.y;
}

void Entity_GetTransform(struct Entity* e, Vec3 pos, Vec3 scale, struct Matrix* m) {
	struct Matrix tmp;
	Matrix_Scale(m, scale.x, scale.y, scale.z);

	if (e->RotZ) {
		Matrix_RotateZ( &tmp, -e->RotZ * MATH_DEG2RAD);
		Matrix_MulBy(m, &tmp);
	}
	if (e->RotX) {
		Matrix_RotateX( &tmp, -e->RotX * MATH_DEG2RAD);
		Matrix_MulBy(m, &tmp);
	}
	if (e->RotY) {
		Matrix_RotateY( &tmp, -e->RotY * MATH_DEG2RAD);
		Matrix_MulBy(m, &tmp);
	}

	Matrix_Translate(&tmp, pos.x, pos.y, pos.z);
	Matrix_MulBy(m,  &tmp);
	/* return scale * rotZ * rotX * rotY * translate; */
}

void Entity_GetPickingBounds(struct Entity* e, struct AABB* bb) {
	AABB_Offset(bb, &e->ModelAABB, &e->Position);
}

void Entity_GetBounds(struct Entity* e, struct AABB* bb) {
	AABB_Make(bb, &e->Position, &e->Size);
}

static void Entity_ParseScale(struct Entity* e, const cc_string* scale) {
	float value;
	if (!Convert_ParseFloat(scale, &value)) return;
	value = max(value, 0.001f);

	/* local player doesn't allow giant model scales */
	/* (can't climb stairs, extremely CPU intensive collisions) */
	if (e->Flags & ENTITY_FLAG_MODEL_RESTRICTED_SCALE) {
		value = min(value, e->Model->maxScale);
	}
	Vec3_Set(e->ModelScale, value,value,value);
}

static void Entity_SetBlockModel(struct Entity* e, const cc_string* model) {
	static const cc_string block = String_FromConst("block");
	int raw = Block_Parse(model);

	if (raw == -1) {
		/* use default humanoid model */
		e->Model      = Models.Human;
	} else {	
		e->ModelBlock = (BlockID)raw;
		e->Model      = Model_Get(&block);
	}
}

void Entity_SetModel(struct Entity* e, const cc_string* model) {
	cc_string name, scale;
	Vec3_Set(e->ModelScale, 1,1,1);
	String_UNSAFE_Separate(model, '|', &name, &scale);

	/* 'giant' model kept for backwards compatibility */
	if (String_CaselessEqualsConst(&name, "giant")) {
		name = String_FromReadonly("humanoid");
		Vec3_Set(e->ModelScale, 2,2,2);
	}

	e->ModelBlock = BLOCK_AIR;
	e->Model      = Model_Get(&name);
	if (!e->Model) Entity_SetBlockModel(e, &name);

	Entity_ParseScale(e, &scale);
	Entity_UpdateModelBounds(e);

	if (e->Flags & ENTITY_FLAG_HAS_MODELVB)
		Gfx_DeleteDynamicVb(&e->ModelVB);
}

void Entity_UpdateModelBounds(struct Entity* e) {
	struct Model* model = e->Model;
	model->GetCollisionSize(e);
	model->GetPickingBounds(e);

	Vec3_Mul3By(&e->Size,          &e->ModelScale);
	Vec3_Mul3By(&e->ModelAABB.Min, &e->ModelScale);
	Vec3_Mul3By(&e->ModelAABB.Max, &e->ModelScale);
}

cc_bool Entity_TouchesAny(struct AABB* bounds, Entity_TouchesCondition condition) {
	IVec3 bbMin, bbMax;
	BlockID block;
	struct AABB blockBB;
	Vec3 v;
	int x, y, z;

	IVec3_Floor(&bbMin, &bounds->Min);
	IVec3_Floor(&bbMax, &bounds->Max);

	bbMin.x = max(bbMin.x, 0); bbMax.x = min(bbMax.x, World.MaxX);
	bbMin.y = max(bbMin.y, 0); bbMax.y = min(bbMax.y, World.MaxY);
	bbMin.z = max(bbMin.z, 0); bbMax.z = min(bbMax.z, World.MaxZ);

	for (y = bbMin.y; y <= bbMax.y; y++) { v.y = (float)y;
		for (z = bbMin.z; z <= bbMax.z; z++) { v.z = (float)z;
			for (x = bbMin.x; x <= bbMax.x; x++) { v.x = (float)x;

				block = World_GetBlock(x, y, z);
				Vec3_Add(&blockBB.Min, &v, &Blocks.MinBB[block]);
				Vec3_Add(&blockBB.Max, &v, &Blocks.MaxBB[block]);

				if (!AABB_Intersects(&blockBB, bounds)) continue;
				if (condition(block)) return true;
			}
		}
	}
	return false;
}

static cc_bool IsRopeCollide(BlockID b) { return Blocks.ExtendedCollide[b] == COLLIDE_CLIMB; }
cc_bool Entity_TouchesAnyRope(struct Entity* e) {
	struct AABB bounds; Entity_GetBounds(e, &bounds);
	bounds.Max.y += 0.5f / 16.0f;
	return Entity_TouchesAny(&bounds, IsRopeCollide);
}

static const Vec3 entity_liqExpand = { 0.25f/16.0f, 0.0f/16.0f, 0.25f/16.0f };
static cc_bool IsLavaCollide(BlockID b) { return Blocks.ExtendedCollide[b] == COLLIDE_LAVA; }
cc_bool Entity_TouchesAnyLava(struct Entity* e) {
	struct AABB bounds; Entity_GetBounds(e, &bounds);
	AABB_Offset(&bounds, &bounds, &entity_liqExpand);
	return Entity_TouchesAny(&bounds, IsLavaCollide);
}

static cc_bool IsWaterCollide(BlockID b) { return Blocks.ExtendedCollide[b] == COLLIDE_WATER; }
cc_bool Entity_TouchesAnyWater(struct Entity* e) {
	struct AABB bounds; Entity_GetBounds(e, &bounds);
	AABB_Offset(&bounds, &bounds, &entity_liqExpand);
	return Entity_TouchesAny(&bounds, IsWaterCollide);
}


/*########################################################################################################################*
*------------------------------------------------------Entity skins-------------------------------------------------------*
*#########################################################################################################################*/
static struct Entity* Entity_FirstOtherWithSameSkinAndFetchedSkin(struct Entity* except) {
	struct Entity* e;
	cc_string skin, eSkin;
	int i;

	skin = String_FromRawArray(except->SkinRaw);
	for (i = 0; i < ENTITIES_MAX_COUNT; i++) {
		if (!Entities.List[i] || Entities.List[i] == except) continue;

		e     = Entities.List[i];
		eSkin = String_FromRawArray(e->SkinRaw);
		if (e->SkinFetchState && String_Equals(&skin, &eSkin)) return e;
	}
	return NULL;
}

/* Copies skin data from another entity */
static void Entity_CopySkin(struct Entity* dst, struct Entity* src) {
	dst->TextureId    = src->TextureId;	
	dst->SkinType     = src->SkinType;
	dst->uScale       = src->uScale;
	dst->vScale       = src->vScale;
	dst->MobTextureId = src->MobTextureId;
}

/* Resets skin data for the given entity */
static void Entity_ResetSkin(struct Entity* e) {
	e->uScale = 1.0f; e->vScale = 1.0f;
	e->MobTextureId = 0;
	e->TextureId    = 0;
	e->SkinType     = SKIN_64x32;
}

/* Copies or resets skin data for all entity with same skin */
static void Entity_SetSkinAll(struct Entity* source, cc_bool reset) {
	struct Entity* e;
	cc_string skin, eSkin;
	int i;

	skin = String_FromRawArray(source->SkinRaw);
	source->MobTextureId = Utils_IsUrlPrefix(&skin) ? source->TextureId : 0;

	for (i = 0; i < ENTITIES_MAX_COUNT; i++) {
		if (!Entities.List[i]) continue;

		e     = Entities.List[i];
		eSkin = String_FromRawArray(e->SkinRaw);
		if (!String_Equals(&skin, &eSkin)) continue;

		if (reset) {
			Entity_ResetSkin(e);
		} else {
			Entity_CopySkin(e, source);
		}
		e->SkinFetchState = SKIN_FETCH_COMPLETED;
	}
}

/* Clears hat area from a skin bitmap if it's completely white or black,
   so skins edited with Microsoft Paint or similiar don't have a solid hat */
static void Entity_ClearHat(struct Bitmap* bmp, cc_uint8 skinType) {
	int sizeX  = (bmp->width / 64) * 32;
	int yScale = skinType == SKIN_64x32 ? 32 : 64;
	int sizeY  = (bmp->height / yScale) * 16;
	int x, y;

	/* determine if we actually need filtering */
	for (y = 0; y < sizeY; y++) {
		BitmapCol* row = Bitmap_GetRow(bmp, y) + sizeX;
		for (x = 0; x < sizeX; x++) {
			if (BitmapCol_A(row[x]) != 255) return;
		}
	}

	/* only perform filtering when the entire hat is opaque */
	for (y = 0; y < sizeY; y++) {
		BitmapCol* row = Bitmap_GetRow(bmp, y) + sizeX;
		for (x = 0; x < sizeX; x++) {
			BitmapCol c = row[x];
			if (c == BITMAPCOLOR_WHITE || c == BITMAPCOLOR_BLACK) row[x] = 0;
		}
	}
}

/* Ensures skin is a power of two size, resizing if needed. */
static cc_result EnsurePow2Skin(struct Entity* e, struct Bitmap* bmp) {
	struct Bitmap scaled;
	cc_uint32 stride;
	int width, height;
	int y;

	width  = Math_NextPowOf2(bmp->width);
	height = Math_NextPowOf2(bmp->height);
	if (width == bmp->width && height == bmp->height) return 0;

	scaled.width  = width; 
	scaled.height = height;
	scaled.scan0  = (BitmapCol*)Mem_TryAllocCleared(width * height, BITMAPCOLOR_SIZE);
	if (!scaled.scan0) return ERR_OUT_OF_MEMORY;

	e->uScale = (float)bmp->width  / width;
	e->vScale = (float)bmp->height / height;
	stride = bmp->width * 4;

	for (y = 0; y < bmp->height; y++) {
		BitmapCol* src = Bitmap_GetRow(bmp, y);
		BitmapCol* dst = Bitmap_GetRow(&scaled, y);
		Mem_Copy(dst, src, stride);
	}

	Mem_Free(bmp->scan0);
	*bmp = scaled;
	return 0;
}

static cc_result ApplySkin(struct Entity* e, struct Bitmap* bmp, struct Stream* src, cc_string* skin) {
	cc_result res;
	if ((res = Png_Decode(bmp, src))) return res;

	Gfx_DeleteTexture(&e->TextureId);
	Entity_SetSkinAll(e, true);
	if ((res = EnsurePow2Skin(e, bmp))) return res;
	e->SkinType = Utils_CalcSkinType(bmp);

	if (!Gfx_CheckTextureSize(bmp->width, bmp->height, 0)) {
		Chat_Add1("&cSkin %s is too large", skin);
	} else {
		if (e->Model->flags & MODEL_FLAG_CLEAR_HAT)
			Entity_ClearHat(bmp, e->SkinType);

		e->TextureId = Gfx_CreateTexture(bmp, TEXTURE_FLAG_MANAGED, false);
		Entity_SetSkinAll(e, false);
	}
	return 0;
}

static void LogInvalidSkin(cc_result res, const cc_string* skin, const cc_uint8* data, int size) {
	cc_string msg; char msgBuffer[256];
	String_InitArray(msg, msgBuffer);

	Logger_FormatWarn2(&msg, res, "decoding skin", skin, Platform_DescribeError);
	if (res != PNG_ERR_INVALID_SIG) { Logger_WarnFunc(&msg); return; }

	String_AppendConst(&msg, " (got ");
	String_AppendAll(  &msg, data, min(size, 8));
	String_AppendConst(&msg, ")");
	Logger_WarnFunc(&msg);
}

static void Entity_CheckSkin(struct Entity* e) {
	struct Entity* first;
	struct HttpRequest item;
	struct Stream mem;
	struct Bitmap bmp;
	cc_string skin;
	cc_uint8 flags;
	cc_result res;

	/* Don't check skin if don't have to */
	if (!e->Model->usesSkin) return;
	if (e->SkinFetchState == SKIN_FETCH_COMPLETED) return;
	skin = String_FromRawArray(e->SkinRaw);

	if (!e->SkinFetchState) {
		first = Entity_FirstOtherWithSameSkinAndFetchedSkin(e);
		flags = e == &LocalPlayer_Instances[0].Base ? HTTP_FLAG_NOCACHE : 0;

		if (!first) {
			e->_skinReqID     = Http_AsyncGetSkin(&skin, flags);
			e->SkinFetchState = SKIN_FETCH_DOWNLOADING;
		} else {
			Entity_CopySkin(e, first);
			e->SkinFetchState = SKIN_FETCH_COMPLETED;
			return;
		}
	}

	if (!Http_GetResult(e->_skinReqID, &item)) return;

	if (!item.success) {
		Entity_SetSkinAll(e, true);
	} else {
		Stream_ReadonlyMemory(&mem, item.data, item.size);

		if ((res = ApplySkin(e, &bmp, &mem, &skin))) {
			LogInvalidSkin(res, &skin, item.data, item.size);
		}
		Mem_Free(bmp.scan0);
	}
	HttpRequest_Free(&item);
}

/* Returns true if no other entities are sharing this skin texture */
static cc_bool CanDeleteTexture(struct Entity* except) {
	int i;
	if (!except->TextureId) return false;

	for (i = 0; i < ENTITIES_MAX_COUNT; i++)
	{
		if (!Entities.List[i] || Entities.List[i] == except)  continue;
		if (Entities.List[i]->TextureId == except->TextureId) return false;
	}
	return true;
}

CC_NOINLINE static void DeleteSkin(struct Entity* e) {
	if (CanDeleteTexture(e)) Gfx_DeleteTexture(&e->TextureId);

	Entity_ResetSkin(e);
	e->SkinFetchState = 0;
}

void Entity_SetSkin(struct Entity* e, const cc_string* skin) {
	cc_string tmp; char tmpBuffer[STRING_SIZE];
	DeleteSkin(e);

	if (Utils_IsUrlPrefix(skin)) {
		tmp = *skin;
	} else {
		String_InitArray(tmp, tmpBuffer);
		String_AppendColorless(&tmp, skin);
	}
	String_CopyToRawArray(e->SkinRaw, &tmp);
}

void Entity_LerpAngles(struct Entity* e, float t) {
	struct EntityLocation* prev = &e->prev;
	struct EntityLocation* next = &e->next;

	e->Pitch = Math_LerpAngle(prev->pitch, next->pitch, t);
	e->Yaw   = Math_LerpAngle(prev->yaw,   next->yaw,   t);
	e->RotX  = Math_LerpAngle(prev->rotX,  next->rotX,  t);
	e->RotY  = Math_LerpAngle(prev->rotY,  next->rotY,  t);
	e->RotZ  = Math_LerpAngle(prev->rotZ,  next->rotZ,  t);
}


/*########################################################################################################################*
*--------------------------------------------------------Entities---------------------------------------------------------*
*#########################################################################################################################*/
struct _EntitiesData Entities;

void Entities_Tick(struct ScheduledTask* task) {
	int i;
	for (i = 0; i < ENTITIES_MAX_COUNT; i++)
	{
		if (!Entities.List[i]) continue;
		Entities.List[i]->VTABLE->Tick(Entities.List[i], task->interval);
	}
}

void Entities_RenderModels(float delta, float t) {
	int i;
	Gfx_SetAlphaTest(true);
	
	for (i = 0; i < ENTITIES_MAX_COUNT; i++)
	{
		if (!Entities.List[i]) continue;
		Entities.List[i]->VTABLE->RenderModel(Entities.List[i], delta, t);
	}
	Gfx_SetAlphaTest(false);
}

static void Entities_ContextLost(void* obj) {
	struct Entity* entity;
	int i;

	for (i = 0; i < ENTITIES_MAX_COUNT; i++)
	{
		entity = Entities.List[i];
		if (!entity) continue;

		if (entity->Flags & ENTITY_FLAG_HAS_MODELVB)
			Gfx_DeleteDynamicVb(&entity->ModelVB);

		if (!Gfx.ManagedTextures)
			DeleteSkin(entity);
	}
}
/* No OnContextCreated, skin textures remade when needed */

void Entities_Remove(int id) {
	struct Entity* e = Entities.List[id];
	if (!e) return;

	Event_RaiseInt(&EntityEvents.Removed, id);
	e->VTABLE->Despawn(e);
	Entities.List[id] = NULL;

	/* TODO: Move to EntityEvents.Removed callback instead */
	if (id < TABLIST_MAX_NAMES && TabList_EntityLinked_Get(id)) {
		TabList_Remove(id);
		TabList_EntityLinked_Reset(id);
	}
}

int Entities_GetClosest(struct Entity* src) {
	Vec3 eyePos = Entity_GetEyePosition(src);
	Vec3 dir    = Vec3_GetDirVector(src->Yaw * MATH_DEG2RAD, src->Pitch * MATH_DEG2RAD);
	float closestDist = -200; /* NOTE: was previously positive infinity */
	int targetID = -1;

	float t0, t1;
	int i;

	for (i = 0; i < ENTITIES_MAX_COUNT; i++) /* because we don't want to pick against local player */
	{
		struct Entity* e = Entities.List[i];
		if (!e || e == &Entities.CurPlayer->Base) continue;
		if (!Intersection_RayIntersectsRotatedBox(eyePos, dir, e, &t0, &t1)) continue;

		if (targetID < 0 || t0 < closestDist) {
			closestDist = t0;
			targetID    = i;
		}
	}
	return targetID;
}

static void Player_Despawn(struct Entity* e) {
	DeleteSkin(e);
	EntityNames_Delete(e);

	if (e->Flags & ENTITY_FLAG_HAS_MODELVB)
		Gfx_DeleteDynamicVb(&e->ModelVB);
}


/*########################################################################################################################*
*--------------------------------------------------------TabList----------------------------------------------------------*
*#########################################################################################################################*/
struct _TabListData TabList;

/* Removes the names from the names buffer for the given id. */
static void TabList_Delete(EntityID id) {
	int i, index;
	index = TabList.NameOffsets[id];
	if (!index) return;

	StringsBuffer_Remove(&TabList._buffer, index - 1);
	StringsBuffer_Remove(&TabList._buffer, index - 2);
	StringsBuffer_Remove(&TabList._buffer, index - 3);

	/* Indices after this entry need to be shifted down */
	for (i = 0; i < TABLIST_MAX_NAMES; i++) {
		if (TabList.NameOffsets[i] > index) TabList.NameOffsets[i] -= 3;
	}
}

void TabList_Remove(EntityID id) {
	TabList_Delete(id);
	TabList.NameOffsets[id] = 0;
	TabList.GroupRanks[id]  = 0;
	Event_RaiseInt(&TabListEvents.Removed, id);
}

void TabList_Set(EntityID id, const cc_string* player_, const cc_string* list, const cc_string* group, cc_uint8 rank) {
	cc_string oldPlayer, oldList, oldGroup;
	cc_uint8 oldRank;
	struct Event_Int* events;

	/* Player name shouldn't have colour codes */
	/*  (intended for e.g. tab autocomplete) */
	cc_string player; char playerBuffer[STRING_SIZE];
	String_InitArray(player, playerBuffer);
	String_AppendColorless(&player, player_);
	
	if (TabList.NameOffsets[id]) {
		oldPlayer = TabList_UNSAFE_GetPlayer(id);
		oldList   = TabList_UNSAFE_GetList(id);
		oldGroup  = TabList_UNSAFE_GetGroup(id);
		oldRank   = TabList.GroupRanks[id];

		/* Don't redraw the tab list if nothing changed */
		if (String_Equals(&player, &oldPlayer) && String_Equals(list, &oldList)
			&& String_Equals(group, &oldGroup) && rank == oldRank) return;

		events = &TabListEvents.Changed;
	} else {
		events = &TabListEvents.Added;
	}
	TabList_Delete(id);

	StringsBuffer_Add(&TabList._buffer, &player);
	StringsBuffer_Add(&TabList._buffer, list);
	StringsBuffer_Add(&TabList._buffer, group);

	TabList.NameOffsets[id] = TabList._buffer.count;
	TabList.GroupRanks[id]  = rank;
	Event_RaiseInt(events, id);
}

static void Tablist_Init(void) {
	TabList_Set(ENTITIES_SELF_ID, &Game_Username, &Game_Username, &String_Empty, 0);
}

static void TabList_Clear(void) {
	Mem_Set(TabList.NameOffsets, 0, sizeof(TabList.NameOffsets));
	Mem_Set(TabList.GroupRanks,  0, sizeof(TabList.GroupRanks));
	StringsBuffer_Clear(&TabList._buffer);
}

struct IGameComponent TabList_Component = {
	Tablist_Init,  /* Init  */
	TabList_Clear, /* Free  */
	TabList_Clear  /* Reset */
};


/*########################################################################################################################*
*------------------------------------------------------LocalPlayer--------------------------------------------------------*
*#########################################################################################################################*/
struct LocalPlayer LocalPlayer_Instances[MAX_LOCAL_PLAYERS];
static cc_bool hackPermMsgs;
static struct LocalPlayerInput* sources_head;
static struct LocalPlayerInput* sources_tail;

void LocalPlayerInput_Add(struct LocalPlayerInput* source) {
	LinkedList_Append(source, sources_head, sources_tail);
}

void LocalPlayerInput_Remove(struct LocalPlayerInput* source) {
	struct LocalPlayerInput* cur;
	LinkedList_Remove(source, cur, sources_head, sources_tail);
}

float LocalPlayer_JumpHeight(struct LocalPlayer* p) {
	return (float)PhysicsComp_CalcMaxHeight(p->Physics.JumpVel);
}

void LocalPlayer_SetInterpPosition(struct LocalPlayer* p, float t) {
	if (!(p->Hacks.WOMStyleHacks && p->Hacks.Noclip)) {
		Vec3_Lerp(&p->Base.Position, &p->Base.prev.pos, &p->Base.next.pos, t);
	}
	Entity_LerpAngles(&p->Base, t);
}

static void LocalPlayer_HandleInput(struct LocalPlayer* p, float* xMoving, float* zMoving) {
	struct HacksComp* hacks = &p->Hacks;
	struct LocalPlayerInput* input;
	if (Gui.InputGrab) return;

	/* keyboard input, touch, joystick, etc */
	for (input = sources_head; input; input = input->next) {
		input->GetMovement(p, xMoving, zMoving);
	}
	*xMoving *= 0.98f;
	*zMoving *= 0.98f;

	if (hacks->WOMStyleHacks && hacks->Enabled && hacks->CanNoclip) {
		if (hacks->Noclip) {
			/* need a { } block because it's a macro */
			Vec3_Set(p->Base.Velocity, 0,0,0);
		}
		HacksComp_SetNoclip(hacks, hacks->_noclipping);
	}
}

static void LocalPlayer_SetLocation(struct Entity* e, struct LocationUpdate* update) {
	struct LocalPlayer* p = (struct LocalPlayer*)e;
	LocalInterpComp_SetLocation(&p->Interp, update, e);
}

static void LocalPlayer_Tick(struct Entity* e, float delta) {
	struct LocalPlayer* p = (struct LocalPlayer*)e;
	struct HacksComp* hacks = &p->Hacks;
	float xMoving = 0, zMoving = 0;
	cc_bool wasOnGround;
	Vec3 headingVelocity;

	if (!World.Loaded) return;
	p->Collisions.StepSize = hacks->FullBlockStep && hacks->Enabled && hacks->CanSpeed ? 1.0f : 0.5f;
	p->OldVelocity = e->Velocity;
	wasOnGround    = e->OnGround;

	LocalInterpComp_AdvanceState(&p->Interp, e);
	LocalPlayer_HandleInput(p, &xMoving, &zMoving);
	hacks->Floating = hacks->Noclip || hacks->Flying;
	if (!hacks->Floating && hacks->CanBePushed) PhysicsComp_DoEntityPush(e);

	/* Immediate stop in noclip mode */
	if (!hacks->NoclipSlide && (hacks->Noclip && xMoving == 0 && zMoving == 0)) {
		Vec3_Set(e->Velocity, 0,0,0);
	}

	PhysicsComp_UpdateVelocityState(&p->Physics);
	headingVelocity = Vec3_RotateY3(xMoving, 0, zMoving, e->Yaw * MATH_DEG2RAD);
	PhysicsComp_PhysicsTick(&p->Physics, headingVelocity);

	/* Fixes high jump, when holding down a movement key, jump, fly, then let go of fly key */
	if (p->Hacks.Floating) e->Velocity.y = 0.0f;

	e->next.pos = e->Position; e->Position = e->prev.pos;
	AnimatedComp_Update(e, e->prev.pos, e->next.pos, delta);
	TiltComp_Update(p, &p->Tilt, delta);

	Entity_CheckSkin(&p->Base);
	SoundComp_Tick(p, wasOnGround);
}

static void LocalPlayer_RenderModel(struct Entity* e, float delta, float t) {
	struct LocalPlayer* p = (struct LocalPlayer*)e;
	AnimatedComp_GetCurrent(e, t);

	if (!Camera.Active->isThirdPerson && p == Entities.CurPlayer) return;
	Model_Render(e->Model, e);
}

static cc_bool LocalPlayer_ShouldRenderName(struct Entity* e) {
	return Camera.Active->isThirdPerson;
}

static void LocalPlayer_CheckJumpVelocity(void* obj) {
	struct LocalPlayer* p = (struct LocalPlayer*)obj;
	if (!HacksComp_CanJumpHigher(&p->Hacks)) {
		p->Physics.JumpVel = p->Physics.ServerJumpVel;
	}
}

static const struct EntityVTABLE localPlayer_VTABLE = {
	LocalPlayer_Tick,        Player_Despawn,         LocalPlayer_SetLocation, Entity_GetColor,
	LocalPlayer_RenderModel, LocalPlayer_ShouldRenderName
};
static void LocalPlayer_Init(struct LocalPlayer* p, int index) {
	struct HacksComp* hacks = &p->Hacks;

	Entity_Init(&p->Base);
	Entity_SetName(&p->Base, &Game_Username);
	Entity_SetSkin(&p->Base, &Game_Username);
	Event_Register_(&UserEvents.HackPermsChanged, p, LocalPlayer_CheckJumpVelocity);

	p->Collisions.Entity = &p->Base;
	HacksComp_Init(hacks);
	PhysicsComp_Init(&p->Physics, &p->Base);
	TiltComp_Init(&p->Tilt);

	p->Base.Flags |= ENTITY_FLAG_MODEL_RESTRICTED_SCALE;
	p->ReachDistance = 5.0f;
	p->Physics.Hacks = &p->Hacks;
	p->Physics.Collisions = &p->Collisions;
	p->Base.VTABLE   = &localPlayer_VTABLE;
	p->index = index;

	hacks->Enabled = !Game_PureClassic && Options_GetBool(OPT_HACKS_ENABLED, true);
	if (Game_ClassicMode) return;

	hacks->SpeedMultiplier = Options_GetFloat(OPT_SPEED_FACTOR,  0.1f, 50.0f, 10.0f);
	hacks->PushbackPlacing = Options_GetBool(OPT_PUSHBACK_PLACING, false);
	hacks->NoclipSlide     = Options_GetBool(OPT_NOCLIP_SLIDE,     false);
	hacks->WOMStyleHacks   = Options_GetBool(OPT_WOM_STYLE_HACKS,  false);
	hacks->FullBlockStep   = Options_GetBool(OPT_FULL_BLOCK_STEP,  false);
	p->Physics.UserJumpVel = Options_GetFloat(OPT_JUMP_VELOCITY, 0.0f, 52.0f, 0.42f);
	p->Physics.JumpVel     = p->Physics.UserJumpVel;
	hackPermMsgs           = Options_GetBool(OPT_HACK_PERM_MSGS, true);
}

void LocalPlayer_ResetJumpVelocity(struct LocalPlayer* p) {
	cc_bool higher = HacksComp_CanJumpHigher(&p->Hacks);

	p->Physics.JumpVel       = higher ? p->Physics.UserJumpVel : 0.42f;
	p->Physics.ServerJumpVel = p->Physics.JumpVel;
}

static void LocalPlayer_Reset(struct LocalPlayer* p) {
	p->ReachDistance = 5.0f;
	Vec3_Set(p->Base.Velocity, 0,0,0);
	LocalPlayer_ResetJumpVelocity(p);
}

static void LocalPlayers_Reset(void) {
	int i;
	for (i = 0; i < Game_NumStates; i++)
	{
		LocalPlayer_Reset(&LocalPlayer_Instances[i]);
	}
}

static void LocalPlayer_OnNewMap(struct LocalPlayer* p) {
	Vec3_Set(p->Base.Velocity, 0,0,0);
	Vec3_Set(p->OldVelocity,   0,0,0);

	p->_warnedRespawn = false;
	p->_warnedFly     = false;
	p->_warnedNoclip  = false;
	p->_warnedZoom    = false;
}

static void LocalPlayers_OnNewMap(void) {
	int i;
	for (i = 0; i < Game_NumStates; i++)
	{
		LocalPlayer_OnNewMap(&LocalPlayer_Instances[i]);
	}
}

static cc_bool LocalPlayer_IsSolidCollide(BlockID b) { return Blocks.Collide[b] == COLLIDE_SOLID; }

static void LocalPlayer_DoRespawn(struct LocalPlayer* p) {
	struct EntityLocation* prev;
	struct LocationUpdate update;
	struct AABB bb;
	Vec3 spawn = p->Spawn;
	IVec3 pos;
	BlockID block;
	float height, spawnY;
	int y;

	if (!World.Loaded) return;
	IVec3_Floor(&pos, &spawn);	

	/* Spawn player at highest solid position to match vanilla Minecraft classic */
	/* Only when player can noclip, since this can let you 'clip' to above solid blocks */
	if (p->Hacks.CanNoclip) {
		AABB_Make(&bb, &spawn, &p->Base.Size);
		for (y = pos.y; y <= World.Height; y++) {
			spawnY = Respawn_HighestSolidY(&bb);

			if (spawnY == RESPAWN_NOT_FOUND) {
				block   = World_SafeGetBlock(pos.x, y, pos.z);
				height  = Blocks.Collide[block] == COLLIDE_SOLID ? Blocks.MaxBB[block].y : 0.0f;
				spawn.y = y + height + ENTITY_ADJUSTMENT;
				break;
			}
			bb.Min.y += 1.0f; bb.Max.y += 1.0f;
		}
	}

	prev = &p->Base.prev;
	CPE_SendNotifyPositionAction(3, prev->pos.x, prev->pos.y, prev->pos.z);

	/* Adjust the position to be slightly above the ground, so that */
	/*  it's obvious to the player that they are being respawned */
	spawn.y += 2.0f/16.0f;

	update.flags = LU_HAS_POS | LU_HAS_YAW | LU_HAS_PITCH;
	update.pos   = spawn;
	update.yaw   = p->SpawnYaw;
	update.pitch = p->SpawnPitch;
	p->Base.VTABLE->SetLocation(&p->Base, &update);

	Vec3_Set(p->Base.Velocity, 0,0,0);
	/* Update onGround, otherwise if 'respawn' then 'space' is pressed, you still jump into the air if onGround was true before */
	Entity_GetBounds(&p->Base, &bb);
	bb.Min.y -= 0.01f; bb.Max.y = bb.Min.y;
	p->Base.OnGround = Entity_TouchesAny(&bb, LocalPlayer_IsSolidCollide);
}

static cc_bool LocalPlayer_HandleRespawn(int key, struct InputDevice* device) {
	struct LocalPlayer* p = &LocalPlayer_Instances[device->mappedIndex];
	if (Gui.InputGrab) return false;
	
	if (p->Hacks.CanRespawn) {
		LocalPlayer_DoRespawn(p);
		return true;
	} else if (!p->_warnedRespawn) {
		p->_warnedRespawn = true;
		if (hackPermMsgs) Chat_AddRaw("&cRespawning is currently disabled");
	}
	return false;
}

static cc_bool LocalPlayer_HandleSetSpawn(int key, struct InputDevice* device) {
	struct LocalPlayer* p = &LocalPlayer_Instances[device->mappedIndex];
	if (Gui.InputGrab) return false;
	
	if (p->Hacks.CanRespawn) {

		if (!p->Hacks.CanNoclip && !p->Base.OnGround) {
			Chat_AddRaw("&cCannot set spawn midair when noclip is disabled");
			return false;
		}

		/* Spawn is normally centered to match vanilla Minecraft classic */
		if (!p->Hacks.CanNoclip) {
			/* Don't want to use Position because it is interpolated between prev and next. */
			/* This means it can be halfway between stepping up a stair and clip through the floor. */
			p->Spawn   = p->Base.prev.pos;
		} else {
			p->Spawn.x = Math_Floor(p->Base.Position.x) + 0.5f;
			p->Spawn.y = p->Base.Position.y;
			p->Spawn.z = Math_Floor(p->Base.Position.z) + 0.5f;
		}
		
		p->SpawnYaw   = p->Base.Yaw;
		if (!Game_ClassicMode) p->SpawnPitch = p->Base.Pitch;

		CPE_SendNotifyPositionAction(4, p->Spawn.x, p->Spawn.y, p->Spawn.z);
	}
	return LocalPlayer_HandleRespawn(key, device);
}

static cc_bool LocalPlayer_HandleFly(int key, struct InputDevice* device) {
	struct LocalPlayer* p = &LocalPlayer_Instances[device->mappedIndex];
	if (Gui.InputGrab) return false;

	if (p->Hacks.CanFly && p->Hacks.Enabled) {
		HacksComp_SetFlying(&p->Hacks, !p->Hacks.Flying);
		return true;
	} else if (!p->_warnedFly) {
		p->_warnedFly = true;
		if (hackPermMsgs) Chat_AddRaw("&cFlying is currently disabled");
	}
	return false;
}

static cc_bool LocalPlayer_HandleNoclip(int key, struct InputDevice* device) {
	struct LocalPlayer* p = &LocalPlayer_Instances[device->mappedIndex];
	p->Hacks._noclipping = true;
	if (Gui.InputGrab) return false;

	if (p->Hacks.CanNoclip && p->Hacks.Enabled) {
		if (p->Hacks.WOMStyleHacks) return true; /* don't handle this here */
		if (p->Hacks.Noclip) p->Base.Velocity.y = 0;

		HacksComp_SetNoclip(&p->Hacks, !p->Hacks.Noclip);
		return true;
	} else if (!p->_warnedNoclip) {
		p->_warnedNoclip = true;
		if (hackPermMsgs) Chat_AddRaw("&cNoclip is currently disabled");
	}
	return false;
}

static cc_bool LocalPlayer_HandleJump(int key, struct InputDevice* device) {
	struct LocalPlayer* p = &LocalPlayer_Instances[device->mappedIndex];
	struct HacksComp* hacks     = &p->Hacks;
	struct PhysicsComp* physics = &p->Physics;
	int maxJumps;
	if (Gui.InputGrab) return false;
	physics->Jumping = true;

	if (!p->Base.OnGround && !(hacks->Flying || hacks->Noclip)) {
		maxJumps = hacks->CanDoubleJump && hacks->WOMStyleHacks ? 2 : 0;
		maxJumps = max(maxJumps, hacks->MaxJumps - 1);

		if (physics->MultiJumps < maxJumps) {
			PhysicsComp_DoNormalJump(physics);
			physics->MultiJumps++;
		}
		return true;
	}
	return false;
}


static cc_bool LocalPlayer_TriggerHalfSpeed(int key, struct InputDevice* device) {
	struct HacksComp* hacks = &LocalPlayer_Instances[device->mappedIndex].Hacks;
	cc_bool touch = device->type == INPUT_DEVICE_TOUCH;
	if (Gui.InputGrab) return false;

	hacks->HalfSpeeding = (!touch || !hacks->HalfSpeeding) && hacks->Enabled;
	return true;
}

static cc_bool LocalPlayer_TriggerSpeed(int key, struct InputDevice* device) {
	struct HacksComp* hacks = &LocalPlayer_Instances[device->mappedIndex].Hacks;
	cc_bool touch = device->type == INPUT_DEVICE_TOUCH;
	if (Gui.InputGrab) return false;

	hacks->Speeding = (!touch || !hacks->Speeding) && hacks->Enabled;
	return true;
}

static void LocalPlayer_ReleaseHalfSpeed(int key, struct InputDevice* device) {
	struct HacksComp* hacks = &LocalPlayer_Instances[device->mappedIndex].Hacks;
	if (device->type != INPUT_DEVICE_TOUCH) hacks->HalfSpeeding = false;
}

static void LocalPlayer_ReleaseSpeed(int key, struct InputDevice* device) {
	struct HacksComp* hacks = &LocalPlayer_Instances[device->mappedIndex].Hacks;
	if (device->type != INPUT_DEVICE_TOUCH) hacks->Speeding = false;
}


static cc_bool LocalPlayer_TriggerFlyUp(int key, struct InputDevice* device) {
	struct HacksComp* hacks = &LocalPlayer_Instances[device->mappedIndex].Hacks;
	if (Gui.InputGrab) return false;
	
	hacks->FlyingUp = true;
	return hacks->CanFly && hacks->Enabled;
}

static cc_bool LocalPlayer_TriggerFlyDown(int key, struct InputDevice* device) {
	struct HacksComp* hacks = &LocalPlayer_Instances[device->mappedIndex].Hacks;
	if (Gui.InputGrab) return false;
	
	hacks->FlyingDown = true;
	return hacks->CanFly && hacks->Enabled;
}

static void LocalPlayer_ReleaseFlyUp(int key, struct InputDevice* device) {
	LocalPlayer_Instances[device->mappedIndex].Hacks.FlyingUp   = false;
}

static void LocalPlayer_ReleaseFlyDown(int key, struct InputDevice* device) {
	LocalPlayer_Instances[device->mappedIndex].Hacks.FlyingDown = false;
}

static void LocalPlayer_ReleaseJump(int key, struct InputDevice* device) {
	LocalPlayer_Instances[device->mappedIndex].Physics.Jumping = false;
}

static void LocalPlayer_ReleaseNoclip(int key, struct InputDevice* device) {
	LocalPlayer_Instances[device->mappedIndex].Hacks._noclipping = false;
}

static void LocalPlayer_HookBinds(void) {
	Bind_OnTriggered[BIND_RESPAWN]   = LocalPlayer_HandleRespawn;
	Bind_OnTriggered[BIND_SET_SPAWN] = LocalPlayer_HandleSetSpawn;
	Bind_OnTriggered[BIND_FLY]       = LocalPlayer_HandleFly;
	Bind_OnTriggered[BIND_NOCLIP]    = LocalPlayer_HandleNoclip;
	Bind_OnTriggered[BIND_JUMP]      = LocalPlayer_HandleJump;

	Bind_OnTriggered[BIND_HALF_SPEED] = LocalPlayer_TriggerHalfSpeed;
	Bind_OnTriggered[BIND_SPEED]      = LocalPlayer_TriggerSpeed;
	Bind_OnReleased[BIND_HALF_SPEED]  = LocalPlayer_ReleaseHalfSpeed;
	Bind_OnReleased[BIND_SPEED]       = LocalPlayer_ReleaseSpeed;

	Bind_OnTriggered[BIND_FLY_UP]   = LocalPlayer_TriggerFlyUp;
	Bind_OnTriggered[BIND_FLY_DOWN] = LocalPlayer_TriggerFlyDown;
	Bind_OnReleased[BIND_FLY_UP]    = LocalPlayer_ReleaseFlyUp;
	Bind_OnReleased[BIND_FLY_DOWN]  = LocalPlayer_ReleaseFlyDown;

	Bind_OnReleased[BIND_JUMP]    = LocalPlayer_ReleaseJump;
	Bind_OnReleased[BIND_NOCLIP]  = LocalPlayer_ReleaseNoclip;
}

cc_bool LocalPlayer_CheckCanZoom(struct LocalPlayer* p) {
	if (p->Hacks.CanFly) return true;

	if (!p->_warnedZoom) {
		p->_warnedZoom = true;
		if (hackPermMsgs) Chat_AddRaw("&cCannot zoom camera out as flying is currently disabled");
	}
	return false;
}

void LocalPlayers_MoveToSpawn(struct LocationUpdate* update) {
	struct LocalPlayer* p;
	int i;
	
	for (i = 0; i < Game_NumStates; i++)
	{
		p = &LocalPlayer_Instances[i];
		p->Base.VTABLE->SetLocation(&p->Base, update);
		
		if (update->flags & LU_HAS_POS)   p->Spawn      = update->pos;
		if (update->flags & LU_HAS_YAW)   p->SpawnYaw   = update->yaw;
		if (update->flags & LU_HAS_PITCH) p->SpawnPitch = update->pitch;
	}
	
	/* TODO: This needs to be before new map... */
	Camera.CurrentPos = Camera.Active->GetPosition(0.0f);
}

void LocalPlayer_CalcDefaultSpawn(struct LocalPlayer* p, struct LocationUpdate* update) {
	float x = (World.Width  / 2) + 0.5f;
	float z = (World.Length / 2) + 0.5f;

	update->flags = LU_HAS_POS | LU_HAS_YAW | LU_HAS_PITCH;
	update->pos   = Respawn_FindSpawnPosition(x, z, p->Base.Size);
	update->yaw   = 0.0f;
	update->pitch = 0.0f;
}


/*########################################################################################################################*
*-------------------------------------------------------NetPlayer---------------------------------------------------------*
*#########################################################################################################################*/
struct NetPlayer NetPlayers_List[MAX_NET_PLAYERS];

static void NetPlayer_SetLocation(struct Entity* e, struct LocationUpdate* update) {
	struct NetPlayer* p = (struct NetPlayer*)e;
	NetInterpComp_SetLocation(&p->Interp, update, e);
}

static void NetPlayer_Tick(struct Entity* e, float delta) {
	struct NetPlayer* p = (struct NetPlayer*)e;
	NetInterpComp_AdvanceState(&p->Interp, e);

	Entity_CheckSkin(e);
	AnimatedComp_Update(e, e->prev.pos, e->next.pos, delta);
}

static void NetPlayer_RenderModel(struct Entity* e, float delta, float t) {
	Vec3_Lerp(&e->Position, &e->prev.pos, &e->next.pos, t);
	Entity_LerpAngles(e, t);

	AnimatedComp_GetCurrent(e, t);
	e->ShouldRender = Model_ShouldRender(e);
	/* Original classic only shows players up to 64 blocks away */
	if (Game_ClassicMode) e->ShouldRender &= Model_RenderDistance(e) <= 64 * 64;

	if (e->ShouldRender) Model_Render(e->Model, e);
}

static cc_bool NetPlayer_ShouldRenderName(struct Entity* e) {
	float distance;
	int threshold;
	if (!e->ShouldRender) return false;

	distance  = Model_RenderDistance(e);
	threshold = Entities.NamesMode == NAME_MODE_ALL_UNSCALED ? 8192 * 8192 : 32 * 32;
	return distance <= (float)threshold;
}

static const struct EntityVTABLE netPlayer_VTABLE = {
	NetPlayer_Tick,        Player_Despawn,       NetPlayer_SetLocation, Entity_GetColor,
	NetPlayer_RenderModel, NetPlayer_ShouldRenderName
};
void NetPlayer_Init(struct NetPlayer* p) {
	Mem_Set(p, 0, sizeof(struct NetPlayer));
	Entity_Init(&p->Base);
	p->Base.Flags |= ENTITY_FLAG_CLASSIC_ADJUST;
	p->Base.VTABLE = &netPlayer_VTABLE;
}


/*########################################################################################################################*
*---------------------------------------------------Entities component----------------------------------------------------*
*#########################################################################################################################*/
static void Entities_Init(void) {
	int i;
	Event_Register_(&GfxEvents.ContextLost, NULL, Entities_ContextLost);

	Entities.NamesMode = Options_GetEnum(OPT_NAMES_MODE, NAME_MODE_HOVERED,
		NameMode_Names, Array_Elems(NameMode_Names));
	if (Game_ClassicMode) Entities.NamesMode = NAME_MODE_HOVERED;

	Entities.ShadowsMode = Options_GetEnum(OPT_ENTITY_SHADOW, SHADOW_MODE_NONE,
		ShadowMode_Names, Array_Elems(ShadowMode_Names));
	if (Game_ClassicMode) Entities.ShadowsMode = SHADOW_MODE_NONE;

	for (i = 0; i < Game_NumStates; i++)
	{
		LocalPlayer_Init(&LocalPlayer_Instances[i], i);
		Entities.List[MAX_NET_PLAYERS + i] = &LocalPlayer_Instances[i].Base;
	}
	for (; i < MAX_LOCAL_PLAYERS; i++)
	{
		Entities.List[MAX_NET_PLAYERS + i] = NULL;
	}
	Entities.CurPlayer = &LocalPlayer_Instances[0];
	LocalPlayer_HookBinds();
}

static void Entities_Free(void) {
	int i;
	for (i = 0; i < ENTITIES_MAX_COUNT; i++)
	{
		Entities_Remove(i);
	}
	sources_head = NULL;
}

struct IGameComponent Entities_Component = {
	Entities_Init,  /* Init  */
	Entities_Free,  /* Free  */
	LocalPlayers_Reset,    /* Reset */
	LocalPlayers_OnNewMap, /* OnNewMap */
};
