diff --git a/README.md b/README.md index 284bf15..5131903 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,10 @@ This program comes without any warranty, to the extent permitted by applicable l * Has optional script to add crontab entry (to schedule update script every minute that runs the py file unless the py file is not complete [took longer than 1 minute]) ## Developer Notes: +* Player username privacy: check_players in chunkymap-regen.py intentionally makes up an index and uses that as the filename on the destination, so that ajax can update players without knowing either their id (filename of minetest player file) or display name (listed in the player file) +(this way, only usernames can be known if chunkymap.php allows that, or the person is logged in to the server) +Because of the feature, chunkymap-regen.py must prevent duplicates based on value of id in the resulting yml files (minetest player filename as id). +This should be hard to corrupt since id is used as the indexer for the players dict (however, extra files with no matching entry in the dict will still need to be deleted if they exist) * games_path and mods_path should not be stored in minetestmeta.yml, since otherwise the values may deviate from the parent directories. To avoid this problem, instead derive the paths from the parent paths using your favorite language such as in the following examples: games_path = os.path.join(minetestinfo.get_var("shared_minetest_path"), "games") diff --git a/chunkymap-regen.py b/chunkymap-regen.py index fc3ef23..705069b 100644 --- a/chunkymap-regen.py +++ b/chunkymap-regen.py @@ -207,6 +207,7 @@ class MTChunks: colors_path = None python_exe_path = None chunks = None + players = None # dict with id as subscript, each containing player metadata dict decachunks = None rendered_this_session_count = None #force_rerender_decachunks_enable = None @@ -1131,9 +1132,61 @@ class MTChunks: self.create_chunk_folder(chunky_x, chunky_z) self.chunks[chunk_luid].save_yaml(chunk_yaml_path) print(min_indent+"(saved yaml to '"+chunk_yaml_path+"')") + + def get_new_player_index(self): + result = None + if self.players is not None: + result = 0 + for this_key in self.players.keys(): + this_player = self.players[this_key] + if "index" in this_player: + if this_player["index"]==result: + result = this_player["index"] + 1 + return result + + def save_player(self, playerid): + if self.players is not None: + if playerid is not None: + if playerid in self.players: + if not os.path.isdir(self.chunkymap_players_path): + os.makedirs(self.chunkymap_players_path) + self.deny_http_access(self.chunkymap_players_path) + this_player = self.players[playerid] + if "index" in this_player: + player_path = os.path.join(self.chunkymap_players_path, this_player["index"]) + save_conf_from_dict(player_path, this_player, ":") + else: + print("ERROR: cannot save player since missing 'index' ('index' is used for filename on map)") + else: + print("ERROR: tried to save nonexistant player id '"+str(playerid)+"'") + else: + print("ERROR: save_player(None) was attempted.") + else: + print("ERROR: Tried save_player but the players dict is not ready (self.players is None)") def check_players(self): print("PROCESSING PLAYERS") + if self.players is None: + self.players = {} + if os.path.isdir(self.chunkymap_players_path): + folder_path = self.chunkymap_players_path + for sub_name in os.listdir(folder_path): + sub_path = os.path.join(folder_path,sub_name) + if os.path.isfile(sub_path): + if (sub_name[:1]!="."): + if len(sub_name)>4 and sub_name[-4:]==".yml": + player_dict = get_dict_from_conf_file(sub_path,":") + if player_dict is not None: + if "id" in player_dict: + if (player_dict["id"] is not None) and (len(player_dict["id"])>0): + self.players[player_dict["id"]] = player_dict + else: + print("ERROR: no 'id' in chunkymap player entry '"+sub_path+"'") + else: + print("ERROR: could not read any yaml values from '"+sub_path+"'") + else: + os.makedirs(self.chunkymap_players_path) + self.deny_http_access(self.chunkymap_players_path) players_path = os.path.join(minetestinfo.get_var("primary_world_path"), "players") player_count = 0 @@ -1169,7 +1222,28 @@ class MTChunks: is_enough_data = True break ins.close() - player_dest_path = os.path.join(self.chunkymap_players_path,file_name+".yml") + player_index = None + this_player = None + is_changed = False + if file_name in self.players: + this_player = self.players[file_name] + if "index" in this_player: + player_index = self.players[file_name]["index"] + else: + player_index = self.get_new_player_index() + self.players[file_name]["index"] = player_index + is_changed = True + else: + this_player = {} + player_index = self.get_new_player_index() + this_player["index"] = player_index + self.players[file_name] = this_player + is_changed = True + player_dest_path = None + if player_index is not None: + player_dest_path = os.path.join(self.chunkymap_players_path, player_index+".yml") + else: + print("ERROR: player_index is None for '"+file_name+"' (this should never happen)") player_x = None player_y = None player_z = None @@ -1196,24 +1270,44 @@ class MTChunks: #if is_enough_data: #if player_name!="singleplayer": - map_player_dict = get_dict_from_conf_file(player_dest_path,":") + #this_player = get_dict_from_conf_file(player_dest_path,":") #map_player_position_tuple = None saved_player_x = None saved_player_y = None saved_player_y = None - if map_player_dict is not None: - #map_player_position_tuple = saved_player_x, saved_player_y, saved_player_z - if "x" in map_player_dict.keys(): - saved_player_x = float(map_player_dict["x"]) - if "y" in map_player_dict.keys(): - saved_player_y = float(map_player_dict["y"]) - if "z" in map_player_dict.keys(): - saved_player_z = float(map_player_dict["z"]) - - #if (map_player_dict is None) or not is_same_fvec3( map_player_position_tuple, player_position_tuple): - if (map_player_dict is None) or (saved_player_x is None) or (saved_player_z is None) or (int(saved_player_x)!=int(player_x)) or (int(saved_player_y)!=int(player_y)) or (int(saved_player_z)!=int(player_z)): + #map_player_position_tuple = saved_player_x, saved_player_y, saved_player_z + is_moved = False + if "x" in this_player.keys(): + saved_player_x = float(this_player["x"]) + if int(saved_player_x) != int(player_x): + is_moved = True + else: + this_player["x"] = player_x + is_moved = True + if "y" in this_player.keys(): + saved_player_y = float(this_player["y"]) + if int(saved_player_y) != int(player_y): + is_moved = True + else: + this_player["y"] = player_y + is_moved = True + if "z" in this_player.keys(): + saved_player_z = float(this_player["z"]) + if int(saved_player_z) != int(player_z): + is_moved = True + else: + this_player["z"] = player_z + is_moved = True + if is_moved: + is_changed = True + + + #if (this_player is None) or not is_same_fvec3( map_player_position_tuple, player_position_tuple): + #if (this_player is None) or (saved_player_x is None) or (saved_player_z is None) or (int(saved_player_x)!=int(player_x)) or (int(saved_player_y)!=int(player_y)) or (int(saved_player_z)!=int(player_z)): + if is_changed: # don't check y since y is elevation in minetest, don't use float since subblock position doesn't matter to map - if map_player_dict is not None and saved_player_x is not None and saved_player_y is not None and saved_player_z is not None: + #if this_player is not None and saved_player_x is not None and saved_player_y is not None and saved_player_z is not None: + if is_moved: #print("PLAYER MOVED: "+str(player_name)+" moved from "+str(map_player_position_tuple)+" to "+str(player_position_tuple)) if self.verbose_enable: print("PLAYER MOVED: "+str(player_name)+" moved from "+str(saved_player_x)+","+str(saved_player_y)+","+str(saved_player_z)+" to "+str(player_x)+","+str(player_y)+","+str(player_z)) @@ -1222,19 +1316,21 @@ class MTChunks: if self.verbose_enable: print("SAVING YAML for player '"+str(player_name)+"'") players_saved_count += 1 - outs = open(player_dest_path, 'w') - if player_name is not None: - outs.write("name:"+player_name+"\n") # python automatically uses correct newline for your os when you put "\n" - #if player_position is not None: - # outs.write("position:"+player_position+"\n") - if player_x is not None: - outs.write("x:"+str(player_x)+"\n") - if player_y is not None: - outs.write("y:"+str(player_y)+"\n") - if player_z is not None: - outs.write("z:"+str(player_z)+"\n") - outs.write("is_enough_data:"+str(is_enough_data)) - outs.close() + save_conf_from_dict(player_dest_path, this_player, ":", save_nulls_enable=False) + #outs = open(player_dest_path, 'w') + #outs.write("id:"+file_name) + #if player_name is not None: + # outs.write("name:"+player_name+"\n") # python automatically uses correct newline for your os when you put "\n" + ##if player_position is not None: + ## outs.write("position:"+player_position+"\n") + #if player_x is not None: + # outs.write("x:"+str(player_x)+"\n") + #if player_y is not None: + # outs.write("y:"+str(player_y)+"\n") + #if player_z is not None: + # outs.write("z:"+str(player_z)+"\n") + #outs.write("is_enough_data:"+str(is_enough_data)) + #outs.close() player_written_count += 1 else: #if self.verbose_enable: @@ -1244,7 +1340,7 @@ class MTChunks: #if not self.verbose_enable: print("PLAYERS:") print(" saved: "+str(player_written_count)+" (moved:"+str(players_moved_count)+"; new:"+str(players_saved_count)+")") - print(" didn't move: "+str(players_didntmove_count)) + print(" didn't change: "+str(players_didntmove_count)) def is_chunk_traversed_by_player(self, chunk_luid): result = False diff --git a/minetestinfo.py b/minetestinfo.py index 3f5e444..4f21171 100644 --- a/minetestinfo.py +++ b/minetestinfo.py @@ -33,6 +33,8 @@ prepackaged_game_mod_list = list() prepackaged_gameid = "minetest_game" new_mod_list = list() +user_excluded_mod_count = 0 + minetestinfo = ConfigManager(os.path.join(os.path.dirname(os.path.abspath(__file__)), "minetestmeta.yml"), ":") os_name="linux" @@ -305,7 +307,7 @@ def load_world_and_mod_data(): print("") if len(prepackaged_game_mod_list)<1: prepackaged_game_mod_list = get_modified_mod_list_from_game_path(prepackaged_game_mod_list, prepackaged_game_path) - print(prepackaged_gameid+" has the following mod(s): "+','.join(prepackaged_game_mod_list)) + print(prepackaged_gameid+" has "+str(len(prepackaged_game_mod_list))+" mod(s): "+','.join(prepackaged_game_mod_list)) if minetestinfo.contains("game_path") and os.path.isdir(minetestinfo.get_var("game_path")): loaded_mod_list = get_modified_mod_list_from_game_path(loaded_mod_list, minetestinfo.get_var("game_path")) @@ -318,11 +320,15 @@ def load_world_and_mod_data(): if len(new_mod_list)>0: new_mod_list_msg = ": "+','.join(new_mod_list) gameid = os.path.basename(minetestinfo.get_var("game_path")) + print("") print(gameid+" has "+str(len(new_mod_list))+" mod(s) beyond "+prepackaged_gameid+new_mod_list_msg+")") + if (user_excluded_mod_count>0): + print(" (not including "+str(user_excluded_mod_count)+" mods(s) excluded by world.mt)") else: print("Could not find game folder '"+minetestinfo.get_var("game_path")+"'. Please fix game_path in '"+minetestinfo._config_path+"' to point to your subgame, so that game and mod management features will work.") def get_modified_mod_list_from_game_path(mod_list, game_path): + global user_excluded_mod_count if mod_list is None: mod_list = list() if game_path is not None and os.path.isdir(game_path): @@ -330,6 +336,7 @@ def get_modified_mod_list_from_game_path(mod_list, game_path): folder_path = mods_path missing_load_mod_setting_count = 0 check_world_mt() + user_excluded_mod_count = 0 for sub_name in os.listdir(folder_path): sub_path = os.path.join(folder_path,sub_name) if os.path.isdir(sub_path): @@ -338,6 +345,8 @@ def get_modified_mod_list_from_game_path(mod_list, game_path): load_mod_variable_name = "load_mod_"+sub_name if (world_mt_mapvars is not None) and (load_mod_variable_name in world_mt_mapvars): load_this_mod = get_world_var(load_mod_variable_name) + if load_this_mod != True: + user_excluded_mod_count += 1 if load_this_mod == True: if sub_name not in mod_list: mod_list.append(sub_name) diff --git a/web/chunkymap.php b/web/chunkymap.php index 7354e38..1208d25 100644 --- a/web/chunkymap.php +++ b/web/chunkymap.php @@ -252,8 +252,8 @@ function get_javascript_bool_value($this_bool) { } //chunk_mode_enable: shows chunk png images instead of decachunk jpg images (slower) -//debug_mode_enable: draws colored rectangles based on yml files instead of drawing images -function echo_chunkymap_canvas($chunk_mode_enable, $debug_mode_enable, $html4_mode_enable) { +//visual_debug_enable: draws colored rectangles based on yml files instead of drawing images +function echo_chunkymap_canvas($chunk_mode_enable, $visual_debug_enable, $html4_mode_enable) { global $chunkymap_view_x; global $chunkymap_view_z; global $chunkymap_view_zoom; @@ -346,7 +346,7 @@ function echo_chunkymap_canvas($chunk_mode_enable, $debug_mode_enable, $html4_mo var chunkymap_view_min_zoom='.$chunkymap_view_min_zoom.'; var chunkymap_zoom_delta='.$chunkymap_change_zoom_multiplier.'; var chunk_mode_enable='.get_javascript_bool_value($chunk_mode_enable).'; - var debug_mode_enable='.get_javascript_bool_value($debug_mode_enable).'; + var visual_debug_enable='.get_javascript_bool_value($visual_debug_enable).'; var chunks_per_tile_x_count='.$chunks_per_tile_x_count.'; var chunks_per_tile_z_count='.$chunks_per_tile_z_count.'; var tile_w='.$tile_w.'; @@ -529,8 +529,6 @@ function echo_chunkymap_canvas($chunk_mode_enable, $debug_mode_enable, $html4_mo //size_1pt_pixel_count = ctx.canvas.height/600.0; var bw_index = 0; - - for (i=0; i'; + echo ''; echo ''; echo ''; echo ''; echo ''; $this_tiley_z=$max_tiley_z; //start at max since screen is inverted cartesian - if ($debug_mode_enable!==true) { + if ($visual_debug_enable!==true) { //this table loads the images then is hidden when javascript runs successfully, but it is also a map though not very functional echo ''."\r\n"; echo ' '."\r\n"; diff --git a/web/viewchunkymap.php b/web/viewchunkymap.php index b36c465..6b4a8ee 100644 --- a/web/viewchunkymap.php +++ b/web/viewchunkymap.php @@ -27,9 +27,9 @@ if (is_file('chunkymap.php')) { set_chunkymap_view($chunkymap_view_x,$chunkymap_view_z,$chunkymap_view_zoom); //echo "
"; $chunk_mode_enable=true; //(this should normally be false) if true, uses 16x16 png files instead of the 160x160 decachunks; it is slower but may have more of the map during times when new chunks are explored but before the render queue is done and the decachunk images are created from the chunk images.); - $debug_mode_enable=false; //if true, this renders colors based on yml files instead of drawing images (and does not echo images at all) + $visual_debug_enable=false; //if true, this renders colors based on yml files instead of drawing images (and does not echo images at all) $html4_mode_enable=false; //if true, does not echo canvas nor client-side scripting - echo_chunkymap_canvas($chunk_mode_enable,$debug_mode_enable,$html4_mode_enable); + echo_chunkymap_canvas($chunk_mode_enable,$visual_debug_enable,$html4_mode_enable); //echo_chunkymap_as_chunk_table(false); //echo_decachunk_table(); //echo "
";