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