Source code for game.environment.degradation

"""Environmental degradation system."""

from ..utils.constants import *

[docs] class DegradationSystem: """Manages per-tile environmental degradation and island-wide recovery. Each game tick this system: 1. Applies degradation from every pig and villager to the tile they occupy. 2. Recalculates :attr:`overall_degradation` as the mean degradation across all island (grass / forest) tiles. 3. Applies natural recovery to every island tile. 4. Spreads cascading degradation from heavily degraded tiles (> 0.8) to their 8 neighbours. """ def __init__(self, isometric_map): """Initialise the degradation system. Args: isometric_map (IsometricMap): The map whose :attr:`~game.isometric_map.IsometricMap.degradation_levels` array will be read and written each tick. """
[docs] self.isometric_map = isometric_map
#: Mean degradation across all island tiles, in [0, 1]. #: Checked by :meth:`is_ecosystem_collapsed`.
[docs] self.overall_degradation = 0.0
#: Maps warning thresholds (0.3, 0.5, 0.7, 0.9) to a bool tracking #: whether the warning has already fired this game.
[docs] self.degradation_threshold_warnings = { 0.3: False, 0.5: False, 0.7: False, 0.9: False }
[docs] def update(self, dt, entity_manager): """Advance the degradation simulation by one time step. Applies entity-driven degradation, recalculates :attr:`overall_degradation`, runs natural recovery, and triggers cascading spread. Args: dt (float): Elapsed time in seconds since the last frame. entity_manager (EntityManager): Provides access to the current lists of pigs and villagers whose positions are used to locate degradation sources. """ # --- NEW: Apply degradation from pigs and villagers using new constants --- for pig in entity_manager.pigs: if self.isometric_map.is_valid_position(pig.x, pig.y): self.isometric_map.add_degradation(pig.x, pig.y, PIG_DEGRADATION_RATE * dt) for villager in entity_manager.villagers: if self.isometric_map.is_valid_position(villager.x, villager.y): self.isometric_map.add_degradation(villager.x, villager.y, VILLAGER_DEGRADATION_RATE * dt) # --- Calculate overall degradation based only on 'grass' and 'forest' tiles --- total_degradation = 0 island_tiles = 0 for y in range(self.isometric_map.height): for x in range(self.isometric_map.width): gid = self.isometric_map.map_data_gids[y][x] tile_type = self.isometric_map.tile_id_mapping.get(gid) if tile_type in ISLAND_TILES: total_degradation += self.isometric_map.degradation_levels[y][x] island_tiles += 1 if island_tiles > 0: self.overall_degradation = total_degradation / island_tiles else: self.overall_degradation = 0.0 # --- Natural recovery using direct array access --- for y in range(self.isometric_map.height): for x in range(self.isometric_map.width): gid = self.isometric_map.map_data_gids[y][x] tile_type = self.isometric_map.tile_id_mapping.get(gid) if tile_type in ISLAND_TILES: current_degradation = self.isometric_map.degradation_levels[y][x] if current_degradation > 0: recovered_amount = NATURAL_RECOVERY_RATE * dt new_degradation = max(0, current_degradation - recovered_amount) self.isometric_map.degradation_levels[y][x] = new_degradation # Cascading degradation effect self._apply_cascading_degradation(dt)
def _apply_cascading_degradation(self, dt): """Spread degradation from heavily degraded tiles to their neighbours. Any non-water tile whose degradation level exceeds **0.8** acts as a source and adds ``CASCADE_DEGRADATION_RATE * dt * current_degradation`` to each of its 8 neighbouring non-water tiles. All changes are accumulated in a temporary buffer and applied atomically so that sources and sinks within the same frame do not influence each other. Args: dt (float): Elapsed time in seconds since the last frame. """ degradation_changes = [[0.0 for _ in range(self.isometric_map.width)] for _ in range(self.isometric_map.height)] for y in range(self.isometric_map.height): for x in range(self.isometric_map.width): gid = self.isometric_map.map_data_gids[y][x] tile_type = self.isometric_map.tile_id_mapping.get(gid) if tile_type == 'water' or tile_type is None: continue current_degradation = self.isometric_map.degradation_levels[y][x] if current_degradation > 0.8: for dy in [-1, 0, 1]: for dx in [-1, 0, 1]: if dx == 0 and dy == 0: continue nx, ny = x + dx, y + dy if 0 <= ny < self.isometric_map.height and 0 <= nx < self.isometric_map.width: neighbor_gid = self.isometric_map.map_data_gids[ny][nx] neighbor_type = self.isometric_map.tile_id_mapping.get(neighbor_gid) if neighbor_type != 'water' and neighbor_type is not None: # --- USE CONSTANT FOR CASCADE RATE --- cascade_amount = CASCADE_DEGRADATION_RATE * dt * current_degradation degradation_changes[ny][nx] += cascade_amount # Apply the calculated changes for y in range(self.isometric_map.height): for x in range(self.isometric_map.width): if degradation_changes[y][x] > 0: current_level = self.isometric_map.degradation_levels[y][x] self.isometric_map.degradation_levels[y][x] = min(1.0, current_level + degradation_changes[y][x])
[docs] def get_overall_degradation(self): """Return the current island-wide degradation level. Returns: float: Mean degradation across all island tiles, in [0, 1]. """ return self.overall_degradation
[docs] def is_ecosystem_collapsed(self): """Return whether the ecosystem has passed the collapse threshold. Collapse is defined as :attr:`overall_degradation` exceeding ``ECOSYSTEM_COLLAPSE_THRESHOLD`` (see :mod:`game.utils.constants`). When this returns ``True`` the game triggers a loss condition. Returns: bool: ``True`` if the ecosystem has collapsed. """ return self.overall_degradation > ECOSYSTEM_COLLAPSE_THRESHOLD
[docs] def get_degradation_status(self): """Return a human-readable label for the current degradation level. Returns: str: One of ``"Healthy"``, ``"Slight Damage"``, ``"Moderate Damage"``, ``"Heavy Damage"``, or ``"Critical Damage"``. """ if self.overall_degradation < 0.2: return "Healthy" elif self.overall_degradation < 0.4: return "Slight Damage" elif self.overall_degradation < 0.6: return "Moderate Damage" elif self.overall_degradation < 0.8: return "Heavy Damage" else: return "Critical Damage"