bastd.ui.partyqueue
UI related to waiting in line for a party.
1# Released under the MIT License. See LICENSE for details. 2# 3"""UI related to waiting in line for a party.""" 4 5from __future__ import annotations 6 7import random 8import time 9from typing import TYPE_CHECKING 10 11import _ba 12import ba 13 14if TYPE_CHECKING: 15 from typing import Any, Sequence 16 17 18class PartyQueueWindow(ba.Window): 19 """Window showing players waiting to join a server.""" 20 21 # ewww this needs quite a bit of de-linting if/when i revisit it.. 22 # pylint: disable=invalid-name 23 # pylint: disable=consider-using-dict-comprehension 24 class Dude: 25 """Represents a single dude waiting in a server line.""" 26 27 def __init__(self, parent: PartyQueueWindow, distance: float, 28 initial_offset: float, is_player: bool, account_id: str, 29 name: str): 30 self.claimed = False 31 self._line_left = parent.get_line_left() 32 self._line_width = parent.get_line_width() 33 self._line_bottom = parent.get_line_bottom() 34 self._target_distance = distance 35 self._distance = distance + initial_offset 36 self._boost_brightness = 0.0 37 self._debug = False 38 self._sc = sc = 1.1 if is_player else 0.6 + random.random() * 0.2 39 self._y_offs = -30.0 if is_player else -47.0 * sc 40 self._last_boost_time = 0.0 41 self._color = (0.2, 1.0, 42 0.2) if is_player else (0.5 + 0.3 * random.random(), 43 0.4 + 0.2 * random.random(), 44 0.5 + 0.3 * random.random()) 45 self._eye_color = (0.7 * 1.0 + 0.3 * self._color[0], 46 0.7 * 1.0 + 0.3 * self._color[1], 47 0.7 * 1.0 + 0.3 * self._color[2]) 48 self._body_image = ba.buttonwidget( 49 parent=parent.get_root_widget(), 50 selectable=True, 51 label='', 52 size=(sc * 60, sc * 80), 53 color=self._color, 54 texture=parent.lineup_tex, 55 model_transparent=parent.lineup_1_transparent_model) 56 ba.buttonwidget(edit=self._body_image, 57 on_activate_call=ba.WeakCall( 58 parent.on_account_press, account_id, 59 self._body_image)) 60 ba.widget(edit=self._body_image, autoselect=True) 61 self._eyes_image = ba.imagewidget( 62 parent=parent.get_root_widget(), 63 size=(sc * 36, sc * 18), 64 texture=parent.lineup_tex, 65 color=self._eye_color, 66 model_transparent=parent.eyes_model) 67 self._name_text = ba.textwidget(parent=parent.get_root_widget(), 68 size=(0, 0), 69 shadow=0, 70 flatness=1.0, 71 text=name, 72 maxwidth=100, 73 h_align='center', 74 v_align='center', 75 scale=0.75, 76 color=(1, 1, 1, 0.6)) 77 self._update_image() 78 79 # DEBUG: vis target pos.. 80 self._body_image_target: ba.Widget | None 81 self._eyes_image_target: ba.Widget | None 82 if self._debug: 83 self._body_image_target = ba.imagewidget( 84 parent=parent.get_root_widget(), 85 size=(sc * 60, sc * 80), 86 color=self._color, 87 texture=parent.lineup_tex, 88 model_transparent=parent.lineup_1_transparent_model) 89 self._eyes_image_target = ba.imagewidget( 90 parent=parent.get_root_widget(), 91 size=(sc * 36, sc * 18), 92 texture=parent.lineup_tex, 93 color=self._eye_color, 94 model_transparent=parent.eyes_model) 95 # (updates our image positions) 96 self.set_target_distance(self._target_distance) 97 else: 98 self._body_image_target = self._eyes_image_target = None 99 100 def __del__(self) -> None: 101 102 # ew. our destructor here may get called as part of an internal 103 # widget tear-down. 104 # running further widget calls here can quietly break stuff, so we 105 # need to push a deferred call to kill these as necessary instead. 106 # (should bulletproof internal widget code to give a clean error 107 # in this case) 108 def kill_widgets(widgets: Sequence[ba.Widget | None]) -> None: 109 for widget in widgets: 110 if widget: 111 widget.delete() 112 113 ba.pushcall( 114 ba.Call(kill_widgets, [ 115 self._body_image, self._eyes_image, 116 self._body_image_target, self._eyes_image_target, 117 self._name_text 118 ])) 119 120 def set_target_distance(self, dist: float) -> None: 121 """Set distance for a dude.""" 122 self._target_distance = dist 123 if self._debug: 124 sc = self._sc 125 position = (self._line_left + self._line_width * 126 (1.0 - self._target_distance), 127 self._line_bottom - 30) 128 ba.imagewidget(edit=self._body_image_target, 129 position=(position[0] - sc * 30, 130 position[1] - sc * 25 - 70)) 131 ba.imagewidget(edit=self._eyes_image_target, 132 position=(position[0] - sc * 18, 133 position[1] + sc * 31 - 70)) 134 135 def step(self, smoothing: float) -> None: 136 """Step this dude.""" 137 self._distance = (smoothing * self._distance + 138 (1.0 - smoothing) * self._target_distance) 139 self._update_image() 140 self._boost_brightness *= 0.9 141 142 def _update_image(self) -> None: 143 sc = self._sc 144 position = (self._line_left + self._line_width * 145 (1.0 - self._distance), self._line_bottom + 40) 146 brightness = 1.0 + self._boost_brightness 147 ba.buttonwidget(edit=self._body_image, 148 position=(position[0] - sc * 30, 149 position[1] - sc * 25 + self._y_offs), 150 color=(self._color[0] * brightness, 151 self._color[1] * brightness, 152 self._color[2] * brightness)) 153 ba.imagewidget(edit=self._eyes_image, 154 position=(position[0] - sc * 18, 155 position[1] + sc * 31 + self._y_offs), 156 color=(self._eye_color[0] * brightness, 157 self._eye_color[1] * brightness, 158 self._eye_color[2] * brightness)) 159 ba.textwidget(edit=self._name_text, 160 position=(position[0] - sc * 0, 161 position[1] + sc * 40.0)) 162 163 def boost(self, amount: float, smoothing: float) -> None: 164 """Boost this dude.""" 165 del smoothing # unused arg 166 self._distance = max(0.0, self._distance - amount) 167 self._update_image() 168 self._last_boost_time = time.time() 169 self._boost_brightness += 0.6 170 171 def __init__(self, queue_id: str, address: str, port: int): 172 ba.app.ui.have_party_queue_window = True 173 self._address = address 174 self._port = port 175 self._queue_id = queue_id 176 self._width = 800 177 self._height = 400 178 self._last_connect_attempt_time: float | None = None 179 self._last_transaction_time: float | None = None 180 self._boost_button: ba.Widget | None = None 181 self._boost_price: ba.Widget | None = None 182 self._boost_label: ba.Widget | None = None 183 self._field_shown = False 184 self._dudes: list[PartyQueueWindow.Dude] = [] 185 self._dudes_by_id: dict[int, PartyQueueWindow.Dude] = {} 186 self._line_left = 40.0 187 self._line_width = self._width - 190 188 self._line_bottom = self._height * 0.4 189 self.lineup_tex = ba.gettexture('playerLineup') 190 self._smoothing = 0.0 191 self._initial_offset = 0.0 192 self._boost_tickets = 0 193 self._boost_strength = 0.0 194 self._angry_computer_transparent_model = ba.getmodel( 195 'angryComputerTransparent') 196 self._angry_computer_image: ba.Widget | None = None 197 self.lineup_1_transparent_model = ba.getmodel( 198 'playerLineup1Transparent') 199 self._lineup_2_transparent_model = ba.getmodel( 200 'playerLineup2Transparent') 201 self._lineup_3_transparent_model = ba.getmodel( 202 'playerLineup3Transparent') 203 self._lineup_4_transparent_model = ba.getmodel( 204 'playerLineup4Transparent') 205 self._line_image: ba.Widget | None = None 206 self.eyes_model = ba.getmodel('plasticEyesTransparent') 207 self._white_tex = ba.gettexture('white') 208 uiscale = ba.app.ui.uiscale 209 super().__init__(root_widget=ba.containerwidget( 210 size=(self._width, self._height), 211 color=(0.45, 0.63, 0.15), 212 transition='in_scale', 213 scale=(1.4 if uiscale is ba.UIScale.SMALL else 214 1.2 if uiscale is ba.UIScale.MEDIUM else 1.0))) 215 216 self._cancel_button = ba.buttonwidget(parent=self._root_widget, 217 scale=1.0, 218 position=(60, self._height - 80), 219 size=(50, 50), 220 label='', 221 on_activate_call=self.close, 222 autoselect=True, 223 color=(0.45, 0.63, 0.15), 224 icon=ba.gettexture('crossOut'), 225 iconscale=1.2) 226 ba.containerwidget(edit=self._root_widget, 227 cancel_button=self._cancel_button) 228 229 self._title_text = ba.textwidget( 230 parent=self._root_widget, 231 position=(self._width * 0.5, self._height * 0.55), 232 size=(0, 0), 233 color=(1.0, 3.0, 1.0), 234 scale=1.3, 235 h_align='center', 236 v_align='center', 237 text=ba.Lstr(resource='internal.connectingToPartyText'), 238 maxwidth=self._width * 0.65) 239 240 self._tickets_text = ba.textwidget(parent=self._root_widget, 241 position=(self._width - 180, 242 self._height - 20), 243 size=(0, 0), 244 color=(0.2, 1.0, 0.2), 245 scale=0.7, 246 h_align='center', 247 v_align='center', 248 text='') 249 250 # update at roughly 30fps 251 self._update_timer = ba.Timer(0.033, 252 ba.WeakCall(self.update), 253 repeat=True, 254 timetype=ba.TimeType.REAL) 255 self.update() 256 257 def __del__(self) -> None: 258 try: 259 ba.app.ui.have_party_queue_window = False 260 _ba.add_transaction({ 261 'type': 'PARTY_QUEUE_REMOVE', 262 'q': self._queue_id 263 }) 264 _ba.run_transactions() 265 except Exception: 266 ba.print_exception('Error removing self from party queue.') 267 268 def get_line_left(self) -> float: 269 """(internal)""" 270 return self._line_left 271 272 def get_line_width(self) -> float: 273 """(internal)""" 274 return self._line_width 275 276 def get_line_bottom(self) -> float: 277 """(internal)""" 278 return self._line_bottom 279 280 def on_account_press(self, account_id: str | None, 281 origin_widget: ba.Widget) -> None: 282 """A dude was clicked so we should show his account info.""" 283 from bastd.ui.account import viewer 284 if account_id is None: 285 ba.playsound(ba.getsound('error')) 286 return 287 viewer.AccountViewerWindow( 288 account_id=account_id, 289 position=origin_widget.get_screen_space_center()) 290 291 def close(self) -> None: 292 """Close the ui.""" 293 ba.containerwidget(edit=self._root_widget, transition='out_scale') 294 295 def _update_field(self, response: dict[str, Any]) -> None: 296 if self._angry_computer_image is None: 297 self._angry_computer_image = ba.imagewidget( 298 parent=self._root_widget, 299 position=(self._width - 180, self._height * 0.5 - 65), 300 size=(150, 150), 301 texture=self.lineup_tex, 302 model_transparent=self._angry_computer_transparent_model) 303 if self._line_image is None: 304 self._line_image = ba.imagewidget( 305 parent=self._root_widget, 306 color=(0.0, 0.0, 0.0), 307 opacity=0.2, 308 position=(self._line_left, self._line_bottom - 2.0), 309 size=(self._line_width, 4.0), 310 texture=self._white_tex) 311 312 # now go through the data they sent, creating dudes for us and our 313 # enemies as needed and updating target positions on all of them.. 314 315 # mark all as unclaimed so we know which ones to kill off.. 316 for dude in self._dudes: 317 dude.claimed = False 318 319 # always have a dude for ourself.. 320 if -1 not in self._dudes_by_id: 321 dude = self.Dude( 322 self, response['d'], self._initial_offset, True, 323 _ba.get_v1_account_misc_read_val_2('resolvedAccountID', None), 324 _ba.get_v1_account_display_string()) 325 self._dudes_by_id[-1] = dude 326 self._dudes.append(dude) 327 else: 328 self._dudes_by_id[-1].set_target_distance(response['d']) 329 self._dudes_by_id[-1].claimed = True 330 331 # now create/destroy enemies 332 for (enemy_id, enemy_distance, enemy_account_id, 333 enemy_name) in response['e']: 334 if enemy_id not in self._dudes_by_id: 335 dude = self.Dude(self, enemy_distance, self._initial_offset, 336 False, enemy_account_id, enemy_name) 337 self._dudes_by_id[enemy_id] = dude 338 self._dudes.append(dude) 339 else: 340 self._dudes_by_id[enemy_id].set_target_distance(enemy_distance) 341 self._dudes_by_id[enemy_id].claimed = True 342 343 # remove unclaimed dudes from both of our lists 344 # noinspection PyUnresolvedReferences 345 self._dudes_by_id = dict([ 346 item for item in list(self._dudes_by_id.items()) if item[1].claimed 347 ]) 348 self._dudes = [dude for dude in self._dudes if dude.claimed] 349 350 def _hide_field(self) -> None: 351 if self._angry_computer_image: 352 self._angry_computer_image.delete() 353 self._angry_computer_image = None 354 if self._line_image: 355 self._line_image.delete() 356 self._line_image = None 357 self._dudes = [] 358 self._dudes_by_id = {} 359 360 def on_update_response(self, response: dict[str, Any] | None) -> None: 361 """We've received a response from an update to the server.""" 362 # pylint: disable=too-many-branches 363 if not self._root_widget: 364 return 365 366 # Seeing this in logs; debugging... 367 if not self._title_text: 368 print('PartyQueueWindows update: Have root but no title_text.') 369 return 370 371 if response is not None: 372 should_show_field = (response.get('d') is not None) 373 self._smoothing = response['s'] 374 self._initial_offset = response['o'] 375 376 # If they gave us a position, show the field. 377 if should_show_field: 378 ba.textwidget(edit=self._title_text, 379 text=ba.Lstr(resource='waitingInLineText'), 380 position=(self._width * 0.5, 381 self._height * 0.85)) 382 self._update_field(response) 383 self._field_shown = True 384 if not should_show_field and self._field_shown: 385 ba.textwidget( 386 edit=self._title_text, 387 text=ba.Lstr(resource='internal.connectingToPartyText'), 388 position=(self._width * 0.5, self._height * 0.55)) 389 self._hide_field() 390 self._field_shown = False 391 392 # if they told us there's a boost button, update.. 393 if response.get('bt') is not None: 394 self._boost_tickets = response['bt'] 395 self._boost_strength = response['ba'] 396 if self._boost_button is None: 397 self._boost_button = ba.buttonwidget( 398 parent=self._root_widget, 399 scale=1.0, 400 position=(self._width * 0.5 - 75, 20), 401 size=(150, 100), 402 button_type='square', 403 label='', 404 on_activate_call=self.on_boost_press, 405 enable_sound=False, 406 color=(0, 1, 0), 407 autoselect=True) 408 self._boost_label = ba.textwidget( 409 parent=self._root_widget, 410 draw_controller=self._boost_button, 411 position=(self._width * 0.5, 88), 412 size=(0, 0), 413 color=(0.8, 1.0, 0.8), 414 scale=1.5, 415 h_align='center', 416 v_align='center', 417 text=ba.Lstr(resource='boostText'), 418 maxwidth=150) 419 self._boost_price = ba.textwidget( 420 parent=self._root_widget, 421 draw_controller=self._boost_button, 422 position=(self._width * 0.5, 50), 423 size=(0, 0), 424 color=(0, 1, 0), 425 scale=0.9, 426 h_align='center', 427 v_align='center', 428 text=ba.charstr(ba.SpecialChar.TICKET) + 429 str(self._boost_tickets), 430 maxwidth=150) 431 else: 432 if self._boost_button is not None: 433 self._boost_button.delete() 434 self._boost_button = None 435 if self._boost_price is not None: 436 self._boost_price.delete() 437 self._boost_price = None 438 if self._boost_label is not None: 439 self._boost_label.delete() 440 self._boost_label = None 441 442 # if they told us to go ahead and try and connect, do so.. 443 # (note: servers will disconnect us if we try to connect before 444 # getting this go-ahead, so don't get any bright ideas...) 445 if response.get('c', False): 446 # enforce a delay between connection attempts 447 # (in case they're jamming on the boost button) 448 now = time.time() 449 if (self._last_connect_attempt_time is None 450 or now - self._last_connect_attempt_time > 10.0): 451 _ba.connect_to_party(address=self._address, 452 port=self._port, 453 print_progress=False) 454 self._last_connect_attempt_time = now 455 456 def on_boost_press(self) -> None: 457 """Boost was pressed.""" 458 from bastd.ui import account 459 from bastd.ui import getcurrency 460 if _ba.get_v1_account_state() != 'signed_in': 461 account.show_sign_in_prompt() 462 return 463 464 if _ba.get_v1_account_ticket_count() < self._boost_tickets: 465 ba.playsound(ba.getsound('error')) 466 getcurrency.show_get_tickets_prompt() 467 return 468 469 ba.playsound(ba.getsound('laserReverse')) 470 _ba.add_transaction( 471 { 472 'type': 'PARTY_QUEUE_BOOST', 473 't': self._boost_tickets, 474 'q': self._queue_id 475 }, 476 callback=ba.WeakCall(self.on_update_response)) 477 # lets not run these immediately (since they may be rapid-fire, 478 # just bucket them until the next tick) 479 480 # the transaction handles the local ticket change, but we apply our 481 # local boost vis manually here.. 482 # (our visualization isn't really wired up to be transaction-based) 483 our_dude = self._dudes_by_id.get(-1) 484 if our_dude is not None: 485 our_dude.boost(self._boost_strength, self._smoothing) 486 487 def update(self) -> None: 488 """Update!""" 489 if not self._root_widget: 490 return 491 492 # Update boost-price. 493 if self._boost_price is not None: 494 ba.textwidget(edit=self._boost_price, 495 text=ba.charstr(ba.SpecialChar.TICKET) + 496 str(self._boost_tickets)) 497 498 # Update boost button color based on if we have enough moola. 499 if self._boost_button is not None: 500 can_boost = ( 501 (_ba.get_v1_account_state() == 'signed_in' 502 and _ba.get_v1_account_ticket_count() >= self._boost_tickets)) 503 ba.buttonwidget(edit=self._boost_button, 504 color=(0, 1, 0) if can_boost else (0.7, 0.7, 0.7)) 505 506 # Update ticket-count. 507 if self._tickets_text is not None: 508 if self._boost_button is not None: 509 if _ba.get_v1_account_state() == 'signed_in': 510 val = ba.charstr(ba.SpecialChar.TICKET) + str( 511 _ba.get_v1_account_ticket_count()) 512 else: 513 val = ba.charstr(ba.SpecialChar.TICKET) + '???' 514 ba.textwidget(edit=self._tickets_text, text=val) 515 else: 516 ba.textwidget(edit=self._tickets_text, text='') 517 518 current_time = ba.time(ba.TimeType.REAL) 519 if (self._last_transaction_time is None 520 or current_time - self._last_transaction_time > 521 0.001 * _ba.get_v1_account_misc_read_val('pqInt', 5000)): 522 self._last_transaction_time = current_time 523 _ba.add_transaction( 524 { 525 'type': 'PARTY_QUEUE_QUERY', 526 'q': self._queue_id 527 }, 528 callback=ba.WeakCall(self.on_update_response)) 529 _ba.run_transactions() 530 531 # step our dudes 532 for dude in self._dudes: 533 dude.step(self._smoothing)
class
PartyQueueWindow(ba.ui.Window):
19class PartyQueueWindow(ba.Window): 20 """Window showing players waiting to join a server.""" 21 22 # ewww this needs quite a bit of de-linting if/when i revisit it.. 23 # pylint: disable=invalid-name 24 # pylint: disable=consider-using-dict-comprehension 25 class Dude: 26 """Represents a single dude waiting in a server line.""" 27 28 def __init__(self, parent: PartyQueueWindow, distance: float, 29 initial_offset: float, is_player: bool, account_id: str, 30 name: str): 31 self.claimed = False 32 self._line_left = parent.get_line_left() 33 self._line_width = parent.get_line_width() 34 self._line_bottom = parent.get_line_bottom() 35 self._target_distance = distance 36 self._distance = distance + initial_offset 37 self._boost_brightness = 0.0 38 self._debug = False 39 self._sc = sc = 1.1 if is_player else 0.6 + random.random() * 0.2 40 self._y_offs = -30.0 if is_player else -47.0 * sc 41 self._last_boost_time = 0.0 42 self._color = (0.2, 1.0, 43 0.2) if is_player else (0.5 + 0.3 * random.random(), 44 0.4 + 0.2 * random.random(), 45 0.5 + 0.3 * random.random()) 46 self._eye_color = (0.7 * 1.0 + 0.3 * self._color[0], 47 0.7 * 1.0 + 0.3 * self._color[1], 48 0.7 * 1.0 + 0.3 * self._color[2]) 49 self._body_image = ba.buttonwidget( 50 parent=parent.get_root_widget(), 51 selectable=True, 52 label='', 53 size=(sc * 60, sc * 80), 54 color=self._color, 55 texture=parent.lineup_tex, 56 model_transparent=parent.lineup_1_transparent_model) 57 ba.buttonwidget(edit=self._body_image, 58 on_activate_call=ba.WeakCall( 59 parent.on_account_press, account_id, 60 self._body_image)) 61 ba.widget(edit=self._body_image, autoselect=True) 62 self._eyes_image = ba.imagewidget( 63 parent=parent.get_root_widget(), 64 size=(sc * 36, sc * 18), 65 texture=parent.lineup_tex, 66 color=self._eye_color, 67 model_transparent=parent.eyes_model) 68 self._name_text = ba.textwidget(parent=parent.get_root_widget(), 69 size=(0, 0), 70 shadow=0, 71 flatness=1.0, 72 text=name, 73 maxwidth=100, 74 h_align='center', 75 v_align='center', 76 scale=0.75, 77 color=(1, 1, 1, 0.6)) 78 self._update_image() 79 80 # DEBUG: vis target pos.. 81 self._body_image_target: ba.Widget | None 82 self._eyes_image_target: ba.Widget | None 83 if self._debug: 84 self._body_image_target = ba.imagewidget( 85 parent=parent.get_root_widget(), 86 size=(sc * 60, sc * 80), 87 color=self._color, 88 texture=parent.lineup_tex, 89 model_transparent=parent.lineup_1_transparent_model) 90 self._eyes_image_target = ba.imagewidget( 91 parent=parent.get_root_widget(), 92 size=(sc * 36, sc * 18), 93 texture=parent.lineup_tex, 94 color=self._eye_color, 95 model_transparent=parent.eyes_model) 96 # (updates our image positions) 97 self.set_target_distance(self._target_distance) 98 else: 99 self._body_image_target = self._eyes_image_target = None 100 101 def __del__(self) -> None: 102 103 # ew. our destructor here may get called as part of an internal 104 # widget tear-down. 105 # running further widget calls here can quietly break stuff, so we 106 # need to push a deferred call to kill these as necessary instead. 107 # (should bulletproof internal widget code to give a clean error 108 # in this case) 109 def kill_widgets(widgets: Sequence[ba.Widget | None]) -> None: 110 for widget in widgets: 111 if widget: 112 widget.delete() 113 114 ba.pushcall( 115 ba.Call(kill_widgets, [ 116 self._body_image, self._eyes_image, 117 self._body_image_target, self._eyes_image_target, 118 self._name_text 119 ])) 120 121 def set_target_distance(self, dist: float) -> None: 122 """Set distance for a dude.""" 123 self._target_distance = dist 124 if self._debug: 125 sc = self._sc 126 position = (self._line_left + self._line_width * 127 (1.0 - self._target_distance), 128 self._line_bottom - 30) 129 ba.imagewidget(edit=self._body_image_target, 130 position=(position[0] - sc * 30, 131 position[1] - sc * 25 - 70)) 132 ba.imagewidget(edit=self._eyes_image_target, 133 position=(position[0] - sc * 18, 134 position[1] + sc * 31 - 70)) 135 136 def step(self, smoothing: float) -> None: 137 """Step this dude.""" 138 self._distance = (smoothing * self._distance + 139 (1.0 - smoothing) * self._target_distance) 140 self._update_image() 141 self._boost_brightness *= 0.9 142 143 def _update_image(self) -> None: 144 sc = self._sc 145 position = (self._line_left + self._line_width * 146 (1.0 - self._distance), self._line_bottom + 40) 147 brightness = 1.0 + self._boost_brightness 148 ba.buttonwidget(edit=self._body_image, 149 position=(position[0] - sc * 30, 150 position[1] - sc * 25 + self._y_offs), 151 color=(self._color[0] * brightness, 152 self._color[1] * brightness, 153 self._color[2] * brightness)) 154 ba.imagewidget(edit=self._eyes_image, 155 position=(position[0] - sc * 18, 156 position[1] + sc * 31 + self._y_offs), 157 color=(self._eye_color[0] * brightness, 158 self._eye_color[1] * brightness, 159 self._eye_color[2] * brightness)) 160 ba.textwidget(edit=self._name_text, 161 position=(position[0] - sc * 0, 162 position[1] + sc * 40.0)) 163 164 def boost(self, amount: float, smoothing: float) -> None: 165 """Boost this dude.""" 166 del smoothing # unused arg 167 self._distance = max(0.0, self._distance - amount) 168 self._update_image() 169 self._last_boost_time = time.time() 170 self._boost_brightness += 0.6 171 172 def __init__(self, queue_id: str, address: str, port: int): 173 ba.app.ui.have_party_queue_window = True 174 self._address = address 175 self._port = port 176 self._queue_id = queue_id 177 self._width = 800 178 self._height = 400 179 self._last_connect_attempt_time: float | None = None 180 self._last_transaction_time: float | None = None 181 self._boost_button: ba.Widget | None = None 182 self._boost_price: ba.Widget | None = None 183 self._boost_label: ba.Widget | None = None 184 self._field_shown = False 185 self._dudes: list[PartyQueueWindow.Dude] = [] 186 self._dudes_by_id: dict[int, PartyQueueWindow.Dude] = {} 187 self._line_left = 40.0 188 self._line_width = self._width - 190 189 self._line_bottom = self._height * 0.4 190 self.lineup_tex = ba.gettexture('playerLineup') 191 self._smoothing = 0.0 192 self._initial_offset = 0.0 193 self._boost_tickets = 0 194 self._boost_strength = 0.0 195 self._angry_computer_transparent_model = ba.getmodel( 196 'angryComputerTransparent') 197 self._angry_computer_image: ba.Widget | None = None 198 self.lineup_1_transparent_model = ba.getmodel( 199 'playerLineup1Transparent') 200 self._lineup_2_transparent_model = ba.getmodel( 201 'playerLineup2Transparent') 202 self._lineup_3_transparent_model = ba.getmodel( 203 'playerLineup3Transparent') 204 self._lineup_4_transparent_model = ba.getmodel( 205 'playerLineup4Transparent') 206 self._line_image: ba.Widget | None = None 207 self.eyes_model = ba.getmodel('plasticEyesTransparent') 208 self._white_tex = ba.gettexture('white') 209 uiscale = ba.app.ui.uiscale 210 super().__init__(root_widget=ba.containerwidget( 211 size=(self._width, self._height), 212 color=(0.45, 0.63, 0.15), 213 transition='in_scale', 214 scale=(1.4 if uiscale is ba.UIScale.SMALL else 215 1.2 if uiscale is ba.UIScale.MEDIUM else 1.0))) 216 217 self._cancel_button = ba.buttonwidget(parent=self._root_widget, 218 scale=1.0, 219 position=(60, self._height - 80), 220 size=(50, 50), 221 label='', 222 on_activate_call=self.close, 223 autoselect=True, 224 color=(0.45, 0.63, 0.15), 225 icon=ba.gettexture('crossOut'), 226 iconscale=1.2) 227 ba.containerwidget(edit=self._root_widget, 228 cancel_button=self._cancel_button) 229 230 self._title_text = ba.textwidget( 231 parent=self._root_widget, 232 position=(self._width * 0.5, self._height * 0.55), 233 size=(0, 0), 234 color=(1.0, 3.0, 1.0), 235 scale=1.3, 236 h_align='center', 237 v_align='center', 238 text=ba.Lstr(resource='internal.connectingToPartyText'), 239 maxwidth=self._width * 0.65) 240 241 self._tickets_text = ba.textwidget(parent=self._root_widget, 242 position=(self._width - 180, 243 self._height - 20), 244 size=(0, 0), 245 color=(0.2, 1.0, 0.2), 246 scale=0.7, 247 h_align='center', 248 v_align='center', 249 text='') 250 251 # update at roughly 30fps 252 self._update_timer = ba.Timer(0.033, 253 ba.WeakCall(self.update), 254 repeat=True, 255 timetype=ba.TimeType.REAL) 256 self.update() 257 258 def __del__(self) -> None: 259 try: 260 ba.app.ui.have_party_queue_window = False 261 _ba.add_transaction({ 262 'type': 'PARTY_QUEUE_REMOVE', 263 'q': self._queue_id 264 }) 265 _ba.run_transactions() 266 except Exception: 267 ba.print_exception('Error removing self from party queue.') 268 269 def get_line_left(self) -> float: 270 """(internal)""" 271 return self._line_left 272 273 def get_line_width(self) -> float: 274 """(internal)""" 275 return self._line_width 276 277 def get_line_bottom(self) -> float: 278 """(internal)""" 279 return self._line_bottom 280 281 def on_account_press(self, account_id: str | None, 282 origin_widget: ba.Widget) -> None: 283 """A dude was clicked so we should show his account info.""" 284 from bastd.ui.account import viewer 285 if account_id is None: 286 ba.playsound(ba.getsound('error')) 287 return 288 viewer.AccountViewerWindow( 289 account_id=account_id, 290 position=origin_widget.get_screen_space_center()) 291 292 def close(self) -> None: 293 """Close the ui.""" 294 ba.containerwidget(edit=self._root_widget, transition='out_scale') 295 296 def _update_field(self, response: dict[str, Any]) -> None: 297 if self._angry_computer_image is None: 298 self._angry_computer_image = ba.imagewidget( 299 parent=self._root_widget, 300 position=(self._width - 180, self._height * 0.5 - 65), 301 size=(150, 150), 302 texture=self.lineup_tex, 303 model_transparent=self._angry_computer_transparent_model) 304 if self._line_image is None: 305 self._line_image = ba.imagewidget( 306 parent=self._root_widget, 307 color=(0.0, 0.0, 0.0), 308 opacity=0.2, 309 position=(self._line_left, self._line_bottom - 2.0), 310 size=(self._line_width, 4.0), 311 texture=self._white_tex) 312 313 # now go through the data they sent, creating dudes for us and our 314 # enemies as needed and updating target positions on all of them.. 315 316 # mark all as unclaimed so we know which ones to kill off.. 317 for dude in self._dudes: 318 dude.claimed = False 319 320 # always have a dude for ourself.. 321 if -1 not in self._dudes_by_id: 322 dude = self.Dude( 323 self, response['d'], self._initial_offset, True, 324 _ba.get_v1_account_misc_read_val_2('resolvedAccountID', None), 325 _ba.get_v1_account_display_string()) 326 self._dudes_by_id[-1] = dude 327 self._dudes.append(dude) 328 else: 329 self._dudes_by_id[-1].set_target_distance(response['d']) 330 self._dudes_by_id[-1].claimed = True 331 332 # now create/destroy enemies 333 for (enemy_id, enemy_distance, enemy_account_id, 334 enemy_name) in response['e']: 335 if enemy_id not in self._dudes_by_id: 336 dude = self.Dude(self, enemy_distance, self._initial_offset, 337 False, enemy_account_id, enemy_name) 338 self._dudes_by_id[enemy_id] = dude 339 self._dudes.append(dude) 340 else: 341 self._dudes_by_id[enemy_id].set_target_distance(enemy_distance) 342 self._dudes_by_id[enemy_id].claimed = True 343 344 # remove unclaimed dudes from both of our lists 345 # noinspection PyUnresolvedReferences 346 self._dudes_by_id = dict([ 347 item for item in list(self._dudes_by_id.items()) if item[1].claimed 348 ]) 349 self._dudes = [dude for dude in self._dudes if dude.claimed] 350 351 def _hide_field(self) -> None: 352 if self._angry_computer_image: 353 self._angry_computer_image.delete() 354 self._angry_computer_image = None 355 if self._line_image: 356 self._line_image.delete() 357 self._line_image = None 358 self._dudes = [] 359 self._dudes_by_id = {} 360 361 def on_update_response(self, response: dict[str, Any] | None) -> None: 362 """We've received a response from an update to the server.""" 363 # pylint: disable=too-many-branches 364 if not self._root_widget: 365 return 366 367 # Seeing this in logs; debugging... 368 if not self._title_text: 369 print('PartyQueueWindows update: Have root but no title_text.') 370 return 371 372 if response is not None: 373 should_show_field = (response.get('d') is not None) 374 self._smoothing = response['s'] 375 self._initial_offset = response['o'] 376 377 # If they gave us a position, show the field. 378 if should_show_field: 379 ba.textwidget(edit=self._title_text, 380 text=ba.Lstr(resource='waitingInLineText'), 381 position=(self._width * 0.5, 382 self._height * 0.85)) 383 self._update_field(response) 384 self._field_shown = True 385 if not should_show_field and self._field_shown: 386 ba.textwidget( 387 edit=self._title_text, 388 text=ba.Lstr(resource='internal.connectingToPartyText'), 389 position=(self._width * 0.5, self._height * 0.55)) 390 self._hide_field() 391 self._field_shown = False 392 393 # if they told us there's a boost button, update.. 394 if response.get('bt') is not None: 395 self._boost_tickets = response['bt'] 396 self._boost_strength = response['ba'] 397 if self._boost_button is None: 398 self._boost_button = ba.buttonwidget( 399 parent=self._root_widget, 400 scale=1.0, 401 position=(self._width * 0.5 - 75, 20), 402 size=(150, 100), 403 button_type='square', 404 label='', 405 on_activate_call=self.on_boost_press, 406 enable_sound=False, 407 color=(0, 1, 0), 408 autoselect=True) 409 self._boost_label = ba.textwidget( 410 parent=self._root_widget, 411 draw_controller=self._boost_button, 412 position=(self._width * 0.5, 88), 413 size=(0, 0), 414 color=(0.8, 1.0, 0.8), 415 scale=1.5, 416 h_align='center', 417 v_align='center', 418 text=ba.Lstr(resource='boostText'), 419 maxwidth=150) 420 self._boost_price = ba.textwidget( 421 parent=self._root_widget, 422 draw_controller=self._boost_button, 423 position=(self._width * 0.5, 50), 424 size=(0, 0), 425 color=(0, 1, 0), 426 scale=0.9, 427 h_align='center', 428 v_align='center', 429 text=ba.charstr(ba.SpecialChar.TICKET) + 430 str(self._boost_tickets), 431 maxwidth=150) 432 else: 433 if self._boost_button is not None: 434 self._boost_button.delete() 435 self._boost_button = None 436 if self._boost_price is not None: 437 self._boost_price.delete() 438 self._boost_price = None 439 if self._boost_label is not None: 440 self._boost_label.delete() 441 self._boost_label = None 442 443 # if they told us to go ahead and try and connect, do so.. 444 # (note: servers will disconnect us if we try to connect before 445 # getting this go-ahead, so don't get any bright ideas...) 446 if response.get('c', False): 447 # enforce a delay between connection attempts 448 # (in case they're jamming on the boost button) 449 now = time.time() 450 if (self._last_connect_attempt_time is None 451 or now - self._last_connect_attempt_time > 10.0): 452 _ba.connect_to_party(address=self._address, 453 port=self._port, 454 print_progress=False) 455 self._last_connect_attempt_time = now 456 457 def on_boost_press(self) -> None: 458 """Boost was pressed.""" 459 from bastd.ui import account 460 from bastd.ui import getcurrency 461 if _ba.get_v1_account_state() != 'signed_in': 462 account.show_sign_in_prompt() 463 return 464 465 if _ba.get_v1_account_ticket_count() < self._boost_tickets: 466 ba.playsound(ba.getsound('error')) 467 getcurrency.show_get_tickets_prompt() 468 return 469 470 ba.playsound(ba.getsound('laserReverse')) 471 _ba.add_transaction( 472 { 473 'type': 'PARTY_QUEUE_BOOST', 474 't': self._boost_tickets, 475 'q': self._queue_id 476 }, 477 callback=ba.WeakCall(self.on_update_response)) 478 # lets not run these immediately (since they may be rapid-fire, 479 # just bucket them until the next tick) 480 481 # the transaction handles the local ticket change, but we apply our 482 # local boost vis manually here.. 483 # (our visualization isn't really wired up to be transaction-based) 484 our_dude = self._dudes_by_id.get(-1) 485 if our_dude is not None: 486 our_dude.boost(self._boost_strength, self._smoothing) 487 488 def update(self) -> None: 489 """Update!""" 490 if not self._root_widget: 491 return 492 493 # Update boost-price. 494 if self._boost_price is not None: 495 ba.textwidget(edit=self._boost_price, 496 text=ba.charstr(ba.SpecialChar.TICKET) + 497 str(self._boost_tickets)) 498 499 # Update boost button color based on if we have enough moola. 500 if self._boost_button is not None: 501 can_boost = ( 502 (_ba.get_v1_account_state() == 'signed_in' 503 and _ba.get_v1_account_ticket_count() >= self._boost_tickets)) 504 ba.buttonwidget(edit=self._boost_button, 505 color=(0, 1, 0) if can_boost else (0.7, 0.7, 0.7)) 506 507 # Update ticket-count. 508 if self._tickets_text is not None: 509 if self._boost_button is not None: 510 if _ba.get_v1_account_state() == 'signed_in': 511 val = ba.charstr(ba.SpecialChar.TICKET) + str( 512 _ba.get_v1_account_ticket_count()) 513 else: 514 val = ba.charstr(ba.SpecialChar.TICKET) + '???' 515 ba.textwidget(edit=self._tickets_text, text=val) 516 else: 517 ba.textwidget(edit=self._tickets_text, text='') 518 519 current_time = ba.time(ba.TimeType.REAL) 520 if (self._last_transaction_time is None 521 or current_time - self._last_transaction_time > 522 0.001 * _ba.get_v1_account_misc_read_val('pqInt', 5000)): 523 self._last_transaction_time = current_time 524 _ba.add_transaction( 525 { 526 'type': 'PARTY_QUEUE_QUERY', 527 'q': self._queue_id 528 }, 529 callback=ba.WeakCall(self.on_update_response)) 530 _ba.run_transactions() 531 532 # step our dudes 533 for dude in self._dudes: 534 dude.step(self._smoothing)
Window showing players waiting to join a server.
PartyQueueWindow(queue_id: str, address: str, port: int)
172 def __init__(self, queue_id: str, address: str, port: int): 173 ba.app.ui.have_party_queue_window = True 174 self._address = address 175 self._port = port 176 self._queue_id = queue_id 177 self._width = 800 178 self._height = 400 179 self._last_connect_attempt_time: float | None = None 180 self._last_transaction_time: float | None = None 181 self._boost_button: ba.Widget | None = None 182 self._boost_price: ba.Widget | None = None 183 self._boost_label: ba.Widget | None = None 184 self._field_shown = False 185 self._dudes: list[PartyQueueWindow.Dude] = [] 186 self._dudes_by_id: dict[int, PartyQueueWindow.Dude] = {} 187 self._line_left = 40.0 188 self._line_width = self._width - 190 189 self._line_bottom = self._height * 0.4 190 self.lineup_tex = ba.gettexture('playerLineup') 191 self._smoothing = 0.0 192 self._initial_offset = 0.0 193 self._boost_tickets = 0 194 self._boost_strength = 0.0 195 self._angry_computer_transparent_model = ba.getmodel( 196 'angryComputerTransparent') 197 self._angry_computer_image: ba.Widget | None = None 198 self.lineup_1_transparent_model = ba.getmodel( 199 'playerLineup1Transparent') 200 self._lineup_2_transparent_model = ba.getmodel( 201 'playerLineup2Transparent') 202 self._lineup_3_transparent_model = ba.getmodel( 203 'playerLineup3Transparent') 204 self._lineup_4_transparent_model = ba.getmodel( 205 'playerLineup4Transparent') 206 self._line_image: ba.Widget | None = None 207 self.eyes_model = ba.getmodel('plasticEyesTransparent') 208 self._white_tex = ba.gettexture('white') 209 uiscale = ba.app.ui.uiscale 210 super().__init__(root_widget=ba.containerwidget( 211 size=(self._width, self._height), 212 color=(0.45, 0.63, 0.15), 213 transition='in_scale', 214 scale=(1.4 if uiscale is ba.UIScale.SMALL else 215 1.2 if uiscale is ba.UIScale.MEDIUM else 1.0))) 216 217 self._cancel_button = ba.buttonwidget(parent=self._root_widget, 218 scale=1.0, 219 position=(60, self._height - 80), 220 size=(50, 50), 221 label='', 222 on_activate_call=self.close, 223 autoselect=True, 224 color=(0.45, 0.63, 0.15), 225 icon=ba.gettexture('crossOut'), 226 iconscale=1.2) 227 ba.containerwidget(edit=self._root_widget, 228 cancel_button=self._cancel_button) 229 230 self._title_text = ba.textwidget( 231 parent=self._root_widget, 232 position=(self._width * 0.5, self._height * 0.55), 233 size=(0, 0), 234 color=(1.0, 3.0, 1.0), 235 scale=1.3, 236 h_align='center', 237 v_align='center', 238 text=ba.Lstr(resource='internal.connectingToPartyText'), 239 maxwidth=self._width * 0.65) 240 241 self._tickets_text = ba.textwidget(parent=self._root_widget, 242 position=(self._width - 180, 243 self._height - 20), 244 size=(0, 0), 245 color=(0.2, 1.0, 0.2), 246 scale=0.7, 247 h_align='center', 248 v_align='center', 249 text='') 250 251 # update at roughly 30fps 252 self._update_timer = ba.Timer(0.033, 253 ba.WeakCall(self.update), 254 repeat=True, 255 timetype=ba.TimeType.REAL) 256 self.update()
def
on_account_press(self, account_id: str | None, origin_widget: _ba.Widget) -> None:
281 def on_account_press(self, account_id: str | None, 282 origin_widget: ba.Widget) -> None: 283 """A dude was clicked so we should show his account info.""" 284 from bastd.ui.account import viewer 285 if account_id is None: 286 ba.playsound(ba.getsound('error')) 287 return 288 viewer.AccountViewerWindow( 289 account_id=account_id, 290 position=origin_widget.get_screen_space_center())
A dude was clicked so we should show his account info.
def
close(self) -> None:
292 def close(self) -> None: 293 """Close the ui.""" 294 ba.containerwidget(edit=self._root_widget, transition='out_scale')
Close the ui.
def
on_update_response(self, response: dict[str, typing.Any] | None) -> None:
361 def on_update_response(self, response: dict[str, Any] | None) -> None: 362 """We've received a response from an update to the server.""" 363 # pylint: disable=too-many-branches 364 if not self._root_widget: 365 return 366 367 # Seeing this in logs; debugging... 368 if not self._title_text: 369 print('PartyQueueWindows update: Have root but no title_text.') 370 return 371 372 if response is not None: 373 should_show_field = (response.get('d') is not None) 374 self._smoothing = response['s'] 375 self._initial_offset = response['o'] 376 377 # If they gave us a position, show the field. 378 if should_show_field: 379 ba.textwidget(edit=self._title_text, 380 text=ba.Lstr(resource='waitingInLineText'), 381 position=(self._width * 0.5, 382 self._height * 0.85)) 383 self._update_field(response) 384 self._field_shown = True 385 if not should_show_field and self._field_shown: 386 ba.textwidget( 387 edit=self._title_text, 388 text=ba.Lstr(resource='internal.connectingToPartyText'), 389 position=(self._width * 0.5, self._height * 0.55)) 390 self._hide_field() 391 self._field_shown = False 392 393 # if they told us there's a boost button, update.. 394 if response.get('bt') is not None: 395 self._boost_tickets = response['bt'] 396 self._boost_strength = response['ba'] 397 if self._boost_button is None: 398 self._boost_button = ba.buttonwidget( 399 parent=self._root_widget, 400 scale=1.0, 401 position=(self._width * 0.5 - 75, 20), 402 size=(150, 100), 403 button_type='square', 404 label='', 405 on_activate_call=self.on_boost_press, 406 enable_sound=False, 407 color=(0, 1, 0), 408 autoselect=True) 409 self._boost_label = ba.textwidget( 410 parent=self._root_widget, 411 draw_controller=self._boost_button, 412 position=(self._width * 0.5, 88), 413 size=(0, 0), 414 color=(0.8, 1.0, 0.8), 415 scale=1.5, 416 h_align='center', 417 v_align='center', 418 text=ba.Lstr(resource='boostText'), 419 maxwidth=150) 420 self._boost_price = ba.textwidget( 421 parent=self._root_widget, 422 draw_controller=self._boost_button, 423 position=(self._width * 0.5, 50), 424 size=(0, 0), 425 color=(0, 1, 0), 426 scale=0.9, 427 h_align='center', 428 v_align='center', 429 text=ba.charstr(ba.SpecialChar.TICKET) + 430 str(self._boost_tickets), 431 maxwidth=150) 432 else: 433 if self._boost_button is not None: 434 self._boost_button.delete() 435 self._boost_button = None 436 if self._boost_price is not None: 437 self._boost_price.delete() 438 self._boost_price = None 439 if self._boost_label is not None: 440 self._boost_label.delete() 441 self._boost_label = None 442 443 # if they told us to go ahead and try and connect, do so.. 444 # (note: servers will disconnect us if we try to connect before 445 # getting this go-ahead, so don't get any bright ideas...) 446 if response.get('c', False): 447 # enforce a delay between connection attempts 448 # (in case they're jamming on the boost button) 449 now = time.time() 450 if (self._last_connect_attempt_time is None 451 or now - self._last_connect_attempt_time > 10.0): 452 _ba.connect_to_party(address=self._address, 453 port=self._port, 454 print_progress=False) 455 self._last_connect_attempt_time = now
We've received a response from an update to the server.
def
on_boost_press(self) -> None:
457 def on_boost_press(self) -> None: 458 """Boost was pressed.""" 459 from bastd.ui import account 460 from bastd.ui import getcurrency 461 if _ba.get_v1_account_state() != 'signed_in': 462 account.show_sign_in_prompt() 463 return 464 465 if _ba.get_v1_account_ticket_count() < self._boost_tickets: 466 ba.playsound(ba.getsound('error')) 467 getcurrency.show_get_tickets_prompt() 468 return 469 470 ba.playsound(ba.getsound('laserReverse')) 471 _ba.add_transaction( 472 { 473 'type': 'PARTY_QUEUE_BOOST', 474 't': self._boost_tickets, 475 'q': self._queue_id 476 }, 477 callback=ba.WeakCall(self.on_update_response)) 478 # lets not run these immediately (since they may be rapid-fire, 479 # just bucket them until the next tick) 480 481 # the transaction handles the local ticket change, but we apply our 482 # local boost vis manually here.. 483 # (our visualization isn't really wired up to be transaction-based) 484 our_dude = self._dudes_by_id.get(-1) 485 if our_dude is not None: 486 our_dude.boost(self._boost_strength, self._smoothing)
Boost was pressed.
def
update(self) -> None:
488 def update(self) -> None: 489 """Update!""" 490 if not self._root_widget: 491 return 492 493 # Update boost-price. 494 if self._boost_price is not None: 495 ba.textwidget(edit=self._boost_price, 496 text=ba.charstr(ba.SpecialChar.TICKET) + 497 str(self._boost_tickets)) 498 499 # Update boost button color based on if we have enough moola. 500 if self._boost_button is not None: 501 can_boost = ( 502 (_ba.get_v1_account_state() == 'signed_in' 503 and _ba.get_v1_account_ticket_count() >= self._boost_tickets)) 504 ba.buttonwidget(edit=self._boost_button, 505 color=(0, 1, 0) if can_boost else (0.7, 0.7, 0.7)) 506 507 # Update ticket-count. 508 if self._tickets_text is not None: 509 if self._boost_button is not None: 510 if _ba.get_v1_account_state() == 'signed_in': 511 val = ba.charstr(ba.SpecialChar.TICKET) + str( 512 _ba.get_v1_account_ticket_count()) 513 else: 514 val = ba.charstr(ba.SpecialChar.TICKET) + '???' 515 ba.textwidget(edit=self._tickets_text, text=val) 516 else: 517 ba.textwidget(edit=self._tickets_text, text='') 518 519 current_time = ba.time(ba.TimeType.REAL) 520 if (self._last_transaction_time is None 521 or current_time - self._last_transaction_time > 522 0.001 * _ba.get_v1_account_misc_read_val('pqInt', 5000)): 523 self._last_transaction_time = current_time 524 _ba.add_transaction( 525 { 526 'type': 'PARTY_QUEUE_QUERY', 527 'q': self._queue_id 528 }, 529 callback=ba.WeakCall(self.on_update_response)) 530 _ba.run_transactions() 531 532 # step our dudes 533 for dude in self._dudes: 534 dude.step(self._smoothing)
Update!
Inherited Members
- ba.ui.Window
- get_root_widget
class
PartyQueueWindow.Dude:
25 class Dude: 26 """Represents a single dude waiting in a server line.""" 27 28 def __init__(self, parent: PartyQueueWindow, distance: float, 29 initial_offset: float, is_player: bool, account_id: str, 30 name: str): 31 self.claimed = False 32 self._line_left = parent.get_line_left() 33 self._line_width = parent.get_line_width() 34 self._line_bottom = parent.get_line_bottom() 35 self._target_distance = distance 36 self._distance = distance + initial_offset 37 self._boost_brightness = 0.0 38 self._debug = False 39 self._sc = sc = 1.1 if is_player else 0.6 + random.random() * 0.2 40 self._y_offs = -30.0 if is_player else -47.0 * sc 41 self._last_boost_time = 0.0 42 self._color = (0.2, 1.0, 43 0.2) if is_player else (0.5 + 0.3 * random.random(), 44 0.4 + 0.2 * random.random(), 45 0.5 + 0.3 * random.random()) 46 self._eye_color = (0.7 * 1.0 + 0.3 * self._color[0], 47 0.7 * 1.0 + 0.3 * self._color[1], 48 0.7 * 1.0 + 0.3 * self._color[2]) 49 self._body_image = ba.buttonwidget( 50 parent=parent.get_root_widget(), 51 selectable=True, 52 label='', 53 size=(sc * 60, sc * 80), 54 color=self._color, 55 texture=parent.lineup_tex, 56 model_transparent=parent.lineup_1_transparent_model) 57 ba.buttonwidget(edit=self._body_image, 58 on_activate_call=ba.WeakCall( 59 parent.on_account_press, account_id, 60 self._body_image)) 61 ba.widget(edit=self._body_image, autoselect=True) 62 self._eyes_image = ba.imagewidget( 63 parent=parent.get_root_widget(), 64 size=(sc * 36, sc * 18), 65 texture=parent.lineup_tex, 66 color=self._eye_color, 67 model_transparent=parent.eyes_model) 68 self._name_text = ba.textwidget(parent=parent.get_root_widget(), 69 size=(0, 0), 70 shadow=0, 71 flatness=1.0, 72 text=name, 73 maxwidth=100, 74 h_align='center', 75 v_align='center', 76 scale=0.75, 77 color=(1, 1, 1, 0.6)) 78 self._update_image() 79 80 # DEBUG: vis target pos.. 81 self._body_image_target: ba.Widget | None 82 self._eyes_image_target: ba.Widget | None 83 if self._debug: 84 self._body_image_target = ba.imagewidget( 85 parent=parent.get_root_widget(), 86 size=(sc * 60, sc * 80), 87 color=self._color, 88 texture=parent.lineup_tex, 89 model_transparent=parent.lineup_1_transparent_model) 90 self._eyes_image_target = ba.imagewidget( 91 parent=parent.get_root_widget(), 92 size=(sc * 36, sc * 18), 93 texture=parent.lineup_tex, 94 color=self._eye_color, 95 model_transparent=parent.eyes_model) 96 # (updates our image positions) 97 self.set_target_distance(self._target_distance) 98 else: 99 self._body_image_target = self._eyes_image_target = None 100 101 def __del__(self) -> None: 102 103 # ew. our destructor here may get called as part of an internal 104 # widget tear-down. 105 # running further widget calls here can quietly break stuff, so we 106 # need to push a deferred call to kill these as necessary instead. 107 # (should bulletproof internal widget code to give a clean error 108 # in this case) 109 def kill_widgets(widgets: Sequence[ba.Widget | None]) -> None: 110 for widget in widgets: 111 if widget: 112 widget.delete() 113 114 ba.pushcall( 115 ba.Call(kill_widgets, [ 116 self._body_image, self._eyes_image, 117 self._body_image_target, self._eyes_image_target, 118 self._name_text 119 ])) 120 121 def set_target_distance(self, dist: float) -> None: 122 """Set distance for a dude.""" 123 self._target_distance = dist 124 if self._debug: 125 sc = self._sc 126 position = (self._line_left + self._line_width * 127 (1.0 - self._target_distance), 128 self._line_bottom - 30) 129 ba.imagewidget(edit=self._body_image_target, 130 position=(position[0] - sc * 30, 131 position[1] - sc * 25 - 70)) 132 ba.imagewidget(edit=self._eyes_image_target, 133 position=(position[0] - sc * 18, 134 position[1] + sc * 31 - 70)) 135 136 def step(self, smoothing: float) -> None: 137 """Step this dude.""" 138 self._distance = (smoothing * self._distance + 139 (1.0 - smoothing) * self._target_distance) 140 self._update_image() 141 self._boost_brightness *= 0.9 142 143 def _update_image(self) -> None: 144 sc = self._sc 145 position = (self._line_left + self._line_width * 146 (1.0 - self._distance), self._line_bottom + 40) 147 brightness = 1.0 + self._boost_brightness 148 ba.buttonwidget(edit=self._body_image, 149 position=(position[0] - sc * 30, 150 position[1] - sc * 25 + self._y_offs), 151 color=(self._color[0] * brightness, 152 self._color[1] * brightness, 153 self._color[2] * brightness)) 154 ba.imagewidget(edit=self._eyes_image, 155 position=(position[0] - sc * 18, 156 position[1] + sc * 31 + self._y_offs), 157 color=(self._eye_color[0] * brightness, 158 self._eye_color[1] * brightness, 159 self._eye_color[2] * brightness)) 160 ba.textwidget(edit=self._name_text, 161 position=(position[0] - sc * 0, 162 position[1] + sc * 40.0)) 163 164 def boost(self, amount: float, smoothing: float) -> None: 165 """Boost this dude.""" 166 del smoothing # unused arg 167 self._distance = max(0.0, self._distance - amount) 168 self._update_image() 169 self._last_boost_time = time.time() 170 self._boost_brightness += 0.6
Represents a single dude waiting in a server line.
PartyQueueWindow.Dude( parent: bastd.ui.partyqueue.PartyQueueWindow, distance: float, initial_offset: float, is_player: bool, account_id: str, name: str)
28 def __init__(self, parent: PartyQueueWindow, distance: float, 29 initial_offset: float, is_player: bool, account_id: str, 30 name: str): 31 self.claimed = False 32 self._line_left = parent.get_line_left() 33 self._line_width = parent.get_line_width() 34 self._line_bottom = parent.get_line_bottom() 35 self._target_distance = distance 36 self._distance = distance + initial_offset 37 self._boost_brightness = 0.0 38 self._debug = False 39 self._sc = sc = 1.1 if is_player else 0.6 + random.random() * 0.2 40 self._y_offs = -30.0 if is_player else -47.0 * sc 41 self._last_boost_time = 0.0 42 self._color = (0.2, 1.0, 43 0.2) if is_player else (0.5 + 0.3 * random.random(), 44 0.4 + 0.2 * random.random(), 45 0.5 + 0.3 * random.random()) 46 self._eye_color = (0.7 * 1.0 + 0.3 * self._color[0], 47 0.7 * 1.0 + 0.3 * self._color[1], 48 0.7 * 1.0 + 0.3 * self._color[2]) 49 self._body_image = ba.buttonwidget( 50 parent=parent.get_root_widget(), 51 selectable=True, 52 label='', 53 size=(sc * 60, sc * 80), 54 color=self._color, 55 texture=parent.lineup_tex, 56 model_transparent=parent.lineup_1_transparent_model) 57 ba.buttonwidget(edit=self._body_image, 58 on_activate_call=ba.WeakCall( 59 parent.on_account_press, account_id, 60 self._body_image)) 61 ba.widget(edit=self._body_image, autoselect=True) 62 self._eyes_image = ba.imagewidget( 63 parent=parent.get_root_widget(), 64 size=(sc * 36, sc * 18), 65 texture=parent.lineup_tex, 66 color=self._eye_color, 67 model_transparent=parent.eyes_model) 68 self._name_text = ba.textwidget(parent=parent.get_root_widget(), 69 size=(0, 0), 70 shadow=0, 71 flatness=1.0, 72 text=name, 73 maxwidth=100, 74 h_align='center', 75 v_align='center', 76 scale=0.75, 77 color=(1, 1, 1, 0.6)) 78 self._update_image() 79 80 # DEBUG: vis target pos.. 81 self._body_image_target: ba.Widget | None 82 self._eyes_image_target: ba.Widget | None 83 if self._debug: 84 self._body_image_target = ba.imagewidget( 85 parent=parent.get_root_widget(), 86 size=(sc * 60, sc * 80), 87 color=self._color, 88 texture=parent.lineup_tex, 89 model_transparent=parent.lineup_1_transparent_model) 90 self._eyes_image_target = ba.imagewidget( 91 parent=parent.get_root_widget(), 92 size=(sc * 36, sc * 18), 93 texture=parent.lineup_tex, 94 color=self._eye_color, 95 model_transparent=parent.eyes_model) 96 # (updates our image positions) 97 self.set_target_distance(self._target_distance) 98 else: 99 self._body_image_target = self._eyes_image_target = None
def
set_target_distance(self, dist: float) -> None:
121 def set_target_distance(self, dist: float) -> None: 122 """Set distance for a dude.""" 123 self._target_distance = dist 124 if self._debug: 125 sc = self._sc 126 position = (self._line_left + self._line_width * 127 (1.0 - self._target_distance), 128 self._line_bottom - 30) 129 ba.imagewidget(edit=self._body_image_target, 130 position=(position[0] - sc * 30, 131 position[1] - sc * 25 - 70)) 132 ba.imagewidget(edit=self._eyes_image_target, 133 position=(position[0] - sc * 18, 134 position[1] + sc * 31 - 70))
Set distance for a dude.
def
step(self, smoothing: float) -> None:
136 def step(self, smoothing: float) -> None: 137 """Step this dude.""" 138 self._distance = (smoothing * self._distance + 139 (1.0 - smoothing) * self._target_distance) 140 self._update_image() 141 self._boost_brightness *= 0.9
Step this dude.
def
boost(self, amount: float, smoothing: float) -> None:
164 def boost(self, amount: float, smoothing: float) -> None: 165 """Boost this dude.""" 166 del smoothing # unused arg 167 self._distance = max(0.0, self._distance - amount) 168 self._update_image() 169 self._last_boost_time = time.time() 170 self._boost_brightness += 0.6
Boost this dude.