Skip to content

Thunderstorm

Demo

Quick Start

thunderstorm.py
from terminaltexteffects.effects import Thunderstorm

effect = Thunderstorm("YourTextHere")
with effect.terminal_output() as terminal:
    for frame in effect:
        terminal.print(frame)

Create a Thunderstorm in the terminal.

Classes:

Name Description
- Thunderstorm

Effect class for the Thunderstorm effect.

- ThunderstormConfig

Configuration for the Thunderstorm effect.

- ThunderstormIterator

Iterates over the effect. Does not normally need to be called directly.

Thunderstorm

Bases: BaseEffect[ThunderstormConfig]

Create a thunderstorm in the terminal.

Rain falls across the canvas. Lightning strikes illuminate the scene and cause sparks at the point of impact. Characters struck by lightning glow.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
class Thunderstorm(BaseEffect[ThunderstormConfig]):
    """Create a thunderstorm in the terminal.

    Rain falls across the canvas. Lightning strikes illuminate the scene and
    cause sparks at the point of impact. Characters struck by lightning glow.
    """

    @property
    def _config_cls(self) -> type[ThunderstormConfig]:
        return ThunderstormConfig

    @property
    def _iterator_cls(self) -> type[ThunderstormIterator]:
        return ThunderstormIterator

ThunderstormConfig dataclass

Bases: BaseConfig

Effect configuration dataclass.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
@dataclass
class ThunderstormConfig(BaseConfig):
    """Effect configuration dataclass."""

    parser_spec: ParserSpec = ParserSpec(
        name="thunderstorm",
        help="Create a thunderstorm in the terminal.",
        description="thunderstorm | Create a thunderstorm in the terminal.",
        epilog=(
            "terminaltexteffects thunderstorm --lightning-color 68A3E8 "
            "--glowing-text-color EF5411 --text-glow-time 10 "
            "--raindrop-symbols '\\' '.' ',' --spark-symbols '*' '.' '`' "
            "--spark-glow-color ff4d00 --spark-glow-time 30 "
            "--storm-time 10 "
            "--final-gradient-stops 8A008A 00D1FF FFFFFF "
            "--final-gradient-steps 12 --final-gradient-frames 5 "
            "--final-gradient-direction vertical"
        ),
    )

    lightning_color: tte.Color = ArgSpec(
        name="--lightning-color",
        type=argutils.ColorArg.type_parser,
        default=tte.Color("#68A3E8"),
        metavar=argutils.ColorArg.METAVAR,
        help="Color for the lightning strike.",
    )  # pyright: ignore[reportAssignmentType]
    "Color: Color for the lightning strike."

    glowing_text_color: tte.Color = ArgSpec(
        name="--glowing-text-color",
        type=argutils.ColorArg.type_parser,
        default=tte.Color("#EF5411"),
        metavar=argutils.ColorArg.METAVAR,
        help="Color for the text when glowing after a lightning strike.",
    )  # pyright: ignore[reportAssignmentType]
    "Color: Color for the text when glowing after a lightning strike."

    text_glow_time: int = ArgSpec(
        name="--text-glow-time",
        type=argutils.PositiveInt.type_parser,
        default=6,
        metavar=argutils.PositiveInt.METAVAR,
        help="Duration, in number of frames, for the glowing/cooling animation for post-lightning text glow.",
    )  # pyright: ignore[reportAssignmentType]
    "int: Duration, in number of frames, for the glowing/cooling animation for post-lightning text glow."

    raindrop_symbols: tuple[str, ...] = ArgSpec(
        name="--raindrop-symbols",
        type=argutils.Symbol.type_parser,
        default=("\\", ".", ","),
        nargs="+",
        action=argutils.TupleAction,
        metavar=argutils.Symbol.METAVAR,
        help="Symbols to use for the raindrops.",
    )  # pyright: ignore[reportAssignmentType]
    "tuple[str, ...]: Symbols to use for the raindrops."

    spark_symbols: tuple[str, ...] = ArgSpec(
        name="--spark-symbols",
        type=argutils.Symbol.type_parser,
        default=("*", ".", "'"),
        nargs="+",
        action=argutils.TupleAction,
        metavar=argutils.Symbol.METAVAR,
        help="Symbols to use for the lightning impact sparks.",
    )  # pyright: ignore[reportAssignmentType]
    "tuple[str, ...]: Symbols to use for the lightning impact sparks."

    spark_glow_color: tte.Color = ArgSpec(
        name="--spark-glow-color",
        type=argutils.ColorArg.type_parser,
        default=tte.Color("#ff4d00"),
        metavar=argutils.ColorArg.METAVAR,
        help="Color for the spark glow after a lightning strike.",
    )  # pyright: ignore[reportAssignmentType]
    "Color: Color for the spark glow after a lightning strike."

    spark_glow_time: int = ArgSpec(
        name="--spark-glow-time",
        type=argutils.PositiveInt.type_parser,
        default=18,
        metavar=argutils.PositiveInt.METAVAR,
        help="Duration, in number of frames, for the cooling animation for post-lightning sparks.",
    )  # pyright: ignore[reportAssignmentType]
    "int: Duration, in number of frames, for the cooling animation for post-lightning sparks."

    storm_time: int = ArgSpec(
        name="--storm-time",
        type=argutils.PositiveInt.type_parser,
        default=12,
        metavar=argutils.PositiveInt.METAVAR,
        help="Duration, in seconds, the storm will occur.",
    )  # pyright: ignore[reportAssignmentType]
    "int: Duration, in seconds, the storm will occur."

    final_gradient_stops: tuple[tte.Color, ...] = ArgSpec(
        name="--final-gradient-stops",
        type=argutils.ColorArg.type_parser,
        nargs="+",
        action=argutils.TupleAction,
        default=(tte.Color("#8A008A"), tte.Color("#00D1FF"), tte.Color("#FFFFFF")),
        metavar=argutils.ColorArg.METAVAR,
        help=(
            "Space separated, unquoted, list of colors for the character gradient (applied across the canvas). "
            "If only one color is provided, the characters will be displayed in that color."
        ),
    )  # pyright: ignore[reportAssignmentType]
    (
        "tuple[Color, ...]: Space separated, unquoted, list of colors for the character gradient "
        "(applied across the canvas). If only one color is provided, the characters will be displayed in that color."
    )

    final_gradient_steps: tuple[int, ...] = ArgSpec(
        name="--final-gradient-steps",
        type=argutils.PositiveInt.type_parser,
        nargs="+",
        action=argutils.TupleAction,
        default=(12,),
        metavar=argutils.PositiveInt.METAVAR,
        help=(
            "Space separated, unquoted, list of the number of gradient steps to use. More steps will "
            "create a smoother and longer gradient animation."
        ),
    )  # pyright: ignore[reportAssignmentType]
    (
        "tuple[int, ...]: Space separated, unquoted, list of the number of gradient steps to use. More "
        "steps will create a smoother and longer gradient animation."
    )

    final_gradient_frames: int = ArgSpec(
        name="--final-gradient-frames",
        type=argutils.PositiveInt.type_parser,
        default=3,
        metavar=argutils.PositiveInt.METAVAR,
        help="Number of frames to display each gradient step. Increase to slow down the gradient animation.",
    )  # pyright: ignore[reportAssignmentType]
    "int: Number of frames to display each gradient step. Increase to slow down the gradient animation."

    final_gradient_direction: tte.Gradient.Direction = ArgSpec(
        name="--final-gradient-direction",
        type=argutils.GradientDirection.type_parser,
        default=tte.Gradient.Direction.VERTICAL,
        metavar=argutils.GradientDirection.METAVAR,
        help="Direction of the final gradient.",
    )  # pyright: ignore[reportAssignmentType]
    "Gradient.Direction : Direction of the final gradient."

final_gradient_direction = ArgSpec(name='--final-gradient-direction', type=(argutils.GradientDirection.type_parser), default=(tte.Gradient.Direction.VERTICAL), metavar=(argutils.GradientDirection.METAVAR), help='Direction of the final gradient.') class-attribute instance-attribute

Gradient.Direction : Direction of the final gradient.

final_gradient_frames = ArgSpec(name='--final-gradient-frames', type=(argutils.PositiveInt.type_parser), default=3, metavar=(argutils.PositiveInt.METAVAR), help='Number of frames to display each gradient step. Increase to slow down the gradient animation.') class-attribute instance-attribute

int: Number of frames to display each gradient step. Increase to slow down the gradient animation.

final_gradient_steps = ArgSpec(name='--final-gradient-steps', type=(argutils.PositiveInt.type_parser), nargs='+', action=(argutils.TupleAction), default=(12,), metavar=(argutils.PositiveInt.METAVAR), help='Space separated, unquoted, list of the number of gradient steps to use. More steps will create a smoother and longer gradient animation.') class-attribute instance-attribute

tuple[int, ...]: Space separated, unquoted, list of the number of gradient steps to use. More steps will create a smoother and longer gradient animation.

final_gradient_stops = ArgSpec(name='--final-gradient-stops', type=(argutils.ColorArg.type_parser), nargs='+', action=(argutils.TupleAction), default=(tte.Color('#8A008A'), tte.Color('#00D1FF'), tte.Color('#FFFFFF')), metavar=(argutils.ColorArg.METAVAR), help='Space separated, unquoted, list of colors for the character gradient (applied across the canvas). If only one color is provided, the characters will be displayed in that color.') class-attribute instance-attribute

tuple[Color, ...]: Space separated, unquoted, list of colors for the character gradient (applied across the canvas). If only one color is provided, the characters will be displayed in that color.

glowing_text_color = ArgSpec(name='--glowing-text-color', type=(argutils.ColorArg.type_parser), default=(tte.Color('#EF5411')), metavar=(argutils.ColorArg.METAVAR), help='Color for the text when glowing after a lightning strike.') class-attribute instance-attribute

Color: Color for the text when glowing after a lightning strike.

lightning_color = ArgSpec(name='--lightning-color', type=(argutils.ColorArg.type_parser), default=(tte.Color('#68A3E8')), metavar=(argutils.ColorArg.METAVAR), help='Color for the lightning strike.') class-attribute instance-attribute

Color: Color for the lightning strike.

raindrop_symbols = ArgSpec(name='--raindrop-symbols', type=(argutils.Symbol.type_parser), default=('\\', '.', ','), nargs='+', action=(argutils.TupleAction), metavar=(argutils.Symbol.METAVAR), help='Symbols to use for the raindrops.') class-attribute instance-attribute

tuple[str, ...]: Symbols to use for the raindrops.

spark_glow_color = ArgSpec(name='--spark-glow-color', type=(argutils.ColorArg.type_parser), default=(tte.Color('#ff4d00')), metavar=(argutils.ColorArg.METAVAR), help='Color for the spark glow after a lightning strike.') class-attribute instance-attribute

Color: Color for the spark glow after a lightning strike.

spark_glow_time = ArgSpec(name='--spark-glow-time', type=(argutils.PositiveInt.type_parser), default=18, metavar=(argutils.PositiveInt.METAVAR), help='Duration, in number of frames, for the cooling animation for post-lightning sparks.') class-attribute instance-attribute

int: Duration, in number of frames, for the cooling animation for post-lightning sparks.

spark_symbols = ArgSpec(name='--spark-symbols', type=(argutils.Symbol.type_parser), default=('*', '.', "'"), nargs='+', action=(argutils.TupleAction), metavar=(argutils.Symbol.METAVAR), help='Symbols to use for the lightning impact sparks.') class-attribute instance-attribute

tuple[str, ...]: Symbols to use for the lightning impact sparks.

storm_time = ArgSpec(name='--storm-time', type=(argutils.PositiveInt.type_parser), default=12, metavar=(argutils.PositiveInt.METAVAR), help='Duration, in seconds, the storm will occur.') class-attribute instance-attribute

int: Duration, in seconds, the storm will occur.

text_glow_time = ArgSpec(name='--text-glow-time', type=(argutils.PositiveInt.type_parser), default=6, metavar=(argutils.PositiveInt.METAVAR), help='Duration, in number of frames, for the glowing/cooling animation for post-lightning text glow.') class-attribute instance-attribute

int: Duration, in number of frames, for the glowing/cooling animation for post-lightning text glow.

ThunderstormIterator

Bases: BaseEffectIterator[ThunderstormConfig]

Effect iterator for the NamedEffect effect.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
class ThunderstormIterator(BaseEffectIterator[ThunderstormConfig]):
    """Effect iterator for the NamedEffect effect."""

    def __init__(self, effect: Thunderstorm) -> None:
        """Initialize the effect iterator.

        Args:
            effect (NamedEffect): The effect to iterate over.

        """
        super().__init__(effect)
        self.character_final_color_map: dict[tte.EffectCharacter, tte.Color] = {}
        self.delay = 0
        self.strike_progression_delay = 0
        self.rain_drops: list[tte.EffectCharacter] = []
        self.pending_strike_chars: list[EffectCharacter] = []
        self.available_strike_chars: list[EffectCharacter] = []
        self.active_strike_chars: list[EffectCharacter] = []
        self.pending_sparks: list[EffectCharacter] = []
        self.available_sparks: list[EffectCharacter] = []
        self.pending_glow_chars: list[EffectCharacter] = []
        self.strike_in_progress: bool = False
        self.flashing: bool = False
        self.strike_branch_chance = 0.05
        self.phase: str = "pre-storm"
        self.storm_start_time = time.monotonic()
        self.build()

    def build(self) -> None:
        """Build the effect."""
        final_gradient = tte.Gradient(*self.config.final_gradient_stops, steps=self.config.final_gradient_steps)
        final_gradient_mapping = final_gradient.build_coordinate_color_mapping(
            self.terminal.canvas.text_bottom,
            self.terminal.canvas.text_top,
            self.terminal.canvas.text_left,
            self.terminal.canvas.text_right,
            self.config.final_gradient_direction,
        )
        self.build_raindrop_characters()
        self.build_spark_characters()
        self.build_strike_characters()

        # setup scenes on text characters
        all_chars = self.terminal.get_characters()
        for text_char in all_chars:
            faded_color = tte.Animation.adjust_color_brightness(
                final_gradient_mapping[text_char.input_coord],
                brightness=0.5,
            )
            # post-strike glow and cool scene
            glow_gradient = tte.Gradient(self.config.glowing_text_color, faded_color, steps=7)
            glow_scn = text_char.animation.new_scene(scene_id="glow")
            for color in glow_gradient:
                glow_scn.add_frame(symbol=text_char.input_symbol, colors=tte.ColorPair(fg=color), duration=6)

            # fade before storm scene
            fade_gradient = tte.Gradient(final_gradient_mapping[text_char.input_coord], faded_color, steps=7)
            fade_scn = text_char.animation.new_scene(scene_id="fade")
            for color in fade_gradient:
                fade_scn.add_frame(symbol=text_char.input_symbol, colors=tte.ColorPair(fg=color), duration=12)

            unfade_gradient = list(fade_gradient)[::-1]
            unfade_scn = text_char.animation.new_scene(scene_id="unfade")
            for color in unfade_gradient:
                unfade_scn.add_frame(symbol=text_char.input_symbol, colors=tte.ColorPair(fg=color), duration=12)

            # lightning flash scene
            lightning_flash_color = tte.Animation.adjust_color_brightness(
                final_gradient_mapping[text_char.input_coord],
                brightness=1.7,
            )
            strike_scn = text_char.animation.new_scene(scene_id="flash")
            flash_gradient = tte.Gradient(faded_color, lightning_flash_color, steps=7, loop=True)
            for color in flash_gradient:
                strike_scn.add_frame(symbol=text_char.input_symbol, colors=tte.ColorPair(fg=color), duration=6)

            self.terminal.set_character_visibility(text_char, is_visible=True)

        # setup a reference character callback to indicate when the pre-storm fade has completed
        def fade_complete(*_: typing.Any) -> None:
            self.phase = "storm"
            self.storm_start_time = time.monotonic()

        reference_char = all_chars[0]
        reference_char.event_handler.register_event(
            event=tte.Event.SCENE_COMPLETE,
            caller="fade",
            action=tte.Action.CALLBACK,
            target=tte.EventHandler.Callback(fade_complete),
        )

    def set_strike_in_progress_false(self, *_: typing.Any) -> None:
        """Reset the strike in progress flag."""
        self.strike_in_progress = False

    def make_char_glow(self, strike_char: tte.EffectCharacter) -> None:
        """Activate the 'cool' scene on any text character behind a strike character.

        Args:
            strike_char (tte.EffectCharacter): Strike character.

        """
        input_char = self.terminal.get_character_by_input_coord(strike_char.motion.current_coord)
        if input_char and input_char.is_visible:
            input_char.animation.activate_scene("glow")
            self.pending_glow_chars.append(input_char)

    def get_next_strike_char(self) -> tte.EffectCharacter:
        """Get the next available strike character.

        If no characters are available, new ones will be created.

        Returns:
            tte.EffectCharacter: The next available strike character.

        """
        if not self.available_strike_chars:
            self.build_strike_characters(20)
        strike_char = self.available_strike_chars.pop()
        strike_char.animation.scenes.clear()
        strike_char.event_handler.registered_events.clear()
        return strike_char

    def get_next_spark_char(self) -> tte.EffectCharacter:
        """Get the next available spark character.

        If no characters are available, new ones will be created.

        Returns:
            tte.EffectCharacter: The next available spark character.

        """
        if not self.available_sparks:
            self.build_spark_characters(20)
        spark_char = self.available_sparks.pop()
        spark_char.motion.paths.clear()
        spark_char.event_handler.registered_events.clear()
        return spark_char

    def setup_sparks_for_impact(self) -> None:
        """Configure sparks for the impact of a lightning strike."""
        # setup sparks at lightning strike bottom impact
        last_strike_char = self.pending_strike_chars[-1]
        for _ in range(random.randint(6, 10)):
            spark_char = self.get_next_spark_char()
            spark_char.motion.set_coordinate(last_strike_char.motion.current_coord)

            spark_path = spark_char.motion.new_path(
                speed=random.uniform(0.1, 0.25),
                ease=tte.easing.out_quint,
                hold_time=30,
            )
            spark_target = tte.Coord(
                column=last_strike_char.motion.current_coord.column + random.randint(4, 20) * random.choice((1, -1)),
                row=self.terminal.canvas.bottom,
            )
            bezier_column = last_strike_char.motion.current_coord.column - (
                (last_strike_char.motion.current_coord.column - spark_target.column) // 2
            )
            spark_path.new_waypoint(
                coord=spark_target,
                bezier_control=tte.Coord(column=bezier_column, row=random.randint(1, self.terminal.canvas.top)),
            )
            spark_char.event_handler.register_event(
                event=tte.Event.PATH_COMPLETE,
                caller=spark_path,
                action=tte.Action.CALLBACK,
                target=tte.EventHandler.Callback(lambda c: self.terminal.set_character_visibility(c, is_visible=False)),
            )
            spark_char.event_handler.register_event(
                event=tte.Event.PATH_COMPLETE,
                caller=spark_path,
                action=tte.Action.CALLBACK,
                target=tte.EventHandler.Callback(lambda c: self.available_sparks.append(c)),
            )

            spark_char.animation.activate_scene("glow")
            spark_char.motion.activate_path(spark_path)
            self.pending_sparks.append(spark_char)

    def setup_lightning_strike(self, branch_neighbor: tte.EffectCharacter | None = None) -> None:
        """Build a lightning strike effect."""
        if branch_neighbor is not None:
            column, row = branch_neighbor.motion.current_coord
            row = branch_neighbor.motion.current_coord.row
        else:
            column = random.randint(1, self.terminal.canvas.right)
            row = self.terminal.canvas.top

        while row >= self.terminal.canvas.bottom:
            if not self.available_strike_chars:
                self.build_strike_characters(20)
            if branch_neighbor is not None:
                if branch_neighbor.input_symbol == "/":
                    column += 1
                    symbol = random.choice(("|", "\\"))
                elif branch_neighbor.input_symbol == "\\":
                    column -= 1
                    symbol = random.choice(("|", "/"))
                else:
                    delta = random.choice((-1, 1))
                    column += delta
                    symbol = "\\" if delta == 1 else "/"
            else:
                symbol = random.choice(("\\", "/", "|"))

            strike_char = self.get_next_strike_char()
            strike_char.motion.set_coordinate(tte.Coord(column, row))
            strike_char.animation.set_appearance(symbol=symbol, colors=tte.ColorPair(fg=self.config.lightning_color))
            row -= 1
            if symbol == "\\":
                column += 1
            elif symbol == "/":
                column -= 1

            self.pending_strike_chars.append(strike_char)
            if random.random() < self.strike_branch_chance and branch_neighbor is None:
                self.strike_branch_chance -= 0.01
                self.setup_lightning_strike(branch_neighbor=strike_char)
            branch_neighbor = None
        self.strike_branch_chance = 0.05
        self.setup_sparks_for_impact()

    def build_raindrop_characters(self, count: int = 50) -> None:
        """Build raindrop characters."""
        for _ in range(count):
            spawn_column = random.randint(1 - self.terminal.canvas.top, self.terminal.canvas.right)
            rain_char = self.terminal.add_character(
                symbol=random.choice(self.config.raindrop_symbols),
                coord=tte.Coord(column=spawn_column - 1, row=self.terminal.canvas.top + 1),
            )
            rain_char.layer = 1
            rain_char.animation.set_appearance(
                symbol=rain_char.input_symbol,
                colors=tte.ColorPair(fg=tte.Color("#aaaaff")),
            )
            fall_path = rain_char.motion.new_path(path_id="fall", speed=1)
            fall_path.new_waypoint(
                tte.Coord(column=spawn_column + self.terminal.canvas.top, row=self.terminal.canvas.bottom - 1),
            )
            rain_char.motion.activate_path(fall_path)

            rain_char.event_handler.register_event(
                tte.Event.PATH_COMPLETE,
                fall_path,
                tte.Action.CALLBACK,
                rain_char.event_handler.Callback(lambda c: self.rain_drops.append(c)),
            )

            self.terminal.set_character_visibility(rain_char, is_visible=True)
            self.rain_drops.append(rain_char)

    def build_spark_characters(self, count: int = 100) -> None:
        """Build spark characters for the lightning strike effect."""
        spark_gradient = tte.Gradient(
            self.config.spark_glow_color,
            self.terminal.config.terminal_background_color,
            steps=7,
        )
        for _ in range(count):
            spark = self.terminal.add_character(
                symbol=random.choice(self.config.spark_symbols),
                coord=tte.Coord(1, 1),
            )
            spark.layer = 2

            spark_scn = spark.animation.new_scene(scene_id="glow", ease=tte.easing.in_circ)
            for color in spark_gradient:
                spark_scn.add_frame(
                    symbol=spark.input_symbol,
                    colors=tte.ColorPair(fg=color),
                    duration=self.config.spark_glow_time,
                )
            self.available_sparks.append(spark)

    def build_strike_characters(self, count: int = 200) -> None:
        """Build strike characters for the lightning strike effect."""
        for _ in range(count):
            strike_char = self.terminal.add_character(
                symbol="|",
                coord=tte.Coord(1, 1),
            )
            self.available_strike_chars.append(strike_char)

    def lightning_strike(self) -> None:
        """Trigger a lightning strike effect."""
        self.setup_lightning_strike()
        strike_base_color = self.config.lightning_color
        strike_flash_color = tte.Animation.adjust_color_brightness(strike_base_color, 1.7)
        strike_gradient = tte.Gradient(
            strike_base_color,
            strike_flash_color,
            steps=7,
            loop=True,
        )
        fade_gradient = tte.Gradient(strike_base_color, self.terminal.config.terminal_background_color, steps=6)
        layer = 1
        flash_ease = tte.easing.make_easing(0, 1.6, 1, random.uniform(-0.6, 0.4))
        for strike_char in self.pending_strike_chars:
            flash_scn = strike_char.animation.new_scene(scene_id="flash", ease=flash_ease)
            for color in strike_gradient:
                flash_scn.add_frame(
                    symbol=strike_char.animation.current_character_visual.symbol,
                    colors=tte.ColorPair(fg=color),
                    duration=6,
                )
            fade_scn = strike_char.animation.new_scene(scene_id="fade")
            for color in fade_gradient:
                fade_scn.add_frame(
                    symbol=strike_char.animation.current_character_visual.symbol,
                    colors=tte.ColorPair(fg=color),
                    duration=2,
                )
            strike_char.layer = layer
            strike_char.event_handler.register_event(
                event=tte.Event.SCENE_COMPLETE,
                caller=flash_scn,
                action=tte.Action.ACTIVATE_SCENE,
                target=fade_scn,
            )
            strike_char.event_handler.register_event(
                event=tte.Event.SCENE_COMPLETE,
                caller=fade_scn,
                action=tte.Action.CALLBACK,
                target=tte.EventHandler.Callback(lambda c: self.terminal.set_character_visibility(c, is_visible=False)),
            )
            strike_char.event_handler.register_event(
                event=tte.Event.SCENE_COMPLETE,
                caller=fade_scn,
                action=tte.Action.CALLBACK,
                target=tte.EventHandler.Callback(self.make_char_glow),
            )
            strike_char.event_handler.register_event(
                event=tte.Event.SCENE_COMPLETE,
                caller=fade_scn,
                action=tte.Action.CALLBACK,
                target=tte.EventHandler.Callback(lambda c: self.available_strike_chars.append(c)),
            )

        for text_char in self.terminal.get_characters():
            flash_scene = text_char.animation.query_scene("flash")
            flash_scene.ease = flash_ease  # pyright: ignore[reportOptionalMemberAccess]

    def step_lightning_strike(self) -> None:
        """Progress the lightning strike effect."""
        if self.strike_progression_delay:
            self.strike_progression_delay -= 1
            return
        if self.pending_strike_chars:
            for _ in range(random.randint(1, 3)):
                if not self.pending_strike_chars:
                    break
                next_strike_char = self.pending_strike_chars.pop(0)
                self.active_strike_chars.append(next_strike_char)
                self.terminal.set_character_visibility(next_strike_char, is_visible=True)
                self.strike_progression_delay = 1

                # if the last strike_char was activated, activate the sparks
                # and setup the post-fade callback to indicate the strike has
                # ended
                if not self.pending_strike_chars:
                    while self.pending_sparks:
                        spark = self.pending_sparks.pop()
                        self.terminal.set_character_visibility(spark, is_visible=True)
                        self.active_characters.add(spark)
                    next_strike_char.event_handler.register_event(
                        event=tte.Event.SCENE_COMPLETE,
                        caller="fade",
                        action=tte.Action.CALLBACK,
                        target=tte.EventHandler.Callback(self.set_strike_in_progress_false),
                    )

                    # activate the flash scene on all strike chars and text
                    for strike_char in self.active_strike_chars:
                        strike_char.animation.activate_scene("flash")
                        self.active_characters.add(strike_char)
                    self.active_strike_chars.clear()

                    for text_char in self.terminal.get_characters():
                        text_char.animation.activate_scene("flash")
                        self.active_characters.add(text_char)

    def rain(self) -> None:
        """Handle the rain effect."""
        if self.rain_drops:
            if not self.delay:
                for _ in range(random.randint(1, 6)):
                    if not self.rain_drops:
                        self.build_raindrop_characters(20)
                    drop = self.rain_drops.pop(random.randint(0, len(self.rain_drops) - 1))
                    drop.motion.set_coordinate(drop.input_coord)
                    fall_path = drop.motion.query_path("fall")
                    fall_path.speed = random.uniform(0.5, 1.5)
                    drop.motion.activate_path(fall_path)
                    self.active_characters.add(drop)
                self.delay = random.randint(1, 7)
            else:
                self.delay -= 1

    def pre_storm_text_fade(self) -> None:
        """Activate the fade effect for all text characters before the storm."""
        for char in self.terminal.get_characters():
            char.animation.activate_scene("fade")
            self.active_characters.add(char)

    def post_storm_text_fade_in(self) -> None:
        """Active the fade in scene for all text characters after the storm clears."""
        for char in self.terminal.get_characters():
            char.animation.activate_scene("unfade")
            self.active_characters.add(char)

    def __next__(self) -> str:
        """Return the next frame of the effect."""
        if self.active_characters or self.phase != "complete":
            if self.phase == "pre-storm":
                self.pre_storm_text_fade()
                self.phase = "waiting"
            elif self.phase == "storm":
                self.rain()
                if not self.strike_in_progress and random.random() < 0.008:
                    self.strike_in_progress = True
                    self.lightning_strike()
                if self.strike_in_progress:
                    self.step_lightning_strike()

                for char in self.pending_glow_chars:
                    self.active_characters.add(char)
                self.pending_glow_chars.clear()
                if (time.monotonic() - self.storm_start_time) >= self.config.storm_time and not self.strike_in_progress:
                    self.post_storm_text_fade_in()
                    self.phase = "complete"
            self.update()
            return self.frame
        raise StopIteration

__init__(effect)

Initialize the effect iterator.

Parameters:

Name Type Description Default
effect NamedEffect

The effect to iterate over.

required
Source code in terminaltexteffects/effects/effect_thunderstorm.py
def __init__(self, effect: Thunderstorm) -> None:
    """Initialize the effect iterator.

    Args:
        effect (NamedEffect): The effect to iterate over.

    """
    super().__init__(effect)
    self.character_final_color_map: dict[tte.EffectCharacter, tte.Color] = {}
    self.delay = 0
    self.strike_progression_delay = 0
    self.rain_drops: list[tte.EffectCharacter] = []
    self.pending_strike_chars: list[EffectCharacter] = []
    self.available_strike_chars: list[EffectCharacter] = []
    self.active_strike_chars: list[EffectCharacter] = []
    self.pending_sparks: list[EffectCharacter] = []
    self.available_sparks: list[EffectCharacter] = []
    self.pending_glow_chars: list[EffectCharacter] = []
    self.strike_in_progress: bool = False
    self.flashing: bool = False
    self.strike_branch_chance = 0.05
    self.phase: str = "pre-storm"
    self.storm_start_time = time.monotonic()
    self.build()

__next__()

Return the next frame of the effect.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
def __next__(self) -> str:
    """Return the next frame of the effect."""
    if self.active_characters or self.phase != "complete":
        if self.phase == "pre-storm":
            self.pre_storm_text_fade()
            self.phase = "waiting"
        elif self.phase == "storm":
            self.rain()
            if not self.strike_in_progress and random.random() < 0.008:
                self.strike_in_progress = True
                self.lightning_strike()
            if self.strike_in_progress:
                self.step_lightning_strike()

            for char in self.pending_glow_chars:
                self.active_characters.add(char)
            self.pending_glow_chars.clear()
            if (time.monotonic() - self.storm_start_time) >= self.config.storm_time and not self.strike_in_progress:
                self.post_storm_text_fade_in()
                self.phase = "complete"
        self.update()
        return self.frame
    raise StopIteration

build()

Build the effect.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
def build(self) -> None:
    """Build the effect."""
    final_gradient = tte.Gradient(*self.config.final_gradient_stops, steps=self.config.final_gradient_steps)
    final_gradient_mapping = final_gradient.build_coordinate_color_mapping(
        self.terminal.canvas.text_bottom,
        self.terminal.canvas.text_top,
        self.terminal.canvas.text_left,
        self.terminal.canvas.text_right,
        self.config.final_gradient_direction,
    )
    self.build_raindrop_characters()
    self.build_spark_characters()
    self.build_strike_characters()

    # setup scenes on text characters
    all_chars = self.terminal.get_characters()
    for text_char in all_chars:
        faded_color = tte.Animation.adjust_color_brightness(
            final_gradient_mapping[text_char.input_coord],
            brightness=0.5,
        )
        # post-strike glow and cool scene
        glow_gradient = tte.Gradient(self.config.glowing_text_color, faded_color, steps=7)
        glow_scn = text_char.animation.new_scene(scene_id="glow")
        for color in glow_gradient:
            glow_scn.add_frame(symbol=text_char.input_symbol, colors=tte.ColorPair(fg=color), duration=6)

        # fade before storm scene
        fade_gradient = tte.Gradient(final_gradient_mapping[text_char.input_coord], faded_color, steps=7)
        fade_scn = text_char.animation.new_scene(scene_id="fade")
        for color in fade_gradient:
            fade_scn.add_frame(symbol=text_char.input_symbol, colors=tte.ColorPair(fg=color), duration=12)

        unfade_gradient = list(fade_gradient)[::-1]
        unfade_scn = text_char.animation.new_scene(scene_id="unfade")
        for color in unfade_gradient:
            unfade_scn.add_frame(symbol=text_char.input_symbol, colors=tte.ColorPair(fg=color), duration=12)

        # lightning flash scene
        lightning_flash_color = tte.Animation.adjust_color_brightness(
            final_gradient_mapping[text_char.input_coord],
            brightness=1.7,
        )
        strike_scn = text_char.animation.new_scene(scene_id="flash")
        flash_gradient = tte.Gradient(faded_color, lightning_flash_color, steps=7, loop=True)
        for color in flash_gradient:
            strike_scn.add_frame(symbol=text_char.input_symbol, colors=tte.ColorPair(fg=color), duration=6)

        self.terminal.set_character_visibility(text_char, is_visible=True)

    # setup a reference character callback to indicate when the pre-storm fade has completed
    def fade_complete(*_: typing.Any) -> None:
        self.phase = "storm"
        self.storm_start_time = time.monotonic()

    reference_char = all_chars[0]
    reference_char.event_handler.register_event(
        event=tte.Event.SCENE_COMPLETE,
        caller="fade",
        action=tte.Action.CALLBACK,
        target=tte.EventHandler.Callback(fade_complete),
    )

build_raindrop_characters(count=50)

Build raindrop characters.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
def build_raindrop_characters(self, count: int = 50) -> None:
    """Build raindrop characters."""
    for _ in range(count):
        spawn_column = random.randint(1 - self.terminal.canvas.top, self.terminal.canvas.right)
        rain_char = self.terminal.add_character(
            symbol=random.choice(self.config.raindrop_symbols),
            coord=tte.Coord(column=spawn_column - 1, row=self.terminal.canvas.top + 1),
        )
        rain_char.layer = 1
        rain_char.animation.set_appearance(
            symbol=rain_char.input_symbol,
            colors=tte.ColorPair(fg=tte.Color("#aaaaff")),
        )
        fall_path = rain_char.motion.new_path(path_id="fall", speed=1)
        fall_path.new_waypoint(
            tte.Coord(column=spawn_column + self.terminal.canvas.top, row=self.terminal.canvas.bottom - 1),
        )
        rain_char.motion.activate_path(fall_path)

        rain_char.event_handler.register_event(
            tte.Event.PATH_COMPLETE,
            fall_path,
            tte.Action.CALLBACK,
            rain_char.event_handler.Callback(lambda c: self.rain_drops.append(c)),
        )

        self.terminal.set_character_visibility(rain_char, is_visible=True)
        self.rain_drops.append(rain_char)

build_spark_characters(count=100)

Build spark characters for the lightning strike effect.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
def build_spark_characters(self, count: int = 100) -> None:
    """Build spark characters for the lightning strike effect."""
    spark_gradient = tte.Gradient(
        self.config.spark_glow_color,
        self.terminal.config.terminal_background_color,
        steps=7,
    )
    for _ in range(count):
        spark = self.terminal.add_character(
            symbol=random.choice(self.config.spark_symbols),
            coord=tte.Coord(1, 1),
        )
        spark.layer = 2

        spark_scn = spark.animation.new_scene(scene_id="glow", ease=tte.easing.in_circ)
        for color in spark_gradient:
            spark_scn.add_frame(
                symbol=spark.input_symbol,
                colors=tte.ColorPair(fg=color),
                duration=self.config.spark_glow_time,
            )
        self.available_sparks.append(spark)

build_strike_characters(count=200)

Build strike characters for the lightning strike effect.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
def build_strike_characters(self, count: int = 200) -> None:
    """Build strike characters for the lightning strike effect."""
    for _ in range(count):
        strike_char = self.terminal.add_character(
            symbol="|",
            coord=tte.Coord(1, 1),
        )
        self.available_strike_chars.append(strike_char)

get_next_spark_char()

Get the next available spark character.

If no characters are available, new ones will be created.

Returns:

Type Description
EffectCharacter

tte.EffectCharacter: The next available spark character.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
def get_next_spark_char(self) -> tte.EffectCharacter:
    """Get the next available spark character.

    If no characters are available, new ones will be created.

    Returns:
        tte.EffectCharacter: The next available spark character.

    """
    if not self.available_sparks:
        self.build_spark_characters(20)
    spark_char = self.available_sparks.pop()
    spark_char.motion.paths.clear()
    spark_char.event_handler.registered_events.clear()
    return spark_char

get_next_strike_char()

Get the next available strike character.

If no characters are available, new ones will be created.

Returns:

Type Description
EffectCharacter

tte.EffectCharacter: The next available strike character.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
def get_next_strike_char(self) -> tte.EffectCharacter:
    """Get the next available strike character.

    If no characters are available, new ones will be created.

    Returns:
        tte.EffectCharacter: The next available strike character.

    """
    if not self.available_strike_chars:
        self.build_strike_characters(20)
    strike_char = self.available_strike_chars.pop()
    strike_char.animation.scenes.clear()
    strike_char.event_handler.registered_events.clear()
    return strike_char

lightning_strike()

Trigger a lightning strike effect.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
def lightning_strike(self) -> None:
    """Trigger a lightning strike effect."""
    self.setup_lightning_strike()
    strike_base_color = self.config.lightning_color
    strike_flash_color = tte.Animation.adjust_color_brightness(strike_base_color, 1.7)
    strike_gradient = tte.Gradient(
        strike_base_color,
        strike_flash_color,
        steps=7,
        loop=True,
    )
    fade_gradient = tte.Gradient(strike_base_color, self.terminal.config.terminal_background_color, steps=6)
    layer = 1
    flash_ease = tte.easing.make_easing(0, 1.6, 1, random.uniform(-0.6, 0.4))
    for strike_char in self.pending_strike_chars:
        flash_scn = strike_char.animation.new_scene(scene_id="flash", ease=flash_ease)
        for color in strike_gradient:
            flash_scn.add_frame(
                symbol=strike_char.animation.current_character_visual.symbol,
                colors=tte.ColorPair(fg=color),
                duration=6,
            )
        fade_scn = strike_char.animation.new_scene(scene_id="fade")
        for color in fade_gradient:
            fade_scn.add_frame(
                symbol=strike_char.animation.current_character_visual.symbol,
                colors=tte.ColorPair(fg=color),
                duration=2,
            )
        strike_char.layer = layer
        strike_char.event_handler.register_event(
            event=tte.Event.SCENE_COMPLETE,
            caller=flash_scn,
            action=tte.Action.ACTIVATE_SCENE,
            target=fade_scn,
        )
        strike_char.event_handler.register_event(
            event=tte.Event.SCENE_COMPLETE,
            caller=fade_scn,
            action=tte.Action.CALLBACK,
            target=tte.EventHandler.Callback(lambda c: self.terminal.set_character_visibility(c, is_visible=False)),
        )
        strike_char.event_handler.register_event(
            event=tte.Event.SCENE_COMPLETE,
            caller=fade_scn,
            action=tte.Action.CALLBACK,
            target=tte.EventHandler.Callback(self.make_char_glow),
        )
        strike_char.event_handler.register_event(
            event=tte.Event.SCENE_COMPLETE,
            caller=fade_scn,
            action=tte.Action.CALLBACK,
            target=tte.EventHandler.Callback(lambda c: self.available_strike_chars.append(c)),
        )

    for text_char in self.terminal.get_characters():
        flash_scene = text_char.animation.query_scene("flash")
        flash_scene.ease = flash_ease  # pyright: ignore[reportOptionalMemberAccess]

make_char_glow(strike_char)

Activate the 'cool' scene on any text character behind a strike character.

Parameters:

Name Type Description Default
strike_char EffectCharacter

Strike character.

required
Source code in terminaltexteffects/effects/effect_thunderstorm.py
def make_char_glow(self, strike_char: tte.EffectCharacter) -> None:
    """Activate the 'cool' scene on any text character behind a strike character.

    Args:
        strike_char (tte.EffectCharacter): Strike character.

    """
    input_char = self.terminal.get_character_by_input_coord(strike_char.motion.current_coord)
    if input_char and input_char.is_visible:
        input_char.animation.activate_scene("glow")
        self.pending_glow_chars.append(input_char)

post_storm_text_fade_in()

Active the fade in scene for all text characters after the storm clears.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
def post_storm_text_fade_in(self) -> None:
    """Active the fade in scene for all text characters after the storm clears."""
    for char in self.terminal.get_characters():
        char.animation.activate_scene("unfade")
        self.active_characters.add(char)

pre_storm_text_fade()

Activate the fade effect for all text characters before the storm.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
def pre_storm_text_fade(self) -> None:
    """Activate the fade effect for all text characters before the storm."""
    for char in self.terminal.get_characters():
        char.animation.activate_scene("fade")
        self.active_characters.add(char)

rain()

Handle the rain effect.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
def rain(self) -> None:
    """Handle the rain effect."""
    if self.rain_drops:
        if not self.delay:
            for _ in range(random.randint(1, 6)):
                if not self.rain_drops:
                    self.build_raindrop_characters(20)
                drop = self.rain_drops.pop(random.randint(0, len(self.rain_drops) - 1))
                drop.motion.set_coordinate(drop.input_coord)
                fall_path = drop.motion.query_path("fall")
                fall_path.speed = random.uniform(0.5, 1.5)
                drop.motion.activate_path(fall_path)
                self.active_characters.add(drop)
            self.delay = random.randint(1, 7)
        else:
            self.delay -= 1

set_strike_in_progress_false(*_)

Reset the strike in progress flag.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
def set_strike_in_progress_false(self, *_: typing.Any) -> None:
    """Reset the strike in progress flag."""
    self.strike_in_progress = False

setup_lightning_strike(branch_neighbor=None)

Build a lightning strike effect.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
def setup_lightning_strike(self, branch_neighbor: tte.EffectCharacter | None = None) -> None:
    """Build a lightning strike effect."""
    if branch_neighbor is not None:
        column, row = branch_neighbor.motion.current_coord
        row = branch_neighbor.motion.current_coord.row
    else:
        column = random.randint(1, self.terminal.canvas.right)
        row = self.terminal.canvas.top

    while row >= self.terminal.canvas.bottom:
        if not self.available_strike_chars:
            self.build_strike_characters(20)
        if branch_neighbor is not None:
            if branch_neighbor.input_symbol == "/":
                column += 1
                symbol = random.choice(("|", "\\"))
            elif branch_neighbor.input_symbol == "\\":
                column -= 1
                symbol = random.choice(("|", "/"))
            else:
                delta = random.choice((-1, 1))
                column += delta
                symbol = "\\" if delta == 1 else "/"
        else:
            symbol = random.choice(("\\", "/", "|"))

        strike_char = self.get_next_strike_char()
        strike_char.motion.set_coordinate(tte.Coord(column, row))
        strike_char.animation.set_appearance(symbol=symbol, colors=tte.ColorPair(fg=self.config.lightning_color))
        row -= 1
        if symbol == "\\":
            column += 1
        elif symbol == "/":
            column -= 1

        self.pending_strike_chars.append(strike_char)
        if random.random() < self.strike_branch_chance and branch_neighbor is None:
            self.strike_branch_chance -= 0.01
            self.setup_lightning_strike(branch_neighbor=strike_char)
        branch_neighbor = None
    self.strike_branch_chance = 0.05
    self.setup_sparks_for_impact()

setup_sparks_for_impact()

Configure sparks for the impact of a lightning strike.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
def setup_sparks_for_impact(self) -> None:
    """Configure sparks for the impact of a lightning strike."""
    # setup sparks at lightning strike bottom impact
    last_strike_char = self.pending_strike_chars[-1]
    for _ in range(random.randint(6, 10)):
        spark_char = self.get_next_spark_char()
        spark_char.motion.set_coordinate(last_strike_char.motion.current_coord)

        spark_path = spark_char.motion.new_path(
            speed=random.uniform(0.1, 0.25),
            ease=tte.easing.out_quint,
            hold_time=30,
        )
        spark_target = tte.Coord(
            column=last_strike_char.motion.current_coord.column + random.randint(4, 20) * random.choice((1, -1)),
            row=self.terminal.canvas.bottom,
        )
        bezier_column = last_strike_char.motion.current_coord.column - (
            (last_strike_char.motion.current_coord.column - spark_target.column) // 2
        )
        spark_path.new_waypoint(
            coord=spark_target,
            bezier_control=tte.Coord(column=bezier_column, row=random.randint(1, self.terminal.canvas.top)),
        )
        spark_char.event_handler.register_event(
            event=tte.Event.PATH_COMPLETE,
            caller=spark_path,
            action=tte.Action.CALLBACK,
            target=tte.EventHandler.Callback(lambda c: self.terminal.set_character_visibility(c, is_visible=False)),
        )
        spark_char.event_handler.register_event(
            event=tte.Event.PATH_COMPLETE,
            caller=spark_path,
            action=tte.Action.CALLBACK,
            target=tte.EventHandler.Callback(lambda c: self.available_sparks.append(c)),
        )

        spark_char.animation.activate_scene("glow")
        spark_char.motion.activate_path(spark_path)
        self.pending_sparks.append(spark_char)

step_lightning_strike()

Progress the lightning strike effect.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
def step_lightning_strike(self) -> None:
    """Progress the lightning strike effect."""
    if self.strike_progression_delay:
        self.strike_progression_delay -= 1
        return
    if self.pending_strike_chars:
        for _ in range(random.randint(1, 3)):
            if not self.pending_strike_chars:
                break
            next_strike_char = self.pending_strike_chars.pop(0)
            self.active_strike_chars.append(next_strike_char)
            self.terminal.set_character_visibility(next_strike_char, is_visible=True)
            self.strike_progression_delay = 1

            # if the last strike_char was activated, activate the sparks
            # and setup the post-fade callback to indicate the strike has
            # ended
            if not self.pending_strike_chars:
                while self.pending_sparks:
                    spark = self.pending_sparks.pop()
                    self.terminal.set_character_visibility(spark, is_visible=True)
                    self.active_characters.add(spark)
                next_strike_char.event_handler.register_event(
                    event=tte.Event.SCENE_COMPLETE,
                    caller="fade",
                    action=tte.Action.CALLBACK,
                    target=tte.EventHandler.Callback(self.set_strike_in_progress_false),
                )

                # activate the flash scene on all strike chars and text
                for strike_char in self.active_strike_chars:
                    strike_char.animation.activate_scene("flash")
                    self.active_characters.add(strike_char)
                self.active_strike_chars.clear()

                for text_char in self.terminal.get_characters():
                    text_char.animation.activate_scene("flash")
                    self.active_characters.add(text_char)

get_effect_resources()

Get the command, effect class, and configuration class for the effect.

Returns:

Type Description
tuple[str, type[BaseEffect], type[BaseConfig]]

tuple[str, type[BaseEffect], type[BaseConfig]]: The command name, effect class, and configuration class.

Source code in terminaltexteffects/effects/effect_thunderstorm.py
def get_effect_resources() -> tuple[str, type[BaseEffect], type[BaseConfig]]:
    """Get the command, effect class, and configuration class for the effect.

    Returns:
        tuple[str, type[BaseEffect], type[BaseConfig]]: The command name, effect class, and configuration class.

    """
    return "thunderstorm", Thunderstorm, ThunderstormConfig