
    Ii?;                        S r SSKJrJr  SSKJrJr  SSKJrJ	r	  SSK
r
SSKJr  SSKJr  SSKJr  SS	KJr  S
SKJr  S
SKJrJr  S
SKJrJr  S
SKJr  S
SKJr  S
SK J!r!  S
SK"J#r#  S
SK$J%r%  S
SK&J'r'  S
SK(J)r)  S
SK*J+r+  S
SK,J-r-  S
SK.J/r/  S
SK0J1r1J2r2  S
SK3J4r4  S
SK5J6r6  S
SK7J8r8  S
SK9J:r:  SSK;J<r<  \ " S S5      5       r=\ " S  S!\5      5       r>\r\ " S" S#\\\\!\#\%\'\)\+\-\/\1\4\6\8\\:5      5       r?g)$z%Base game class and player dataclass.    )	dataclassfield)AnyClassVar)ABCabstractmethodN)DataClassJSONMixin)
BaseConfig)User)Bot   )	ActionSet)GameOptionsOptionsHandlerMixin)
GameResultPlayerResult)TeamManager)GameSoundMixin)GameCommunicationMixin)GameResultMixin)DurationEstimateMixin)GameScoresMixin)GamePredictionMixin)TurnManagementMixin)MenuManagementMixin)ActionVisibilityMixin)LobbyActionsMixin	BOT_NAMES)EventHandlingMixin)ActionSetCreationMixin)ActionExecutionMixin)ActionSetSystemMixin)Keybindc                   P    \ rS rSr% SrSr\S-  \S'   Sr\	S-  \S'   Sr
\\S'   Srg)	ActionContext%   a  Context passed to action handlers when triggered by keybind.

Attributes:
    menu_item_id: ID of the selected menu item when the keybind fired.
    menu_index: 1-based index of the selected menu item.
    from_keybind: True if triggered via keybind, False if via menu.
Nmenu_item_id
menu_indexFfrom_keybind )__name__
__module____qualname____firstlineno____doc__r'   str__annotations__r(   intr)   bool__static_attributes__r*       0c:\Users\dbart\PlayPalace11\server\games\base.pyr%   r%   %   s/      $L#*#!Jd
!L$r5   r%   c                       \ rS rSr% Sr\\S'   \\S'   Sr\\S'   Sr	\\S'   Sr
\\S'   S	r\\S
'   Sr\S-  \S'   Sr\S-  \S'   Sr\\S'   Srg)Player4   a  A player in a game (serialized with game state).

The associated User object is not serialized and is reattached after load.

Attributes:
    id: Unique identifier (user UUID for humans, generated for bots).
    name: Display name.
    is_bot: True for bot players.
    is_virtual_bot: True for server-level virtual bots (count in stats).
    is_spectator: True if spectating.
    bot_think_ticks: Ticks until bot can act.
    bot_pending_action: Action to execute when ready.
    bot_target: Game-specific target (e.g., score to reach).
idnameFis_botis_virtual_botis_spectatorr   bot_think_ticksNbot_pending_action
bot_targetreplaced_humanr*   )r+   r,   r-   r.   r/   r0   r1   r<   r3   r=   r>   r?   r2   r@   rA   rB   r4   r*   r5   r6   r8   r8   4   sb     	G
IFD ND L$OS%)d
)!Jd
! ND r5   r8   c            	          \ rS rSr% Sr " S S\5      r\" \S9r	\\
   \S'   Sr\\S'   S	r\\S
'   Sr\\S'   Sr\\S'   Sr\\S'   Sr\\S'   Sr\\S'   Sr\\S'   Sr\\S'   \" \S9r\\   \S'   Sr\\S'   Sr\\S'   \" \S9r\\S'   Sr\\S'   \" \S9r\\\\\4      \S'   S	r \\S'   \" \S9r!\\\\"   4   \S'   \" \#S9r$\#\S'   S r%SBS" jr&\'\(S \4S# j5       5       r)\'\(S \4S$ j5       5       r*/ r+\,\\      \S%'   \'S \4S& j5       r-\'S \4S' j5       r.\'S \4S( j5       r/\'S \4S) j5       r0\'S \\   4S* j5       r1S \\   \\\\4      -  4S+ jr2S,\S \S!-  4S- jr3SBS. jr4SCS0 jr5\(SBS1 j5       r6SBS2 jr7SBS3 jr8S4\S5\9S S!4S6 jr:S/\
S \9S!-  4S7 jr;S4\S \
S!-  4S8 jr<S9\S \
S!-  4S: jr=SBS; jr> SDS/\
S!-  S<\S=\S S!4S> jjr?S4\S \\\\4      4S? jr@\AS \#4S@ j5       rBSArCg!)EGameU   a  Abstract base class for all games.

Games are dataclasses serialized with Mashumaro. All authoritative state
must live in dataclass fields; runtime-only objects are rebuilt after load.

Responsibilities:
    - Maintain authoritative game state.
    - Expose actions and keybinds for players.
    - Advance turns and manage lifecycle (waiting/playing/finished).
    - Provide menu content for the client.

Phases:
    - waiting: Lobby phase; host can add bots and start.
    - playing: Game in progress.
    - finished: Game over.

Notes:
    - Use broadcast_l / broadcast_personal_l for table transcript messages.
    - Use user.speak_l for command responses or private status checks.
c                       \ rS rSrSrSrg)Game.Config~   Tr*   N)r+   r,   r-   r.   serialize_by_aliasr4   r*   r5   r6   ConfigrG   ~   s    !r5   rJ   )default_factoryplayersr   roundFgame_activewaitingstatus hostcurrent_musiccurrent_ambience
turn_index   turn_directionturn_skip_countturn_player_idsidleround_timer_stateround_timer_ticksscheduled_soundssound_scheduler_tickevent_queueis_animatingplayer_action_sets_team_managerc                    0 U l         SU l        0 U l        0 U l        0 U l        [        5       U l        [        5       U l        SU l        / U l	        / U l
        / U l        SU l        [        R                  " 5       U l        0 U l        0 U l        g)z Initialize non-serialized state.NF)_users_table	_keybinds_pending_actions_action_contextset_status_box_open_actions_menu_open
_destroyed_estimate_threads_estimate_results_estimate_errors_estimate_running	threadingLock_estimate_lock_transcripts_options_pathselfs    r6   __post_init__Game.__post_init__   s     (*  	 13  	 +.%,/E %9;,.+-',.7nn.>=?35r5   returnNc                     g)zRebuild runtime-only state after deserialization.

Subclasses can override to rebuild non-serialized objects. Base turn
management and sound scheduling are stored in serialized fields, so
they do not require rebuilding.
Nr*   rv   s    r6   rebuild_runtime_stateGame.rebuild_runtime_state   s     	r5   c                     g)z8Return the display name of this game (English fallback).Nr*   clss    r6   get_nameGame.get_name        	r5   c                     g)z)Return the type identifier for this game.Nr*   r   s    r6   get_typeGame.get_type   r   r5   relevant_preferencesc                 (    SU R                  5        3$ )z1Return the localization key for this game's name.z
game-name-)r   r   s    r6   get_name_keyGame.get_name_key   s     CLLN+,,r5   c                     g)z3Return the category localization key for this game.zcategory-uncategorizedr*   r   s    r6   get_categoryGame.get_category   s     (r5   c                     g)z!Return minimum number of players.r   r*   r   s    r6   get_min_playersGame.get_min_players        r5   c                     g)z!Return maximum number of players.   r*   r   s    r6   get_max_playersGame.get_max_players   r   r5   c                     / $ )a  Return additional leaderboard types this game supports.

Override in subclasses to add game-specific leaderboards.
Each dict should have:
- "id": leaderboard type identifier (e.g., "best_single_turn")
- "path": dot-separated path to value in custom_data
          Use {player_id} or {player_name} as placeholders
          e.g., "player_stats.{player_name}.best_turn"
          OR for ratio calculations, use:
- "numerator": path to numerator value
- "denominator": path to denominator value
          (values are summed across games, then divided)
- "aggregate": how to combine values across games
               "sum", "max", or "avg"
- "format": entry format key suffix (e.g., "score" for leaderboard-score-entry)
- "decimals": optional, number of decimal places (default 0)

The server will look up localization keys like:
- "leaderboard-type-{id}" for menu display (with underscores as hyphens)
- "leaderboard-{format}-entry" for each entry
r*   r   s    r6   get_leaderboard_typesGame.get_leaderboard_types   s	    . 	r5   c                     / n[        U R                   Vs/ s H  o"R                  (       a  M  UPM     sn5      nX0R                  5       :  a#  UR	                  SSU R                  5       045        U$ s  snf )af  Validate game configuration before starting.

Returns a list of localization keys for any errors found,
or a list of (error_key, kwargs) tuples for errors that need context.
Override in subclasses to add game-specific validation.

Examples:
    return ["pig-error-min-bank-too-high"]
    return [("scopa-error-not-enough-cards", {"decks": 1, "players": 4})]
zaction-need-more-playersmin_players)lenrL   r>   r   append)rw   errorspactive_counts       r6   prestart_validateGame.prestart_validate   sk     t||J|!>>A|JK..00MM."D$8$8$:;  Ks
   A3A3	team_modec                     U R                  5       n[        U5      nSU;   d  [        S U 5       5      (       a  [        R                  " U5      n[        R
                  " X5      (       d  gg)zHelper to validate team mode for current player count.

Args:
    team_mode: Internal team mode string (e.g., "individual", "2v2").

Returns:
    Localization key for error if invalid, None if valid.
 c              3   P   #    U  H  oS :w  d  M
  UR                  5       v   M     g7f)vN)isupper).0cs     r6   	<genexpr>+Game._validate_team_mode.<locals>.<genexpr>"  s     "N	1#X;199;;	s   	&&zgame-error-invalid-team-modeN)get_active_playersr   anyr   parse_display_to_team_modeis_valid_team_mode)rw   r   active_playersnum_playerss       r6   _validate_team_modeGame._validate_team_mode  s_     002.) )s"N	"NNN#>>yII --iEE1r5   c                 ^    U R                   (       a  U R                  U R                   l        gg)z*Synchronize table status with game status.N)re   rP   rv   s    r6   _sync_table_statusGame._sync_table_status+  s    ;;!%DKK r5   playerc                    U R                   S:w  a  gSUl        SUl        U R                  R	                  UR
                  S5        [        UR                  UR
                  S9nU R                  UR
                  U5        g)z1Replace a human player with a bot (shared logic).playingNT)uuid)	rP   rB   r<   rd   popr:   r   r;   attach_user)rw   r   bot_users      r6   _replace_with_botGame._replace_with_bot0  s^    ;;)# $		4(v{{3H-r5   c                     g)z4Start game logic after lobby transitions to playing.Nr*   rv   s    r6   on_startGame.on_start<  s     	r5   c                 D    U R                  5         U R                  5         g)zRun per-tick logic (50ms). Override for bots/timers.

Subclasses should call super().on_tick() to ensure base functionality runs.
N)process_scheduled_soundscheck_estimate_completionrv   s    r6   on_tickGame.on_tickA  s    
 	%%'&&(r5   c                     g)z?Handle round-timer expiry for games using RoundTransitionTimer.Nr*   rv   s    r6   on_round_timer_readyGame.on_round_timer_readyJ  s    r5   	player_iduserc                     X R                   U'   U R                  (       a  UR                  U R                  5        U R                  (       a  UR	                  U R                  5        gg)z Attach a user to a player by ID.N)rd   rS   
play_musicrT   play_ambience)rw   r   r   s      r6   r   Game.attach_userP  sK    !%IOOD../  t445 !r5   c                 L    U R                   R                  UR                  5      $ )zGet the user for a player.)rd   getr:   rw   r   s     r6   get_userGame.get_userY  s    {{vyy))r5   c                 R    U R                    H  nUR                  U:X  d  M  Us  $    g)zGet a player by ID (UUID).N)rL   r:   )rw   r   r   s      r6   get_player_by_idGame.get_player_by_id]  s&    llFyyI% # r5   r;   c                 R    U R                    H  nUR                  U:X  d  M  Us  $    g)z<Get a player by display name. Note: Names may not be unique.N)rL   r;   )rw   r;   r   s      r6   get_player_by_nameGame.get_player_by_named  s&    llF{{d" # r5   c                     U R                    Vs0 s H"  oR                  (       a  M  UR                  / _M$     snU l        gs  snf )z1Initialize transcript storage for seated players.N)rL   r>   r:   rt   r   s     r6   _reset_transcriptsGame._reset_transcriptsk  s0    9=avM`M`]VYY]aas
   AAtextbufferc                     U(       a  UR                   (       a  gU R                  R                  UR                  / 5      R	                  X#S.5        g)z&Store a transcript entry for a player.N)r   r   )r>   rt   
setdefaultr:   r   )rw   r   r   r   s       r6   record_transcript_eventGame.record_transcript_evento  s:     ,,$$VYY3::D;[\r5   c                 L    [        U R                  R                  U/ 5      5      $ )z+Return the transcript history for a player.)listrt   r   )rw   r   s     r6   get_transcriptGame.get_transcriptw  s     D%%)))R899r5   c                     U R                   $ )z#Get the team manager for this game.)rb   rv   s    r6   team_managerGame.team_manager{  s     !!!r5   )rh   rk   rl   ro   rs   rn   rp   rm   rf   ru   rg   rj   re   rt   rd   )rz   N)r   r8   rz   N)table)Dr+   r,   r-   r.   r/   r
   rJ   r   r   rL   r8   r1   rM   r2   rN   r3   rP   r0   rR   rS   rT   rU   rW   rX   rY   r[   r\   r]   r^   r_   tupledictr`   ra   r   r   rb   rx   r|   classmethodr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   propertyr   r4   r*   r5   r6   rD   rD   U   sN   (*" "
 "$7GT&\7E3NKFCD#NM3cJNCOS!&"OT#Y  $s#s"48d8 !#!/40KeCdN+,  L$5:45PS$y/12P!&{!CM;C60        
 13(49-2-S - - (S ( (       d4j  049tE#t)4D/E#E ,S S4Z ,-

.  )6S 6 6 6*v *$+ *# &4- s v} b
 ?F]tm]+.]8;]	]: :T#s(^0D : "k " "r5   rD   )@r/   dataclassesr   r   typingr   r   abcr   r   rq   mashumaro.mixins.jsonr	   mashumaro.configr
   server.core.users.baser   server.core.users.botr   game_utils.actionsr   game_utils.optionsr   DeclarativeGameOptionsr   game_utils.game_resultr   r   game_utils.teamsr   game_utils.game_sound_mixinr   #game_utils.game_communication_mixinr   game_utils.game_result_mixinr   "game_utils.duration_estimate_mixinr   game_utils.game_scores_mixinr    game_utils.game_prediction_mixinr    game_utils.turn_management_mixinr    game_utils.menu_management_mixinr   "game_utils.action_visibility_mixinr   game_utils.lobby_actions_mixinr   r   game_utils.event_handling_mixinr   $game_utils.action_set_creation_mixinr    !game_utils.action_execution_mixinr!   "game_utils.action_set_system_mixinr"   server.core.ui.keybindsr#   r%   r8   rD   r*   r5   r6   <module>r     s    + (   #  4 ' ' % * > * 8 H : F : B B B F I @ I D E +    ! ! !: % h"#h" h"r5   