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