Source code for bascenev1lib.actor.respawnicon

# Released under the MIT License. See LICENSE for details.
#
"""Implements respawn icon actor."""

from __future__ import annotations

import weakref

import bascenev1 as bs


[docs] class RespawnIcon: """An icon with a countdown that appears alongside the screen. This is used to indicate that a player is waiting to respawn. """ _MASKTEXSTORENAME = bs.storagename('masktex') _ICONSSTORENAME = bs.storagename('icons') def __init__(self, player: bs.Player, respawn_time: float): """Instantiate with a Player and respawn_time (in seconds).""" # pylint: disable=too-many-locals self._visible = True self._dots_epic_only = False on_right, offs_extra, respawn_icons = self._get_context(player) # Cache our mask tex on the team for easy access. mask_tex = player.team.customdata.get(self._MASKTEXSTORENAME) if mask_tex is None: mask_tex = bs.gettexture('characterIconMask') player.team.customdata[self._MASKTEXSTORENAME] = mask_tex assert isinstance(mask_tex, bs.Texture) # Now find the first unused slot and use that. index = 0 while ( index in respawn_icons and respawn_icons[index]() is not None and respawn_icons[index]().visible ): index += 1 respawn_icons[index] = weakref.ref(self) offs = offs_extra + index * -53 icon = player.get_icon() texture = icon['texture'] h_offs = -10 ipos = (-40 - h_offs if on_right else 40 + h_offs, -180 + offs) self._image: bs.NodeActor | None = bs.NodeActor( bs.newnode( 'image', attrs={ 'texture': texture, 'tint_texture': icon['tint_texture'], 'tint_color': icon['tint_color'], 'tint2_color': icon['tint2_color'], 'mask_texture': mask_tex, 'position': ipos, 'scale': (32, 32), 'opacity': 1.0, 'absolute_scale': True, 'attach': 'topRight' if on_right else 'topLeft', }, ) ) assert self._image.node bs.animate(self._image.node, 'opacity', {0.0: 0, 0.2: 0.7}) npos = (-40 - h_offs if on_right else 40 + h_offs, -205 + 49 + offs) self._name: bs.NodeActor | None = bs.NodeActor( bs.newnode( 'text', attrs={ 'v_attach': 'top', 'h_attach': 'right' if on_right else 'left', 'text': bs.Lstr(value=player.getname()), 'maxwidth': 100, 'h_align': 'center', 'v_align': 'center', 'shadow': 1.0, 'flatness': 1.0, 'color': bs.safecolor(icon['tint_color']), 'scale': 0.5, 'position': npos, }, ) ) assert self._name.node bs.animate(self._name.node, 'scale', {0: 0, 0.1: 0.5}) tpos = (-60 - h_offs if on_right else 60 + h_offs, -193 + offs) self._text: bs.NodeActor | None = bs.NodeActor( bs.newnode( 'text', attrs={ 'position': tpos, 'h_attach': 'right' if on_right else 'left', 'h_align': 'right' if on_right else 'left', 'scale': 0.9, 'shadow': 0.5, 'flatness': 0.5, 'v_attach': 'top', 'color': bs.safecolor(icon['tint_color']), 'text': '', }, ) ) dpos = [ipos[0] + (7 if on_right else -7), ipos[1] - 16] self._dec_text: bs.NodeActor | None = None if ( self._dots_epic_only and bs.getactivity().globalsnode.slow_motion or not self._dots_epic_only ): self._dec_text = bs.NodeActor( bs.newnode( 'text', attrs={ 'position': dpos, 'h_attach': 'right' if on_right else 'left', 'h_align': 'right' if on_right else 'left', 'scale': 0.65, 'shadow': 0.5, 'flatness': 0.5, 'v_attach': 'top', 'color': bs.safecolor(icon['tint_color']), 'text': '', }, ) ) assert self._text.node bs.animate(self._text.node, 'scale', {0: 0, 0.1: 0.9}) if self._dec_text: bs.animate(self._dec_text.node, 'scale', {0: 0, 0.1: 0.65}) self._respawn_time = bs.time() + respawn_time self._dec_timer: bs.Timer | None = None self._update() self._timer: bs.Timer | None = bs.Timer( 1.0, bs.WeakCall(self._update), repeat=True ) @property def visible(self) -> bool: """Is this icon still visible?""" return self._visible def _get_context(self, player: bs.Player) -> tuple[bool, float, dict]: """Return info on where we should be shown and stored.""" activity = bs.getactivity() if isinstance(activity.session, bs.DualTeamSession): on_right = player.team.id % 2 == 1 # Store a list of icons in the team. icons = player.team.customdata.get(self._ICONSSTORENAME) if icons is None: player.team.customdata[self._ICONSSTORENAME] = icons = {} assert isinstance(icons, dict) offs_extra = -20 else: on_right = False # Store a list of icons in the activity. icons = activity.customdata.get(self._ICONSSTORENAME) if icons is None: activity.customdata[self._ICONSSTORENAME] = icons = {} assert isinstance(icons, dict) if isinstance(activity.session, bs.FreeForAllSession): offs_extra = -150 else: offs_extra = -20 return on_right, offs_extra, icons def _dec_step(self, display: list) -> None: if not self._dec_text: self._dec_timer = None return old_text: bs.Lstr | str = self._dec_text.node.text iterate: int # Get the following display text using our current one. try: iterate = display.index(old_text) + 1 # If we don't match any in the display list, we # can assume we've just started iterating. except ValueError: iterate = 0 # Kill the timer if we're at the last iteration. if iterate >= len(display): self._dec_timer = None return self._dec_text.node.text = display[iterate] def _update(self) -> None: remaining = int(round(self._respawn_time - bs.time())) if remaining > 0: assert self._text is not None if self._text.node: self._text.node.text = str(remaining) if self._dec_text: # Display our decimal dots. self._dec_text.node.text = '...' # Start the timer to tick down. self._dec_timer = bs.Timer( 0.25, bs.WeakCall(self._dec_step, ['..', '.', '']), repeat=True, ) else: self._visible = False self._image = self._text = self._dec_text = self._timer = ( self._name ) = None
# Docs-generation hack; import some stuff that we likely only forward-declared # in our actual source code so that docs tools can find it. from typing import (Coroutine, Any, Literal, Callable, Generator, Awaitable, Sequence, Self) import asyncio from concurrent.futures import Future from pathlib import Path from enum import Enum