2
2
3
3
import argparse
4
4
import asyncio
5
+ import copy
5
6
import functools
6
7
import logging
7
8
import zlib
22
23
23
24
ModuleUpdate .update ()
24
25
26
+ if typing .TYPE_CHECKING :
27
+ import ssl
28
+
25
29
import websockets
26
30
import colorama
27
31
try :
40
44
print_command_compatability_threshold = Version (0 , 3 , 5 ) # Remove backwards compatibility around 0.3.7
41
45
colorama .init ()
42
46
47
+
48
+ def remove_from_list (container , value ):
49
+ try :
50
+ container .remove (value )
51
+ except ValueError :
52
+ pass
53
+ return container
54
+
55
+
56
+ def pop_from_container (container , value ):
57
+ try :
58
+ container .pop (value )
59
+ except ValueError :
60
+ pass
61
+ return container
62
+
63
+
64
+ def update_dict (dictionary , entries ):
65
+ dictionary .update (entries )
66
+ return dictionary
67
+
68
+
43
69
# functions callable on storable data on the server by clients
44
70
modify_functions = {
45
71
"add" : operator .add , # add together two objects, using python's "+" operator (works on strings and lists as append)
56
82
"and" : operator .and_ ,
57
83
"left_shift" : operator .lshift ,
58
84
"right_shift" : operator .rshift ,
85
+ # lists/dicts
86
+ "remove" : remove_from_list ,
87
+ "pop" : pop_from_container ,
88
+ "update" : update_dict ,
59
89
}
60
90
61
91
@@ -116,7 +146,6 @@ class Context:
116
146
"location_check_points" : int ,
117
147
"server_password" : str ,
118
148
"password" : str ,
119
- "forfeit_mode" : str , # TODO remove around 0.4
120
149
"release_mode" : str ,
121
150
"remaining_mode" : str ,
122
151
"collect_mode" : str ,
@@ -138,7 +167,7 @@ class Context:
138
167
non_hintable_names : typing .Dict [str , typing .Set [str ]]
139
168
140
169
def __init__ (self , host : str , port : int , server_password : str , password : str , location_check_points : int ,
141
- hint_cost : int , item_cheat : bool , forfeit_mode : str = "disabled" , collect_mode = "disabled" ,
170
+ hint_cost : int , item_cheat : bool , release_mode : str = "disabled" , collect_mode = "disabled" ,
142
171
remaining_mode : str = "disabled" , auto_shutdown : typing .SupportsFloat = 0 , compatibility : int = 2 ,
143
172
log_network : bool = False ):
144
173
super (Context , self ).__init__ ()
@@ -154,7 +183,7 @@ def __init__(self, host: str, port: int, server_password: str, password: str, lo
154
183
self .player_names : typing .Dict [team_slot , str ] = {}
155
184
self .player_name_lookup : typing .Dict [str , team_slot ] = {}
156
185
self .connect_names = {} # names of slots clients can connect to
157
- self .allow_forfeits = {}
186
+ self .allow_releases = {}
158
187
# player location_id item_id target_player_id
159
188
self .locations = {}
160
189
self .host = host
@@ -171,7 +200,7 @@ def __init__(self, host: str, port: int, server_password: str, password: str, lo
171
200
self .location_check_points = location_check_points
172
201
self .hints_used = collections .defaultdict (int )
173
202
self .hints : typing .Dict [team_slot , typing .Set [NetUtils .Hint ]] = collections .defaultdict (set )
174
- self .release_mode : str = forfeit_mode
203
+ self .release_mode : str = release_mode
175
204
self .remaining_mode : str = remaining_mode
176
205
self .collect_mode : str = collect_mode
177
206
self .item_cheat = item_cheat
@@ -545,7 +574,7 @@ def set_save(self, savedata: dict):
545
574
self .location_check_points = savedata ["game_options" ]["location_check_points" ]
546
575
self .server_password = savedata ["game_options" ]["server_password" ]
547
576
self .password = savedata ["game_options" ]["password" ]
548
- self .release_mode = savedata ["game_options" ][ "release_mode" ]
577
+ self .release_mode = savedata ["game_options" ]. get ( "release_mode" , savedata [ "game_options" ]. get ( "forfeit_mode" , "goal" ))
549
578
self .remaining_mode = savedata ["game_options" ]["remaining_mode" ]
550
579
self .collect_mode = savedata ["game_options" ]["collect_mode" ]
551
580
self .item_cheat = savedata ["game_options" ]["item_cheat" ]
@@ -590,6 +619,8 @@ def slot_set(self, slot) -> typing.Set[int]:
590
619
591
620
def _set_options (self , server_options : dict ):
592
621
for key , value in server_options .items ():
622
+ if key == "forfeit_mode" :
623
+ key = "release_mode"
593
624
data_type = self .simple_options .get (key , None )
594
625
if data_type is not None :
595
626
if value not in {False , True , None }: # some can be boolean OR text, such as password
@@ -1226,7 +1257,7 @@ def _cmd_status(self, tag:str="") -> bool:
1226
1257
1227
1258
def _cmd_release (self ) -> bool :
1228
1259
"""Sends remaining items in your world to their recipients."""
1229
- if self .ctx .allow_forfeits .get ((self .client .team , self .client .slot ), False ):
1260
+ if self .ctx .allow_releases .get ((self .client .team , self .client .slot ), False ):
1230
1261
release_player (self .ctx , self .client .team , self .client .slot )
1231
1262
return True
1232
1263
if "enabled" in self .ctx .release_mode :
@@ -1735,7 +1766,7 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
1735
1766
return
1736
1767
args ["cmd" ] = "SetReply"
1737
1768
value = ctx .stored_data .get (args ["key" ], args .get ("default" , 0 ))
1738
- args ["original_value" ] = value
1769
+ args ["original_value" ] = copy . copy ( value )
1739
1770
for operation in args ["operations" ]:
1740
1771
func = modify_functions [operation ["operation" ]]
1741
1772
value = func (value , operation ["value" ])
@@ -1882,7 +1913,7 @@ def _cmd_allow_release(self, player_name: str) -> bool:
1882
1913
player = self .resolve_player (player_name )
1883
1914
if player :
1884
1915
team , slot , name = player
1885
- self .ctx .allow_forfeits [(team , slot )] = True
1916
+ self .ctx .allow_releases [(team , slot )] = True
1886
1917
self .output (f"Player { name } is now allowed to use the !release command at any time." )
1887
1918
return True
1888
1919
@@ -1895,7 +1926,7 @@ def _cmd_forbid_release(self, player_name: str) -> bool:
1895
1926
player = self .resolve_player (player_name )
1896
1927
if player :
1897
1928
team , slot , name = player
1898
- self .ctx .allow_forfeits [(team , slot )] = False
1929
+ self .ctx .allow_releases [(team , slot )] = False
1899
1930
self .output (f"Player { name } has to follow the server restrictions on use of the !release command." )
1900
1931
return True
1901
1932
@@ -2050,7 +2081,7 @@ def attrtype(input_text: str):
2050
2081
return input_text
2051
2082
setattr (self .ctx , option_name , attrtype (option ))
2052
2083
self .output (f"Set option { option_name } to { getattr (self .ctx , option_name )} " )
2053
- if option_name in {"forfeit_mode" , " release_mode" , "remaining_mode" , "collect_mode" }: # TODO remove forfeit_mode with 0.4
2084
+ if option_name in {"release_mode" , "remaining_mode" , "collect_mode" }:
2054
2085
self .ctx .broadcast_all ([{"cmd" : "RoomUpdate" , 'permissions' : get_permissions (self .ctx )}])
2055
2086
elif option_name in {"hint_cost" , "location_check_points" }:
2056
2087
self .ctx .broadcast_all ([{"cmd" : "RoomUpdate" , option_name : getattr (self .ctx , option_name )}])
@@ -2090,6 +2121,8 @@ def parse_args() -> argparse.Namespace:
2090
2121
parser .add_argument ('--password' , default = defaults ["password" ])
2091
2122
parser .add_argument ('--savefile' , default = defaults ["savefile" ])
2092
2123
parser .add_argument ('--disable_save' , default = defaults ["disable_save" ], action = 'store_true' )
2124
+ parser .add_argument ('--cert' , help = "Path to a SSL Certificate for encryption." )
2125
+ parser .add_argument ('--cert_key' , help = "Path to SSL Certificate Key file" )
2093
2126
parser .add_argument ('--loglevel' , default = defaults ["loglevel" ],
2094
2127
choices = ['debug' , 'info' , 'warning' , 'error' , 'critical' ])
2095
2128
parser .add_argument ('--location_check_points' , default = defaults ["location_check_points" ], type = int )
@@ -2162,6 +2195,14 @@ async def auto_shutdown(ctx, to_cancel=None):
2162
2195
await asyncio .sleep (seconds )
2163
2196
2164
2197
2198
+ def load_server_cert (path : str , cert_key : typing .Optional [str ]) -> "ssl.SSLContext" :
2199
+ import ssl
2200
+ ssl_context = ssl .SSLContext (ssl .PROTOCOL_TLS_SERVER )
2201
+ ssl_context .load_default_certs ()
2202
+ ssl_context .load_cert_chain (path , cert_key if cert_key else path )
2203
+ return ssl_context
2204
+
2205
+
2165
2206
async def main (args : argparse .Namespace ):
2166
2207
Utils .init_logging ("Server" , loglevel = args .loglevel .lower ())
2167
2208
@@ -2197,8 +2238,10 @@ async def main(args: argparse.Namespace):
2197
2238
2198
2239
ctx .init_save (not args .disable_save )
2199
2240
2241
+ ssl_context = load_server_cert (args .cert , args .cert_key ) if args .cert else None
2242
+
2200
2243
ctx .server = websockets .serve (functools .partial (server , ctx = ctx ), host = ctx .host , port = ctx .port , ping_timeout = None ,
2201
- ping_interval = None )
2244
+ ping_interval = None , ssl = ssl_context )
2202
2245
ip = args .host if args .host else Utils .get_public_ipv4 ()
2203
2246
logging .info ('Hosting game at %s:%d (%s)' % (ip , ctx .port ,
2204
2247
'No password' if not ctx .password else 'Password: %s' % ctx .password ))
0 commit comments