Merge branch 'NSDeathman-Personal' of https://github.com/NSDeathman/MinecraftConsoles into NSDeathman-Personal

This commit is contained in:
NSDeathman 2026-03-11 14:02:43 +03:00
commit fb87883b54
26 changed files with 973 additions and 744 deletions

8
.gitattributes vendored
View file

@ -1,8 +0,0 @@
*.png filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
*.binka filter=lfs diff=lfs merge=lfs -text
*.arc filter=lfs diff=lfs merge=lfs -text
*.ttf filter=lfs diff=lfs merge=lfs -text
*.bin filter=lfs diff=lfs merge=lfs -text
*.ico filter=lfs diff=lfs merge=lfs -text

View file

@ -46,6 +46,13 @@ However, we would accept changes that...
- Having workable multi-platform compilation for ARM, Consoles, Linux - Having workable multi-platform compilation for ARM, Consoles, Linux
- Being a good base for further expansion and modding of LCE, such as backports and "modpacks". - Being a good base for further expansion and modding of LCE, such as backports and "modpacks".
# Scope of PRs
All Pull Requests should fully document the changes they include in their file changes. They should also be limited to one general topic and not touch all over the codebase unless its justifiable.
For example, we would not accept a PR that reworks UI, multiplayer code, and furnace ticking even if its a "fixup" PR as its too difficult to review a ton of code changes that are all irrelevant from each other. However, a PR focused on adding a bunch of commands or fixes several crashes that are otherwise irrelevant to each other would be accepted.
If your PR includes any undocumented changes it will be closed.
# Use of AI and LLMs # Use of AI and LLMs
We currently do not accept any new code into the project that was written largely, entirely, or even noticably by an LLM. All contributions should be made by humans that understand the codebase. We currently do not accept any new code into the project that was written largely, entirely, or even noticably by an LLM. All contributions should be made by humans that understand the codebase.

View file

@ -18,6 +18,10 @@ void ConsoleSoundEngine::SetIsPlayingStreamingGameMusic(bool bVal)
{ {
m_bIsPlayingStreamingGameMusic=bVal; m_bIsPlayingStreamingGameMusic=bVal;
} }
bool ConsoleSoundEngine::GetIsPlayingMenuMusic()
{
return m_bIsPlayingMenuMusic;
}
bool ConsoleSoundEngine::GetIsPlayingEndMusic() bool ConsoleSoundEngine::GetIsPlayingEndMusic()
{ {
return m_bIsPlayingEndMusic; return m_bIsPlayingEndMusic;
@ -26,6 +30,10 @@ bool ConsoleSoundEngine::GetIsPlayingNetherMusic()
{ {
return m_bIsPlayingNetherMusic; return m_bIsPlayingNetherMusic;
} }
void ConsoleSoundEngine::SetIsPlayingMenuMusic(bool bVal)
{
m_bIsPlayingMenuMusic = bVal;
}
void ConsoleSoundEngine::SetIsPlayingEndMusic(bool bVal) void ConsoleSoundEngine::SetIsPlayingEndMusic(bool bVal)
{ {
m_bIsPlayingEndMusic=bVal; m_bIsPlayingEndMusic=bVal;

View file

@ -61,9 +61,11 @@ public:
virtual bool GetIsPlayingStreamingGameMusic() ; virtual bool GetIsPlayingStreamingGameMusic() ;
virtual void SetIsPlayingStreamingCDMusic(bool bVal) ; virtual void SetIsPlayingStreamingCDMusic(bool bVal) ;
virtual void SetIsPlayingStreamingGameMusic(bool bVal) ; virtual void SetIsPlayingStreamingGameMusic(bool bVal) ;
virtual bool GetIsPlayingMenuMusic();
virtual bool GetIsPlayingEndMusic() ; virtual bool GetIsPlayingEndMusic() ;
virtual bool GetIsPlayingNetherMusic() ; virtual bool GetIsPlayingNetherMusic() ;
virtual void SetIsPlayingEndMusic(bool bVal) ; virtual void SetIsPlayingEndMusic(bool bVal) ;
virtual void SetIsPlayingMenuMusic(bool bVal);
virtual void SetIsPlayingNetherMusic(bool bVal) ; virtual void SetIsPlayingNetherMusic(bool bVal) ;
static const WCHAR *wchSoundNames[eSoundType_MAX]; static const WCHAR *wchSoundNames[eSoundType_MAX];
static const WCHAR *wchUISoundNames[eSFX_MAX]; static const WCHAR *wchUISoundNames[eSFX_MAX];
@ -94,6 +96,7 @@ private:
bool m_bIsPlayingStreamingCDMusic; bool m_bIsPlayingStreamingCDMusic;
bool m_bIsPlayingStreamingGameMusic; bool m_bIsPlayingStreamingGameMusic;
bool m_bIsPlayingMenuMusic;
bool m_bIsPlayingEndMusic; bool m_bIsPlayingEndMusic;
bool m_bIsPlayingNetherMusic; bool m_bIsPlayingNetherMusic;
}; };

View file

@ -0,0 +1,97 @@
#include "stdafx.h"
#include "MusicTrackManager.h"
#include "../Minecraft.World/Random.h"
MusicTrackManager::MusicTrackManager(Random* rng)
: m_random(rng)
{
// The domains will be filled later via setDomainRange().
}
MusicTrackManager::~MusicTrackManager()
{
// unordered_map will automatically destroy its DomainInfo objects,
// which in turn delete[] the heard arrays.
}
void MusicTrackManager::setDomainRange(Domain domain, int minIdx, int maxIdx)
{
assert(minIdx <= maxIdx);
// Use emplace to construct the DomainInfo in-place.
// If the domain already exists, this will replace it (C++17).
m_domains.erase(domain); // Remove old entry if any
m_domains.emplace(domain, DomainInfo(minIdx, maxIdx));
}
int MusicTrackManager::selectTrack(Domain domain)
{
// Special case: if domain is None, return -1 (no track).
if (domain == Domain::None)
return -1;
DomainInfo& info = getInfo(domain);
// If range contains only one track, just return that track.
if (info.trackCount == 1)
return info.minIdx;
// Check whether all tracks have been heard.
bool allHeard = true;
for (int i = 0; i < info.trackCount; ++i)
{
if (!info.heard[i])
{
allHeard = false;
break;
}
}
// If all tracks have been heard, reset the heard flags.
if (allHeard)
{
std::memset(info.heard, 0, sizeof(bool) * info.trackCount);
}
// Try up to (trackCount/2 + 1) times to pick a track that hasn't been heard.
// This biases toward unplayed tracks but doesn't guarantee it if the random
// keeps hitting played ones. It's a compromise between fairness and performance.
const int maxAttempts = info.trackCount / 2 + 1;
for (int attempt = 0; attempt < maxAttempts; ++attempt)
{
int idx = m_random->nextInt(info.trackCount); // 0 .. trackCount-1
if (!info.heard[idx])
{
info.heard[idx] = true;
return info.minIdx + idx;
}
}
// Fallback: if we couldn't find an unplayed track (should be rare),
// just pick any random track and mark it as heard.
int fallbackIdx = m_random->nextInt(info.trackCount);
info.heard[fallbackIdx] = true;
return info.minIdx + fallbackIdx;
}
void MusicTrackManager::resetDomain(Domain domain)
{
DomainInfo& info = getInfo(domain);
std::memset(info.heard, 0, sizeof(bool) * info.trackCount);
}
// ---------- private helpers ----------
MusicTrackManager::DomainInfo& MusicTrackManager::getInfo(Domain domain)
{
auto it = m_domains.find(domain);
assert(it != m_domains.end() && "Domain not initialized. Call setDomainRange first.");
return it->second;
}
const MusicTrackManager::DomainInfo& MusicTrackManager::getInfo(Domain domain) const
{
auto it = m_domains.find(domain);
assert(it != m_domains.end() && "Domain not initialized.");
return it->second;
}

View file

@ -0,0 +1,117 @@
#pragma once
#include <unordered_map>
#include <cstring> // for memset
#include <cassert>
// Forward declaration of the random number generator used by SoundEngine.
// Replace with your actual random class if different.
class Random;
/**
* Manages selection of music tracks across different gameplay domains
* (Menu, Overworld Survival, Overworld Creative, Nether, End).
* Each domain maintains its own range of track indices and a history
* of recently played tracks to avoid immediate repetition.
*/
class MusicTrackManager
{
public:
// Enumeration of all music domains. The values match the original
// SoundEngine constants for easy conversion.
enum class Domain
{
Menu = -1,
OverworldSurvival = 0,
OverworldCreative = 1,
Nether = 2,
End = 3,
None = 4 // Special value for when no background music should play (e.g., during a music disc)
};
/**
* Constructor.
* @param rng Pointer to a random number generator (must remain valid).
*/
explicit MusicTrackManager(Random* rng);
/**
* Destructor frees all dynamically allocated heard-track arrays.
*/
~MusicTrackManager();
// Prevent copying (to avoid double deletion of internal arrays).
MusicTrackManager(const MusicTrackManager&) = delete;
MusicTrackManager& operator=(const MusicTrackManager&) = delete;
/**
* Sets the inclusive index range for a given domain.
* @param domain The music domain.
* @param minIdx First valid track index.
* @param maxIdx Last valid track index (must be >= minIdx).
*/
void setDomainRange(Domain domain, int minIdx, int maxIdx);
/**
* Selects a track index for the specified domain.
* Tries to return a track that has not been played recently;
* if all tracks have been played, resets the history and picks one randomly.
* For domains with only one track, that track is always returned.
* @param domain The domain for which to choose a track.
* @return A valid track index within the domain's range.
*/
int selectTrack(Domain domain);
/**
* Resets the heard history for a domain, marking all tracks as "not heard".
* Useful when switching domains or after a major game state change.
* @param domain The domain to reset.
*/
void resetDomain(Domain domain);
int getDomainMin(Domain d) const { return getInfo(d).minIdx; }
int getDomainMax(Domain d) const { return getInfo(d).maxIdx; }
private:
// Internal structure storing range and heard-array for a domain.
struct DomainInfo
{
int minIdx; // First track index (inclusive)
int maxIdx; // Last track index (inclusive)
int trackCount; // Number of tracks in this domain
bool* heard; // Dynamic array: heard[i] == true if track (minIdx + i) has been played recently
DomainInfo(int minVal = 0, int maxVal = 0)
: minIdx(minVal), maxIdx(maxVal), trackCount(maxVal - minVal + 1)
{
heard = new bool[trackCount](); // value-initialized to false
}
// Move constructor (optional, but needed if we want to store in unordered_map with emplace)
DomainInfo(DomainInfo&& other) noexcept
: minIdx(other.minIdx), maxIdx(other.maxIdx), trackCount(other.trackCount), heard(other.heard)
{
other.heard = nullptr; // prevent double deletion
}
// Destructor
~DomainInfo()
{
delete[] heard;
}
// No copy
DomainInfo(const DomainInfo&) = delete;
DomainInfo& operator=(const DomainInfo&) = delete;
};
// Map from Domain to its info.
std::unordered_map<Domain, DomainInfo> m_domains;
// Pointer to the random number generator.
Random* m_random;
// Helper to get the info for a domain (assumes domain exists).
DomainInfo& getInfo(Domain domain);
const DomainInfo& getInfo(Domain domain) const;
};

View file

@ -114,6 +114,9 @@ const char *SoundEngine::m_szStreamFileA[eStream_Max]=
"hal4", "hal4",
"nuance1", "nuance1",
"nuance2", "nuance2",
"piano1",
"piano2",
"piano3", // 11
#ifndef _XBOX #ifndef _XBOX
"creative1", "creative1",
@ -121,26 +124,23 @@ const char *SoundEngine::m_szStreamFileA[eStream_Max]=
"creative3", "creative3",
"creative4", "creative4",
"creative5", "creative5",
"creative6", "creative6", // 17
"menu1", "menu1",
"menu2", "menu2",
"menu3", "menu3",
"menu4", "menu4", // 21
#endif #endif
"piano1",
"piano2",
"piano3",
// Nether // Nether
"nether1", "nether1",
"nether2", "nether2",
"nether3", "nether3",
"nether4", "nether4", // 25
// The End // The End
"the_end_dragon_alive", "the_end_dragon_alive",
"the_end_end", "the_end_end", // 27
// CDs // CDs
"11", "11",
@ -191,23 +191,22 @@ void SoundEngine::init(Options* pOptions)
return; return;
} }
void SoundEngine::SetStreamingSounds(int iOverworldMin, int iOverWorldMax, int iNetherMin, int iNetherMax, int iEndMin, int iEndMax, int iCD1) void SoundEngine::SetStreamingSounds(int iMenuMin, int iMenuMax,
int iOverworldSurvivalMin, int iOverWorldSurvivalMax,
int iOverworldCreativeMin, int iOverWorldCreativeMax,
int iNetherMin, int iNetherMax,
int iEndMin, int iEndMax,
int iCD1)
{ {
m_iStream_Overworld_Min=iOverworldMin; using Domain = MusicTrackManager::Domain;
m_iStream_Overworld_Max=iOverWorldMax;
m_iStream_Nether_Min=iNetherMin;
m_iStream_Nether_Max=iNetherMax;
m_iStream_End_Min=iEndMin;
m_iStream_End_Max=iEndMax;
m_iStream_CD_1=iCD1;
// array to monitor recently played tracks m_musicTrackManager.setDomainRange(Domain::Menu, iMenuMin, iMenuMax);
if(m_bHeardTrackA) m_musicTrackManager.setDomainRange(Domain::OverworldSurvival, iOverworldSurvivalMin, iOverWorldSurvivalMax);
{ m_musicTrackManager.setDomainRange(Domain::OverworldCreative, iOverworldCreativeMin, iOverWorldCreativeMax);
delete [] m_bHeardTrackA; m_musicTrackManager.setDomainRange(Domain::Nether, iNetherMin, iNetherMax);
} m_musicTrackManager.setDomainRange(Domain::End, iEndMin, iEndMax);
m_bHeardTrackA = new bool[iEndMax+1];
memset(m_bHeardTrackA,0,sizeof(bool)*iEndMax+1); m_iStream_CD_1 = iCD1;
} }
void SoundEngine::updateMiniAudio() void SoundEngine::updateMiniAudio()
@ -391,9 +390,8 @@ void SoundEngine::tick(shared_ptr<Mob> *players, float a)
// SoundEngine // SoundEngine
// //
///////////////////////////////////////////// /////////////////////////////////////////////
SoundEngine::SoundEngine() SoundEngine::SoundEngine(): random(new Random()), m_musicTrackManager(random), m_currentMusicDomain(MusicTrackManager::Domain::Menu)
{ {
random = new Random();
memset(&m_engine, 0, sizeof(ma_engine)); memset(&m_engine, 0, sizeof(ma_engine));
memset(&m_engineConfig, 0, sizeof(ma_engine_config)); memset(&m_engineConfig, 0, sizeof(ma_engine_config));
m_musicStreamActive = false; m_musicStreamActive = false;
@ -401,15 +399,29 @@ SoundEngine::SoundEngine()
m_iMusicDelay=0; m_iMusicDelay=0;
m_validListenerCount=0; m_validListenerCount=0;
m_bHeardTrackA=nullptr; m_musicTrackManager.setDomainRange(MusicTrackManager::Domain::Menu,
eStream_Overworld_Menu1,
eStream_Overworld_Menu4);
// Start the streaming music playing some music from the overworld m_musicTrackManager.setDomainRange(MusicTrackManager::Domain::OverworldSurvival,
SetStreamingSounds(eStream_Overworld_Calm1,eStream_Overworld_piano3, eStream_Overworld_Calm1,
eStream_Nether1,eStream_Nether4, eStream_Overworld_piano3);
eStream_end_dragon,eStream_end_end,
eStream_CD_1);
m_musicID=getMusicID(LevelData::DIMENSION_OVERWORLD); m_musicTrackManager.setDomainRange(MusicTrackManager::Domain::OverworldCreative,
eStream_Overworld_Creative1,
eStream_Overworld_Creative6);
m_musicTrackManager.setDomainRange(MusicTrackManager::Domain::Nether,
eStream_Nether1,
eStream_Nether4);
m_musicTrackManager.setDomainRange(MusicTrackManager::Domain::End,
eStream_end_dragon,
eStream_end_end);
m_iStream_CD_1 = eStream_CD_1;
m_musicID = getTrackForDomain(MusicTrackManager::Domain::Menu);
m_StreamingAudioInfo.bIs3D=false; m_StreamingAudioInfo.bIs3D=false;
m_StreamingAudioInfo.x=0; m_StreamingAudioInfo.x=0;
@ -660,12 +672,36 @@ void SoundEngine::playUI(int iSound, float volume, float pitch)
// playStreaming // playStreaming
// //
///////////////////////////////////////////// /////////////////////////////////////////////
MusicTrackManager::Domain SoundEngine::determineCurrentMusicDomain() const
{
Minecraft* mc = Minecraft::GetInstance();
if (!mc || !mc->level)
return MusicTrackManager::Domain::Menu;
bool inEnd = false, inNether = false, creative = false;
for (unsigned int i = 0; i < MAX_LOCAL_PLAYERS; ++i)
{
auto player = mc->localplayers[i];
if (!player) continue;
if (player->dimension == LevelData::DIMENSION_END)
inEnd = true;
else if (player->dimension == LevelData::DIMENSION_NETHER)
inNether = true;
if (player->level->getLevelData()->getGameType()->isCreative())
creative = true;
}
if (inEnd) return MusicTrackManager::Domain::End;
if (inNether) return MusicTrackManager::Domain::Nether;
if (creative) return MusicTrackManager::Domain::OverworldCreative;
return MusicTrackManager::Domain::OverworldSurvival;
}
void SoundEngine::playStreaming(const wstring& name, float x, float y, float z, float volume, float pitch, bool bMusicDelay) void SoundEngine::playStreaming(const wstring& name, float x, float y, float z, float volume, float pitch, bool bMusicDelay)
{ {
// This function doesn't actually play a streaming sound, just sets states and an id for the music tick to play it
// Level audio will be played when a play with an empty name comes in
// CD audio will be played when a named stream comes in
m_StreamingAudioInfo.x = x; m_StreamingAudioInfo.x = x;
m_StreamingAudioInfo.y = y; m_StreamingAudioInfo.y = y;
m_StreamingAudioInfo.z = z; m_StreamingAudioInfo.z = z;
@ -673,163 +709,43 @@ void SoundEngine::playStreaming(const wstring& name, float x, float y , float z,
m_StreamingAudioInfo.pitch = pitch; m_StreamingAudioInfo.pitch = pitch;
if (m_StreamState == eMusicStreamState_Playing) if (m_StreamState == eMusicStreamState_Playing)
{
m_StreamState = eMusicStreamState_Stop; m_StreamState = eMusicStreamState_Stop;
}
else if (m_StreamState == eMusicStreamState_Opening) else if (m_StreamState == eMusicStreamState_Opening)
{
m_StreamState = eMusicStreamState_OpeningCancel; m_StreamState = eMusicStreamState_OpeningCancel;
}
if (name.empty()) if (name.empty())
{ {
// music, or stop CD
m_StreamingAudioInfo.bIs3D = false; m_StreamingAudioInfo.bIs3D = false;
m_iMusicDelay = bMusicDelay ? random->nextInt(20 * 60 * 3) : 0;
// we need a music id
// random delay of up to 3 minutes for music
m_iMusicDelay = random->nextInt(20 * 60 * 3);//random->nextInt(20 * 60 * 10) + 20 * 60 * 10;
#ifdef _DEBUG #ifdef _DEBUG
m_iMusicDelay = 0; m_iMusicDelay = 0;
#endif #endif
Minecraft *pMinecraft=Minecraft::GetInstance();
bool playerInEnd=false; MusicTrackManager::Domain domain = determineCurrentMusicDomain();
bool playerInNether=false; m_currentMusicDomain = domain;
m_musicID = getTrackForDomain(domain);
for(unsigned int i=0;i<MAX_LOCAL_PLAYERS;i++)
{
if(pMinecraft->localplayers[i]!=nullptr)
{
if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_END)
{
playerInEnd=true;
}
else if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_NETHER)
{
playerInNether=true;
}
}
}
if(playerInEnd)
{
m_musicID = getMusicID(LevelData::DIMENSION_END);
}
else if(playerInNether)
{
m_musicID = getMusicID(LevelData::DIMENSION_NETHER);
} }
else else
{ {
m_musicID = getMusicID(LevelData::DIMENSION_OVERWORLD); // Disk (jukebox)
}
}
else
{
// jukebox
m_StreamingAudioInfo.bIs3D = true; m_StreamingAudioInfo.bIs3D = true;
m_musicID = getMusicID(name); m_musicID = getMusicID(name);
m_iMusicDelay = 0; m_iMusicDelay = 0;
} }
} }
int SoundEngine::GetRandomishTrack(int iStart,int iEnd)
{
// 4J-PB - make it more likely that we'll get a track we've not heard for a while, although repeating tracks sometimes is fine
// if all tracks have been heard, clear the flags
bool bAllTracksHeard=true;
int iVal=iStart;
for(size_t i=iStart;i<=iEnd;i++)
{
if(m_bHeardTrackA[i]==false)
{
bAllTracksHeard=false;
app.DebugPrintf("Not heard all tracks yet\n");
break;
}
}
if(bAllTracksHeard)
{
app.DebugPrintf("Heard all tracks - resetting the tracking array\n");
for(size_t i=iStart;i<=iEnd;i++)
{
m_bHeardTrackA[i]=false;
}
}
// trying to get a track we haven't heard, but not too hard
for(size_t i=0;i<=((iEnd-iStart)/2);i++)
{
// random->nextInt(1) will always return 0
iVal=random->nextInt((iEnd-iStart)+1)+iStart;
if(m_bHeardTrackA[iVal]==false)
{
// not heard this
app.DebugPrintf("(%d) Not heard track %d yet, so playing it now\n",i,iVal);
m_bHeardTrackA[iVal]=true;
break;
}
else
{
app.DebugPrintf("(%d) Skipping track %d already heard it recently\n",i,iVal);
}
}
app.DebugPrintf("Select track %d\n",iVal);
return iVal;
}
///////////////////////////////////////////// /////////////////////////////////////////////
// //
// getMusicID // getTrackForDomain(MusicTrackManager::Domain domain)
// //
///////////////////////////////////////////// /////////////////////////////////////////////
int SoundEngine::getMusicID(int iDomain) int SoundEngine::getTrackForDomain(MusicTrackManager::Domain domain)
{ {
int iRandomVal=0; if (domain == MusicTrackManager::Domain::End)
Minecraft *pMinecraft=Minecraft::GetInstance(); return m_musicTrackManager.getDomainMin(domain);
// Before the game has started?
if(pMinecraft==nullptr)
{
// any track from the overworld
return GetRandomishTrack(m_iStream_Overworld_Min,m_iStream_Overworld_Max);
}
if(pMinecraft->skins->isUsingDefaultSkin())
{
switch(iDomain)
{
case LevelData::DIMENSION_END:
// the end isn't random - it has different music depending on whether the dragon is alive or not, but we've not added the dead dragon music yet
return m_iStream_End_Min;
case LevelData::DIMENSION_NETHER:
return GetRandomishTrack(m_iStream_Nether_Min,m_iStream_Nether_Max);
//return m_iStream_Nether_Min + random->nextInt(m_iStream_Nether_Max-m_iStream_Nether_Min);
default: //overworld
//return m_iStream_Overworld_Min + random->nextInt(m_iStream_Overworld_Max-m_iStream_Overworld_Min);
return GetRandomishTrack(m_iStream_Overworld_Min,m_iStream_Overworld_Max);
}
}
else else
{ return m_musicTrackManager.selectTrack(domain);
// using a texture pack - may have multiple End music tracks
switch(iDomain)
{
case LevelData::DIMENSION_END:
return GetRandomishTrack(m_iStream_End_Min,m_iStream_End_Max);
case LevelData::DIMENSION_NETHER:
//return m_iStream_Nether_Min + random->nextInt(m_iStream_Nether_Max-m_iStream_Nether_Min);
return GetRandomishTrack(m_iStream_Nether_Min,m_iStream_Nether_Max);
default: //overworld
//return m_iStream_Overworld_Min + random->nextInt(m_iStream_Overworld_Max-m_iStream_Overworld_Min);
return GetRandomishTrack(m_iStream_Overworld_Min,m_iStream_Overworld_Max);
}
}
} }
///////////////////////////////////////////// /////////////////////////////////////////////
@ -958,23 +874,34 @@ void SoundEngine::playMusicTick()
#endif #endif
} }
// AP - moved to a separate function so it can be called from the mixer callback on Vita //=============================================================================
// playMusicUpdate
// Called every frame (or via mixer callback on Vita) to manage streaming music.
// Handles state machine for background music and music discs (jukebox).
//=============================================================================
void SoundEngine::playMusicUpdate() void SoundEngine::playMusicUpdate()
{ {
static float fMusicVol = 0.0f; // Cache the current master music volume (may be zero if system music is playing)
fMusicVol = getMasterMusicVolume(); float masterVolume = getMasterMusicVolume();
//-------------------------------------------------------------------------
// State machine for streaming audio (background music or discs)
//-------------------------------------------------------------------------
switch (m_StreamState) switch (m_StreamState)
{ {
//---------------------------------------------------------------------
// IDLE no stream is open; waiting for a delay or ready to start
//---------------------------------------------------------------------
case eMusicStreamState_Idle: case eMusicStreamState_Idle:
{
// start a stream playing // If a delay is active (e.g., between tracks), decrement and wait
if (m_iMusicDelay > 0) if (m_iMusicDelay > 0)
{ {
m_iMusicDelay--; m_iMusicDelay--;
return; return;
} }
// Sanity check: if a stream is already active, something went wrong
if (m_musicStreamActive) if (m_musicStreamActive)
{ {
app.DebugPrintf("WARNING: m_musicStreamActive already true in Idle state, resetting to Playing\n"); app.DebugPrintf("WARNING: m_musicStreamActive already true in Idle state, resetting to Playing\n");
@ -982,15 +909,20 @@ void SoundEngine::playMusicUpdate()
return; return;
} }
if(m_musicID!=-1) // If no track ID is selected, fallback to a default (should not happen)
if (m_musicID == -1)
{ {
// start playing it m_musicID = m_musicTrackManager.selectTrack(MusicTrackManager::Domain::Menu);
}
// Build the full file path for the selected music ID.
// This block handles platform-specific paths, DLC/mash-up packs, and CD music.
// The result is stored in m_szStreamName.
{
// Start with the base music path (platformdependent)
#if (defined __PS3__ || defined __PSVITA__ || defined __ORBIS__) #if (defined __PS3__ || defined __PSVITA__ || defined __ORBIS__)
#ifdef __PS3__ #ifdef __PS3__
// 4J-PB - Need to check if we are a patched BD build // PS3 special case: booted from disc patch vs. installed data
if (app.GetBootedFromDiscPatch()) if (app.GetBootedFromDiscPatch())
{ {
sprintf(m_szStreamName, "%s/%s", app.GetBDUsrDirPath(m_szMusicPath), m_szMusicPath); sprintf(m_szStreamName, "%s/%s", app.GetBDUsrDirPath(m_szMusicPath), m_szMusicPath);
@ -1001,17 +933,18 @@ void SoundEngine::playMusicUpdate()
sprintf(m_szStreamName, "%s/%s", getUsrDirPath(), m_szMusicPath); sprintf(m_szStreamName, "%s/%s", getUsrDirPath(), m_szMusicPath);
} }
#else #else
// Other consoles (Vita, Orbis)
sprintf(m_szStreamName, "%s/%s", getUsrDirPath(), m_szMusicPath); sprintf(m_szStreamName, "%s/%s", getUsrDirPath(), m_szMusicPath);
#endif #endif
#else #else
// Windows / Durango plain relative path
strcpy((char*)m_szStreamName, m_szMusicPath); strcpy((char*)m_szStreamName, m_szMusicPath);
#endif #endif
// are we using a mash-up pack?
//if(pMinecraft && !pMinecraft->skins->isUsingDefaultSkin() && pMinecraft->skins->getSelected()->hasAudio()) // Check if a mashup pack (DLC) is active and has custom audio
if (Minecraft::GetInstance()->skins->getSelected()->hasAudio()) if (Minecraft::GetInstance()->skins->getSelected()->hasAudio())
{ {
// It's a mash-up - need to use the DLC path for the music // Mashup pack: use DLC audio files
TexturePack* pTexPack = Minecraft::GetInstance()->skins->getSelected(); TexturePack* pTexPack = Minecraft::GetInstance()->skins->getSelected();
DLCTexturePack* pDLCTexPack = (DLCTexturePack*)pTexPack; DLCTexturePack* pDLCTexPack = (DLCTexturePack*)pTexPack;
DLCPack* pack = pDLCTexPack->getDLCInfoParentPack(); DLCPack* pack = pDLCTexPack->getDLCInfoParentPack();
@ -1019,22 +952,23 @@ void SoundEngine::playMusicUpdate()
app.DebugPrintf("Mashup pack\n"); app.DebugPrintf("Mashup pack\n");
// build the name // Determine whether this is game music (track index < first CD) or a CD
// if the music ID is beyond the end of the texture pack music files, then it's a CD
if (m_musicID < m_iStream_CD_1) if (m_musicID < m_iStream_CD_1)
{ {
// Game music from the mashup pack
SetIsPlayingStreamingGameMusic(true); SetIsPlayingStreamingGameMusic(true);
SetIsPlayingStreamingCDMusic(false); SetIsPlayingStreamingCDMusic(false);
m_MusicType = eMusicType_Game; m_MusicType = eMusicType_Game;
m_StreamingAudioInfo.bIs3D = false; m_StreamingAudioInfo.bIs3D = false;
#ifdef _XBOX_ONE #ifdef _XBOX_ONE
// Xbox One: use StorageManager to resolve TPACK path
wstring& wstrSoundName = dlcAudioFile->GetSoundName(m_musicID); wstring& wstrSoundName = dlcAudioFile->GetSoundName(m_musicID);
wstring wstrFile = L"TPACK:\\Data\\" + wstrSoundName + L".wav"; wstring wstrFile = L"TPACK:\\Data\\" + wstrSoundName + L".wav";
std::wstring mountedPath = StorageManager.GetMountedPath(wstrFile); std::wstring mountedPath = StorageManager.GetMountedPath(wstrFile);
wcstombs(m_szStreamName, mountedPath.c_str(), 255); wcstombs(m_szStreamName, mountedPath.c_str(), 255);
#else #else
// Other platforms: convert wstring to char and build TPACK path
wstring& wstrSoundName = dlcAudioFile->GetSoundName(m_musicID); wstring& wstrSoundName = dlcAudioFile->GetSoundName(m_musicID);
char szName[255]; char szName[255];
wcstombs(szName, wstrSoundName.c_str(), 255); wcstombs(szName, wstrSoundName.c_str(), 255);
@ -1050,12 +984,13 @@ void SoundEngine::playMusicUpdate()
} }
else else
{ {
// CD track from the mashup pack
SetIsPlayingStreamingGameMusic(false); SetIsPlayingStreamingGameMusic(false);
SetIsPlayingStreamingCDMusic(true); SetIsPlayingStreamingCDMusic(true);
m_MusicType = eMusicType_CD; m_MusicType = eMusicType_CD;
m_StreamingAudioInfo.bIs3D = true; m_StreamingAudioInfo.bIs3D = true;
// Need to adjust to index into the cds in the game's m_szStreamFileA // Append "cds/" and the base filename (from the global stream file array)
strcat((char*)m_szStreamName, "cds/"); strcat((char*)m_szStreamName, "cds/");
strcat((char*)m_szStreamName, m_szStreamFileA[m_musicID - m_iStream_CD_1 + eStream_CD_1]); strcat((char*)m_szStreamName, m_szStreamFileA[m_musicID - m_iStream_CD_1 + eStream_CD_1]);
strcat((char*)m_szStreamName, ".wav"); strcat((char*)m_szStreamName, ".wav");
@ -1063,17 +998,18 @@ void SoundEngine::playMusicUpdate()
} }
else else
{ {
// 4J-PB - if this is a PS3 disc patch, we have to check if the music file is in the patch data // No mashup pack use standard Minecraft music files
#ifdef __PS3__ #ifdef __PS3__
// PS3 disc patch handling
if (app.GetBootedFromDiscPatch() && (m_musicID < m_iStream_CD_1)) if (app.GetBootedFromDiscPatch() && (m_musicID < m_iStream_CD_1))
{ {
// rebuild the path for the music // Rebuild path for patch data
strcpy((char*)m_szStreamName, m_szMusicPath); strcpy((char*)m_szStreamName, m_szMusicPath);
strcat((char*)m_szStreamName, "music/"); strcat((char*)m_szStreamName, "music/");
strcat((char*)m_szStreamName, m_szStreamFileA[m_musicID]); strcat((char*)m_szStreamName, m_szStreamFileA[m_musicID]);
strcat((char*)m_szStreamName, ".wav"); strcat((char*)m_szStreamName, ".wav");
// check if this is in the patch data // Check if file exists in patch area; if not, fallback to original path
sprintf(m_szStreamName, "%s/%s", app.GetBDUsrDirPath(m_szStreamName), m_szMusicPath); sprintf(m_szStreamName, "%s/%s", app.GetBDUsrDirPath(m_szStreamName), m_szMusicPath);
strcat((char*)m_szStreamName, "music/"); strcat((char*)m_szStreamName, "music/");
strcat((char*)m_szStreamName, m_szStreamFileA[m_musicID]); strcat((char*)m_szStreamName, m_szStreamFileA[m_musicID]);
@ -1086,75 +1022,72 @@ void SoundEngine::playMusicUpdate()
} }
else if (m_musicID < m_iStream_CD_1) else if (m_musicID < m_iStream_CD_1)
{ {
// Standard game music (nonCD)
SetIsPlayingStreamingGameMusic(true); SetIsPlayingStreamingGameMusic(true);
SetIsPlayingStreamingCDMusic(false); SetIsPlayingStreamingCDMusic(false);
m_MusicType = eMusicType_Game; m_MusicType = eMusicType_Game;
m_StreamingAudioInfo.bIs3D = false; m_StreamingAudioInfo.bIs3D = false;
// build the name
strcat((char*)m_szStreamName, "music/"); strcat((char*)m_szStreamName, "music/");
strcat((char*)m_szStreamName, m_szStreamFileA[m_musicID]); strcat((char*)m_szStreamName, m_szStreamFileA[m_musicID]);
strcat((char*)m_szStreamName, ".wav"); strcat((char*)m_szStreamName, ".wav");
} }
else else
{ {
// CD music
SetIsPlayingStreamingGameMusic(false); SetIsPlayingStreamingGameMusic(false);
SetIsPlayingStreamingCDMusic(true); SetIsPlayingStreamingCDMusic(true);
m_MusicType = eMusicType_CD; m_MusicType = eMusicType_CD;
m_StreamingAudioInfo.bIs3D = true; m_StreamingAudioInfo.bIs3D = true;
// build the name
strcat((char*)m_szStreamName, "cds/"); strcat((char*)m_szStreamName, "cds/");
strcat((char*)m_szStreamName, m_szStreamFileA[m_musicID]); strcat((char*)m_szStreamName, m_szStreamFileA[m_musicID]);
strcat((char*)m_szStreamName, ".wav"); strcat((char*)m_szStreamName, ".wav");
} }
#else #else
// NonPS3 platforms
if (m_musicID < m_iStream_CD_1) if (m_musicID < m_iStream_CD_1)
{ {
// Game music
SetIsPlayingStreamingGameMusic(true); SetIsPlayingStreamingGameMusic(true);
SetIsPlayingStreamingCDMusic(false); SetIsPlayingStreamingCDMusic(false);
m_MusicType = eMusicType_Game; m_MusicType = eMusicType_Game;
m_StreamingAudioInfo.bIs3D = false; m_StreamingAudioInfo.bIs3D = false;
// build the name
strcat((char*)m_szStreamName, "music/"); strcat((char*)m_szStreamName, "music/");
} }
else else
{ {
// CD music
SetIsPlayingStreamingGameMusic(false); SetIsPlayingStreamingGameMusic(false);
SetIsPlayingStreamingCDMusic(true); SetIsPlayingStreamingCDMusic(true);
m_MusicType = eMusicType_CD; m_MusicType = eMusicType_CD;
m_StreamingAudioInfo.bIs3D = true; m_StreamingAudioInfo.bIs3D = true;
// build the name
strcat((char*)m_szStreamName, "cds/"); strcat((char*)m_szStreamName, "cds/");
} }
// Append the base filename (from global array) and extension
strcat((char*)m_szStreamName, m_szStreamFileA[m_musicID]); strcat((char*)m_szStreamName, m_szStreamFileA[m_musicID]);
strcat((char*)m_szStreamName, ".wav"); strcat((char*)m_szStreamName, ".wav");
#endif #endif
} }
// wstring name = m_szStreamFileA[m_musicID]; // Verify that the file exists; if not, try alternative extensions (.ogg, .mp3)
// char *SoundName = (char *)ConvertSoundPathToName(name);
// strcat((char *)szStreamName,SoundName);
FILE* pFile = nullptr; FILE* pFile = nullptr;
if (fopen_s(&pFile, reinterpret_cast<char*>(m_szStreamName), "rb") == 0 && pFile) if (fopen_s(&pFile, reinterpret_cast<char*>(m_szStreamName), "rb") == 0 && pFile)
{ {
fclose(pFile); fclose(pFile);
} }
else else
{ {
// File not found try changing the extension
const char* extensions[] = { ".ogg", ".mp3", ".wav" }; const char* extensions[] = { ".ogg", ".mp3", ".wav" };
size_t extCount = sizeof(extensions) / sizeof(extensions[0]); size_t extCount = sizeof(extensions) / sizeof(extensions[0]);
bool found = false; bool found = false;
// Find the position of the current extension (assumed to be ".wav")
char* dotPos = strrchr(reinterpret_cast<char*>(m_szStreamName), '.'); char* dotPos = strrchr(reinterpret_cast<char*>(m_szStreamName), '.');
if (dotPos != nullptr && (dotPos - reinterpret_cast<char*>(m_szStreamName)) < 250) if (dotPos != nullptr && (dotPos - reinterpret_cast<char*>(m_szStreamName)) < 250)
{ {
for (size_t i = 0; i < extCount; i++) for (size_t i = 0; i < extCount; ++i)
{ {
strcpy_s(dotPos, 5, extensions[i]); strcpy_s(dotPos, 5, extensions[i]); // Replace extension
if (fopen_s(&pFile, reinterpret_cast<char*>(m_szStreamName), "rb") == 0 && pFile) if (fopen_s(&pFile, reinterpret_cast<char*>(m_szStreamName), "rb") == 0 && pFile)
{ {
fclose(pFile); fclose(pFile);
@ -1166,62 +1099,74 @@ void SoundEngine::playMusicUpdate()
if (!found) if (!found)
{ {
// Restore original extension for error message
if (dotPos != nullptr) if (dotPos != nullptr)
{
strcpy_s(dotPos, 5, ".wav"); strcpy_s(dotPos, 5, ".wav");
}
app.DebugPrintf("WARNING: No audio file found for music ID %d (tried .ogg, .mp3, .wav)\n", m_musicID); app.DebugPrintf("WARNING: No audio file found for music ID %d (tried .ogg, .mp3, .wav)\n", m_musicID);
return; return; // Abort stay in Idle
} }
} }
} // end file path construction
app.DebugPrintf("Starting streaming - %s\n", m_szStreamName); app.DebugPrintf("Starting streaming - %s\n", m_szStreamName);
// Launch a thread to open the audio file (prevents blocking the main thread)
m_openStreamThread = new C4JThread(OpenStreamThreadProc, this, "OpenStreamThreadProc"); m_openStreamThread = new C4JThread(OpenStreamThreadProc, this, "OpenStreamThreadProc");
m_openStreamThread->Run(); m_openStreamThread->Run();
m_StreamState = eMusicStreamState_Opening; m_StreamState = eMusicStreamState_Opening;
}
break; break;
}
//---------------------------------------------------------------------
// OPENING waiting for the background thread to finish opening the file
//---------------------------------------------------------------------
case eMusicStreamState_Opening: case eMusicStreamState_Opening:
{
if (!m_openStreamThread->isRunning()) if (!m_openStreamThread->isRunning())
{ {
// Thread finished
delete m_openStreamThread; delete m_openStreamThread;
m_openStreamThread = nullptr; m_openStreamThread = nullptr;
app.DebugPrintf("OpenStreamThreadProc finished. m_musicStreamActive=%d\n", m_musicStreamActive); app.DebugPrintf("OpenStreamThreadProc finished. m_musicStreamActive=%d\n", m_musicStreamActive);
// If the stream failed to open, try a fallback (e.g., if we used a numbered variant)
if (!m_musicStreamActive) if (!m_musicStreamActive)
{ {
const char* currentExt = strrchr(reinterpret_cast<char*>(m_szStreamName), '.'); const char* currentExt = strrchr(reinterpret_cast<char*>(m_szStreamName), '.');
if (currentExt && _stricmp(currentExt, ".wav") == 0) if (currentExt && _stricmp(currentExt, ".wav") == 0)
{ {
// Attempt to rebuild the path using the base filename (without number)
const bool isCD = (m_musicID >= m_iStream_CD_1); const bool isCD = (m_musicID >= m_iStream_CD_1);
const char* folder = isCD ? "cds/" : "music/"; const char* folder = isCD ? "cds/" : "music/";
int n = sprintf_s(reinterpret_cast<char*>(m_szStreamName), 512, "%s%s%s.wav",
int n = sprintf_s(reinterpret_cast<char*>(m_szStreamName), 512, "%s%s%s.wav", m_szMusicPath, folder, m_szStreamFileA[m_musicID]); m_szMusicPath, folder, m_szStreamFileA[m_musicID]);
if (n > 0) if (n > 0)
{ {
FILE* pFile = nullptr; FILE* pFile = nullptr;
if (fopen_s(&pFile, reinterpret_cast<char*>(m_szStreamName), "rb") == 0 && pFile) if (fopen_s(&pFile, reinterpret_cast<char*>(m_szStreamName), "rb") == 0 && pFile)
{ {
fclose(pFile); fclose(pFile);
// Retry opening with the new path
m_openStreamThread = new C4JThread(OpenStreamThreadProc, this, "OpenStreamThreadProc"); m_openStreamThread = new C4JThread(OpenStreamThreadProc, this, "OpenStreamThreadProc");
m_openStreamThread->Run(); m_openStreamThread->Run();
break; break; // stay in Opening
} }
} }
} }
// No fallback worked go back to Idle
m_StreamState = eMusicStreamState_Idle; m_StreamState = eMusicStreamState_Idle;
break; break;
} }
// Stream opened successfully; configure spatialization, pitch, volume
if (m_StreamingAudioInfo.bIs3D) if (m_StreamingAudioInfo.bIs3D)
{ {
ma_sound_set_spatialization_enabled(&m_musicStream, MA_TRUE); ma_sound_set_spatialization_enabled(&m_musicStream, MA_TRUE);
ma_sound_set_position(&m_musicStream, m_StreamingAudioInfo.x, m_StreamingAudioInfo.y, m_StreamingAudioInfo.z); ma_sound_set_position(&m_musicStream,
m_StreamingAudioInfo.x,
m_StreamingAudioInfo.y,
m_StreamingAudioInfo.z);
} }
else else
{ {
@ -1230,16 +1175,23 @@ void SoundEngine::playMusicUpdate()
ma_sound_set_pitch(&m_musicStream, m_StreamingAudioInfo.pitch); ma_sound_set_pitch(&m_musicStream, m_StreamingAudioInfo.pitch);
float finalVolume = m_StreamingAudioInfo.volume * getMasterMusicVolume(); float finalVolume = m_StreamingAudioInfo.volume * masterVolume;
ma_sound_set_volume(&m_musicStream, finalVolume); ma_sound_set_volume(&m_musicStream, finalVolume);
// Start playback
ma_result startResult = ma_sound_start(&m_musicStream); ma_result startResult = ma_sound_start(&m_musicStream);
app.DebugPrintf("ma_sound_start result: %d\n", startResult); app.DebugPrintf("ma_sound_start result: %d\n", startResult);
m_StreamState = eMusicStreamState_Playing; m_StreamState = eMusicStreamState_Playing;
} }
break; break;
}
//---------------------------------------------------------------------
// OPENINGCANCEL user requested stop while opening
//---------------------------------------------------------------------
case eMusicStreamState_OpeningCancel: case eMusicStreamState_OpeningCancel:
{
if (!m_openStreamThread->isRunning()) if (!m_openStreamThread->isRunning())
{ {
delete m_openStreamThread; delete m_openStreamThread;
@ -1247,7 +1199,13 @@ void SoundEngine::playMusicUpdate()
m_StreamState = eMusicStreamState_Stop; m_StreamState = eMusicStreamState_Stop;
} }
break; break;
}
//---------------------------------------------------------------------
// STOP actively stop the current stream
//---------------------------------------------------------------------
case eMusicStreamState_Stop: case eMusicStreamState_Stop:
{
if (m_musicStreamActive) if (m_musicStreamActive)
{ {
ma_sound_stop(&m_musicStream); ma_sound_stop(&m_musicStream);
@ -1255,17 +1213,20 @@ void SoundEngine::playMusicUpdate()
m_musicStreamActive = false; m_musicStreamActive = false;
} }
// Clear flags indicating what type of music was playing
SetIsPlayingStreamingCDMusic(false); SetIsPlayingStreamingCDMusic(false);
SetIsPlayingStreamingGameMusic(false); SetIsPlayingStreamingGameMusic(false);
m_StreamState = eMusicStreamState_Idle; m_StreamState = eMusicStreamState_Idle;
break; break;
case eMusicStreamState_Stopping: }
break;
case eMusicStreamState_Play: //---------------------------------------------------------------------
break; // PLAYING the stream is actively playing
//---------------------------------------------------------------------
case eMusicStreamState_Playing: case eMusicStreamState_Playing:
{ {
// Optional periodic debug logging (once per second at 60 fps)
static int frameCount = 0; static int frameCount = 0;
if (frameCount++ % 60 == 0) if (frameCount++ % 60 == 0)
{ {
@ -1274,109 +1235,45 @@ void SoundEngine::playMusicUpdate()
bool isPlaying = ma_sound_is_playing(&m_musicStream); bool isPlaying = ma_sound_is_playing(&m_musicStream);
float vol = ma_sound_get_volume(&m_musicStream); float vol = ma_sound_get_volume(&m_musicStream);
bool isAtEnd = ma_sound_at_end(&m_musicStream); bool isAtEnd = ma_sound_at_end(&m_musicStream);
// (debug info could be printed here if needed)
} }
} }
}
// Separate handling for game music (background) and CD music (jukebox)
if (GetIsPlayingStreamingGameMusic()) if (GetIsPlayingStreamingGameMusic())
{ {
//if(m_MusicInfo.pCue!=nullptr) // Background music: check if the musical context has changed
MusicTrackManager::Domain currentDomain = determineCurrentMusicDomain();
// If the domain changed, we need to stop the current track
// so that a new one (appropriate for the new domain) will start.
if (currentDomain != m_currentMusicDomain)
{ {
bool playerInEnd = false; m_StreamState = eMusicStreamState_Stop;
bool playerInNether=false; // Store the new domain for when we restart
Minecraft *pMinecraft = Minecraft::GetInstance(); m_currentMusicDomain = currentDomain;
for(unsigned int i = 0; i < MAX_LOCAL_PLAYERS; ++i) m_musicID = getTrackForDomain(currentDomain);
{ break;
if(pMinecraft->localplayers[i]!=nullptr)
{
if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_END)
{
playerInEnd=true;
}
else if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_NETHER)
{
playerInNether=true;
}
}
} }
if(playerInEnd && !GetIsPlayingEndMusic()) // Update volume if master volume changed
{
m_StreamState=eMusicStreamState_Stop;
// Set the end track
m_musicID = getMusicID(LevelData::DIMENSION_END);
SetIsPlayingEndMusic(true);
SetIsPlayingNetherMusic(false);
}
else if(!playerInEnd && GetIsPlayingEndMusic())
{
if(playerInNether)
{
m_StreamState=eMusicStreamState_Stop;
// Set the end track
m_musicID = getMusicID(LevelData::DIMENSION_NETHER);
SetIsPlayingEndMusic(false);
SetIsPlayingNetherMusic(true);
}
else
{
m_StreamState=eMusicStreamState_Stop;
// Set the end track
m_musicID = getMusicID(LevelData::DIMENSION_OVERWORLD);
SetIsPlayingEndMusic(false);
SetIsPlayingNetherMusic(false);
}
}
else if (playerInNether && !GetIsPlayingNetherMusic())
{
m_StreamState=eMusicStreamState_Stop;
// set the Nether track
m_musicID = getMusicID(LevelData::DIMENSION_NETHER);
SetIsPlayingNetherMusic(true);
SetIsPlayingEndMusic(false);
}
else if(!playerInNether && GetIsPlayingNetherMusic())
{
if(playerInEnd)
{
m_StreamState=eMusicStreamState_Stop;
// set the Nether track
m_musicID = getMusicID(LevelData::DIMENSION_END);
SetIsPlayingNetherMusic(false);
SetIsPlayingEndMusic(true);
}
else
{
m_StreamState=eMusicStreamState_Stop;
// set the Nether track
m_musicID = getMusicID(LevelData::DIMENSION_OVERWORLD);
SetIsPlayingNetherMusic(false);
SetIsPlayingEndMusic(false);
}
}
// volume change required?
if (m_musicStreamActive) if (m_musicStreamActive)
{ {
float finalVolume = m_StreamingAudioInfo.volume * fMusicVol; float finalVolume = m_StreamingAudioInfo.volume * masterVolume;
ma_sound_set_volume(&m_musicStream, finalVolume); ma_sound_set_volume(&m_musicStream, finalVolume);
} }
} }
}
else else
{ {
// Music disc playing - if it's a 3D stream, then set the position - we don't have any streaming audio in the world that moves, so this isn't // Music disc playing (jukebox)
// required unless we have more than one listener, and are setting the listening position to the origin and setting a fake position
// for the sound down the z axis
if (m_StreamingAudioInfo.bIs3D && m_validListenerCount > 1) if (m_StreamingAudioInfo.bIs3D && m_validListenerCount > 1)
{ {
// For splitscreen, we need to position the sound relative to the closest listener.
// (The engine's listener is set to the origin; we simulate distance by moving the sound.)
int iClosestListener = 0; int iClosestListener = 0;
float fClosestDist = 1e6f; float fClosestDist = 1e6f;
for (size_t i = 0; i < MAX_LOCAL_PLAYERS; i++) for (size_t i = 0; i < MAX_LOCAL_PLAYERS; ++i)
{ {
if (m_ListenerA[i].bValid) if (m_ListenerA[i].bValid)
{ {
@ -1393,6 +1290,7 @@ void SoundEngine::playMusicUpdate()
} }
} }
// Compute sound position relative to that listener
float relX = m_StreamingAudioInfo.x - m_ListenerA[iClosestListener].vPosition.x; float relX = m_StreamingAudioInfo.x - m_ListenerA[iClosestListener].vPosition.x;
float relY = m_StreamingAudioInfo.y - m_ListenerA[iClosestListener].vPosition.y; float relY = m_StreamingAudioInfo.y - m_ListenerA[iClosestListener].vPosition.y;
float relZ = m_StreamingAudioInfo.z - m_ListenerA[iClosestListener].vPosition.z; float relZ = m_StreamingAudioInfo.z - m_ListenerA[iClosestListener].vPosition.z;
@ -1403,58 +1301,45 @@ void SoundEngine::playMusicUpdate()
} }
} }
} }
break; break;
}
//---------------------------------------------------------------------
// COMPLETED the current track reached its end naturally
//---------------------------------------------------------------------
case eMusicStreamState_Completed: case eMusicStreamState_Completed:
{ {
// random delay of up to 3 minutes for music // Set a random delay (03 minutes) before playing the next track
m_iMusicDelay = random->nextInt(20 * 60 * 3);//random->nextInt(20 * 60 * 10) + 20 * 60 * 10; m_iMusicDelay = random->nextInt(20 * 60 * 3);
// Check if we have a local player in The Nether or in The End, and play that music if they are
Minecraft *pMinecraft=Minecraft::GetInstance();
bool playerInEnd=false;
bool playerInNether=false;
for(unsigned int i=0;i<MAX_LOCAL_PLAYERS;i++) // Determine the current musical context
{ m_currentMusicDomain = determineCurrentMusicDomain();
if(pMinecraft->localplayers[i]!=nullptr)
{ // Select a new track ID appropriate for that domain
if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_END) m_musicID = getTrackForDomain(m_currentMusicDomain);
{
playerInEnd=true; // Update the flags that track which domain is active (for compatibility)
} // These are used elsewhere; we keep them in sync.
else if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_NETHER) SetIsPlayingMenuMusic(m_currentMusicDomain == MusicTrackManager::Domain::Menu);
{ SetIsPlayingEndMusic(m_currentMusicDomain == MusicTrackManager::Domain::End);
playerInNether=true; SetIsPlayingNetherMusic(m_currentMusicDomain == MusicTrackManager::Domain::Nether);
}
}
}
if(playerInEnd)
{
m_musicID = getMusicID(LevelData::DIMENSION_END);
SetIsPlayingEndMusic(true);
SetIsPlayingNetherMusic(false);
}
else if(playerInNether)
{
m_musicID = getMusicID(LevelData::DIMENSION_NETHER);
SetIsPlayingNetherMusic(true);
SetIsPlayingEndMusic(false);
}
else
{
m_musicID = getMusicID(LevelData::DIMENSION_OVERWORLD);
SetIsPlayingNetherMusic(false);
SetIsPlayingEndMusic(false);
}
m_StreamState = eMusicStreamState_Idle; m_StreamState = eMusicStreamState_Idle;
}
break; break;
} }
// check the status of the stream - this is for when a track completes rather than is stopped by the user action //---------------------------------------------------------------------
// States that require no action (STOPPING, PLAY unused)
//---------------------------------------------------------------------
case eMusicStreamState_Stopping:
case eMusicStreamState_Play:
break;
}
//-------------------------------------------------------------------------
// End-ofstream detection (common to all states)
// If the stream is active but has finished playing, transition to Completed.
//-------------------------------------------------------------------------
if (m_musicStreamActive) if (m_musicStreamActive)
{ {
if (!ma_sound_is_playing(&m_musicStream) && ma_sound_at_end(&m_musicStream)) if (!ma_sound_is_playing(&m_musicStream) && ma_sound_at_end(&m_musicStream))

View file

@ -3,6 +3,7 @@ class Mob;
class Options; class Options;
using namespace std; using namespace std;
#include "..\..\Minecraft.World\SoundTypes.h" #include "..\..\Minecraft.World\SoundTypes.h"
#include "MusicTrackManager.h"
#include "miniaudio.h" #include "miniaudio.h"
@ -17,6 +18,10 @@ enum eMUSICFILES
eStream_Overworld_hal4, eStream_Overworld_hal4,
eStream_Overworld_nuance1, eStream_Overworld_nuance1,
eStream_Overworld_nuance2, eStream_Overworld_nuance2,
eStream_Overworld_piano1,
eStream_Overworld_piano2,
eStream_Overworld_piano3, // <-- make piano3 the last overworld one
#ifndef _XBOX #ifndef _XBOX
// Add the new music tracks // Add the new music tracks
eStream_Overworld_Creative1, eStream_Overworld_Creative1,
@ -25,14 +30,12 @@ enum eMUSICFILES
eStream_Overworld_Creative4, eStream_Overworld_Creative4,
eStream_Overworld_Creative5, eStream_Overworld_Creative5,
eStream_Overworld_Creative6, eStream_Overworld_Creative6,
eStream_Overworld_Menu1, eStream_Overworld_Menu1,
eStream_Overworld_Menu2, eStream_Overworld_Menu2,
eStream_Overworld_Menu3, eStream_Overworld_Menu3,
eStream_Overworld_Menu4, eStream_Overworld_Menu4,
#endif #endif
eStream_Overworld_piano1,
eStream_Overworld_piano2,
eStream_Overworld_piano3, // <-- make piano3 the last overworld one
// Nether // Nether
eStream_Nether1, eStream_Nether1,
eStream_Nether2, eStream_Nether2,
@ -106,6 +109,7 @@ extern std::vector<MiniAudioSound*> m_activeSounds;
class SoundEngine : public ConsoleSoundEngine class SoundEngine : public ConsoleSoundEngine
{ {
static const int MAX_SAME_SOUNDS_PLAYING = 8; // 4J added static const int MAX_SAME_SOUNDS_PLAYING = 8; // 4J added
public: public:
SoundEngine(); SoundEngine();
void destroy() override; void destroy() override;
@ -125,10 +129,11 @@ public:
void addMusic(const wstring& name, File *file) override; void addMusic(const wstring& name, File *file) override;
void addStreaming(const wstring& name, File *file) override; void addStreaming(const wstring& name, File *file) override;
char *ConvertSoundPathToName(const wstring& name, bool bConvertSpaces=false) override; char *ConvertSoundPathToName(const wstring& name, bool bConvertSpaces=false) override;
bool isStreamingWavebankReady(); // 4J Added bool isStreamingWavebankReady();
int getMusicID(int iDomain); MusicTrackManager::Domain determineCurrentMusicDomain() const;
int getTrackForDomain(MusicTrackManager::Domain domain);
int getMusicID(const wstring& name); int getMusicID(const wstring& name);
void SetStreamingSounds(int iOverworldMin, int iOverWorldMax, int iNetherMin, int iNetherMax, int iEndMin, int iEndMax, int iCD1); void SetStreamingSounds(int iMenuMin, int iMenuMax, int iOverworldSurvivalMin, int iOverWorldSurvivalMax, int iOverworldCreativeMin, int iOverWorldCreativeMax, int iNetherMin, int iNetherMax, int iEndMin, int iEndMax, int iCD1);
void updateMiniAudio(); void updateMiniAudio();
void playMusicUpdate(); void playMusicUpdate();
@ -142,7 +147,9 @@ private:
{ return iMinSpeakers;} { return iMinSpeakers;}
#endif #endif
int GetRandomishTrack(int iStart,int iEnd); Random* random;
MusicTrackManager m_musicTrackManager;
MusicTrackManager::Domain m_currentMusicDomain;
ma_engine m_engine; ma_engine m_engine;
ma_engine_config m_engineConfig; ma_engine_config m_engineConfig;
@ -157,8 +164,6 @@ private:
AUDIO_LISTENER m_ListenerA[MAX_LOCAL_PLAYERS]; AUDIO_LISTENER m_ListenerA[MAX_LOCAL_PLAYERS];
int m_validListenerCount; int m_validListenerCount;
Random *random;
int m_musicID; int m_musicID;
int m_iMusicDelay; int m_iMusicDelay;
int m_StreamState; int m_StreamState;
@ -174,12 +179,7 @@ private:
char m_szStreamName[255]; char m_szStreamName[255];
int CurrentSoundsPlaying[eSoundType_MAX+eSFX_MAX]; int CurrentSoundsPlaying[eSoundType_MAX+eSFX_MAX];
// streaming music files - will be different for mash-up packs
int m_iStream_Overworld_Min,m_iStream_Overworld_Max;
int m_iStream_Nether_Min,m_iStream_Nether_Max;
int m_iStream_End_Min,m_iStream_End_Max;
int m_iStream_CD_1; int m_iStream_CD_1;
bool *m_bHeardTrackA;
#ifdef __ORBIS__ #ifdef __ORBIS__
int32_t m_hBGMAudio; int32_t m_hBGMAudio;

View file

@ -3754,7 +3754,9 @@ void CMinecraftApp::HandleXuiActions(void)
// need to stop the streaming audio - by playing streaming audio from the default texture pack now // need to stop the streaming audio - by playing streaming audio from the default texture pack now
// reset the streaming sounds back to the normal ones // reset the streaming sounds back to the normal ones
#ifndef _XBOX #ifndef _XBOX
pMinecraft->soundEngine->SetStreamingSounds(eStream_Overworld_Calm1,eStream_Overworld_piano3, pMinecraft->soundEngine->SetStreamingSounds(eStream_Overworld_Menu1, eStream_Overworld_Menu4,
eStream_Overworld_Calm1, eStream_Overworld_piano3,
eStream_Overworld_Creative1, eStream_Overworld_Creative6,
eStream_Nether1, eStream_Nether4, eStream_Nether1, eStream_Nether4,
eStream_end_dragon, eStream_end_end, eStream_end_dragon, eStream_end_end,
eStream_CD_1); eStream_CD_1);

View file

@ -344,6 +344,7 @@ void GameRuleManager::writeRuleFile(DataOutputStream *dos)
// Write schematic files. // Write schematic files.
unordered_map<wstring, ConsoleSchematicFile *> *files; unordered_map<wstring, ConsoleSchematicFile *> *files;
files = getLevelGenerationOptions()->getUnfinishedSchematicFiles(); files = getLevelGenerationOptions()->getUnfinishedSchematicFiles();
dos->writeInt((int)files->size());
for ( auto& it : *files ) for ( auto& it : *files )
{ {
const wstring& filename = it.first; const wstring& filename = it.first;
@ -497,17 +498,36 @@ bool GameRuleManager::readRuleFile(LevelGenerationOptions *lgo, byte *dIn, UINT
}*/ }*/
// subfile // subfile
// Old saves didn't write a numFiles count before the schematic entries.
// Detect this: a real count is small, but a UTF filename prefix reads as a large int.
UINT numFiles = contentDis->readInt(); UINT numFiles = contentDis->readInt();
if (lgo->isFromSave() && numFiles > 100)
{
contentDis->skip(-4);
while (true)
{
int peek = contentDis->readInt();
if (peek <= 100) { contentDis->skip(-4); break; }
contentDis->skip(-4);
wstring sFilename = contentDis->readUTF();
int length = contentDis->readInt();
byteArray ba( length );
contentDis->read(ba);
levelGenerator->loadSchematicFile(sFilename, ba.data, ba.length);
}
}
else
{
for (UINT i = 0; i < numFiles; i++) for (UINT i = 0; i < numFiles; i++)
{ {
wstring sFilename = contentDis->readUTF(); wstring sFilename = contentDis->readUTF();
int length = contentDis->readInt(); int length = contentDis->readInt();
byteArray ba( length ); byteArray ba( length );
contentDis->read(ba); contentDis->read(ba);
levelGenerator->loadSchematicFile(sFilename, ba.data, ba.length); levelGenerator->loadSchematicFile(sFilename, ba.data, ba.length);
}
} }
LEVEL_GEN_ID lgoID = LEVEL_GEN_ID_NULL; LEVEL_GEN_ID lgoID = LEVEL_GEN_ID_NULL;

View file

@ -455,6 +455,74 @@ unordered_map<wstring, ConsoleSchematicFile *> *LevelGenerationOptions::getUnfin
void LevelGenerationOptions::loadBaseSaveData() void LevelGenerationOptions::loadBaseSaveData()
{ {
#ifdef _WINDOWS64
int gameRulesCount = m_parentDLCPack ? m_parentDLCPack->getDLCItemsCount(DLCManager::e_DLCType_GameRulesHeader) : 0;
wstring baseSave = getBaseSavePath();
wstring packName = baseSave.substr(0, baseSave.find(L'.'));
for (int i = 0; i < gameRulesCount; ++i)
{
DLCGameRulesHeader* dlcFile = static_cast<DLCGameRulesHeader*>(m_parentDLCPack->getFile(DLCManager::e_DLCType_GameRulesHeader, i));
if (!dlcFile->getGrfPath().empty())
{
File grf(L"Windows64Media\\DLC\\" + packName + L"\\Data\\" + dlcFile->getGrfPath());
if (grf.exists())
{
wstring path = grf.getPath();
HANDLE fileHandle = CreateFileW(path.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
if (fileHandle != INVALID_HANDLE_VALUE)
{
DWORD dwFileSize = grf.length();
DWORD bytesRead;
PBYTE pbData = new BYTE[dwFileSize];
BOOL bSuccess = ReadFile(fileHandle, pbData, dwFileSize, &bytesRead, nullptr);
CloseHandle(fileHandle);
if (bSuccess)
{
dlcFile->setGrfData(pbData, dwFileSize, m_stringTable);
app.m_gameRules.setLevelGenerationOptions(dlcFile->lgo);
}
delete[] pbData;
}
}
}
}
if (requiresBaseSave() && !getBaseSavePath().empty())
{
File save(L"Windows64Media\\DLC\\" + packName + L"\\Data\\" + baseSave);
if (save.exists())
{
wstring path = save.getPath();
HANDLE fileHandle = CreateFileW(path.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
if (fileHandle != INVALID_HANDLE_VALUE)
{
DWORD dwFileSize = GetFileSize(fileHandle, nullptr);
DWORD bytesRead;
PBYTE pbData = new BYTE[dwFileSize];
BOOL bSuccess = ReadFile(fileHandle, pbData, dwFileSize, &bytesRead, nullptr);
CloseHandle(fileHandle);
if (bSuccess)
setBaseSaveData(pbData, dwFileSize);
else
delete[] pbData;
}
}
}
setLoadedData();
app.SetAction(ProfileManager.GetPrimaryPad(), eAppAction_ReloadTexturePack);
#else
int mountIndex = -1; int mountIndex = -1;
if(m_parentDLCPack != nullptr) mountIndex = m_parentDLCPack->GetDLCMountIndex(); if(m_parentDLCPack != nullptr) mountIndex = m_parentDLCPack->GetDLCMountIndex();
@ -481,6 +549,7 @@ void LevelGenerationOptions::loadBaseSaveData()
setLoadedData(); setLoadedData();
app.SetAction(ProfileManager.GetPrimaryPad(), eAppAction_ReloadTexturePack); app.SetAction(ProfileManager.GetPrimaryPad(), eAppAction_ReloadTexturePack);
} }
#endif
} }
int LevelGenerationOptions::packMounted(LPVOID pParam,int iPad,DWORD dwErr,DWORD dwLicenceMask) int LevelGenerationOptions::packMounted(LPVOID pParam,int iPad,DWORD dwErr,DWORD dwLicenceMask)

View file

@ -942,13 +942,18 @@ int CGameNetworkManager::ServerThreadProc( void* lpParameter )
app.SetGameHostOption(eGameHostOption_All,param->settings); app.SetGameHostOption(eGameHostOption_All,param->settings);
// 4J Stu - If we are loading a DLC save that's separate from the texture pack, load // 4J Stu - If we are loading a DLC save that's separate from the texture pack, load
if( param->levelGen != nullptr && (param->texturePackId == 0 || param->levelGen->getRequiredTexturePackId() != param->texturePackId) ) if (param != nullptr && param->levelGen != nullptr && param->levelGen->isFromDLC())
{ {
while((Minecraft::GetInstance()->skins->needsUIUpdate() || ui.IsReloadingSkin())) while((Minecraft::GetInstance()->skins->needsUIUpdate() || ui.IsReloadingSkin()))
{ {
Sleep(1); Sleep(1);
} }
param->levelGen->loadBaseSaveData(); param->levelGen->loadBaseSaveData();
while (!param->levelGen->hasLoadedData())
{
Sleep(1);
}
} }
} }

View file

@ -195,8 +195,8 @@ void IUIScene_HUD::renderPlayerHealth()
// Update health // Update health
bool blink = pMinecraft->localplayers[iPad]->invulnerableTime / 3 % 2 == 1; bool blink = pMinecraft->localplayers[iPad]->invulnerableTime / 3 % 2 == 1;
if (pMinecraft->localplayers[iPad]->invulnerableTime < 10) blink = false; if (pMinecraft->localplayers[iPad]->invulnerableTime < 10) blink = false;
int currentHealth = pMinecraft->localplayers[iPad]->getHealth(); int currentHealth = static_cast<int>(ceil(pMinecraft->localplayers[iPad]->getHealth()));
int oldHealth = pMinecraft->localplayers[iPad]->lastHealth; int oldHealth = static_cast<int>(ceil(pMinecraft->localplayers[iPad]->lastHealth));
bool bHasPoison = pMinecraft->localplayers[iPad]->hasEffect(MobEffect::poison); bool bHasPoison = pMinecraft->localplayers[iPad]->hasEffect(MobEffect::poison);
bool bHasWither = pMinecraft->localplayers[iPad]->hasEffect(MobEffect::wither); bool bHasWither = pMinecraft->localplayers[iPad]->hasEffect(MobEffect::wither);
AttributeInstance *maxHealthAttribute = pMinecraft->localplayers[iPad]->getAttribute(SharedMonsterAttributes::MAX_HEALTH); AttributeInstance *maxHealthAttribute = pMinecraft->localplayers[iPad]->getAttribute(SharedMonsterAttributes::MAX_HEALTH);

View file

@ -2064,7 +2064,9 @@ void UIController::NavigateToHomeMenu()
{ {
// need to stop the streaming audio - by playing streaming audio from the default texture pack now // need to stop the streaming audio - by playing streaming audio from the default texture pack now
// reset the streaming sounds back to the normal ones // reset the streaming sounds back to the normal ones
pMinecraft->soundEngine->SetStreamingSounds(eStream_Overworld_Calm1,eStream_Overworld_piano3, pMinecraft->soundEngine->SetStreamingSounds(eStream_Overworld_Menu1, eStream_Overworld_Menu4,
eStream_Overworld_Calm1, eStream_Overworld_piano3,
eStream_Overworld_Creative1, eStream_Overworld_Creative6,
eStream_Nether1, eStream_Nether4, eStream_Nether1, eStream_Nether4,
eStream_end_dragon, eStream_end_end, eStream_end_dragon, eStream_end_end,
eStream_CD_1); eStream_CD_1);

View file

@ -578,10 +578,13 @@ bool UIScene::handleMouseClick(F32 x, F32 y)
if (bestCtrl->getControlType() == UIControl::eCheckBox) if (bestCtrl->getControlType() == UIControl::eCheckBox)
{ {
UIControl_CheckBox *cb = static_cast<UIControl_CheckBox*>(bestCtrl); UIControl_CheckBox *cb = static_cast<UIControl_CheckBox*>(bestCtrl);
if (cb->IsEnabled())
{
bool newState = !cb->IsChecked(); bool newState = !cb->IsChecked();
cb->setChecked(newState); cb->setChecked(newState);
handleCheckboxToggled((F64)bestId, newState); handleCheckboxToggled((F64)bestId, newState);
} }
}
else else
{ {
handlePress((F64)bestId, 0); handlePress((F64)bestId, 0);

View file

@ -483,7 +483,7 @@ int DLCTexturePack::packMounted(LPVOID pParam,int iPad,DWORD dwErr,DWORD dwLicen
iEndStart=iOverworldC+iNetherC; iEndStart=iOverworldC+iNetherC;
iEndC=dlcFile->GetCountofType(DLCAudioFile::e_AudioType_End); iEndC=dlcFile->GetCountofType(DLCAudioFile::e_AudioType_End);
Minecraft::GetInstance()->soundEngine->SetStreamingSounds(iOverworldStart,iOverworldStart+iOverworldC-1, Minecraft::GetInstance()->soundEngine->SetStreamingSounds(iOverworldStart,iOverworldStart+iOverworldC-1, iOverworldStart, iOverworldStart + iOverworldC - 1, iOverworldStart, iOverworldStart + iOverworldC - 1,
iNetherStart,iNetherStart+iNetherC-1,iEndStart,iEndStart+iEndC-1,iEndStart+iEndC); // push the CD start to after iNetherStart,iNetherStart+iNetherC-1,iEndStart,iEndStart+iEndC-1,iEndStart+iEndC); // push the CD start to after
} }
#endif #endif

View file

@ -937,7 +937,11 @@ bool MinecraftServer::loadLevel(LevelStorageSource *storageSource, const wstring
storage = shared_ptr<McRegionLevelStorage>(new McRegionLevelStorage(newFormatSave, File(L"."), name, true)); storage = shared_ptr<McRegionLevelStorage>(new McRegionLevelStorage(newFormatSave, File(L"."), name, true));
#else #else
storage = std::make_shared<McRegionLevelStorage>(new ConsoleSaveFileOriginal(L""), File(L"."), name, true); ConsoleSaveFileOriginal* pSave = new ConsoleSaveFileOriginal(L"");
pSave->ConvertToLocalPlatform();
storage = std::make_shared<McRegionLevelStorage>(pSave, File(L"."), name, true);
#endif #endif
} }

View file

@ -1806,8 +1806,6 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
InputManager.Tick(); InputManager.Tick();
// Detect KBM vs controller input mode // Detect KBM vs controller input mode
if (InputManager.IsPadConnected(0))
{
const bool controllerUsed = InputManager.ButtonPressed(0) || const bool controllerUsed = InputManager.ButtonPressed(0) ||
InputManager.GetJoypadStick_LX(0, false) != 0.0f || InputManager.GetJoypadStick_LX(0, false) != 0.0f ||
InputManager.GetJoypadStick_LY(0, false) != 0.0f || InputManager.GetJoypadStick_LY(0, false) != 0.0f ||
@ -1818,11 +1816,6 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
g_KBMInput.SetKBMActive(false); g_KBMInput.SetKBMActive(false);
else if (g_KBMInput.HasAnyInput()) else if (g_KBMInput.HasAnyInput())
g_KBMInput.SetKBMActive(true); g_KBMInput.SetKBMActive(true);
}
else
{
g_KBMInput.SetKBMActive(true);
}
if (!g_KBMInput.IsMouseGrabbed()) if (!g_KBMInput.IsMouseGrabbed())
{ {

View file

@ -23,6 +23,9 @@ bool BonusChestFeature::place(Level *level, Random *random, int x, int y, int z)
// any surrounding torches where appropriate. // any surrounding torches where appropriate.
bool BonusChestFeature::place(Level *level, Random *random, int x, int y, int z, bool force) bool BonusChestFeature::place(Level *level, Random *random, int x, int y, int z, bool force)
{
//Will only spawn a bonus chest if the world is new and has never been saved.
if (level->isNew)
{ {
if( !force ) if( !force )
{ {
@ -85,4 +88,6 @@ bool BonusChestFeature::place(Level *level, Random *random, int x, int y, int z,
} }
return false; return false;
}
} }

View file

@ -409,9 +409,14 @@ bool EnderMan::hurt(DamageSource *source, float damage)
setCreepy(true); setCreepy(true);
if ( dynamic_cast<EntityDamageSource *>(source) != nullptr && source->getEntity()->instanceof(eTYPE_PLAYER)) if ( dynamic_cast<EntityDamageSource *>(source) != nullptr && source->getEntity()->instanceof(eTYPE_PLAYER))
{
if (!dynamic_pointer_cast<Player>(source->getEntity())->abilities.invulnerable)
{ {
aggroedByPlayer = true; aggroedByPlayer = true;
} }
else setCreepy(false);
}
if (dynamic_cast<IndirectEntityDamageSource *>(source) != nullptr) if (dynamic_cast<IndirectEntityDamageSource *>(source) != nullptr)
{ {

View file

@ -515,6 +515,15 @@ bool EntityHorse::canSpawn()
return Animal::canSpawn(); return Animal::canSpawn();
} }
bool EntityHorse::removeWhenFarAway()
{
if (isTamed()) return false;
if (isSaddled()) return false;
if (isLeashed()) return false;
if (getArmorType() > 0) return false;
return Animal::removeWhenFarAway();
}
shared_ptr<EntityHorse> EntityHorse::getClosestMommy(shared_ptr<Entity> baby, double searchRadius) shared_ptr<EntityHorse> EntityHorse::getClosestMommy(shared_ptr<Entity> baby, double searchRadius)
{ {

View file

@ -192,6 +192,7 @@ private:
public: public:
virtual void containerChanged(); virtual void containerChanged();
virtual bool canSpawn(); virtual bool canSpawn();
virtual bool removeWhenFarAway() override;
protected: protected:
virtual shared_ptr<EntityHorse> getClosestMommy(shared_ptr<Entity> baby, double searchRadius); virtual shared_ptr<EntityHorse> getClosestMommy(shared_ptr<Entity> baby, double searchRadius);

View file

@ -64,30 +64,36 @@ void MinecartTNT::destroy(DamageSource *source)
double speedSqr = xd * xd + zd * zd; double speedSqr = xd * xd + zd * zd;
if (!source->isExplosion()) if (!app.GetGameHostOption(eGameHostOption_TNT) || !source->isExplosion())
{ {
spawnAtLocation(std::make_shared<ItemInstance>(Tile::tnt, 1), 0); spawnAtLocation( shared_ptr<ItemInstance>( new ItemInstance(Tile::tnt, 1) ), 0);
} }
if (app.GetGameHostOption(eGameHostOption_TNT))
{
if (source->isFire() || source->isExplosion() || speedSqr >= 0.01f) if (source->isFire() || source->isExplosion() || speedSqr >= 0.01f)
{ {
explode(speedSqr); explode(speedSqr);
} }
} }
}
void MinecartTNT::explode(double speedSqr) void MinecartTNT::explode(double speedSqr)
{ {
if (!app.GetGameHostOption(eGameHostOption_TNT))
{
remove();
return;
}
if (!level->isClientSide) if (!level->isClientSide)
{ {
double speed = sqrt(speedSqr); double speed = sqrt(speedSqr);
if (speed > 5.0) speed = 5.0; if (speed > 5) speed = 5;
if (app.GetGameHostOption(eGameHostOption_TNT)) level->explode(shared_from_this(), x, y, z, (float) (4 + random->nextDouble() * 1.5f * speed), true);
{
level->explode(shared_from_this(), x, y, z, static_cast<float>(4 + random->nextDouble() * 1.5f * speed), true);
remove(); remove();
} }
} }
}
void MinecartTNT::causeFallDamage(float distance) void MinecartTNT::causeFallDamage(float distance)
{ {
@ -121,6 +127,8 @@ void MinecartTNT::handleEntityEvent(byte eventId)
} }
void MinecartTNT::primeFuse() void MinecartTNT::primeFuse()
{
if (app.GetGameHostOption(eGameHostOption_TNT))
{ {
fuse = 80; fuse = 80;
@ -130,6 +138,7 @@ void MinecartTNT::primeFuse()
level->playEntitySound(shared_from_this(), eSoundType_RANDOM_FUSE, 1, 1.0f); level->playEntitySound(shared_from_this(), eSoundType_RANDOM_FUSE, 1, 1.0f);
} }
} }
}
int MinecartTNT::getFuse() int MinecartTNT::getFuse()
{ {

View file

@ -59,9 +59,16 @@ bool Monster::hurt(DamageSource *source, float dmg)
if (rider.lock() == sourceEntity || riding == sourceEntity) return true; if (rider.lock() == sourceEntity || riding == sourceEntity) return true;
if (sourceEntity != shared_from_this()) if (sourceEntity != shared_from_this())
{
if (sourceEntity->instanceof(eTYPE_PLAYER))
{
if (!dynamic_pointer_cast<Player>(sourceEntity)->abilities.invulnerable)
{ {
attackTarget = sourceEntity; attackTarget = sourceEntity;
} }
}
else attackTarget = sourceEntity;
}
return true; return true;
} }
return false; return false;

View file

@ -12,6 +12,11 @@ bool RegionFileCache::useSplitSaves(ESavePlatform platform)
case SAVE_FILE_PLATFORM_XBONE: case SAVE_FILE_PLATFORM_XBONE:
case SAVE_FILE_PLATFORM_PS4: case SAVE_FILE_PLATFORM_PS4:
return true; return true;
case SAVE_FILE_PLATFORM_WIN64:
{
LevelGenerationOptions* lgo = app.getLevelGenerationOptions();
return (lgo != nullptr && lgo->isFromDLC());
}
default: default:
return false; return false;
}; };

View file

@ -6,10 +6,10 @@
## Introduction ## Introduction
This project contains the source code of Minecraft Legacy Console Edition v1.6.0560.0 (TU19) from https://archive.org/details/minecraft-legacy-console-edition-source-code, with some fixes and improvements applied. This project contains the source code of Minecraft Legacy Console Edition v1.6.0560.0 (TU19) with some fixes and improvements applied.
## Download ## Download
Windows users can download our [Nightly Build](https://github.com/smartcmd/MinecraftConsoles/releases/tag/nightly)! Simply download the `.zip` file and extract it to a folder where you'd like to keep the game. You can set your username in `username.txt` (you'll have to make this file) and add servers to connect to in `servers.txt` Windows users can download our [Nightly Build](https://github.com/smartcmd/MinecraftConsoles/releases/tag/nightly)! Simply download the `.zip` file and extract it to a folder where you'd like to keep the game. You can set your username in `username.txt` (you'll have to make this file)
## Platform Support ## Platform Support
@ -35,30 +35,11 @@ Basic LAN multiplayer is available on the Windows build
- Other players on the same LAN can discover the session from the in-game Join Game menu - Other players on the same LAN can discover the session from the in-game Join Game menu
- Game connections use TCP port `25565` by default - Game connections use TCP port `25565` by default
- LAN discovery uses UDP port `25566` - LAN discovery uses UDP port `25566`
- Add servers to your server list with `servers.txt` (temp solution) - Add servers to your server list with the in-game Add Server button (temp)
- Rename yourself without losing data by keeping your `uid.dat` - Rename yourself without losing data by keeping your `uid.dat`
Parts of this feature are based on code from [LCEMP](https://github.com/LCEMP/LCEMP) (thanks!) Parts of this feature are based on code from [LCEMP](https://github.com/LCEMP/LCEMP) (thanks!)
### servers.txt
To add a server to your game, create the `servers.txt` file in the same directory as you have `Minecraft.Client.exe`. Inside, follow this format:
```
serverip.example.com
25565
The name of your server in UI!
```
For example, here's a valid servers.txt
```
1.1.1.1
25565
Cloudflare's Very Own LCE Server
127.0.0.1
25565
Localhost Test Crap
```
### Launch Arguments ### Launch Arguments
| Argument | Description | | Argument | Description |
@ -100,7 +81,7 @@ Minecraft.Client.exe -name Steve -fullscreen
2. Clone the repository. 2. Clone the repository.
3. Open the project by double-clicking `MinecraftConsoles.sln`. 3. Open the project by double-clicking `MinecraftConsoles.sln`.
4. Make sure `Minecraft.Client` is set as the Startup Project. 4. Make sure `Minecraft.Client` is set as the Startup Project.
5. Set the build configuration to **Debug** (Release is also OK but has some bugs) and the target platform to **Windows64**, then build and run. 5. Set the build configuration to **Debug** (Release is also ok but missing some debug features) and the target platform to **Windows64**, then build and run.
### CMake (Windows x64) ### CMake (Windows x64)