diff --git a/README.md b/README.md index 71a7ebf..5bfc788 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,9 @@ License: (see LICENSE in notepad or your favorite text editor) This program comes without any warranty, to the extent permitted by applicable law. ## Features: +* To loop program (reduces disc reads since stores certain info) run like: + chunkymap-regen.py --loop true +* Change program status while looping by placing chunkymap-signals.txt in the same folder as chunkymap-regen.py (see chunkymap-signals example.txt) * Has static html version of map (echo_chunkymap_table() php function) * Zoom in and out * optionally echo name of world that was detected by the scheduled py file @@ -16,6 +19,9 @@ 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: +* the map update function is only able to detect new chunks, and only checks edge chunks if player is present in one + ## Requirements: * A minetest version compatible with minetestmapper-numpy.py Made by Jogge, modified by celeron55 * Python 2.7 (any 2.7.x) diff --git a/chunkymap-genresults/chunk_x-4z5_mapper_result.txt b/chunkymap-genresults/chunk_x-4z5_mapper_result.txt new file mode 100644 index 0000000..9304926 --- /dev/null +++ b/chunkymap-genresults/chunk_x-4z5_mapper_result.txt @@ -0,0 +1,10 @@ +Result image (w=16 h=16) will be written to C:\Users\jgustafson\Documents\GitHub\minetest-chunkymap\chunk_x-4z5.png +Unknown node names: default:grass_1 flowers:mushroom_fertile_brown flowers:mushroom_fertile_red air +Unknown node ids: 0x0 0x1 0x9 0xc 0x7 +Drawing image +Saving to: C:\Users\jgustafson\Documents\GitHub\minetest-chunkymap\chunk_x-4z5.png +('PNG Region: ', [-64, -64, 80, 80]) +('pngMinX: ', '-64') +('pngMaxZ: ', '80') +('Pixels PerNode: ', 1) +('border: ', 0) diff --git a/chunkymap-regen.py b/chunkymap-regen.py index 684291a..bde3365 100644 --- a/chunkymap-regen.py +++ b/chunkymap-regen.py @@ -3,7 +3,18 @@ import os import subprocess import traceback import argparse - +import time +import sys +import timeit +from timeit import default_timer as best_timer + +#best_timer = timeit.default_timer +#if sys.platform == "win32": + # on Windows, the best timer is time.clock() +# best_timer = time.clock +#else: + # on most other platforms, the best timer is time.time() +# best_timer = time.time # REQUIRES: see README.md # The way to do a full render is deleting all files from the folder self.chunkymap_data_path such as /var/www/html/minetest/chunkymapdata (or chunkymap in current directory on Windows) @@ -15,10 +26,31 @@ import argparse #region server-specific options def get_dict_from_conf_file(path,assignment_operator="="): + results = None + results = get_dict_modified_by_conf_file(results, path, assignment_operator) + return results + +def RepresentsInt(s): + try: + int(s) + return True + except ValueError: + return False + +def RepresentsFloat(s): + try: + float(s) + return True + except ValueError: + return False + +def get_dict_modified_by_conf_file(this_dict, path,assignment_operator="="): results = None #print ("Checking "+str(path)+" for settings...") if os.path.isfile(path): - results = {} + results = this_dict + if (results is None) or (type(results) is not dict): + results = {} ins = open(path, 'r') line = True while line: @@ -32,11 +64,35 @@ def get_dict_from_conf_file(path,assignment_operator="="): if ao_index-1): + cparen_index = line_strip.find(")", oparen_index+1) + if (cparen_index>-1): + operations_string = line_strip[oparen_index+1:cparen_index] + operation_list = operations_string.split(" ") + #if len(operation_list)==2: + for operation_string in operation_list: + if "=" in operation_string: + chunks = operation_string.split("=") + if len(chunks)==2: + if chunks[0].strip()=="w": + try: + self.chunk_dict["image_w"]=int(chunks[1].strip()) + except: + print("Bad value for image w:"+str(chunks[1])) + elif chunks[0].strip()=="h": + try: + self.chunk_dict["image_h"]=int(chunks[1].strip()) + except: + print("Bad value for image h:"+str(chunks[1])) + else: + print("Bad name for image variable so ignoring variable named '"+str(chunks[0])+"'") + else: + print("Bad assignment (not 2 sides) so ignoring command '"+operation_string+"'") + else: + print("Bad assignment (operator) so ignoring command '"+operation_string+"'") + #else: + # print("Bad assignment count so ignoring operations string '"+operations_string+"'") + elif "PNG Region" in line_strip: + obracket_index = line_strip.find("[") + if obracket_index>-1: + cbracket_index = line_strip.find("]", obracket_index+1) + if cbracket_index>-1: + rect_values_string = line_strip[obracket_index+1:cbracket_index] + rect_values_list = rect_values_string.split(",") + if len(rect_values_list)==4: + #pngregion=[pngminx, pngmaxx, pngminz, pngmaxz] #from minetestmapper-numpy.py + self.chunk_dict["image_left"]=int(rect_values_list[0].strip()) + self.chunk_dict["image_right"]=int(rect_values_list[1].strip()) + self.chunk_dict["image_top"]=int(rect_values_list[2].strip()) + self.chunk_dict["image_bottom"]=int(rect_values_list[3].strip()) + else: + print("Bad map rect, so ignoring: "+rect_values_string) + ins.close() + class MTChunks: @@ -142,24 +287,35 @@ class MTChunks: pixelspernode = 1 refresh_map_enable = None refresh_players_enable = None + refresh_map_seconds = None + refresh_players_seconds = None + last_players_refresh_second = None + last_map_refresh_second = None #ALSO save to YAML: #total_generated_count = 0 #endregion values to save to YAML + + loop_enable = None + is_verbose = None world_blacklist = None def __init__(self): #formerly checkpaths() in global scope + self.is_verbose = True + self.loop_enable = True self.refresh_map_enable = True self.refresh_players_enable = True self.chunks = {} self.username = "owner" self.website_root="/var/www/html/minetest" self.world_name = "FCAGameAWorld" - os_name="linux" + self.os_name="linux" + self.refresh_map_seconds = 10 + self.refresh_players_seconds = 3 input_string = "" if (os.path.sep!="/"): - os_name="windows" + self.os_name="windows" #input_string = input("Which self.username contains minetest/util/minetestmapper-numpy.py (minetest not .minetest) ["+self.username+"]?") if (len(input_string)>0): self.username = input_string @@ -174,13 +330,13 @@ class MTChunks: self.world_name = input_string #region server-specific options self.profiles_path = "/home" - if os_name=="windows": + if self.os_name=="windows": self.profiles_path = "C:\\Users" self.profile_path = os.path.join(self.profiles_path, self.username) #if (not os.path.isdir(self.profile_path)): # self.profile_path = os.path.join(self.profiles_path, "jgustafson") self.dotminetest_path = os.path.join(self.profile_path,".minetest") - if (os_name=="windows"): self.dotminetest_path = "C:\\games\\Minetest" + if (self.os_name=="windows"): self.dotminetest_path = "C:\\games\\Minetest" self.worlds_path = os.path.join(self.dotminetest_path,"worlds") self.world_path = os.path.join(self.worlds_path, self.world_name) auto_chosen_world = False @@ -211,22 +367,23 @@ class MTChunks: break self.python_exe_path = "python" - self.is_save_output_ok = True # this is probably required to avoid minutely writes - + self.is_save_output_ok = True # Keeping output after analyzing it is no longer necessary since results are saved to YAML, but keeping output provides debug info since is the output of minetestmapper-numpy.py + try: alt_path = "C:\\python27\python.exe" if os.path.isfile(alt_path): self.python_exe_path = alt_path + #else may be in path--assume installer worked except: pass # do nothing, probably linux self.mtmn_path = os.path.join( self.profile_path, "minetest/util/minetestmapper-numpy.py" ) self.colors_path = os.path.join( self.profile_path, "minetest/util/colors.txt" ) - if os_name=="windows": - self.mtmn_path = os.path.join(os.path.dirname(__file__), "minetestmapper-numpy.py") - self.colors_path = os.path.join(os.path.dirname(__file__), "colors.txt") - self.website_root = os.path.dirname(__file__) + if self.os_name=="windows": + self.mtmn_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "minetestmapper-numpy.py") + self.colors_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "colors.txt") + self.website_root = os.path.dirname(os.path.abspath(__file__)) self.chunkx_min = 0 self.chunkz_min = 0 @@ -238,76 +395,6 @@ class MTChunks: self.minheight = -32 self.pixelspernode = 1 - def set_from_genresult(self, mtchunk, chunk_luid): - dest_genresult_path = self.get_chunk_genresult_path(chunk_luid) - result = False - if os.path.isfile(dest_genresult_path): - #may have data such as: - #Result image (w=16 h=16) will be written to chunk_x0z0.png - #Unknown node names: meze:meze default:stone_with_iron air default:dirt_with_snow default:stone_with_copper default:snow - #Unknown node ids: 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 - #Drawing image - #Saving to: chunk_x0z0.png - #('PNG Region: ', [0, 64, 0, 64]) - #('Pixels PerNode: ', 1) - #('border: ', 0) - mtchunk.is_marked = True - ins = open(dest_genresult_path, 'r') - line = True - while line: - line = ins.readline() - if line: - line_strip = line.strip() - if "data does not exist" in line_strip: - mtchunk.is_marked_empty = True - break - elif "Result image" in line_strip: - oparen_index = line_strip.find("(") - if (oparen_index>-1): - cparen_index = line_strip.find(")", oparen_index+1) - if (cparen_index>-1): - operations_string = line_strip[oparen_index+1:cparen_index] - operation_list = operations_string.split(" ") - #if len(operation_list)==2: - for operation_string in operation_list: - if "=" in operation_string: - chunks = operation_string.split("=") - if len(chunks)==2: - if chunks[0].strip()=="w": - try: - mtchunk.image_w=int(chunks[1].strip()) - except: - print("Bad value for image w:"+str(chunks[1])) - elif chunks[0].strip()=="h": - try: - mtchunk.image_h=int(chunks[1].strip()) - except: - print("Bad value for image h:"+str(chunks[1])) - else: - print("Bad name for image variable so ignoring variable named '"+str(chunks[0])+"'") - else: - print("Bad assignment (not 2 sides) so ignoring command '"+operation_string+"'") - else: - print("Bad assignment (operator) so ignoring command '"+operation_string+"'") - #else: - # print("Bad assignment count so ignoring operations string '"+operations_string+"'") - elif "PNG Region" in line_strip: - obracket_index = line_strip.find("[") - if obracket_index>-1: - cbracket_index = line_strip.find("]", obracket_index+1) - if cbracket_index>-1: - rect_values_string = line_strip[obracket_index+1:cbracket_index] - rect_values_list = rect_values_string.split(",") - if len(rect_values_list)==4: - #pngregion=[pngminx, pngmaxx, pngminz, pngmaxz] #from minetestmapper-numpy.py - mtchunk.image_left=int(rect_values_list[0].strip()) - mtchunk.image_right=int(rect_values_list[1].strip()) - mtchunk.image_top=int(rect_values_list[2].strip()) - mtchunk.image_bottom=int(rect_values_list[3].strip()) - else: - print("Bad map rect, so ignoring: "+rect_values_string) - ins.close() - @@ -335,7 +422,13 @@ class MTChunks: return "chunk_"+chunk_luid+".png" def get_chunk_image_tmp_path(self, chunk_luid): - return os.path.join(os.path.dirname(__file__), self.get_chunk_image_name(chunk_luid)) + return os.path.join(os.path.dirname(os.path.abspath(__file__)), self.get_chunk_image_name(chunk_luid)) + + def get_signal_name(self): + return "chunkymap-signals.txt" + + def get_signal_path(self): + return os.path.join(os.path.dirname(os.path.abspath(__file__)), self.get_signal_name()) def get_chunk_image_path(self, chunk_luid): return os.path.join(self.chunkymap_data_path, self.get_chunk_image_name(chunk_luid)) @@ -343,8 +436,11 @@ class MTChunks: def get_chunk_genresult_name(self, chunk_luid): return "chunk_"+chunk_luid+"_mapper_result.txt" + def get_chunk_genresults_tmp_folder(self, chunk_luid): + return os.path.join(os.path.dirname(os.path.abspath(__file__)), "chunkymap-genresults") + def get_chunk_genresult_tmp_path(self, chunk_luid): - return os.path.join(os.path.dirname(__file__), self.get_chunk_genresult_name(chunk_luid)) + return os.path.join(self.get_chunk_genresults_tmp_folder(chunk_luid), self.get_chunk_genresult_name(chunk_luid)) def get_chunk_yaml_name(self, chunk_luid): return "chunk_"+chunk_luid+".yml" @@ -389,43 +485,6 @@ class MTChunks: ins.close() return result - - def get_chunk_genresult_path(self, chunk_luid): - return os.path.join(self.chunkymap_data_path, self.get_chunk_genresult_name(chunk_luid)) - - #deprecated - def is_genresult_marked(self, chunk_luid): - result = False - dest_genresult_path = self.get_chunk_genresult_path(chunk_luid) - if os.path.isfile(dest_genresult_path): - result = True - return result - - #deprecated - def is_genresult_marked_empty(self, chunk_luid): - dest_genresult_path = self.get_chunk_genresult_path(chunk_luid) - result = False - if os.path.isfile(dest_genresult_path): - ins = open(dest_genresult_path, 'r') - line = True - while line: - line = ins.readline() - if line: - line_strip = line.strip() - if "data does not exist" in line_strip: - result = True - break - ins.close() - return result - - def remove_genresult(self, chunk_luid): - result = False - dest_genresult_path = self.get_chunk_genresult_path(chunk_luid) - if os.path.isfile(dest_genresult_path): - result = True - os.remove(dest_genresult_path) - return result - def remove_chunk_image(self, chunk_luid): result = False tmp_png_path = self.get_chunk_image_path(chunk_luid) @@ -436,49 +495,54 @@ class MTChunks: def remove_chunk(self, chunk_luid): result = False - out_path = self.get_chunk_genresult_path(chunk_luid) + out_path = self.get_chunk_genresult_tmp_path(chunk_luid) tmp_png_path = self.get_chunk_image_path(chunk_luid) - if os.path.isfile(out_path): - os.remove(out_path) - result = True + yml_path = self.get_chunk_yaml_path(chunk_luid) if os.path.isfile(tmp_png_path): os.remove(tmp_png_path) result = True + if os.path.isfile(yml_path): + os.remove(yml_path) + result = True + if os.path.isfile(out_path): + os.remove(out_path) + result = True return result def is_chunk_rendered_on_dest(self, chunk_luid): #formerly is_chunk_empty_on_dest (reversed) is_rendered = False - #is_chunk_out_empty = self.is_chunk_yaml_marked_empty(chunk_luid) - #dest_genresult_path = self.get_chunk_genresult_path(chunk_luid) dest_png_path = self.get_chunk_image_path(chunk_luid) if os.path.isfile(dest_png_path): - #os.remove(dest_genresult_path) is_rendered = True return is_rendered def prepare_chunk_meta(self, chunk_luid): if chunk_luid not in self.chunks.keys(): self.chunks[chunk_luid] = MTChunk() + yaml_path = self.get_chunk_yaml_path(chunk_luid) + if os.path.isfile(yaml_path): + self.chunks[chunk_luid].load_yaml(yaml_path) def render_chunk(self, x, z): result = False chunk_luid = self.get_chunk_luid(x,z) png_name = self.get_chunk_image_name(chunk_luid) tmp_png_path = self.get_chunk_image_tmp_path(chunk_luid) - cmd_suffix = "" genresult_name = self.get_chunk_genresult_name(chunk_luid) + genresult_tmp_folder_path = self.get_chunk_genresults_tmp_folder(chunk_luid) + if not os.path.isdir(genresult_tmp_folder_path): + os.makedirs(genresult_tmp_folder_path) genresult_path = self.get_chunk_genresult_tmp_path(chunk_luid) - if self.is_save_output_ok: - cmd_suffix = " > \""+genresult_path+"\"" x_min = x * self.chunk_size x_max = x * self.chunk_size + self.chunk_size - 1 z_min = z * self.chunk_size z_max = z * self.chunk_size + self.chunk_size - 1 #print ("generating x = " + str(x_min) + " to " + str(x_max) + " , z = " + str(z_min) + " to " + str(z_max)) + cmd_suffix = "" + cmd_suffix = " > \""+genresult_path+"\"" cmd_string = self.python_exe_path + " \""+self.mtmn_path + "\" --region " + str(x_min) + " " + str(x_max) + " " + str(z_min) + " " + str(z_max) + " --maxheight "+str(self.maxheight)+" --minheight "+str(self.minheight)+" --pixelspernode "+str(self.pixelspernode)+" \""+self.world_path+"\" \""+tmp_png_path+"\"" + cmd_suffix dest_png_path = self.get_chunk_image_path(chunk_luid) - dest_genresult_path = self.get_chunk_genresult_path(chunk_luid) #is_empty_chunk = is_chunk_yaml_marked(chunk_luid) and is_chunk_yaml_marked_empty(chunk_luid) print (cmd_string) subprocess.call(cmd_string, shell=True) # TODO: remember not to allow arbitrary command execution, which could happen if input contains ';' when using shell=True @@ -492,27 +556,23 @@ class MTChunks: try: os.rename(tmp_png_path, dest_png_path) print("(moved to '"+dest_png_path+"')") - self.prepare_chunk_meta(chunk_luid) + self.prepare_chunk_meta(chunk_luid) # DOES load existing yml if exists self.chunks[chunk_luid].is_fresh = True except: print ("Could not finish moving '"+tmp_png_path+"' to '"+dest_png_path+"'") try: - if (os.path.isfile(dest_genresult_path)): - os.remove(dest_genresult_path) - if self.is_save_output_ok: - - #os.rename(genresult_path, dest_genresult_path) - #print("(moved to '"+dest_genresult_path+"')") - - tmp_chunk = MTChunk() - self.set_from_genresult(tmp_chunk,chunk_luid) - chunk_yaml_path = self.get_chunk_yaml_path(chunk_luid) - tmp_chunk.save_yaml(chunk_yaml_path) - print("(saved yaml to '"+chunk_yaml_path+"')") - os.remove(genresult_path) - else: + self.prepare_chunk_meta(chunk_luid) # DOES load existing yml if exists + this_chunk = self.chunks[chunk_luid] + #this_chunk = MTChunk() + this_chunk.set_from_genresult(genresult_path) + chunk_yaml_path = self.get_chunk_yaml_path(chunk_luid) + this_chunk.save_yaml(chunk_yaml_path) + this_chunk.save_yaml(chunk_yaml_path) + print("(saved yaml to '"+chunk_yaml_path+"')") + if not self.is_save_output_ok: if os.path.isfile(genresult_path): os.remove(genresult_path) + except: print ("Could not finish deleting/moving output") @@ -523,6 +583,7 @@ class MTChunks: def check_players(self): # NOT NEEDED: if os.path.isfile(self.mtmn_path) and os.path.isfile(self.colors_path): + print("PROCESSING PLAYERS") self.chunkymap_data_path=os.path.join(self.website_root,"chunkymapdata") chunkymap_players_name = "players" chunkymap_players_path = os.path.join(self.chunkymap_data_path, chunkymap_players_name) @@ -535,6 +596,9 @@ class MTChunks: players_path = os.path.join(self.world_path, "players") player_count = 0 player_written_count = 0 + players_moved_count = 0 + players_didntmove_count = 0 + players_saved_count = 0 for dirname, dirnames, filenames in os.walk(players_path): for filename in filenames: file_fullname = os.path.join(players_path,filename) @@ -582,7 +646,7 @@ class MTChunks: chunk_y = int((float(player_y)/self.chunk_size)) chunk_z = int((float(player_z)/self.chunk_size)) chunk_luid = self.get_chunk_luid(chunk_x, chunk_z) - self.prepare_chunk_meta(chunk_luid) + self.prepare_chunk_meta(chunk_luid) # DOES load existing yml if exists self.chunks[chunk_luid].is_player_in_this_chunk = True #if is_enough_data: @@ -600,16 +664,19 @@ class MTChunks: 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_z)!=int(player_z)): + 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)): # 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_z is not None: + 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: #print("PLAYER MOVED: "+str(player_name)+" moved from "+str(map_player_position_tuple)+" to "+str(player_position_tuple)) - 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)) + if self.is_verbose: + 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)) + players_moved_count += 1 else: - print("SAVING YAML for player '"+str(player_name)+"'") + if self.is_verbose: + 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" @@ -625,9 +692,14 @@ class MTChunks: outs.close() player_written_count += 1 else: - print("DIDN'T MOVE: "+str(player_name)) + if self.is_verbose: + print("DIDN'T MOVE: "+str(player_name)) + players_didntmove_count += 1 player_count += 1 - + if not self.is_verbose: + print("PLAYERS:") + print(" saved: "+str(player_written_count)+" (moved:"+str(players_moved_count)+"; new:"+str(players_saved_count)+")") + print(" didn't move: "+str(player_name)) def is_player_at_luid(self, chunk_luid): result = False @@ -643,7 +715,7 @@ class MTChunks: def check_map(self): if os.path.isfile(self.mtmn_path) and os.path.isfile(self.colors_path): - + rendered_count = 0 self.chunkymap_data_path=os.path.join(self.website_root,"chunkymapdata") yaml_name = "generated.yml" world_yaml_path = os.path.join(self.chunkymap_data_path, yaml_name) @@ -657,12 +729,13 @@ class MTChunks: self.deny_http_access(self.chunkymap_data_path) mapvars = get_dict_from_conf_file(world_yaml_path,":") - #is_testonly == (os_name=="windows") + #is_testonly == (self.os_name=="windows") if mapvars is not None and set(['world_name']).issubset(mapvars): - #print (" (FOUND self.world_name)") + #if self.is_verbose: + # print (" (FOUND self.world_name)") if mapvars["world_name"] != self.world_name: - print ("REMOVING data since from different world (map '"+str(mapvars["world_name"])+"' is not '"+str(self.world_name)+"')...") + print ("Removing ALL map data since from WORLD NAME is different (map '"+str(mapvars["world_name"])+"' is not '"+str(self.world_name)+"')...") for dirname, dirnames, filenames in os.walk(self.chunkymap_data_path): #index = 0 #for j in range(0,len(filenames)): @@ -673,7 +746,8 @@ class MTChunks: for filename in filenames: if filename[0] != ".": file_fullname = os.path.join(self.chunkymap_data_path,filename) - print (" EXAMINING "+filename) + if self.is_verbose: + print (" EXAMINING "+filename) badstart_string = "chunk" if (len(filename) >= len(badstart_string)) and (filename[:len(badstart_string)]==badstart_string): os.remove(file_fullname) @@ -683,12 +757,14 @@ class MTChunks: #for j in range(0,len(filenames)): # i = len(filenames) - 0 - 1 # if filenames[i][0] == ".": + # if self.is_verbose: # print (" SKIPPING "+filenames[i]) # filenames.remove_at(i) for filename in filenames: if filename[0] != ".": file_fullname = os.path.join(self.chunkymap_data_path,filename) - print (" EXAMINING "+filename) + if self.is_verbose: + print (" EXAMINING "+filename) badend_string = ".yml" if (len(filename) >= len(badend_string)) and (filename[len(filename)-len(badend_string):]==badend_string): os.remove(file_fullname) @@ -705,7 +781,7 @@ class MTChunks: #if str(self.world_name) != str(mapvars["world_name"]): # is_different_world = True # print("FULL RENDER since chosen world name '"+self.world_name+"' does not match previously rendered world name '"+mapvars["world_name"]+"'") - + print("PROCESSING MAP DATA") while outline_generates_count > 0: outline_generates_count = 0 for z in range (self.chunkz_min,self.chunkz_max+1): @@ -730,33 +806,49 @@ class MTChunks: if self.is_chunk_yaml_marked(chunk_luid): if self.is_chunk_yaml_marked_empty(chunk_luid): is_render_needed = True - print (chunk_luid+": RENDERING nonfresh previously marked empty (player in it)") + if self.is_verbose: + print (chunk_luid+": RENDERING nonfresh previously marked empty (player in it)") + else: + sys.stdout.write('.') else: - print (chunk_luid+": SKIPPING nonfresh previously marked (player in it)") + if self.is_verbose: + print (chunk_luid+": SKIPPING nonfresh previously marked (player in it)") + #else: + #sys.stdout.write('.') else: is_render_needed = True - print (chunk_luid+": RENDERING nonfresh unmarked (player in it)") + if self.is_verbose: + print (chunk_luid+": RENDERING nonfresh unmarked (player in it)") + else: + sys.stdout.write('.') else: if (not self.is_chunk_yaml_marked(chunk_luid)): is_render_needed = True - print (chunk_luid+": RENDERING nonfresh unmarked (simple check since has no player)") + if self.is_verbose: + print (chunk_luid+": RENDERING nonfresh unmarked (simple check since has no player)") + else: + sys.stdout.write('.') else: - print (chunk_luid+": SKIPPING nonfresh previously marked (simple check since has no player)") + if self.is_verbose: + print (chunk_luid+": SKIPPING nonfresh previously marked (simple check since has no player)") else: - print (chunk_luid+": SKIPPING fresh chunk") + if self.is_verbose: + print (chunk_luid+": SKIPPING fresh chunk") #if (not self.is_chunk_yaml_marked(chunk_luid)): #is_render_needed = True # This should never happen since keeping the output of minetestmapper-numpy.py (after analyzing that output) is deprecated: #if self.is_genresult_marked(chunk_luid) and not self.is_chunk_yaml_present(chunk_luid): # tmp_chunk = MTChunk() - # self.set_from_genresult(tmp_chunk,chunk_luid) + # genresult_path = self.get_chunk_genresult_tmp_path(chunk_luid) + # tmp_chunk.set_from_genresult(genresult_path) # chunk_yaml_path = self.get_chunk_yaml_path(chunk_luid) # tmp_chunk.save_yaml(chunk_yaml_path) # print("(saved yaml to '"+chunk_yaml_path+"')") if is_render_needed: + rendered_count += 1 if (self.render_chunk(x,z)): total_generated_count += 1 outline_generates_count += 1 @@ -765,12 +857,14 @@ class MTChunks: total_generated_count += 1 outline_generates_count += 1 tmp_png_path = self.get_chunk_image_path(chunk_luid) - print(chunk_luid+": Skipping existing map tile file " + tmp_png_path + " (delete it to re-render)") + if self.is_verbose: + print(chunk_luid+": Skipping existing map tile file " + tmp_png_path + " (delete it to re-render)") #elif is_empty_chunk: #print("Skipping empty chunk " + chunk_luid) #else: #print(chunk_luid+": Not rendered on dest.") - print ("") # blank line before next z so output is more readable + if self.is_verbose: + print ("") # blank line before next z so output is more readable self.chunkx_min -= 1 self.chunkz_min -= 1 self.chunkx_max += 1 @@ -820,9 +914,96 @@ class MTChunks: outs.write("total_generated_count:"+str(total_generated_count) + "\n") outs.close() else: - print ("(Not saving '"+world_yaml_path+"' since same value of each current variable is already in file as loaded)") + if self.is_verbose: + print (" (Not saving '"+world_yaml_path+"' since same value of each current variable is already in file as loaded)") + if not self.is_verbose: + print(" rendered: "+str(rendered_count)+" (only checks for new chunks)") else: - print ("failed since this folder must contain colors.txt and minetestmapper-numpy.py") + print ("MAP ERROR: failed since this folder must contain colors.txt and minetestmapper-numpy.py") + + def read_then_remove_signals(self): + signal_path = self.get_signal_path() + if os.path.isfile(signal_path): + signals = get_dict_from_conf_file(signal_path) + if signals is not None: + for this_key in signals.keys: + if this_key=="loop_enable": + if not signals[this_key]: + self.loop_enable = False + else: + print("WARNING: Got signal to change loop_enable to True, so doing nothing") + elif this_key=="refresh_players_enable": + if type(signals[this_key]) is bool: + self.refresh_players_enable = signals[this_key] + else: + print("ERROR: expected bool for "+this_key) + elif this_key=="refresh_map_seconds": + if (type(signals[this_key]) is float) or (type(signals[this_key]) is int): + if float(signals[this_key])>=1.0: + self.refresh_map_seconds = float(signals[this_key]) + else: + print("ERROR: expected >=1 seconds for refresh_map_seconds (int or float)") + else: + print("ERROR: expected int for "+this_key) + elif this_key=="refresh_players_seconds": + if (type(signals[this_key]) is float) or (type(signals[this_key]) is int): + if float(signals[this_key])>=1.0: + self.refresh_players_seconds = float(signals[this_key]) + else: + print("ERROR: expected >=1 seconds for refresh_players_seconds (int or float)") + else: + print("ERROR: expected int for "+this_key) + elif this_key=="refresh_map_enable": + if type(signals[this_key]) is bool: + self.refresh_map_enable = signals[this_key] + else: + print("ERROR: expected bool for "+this_key) + + else: + print("ERROR: unknown signal '"+this_key+"'") + + + else: + print("WARNING: blank '"+signal_path+"'") + try: + os.remove() + except: + print("FATAL ERROR: "+__file__+" must have permission to remove '"+signal_path+"' so exiting to avoid inability to avoid repeating commands at next launch.") + self.loop_enable = False + + def run_loop(self): + #self.last_run_second = best_timer() + self.loop_enable = True + self.is_verbose = False + while self.loop_enable: + before_second = best_timer() + run_wait_seconds = self.refresh_map_seconds + if self.refresh_players_seconds < run_wait_seconds: + run_wait_seconds = self.refresh_players_seconds + print("") + self.read_then_remove_signals() + if self.loop_enable: + if self.refresh_players_enable: + if self.last_players_refresh_second is None or (best_timer()-self.last_players_refresh_second > self.refresh_players_seconds ): + last_players_refresh_second = best_timer() + self.check_players() + else: + print("waiting before doing player update") + else: + print("player update is not enabled") + if self.refresh_map_enable: + if self.last_map_refresh_second is None or (best_timer()-self.last_map_refresh_second > self.refresh_map_seconds): + last_map_refresh_second = best_timer() + self.check_map() + else: + print("waiting before doing map update") + else: + print("map update is not enabled") + else: + self.is_verbose = True + run_wait_seconds -= (best_timer()-before_second) + if (int(float(run_wait_seconds)+.5)>0.0): + time.sleep(run_wait_seconds) def run(self): if self.refresh_players_enable: @@ -831,17 +1012,17 @@ class MTChunks: self.check_map() if __name__ == '__main__': + mtchunks = MTChunks() + signal_path = mtchunks.get_signal_path() + stop_line = "loop_enable:False" parser = argparse.ArgumentParser(description='A mapper for minetest') parser.add_argument('--skip-map', type = bool, metavar = ('skip_map'), default = False, help = 'draw map tiles and save YAML files for chunkymap.php to use') - #parser.add_argument('--skip-map',action='store_const', const = True, default = False, help = 'Do not draw map tiles (which would save PNG and YAML files for chunkymap.php to use)') parser.add_argument('--skip-players', type = bool, metavar = ('skip_players'), default = False, help = 'update player YAML files for chunkymap.php to use') - #parser.add_argument('--skip-players',action='store_const', const = True, default = False, help = 'Do not update players (which would save YAML files for chunkymap.php to use)') - #parser = argparse.ArgumentParser(description='Process some integers.') + parser.add_argument('--loop', type = bool, metavar = ('loop'), default = False, help = 'keep running until "'+signal_path+'" contains the line '+stop_line) args = parser.parse_args() - mtchunks = MTChunks() + if not args.skip_players: if not args.skip_map: - mtchunks.refresh_players_enable = False print("Drawing players and map") else: mtchunks.refresh_map_enable = False @@ -851,10 +1032,12 @@ if __name__ == '__main__': mtchunks.refresh_players_enable = False print("Drawing map only") else: + mtchunks.refresh_players_enable = False mtchunks.refresh_map_enable = False print("Nothing to do since "+str(args)) - - #input("press enter to exit...") - mtchunks.run() - - + if mtchunks.refresh_players_enable or mtchunks.refresh_map_enable: + if args.loop: + print("To stop chunkymap-regen loop, save a line '"+stop_line+"' to '"+signal_path+"'") + mtchunks.run_loop() + else: + mtchunks.run() diff --git a/chunkymap-signals example.txt b/chunkymap-signals example.txt new file mode 100644 index 0000000..686982c --- /dev/null +++ b/chunkymap-signals example.txt @@ -0,0 +1,11 @@ +#to stop program: +loop_enable:False + +#to stop checking for player movements (players will timeout and the php will stop showing the players due to old file modified times) +refresh_players_enable:False +#to stop checking for map updates (NOTE: the map update function is only able to detect new chunks, and only checks edge chunks if player is present in one) +refresh_map_enable:False + +#other options: +refresh_players_seconds:60 +refresh_map_seconds:300 \ No newline at end of file diff --git a/web/chunkymap.php b/web/chunkymap.php index d6388d8..2266de5 100644 --- a/web/chunkymap.php +++ b/web/chunkymap.php @@ -552,7 +552,7 @@ function echo_chunkymap_table() { $text_style="color:white;"; if ($is_expired==false) { if ($is_idle==true) { - $img_border_style="border: 1px solid gray;"; + $img_border_style="border: 1px solid rgba(128,128,128,.5);"; $img_style.="opacity: 0.4; filter: alpha(opacity=40);"; //filter is for IE8 and below $text_style="color:white; opacity: 0.4; filter: alpha(opacity=40);"; //filter is for IE8 and below } diff --git a/web/example.php b/web/example.php index a0989be..3ee784f 100644 --- a/web/example.php +++ b/web/example.php @@ -1,5 +1,8 @@ + Chunkymap Example Page + +