feat: implement hardcore hearts with game mode lock

Display hardcore heart textures when a world is in hardcore mode,
matching Java Edition behavior. Hearts switch between normal/hardcore
across all states (poison, wither, flash) and all HUD resolutions.

C++ changes:
- IUIScene_HUD: check isHardcore() and call SetHardcoreMode() each tick
- UIScene_HUD: send hardcore boolean to Flash via Iggy, invalidate
  SetHealth dirty check on state change to force heart redraw
- CreateWorldMenu/LoadMenu: lock game mode to Survival when hardcore
- MinecraftServer: gate server.properties hardcore override behind
  MINECRAFT_SERVER_BUILD so offline worlds preserve their saved flag

SWF changes (via new Java tools):
- AddHardcoreBitmaps: adds 10 hardcore heart bitmaps to graphics SWFs
- AddHardcoreHearts: adds 10 new frames (15-24) to health sprite
- PatchHudABC: patches HUD ActionScript bytecode with SetHardcore
  method and frame offset logic (+14 normal/poison, +6 wither)

Also updates README changelog styling with consistent ### headings.
This commit is contained in:
itsRevela
2026-03-30 13:50:29 -05:00
parent a4f55dde16
commit 3aa2d23fa9
16 changed files with 1538 additions and 30 deletions
Binary file not shown.
+12 -2
View File
@@ -5,6 +5,8 @@
#include "..\..\..\Minecraft.World\net.minecraft.world.item.h"
#include "..\..\..\Minecraft.World\net.minecraft.world.entity.ai.attributes.h"
#include "..\..\..\Minecraft.World\net.minecraft.world.entity.monster.h"
#include "..\..\MultiPlayerLevel.h"
#include "..\..\..\Minecraft.World\LevelData.h"
#include "IUIScene_HUD.h"
#include "UI.h"
@@ -20,6 +22,7 @@ IUIScene_HUD::IUIScene_HUD()
m_lastMaxHealth = 20;
m_lastHealthBlink = false;
m_lastHealthPoison = false;
m_lastHealthHardcore = false;
m_iCurrentFood = -1;
m_lastFoodPoison = false;
m_lastAir = 10;
@@ -94,9 +97,10 @@ void IUIScene_HUD::updateFrameTick()
ShowHealth(false);
ShowFood(false);
ShowAir(false);
ShowArmour(false);
ShowArmour(false);
ShowExpBar(false);
SetHealthAbsorb(0);
SetHealthAbsorb(0);
SetHardcoreMode(false);
}
if(pMinecraft->localplayers[iPad]->isRidingJumpable())
@@ -206,6 +210,12 @@ void IUIScene_HUD::renderPlayerHealth()
// Update armour
int armor = pMinecraft->localplayers[iPad]->getArmorValue();
// Check hardcore mode
bool bHardcore = pMinecraft->level != nullptr
&& pMinecraft->level->getLevelData() != nullptr
&& pMinecraft->level->getLevelData()->isHardcore();
SetHardcoreMode(bHardcore);
SetHealth(currentHealth, oldHealth, blink, bHasPoison || bHasWither, bHasWither);
SetHealthAbsorb(totalAbsorption);
@@ -11,6 +11,7 @@ protected:
int m_iCurrentHealth;
int m_lastMaxHealth;
bool m_lastHealthBlink, m_lastHealthPoison, m_lastHealthWither;
bool m_lastHealthHardcore;
int m_iCurrentFood;
bool m_lastFoodPoison;
int m_lastAir, m_currentExtraAir;
@@ -46,6 +47,7 @@ protected:
virtual void SetActiveSlot(int slot) = 0;
virtual void SetHealth(int iHealth, int iLastHealth, bool bBlink, bool bPoison, bool bWither) = 0;
virtual void SetHardcoreMode(bool bHardcore) = 0;
virtual void SetFood(int iFood, int iLastFood, bool bPoison) = 0;
virtual void SetAir(int iAir, int extra) = 0;
virtual void SetArmour(int iArmour) = 0;
@@ -461,6 +461,8 @@ void UIScene_CreateWorldMenu::handlePress(F64 controlId, F64 childId)
}
break;
case eControl_GameModeToggle:
if (s_bHardcore)
break; // Hardcore mode locks game mode to Survival
switch(m_iGameModeId)
{
case 0: // Creative
@@ -473,7 +475,7 @@ void UIScene_CreateWorldMenu::handlePress(F64 controlId, F64 childId)
m_iGameModeId = GameType::ADVENTURE->getId();
m_bGameModeCreative = false;
break;
case 2: // Survival
case 2: // Survival
m_buttonGamemode.setLabel(app.GetString(IDS_GAMEMODE_SURVIVAL));
m_iGameModeId = GameType::SURVIVAL->getId();
m_bGameModeCreative = false;
@@ -665,6 +667,14 @@ void UIScene_CreateWorldMenu::handleSliderMove(F64 sliderId, F64 currentValue)
else
swprintf( (WCHAR *)TempString, 256, L"%ls: %ls", app.GetString( IDS_SLIDER_DIFFICULTY ),app.GetString(m_iDifficultyTitleSettingA[value]));
m_sliderDifficulty.setLabel(TempString);
// Hardcore locks game mode to Survival
if (s_bHardcore && m_iGameModeId != GameType::SURVIVAL->getId())
{
m_iGameModeId = GameType::SURVIVAL->getId();
m_bGameModeCreative = false;
m_buttonGamemode.setLabel(app.GetString(IDS_GAMEMODE_SURVIVAL));
}
break;
}
}
@@ -655,6 +655,23 @@ void UIScene_HUD::SetHorseJumpBarProgress(float progress)
}
}
void UIScene_HUD::SetHardcoreMode(bool bHardcore)
{
IggyDataValue result;
IggyDataValue value[1];
value[0].type = IGGY_DATATYPE_boolean;
value[0].boolval = bHardcore;
IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetHardcore , 1 , value );
// When hardcore state changes, invalidate SetHealth's dirty check
// so hearts are redrawn with the correct frame set on the next tick
if(bHardcore != m_lastHealthHardcore)
{
m_lastHealthHardcore = bHardcore;
m_lastMaxHealth = -1;
}
}
void UIScene_HUD::SetHealthAbsorb(int healthAbsorb)
{
if(m_iCurrentHealthAbsorb != healthAbsorb)
+4
View File
@@ -25,6 +25,7 @@ protected:
IggyName m_funcRepositionHud, m_funcSetDisplayName, m_funcSetTooltipsEnabled;
IggyName m_funcSetRidingHorse, m_funcSetHorseHealth, m_funcSetHorseJumpBarProgress;
IggyName m_funcSetHealthAbsorb;
IggyName m_funcSetHardcore;
UI_BEGIN_MAP_ELEMENTS_AND_NAMES(UIScene)
UI_MAP_ELEMENT(m_labelChatText[0],"Label1")
UI_MAP_ELEMENT(m_labelChatText[1],"Label2")
@@ -89,6 +90,7 @@ protected:
UI_MAP_NAME(m_funcSetHorseJumpBarProgress, L"SetHorseJumpBarProgress")
UI_MAP_NAME(m_funcSetHealthAbsorb, L"SetHealthAbsorb")
UI_MAP_NAME(m_funcSetHardcore, L"SetHardcore")
UI_END_MAP_ELEMENTS_AND_NAMES()
public:
@@ -159,6 +161,8 @@ private:
void SetHealthAbsorb(int healthAbsorb);
void SetHardcoreMode(bool bHardcore);
public:
void SetSelectedLabel(const wstring &label);
void ShowDisplayName(bool show);
@@ -579,6 +579,11 @@ void UIScene_LoadMenu::tick()
WCHAR TempString[256];
swprintf( (WCHAR *)TempString, 256, L"%ls: %ls", app.GetString( IDS_SLIDER_DIFFICULTY ), L"Hardcore");
m_sliderDifficulty.init(TempString, eControl_Difficulty, 0, 4, 4);
// Hardcore locks game mode to Survival
m_iGameModeId = GameType::SURVIVAL->getId();
m_bGameModeCreative = false;
m_buttonGamemode.setLabel(app.GetString(IDS_GAMEMODE_SURVIVAL));
}
}
@@ -733,6 +738,8 @@ void UIScene_LoadMenu::handlePress(F64 controlId, F64 childId)
switch(static_cast<int>(controlId))
{
case eControl_GameMode:
if (m_bHardcore)
break; // Hardcore mode locks game mode to Survival
switch(m_iGameModeId)
{
case 0: // Survival
+4 -1
View File
@@ -1008,8 +1008,11 @@ bool MinecraftServer::loadLevel(LevelStorageSource *storageSource, const wstring
#endif
levels[i]->getLevelData()->setGameType(gameType);
// Apply hardcore flag from host option to level data so loaded worlds respect server.properties
#ifdef MINECRAFT_SERVER_BUILD
// Dedicated server: server.properties hardcore flag is authoritative
levels[i]->getLevelData()->setHardcore(isHardcore());
#endif
// Offline/client-hosted: keep the world's saved hardcore flag from NBT
if(app.getLevelGenerationOptions() != nullptr)
{