diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Entity.c |
initial commit
Diffstat (limited to 'src/Entity.c')
-rw-r--r-- | src/Entity.c | 1107 |
1 files changed, 1107 insertions, 0 deletions
diff --git a/src/Entity.c b/src/Entity.c new file mode 100644 index 0000000..b400c85 --- /dev/null +++ b/src/Entity.c @@ -0,0 +1,1107 @@ +#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 "Gui.h" +#include "Stream.h" +#include "Bitmap.h" +#include "Logger.h" +#include "Options.h" +#include "Errors.h" +#include "Utils.h" +#include "EntityRenderers.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(EntityID 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 (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 == -1 || 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) { + /* TODO: Don't always turn these off anytime a screen is opened, only do it on InputUp */ + p->Physics.Jumping = false; hacks->FlyingUp = false; hacks->FlyingDown = false; + 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; + + p->Physics.Jumping = InputBind_IsPressed(BIND_JUMP); + hacks->FlyingUp = InputBind_IsPressed(BIND_FLY_UP); + hacks->FlyingDown = InputBind_IsPressed(BIND_FLY_DOWN); + + 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, InputBind_IsPressed(BIND_NOCLIP)); + } +} + +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); + TiltComp_GetCurrent(p, &p->Tilt, 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); + /* p->Base.Health = 20; TODO: survival mode stuff */ + 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_NumLocalPlayers; 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_NumLocalPlayers; 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 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 (true) { // 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; + } + } + + /* 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 LocalPlayer* p = Entities.CurPlayer; + if (true) { // 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 LocalPlayer* p = Entities.CurPlayer; + if (true) { // p->Hacks.CanRespawn // + + if (!true && !p->Base.OnGround) { // p->Hacks.CanNoclip // + Chat_AddRaw("&cCannot set spawn midair when noclip is disabled"); + return false; + } + + /* Spawn is normally centered to match vanilla Minecraft classic */ + if (!true) { // 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; + p->SpawnPitch = p->Base.Pitch; + } + return LocalPlayer_HandleRespawn(key); +} + +static cc_bool LocalPlayer_HandleFly(int key) { + struct LocalPlayer* p = Entities.CurPlayer; + + if (true && p->Hacks.Enabled) { // p->Hacks.CanFly // + 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 LocalPlayer* p = Entities.CurPlayer; + + if (true && p->Hacks.Enabled) { // p->Hacks.CanNoclip // + 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 LocalPlayer* p = Entities.CurPlayer; + struct HacksComp* hacks = &p->Hacks; + struct PhysicsComp* physics = &p->Physics; + int maxJumps; + + 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 HacksComp* hacks = &Entities.CurPlayer->Hacks; + hacks->HalfSpeeding = hacks->Enabled; + return true; +} + +static cc_bool LocalPlayer_TriggerSpeed(int key) { + struct HacksComp* hacks = &Entities.CurPlayer->Hacks; + hacks->Speeding = hacks->Enabled; + return true; +} + +static void LocalPlayer_ReleaseHalfSpeed(int key) { + struct HacksComp* hacks = &Entities.CurPlayer->Hacks; + hacks->HalfSpeeding = false; +} + +static void LocalPlayer_ReleaseSpeed(int key) { + struct HacksComp* hacks = &Entities.CurPlayer->Hacks; + hacks->Speeding = 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; +} + +cc_bool LocalPlayer_CheckCanZoom(struct LocalPlayer* p) { + if (true) return true; // p->Hacks.CanFly // + + 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_NumLocalPlayers; 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_NumLocalPlayers; i++) + { + LocalPlayer_Init(&LocalPlayer_Instances[i], i); + Entities.List[MAX_NET_PLAYERS + i] = &LocalPlayer_Instances[i].Base; + } + Entities.CurPlayer = &LocalPlayer_Instances[0]; + LocalPlayer_HookBinds(); +} + +static void Entities_Free(void) { + int i; + for (i = 0; i < ENTITIES_MAX_COUNT; i++) + { + Entities_Remove((EntityID)i); + } + sources_head = NULL; +} + +struct IGameComponent Entities_Component = { + Entities_Init, /* Init */ + Entities_Free, /* Free */ + LocalPlayers_Reset, /* Reset */ + LocalPlayers_OnNewMap, /* OnNewMap */ +}; |