bacommon.bs
BombSquad specific bits.
1# Released under the MIT License. See LICENSE for details. 2# 3"""BombSquad specific bits.""" 4 5from __future__ import annotations 6 7import datetime 8from enum import Enum 9from dataclasses import dataclass, field 10from typing import Annotated, override, assert_never 11 12from efro.util import pairs_to_flat 13from efro.dataclassio import ioprepped, IOAttrs, IOMultiType 14from efro.message import Message, Response 15 16# Token counts for our various packs. 17TOKENS1_COUNT = 50 18TOKENS2_COUNT = 500 19TOKENS3_COUNT = 1200 20TOKENS4_COUNT = 2600 21 22 23@ioprepped 24@dataclass 25class PrivatePartyMessage(Message): 26 """Message asking about info we need for private-party UI.""" 27 28 need_datacode: Annotated[bool, IOAttrs('d')] 29 30 @override 31 @classmethod 32 def get_response_types(cls) -> list[type[Response] | None]: 33 return [PrivatePartyResponse] 34 35 36@ioprepped 37@dataclass 38class PrivatePartyResponse(Response): 39 """Here's that private party UI info you asked for, boss.""" 40 41 success: Annotated[bool, IOAttrs('s')] 42 tokens: Annotated[int, IOAttrs('t')] 43 gold_pass: Annotated[bool, IOAttrs('g')] 44 datacode: Annotated[str | None, IOAttrs('d')] 45 46 47class ClassicChestAppearance(Enum): 48 """Appearances bombsquad classic chests can have.""" 49 50 UNKNOWN = 'u' 51 DEFAULT = 'd' 52 L1 = 'l1' 53 L2 = 'l2' 54 L3 = 'l3' 55 L4 = 'l4' 56 L5 = 'l5' 57 L6 = 'l6' 58 59 @property 60 def pretty_name(self) -> str: 61 """Pretty name for the chest in English.""" 62 # pylint: disable=too-many-return-statements 63 cls = type(self) 64 65 if self is cls.UNKNOWN: 66 return 'Unknown Chest' 67 if self is cls.DEFAULT: 68 return 'Chest' 69 if self is cls.L1: 70 return 'L1 Chest' 71 if self is cls.L2: 72 return 'L2 Chest' 73 if self is cls.L3: 74 return 'L3 Chest' 75 if self is cls.L4: 76 return 'L4 Chest' 77 if self is cls.L5: 78 return 'L5 Chest' 79 if self is cls.L6: 80 return 'L6 Chest' 81 82 assert_never(self) 83 84 85@ioprepped 86@dataclass 87class ClassicAccountLiveData: 88 """Live account data fed to the client in the bs classic app mode.""" 89 90 @dataclass 91 class Chest: 92 """A lovely chest.""" 93 94 appearance: Annotated[ 95 ClassicChestAppearance, 96 IOAttrs('a', enum_fallback=ClassicChestAppearance.UNKNOWN), 97 ] 98 unlock_time: Annotated[datetime.datetime, IOAttrs('t')] 99 ad_allow_time: Annotated[datetime.datetime | None, IOAttrs('at')] 100 101 class LeagueType(Enum): 102 """Type of league we are in.""" 103 104 BRONZE = 'b' 105 SILVER = 's' 106 GOLD = 'g' 107 DIAMOND = 'd' 108 109 tickets: Annotated[int, IOAttrs('ti')] 110 111 tokens: Annotated[int, IOAttrs('to')] 112 gold_pass: Annotated[bool, IOAttrs('g')] 113 remove_ads: Annotated[bool, IOAttrs('r')] 114 115 achievements: Annotated[int, IOAttrs('a')] 116 achievements_total: Annotated[int, IOAttrs('at')] 117 118 league_type: Annotated[LeagueType | None, IOAttrs('lt')] 119 league_num: Annotated[int | None, IOAttrs('ln')] 120 league_rank: Annotated[int | None, IOAttrs('lr')] 121 122 level: Annotated[int, IOAttrs('lv')] 123 xp: Annotated[int, IOAttrs('xp')] 124 xpmax: Annotated[int, IOAttrs('xpm')] 125 126 inbox_count: Annotated[int, IOAttrs('ibc')] 127 inbox_count_is_max: Annotated[bool, IOAttrs('ibcm')] 128 129 chests: Annotated[dict[str, Chest], IOAttrs('c')] 130 131 132class DisplayItemTypeID(Enum): 133 """Type ID for each of our subclasses.""" 134 135 UNKNOWN = 'u' 136 TICKETS = 't' 137 TOKENS = 'k' 138 TEST = 's' 139 CHEST = 'c' 140 141 142class DisplayItem(IOMultiType[DisplayItemTypeID]): 143 """Some amount of something that can be shown or described. 144 145 Used to depict chest contents or other rewards or prices. 146 """ 147 148 @override 149 @classmethod 150 def get_type_id(cls) -> DisplayItemTypeID: 151 # Require child classes to supply this themselves. If we did a 152 # full type registry/lookup here it would require us to import 153 # everything and would prevent lazy loading. 154 raise NotImplementedError() 155 156 @override 157 @classmethod 158 def get_type(cls, type_id: DisplayItemTypeID) -> type[DisplayItem]: 159 """Return the subclass for each of our type-ids.""" 160 # pylint: disable=cyclic-import 161 162 t = DisplayItemTypeID 163 if type_id is t.UNKNOWN: 164 return UnknownDisplayItem 165 if type_id is t.TICKETS: 166 return TicketsDisplayItem 167 if type_id is t.TOKENS: 168 return TokensDisplayItem 169 if type_id is t.TEST: 170 return TestDisplayItem 171 if type_id is t.CHEST: 172 return ChestDisplayItem 173 174 # Important to make sure we provide all types. 175 assert_never(type_id) 176 177 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 178 """Return a string description and subs for the item. 179 180 These decriptions are baked into the DisplayItemWrapper and 181 should be accessed from there when available. This allows 182 clients to give descriptions even for newer display items they 183 don't recognize. 184 """ 185 raise NotImplementedError() 186 187 # Implement fallbacks so client can digest item lists even if they 188 # contain unrecognized stuff. DisplayItemWrapper contains basic 189 # baked down info that they can still use in such cases. 190 @override 191 @classmethod 192 def get_unknown_type_fallback(cls) -> DisplayItem: 193 return UnknownDisplayItem() 194 195 196@ioprepped 197@dataclass 198class UnknownDisplayItem(DisplayItem): 199 """Something we don't know how to display.""" 200 201 @override 202 @classmethod 203 def get_type_id(cls) -> DisplayItemTypeID: 204 return DisplayItemTypeID.UNKNOWN 205 206 @override 207 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 208 import logging 209 210 # Make noise but don't break. 211 logging.exception( 212 'UnknownDisplayItem.get_description() should never be called.' 213 ' Always access descriptions on the DisplayItemWrapper.' 214 ) 215 return 'Unknown', [] 216 217 218@ioprepped 219@dataclass 220class TicketsDisplayItem(DisplayItem): 221 """Some amount of tickets.""" 222 223 count: Annotated[int, IOAttrs('c')] 224 225 @override 226 @classmethod 227 def get_type_id(cls) -> DisplayItemTypeID: 228 return DisplayItemTypeID.TICKETS 229 230 @override 231 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 232 return '${C} Tickets', [('${C}', str(self.count))] 233 234 235@ioprepped 236@dataclass 237class TokensDisplayItem(DisplayItem): 238 """Some amount of tokens.""" 239 240 count: Annotated[int, IOAttrs('c')] 241 242 @override 243 @classmethod 244 def get_type_id(cls) -> DisplayItemTypeID: 245 return DisplayItemTypeID.TOKENS 246 247 @override 248 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 249 return '${C} Tokens', [('${C}', str(self.count))] 250 251 252@ioprepped 253@dataclass 254class TestDisplayItem(DisplayItem): 255 """Fills usable space for a display-item - good for calibration.""" 256 257 @override 258 @classmethod 259 def get_type_id(cls) -> DisplayItemTypeID: 260 return DisplayItemTypeID.TEST 261 262 @override 263 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 264 return 'Test Display Item Here', [] 265 266 267@ioprepped 268@dataclass 269class ChestDisplayItem(DisplayItem): 270 """Display a chest.""" 271 272 appearance: Annotated[ClassicChestAppearance, IOAttrs('a')] 273 274 @override 275 @classmethod 276 def get_type_id(cls) -> DisplayItemTypeID: 277 return DisplayItemTypeID.CHEST 278 279 @override 280 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 281 return self.appearance.pretty_name, [] 282 283 284@ioprepped 285@dataclass 286class DisplayItemWrapper: 287 """Wraps a DisplayItem and common info.""" 288 289 item: Annotated[DisplayItem, IOAttrs('i')] 290 description: Annotated[str, IOAttrs('d')] 291 description_subs: Annotated[list[str] | None, IOAttrs('s')] 292 293 @classmethod 294 def for_display_item(cls, item: DisplayItem) -> DisplayItemWrapper: 295 """Convenience method to wrap a DisplayItem.""" 296 desc, subs = item.get_description() 297 return DisplayItemWrapper(item, desc, pairs_to_flat(subs)) 298 299 300@ioprepped 301@dataclass 302class ChestInfoMessage(Message): 303 """Request info about a chest.""" 304 305 chest_id: Annotated[str, IOAttrs('i')] 306 307 @override 308 @classmethod 309 def get_response_types(cls) -> list[type[Response] | None]: 310 return [ChestInfoResponse] 311 312 313@ioprepped 314@dataclass 315class ChestInfoResponse(Response): 316 """Here's that chest info you asked for, boss.""" 317 318 @dataclass 319 class Chest: 320 """A lovely chest.""" 321 322 @dataclass 323 class PrizeSet: 324 """A possible set of prizes for this chest.""" 325 326 weight: Annotated[float, IOAttrs('w')] 327 contents: Annotated[list[DisplayItemWrapper], IOAttrs('c')] 328 329 appearance: Annotated[ 330 ClassicChestAppearance, 331 IOAttrs('a', enum_fallback=ClassicChestAppearance.UNKNOWN), 332 ] 333 334 # How much it costs to unlock *now*. 335 unlock_tokens: Annotated[int, IOAttrs('tk')] 336 337 # When it unlocks on its own. 338 unlock_time: Annotated[datetime.datetime, IOAttrs('t')] 339 340 # Possible prizes we contain. 341 prizesets: Annotated[list[PrizeSet], IOAttrs('p')] 342 343 # Are ads allowed now? 344 ad_allow: Annotated[bool, IOAttrs('aa')] 345 346 chest: Annotated[Chest | None, IOAttrs('c')] 347 user_tokens: Annotated[int | None, IOAttrs('t')] 348 349 350class ClientUITypeID(Enum): 351 """Type ID for each of our subclasses.""" 352 353 UNKNOWN = 'u' 354 BASIC = 'b' 355 356 357class ClientUI(IOMultiType[ClientUITypeID]): 358 """Defines some user interface on the client.""" 359 360 @override 361 @classmethod 362 def get_type_id(cls) -> ClientUITypeID: 363 # Require child classes to supply this themselves. If we did a 364 # full type registry/lookup here it would require us to import 365 # everything and would prevent lazy loading. 366 raise NotImplementedError() 367 368 @override 369 @classmethod 370 def get_type(cls, type_id: ClientUITypeID) -> type[ClientUI]: 371 """Return the subclass for each of our type-ids.""" 372 # pylint: disable=cyclic-import 373 out: type[ClientUI] 374 375 t = ClientUITypeID 376 if type_id is t.UNKNOWN: 377 out = UnknownClientUI 378 elif type_id is t.BASIC: 379 out = BasicClientUI 380 else: 381 # Important to make sure we provide all types. 382 assert_never(type_id) 383 return out 384 385 @override 386 @classmethod 387 def get_unknown_type_fallback(cls) -> ClientUI: 388 # If we encounter some future message type we don't know 389 # anything about, drop in a placeholder. 390 return UnknownClientUI() 391 392 393@ioprepped 394@dataclass 395class UnknownClientUI(ClientUI): 396 """Fallback type for unrecognized entries.""" 397 398 @override 399 @classmethod 400 def get_type_id(cls) -> ClientUITypeID: 401 return ClientUITypeID.UNKNOWN 402 403 404class BasicClientUIComponentTypeID(Enum): 405 """Type ID for each of our subclasses.""" 406 407 UNKNOWN = 'u' 408 TEXT = 't' 409 LINK = 'l' 410 BS_CLASSIC_TOURNEY_RESULT = 'ct' 411 DISPLAY_ITEMS = 'di' 412 EXPIRE_TIME = 'd' 413 414 415class BasicClientUIComponent(IOMultiType[BasicClientUIComponentTypeID]): 416 """Top level class for our multitype.""" 417 418 @override 419 @classmethod 420 def get_type_id(cls) -> BasicClientUIComponentTypeID: 421 # Require child classes to supply this themselves. If we did a 422 # full type registry/lookup here it would require us to import 423 # everything and would prevent lazy loading. 424 raise NotImplementedError() 425 426 @override 427 @classmethod 428 def get_type( 429 cls, type_id: BasicClientUIComponentTypeID 430 ) -> type[BasicClientUIComponent]: 431 """Return the subclass for each of our type-ids.""" 432 # pylint: disable=cyclic-import 433 434 t = BasicClientUIComponentTypeID 435 if type_id is t.UNKNOWN: 436 return BasicClientUIComponentUnknown 437 if type_id is t.TEXT: 438 return BasicClientUIComponentText 439 if type_id is t.LINK: 440 return BasicClientUIComponentLink 441 if type_id is t.BS_CLASSIC_TOURNEY_RESULT: 442 return BasicClientUIBsClassicTourneyResult 443 if type_id is t.DISPLAY_ITEMS: 444 return BasicClientUIDisplayItems 445 if type_id is t.EXPIRE_TIME: 446 return BasicClientUIExpireTime 447 448 # Important to make sure we provide all types. 449 assert_never(type_id) 450 451 @override 452 @classmethod 453 def get_unknown_type_fallback(cls) -> BasicClientUIComponent: 454 # If we encounter some future message type we don't know 455 # anything about, drop in a placeholder. 456 return BasicClientUIComponentUnknown() 457 458 459@ioprepped 460@dataclass 461class BasicClientUIComponentUnknown(BasicClientUIComponent): 462 """An unknown basic client component type. 463 464 In practice these should never show up since the master-server 465 generates these on the fly for the client and so should not send 466 clients one they can't digest. 467 """ 468 469 @override 470 @classmethod 471 def get_type_id(cls) -> BasicClientUIComponentTypeID: 472 return BasicClientUIComponentTypeID.UNKNOWN 473 474 475@ioprepped 476@dataclass 477class BasicClientUIComponentText(BasicClientUIComponent): 478 """Show some text in the inbox message.""" 479 480 text: Annotated[str, IOAttrs('t')] 481 subs: Annotated[list[str], IOAttrs('s', store_default=False)] = field( 482 default_factory=list 483 ) 484 scale: Annotated[float, IOAttrs('sc', store_default=False)] = 1.0 485 color: Annotated[ 486 tuple[float, float, float, float], IOAttrs('c', store_default=False) 487 ] = (1.0, 1.0, 1.0, 1.0) 488 spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0 489 spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0 490 491 @override 492 @classmethod 493 def get_type_id(cls) -> BasicClientUIComponentTypeID: 494 return BasicClientUIComponentTypeID.TEXT 495 496 497@ioprepped 498@dataclass 499class BasicClientUIComponentLink(BasicClientUIComponent): 500 """Show a link in the inbox message.""" 501 502 url: Annotated[str, IOAttrs('u')] 503 label: Annotated[str, IOAttrs('l')] 504 subs: Annotated[list[str], IOAttrs('s', store_default=False)] = field( 505 default_factory=list 506 ) 507 spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0 508 spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0 509 510 @override 511 @classmethod 512 def get_type_id(cls) -> BasicClientUIComponentTypeID: 513 return BasicClientUIComponentTypeID.LINK 514 515 516@ioprepped 517@dataclass 518class BasicClientUIBsClassicTourneyResult(BasicClientUIComponent): 519 """Show info about a classic tourney.""" 520 521 tournament_id: Annotated[str, IOAttrs('t')] 522 game: Annotated[str, IOAttrs('g')] 523 players: Annotated[int, IOAttrs('p')] 524 rank: Annotated[int, IOAttrs('r')] 525 trophy: Annotated[str | None, IOAttrs('tr')] 526 prizes: Annotated[list[DisplayItemWrapper], IOAttrs('pr')] 527 528 @override 529 @classmethod 530 def get_type_id(cls) -> BasicClientUIComponentTypeID: 531 return BasicClientUIComponentTypeID.BS_CLASSIC_TOURNEY_RESULT 532 533 534@ioprepped 535@dataclass 536class BasicClientUIDisplayItems(BasicClientUIComponent): 537 """Show some display-items.""" 538 539 items: Annotated[list[DisplayItemWrapper], IOAttrs('d')] 540 width: Annotated[float, IOAttrs('w')] = 100.0 541 spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0 542 spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0 543 544 @override 545 @classmethod 546 def get_type_id(cls) -> BasicClientUIComponentTypeID: 547 return BasicClientUIComponentTypeID.DISPLAY_ITEMS 548 549 550@ioprepped 551@dataclass 552class BasicClientUIExpireTime(BasicClientUIComponent): 553 """Show expire-time.""" 554 555 time: Annotated[datetime.datetime, IOAttrs('d')] 556 spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0 557 spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0 558 559 @override 560 @classmethod 561 def get_type_id(cls) -> BasicClientUIComponentTypeID: 562 return BasicClientUIComponentTypeID.EXPIRE_TIME 563 564 565@ioprepped 566@dataclass 567class BasicClientUI(ClientUI): 568 """A basic UI for the client.""" 569 570 class ButtonLabel(Enum): 571 """Distinct button labels we support.""" 572 573 UNKNOWN = 'u' 574 OK = 'o' 575 APPLY = 'a' 576 CANCEL = 'c' 577 ACCEPT = 'ac' 578 DECLINE = 'dn' 579 IGNORE = 'ig' 580 CLAIM = 'cl' 581 DISCARD = 'd' 582 583 class InteractionStyle(Enum): 584 """Overall interaction styles we support.""" 585 586 UNKNOWN = 'u' 587 BUTTON_POSITIVE = 'p' 588 BUTTON_POSITIVE_NEGATIVE = 'pn' 589 590 components: Annotated[list[BasicClientUIComponent], IOAttrs('s')] 591 592 interaction_style: Annotated[ 593 InteractionStyle, IOAttrs('i', enum_fallback=InteractionStyle.UNKNOWN) 594 ] = InteractionStyle.BUTTON_POSITIVE 595 596 button_label_positive: Annotated[ 597 ButtonLabel, IOAttrs('p', enum_fallback=ButtonLabel.UNKNOWN) 598 ] = ButtonLabel.OK 599 600 button_label_negative: Annotated[ 601 ButtonLabel, IOAttrs('n', enum_fallback=ButtonLabel.UNKNOWN) 602 ] = ButtonLabel.CANCEL 603 604 @override 605 @classmethod 606 def get_type_id(cls) -> ClientUITypeID: 607 return ClientUITypeID.BASIC 608 609 def contains_unknown_elements(self) -> bool: 610 """Whether something within us is an unknown type or enum.""" 611 return ( 612 self.interaction_style is self.InteractionStyle.UNKNOWN 613 or self.button_label_positive is self.ButtonLabel.UNKNOWN 614 or self.button_label_negative is self.ButtonLabel.UNKNOWN 615 or any( 616 c.get_type_id() is BasicClientUIComponentTypeID.UNKNOWN 617 for c in self.components 618 ) 619 ) 620 621 622@ioprepped 623@dataclass 624class ClientUIWrapper: 625 """Wrapper for a ClientUI and its common data.""" 626 627 id: Annotated[str, IOAttrs('i')] 628 createtime: Annotated[datetime.datetime, IOAttrs('c')] 629 ui: Annotated[ClientUI, IOAttrs('e')] 630 631 632@ioprepped 633@dataclass 634class InboxRequestMessage(Message): 635 """Message requesting our inbox.""" 636 637 @override 638 @classmethod 639 def get_response_types(cls) -> list[type[Response] | None]: 640 return [InboxRequestResponse] 641 642 643@ioprepped 644@dataclass 645class InboxRequestResponse(Response): 646 """Here's that inbox contents you asked for, boss.""" 647 648 wrappers: Annotated[list[ClientUIWrapper], IOAttrs('w')] 649 650 # Printable error if something goes wrong. 651 error: Annotated[str | None, IOAttrs('e')] = None 652 653 654class ClientUIAction(Enum): 655 """Types of actions we can run.""" 656 657 BUTTON_PRESS_POSITIVE = 'p' 658 BUTTON_PRESS_NEGATIVE = 'n' 659 660 661class ClientEffectTypeID(Enum): 662 """Type ID for each of our subclasses.""" 663 664 UNKNOWN = 'u' 665 SCREEN_MESSAGE = 'm' 666 SOUND = 's' 667 DELAY = 'd' 668 CHEST_WAIT_TIME_ANIMATION = 't' 669 TICKETS_ANIMATION = 'ta' 670 TOKENS_ANIMATION = 'toa' 671 672 673class ClientEffect(IOMultiType[ClientEffectTypeID]): 674 """Something that can happen on the client. 675 676 This can include screen messages, sounds, visual effects, etc. 677 """ 678 679 @override 680 @classmethod 681 def get_type_id(cls) -> ClientEffectTypeID: 682 # Require child classes to supply this themselves. If we did a 683 # full type registry/lookup here it would require us to import 684 # everything and would prevent lazy loading. 685 raise NotImplementedError() 686 687 @override 688 @classmethod 689 def get_type(cls, type_id: ClientEffectTypeID) -> type[ClientEffect]: 690 """Return the subclass for each of our type-ids.""" 691 # pylint: disable=cyclic-import 692 # pylint: disable=too-many-return-statements 693 694 t = ClientEffectTypeID 695 if type_id is t.UNKNOWN: 696 return ClientEffectUnknown 697 if type_id is t.SCREEN_MESSAGE: 698 return ClientEffectScreenMessage 699 if type_id is t.SOUND: 700 return ClientEffectSound 701 if type_id is t.DELAY: 702 return ClientEffectDelay 703 if type_id is t.CHEST_WAIT_TIME_ANIMATION: 704 return ClientEffectChestWaitTimeAnimation 705 if type_id is t.TICKETS_ANIMATION: 706 return ClientEffectTicketsAnimation 707 if type_id is t.TOKENS_ANIMATION: 708 return ClientEffectTokensAnimation 709 710 # Important to make sure we provide all types. 711 assert_never(type_id) 712 713 @override 714 @classmethod 715 def get_unknown_type_fallback(cls) -> ClientEffect: 716 # If we encounter some future message type we don't know 717 # anything about, drop in a placeholder. 718 return ClientEffectUnknown() 719 720 721@ioprepped 722@dataclass 723class ClientEffectUnknown(ClientEffect): 724 """Fallback substitute for types we don't recognize.""" 725 726 @override 727 @classmethod 728 def get_type_id(cls) -> ClientEffectTypeID: 729 return ClientEffectTypeID.UNKNOWN 730 731 732@ioprepped 733@dataclass 734class ClientEffectScreenMessage(ClientEffect): 735 """Display a screen-message.""" 736 737 message: Annotated[str, IOAttrs('m')] 738 subs: Annotated[list[str], IOAttrs('s')] 739 color: Annotated[tuple[float, float, float], IOAttrs('c')] = (1.0, 1.0, 1.0) 740 741 @override 742 @classmethod 743 def get_type_id(cls) -> ClientEffectTypeID: 744 return ClientEffectTypeID.SCREEN_MESSAGE 745 746 747@ioprepped 748@dataclass 749class ClientEffectSound(ClientEffect): 750 """Play a sound.""" 751 752 class Sound(Enum): 753 """Sounds that can be made alongside the message.""" 754 755 UNKNOWN = 'u' 756 CASH_REGISTER = 'c' 757 ERROR = 'e' 758 POWER_DOWN = 'p' 759 GUN_COCKING = 'g' 760 761 sound: Annotated[Sound, IOAttrs('s', enum_fallback=Sound.UNKNOWN)] 762 volume: Annotated[float, IOAttrs('v')] = 1.0 763 764 @override 765 @classmethod 766 def get_type_id(cls) -> ClientEffectTypeID: 767 return ClientEffectTypeID.SOUND 768 769 770@ioprepped 771@dataclass 772class ClientEffectChestWaitTimeAnimation(ClientEffect): 773 """Animate chest wait time changing.""" 774 775 chestid: Annotated[str, IOAttrs('c')] 776 duration: Annotated[float, IOAttrs('u')] 777 startvalue: Annotated[datetime.datetime, IOAttrs('o')] 778 endvalue: Annotated[datetime.datetime, IOAttrs('n')] 779 780 @override 781 @classmethod 782 def get_type_id(cls) -> ClientEffectTypeID: 783 return ClientEffectTypeID.CHEST_WAIT_TIME_ANIMATION 784 785 786@ioprepped 787@dataclass 788class ClientEffectTicketsAnimation(ClientEffect): 789 """Animate tickets count.""" 790 791 duration: Annotated[float, IOAttrs('u')] 792 startvalue: Annotated[int, IOAttrs('s')] 793 endvalue: Annotated[int, IOAttrs('e')] 794 795 @override 796 @classmethod 797 def get_type_id(cls) -> ClientEffectTypeID: 798 return ClientEffectTypeID.TICKETS_ANIMATION 799 800 801@ioprepped 802@dataclass 803class ClientEffectTokensAnimation(ClientEffect): 804 """Animate tokens count.""" 805 806 duration: Annotated[float, IOAttrs('u')] 807 startvalue: Annotated[int, IOAttrs('s')] 808 endvalue: Annotated[int, IOAttrs('e')] 809 810 @override 811 @classmethod 812 def get_type_id(cls) -> ClientEffectTypeID: 813 return ClientEffectTypeID.TOKENS_ANIMATION 814 815 816@ioprepped 817@dataclass 818class ClientEffectDelay(ClientEffect): 819 """Delay effect processing.""" 820 821 seconds: Annotated[float, IOAttrs('s')] 822 823 @override 824 @classmethod 825 def get_type_id(cls) -> ClientEffectTypeID: 826 return ClientEffectTypeID.DELAY 827 828 829@ioprepped 830@dataclass 831class ClientUIActionMessage(Message): 832 """Do something to a client ui.""" 833 834 id: Annotated[str, IOAttrs('i')] 835 action: Annotated[ClientUIAction, IOAttrs('a')] 836 837 @override 838 @classmethod 839 def get_response_types(cls) -> list[type[Response] | None]: 840 return [ClientUIActionResponse] 841 842 843@ioprepped 844@dataclass 845class ClientUIActionResponse(Response): 846 """Did something to that inbox entry, boss.""" 847 848 class ErrorType(Enum): 849 """Types of errors that may have occurred.""" 850 851 # Probably a future error type we don't recognize. 852 UNKNOWN = 'u' 853 854 # Something went wrong on the server, but specifics are not 855 # relevant. 856 INTERNAL = 'i' 857 858 # The entry expired on the server. In various cases such as 'ok' 859 # buttons this can generally be ignored. 860 EXPIRED = 'e' 861 862 error_type: Annotated[ 863 ErrorType | None, IOAttrs('et', enum_fallback=ErrorType.UNKNOWN) 864 ] 865 866 # User facing error message in the case of errors. 867 error_message: Annotated[str | None, IOAttrs('em')] 868 869 effects: Annotated[list[ClientEffect], IOAttrs('fx')] 870 871 872@ioprepped 873@dataclass 874class ScoreSubmitMessage(Message): 875 """Let the server know we got some score in something.""" 876 877 score_token: Annotated[str, IOAttrs('t')] 878 879 @override 880 @classmethod 881 def get_response_types(cls) -> list[type[Response] | None]: 882 return [ScoreSubmitResponse] 883 884 885@ioprepped 886@dataclass 887class ScoreSubmitResponse(Response): 888 """Did something to that inbox entry, boss.""" 889 890 # Things we should show on our end. 891 effects: Annotated[list[ClientEffect], IOAttrs('fx')] 892 893 894@ioprepped 895@dataclass 896class ChestActionMessage(Message): 897 """Request action about a chest.""" 898 899 class Action(Enum): 900 """Types of actions we can request.""" 901 902 # Unlocking (for free or with tokens). 903 UNLOCK = 'u' 904 905 # Watched an ad to reduce wait. 906 AD = 'ad' 907 908 action: Annotated[Action, IOAttrs('a')] 909 910 # Tokens we are paying (only applies to unlock). 911 token_payment: Annotated[int, IOAttrs('t')] 912 913 chest_id: Annotated[str, IOAttrs('i')] 914 915 @override 916 @classmethod 917 def get_response_types(cls) -> list[type[Response] | None]: 918 return [ChestActionResponse] 919 920 921@ioprepped 922@dataclass 923class ChestActionResponse(Response): 924 """Here's the results of that action you asked for, boss.""" 925 926 # Tokens that were actually charged. 927 tokens_charged: Annotated[int, IOAttrs('t')] = 0 928 929 # If present, signifies the chest has been opened and we should show 930 # the user this stuff that was in it. 931 contents: Annotated[list[DisplayItemWrapper] | None, IOAttrs('c')] = None 932 933 # If contents are present, which of the chest's prize-sets they 934 # represent. 935 prizeindex: Annotated[int, IOAttrs('i')] = 0 936 937 # Printable error if something goes wrong. 938 error: Annotated[str | None, IOAttrs('e')] = None 939 940 # Printable warning. Shown in orange with an error sound. Does not 941 # mean the action failed; only that there's something to tell the 942 # users such as 'It looks like you are faking ad views; stop it or 943 # you won't have ad options anymore.' 944 warning: Annotated[str | None, IOAttrs('w', store_default=False)] = None 945 946 # Printable success message. Shown in green with a cash-register 947 # sound. Can be used for things like successful wait reductions via 948 # ad views. Used in builds earlier than 22311; can remove once 949 # 22311+ is ubiquitous. 950 success_msg: Annotated[str | None, IOAttrs('s', store_default=False)] = None 951 952 # Effects to show on the client. Replaces warning and success_msg in 953 # build 22311 or newer. 954 effects: Annotated[ 955 list[ClientEffect], IOAttrs('fx', store_default=False) 956 ] = field(default_factory=list)
24@ioprepped 25@dataclass 26class PrivatePartyMessage(Message): 27 """Message asking about info we need for private-party UI.""" 28 29 need_datacode: Annotated[bool, IOAttrs('d')] 30 31 @override 32 @classmethod 33 def get_response_types(cls) -> list[type[Response] | None]: 34 return [PrivatePartyResponse]
Message asking about info we need for private-party UI.
37@ioprepped 38@dataclass 39class PrivatePartyResponse(Response): 40 """Here's that private party UI info you asked for, boss.""" 41 42 success: Annotated[bool, IOAttrs('s')] 43 tokens: Annotated[int, IOAttrs('t')] 44 gold_pass: Annotated[bool, IOAttrs('g')] 45 datacode: Annotated[str | None, IOAttrs('d')]
Here's that private party UI info you asked for, boss.
48class ClassicChestAppearance(Enum): 49 """Appearances bombsquad classic chests can have.""" 50 51 UNKNOWN = 'u' 52 DEFAULT = 'd' 53 L1 = 'l1' 54 L2 = 'l2' 55 L3 = 'l3' 56 L4 = 'l4' 57 L5 = 'l5' 58 L6 = 'l6' 59 60 @property 61 def pretty_name(self) -> str: 62 """Pretty name for the chest in English.""" 63 # pylint: disable=too-many-return-statements 64 cls = type(self) 65 66 if self is cls.UNKNOWN: 67 return 'Unknown Chest' 68 if self is cls.DEFAULT: 69 return 'Chest' 70 if self is cls.L1: 71 return 'L1 Chest' 72 if self is cls.L2: 73 return 'L2 Chest' 74 if self is cls.L3: 75 return 'L3 Chest' 76 if self is cls.L4: 77 return 'L4 Chest' 78 if self is cls.L5: 79 return 'L5 Chest' 80 if self is cls.L6: 81 return 'L6 Chest' 82 83 assert_never(self)
Appearances bombsquad classic chests can have.
60 @property 61 def pretty_name(self) -> str: 62 """Pretty name for the chest in English.""" 63 # pylint: disable=too-many-return-statements 64 cls = type(self) 65 66 if self is cls.UNKNOWN: 67 return 'Unknown Chest' 68 if self is cls.DEFAULT: 69 return 'Chest' 70 if self is cls.L1: 71 return 'L1 Chest' 72 if self is cls.L2: 73 return 'L2 Chest' 74 if self is cls.L3: 75 return 'L3 Chest' 76 if self is cls.L4: 77 return 'L4 Chest' 78 if self is cls.L5: 79 return 'L5 Chest' 80 if self is cls.L6: 81 return 'L6 Chest' 82 83 assert_never(self)
Pretty name for the chest in English.
86@ioprepped 87@dataclass 88class ClassicAccountLiveData: 89 """Live account data fed to the client in the bs classic app mode.""" 90 91 @dataclass 92 class Chest: 93 """A lovely chest.""" 94 95 appearance: Annotated[ 96 ClassicChestAppearance, 97 IOAttrs('a', enum_fallback=ClassicChestAppearance.UNKNOWN), 98 ] 99 unlock_time: Annotated[datetime.datetime, IOAttrs('t')] 100 ad_allow_time: Annotated[datetime.datetime | None, IOAttrs('at')] 101 102 class LeagueType(Enum): 103 """Type of league we are in.""" 104 105 BRONZE = 'b' 106 SILVER = 's' 107 GOLD = 'g' 108 DIAMOND = 'd' 109 110 tickets: Annotated[int, IOAttrs('ti')] 111 112 tokens: Annotated[int, IOAttrs('to')] 113 gold_pass: Annotated[bool, IOAttrs('g')] 114 remove_ads: Annotated[bool, IOAttrs('r')] 115 116 achievements: Annotated[int, IOAttrs('a')] 117 achievements_total: Annotated[int, IOAttrs('at')] 118 119 league_type: Annotated[LeagueType | None, IOAttrs('lt')] 120 league_num: Annotated[int | None, IOAttrs('ln')] 121 league_rank: Annotated[int | None, IOAttrs('lr')] 122 123 level: Annotated[int, IOAttrs('lv')] 124 xp: Annotated[int, IOAttrs('xp')] 125 xpmax: Annotated[int, IOAttrs('xpm')] 126 127 inbox_count: Annotated[int, IOAttrs('ibc')] 128 inbox_count_is_max: Annotated[bool, IOAttrs('ibcm')] 129 130 chests: Annotated[dict[str, Chest], IOAttrs('c')]
Live account data fed to the client in the bs classic app mode.
91 @dataclass 92 class Chest: 93 """A lovely chest.""" 94 95 appearance: Annotated[ 96 ClassicChestAppearance, 97 IOAttrs('a', enum_fallback=ClassicChestAppearance.UNKNOWN), 98 ] 99 unlock_time: Annotated[datetime.datetime, IOAttrs('t')] 100 ad_allow_time: Annotated[datetime.datetime | None, IOAttrs('at')]
A lovely chest.
102 class LeagueType(Enum): 103 """Type of league we are in.""" 104 105 BRONZE = 'b' 106 SILVER = 's' 107 GOLD = 'g' 108 DIAMOND = 'd'
Type of league we are in.
133class DisplayItemTypeID(Enum): 134 """Type ID for each of our subclasses.""" 135 136 UNKNOWN = 'u' 137 TICKETS = 't' 138 TOKENS = 'k' 139 TEST = 's' 140 CHEST = 'c'
Type ID for each of our subclasses.
143class DisplayItem(IOMultiType[DisplayItemTypeID]): 144 """Some amount of something that can be shown or described. 145 146 Used to depict chest contents or other rewards or prices. 147 """ 148 149 @override 150 @classmethod 151 def get_type_id(cls) -> DisplayItemTypeID: 152 # Require child classes to supply this themselves. If we did a 153 # full type registry/lookup here it would require us to import 154 # everything and would prevent lazy loading. 155 raise NotImplementedError() 156 157 @override 158 @classmethod 159 def get_type(cls, type_id: DisplayItemTypeID) -> type[DisplayItem]: 160 """Return the subclass for each of our type-ids.""" 161 # pylint: disable=cyclic-import 162 163 t = DisplayItemTypeID 164 if type_id is t.UNKNOWN: 165 return UnknownDisplayItem 166 if type_id is t.TICKETS: 167 return TicketsDisplayItem 168 if type_id is t.TOKENS: 169 return TokensDisplayItem 170 if type_id is t.TEST: 171 return TestDisplayItem 172 if type_id is t.CHEST: 173 return ChestDisplayItem 174 175 # Important to make sure we provide all types. 176 assert_never(type_id) 177 178 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 179 """Return a string description and subs for the item. 180 181 These decriptions are baked into the DisplayItemWrapper and 182 should be accessed from there when available. This allows 183 clients to give descriptions even for newer display items they 184 don't recognize. 185 """ 186 raise NotImplementedError() 187 188 # Implement fallbacks so client can digest item lists even if they 189 # contain unrecognized stuff. DisplayItemWrapper contains basic 190 # baked down info that they can still use in such cases. 191 @override 192 @classmethod 193 def get_unknown_type_fallback(cls) -> DisplayItem: 194 return UnknownDisplayItem()
Some amount of something that can be shown or described.
Used to depict chest contents or other rewards or prices.
149 @override 150 @classmethod 151 def get_type_id(cls) -> DisplayItemTypeID: 152 # Require child classes to supply this themselves. If we did a 153 # full type registry/lookup here it would require us to import 154 # everything and would prevent lazy loading. 155 raise NotImplementedError()
Return the type-id for this subclass.
157 @override 158 @classmethod 159 def get_type(cls, type_id: DisplayItemTypeID) -> type[DisplayItem]: 160 """Return the subclass for each of our type-ids.""" 161 # pylint: disable=cyclic-import 162 163 t = DisplayItemTypeID 164 if type_id is t.UNKNOWN: 165 return UnknownDisplayItem 166 if type_id is t.TICKETS: 167 return TicketsDisplayItem 168 if type_id is t.TOKENS: 169 return TokensDisplayItem 170 if type_id is t.TEST: 171 return TestDisplayItem 172 if type_id is t.CHEST: 173 return ChestDisplayItem 174 175 # Important to make sure we provide all types. 176 assert_never(type_id)
Return the subclass for each of our type-ids.
178 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 179 """Return a string description and subs for the item. 180 181 These decriptions are baked into the DisplayItemWrapper and 182 should be accessed from there when available. This allows 183 clients to give descriptions even for newer display items they 184 don't recognize. 185 """ 186 raise NotImplementedError()
Return a string description and subs for the item.
These decriptions are baked into the DisplayItemWrapper and should be accessed from there when available. This allows clients to give descriptions even for newer display items they don't recognize.
191 @override 192 @classmethod 193 def get_unknown_type_fallback(cls) -> DisplayItem: 194 return UnknownDisplayItem()
Return a fallback object in cases of unrecognized types.
This can allow newer data to remain readable in older environments. Use caution with this option, however, as it effectively modifies data.
197@ioprepped 198@dataclass 199class UnknownDisplayItem(DisplayItem): 200 """Something we don't know how to display.""" 201 202 @override 203 @classmethod 204 def get_type_id(cls) -> DisplayItemTypeID: 205 return DisplayItemTypeID.UNKNOWN 206 207 @override 208 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 209 import logging 210 211 # Make noise but don't break. 212 logging.exception( 213 'UnknownDisplayItem.get_description() should never be called.' 214 ' Always access descriptions on the DisplayItemWrapper.' 215 ) 216 return 'Unknown', []
Something we don't know how to display.
202 @override 203 @classmethod 204 def get_type_id(cls) -> DisplayItemTypeID: 205 return DisplayItemTypeID.UNKNOWN
Return the type-id for this subclass.
207 @override 208 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 209 import logging 210 211 # Make noise but don't break. 212 logging.exception( 213 'UnknownDisplayItem.get_description() should never be called.' 214 ' Always access descriptions on the DisplayItemWrapper.' 215 ) 216 return 'Unknown', []
Return a string description and subs for the item.
These decriptions are baked into the DisplayItemWrapper and should be accessed from there when available. This allows clients to give descriptions even for newer display items they don't recognize.
Inherited Members
219@ioprepped 220@dataclass 221class TicketsDisplayItem(DisplayItem): 222 """Some amount of tickets.""" 223 224 count: Annotated[int, IOAttrs('c')] 225 226 @override 227 @classmethod 228 def get_type_id(cls) -> DisplayItemTypeID: 229 return DisplayItemTypeID.TICKETS 230 231 @override 232 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 233 return '${C} Tickets', [('${C}', str(self.count))]
Some amount of tickets.
226 @override 227 @classmethod 228 def get_type_id(cls) -> DisplayItemTypeID: 229 return DisplayItemTypeID.TICKETS
Return the type-id for this subclass.
231 @override 232 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 233 return '${C} Tickets', [('${C}', str(self.count))]
Return a string description and subs for the item.
These decriptions are baked into the DisplayItemWrapper and should be accessed from there when available. This allows clients to give descriptions even for newer display items they don't recognize.
Inherited Members
236@ioprepped 237@dataclass 238class TokensDisplayItem(DisplayItem): 239 """Some amount of tokens.""" 240 241 count: Annotated[int, IOAttrs('c')] 242 243 @override 244 @classmethod 245 def get_type_id(cls) -> DisplayItemTypeID: 246 return DisplayItemTypeID.TOKENS 247 248 @override 249 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 250 return '${C} Tokens', [('${C}', str(self.count))]
Some amount of tokens.
243 @override 244 @classmethod 245 def get_type_id(cls) -> DisplayItemTypeID: 246 return DisplayItemTypeID.TOKENS
Return the type-id for this subclass.
248 @override 249 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 250 return '${C} Tokens', [('${C}', str(self.count))]
Return a string description and subs for the item.
These decriptions are baked into the DisplayItemWrapper and should be accessed from there when available. This allows clients to give descriptions even for newer display items they don't recognize.
Inherited Members
253@ioprepped 254@dataclass 255class TestDisplayItem(DisplayItem): 256 """Fills usable space for a display-item - good for calibration.""" 257 258 @override 259 @classmethod 260 def get_type_id(cls) -> DisplayItemTypeID: 261 return DisplayItemTypeID.TEST 262 263 @override 264 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 265 return 'Test Display Item Here', []
Fills usable space for a display-item - good for calibration.
258 @override 259 @classmethod 260 def get_type_id(cls) -> DisplayItemTypeID: 261 return DisplayItemTypeID.TEST
Return the type-id for this subclass.
263 @override 264 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 265 return 'Test Display Item Here', []
Return a string description and subs for the item.
These decriptions are baked into the DisplayItemWrapper and should be accessed from there when available. This allows clients to give descriptions even for newer display items they don't recognize.
Inherited Members
268@ioprepped 269@dataclass 270class ChestDisplayItem(DisplayItem): 271 """Display a chest.""" 272 273 appearance: Annotated[ClassicChestAppearance, IOAttrs('a')] 274 275 @override 276 @classmethod 277 def get_type_id(cls) -> DisplayItemTypeID: 278 return DisplayItemTypeID.CHEST 279 280 @override 281 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 282 return self.appearance.pretty_name, []
Display a chest.
275 @override 276 @classmethod 277 def get_type_id(cls) -> DisplayItemTypeID: 278 return DisplayItemTypeID.CHEST
Return the type-id for this subclass.
280 @override 281 def get_description(self) -> tuple[str, list[tuple[str, str]]]: 282 return self.appearance.pretty_name, []
Return a string description and subs for the item.
These decriptions are baked into the DisplayItemWrapper and should be accessed from there when available. This allows clients to give descriptions even for newer display items they don't recognize.
Inherited Members
285@ioprepped 286@dataclass 287class DisplayItemWrapper: 288 """Wraps a DisplayItem and common info.""" 289 290 item: Annotated[DisplayItem, IOAttrs('i')] 291 description: Annotated[str, IOAttrs('d')] 292 description_subs: Annotated[list[str] | None, IOAttrs('s')] 293 294 @classmethod 295 def for_display_item(cls, item: DisplayItem) -> DisplayItemWrapper: 296 """Convenience method to wrap a DisplayItem.""" 297 desc, subs = item.get_description() 298 return DisplayItemWrapper(item, desc, pairs_to_flat(subs))
Wraps a DisplayItem and common info.
301@ioprepped 302@dataclass 303class ChestInfoMessage(Message): 304 """Request info about a chest.""" 305 306 chest_id: Annotated[str, IOAttrs('i')] 307 308 @override 309 @classmethod 310 def get_response_types(cls) -> list[type[Response] | None]: 311 return [ChestInfoResponse]
Request info about a chest.
314@ioprepped 315@dataclass 316class ChestInfoResponse(Response): 317 """Here's that chest info you asked for, boss.""" 318 319 @dataclass 320 class Chest: 321 """A lovely chest.""" 322 323 @dataclass 324 class PrizeSet: 325 """A possible set of prizes for this chest.""" 326 327 weight: Annotated[float, IOAttrs('w')] 328 contents: Annotated[list[DisplayItemWrapper], IOAttrs('c')] 329 330 appearance: Annotated[ 331 ClassicChestAppearance, 332 IOAttrs('a', enum_fallback=ClassicChestAppearance.UNKNOWN), 333 ] 334 335 # How much it costs to unlock *now*. 336 unlock_tokens: Annotated[int, IOAttrs('tk')] 337 338 # When it unlocks on its own. 339 unlock_time: Annotated[datetime.datetime, IOAttrs('t')] 340 341 # Possible prizes we contain. 342 prizesets: Annotated[list[PrizeSet], IOAttrs('p')] 343 344 # Are ads allowed now? 345 ad_allow: Annotated[bool, IOAttrs('aa')] 346 347 chest: Annotated[Chest | None, IOAttrs('c')] 348 user_tokens: Annotated[int | None, IOAttrs('t')]
Here's that chest info you asked for, boss.
319 @dataclass 320 class Chest: 321 """A lovely chest.""" 322 323 @dataclass 324 class PrizeSet: 325 """A possible set of prizes for this chest.""" 326 327 weight: Annotated[float, IOAttrs('w')] 328 contents: Annotated[list[DisplayItemWrapper], IOAttrs('c')] 329 330 appearance: Annotated[ 331 ClassicChestAppearance, 332 IOAttrs('a', enum_fallback=ClassicChestAppearance.UNKNOWN), 333 ] 334 335 # How much it costs to unlock *now*. 336 unlock_tokens: Annotated[int, IOAttrs('tk')] 337 338 # When it unlocks on its own. 339 unlock_time: Annotated[datetime.datetime, IOAttrs('t')] 340 341 # Possible prizes we contain. 342 prizesets: Annotated[list[PrizeSet], IOAttrs('p')] 343 344 # Are ads allowed now? 345 ad_allow: Annotated[bool, IOAttrs('aa')]
A lovely chest.
323 @dataclass 324 class PrizeSet: 325 """A possible set of prizes for this chest.""" 326 327 weight: Annotated[float, IOAttrs('w')] 328 contents: Annotated[list[DisplayItemWrapper], IOAttrs('c')]
A possible set of prizes for this chest.
351class ClientUITypeID(Enum): 352 """Type ID for each of our subclasses.""" 353 354 UNKNOWN = 'u' 355 BASIC = 'b'
Type ID for each of our subclasses.
358class ClientUI(IOMultiType[ClientUITypeID]): 359 """Defines some user interface on the client.""" 360 361 @override 362 @classmethod 363 def get_type_id(cls) -> ClientUITypeID: 364 # Require child classes to supply this themselves. If we did a 365 # full type registry/lookup here it would require us to import 366 # everything and would prevent lazy loading. 367 raise NotImplementedError() 368 369 @override 370 @classmethod 371 def get_type(cls, type_id: ClientUITypeID) -> type[ClientUI]: 372 """Return the subclass for each of our type-ids.""" 373 # pylint: disable=cyclic-import 374 out: type[ClientUI] 375 376 t = ClientUITypeID 377 if type_id is t.UNKNOWN: 378 out = UnknownClientUI 379 elif type_id is t.BASIC: 380 out = BasicClientUI 381 else: 382 # Important to make sure we provide all types. 383 assert_never(type_id) 384 return out 385 386 @override 387 @classmethod 388 def get_unknown_type_fallback(cls) -> ClientUI: 389 # If we encounter some future message type we don't know 390 # anything about, drop in a placeholder. 391 return UnknownClientUI()
Defines some user interface on the client.
361 @override 362 @classmethod 363 def get_type_id(cls) -> ClientUITypeID: 364 # Require child classes to supply this themselves. If we did a 365 # full type registry/lookup here it would require us to import 366 # everything and would prevent lazy loading. 367 raise NotImplementedError()
Return the type-id for this subclass.
369 @override 370 @classmethod 371 def get_type(cls, type_id: ClientUITypeID) -> type[ClientUI]: 372 """Return the subclass for each of our type-ids.""" 373 # pylint: disable=cyclic-import 374 out: type[ClientUI] 375 376 t = ClientUITypeID 377 if type_id is t.UNKNOWN: 378 out = UnknownClientUI 379 elif type_id is t.BASIC: 380 out = BasicClientUI 381 else: 382 # Important to make sure we provide all types. 383 assert_never(type_id) 384 return out
Return the subclass for each of our type-ids.
386 @override 387 @classmethod 388 def get_unknown_type_fallback(cls) -> ClientUI: 389 # If we encounter some future message type we don't know 390 # anything about, drop in a placeholder. 391 return UnknownClientUI()
Return a fallback object in cases of unrecognized types.
This can allow newer data to remain readable in older environments. Use caution with this option, however, as it effectively modifies data.
394@ioprepped 395@dataclass 396class UnknownClientUI(ClientUI): 397 """Fallback type for unrecognized entries.""" 398 399 @override 400 @classmethod 401 def get_type_id(cls) -> ClientUITypeID: 402 return ClientUITypeID.UNKNOWN
Fallback type for unrecognized entries.
399 @override 400 @classmethod 401 def get_type_id(cls) -> ClientUITypeID: 402 return ClientUITypeID.UNKNOWN
Return the type-id for this subclass.
Inherited Members
405class BasicClientUIComponentTypeID(Enum): 406 """Type ID for each of our subclasses.""" 407 408 UNKNOWN = 'u' 409 TEXT = 't' 410 LINK = 'l' 411 BS_CLASSIC_TOURNEY_RESULT = 'ct' 412 DISPLAY_ITEMS = 'di' 413 EXPIRE_TIME = 'd'
Type ID for each of our subclasses.
416class BasicClientUIComponent(IOMultiType[BasicClientUIComponentTypeID]): 417 """Top level class for our multitype.""" 418 419 @override 420 @classmethod 421 def get_type_id(cls) -> BasicClientUIComponentTypeID: 422 # Require child classes to supply this themselves. If we did a 423 # full type registry/lookup here it would require us to import 424 # everything and would prevent lazy loading. 425 raise NotImplementedError() 426 427 @override 428 @classmethod 429 def get_type( 430 cls, type_id: BasicClientUIComponentTypeID 431 ) -> type[BasicClientUIComponent]: 432 """Return the subclass for each of our type-ids.""" 433 # pylint: disable=cyclic-import 434 435 t = BasicClientUIComponentTypeID 436 if type_id is t.UNKNOWN: 437 return BasicClientUIComponentUnknown 438 if type_id is t.TEXT: 439 return BasicClientUIComponentText 440 if type_id is t.LINK: 441 return BasicClientUIComponentLink 442 if type_id is t.BS_CLASSIC_TOURNEY_RESULT: 443 return BasicClientUIBsClassicTourneyResult 444 if type_id is t.DISPLAY_ITEMS: 445 return BasicClientUIDisplayItems 446 if type_id is t.EXPIRE_TIME: 447 return BasicClientUIExpireTime 448 449 # Important to make sure we provide all types. 450 assert_never(type_id) 451 452 @override 453 @classmethod 454 def get_unknown_type_fallback(cls) -> BasicClientUIComponent: 455 # If we encounter some future message type we don't know 456 # anything about, drop in a placeholder. 457 return BasicClientUIComponentUnknown()
Top level class for our multitype.
419 @override 420 @classmethod 421 def get_type_id(cls) -> BasicClientUIComponentTypeID: 422 # Require child classes to supply this themselves. If we did a 423 # full type registry/lookup here it would require us to import 424 # everything and would prevent lazy loading. 425 raise NotImplementedError()
Return the type-id for this subclass.
427 @override 428 @classmethod 429 def get_type( 430 cls, type_id: BasicClientUIComponentTypeID 431 ) -> type[BasicClientUIComponent]: 432 """Return the subclass for each of our type-ids.""" 433 # pylint: disable=cyclic-import 434 435 t = BasicClientUIComponentTypeID 436 if type_id is t.UNKNOWN: 437 return BasicClientUIComponentUnknown 438 if type_id is t.TEXT: 439 return BasicClientUIComponentText 440 if type_id is t.LINK: 441 return BasicClientUIComponentLink 442 if type_id is t.BS_CLASSIC_TOURNEY_RESULT: 443 return BasicClientUIBsClassicTourneyResult 444 if type_id is t.DISPLAY_ITEMS: 445 return BasicClientUIDisplayItems 446 if type_id is t.EXPIRE_TIME: 447 return BasicClientUIExpireTime 448 449 # Important to make sure we provide all types. 450 assert_never(type_id)
Return the subclass for each of our type-ids.
452 @override 453 @classmethod 454 def get_unknown_type_fallback(cls) -> BasicClientUIComponent: 455 # If we encounter some future message type we don't know 456 # anything about, drop in a placeholder. 457 return BasicClientUIComponentUnknown()
Return a fallback object in cases of unrecognized types.
This can allow newer data to remain readable in older environments. Use caution with this option, however, as it effectively modifies data.
460@ioprepped 461@dataclass 462class BasicClientUIComponentUnknown(BasicClientUIComponent): 463 """An unknown basic client component type. 464 465 In practice these should never show up since the master-server 466 generates these on the fly for the client and so should not send 467 clients one they can't digest. 468 """ 469 470 @override 471 @classmethod 472 def get_type_id(cls) -> BasicClientUIComponentTypeID: 473 return BasicClientUIComponentTypeID.UNKNOWN
An unknown basic client component type.
In practice these should never show up since the master-server generates these on the fly for the client and so should not send clients one they can't digest.
470 @override 471 @classmethod 472 def get_type_id(cls) -> BasicClientUIComponentTypeID: 473 return BasicClientUIComponentTypeID.UNKNOWN
Return the type-id for this subclass.
Inherited Members
476@ioprepped 477@dataclass 478class BasicClientUIComponentText(BasicClientUIComponent): 479 """Show some text in the inbox message.""" 480 481 text: Annotated[str, IOAttrs('t')] 482 subs: Annotated[list[str], IOAttrs('s', store_default=False)] = field( 483 default_factory=list 484 ) 485 scale: Annotated[float, IOAttrs('sc', store_default=False)] = 1.0 486 color: Annotated[ 487 tuple[float, float, float, float], IOAttrs('c', store_default=False) 488 ] = (1.0, 1.0, 1.0, 1.0) 489 spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0 490 spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0 491 492 @override 493 @classmethod 494 def get_type_id(cls) -> BasicClientUIComponentTypeID: 495 return BasicClientUIComponentTypeID.TEXT
Show some text in the inbox message.
492 @override 493 @classmethod 494 def get_type_id(cls) -> BasicClientUIComponentTypeID: 495 return BasicClientUIComponentTypeID.TEXT
Return the type-id for this subclass.
Inherited Members
498@ioprepped 499@dataclass 500class BasicClientUIComponentLink(BasicClientUIComponent): 501 """Show a link in the inbox message.""" 502 503 url: Annotated[str, IOAttrs('u')] 504 label: Annotated[str, IOAttrs('l')] 505 subs: Annotated[list[str], IOAttrs('s', store_default=False)] = field( 506 default_factory=list 507 ) 508 spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0 509 spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0 510 511 @override 512 @classmethod 513 def get_type_id(cls) -> BasicClientUIComponentTypeID: 514 return BasicClientUIComponentTypeID.LINK
Show a link in the inbox message.
511 @override 512 @classmethod 513 def get_type_id(cls) -> BasicClientUIComponentTypeID: 514 return BasicClientUIComponentTypeID.LINK
Return the type-id for this subclass.
Inherited Members
517@ioprepped 518@dataclass 519class BasicClientUIBsClassicTourneyResult(BasicClientUIComponent): 520 """Show info about a classic tourney.""" 521 522 tournament_id: Annotated[str, IOAttrs('t')] 523 game: Annotated[str, IOAttrs('g')] 524 players: Annotated[int, IOAttrs('p')] 525 rank: Annotated[int, IOAttrs('r')] 526 trophy: Annotated[str | None, IOAttrs('tr')] 527 prizes: Annotated[list[DisplayItemWrapper], IOAttrs('pr')] 528 529 @override 530 @classmethod 531 def get_type_id(cls) -> BasicClientUIComponentTypeID: 532 return BasicClientUIComponentTypeID.BS_CLASSIC_TOURNEY_RESULT
Show info about a classic tourney.
529 @override 530 @classmethod 531 def get_type_id(cls) -> BasicClientUIComponentTypeID: 532 return BasicClientUIComponentTypeID.BS_CLASSIC_TOURNEY_RESULT
Return the type-id for this subclass.
Inherited Members
535@ioprepped 536@dataclass 537class BasicClientUIDisplayItems(BasicClientUIComponent): 538 """Show some display-items.""" 539 540 items: Annotated[list[DisplayItemWrapper], IOAttrs('d')] 541 width: Annotated[float, IOAttrs('w')] = 100.0 542 spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0 543 spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0 544 545 @override 546 @classmethod 547 def get_type_id(cls) -> BasicClientUIComponentTypeID: 548 return BasicClientUIComponentTypeID.DISPLAY_ITEMS
Show some display-items.
545 @override 546 @classmethod 547 def get_type_id(cls) -> BasicClientUIComponentTypeID: 548 return BasicClientUIComponentTypeID.DISPLAY_ITEMS
Return the type-id for this subclass.
Inherited Members
551@ioprepped 552@dataclass 553class BasicClientUIExpireTime(BasicClientUIComponent): 554 """Show expire-time.""" 555 556 time: Annotated[datetime.datetime, IOAttrs('d')] 557 spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0 558 spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0 559 560 @override 561 @classmethod 562 def get_type_id(cls) -> BasicClientUIComponentTypeID: 563 return BasicClientUIComponentTypeID.EXPIRE_TIME
Show expire-time.
560 @override 561 @classmethod 562 def get_type_id(cls) -> BasicClientUIComponentTypeID: 563 return BasicClientUIComponentTypeID.EXPIRE_TIME
Return the type-id for this subclass.
Inherited Members
566@ioprepped 567@dataclass 568class BasicClientUI(ClientUI): 569 """A basic UI for the client.""" 570 571 class ButtonLabel(Enum): 572 """Distinct button labels we support.""" 573 574 UNKNOWN = 'u' 575 OK = 'o' 576 APPLY = 'a' 577 CANCEL = 'c' 578 ACCEPT = 'ac' 579 DECLINE = 'dn' 580 IGNORE = 'ig' 581 CLAIM = 'cl' 582 DISCARD = 'd' 583 584 class InteractionStyle(Enum): 585 """Overall interaction styles we support.""" 586 587 UNKNOWN = 'u' 588 BUTTON_POSITIVE = 'p' 589 BUTTON_POSITIVE_NEGATIVE = 'pn' 590 591 components: Annotated[list[BasicClientUIComponent], IOAttrs('s')] 592 593 interaction_style: Annotated[ 594 InteractionStyle, IOAttrs('i', enum_fallback=InteractionStyle.UNKNOWN) 595 ] = InteractionStyle.BUTTON_POSITIVE 596 597 button_label_positive: Annotated[ 598 ButtonLabel, IOAttrs('p', enum_fallback=ButtonLabel.UNKNOWN) 599 ] = ButtonLabel.OK 600 601 button_label_negative: Annotated[ 602 ButtonLabel, IOAttrs('n', enum_fallback=ButtonLabel.UNKNOWN) 603 ] = ButtonLabel.CANCEL 604 605 @override 606 @classmethod 607 def get_type_id(cls) -> ClientUITypeID: 608 return ClientUITypeID.BASIC 609 610 def contains_unknown_elements(self) -> bool: 611 """Whether something within us is an unknown type or enum.""" 612 return ( 613 self.interaction_style is self.InteractionStyle.UNKNOWN 614 or self.button_label_positive is self.ButtonLabel.UNKNOWN 615 or self.button_label_negative is self.ButtonLabel.UNKNOWN 616 or any( 617 c.get_type_id() is BasicClientUIComponentTypeID.UNKNOWN 618 for c in self.components 619 ) 620 )
A basic UI for the client.
605 @override 606 @classmethod 607 def get_type_id(cls) -> ClientUITypeID: 608 return ClientUITypeID.BASIC
Return the type-id for this subclass.
610 def contains_unknown_elements(self) -> bool: 611 """Whether something within us is an unknown type or enum.""" 612 return ( 613 self.interaction_style is self.InteractionStyle.UNKNOWN 614 or self.button_label_positive is self.ButtonLabel.UNKNOWN 615 or self.button_label_negative is self.ButtonLabel.UNKNOWN 616 or any( 617 c.get_type_id() is BasicClientUIComponentTypeID.UNKNOWN 618 for c in self.components 619 ) 620 )
Whether something within us is an unknown type or enum.
Inherited Members
571 class ButtonLabel(Enum): 572 """Distinct button labels we support.""" 573 574 UNKNOWN = 'u' 575 OK = 'o' 576 APPLY = 'a' 577 CANCEL = 'c' 578 ACCEPT = 'ac' 579 DECLINE = 'dn' 580 IGNORE = 'ig' 581 CLAIM = 'cl' 582 DISCARD = 'd'
Distinct button labels we support.
584 class InteractionStyle(Enum): 585 """Overall interaction styles we support.""" 586 587 UNKNOWN = 'u' 588 BUTTON_POSITIVE = 'p' 589 BUTTON_POSITIVE_NEGATIVE = 'pn'
Overall interaction styles we support.
623@ioprepped 624@dataclass 625class ClientUIWrapper: 626 """Wrapper for a ClientUI and its common data.""" 627 628 id: Annotated[str, IOAttrs('i')] 629 createtime: Annotated[datetime.datetime, IOAttrs('c')] 630 ui: Annotated[ClientUI, IOAttrs('e')]
Wrapper for a ClientUI and its common data.
633@ioprepped 634@dataclass 635class InboxRequestMessage(Message): 636 """Message requesting our inbox.""" 637 638 @override 639 @classmethod 640 def get_response_types(cls) -> list[type[Response] | None]: 641 return [InboxRequestResponse]
Message requesting our inbox.
644@ioprepped 645@dataclass 646class InboxRequestResponse(Response): 647 """Here's that inbox contents you asked for, boss.""" 648 649 wrappers: Annotated[list[ClientUIWrapper], IOAttrs('w')] 650 651 # Printable error if something goes wrong. 652 error: Annotated[str | None, IOAttrs('e')] = None
Here's that inbox contents you asked for, boss.
655class ClientUIAction(Enum): 656 """Types of actions we can run.""" 657 658 BUTTON_PRESS_POSITIVE = 'p' 659 BUTTON_PRESS_NEGATIVE = 'n'
Types of actions we can run.
662class ClientEffectTypeID(Enum): 663 """Type ID for each of our subclasses.""" 664 665 UNKNOWN = 'u' 666 SCREEN_MESSAGE = 'm' 667 SOUND = 's' 668 DELAY = 'd' 669 CHEST_WAIT_TIME_ANIMATION = 't' 670 TICKETS_ANIMATION = 'ta' 671 TOKENS_ANIMATION = 'toa'
Type ID for each of our subclasses.
674class ClientEffect(IOMultiType[ClientEffectTypeID]): 675 """Something that can happen on the client. 676 677 This can include screen messages, sounds, visual effects, etc. 678 """ 679 680 @override 681 @classmethod 682 def get_type_id(cls) -> ClientEffectTypeID: 683 # Require child classes to supply this themselves. If we did a 684 # full type registry/lookup here it would require us to import 685 # everything and would prevent lazy loading. 686 raise NotImplementedError() 687 688 @override 689 @classmethod 690 def get_type(cls, type_id: ClientEffectTypeID) -> type[ClientEffect]: 691 """Return the subclass for each of our type-ids.""" 692 # pylint: disable=cyclic-import 693 # pylint: disable=too-many-return-statements 694 695 t = ClientEffectTypeID 696 if type_id is t.UNKNOWN: 697 return ClientEffectUnknown 698 if type_id is t.SCREEN_MESSAGE: 699 return ClientEffectScreenMessage 700 if type_id is t.SOUND: 701 return ClientEffectSound 702 if type_id is t.DELAY: 703 return ClientEffectDelay 704 if type_id is t.CHEST_WAIT_TIME_ANIMATION: 705 return ClientEffectChestWaitTimeAnimation 706 if type_id is t.TICKETS_ANIMATION: 707 return ClientEffectTicketsAnimation 708 if type_id is t.TOKENS_ANIMATION: 709 return ClientEffectTokensAnimation 710 711 # Important to make sure we provide all types. 712 assert_never(type_id) 713 714 @override 715 @classmethod 716 def get_unknown_type_fallback(cls) -> ClientEffect: 717 # If we encounter some future message type we don't know 718 # anything about, drop in a placeholder. 719 return ClientEffectUnknown()
Something that can happen on the client.
This can include screen messages, sounds, visual effects, etc.
680 @override 681 @classmethod 682 def get_type_id(cls) -> ClientEffectTypeID: 683 # Require child classes to supply this themselves. If we did a 684 # full type registry/lookup here it would require us to import 685 # everything and would prevent lazy loading. 686 raise NotImplementedError()
Return the type-id for this subclass.
688 @override 689 @classmethod 690 def get_type(cls, type_id: ClientEffectTypeID) -> type[ClientEffect]: 691 """Return the subclass for each of our type-ids.""" 692 # pylint: disable=cyclic-import 693 # pylint: disable=too-many-return-statements 694 695 t = ClientEffectTypeID 696 if type_id is t.UNKNOWN: 697 return ClientEffectUnknown 698 if type_id is t.SCREEN_MESSAGE: 699 return ClientEffectScreenMessage 700 if type_id is t.SOUND: 701 return ClientEffectSound 702 if type_id is t.DELAY: 703 return ClientEffectDelay 704 if type_id is t.CHEST_WAIT_TIME_ANIMATION: 705 return ClientEffectChestWaitTimeAnimation 706 if type_id is t.TICKETS_ANIMATION: 707 return ClientEffectTicketsAnimation 708 if type_id is t.TOKENS_ANIMATION: 709 return ClientEffectTokensAnimation 710 711 # Important to make sure we provide all types. 712 assert_never(type_id)
Return the subclass for each of our type-ids.
714 @override 715 @classmethod 716 def get_unknown_type_fallback(cls) -> ClientEffect: 717 # If we encounter some future message type we don't know 718 # anything about, drop in a placeholder. 719 return ClientEffectUnknown()
Return a fallback object in cases of unrecognized types.
This can allow newer data to remain readable in older environments. Use caution with this option, however, as it effectively modifies data.
722@ioprepped 723@dataclass 724class ClientEffectUnknown(ClientEffect): 725 """Fallback substitute for types we don't recognize.""" 726 727 @override 728 @classmethod 729 def get_type_id(cls) -> ClientEffectTypeID: 730 return ClientEffectTypeID.UNKNOWN
Fallback substitute for types we don't recognize.
727 @override 728 @classmethod 729 def get_type_id(cls) -> ClientEffectTypeID: 730 return ClientEffectTypeID.UNKNOWN
Return the type-id for this subclass.
Inherited Members
733@ioprepped 734@dataclass 735class ClientEffectScreenMessage(ClientEffect): 736 """Display a screen-message.""" 737 738 message: Annotated[str, IOAttrs('m')] 739 subs: Annotated[list[str], IOAttrs('s')] 740 color: Annotated[tuple[float, float, float], IOAttrs('c')] = (1.0, 1.0, 1.0) 741 742 @override 743 @classmethod 744 def get_type_id(cls) -> ClientEffectTypeID: 745 return ClientEffectTypeID.SCREEN_MESSAGE
Display a screen-message.
742 @override 743 @classmethod 744 def get_type_id(cls) -> ClientEffectTypeID: 745 return ClientEffectTypeID.SCREEN_MESSAGE
Return the type-id for this subclass.
Inherited Members
748@ioprepped 749@dataclass 750class ClientEffectSound(ClientEffect): 751 """Play a sound.""" 752 753 class Sound(Enum): 754 """Sounds that can be made alongside the message.""" 755 756 UNKNOWN = 'u' 757 CASH_REGISTER = 'c' 758 ERROR = 'e' 759 POWER_DOWN = 'p' 760 GUN_COCKING = 'g' 761 762 sound: Annotated[Sound, IOAttrs('s', enum_fallback=Sound.UNKNOWN)] 763 volume: Annotated[float, IOAttrs('v')] = 1.0 764 765 @override 766 @classmethod 767 def get_type_id(cls) -> ClientEffectTypeID: 768 return ClientEffectTypeID.SOUND
Play a sound.
765 @override 766 @classmethod 767 def get_type_id(cls) -> ClientEffectTypeID: 768 return ClientEffectTypeID.SOUND
Return the type-id for this subclass.
Inherited Members
753 class Sound(Enum): 754 """Sounds that can be made alongside the message.""" 755 756 UNKNOWN = 'u' 757 CASH_REGISTER = 'c' 758 ERROR = 'e' 759 POWER_DOWN = 'p' 760 GUN_COCKING = 'g'
Sounds that can be made alongside the message.
771@ioprepped 772@dataclass 773class ClientEffectChestWaitTimeAnimation(ClientEffect): 774 """Animate chest wait time changing.""" 775 776 chestid: Annotated[str, IOAttrs('c')] 777 duration: Annotated[float, IOAttrs('u')] 778 startvalue: Annotated[datetime.datetime, IOAttrs('o')] 779 endvalue: Annotated[datetime.datetime, IOAttrs('n')] 780 781 @override 782 @classmethod 783 def get_type_id(cls) -> ClientEffectTypeID: 784 return ClientEffectTypeID.CHEST_WAIT_TIME_ANIMATION
Animate chest wait time changing.
781 @override 782 @classmethod 783 def get_type_id(cls) -> ClientEffectTypeID: 784 return ClientEffectTypeID.CHEST_WAIT_TIME_ANIMATION
Return the type-id for this subclass.
Inherited Members
787@ioprepped 788@dataclass 789class ClientEffectTicketsAnimation(ClientEffect): 790 """Animate tickets count.""" 791 792 duration: Annotated[float, IOAttrs('u')] 793 startvalue: Annotated[int, IOAttrs('s')] 794 endvalue: Annotated[int, IOAttrs('e')] 795 796 @override 797 @classmethod 798 def get_type_id(cls) -> ClientEffectTypeID: 799 return ClientEffectTypeID.TICKETS_ANIMATION
Animate tickets count.
796 @override 797 @classmethod 798 def get_type_id(cls) -> ClientEffectTypeID: 799 return ClientEffectTypeID.TICKETS_ANIMATION
Return the type-id for this subclass.
Inherited Members
802@ioprepped 803@dataclass 804class ClientEffectTokensAnimation(ClientEffect): 805 """Animate tokens count.""" 806 807 duration: Annotated[float, IOAttrs('u')] 808 startvalue: Annotated[int, IOAttrs('s')] 809 endvalue: Annotated[int, IOAttrs('e')] 810 811 @override 812 @classmethod 813 def get_type_id(cls) -> ClientEffectTypeID: 814 return ClientEffectTypeID.TOKENS_ANIMATION
Animate tokens count.
811 @override 812 @classmethod 813 def get_type_id(cls) -> ClientEffectTypeID: 814 return ClientEffectTypeID.TOKENS_ANIMATION
Return the type-id for this subclass.
Inherited Members
817@ioprepped 818@dataclass 819class ClientEffectDelay(ClientEffect): 820 """Delay effect processing.""" 821 822 seconds: Annotated[float, IOAttrs('s')] 823 824 @override 825 @classmethod 826 def get_type_id(cls) -> ClientEffectTypeID: 827 return ClientEffectTypeID.DELAY
Delay effect processing.
824 @override 825 @classmethod 826 def get_type_id(cls) -> ClientEffectTypeID: 827 return ClientEffectTypeID.DELAY
Return the type-id for this subclass.
Inherited Members
830@ioprepped 831@dataclass 832class ClientUIActionMessage(Message): 833 """Do something to a client ui.""" 834 835 id: Annotated[str, IOAttrs('i')] 836 action: Annotated[ClientUIAction, IOAttrs('a')] 837 838 @override 839 @classmethod 840 def get_response_types(cls) -> list[type[Response] | None]: 841 return [ClientUIActionResponse]
Do something to a client ui.
844@ioprepped 845@dataclass 846class ClientUIActionResponse(Response): 847 """Did something to that inbox entry, boss.""" 848 849 class ErrorType(Enum): 850 """Types of errors that may have occurred.""" 851 852 # Probably a future error type we don't recognize. 853 UNKNOWN = 'u' 854 855 # Something went wrong on the server, but specifics are not 856 # relevant. 857 INTERNAL = 'i' 858 859 # The entry expired on the server. In various cases such as 'ok' 860 # buttons this can generally be ignored. 861 EXPIRED = 'e' 862 863 error_type: Annotated[ 864 ErrorType | None, IOAttrs('et', enum_fallback=ErrorType.UNKNOWN) 865 ] 866 867 # User facing error message in the case of errors. 868 error_message: Annotated[str | None, IOAttrs('em')] 869 870 effects: Annotated[list[ClientEffect], IOAttrs('fx')]
Did something to that inbox entry, boss.
849 class ErrorType(Enum): 850 """Types of errors that may have occurred.""" 851 852 # Probably a future error type we don't recognize. 853 UNKNOWN = 'u' 854 855 # Something went wrong on the server, but specifics are not 856 # relevant. 857 INTERNAL = 'i' 858 859 # The entry expired on the server. In various cases such as 'ok' 860 # buttons this can generally be ignored. 861 EXPIRED = 'e'
Types of errors that may have occurred.
873@ioprepped 874@dataclass 875class ScoreSubmitMessage(Message): 876 """Let the server know we got some score in something.""" 877 878 score_token: Annotated[str, IOAttrs('t')] 879 880 @override 881 @classmethod 882 def get_response_types(cls) -> list[type[Response] | None]: 883 return [ScoreSubmitResponse]
Let the server know we got some score in something.
886@ioprepped 887@dataclass 888class ScoreSubmitResponse(Response): 889 """Did something to that inbox entry, boss.""" 890 891 # Things we should show on our end. 892 effects: Annotated[list[ClientEffect], IOAttrs('fx')]
Did something to that inbox entry, boss.
895@ioprepped 896@dataclass 897class ChestActionMessage(Message): 898 """Request action about a chest.""" 899 900 class Action(Enum): 901 """Types of actions we can request.""" 902 903 # Unlocking (for free or with tokens). 904 UNLOCK = 'u' 905 906 # Watched an ad to reduce wait. 907 AD = 'ad' 908 909 action: Annotated[Action, IOAttrs('a')] 910 911 # Tokens we are paying (only applies to unlock). 912 token_payment: Annotated[int, IOAttrs('t')] 913 914 chest_id: Annotated[str, IOAttrs('i')] 915 916 @override 917 @classmethod 918 def get_response_types(cls) -> list[type[Response] | None]: 919 return [ChestActionResponse]
Request action about a chest.
900 class Action(Enum): 901 """Types of actions we can request.""" 902 903 # Unlocking (for free or with tokens). 904 UNLOCK = 'u' 905 906 # Watched an ad to reduce wait. 907 AD = 'ad'
Types of actions we can request.
922@ioprepped 923@dataclass 924class ChestActionResponse(Response): 925 """Here's the results of that action you asked for, boss.""" 926 927 # Tokens that were actually charged. 928 tokens_charged: Annotated[int, IOAttrs('t')] = 0 929 930 # If present, signifies the chest has been opened and we should show 931 # the user this stuff that was in it. 932 contents: Annotated[list[DisplayItemWrapper] | None, IOAttrs('c')] = None 933 934 # If contents are present, which of the chest's prize-sets they 935 # represent. 936 prizeindex: Annotated[int, IOAttrs('i')] = 0 937 938 # Printable error if something goes wrong. 939 error: Annotated[str | None, IOAttrs('e')] = None 940 941 # Printable warning. Shown in orange with an error sound. Does not 942 # mean the action failed; only that there's something to tell the 943 # users such as 'It looks like you are faking ad views; stop it or 944 # you won't have ad options anymore.' 945 warning: Annotated[str | None, IOAttrs('w', store_default=False)] = None 946 947 # Printable success message. Shown in green with a cash-register 948 # sound. Can be used for things like successful wait reductions via 949 # ad views. Used in builds earlier than 22311; can remove once 950 # 22311+ is ubiquitous. 951 success_msg: Annotated[str | None, IOAttrs('s', store_default=False)] = None 952 953 # Effects to show on the client. Replaces warning and success_msg in 954 # build 22311 or newer. 955 effects: Annotated[ 956 list[ClientEffect], IOAttrs('fx', store_default=False) 957 ] = field(default_factory=list)
Here's the results of that action you asked for, boss.