github.com/df-mc/dragonfly@v0.9.13/server/player/hunger.go (about)

     1  package player
     2  
     3  import (
     4  	"sync"
     5  )
     6  
     7  // hungerManager handles the changes in hunger, exhaustion and saturation of a player.
     8  type hungerManager struct {
     9  	mu              sync.RWMutex
    10  	foodLevel       int
    11  	saturationLevel float64
    12  	exhaustionLevel float64
    13  	foodTick        int
    14  }
    15  
    16  // newHungerManager returns a new hunger manager with the default values for food level, saturation level and
    17  // exhaustion level.
    18  func newHungerManager() *hungerManager {
    19  	return &hungerManager{foodLevel: 20, saturationLevel: 5}
    20  }
    21  
    22  // Food returns the current food level of a player. The level returned is guaranteed to always be between 0
    23  // and 20.
    24  func (m *hungerManager) Food() int {
    25  	m.mu.RLock()
    26  	defer m.mu.RUnlock()
    27  	return m.foodLevel
    28  }
    29  
    30  // SetFood sets the food level of a player. The level passed must be in a range of 0-20. If the level passed
    31  // is negative, the food level will be set to 0. If the level exceeds 20, the food level will be set to 20.
    32  func (m *hungerManager) SetFood(level int) {
    33  	if level < 0 {
    34  		level = 0
    35  	} else if level > 20 {
    36  		level = 20
    37  	}
    38  	m.mu.Lock()
    39  	defer m.mu.Unlock()
    40  	m.foodLevel = level
    41  }
    42  
    43  // AddFood adds a number of food points to the current food level of a player.
    44  func (m *hungerManager) AddFood(points int) {
    45  	m.mu.Lock()
    46  	defer m.mu.Unlock()
    47  
    48  	level := m.foodLevel + points
    49  	if level < 0 {
    50  		level = 0
    51  	} else if level > 20 {
    52  		level = 20
    53  	}
    54  	m.foodLevel = level
    55  }
    56  
    57  // Reset resets the hunger manager to its default values, identical to those set when creating a new manager
    58  // using newHungerManager.
    59  func (m *hungerManager) Reset() {
    60  	m.mu.Lock()
    61  	m.foodLevel = 20
    62  	m.saturationLevel = 5
    63  	m.exhaustionLevel = 0
    64  	m.foodTick = 0
    65  	m.mu.Unlock()
    66  }
    67  
    68  // exhaust exhausts the player by the amount of points passed. If the total exhaustion level exceeds 4, a
    69  // saturation point, or food point, if saturation is 0, will be subtracted.
    70  func (m *hungerManager) exhaust(points float64) {
    71  	m.mu.Lock()
    72  	defer m.mu.Unlock()
    73  
    74  	m.exhaustionLevel += points
    75  	for {
    76  		if m.exhaustionLevel < 4 {
    77  			break
    78  		}
    79  		// Maximum exhaustion value is 4, so keep removing one saturation point until the exhaustion level
    80  		// is below 4.
    81  		m.exhaustionLevel -= 4
    82  		m.desaturate()
    83  	}
    84  }
    85  
    86  // saturate saturates the player's food and saturation by the amount of points passed. Note that the total
    87  // saturation will never exceed the total food value.
    88  func (m *hungerManager) saturate(food int, saturation float64) {
    89  	m.mu.Lock()
    90  
    91  	level := m.foodLevel + food
    92  	if level < 0 {
    93  		level = 0
    94  	} else if level > 20 {
    95  		level = 20
    96  	}
    97  	m.foodLevel = level
    98  
    99  	sat := m.saturationLevel + saturation
   100  	if sat < 0 {
   101  		sat = 0
   102  	} else if sat > 20 {
   103  		sat = 20
   104  	}
   105  	if sat > float64(m.foodLevel) {
   106  		sat = float64(m.foodLevel)
   107  	}
   108  	m.saturationLevel = sat
   109  	m.mu.Unlock()
   110  }
   111  
   112  // desaturate removes one saturation point from the player. If the saturation level of the player is already
   113  // 0, a point will be subtracted from the food level instead. If that level, too, is already 0, nothing will
   114  // happen.
   115  func (m *hungerManager) desaturate() {
   116  	if m.saturationLevel <= 0 && m.foodLevel != 0 {
   117  		m.foodLevel--
   118  	} else if m.saturationLevel > 0 {
   119  		m.saturationLevel--
   120  		if m.saturationLevel < 0 {
   121  			// Some foods provide saturation with decimals, so we have to account for an overcompensation.
   122  			m.saturationLevel = 0
   123  		}
   124  	}
   125  }
   126  
   127  // canQuicklyRegenerate checks if the player can quickly regenerate. The function returns true if Food() returns 20
   128  // and the player still has saturation left.
   129  // The rate of regeneration is 1/0.5 seconds.
   130  func (m *hungerManager) canQuicklyRegenerate() bool {
   131  	m.mu.RLock()
   132  	defer m.mu.RUnlock()
   133  
   134  	return m.foodLevel == 20 && m.saturationLevel > 0
   135  }
   136  
   137  // canRegenerate checks if the player with the amount of food levels in the hunger manager can regenerate.
   138  // The function returns true if Food() returns either 18-20.
   139  // The rate of regeneration is 1/4 seconds.
   140  func (m *hungerManager) canRegenerate() bool {
   141  	return m.Food() >= 18
   142  }
   143  
   144  // canSprint returns true if the food level of the player is 7 or higher.
   145  func (m *hungerManager) canSprint() bool {
   146  	return m.Food() > 6
   147  }
   148  
   149  // starving checks if the player is currently considered to be starving. True is returned if Food() returns 0.
   150  func (m *hungerManager) starving() bool {
   151  	return m.Food() == 0
   152  }
   153  
   154  // StarvationDamageSource is the world.DamageSource passed when a player is
   155  // dealt damage from an empty food bar.
   156  type StarvationDamageSource struct{}
   157  
   158  func (StarvationDamageSource) ReducedByArmour() bool     { return false }
   159  func (StarvationDamageSource) ReducedByResistance() bool { return false }
   160  func (StarvationDamageSource) Fire() bool                { return false }