diff --git a/Minecraft.Client/Common/Audio/SoundEngine.cpp b/Minecraft.Client/Common/Audio/SoundEngine.cpp index 927b973c..0bc4c831 100644 --- a/Minecraft.Client/Common/Audio/SoundEngine.cpp +++ b/Minecraft.Client/Common/Audio/SoundEngine.cpp @@ -607,7 +607,91 @@ void SoundEngine::play(int iSound, float x, float y, float z, float volume, floa ///////////////////////////////////////////// // -// playUI +// +// startElytraSound / stopElytraSound +// Manages a single persistent looping sound for elytra gliding. +// Call startElytraSound every tick while gliding (it no-ops if already running, +// just updates volume). Call stopElytraSound when gliding ends. +// +// IMPORTANT: m_elytraLoopingSound is NOT added to m_activeSounds. +// The tick() cleanup loop deletes sounds where is_playing()==false. +// A looping sound briefly reports is_playing()==false at the loop point, +// which would cause tick() to free it and leave m_elytraLoopingSound dangling. +// +///////////////////////////////////////////// +void SoundEngine::startElytraSound(float x, float y, float z, float volume, float pitch) +{ + // If already initialized just update volume and pitch - never reinitialize mid-flight. + if (m_elytraLoopingSound != nullptr) + { + float finalVolume = volume * m_MasterEffectsVolume * SFX_VOLUME_MULTIPLIER; + if (finalVolume > SFX_MAX_GAIN) finalVolume = SFX_MAX_GAIN; + ma_sound_set_volume(&m_elytraLoopingSound->sound, finalVolume); + ma_sound_set_pitch(&m_elytraLoopingSound->sound, pitch); + return; + } + + // Resolve file path using the same logic as play(). + wstring name = wchSoundNames[eSoundType_ITEM_ELYTRA_FLYING]; + char* soundName = ConvertSoundPathToName(name); + char basePath[256]; + sprintf_s(basePath, "Windows64Media/Sound/Minecraft/%s", soundName); + + const char* extensions[] = { ".ogg", ".wav", ".mp3" }; + char finalPath[256] = {}; + bool found = false; + for (auto& ext : extensions) + { + char candidate[256]; + sprintf_s(candidate, "%s%s", basePath, ext); + DWORD attr = GetFileAttributesA(candidate); + if (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY)) + { + sprintf_s(finalPath, "%s", candidate); + found = true; + break; + } + } + if (!found) return; + + MiniAudioSound* s = new MiniAudioSound(); + memset(&s->info, 0, sizeof(AUDIO_INFO)); + s->info.volume = volume; s->info.pitch = pitch; + s->info.bIs3D = false; + s->info.iSound = eSoundType_ITEM_ELYTRA_FLYING + eSFX_MAX; + + // Synchronous load so the sound is immediately ready - no ASYNC gap. + if (ma_sound_init_from_file(&m_engine, finalPath, 0, + nullptr, nullptr, &s->sound) != MA_SUCCESS) + { + delete s; + return; + } + + ma_sound_set_spatialization_enabled(&s->sound, MA_FALSE); + ma_sound_set_looping(&s->sound, MA_TRUE); + + float finalVolume = volume * m_MasterEffectsVolume * SFX_VOLUME_MULTIPLIER; + if (finalVolume > SFX_MAX_GAIN) finalVolume = SFX_MAX_GAIN; + ma_sound_set_volume(&s->sound, finalVolume); + ma_sound_set_pitch(&s->sound, pitch); + ma_sound_start(&s->sound); + + // NOT added to m_activeSounds - tick() cleanup would delete it at loop boundaries. + m_elytraLoopingSound = s; +} + +void SoundEngine::stopElytraSound() +{ + if (m_elytraLoopingSound == nullptr) return; + + ma_sound_stop(&m_elytraLoopingSound->sound); + ma_sound_uninit(&m_elytraLoopingSound->sound); + delete m_elytraLoopingSound; + m_elytraLoopingSound = nullptr; +} +///////////////////////////////////////////// +// playUI // ///////////////////////////////////////////// void SoundEngine::playUI(int iSound, float volume, float pitch) diff --git a/Minecraft.Client/Common/Audio/SoundEngine.h b/Minecraft.Client/Common/Audio/SoundEngine.h index 1d2b4f90..5a593df9 100644 --- a/Minecraft.Client/Common/Audio/SoundEngine.h +++ b/Minecraft.Client/Common/Audio/SoundEngine.h @@ -127,6 +127,8 @@ public: void GetSoundName(char *szSoundName,int iSound); #endif void play(int iSound, float x, float y, float z, float volume, float pitch) override; + void startElytraSound(float x, float y, float z, float volume, float pitch); + void stopElytraSound(); void playStreaming(const wstring& name, float x, float y , float z, float volume, float pitch, bool bMusicDelay=true) override; void playUI(int iSound, float volume, float pitch) override; void playMusicTick() override; @@ -159,6 +161,9 @@ private: int GetRandomishTrack(int iStart,int iEnd); + MiniAudioSound* m_elytraLoopingSound = nullptr; + + ma_engine m_engine; ma_engine_config m_engineConfig; ma_sound m_musicStream; diff --git a/Minecraft.Client/Common/Audio/SoundNames.cpp b/Minecraft.Client/Common/Audio/SoundNames.cpp index 05fe5a52..c82228e2 100644 --- a/Minecraft.Client/Common/Audio/SoundNames.cpp +++ b/Minecraft.Client/Common/Audio/SoundNames.cpp @@ -272,6 +272,8 @@ const WCHAR *ConsoleSoundEngine::wchSoundNames[eSoundType_MAX]= L"item.armor.equip_generic6", L"damage.critical", //eSoundType_DAMAGE_CRITICAL, + L"item.elytra.flying" // eSoundType_ITEM_ELYTRA_FLYING + }; diff --git a/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp b/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp index 2b54b2eb..a61edb05 100644 --- a/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp +++ b/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp @@ -1273,6 +1273,10 @@ void IUIScene_AbstractContainerMenu::onMouseTick() case Item::chestplate_gold_Id: case Item::leggings_gold_Id: case Item::boots_gold_Id: + + + case Item::elytra_Id: + buttonY=eToolTipQuickMoveArmor; break; diff --git a/Minecraft.Client/Common/UI/IUIScene_CreativeMenu.cpp b/Minecraft.Client/Common/UI/IUIScene_CreativeMenu.cpp index 29125a97..a3877a0a 100644 --- a/Minecraft.Client/Common/UI/IUIScene_CreativeMenu.cpp +++ b/Minecraft.Client/Common/UI/IUIScene_CreativeMenu.cpp @@ -383,6 +383,7 @@ void IUIScene_CreativeMenu::staticCtor() ITEM(Item::minecart_tnt_Id) ITEM(Item::saddle_Id) ITEM(Item::boat_Id) + ITEM(Item::elytra_Id) // Miscellaneous DEF(eCreativeInventory_Misc) diff --git a/Minecraft.Client/Common/res/1_2_2/item/elytra.png b/Minecraft.Client/Common/res/1_2_2/item/elytra.png new file mode 100644 index 00000000..895c2b51 Binary files /dev/null and b/Minecraft.Client/Common/res/1_2_2/item/elytra.png differ diff --git a/Minecraft.Client/Common/res/TitleUpdate/res/items.png b/Minecraft.Client/Common/res/TitleUpdate/res/items.png index 257fa098..c9ace5b7 100644 Binary files a/Minecraft.Client/Common/res/TitleUpdate/res/items.png and b/Minecraft.Client/Common/res/TitleUpdate/res/items.png differ diff --git a/Minecraft.Client/EntityRenderDispatcher.cpp b/Minecraft.Client/EntityRenderDispatcher.cpp index 9b610b58..7ce3b35a 100644 --- a/Minecraft.Client/EntityRenderDispatcher.cpp +++ b/Minecraft.Client/EntityRenderDispatcher.cpp @@ -189,7 +189,9 @@ EntityRenderDispatcher::EntityRenderDispatcher() it.second->init(this); } - isGuiRender = false; // 4J added + isGuiRender = false; // 4J added + isInventoryRender = false; + } EntityRenderer *EntityRenderDispatcher::getRenderer(eINSTANCEOF e) diff --git a/Minecraft.Client/EntityRenderDispatcher.h b/Minecraft.Client/EntityRenderDispatcher.h index 10740d1b..270ec3f8 100644 --- a/Minecraft.Client/EntityRenderDispatcher.h +++ b/Minecraft.Client/EntityRenderDispatcher.h @@ -32,6 +32,8 @@ public: float playerRotX; Options *options; bool isGuiRender; // 4J added + bool isInventoryRender; + double xPlayer, yPlayer, zPlayer; diff --git a/Minecraft.Client/GameRenderer.cpp b/Minecraft.Client/GameRenderer.cpp index 1d0b9f9b..484b2323 100644 --- a/Minecraft.Client/GameRenderer.cpp +++ b/Minecraft.Client/GameRenderer.cpp @@ -47,6 +47,8 @@ #include "../Minecraft.World/compression.h" #include "PS3/PS3Extras/ShutdownManager.h" #include "BossMobGuiInfo.h" +#include "../Minecraft.World/LivingEntity.h" + #include "TexturePackRepository.h" #include "TexturePack.h" @@ -96,6 +98,10 @@ GameRenderer::GameRenderer(Minecraft *mc) fovOffsetO = 0; cameraRoll = 0; cameraRollO = 0; + m_yBob = 0.0f; + m_yBobO = 0.0f; + m_elytaCamShift = 0.0f; + for( int i = 0; i < 4; i++ ) { fov[i] = 0.0f; @@ -197,6 +203,20 @@ void GameRenderer::tick(bool first) // 4J - add bFirst fovOffsetO = fovOffset; cameraRollO = cameraRoll; + m_yBobO = m_yBob; + + { + shared_ptr yBobPlayer = dynamic_pointer_cast(mc->cameraTargetPlayer); + float target = 0.0f; + if (yBobPlayer != nullptr && !yBobPlayer->onGround + && !(yBobPlayer->abilities.mayfly && yBobPlayer->abilities.flying)) + { + + target = (float)(atan(-yBobPlayer->yd * 0.2) * 15.0); + } + m_yBob += (target - m_yBob) * 0.8f; + } + if (mc->options->smoothCamera) { // update player view in tick() instead of render() to maintain @@ -455,6 +475,11 @@ void GameRenderer::bobView(float a) glRotatef((float) Mth::sin(b * PI) * bob * 3, 0, 0, 1); glRotatef((float) abs(Mth::cos(b * PI - 0.2f) * bob) * 5, 1, 0, 0); glRotatef((float) tilt, 1, 0, 0); + + float yBobAngle = m_yBobO + (m_yBob - m_yBobO) * a; + if (fabsf(yBobAngle) > 0.001f) + glRotatef(yBobAngle, 1, 0, 0); + } void GameRenderer::moveCameraToPlayer(float a) @@ -468,7 +493,71 @@ void GameRenderer::moveCameraToPlayer(float a) double z = player->zo + (player->z - player->zo) * a; - glRotatef(cameraRollO + (cameraRoll - cameraRollO) * a, 0, 0, 1); + { + shared_ptr elytraPlayer = dynamic_pointer_cast(player); + bool elytraFlying = (elytraPlayer != nullptr && elytraPlayer->isElytraFlying()); + float targetShift = elytraFlying ? -1.22f : 0.0f; + m_elytaCamShift += (targetShift - m_elytaCamShift) * 0.15f; + if (fabsf(m_elytaCamShift) < 0.001f) m_elytaCamShift = 0.0f; + + + y += m_elytaCamShift; + } + + + if (localplayer != nullptr && !player->isSleeping()) + { + shared_ptr rollPlayer = dynamic_pointer_cast(player); + if (rollPlayer != nullptr && rollPlayer->isElytraFlying()) + { + + double velX = rollPlayer->xd; + double velZ = rollPlayer->zd; + double horizVelSq = velX * velX + velZ * velZ; + + + Vec3* look = rollPlayer->getLookAngle(); + double horizLookSq = look->x * look->x + look->z * look->z; + + float targetRoll = 0.0f; + if (horizVelSq > 1.0e-6 && horizLookSq > 1.0e-6) + { + double dot = velX * look->x + velZ * look->z; + double cosAngle = dot / sqrt(horizVelSq * horizLookSq); + + if (cosAngle > 1.0) cosAngle = 1.0; + if (cosAngle < -1.0) cosAngle = -1.0; + + + double angle = acos(cosAngle) / 2.5; + if (angle > PI / 8.0) angle = PI / 8.0; + + + double cross = velX * look->z - velZ * look->x; + float sign = (cross < 0.0) ? -1.0f : 1.0f; + + + targetRoll = sign * (float)(angle * (180.0 / PI)); + } + + + cameraRoll += (targetRoll - cameraRoll) * 0.1f; + } + else + { + + if (fabsf(cameraRoll) > 0.01f) + { + cameraRoll += (0.0f - cameraRoll) * 0.15f; + if (fabsf(cameraRoll) < 0.01f) cameraRoll = 0.0f; + } + } + + + if (fabsf(cameraRoll) > 0.01f) + glRotatef(cameraRoll, 0, 0, 1); + } + if (player->isSleeping()) { @@ -574,10 +663,12 @@ void GameRenderer::moveCameraToPlayer(float a) } } - glTranslatef(0, heightOffset, 0); + + glTranslatef(0, heightOffset - m_elytaCamShift, 0); + x = player->xo + (player->x - player->xo) * a; - y = player->yo + (player->y - player->yo) * a - heightOffset; + y = player->yo + (player->y - player->yo) * a - heightOffset + m_elytaCamShift; z = player->zo + (player->z - player->zo) * a; isInClouds = mc->levelRenderer->isInCloud(x, y, z, a); diff --git a/Minecraft.Client/GameRenderer.h b/Minecraft.Client/GameRenderer.h index ed032695..a2ef0023 100644 --- a/Minecraft.Client/GameRenderer.h +++ b/Minecraft.Client/GameRenderer.h @@ -64,6 +64,13 @@ private: float cameraRoll; float cameraRollO; + float m_yBob; + float m_yBobO; + + float m_elytaCamShift; + + + // 4J - changes brought forward from 1.8.2 static const int NUM_LIGHT_TEXTURES = 4;// * 3; int lightTexture[NUM_LIGHT_TEXTURES]; // 4J - changed so that we have one lightTexture per level, to support split screen diff --git a/Minecraft.Client/HumanoidModel.cpp b/Minecraft.Client/HumanoidModel.cpp index 2c83e025..c363f528 100644 --- a/Minecraft.Client/HumanoidModel.cpp +++ b/Minecraft.Client/HumanoidModel.cpp @@ -88,6 +88,17 @@ void HumanoidModel::_init(float g, float yOffset, int texWidth, int texHeight, b cloak = new ModelPart(this, 0, 0); cloak->addHumanoidBox(-5, -0, -1, 10, 16, 1, g); // Cloak + elytraRight = new ModelPart(this, 22, 0); + elytraRight->addHumanoidBox(-10.0f, 0.0f, 0.0f, 10, 20, 2, 0.0f); + elytraRight->setPos(5.0f, 0.0f + yOffset, 0.0f); // Wing Left + + elytraLeft = new ModelPart(this, 22, 0); + elytraLeft->bMirror = true; + elytraLeft->addHumanoidBox(0.0f, 0.0f, 0.0f, 10, 20, 2, 0.0f); + elytraLeft->setPos(-5.0f, 0.0f + yOffset, 0.0f); // Wing Right + + + ear = new ModelPart(this, 24, 0); ear->addHumanoidBox(-3, -6, -1, 6, 6, 1, g); // Ear @@ -186,6 +197,8 @@ void HumanoidModel::_init(float g, float yOffset, int texWidth, int texHeight, b // 4J added - compile now to avoid random performance hit first time cubes are rendered // 4J Stu - Not just performance, but alpha+depth tests don't work right unless we compile here cloak->compile(1.0f/16.0f); + elytraLeft->compile(1.0f / 16.0f); + elytraRight->compile(1.0f / 16.0f); ear->compile(1.0f/16.0f); head->compile(1.0f/16.0f); body->compile(1.0f/16.0f); @@ -211,6 +224,9 @@ void HumanoidModel::_init(float g, float yOffset, int texWidth, int texHeight, b sneaking=false; idle=false; bowAndArrow=false; + elytraFlying = false; + elytraCrouching = false; + // 4J added eating = false; @@ -881,6 +897,57 @@ void HumanoidModel::setupAnim(float time, float r, float bob, float yRot, float } } } + + if (elytraFlying) + { + if (elytraCrouching) + { + arm0->xRot = PI; arm0->yRot = 0.0f; arm0->zRot = 0.0f; arm0->y = 2.0f; + if (sleeve0) { sleeve0->xRot = PI; sleeve0->yRot = 0.0f; sleeve0->zRot = 0.0f; sleeve0->y = 2.0f; } + + arm1->xRot = 0.0f; arm1->yRot = 0.0f; arm1->zRot = 0.0f; arm1->y = 2.0f; + if (sleeve1) { sleeve1->xRot = 0.0f; sleeve1->yRot = 0.0f; sleeve1->zRot = 0.0f; sleeve1->y = 2.0f; } + + leg0->xRot = 0.0f; leg0->yRot = 0.0f; leg0->zRot = 0.0f; + leg1->xRot = 0.0f; leg1->yRot = 0.0f; leg1->zRot = 0.0f; + if (pants0) { pants0->xRot = 0.0f; pants0->yRot = 0.0f; pants0->zRot = 0.0f; } + if (pants1) { pants1->xRot = 0.0f; pants1->yRot = 0.0f; pants1->zRot = 0.0f; } + } + else + { + float elytraTime = (float)(entity->tickCount) * 0.3f; + float spd2 = (float)(entity->xd * entity->xd + entity->yd * entity->yd + entity->zd * entity->zd); + float fDamp = spd2 / 0.2f; + fDamp = fDamp * fDamp * fDamp; + if (fDamp < 1.0f) fDamp = 1.0f; + + float armAmp = 2.0f * r * 0.5f / fDamp; + float legAmp = 1.4f * r / fDamp; + + arm0->xRot = Mth::cos(elytraTime + PI) * armAmp; + arm0->yRot = 0.0f; arm0->zRot = 0.0f; arm0->y = 2.0f; + if (sleeve0) { sleeve0->xRot = arm0->xRot; sleeve0->yRot = 0.0f; sleeve0->zRot = 0.0f; sleeve0->y = 2.0f; } + + arm1->xRot = Mth::cos(elytraTime) * armAmp; + arm1->yRot = 0.0f; arm1->zRot = 0.0f; arm1->y = 2.0f; + if (sleeve1) { sleeve1->xRot = arm1->xRot; sleeve1->yRot = 0.0f; sleeve1->zRot = 0.0f; sleeve1->y = 2.0f; } + + leg0->xRot = Mth::cos(elytraTime) * legAmp; + leg0->yRot = 0.0f; leg0->zRot = 0.0f; + leg1->xRot = Mth::cos(elytraTime + PI) * legAmp; + leg1->yRot = 0.0f; leg1->zRot = 0.0f; + if (pants0) { pants0->xRot = leg0->xRot; pants0->yRot = 0.0f; pants0->zRot = 0.0f; } + if (pants1) { pants1->xRot = leg1->xRot; pants1->yRot = 0.0f; pants1->zRot = 0.0f; } + } + + body->xRot = 0.0f; + body->z = 0.0f; + + + head->xRot = -(float)(PI / 4.0f); + hair->xRot = head->xRot; + } + } void HumanoidModel::renderHair(float scale,bool usecompiled) @@ -903,6 +970,11 @@ void HumanoidModel::renderCloak(float scale,bool usecompiled) { cloak->render(scale,usecompiled); } +void HumanoidModel::renderElytra(float scale, bool usecompiled) +{ + elytraRight->render(scale, usecompiled); + elytraLeft->render(scale, usecompiled); +} void HumanoidModel::render(HumanoidModel *model, float scale, bool usecompiled) { diff --git a/Minecraft.Client/HumanoidModel.h b/Minecraft.Client/HumanoidModel.h index 1e589c80..4c7f95f5 100644 --- a/Minecraft.Client/HumanoidModel.h +++ b/Minecraft.Client/HumanoidModel.h @@ -3,30 +3,32 @@ class HumanoidModel : public Model { public: - ModelPart *head, *hair, *body, *jacket, *arm0, *sleeve0, *arm1, *sleeve1, *leg0, *pants0, *leg1, *pants1, *ear, *cloak; + ModelPart* head, * hair, * body, * jacket, * arm0, * sleeve0, * arm1, * sleeve1, * leg0, * pants0, * leg1, * pants1, * ear, * cloak; + ModelPart* elytraLeft, * elytraRight; int holdingLeftHand; int holdingRightHand; bool idle; bool sneaking; bool bowAndArrow; - bool eating; - float eating_t; - float eating_swing; - unsigned int m_uiAnimOverrideBitmask; + bool eating; + float eating_t; + float eating_swing; + bool elytraFlying; + bool elytraCrouching; + unsigned int m_uiAnimOverrideBitmask; float m_fYOffset; - enum animbits { eAnim_ArmsDown = 0, eAnim_ArmsOutFront, eAnim_NoLegAnim, eAnim_HasIdle, - eAnim_ForceAnim, + eAnim_ForceAnim, eAnim_SingleLegs, eAnim_SingleArms, - eAnim_StatueOfLiberty, - eAnim_DontRenderArmour, - eAnim_NoBobbing, + eAnim_StatueOfLiberty, + eAnim_DontRenderArmour, + eAnim_NoBobbing, eAnim_DisableRenderHead, eAnim_DisableRenderArm0, eAnim_DisableRenderArm1, @@ -42,6 +44,7 @@ public: eAnim_DisableRenderPants1 }; + static const unsigned int m_staticBitmaskIgnorePlayerCustomAnimSetting = (1 << HumanoidModel::eAnim_ForceAnim) | (1 << HumanoidModel::eAnim_DisableRenderArm0) | @@ -75,6 +78,8 @@ public: void renderHair(float scale, bool usecompiled); void renderEars(float scale, bool usecompiled); void renderCloak(float scale, bool usecompiled); + void renderElytra(float scale, bool usecompiled); + void render(HumanoidModel* model, float scale, bool usecompiled); ModelPart* AddOrRetrievePart(SKIN_BOX* pBox); diff --git a/Minecraft.Client/ItemInHandRenderer.cpp b/Minecraft.Client/ItemInHandRenderer.cpp index 052123ac..02599072 100644 --- a/Minecraft.Client/ItemInHandRenderer.cpp +++ b/Minecraft.Client/ItemInHandRenderer.cpp @@ -11,6 +11,8 @@ #include "MultiPlayerLocalPlayer.h" #include "Minimap.h" #include "MultiPlayerLevel.h" +#include "SkullTileRenderer.h" +#include "../Minecraft.World/Facing.h" #include "../Minecraft.World/net.minecraft.world.item.h" #include "../Minecraft.World/net.minecraft.world.level.tile.h" #include "../Minecraft.World/net.minecraft.world.entity.h" @@ -237,6 +239,17 @@ void ItemInHandRenderer::renderItem(shared_ptr mob, shared_ptrid == Item::skull_Id && SkullTileRenderer::instance != nullptr) + { + wstring extra = L""; + if (item->hasTag() && item->getTag()->contains(L"SkullOwner")) + extra = item->getTag()->getString(L"SkullOwner"); + SkullTileRenderer::instance->renderSkull(-0.5f, 0.0f, -0.5f, Facing::UP, 0.0f, item->getAuxValue(), extra); + glPopMatrix(); + return; + }*/ + Tile *tile = Tile::tiles[item->id]; if (item->getIconType() == Icon::TYPE_TERRAIN && tile != nullptr && TileRenderer::canRender(tile->getRenderShape())) { @@ -680,6 +693,16 @@ void ItemInHandRenderer::render(float a) renderItem(player, item, 1, false); } + //else if (item->id == Item::skull_Id && SkullTileRenderer::instance != nullptr) + //{ + // wstring extra = L""; + // if (item->hasTag() && item->getTag()->contains(L"SkullOwner")) + // extra = item->getTag()->getString(L"SkullOwner"); + // glEnable(GL_RESCALE_NORMAL); + // glScalef(2.0f, 2.0f, 2.0f); + // SkullTileRenderer::instance->renderSkull(-0.5f, 0.0f, -0.5f, Facing::UP, 0.0f, item->getAuxValue(), extra); + // glDisable(GL_RESCALE_NORMAL); + //} else { renderItem(player, item, 0, false); diff --git a/Minecraft.Client/LocalPlayer.cpp b/Minecraft.Client/LocalPlayer.cpp index 7f3e3792..3c38f715 100644 --- a/Minecraft.Client/LocalPlayer.cpp +++ b/Minecraft.Client/LocalPlayer.cpp @@ -67,6 +67,9 @@ LocalPlayer::LocalPlayer(Minecraft *minecraft, Level *level, User *user, int dim sprintTriggerTime = 0; sprintTriggerRegisteredReturn = false; twoJumpsRegistered = false; + m_elytraCancelPressCount = 0; + m_elytraCancelWindow = 0; + m_elytraSoundTicks = 0; sprintTime = 0; m_uiInactiveTicks=0; portalTime = 0.0f; @@ -331,6 +334,58 @@ void LocalPlayer::aiStep() } + if (isElytraFlying()) + { + if (m_elytraCancelWindow > 0) m_elytraCancelWindow--; + + if (!wasJumping && input->jumping) + { + m_elytraCancelPressCount++; + if (m_elytraCancelPressCount == 1) + { + m_elytraCancelWindow = 15; + } + else if (m_elytraCancelPressCount >= 2 && m_elytraCancelWindow > 0) + { + setElytraFlying(false); + jumpTriggerTime = 10; + m_elytraCancelPressCount = 0; + m_elytraCancelWindow = 0; + } + } + + if (m_elytraCancelWindow == 0 && m_elytraCancelPressCount > 0) + m_elytraCancelPressCount = 0; + + + m_elytraSoundTicks++; + float vol = 0.0f; + float pitch = 1.0f; + if (m_elytraSoundTicks >= 20) + { + float totalSpeed = (float)Mth::sqrt(xd * xd + yd * yd + zd * zd); + float f1 = totalSpeed / 2.0f; + float speedVol = f1 * f1; + if (speedVol > 1.0f) speedVol = 1.0f; + float fadeFactor = (m_elytraSoundTicks < 40) + ? (float)(m_elytraSoundTicks - 20) / 20.0f + : 1.0f; + vol = speedVol * fadeFactor; + if (vol > 0.8f) + pitch = 1.0f + (vol - 0.8f); + } + if (vol > 0.01f) + minecraft->soundEngine->startElytraSound((float)x, (float)y, (float)z, vol, pitch); + } + else + { + m_elytraCancelPressCount = 0; + m_elytraCancelWindow = 0; + m_elytraSoundTicks = 0; + minecraft->soundEngine->stopElytraSound(); + } + + if (abilities.flying) { // yd = 0; diff --git a/Minecraft.Client/LocalPlayer.h b/Minecraft.Client/LocalPlayer.h index f62c1be9..88e88f23 100644 --- a/Minecraft.Client/LocalPlayer.h +++ b/Minecraft.Client/LocalPlayer.h @@ -31,6 +31,10 @@ protected: int sprintTriggerTime; bool sprintTriggerRegisteredReturn; // 4J added bool twoJumpsRegistered; // 4J added + int m_elytraCancelPressCount; + int m_elytraCancelWindow; + int m_elytraSoundTicks; + unsigned int m_uiInactiveTicks; // To measure time for idle anims diff --git a/Minecraft.Client/PlayerRenderer.cpp b/Minecraft.Client/PlayerRenderer.cpp index 2d25f54d..7969bcb0 100644 --- a/Minecraft.Client/PlayerRenderer.cpp +++ b/Minecraft.Client/PlayerRenderer.cpp @@ -288,14 +288,21 @@ void PlayerRenderer::render(shared_ptr _mob, double x, double y, double { armorParts1->eating = armorParts2->eating = resModel->eating = false; } + // Suppress crouch pose while elytra flying (superman pose is applied instead) + bool effectiveSneaking = mob->isSneaking() && !mob->isElytraFlying(); + armorParts1->sneaking = armorParts2->sneaking = resModel->sneaking = effectiveSneaking; + // Signal models: elytraFlying suppresses walk/bob; elytraCrouching adds superman arms + // Suppress elytra pose in the inventory screen (isInventoryRender=true during that pass). + bool elytraFlying = mob->isElytraFlying() && !EntityRenderDispatcher::instance->isInventoryRender; + bool elytraCrouch = elytraFlying && mob->isSneaking(); + armorParts1->elytraFlying = armorParts2->elytraFlying = resModel->elytraFlying = elytraFlying; + armorParts1->elytraCrouching = armorParts2->elytraCrouching = resModel->elytraCrouching = elytraCrouch; - armorParts1->sneaking = armorParts2->sneaking = resModel->sneaking = mob->isSneaking(); double yp = y - mob->heightOffset; - if (mob->isSneaking()) + if (mob->isSneaking() && !mob->instanceof(eTYPE_LOCALPLAYER)) { yp -= 2 / 16.0f; } - if (mob->getAnimOverrideBitmask() & (1 << HumanoidModel::eAnim_SmallModel)) { if (mob->isRiding()) @@ -357,6 +364,8 @@ void PlayerRenderer::render(shared_ptr _mob, double x, double y, double } armorParts1->bowAndArrow = armorParts2->bowAndArrow = resModel->bowAndArrow = false; armorParts1->sneaking = armorParts2->sneaking = resModel->sneaking = false; + armorParts1->elytraFlying = armorParts2->elytraFlying = resModel->elytraFlying = false; + armorParts1->elytraCrouching = armorParts2->elytraCrouching = resModel->elytraCrouching = false; armorParts1->holdingRightHand = armorParts2->holdingRightHand = resModel->holdingRightHand = 0; } @@ -497,8 +506,6 @@ void PlayerRenderer::additionalRendering(shared_ptr _mob, float a) // 4J Stu - Fix for sprint-flying causing the cape to rotate up by 180 degrees or more float xRot = 6.0f + lean / 2 + flap; - if (xRot > 64.0f) xRot = 64.0f; - glRotatef(xRot, 1, 0, 0); glRotatef(lean2 / 2, 0, 0, 1); glRotatef(-lean2 / 2, 0, 1, 0); @@ -507,101 +514,182 @@ void PlayerRenderer::additionalRendering(shared_ptr _mob, float a) glPopMatrix(); } - shared_ptr item = mob->inventory->getSelected(); - - if (item != nullptr) { - glPushMatrix(); - resModel->arm0->translateTo(1 / 16.0f); - glTranslatef(-1 / 16.0f, 7 / 16.0f, 1 / 16.0f); + shared_ptr chestItem = mob->inventory->armor[LivingEntity::SLOT_CHEST - 1]; + if (chestItem != nullptr && dynamic_cast(chestItem->getItem()) != nullptr && !mob->isInvisible()) + { + static ResourceLocation elytraTexture(L"item/elytra.png"); + bindTexture(&elytraTexture); - if (mob->fishing != nullptr) - { - item = std::make_shared(Item::stick); - } + float brightness2 = SharedConstants::TEXTURE_LIGHTING ? 1 : mob->getBrightness(a); + glColor3f(brightness2, brightness2, brightness2); - UseAnim anim = UseAnim_none;//null; - if (mob->getUseItemDuration() > 0) - { - anim = item->getUseAnimation(); - } - if (item->id < 256 && TileRenderer::canRender(Tile::tiles[item->id]->getRenderShape())) - { - float s = 8 / 16.0f; - glTranslatef(-0 / 16.0f, 3 / 16.0f, -5 / 16.0f); - s *= 0.75f; - glRotatef(20, 1, 0, 0); - glRotatef(45, 0, 1, 0); - glScalef(-s, -s, s); - } - else if (item->id == Item::bow->id) - { - float s = 10 / 16.0f; - glTranslatef(0 / 16.0f, 2 / 16.0f, 5 / 16.0f); - glRotatef(-20, 0, 1, 0); - glScalef(s, -s, s); - glRotatef(-100, 1, 0, 0); - glRotatef(45, 0, 1, 0); - } - else if (Item::items[item->id]->isHandEquipped()) - { - float s = 10 / 16.0f; - if (Item::items[item->id]->isMirroredArt()) + float wf = 0.2617994f; + float wf1 = -0.2617994f; + float wf2 = resModel->body->y; + float wf3 = 0.0f; + + if (mob->isElytraFlying() && !EntityRenderDispatcher::instance->isInventoryRender) { - glRotatef(180, 0, 0, 1); - glTranslatef(0, -2 / 16.0f, 0); + float f4 = 1.0f; + if (mob->yd < 0.0) + { + double speed = sqrt(mob->xd * mob->xd + mob->yd * mob->yd + mob->zd * mob->zd); + if (speed > 0.0) + { + double normY = mob->yd / speed; + f4 = 1.0f - (float)pow(-normY, 1.5); + if (f4 < 0.0f) f4 = 0.0f; + if (f4 > 1.0f) f4 = 1.0f; + } + } + wf = f4 * 0.34906584f + (1.0f - f4) * wf; + wf1 = f4 * (-(float)(PI / 2.0)) + (1.0f - f4) * wf1; } + else if (mob->isSneaking()) + { + wf = (float)(PI * 2.0 / 9.0); + wf1 = -(float)(PI / 4.0); + wf2 = 0.0f; + wf3 = 0.08726646f; + } + + if (EntityRenderDispatcher::instance->isInventoryRender) + { + mob->rotateElytraX = wf; + mob->rotateElytraY = wf3; + mob->rotateElytraZ = wf1; + } + else + { + mob->rotateElytraX += (wf - mob->rotateElytraX) * 0.3f; + mob->rotateElytraY += (wf3 - mob->rotateElytraY) * 0.3f; + mob->rotateElytraZ += (wf1 - mob->rotateElytraZ) * 0.3f; + } + + + humanoidModel->elytraRight->y = wf2; + humanoidModel->elytraRight->xRot = mob->rotateElytraX; + humanoidModel->elytraRight->yRot = mob->rotateElytraY; + humanoidModel->elytraRight->zRot = mob->rotateElytraZ; + + humanoidModel->elytraLeft->y = wf2; + humanoidModel->elytraLeft->xRot = mob->rotateElytraX; + humanoidModel->elytraLeft->yRot = -mob->rotateElytraY; + humanoidModel->elytraLeft->zRot = -mob->rotateElytraZ; + + glPushMatrix(); + glTranslatef(0, 0.0f, (2.0f + 0.125f) / 16.0f); + humanoidModel->renderElytra(1 / 16.0f, true); + glPopMatrix(); + } + + + shared_ptr item = mob->inventory->getSelected(); + + if (item != nullptr) + { + glPushMatrix(); + resModel->arm0->translateTo(1 / 16.0f); + glTranslatef(-1 / 16.0f, 7 / 16.0f, 1 / 16.0f); + + if (mob->fishing != nullptr) + { + item = std::make_shared(Item::stick); + } + + UseAnim anim = UseAnim_none;//null; if (mob->getUseItemDuration() > 0) { - if (anim == UseAnim_block) + anim = item->getUseAnimation(); + } + + if (item->id < 256 && TileRenderer::canRender(Tile::tiles[item->id]->getRenderShape())) + { + float s = 8 / 16.0f; + glTranslatef(-0 / 16.0f, 3 / 16.0f, -5 / 16.0f); + s *= 0.75f; + glRotatef(20, 1, 0, 0); + glRotatef(45, 0, 1, 0); + glScalef(-s, -s, s); + } + else if (item->id == Item::bow->id) + { + float s = 10 / 16.0f; + glTranslatef(0 / 16.0f, 2 / 16.0f, 5 / 16.0f); + glRotatef(-20, 0, 1, 0); + glScalef(s, -s, s); + glRotatef(-100, 1, 0, 0); + glRotatef(45, 0, 1, 0); + } + else if (Item::items[item->id]->isHandEquipped()) + { + float s = 10 / 16.0f; + if (Item::items[item->id]->isMirroredArt()) { - glTranslatef(0.05f, 0, -0.1f); - glRotatef(-50, 0, 1, 0); - glRotatef(-10, 1, 0, 0); - glRotatef(-60, 0, 0, 1); + glRotatef(180, 0, 0, 1); + glTranslatef(0, -2 / 16.0f, 0); + } + if (mob->getUseItemDuration() > 0) + { + if (anim == UseAnim_block) + { + glTranslatef(0.05f, 0, -0.1f); + glRotatef(-50, 0, 1, 0); + glRotatef(-10, 1, 0, 0); + glRotatef(-60, 0, 0, 1); + } + } + glTranslatef(0, 3 / 16.0f, 0); + glScalef(s, -s, s); + glRotatef(-100, 1, 0, 0); + glRotatef(45, 0, 1, 0); + } + else if (item->id == Item::skull_Id) + { + float s = 0.5f; + glTranslatef(0, -3 / 16.0f, -4 / 16.0f); + glRotatef(45, 1, 0, 0); + glRotatef(45, 0, 1, 0); + glScalef(-s, -s, s); + } + else + { + float s = 6 / 16.0f; + glTranslatef(+4 / 16.0f, +3 / 16.0f, -3 / 16.0f); + glScalef(s, s, s); + glRotatef(60, 0, 0, 1); + glRotatef(-90, 1, 0, 0); + glRotatef(20, 0, 0, 1); + } + + if (item->getItem()->hasMultipleSpriteLayers()) + { + for (int layer = 0; layer <= 1; layer++) + { + int col = item->getItem()->getColor(item, layer); + float red = ((col >> 16) & 0xff) / 255.0f; + float g = ((col >> 8) & 0xff) / 255.0f; + float b = ((col) & 0xff) / 255.0f; + + glColor4f(red, g, b, 1); + this->entityRenderDispatcher->itemInHandRenderer->renderItem(mob, item, layer, false); } } - glTranslatef(0, 3 / 16.0f, 0); - glScalef(s, -s, s); - glRotatef(-100, 1, 0, 0); - glRotatef(45, 0, 1, 0); - } - else - { - float s = 6 / 16.0f; - glTranslatef(+4 / 16.0f, +3 / 16.0f, -3 / 16.0f); - glScalef(s, s, s); - glRotatef(60, 0, 0, 1); - glRotatef(-90, 1, 0, 0); - glRotatef(20, 0, 0, 1); - } - - if (item->getItem()->hasMultipleSpriteLayers()) - { - for (int layer = 0; layer <= 1; layer++) + else { - int col = item->getItem()->getColor(item, layer); + int col = item->getItem()->getColor(item, 0); float red = ((col >> 16) & 0xff) / 255.0f; float g = ((col >> 8) & 0xff) / 255.0f; float b = ((col) & 0xff) / 255.0f; glColor4f(red, g, b, 1); - this->entityRenderDispatcher->itemInHandRenderer->renderItem(mob, item, layer, false); + this->entityRenderDispatcher->itemInHandRenderer->renderItem(mob, item, 0); } - } - else - { - int col = item->getItem()->getColor(item, 0); - float red = ((col >> 16) & 0xff) / 255.0f; - float g = ((col >> 8) & 0xff) / 255.0f; - float b = ((col) & 0xff) / 255.0f; - glColor4f(red, g, b, 1); - this->entityRenderDispatcher->itemInHandRenderer->renderItem(mob, item, 0); + glPopMatrix(); } - - glPopMatrix(); } } @@ -746,10 +834,34 @@ void PlayerRenderer::setupRotations(shared_ptr _mob, float bob, fl glRotatef(getFlipDegrees(mob), 0, 0, 1); glRotatef(270, 0, 1, 0); } - else + else if (mob->isElytraFlying() && !EntityRenderDispatcher::instance->isInventoryRender) { LivingEntityRenderer::setupRotations(mob, bob, bodyRot, a); + + float f = (float)mob->ticksElytraFlying + a; + float f1 = Mth::clamp(f * f / 100.0f, 0.0f, 1.0f); + glRotatef(f1 * (-90.0f - mob->xRot), 1.0f, 0.0f, 0.0f); + + Vec3* look = mob->getLookAngle(); + double d0 = mob->xd * mob->xd + mob->zd * mob->zd; + double d1 = look->x * look->x + look->z * look->z; + + if (d0 > 0.0 && d1 > 0.0) + { + double d2 = (mob->xd * look->x + mob->zd * look->z) / (sqrt(d0) * sqrt(d1)); + if (d2 > 1.0) d2 = 1.0; + if (d2 < -1.0) d2 = -1.0; + double d3 = mob->xd * look->z - mob->zd * look->x; + float sign = (d3 >= 0.0) ? 1.0f : -1.0f; + glRotatef(sign * (float)(acos(d2) * 180.0 / PI), 0.0f, 1.0f, 0.0f); + } + } + else + { + LivingEntityRenderer::setupRotations(mob, bob, bodyRot, a); + } + } // 4J Added override to stop rendering shadow if player is invisible diff --git a/Minecraft.Client/PreStitchedTextureMap.cpp b/Minecraft.Client/PreStitchedTextureMap.cpp index 2d7326c7..cbc5ef84 100644 --- a/Minecraft.Client/PreStitchedTextureMap.cpp +++ b/Minecraft.Client/PreStitchedTextureMap.cpp @@ -570,6 +570,8 @@ void PreStitchedTextureMap::loadUVs() ADD_ICON(14, 6, L"rabbitHide") ADD_ICON(13, 14, L"prismarineCrystal"); ADD_ICON(13, 13, L"prismarineShard"); + ADD_ICON(13, 12, L"elytra"); + ADD_ICON(13, 11, L"broken_elytra"); ADD_ICON_WITH_NAME(14, 7, L"compassP0", L"compass") // 4J Added ADD_ICON_WITH_NAME(14, 8, L"compassP1", L"compass") // 4J Added diff --git a/Minecraft.Client/Windows64Media/Sound/Minecraft/item/elytra/flying.ogg b/Minecraft.Client/Windows64Media/Sound/Minecraft/item/elytra/flying.ogg new file mode 100644 index 00000000..c426aa77 Binary files /dev/null and b/Minecraft.Client/Windows64Media/Sound/Minecraft/item/elytra/flying.ogg differ diff --git a/Minecraft.Client/Windows64Media/loc/stringsGeneric.xml b/Minecraft.Client/Windows64Media/loc/stringsGeneric.xml index 6c2c33a3..3dc913a4 100644 --- a/Minecraft.Client/Windows64Media/loc/stringsGeneric.xml +++ b/Minecraft.Client/Windows64Media/loc/stringsGeneric.xml @@ -9393,4 +9393,7 @@ All Ender Chests in a world are linked. Items placed into an Ender Chest are acc Red Sandstone Slab + + Elytra + diff --git a/Minecraft.World/ArmorRecipes.cpp b/Minecraft.World/ArmorRecipes.cpp index c74c02a3..0717e694 100644 --- a/Minecraft.World/ArmorRecipes.cpp +++ b/Minecraft.World/ArmorRecipes.cpp @@ -92,6 +92,8 @@ ArmorRecipes::_eArmorType ArmorRecipes::GetArmorType(int iId) case Item::chestplate_iron_Id: case Item::chestplate_diamond_Id: case Item::chestplate_gold_Id: + case Item::elytra_Id: + return eArmorType_Chestplate; break; diff --git a/Minecraft.World/ArmorSlot.cpp b/Minecraft.World/ArmorSlot.cpp index 2e85fed3..82be3067 100644 --- a/Minecraft.World/ArmorSlot.cpp +++ b/Minecraft.World/ArmorSlot.cpp @@ -31,6 +31,11 @@ bool ArmorSlot::mayPlace(shared_ptr item) { return slotNum == 0; } + if (item->getItem()->id == Item::elytra_Id) + { + return slotNum == ArmorItem::SLOT_TORSO; + } + return false; } diff --git a/Minecraft.World/ElytraItem.cpp b/Minecraft.World/ElytraItem.cpp new file mode 100644 index 00000000..26ac7bce --- /dev/null +++ b/Minecraft.World/ElytraItem.cpp @@ -0,0 +1,92 @@ +#include "stdafx.h" +#include "net.minecraft.world.h" +#include "net.minecraft.world.level.h" +#include "net.minecraft.world.entity.h" +#include "net.minecraft.world.entity.player.h" +#include "net.minecraft.world.item.h" +#include "ElytraItem.h" + +// Internal item ID: 187 → public item ID: 443 (256 + 187) +ElytraItem::ElytraItem() : Item(187) +{ + maxStackSize = 1; + setMaxDamage(432); +} + +bool ElytraItem::isFlyEnabled(shared_ptr item) +{ + return item->getDamageValue() < item->getMaxDamage() - 1; +} + +bool ElytraItem::TestUse(shared_ptr instance, Level* level, shared_ptr player) +{ + return true; +} + +shared_ptr ElytraItem::use(shared_ptr instance, Level* level, shared_ptr player) +{ + // Elytra equips to the chest slot (SLOT_CHEST = 3, armor array index = SLOT_CHEST - 1 = 2) + const int chestSlot = LivingEntity::SLOT_CHEST - 1; + + ItemInstance copy = *instance->copy_not_shared(); + + if (!player->abilities.instabuild) + { + if (player->inventory->armor[chestSlot] == nullptr) + { + player->inventory->armor[chestSlot] = make_shared(copy); + player->inventory->removeItemNoUpdate(player->inventory->selected); + instance->count = 0; + } + else + { + player->inventory->setItem(player->inventory->selected, player->inventory->armor[chestSlot]); + player->inventory->armor[chestSlot] = make_shared(copy); + } + } + else + { + if (player->inventory->armor[chestSlot] == nullptr) + { + player->inventory->armor[chestSlot] = make_shared(copy); + } + else + { + player->inventory->setItem(player->inventory->selected, player->inventory->armor[chestSlot]); + player->inventory->armor[chestSlot] = make_shared(copy); + } + } + + // Play cloth armor equip sound (range 194–199) + player->playSound(194, 0.5f, 1.0f); + + return instance; +} + +bool ElytraItem::isValidRepairItem(shared_ptr source, shared_ptr repairItem) +{ + return repairItem->id == Item::leather_Id; +} + +void ElytraItem::registerIcons(IconRegister* iconRegister) +{ + Item::registerIcons(iconRegister); + m_brokenElytraIcon = iconRegister->registerIcon(L"broken_elytra"); +} + +Icon* ElytraItem::getLayerIcon(int auxValue, int spriteLayer) +{ + // auxValue may be the damage value in certain render paths + if (auxValue < getMaxDamage() - 1) + return icon; + else + return m_brokenElytraIcon; +} + +Icon* ElytraItem::getIcon(int auxValue) +{ + if (auxValue < getMaxDamage() - 1) + return icon; + else + return m_brokenElytraIcon; +} diff --git a/Minecraft.World/ElytraItem.h b/Minecraft.World/ElytraItem.h new file mode 100644 index 00000000..510b027a --- /dev/null +++ b/Minecraft.World/ElytraItem.h @@ -0,0 +1,20 @@ +#pragma once + +#include "Item.h" + +class ElytraItem : public Item +{ +public: + ElytraItem(); + + static bool isFlyEnabled(shared_ptr item); + + virtual bool TestUse(shared_ptr instance, Level* level, shared_ptr player) override; + virtual shared_ptr use(shared_ptr instance, Level* level, shared_ptr player) override; + virtual bool isValidRepairItem(shared_ptr source, shared_ptr repairItem) override; + virtual void registerIcons(IconRegister* iconRegister) override; + virtual Icon* getLayerIcon(int auxValue, int spriteLayer) override; + virtual Icon* getIcon(int auxValue) override; + + Icon* m_brokenElytraIcon = nullptr; +}; diff --git a/Minecraft.World/Item.cpp b/Minecraft.World/Item.cpp index 46a52f19..ed0bbbf3 100644 --- a/Minecraft.World/Item.cpp +++ b/Minecraft.World/Item.cpp @@ -17,6 +17,7 @@ #include "Item.h" #include "HangingEntityItem.h" #include "HtmlString.h" +#include "ElytraItem.h" typedef Item::Tier _Tier; @@ -271,6 +272,8 @@ Item* Item::rabbitStew = nullptr; Item* Item::prismarine_crystal = nullptr; Item* Item::prismarine_shard = nullptr; +Item* Item::elytra = nullptr; + void Item::staticCtor() { @@ -537,9 +540,11 @@ void Item::staticCtor() Item::armor_stand = (new ArmorStandItem(160)) ->setBaseItemTypeAndMaterial(eBaseItemType_HangingItem,eMaterial_cloth)->setIconName(L"armorStand")->setDescriptionId(IDS_ITEM_ARMOR_STAND)->setUseDescriptionId(IDS_DESC_ARMOR_STAND); Item::prismarine_crystal = (new Item(154))->setIconName(L"prismarineCrystal")->setDescriptionId(IDS_ITEM_PRISMARINE_CRYSTAL)->setUseDescriptionId(IDS_ITEM_PRISMARINE_CRYSTAL_DESC); Item::prismarine_shard = (new Item(153))->setIconName(L"prismarineShard")->setDescriptionId(IDS_ITEM_PRISMARINE_SHARD)->setUseDescriptionId(IDS_ITEM_PRISMARINE_SHARD_DESC); + Item::elytra = (new ElytraItem())->setBaseItemTypeAndMaterial(eBaseItemType_chestplate, eMaterial_cloth)->setIconName(L"elytra")->setDescriptionId(IDS_ITEM_ELYTRA)->setUseDescriptionId(IDS_ITEM_ELYTRA); } + // 4J Stu - We need to do this after the staticCtor AND after staticCtors for other class // eg Recipes void Item::staticInit() diff --git a/Minecraft.World/Item.h b/Minecraft.World/Item.h index 3d00c925..8c474f5c 100644 --- a/Minecraft.World/Item.h +++ b/Minecraft.World/Item.h @@ -436,6 +436,7 @@ public: static Item* prismarine_crystal; static Item* prismarine_shard; + static Item* elytra; static const int shovel_iron_Id = 256; @@ -667,6 +668,8 @@ public: static const int door_acacia_Id = 430; static const int door_dark_Id = 431; + + static const int elytra_Id = 443; //TU31 diff --git a/Minecraft.World/Player.cpp b/Minecraft.World/Player.cpp index d6b9b9ed..52d83d61 100644 --- a/Minecraft.World/Player.cpp +++ b/Minecraft.World/Player.cpp @@ -42,6 +42,7 @@ #include "../Minecraft.Client/LocalPlayer.h" #include "../Minecraft.Client/HumanoidModel.h" #include "SoundTypes.h" +#include "ElytraItem.h" @@ -83,6 +84,14 @@ void Player::_init() m_uiDebugOptions=0L; jumpTriggerTime = 0; + ticksElytraFlying = 0; + rotateElytraX = 0.2617994f; + rotateElytraY = 0.0f; + rotateElytraZ = -0.2617994f; + m_elytraImpactYd = 0.0f; + m_wasElytraFlying = false; + m_elytraFallProtectTicks = 0; + takeXpDelay = 0; experienceLevel = totalExperience = 0; experienceProgress = 0.0f; @@ -1000,6 +1009,8 @@ void Player::serverAiStep() void Player::aiStep() { if (jumpTriggerTime > 0) jumpTriggerTime--; + if (m_elytraFallProtectTicks > 0) m_elytraFallProtectTicks--; + if (level->difficulty == Difficulty::PEACEFUL && getHealth() < getMaxHealth() && level->getGameRules()->getBoolean(GameRules::RULE_NATURAL_REGENERATION)) { @@ -1008,8 +1019,43 @@ void Player::aiStep() inventory->tick(); oBob = bob; + if (jumping && !onGround && yd < 0.0 && !isElytraFlying() && !abilities.flying && jumpTriggerTime == 0) + { + shared_ptr chestItem = inventory->armor[LivingEntity::SLOT_CHEST - 1]; + if (chestItem != nullptr && chestItem->getItem() != nullptr) + { + ElytraItem* elytra = dynamic_cast(chestItem->getItem()); + if (elytra != nullptr && ElytraItem::isFlyEnabled(chestItem)) + setElytraFlying(true); + } + } + + + LivingEntity::aiStep(); + if (isElytraFlying() && !onGround) + { + ticksElytraFlying++; + + if (!level->isClientSide && ticksElytraFlying % 20 == 0) + { + shared_ptr chestItem = inventory->armor[LivingEntity::SLOT_CHEST - 1]; + if (chestItem != nullptr && dynamic_cast(chestItem->getItem()) != nullptr) + { + chestItem->hurtAndBreak(1, dynamic_pointer_cast(shared_from_this())); + if (!ElytraItem::isFlyEnabled(chestItem)) + setElytraFlying(false); + } + else + { + setElytraFlying(false); + } + } + } + + + AttributeInstance *speed = getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED); if (!level->isClientSide) speed->setBaseValue(abilities.getWalkingSpeed()); flyingSpeed = defaultFlySpeed; @@ -1389,6 +1435,8 @@ bool Player::hurt(DamageSource *source, float dmg) if ( hasInvulnerablePrivilege() || (abilities.invulnerable && !source->isBypassInvul()) ) return false; + + // 4J-JEV: Fix for PSVita: #3987 - [IN GAME] The user can take damage/die, when attempting to re-enter fly mode when falling from a height. if ( source == DamageSource::fall && isAllowedToFly() && abilities.flying ) return false; @@ -2110,7 +2158,90 @@ void Player::travel(float xa, float ya) { double preX = x, preY = y, preZ = z; - if (abilities.flying && riding == nullptr) + m_elytraImpactYd = (float)yd; + + if (isElytraFlying() && riding == nullptr) + { + double preHorizSpeed = Mth::sqrt(xd * xd + zd * zd); + + + if (yd > -0.5) + fallDistance = 1.0f; + + Vec3* look = getLookAngle(); + float pitchRad = xRot * (PI / 180.0f); + double horizLookLen = Mth::sqrt(look->x * look->x + look->z * look->z); + double horizSpeed = Mth::sqrt(xd * xd + zd * zd); + + + float cosPitch = Mth::cos(pitchRad); + cosPitch = cosPitch * cosPitch; + + + yd += -0.08 + (double)cosPitch * 0.06; + + if (yd < 0.0 && horizLookLen > 0.0) + { + double thrust = yd * -0.1 * (double)cosPitch; + yd += thrust; + xd += look->x * thrust / horizLookLen; + zd += look->z * thrust / horizLookLen; + } + + if (pitchRad < 0.0f && horizLookLen > 0.0) + { + double boost = horizSpeed * (double)(-Mth::sin(pitchRad)) * 0.04; + yd += boost * 3.2; + xd -= look->x * boost / horizLookLen; + zd -= look->z * boost / horizLookLen; + } + + + if (horizLookLen > 0.0) + { + xd += (look->x / horizLookLen * horizSpeed - xd) * 0.1; + zd += (look->z / horizLookLen * horizSpeed - zd) * 0.1; + } + + // Air drag + xd *= 0.99; + yd *= 0.98; + zd *= 0.99; + + if (jumping && abilities.instabuild) + yd += (double)abilities.getFlyingSpeed() * 3.0; + + m_elytraImpactYd = (float)yd; + move(xd, yd, zd); + + if (horizontalCollision && !verticalCollision) + { + double postHorizSpeed = Mth::sqrt(xd * xd + zd * zd); + double speedLost = preHorizSpeed - postHorizSpeed; + float damage = (float)(speedLost * 10.0 - 3.0); + + if (damage > 0.0f) + onElytraKineticDamage(damage); + } + + if (onGround) + { + bool canStandUp = true; + if (level != nullptr) + { + float halfW = bbWidth / 2.0f; + AABB* standingBB = AABB::newTemp( + x - halfW, bb->y0, z - halfW, + x + halfW, bb->y0 + 1.8, z + halfW); + AABBList* collisions = level->getCubes(shared_from_this(), standingBB, true); + canStandUp = collisions->empty(); + } + if (canStandUp) + setElytraFlying(false); + } + } + else if (abilities.flying && riding == nullptr) + { double ydo = yd; float ofs = flyingSpeed; @@ -2278,6 +2409,13 @@ void Player::checkRidingStatistiscs(double dx, double dy, double dz) } } } +void Player::checkFallDamage(double ya, bool onGround) +{ + LivingEntity::checkFallDamage(ya, onGround); +} + + + void Player::causeFallDamage(float distance) @@ -2686,6 +2824,37 @@ bool Player::isCapeHidden() { return getPlayerFlag(FLAG_HIDE_CAPE); } +bool Player::isElytraFlying() +{ + return getPlayerFlag(FLAG_ELYTRA_FLYING); +} + +void Player::onElytraKineticDamage(float damage) +{ + hurt(DamageSource::fall, damage); +} + +void Player::setElytraFlying(bool flying) +{ + setPlayerFlag(FLAG_ELYTRA_FLYING, flying); + if (flying) + { + m_wasElytraFlying = false; + setSize(0.6f, 0.6f); + fallDistance = 0.0f; + } + else + { + ticksElytraFlying = 0; + + m_wasElytraFlying = true; + m_elytraFallProtectTicks = 60; + setSize(0.6f, 1.8f); + + } +} + + bool Player::isPushedByWater() { diff --git a/Minecraft.World/Player.h b/Minecraft.World/Player.h index c74305d7..61413adc 100644 --- a/Minecraft.World/Player.h +++ b/Minecraft.World/Player.h @@ -61,6 +61,8 @@ private: protected: static const int FLAG_HIDE_CAPE = 1; + static const int FLAG_ELYTRA_FLYING = 2; // bit 2 of DATA_PLAYER_FLAGS_ID + public: shared_ptr inventory; @@ -79,6 +81,20 @@ protected: int jumpTriggerTime; public: + int ticksElytraFlying; + float rotateElytraX; + float rotateElytraY; + float rotateElytraZ; + float m_elytraImpactYd; + bool m_wasElytraFlying; + int m_elytraFallProtectTicks; + bool isElytraFlying(); + virtual void setElytraFlying(bool flying); + + virtual void onElytraKineticDamage(float damage); + +public: + BYTE userType; float oBob, bob; @@ -351,6 +367,7 @@ private: bool m_bAwardedOnARail; protected: + virtual void checkFallDamage(double ya, bool onGround) override; virtual void causeFallDamage(float distance); public: diff --git a/Minecraft.World/SoundTypes.h b/Minecraft.World/SoundTypes.h index eb3283cb..971d4661 100644 --- a/Minecraft.World/SoundTypes.h +++ b/Minecraft.World/SoundTypes.h @@ -261,6 +261,8 @@ enum eSOUND_TYPE eSoundType_ITEM_ARMOR_equipGeneric6, eSoundType_DAMAGE_CRITICAL, + eSoundType_ITEM_ELYTRA_FLYING, + eSoundType_MAX }; diff --git a/Minecraft.World/cmake/sources/Common.cmake b/Minecraft.World/cmake/sources/Common.cmake index 941492e2..555feb7f 100644 --- a/Minecraft.World/cmake/sources/Common.cmake +++ b/Minecraft.World/cmake/sources/Common.cmake @@ -1077,6 +1077,8 @@ set(_MINECRAFT_WORLD_COMMON_NET_MINECRAFT_WORLD_ITEM "${CMAKE_CURRENT_SOURCE_DIR}/DyePowderItem.h" "${CMAKE_CURRENT_SOURCE_DIR}/EggItem.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/EggItem.h" + "${CMAKE_CURRENT_SOURCE_DIR}/ElytraItem.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ElytraItem.h" "${CMAKE_CURRENT_SOURCE_DIR}/EmptyMapItem.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/EmptyMapItem.h" "${CMAKE_CURRENT_SOURCE_DIR}/EnchantedBookItem.cpp" diff --git a/Minecraft.World/net.minecraft.world.item.h b/Minecraft.World/net.minecraft.world.item.h index 863e75a2..bd3b6c0a 100644 --- a/Minecraft.World/net.minecraft.world.item.h +++ b/Minecraft.World/net.minecraft.world.item.h @@ -1,6 +1,7 @@ #pragma once #include "ArmorItem.h" +#include "ElytraItem.h" #include "BedItem.h" #include "BoatItem.h" #include "BowItem.h"