Merge branch 'smartcmd:main' into NSDeathman-Personal

This commit is contained in:
NSDeathman 2026-03-11 13:42:00 +03:00 committed by GitHub
commit 5c46c2428c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 193 additions and 71 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
- 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
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

@ -344,6 +344,7 @@ void GameRuleManager::writeRuleFile(DataOutputStream *dos)
// Write schematic files.
unordered_map<wstring, ConsoleSchematicFile *> *files;
files = getLevelGenerationOptions()->getUnfinishedSchematicFiles();
dos->writeInt((int)files->size());
for ( auto& it : *files )
{
const wstring& filename = it.first;
@ -497,17 +498,36 @@ bool GameRuleManager::readRuleFile(LevelGenerationOptions *lgo, byte *dIn, UINT
}*/
// 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();
for (UINT i = 0; i < numFiles; i++)
if (lgo->isFromSave() && numFiles > 100)
{
wstring sFilename = contentDis->readUTF();
int length = contentDis->readInt();
byteArray ba( length );
contentDis->read(ba);
levelGenerator->loadSchematicFile(sFilename, ba.data, ba.length);
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++)
{
wstring sFilename = contentDis->readUTF();
int length = contentDis->readInt();
byteArray ba( length );
contentDis->read(ba);
levelGenerator->loadSchematicFile(sFilename, ba.data, ba.length);
}
}
LEVEL_GEN_ID lgoID = LEVEL_GEN_ID_NULL;

View file

@ -455,6 +455,74 @@ unordered_map<wstring, ConsoleSchematicFile *> *LevelGenerationOptions::getUnfin
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;
if(m_parentDLCPack != nullptr) mountIndex = m_parentDLCPack->GetDLCMountIndex();
@ -481,6 +549,7 @@ void LevelGenerationOptions::loadBaseSaveData()
setLoadedData();
app.SetAction(ProfileManager.GetPrimaryPad(), eAppAction_ReloadTexturePack);
}
#endif
}
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);
// 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()))
{
Sleep(1);
}
param->levelGen->loadBaseSaveData();
while (!param->levelGen->hasLoadedData())
{
Sleep(1);
}
}
}

View file

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

View file

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

View file

@ -937,7 +937,11 @@ bool MinecraftServer::loadLevel(LevelStorageSource *storageSource, const wstring
storage = shared_ptr<McRegionLevelStorage>(new McRegionLevelStorage(newFormatSave, File(L"."), name, true));
#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
}

View file

@ -24,18 +24,21 @@ bool BonusChestFeature::place(Level *level, Random *random, int x, int y, int z)
bool BonusChestFeature::place(Level *level, Random *random, int x, int y, int z, bool force)
{
if( !force )
//Will only spawn a bonus chest if the world is new and has never been saved.
if (level->isNew)
{
int t = 0;
while (((t = level->getTile(x, y, z)) == 0 || t == Tile::leaves_Id) && y > 1)
if( !force )
{
int t = 0;
while (((t = level->getTile(x, y, z)) == 0 || t == Tile::leaves_Id) && y > 1)
y--;
if (y < 1)
{
return false;
if (y < 1)
{
return false;
}
y++;
}
y++;
}
for (int i = 0; i < 4; i++)
{
@ -85,4 +88,6 @@ bool BonusChestFeature::place(Level *level, Random *random, int x, int y, int z,
}
return false;
}
}

View file

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

View file

@ -515,6 +515,15 @@ bool EntityHorse::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)
{

View file

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

View file

@ -64,28 +64,34 @@ void MinecartTNT::destroy(DamageSource *source)
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 (source->isFire() || source->isExplosion() || speedSqr >= 0.01f)
if (app.GetGameHostOption(eGameHostOption_TNT))
{
explode(speedSqr);
if (source->isFire() || source->isExplosion() || speedSqr >= 0.01f)
{
explode(speedSqr);
}
}
}
void MinecartTNT::explode(double speedSqr)
{
if (!app.GetGameHostOption(eGameHostOption_TNT))
{
remove();
return;
}
if (!level->isClientSide)
{
double speed = sqrt(speedSqr);
if (speed > 5.0) speed = 5.0;
if (app.GetGameHostOption(eGameHostOption_TNT))
{
level->explode(shared_from_this(), x, y, z, static_cast<float>(4 + random->nextDouble() * 1.5f * speed), true);
remove();
}
if (speed > 5) speed = 5;
level->explode(shared_from_this(), x, y, z, (float) (4 + random->nextDouble() * 1.5f * speed), true);
remove();
}
}
@ -122,12 +128,15 @@ void MinecartTNT::handleEntityEvent(byte eventId)
void MinecartTNT::primeFuse()
{
fuse = 80;
if (!level->isClientSide)
if (app.GetGameHostOption(eGameHostOption_TNT))
{
level->broadcastEntityEvent(shared_from_this(), EVENT_PRIME);
level->playEntitySound(shared_from_this(), eSoundType_RANDOM_FUSE, 1, 1.0f);
fuse = 80;
if (!level->isClientSide)
{
level->broadcastEntityEvent(shared_from_this(), EVENT_PRIME);
level->playEntitySound(shared_from_this(), eSoundType_RANDOM_FUSE, 1, 1.0f);
}
}
}

View file

@ -60,7 +60,14 @@ bool Monster::hurt(DamageSource *source, float dmg)
if (sourceEntity != shared_from_this())
{
attackTarget = sourceEntity;
if (sourceEntity->instanceof(eTYPE_PLAYER))
{
if (!dynamic_pointer_cast<Player>(sourceEntity)->abilities.invulnerable)
{
attackTarget = sourceEntity;
}
}
else attackTarget = sourceEntity;
}
return true;
}

View file

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

View file

@ -6,10 +6,10 @@
## 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
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
@ -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
- Game connections use TCP port `25565` by default
- 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`
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
| Argument | Description |
@ -100,7 +81,7 @@ Minecraft.Client.exe -name Steve -fullscreen
2. Clone the repository.
3. Open the project by double-clicking `MinecraftConsoles.sln`.
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)