
    Ii                       S r SSKrSSKrSSKJr  SSKrSSKrSSKrSSKrSSKrSSK	r	SSK
Jr  SSKJrJr  SSKJr  SSKJr  SSKrSSKrSSKJr  SS	KJr   SSKrSS
KJr  SSKJrJrJrJ r   SSK!J"r"J#r#J$r$  SSK%J&r&J'r'  SSK(J)r)  SSK*J+r+J,r,  SSK-J.r.  SSK/J0r0  SSK1J2r2J3r3  SSK4J5r5  SSK6J7r7J8r8  SSK9J:r:  SSK;J<r<  SSK=J>r>J?r?J@r@  SSKAJBrBJCrCJDrDJErE  SSKFJGrGJHrH  SSKIJJrJ  SSKKJLrL  SSKMJNrN  SSKOJPrP  S rQS!rR\R                  " S"5      rT\R                  " S#5      rUS$rVS%rWS&rXS'rYS(rZS)r[S$r\Sr]S*r^S+r_S+r`S+raS,rbS-rcS.rdS/reS0\S1\fS2\f4S3 jrg\" \h5      R                  R                  rj\" \h5      R                  5       R                  S   rm\mS4-  S5-  rn\jS6-  roS2\4S7 jrp " S8 S9\)\+\.5      rq     SRS:\rS-  S;\sS-  S<\r\-  S-  S=\r\-  S-  S>\fS2S4S? jjrtSSS@ jruSA\R                  S2S4SB jrwSC\SD\S2\f4SE jrxSF\S2\y\f\f4   4SG jrzSF\SC\SH\fS2S4SI jr{SC\S2\y\s\s\s\s4   4SJ jr|SK\sSL\sS2\r4SM jr}SK\sSL\sS2\r4SN jr~S:\rS-  SC\S2\r4SO jrS;\sS-  SC\S2\s4SP jrS<\r\-  S-  S=\r\-  S-  SC\S2\y\r\-  S-  \r\-  S-  4   4SQ jrg! \ a    SSKr GN+f = f)Tz0Main server class that ties everything together.    N)asynccontextmanager)deque)datetimetimezone)getpass)Path)Enum)Any)ValidationError   )get_default_config_pathget_example_config_pathensure_default_config_dirload_full_config)ModeSnapshotServerLifecycleState
ServerMode)TickSchedulerload_server_config)AdministrationMixin)DocumentBrowsingMixin_DOCUMENTS_DIR)TranscriberRoleMixin)VirtualBotManager   )WebSocketServerClientConnection)Database)AuthManager
AuthResult)TableManager)NetworkUser)MenuItemEscapeBehavior
TrustLevel)UserPreferencesDiceKeepingStylePREF_CATEGORIESPrefMeta)GameRegistryget_game_class)Localization)show_yes_no_menu)DocumentManager)CLIENT_TO_SERVER_PACKET_ADAPTERz11.0.0%PLAYPALACE_SUPPRESS_BOOTSTRAP_WARNINGzplaypalace.packetszplaypalace.server             i      
   <   i  i ' startuplocalizationvaluedefaultreturnc                    [        U [        5      (       a  U $ [        U [        5      (       a,  U R                  5       R	                  5       nUS;   a  gUS;   a  g[        U [
        [        45      (       a  [        U 5      $ U$ )zParse truthy values from config inputs with a fallback default.

Args:
    value: Raw value from config input.
    default: Default value when parsing fails.

Returns:
    Parsed boolean value.
>   1onyestrueT>   0noofffalseF)
isinstanceboolstrstriplowerintfloat)r:   r;   lowereds      1c:\Users\dbart\PlayPalace11\server\core\server.py_coerce_boolrO   O   so     %%++-%%'0011%#u&&E{N    varserverlocalesc                  6    [         R                  SSS9  [         $ )z@Ensure the repo-local var directory exists for server artifacts.Tparentsexist_ok)_VAR_SERVER_DIRmkdir rP   rN   _ensure_var_server_dirr[   m   s    $6rP   c                   @   \ rS rSrSr        SS\S\S\S\\-  S-  S	\\-  S-  S
\\-  S-  S\\-  S-  S\4S jjr	SS jr
SS jrSS jrSS jr\S\S\4S j5       r\S\S-  S\S-  S\\\4   4S j5       rS\S\S\S\\\\S-  4   4S jr\S\S\SS4S j5       rS\\\\   4   S\S\S\S \S\4S! jrS\\\\   4   S\S\S \S\4
S" jrS\\\\   4   S\S \SS4S# jrS$\S\S\S\S-  4S% jrS\SS4S& jrS$\S\S\S-  4S' jrS$\S\S\S-  4S( jrSS) jrSS* jr S\4S+ jr!\S,\"SS4S- j5       r#SS. jr$SS/ jr%SS0 jr&S\S1\'SS4S2 jr(S1\'SS4S3 jr)\*SS\S4\+S-  4S5 jj5       r,S\S1\'SS4S6 jr-S1\'S7\S\\\.4   4S8 jr/S1\'S7\S\\\.4   4S9 jr0\S1\'S\4S: j5       r1S1\'S\4S; jr2\S<\+S\4S= j5       r3SS> jr4SS? jr5SS@ jr6SSA jr7SSB jr8\SC\9Rt                  SS4SD j5       r;S,\"SE\SS4SF jr<S,\"SG\SS4SH jr=S\SS4SI jr>S\SS4SJ jr?SK\SL\SM\SS4SN jr@SO\SS4SP jrASQ\SS4SR jrBSS\ST\SS4SU jrCS\SV\SS4SW jrDS\SV\SS4SX jrESYSZ.S\S\S\S[\S\\S]\S^\S_\SS4S` jjrFSaSbS\G4Sc jrHS\S\SaSbSd\GS\\"\4   4
Se jrIS\S\S[\S\\S]\S^\S_\SS4Sf jrJS,\"S\4Sg jrKS,\"S\4Sh jrLS,\"SS4Si jrMS,\"SS4Sj jrNS,\"S\S\4Sk jrOS\SV\SS4Sl jrPS\SV\SS4Sm jrQ\S\S\SS4Sn j5       rR\S\So\S\SS4Sp j5       rSS\SV\SS4Sq jrTS\SS4Sr jrUSSs.S,\"St\SS4Su jjrVS,\"SS4Sv jrWS,\"Sw\SS4Sx jrXS,\"ST\SS4Sy jrYS,\"S\4Sz jrZ\[R                  S{\[R                  S|0r^S,\"SS4S} jr_SS~.S,\"Sw\S\SS4S jjr`SS~.S,\"S\S\SS4S jjra SS,\"S\ST\S-  SS4S jjrbS\S\GS\S\cS\4
S jrdS\S\cS\eS\4S jrfS,\"S\SS4S jrgS,\"S\SS4S jrhS,\"S\4S jriS,\"S\SS4S jrjS\SV\SS4S jrkS,\"S\S-  S\S\SS4
S jrlS,\"S\S-  SV\SS4S jrmS,\"S\S\S\S-  SS4
S jrnS,\"S\4S jroS,\"S\SS4S jrpS,\"SS4S jrqS,\"S\SS4S jrrS,\"S\SS4S jrsS,\"SS4S jrtS,\"SS4S jruS,\"S\SS4S jrvS,\"S\SS4S jrwS,\"S\SS4S jrxS,\"S\SS4S jryS,\"SS4S jrzS,\"S\S\SS4S jr{S,\"S\S\SS4S jr|S,\"S\S\SS4S jr}S,\"S\SS4S jr~S,\"SSST\SS4S jrS,\"S\SS4S jrS,\"S\S\SS4S jrS,\"S\S\SS4S jrS,\"S\S\SS4S jrS,\"S\SS4S jrS,\"SS4S jrS,\"ST\SS4S jrST\S\4S jrS,\"ST\S\SS4S jrS,\"ST\S\SS4S jrS,\"ST\S\SS4S jrS,\"ST\S\SS4S jrS,\"ST\S\SS4S jrS\S\SG\SL\S\S-  4
S jrS,\"ST\S\S\SS4
S jrS,\"S\S\SS4S jrS,\"S\S\SS4S jrS,\"S\S\SS4S jrS,\"S\4S jrS,\"ST\SS4S jrS,\"S\S\SS4S jrS,\"S\S\S\\\4   S-  4S jrS,\"S\S\S\\\   \\   \\   4   4S jrSG\S\S-  4S jrS\SL\SG\S\4S jrS\\   S\\   S\\   S\S\S-  4
S jrS<\S\S\4S jrS,\"S\S\S\4S jrS\S\S\S-  4S jrS,\"S\S\SS4S jrS,\"S\S\SS4S jrSS jrSS jrS\SS4S jrS\SV\SS4S jrS\SV\SS4S jrS\SV\SS4S jrS\SV\SS4S jrS\\   4S jrS,\"S\\   4S jrS,\"SS4S jrS,\"S\SS4S jrS\SS4S jrS\SS4S jrS\SS4S jrSrg)Servers   zi
Main PlayPalace v11 server.

Coordinates all components: network, auth, tables, games, and persistence.
NFhostportdb_pathlocales_dirssl_certssl_keyconfig_pathpreload_localesc	                     Xl         X l        XPl        X`l        SU l        SU l        US:X  a  [        5       S-  n	O%[        U5      n	U	R                  R                  SSS9  [        U	5      U l        SU l        [        5       U l        X R                  l        SU l        SU l        0 U l        0 U l        SU l        [+        [,        5      U l        [1        U 5      U l        SU l        [6        U l        [:        U l        [>        U l         [B        U l"        [F        U l$        U(       a  [        U5      O	[K        5       U l&        SU l'        SU l(        SU l)        Xl*        [V        U l,        [Z        U l.        [^        U l0        [b        U l2        [f        U l4        [j        U l6        [n        U l8        [n        U l9        [t        U l;        [x        U l=        0 U l>        0 U l?        0 U l@        0 U lA        [        5       U lC        U R                  R                  [        SS	9  SU lF        U R                  5         Uc  [        n
OB[        U5      nUR                  5       (       d   [        U-  nUR                  5       (       a  UnUn
[        R                  " XR
                  S
9  g)a  Initialize the server and core managers.

Args:
    host: Address to bind the server to.
    port: Port to bind the server to.
    db_path: Path to the sqlite database file.
    locales_dir: Optional directory for locale files.
    ssl_cert: Optional SSL certificate path for TLS.
    ssl_key: Optional SSL private key path for TLS.
    config_path: Optional config.toml path override.
    preload_locales: Whether to block startup while compiling all locales.
enNplaypalace.dbTrU   auto_commitFzServer is starting up.message)enabled_locales)Nr_   r`   	_ssl_cert_ssl_key_default_locale_enabled_localesr[   r   parentrY   r   _db_authr!   _tables_server
_ws_server_tick_scheduler_users_user_states_contribution_moder.   r   
_documentsr   _virtual_bots_localization_warmup_taskDEFAULT_USERNAME_MIN_LENGTH_username_min_lengthDEFAULT_USERNAME_MAX_LENGTH_username_max_lengthDEFAULT_PASSWORD_MIN_LENGTH_password_min_lengthDEFAULT_PASSWORD_MAX_LENGTH_password_max_lengthDEFAULT_WS_MAX_MESSAGE_BYTES_ws_max_message_sizer   _config_path_allow_insecure_ws_block_new_accounts_auto_approve_new_accounts_preload_locales!DEFAULT_LOGIN_ATTEMPTS_PER_MINUTE_login_ip_limit!DEFAULT_LOGIN_FAILURES_PER_MINUTE_login_user_limit(DEFAULT_REGISTRATION_ATTEMPTS_PER_MINUTE_registration_ip_limit#DEFAULT_REFRESH_ATTEMPTS_PER_MINUTE_refresh_ip_limit DEFAULT_ACCESS_TOKEN_TTL_SECONDS_access_token_ttl_seconds!DEFAULT_REFRESH_TOKEN_TTL_SECONDS_refresh_token_ttl_secondsLOGIN_RATE_WINDOW_SECONDS_login_ip_window_login_user_window REGISTRATION_RATE_WINDOW_SECONDS_registration_ip_windowREFRESH_RATE_WINDOW_SECONDS_refresh_ip_window_login_attempts_ip_login_attempts_user_registration_attempts_ip_refresh_attempts_ipr   
_lifecycleadd_gateSTARTUP_GATE_ID_localization_gate_registered_load_config_settings_DEFAULT_LOCALES_DIRis_absolute_MODULE_DIRexistsr,   init)selfr_   r`   ra   rb   rc   rd   re   rf   db_path_objresolved_localesprovided_locales	candidates                rN   __init__Server.__init__z   s;   . 		!#26o%02_DKw-K$$TD$A K()-
#~#2659 /1-/ #0).9 /t4>B& %@!$?!$?!$?!$@!1<D-BYB["'#( */' /@!B&N#!D)I&*K' 9";'G$"=;==?!BD&=?!.0  :R S-2*""$ 3#K0#//11'*::	##%%'0$/*<Q<QRrP   r<   c                 n  #    [        U R                  5      nUR                  S5      nUb5   [        U5      nUS:  a#  [        S[        R                  S9  [        S5      eU R                  5       I Sh  vN   U R                  5         U R                  R                  5         [        U R                  5      U l        U R                  R!                  5       nU(       a  [        SU S	35        U R#                  5         U R%                  5         U R&                  R)                  5       n[        S
U S35         U R*                  R-                  5         U R*                  R/                  5       nUS:  a  [        SU S35        [1        U R2                  U R4                  U R6                  U R8                  U R:                  U R<                  U R>                  U R@                  S9U l!        U RB                  RE                  5       I Sh  vN   U R<                  (       d  [        S5        U R@                  [F        :w  a  [        SU R@                   S35        [I        U RJ                  U5      U l&        U RL                  RE                  5       I Sh  vN   U R<                  (       a  SOSn[        SU SU R2                   SU R4                   35        U R2                  S:X  a  [        S5        OU R2                  S:X  a  [        S5        U RO                  5         U RP                  RS                  [T        5        g! [        [
        4 a/  n[        SU SU 3[        R                  S9  [        S5      UeSnAff = f GN! [
         a,  n[        SU 3[        R                  S9  [        S5      UeSnAff = f GN GN*7f)zStart the server.tick_interval_msNz'ERROR: Invalid tick_interval_ms value 'z' in server configuration: filer   z7ERROR: tick_interval_ms must be at least 1 millisecond.zUser 'z4' has been promoted to server owner (trust level 3).Loaded z documents.z*ERROR: Invalid virtual bot configuration: r   z	Restored z$ virtual bots from previous session.)r_   r`   
on_connecton_disconnect
on_messagerc   rd   max_message_sizezLWARNING: Running without TLS (ws://). Credentials will be sent in plaintext.z$Max inbound websocket message size: z byteswsswszServer running on z://:	127.0.0.1zIBind IP is 127.0.0.1, use 0.0.0.0 to allow connections on all interfaces.z0.0.0.0z@Bind IP is 0.0.0.0, use 127.0.0.1 to limit to local connections.)+r   r   getrK   	TypeError
ValueErrorprintsysstderr
SystemExit_preload_locales_if_requested_validate_transport_securityrs   connectr   rt   initialize_trust_levels_warn_if_no_users_load_tablesr|   loadr}   load_config
load_stater   r_   r`   _on_client_connect_on_client_disconnect_on_client_messagern   ro   r   rw   startr   r   _on_tickrx   _start_localization_warmupr   resolve_gater   )r   server_configr   excpromoted_user	doc_countloadedprotocols           rN   r   Server.start   s-     +4+<+<=(,,-?@'-#&'7#8   !#M !m#00222 	))+ 	 *
 88:F=/)]^_  	 OO((*		{+./	)**, ##..0A:IfX%IJK *..44..^^MM!66	
 oo##%%%~~`a$$(DD89R9R8SSYZ[  -T]]<LM""((*** !NN5"8*C		{!DII;GH99#]^YY)#TU'')$$_5Y z* -=>N=OOjknjop !m,- 	32  	)>seD3::VQ-S(	)$ 	& 	+sy   *N5L1 <N54M35CN57M6 B1N5N/BN5N2	B(N51M0*M++M00N56
N, 'N''N,,N52N5c                   #    [        S5        U R                  (       ab  U R                  R                  5         [        R                  " [
        R                  5         U R                  I Sh  vN   SSS5        SU l        U R                  5         U R                  R                  5         U R                  (       a"  U R                  R                  5       I Sh  vN   U R                  (       a"  U R                  R                  5       I Sh  vN   U R                  R                  5         [        S5        g N! , (       d  f       N= f Np N?7f)zStop the server.zStopping server...NzServer stopped.)r   r~   cancel
contextlibsuppressasyncioCancelledError_save_tablesr}   
save_staterx   stoprw   rs   closer   s    rN   r   Server.stop2  s     "#))**113$$W%;%;<4444 =-1D* 	 	%%' &&++--- ??//&&((( 	 ) 5 =< . )sO   AED9-D7.D92A'EE
2EE*E7D99
EEEc                 F
   U R                   b  [        U R                   5      OSnUb  UR                  5       (       d  g [        US5       n[        R
                  " U5      nSSS5        WR                  S5      n[        U[        5      (       d  0 nUR                  S5      n[        U[        5      (       a  UR                  S5      n[        U[        5      (       a*  UR                  5       (       a  UR                  5       U l        UR                  S5      n[        U[         5      (       a  [#        S U 5       5      (       a  Xl        S)S
[        [        [&        4   S[        S[(        S[(        S[(        4
S jjn	U(       Ga<  [+        UR                  S5      U R,                  5      U l        [+        UR                  S5      U R.                  5      U l        U	" USU R0                  5      U l        U	" USU R2                  U R0                  5      U l        U	" USU R4                  5      U l        U	" USU R6                  U R4                  5      U l        U	" USU R8                  SS9U l        U R0                  U R2                  :  a  U R0                  U l        U R4                  U R6                  :  a  U R4                  U l        UR                  S5      n
[        U
[        5      (       aC  U	" U
SU R:                  S	S9nXl        [+        U
R                  S5      U R<                  5      U l        [        U[        5      (       a  UR                  S5      OSn[        U[        5      (       a  U	" USU R>                  SS9U l        U	" USU R@                  SS9U l         U	" US U RB                  SS9U l!        U	" US!U RD                  SS9U l"        U	" US"U RF                  S	S9U l#        U	" US#U RH                  S	S9U l$        U	" US$U RJ                  S	S9U l%        U	" US%U RL                  S	S9U l&        UR                  S&5      n[        U[        5      (       ak  UR                  S'5      n[        U[        5      (       aE  UR                  5       RO                  5       S(;   a#  UR                  5       RO                  5       U l(        U RP                  U RR                  l*        g! , (       d  f       GNy= f! [        [        R                  4 a   n[        R                  SX5         SnAgSnAff = f)*zALoad credential and network limits from config.toml if available.Nrbz!Failed to load config from %s: %sauthr9   default_localerm   c              3   B   #    U  H  n[        U[        5      v   M     g 7fN)rF   rH   ).0vs     rN   	<genexpr>/Server._load_config_settings.<locals>.<genexpr>f  s     0UWAs1C1CWs   r   sourcekeycurrentminimumr<   c                     U R                  U5      nUc  U$  [        U5      n[        X55      $ ! [        [        4 a    [        R                  SXU5        Us $ f = f)z7Read an integer limit from config with a minimum clamp.z3Invalid config value for '%s': %r, using default %d)r   rK   r   r   LOGwarningmax)r   r   r   r   r:   	value_ints         rN   _read_limit1Server._load_config_settings.<locals>._read_limiti  se    JJsOE}J	 w** z* I s   . )AAauto_approve_new_accountsblock_new_accountsusername_min_lengthusername_max_lengthpassword_min_lengthpassword_max_lengthrefresh_token_ttl_secondsr7   )r   networkmax_message_bytesallow_insecure_wsrate_limitslogin_per_minuter   login_failures_per_minuteregistration_per_minuterefresh_per_minutelogin_window_secondslogin_failure_window_secondsregistration_window_secondsrefresh_window_seconds	documentscontribution_mode)manualrj   auto_pr)r   )+r   r   r   opentomllibr   OSErrorTOMLDecodeErrorr   errorr   rF   dictrH   rI   rp   listallrq   r
   rK   rO   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   rJ   r{   r|   r  )r   path_objfconfigr   auth_cfg
locale_cfgr   enabledr   net_cfg	max_bytesrate_cfgdocs_cfgmodes                  rN   r   Server._load_config_settingsO  s   .2.?.?.K4))*QU8??#4#4	h%)0a & ::f%(D))HZZ/
j$'''^^,<=N.#..>3G3G3I3I'5';';'=$ nn%67G'4((S0UW0U-U-U(/%	+S#X 	+S 	+3 	+QT 	+]` 	+ .:894;Z;Z/D+ (412D4L4L(D$ )4/1J1J)D% )4%))))	)D% )4/1J1J)D% )4%))))	)D% /:5t7V7V`b/D+
 ((4+D+DD,0,E,E)((4+D+DD,0,E,E)**Y'gt$$#,d.G.GQRI )2%&2/0$2I2I'D# 3=Xt2L2L8<<.RVh%%#.,d.B.BA$D  &15t7M7MWX&D" +63T5P5PZ[+D' &1.0F0FPQ&D" %00$2G2GQR%D! '28$:Q:Q[\'D# ,779U9U_`,D( '22D4K4KUV'D# ::k*h%%<< 34D$$$););)= B * +/**,*<*<*>',0,C,C)c &%001 	II98I	s5   S& S"S& 
S#S& #S& &T  TT c                 ^   U R                   (       aE  U R                  (       d  U R                  (       a#  [        S[        R
                  S9  [        S5      eU R                   (       dF  U R                  (       a  U R                  (       d#  [        S[        R
                  S9  [        S5      egg)z?Validate TLS/insecure configuration and exit on invalid combos.zERROR: allow_insecure_ws=true cannot be combined with SSL certificate or key. Remove the certificate settings or disable insecure mode.r   r   zdERROR: TLS is required. Provide --ssl-cert and --ssl-key or set [network].allow_insecure_ws to true.N)r   rn   ro   r   r   r   r   r   s    rN   r   #Server._validate_transport_security  sw    ""$--LZZ
 Q-&&dmm>ZZ
 Q- GT&rP   clientc                 b    U R                   (       d  gU R                   R                  S5      S   $ )z+Return the client IP string (or "unknown").unknownr   r   )addresssplit)r/  s    rN   _get_client_ipServer._get_client_ip  s(     ~~~~##C(++rP   usernamepasswordc                 P    U =(       d    SR                  5       n U=(       d    SnX4$ )z.Normalize credential fields before validation. )rI   )r6  r7  s     rN   _sanitize_credentialsServer._sanitize_credentials  s(     N))+>r!!rP   localec          	         U R                  X5      u  p[        U5      U R                  :  d  [        U5      U R                  :  a.  UU[        R
                  " USU R                  U R                  S94$ [        U5      U R                  :  d  [        U5      U R                  :  a.  UU[        R
                  " USU R                  U R                  S94$ XS4$ )zSValidate username/password lengths and return a localized error message if invalid.zcredential-username-length)minr   zcredential-password-lengthN)r:  lenr   r   r,   r   r   r   )r   r6  r7  r<  s       rN   _validate_credentialsServer._validate_credentials  s     "77Kx=4444HHaHa8a  01111		 	 x=4444HHaHa8a  01111		 	 4''rP   rl   c                    #    U R                  SSS.5      I Sh  vN   U R                  SUS.5      I Sh  vN   U R                  SSS	S	US
.5      I Sh  vN   g N@ N& N	7f)z=Send a credential validation error and disconnect the client.
play_soundaccounterror.oggtypenameNspeak)rF  text
disconnectFTrF  	reconnectshow_messagereturn_to_loginrl   send)r/  rl   s     rN   _send_credential_errorServer._send_credential_error	  sn      kk<9KLMMMkk7G<===kk$" $#'"
 	
 	
 	N=	
s1   A!AA!AA!AA!A!A!bucketr   limitwindownowc                     US::  a  gUR                  U[        5       5      nU(       a/  XVS   -
  U:  a$  UR                  5         U(       a  XVS   -
  U:  a  M$  [        U5      U:  a  gUR	                  U5        g)aD  Record and evaluate a rate-limit attempt.

Args:
    bucket: Mapping of key -> deque[timestamps].
    key: Rate-limit key (e.g., IP or username).
    limit: Max attempts within the window.
    window: Window size in seconds.
    now: Current monotonic time.

Returns:
    True if the attempt is allowed, False if throttled.
r   TF)
setdefaultr   popleftr?  append)r   rS  r   rT  rU  rV  dqs          rN   _allow_attemptServer._allow_attempt  sj     A:sEG,Sa5[6)JJL Sa5[6)r7e
		#rP   c                     UR                  U5      nU(       d  gU(       a/  XES   -
  U:  a$  UR                  5         U(       a  XES   -
  U:  a  M$  U(       d  UR                  US5        g[        U5      $ )z5Return count of attempts within the window for a key.r   N)r   rY  popr?  )r   rS  r   rU  rV  r[  s         rN   _get_attempt_countServer._get_attempt_count1  s_     ZZ_Sa5[6)JJL Sa5[6)JJsD!2wrP   c                 Z    UR                  U[        5       5      nUR                  U5        g)z&Record an attempt timestamp for a key.N)rX  r   rZ  )r   rS  r   rV  r[  s        rN   _record_attemptServer._record_attempt?  s!    sEG,
		#rP   	client_ipc                   [         R                  " 5       nU R                  U R                  XR                  U R
                  U5      (       d  [        R                  " US5      $ U(       a]  U R                  U R                  X R                  U5      nU R                  S:  a&  XPR                  :  a  [        R                  " US5      $ g)zHCheck login rate limits and return a localized error message if blocked.zrate-limit-login-ipr   zrate-limit-login-userN)time	monotonicr\  r   r   r   r,   r   r`  r   r   r   )r   re  r6  r<  rV  failuress         rN   _check_login_rate_limitServer._check_login_rate_limitD  s    nn""##Y0D0DdF[F[]`
 
  ##F,ABB..))85L5LcH %%)h:P:P.P#''0GHHrP   c                     U(       a  U R                   S::  a  g[        R                  " 5       nU R                  U R                  XR
                  U5        U R                  U R                  X5        g)z9Record a failed login attempt for username rate limiting.r   N)r   rg  rh  r`  r   r   rc  )r   r6  rV  s      rN   _record_login_failureServer._record_login_failureS  sV    411Q6nn 9 98E\E\^abT66FrP   c                    [         R                  " 5       nU R                  U R                  UU R                  U R
                  U5      (       d  [        R                  " US5      $ g)zOCheck registration rate limits and return a localized error message if blocked.zrate-limit-registrationN)rg  rh  r\  r   r   r   r,   r   r   re  r<  rV  s       rN   _check_registration_rate_limit%Server._check_registration_rate_limit[  s[    nn""**''((
 
  ##F,EFFrP   c                    [         R                  " 5       nU R                  U R                  UU R                  U R
                  U5      (       d  [        R                  " US5      $ g)zPCheck refresh token rate limits and return a localized error message if blocked.zrate-limit-refreshN)rg  rh  r\  r   r   r   r,   r   rp  s       rN   _check_refresh_rate_limit Server._check_refresh_rate_limith  s[    nn""%%""##
 
  ##F,@AArP   c                    [         R                  R                  [        5      (       a  g U R                  R                  5       S:  a  g [        S[         S35        g! [         a    [        R                  SSS9   gf = f)z.Print a warning if no user accounts exist yet.Nr   z%Failed to check user count at startupTexc_infozWARNING: No user accounts exist. Run `uv run python -m server.cli bootstrap-owner --username <name>` to create an initial administrator before exposing this server on the network. Set z4=1 to suppress this warning for CI or local testing.)
osenvironr   BOOTSTRAP_WARNING_ENVrs   get_user_count	Exceptionr   r   r   r   s    rN   r   Server._warn_if_no_usersu  s    ::>>/00	xx&&(1, - 	 )))]_	
	  	KK?$KO	s   A B ?B c                     U R                   (       a  gU R                  (       a  g[        R                  " S5        [        R
                  " 5       nUR                  U R                  5       5      U l        [        S5        g)z4Kick off localization compilation in the background.NTzfLocalization bundles compiling in background (pass --preload-locales to block startup until finished).)	r   r~   r,   set_warmup_activer   get_running_loopcreate_task_warm_locales_asyncr   )r   loops     rN   r   !Server._start_localization_warmup  s`      ))&&t,''))-)9)9$:R:R:T)U&H	
rP   c                 ,    [         R                  " 5       $ )zAReturn whether non-blocking localization warmup is still running.)r,   is_warmup_activer   s    rN   _is_localization_warmup_active%Server._is_localization_warmup_active  s    ,,..rP   userc                 $    U R                  SSS9  g)z/Tell the user localization is still warming up.z"localization-in-progress-try-againmiscbufferN)speak_l)r  s    rN    _notify_localization_in_progress'Server._notify_localization_in_progress  s     	9&IrP   c                   #    [         R                  " S5      n U R                  5       I Sh  vN   U R                  R	                  [
        5        [        S5        [         R"                  " S	5        g NE! [         a_    UR                  S5        U R                  R                  SS9  U R                  U R                  R                  5       5      I Sh  vN     N[        R                   a     N[         a_    UR                  S5        U R                  R                  SS9  U R                  U R                  R                  5       5      I Sh  vN     Nf = f! [         R"                  " S	5        f = f7f)
z4Compile all locale bundles without blocking startup.
playpalaceNzLocalization bundles compiled.z8Localization preload aborted due to configuration error.z7Localization preload failed due to configuration error.rk   zLocalization preload failedz/Localization preload failed. Check server logs.F)logging	getLogger)_run_localization_warmup_in_daemon_threadr   r   LOCALIZATION_GATE_IDr   r   r   enter_maintenance_disconnect_clients_for_statussnapshotr   r   r}  	exceptionr,   r  )r   loggers     rN   r  Server._warm_locales_async  s1    ""<0	2@@BBBOO(()=>23  **51% C  	RNNUVOO--Q .  55doo6N6N6PQQQ%% 	 	R:;OO--I .  55doo6N6N6PQQQ	R **51sv   E8A5 A3.A5 E83A5 5A ECEE E0E 2AEEEE EE E55E8c                   ^^^^#    [         R                  " 5       mTR                  5       mS
U4S jjmS[        SS4U4S jjmS
UUU4S jjn[        R
                  " USSS	9nUR                  5         TI Sh  vN   g N7f)zERun localization warmup in a daemon thread so shutdown isn't blocked.r<   Nc                  T   > T R                  5       (       d  T R                  S 5        g g r   )done
set_result)r  s   rN   
_finish_okDServer._run_localization_warmup_in_daemon_thread.<locals>._finish_ok  s    99;;% rP   r   c                 T   > TR                  5       (       d  TR                  U 5        g g r   )r  set_exception)r   r  s    rN   _finish_errEServer._run_localization_warmup_in_daemon_thread.<locals>._finish_err  s!    99;;""3' rP   c                  ~  >  [         R                  " 5         [        R                  " [        5         TR                  T5        S S S 5        g ! , (       d  f       g = f! [         aT  n [        R                  " [        5         TR                  TU 5        S S S 5         S n A g ! , (       d  f        S n A g = fS n A ff = fr   )r,   preload_bundlesr   r   RuntimeErrorcall_soon_threadsafeBaseException)r   r  r  r  s    rN   _workerAServer._run_localization_warmup_in_daemon_thread.<locals>._worker  s}    :,,.
  ((6--j9 766	 ! @((6--k3? 7666@s@   A A
A
B<(B7B"B7"
B4	,B74B77B<Tzplaypalace-localization-warmup)targetdaemonrG  r<   N)r   r  create_futurer  	threadingThreadr   )r   r  threadr  r  r  r  s      @@@@rN   r  0Server._run_localization_warmup_in_daemon_thread  sq     '')%)%7%7%9	&	(] 	(t 	(	: 	: !!1

 	

s   A3B9A?:Bc                 r    U R                   (       a  gSU l         U R                  R                  [        SS9  g)z,Register the localization gate exactly once.NTz!Compiling localization bundles...rk   )r   r   r   r  r   s    rN   _ensure_localization_gate Server._ensure_localization_gate  s/    ---1*  !5?b crP   r  c                 B   #    U R                  X5      I Sh  vN   g N7f)zKInform a newly connected client about current server status and disconnect.N)_send_status_and_disconnectr   r/  r  s      rN   !_reject_client_during_unavailable(Server._reject_client_during_unavailable  s      ..v@@@s   c           	      @  #    U R                   (       a  U R                   R                  (       d  g[        R                  " [	        U R                   R                  R                  5       5       Vs/ s H  nU R                  X!5      PM     sn6 I Sh  vN   gs  snf  N
7f)zHBroadcast lifecycle status to all connected clients and disconnect them.N)rw   clientsr   gatherr  valuesr  )r   r  r/  s      rN   r  %Server._disconnect_clients_for_status  s{     doo&=&=nn #4??#:#:#A#A#CDDF 00BD
 	
 	
	
s   A-B/B
BBB	resume_atc                "  #    U R                   R                  XS9  U R                  U R                   R                  5       5      I Sh  vN    S7v   U R                   R	                  5         g N%! U R                   R	                  5         f = f7f)zLContext manager for internal maintenance tasks that require pausing clients.)rl   r  N)r   r  r  r  exit_maintenance)r   rl   r  s      rN   maintenance_modeServer.maintenance_mode  sn      	))')O11$//2J2J2LMMM	/OO,,.	 	N OO,,.s*   ABA.	BA0 B0BBc                   #    U R                  U5      nU R                  X#5      nU R                  X#5      nUR                  U5      I Sh  vN   UR                  U5      I Sh  vN   UR	                  5       I Sh  vN   g N6 N N	7f)z?Send a lifecycle status update followed by a disconnect packet.N)_calculate_retry_after_build_status_packet_build_status_disconnectrP  r   )r   r/  r  retry_afterstatus_packetdisconnect_packets         rN   r  "Server._send_status_and_disconnect  sw      11(;11(H 99(Pkk-(((kk+,,,lln 	),s6   AB
BB#B$B;B<BBBr  c                     SUR                   R                  US.nUR                  (       a  UR                  US'   UR                  (       a  U R	                  UR                  5      US'   U$ )z'Construct a status payload for clients.server_status)rF  r+  r  rl   r  )r+  r:   rl   r  _format_datetime)r   r  r  payloads       rN   r  Server._build_status_packet  sb     $MM''&&

 !)!1!1GI#'#8#89K9K#LGK rP   c                 `    U R                  U5      nSSSSUR                  R                  UUS.$ )zFConstruct the disconnect payload paired with a lifecycle notification.rJ  FT)rF  rL  rM  rN  status_moder  rl   )_format_status_messager+  r:   )r   r  r  message_texts       rN   r  Server._build_status_disconnect  s=     228<  ##==..&#
 	
rP   c                     U R                   =(       d    SnU R                  (       a'  [        R                  U R                  5      nU SU S3nU$ )z0Build a human-readable lifecycle status summary.z"Server is temporarily unavailable.z Expected availability: .)rl   r  r]   r  )r  rl   resume_texts      rN   r  Server._format_status_message  sL     ""J&J 11(2D2DEK 	!9+aHGrP   c                 N   UR                   (       aV  [        R                  " [        R                  5      n[        UR                   U-
  R                  5       5      n[        SU5      $ UR                  [        R                  :X  a  gUR                  [        R                  :X  a  gg)z.Compute a recommended retry delay for clients.r   r5      )r  r   rV  r   utcrK   total_secondsr   r+  r   INITIALIZINGMAINTENANCE)r   r  rV  deltas       rN   r  Server._calculate_retry_after'  st    ,,x||,C++c1@@BCEq%= ==J333==J222rP   r:   c                     U R                   c  U R                  [        R                  S9n U R	                  [        R                  5      R                  5       nUR                  S5      (       a  USS S-   nU$ )z<Format datetimes as ISO-8601 strings with Z suffix when UTC.N)tzinfoz+00:00iZ)r  replacer   r  
astimezone	isoformatendswith)r:   	iso_values     rN   r  Server._format_datetime3  sf     <<MMM6E$$X\\2<<>	h''!#2,IrP   c                    #    U R                   (       d  gU R                  5         [        R                  " [        R
                  5      I Sh  vN   U R                  R                  [        5        g N$7f)z7Synchronously compile locales when preload flag is set.N)	r   r  r   	to_threadr,   r  r   r   r  r   s    rN   r   $Server._preload_locales_if_requested=  sO     $$&&( < <===$$%9: 	>s   A
A3A1%A3c                    SSK Jn  U R                  R                  5       nU GH$  nU R                  R                  U5        UR                  (       d  M2  [        UR                  5      nU(       d  [        SUR                   35        Mh  UR                  UR                  5      nUR                  5         XSl        X5l        UR                  5         [        US5      (       a  UR!                  5         UR"                   HD  nUR$                  (       d  M  U" UR&                  5      nUR)                  UR*                  U5        MF     GM'     [        S[-        U5       S35        U R                  R/                  5         g)z2Load tables from database and restore their games.r   Botz'WARNING: Could not find game class for _reset_transcriptsr   z tables from database.N)	users.botr  rs   load_all_tablesru   	add_table	game_jsonr+   	game_typer   	from_jsonrebuild_runtime_stategame_tablesetup_keybindshasattrr  playersis_botrG  attach_useridr?  delete_all_tables)r   r  tablestable
game_classr  playerbot_users           rN   r   Server._load_tablesE  s    "))+ELL""5) +EOO<
!CEOOCTUV "++EOO<**,!
# ##%4!566++- #llF}}}#&v{{#3((H= +- 6 	F}$:;< 	""$rP   c                     U R                   R                  5       nU R                  R                  U5        [	        S[        U5       S35        g)zSave all tables to database.zSaved z tables to database.N)ru   save_allrs   save_all_tablesr   r?  )r   r  s     rN   r   Server._save_tablesk  s<    &&(  (s6{m#789rP   c                     U R                   R                  5         U R                  R                  5         U R                  5         g)zCalled every tick (50ms).N)ru   on_tickr}   _flush_user_messagesr   s    rN   r   Server._on_tickq  s6     	 	""$ 	!!#rP   c                 |   U R                   R                  5        H  u  pUR                  5       nU(       d  M  U R                  (       d  M1  U R                  R	                  U5      nU(       d  MU  U HC  n[
        R                  " UR                  U5      5      nUR                  U R                  5        ME     M     g)z'Send all queued messages for all users.N)
ry   itemsget_queued_messagesrw   get_client_by_usernamer   r  rP  add_done_callback_log_send_task_exception)r   r6  r  messagesr/  msgtasks          rN   r  Server._flush_user_messages|  s    "kk//1NH//1HxDOOO??I6'&226;;s3CD..t/L/LM  ( 2rP   r  c                     U R                  5       (       a  gU R                  5       nUb  [        R                  SU5        gg)z/Log exceptions from fire-and-forget send tasks.Nz Error sending queued message: %s)	cancelledr  r   r   )r  r   s     rN   r  Server._log_send_task_exception  s9     >>nn?KK:C@ rP   
new_clientc           
      z  #    UR                   nU(       a_  SUl         UR                  SSSS[        R                  " UR
                  S5      S.5      I Sh  vN    UR                  5       I Sh  vN   UR                  Ul        SUl        UR!                  U5        g NG! [        [        [        R                  R                  4 a   n[        R                  SU5         SnANSnAff = f N|! [        [        [        R                  R                  4 a   n[        R                  SU5         SnANSnAff = f7f)	zNDisconnect the existing client session for a user and bind the new connection.TrJ  Fzalready-logged-inrK  Nz%Failed to notify replaced session: %sz$Failed to close replaced session: %s)
connectionreplacedrP  r,   r   r<  r  r  
websockets
exceptionsConnectionClosedr   debugr   r6  authenticatedset_connection)r   r  r"  
old_clientr   s        rN   _handoff_existing_session Server._handoff_existing_session  s     __
"&JH oo ,%*(,+/#/#3#3DKKAT#U  G &&((( #mm
#'
 J'# \:+@+@+Q+QR H		A3GGH )\:+@+@+Q+QR G		@#FFGsv   D;9B! BB! C/ 0C-1C/ 5*D;B! !)C*
C% D;%C**D;-C/ /)D8D3.D;3D88D;	player_idc                     [        US5      (       d  gUR                  U5      nU(       d  gU HF  nSUR                  SS5      SS.nUR                  S5      nU(       a  XvS'   UR                  U5        MH     g)	z-Queue buffered transcript packets for a user.get_transcriptNrH  rI  r9  T)rF  rI  mutedr  )r  r1  r   queue_packet)r   r  r  r/  historyentrypacketbuffer_names           rN   _queue_transcript_replayServer._queue_transcript_replay  sw    t-..%%i0E		&"-F
  ))H-K#.x f% rP   c                 B  #    U R                   R                  5       nUR                  [        R                  :w  aI  [        SUR                  R                   SUR                   35        U R                  X5      I Sh  vN   g[        SUR                   35        g N7f)zHandle new client connection.zClient deferred (z): NzClient connected: )	r   r  r+  r   RUNNINGr   r:   r2  r  r  s      rN   r   Server._on_client_connect  s     ??++-==J...%hmm&9&9%:#fnn=MNO88JJJ"6>>"234 Ks   A<B>B?Bc                   #    UR                   =(       d    Sn[        SU SUR                   35        [        USS5      (       a  gUR                   (       Ga  UR                   nU R                  R                  U5      nU R                  R                  U5      nU(       a  U(       a  UR                  (       aG  UR                  R                  UR                  5      nU(       a  UR                  R                  U5        [        UR                  5      S::  a  UR                  U5        U(       a}  UR                  (       al  UR                   ["        R$                  :w  aN  UR                   R&                  ["        R(                  R&                  :  nU(       a  SOS	nU R+                  S
X'5        U R                  R-                  US5        U R.                  R-                  US5        gg7f)zHandle client disconnection.r1  zClient disconnected: @r%  FNr   zofflineadmin.oggzoffline.oggzuser-offline)r6  r   r2  getattrru   find_user_tablery   r   r  get_player_by_iduuid_perform_leave_gamer?  membersremove_memberapprovedtrust_levelr%   BANNEDr:   ADMIN_broadcast_presence_lr_  rz   )r   r/  r6  r  r  r
  is_adminoffline_sounds           rN   r   Server._on_client_disconnect  sY    ??/i%hZq0@AB6:u--???HLL00:E;;??8,D::"ZZ88CF

66v>u}}%*''1 $*:*:j>O>O*O++11Z5E5E5K5KK6> 2M**>8S KKOOHd+!!(D1/ s   G&G(
message_idplayer_namesoundc                     U R                   R                  5        H:  u  pEUR                  (       d  M  UR                  USUS9  UR	                  U5        M<     g)zTBroadcast a localized presence announcement to all approved online users with sound.activityr  r
  N)ry   r  rF  r  rC  )r   rN  rO  rP  r6  r  s         rN   rJ  Server._broadcast_presence_l  sC    "kk//1NH==LLJ{LKOOE"	 2rP   
admin_namec                     U R                   R                  5        H)  u  p#UR                  (       d  M  UR                  SSUS9  M+     g)z=Broadcast an admin announcement to all approved online users.zuser-is-adminrR  rS  Nry   r  rF  r  )r   rU  r6  r  s       rN   _broadcast_admin_announcement$Server._broadcast_admin_announcement  s7    "kk//1NH==LLJLO 2rP   
owner_namec                     U R                   R                  5        H)  u  p#UR                  (       d  M  UR                  SSUS9  M+     g)zCBroadcast a server owner announcement to all approved online users.zuser-is-server-ownerrR  rS  NrW  )r   rZ  r6  r  s       rN   $_broadcast_server_owner_announcement+Server._broadcast_server_owner_announcement  s8    "kk//1NH==LL/
:LV 2rP   	host_namer  c                    [        U5      nU(       d  gUR                  5       nU R                  R                  5        H  u  pVUR                  (       d  M  U R
                  R                  U0 5      nUR                  S5      S:X  a  MK  [        R                  " UR                  U5      nUR                  SSXS9  UR                  S5        M     g)zSBroadcast a table creation announcement to all approved online users not in a game.Nmenuin_gameztable-createdrR  )r  r_   r  ztable_created.ogg)r+   get_name_keyry   r  rF  rz   r   r,   r<  r  rC  )	r   r^  r  r	  name_keyr6  r  state	game_names	            rN   _broadcast_table_createdServer._broadcast_table_created  s    #I.
**,"kk//1NH==%%))(B7Eyy I-$((h?ILL)L\OO/0 2rP   r6  c                 r  #     U R                  X5      I Sh  vN   g N! [         a    UR                  =(       d    UR                  n[        R                  SU5        UR                  (       a%  U R                  R                  UR                  5      OSnU(       a  UR                  S5         g gf = f7f)z$Handle incoming message from client.Nz*Unhandled error processing message from %szinternal-error)	_dispatch_client_messager}  r6  r2  r   r  ry   r   r  )r   r/  r6  
identifierr  s        rN   r   Server._on_client_message  s     	///??? 	/:FNNJMMF
S7=4;;??6??3DD-. 		/s2   B7    B7  BB4/B73B44B7c                   #     [         R                  " U5      nUR                  SS9nUR                  S5      nUS:X  a  U R                  X5      I Sh  vN   gUS:X  a  U R                  X5      I Sh  vN   gUS:X  a  U R                  X5      I Sh  vN   gUR                  (       d  gUS	:X  a  U R                  U5      I Sh  vN   gUS
:X  a  U R                  X5      I Sh  vN   gU R                  R                  UR                  5      nU(       a  UR                   (       d  gUS:X  a  U R#                  X5      I Sh  vN   gUS:X  a  U R%                  X5      I Sh  vN   gUS:X  a  U R'                  X5      I Sh  vN   gUS:X  a  U R)                  X5      I Sh  vN   gUS:X  a  U R+                  U5      I Sh  vN   gUS:X  a  U R-                  U5      I Sh  vN   gg! [         a?  nUR                  =(       d    UR
                  n[        R                  SXT5         SnAgSnAff = f GN GN GN GNz GN] GN N N N N Nl7f)z?Dispatch an incoming client message to the appropriate handler.T)exclude_nonez#Dropping invalid packet from %s: %sNrF  	authorizeregisterrefresh_sessionpingr`  keybinddocument_editoreditboxchatlist_onlinelist_online_with_games)r/   validate_python
model_dumpr   r6  r2  PACKET_LOGGERr   r   _handle_authorize_handle_register_handle_refresh_sessionr*  _handle_ping_handle_menury   rF  _handle_keybind_handle_document_editor_packet_handle_editbox_handle_chat_handle_list_online_handle_list_online_with_games)r   r/  r6  packet_modelr   rj  packet_typer  s           rN   ri  Server._dispatch_client_message  s    	:JJ6RL!,,$,?F jj(+%((888J&''777--..v>>>%%F"##F+++F"##F333 ;;??6??3DDMMi'**6::: 1199&III	)**6:::&''777-..v666 8899&AAA 9O  	:FNNJ!!"GY	 97> , 4 ;I:76As   I%G+ +IH7I4H:5IH=1II I&I'AIII$I	%III$I%III$I%I+
H455H/*I/H44I:I=I III	IIIIIauthorize_success)success_typesession_tokensession_expires_atrefresh_tokenrefresh_expires_atr  c          
        #    U R                   R                  U5      n	U	(       d0  U R                  U[        R                  " US5      5      I Sh  vN   gU R                  U	5      n
U R                  XX5      I Sh  vN u  pU R                  UUUUUUUS9I Sh  vN   U R                  U5      I Sh  vN   U R                  U5      I Sh  vN (       a  gU R                  U5      (       a  gU(       a  U R                  U5        UR                  R                  [        R                  R                  :  a  U R!                  U5        U R#                  X5      (       d  U R%                  U5        gg GN# N N N N7f)z1Attach user state and send login success packets.zaccount-not-foundN)r  r  r  r  r  )rt   get_userrQ  r,   r   _load_user_preferences_attach_or_update_user_send_login_success_send_game_list_handle_banned_login_handle_unapproved_login_broadcast_login_presencerG  r:   r%   rI   _notify_pending_account_requests_restore_login_table_show_main_menu)r   r/  r6  r<  r  r  r  r  r  user_recordpreferencesr  is_new_logins                rN   _finalize_loginServer._finalize_loginE  sw     jj))(3--((1DE   11+>#'#>#>k$
 
 &&'1'1% ' 
 	
 	
 ""6*** **4000 ((.. **40 !!Z%5%5%;%;;11$7 ((88  & 9O

	
 	+ 1s[   AE=E2+E=;E5<E=E7E=3E94E=E;B&E=5E=7E=9E=;E=r  AuthUserRecordc                 \   UR                   (       a7   [        R                  " UR                   5      n[        R                  " U5      $ [        5       $ ! [        R
                  [        [        [        4 a4  n[        R                  SUR                  U5         SnA[        5       $ SnAff = f)z2Load stored preferences, falling back to defaults.z<Corrupt preferences for user '%s', resetting to defaults: %sN)preferences_jsonjsonloadsr&   	from_dictJSONDecodeErrorKeyErrorr   r   r   r   rB  )r   r  
prefs_datar   s       rN   r  Server._load_user_preferences  s    ''!ZZ(D(DE
&00<<    (((IzJ R$$c    s   5A $B+7!B&&B+r  c                 6  #    UR                   =(       d    SnUR                  nUR                  =(       d    [        R                  nUR
                  nU R                  R                  U5      n	U	(       a  U R                  X5      I Sh  vN   U	R                  U5        U	R                  U5        U	R                  U5        U	R                  U5        U	R                  UR                  5        U	R                  UR                   5        U	R#                  UR$                  5        U	S4$ X!l        SUl        [+        UUUUUUUUR$                  S9n
U
R                  UR                  5        U
R                  UR                   5        XR                  U'   U
S4$  GN7f)z<Attach a connection to an existing user or create a new one.rh   NFT)rB  r  rG  rF  fluent_languages)r<  rB  rG  r%   USERrF  ry   r   r-  
set_localeset_preferencesset_trust_levelset_approvedset_client_typeclient_typeset_platformplatformset_fluent_languagesr  r6  r*  r"   )r   r/  r6  r  r  r<  	user_uuidrG  is_approvedexisting_userr  s              rN   r  Server._attach_or_update_user  s`     ##+t$$	!--@!**100GGG$$V,))+6))+6&&{3))&*<*<=&&v7..{/K/KL %''"### (99	
 	V//0&//* $HTz3 Hs   BF	F
DFc                   #    UUUUUS.nUS:X  a  UR                  S[        S.5        OUR                  S[        S.5        UR                  U5      I Sh  vN   [        SU SUR                   35        g N 7f)z&Send the login/refresh success packet.)r6  r  r  r  r  r  )rF  versionrefresh_session_successNzClient authorized: r>  )updateVERSIONrP  r   r2  )	r   r/  r6  r  r  r  r  r  r  s	            rN   r  Server._send_login_success  s~      !*"4*"4
 ..NN$7GLMNN$='RSkk'"""#H:Qv~~.>?@ 	#s   AA9A7!A9c                   #    UR                   [        R                  :w  a  g[        R                  " UR
                  S5      nUR                  S5        UR                  SSS9  UR                  5        H&  nUR                  R                  U5      I Sh  vN   M(     UR                  R                  SSSUS	.5      I Sh  vN   g N1 N7f)
z"Handle disconnecting banned users.Fzaccount-bannedzaccountban.oggrR  r  NrJ  T)rF  rL  rM  rl   )rG  r%   rH  r,   r   r<  rC  r  r  r$  rP  )r   r  ban_messager  s       rN   r  Server._handle_banned_login  s     z000"&&t{{4DE()%j9++-C//&&s+++ .oo""$" $&	
 	
 	
  ,	
s$   BCC+CC	C	Cc                 j    UR                   (       a  gUR                  SSS9  U R                  U5        g)z0Route unapproved users to the limited main menu.Fwaiting-for-approvalrR  r  T)rF  r  r  r   r  s     rN   r  Server._handle_unapproved_login  s.    ==+J?T"rP   c                    UR                   R                  [        R                  R                  :  a  SOSnU R	                  SUR
                  U5        UR                   R                  [        R                  R                  :  a  U R                  UR
                  5        gUR                   R                  [        R                  R                  :  a  U R                  UR
                  5        gg)z0Broadcast login presence and role announcements.zonlineadmin.oggz
online.oggzuser-onlineN)	rG  r:   r%   rI  rJ  r6  SERVER_OWNERr\  rX  )r   r  online_sounds      rN   r   Server._broadcast_login_presence  s     "&!1!1!7!7:;K;K;Q;Q!QWc 	 	""=$--N!!Z%<%<%B%BB55dmmD##z'7'7'='==..t}}= >rP   c                     U R                   R                  SS9nU(       d  gUR                  SSS9  UR                  S5        g)z.Notify admins about pending account approvals.T)exclude_bannedNaccount-requestrR  r  accountrequest.ogg)rs   get_pending_usersr  rC  )r   r  pending_userss      rN   r  'Server._notify_pending_account_requests  s=    22$2G&z:,-rP   c                    U R                   R                  U5      nU(       a  UR                  (       d  gUR                  nUR                  X!5        UR	                  X!SS9  UR                  UR                  5      nU(       d  gUR                  nU(       a  SUl        UR                  UR                  U5        U(       a;  UR                  SUR                  S9  UR                  S5        UR                  5         SUR                  S.U R                  U'   UR                  U5        U R!                  XUR                  5        g)	z4Attempt to restore a user into their existing table.Fas_spectatorTplayer-took-overr
  join.oggra  r`  table_id)ru   r@  r  r  
add_memberrA  rB  r  r  broadcast_lr6  broadcast_soundrebuild_all_menusr  rz   rebuild_player_menur8  )r   r  r6  r  r  r
  was_bots          rN   r  Server._restore_login_table  s   ,,X6%**zz()e<&&tyy1--!FMD)/F  ,""$ '
(# 	  (%%d&))<rP   c                   #    UR                  SS5      nUR                  SS5      nUR                  S5      nUR                  S5      =(       d    U R                  nUR                  S5      =(       d    SUl        UR                  S5      =(       d    SUl        U(       a  U R                  R                  U5      nU(       d0  U R                  U[        R                   " US5      5      I S	h  vN   g	U(       aR  UR                  5       UR                  5       :w  a0  U R                  U[        R                   " US
5      5      I S	h  vN   g	UnGO_U R                  X4US9u  pn
U
(       a  U R                  X5      I S	h  vN   g	U R                  U5      nU R                  XUS9nU(       a  U R                  X5      I S	h  vN   g	U R                  R                  X5      nU[        R                  :w  Ga  U[        R                  :X  a  U R!                  U5        [        R                   " US5      nUR#                  SSS.5      I S	h  vN   UR#                  SUSS.5      I S	h  vN   UR#                  SSSSUS.5      I S	h  vN   g	U R$                  (       + =(       a    U R&                  R)                  5       S:  nU R*                  (       a  U R-                  X5      I S	h  vN   g	U R                  R/                  XU R$                  US9(       d  U R!                  U5        [        R                   " US5      nUR#                  SSS.5      I S	h  vN   UR#                  SUSS.5      I S	h  vN   UR#                  SSSSUS.5      I S	h  vN   g	U(       a  U R1                  SS5        U R                  R3                  XR4                  5      u  nnU R                  R7                  XR8                  5      u  nnU R;                  UUUUUUUSS9I S	h  vN   g	 GN1 GN GN GNg GN GN GN GNN N N N N#7f)zAuthorize a client and attach a NetworkUser if successful.

Args:
    client: Client connection.
    packet: Incoming authorize payload.
r6  r9  r7  r  r<  r  r  session-expiredNzsession-token-mismatchr<  zincorrect-passwordrC  rD  rE  rH  rR  rF  rI  r  rJ  FTrK  r   rF  r<  zincorrect-usernamer  r  r  r<  r  r  r  r  r  )r   rp   r  r  rt   validate_sessionrQ  r,   rJ   r@  r4  rj  authenticater    SUCCESSWRONG_PASSWORDrm  rP  r   rs   r|  r   _send_accounts_blockedro  _notify_adminscreate_sessionr   create_refresh_tokenr   r  )r   r/  r6  username_rawpassword_rawr  r<  token_usernamer6  r7  r  re  throttle_messageauth_resulterror_messageneeds_approvalaccess_tokenaccess_expiresr  refresh_expiress                       rN   r{  Server._handle_authorize  s     zz*b1zz*b1

?3H%=)=)=#ZZ6<" **Z06B!ZZ88GN!11L,,V5FG    4 4 6,:L:L:N N11L,,V5MN   %H(,(B(B6 )C )%H 11&@@@++F3I#;;IX^;_11&KKK **11(EKj000*";";;..x8$0$4$4V=Q$RM ++|EW&XYYY ++!(-:V   !++$0).,0/3'4    &*%D%D!D!fI`I`IbefIf++55fEEEzz**8HgHgpv*w..x8$0$4$4V=Q$RM ++|EW&XYYY ++!(-:V   !++$0).,0/3'4    "''(9;OP'+zz'@'@44(
$n *.)H)H55*
& ""&-'., # 	
 		
 		
e
 A L Z F Z,		
s   C+P>-P.AP>P!7P>>P$?AP> P'BP>	P*
P>&P-'P>P0AP>%P3&A.P>P6P>1P82P>P:BP>P<P>!P>$P>'P>*P>-P>0P>3P>6P>8P>:P><P>c                   #    UR                  SS5      nUR                  SS5      nUR                  S5      =(       d    U R                  nU R                  X4US9u  pgnU(       a  UR                  SUSS.5      I S	h  vN   g	U R	                  U5      n	U R                  XS9n
U
(       a  UR                  SU
SS.5      I S	h  vN   g	U R                  (       + =(       a    U R                  R                  5       S
:  nU R                  (       a  U R                  X5      I S	h  vN   g	U R                  R                  XgU R                  US9(       aM  UR                  S[        R                   " US5      SS.5      I S	h  vN   U(       a  U R                  SS5        g	g	UR                  S[        R                   " US5      SS.5      I S	h  vN   g	 GNU GN N NZ N7f)zRegister a new user from the registration dialog.

Args:
    client: Client connection.
    packet: Incoming register payload.
r6  r9  r7  r<  r  rH  rR  r  Nr   r  zregistration-successr  r  zregistration-username-taken)r   rp   r@  rP  r4  rq  r   rs   r|  r   r  rt   ro  r,   r  )r   r/  r6  r  r  r<  r6  r7  r  re  r  r  s               rN   r|  Server._handle_register  s     zz*b1zz*b1H%=)=)=$($>$>v %? %
!E ++wTUUU''/	>>y>X++w8HT^_``` "<<<^AXAXAZ]^A^##--f===::xD<[<[djk++$((1GH$    ##$57KL  ++#(,,V5RS(  5 V a >s^   A;G=G>AGGAG"G#AG?G AGGGGGGGc                    #    [         R                  " US5      nU R                  SSS.5      I Sh  vN   U R                  SUSS.5      I Sh  vN   U R                  S	S
SSUS.5      I Sh  vN   g NA N& N	7f)zKInform the client that new account registration is disabled and disconnect.zaccounts-blockedrC  rD  rE  NrH  rR  r  rJ  FTrK  )r,   r   rP  )r/  r<  r  s      rN   r  Server._send_accounts_blocked  s      %((1CDkk<9KLMMMkk7MZXYYYkk  #$
  	 	 	NY	s3   /A9A3A9A5A9-A7.A95A97A9reasonc           
         #    U R                  SUS.5      I Sh  vN   U R                  SSSS[        R                  " US5      S.5      I Sh  vN   g N9 N7f)	zHSend a refresh failure with a specific reason and disconnect the client.refresh_session_failure)rF  rl   NrJ  FTr  rK  )rP  r,   r   )r/  r  r<  s      rN   _send_refresh_failureServer._send_refresh_failure  sb      kk#<PQQQkk$" $#''++F4EF
 	
 	
 	R	
s!   AA3AAAAc                 v  #    UR                  SS5      nUR                  SS5      nUR                  S5      =(       d    U R                  nUR                  S5      =(       d    SUl        UR                  S5      =(       d    SUl        U R	                  U5      nU R                  XeS9nU(       a  U R                  X5      I Sh  vN   gU R                  R                  X0R                  U R                  5      nU(       d1  U R                  U[        R                   " US	5      U5      I Sh  vN   gUu  ppnU(       aS  UR                  5       U	R                  5       :w  a1  U R                  U[        R                   " US
5      U5      I Sh  vN   gU R                  UU	UU
UUUSS9I Sh  vN   g N N N( N7f)z0Refresh an access session using a refresh token.r  r9  r6  r<  r  r  r  Nzrefresh-token-expiredzrefresh-token-mismatchr  r  )r   rp   r  r  r4  rt  rQ  rt   rp  r   r   r  r,   rJ   r  )r   r/  r6  r  username_hintr<  re  r  resultr6  r  r  new_refresh_tokenr  s                 rN   r}  Server._handle_refresh_session  s    

?B7

:r2H%=)=)=#ZZ6<" **Z06B''/	99)9S--fGGG++994;Z;Z
 ,,((1HI6   U[R?]002hnn6FF,,((1IJF   ""&-+.2 # 	
 		
 		
' H
		
sJ   CF9F1A'F9,F3-AF9F5F9+F7,F93F95F97F9c                    #    / n[         R                  " 5        H3  nUR                  UR                  5       UR	                  5       S.5        M5     UR                  SUS.5      I Sh  vN   g N7f)z/Send the list of available games to the client.rE  update_options_lists)rF  gamesN)r*   get_allrZ  get_typeget_namerP  )r   r/  r
  r	  s       rN   r  Server._send_game_list  s`     &..0JLL&//1&//1 1 kk#9EJKKKs   A&A0(A.)A0reset_historyr  c                ,   U(       a\  [        USS5      n[        U[        5      (       a:  [        UR	                  5       5       H  nUS:w  d  M  UR                  US5        M     / nUR                  (       Ga  [        [        R                  " UR                  S5      SS9[        [        R                  " UR                  S5      SS9[        [        R                  " UR                  S5      S	S9[        [        R                  " UR                  S
5      S
S9[        [        R                  " UR                  S5      SS9[        [        R                  " UR                  S5      SS9[        [        R                  " UR                  S5      SS9/nUR                  R                  [        R                  R                  :  a8  UR                  [        [        R                  " UR                  S5      SS95        Oz[        [        R                  " UR                  S
5      S
S9[        [        R                  " UR                  S5      SS9[        [        R                  " UR                  S5      SS9/nUR                  [        [        R                  " UR                  S5      SS95        UR!                  SUS["        R$                  U(       a  SOSS9  UR'                  S5        UR)                  5         SS0U R*                  UR,                  '   g)zShow the main menu to a user._current_menusN	main_menuplayrI  r  zview-active-tablesactive_tableszsaved-tablessaved_tablesleaderboardszmy-statsmy_statsoptionszdocuments-menu-titler  administrationlogoutTr   multiletterescape_behaviorpositionzmainmus.oggr`  )r?  rF   r  r  keysr_  rF  r#   r,   r   r<  rG  r:   r%   rI  rZ  	show_menur$   SELECT_LAST
play_musicstop_ambiencerz   r6  )r   r  r  current_menusmenu_idr  s         rN   r  Server._show_main_menu  sg   #D*:DAM-..#M$6$6$89G+-%))'48  : === l..t{{FCO%))$++7KL& l..t{{NKP^_l..t{{NKP^_l..t{{JGJWl..t{{IF9U%))$++7MNS^E %%)9)9)?)??)--dkk;KLQa l..t{{NKP^_l..t{{IF9U%))$++7MNS^E 	X<#3#3DKK#JxXY*66'QT 	 	
 	&,2K+@$--(rP   c           	         [         R                  " 5       n/ n[        UR                  5       5       Ha  n[        R
                  " UR                  U5      n[        UR                  U/ 5      5      nUR                  [        U SU S3SU 3S95        Mc     UR                  [        [        R
                  " UR                  S5      SS95        UR                  SUS[        R                  S9  S	S0U R                  UR                  '   g
)zShow game categories menu. ()	category_r  backcategories_menuTr  r  r`  N)r*   get_by_categorysortedr!  r,   r   r<  r?  rZ  r#   r"  r$   r#  rz   r6  )r   r  
categoriesr  category_keycategory_name
game_counts          rN   _show_categories_menuServer._show_categories_menuE  s    !113
":??#45L(,,T[[,GMZ^^L"=>JLL)?"ZL:"<.1 6 	X<#3#3DKK#HVTU*66	 	 	
 -34E+F$--(rP   categoryc           	         [         R                  " 5       nUR                  U/ 5      n/ nU H\  n[        R                  " UR                  UR                  5       5      nUR                  [        USUR                  5        3S95        M^     UR                  [        [        R                  " UR                  S5      SS95        UR                  SUS[        R                  S9  SUS.U R                  UR                  '   g)	zShow games in a category.game_r  r-  
games_menuTr/  )r`  r8  N)r*   r0  r   r,   r<  rb  rZ  r#   r  r"  r$   r#  rz   r6  )r   r  r8  r2  r
  r  r	  re  s           rN   _show_games_menuServer._show_games_menu\  s    !113
x,J$((j6M6M6OPILLyuZ=P=P=R<S5TUV   	X<#3#3DKK#HVTU*66	 	 	
 5Ah+W$--(rP   c                    U R                   R                  U5      n[        U5      nU(       a/  [        R                  " UR
                  UR                  5       5      OUn[        [        R                  " UR
                  S5      SS9/nU H  n[        UR                  5      nUR                   V	s/ s H*  oR                  UR                  :w  d  M  U	R                  PM,     n
n	[        R                  " UR
                  U
5      nUS:X  a  SnOU
(       a  SnOSnUR                  [        [        R                  " UR
                  UUR                  UUS9S	UR                   3S95        M     UR                  [        [        R                  " UR
                  S
5      S
S95        UR                  SUS[         R"                  S9  SUUS.U R$                  UR                  '   gs  sn	f )z!Show available tables for a game.zcreate-tablecreate_tabler  r   ztable-listing-oneztable-listing-withztable-listing)r_   countrD  table_r-  tables_menuTr/  r`  r  re  N)ru   get_waiting_tablesr+   r,   r   r<  rb  r#   r?  rD  r6  r_   format_list_andrZ  r  r"  r$   r#  rz   )r   r  r  r  r	  re  r  r  member_countmembermember_namesmembers_strlisting_keys                rN   _show_tables_menuServer._show_tables_menuo  s   00;#I.
HRLT[[**A*A*CDXa 	 |//^LQ_`aEu}}-L.3mm.;FRWR\R\?\m   '66t{{LQKq 12-LL%))#"ZZ* +  /0	 2 	X<#3#3DKK#HVTU*66	 	 	
 """,
$--(?s   1G$G$c                    U R                   R                  5       nU(       d  UR                  S5        g/ nU GH8  n[        UR                  5      nU(       a/  [
        R                  " UR                  UR                  5       5      OUR                  n[        UR                  5      nUR                   Vs/ s H*  oR                  UR                  :w  d  M  UR                  PM,     n	n[
        R                  " UR                  U	5      n
US:X  a  SnOU	(       a  SnOSnUR                  [        [
        R                  " UR                  UUUR                  UU
S9SUR                    3S	95        GM;     UR                  [        [
        R                  " UR                  S
5      S
S	95        UR#                  SUS[$        R&                  S9  SS0U R(                  UR                  '   gs  snf )zqShow available tables across all games.

Returns True if the menu was shown, False if there was nothing to show.
zno-active-tablesFr   ztable-listing-game-oneztable-listing-game-withztable-listing-game)r  r_   r@  rD  rA  r  r-  active_tables_menuTr/  r`  )ru   rD  r  r+   r  r,   r   r<  rb  r?  rD  r6  r_   rE  rZ  r#   r  r"  r$   r#  rz   )r   r  r  r  r  r	  re  rF  rG  rH  rI  rJ  s               rN   _show_active_tables_menuServer._show_active_tables_menu  s   
 002LL+, "E'8J    j.E.E.GH__ 
 u}}-L.3mm.;FRWR\R\?\m   '66t{{LQKq 672LL%))#&"ZZ* +  /0
% > 	X<#3#3DKK#HVTU *66	 	 	
 -34H+I$--(?s   6G)G)dice-keeping-style-indexeszdice-keeping-style-valuesc           
      &   U R                  5       (       aB  SUR                   3n[        R                  " UR                  U5      nX2:X  a  UR                  nOO[        R                  " UR                  UR                  S9nUR                  UR                  UR                  5      n[        [        R                  " UR                  SUS9SS9[        [        R                  " UR                  S[        UR                  5      S9S	S9/n[         HB  u  pg[        R                  " UR                  U5      nUR                  [        US
U 3S95        MD     UR                  [        [        R                  " UR                  S5      SS95        UR                  [        [        R                  " UR                  S5      SS95        UR                  SUS[        R                  S9  UR                  S5        SS0U R                  UR                  '   g)zJShow top-level options menu: language, fluent langs, then pref categories.	language-fallbackzlanguage-optionlanguagerW  r  zfluent-languages-option)r@  r  	pref_cat_zpref-reset-allpref_reset_allr-  options_menuTr/  settingsmus.oggr`  N)r  r<  r,   r   get_available_languagesr#   r?  r  r(   rZ  r"  r$   r#  r$  rz   r6  )	r   r  lang_keycurrent_lang	languagesr  cat_key
cat_fluentcat_names	            rN   _show_options_menuServer._show_options_menu  s   ..00"4;;-0H'++DKKBL'#{{$<<T[[SWS^S^_I$==dkkBL !%%dkk3D|\ !%%KK-d334
 &
  $3G#''Z@HLLxiy4IJK $3
 	!%%dkk3CD#	
 	X<#3#3DKK#HVTU*66	 	 	
 	)*,2N+C$--(rP   refreshrf  c          
         UR                   n[        R                  " U5      n/ nU H>  u  pxU R                  UR                  XGU5      n	UR                  [        U	SU 3S95        M@     Sn
[         H-  u  pX:X  d  M  [        R                  " UR                  U5      n
  O   UR                  [        [        R                  " UR                  SU
S9SU 3S95        UR                  [        [        R                  " UR                  S5      SS95        U(       a  UR                  SU5        gUR                  SUS	[        R                  S
9  UR                  S5        SUS.U R                  UR                   '   g)zShow preferences within a category.

Args:
    refresh: If True, send a lightweight items-only update instead of
        a full menu rebuild.  Used after toggling a value so screen
        readers don't re-read the entire menu.
pref_r  r9  zpref-reset-category)r8  pref_reset_cat_r-  pref_category_menuTr/  r[  )r`  pref_categoryN)r  r&   get_fields_for_category_get_pref_labelr<  rZ  r#   r(   r,   r   update_menur"  r$   r#  r$  rz   r6  )r   r  r8  rf  prefspref_fieldsr  rG  metalabelrb  r`  ra  s                rN   _show_pref_category_menuServer._show_pref_category_menu  sT      %==hG "%JD((e4HELLu5@A &
 #2G"'++DKKD $3 	!%%dkk3HS[\$XJ/	
 	X<#3#3DKK#HVTU159NN$  . : :	   OO-.,!)0Ddmm,rP   
field_namec                   [         R                  " U5      nU(       d  gUR                  n[        XR5      n/ nU R	                  UR
                  XF5      n[        R                  " UR
                  S[        R                  " UR
                  S5      US9n	UR                  [        U	SS95        [        R                  " U5      n
U
(       a  U
 H  n[        R                  " U5      nU(       d  M"  [        R                  " UR
                  UR                  5       5      nUR                  X+5      (       a.  UR                  X+5      nU R	                  UR
                  XN5      nO![        R                  " UR
                  S5      n[        R                  " UR
                  SUUS9nUR                  [        USU 3S95        M     UR                  [        [        R                  " UR
                  S5      SS95        U(       a  UR                  S	U5        gUR!                  S	US
["        R$                  S9  UR'                  S5        S	UUR(                  S.U R*                  UR,                  '   g)a7  Show detail menu for a pref with per-game overrides.

Layout:
    Default: <global value>  (toggle/choose)
    Farkle: Default          (toggle/choose with Default option)
    Yahtzee: On
    ...
    Back

Args:
    refresh: If True, send a lightweight items-only update instead of
        a full menu rebuild.
Nzpref-per-game-forpref-default)r  r:   detail_globalr  detail_game_r-  pref_detail_menuTr/  r[  )r`  
pref_fieldrk  )r&   get_pref_metar  r?  _format_pref_valuer<  r,   r   rZ  r#   r*   get_games_for_preferencerb  has_game_overrideget_game_overridern  r"  r$   r#  r$  r8  rz   r6  )r   r  ru  rf  rq  ro  global_valuer  global_displaydefault_labelrelevant_gamesr  game_clsre  raw
value_textrr  s                    rN   _show_pref_detail_menuServer._show_pref_detail_menu;  s     ,,Z8  u1 " 00dQ$((KK!!$++~> 	
 	X=_EF &>>zJ+	'++I6(,,T[[(:O:O:QR	**:AA11*HC!%!8!8d!PJ!-!1!1$++~!NJ$((KK'"$	 X5|I;5OPQ% ,( 	X<#3#3DKK#HVTU/7NN"  . : :	   OO-.*(!%0Ddmm,rP   c           	         UR                   n[        R                  " U5      nU(       a  UR                  (       d  g/ nSnU(       a  UR	                  X#5      nUb  [        U5      OSn	USL n
U
(       a  SOSnUR                  [        U [        R                  " UR                  S5       3SS95        U
(       a  Sn[        UR                  SS	9 Hc  u  nu  pX:H  nU(       a  SOSn[        R                  " UR                  U5      nUR                  [        U U 3S
U 3S95        U(       d  Ma  UnMe     O[        XB5      n[        U[        5      (       a  UR                  n[        UR                  SS	9 H]  u  nu  pUU:X  a  SOSn[        R                  " UR                  U5      nUR                  [        U U 3S
U 3S95        UU:X  d  M[  UnM_     UR                  [        [        R                  " UR                  S5      SS95        UR!                  SUS["        R$                  US9  UR'                  S5        SUUR(                  US.U R*                  UR,                  '   g)zShow menu choices for a menu-type preference.

If game_type is set, shows choices for a per-game override
(with an extra Default option at the top).
Nr   * r9  rw  choice_defaultr  r   r   choice_r-  pref_choices_menuTr  r[  )r`  r{  rk  pref_game_type)r  r&   r|  choicesr  rH   rZ  r#   r,   r   r<  	enumerater?  rF   r	   r:   r"  r$   r#  r$  r8  rz   r6  )r   r  ru  r  ro  rq  r  selected_positioncurrent_overridecurrent_raw
is_defaultprefixindexr:   
fluent_keyis_selectedchoice_namecurrent_values                     rN   _show_pref_menu_choicesServer._show_pref_menu_choices  sA      ,,Z84<< "$66zM3C3O#./UYK *T1J'TRFLL"8L$4$4T[[.$Q#RS' $%!.7A.N**)2!,"*..t{{JGXfXk]+C'RWQXHYZ[;(-% /O $E6M-.. - 3 3.7A.N**!&-!7R*..t{{JGXfXk]+C'RWQXHYZ[M)(-% /O 	X<#3#3DKK#HVTU*66& 	 	
 	)*'$!]]'	,
$--(rP   ro  rG  rq  c                 D   [        X#5      nUR                  S:X  a>  [        R                  " X(       a  SOS5      n[        R                  " XR                  US9$ UR                  S:X  a  UR
                  (       a  [        U[        5      (       a  UR                  OUnUR
                   HA  u  pX:X  d  M  [        R                  " X5      n
[        R                  " XR                  U
S9s  $    [        R                  " XR                  [        U5      S9$ [        U5      $ )z4Generate the localized label for a preference field.rG   	option-on
option-off)statusr`  choice)
r?  kindr,   r   rr  r  rF   r	   r:   rH   )r   r<  ro  rG  rq  r:   r  r  
choice_valr  choice_texts              rN   rm  Server._get_pref_label  s     $99!%%fUkUF##FJJvFFYY& T\\!+E4!8!8%++eC*.,,&
$"."2"26"FK'++FJJ{SS +7  ##FJJs3xHH5zrP   r  c                    UR                   S:X  a#  [        R                  " X(       a  S5      $ S5      $ UR                   S:X  az  UR                  (       ai  [	        U[
        5      (       a  UR                  O
[        U5      nUR                   H"  u  pVXT:X  d  M  [        R                  " X5      s  $    [        U5      $ [        U5      $ )z*Format a raw preference value for display.rG   r  r  r`  )r  r,   r   r  rF   r	   r:   rH   )r   r<  rq  r  val_strr  r  s          rN   r}  Server._format_pref_value  s    99##F3KQQLQQYY& T\\#-c4#8#8ciic#hG*.,,&
('++F?? +7 s8O3xrP   	lang_codec                 v  #    U R                  5       (       a#  U R                  U5        U R                  U5        g[        R                  " UR
                  S9nX#;   aI  UR                  U5        U R                  R                  UR                  U5        UR                  SX2   S9  U R                  U5        g7f)z-Apply a locale change from the language menu.NrT  zlanguage-changedrV  )r  r  rc  r,   r\  r<  r  rs   update_user_localer6  r  )r   r  r  r_  s       rN   _apply_locale_changeServer._apply_locale_change  s     ..0011$7##D) 88$++N	!OOI&HH''yALL+i6JLK%s   B7B9selection_idc                 <   #    SSK Jn  U" X5      I Sh  vN   g N7f)z@Thin wrapper that delegates to the common language menu handler.r   )handle_language_menu_selectionN)server.core.ui.common_flowsr  )r   r  r  r  s       rN   _handle_language_menu_dispatch%Server._handle_language_menu_dispatch  s     N,T@@@s   c                    U R                   R                  UR                  5      nU(       d  UR                  S5        g/ nU H3  nUR	                  [        UR                  SUR                   3S95        M5     UR	                  [        [        R                  " UR                  S5      SS95        UR                  SUS[        R                  S9  S	S0U R                  UR                  '   g)
zaShow saved tables menu.

Returns True if the menu was shown, False if there was nothing to show.
zno-saved-tablesFsaved_r  r-  saved_tables_menuTr/  r`  )rs   get_user_saved_tablesr6  r  rZ  r#   	save_namer  r,   r   r<  r"  r$   r#  rz   )r   r  savedr  records        rN   _show_saved_tables_menuServer._show_saved_tables_menu  s    
 ..t}}=LL*+FLLv'7'7fVYYK<PQR X<#3#3DKK#HVTU*66	 	 	
 -34G+H$--(rP   save_idc                 p   [        [        R                  " UR                  S5      SS9[        [        R                  " UR                  S5      SS9[        [        R                  " UR                  S5      SS9/nUR	                  SUS[
        R                  S	9  SUS
.U R                  UR                  '   g)z1Show actions for a saved table (restore, delete).zrestore-tablerestorer  zdelete-saved-tabledeleter-  saved_table_actions_menuTr/  )r`  r  N)	r#   r,   r   r<  r"  r$   r#  rz   r6  )r   r  r  r  s       rN   _show_saved_table_actions_menu%Server._show_saved_table_actions_menu  s     ,**4;;HYW,**4;;8LMRZ[,**4;;?FK

 	&*66	 	 	
 /,
$--(rP   c                   #    UR                   nU(       d  gU R                  R                  U5      nU(       d  gUR                  SS5      nU R                  R                  U0 5      nUR                  S5      nU R	                  XGU5        U R
                  R                  U5      nU(       a  UR                  (       a  UR                  R                  UR                  5      n	U	(       ao  UR                  R                  X5        UR                  R                  R                  UR                  5      n
XLa!  UR                  U5        U R                  USS9  gU R                  XEXg5      I Sh  vN   U R                  UUUUS9  g N7f)zhHandle menu selection packets.

Args:
    client: Client connection.
    packet: Incoming menu payload.
Nr  r9  r`  Tr  )r  previous_menur  previous_state)r6  ry   r   rz   _remember_menu_positionru   r@  r  rA  rB  handle_eventrE  r  _dispatch_menu_selection"_prune_menu_history_after_dispatch)r   r/  r6  r6  r  r  rd  current_menur  r
  	game_users              rN   r  Server._handle_menu,  sC     ??{{x(zz."5!!%%h3yy($$T@ ,,X6UZZZZ00;F

''7!JJ--11$))<	(''1((T(B++DTTT//&% 	 	0 	
 	Us   E-F	/F0F	r  r  c                   U(       d  gU R                   R                  UR                  0 5      nUR                  S5      nXb:X  a  gUS:w  a  US:w  a  g[        USS5      n[	        U[
        5      (       a  UR                  US5        gg)zPrune menu history for back-style navigation.

Keeps position memory scoped to the current navigation path by dropping
menus that were exited with a back action.
Nr`  r-  online_usersr  )rz   r   r6  r?  rF   r  r_  )r   r  r  r  r  	new_statenew_menur&  s           rN   r  )Server._prune_menu_history_after_dispatchV  s     %%))$--<	==($6!m~&E&6=mT**mT2 +rP   r  c                    U(       d  g[        US0 5      nUR                  U5      nU(       d  gUR                  S5      n[        U[        5      (       a  US:  a  XeS'   gUR                  S5      nU(       d  gUR                  S/ 5      n[	        USS	9 H7  u  p[        U
[
        5      (       d  M  U
R                  S
5      U:X  d  M3  XS'     g   g)z<Store the user's last selected position for the active menu.Nr  	selectionr   r   r  r  r   r  r  )r?  r   rF   rK   r  r  )r   r  r  r6  r&  
menu_stater  r  r  r  items              rN   r  Server._remember_menu_positiono  s     &6;"&&|4
JJ{+	i%%)a-%.z"zz.1w+$U!4KE$%%$((4.L*H).:& 5rP   rd  c                   #    0 SU R                   X44_SU R                  X44_SU R                  XU44_SU R                  XU44_SU R                  XU44_SU R
                  X44_SU R                  XU44_SU R                  X44_S	U R                  X44_S
U R                  X44_SU R                  X44_SU R                  X44_SU R                  X44_SU R                  XU44_SU R                  XU44_SU R                  XU44_SU R                   XU44_U R"                  XU44U R$                  XU44U R&                  XU44S.EU R)                  XU5      E0 SU R*                  X44_SU R,                  X44_SU R.                  X44_SU R0                  XU44_SU R2                  X44_SU R4                  X44_SU R6                  XU44_SU R8                  XU44_SU R:                  XU44_SU R<                  X44_SU R>                  XU44_SU R@                  XU44_SU RB                  X44_S U RD                  X44_S!U RF                  XU44_S"U RH                  XU44_S#U RJ                  X44_ES$U RL                  X440EnU(       d  g%URO                  U5      nU(       d  g%Uu  pxU" U6 n	[P        RR                  " U	5      (       a  U	I S%h  vN   g%g% N7f)&z7Dispatch menu selections based on current menu context.r  logout_confirm_menur.  r;  rB  rN  	join_menurZ  language_menudice_keeping_style_menurj  rz  r  r  r  leaderboards_menuleaderboard_types_menu)game_leaderboardmy_stats_menumy_game_statsr  
admin_menuaccount_approval_menupending_user_actions_menupromote_admin_menudemote_admin_menupromote_confirm_menudemote_confirm_menubroadcast_choice_menutransfer_ownership_menutransfer_ownership_confirm_menutransfer_broadcast_choice_menuban_user_menuunban_user_menuban_confirm_menuunban_confirm_menuvirtual_bots_menuvirtual_bots_clear_confirm_menuN)*_handle_main_menu_selection _handle_logout_confirm_selection_handle_categories_selection_handle_games_selection_handle_tables_selection_handle_active_tables_selection_handle_join_selection_handle_options_selectionr  $_handle_dice_keeping_style_selection_handle_pref_category_selection_handle_pref_detail_selection_handle_pref_choices_selection_handle_saved_tables_selection%_handle_saved_table_actions_selection_handle_leaderboards_selection#_handle_leaderboard_types_selection"_handle_game_leaderboard_selection_handle_my_stats_selection_handle_my_game_stats_selection_get_document_menu_handlers_restore_previous_menu_handle_admin_menu_selection"_handle_account_approval_selection&_handle_pending_user_actions_selection_handle_promote_admin_selection_handle_demote_admin_selection!_handle_promote_confirm_selection _handle_demote_confirm_selection"_handle_broadcast_choice_selection$_handle_transfer_ownership_selection,_handle_transfer_ownership_confirm_selection+_handle_transfer_broadcast_choice_selection_handle_ban_user_selection_handle_unban_user_selection_handle_ban_confirm_selection_handle_unban_confirm_selection_handle_virtual_bots_selection,_handle_virtual_bots_clear_confirm_selectionr   r   iscoroutine)
r   r  r  rd  r  handlershandler_entryfuncargsr  s
             rN   r  Server._dispatch_menu_selection  s    R7
$::T<PQR7
!D$I$IDK_#`R7
  A ADX]C^_R7
 477$e9TU	R7

 D99DPU;VWR7
 !4#G#G$I]"^R7
 $55E7RSR7
 T;;d=QRR7
 dAADCWXR7
 &99$(R7
 !4#G#G$I]"^R7
 !C!CdEY ZR7
   $"E"EG[!\!R7
"  $"E"E\aGb!c#R7
$ '::U+)%R7
,  $"E"E\aGb!c-R7
. %88U+'/R7
8 77U+! #==TY?Z["BBTY^D_`AR7
B ..t5ICR7
D T884-HER7
F 4<<t>RSGR7
H $77$&IR7
P (;;U+*QR7
X !4#G#G$I]"^YR7
Z  $"E"EG[!\[R7
\ #66U+%]R7
d "55U+$eR7
l $77U+&mR7
t &99$(uR7
| .AAU+0}R7
D -@@U+/ER7
L d==?STMR7
N  A ADCWXOR7
P !C!CdZ_E` aQR7
R !44U+#SR7
Z  $"E"EG[!\[R7
\ .AA$0]R7
f  \2"
tv&&LL 's   KKKKc                     UR                   (       a  gUR                  R                  [        R                  R                  :  a  gUR                  SSS9  U R                  U5        g)zVReturn True if user is approved or admin/server owner; otherwise show approval notice.Tr  rR  r  F)rF  rG  r:   r%   rI  r  r  r  s     rN   _ensure_user_approvedServer._ensure_user_approved  sQ    ==!!Z%5%5%;%;;+J?T"rP   c                 ,  #    US:X  a)  U R                  U5      (       d  gU R                  U5        gUS:X  a/  U R                  U5      (       d  gU R                  U5      (       d  ggUS:X  a/  U R                  U5      (       d  gU R                  U5      (       d  ggUS:X  a)  U R                  U5      (       d  gU R	                  U5        gUS:X  a/  U R                  U5      (       d  gU R                  U5      (       d  ggUS:X  a  U R                  U5        gUS:X  a  U R                  U5        gUS	:X  aE  UR                  R                  [        R                  R                  :  a  U R                  U5        ggUS
:X  a  U R                  U5        gg7f)zdHandle main menu selections.

Args:
    user: Acting user.
    selection_id: Selected menu item id.
r  Nr  r  r  r  r  r  r  r  )r#  r6  rO  r  _show_leaderboards_menu_show_my_stats_menu_show_documents_menurc  rG  r:   r%   rI  _show_admin_menu_show_logout_confirm_menur   r  r  s      rN   r  "Server._handle_main_menu_selection  s~     6!--d33&&t,_,--d330066 7^+--d33//55 6^+--d33((.Z'--d33++D11 2[(%%d+Y&##D)--%%)9)9)?)??%%d+ @X%**40 &s   FFc                     [         R                  " UR                  S5      n[        USU5        SS0U R                  UR
                  '   g)z'Show confirmation menu for logging out.zconfirm-logoutr  r`  N)r,   r   r<  r-   rz   r6  )r   r  questions      rN   r*   Server._show_logout_confirm_menu	  s@    ##DKK1AB4h?,24I+J$--(rP   c                    #    US:X  a7  UR                  SSS9  UR                  R                  SSS.5      I Sh  vN   gU R                  U5        g N7f)	z*Handle logout confirmation menu selection.r@   goodbyerR  r  rJ  F)rF  rL  N)r  r$  rP  r  r+  s      rN   r  'Server._handle_logout_confirm_selection%	  sN     5 LL:L6//&&5'QRRR  & Ss   8AAAc                   ^ #    US:X  a?  SSK Jn  U" UST R                  U 4S jS9(       a  SS0T R                  UR                  '   ggUS	:X  a  T R                  U5        gUR                  S
5      (       a  USS nT R                  X5        gUS:X  aN  UR                  R                  5         T R                  U5        UR                  S5        T R                  U5        gUS:X  a  T R                  U5        gg7f)z)Handle top-level options menu selections.rW  r   show_language_menuTc                 &   > TR                  U 5      $ r   )rc  )ur   s    rN   <lambda>2Server._handle_options_selection.<locals>.<lambda>6	  s    $"9"9!"<rP   )include_native_names	on_selecton_backr`  r  r  rX  	   NrY  pref-reset-doner-  )r  r5  r  rz   r6  _show_fluent_languages_menu
startswithrs  r  	reset_all_save_user_preferencesr  rc  r  )r   r  r  r5  r8  s   `    rN   r   Server._handle_options_selection-	  s     :%F!%)33<	 5;O3L!!$--0 //,,T2$$[11#AB'H))$9--&&(''-LL*+##D)V#  & $s   C8C;c           	      f  ^ ^ T R                  5       (       a  T R                  U5        gSSKJn  [	        UR
                  5      mS[        S[        [           SS4U 4S jjnS[        SS4UU 4S jjnU" US	S
[        UR
                  5      UUS9(       a  SS0T R                  UR                  '   gg)z"Show fluent languages toggle menu.Nr   r4  r7  selectedr<   c                    > [        U5      U R                  S S & TR                  R                  U R                  U R                  5        TR                  U 5        g r   )r  r  rs   set_user_fluent_languagesr6  rc  )r7  rE  r   s     rN   on_done3Server._show_fluent_languages_menu.<locals>.on_doneP	  sC    $(NAq!HH..qzz1;M;MN##A&rP   c                 F   > TU R                   S S & TR                  U 5        g r   )r  rc  )r7  originalr   s    rN   	on_cancel5Server._show_fluent_languages_menu.<locals>.on_cancelU	  s!    $,Aq!##A&rP   FT)highlight_active_localemulti_selectrE  rH  rL  r`  r  )r  r  r  r5  r  r  r"   setrH   rz   r6  )r   r  r5  rH  rL  rK  s   `    @rN   r?  "Server._show_fluent_languages_menuF	  s    ..0011$7B--.	'{ 	'c#h 	'4 	'
	' 	' 	' 	' $)../
 17/HDdmm,
rP   c                 X   / nUR                   R                  nSn[        U R                  R	                  5       SS9 He  u  nu  pgXc:X  a  SOSn[
        R                  " UR                  U5      n	UR                  [        U U	 3SUR                   3S95        Xc:X  d  Mc  UnMg     UR                  [        [
        R                  " UR                  S5      SS95        UR                  SUS	[        R                  US
9  UR                  S5        SS0U R                  UR                   '   g)z'Show dice keeping style selection menu.r   r  r  r9  style_r  r-  r  Tr  r[  r`  N)r  dice_keeping_styler  DICE_KEEPING_STYLESr  r,   r   r<  rZ  r#   r:   r"  r$   r#  r$  rz   r6  )
r   r  r  current_styler  r  stylerc  r  rG  s
             rN   _show_dice_keeping_style_menu$Server._show_dice_keeping_style_menuc	  s   ((;;(1$2J2J2P2P2RZ[(\$E$E"3TF##DKK:DLL&$'8vekk]=STU%$)! )] 	X<#3#3DKK#HVTU%*66& 	 	
 	)*,24M+N$--(rP   c                   #    UR                  S5      (       a  USS n[        R                  " U5      nXAR                  l        U R                  U5        U R                  R                  US5      n[        R                  " UR                  U5      nUR                  SUS9  U R                  U5        gU R                  U5        g7f)zlHandle dice keeping style selection.

Args:
    user: Acting user.
    selection_id: Selected menu item id.
rS     NrQ  zdice-keeping-style-changed)rW  )r@  r'   from_strr  rT  rB  rU  r   r,   r<  r  rc  )r   r  r  style_valuerW  	style_key
style_names          rN   r  +Server._handle_dice_keeping_style_selectiony	  s      ""8,,&qr*K$--k:E27/''-0044U<XYI%))$++yAJLL5ZLH##D)%s   CCc                   #    U R                   R                  UR                  0 5      nUR                  SS5      nUR                  S5      (       a  UR                  S5      (       d  USS n[        R
                  " U5      nU(       d  g[        R                  " U5      (       a  U R                  X5        gUR                  S:X  ad  UR                  n[        Xu5      (       + n[        XuU5        UR                  U(       a  SOS	5        U R                  U5        U R                  XS
S9  gUR                  S:X  a  U R!                  X5        ggUR                  S5      (       aS  USS n	UR                  R#                  U	5        U R                  U5        UR%                  S5        U R                  XS
S9  gUS:X  a  U R'                  U5        gg7f)z4Handle selections within a preference category menu.rk  r9  rh  pref_reset_r5   NrG   checkbox_list_on.wavcheckbox_list_off.wavTre  r`  ri     r>  r-  )rz   r   r6  r@  r&   r|  r*   r~  r  r  r  r?  setattrrC  rB  rs  r  reset_categoryr  rc  )
r   r  r  rd  r8  ru  rq  ro  new_valcats
             rN   r  &Server._handle_pref_category_selection	  s    !!%%dmmR899_b1""7++L4K4KM4Z4Z%ab)J"00<D44Z@@++D=f$((%e8873' 6G^_++D1--dd-Kf$,,T> % $$%677rs#C++C0''-LL*+))$T)BV###D) $s   GGc                 B  #    U R                   R                  UR                  0 5      nUR                  SS5      nUR                  SS5      n[        R                  " U5      nU(       d  gUS:X  a  UR
                  S:X  ad  UR                  n[        Xt5      (       + n[        XtU5        UR                  U(       a  SOS5        U R                  U5        U R                  XS	S
9  gUR
                  S:X  a  U R                  X5        ggUR                  S5      (       a  USS n	UR
                  S:X  a  UR                  nUR                  XI5      n
U
c$  UR                  XIS	5        UR                  S5        OKU
S	L a$  UR                  XIS5        UR                  S5        O"UR!                  XI5        UR                  S5        U R                  U5        U R                  XS	S
9  gUR
                  S:X  a  U R                  XU	S9  ggUS:X  a  U R#                  X5        gg7f)zDHandle selections in the pref detail menu (global + per-game lines).r{  r9  rk  Nrx  rG   rc  rd  Tre  r`  ry     F)r  r-  )rz   r   r6  r&   r|  r  r  r?  rf  rC  rB  r  r  r@  r  set_game_overrideclear_game_overriders  )r   r  r  rd  ru  r8  rq  ro  rh  r  r   s              rN   r   $Server._handle_pref_detail_selection	  s    !!%%dmmR8YY|R0
99_b1,,Z8?*yyF"((%e8873' 6G^_++D1++Dd+Kf$,,T> %$$^44$RS)IyyF"((11*H?++J4HOO$:;_++J5IOO$;<--jDOO$;<++D1++Dd+Kf$,,T,S %V#))$9 $s   HHc                   #    U R                   R                  UR                  0 5      nUR                  SS5      nUR                  SS5      nUR                  S5      n[        R                  " U5      nUR                  S5      (       Ga9  U(       Ga1  USS nU(       aa  US:X  a  UR                  R                  XF5        OUR                  R                  XFU5        U R                  U5        U R                  X5        gUR                  n	UR                  (       a   UR                  U5      n
OUn
[%        XU
5        U R                  U5        U R'                  UR(                  Xz5      nUR#                  UR*                  US9  [,        R.                  " U5      (       a  U R                  X5        gU R1                  X5        gUS:X  aF  U(       a-  [,        R.                  " U5      (       a  U R                  X5        gU R1                  X5        gg! [        [        4 aB    UR                  n
[        R!                  S	UR                  X5        UR#                  S
5         GN0f = f7f)z.Handle menu choice selection for a preference.r{  r9  rk  r  r     Nr;   zNUser '%s' sent invalid preference value '%s' for '%s', falling back to defaultzpref-invalid-valuer  r-  )rz   r   r6  r&   r|  r@  r  rn  rm  rB  r  
enum_classr   r  r;   r   r   r  rf  r}  r<  
change_msgr*   r~  rs  )r   r  r  rd  ru  r8  r  rq  	value_strro  rh  displays               rN   r  %Server._handle_pref_choices_selection	  s    !!%%dmmR8YY|R0
99_b1II./	,,Z8""9--$$QR(I	)$$88O$$66ziX++D1++D= ((??	;"&//)"< (G73++D111$++tMT__W=88DD//A11$AV#==jII++D=--d=	 $) '1 ;"&,,6 MM9
 %9:;s,   DI&H /C"I&AI#I&"I##I&c                     [         R                  " UR                  R                  5       5      nU R                  R                  UR                  U5        g)z"Save user preferences to database.N)r  dumpsr  to_dictrs   update_user_preferencesr6  )r   r  
prefs_jsons      rN   rB  Server._save_user_preferences
  s7    ZZ 0 0 8 8 :;
((
CrP   c                    #    UR                  S5      (       a  USS nU R                  X5        gUS:X  a  U R                  U5        gg7f)zHandle category selection.

Args:
    user: Acting user.
    selection_id: Selected category id.
    state: Current menu state.
r,  r=  Nr-  )r@  r<  r  )r   r  r  rd  r8  s        rN   r  #Server._handle_categories_selection
  sM      "";//#AB'H!!$1V#  & $   AA	c                    #    UR                  S5      (       a  USS nU R                  X5        gUS:X  a  U R                  U5        gg7f)zxHandle game selection.

Args:
    user: Acting user.
    selection_id: Selected game id.
    state: Current menu state.
r:  r5   Nr-  )r@  rK  r6  r   r  r  rd  r  s        rN   r  Server._handle_games_selection&
  sM      ""7++$QR(I""43V#&&t, $r  c                   ^#    UR                  SS5      mUS:X  a  U R                  R                  TUR                  U5      n[	        T5      nU(       a  U" 5       nXdl        XFl        UR                  UR                  U5        U R                  UR                  T5        UR                  5       nUR                  5       nUR                  S[        UR                  5      UUSS9  SUR                  S.U R                  UR                  '   gUR!                  S	5      (       a^  US
S n	U R                  R#                  U	5      nU(       a  U R%                  XT5        gUR                  S5        U R'                  UT5        gUS:X  aw  Sn
[(        R*                  " 5       R-                  5        H#  u  p[/        U4S jU 5       5      (       d  M!  Un
  O   U
(       a  U R1                  X5        gU R3                  U5        gg7f)zHandle tables menu selection.

Args:
    user: Acting user.
    selection_id: Selected table id or action.
    state: Current menu state.
r  r9  r?  zwaiting-for-playersr  )r   r>  r   r  ra  r  rA  r[  Ntable-not-existsr-  c              3   H   >#    U  H  oR                  5       T:H  v   M     g 7fr   )r  )r   gr  s     rN   r   2Server._handle_tables_selection.<locals>.<genexpr>j
  s     @%Qzz|y0%s   ")r   ru   r?  r6  r+   r  r  initialize_lobbyrf  get_min_playersget_max_playersr  r?  r  r  rz   r@  	get_table_auto_join_tablerK  r*   r0  r  anyr<  r6  )r   r  r  rd  r  r	  r  min_playersmax_playersr  r8  ri  r
  r  s                @rN   r  Server._handle_tables_selection6
  s     IIk2.	>)LL--iME (	2J!|!
#%%dmmT: --dmmYG(88:(88:)-##"   "!NN0Ddmm,
 $$X..#AB'HLL**84E%%d9=/0&&tY7V#H*::<BBD
@%@@@"H E %%d5**40 $s   GG?2G?c                 d  #    UR                  S5      (       a~  USS nU R                  R                  U5      nU(       a  U R                  XUR                  5        gUR                  S5        U R                  U5      (       d  U R                  U5        ggUS:X  a  U R                  U5        gg7f)zrHandle active tables menu selection.

Args:
    user: Acting user.
    selection_id: Selected table id or action.
rA  r[  Nr  r-  )r@  ru   r  r  r  r  rO  r  )r   r  r  r  r  s        rN   r  &Server._handle_active_tables_selectionr
  s      ""8,,#AB'HLL**84E%%d5??C/044T::((. ;V#  & $s   B.B0r  Tablec                 R   UR                   nU(       d#  UR                  S5        U R                  X5        gUR                  nUR                  S:g  =(       a-    [        S UR                   5       5      UR                  5       :  nU(       as  UR                  UR                  USS9  UR                  UR                  U5        UR                  SUR                  S9  UR                  S	5        UR                  5         OUR                  UR                  US
S9  UR                  UR                  U5        UR                  SUR                  S9  UR                  SUR                  S9  UR                  S5        UR                  5         SUS.U R                   UR                  '   g)aB  Automatically join a table as player or spectator.

Joins as player if:
    - Game has not started yet (status is "waiting").
    - Game has room for more players (less than max_players).
Otherwise joins as spectator.

Args:
    user: User joining the table.
    table: Table to join.
    game_type: Game type identifier.
r  Nplayingc              3   J   #    U  H  oR                   (       a  M  S v   M     g7fr   Nis_spectatorr   ps     rN   r   *Server._auto_join_table.<locals>.<genexpr>
  s     B|!>>AA|   #	#Fr  table-joinedr  r  Tspectator-joinedr_   now-spectatingjoin_spectator.oggra  r  )r  r  rK  r  r  sumr  r  r  r6  
add_playerr  r  r  add_spectatorr_   rz   )r   r  r  r  r  r  can_join_as_players          rN   r  Server._auto_join_table
  sa    zzLL+,""43>> KK9$ \Bt||BBTEYEYE[[ 	
 T]]DuEOODMM40^DMMB  ,""$ T]]DtDt}}d3LL+%**L=-dmmD  !56""$4=8+T$--(rP   c                     UR                  S5      S:X  a)  U R                  U5      (       d  U R                  U5        ggU R                  XR                  SS5      5        g)z1Return to the appropriate tables menu after join.return_menurN  r  r9  N)r   rO  r  rK  )r   r  rd  s      rN   _return_from_join_menuServer._return_from_join_menu
  sS    99]#';;0066$$T* 7 ""4;)CDrP   c                   #    UR                  S5      nU R                  R                  U5      nU(       a  UR                  (       d#  UR	                  S5        U R                  X5        gUR                  nUS:X  Ga~  UR                  S:X  Ga  SnUR                   H4  nUR                  UR                  :X  d  M  UR                  (       d  M2  Un  O   U(       a  SUl
        UR                  UR                  U5        UR                  UR                  USS9  UR                  SUR                  S	9  UR                  S
5        UR!                  5         SUS.U R"                  UR                  '   gUR                  UR                  USS9  UR%                  UR                  U5        UR	                  SUR&                  S9  UR                  SUR                  S	9  UR                  S5        UR!                  5         SUS.U R"                  UR                  '   g[)        S UR                   5       5      n	XR+                  5       :  a#  UR	                  S5        U R                  X5        gUR                  UR                  USS9  UR-                  UR                  U5        UR                  SUR                  S	9  UR                  S
5        UR!                  5         SUS.U R"                  UR                  '   gUS:X  a  UR                  UR                  USS9  UR%                  UR                  U5        UR	                  SUR&                  S9  UR                  SUR                  S	9  UR                  S5        UR!                  5         SUS.U R"                  UR                  '   gUS:X  a  U R                  X5        gg7f)zHandle join menu selection.

Args:
    user: Acting user.
    selection_id: Selected join option.
    state: Current menu state.
r  r  Njoin_playerr  Fr  r  r  r  ra  r  Tr  r  r  r  c              3   J   #    U  H  oR                   (       a  M  S v   M     g7fr  r  r  s     rN   r   0Server._handle_join_selection.<locals>.<genexpr>
  s     M,Qnnqq,r  z
table-fullr  join_spectatorr-  )r   ru   r  r  r  r  r  r  r  rB  r  r  r  r6  r  r  r  rz   r  r_   r  r  r  )
r   r  r  rd  r  r  r  matching_playerr  active_counts
             rN   r  Server._handle_join_selection
  s0     99Z(&&x0EJJLL+,''4zz=({{i'"&Atttyy(QXXX*+ &
 #-2O*$$_%7%7>$$T]]Du$M$$%7$N((4**, )$,8D%%dmm4  $$T]]Dt$L&&t}}d;LL!3%**LE$$%5dmm$L(()=>**, )$,8D%%dmm4 M$,,MML3355\*++D8 T]]DuEOODMM40^DMMB  ,""$8Ax/XDdmm,--T]]DtDt}}d3LL+%**L=-dmmD  !56""$8Ax/XDdmm,V#''4 $s   B5O;OK5Oc                    #    UR                  S5      (       a   [        USS 5      nU R                  X5        gUS:X  a  U R                  U5        gg7f)zHandle saved tables menu selection.

Args:
    user: Acting user.
    selection_id: Selected saved table item.
    state: Current menu state.
r  r[  Nr-  )r@  rK   r  r  r   r  r  rd  r  s        rN   r  %Server._handle_saved_tables_selection  sR      ""8,,,qr*+G//>V#  & $s   AAc                   #    UR                  S5      nU(       d  U R                  U5        gUS:X  a  U R                  X5      I Sh  vN   gUS:X  aU  U R                  R	                  U5        UR                  S5        U R                  U5      (       d  U R                  U5        ggUS:X  a)  U R                  U5      (       d  U R                  U5        ggg N7f)zHandle saved table actions (restore/delete).

Args:
    user: Acting user.
    selection_id: Selected action id.
    state: Current menu state.
r  Nr  r  zsaved-table-deletedr-  )r   r  _restore_saved_tablers   delete_saved_tabler  r  r  s        rN   r  ,Server._handle_saved_table_actions_selection  s      ))I&  &9$++D:::X%HH''0LL.///55$$T* 6V#//55$$T* 6 $ ;s   ACCBCc                   #    SSK nSSKJn  U R                  R	                  U5      nU(       d#  UR                  S5        U R                  U5        g[        UR                  5      nU(       d#  UR                  S5        U R                  U5        gUR                  " UR                  5      nU Vs/ s H  oR                  SS5      (       a  M  UPM     n	n/ n
U	 Hk  nUR                  S	5      nXR                  ;  a  U
R                  U5        M6  U R                  R                  U5      nU(       d  MZ  U
R                  U5        Mm     U
(       aG  UR                  S
SR!                  U
5      S9  U R#                  U5      (       d  U R                  U5        gU R                  R%                  UR                  UR&                  U5      nUR)                  UR*                  5      nUR-                  5         Xl        Xl        UR&                  Ul        U H  nUR                  S	5      nUR                  SS5      nUR5                  U5      nU(       d  M@  U(       a/  U" UUR6                  S9nUR9                  UR6                  U5        Mv  U R                  R                  U5      nU(       d  M  UR;                  UUSS9  UR9                  UR6                  U5        SUR<                  S.U R>                  U'   M     URA                  5         URC                  5         URE                  S5        U R                  RG                  U5        gs  snf 7f)zgRestore a saved table into an active table.

Args:
    user: Acting user.
    save_id: Saved table id.
r   Nr   r  r  game-type-not-foundr  Fr6  zmissing-players, )r  )rB  r  ra  r  ztable-restored)$r  r  r  rs   get_saved_tabler  r  r+   r  r  members_jsonr   ry   rZ  ru   r@  joinr  r?  r6  r  r  r  r  r  r_   get_player_by_namer  r  r  r  rz   r   r  r  r  )r   r  r  r  r  r  r	  members_datamhuman_playersmissing_playersrG  member_usernameexisting_tabler  r  r  r
  r  member_users                       rN   r  Server._restore_saved_table7  s     	"))'2LL+,  & $F$4$45
LL./  & zz&"5"56$0OLqh8NLO #F$jj4Okk1&&7 "&!=!=o!N!>#**?; $ LL*DIIo4NLO//55$$T* ))&*:*:DMM4P ##F$4$45""$
 MM	
 #F$jj4OZZ%0F ,,_=FVYY?  H5 #kkooo>;$$_kPU$V$$VYY< )$)NN:D%%o6' #4 	 	  	)* 	##G,M Ps'   B/M1MMAM5E4M-BMc           
          [         R                  " 5       n/ n[        UR                  5       5       Hg  nX$    H\  n[        R
                  " UR                  UR                  5       5      nUR                  [        USUR                  5        3S95        M^     Mi     UR                  [        [        R
                  " UR                  S5      SS95        UR                  SUS[        R                  S9  SS0U R                  UR                  '   g)	zEShow leaderboards game selection menu.

Args:
    user: Acting user.
lb_r  r-  r  Tr/  r`  N)r*   r0  r1  r!  r,   r   r<  rb  rZ  r#   r  r"  r$   r#  rz   r6  )r   r  r2  r  r3  r	  re  s          rN   r&  Server._show_leaderboards_menu  s     "113
 #:??#45L(6
(,,T[[*:Q:Q:ST	X93z?R?R?T>U9VWX 7 6
 	X<#3#3DKK#HVTU*66	 	 	
 -34G+H$--(rP   c           
      f   [        U5      nU(       d  UR                  S5        gU R                  R                  USS9nU(       d  UR                  S5        g[        R
                  " UR                  UR                  5       5      n[        [        R
                  " UR                  S5      SS9[        [        R
                  " UR                  S	5      S
S9[        [        R
                  " UR                  S5      SS9[        [        R
                  " UR                  S5      SS9[        [        R
                  " UR                  S5      SS9/nUR                  5        HX  nUS   nSUR                  SS5       3n	UR                  [        [        R
                  " UR                  U	5      SU 3S95        MZ     UR                  [        [        R
                  " UR                  S5      SS95        UR                  SUS[        R                  S9  SUUS.U R                  UR                   '   g)ztShow leaderboard type selection menu for a game.

Args:
    user: Acting user.
    game_type: Game type identifier.
r  Nr   rT  zleaderboard-no-datazleaderboard-type-wins	type_winsr  zleaderboard-type-ratingtype_ratingzleaderboard-type-total-scoretype_total_scorezleaderboard-type-high-scoretype_high_scorezleaderboard-type-games-playedtype_games_playedr  leaderboard-type-_-type_r-  r  Tr/  rC  )r+   r  rs   get_game_statsr,   r   r<  rb  r#   get_leaderboard_typesr  rZ  r"  r$   r#  rz   r6  )
r   r  r  r	  resultsre  r  	lb_configlb_idloc_keys
             rN   _show_leaderboard_types_menu#Server._show_leaderboard_types_menu  s    $I.
LL./ (()))1)=LL./ $$T[[*2I2I2KL	 !%%dkk3JK !%%dkk3LM  !%%dkk3QR% !%%dkk3PQ$ !%%dkk3RS&#
0 $99;IdOE)%--S*A)BCGLL%))$++w?ug	 < 	X<#3#3DKK#HVTU$*66	 	 	
 -"",
$--(rP   c                    SSK JnJn  SSKnU R                  R                  USS9n/ nU H  nUS   (       a  UR                  " US   5      O0 nU R                  R                  US   5      n	U	 V
s/ s H%  n
U" U
S   U
S	   U
S
   U
R                  SS5      S9PM'     nn
UR                  U" US   US   US   UUS95        M     U$ s  sn
f )z'Get game results as GameResult objects.r   )
GameResultPlayerResultr   Nd   r     r/  rO  r  is_virtual_botF)r/  rO  r  r  r   r1   )r  	timestampduration_ticksplayer_resultscustom_data)
game_utils.game_resultr  r  r  rs   r  r  get_game_result_playersr   rZ  )r   r  r  r  r  r  game_resultsrowr  player_rowsr  r  s               rN   _get_game_resultsServer._get_game_results  s    E(()))3)?C03A$**SV,BK((::3q6BK % %A n !- 0X;#$55)95#A	 %   !!f!!f#&q6#1 + , 's   1,Cre  c                    SSK Jn  U R                  U5      n0 nU H  nUR                  R	                  S5      nUR
                   H  n	U	R                  (       a  U	R                  (       d  M'  U	R                  U;  a  SSU	R                  S.XiR                  '   XR                  :X  a  XiR                     S==   S-  ss'   M}  XiR                     S==   S-  ss'   M     M     [        UR                  5       S	 S
S9n
/ n[        U
SS S5       Hq  u  nu  pUS   nUS   nUU-   n[        US:  a  UU-  S-  OS5      nUR                  [        [         R                  " UR"                  SUUS   UUUS9SU 3S95        Ms     UR                  [        [         R                  " UR"                  S5      SS95        UR%                  SUS
[&        R(                  S9  SUUS.U R*                  UR,                  '   g)zShow win count leaderboard for a game.

Args:
    user: Acting user.
    game_type: Game type identifier.
    game_name: Localized game name.
r   LeaderboardHelperwinner_namer   )winslossesrG  r  r   r  c                     U S   S   $ )Nr   r  rZ   xs    rN   r8  /Server._show_wins_leaderboard.<locals>.<lambda>7  s    AaDLrP   Tr   reverseNr6   r  zleaderboard-wins-entryrG  )rankr
  r  r  
percentageentry_r  r-  r  r/  rC  )game_utils.stats_helpersr  r  r  r   r  r  r  r/  rO  r1  r  r  roundrZ  r#   r,   r<  r"  r$   r#  rz   r6  )r   r  r  re  r  r  player_statsr  r  r  sorted_playersr  r  r/  statsr  r  totalr  s                      rN   _show_wins_leaderboardServer._show_wins_leaderboard  s    	A--i8 )+"F ,,00?K**88A$4$4;;l2 !"# !1L-
 --/ -f5:5 -h71<7 + #"   2 2 4:PZ^_(1."2Eq(I$D$9=D8_F6MEuqyus 2aHJLL%))0!$V}!%#-  v )J( 	X<#3#3DKK#HVTU*66	 	 	
 '"",
$--(rP   c                    SSK Jn  U" U R                  U5      nUR                  SS9n/ nU(       d:  UR	                  [        [        R                  " UR                  S5      SS95        GO[        US5       GH   u  pU	R                  n
U R                  R                  US	S9nU HU  nU R                  R                  US
   5      nU H  nUS   U	R                  :X  d  M  US   n
  O   XR                  :w  d  MU    O   UR	                  [        [        R                  " UR                  SUU
[        U	R                  5      [        U	R                  S5      [        U	R                   S5      S9SU 3S95        GM     UR	                  [        [        R                  " UR                  S5      SS95        UR#                  SUS[$        R&                  S9  SUUS.U R(                  UR*                  '   g)zShow skill rating leaderboard.

Args:
    user: Acting user.
    game_type: Game type identifier.
    game_name: Localized game name.
r   RatingHelperr6   r  zleaderboard-no-ratingsno_datar  r   r  r   r/  rO  zleaderboard-rating-entry)r  r
  ratingmusigmar  r-  r  Tr/  rC  N)r   r
  rs   get_leaderboardrZ  r#   r,   r   r<  r  r/  r  r  r  ordinalr  r  r"  r$   r#  rz   r6  )r   r  r  re  r
  rating_helperratingsr  r  r  rO  r  r  r  r  s                  rN   _show_rating_leaderboardServer._show_rating_leaderboard]  s    	<$TXXy9//b/9LL%))$++7OP  !*'1 5$..((11)31G%F"hh>>vayIG$[>V-=-==*+M*:K! % #&6&66 & )-- KK6!%#.#(#8$VYY2"'a"8 $D6? !6: 	X<#3#3DKK#HVTU*66	 	 	
 '"",
$--(rP   c                    SSK Jn  U R                  U5      n0 nU H  nUR                  R	                  S0 5      nUR
                   H  n	U	R                  (       a  U	R                  (       d  M'  U	R                  U;  a  SU	R                  S.XiR                  '   UR	                  U	R                  S5      n
U
(       d  Mw  XiR                     S==   U
-  ss'   M     M     [        UR                  5       S SS	9n/ n[        US
S S5       HS  u  nu  pUR                  [        [        R                  " UR                   SUUS   [#        US   5      S9SU 3S95        MU     UR                  [        [        R                  " UR                   S5      SS95        UR%                  SUS[&        R(                  S9  SUUS.U R*                  UR,                  '   g
)zShow total score leaderboard.

Args:
    user: Acting user.
    game_type: Game type identifier.
    game_name: Localized game name.
r   r  final_scoresr   )r  rG  r  c                     U S   S   $ )Nr   r  rZ   r  s    rN   r8  6Server._show_total_score_leaderboard.<locals>.<lambda>  s    QqT']rP   Tr  Nr6   r   leaderboard-score-entryrG  r  r
  r:   r  r  r-  r  r/  rC  )r   r  r  r  r   r  r  r  r/  rO  r1  r  r  rZ  r#   r,   r<  rK   r"  r$   r#  rz   r6  )r   r  r  re  r  r  player_scoresr  r  r  scorer  r  r  r/  r  s                   rN   _show_total_score_leaderboard$Server._show_total_score_leaderboard  s    	A--i8 *,"F!--11."EL**88A$4$4;;m3;<amm1TM++.$((:5!++.w75@7 + #   3 3 5;R\`a(1."2Eq(I$D$9LL%))1!$V}!%.1  v	 )J 	X<#3#3DKK#HVTU*66	 	 	
 '"",
$--(rP   c                    U R                  U5      n0 nU H  nUR                  R                  S0 5      nUR                   H  nUR                  (       a  UR
                  (       d  M'  UR                  UR                  S5      n	UR                  U;  a  XR                  S.XXR                  '   Mo  XUR                     S   :  d  M  XUR                     S'   M     M     [        UR                  5       S SS9n
/ n[        U
SS	 S
5       HS  u  nu  pUR                  [        [        R                  " UR                  SUUS   [        US   5      S9SU 3S95        MU     UR                  [        [        R                  " UR                  S5      SS95        UR!                  SUS["        R$                  S9  SUUS.U R&                  UR(                  '   g)zShow high score leaderboard.

Args:
    user: Acting user.
    game_type: Game type identifier.
    game_name: Localized game name.
r  r   )highrG  r   c                     U S   S   $ )Nr   r   rZ   r  s    rN   r8  5Server._show_high_score_leaderboard.<locals>.<lambda>  s    1Q4<rP   Tr  Nr6   r   r  rG  r  r  r  r-  r  r/  rC  )r  r  r   r  r  r  rO  r/  r1  r  r  rZ  r#   r,   r<  rK   r"  r$   r#  rz   r6  )r   r  r  re  r  player_highr  r  r  r  r  r  r  r/  r  s                  rN   _show_high_score_leaderboard#Server._show_high_score_leaderboard  s    --i8 (*"F!--11."EL**88A$4$4$((:;;k18=}}/UK,5f==7<,V4 + #   1 1 39OY]^(1."2Eq(I$D$9LL%))1!$V}!%-0  v	 )J 	X<#3#3DKK#HVTU*66	 	 	
 '"",
$--(rP   c                    U R                  U5      n0 nU H~  nUR                   Hk  nUR                  (       a  UR                  (       d  M'  UR                  U;  a  SUR
                  S.XWR                  '   XWR                     S==   S-  ss'   Mm     M     [        UR                  5       S SS9n/ n	[        USS	 S5       HJ  u  n
u  pU	R                  [        [        R                  " UR                  S
U
US   US   S9SU
 3S95        ML     U	R                  [        [        R                  " UR                  S5      SS95        UR                  SU	S[        R                   S9  SUUS.U R"                  UR$                  '   g)zShow games played leaderboard.

Args:
    user: Acting user.
    game_type: Game type identifier.
    game_name: Localized game name.
r   )r@  rG  r@  r   c                     U S   S   $ )Nr   r@  rZ   r  s    rN   r8  7Server._show_games_played_leaderboard.<locals>.<lambda>-  s    AaDMrP   Tr  Nr6   zleaderboard-games-entryrG  r  r  r  r-  r  r/  rC  )r  r  r  r  r/  rO  r1  r  r  rZ  r#   r,   r   r<  r"  r$   r#  rz   r6  )r   r  r  re  r  player_gamesr  r  r  r  r  r/  r  s                rN   _show_games_played_leaderboard%Server._show_games_played_leaderboard  sr    --i8 )+"F**88A$4$4;;l2:;Q]]0SL-[[)'2a72 + #   2 2 4:Q[_`(1."2Eq(I$D$9LL%))1!$V}#Gn  v	 )J 	X<#3#3DKK#HVTU*66	 	 	
 '"",
$--(rP   datapathc                    UR                  SU5      nUR                  SU5      nUR                  S5      nUnU H#  n[        U[        5      (       a  X;   a  Xx   nM#    g   [        U[        [
        45      (       a  [        U5      $ g)z{Extract a value from custom_data using a dot-separated path.

Supports {player_id} and {player_name} placeholders in path.
{player_id}{player_name}r  N)r  r3  rF   r  rK   rL   )	r   r,  r-  r/  rO  resolved_pathpartsr   parts	            rN   _extract_value_from_pathServer._extract_value_from_pathM  s     ]I>%--o{K ##C(D'4((T_!-	  gU|,,>!rP   r#  c                 `   U R                  U5      nUS   nUR                  SS5      nUR                  SS5      nUR                  SS5      n	SU;   =(       a    S	U;   n
0 nU GHm  nUR                  nUR                   GHL  nUR                  (       a  UR
                  (       d  M(  UR                  U;  a  UR                  / / / S
.XR                  '   U
(       a  U R                  XS   UR                  UR                  5      nU R                  XS	   UR                  UR                  5      nUbG  UbB  XR                     S   R                  U5        XR                     S   R                  U5        M  M  M  U R                  XS   UR                  UR                  5      nUc  GM,  XR                     S   R                  U5        GMO     GMp     / nUR                  5        H  u  nnU
(       aB  [        US   5      n[        US   5      nUS:  a  UU-  nUR                  UUS   U45        MM  MO  US   nU(       d  M]  US:X  a  [        U5      nO;US:X  a  [        U5      nO)US:X  a  [        U5      [        U5      -  nO[        U5      nUR                  UUS   U45        M     UR                  S SS9  / nSU S3n[        USS S5       Hc  u  nu  nnnU	S:  a  [!        UU	5      O
[#        U5      nUR                  [%        [&        R                  " UR(                  UUUUS9SU 3S95        Me     UR                  [%        [&        R                  " UR(                  S5      SS95        UR+                  SUS[,        R.                  S9  SUUS .U R0                  UR2                  '   g)!zShow a custom leaderboard using declarative config.

Args:
    user: Acting user.
    game_type: Game type identifier.
    game_name: Localized game name.
    config: Leaderboard config dict from game class.
r  	aggregater  formatr  decimalsr   	numeratordenominator)rG  r  
numeratorsdenominatorsNr<  r=  r-  r  rG  r   avgc                     U S   $ )Nr   rZ   r  s    rN   r8  1Server._show_custom_leaderboard.<locals>.<lambda>  s    1rP   Tr  zleaderboard-z-entryr6   r   r  r  r  r-  r  r/  rC  )r  r   r  r  r  r  r/  rO  r4  rZ  r  r  r   r?  sortr  r  rK   r#   r,   r<  r"  r$   r#  rz   r6  )r   r  r  re  r#  r  r  r7  
format_keyr9  is_ratioplayer_datar  r  r  numdenomr:   r  r/  r,  	total_numtotal_denomr  r  	entry_keyr  rG  display_values                                rN   _show_custom_leaderboardServer._show_custom_leaderboardf  s    --i8tJJ{E2	ZZ'2
::j!, &(D]f-D (*"F ,,K**88A$4$4;;k1 !"$&((*	0K, 77#K%8!++q}}C !99#M%:AKKE 5+<#KK0>EEcJ#KK0@GGN ,= !99#F^Q[[!--E (#KK0:AA%H7 + #@ 79*002OIt\ 23	!$~"67?%3E!(()T&\5)IJ # h%KE%'KE%'K#f+5EKE$$ifu%EF+  30 	~t< ":,f5	.7cr8JA.N*D*9dE6>lE%2E
MLL%))!!#+  v	 /O 	X<#3#3DKK#HVTU*66	 	 	
 '"",
$--(rP   c                    #    UR                  S5      (       a  USS nU R                  X5        gUS:X  a  U R                  U5        gg7f)zHandle leaderboards menu selection.

Args:
    user: Acting user.
    selection_id: Selected menu item id.
    state: Current menu state.
r  r1   Nr-  )r@  r  r  r  s        rN   r  %Server._handle_leaderboards_selection  sM      ""5))$QR(I--d>V#  & $r  c                 D  #    UR                  SS5      nUR                  SS5      nUS:X  a  U R                  XU5        gUS:X  a  U R                  XU5        gUS:X  a  U R                  XU5        gUS:X  a  U R	                  XU5        gUS:X  a  U R                  XU5        gUS	:X  a  U R                  U5        gUR                  S
5      (       aM  USS n[        U5      nU(       a5  UR                  5        H   nUS   U:X  d  M  U R                  XXX5          g   ggg7f)zHandle leaderboard type selection.

Args:
    user: Acting user.
    selection_id: Selected leaderboard type id.
    state: Current menu state.
r  r9  re  r  r  r  r  r  r-  r  r5   Nr  )r   r  r  r  r$  r*  r&  r@  r+   r  rK  )	r   r  r  rd  r  re  r  r	  r#  s	            rN   r  *Server._handle_leaderboard_types_selection  s!     IIk2.	IIk2.	 ;&''C]*))$9E//..t	J..--dyI00//KV#((.$$W-- $E'	2J(>>@Fd|u,55dyY A 	 .s   DD D c                    #    US:X  a6  UR                  SS5      nUR                  SS5      nU R                  X5        gg7f)zHandle game leaderboard menu selection.

Args:
    user: Acting user.
    selection_id: Selected menu item id.
    state: Current menu state.
r-  r  r9  re  N)r   r  )r   r  r  rd  r  re  s         rN   r  )Server._handle_game_leaderboard_selection  sB      6!		+r2I		+r2I--d> "s   >A c           	        ^ [         R                  " 5       n/ n[        UR                  5       5       H  nX$    H  nUR	                  5       nU R                  U5      n[        U4S jU 5       5      nU(       d  MB  [        R                  " TR                  UR                  5       5      n	UR                  [        U	SU 3S95        M     M     U(       d  TR                  S5        gUR                  [        [        R                  " TR                  S5      SS95        TR                  SUS[        R                   S	9  S
S0U R"                  TR$                  '   g)zvShow game selection menu for personal stats.

Returns True if the menu was shown, False if there was nothing to show.
c              3   ~   >#    U  H2  nUR                     H  nUR                  TR                  :H  v   M      M4     g 7fr   )r  r/  rB  )r   r  r  r  s      rN   r   -Server._show_my_stats_menu.<locals>.<genexpr>5  s8       ".#22 KK499,2 -".s   :=stats_r  zmy-stats-no-gamesFr-  r  Tr/  r`  )r*   r0  r1  r!  r  r  r  r,   r   r<  rb  rZ  r#   r  r"  r$   r#  rz   r6  )
r   r  r2  r  r3  r	  r  r  	has_statsre  s
    `        rN   r'  Server._show_my_stats_menu'  s,   
 "113
 #:??#45L(6
&//1	#55i@  ".  	
 9 , 0 0j>U>U>W XILLyvi[=Q!RS 7 6 LL,-X<#3#3DKK#HVTU*66	 	 	
 -3O+D$--(rP   c                    SSK Jn  [        U5      nU(       d  UR                  S5        g[        R
                  " UR                  UR                  5       5      nU R                  U5      nSnSnSn	Sn
SnU H  nUR                  R                  S5      nUR                  R                  S0 5      nUR                  R                  S0 5      nUR                   H  nUR                  UR                  :X  d  M  US	-  nUUR                  :X  a  US	-  nOUS	-  nUR                  UR                  S5      nU(       d  UR                  UR                  S5      nU	U-  n	UU
:  d  M  Un
M     M     US:X  a  UR                  S
5        g/ n[        US:  a  X{-  S-  OS5      nUR                  [!        [        R
                  " UR                  SUS9SS95        UR                  [!        [        R
                  " UR                  SUS9SS95        UR                  [!        [        R
                  " UR                  SUS9SS95        UR                  [!        [        R
                  " UR                  SUS9SS95        U	S:  an  UR                  [!        [        R
                  " UR                  SU	S9SS95        UR                  [!        [        R
                  " UR                  SU
S9SS95        U" U R"                  U5      nUR%                  UR                  5      nUR&                  S:w  d  UR(                  S:w  au  UR                  [!        [        R
                  " UR                  S[        UR*                  5      [        UR&                  S	5      [        UR(                  S	5      S9SS95        O8UR                  [!        [        R
                  " UR                  S5      S S95        U R-                  XUU5        UR                  [!        [        R
                  " UR                  S!5      S!S95        UR/                  S"US#[0        R2                  S$9  S"UUS%.U R4                  UR6                  '   g)&zlShow personal stats for a specific game.

Args:
    user: Acting user.
    game_type: Game type identifier.
r   r	  r  Nr   r  r  final_lightr   zmy-stats-no-datar  zmy-stats-games-playedr:   games_playedr  zmy-stats-winsr  zmy-stats-lossesr  zmy-stats-winratewinratezmy-stats-total-scoretotal_scorezmy-stats-high-score
high_scoreg      9@g @zmy-stats-rating)r:   r  r  r  zmy-stats-no-rating	no_ratingr-  r  Tr/  rC  )r   r
  r+   r  r,   r   r<  rb  r  r  r  r/  rB  rO  r  rZ  r#   rs   
get_ratingr  r  r  _add_custom_statsr"  r$   r#  rz   r6  )r   r  r  r
  r	  re  r  r  r  r^  r_  r\  r  r  r  rZ  r  r  r  r]  r  r  s                         rN   _show_my_game_statsServer._show_my_game_statsM  s    	<#I.
LL./ $$T[[*2I2I2KL	--i8 
"F ,,00?K!--11."EL ,,00CK**;;$))+ A%L"amm3	! ),,Q]]A>E  +q A5(Kz)%*
 + #* 1LL+,|a7G,s2QO!%%dkk3JR^_!	
 	!%%dkk?$O	
 	!%%dkk3DFS	
 	!%%dkk3EWU	
 ?LL%))$++7MU`a$ LL%))$++7LT^_# %TXXy9))$))499 8LL%)))#FNN3 A.#FLL!4  	 LL%))$++7KL" 	tuEX<#3#3DKK#HVTU*66	 	 	
 $"",
$--(rP   r  r  c                     UR                  5        H>  nU R                  XU5      nU(       d  M  Uu  pxUR                  [        USU 3S95        M@     g)zAdd game-specific custom stats from leaderboard configs.

Args:
    user: Acting user.
    game_class: Game class for leaderboard config.
    game_results: GameResult list for the game.
    items: Menu item list to append to.
custom_r  N)r  _build_custom_statrZ  r#   )	r   r  r	  r  r  r#  custom_statr  rI  s	            rN   rb  Server._add_custom_stats  sP     !668F11$MK%KELLt'%0ABC 9rP   c                     US   nUR                  SS5      nUR                  SS5      nU R                  XU5      u  pxn	U R                  XxX5      n
U
c  gU R                  X5      nU R	                  XU5      nXL4$ )z3Build a custom stat string from leaderboard config.r  r7  r  r9  r   N)r   _collect_custom_stat_values_aggregate_custom_stat_format_custom_stat_value_format_custom_stat_text)r   r  r#  r  r  r7  r9  r  
num_valuesdenom_valuesfinal_valueformatted_valuerI  s                rN   rg  Server._build_custom_stat  s     tJJ{E2	::j!,+/+K+K,,
(L 11&l^88O,,T/J{rP   c                    UR                  S5      nUR                  S5      nUR                  S5      n/ n/ n/ n	U GH  n
U R                  XR                  5      nU(       d  M(  U
R                  nU(       aE  U R	                  XKUR                  5      nU R                  X5      nUb  UR                  U5        M~  M  U(       d  M  U(       d  M  U R	                  X[UR                  5      nU R	                  XkUR                  5      nU R                  X5      nU R                  UU5      nUc  M  Uc  M  UR                  U5        U	R                  U5        GM     XxU	4$ )z>Extract raw custom stat values for a user across game results.r-  r:  r;  )r   _find_player_namerB  r  _resolve_stat_path_extract_path_valuerZ  )r   r  r#  r  r-  numerator_pathdenominator_pathr  ro  rp  r  rO  r  r1  r:   num_path
denom_pathnum_val	denom_vals                      rN   rk  "Server._collect_custom_stat_values  sA    zz&!K0!::m4 "$
$&"F00CK ,,K $ 7 7499 U00L$MM%( %$4$422>PTPYPYZ!445ETXT]T]^
22;I 44[*M	&9+@%%g. ''	2% #( <//rP   c                 f    UR                    H!  nUR                  U:X  d  M  UR                  s  $    g)z7Find the player name for a given player id in a result.N)r  r/  rO  )r   r  r/  r  s       rN   ru  Server._find_player_name  s-    &&A{{i'}}$ ' rP   c                 F    UR                  SU5      R                  SU5      $ )z'Substitute player tokens in stat paths.r0  r/  )r  )r   r-  rO  r/  s       rN   rv  Server._resolve_stat_path%  s!    ||O[9AA-QZ[[rP   r  ro  rp  r7  c                     U(       a@  US:X  a  [        U5      $ US:X  a  [        U5      $ US:X  a  [        U5      [        U5      -  $ gU(       a'  U(       a   [        U5      n[        U5      nUS:  a  XV-  $ g)z0Aggregate raw stat values based on config rules.r  r   r>  Nr   )r  r   r?  )r   r  ro  rp  r7  rG  rH  s          rN   rl  Server._aggregate_custom_stat)  st     E!6{"E!6{"E!6{S[00,JIl+KQ ..rP   r9  c                 F    US:  a  USU S3 $ [        [        U5      5      $ )z'Format a custom stat value for display.r   r  r"  )rH   r  )r   r:   r9  s      rN   rm   Server._format_custom_stat_valueB  s+    a<AhZq=)*5<  rP   r  rr  c                     SUR                  SS5       3n[        R                  " UR                  XCS9nXT:w  a  U$ SUR                  SS5       3n[        R                  " UR                  U5      nU SU 3$ )z+Build the localized text for a custom stat.z	my-stats-r  r  r[  r  z: )r  r,   r   r<  )r   r  r  rr  r  rI  type_key	type_names           rN   rn  Server._format_custom_stat_textH  sz    emmC567WL?K&u}}S#'>&?@ $$T[[(;	B/00rP   c                     UR                  S5      nUnU H#  n[        U[        5      (       a  XT;   a  XE   nM#    g   [        U[        [        45      (       a  [	        U5      $ g)zExtract a value from nested dict using dot-notation path.

Args:
    data: Nested dict to read from.
    path: Dot-separated path string.

Returns:
    Float value if found, otherwise None.
r  N)r3  rF   r  rK   rL   )r   r,  r-  r2  r   r3  s         rN   rw  Server._extract_path_valueS  s[     

3D'4((T_!-	 
 gU|,,>!rP   c                    #    US:X  a  U R                  U5        gUR                  S5      (       a  USS nU R                  X5        gg7f)zHandle my stats game selection.

Args:
    user: Acting user.
    selection_id: Selected menu item id.
    state: Current menu state.
r-  rV  r[  N)r  r@  rc  r  s        rN   r  !Server._handle_my_stats_selectionh  sM      6!  &$$X..$QR(I$$T5 /r  c                 j   #    US:X  a)  U R                  U5      (       d  U R                  U5        ggg7f)zHandle my game stats menu selection.

Args:
    user: Acting user.
    selection_id: Selected menu item id.
    state: Current menu state.
r-  N)r'  r  )r   r  r  rd  s       rN   r  &Server._handle_my_game_stats_selectionx  s8      6!++D11$$T* 2 "s   13c                    UR                   (       d  gUR                   R                   HT  nUR                  (       a  M  U R                  R	                  UR
                  5      nU(       d  MD  U R                  USS9  MV     g)zCHandle table destruction.

Args:
    table: Table being destroyed.
NTr  )r  r  r  ry   r   rG  r  )r   r  r
  player_users       rN   on_table_destroyServer.on_table_destroy  sY     zzjj((F==="kkoofkk:;((D(I	 )rP   c                 V   SSK Jn  [        X5      (       d  gU R                  R	                  UR
                  UR                  UR                  UR                   Vs/ s H2  nUR                  UR                  UR                  [        USS5      4PM4     snUR                  S9  gs  snf )zHHandle game result persistence.

Args:
    result: GameResult instance.
r   )r  Nr  F)r  r  r  r  r  )r  r  rF   rs   save_game_resultr  r  r  r  r/  rO  r  r?  r  )r   r  r  r  s       rN   on_game_resultServer.on_game_result  s     	8&-- 	!!&&&&!00  ...A ammQXXwqBRTY7Z[. ** 	" 		
s   9B&c                    SSK nSSKJn  UR                  nU(       d  gUR                  5        SUR                  " 5       S 3nUR                  5       n/ nUR                   H+  n	UR                  U	R                  U	R                  S.5        M-     UR                  " U5      n
U R                  R                  UUUR                  UU
S9  UR                  S5        UR                  5         g)	z_Handle table save request.

Args:
    table: Table to save.
    username: Requesting username.
r   N)r    - z%Y-%m-%d %H:%M)r6  r  )r6  r  r  r  r  ztable-saved-destroying)r  r   r  r  rV  to_jsonr  rZ  rG  r  rx  rs   save_user_tabler  r  destroy)r   r  r6  r  r   r  r  r  r  r
  r  s              rN   on_table_saveServer.on_table_save  s     	%zz }}'s8<<>.*IJ	 LLN	 llF &$mm # zz,/ 	  oo% 	! 	
 	12rP   c                 T  #    UR                   nU(       d  gU R                  R                  U5      nU R                  R	                  U5      nU(       Gd  U(       Ga  UR                  S5      S:X  a  UR                  S5      (       a  U R                  R                  5        Vs/ s HN  nUR                  (       d  M  U R                  R	                  UR                   5      (       a  MB  UR                   PMP     nnU(       d  UR                  S5        g[        R                  " UR                  U5      n[        U5      S:X  a  SOSn	UR                  U	[        U5      US	9  gU(       a  UR                  (       a  U(       a  UR                  R                  UR                  5      n
U
(       aq  UR                  R                  X5        UR                  R                  R                  UR                  5      nXLa"  UR!                  U5        U R#                  US
S9  ggggggs  snf 7f)zcHandle keybind events.

Args:
    client: Client connection.
    packet: Incoming keybind payload.
Nr   wcontrolonline-users-noner   online-users-oneonline-users-manyr@  usersTr  )r6  ry   r   ru   r@  r  rF  r  r,   rE  r<  r?  r  rA  rB  r  rE  r  )r   r/  r6  r6  r  r  r7  r  namesr   r
  r  s               rN   r  Server._handle_keybind  s     ??{{x(,,X6zz% C'FJJy,A,A "[[//11zz *.,,*F*Fqzz*R AJJ1  
 LL!45$44T[['J,/LA,=(CVSGEBUZZDZZ00;F

''7!JJ--11$))<	(''1((T(B )	  %)Z5s   B"H($H#=(H#)H#9D/H(c                    #    UR                   nU(       d  gU R                  R                  U5      nU(       d  gU R                  R                  U0 5      nU R	                  XBU5      I Sh  vN   g N7f)z8Forward document editor responses to the browsing mixin.N)r6  ry   r   rz    _handle_document_editor_response)r   r/  r6  r6  r  rd  s         rN   r  %Server._handle_document_editor_packet  sZ     ??{{x(!!%%h333D%HHHs   A)A3+A1,A3c                    #    UR                   nU(       d  gU R                  R                  U5      nU(       d  gU R                  R                  U0 5      nUR                  S5      nU R	                  XFX%5      I Sh  vN (       a  gUS:X  a-  UR                  SS5      nU R                  XGU5      I Sh  vN   gUS:X  a-  UR                  SS5      nU R                  XGU5      I Sh  vN   gUS:X  a-  UR                  SS5      nU R                  XGU5      I Sh  vN   gU R                  R                  U5      nU(       a  UR                  (       a  UR                  R                  UR                  5      n	U	(       aq  UR                  R                  X5        UR                  R                  R                  UR                  5      n
XLa"  UR                  U5        U R                  USS	9  ggggg GNw GNB GN N7f)
zhHandle editbox submissions.

Args:
    client: Client connection.
    packet: Incoming editbox payload.
Nr`  decline_reason_editboxrI  r9  ban_reason_editboxunban_reason_editboxTr  )r6  ry   r   rz   _handle_document_editbox_handle_decline_reason_editbox_handle_ban_reason_editbox_handle_unban_reason_editboxru   r@  r  rA  rB  r  rE  r  )r   r/  r6  r6  r  rd  r  rI  r  r
  r  s              rN   r  Server._handle_editbox	  s     ??{{x( !!%%h3yy(..t6QQQ33::fb)D55d%HHH//::fb)D11$eDDD11::fb)D33DFFF ,,X6UZZZZ00;F

''7!JJ--11$))<	(''1((T(B )	   5) R
 I
 E
 GsI   A:G><G3=7G>4G652G>'G9(2G>G<CG>6G>9G><G>c                   #    UR                   nU(       d  gUR                  SS5      nUR                  SS5      nUR                  SS5      nSUUUUS	.nUS:X  Ga.  U R                  R                  U5      nU(       a  UR                   V	s/ s H  oR                   PM     sn	 H]  n
U R
                  R                  U
5      nU(       d  M'  UR                  (       d  M:  UR                  R                  U5      I Sh  vN   M_     gU R
                  R                  5        He  nUR                  (       d  M  U R                  R                  UR                   5      (       a  MB  UR                  R                  U5      I Sh  vN   Mg     gUS
:X  aX  U R
                  R                  5        H9  nUR                  (       d  M  UR                  R                  U5      I Sh  vN   M;     ggs  sn	f  N No N7f)zHandle chat message.Nconvolocalrl   r9  rW  Otherru  )rF  r  senderrl   rW  global)
r6  r   ru   r@  rD  ry   rF  r$  rP  r  )r   r/  r6  r6  r  rl   rW  chat_packetr  r  member_namer  s               rN   r  Server._handle_chat:  s    ??

7G,**Y+::j'2  
 GLL00:E8=#F1JJ#FK;;??;7Dt"oo22;??? $G
 !KK..0D== ||33DMMBB //..{;;; 1 h**,===//..{;;; -  $G @ <
 <sU   B
G4G)!&G4G4G4<G.=BG4 G0;G4 G4G2G40G42G4c                 b    [        U R                  R                  5       [        R                  S9$ )z'Return sorted list of online usernames.)r   )r1  ry   r!  rH   rJ   r   s    rN   _get_online_usernamesServer._get_online_usernames`  s     dkk&&(cii88rP   c                    / nU R                  5        GH,  nU R                  R                  U5      nU(       a"  [        US5      (       a  UR	                  5       nOSnU(       a3  UR
                  (       d"  [        R                  " UR                  S5      nOU R                  R                  U5      nU(       aX  [        UR                  5      nU(       a/  [        R                  " UR                  UR                  5       5      OUR                  nO![        R                  " UR                  S5      nU/n	U(       ae  [        R                  " UR                  S5      n
[        R                  " UR                  SUR                   35      nU	R                  U
 SU 35        U(       a  [        USS5      OSnU(       a  [        US	S5      OSnU(       a0  U(       a  U	R                  U S
U S35        OU	R                  U5        SR                  U	5      nU(       a  U S
U SU 3nOU SU 3nUR                  U5        GM/     U(       d0  UR                  [        R                  " UR                  S5      5        U$ )zFormat online users with detailed info for menu display.

Format: ``Username (Xh) - Status, Language LangName, ClientType (Platform)``
All labels are localized to the requesting *user*'s locale.
format_time_onliner9  zonline-user-waiting-approvalzonline-user-not-in-gamerW  rS   r  r  r*  r+  r  z) - r  r  )r  ry   r   r  r  rF  r,   r<  ru   r@  r+   r  rb  rZ  r?  r  )r   r  linesr6  online_usertime_strr  r  r	  r2  
lang_label	lang_namer  platform_strdetaillines                   rN   _format_online_users_lines!Server._format_online_users_linesd  s    224H++//(3K w{4HII&99; ;#7#7%))$++7UV44X>!/!@J & %((j6M6M6OP"__  *--dkk;TUF HE )--dkk:F
(,,T[[IkFXFXEY:Z[	
|1YK89 FQ'+}bAVXKCN7;
B?TVLLLK=<.!BCLL-YYu%F"2hZtF8<"3vh/LL_ 5` LL))$++7JKLrP   c                 *   U R                   R                  UR                  0 5      nUR                  S5      nSnU(       a  [        US0 5      nUR                  U5      nU R	                  U5       Vs/ s H  n[        USS9PM     nnUR                  SUS[        R                  SS	9  [        US
S5      nUR                  S5        SUU[        U5      [        U[        5      (       a  [        U5      OSS.U R                   UR                  '   gs  snf )z1Show online users with games in a read-only menu.r`  Nr  r  r  r  Fr   r  _current_musiczplayersmus.ogg)r`  return_menu_idr  return_statereturn_music)rz   r   r6  r?  r  r#   r"  r$   r#  r$  r  rF   )	r   r  current_stateprevious_menu_idr  r&  r  r  previous_musics	            rN   _show_online_users_menuServer._show_online_users_menu  s   ))--dmmR@(,,V4#D*:B?M)--.>?M ?C>]>]^b>c
>cdH$=1>c 	 
 	*66 	 	
 !'7>()".( /4>~t4T4TD0Z^,
$--(
s   2Dc                    UR                  S5      nUR                  S5      nU(       a  U(       d  U R                  U5        gUR                  UUR                  S/ 5      UR                  SS5      [        UR                  SS5      5      UR                  S	5      UR                  S
S5      UR                  SS5      S9  UR                  S5      n[	        U[
        5      (       aV  UR                  S5      n[	        U[        5      (       a0  U(       a)  UR                  U[        UR                  SS5      5      S9  [        UR                  S0 5      5      nX7S'   XpR                  UR                  '   g)z>Restore the previous menu after closing the online users list.r  r  Nr  multiletter_enabledTr  rr  r   grid_enabledF
grid_widthr   )r  r  r   r  r  r  rG  looping)r  r  r`  )r   r  r"  r$   rF   r  rH   r$  rG   rz   r6  )r   r  rd  r  r  r  
music_namerestored_states           rN   r
  Server._restore_previous_menu  s>    99%56		-0}  &gr*%))*?F*=+<+<=NPY+Z["&&z2&**>5A$((q9 	 	
 yy0lD))%))&1J*c**z
D9I9I)UY9Z4[\eii;<!1v+9$--(rP   c                 p  #    UR                   nU(       d  gU R                  R                  U5      nU(       d  gU R                  5       n[	        U5      nUS:X  a  UR                  S5        g[        R                  " UR                  U5      nUS:X  a  UR                  SUS9  gUR                  SXVS9  g7f)	z%Handle request for online users list.Nr   r  r   r  )r  r  r  )	r6  ry   r   r  r?  r  r,   rE  r<  )r   r/  r6  r  onliner@  	users_strs          rN   r  Server._handle_list_online  s     ??{{x(++-FA:LL,- 00fE	A:LL+9L=LL,ELKs   B4B6c                   #    UR                   nU(       d  gU R                  R                  U5      nU(       d  gU R                  R	                  U5      nU(       ah  UR
                  (       aW  UR
                  R                  UR                  5      nU(       a+  UR
                  R                  XPR                  U5      5        gU R                  U5        g7f)z4Handle request for online users list with game info.N)r6  ry   r   ru   r@  r  rA  rB  
status_boxr  r  )r   r/  r6  r  r  r
  s         rN   r  %Server._handle_list_online_with_games  s     ??{{x(,,X6UZZZZ00;F

%%f.M.Md.ST$$T*s   CCc                 F   #    UR                  SS05      I Sh  vN   g N7f)z4Handle ping request - respond immediately with pong.rF  pongNrO  )r   r/  s     rN   r~  Server._handle_ping  s     kk66*+++s   !!)+r   r   rt   r   r   r   r{   rs   rp   r|   rq   r   r   r~   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   rn   ro   ru   rx   rz   r   r   ry   r}   r   rw   r_   r`   )z::@  ri   NNNNFr  r   )__name__
__module____qualname____firstlineno____doc__rH   rK   r   rG   r   r   r   r   r   staticmethodr   r4  tupler:  r@  rQ  r  r   rL   r\  r`  rc  rj  rm  rq  rt  r   r   r  r"   r  r  r  r  r   r  r  r   r   r  r  objectr  r  r  r  r  r   r   r   r   r  r   Taskr  r-  r8  r   r   rJ  rX  r\  rf  r   ri  r  r&   r  r  r  r  r  r  r  r  r{  r|  r  r  r}  r  r  r6  r<  rK  rO  r'   
PLAYPALACE	QUENTIN_CrU  rc  rs  r  r  r)   rm  r
   r}  r  r  r  r  r  r  r  r  r#  r  r*  r  r  r?  rX  r  r  r   r  rB  r  r  r  r  r  r  r  r  r  r  r&  r  r  r  r  r  r  r$  r*  r4  rK  r  r  r  r'  rc  rb  rg  rk  ru  rv  rl  rm  rn  rw  r  r  r  r  r  r  r  r  r  r  r  r  r
  r  r  r~  __static_attributes__rZ   rP   rN   r]   r]   s   s(    &)-&*%))- %`S`S `S 	`S
 4Z$&`S *t#`S td"`S 4Z$&`S `SDT6l!:xDt $ ,/ ,C , , "d
 "cDj "USVX[S[_ " "(('*(7:(	sCt#	$(@ 
-= 
 
PT 
 
3e,-47@CMRY^	23e,-47AFMR	d3e+<&= C e X\ 
  PS X[^bXb Gc Gd G  PSVZPZ 3 3 3QU: 
$
/ / J{ Jt J J20>dA&A2>A	A	
\ 	
d 	
 /c /ho / /	&	2>			\  PTUXZ`U`Pa 
$
36
	c6k	
  #  
| 
 
  S  ;$%L:	$	N Aw|| A A A((-=(	(6&[ &3 &SW &$5/? 5D 522B 2t 2># ## #c #VZ #P P PWs Wt W1# 1# 1$ 1 	//? 	/ 	/RV 	/-B5E -Bt -BX\ -Br 08' 8' 8'
 8' 8'  8' 8'  8' 8' 
8't!2B ! !( ( ( &	(
 %( 
{D 	!(TA A A
 A  A A  A A 
A4{ t &[ T 
>k 
>d 
>.[ .T .   <l
.> l
 l
QU l
\0-= 0t 0PT 0d -= s t   
,< 
c 
SV 
[_ 
 
&
4D &
d &
W[ &
PL,< L L KP 5AK 5A4 5ATX 5AnG+ G$ G.X[ XC XD X&/
k /
c /
d /
b1[ 1T 1j 	##%A""$?
0D{ 0Dt 0Df DI..+..<@.	.b FKJJ-0J>BJ	JZ KOD
D
-0D
=@4ZD
	D
T"19<DL	"
 
H 
3 
3 
&{ &s &t &A ATW A\` AK D 2
; 
 
QU 
$(
)9 (
4 (
D (
T3 3 Tz	3
 3 3 
32/2TzCG	2cc c 	c
 Djc 
cJ+ $ &1k &1QT &1Y] &1PKk Kd K'; 'VY '^b ''K 's 'W[ '2I I I:O+ O$ O,&&/2&	&4*+ *UX *]a *B(: (:SV (:[_ (:T1> 1>TW 1>\` 1>fD; D4 D
''/2';?'	' --/2-;?-	- :1:1/2:1;?:1	:1x'+ 'UX ']a '&+U[ +U +US +UUY +UZE; Et E EQ5Q5/2Q5;?Q5	Q5f''/2';?'	' ++/2+;?+	+4_-{ _-S _-T _-BIK ID I2E
 E
 E
QU E
N3 4 @C
; C
3 C
SV C
[_ C
J@
[ @
S @
UX @
]a @
D;
;
,/;
<?;
	;
z8
8
,/8
<?8
	8
t5
5
,/5
<?5
	5
n #03BE	2w
w
 w
 	w

 w
 
w
r''/2';?'	' ""/2";?"	"H??/2?;??	?($ $ $LD
 D
 D
 D
LDD 	D
 D 
D,)-=A	sCx4	& 0 0)- 0=A 0	tE{DKe4	5 0D3 3: \s \ \ \QT \U K 5k	
  
2!u ! ! !	1[ 	1 	1WZ 	1_b 	1 C EDL *66/26;?6	6 ++/2+;?+	+J
.*S *T *X#C,< #Cd #Ct #CJ	I;K 	IUY 	I^b 	I/C,< /Cd /Ct /Cb$<)9 $<4 $<D $<L9tCy 99{ 9tCy 9v
K 
D 
::; :t : :4L0@ LT L*+;K +PT +&,)9 ,d ,rP   r]   r_   r`   rc   rd   rf   c           	        #    [        5         [        [        R                  " 5       5        [	        5       n[        5       n[        5       S-  n[        XV5      (       a  g[        U5      u  pU	(       a  [        XuU5        [        X5      n [        X5      n[        X#U5      u  p#[        S[         S35        [        U UUU[!        U5      US9n
U
R#                  5       I Sh  vN     [        R$                  " S5      I Sh  vN   M    N& N! [&         a     Of = fU
R)                  5       I Sh  vN    g! U
R)                  5       I Sh  vN    f = f7f)a  Run the server.

Args:
    host: Host address to bind to (falls back to [server].bind_ip in config)
    port: Port number to listen on (falls back to [server].port in config, then 8000)
    ssl_cert: Path to SSL certificate file (falls back to [network].ssl_cert in config)
    ssl_key: Path to SSL private key file (falls back to [network].ssl_key in config)
    preload_locales: Whether to block on localization compilation.
ri   NzStarting PlayPalace vz
 server...)r_   r`   rc   rd   ra   rf   r   )_configure_logging_install_exception_handlersr   r  r   r   r[   _ensure_config_file_inspect_database_ensure_server_owner_resolve_bind_host_resolve_port_resolve_sslr   r  r]   rH   r   sleepKeyboardInterruptr   )r_   r`   rc   rd   rf   re   example_pathra   
db_createdneeds_ownerrR   s              rN   
run_serverr    s$       8 8 :;)+K*,L$&8G;55/8JW:>d0D+D$XDH	!'*
56G'F ,,.--""" 	 
 #  kkmfkkmsr   CEDE$D	 >D?D	 ED	 	
DD3 DD3 E,D/-E3EE
EEc                  >   [        5       n [        R                  " S5      n[        R                  " [	        U S-  5      5      nUR                  [        R                  5        UR                  U5        [        R                  " [        R                  5      nUR                  [        R                  5        UR                  U5        [        R                  " S5      nUR                  [        R                  5        UR                  U5        UR                  U5        g)z7Configure server error logging to both file and stderr.z/%(asctime)s %(levelname)s %(name)s: %(message)sz
errors.logr  N)r[   r  	FormatterFileHandlerrH   setLevelERRORsetFormatterStreamHandlerr   r   r  
addHandler)log_dirfmtfile_handlerstderr_handlerroots        rN   r  r  :  s    $&G


M
NC&&s7\+A'BCL'--(c"**3::6NGMM*$\*DMM'-- OOL!OON#rP   r  c                 H    S nS nU[         l        U R                  U5        g)z8Install top-level exception handlers for the event loop.c                     U [         [        R                  4;   a  g[        R                  " S5      R                  SXU4S9  g)z;Log uncaught exceptions while skipping shutdown interrupts.Nr  zUncaught exceptionrw  )r  r   r   r  r  r  )exc_typer   tbs      rN   _log_uncaught2_install_exception_handlers.<locals>._log_uncaughtP  sA    )7+A+ABB,'11 H2+> 	2 	
rP   c                 (   UR                  S5      n[        U[        R                  5      (       a  gU(       a%  [        R
                  " S5      R                  SUS9  g[        R
                  " S5      R                  SUR                  S5      5        g)z7Log asyncio exceptions with consistent context details.r  Nr  zAsyncio exceptionrw  zAsyncio error: %srl   )r   rF   r   r   r  r  r  r  )r  contextr   s      rN   _asyncio_exception_handler?_install_exception_handlers.<locals>._asyncio_exception_handlerX  so    kk+&c71122l+556ITW5Xl+112Ew{{S\G]^rP   N)r   
excepthookset_exception_handler)r  r  r  s      rN   r  r  M  s%    
_ #CN9:rP   re   r  c                    U R                  5       (       a  gUR                  5       (       d'  [        SU S3[        R                  S9  [	        S5      e [        5         [        R                  " X5        [        S	U  S
U S35        [        S5        g! [         a/  n[        SU  SU 3[        R                  S9  [	        S5      UeSnAff = f)zFEnsure a server config exists; return True if created and exit needed.Fz'ERROR: Missing configuration template ''.r   r   zERROR: Failed to create 'z' from template: Nz	Created 'z' from 'a  Review the generated configuration before running in production. TLS is required unless you explicitly allow insecure mode.
Edit the file and run the server with:
  uv run python main.py --ssl-cert <cert> --ssl-key <key>
or set [network].allow_insecure_ws=true for local development.T)	r   r   r   r   r   r   shutilcopyfiler  )re   r  r   s      rN   r  r  f  s      5l^2F	
 m%!#2 
Ik](<.
;<		I   %'}4EcUK	
 m$%s    B 
C*CCra   c                 t   U R                  5       (       d  g [        [        U 5      5      nUR                  5         UR	                  5       nUR                  5       nUR                  5         SUS:H  =(       d    USL 4$ ! [         a/  n[        SU  SU 3[        R                  S9  [        S5      UeSnAff = f)	z>Check if the database exists and whether an owner is required.)TTFr   Nz ERROR: Failed to open database 'z': r   r   )r   r   rH   r   r|  get_server_ownerr   r}  r   r   r   r   )ra   database
user_countownerr   s        rN   r  r    s    >>	%CL),,.
))+jAo6$66 %0	SEBTm$%s   A%A> >
B7*B22B7r  c                    SSK Jn  U(       a  [        SU  S35        O[        S5        [        R                  R                  5       (       d#  [        S[        R                  S9  [        S5      e[        U5      u  pEpg[        XE5      n[        Xg5      n	 U" [        U 5      UU	S	S
9  [        SU S35        g! [         a,  n
[        SU
 3[        R                  S9  [        S5      U
eSn
A
ff = f)z,Create the initial server owner if required.r   )bootstrap_ownerzCreating database at 'r!  z8No server owner found in the database. Creating one now.zERROR: Cannot prompt for a server owner in a non-interactive session. Run `uv run python -m server.cli bootstrap-owner --username <name>` to create the initial owner.r   r   T)ra   r6  r7  quietzCreated server owner 'zERROR: N)
server.clir*  r   r   stdinisattyr   r   _load_auth_limits_prompt_username_prompt_passwordrH   r  )ra   re   r  r*  min_user_lenmax_user_lenmin_pass_lenmax_pass_lenr6  r7  r   s              rN   r  r    s    *&wir23HI99+ 		
 m=N{=[:L;H;H
%L		
 	&xj34 %uoCJJ/m$%s   !B6 6
C, 'C''C,c                 f   [         n[        n[        n[        n [	        U S5       n[
        R                  " U5      nSSS5        WR                  S5      n[        U[        5      (       al  [        UR                  SU5      5      n[        UR                  SU5      5      n[        UR                  SU5      5      n[        UR                  SU5      5      nXX44$ ! , (       d  f       N= f! [        [
        R                  [        [        4 a   n[        R!                  SU5         SnANTSnAff = f)	z>Load auth length limits from config, falling back to defaults.r   Nr   r  r  r  r  z*Failed to load auth limits from config: %s)r   r   r   r   r  r  r   r   rF   r  rK   r  r  r   r   r   r)  )	re   r2  r3  r4  r5  r"  r,  r$  r   s	            rN   r/  r/    s    .L.L.L.L
E+t$<<?D %88F#h%%x||,A<PQLx||,A<PQLx||,A<PQLx||,A<PQL |AA %$ W,,iD E		>DDEs.   C, CBC, 
C)%C, ,$D0D++D0min_lenmax_lenc                      [        SU  SU S35      R                  5       nU(       d  [        S5        M5  U [        U5      s=::  a  U::  d  O  [        SU  SU S35        Ma  U$ )z)Prompt for a valid server owner username.zServer owner username (r  	 chars): zUsername cannot be empty.zUsername must be between  and  characters.)inputrI   r   r?  )r7  r8  r6  s      rN   r0  r0    sh    
27)1WIYOPVVX-.3x=3G3-gYeG9LQRrP   c                      [        SU  SU S35      nU(       d  [        S5        M'  U [        U5      s=::  a  U::  d  O  [        SU  SU S35        MS  [        S5      nX#:w  a  [        S	5        Mp  U$ )
z)Prompt for a valid server owner password.zServer owner password (r  r:  zPassword cannot be empty.zPassword must be between r;  r<  zConfirm password: z"Passwords do not match. Try again.)r   r   r?  )r7  r8  r7  confirms       rN   r1  r1    s|    
4WIQwiyQR-.3x=3G3-gYeG9LQR./67rP   c                     U b  U $ [        U5      nUR                  S5      n[        U[        5      (       a%  UR	                  5       (       a  UR	                  5       $ g)z1Resolve bind host from config when none provided.bind_ipr   )r   r   rF   rH   rI   )r_   re   r   rA  s       rN   r  r    sM    &{3M	*G'3GMMOO}}rP   c                     U b  U $ [        U5      nUR                  S5      n[        U[        5      (       a  SUs=::  a  S::  a   U$   Ub  [        R                  SU5        g)z@Resolve port from config when none provided on the command line.r`   r   i  uW   Invalid port %r in config.toml [server].port (must be 1–65535). Falling back to 8000.r  )r   r   rF   rK   r   r   )r`   re   r   cfg_ports       rN   r  r    si    &{3M  (H(C  Q(%;e%; &<$	

 rP   c                    U c  Ub  X4$ [        U5      nUR                  S0 5      nUR                  S5      nUR                  S5      nU(       a
  U(       a  XV4$ [        U5      [        U5      :w  a  [        R	                  S5        g)a$  Resolve SSL cert/key from config when not provided on the command line.

CLI arguments take precedence. If neither cert nor key was given via CLI,
both are read from [network] in config.toml. A partial CLI override (one
but not the other) is caught upstream in main.py before this is called.
r	  rc   rd   zSSL misconfiguration in config.toml: both [network].ssl_cert and [network].ssl_key must be set together. SSL will not be enabled.)NN)r   r   rG   r   r   )rc   rd   re   cfgr'  cfg_certcfg_keys          rN   r   r     s     w2  
;
'Cggi$G{{:&Hkk)$GG  H~g&O	
 rP   )NNNNFr  )r  r   r   r   r  ry  r"  r   r  rg  collectionsr   r   r   r   pathlibr   r  r&  enumr	   typingr
   r  ModuleNotFoundErrortomlipydanticr   config_pathsr   r   r   r   rd  r   r   r   tickr   r   r  r   documents.browsingr   r   documents.transcriber_roler   virtual_botsr   network.websocket_serverr   r   persistence.databaser   	auth.authr   r    tables.managerr!   users.network_userr"   
users.baser#   r$   r%   users.preferencesr&   r'   r(   r)   games.registryr*   r+   messages.localizationr,   ui.common_flowsr-   documents.managerr.   network.packet_modelsr/   r  r{  r  rz  r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  rG   rO   __file__rr   r   resolverV   
_REPO_ROOTrX   r   r[   r]   rH   rK   r  r  AbstractEventLoopr  r  r  r  r  r/  r0  r1  r  r  r   rZ   rP   rN   <module>rd     s   6   *  	  
    '       %  B A 3 / E < + H + / ( + < < [ [ 9 0 - . C ? !!"67+,    ! ( $% !$% !+, (&( # #%    #*  $5 !%  d t 0 8n##**(^##%--a0
u$x/"Y.  MB, "79M MB,bD "&!%!4
*4
*4 Dj44 4Z$	4
 4 
4n$&;g&?&? ;D ;2T  $ <%t %dDj(9 %"!%$ !%T !%t !%PT !%HB4 BE#sC2D,E B(
c 
C 
C 
c C C "S4Z d s d
  # "Dj44Z$  3:cDj4//0	_O  s   I: :
JJ