fix(entity): reset fall distance on Spectator/Creative gamemode switch (#2382)

* fix(entity): reset fall distance on Spectator/Creative gamemode switch

Prevents fall damage when a player switches to Spectator mid-fall and
then back to Survival before landing.

This is a pragmatic workaround — vanilla achieves this through multiple
independent defense layers (noPhysics, per-tick reset, on_ground force,
mayfly check) rather than an explicit reset. The TODOs in the code
track the remaining gaps for a full vanilla-faithful implementation.

Fixes #2372

* docs(entity): document Creative fall-distance over-forgiveness edge case

Add TODO clarifying that once vanilla fall-damage layers 1-4 are
implemented, the reset should be restricted to Spectator only. In
vanilla a non-flying Creative player keeps fallDistance across a
Survival switch (guarded by abilities.mayfly in causeFallDamage);
resetting here over-forgives that edge case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: condense fall damage comment to reference vanilla methods

Reduced comment from 23 lines to 6 by:
- Removing detailed explanations of vanilla defense layers
- Directly referencing Player.aiStep() and Player.causeFallDamage()
- Keeping only actionable TODOs
- Switching to /* */ style

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
BitForge
2026-08-02 23:23:53 -04:00
committed by GitHub
parent 3b0fdfa4f8
commit d1aba46e48

View File

@@ -2961,6 +2961,17 @@ impl Player {
}
}
/* Vanilla doesn't reset fallDistance in setGameMode(), instead relies on
* Player.aiStep() resetting when abilities.flying=true and
* Player.causeFallDamage() returning false when abilities.mayfly=true.
* TODO: Reset fall_distance each tick when abilities.flying=true (mirrors Player.aiStep())
* TODO: Add abilities.allow_flying check in LivingEntity::handle_fall_damage() (mirrors Player.causeFallDamage())
* TODO: Once implemented, restrict this reset to Spectator only.
*/
if matches!(gamemode, GameMode::Creative | GameMode::Spectator) {
self.living_entity.fall_distance.store(0.0);
}
if gamemode != GameMode::Spectator && self.camera_target_id.load().is_some() {
self.camera_target_id.store(None);
self.client.send_packet_now(&CSetCamera::new(