# 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.
category: Gameplay Classes
This is used to indicate that a bascenev1.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