From e9ed5f25e870a53caa94c9ad9faeb38720580974 Mon Sep 17 00:00:00 2001
From: Sandertv <st.ten.veldhuis@gmail.com>
Date: Sun, 5 Jan 2025 17:08:44 +0100
Subject: [PATCH] player/player.go: Handle block breaking server-side. Resolves
 #960. Also fixes weird breaking bugs because `p.breaking` was never set to
 false(!)

---
 server/player/player.go                 | 24 +++++++++++++-----------
 server/session/handler_player_action.go | 17 +++--------------
 2 files changed, 16 insertions(+), 25 deletions(-)

diff --git a/server/player/player.go b/server/player/player.go
index 0d6a75cea..9080e24f3 100644
--- a/server/player/player.go
+++ b/server/player/player.go
@@ -89,9 +89,10 @@ type playerData struct {
 
 	breaking          bool
 	breakingPos       cube.Pos
+	breakingFace      cube.Face
 	lastBreakDuration time.Duration
 
-	breakParticleCounter uint32
+	breakCounter uint32
 
 	hunger *hungerManager
 
@@ -1730,7 +1731,7 @@ func (p *Player) StartBreaking(pos cube.Pos, face cube.Face) {
 		punchable.Punch(pos, face, p.tx, p)
 	}
 
-	p.breaking = true
+	p.breaking, p.breakingFace = true, face
 	p.SwingArm()
 
 	if p.GameMode().CreativeInventory() {
@@ -1786,7 +1787,7 @@ func (p *Player) AbortBreaking() {
 	if !p.breaking {
 		return
 	}
-	p.breaking, p.breakParticleCounter = true, 0
+	p.breaking, p.breakCounter = false, 0
 	for _, viewer := range p.viewers() {
 		viewer.ViewBlockAction(p.breakingPos, block.StopCrackAction{})
 	}
@@ -1800,19 +1801,17 @@ func (p *Player) ContinueBreaking(face cube.Face) {
 		return
 	}
 	pos := p.breakingPos
-
-	p.SwingArm()
-
 	b := p.tx.Block(pos)
 	p.tx.AddParticle(pos.Vec3(), particle.PunchBlock{Block: b, Face: face})
 
-	if p.breakParticleCounter += 1; p.breakParticleCounter%5 == 0 {
+	if p.breakCounter++; p.breakCounter%5 == 0 {
+		p.SwingArm()
+
 		// We send this sound only every so often. Vanilla doesn't send it every tick while breaking
 		// either. Every 5 ticks seems accurate.
-		p.tx.PlaySound(pos.Vec3(), sound.BlockBreaking{Block: p.tx.Block(pos)})
+		p.tx.PlaySound(pos.Vec3(), sound.BlockBreaking{Block: b})
 	}
-	breakTime := p.breakTime(pos)
-	if breakTime != p.lastBreakDuration {
+	if breakTime := p.breakTime(pos); breakTime != p.lastBreakDuration {
 		for _, viewer := range p.viewers() {
 			viewer.ViewBlockAction(pos, block.ContinueCrackAction{BreakTime: breakTime})
 		}
@@ -2348,7 +2347,7 @@ func (p *Player) Tick(tx *world.Tx, current int64) {
 	p.tickFood()
 	p.tickAirSupply()
 
-	if p.Position()[1] < float64(p.tx.Range()[0]) && p.GameMode().AllowsTakingDamage() && current%10 == 0 {
+	if p.Position()[1] < float64(p.tx.Range()[0]) {
 		p.Hurt(4, entity.VoidDamageSource{})
 	}
 	if p.insideOfSolid() {
@@ -2380,6 +2379,9 @@ func (p *Player) Tick(tx *world.Tx, current int64) {
 			c.ContinueCharge(p, tx, p.useContext(), p.useDuration())
 		}
 	}
+	if p.breaking {
+		p.ContinueBreaking(p.breakingFace)
+	}
 
 	for it, ti := range p.cooldowns {
 		if time.Now().After(ti) {
diff --git a/server/session/handler_player_action.go b/server/session/handler_player_action.go
index 74048d338..1f6d980de 100644
--- a/server/session/handler_player_action.go
+++ b/server/session/handler_player_action.go
@@ -45,19 +45,8 @@ func handlePlayerAction(action int32, face int32, pos protocol.BlockPos, entityR
 		defer s.swingingArm.Store(false)
 		c.FinishBreaking()
 	case protocol.PlayerActionCrackBreak:
-		s.swingingArm.Store(true)
-		defer s.swingingArm.Store(false)
-
-		newPos := cube.Pos{int(pos[0]), int(pos[1]), int(pos[2])}
-
-		// Sometimes no new position will be sent using a StartBreak action, so we need to detect a change in the
-		// block to be broken by comparing positions.
-		if newPos != s.breakingPos {
-			s.breakingPos = newPos
-			c.StartBreaking(newPos, cube.Face(face))
-			return nil
-		}
-		c.ContinueBreaking(cube.Face(face))
+		// Don't do anything for this action. It is no longer used. Block
+		// cracking is done fully server-side.
 	case protocol.PlayerActionStartItemUseOn:
 		// TODO: Properly utilize these actions.
 	case protocol.PlayerActionStopItemUseOn:
@@ -65,7 +54,7 @@ func handlePlayerAction(action int32, face int32, pos protocol.BlockPos, entityR
 	case protocol.PlayerActionStartBuildingBlock:
 		// Don't do anything for this action.
 	case protocol.PlayerActionCreativePlayerDestroyBlock:
-	// Don't do anything for this action.
+		// Don't do anything for this action.
 	case protocol.PlayerActionMissedSwing:
 		s.swingingArm.Store(true)
 		defer s.swingingArm.Store(false)