mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-08-04 03:07:07 -04:00
1168 lines
38 KiB
C
1168 lines
38 KiB
C
#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;
|
|
|
|
Bitmap_TryAllocate(&scaled, width, height);
|
|
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 */
|
|
};
|