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).