Add Java Edition flight controls as opt-in setting

Adds a 'Java Flight Controls' checkbox to Help & Options > Settings > Options that enables Java Edition-style creative flying physics when toggled on.

When enabled:

- Symmetric ascend/descend (0.15 per tick each direction)

- Air friction-based movement (flyingSpeed through LivingEntity::travel)

- 2x sprint fly speed multiplier

- Vertical momentum preserved (no yd=0 reset per tick)

When disabled, the original 4J flying behavior is unchanged.

Demo: https://youtu.be/o-I0CFnV7sU

Changes:

- C++: New game setting eGameSetting_JavaFlightControls with bitmask persistence

- UI: Checkbox in SettingsOptionsMenu (all 6 resolution variants + .arc archive)

- Flying: Conditional logic in LocalPlayer::aiStep() and Player::travel()/jumpFromGround()
This commit is contained in:
MCbabel 2026-03-07 04:37:02 +01:00
parent b4d5914293
commit 2344d9c5dd
14 changed files with 90 additions and 21 deletions

View file

@ -104,6 +104,7 @@ enum EGameHostOptionWorldSize
#define GAMESETTING_ANIMATEDCHARACTER 0x00008000
#define GAMESETTING_PS3EULAREAD 0x00010000
#define GAMESETTING_PSVITANETWORKMODEADHOC 0x00020000
#define GAMESETTING_JAVAFLIGHTCONTROLS 0x00040000
// defines for languages

View file

@ -178,6 +178,8 @@ enum eGameSetting
// PSVita
eGameSetting_PSVita_NetworkModeAdhoc,
// Windows64
eGameSetting_JavaFlightControls,
};

View file

@ -2308,6 +2308,13 @@ void CMinecraftApp::SetGameSettings(int iPad,eGameSetting eVal,unsigned char ucV
GameSettingsA[iPad]->bSettingsChanged=true;
}
break;
case eGameSetting_JavaFlightControls:
if(ucVal==1)
GameSettingsA[iPad]->uiBitmaskValues|=GAMESETTING_JAVAFLIGHTCONTROLS;
else
GameSettingsA[iPad]->uiBitmaskValues&=~GAMESETTING_JAVAFLIGHTCONTROLS;
GameSettingsA[iPad]->bSettingsChanged=true;
break;
}
}
@ -2444,6 +2451,9 @@ unsigned char CMinecraftApp::GetGameSettings(int iPad,eGameSetting eVal)
case eGameSetting_PSVita_NetworkModeAdhoc:
return (GameSettingsA[iPad]->uiBitmaskValues&GAMESETTING_PSVITANETWORKMODEADHOC)>>17;
case eGameSetting_JavaFlightControls:
return (GameSettingsA[iPad]->uiBitmaskValues&GAMESETTING_JAVAFLIGHTCONTROLS)?1:0;
}
return 0;
}

View file

@ -35,6 +35,7 @@ UIScene_SettingsOptionsMenu::UIScene_SettingsOptionsMenu(int iPad, void *initDat
m_checkboxShowHints.init(IDS_HINTS,eControl_ShowHints,(app.GetGameSettings(m_iPad,eGameSetting_Hints)!=0));
m_checkboxShowTooltips.init(IDS_IN_GAME_TOOLTIPS,eControl_ShowTooltips,(app.GetGameSettings(m_iPad,eGameSetting_Tooltips)!=0));
m_checkboxInGameGamertags.init(IDS_IN_GAME_GAMERTAGS,eControl_InGameGamertags,(app.GetGameSettings(m_iPad,eGameSetting_GamertagsVisible)!=0));
m_checkboxJavaFlightControls.init(L"Java Flight Controls",eControl_JavaFlightControls,(app.GetGameSettings(m_iPad,eGameSetting_JavaFlightControls)!=0));
// check if we should display the mash-up option
if(m_bNotInGame && app.GetMashupPackWorlds(m_iPad)!=0xFFFFFFFF)
@ -259,6 +260,7 @@ void UIScene_SettingsOptionsMenu::handleReload()
m_checkboxShowHints.init(IDS_HINTS,eControl_ShowHints,(app.GetGameSettings(m_iPad,eGameSetting_Hints)!=0));
m_checkboxShowTooltips.init(IDS_IN_GAME_TOOLTIPS,eControl_ShowTooltips,(app.GetGameSettings(m_iPad,eGameSetting_Tooltips)!=0));
m_checkboxInGameGamertags.init(IDS_IN_GAME_GAMERTAGS,eControl_InGameGamertags,(app.GetGameSettings(m_iPad,eGameSetting_GamertagsVisible)!=0));
m_checkboxJavaFlightControls.init(L"Java Flight Controls",eControl_JavaFlightControls,(app.GetGameSettings(m_iPad,eGameSetting_JavaFlightControls)!=0));
// check if we should display the mash-up option
if(m_bNotInGame && app.GetMashupPackWorlds(m_iPad)!=0xFFFFFFFF)
@ -415,6 +417,7 @@ void UIScene_SettingsOptionsMenu::setGameSettings()
app.SetGameSettings(m_iPad,eGameSetting_GamertagsVisible,m_checkboxInGameGamertags.IsChecked()?1:0);
app.SetGameSettings(m_iPad,eGameSetting_Hints,m_checkboxShowHints.IsChecked()?1:0);
app.SetGameSettings(m_iPad,eGameSetting_Tooltips,m_checkboxShowTooltips.IsChecked()?1:0);
app.SetGameSettings(m_iPad,eGameSetting_JavaFlightControls,m_checkboxJavaFlightControls.IsChecked()?1:0);
// the mashup option will only be shown if some worlds have been previously hidden
if(m_bMashUpWorldsUnhideOption && m_checkboxMashupWorlds.IsChecked())

View file

@ -11,6 +11,7 @@ private:
eControl_ShowHints,
eControl_ShowTooltips,
eControl_InGameGamertags,
eControl_JavaFlightControls,
eControl_ShowMashUpWorlds,
eControl_Autosave,
eControl_Languages,
@ -21,7 +22,7 @@ protected:
static int m_iDifficultyTitleSettingA[4];
private:
UIControl_CheckBox m_checkboxViewBob, m_checkboxShowHints, m_checkboxShowTooltips, m_checkboxInGameGamertags, m_checkboxMashupWorlds; // Checkboxes
UIControl_CheckBox m_checkboxViewBob, m_checkboxShowHints, m_checkboxShowTooltips, m_checkboxInGameGamertags, m_checkboxJavaFlightControls, m_checkboxMashupWorlds; // Checkboxes
UIControl_Slider m_sliderAutosave, m_sliderDifficulty; // Sliders
UIControl_Label m_labelDifficultyText; //Text
UIControl_Button m_buttonLanguageSelect;
@ -31,6 +32,7 @@ private:
UI_MAP_ELEMENT( m_checkboxShowHints, "ShowHints")
UI_MAP_ELEMENT( m_checkboxShowTooltips, "ShowTooltips")
UI_MAP_ELEMENT( m_checkboxInGameGamertags, "InGameGamertags")
UI_MAP_ELEMENT( m_checkboxJavaFlightControls, "JavaFlightControls")
UI_MAP_ELEMENT( m_checkboxMashupWorlds, "ShowMashUpWorlds")
UI_MAP_ELEMENT( m_sliderAutosave, "Autosave")
UI_MAP_ELEMENT( m_sliderDifficulty, "Difficulty")

View file

@ -252,7 +252,8 @@ void LocalPlayer::aiStep()
bool forwardEnoughToContinueSprint = input->ya >= runTreshold;
// 4J - altered this slightly to make sure that the joypad returns to below returnTreshold in between registering two movements up to runThreshold
if ((onGround || abilities.flying) && !isSprinting() && enoughFoodToSprint && !isUsingItem() && !hasEffect(MobEffect::blindness))
bool javaFlight = app.GetGameSettings(m_iPad, eGameSetting_JavaFlightControls) != 0;
if ((onGround || (javaFlight && abilities.flying)) && !isSprinting() && enoughFoodToSprint && !isUsingItem() && !hasEffect(MobEffect::blindness))
{
if( !wasRunning && forwardEnoughToTriggerSprint )
{
@ -278,7 +279,7 @@ void LocalPlayer::aiStep()
}
if (isSneaking()) sprintTriggerTime = 0;
#ifdef _WINDOWS64
if (input->sprinting && (onGround || abilities.flying) && enoughFoodToSprint && !isUsingItem() && !hasEffect(MobEffect::blindness) && !isSneaking())
if (input->sprinting && (onGround || (javaFlight && abilities.flying)) && enoughFoodToSprint && !isUsingItem() && !hasEffect(MobEffect::blindness) && !isSneaking())
{
setSprinting(true);
}
@ -333,10 +334,20 @@ void LocalPlayer::aiStep()
if (abilities.flying)
{
if (ullButtonsPressed & (1LL<<MINECRAFT_ACTION_SNEAK_TOGGLE) ) yd -= 0.15f;
if (input->jumping)
if (javaFlight)
{
yd += 0.15f;
if (ullButtonsPressed & (1LL<<MINECRAFT_ACTION_SNEAK_TOGGLE) ) yd -= 0.15f;
if (input->jumping) yd += 0.15f;
}
else
{
// 4J - note that the 0.42 added for going down is to make it match with what happens when you jump - jumping itself adds 0.42 to yd in Mob::jumpFromGround
if (ullButtonsPressed & (1LL<<MINECRAFT_ACTION_SNEAK_TOGGLE) ) yd -= ( 0.15 + 0.42 );
if (input->jumping)
{
noJumpDelay = 0;
yd += 0.15;
}
}
// snap y rotation to nearest 90 degree axis aligned value
@ -431,22 +442,50 @@ void LocalPlayer::aiStep()
else
#endif
{
flyX = 0.0f;
flyY = 0.0f;
flyZ = 0.0f;
if( ullDpad_filtered & (1LL<<MINECRAFT_ACTION_DPAD_UP))
if (javaFlight)
{
flyY = 0.1f;
// Java Edition style: no sprint-fly viewVector overlay, simple dpad up/down
flyX = 0.0f;
flyY = 0.0f;
flyZ = 0.0f;
if( ullDpad_filtered & (1LL<<MINECRAFT_ACTION_DPAD_UP))
flyY = 0.1f;
if( ullDpad_filtered & (1LL<<MINECRAFT_ACTION_DPAD_DOWN))
flyY = -0.1f;
}
if( ullDpad_filtered & (1LL<<MINECRAFT_ACTION_DPAD_DOWN))
else
{
flyY = -0.1f;
if( isSprinting() )
{
// 4J: Accelrate up to full speed if we are sprinting, moving in the direction of the view vector
flyX = (float)viewVector->x * input->ya;
flyY = (float)viewVector->y * input->ya;
flyZ = (float)viewVector->z * input->ya;
float scale = ((float)(SPRINT_DURATION - sprintTime))/10.0f;
scale = scale * scale;
if ( scale > 1.0f ) scale = 1.0f;
flyX *= scale;
flyY *= scale;
flyZ *= scale;
}
else
{
flyX = 0.0f;
flyY = 0.0f;
flyZ = 0.0f;
if( ullDpad_filtered & (1LL<<MINECRAFT_ACTION_DPAD_UP))
flyY = 0.1f;
if( ullDpad_filtered & (1LL<<MINECRAFT_ACTION_DPAD_DOWN))
flyY = -0.1f;
}
}
}
Player::move(flyX, flyY, flyZ);
fallDistance = 0.0f;
if (!javaFlight) yd = 0.0f;
onGround = true;
}

View file

@ -1015,7 +1015,8 @@ void Player::aiStep()
flyingSpeed = defaultFlySpeed;
if (isSprinting())
{
flyingSpeed += defaultFlySpeed;
bool javaFlight = abilities.flying && app.GetGameSettings(0, eGameSetting_JavaFlightControls) != 0;
flyingSpeed += defaultFlySpeed * (javaFlight ? 1.0f : 0.3f);
}
setSpeed((float) speed->getValue());
@ -2082,7 +2083,8 @@ void Player::awardStat(Stat *stat, byteArray paramBlob)
void Player::jumpFromGround()
{
if (abilities.flying) return;
if (abilities.flying && app.GetGameSettings(0, eGameSetting_JavaFlightControls) != 0)
return;
LivingEntity::jumpFromGround();
@ -2108,12 +2110,22 @@ void Player::travel(float xa, float ya)
{
double savedYd = yd;
float ofs = flyingSpeed;
flyingSpeed = abilities.getFlyingSpeed() * (isSprinting() ? 2 : 1);
bool wasOnGround = onGround;
onGround = false;
LivingEntity::travel(xa, ya);
onGround = wasOnGround;
yd = savedYd * 0.6;
bool javaFlight = app.GetGameSettings(0, eGameSetting_JavaFlightControls) != 0;
if (javaFlight)
{
flyingSpeed = abilities.getFlyingSpeed() * (isSprinting() ? 2 : 1);
bool wasOnGround = onGround;
onGround = false;
LivingEntity::travel(xa, ya);
onGround = wasOnGround;
yd = savedYd * 0.6;
}
else
{
flyingSpeed = abilities.getFlyingSpeed();
LivingEntity::travel(xa, ya);
yd = savedYd * 0.6;
}
flyingSpeed = ofs;
}
else