#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 */ };