bastd.actor.respawnicon

Implements respawn icon actor.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Implements respawn icon actor."""
  4
  5from __future__ import annotations
  6
  7import weakref
  8from typing import TYPE_CHECKING
  9
 10import ba
 11
 12if TYPE_CHECKING:
 13    pass
 14
 15
 16class RespawnIcon:
 17    """An icon with a countdown that appears alongside the screen.
 18
 19    category: Gameplay Classes
 20
 21    This is used to indicate that a ba.Player is waiting to respawn.
 22    """
 23
 24    _MASKTEXSTORENAME = ba.storagename('masktex')
 25    _ICONSSTORENAME = ba.storagename('icons')
 26
 27    def __init__(self, player: ba.Player, respawn_time: float):
 28        """Instantiate with a ba.Player and respawn_time (in seconds)."""
 29        self._visible = True
 30
 31        on_right, offs_extra, respawn_icons = self._get_context(player)
 32
 33        # Cache our mask tex on the team for easy access.
 34        mask_tex = player.team.customdata.get(self._MASKTEXSTORENAME)
 35        if mask_tex is None:
 36            mask_tex = ba.gettexture('characterIconMask')
 37            player.team.customdata[self._MASKTEXSTORENAME] = mask_tex
 38        assert isinstance(mask_tex, ba.Texture)
 39
 40        # Now find the first unused slot and use that.
 41        index = 0
 42        while (
 43            index in respawn_icons
 44            and respawn_icons[index]() is not None
 45            and respawn_icons[index]().visible
 46        ):
 47            index += 1
 48        respawn_icons[index] = weakref.ref(self)
 49
 50        offs = offs_extra + index * -53
 51        icon = player.get_icon()
 52        texture = icon['texture']
 53        h_offs = -10
 54        ipos = (-40 - h_offs if on_right else 40 + h_offs, -180 + offs)
 55        self._image: ba.NodeActor | None = ba.NodeActor(
 56            ba.newnode(
 57                'image',
 58                attrs={
 59                    'texture': texture,
 60                    'tint_texture': icon['tint_texture'],
 61                    'tint_color': icon['tint_color'],
 62                    'tint2_color': icon['tint2_color'],
 63                    'mask_texture': mask_tex,
 64                    'position': ipos,
 65                    'scale': (32, 32),
 66                    'opacity': 1.0,
 67                    'absolute_scale': True,
 68                    'attach': 'topRight' if on_right else 'topLeft',
 69                },
 70            )
 71        )
 72
 73        assert self._image.node
 74        ba.animate(self._image.node, 'opacity', {0.0: 0, 0.2: 0.7})
 75
 76        npos = (-40 - h_offs if on_right else 40 + h_offs, -205 + 49 + offs)
 77        self._name: ba.NodeActor | None = ba.NodeActor(
 78            ba.newnode(
 79                'text',
 80                attrs={
 81                    'v_attach': 'top',
 82                    'h_attach': 'right' if on_right else 'left',
 83                    'text': ba.Lstr(value=player.getname()),
 84                    'maxwidth': 100,
 85                    'h_align': 'center',
 86                    'v_align': 'center',
 87                    'shadow': 1.0,
 88                    'flatness': 1.0,
 89                    'color': ba.safecolor(icon['tint_color']),
 90                    'scale': 0.5,
 91                    'position': npos,
 92                },
 93            )
 94        )
 95
 96        assert self._name.node
 97        ba.animate(self._name.node, 'scale', {0: 0, 0.1: 0.5})
 98
 99        tpos = (-60 - h_offs if on_right else 60 + h_offs, -192 + offs)
100        self._text: ba.NodeActor | None = ba.NodeActor(
101            ba.newnode(
102                'text',
103                attrs={
104                    'position': tpos,
105                    'h_attach': 'right' if on_right else 'left',
106                    'h_align': 'right' if on_right else 'left',
107                    'scale': 0.9,
108                    'shadow': 0.5,
109                    'flatness': 0.5,
110                    'v_attach': 'top',
111                    'color': ba.safecolor(icon['tint_color']),
112                    'text': '',
113                },
114            )
115        )
116
117        assert self._text.node
118        ba.animate(self._text.node, 'scale', {0: 0, 0.1: 0.9})
119
120        self._respawn_time = ba.time() + respawn_time
121        self._update()
122        self._timer: ba.Timer | None = ba.Timer(
123            1.0, ba.WeakCall(self._update), repeat=True
124        )
125
126    @property
127    def visible(self) -> bool:
128        """Is this icon still visible?"""
129        return self._visible
130
131    def _get_context(self, player: ba.Player) -> tuple[bool, float, dict]:
132        """Return info on where we should be shown and stored."""
133        activity = ba.getactivity()
134
135        if isinstance(ba.getsession(), ba.DualTeamSession):
136            on_right = player.team.id % 2 == 1
137
138            # Store a list of icons in the team.
139            icons = player.team.customdata.get(self._ICONSSTORENAME)
140            if icons is None:
141                player.team.customdata[self._ICONSSTORENAME] = icons = {}
142            assert isinstance(icons, dict)
143
144            offs_extra = -20
145        else:
146            on_right = False
147
148            # Store a list of icons in the activity.
149            icons = activity.customdata.get(self._ICONSSTORENAME)
150            if icons is None:
151                activity.customdata[self._ICONSSTORENAME] = icons = {}
152            assert isinstance(icons, dict)
153
154            if isinstance(activity.session, ba.FreeForAllSession):
155                offs_extra = -150
156            else:
157                offs_extra = -20
158        return on_right, offs_extra, icons
159
160    def _update(self) -> None:
161        remaining = int(round(self._respawn_time - ba.time()))
162        if remaining > 0:
163            assert self._text is not None
164            if self._text.node:
165                self._text.node.text = str(remaining)
166        else:
167            self._visible = False
168            self._image = self._text = self._timer = self._name = None
class RespawnIcon:
 17class RespawnIcon:
 18    """An icon with a countdown that appears alongside the screen.
 19
 20    category: Gameplay Classes
 21
 22    This is used to indicate that a ba.Player is waiting to respawn.
 23    """
 24
 25    _MASKTEXSTORENAME = ba.storagename('masktex')
 26    _ICONSSTORENAME = ba.storagename('icons')
 27
 28    def __init__(self, player: ba.Player, respawn_time: float):
 29        """Instantiate with a ba.Player and respawn_time (in seconds)."""
 30        self._visible = True
 31
 32        on_right, offs_extra, respawn_icons = self._get_context(player)
 33
 34        # Cache our mask tex on the team for easy access.
 35        mask_tex = player.team.customdata.get(self._MASKTEXSTORENAME)
 36        if mask_tex is None:
 37            mask_tex = ba.gettexture('characterIconMask')
 38            player.team.customdata[self._MASKTEXSTORENAME] = mask_tex
 39        assert isinstance(mask_tex, ba.Texture)
 40
 41        # Now find the first unused slot and use that.
 42        index = 0
 43        while (
 44            index in respawn_icons
 45            and respawn_icons[index]() is not None
 46            and respawn_icons[index]().visible
 47        ):
 48            index += 1
 49        respawn_icons[index] = weakref.ref(self)
 50
 51        offs = offs_extra + index * -53
 52        icon = player.get_icon()
 53        texture = icon['texture']
 54        h_offs = -10
 55        ipos = (-40 - h_offs if on_right else 40 + h_offs, -180 + offs)
 56        self._image: ba.NodeActor | None = ba.NodeActor(
 57            ba.newnode(
 58                'image',
 59                attrs={
 60                    'texture': texture,
 61                    'tint_texture': icon['tint_texture'],
 62                    'tint_color': icon['tint_color'],
 63                    'tint2_color': icon['tint2_color'],
 64                    'mask_texture': mask_tex,
 65                    'position': ipos,
 66                    'scale': (32, 32),
 67                    'opacity': 1.0,
 68                    'absolute_scale': True,
 69                    'attach': 'topRight' if on_right else 'topLeft',
 70                },
 71            )
 72        )
 73
 74        assert self._image.node
 75        ba.animate(self._image.node, 'opacity', {0.0: 0, 0.2: 0.7})
 76
 77        npos = (-40 - h_offs if on_right else 40 + h_offs, -205 + 49 + offs)
 78        self._name: ba.NodeActor | None = ba.NodeActor(
 79            ba.newnode(
 80                'text',
 81                attrs={
 82                    'v_attach': 'top',
 83                    'h_attach': 'right' if on_right else 'left',
 84                    'text': ba.Lstr(value=player.getname()),
 85                    'maxwidth': 100,
 86                    'h_align': 'center',
 87                    'v_align': 'center',
 88                    'shadow': 1.0,
 89                    'flatness': 1.0,
 90                    'color': ba.safecolor(icon['tint_color']),
 91                    'scale': 0.5,
 92                    'position': npos,
 93                },
 94            )
 95        )
 96
 97        assert self._name.node
 98        ba.animate(self._name.node, 'scale', {0: 0, 0.1: 0.5})
 99
100        tpos = (-60 - h_offs if on_right else 60 + h_offs, -192 + offs)
101        self._text: ba.NodeActor | None = ba.NodeActor(
102            ba.newnode(
103                'text',
104                attrs={
105                    'position': tpos,
106                    'h_attach': 'right' if on_right else 'left',
107                    'h_align': 'right' if on_right else 'left',
108                    'scale': 0.9,
109                    'shadow': 0.5,
110                    'flatness': 0.5,
111                    'v_attach': 'top',
112                    'color': ba.safecolor(icon['tint_color']),
113                    'text': '',
114                },
115            )
116        )
117
118        assert self._text.node
119        ba.animate(self._text.node, 'scale', {0: 0, 0.1: 0.9})
120
121        self._respawn_time = ba.time() + respawn_time
122        self._update()
123        self._timer: ba.Timer | None = ba.Timer(
124            1.0, ba.WeakCall(self._update), repeat=True
125        )
126
127    @property
128    def visible(self) -> bool:
129        """Is this icon still visible?"""
130        return self._visible
131
132    def _get_context(self, player: ba.Player) -> tuple[bool, float, dict]:
133        """Return info on where we should be shown and stored."""
134        activity = ba.getactivity()
135
136        if isinstance(ba.getsession(), ba.DualTeamSession):
137            on_right = player.team.id % 2 == 1
138
139            # Store a list of icons in the team.
140            icons = player.team.customdata.get(self._ICONSSTORENAME)
141            if icons is None:
142                player.team.customdata[self._ICONSSTORENAME] = icons = {}
143            assert isinstance(icons, dict)
144
145            offs_extra = -20
146        else:
147            on_right = False
148
149            # Store a list of icons in the activity.
150            icons = activity.customdata.get(self._ICONSSTORENAME)
151            if icons is None:
152                activity.customdata[self._ICONSSTORENAME] = icons = {}
153            assert isinstance(icons, dict)
154
155            if isinstance(activity.session, ba.FreeForAllSession):
156                offs_extra = -150
157            else:
158                offs_extra = -20
159        return on_right, offs_extra, icons
160
161    def _update(self) -> None:
162        remaining = int(round(self._respawn_time - ba.time()))
163        if remaining > 0:
164            assert self._text is not None
165            if self._text.node:
166                self._text.node.text = str(remaining)
167        else:
168            self._visible = False
169            self._image = self._text = self._timer = self._name = None

An icon with a countdown that appears alongside the screen.

category: Gameplay Classes

This is used to indicate that a ba.Player is waiting to respawn.

RespawnIcon(player: ba._player.Player, respawn_time: float)
 28    def __init__(self, player: ba.Player, respawn_time: float):
 29        """Instantiate with a ba.Player and respawn_time (in seconds)."""
 30        self._visible = True
 31
 32        on_right, offs_extra, respawn_icons = self._get_context(player)
 33
 34        # Cache our mask tex on the team for easy access.
 35        mask_tex = player.team.customdata.get(self._MASKTEXSTORENAME)
 36        if mask_tex is None:
 37            mask_tex = ba.gettexture('characterIconMask')
 38            player.team.customdata[self._MASKTEXSTORENAME] = mask_tex
 39        assert isinstance(mask_tex, ba.Texture)
 40
 41        # Now find the first unused slot and use that.
 42        index = 0
 43        while (
 44            index in respawn_icons
 45            and respawn_icons[index]() is not None
 46            and respawn_icons[index]().visible
 47        ):
 48            index += 1
 49        respawn_icons[index] = weakref.ref(self)
 50
 51        offs = offs_extra + index * -53
 52        icon = player.get_icon()
 53        texture = icon['texture']
 54        h_offs = -10
 55        ipos = (-40 - h_offs if on_right else 40 + h_offs, -180 + offs)
 56        self._image: ba.NodeActor | None = ba.NodeActor(
 57            ba.newnode(
 58                'image',
 59                attrs={
 60                    'texture': texture,
 61                    'tint_texture': icon['tint_texture'],
 62                    'tint_color': icon['tint_color'],
 63                    'tint2_color': icon['tint2_color'],
 64                    'mask_texture': mask_tex,
 65                    'position': ipos,
 66                    'scale': (32, 32),
 67                    'opacity': 1.0,
 68                    'absolute_scale': True,
 69                    'attach': 'topRight' if on_right else 'topLeft',
 70                },
 71            )
 72        )
 73
 74        assert self._image.node
 75        ba.animate(self._image.node, 'opacity', {0.0: 0, 0.2: 0.7})
 76
 77        npos = (-40 - h_offs if on_right else 40 + h_offs, -205 + 49 + offs)
 78        self._name: ba.NodeActor | None = ba.NodeActor(
 79            ba.newnode(
 80                'text',
 81                attrs={
 82                    'v_attach': 'top',
 83                    'h_attach': 'right' if on_right else 'left',
 84                    'text': ba.Lstr(value=player.getname()),
 85                    'maxwidth': 100,
 86                    'h_align': 'center',
 87                    'v_align': 'center',
 88                    'shadow': 1.0,
 89                    'flatness': 1.0,
 90                    'color': ba.safecolor(icon['tint_color']),
 91                    'scale': 0.5,
 92                    'position': npos,
 93                },
 94            )
 95        )
 96
 97        assert self._name.node
 98        ba.animate(self._name.node, 'scale', {0: 0, 0.1: 0.5})
 99
100        tpos = (-60 - h_offs if on_right else 60 + h_offs, -192 + offs)
101        self._text: ba.NodeActor | None = ba.NodeActor(
102            ba.newnode(
103                'text',
104                attrs={
105                    'position': tpos,
106                    'h_attach': 'right' if on_right else 'left',
107                    'h_align': 'right' if on_right else 'left',
108                    'scale': 0.9,
109                    'shadow': 0.5,
110                    'flatness': 0.5,
111                    'v_attach': 'top',
112                    'color': ba.safecolor(icon['tint_color']),
113                    'text': '',
114                },
115            )
116        )
117
118        assert self._text.node
119        ba.animate(self._text.node, 'scale', {0: 0, 0.1: 0.9})
120
121        self._respawn_time = ba.time() + respawn_time
122        self._update()
123        self._timer: ba.Timer | None = ba.Timer(
124            1.0, ba.WeakCall(self._update), repeat=True
125        )

Instantiate with a ba.Player and respawn_time (in seconds).

visible: bool

Is this icon still visible?