bastd.ui.partyqueue
UI related to waiting in line for a party.
1# Released under the MIT License. See LICENSE for details. 2# 3"""UI related to waiting in line for a party.""" 4 5from __future__ import annotations 6 7import random 8import time 9from typing import TYPE_CHECKING 10 11import ba 12import ba.internal 13 14if TYPE_CHECKING: 15 from typing import Any, Sequence 16 17 18class PartyQueueWindow(ba.Window): 19 """Window showing players waiting to join a server.""" 20 21 # ewww this needs quite a bit of de-linting if/when i revisit it.. 22 # pylint: disable=invalid-name 23 # pylint: disable=consider-using-dict-comprehension 24 class Dude: 25 """Represents a single dude waiting in a server line.""" 26 27 def __init__( 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 = ba.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 model_transparent=parent.lineup_1_transparent_model, 69 ) 70 ba.buttonwidget( 71 edit=self._body_image, 72 on_activate_call=ba.WeakCall( 73 parent.on_account_press, account_id, self._body_image 74 ), 75 ) 76 ba.widget(edit=self._body_image, autoselect=True) 77 self._eyes_image = ba.imagewidget( 78 parent=parent.get_root_widget(), 79 size=(sc * 36, sc * 18), 80 texture=parent.lineup_tex, 81 color=self._eye_color, 82 model_transparent=parent.eyes_model, 83 ) 84 self._name_text = ba.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: ba.Widget | None 100 self._eyes_image_target: ba.Widget | None 101 if self._debug: 102 self._body_image_target = ba.imagewidget( 103 parent=parent.get_root_widget(), 104 size=(sc * 60, sc * 80), 105 color=self._color, 106 texture=parent.lineup_tex, 107 model_transparent=parent.lineup_1_transparent_model, 108 ) 109 self._eyes_image_target = ba.imagewidget( 110 parent=parent.get_root_widget(), 111 size=(sc * 36, sc * 18), 112 texture=parent.lineup_tex, 113 color=self._eye_color, 114 model_transparent=parent.eyes_model, 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 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[ba.Widget | None]) -> None: 130 for widget in widgets: 131 if widget: 132 widget.delete() 133 134 ba.pushcall( 135 ba.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 ba.imagewidget( 158 edit=self._body_image_target, 159 position=( 160 position[0] - sc * 30, 161 position[1] - sc * 25 - 70, 162 ), 163 ) 164 ba.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 ba.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 ba.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 ba.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 ba.app.ui.have_party_queue_window = True 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: ba.Widget | None = None 235 self._boost_price: ba.Widget | None = None 236 self._boost_label: ba.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 = ba.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_model = ba.getmodel( 249 'angryComputerTransparent' 250 ) 251 self._angry_computer_image: ba.Widget | None = None 252 self.lineup_1_transparent_model = ba.getmodel( 253 'playerLineup1Transparent' 254 ) 255 self._lineup_2_transparent_model = ba.getmodel( 256 'playerLineup2Transparent' 257 ) 258 self._lineup_3_transparent_model = ba.getmodel( 259 'playerLineup3Transparent' 260 ) 261 self._lineup_4_transparent_model = ba.getmodel( 262 'playerLineup4Transparent' 263 ) 264 self._line_image: ba.Widget | None = None 265 self.eyes_model = ba.getmodel('plasticEyesTransparent') 266 self._white_tex = ba.gettexture('white') 267 uiscale = ba.app.ui.uiscale 268 super().__init__( 269 root_widget=ba.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 ba.UIScale.SMALL 276 else 1.2 277 if uiscale is ba.UIScale.MEDIUM 278 else 1.0 279 ), 280 ) 281 ) 282 283 self._cancel_button = ba.buttonwidget( 284 parent=self._root_widget, 285 scale=1.0, 286 position=(60, self._height - 80), 287 size=(50, 50), 288 label='', 289 on_activate_call=self.close, 290 autoselect=True, 291 color=(0.45, 0.63, 0.15), 292 icon=ba.gettexture('crossOut'), 293 iconscale=1.2, 294 ) 295 ba.containerwidget( 296 edit=self._root_widget, cancel_button=self._cancel_button 297 ) 298 299 self._title_text = ba.textwidget( 300 parent=self._root_widget, 301 position=(self._width * 0.5, self._height * 0.55), 302 size=(0, 0), 303 color=(1.0, 3.0, 1.0), 304 scale=1.3, 305 h_align='center', 306 v_align='center', 307 text=ba.Lstr(resource='internal.connectingToPartyText'), 308 maxwidth=self._width * 0.65, 309 ) 310 311 self._tickets_text = ba.textwidget( 312 parent=self._root_widget, 313 position=(self._width - 180, self._height - 20), 314 size=(0, 0), 315 color=(0.2, 1.0, 0.2), 316 scale=0.7, 317 h_align='center', 318 v_align='center', 319 text='', 320 ) 321 322 # update at roughly 30fps 323 self._update_timer = ba.Timer( 324 0.033, 325 ba.WeakCall(self.update), 326 repeat=True, 327 timetype=ba.TimeType.REAL, 328 ) 329 self.update() 330 331 def __del__(self) -> None: 332 try: 333 ba.app.ui.have_party_queue_window = False 334 ba.internal.add_transaction( 335 {'type': 'PARTY_QUEUE_REMOVE', 'q': self._queue_id} 336 ) 337 ba.internal.run_transactions() 338 except Exception: 339 ba.print_exception('Error removing self from party queue.') 340 341 def get_line_left(self) -> float: 342 """(internal)""" 343 return self._line_left 344 345 def get_line_width(self) -> float: 346 """(internal)""" 347 return self._line_width 348 349 def get_line_bottom(self) -> float: 350 """(internal)""" 351 return self._line_bottom 352 353 def on_account_press( 354 self, account_id: str | None, origin_widget: ba.Widget 355 ) -> None: 356 """A dude was clicked so we should show his account info.""" 357 from bastd.ui.account import viewer 358 359 if account_id is None: 360 ba.playsound(ba.getsound('error')) 361 return 362 viewer.AccountViewerWindow( 363 account_id=account_id, 364 position=origin_widget.get_screen_space_center(), 365 ) 366 367 def close(self) -> None: 368 """Close the ui.""" 369 ba.containerwidget(edit=self._root_widget, transition='out_scale') 370 371 def _update_field(self, response: dict[str, Any]) -> None: 372 if self._angry_computer_image is None: 373 self._angry_computer_image = ba.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 model_transparent=self._angry_computer_transparent_model, 379 ) 380 if self._line_image is None: 381 self._line_image = ba.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 ba.internal.get_v1_account_misc_read_val_2( 405 'resolvedAccountID', None 406 ), 407 ba.internal.get_v1_account_display_string(), 408 ) 409 self._dudes_by_id[-1] = dude 410 self._dudes.append(dude) 411 else: 412 self._dudes_by_id[-1].set_target_distance(response['d']) 413 self._dudes_by_id[-1].claimed = True 414 415 # now create/destroy enemies 416 for ( 417 enemy_id, 418 enemy_distance, 419 enemy_account_id, 420 enemy_name, 421 ) in response['e']: 422 if enemy_id not in self._dudes_by_id: 423 dude = self.Dude( 424 self, 425 enemy_distance, 426 self._initial_offset, 427 False, 428 enemy_account_id, 429 enemy_name, 430 ) 431 self._dudes_by_id[enemy_id] = dude 432 self._dudes.append(dude) 433 else: 434 self._dudes_by_id[enemy_id].set_target_distance(enemy_distance) 435 self._dudes_by_id[enemy_id].claimed = True 436 437 # remove unclaimed dudes from both of our lists 438 # noinspection PyUnresolvedReferences 439 self._dudes_by_id = dict( 440 [ 441 item 442 for item in list(self._dudes_by_id.items()) 443 if item[1].claimed 444 ] 445 ) 446 self._dudes = [dude for dude in self._dudes if dude.claimed] 447 448 def _hide_field(self) -> None: 449 if self._angry_computer_image: 450 self._angry_computer_image.delete() 451 self._angry_computer_image = None 452 if self._line_image: 453 self._line_image.delete() 454 self._line_image = None 455 self._dudes = [] 456 self._dudes_by_id = {} 457 458 def on_update_response(self, response: dict[str, Any] | None) -> None: 459 """We've received a response from an update to the server.""" 460 # pylint: disable=too-many-branches 461 if not self._root_widget: 462 return 463 464 # Seeing this in logs; debugging... 465 if not self._title_text: 466 print('PartyQueueWindows update: Have root but no title_text.') 467 return 468 469 if response is not None: 470 should_show_field = response.get('d') is not None 471 self._smoothing = response['s'] 472 self._initial_offset = response['o'] 473 474 # If they gave us a position, show the field. 475 if should_show_field: 476 ba.textwidget( 477 edit=self._title_text, 478 text=ba.Lstr(resource='waitingInLineText'), 479 position=(self._width * 0.5, self._height * 0.85), 480 ) 481 self._update_field(response) 482 self._field_shown = True 483 if not should_show_field and self._field_shown: 484 ba.textwidget( 485 edit=self._title_text, 486 text=ba.Lstr(resource='internal.connectingToPartyText'), 487 position=(self._width * 0.5, self._height * 0.55), 488 ) 489 self._hide_field() 490 self._field_shown = False 491 492 # if they told us there's a boost button, update.. 493 if response.get('bt') is not None: 494 self._boost_tickets = response['bt'] 495 self._boost_strength = response['ba'] 496 if self._boost_button is None: 497 self._boost_button = ba.buttonwidget( 498 parent=self._root_widget, 499 scale=1.0, 500 position=(self._width * 0.5 - 75, 20), 501 size=(150, 100), 502 button_type='square', 503 label='', 504 on_activate_call=self.on_boost_press, 505 enable_sound=False, 506 color=(0, 1, 0), 507 autoselect=True, 508 ) 509 self._boost_label = ba.textwidget( 510 parent=self._root_widget, 511 draw_controller=self._boost_button, 512 position=(self._width * 0.5, 88), 513 size=(0, 0), 514 color=(0.8, 1.0, 0.8), 515 scale=1.5, 516 h_align='center', 517 v_align='center', 518 text=ba.Lstr(resource='boostText'), 519 maxwidth=150, 520 ) 521 self._boost_price = ba.textwidget( 522 parent=self._root_widget, 523 draw_controller=self._boost_button, 524 position=(self._width * 0.5, 50), 525 size=(0, 0), 526 color=(0, 1, 0), 527 scale=0.9, 528 h_align='center', 529 v_align='center', 530 text=ba.charstr(ba.SpecialChar.TICKET) 531 + str(self._boost_tickets), 532 maxwidth=150, 533 ) 534 else: 535 if self._boost_button is not None: 536 self._boost_button.delete() 537 self._boost_button = None 538 if self._boost_price is not None: 539 self._boost_price.delete() 540 self._boost_price = None 541 if self._boost_label is not None: 542 self._boost_label.delete() 543 self._boost_label = None 544 545 # if they told us to go ahead and try and connect, do so.. 546 # (note: servers will disconnect us if we try to connect before 547 # getting this go-ahead, so don't get any bright ideas...) 548 if response.get('c', False): 549 # enforce a delay between connection attempts 550 # (in case they're jamming on the boost button) 551 now = time.time() 552 if ( 553 self._last_connect_attempt_time is None 554 or now - self._last_connect_attempt_time > 10.0 555 ): 556 ba.internal.connect_to_party( 557 address=self._address, 558 port=self._port, 559 print_progress=False, 560 ) 561 self._last_connect_attempt_time = now 562 563 def on_boost_press(self) -> None: 564 """Boost was pressed.""" 565 from bastd.ui import account 566 from bastd.ui import getcurrency 567 568 if ba.internal.get_v1_account_state() != 'signed_in': 569 account.show_sign_in_prompt() 570 return 571 572 if ba.internal.get_v1_account_ticket_count() < self._boost_tickets: 573 ba.playsound(ba.getsound('error')) 574 getcurrency.show_get_tickets_prompt() 575 return 576 577 ba.playsound(ba.getsound('laserReverse')) 578 ba.internal.add_transaction( 579 { 580 'type': 'PARTY_QUEUE_BOOST', 581 't': self._boost_tickets, 582 'q': self._queue_id, 583 }, 584 callback=ba.WeakCall(self.on_update_response), 585 ) 586 # lets not run these immediately (since they may be rapid-fire, 587 # just bucket them until the next tick) 588 589 # the transaction handles the local ticket change, but we apply our 590 # local boost vis manually here.. 591 # (our visualization isn't really wired up to be transaction-based) 592 our_dude = self._dudes_by_id.get(-1) 593 if our_dude is not None: 594 our_dude.boost(self._boost_strength, self._smoothing) 595 596 def update(self) -> None: 597 """Update!""" 598 if not self._root_widget: 599 return 600 601 # Update boost-price. 602 if self._boost_price is not None: 603 ba.textwidget( 604 edit=self._boost_price, 605 text=ba.charstr(ba.SpecialChar.TICKET) 606 + str(self._boost_tickets), 607 ) 608 609 # Update boost button color based on if we have enough moola. 610 if self._boost_button is not None: 611 can_boost = ( 612 ba.internal.get_v1_account_state() == 'signed_in' 613 and ba.internal.get_v1_account_ticket_count() 614 >= self._boost_tickets 615 ) 616 ba.buttonwidget( 617 edit=self._boost_button, 618 color=(0, 1, 0) if can_boost else (0.7, 0.7, 0.7), 619 ) 620 621 # Update ticket-count. 622 if self._tickets_text is not None: 623 if self._boost_button is not None: 624 if ba.internal.get_v1_account_state() == 'signed_in': 625 val = ba.charstr(ba.SpecialChar.TICKET) + str( 626 ba.internal.get_v1_account_ticket_count() 627 ) 628 else: 629 val = ba.charstr(ba.SpecialChar.TICKET) + '???' 630 ba.textwidget(edit=self._tickets_text, text=val) 631 else: 632 ba.textwidget(edit=self._tickets_text, text='') 633 634 current_time = ba.time(ba.TimeType.REAL) 635 if ( 636 self._last_transaction_time is None 637 or current_time - self._last_transaction_time 638 > 0.001 * ba.internal.get_v1_account_misc_read_val('pqInt', 5000) 639 ): 640 self._last_transaction_time = current_time 641 ba.internal.add_transaction( 642 {'type': 'PARTY_QUEUE_QUERY', 'q': self._queue_id}, 643 callback=ba.WeakCall(self.on_update_response), 644 ) 645 ba.internal.run_transactions() 646 647 # step our dudes 648 for dude in self._dudes: 649 dude.step(self._smoothing)
class
PartyQueueWindow(ba.ui.Window):
19class PartyQueueWindow(ba.Window): 20 """Window showing players waiting to join a server.""" 21 22 # ewww this needs quite a bit of de-linting if/when i revisit it.. 23 # pylint: disable=invalid-name 24 # pylint: disable=consider-using-dict-comprehension 25 class Dude: 26 """Represents a single dude waiting in a server line.""" 27 28 def __init__( 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 = ba.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 model_transparent=parent.lineup_1_transparent_model, 70 ) 71 ba.buttonwidget( 72 edit=self._body_image, 73 on_activate_call=ba.WeakCall( 74 parent.on_account_press, account_id, self._body_image 75 ), 76 ) 77 ba.widget(edit=self._body_image, autoselect=True) 78 self._eyes_image = ba.imagewidget( 79 parent=parent.get_root_widget(), 80 size=(sc * 36, sc * 18), 81 texture=parent.lineup_tex, 82 color=self._eye_color, 83 model_transparent=parent.eyes_model, 84 ) 85 self._name_text = ba.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: ba.Widget | None 101 self._eyes_image_target: ba.Widget | None 102 if self._debug: 103 self._body_image_target = ba.imagewidget( 104 parent=parent.get_root_widget(), 105 size=(sc * 60, sc * 80), 106 color=self._color, 107 texture=parent.lineup_tex, 108 model_transparent=parent.lineup_1_transparent_model, 109 ) 110 self._eyes_image_target = ba.imagewidget( 111 parent=parent.get_root_widget(), 112 size=(sc * 36, sc * 18), 113 texture=parent.lineup_tex, 114 color=self._eye_color, 115 model_transparent=parent.eyes_model, 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 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[ba.Widget | None]) -> None: 131 for widget in widgets: 132 if widget: 133 widget.delete() 134 135 ba.pushcall( 136 ba.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 ba.imagewidget( 159 edit=self._body_image_target, 160 position=( 161 position[0] - sc * 30, 162 position[1] - sc * 25 - 70, 163 ), 164 ) 165 ba.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 ba.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 ba.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 ba.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 ba.app.ui.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: ba.Widget | None = None 236 self._boost_price: ba.Widget | None = None 237 self._boost_label: ba.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 = ba.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_model = ba.getmodel( 250 'angryComputerTransparent' 251 ) 252 self._angry_computer_image: ba.Widget | None = None 253 self.lineup_1_transparent_model = ba.getmodel( 254 'playerLineup1Transparent' 255 ) 256 self._lineup_2_transparent_model = ba.getmodel( 257 'playerLineup2Transparent' 258 ) 259 self._lineup_3_transparent_model = ba.getmodel( 260 'playerLineup3Transparent' 261 ) 262 self._lineup_4_transparent_model = ba.getmodel( 263 'playerLineup4Transparent' 264 ) 265 self._line_image: ba.Widget | None = None 266 self.eyes_model = ba.getmodel('plasticEyesTransparent') 267 self._white_tex = ba.gettexture('white') 268 uiscale = ba.app.ui.uiscale 269 super().__init__( 270 root_widget=ba.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 ba.UIScale.SMALL 277 else 1.2 278 if uiscale is ba.UIScale.MEDIUM 279 else 1.0 280 ), 281 ) 282 ) 283 284 self._cancel_button = ba.buttonwidget( 285 parent=self._root_widget, 286 scale=1.0, 287 position=(60, self._height - 80), 288 size=(50, 50), 289 label='', 290 on_activate_call=self.close, 291 autoselect=True, 292 color=(0.45, 0.63, 0.15), 293 icon=ba.gettexture('crossOut'), 294 iconscale=1.2, 295 ) 296 ba.containerwidget( 297 edit=self._root_widget, cancel_button=self._cancel_button 298 ) 299 300 self._title_text = ba.textwidget( 301 parent=self._root_widget, 302 position=(self._width * 0.5, self._height * 0.55), 303 size=(0, 0), 304 color=(1.0, 3.0, 1.0), 305 scale=1.3, 306 h_align='center', 307 v_align='center', 308 text=ba.Lstr(resource='internal.connectingToPartyText'), 309 maxwidth=self._width * 0.65, 310 ) 311 312 self._tickets_text = ba.textwidget( 313 parent=self._root_widget, 314 position=(self._width - 180, self._height - 20), 315 size=(0, 0), 316 color=(0.2, 1.0, 0.2), 317 scale=0.7, 318 h_align='center', 319 v_align='center', 320 text='', 321 ) 322 323 # update at roughly 30fps 324 self._update_timer = ba.Timer( 325 0.033, 326 ba.WeakCall(self.update), 327 repeat=True, 328 timetype=ba.TimeType.REAL, 329 ) 330 self.update() 331 332 def __del__(self) -> None: 333 try: 334 ba.app.ui.have_party_queue_window = False 335 ba.internal.add_transaction( 336 {'type': 'PARTY_QUEUE_REMOVE', 'q': self._queue_id} 337 ) 338 ba.internal.run_transactions() 339 except Exception: 340 ba.print_exception('Error removing self from party queue.') 341 342 def get_line_left(self) -> float: 343 """(internal)""" 344 return self._line_left 345 346 def get_line_width(self) -> float: 347 """(internal)""" 348 return self._line_width 349 350 def get_line_bottom(self) -> float: 351 """(internal)""" 352 return self._line_bottom 353 354 def on_account_press( 355 self, account_id: str | None, origin_widget: ba.Widget 356 ) -> None: 357 """A dude was clicked so we should show his account info.""" 358 from bastd.ui.account import viewer 359 360 if account_id is None: 361 ba.playsound(ba.getsound('error')) 362 return 363 viewer.AccountViewerWindow( 364 account_id=account_id, 365 position=origin_widget.get_screen_space_center(), 366 ) 367 368 def close(self) -> None: 369 """Close the ui.""" 370 ba.containerwidget(edit=self._root_widget, transition='out_scale') 371 372 def _update_field(self, response: dict[str, Any]) -> None: 373 if self._angry_computer_image is None: 374 self._angry_computer_image = ba.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 model_transparent=self._angry_computer_transparent_model, 380 ) 381 if self._line_image is None: 382 self._line_image = ba.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 ba.internal.get_v1_account_misc_read_val_2( 406 'resolvedAccountID', None 407 ), 408 ba.internal.get_v1_account_display_string(), 409 ) 410 self._dudes_by_id[-1] = dude 411 self._dudes.append(dude) 412 else: 413 self._dudes_by_id[-1].set_target_distance(response['d']) 414 self._dudes_by_id[-1].claimed = True 415 416 # now create/destroy enemies 417 for ( 418 enemy_id, 419 enemy_distance, 420 enemy_account_id, 421 enemy_name, 422 ) in response['e']: 423 if enemy_id not in self._dudes_by_id: 424 dude = self.Dude( 425 self, 426 enemy_distance, 427 self._initial_offset, 428 False, 429 enemy_account_id, 430 enemy_name, 431 ) 432 self._dudes_by_id[enemy_id] = dude 433 self._dudes.append(dude) 434 else: 435 self._dudes_by_id[enemy_id].set_target_distance(enemy_distance) 436 self._dudes_by_id[enemy_id].claimed = True 437 438 # remove unclaimed dudes from both of our lists 439 # noinspection PyUnresolvedReferences 440 self._dudes_by_id = dict( 441 [ 442 item 443 for item in list(self._dudes_by_id.items()) 444 if item[1].claimed 445 ] 446 ) 447 self._dudes = [dude for dude in self._dudes if dude.claimed] 448 449 def _hide_field(self) -> None: 450 if self._angry_computer_image: 451 self._angry_computer_image.delete() 452 self._angry_computer_image = None 453 if self._line_image: 454 self._line_image.delete() 455 self._line_image = None 456 self._dudes = [] 457 self._dudes_by_id = {} 458 459 def on_update_response(self, response: dict[str, Any] | None) -> None: 460 """We've received a response from an update to the server.""" 461 # pylint: disable=too-many-branches 462 if not self._root_widget: 463 return 464 465 # Seeing this in logs; debugging... 466 if not self._title_text: 467 print('PartyQueueWindows update: Have root but no title_text.') 468 return 469 470 if response is not None: 471 should_show_field = response.get('d') is not None 472 self._smoothing = response['s'] 473 self._initial_offset = response['o'] 474 475 # If they gave us a position, show the field. 476 if should_show_field: 477 ba.textwidget( 478 edit=self._title_text, 479 text=ba.Lstr(resource='waitingInLineText'), 480 position=(self._width * 0.5, self._height * 0.85), 481 ) 482 self._update_field(response) 483 self._field_shown = True 484 if not should_show_field and self._field_shown: 485 ba.textwidget( 486 edit=self._title_text, 487 text=ba.Lstr(resource='internal.connectingToPartyText'), 488 position=(self._width * 0.5, self._height * 0.55), 489 ) 490 self._hide_field() 491 self._field_shown = False 492 493 # if they told us there's a boost button, update.. 494 if response.get('bt') is not None: 495 self._boost_tickets = response['bt'] 496 self._boost_strength = response['ba'] 497 if self._boost_button is None: 498 self._boost_button = ba.buttonwidget( 499 parent=self._root_widget, 500 scale=1.0, 501 position=(self._width * 0.5 - 75, 20), 502 size=(150, 100), 503 button_type='square', 504 label='', 505 on_activate_call=self.on_boost_press, 506 enable_sound=False, 507 color=(0, 1, 0), 508 autoselect=True, 509 ) 510 self._boost_label = ba.textwidget( 511 parent=self._root_widget, 512 draw_controller=self._boost_button, 513 position=(self._width * 0.5, 88), 514 size=(0, 0), 515 color=(0.8, 1.0, 0.8), 516 scale=1.5, 517 h_align='center', 518 v_align='center', 519 text=ba.Lstr(resource='boostText'), 520 maxwidth=150, 521 ) 522 self._boost_price = ba.textwidget( 523 parent=self._root_widget, 524 draw_controller=self._boost_button, 525 position=(self._width * 0.5, 50), 526 size=(0, 0), 527 color=(0, 1, 0), 528 scale=0.9, 529 h_align='center', 530 v_align='center', 531 text=ba.charstr(ba.SpecialChar.TICKET) 532 + str(self._boost_tickets), 533 maxwidth=150, 534 ) 535 else: 536 if self._boost_button is not None: 537 self._boost_button.delete() 538 self._boost_button = None 539 if self._boost_price is not None: 540 self._boost_price.delete() 541 self._boost_price = None 542 if self._boost_label is not None: 543 self._boost_label.delete() 544 self._boost_label = None 545 546 # if they told us to go ahead and try and connect, do so.. 547 # (note: servers will disconnect us if we try to connect before 548 # getting this go-ahead, so don't get any bright ideas...) 549 if response.get('c', False): 550 # enforce a delay between connection attempts 551 # (in case they're jamming on the boost button) 552 now = time.time() 553 if ( 554 self._last_connect_attempt_time is None 555 or now - self._last_connect_attempt_time > 10.0 556 ): 557 ba.internal.connect_to_party( 558 address=self._address, 559 port=self._port, 560 print_progress=False, 561 ) 562 self._last_connect_attempt_time = now 563 564 def on_boost_press(self) -> None: 565 """Boost was pressed.""" 566 from bastd.ui import account 567 from bastd.ui import getcurrency 568 569 if ba.internal.get_v1_account_state() != 'signed_in': 570 account.show_sign_in_prompt() 571 return 572 573 if ba.internal.get_v1_account_ticket_count() < self._boost_tickets: 574 ba.playsound(ba.getsound('error')) 575 getcurrency.show_get_tickets_prompt() 576 return 577 578 ba.playsound(ba.getsound('laserReverse')) 579 ba.internal.add_transaction( 580 { 581 'type': 'PARTY_QUEUE_BOOST', 582 't': self._boost_tickets, 583 'q': self._queue_id, 584 }, 585 callback=ba.WeakCall(self.on_update_response), 586 ) 587 # lets not run these immediately (since they may be rapid-fire, 588 # just bucket them until the next tick) 589 590 # the transaction handles the local ticket change, but we apply our 591 # local boost vis manually here.. 592 # (our visualization isn't really wired up to be transaction-based) 593 our_dude = self._dudes_by_id.get(-1) 594 if our_dude is not None: 595 our_dude.boost(self._boost_strength, self._smoothing) 596 597 def update(self) -> None: 598 """Update!""" 599 if not self._root_widget: 600 return 601 602 # Update boost-price. 603 if self._boost_price is not None: 604 ba.textwidget( 605 edit=self._boost_price, 606 text=ba.charstr(ba.SpecialChar.TICKET) 607 + str(self._boost_tickets), 608 ) 609 610 # Update boost button color based on if we have enough moola. 611 if self._boost_button is not None: 612 can_boost = ( 613 ba.internal.get_v1_account_state() == 'signed_in' 614 and ba.internal.get_v1_account_ticket_count() 615 >= self._boost_tickets 616 ) 617 ba.buttonwidget( 618 edit=self._boost_button, 619 color=(0, 1, 0) if can_boost else (0.7, 0.7, 0.7), 620 ) 621 622 # Update ticket-count. 623 if self._tickets_text is not None: 624 if self._boost_button is not None: 625 if ba.internal.get_v1_account_state() == 'signed_in': 626 val = ba.charstr(ba.SpecialChar.TICKET) + str( 627 ba.internal.get_v1_account_ticket_count() 628 ) 629 else: 630 val = ba.charstr(ba.SpecialChar.TICKET) + '???' 631 ba.textwidget(edit=self._tickets_text, text=val) 632 else: 633 ba.textwidget(edit=self._tickets_text, text='') 634 635 current_time = ba.time(ba.TimeType.REAL) 636 if ( 637 self._last_transaction_time is None 638 or current_time - self._last_transaction_time 639 > 0.001 * ba.internal.get_v1_account_misc_read_val('pqInt', 5000) 640 ): 641 self._last_transaction_time = current_time 642 ba.internal.add_transaction( 643 {'type': 'PARTY_QUEUE_QUERY', 'q': self._queue_id}, 644 callback=ba.WeakCall(self.on_update_response), 645 ) 646 ba.internal.run_transactions() 647 648 # step our dudes 649 for dude in self._dudes: 650 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 ba.app.ui.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: ba.Widget | None = None 236 self._boost_price: ba.Widget | None = None 237 self._boost_label: ba.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 = ba.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_model = ba.getmodel( 250 'angryComputerTransparent' 251 ) 252 self._angry_computer_image: ba.Widget | None = None 253 self.lineup_1_transparent_model = ba.getmodel( 254 'playerLineup1Transparent' 255 ) 256 self._lineup_2_transparent_model = ba.getmodel( 257 'playerLineup2Transparent' 258 ) 259 self._lineup_3_transparent_model = ba.getmodel( 260 'playerLineup3Transparent' 261 ) 262 self._lineup_4_transparent_model = ba.getmodel( 263 'playerLineup4Transparent' 264 ) 265 self._line_image: ba.Widget | None = None 266 self.eyes_model = ba.getmodel('plasticEyesTransparent') 267 self._white_tex = ba.gettexture('white') 268 uiscale = ba.app.ui.uiscale 269 super().__init__( 270 root_widget=ba.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 ba.UIScale.SMALL 277 else 1.2 278 if uiscale is ba.UIScale.MEDIUM 279 else 1.0 280 ), 281 ) 282 ) 283 284 self._cancel_button = ba.buttonwidget( 285 parent=self._root_widget, 286 scale=1.0, 287 position=(60, self._height - 80), 288 size=(50, 50), 289 label='', 290 on_activate_call=self.close, 291 autoselect=True, 292 color=(0.45, 0.63, 0.15), 293 icon=ba.gettexture('crossOut'), 294 iconscale=1.2, 295 ) 296 ba.containerwidget( 297 edit=self._root_widget, cancel_button=self._cancel_button 298 ) 299 300 self._title_text = ba.textwidget( 301 parent=self._root_widget, 302 position=(self._width * 0.5, self._height * 0.55), 303 size=(0, 0), 304 color=(1.0, 3.0, 1.0), 305 scale=1.3, 306 h_align='center', 307 v_align='center', 308 text=ba.Lstr(resource='internal.connectingToPartyText'), 309 maxwidth=self._width * 0.65, 310 ) 311 312 self._tickets_text = ba.textwidget( 313 parent=self._root_widget, 314 position=(self._width - 180, self._height - 20), 315 size=(0, 0), 316 color=(0.2, 1.0, 0.2), 317 scale=0.7, 318 h_align='center', 319 v_align='center', 320 text='', 321 ) 322 323 # update at roughly 30fps 324 self._update_timer = ba.Timer( 325 0.033, 326 ba.WeakCall(self.update), 327 repeat=True, 328 timetype=ba.TimeType.REAL, 329 ) 330 self.update()
def
on_account_press(self, account_id: str | None, origin_widget: _ba.Widget) -> None:
354 def on_account_press( 355 self, account_id: str | None, origin_widget: ba.Widget 356 ) -> None: 357 """A dude was clicked so we should show his account info.""" 358 from bastd.ui.account import viewer 359 360 if account_id is None: 361 ba.playsound(ba.getsound('error')) 362 return 363 viewer.AccountViewerWindow( 364 account_id=account_id, 365 position=origin_widget.get_screen_space_center(), 366 )
A dude was clicked so we should show his account info.
def
close(self) -> None:
368 def close(self) -> None: 369 """Close the ui.""" 370 ba.containerwidget(edit=self._root_widget, transition='out_scale')
Close the ui.
def
on_update_response(self, response: dict[str, typing.Any] | None) -> None:
459 def on_update_response(self, response: dict[str, Any] | None) -> None: 460 """We've received a response from an update to the server.""" 461 # pylint: disable=too-many-branches 462 if not self._root_widget: 463 return 464 465 # Seeing this in logs; debugging... 466 if not self._title_text: 467 print('PartyQueueWindows update: Have root but no title_text.') 468 return 469 470 if response is not None: 471 should_show_field = response.get('d') is not None 472 self._smoothing = response['s'] 473 self._initial_offset = response['o'] 474 475 # If they gave us a position, show the field. 476 if should_show_field: 477 ba.textwidget( 478 edit=self._title_text, 479 text=ba.Lstr(resource='waitingInLineText'), 480 position=(self._width * 0.5, self._height * 0.85), 481 ) 482 self._update_field(response) 483 self._field_shown = True 484 if not should_show_field and self._field_shown: 485 ba.textwidget( 486 edit=self._title_text, 487 text=ba.Lstr(resource='internal.connectingToPartyText'), 488 position=(self._width * 0.5, self._height * 0.55), 489 ) 490 self._hide_field() 491 self._field_shown = False 492 493 # if they told us there's a boost button, update.. 494 if response.get('bt') is not None: 495 self._boost_tickets = response['bt'] 496 self._boost_strength = response['ba'] 497 if self._boost_button is None: 498 self._boost_button = ba.buttonwidget( 499 parent=self._root_widget, 500 scale=1.0, 501 position=(self._width * 0.5 - 75, 20), 502 size=(150, 100), 503 button_type='square', 504 label='', 505 on_activate_call=self.on_boost_press, 506 enable_sound=False, 507 color=(0, 1, 0), 508 autoselect=True, 509 ) 510 self._boost_label = ba.textwidget( 511 parent=self._root_widget, 512 draw_controller=self._boost_button, 513 position=(self._width * 0.5, 88), 514 size=(0, 0), 515 color=(0.8, 1.0, 0.8), 516 scale=1.5, 517 h_align='center', 518 v_align='center', 519 text=ba.Lstr(resource='boostText'), 520 maxwidth=150, 521 ) 522 self._boost_price = ba.textwidget( 523 parent=self._root_widget, 524 draw_controller=self._boost_button, 525 position=(self._width * 0.5, 50), 526 size=(0, 0), 527 color=(0, 1, 0), 528 scale=0.9, 529 h_align='center', 530 v_align='center', 531 text=ba.charstr(ba.SpecialChar.TICKET) 532 + str(self._boost_tickets), 533 maxwidth=150, 534 ) 535 else: 536 if self._boost_button is not None: 537 self._boost_button.delete() 538 self._boost_button = None 539 if self._boost_price is not None: 540 self._boost_price.delete() 541 self._boost_price = None 542 if self._boost_label is not None: 543 self._boost_label.delete() 544 self._boost_label = None 545 546 # if they told us to go ahead and try and connect, do so.. 547 # (note: servers will disconnect us if we try to connect before 548 # getting this go-ahead, so don't get any bright ideas...) 549 if response.get('c', False): 550 # enforce a delay between connection attempts 551 # (in case they're jamming on the boost button) 552 now = time.time() 553 if ( 554 self._last_connect_attempt_time is None 555 or now - self._last_connect_attempt_time > 10.0 556 ): 557 ba.internal.connect_to_party( 558 address=self._address, 559 port=self._port, 560 print_progress=False, 561 ) 562 self._last_connect_attempt_time = now
We've received a response from an update to the server.
def
on_boost_press(self) -> None:
564 def on_boost_press(self) -> None: 565 """Boost was pressed.""" 566 from bastd.ui import account 567 from bastd.ui import getcurrency 568 569 if ba.internal.get_v1_account_state() != 'signed_in': 570 account.show_sign_in_prompt() 571 return 572 573 if ba.internal.get_v1_account_ticket_count() < self._boost_tickets: 574 ba.playsound(ba.getsound('error')) 575 getcurrency.show_get_tickets_prompt() 576 return 577 578 ba.playsound(ba.getsound('laserReverse')) 579 ba.internal.add_transaction( 580 { 581 'type': 'PARTY_QUEUE_BOOST', 582 't': self._boost_tickets, 583 'q': self._queue_id, 584 }, 585 callback=ba.WeakCall(self.on_update_response), 586 ) 587 # lets not run these immediately (since they may be rapid-fire, 588 # just bucket them until the next tick) 589 590 # the transaction handles the local ticket change, but we apply our 591 # local boost vis manually here.. 592 # (our visualization isn't really wired up to be transaction-based) 593 our_dude = self._dudes_by_id.get(-1) 594 if our_dude is not None: 595 our_dude.boost(self._boost_strength, self._smoothing)
Boost was pressed.
def
update(self) -> None:
597 def update(self) -> None: 598 """Update!""" 599 if not self._root_widget: 600 return 601 602 # Update boost-price. 603 if self._boost_price is not None: 604 ba.textwidget( 605 edit=self._boost_price, 606 text=ba.charstr(ba.SpecialChar.TICKET) 607 + str(self._boost_tickets), 608 ) 609 610 # Update boost button color based on if we have enough moola. 611 if self._boost_button is not None: 612 can_boost = ( 613 ba.internal.get_v1_account_state() == 'signed_in' 614 and ba.internal.get_v1_account_ticket_count() 615 >= self._boost_tickets 616 ) 617 ba.buttonwidget( 618 edit=self._boost_button, 619 color=(0, 1, 0) if can_boost else (0.7, 0.7, 0.7), 620 ) 621 622 # Update ticket-count. 623 if self._tickets_text is not None: 624 if self._boost_button is not None: 625 if ba.internal.get_v1_account_state() == 'signed_in': 626 val = ba.charstr(ba.SpecialChar.TICKET) + str( 627 ba.internal.get_v1_account_ticket_count() 628 ) 629 else: 630 val = ba.charstr(ba.SpecialChar.TICKET) + '???' 631 ba.textwidget(edit=self._tickets_text, text=val) 632 else: 633 ba.textwidget(edit=self._tickets_text, text='') 634 635 current_time = ba.time(ba.TimeType.REAL) 636 if ( 637 self._last_transaction_time is None 638 or current_time - self._last_transaction_time 639 > 0.001 * ba.internal.get_v1_account_misc_read_val('pqInt', 5000) 640 ): 641 self._last_transaction_time = current_time 642 ba.internal.add_transaction( 643 {'type': 'PARTY_QUEUE_QUERY', 'q': self._queue_id}, 644 callback=ba.WeakCall(self.on_update_response), 645 ) 646 ba.internal.run_transactions() 647 648 # step our dudes 649 for dude in self._dudes: 650 dude.step(self._smoothing)
Update!
Inherited Members
- ba.ui.Window
- get_root_widget
class
PartyQueueWindow.Dude:
25 class Dude: 26 """Represents a single dude waiting in a server line.""" 27 28 def __init__( 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 = ba.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 model_transparent=parent.lineup_1_transparent_model, 70 ) 71 ba.buttonwidget( 72 edit=self._body_image, 73 on_activate_call=ba.WeakCall( 74 parent.on_account_press, account_id, self._body_image 75 ), 76 ) 77 ba.widget(edit=self._body_image, autoselect=True) 78 self._eyes_image = ba.imagewidget( 79 parent=parent.get_root_widget(), 80 size=(sc * 36, sc * 18), 81 texture=parent.lineup_tex, 82 color=self._eye_color, 83 model_transparent=parent.eyes_model, 84 ) 85 self._name_text = ba.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: ba.Widget | None 101 self._eyes_image_target: ba.Widget | None 102 if self._debug: 103 self._body_image_target = ba.imagewidget( 104 parent=parent.get_root_widget(), 105 size=(sc * 60, sc * 80), 106 color=self._color, 107 texture=parent.lineup_tex, 108 model_transparent=parent.lineup_1_transparent_model, 109 ) 110 self._eyes_image_target = ba.imagewidget( 111 parent=parent.get_root_widget(), 112 size=(sc * 36, sc * 18), 113 texture=parent.lineup_tex, 114 color=self._eye_color, 115 model_transparent=parent.eyes_model, 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 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[ba.Widget | None]) -> None: 131 for widget in widgets: 132 if widget: 133 widget.delete() 134 135 ba.pushcall( 136 ba.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 ba.imagewidget( 159 edit=self._body_image_target, 160 position=( 161 position[0] - sc * 30, 162 position[1] - sc * 25 - 70, 163 ), 164 ) 165 ba.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 ba.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 ba.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 ba.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: bastd.ui.partyqueue.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 = ba.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 model_transparent=parent.lineup_1_transparent_model, 70 ) 71 ba.buttonwidget( 72 edit=self._body_image, 73 on_activate_call=ba.WeakCall( 74 parent.on_account_press, account_id, self._body_image 75 ), 76 ) 77 ba.widget(edit=self._body_image, autoselect=True) 78 self._eyes_image = ba.imagewidget( 79 parent=parent.get_root_widget(), 80 size=(sc * 36, sc * 18), 81 texture=parent.lineup_tex, 82 color=self._eye_color, 83 model_transparent=parent.eyes_model, 84 ) 85 self._name_text = ba.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: ba.Widget | None 101 self._eyes_image_target: ba.Widget | None 102 if self._debug: 103 self._body_image_target = ba.imagewidget( 104 parent=parent.get_root_widget(), 105 size=(sc * 60, sc * 80), 106 color=self._color, 107 texture=parent.lineup_tex, 108 model_transparent=parent.lineup_1_transparent_model, 109 ) 110 self._eyes_image_target = ba.imagewidget( 111 parent=parent.get_root_widget(), 112 size=(sc * 36, sc * 18), 113 texture=parent.lineup_tex, 114 color=self._eye_color, 115 model_transparent=parent.eyes_model, 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:
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 ba.imagewidget( 159 edit=self._body_image_target, 160 position=( 161 position[0] - sc * 30, 162 position[1] - sc * 25 - 70, 163 ), 164 ) 165 ba.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.