Source code for bruhanimate.bruheffect.rain_effect

"""
Copyright 2023 Ethan Christensen

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

import random

from ..bruhutil import WIND_DIRECTIONS, Buffer
from .base_effect import BaseEffect
from .settings import RainSettings


[docs] class RainEffect(BaseEffect): """ Effect to simulate the look of rain. """
[docs] def __init__(self, buffer: Buffer, background: str, settings: RainSettings = None): """ Initializes the RainEffect. Args: buffer (Buffer): Effect buffer to push updates to. background (str): Character or string to use for the background. settings (RainSettings, optional): Configuration for the rain effect. Defaults to None. """ super(RainEffect, self).__init__(buffer, background) s = settings or RainSettings() self.image_present = False self.collision = s.collision self.intensity = s.intensity self.swells = s.swells self.swell_direction = 1 self.multiplier = 1 self.wind_direction = ( s.wind_direction if s.wind_direction in WIND_DIRECTIONS else "none" ) self.wind_mappings = { "east": [".\\", -1, ["\\", "."]], "none": [".|", 0, ["|", "."]], "west": ["./", 1, ["/", "."]], } self.lightning = s.lightning self.lightning_chance = s.lightning_chance self._bolts = [] # list of (cells, frames_remaining) self._set_rain()
[docs] def set_multiplier(self, val: int): """ Sets the scroll multiplier (controls shift amount per frame). Args: val (int): Multiplier value. """ self.multiplier = val
[docs] def set_wind_direction(self, direction: str): """ Sets the direction the rain falls. Args: direction (str): Wind direction ("none", "east", or "west"). """ if direction in WIND_DIRECTIONS: self.wind_direction = direction self._set_rain()
[docs] def set_intensity(self, intensity: int): """ Sets the intensity of the rain. Args: intensity (int): Intensity value between 0 and 999. """ if self.swells: if self.intensity == 900: self.swell_direction = -1 if self.intensity == 0: self.swell_direction = 1 self.intensity += self.swell_direction else: self.intensity = intensity if intensity < 1000 else 999 self._set_rain()
[docs] def set_swells(self, swells: bool): """ Enables or disables automatic intensity oscillation. Args: swells (bool): Whether the rain intensity should oscillate. """ self.swells = swells
[docs] def set_lightning(self, lightning: bool, chance: float = None): """ Enables or disables lightning strikes. Args: lightning (bool): Whether to show lightning. chance (float, optional): Per-frame probability of a new bolt (0.0–1.0). """ self.lightning = lightning if chance is not None: self.lightning_chance = chance
def _spawn_bolt(self): w = self.buffer.width() h = self.buffer.height() bolt_height = random.randint(h // 3, h - 2) x = random.randint(2, w - 3) cells = [] chars = ["|", "/", "\\"] for y in range(bolt_height): cells.append((x, y, random.choice(chars))) x = max(0, min(w - 1, x + random.choice([-1, 0, 0, 1]))) self._bolts.append([cells, random.randint(2, 4)]) def _set_rain(self): self.rain = f"{' ' * (1000 - self.intensity)}" if self.intensity > 50: self.rain += "." if self.intensity > 250: self.rain += "." if self.intensity > 500: self.rain += self.wind_mappings[self.wind_direction][0] self.rain_length = len(self.rain)
[docs] def update_collision( self, img_start_x: int, img_start_y: int, img_width: int, img_height: int, collision: bool, smart_transparent: bool = False, image_buffer: Buffer = None, ): """ Configures collision detection with an image. Args: img_start_x (int): Image x position on screen. img_start_y (int): Image y position on screen. img_width (int): Width of the image. img_height (int): Height of the image. collision (bool): Whether to enable collision. smart_transparent (bool, optional): Smart transparency flag. Defaults to False. image_buffer (Buffer, optional): Buffer containing the image. Defaults to None. """ self.image_present = ( True if img_start_x and img_start_y and img_width and img_height else False ) self.collision = collision if self.image_present: self.img_start_x = img_start_x self.img_start_y = img_start_y self.img_height = img_height self.img_width = img_width self.smart_transparent = smart_transparent self.image_buffer = image_buffer else: self.image_buffer = None
[docs] def render_frame(self, frame_number: int): """ Renders a single frame of the rain effect. Args: frame_number (int): The current frame number. """ if self.swells: self.set_intensity(None) if frame_number == 0: self.buffer.put_at( 0, 0, "".join( self.rain[random.randint(0, self.rain_length - 1)] for _ in range(self.buffer.width()) ), ) else: self.buffer.shift( self.wind_mappings[self.wind_direction][1] * self.multiplier ) self.buffer.scroll(-1 * self.multiplier) self.buffer.put_at( 0, 0, "".join( self.rain[random.randint(0, self.rain_length - 1)] for _ in range(self.buffer.width()) ), ) if self.collision: for y in range(self.buffer.height()): for x in range(self.buffer.width()): if self.buffer.get_char(x, y) == "v": self.buffer.put_char(x, y, " ") else: if self.image_present and self.image_buffer: if 0 <= y + 1 < self.buffer.height(): if ( self.image_buffer.buffer[y + 1][x] not in [" ", None] and self.buffer.get_char(x, y) in self.wind_mappings[self.wind_direction][2] ): self.buffer.put_char(x, y, "v") if y == self.buffer.height() - 1: if ( self.buffer.get_char(x, y) in self.wind_mappings[self.wind_direction][2] ): self.buffer.put_char(x, y, "v") if self.lightning: if random.random() < self.lightning_chance: self._spawn_bolt() next_bolts = [] for bolt, remaining in self._bolts: for bx, by, ch in bolt: self.buffer.put_char(bx, by, ch) if remaining > 1: next_bolts.append([bolt, remaining - 1]) self._bolts = next_bolts