Source code for game.environment.villager_happiness

"""Manages the happiness level of the villagers."""

from ..utils.constants import *
import math

[docs] class VillagerHappinessSystem: """Calculates and tracks the average happiness of island villagers. Happiness is composed of three parts: * **Food happiness** — rises with pig count up to an ideal range, then falls off as the population grows too large. * **Environment happiness** — average of ecosystem health (inverse of overall degradation) and current forest coverage. * **Feast bonus** — a temporary boost granted when an emergency feast reduces an over-populated pig herd; decays smoothly over time. The two base components are recalculated once per second to avoid per-frame overhead. The feast bonus is updated every frame for smooth visual decay. """ def __init__(self, isometric_map, entity_manager, degradation_system): """Initialise the happiness system. Args: isometric_map (IsometricMap): Used to query forest tile coverage. entity_manager (EntityManager): Provides the current pig count. degradation_system (DegradationSystem): Provides overall degradation for the environment happiness calculation. """
[docs] self.isometric_map = isometric_map
[docs] self.entity_manager = entity_manager
[docs] self.degradation_system = degradation_system
#: Food-based happiness component, in [0, 1].
[docs] self.food_happiness = 0.75
#: Environment-based happiness component, in [0, 1].
[docs] self.environment_happiness = 0.75
#: Temporary bonus from a recent feast, in [0, 0.5]; decays each frame.
[docs] self.feast_happiness_bonus = 0.0
#: Rate at which :attr:`feast_happiness_bonus` falls, in units per second.
[docs] self.feast_bonus_decay_rate = 0.1
self._update_timer = 0 self._update_interval = 1.0
[docs] def register_feast(self, pigs_slaughtered): """Register that an emergency feast has taken place. If the pig count *before* the feast exceeded ``HAPPINESS_IDEAL_PIG_MAX`` (i.e. the feast actually relieved overpopulation pressure), a one-off :attr:`feast_happiness_bonus` of **0.5** is granted. The bonus then decays each frame via :meth:`update`. Args: pigs_slaughtered (int): Number of pigs removed by the feast. Added back to the current count to reconstruct the pre-feast population for the threshold check. """ pig_count_before_feast = self.entity_manager.get_pig_count() + pigs_slaughtered if pig_count_before_feast > HAPPINESS_IDEAL_PIG_MAX: self.feast_happiness_bonus = 0.5
[docs] def update(self, dt): """Periodically recalculates base happiness and decays any active bonus.""" # Decay the feast bonus over time, this happens every frame for smoothness if self.feast_happiness_bonus > 0: self.feast_happiness_bonus -= self.feast_bonus_decay_rate * dt self.feast_happiness_bonus = max(0, self.feast_happiness_bonus) # Recalculate the slower-moving base happiness components periodically self._update_timer += dt if self._update_timer >= self._update_interval: self._update_timer -= self._update_interval self._calculate_food_happiness() self._calculate_environment_happiness()
def _calculate_food_happiness(self): """Recalculate :attr:`food_happiness` from the current pig population. The curve has three regions: * **Below** ``HAPPINESS_IDEAL_PIG_MIN``: happiness scales linearly from 0 up to 1. * **Within** ``[HAPPINESS_IDEAL_PIG_MIN, HAPPINESS_IDEAL_PIG_MAX]``: happiness is 1.0 (ideal range). * **Above** ``HAPPINESS_IDEAL_PIG_MAX``: happiness falls linearly to 0 as the count approaches ``HAPPINESS_MAX_PIG_COUNT``. """ pig_count = self.entity_manager.get_pig_count() if pig_count < HAPPINESS_IDEAL_PIG_MIN: # Happiness ramps up from 0 to 1 as pig count approaches the ideal minimum if HAPPINESS_IDEAL_PIG_MIN > 0: self.food_happiness = max(0, pig_count / HAPPINESS_IDEAL_PIG_MIN) else: self.food_happiness = 1.0 # If min is 0, any pigs are good elif pig_count <= HAPPINESS_IDEAL_PIG_MAX: # Peak happiness in the ideal range self.food_happiness = 1.0 else: # Happiness drops off as pigs become too numerous over_population = pig_count - HAPPINESS_IDEAL_PIG_MAX max_over = HAPPINESS_MAX_PIG_COUNT - HAPPINESS_IDEAL_PIG_MAX if max_over > 0: self.food_happiness = max(0, 1.0 - (over_population / max_over)) else: self.food_happiness = 0.0 def _calculate_environment_happiness(self): """Recalculate :attr:`environment_happiness` from ecosystem state. The value is the mean of two sub-scores: * **Health score** — ``1 - overall_degradation``. * **Forest score** — fraction of island tiles that are forest. """ health_score = 1.0 - self.degradation_system.get_overall_degradation() forest_percentage = self.isometric_map.get_forest_tile_percentage() self.environment_happiness = (health_score + forest_percentage) / 2
[docs] def get_average_happiness(self): """ Calculates and returns the current average happiness level (0.0 to 1.0). This is calculated on-demand to ensure the value is always current. """ # Calculate the base happiness from its core components base_happiness = (self.food_happiness + self.environment_happiness) / 2 # Add the decaying bonus, ensuring the total doesn't exceed 1.0 current_happiness = min(1.0, base_happiness + self.feast_happiness_bonus) return current_happiness