bascenev1lib.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
  8
  9import bascenev1 as bs
 10
 11
 12class RespawnIcon:
 13    """An icon with a countdown that appears alongside the screen.
 14
 15    category: Gameplay Classes
 16
 17    This is used to indicate that a bascenev1.Player is waiting to respawn.
 18    """
 19
 20    _MASKTEXSTORENAME = bs.storagename('masktex')
 21    _ICONSSTORENAME = bs.storagename('icons')
 22
 23    def __init__(self, player: bs.Player, respawn_time: float):
 24        """Instantiate with a Player and respawn_time (in seconds)."""
 25        # pylint: disable=too-many-locals
 26        self._visible = True
 27        self._dots_epic_only = False
 28
 29        on_right, offs_extra, respawn_icons = self._get_context(player)
 30
 31        # Cache our mask tex on the team for easy access.
 32        mask_tex = player.team.customdata.get(self._MASKTEXSTORENAME)
 33        if mask_tex is None:
 34            mask_tex = bs.gettexture('characterIconMask')
 35            player.team.customdata[self._MASKTEXSTORENAME] = mask_tex
 36        assert isinstance(mask_tex, bs.Texture)
 37
 38        # Now find the first unused slot and use that.
 39        index = 0
 40        while (
 41            index in respawn_icons
 42            and respawn_icons[index]() is not None
 43            and respawn_icons[index]().visible
 44        ):
 45            index += 1
 46        respawn_icons[index] = weakref.ref(self)
 47
 48        offs = offs_extra + index * -53
 49        icon = player.get_icon()
 50        texture = icon['texture']
 51        h_offs = -10
 52        ipos = (-40 - h_offs if on_right else 40 + h_offs, -180 + offs)
 53        self._image: bs.NodeActor | None = bs.NodeActor(
 54            bs.newnode(
 55                'image',
 56                attrs={
 57                    'texture': texture,
 58                    'tint_texture': icon['tint_texture'],
 59                    'tint_color': icon['tint_color'],
 60                    'tint2_color': icon['tint2_color'],
 61                    'mask_texture': mask_tex,
 62                    'position': ipos,
 63                    'scale': (32, 32),
 64                    'opacity': 1.0,
 65                    'absolute_scale': True,
 66                    'attach': 'topRight' if on_right else 'topLeft',
 67                },
 68            )
 69        )
 70
 71        assert self._image.node
 72        bs.animate(self._image.node, 'opacity', {0.0: 0, 0.2: 0.7})
 73
 74        npos = (-40 - h_offs if on_right else 40 + h_offs, -205 + 49 + offs)
 75        self._name: bs.NodeActor | None = bs.NodeActor(
 76            bs.newnode(
 77                'text',
 78                attrs={
 79                    'v_attach': 'top',
 80                    'h_attach': 'right' if on_right else 'left',
 81                    'text': bs.Lstr(value=player.getname()),
 82                    'maxwidth': 100,
 83                    'h_align': 'center',
 84                    'v_align': 'center',
 85                    'shadow': 1.0,
 86                    'flatness': 1.0,
 87                    'color': bs.safecolor(icon['tint_color']),
 88                    'scale': 0.5,
 89                    'position': npos,
 90                },
 91            )
 92        )
 93
 94        assert self._name.node
 95        bs.animate(self._name.node, 'scale', {0: 0, 0.1: 0.5})
 96
 97        tpos = (-60 - h_offs if on_right else 60 + h_offs, -193 + offs)
 98        self._text: bs.NodeActor | None = bs.NodeActor(
 99            bs.newnode(
100                'text',
101                attrs={
102                    'position': tpos,
103                    'h_attach': 'right' if on_right else 'left',
104                    'h_align': 'right' if on_right else 'left',
105                    'scale': 0.9,
106                    'shadow': 0.5,
107                    'flatness': 0.5,
108                    'v_attach': 'top',
109                    'color': bs.safecolor(icon['tint_color']),
110                    'text': '',
111                },
112            )
113        )
114        dpos = [ipos[0] + (7 if on_right else -7), ipos[1] - 16]
115        self._dec_text: bs.NodeActor | None = None
116        if (
117            self._dots_epic_only
118            and bs.getactivity().globalsnode.slow_motion
119            or not self._dots_epic_only
120        ):
121            self._dec_text = bs.NodeActor(
122                bs.newnode(
123                    'text',
124                    attrs={
125                        'position': dpos,
126                        'h_attach': 'right' if on_right else 'left',
127                        'h_align': 'right' if on_right else 'left',
128                        'scale': 0.65,
129                        'shadow': 0.5,
130                        'flatness': 0.5,
131                        'v_attach': 'top',
132                        'color': bs.safecolor(icon['tint_color']),
133                        'text': '',
134                    },
135                )
136            )
137
138        assert self._text.node
139        bs.animate(self._text.node, 'scale', {0: 0, 0.1: 0.9})
140        if self._dec_text:
141            bs.animate(self._dec_text.node, 'scale', {0: 0, 0.1: 0.65})
142
143        self._respawn_time = bs.time() + respawn_time
144        self._dec_timer: bs.Timer | None = None
145        self._update()
146        self._timer: bs.Timer | None = bs.Timer(
147            1.0, bs.WeakCall(self._update), repeat=True
148        )
149
150    @property
151    def visible(self) -> bool:
152        """Is this icon still visible?"""
153        return self._visible
154
155    def _get_context(self, player: bs.Player) -> tuple[bool, float, dict]:
156        """Return info on where we should be shown and stored."""
157        activity = bs.getactivity()
158
159        if isinstance(activity.session, bs.DualTeamSession):
160            on_right = player.team.id % 2 == 1
161
162            # Store a list of icons in the team.
163            icons = player.team.customdata.get(self._ICONSSTORENAME)
164            if icons is None:
165                player.team.customdata[self._ICONSSTORENAME] = icons = {}
166            assert isinstance(icons, dict)
167
168            offs_extra = -20
169        else:
170            on_right = False
171
172            # Store a list of icons in the activity.
173            icons = activity.customdata.get(self._ICONSSTORENAME)
174            if icons is None:
175                activity.customdata[self._ICONSSTORENAME] = icons = {}
176            assert isinstance(icons, dict)
177
178            if isinstance(activity.session, bs.FreeForAllSession):
179                offs_extra = -150
180            else:
181                offs_extra = -20
182        return on_right, offs_extra, icons
183
184    def _dec_step(self, display: list) -> None:
185        if not self._dec_text:
186            self._dec_timer = None
187            return
188        old_text: bs.Lstr | str = self._dec_text.node.text
189        iterate: int
190        # Get the following display text using our current one.
191        try:
192            iterate = display.index(old_text) + 1
193        # If we don't match any in the display list, we
194        # can assume we've just started iterating.
195        except ValueError:
196            iterate = 0
197        # Kill the timer if we're at the last iteration.
198        if iterate >= len(display):
199            self._dec_timer = None
200            return
201        self._dec_text.node.text = display[iterate]
202
203    def _update(self) -> None:
204        remaining = int(round(self._respawn_time - bs.time()))
205
206        if remaining > 0:
207            assert self._text is not None
208            if self._text.node:
209                self._text.node.text = str(remaining)
210                if self._dec_text:
211                    # Display our decimal dots.
212                    self._dec_text.node.text = '...'
213                    # Start the timer to tick down.
214                    self._dec_timer = bs.Timer(
215                        0.25,
216                        bs.WeakCall(self._dec_step, ['..', '.', '']),
217                        repeat=True,
218                    )
219        else:
220            self._visible = False
221            self._image = self._text = self._dec_text = self._timer = (
222                self._name
223            ) = None
class RespawnIcon:
 13class RespawnIcon:
 14    """An icon with a countdown that appears alongside the screen.
 15
 16    category: Gameplay Classes
 17
 18    This is used to indicate that a bascenev1.Player is waiting to respawn.
 19    """
 20
 21    _MASKTEXSTORENAME = bs.storagename('masktex')
 22    _ICONSSTORENAME = bs.storagename('icons')
 23
 24    def __init__(self, player: bs.Player, respawn_time: float):
 25        """Instantiate with a Player and respawn_time (in seconds)."""
 26        # pylint: disable=too-many-locals
 27        self._visible = True
 28        self._dots_epic_only = False
 29
 30        on_right, offs_extra, respawn_icons = self._get_context(player)
 31
 32        # Cache our mask tex on the team for easy access.
 33        mask_tex = player.team.customdata.get(self._MASKTEXSTORENAME)
 34        if mask_tex is None:
 35            mask_tex = bs.gettexture('characterIconMask')
 36            player.team.customdata[self._MASKTEXSTORENAME] = mask_tex
 37        assert isinstance(mask_tex, bs.Texture)
 38
 39        # Now find the first unused slot and use that.
 40        index = 0
 41        while (
 42            index in respawn_icons
 43            and respawn_icons[index]() is not None
 44            and respawn_icons[index]().visible
 45        ):
 46            index += 1
 47        respawn_icons[index] = weakref.ref(self)
 48
 49        offs = offs_extra + index * -53
 50        icon = player.get_icon()
 51        texture = icon['texture']
 52        h_offs = -10
 53        ipos = (-40 - h_offs if on_right else 40 + h_offs, -180 + offs)
 54        self._image: bs.NodeActor | None = bs.NodeActor(
 55            bs.newnode(
 56                'image',
 57                attrs={
 58                    'texture': texture,
 59                    'tint_texture': icon['tint_texture'],
 60                    'tint_color': icon['tint_color'],
 61                    'tint2_color': icon['tint2_color'],
 62                    'mask_texture': mask_tex,
 63                    'position': ipos,
 64                    'scale': (32, 32),
 65                    'opacity': 1.0,
 66                    'absolute_scale': True,
 67                    'attach': 'topRight' if on_right else 'topLeft',
 68                },
 69            )
 70        )
 71
 72        assert self._image.node
 73        bs.animate(self._image.node, 'opacity', {0.0: 0, 0.2: 0.7})
 74
 75        npos = (-40 - h_offs if on_right else 40 + h_offs, -205 + 49 + offs)
 76        self._name: bs.NodeActor | None = bs.NodeActor(
 77            bs.newnode(
 78                'text',
 79                attrs={
 80                    'v_attach': 'top',
 81                    'h_attach': 'right' if on_right else 'left',
 82                    'text': bs.Lstr(value=player.getname()),
 83                    'maxwidth': 100,
 84                    'h_align': 'center',
 85                    'v_align': 'center',
 86                    'shadow': 1.0,
 87                    'flatness': 1.0,
 88                    'color': bs.safecolor(icon['tint_color']),
 89                    'scale': 0.5,
 90                    'position': npos,
 91                },
 92            )
 93        )
 94
 95        assert self._name.node
 96        bs.animate(self._name.node, 'scale', {0: 0, 0.1: 0.5})
 97
 98        tpos = (-60 - h_offs if on_right else 60 + h_offs, -193 + offs)
 99        self._text: bs.NodeActor | None = bs.NodeActor(
100            bs.newnode(
101                'text',
102                attrs={
103                    'position': tpos,
104                    'h_attach': 'right' if on_right else 'left',
105                    'h_align': 'right' if on_right else 'left',
106                    'scale': 0.9,
107                    'shadow': 0.5,
108                    'flatness': 0.5,
109                    'v_attach': 'top',
110                    'color': bs.safecolor(icon['tint_color']),
111                    'text': '',
112                },
113            )
114        )
115        dpos = [ipos[0] + (7 if on_right else -7), ipos[1] - 16]
116        self._dec_text: bs.NodeActor | None = None
117        if (
118            self._dots_epic_only
119            and bs.getactivity().globalsnode.slow_motion
120            or not self._dots_epic_only
121        ):
122            self._dec_text = bs.NodeActor(
123                bs.newnode(
124                    'text',
125                    attrs={
126                        'position': dpos,
127                        'h_attach': 'right' if on_right else 'left',
128                        'h_align': 'right' if on_right else 'left',
129                        'scale': 0.65,
130                        'shadow': 0.5,
131                        'flatness': 0.5,
132                        'v_attach': 'top',
133                        'color': bs.safecolor(icon['tint_color']),
134                        'text': '',
135                    },
136                )
137            )
138
139        assert self._text.node
140        bs.animate(self._text.node, 'scale', {0: 0, 0.1: 0.9})
141        if self._dec_text:
142            bs.animate(self._dec_text.node, 'scale', {0: 0, 0.1: 0.65})
143
144        self._respawn_time = bs.time() + respawn_time
145        self._dec_timer: bs.Timer | None = None
146        self._update()
147        self._timer: bs.Timer | None = bs.Timer(
148            1.0, bs.WeakCall(self._update), repeat=True
149        )
150
151    @property
152    def visible(self) -> bool:
153        """Is this icon still visible?"""
154        return self._visible
155
156    def _get_context(self, player: bs.Player) -> tuple[bool, float, dict]:
157        """Return info on where we should be shown and stored."""
158        activity = bs.getactivity()
159
160        if isinstance(activity.session, bs.DualTeamSession):
161            on_right = player.team.id % 2 == 1
162
163            # Store a list of icons in the team.
164            icons = player.team.customdata.get(self._ICONSSTORENAME)
165            if icons is None:
166                player.team.customdata[self._ICONSSTORENAME] = icons = {}
167            assert isinstance(icons, dict)
168
169            offs_extra = -20
170        else:
171            on_right = False
172
173            # Store a list of icons in the activity.
174            icons = activity.customdata.get(self._ICONSSTORENAME)
175            if icons is None:
176                activity.customdata[self._ICONSSTORENAME] = icons = {}
177            assert isinstance(icons, dict)
178
179            if isinstance(activity.session, bs.FreeForAllSession):
180                offs_extra = -150
181            else:
182                offs_extra = -20
183        return on_right, offs_extra, icons
184
185    def _dec_step(self, display: list) -> None:
186        if not self._dec_text:
187            self._dec_timer = None
188            return
189        old_text: bs.Lstr | str = self._dec_text.node.text
190        iterate: int
191        # Get the following display text using our current one.
192        try:
193            iterate = display.index(old_text) + 1
194        # If we don't match any in the display list, we
195        # can assume we've just started iterating.
196        except ValueError:
197            iterate = 0
198        # Kill the timer if we're at the last iteration.
199        if iterate >= len(display):
200            self._dec_timer = None
201            return
202        self._dec_text.node.text = display[iterate]
203
204    def _update(self) -> None:
205        remaining = int(round(self._respawn_time - bs.time()))
206
207        if remaining > 0:
208            assert self._text is not None
209            if self._text.node:
210                self._text.node.text = str(remaining)
211                if self._dec_text:
212                    # Display our decimal dots.
213                    self._dec_text.node.text = '...'
214                    # Start the timer to tick down.
215                    self._dec_timer = bs.Timer(
216                        0.25,
217                        bs.WeakCall(self._dec_step, ['..', '.', '']),
218                        repeat=True,
219                    )
220        else:
221            self._visible = False
222            self._image = self._text = self._dec_text = self._timer = (
223                self._name
224            ) = None

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.

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

Instantiate with a Player and respawn_time (in seconds).

visible: bool
151    @property
152    def visible(self) -> bool:
153        """Is this icon still visible?"""
154        return self._visible

Is this icon still visible?