#!/usr/bin/env python2 import os import subprocess import traceback import argparse import time import sys import timeit from timeit import default_timer as best_timer #file modified time etc: import time #copyfile etc: import shutil import math from PIL import Image, ImageDraw, ImageFont, ImageColor #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 www_minetest_path/chunkymapdata such as /var/www/html/minetest/chunkymapdata (or chunkymap in current directory on Windows) #minetestmapper-numpy.py calculates the region as follows: #(XMIN','XMAX','ZMIN','ZMAX'), default = (-2000,2000,-2000,2000) #sector_xmin,sector_xmax,sector_zmin,sector_zmax = numpy.array(args.region)/16 #sector_ymin = args.minheight/16 #sector_ymax = args.maxheight/16 #region server-specific options #as per http://interactivepython.org/runestone/static/pythonds/BasicDS/ImplementingaQueueinPython.html #class SimpleQueue: #def __init__(self): #self.items = [] #def isEmpty(self): #return self.items == [] #def enqueue(self, item): #self.items.insert(0,item) #def dequeue(self): #return self.items.pop() #def size(self): #return len(self.items) class InstalledFile: source_dir_path = None dest_dir_path = None file_name = None def __init__(self, file_name, source_dir_path, dest_dir_path): self.file_name=file_name self.source_dir_path=source_dir_path self.dest_dir_path=dest_dir_path def ivec2_equals(pos1, pos2): return (int(pos1[0])==int(pos2[0])) and (int(pos1[1])==int(pos2[1])) 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 view_traceback(): ex_type, ex, tb = sys.exc_info() traceback.print_tb(tb) del tb def print_file(path, min_indent=""): line_count = 0 try: if path is not None: if os.path.isfile(path): if min_indent is None: min_indent = "" ins = open(path, 'r') line = True while line: line = ins.readline() line_count += 1 if line: print(min_indent+line) ins.close() #if line_count==0: #print(min_indent+"print_file WARNING: "+str(line_count)+" line(s) in '"+path+"'") #else: #print(min_indent+"# "+str(line_count)+" line(s) in '"+path+"'") else: print (min_indent+"print_file: file does not exist") else: print (min_indent+"print_file: path is None") except: print(min_indent+"print_file: could not finish") try: ins.close() except: pass return line_count def get_dict_modified_by_conf_file(this_dict, path,assignment_operator="="): results = this_dict #print ("Checking "+str(path)+" for settings...") if (results is None) or (type(results) is not dict): results = {} if os.path.isfile(path): ins = open(path, 'r') line = True while line: line = ins.readline() if line and len(line)>0: line_strip=line.strip() if len(line_strip)>0 and not line_strip[0]=="#": # if not comment if not line_strip[0]=="-": # ignore yaml arrays ao_index = line_strip.find(assignment_operator) if ao_index>=1: # intentionally skip zero-length variable names if ao_index=3 and len(list_b)>=3: result = (float(list_a[0]) == float(list_b[0])) and (float(list_a[1]) == float(list_b[1])) and (float(list_a[2]) == float(list_b[2])) return False class MTDecaChunk: metadata = None last_changed_utc_second = None def __init__(self): self.metadata = {} self.metadata["last_saved_utc_second"] = None self.metadata["luid_list"] = None # what chunks this decachunk contains (as saved to 160px image) def load_yaml(self, yml_path): self.metadata = get_dict_modified_by_conf_file(self.metadata,yml_path,":") def save_yaml(self, yml_path): save_conf_from_dict(yml_path, self.metadata, assignment_operator=":", save_nulls_enable=False) class MTChunk: #x = None #z = None metadata = None is_fresh = None #luid = None def __init__(self): # NOTE: variables that need to be saved (and only they) should be stored in dict self.metadata = {} self.is_fresh = False self.metadata["is_empty"] = False # formerly is_marked_empty self.metadata["is_marked"] = False self.metadata["width"] = None self.metadata["height"] = None self.metadata["image_w"] = None self.metadata["image_h"] = None self.metadata["image_left"] = None self.metadata["image_top"] = None self.metadata["image_right"] = None self.metadata["image_bottom"] = None self.metadata["is_traversed"] = False self.metadata["tags"] = None def load_yaml(self, yml_path): self.metadata = get_dict_modified_by_conf_file(self.metadata,yml_path,":") def save_yaml(self, yml_path): save_conf_from_dict(yml_path, self.metadata, assignment_operator=":", save_nulls_enable=False) #requires output such as from minetestmapper-numpy.py def set_from_genresult(self, this_genresult_path): #this_genresult_path = mtchunks.get_chunk_genresult_path(chunk_luid) result = False if os.path.isfile(this_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) self.metadata["is_marked"] = True ins = open(this_genresult_path, 'r') line = True while line: line = ins.readline() if line: line_strip = line.strip() try: if ("does not exist" in line_strip): # official minetestmapper.py says "World does not exist" but expertmm fork and minetestmapper-numpy.py say "data does not exist" self.metadata["is_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: self.metadata["image_w"]=int(chunks[1].strip()) except: print("Bad value for image w:"+str(chunks[1])) elif chunks[0].strip()=="h": try: self.metadata["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.metadata["image_left"]=int(rect_values_list[0].strip()) self.metadata["image_right"]=int(rect_values_list[1].strip()) self.metadata["image_top"]=int(rect_values_list[2].strip()) self.metadata["image_bottom"]=int(rect_values_list[3].strip()) else: print("Bad map rect, so ignoring: "+rect_values_string) elif (len(line_strip)>5) and (line_strip[:5]=="xmin:"): self.metadata["image_left"] = int(line_strip[5:].strip()) elif (len(line_strip)>5) and (line_strip[:5]=="xmax:"): self.metadata["image_right"] = int(line_strip[5:].strip()) elif (len(line_strip)>5) and (line_strip[:5]=="zmin:"): #(zmin is bottom since cartesian) self.metadata["image_bottom"] = int(line_strip[5:].strip()) elif (len(line_strip)>5) and (line_strip[:5]=="zmax:"): #(zmax is top since cartesian) self.metadata["image_top"] = int(line_strip[5:].strip()) except: print("#failed to parse line:"+str(line_strip)) ins.close() class MTChunks: chunkymap_data_path = None chunkymapdata_worlds_path = None is_save_output_ok = None minetestmapper_fast_sqlite_path = None minetestmapper_custom_path = None minetestmapper_py_path = None colors_path = None python_exe_path = None chunks = None decachunks = None total_newly_rendered = None #region values for subprocess arguments: 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 #endregion values for subprocess arguments: loop_enable = None verbose_enable = None run_count = None todo_positions = None # list of tuples (locations) to render next (for fake recursion) todo_index = None yaml_name = None world_yaml_path = None preload_all_enable = None chunk_yaml_name_opener_string = None chunk_yaml_name_dotext_string = None mapvars = None saved_mapvars = None rendered_count = None backend_string = None #region_separators = None is_backend_detected = None chunkymap_players_name = None chunkymap_players_path = None config = None config_name = None config_path = None data_16px_path = None data_160px_path = None FLAG_EMPTY_HEXCOLOR = "#010000" world_name = None chunkymap_thisworld_data_path = None def __init__(self): #formerly checkpaths() in global scope self.decachunks = {} self.total_newly_rendered = 0 os_name="linux" if (os.path.sep!="/"): os_name="windows" print("Windows detected") self.is_backend_detected = False self.mapvars = {} self.config = {} self.config_name = "chunkymap.yml" self.config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), self.config_name) self.config = get_dict_modified_by_conf_file(self.config, self.config_path, ":") is_config_changed = False if not os.path.isfile(self.config_path): is_config_changed = True print("Creating '"+self.config_path+"'") #if self.config is None: self.mapvars["total_generated_count"] = 0 self.rendered_count = 0 self.preload_all_enable = True self.todo_index = -1 self.todo_positions = list() self.run_count = 0 self.verbose_enable = True self.loop_enable = True self.refresh_map_enable = True self.refresh_players_enable = True self.chunks = {} if "www_minetest_path" not in self.config.keys(): self.config["www_minetest_path"] = "/var/www/html/minetest" if os_name=="windows": self.config["www_minetest_path"] = None prioritized_try_paths = list() prioritized_try_paths.append("C:\\wamp\\www") prioritized_try_paths.append("C:\\www") prioritized_try_paths.append("C:\\Program Files\\Apache Software Foundation\\Apache2.2\\htdocs") #prioritized_try_paths.append("C:\\Program Files\\Apache Software Foundation\\Apache2.2\\htdocs\\folder_test\\website") for try_path in prioritized_try_paths: try: if os.path.isdir(try_path): self.config["www_minetest_path"] = try_path break except: pass if self.config["www_minetest_path"] is None: self.config["www_minetest_path"] = os.path.dirname(os.path.abspath(__file__)) input_string = raw_input("Minetest website (blank for ["+self.config["www_minetest_path"]+"]): ") if (len(input_string)>0): self.config["www_minetest_path"] = input_string is_config_changed = True #print("Set www_minetest_path to '"+self.config["www_minetest_path"]+"'") #else: print("Using www_minetest_path '"+self.config["www_minetest_path"]+"'") print("") self.refresh_map_seconds = 30 #does one chunk at a time so as not to interrupt player updates too often self.refresh_players_seconds = 5 self.chunk_yaml_name_opener_string = "chunk_" self.chunk_yaml_name_dotext_string = ".yml" #self.region_separators = [" "," "," "] input_string = "" profile_path = None if os_name=="windows": profile_path = os.environ['USERPROFILE'] else: profile_path = os.environ['HOME'] if "profile_minetest_path" not in self.config.keys(): self.config["profile_minetest_path"] = os.path.join(profile_path,".minetest") if (os_name=="windows"): self.config["profile_minetest_path"] = "C:\\games\\Minetest" input_string = raw_input("user minetest path containing worlds folder (blank for ["+self.config["profile_minetest_path"]+"]): ") if (len(input_string)>0): self.config["profile_minetest_path"] = input_string is_config_changed = True print("Using profile_minetest_path '"+self.config["profile_minetest_path"]+"'") if not os.path.isdir(self.config["profile_minetest_path"]): print("(WARNING: missing, so please close and update profile_minetest_path in '"+self.config_path+"' before next run)") print("") if "worlds_path" not in self.config.keys(): self.config["worlds_path"] = os.path.join(self.config["profile_minetest_path"],"worlds") is_config_changed = True auto_chosen_world = False is_missing_world = False if "world_path" in self.config.keys(): if not os.path.isdir(self.config["world_path"]): is_missing_world = True if ("world_path" not in self.config.keys()) or is_missing_world: print ("LOOKING FOR WORLDS IN " + self.config["worlds_path"]) for base_path, dirnames, filenames in os.walk(self.config["worlds_path"]): #for j in range(0,len(dirnames)): # i = len(dirnames) - 0 - 1 # if dirnames[i][0] == ".": # print (" SKIPPING "+dirnames[i]) # dirnames.remove_at(i) world_count = 0 for subdirname in dirnames: print (" EXAMINING "+subdirname) if subdirname[0]!=".": world_count += 1 index = 0 world_number = 0 for subdirname in dirnames: print (" EXAMINING "+subdirname) if subdirname[0]!=".": #if (index == len(dirnames)-1): # skip first one because the one on my computer is big if (subdirname!="world") or (world_number==(world_count-1)): self.config["world_path"] = os.path.join(base_path, subdirname) # os.path.join(self.config["worlds_path"], "try7amber") auto_chosen_world = True break world_number += 1 index += 1 if auto_chosen_world: is_config_changed = True break if is_missing_world: print("MISSING WORLD '"+self.config["world_path"]+"'") if auto_chosen_world: print("(so a default was picked below that you can change)") else: print("(and no world could be found in worlds_path '"+self.config["worlds_path"]+"')") input_string = raw_input("World path (or world name if above; blank for ["+self.config["world_path"]+"]): ") if (len(input_string)>0): try_path = os.path.join(self.config["worlds_path"], input_string) this_world_path = input_string if (not os.path.isdir(this_world_path)) and os.path.isdir(try_path): this_world_path = try_path self.config["world_path"] = this_world_path auto_chosen_world = False is_config_changed = True print ("Using world_path '"+self.config["world_path"]+"'") if not os.path.isdir(self.config["world_path"]): print("(ERROR: missing, so please close immediately and update world_path in '"+self.config_path+"' before next run)") print("") self.python_exe_path = "python" if os_name=="windows": 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 worldmt_path = os.path.join(self.config["world_path"], "world.mt") self.backend_string="sqlite3" if (os.path.isfile(worldmt_path)): ins = open(worldmt_path, 'r') line = True while line: line = ins.readline() if line: line_strip = line.strip() if len(line_strip)>0 and line_strip[0]!="#": if line_strip[:7]=="backend": ao_index = line_strip.find("=") if ao_index>-1: self.backend_string = line_strip[ao_index+1:].strip() self.is_backend_detected = True break ins.close() else: print("ERROR: failed to read '"+worldmt_path+"'") self.is_save_output_ok = False # 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 if self.is_backend_detected: print("Detected backend '"+self.backend_string+"' from '"+worldmt_path+"'") else: print("WARNING: Database backend cannot be detected (unable to ensure image generator script will render map)") self.minetestmapper_fast_sqlite_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "minetestmapper-numpy.py") self.minetestmapper_custom_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "minetestmapper-expertmm.py") self.minetestmapper_py_path = self.minetestmapper_fast_sqlite_path if (self.backend_string!="sqlite3"): self.minetestmapper_py_path = self.minetestmapper_custom_path print("Chose image generator script: "+self.minetestmapper_py_path) if not os.path.isfile(self.minetestmapper_py_path): print("ERROR: script does not exist, so exiting "+__file__+".") sys.exit() self.colors_path = os.path.join(os.path.dirname(os.path.abspath(self.minetestmapper_py_path)), "colors.txt") if not os.path.isfile(self.colors_path): print("ERROR: missing '"+self.colors_path+"', so exiting "+__file__+".") sys.exit() self.chunkymap_data_path=os.path.join(self.config["www_minetest_path"],"chunkymapdata") self.chunkymapdata_worlds_path=os.path.join(self.chunkymap_data_path, "worlds") print("Using chunkymap_data_path '"+self.chunkymap_data_path+"'") #if not os.path.isdir(self.chunkymap_data_path): # os.mkdir(self.chunkymap_data_path) htaccess_path = os.path.join(self.chunkymap_data_path,".htaccess") if not os.path.isdir(self.chunkymap_data_path): os.makedirs(self.chunkymap_data_path) print("Created '"+self.chunkymap_data_path+"'") if not os.path.isfile(htaccess_path): self.deny_http_access(self.chunkymap_data_path) print(" (created .htaccess)") htaccess_path = os.path.join(self.chunkymapdata_worlds_path,".htaccess") if not os.path.isdir(self.chunkymapdata_worlds_path): os.makedirs(self.chunkymapdata_worlds_path) print("Created '"+self.chunkymapdata_worlds_path+"'") if not os.path.isfile(htaccess_path): self.deny_http_access(self.chunkymapdata_worlds_path) print(" (created .htaccess)") self.world_name = os.path.basename(self.config["world_path"]) self.chunkymap_thisworld_data_path = os.path.join(self.chunkymapdata_worlds_path, self.world_name) if not os.path.isdir(self.chunkymap_thisworld_data_path): os.makedirs(self.chunkymap_thisworld_data_path) print("Created '"+self.chunkymap_thisworld_data_path+"'") if not os.path.isfile(htaccess_path): self.deny_http_access(self.chunkymap_thisworld_data_path) print(" (created .htaccess)") self.data_16px_path = os.path.join(self.chunkymap_thisworld_data_path, "16px") if not os.path.isdir(self.data_16px_path): os.makedirs(self.data_16px_path) print("Created '"+self.data_16px_path+"'") if not os.path.isfile(htaccess_path): self.deny_http_access(self.data_16px_path) print(" (created .htaccess)") self.data_160px_path = os.path.join(self.chunkymap_thisworld_data_path, "160px") if not os.path.isdir(self.data_160px_path): os.makedirs(self.data_160px_path) print("Created '"+self.data_160px_path+"'") if not os.path.isfile(htaccess_path): self.deny_http_access(self.data_160px_path) print(" (created .htaccess)") #TODO: deny recursively under these folders? doesn't seem that important for security so maybe not (no player info is there) self.install_default_world_data() self.chunkymap_players_name = "players" self.chunkymap_players_path = os.path.join(self.chunkymap_thisworld_data_path, self.chunkymap_players_name) htaccess_path = os.path.join(self.chunkymap_players_path,".htaccess") if not os.path.isdir(self.chunkymap_players_path): os.makedirs(self.chunkymap_players_path) if not os.path.isfile(htaccess_path): self.deny_http_access(self.chunkymap_players_path) self.yaml_name = "generated.yml" self.world_yaml_path = os.path.join(self.chunkymap_thisworld_data_path, self.yaml_name) self.mapvars["chunkx_min"] = 0 self.mapvars["chunkz_min"] = 0 self.mapvars["chunkx_max"] = 0 self.mapvars["chunkz_max"] = 0 self.mapvars["chunk_size"] = 16 self.mapvars["maxheight"] = 96 self.mapvars["minheight"] = -32 self.mapvars["pixelspernode"] = 1 self.saved_mapvars = get_dict_from_conf_file(self.world_yaml_path,":") is_mapvars_changed = False if self.saved_mapvars is None: is_mapvars_changed = True #self.save_mapvars_if_changed() #self.mapvars = get_dict_from_conf_file(self.world_yaml_path,":") #NOTE: do not save or load self.mapvars yet, because if world name is different than saved, chunks must all be redone if self.saved_mapvars is not None: if "chunkx_min" in self.saved_mapvars.keys(): self.mapvars["chunkx_min"] = self.saved_mapvars["chunkx_min"] if "chunkx_max" in self.saved_mapvars.keys(): self.mapvars["chunkx_max"] = self.saved_mapvars["chunkx_max"] if "chunkz_min" in self.saved_mapvars.keys(): self.mapvars["chunkz_min"] = self.saved_mapvars["chunkz_min"] if "chunkz_max" in self.saved_mapvars.keys(): self.mapvars["chunkz_max"] = self.saved_mapvars["chunkz_max"] if self.mapvars is not None: if "chunkx_min" in self.mapvars.keys(): try: self.mapvars["chunkx_min"] = int(self.mapvars["chunkx_min"]) except: print("WARNING: chunkx_min was not int so set to 0") self.mapvars["chunkx_min"] = 0 if "chunkx_max" in self.mapvars.keys(): try: self.mapvars["chunkx_max"] = int(self.mapvars["chunkx_max"]) except: print("WARNING: chunkx_max was not int so set to 0") self.mapvars["chunkx_max"] = 0 if "chunkz_min" in self.mapvars.keys(): try: self.mapvars["chunkz_min"] = int(self.mapvars["chunkz_min"]) except: print("WARNING: chunkz_min was not int so set to 0") self.mapvars["chunkz_min"] = 0 if "chunkz_max" in self.mapvars.keys(): try: self.mapvars["chunkz_max"] = int(self.mapvars["chunkz_max"]) except: print("WARNING: chunkz_max was not int so set to 0") self.mapvars["chunkz_max"] = 0 if is_mapvars_changed: self.save_mapvars_if_changed() if is_config_changed: self.save_config() #def install_default_world_data(self): #source_web_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "web") #dest_web_chunkymapdata_world_path = self.chunkymap_thisworld_data_path #dest_web_chunkymapdata_world_players_path = os.path.join(self.chunkymap_thisworld_data_path, "players") #install_list.append(InstalledFile("singleplayer.png", source_chunkymapdata_players, dest_chunkymapdata_players)) #formerly install_website def install_default_world_data(self): source_web_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "web") source_web_chunkymapdata_path = os.path.join(source_web_path, "chunkymapdata_default") source_web_chunkymapdata_world_path = os.path.join(source_web_chunkymapdata_path, "world") source_web_chunkymapdata_images_path = os.path.join(source_web_chunkymapdata_path, "images") dest_web_path = self.config["www_minetest_path"] dest_web_chunkymapdata_path = os.path.join(self.config["www_minetest_path"],"chunkymapdata") dest_web_chunkymapdata_images_path = os.path.join(dest_web_chunkymapdata_path,"images") install_list = list() install_list.append(InstalledFile("browser.php",source_web_path,dest_web_path)) install_list.append(InstalledFile("chunkymap.php",source_web_path,dest_web_path)) install_list.append(InstalledFile("example.php",source_web_path,dest_web_path)) install_list.append(InstalledFile("zoom-in.png", source_web_chunkymapdata_images_path, dest_web_chunkymapdata_images_path)) install_list.append(InstalledFile("zoom-out.png", source_web_chunkymapdata_images_path, dest_web_chunkymapdata_images_path)) install_list.append(InstalledFile("zoom-in_disabled.png", source_web_chunkymapdata_images_path, dest_web_chunkymapdata_images_path)) install_list.append(InstalledFile("zoom-out_disabled.png", source_web_chunkymapdata_images_path, dest_web_chunkymapdata_images_path)) install_list.append(InstalledFile("start.png", source_web_chunkymapdata_images_path, dest_web_chunkymapdata_images_path)) install_list.append(InstalledFile("target_start.png", source_web_chunkymapdata_images_path, dest_web_chunkymapdata_images_path)) install_list.append(InstalledFile("compass-rose.png", source_web_chunkymapdata_images_path, dest_web_chunkymapdata_images_path)) source_chunkymapdata_players = os.path.join(source_web_chunkymapdata_world_path, "players") dest_chunkymapdata_players = os.path.join(self.chunkymap_thisworld_data_path, "players") install_list.append(InstalledFile("singleplayer.png", source_chunkymapdata_players, dest_chunkymapdata_players)) source_chunkymapdata_markers = os.path.join(source_web_chunkymapdata_world_path, "markers") dest_chunkymapdata_markers = os.path.join(self.chunkymap_thisworld_data_path, "markers") install_list.append(InstalledFile("0.yml", source_chunkymapdata_markers, dest_chunkymapdata_markers)) for this_object in install_list: source_path = os.path.join(this_object.source_dir_path, this_object.file_name) installed_path = os.path.join(this_object.dest_dir_path, this_object.file_name) if os.path.isfile(source_path): if not os.path.isdir(this_object.dest_dir_path): os.makedirs(this_object.dest_dir_path) if not os.path.isfile(installed_path): shutil.copyfile(source_path, installed_path) # DOES replace destination file else: source_mtime_seconds = time.ctime(os.path.getmtime(source_path)) installed_mtime_seconds = time.ctime(os.path.getmtime(installed_path)) if source_mtime_seconds>installed_mtime_seconds: shutil.copyfile(source_path, installed_path) # DOES replace destination file else: print("WARNING: cannot update file since can't find '"+source_path+"'") def deny_http_access(self, dir_path): htaccess_name = ".htaccess" htaccess_path = os.path.join(dir_path, htaccess_name) outs = open(htaccess_path, 'w') outs.write("IndexIgnore *"+"\n") outs.write(""+"\n") outs.write("order allow,deny"+"\n") outs.write("deny from all"+"\n") outs.write(""+"\n") outs.write(""+"\n") outs.write("order allow,deny"+"\n") outs.write("deny from all"+"\n") outs.write(""+"\n") outs.close() def save_config(self): save_conf_from_dict(self.config_path, self.config, ":") #locally unique identifier (unique to world only) def get_chunk_luid(self, chunky_x, chunky_z): return "x"+str(chunky_x)+"z"+str(chunky_z) def get_decachunk_image_name_from_chunk(self, chunky_x, chunky_z): return "decachunk_"+self.get_decachunk_luid_from_chunk(chunky_x, chunky_z)+".jpg" def get_decachunk_image_name_from_decachunk(self, decachunky_x, decachunky_z): return "decachunk_"+self.get_decachunk_luid_from_decachunk(decachunky_x, decachunky_z)+".jpg" def get_decachunk_luid_from_chunk(self, chunky_x, chunky_z): decachunky_x = int(math.floor(chunky_x/10)) decachunky_z = int(math.floor(chunky_z/10)) return self.get_chunk_luid(decachunky_x, decachunky_z) def get_decachunk_luid_from_decachunk(self, decachunky_x, decachunky_z): return self.get_chunk_luid(decachunky_x, decachunky_z) def get_decachunk_yaml_name_from_chunk(self, chunky_x, chunky_z): return "decachunk_"+self.get_decachunk_luid_from_chunk(chunky_x, chunky_z)+".yml" def get_decachunk_yaml_name_from_decachunk(self, decachunky_x, decachunky_z): return "decachunk_"+self.get_decachunk_luid_from_decachunk(decachunky_x, decachunky_z)+".yml" def get_chunk_image_name(self, chunky_x, chunky_z): return "chunk_"+self.get_chunk_luid(chunky_x, chunky_z)+".png" #def get_decachunk_image_tmp_path_from_decachunk(self, chunky_x, chunky_z): #return os.path.join(os.path.dirname(os.path.abspath(__file__)), self.get_decachunk_image_name_from_decachunk(chunky_x, chunky_z)) def get_chunk_image_tmp_path(self, chunky_x, chunky_z): return os.path.join(os.path.dirname(os.path.abspath(__file__)), self.get_chunk_image_name(chunky_x, chunky_z)) 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 check_decachunk_containing_chunk(self, chunky_x, chunky_z): chunk16_coord_list = list() decachunky_x = int(math.floor(chunky_x/10)) decachunky_z = int(math.floor(chunky_z/10)) chunk16x_min = decachunky_x*10 chunk16x_max = chunk16x_min + 15 # NOTE: + 15 even if negative since originally, floor was used chunk16z_min = decachunky_z*10 chunk16z_max = chunk16z_min + 15 # NOTE: + 15 even if negative since originally, floor was used is_any_part_queued = False chunky_z = chunk16x_min while chunky_z <= chunk16z_max: chunky_x = chunk16x_min while chunky_x <= chunk16x_max: coords = (chunky_x, chunky_z) chunk16_coord_list.append( coords ) if self.todo_index0: self.decachunks[decachunk_luid].metadata["contains_chunk_luids"] = ','.join(contains_chunk_luids) else: self.decachunks[decachunk_luid].metadata["contains_chunk_luids"] = None self.decachunks[decachunk_luid].save_yaml(decachunk_yaml_path) def get_chunk_folder_path(self, chunky_x, chunky_z): result = None decachunky_x = int(math.floor(chunky_x/10)) decachunky_z = int(math.floor(chunky_z/10)) result = os.path.join( os.path.join(self.data_16px_path, str(decachunky_x)), str(decachunky_z) ) return result def get_decachunk_folder_path_from_chunk(self, chunky_x, chunky_z): result = None if chunky_x is not None and chunky_z is not None: hectochunky_x = int(math.floor(chunky_x/100)) hectochunky_x = int(math.floor(chunky_z/100)) result = os.path.join( os.path.join(self.data_160px_path, str(hectochunky_x)), str(hectochunky_x) ) return result def get_decachunk_folder_path_from_decachunk(self, decachunky_x, decachunky_z): result = None if decachunky_x is not None and decachunky_z is not None: hectochunky_x = int(math.floor(decachunky_x/10)) hectochunky_x = int(math.floor(decachunky_z/10)) result = os.path.join( os.path.join(self.data_160px_path, str(hectochunky_x)), str(hectochunky_x) ) return result def create_chunk_folder(self, chunky_x, chunky_z): path = self.get_chunk_folder_path(chunky_x, chunky_z) if not os.path.isdir(path): os.makedirs(path) def get_decachunk_image_path_from_chunk(self, chunky_x, chunky_z): return os.path.join(self.get_decachunk_folder_path_from_chunk(chunky_x, chunky_z), self.get_decachunk_image_name_from_chunk(chunky_x, chunky_z)) def get_decachunk_yaml_path_from_chunk(self, chunky_x, chunky_z): return os.path.join(self.get_decachunk_folder_path_from_chunk(chunky_x, chunky_z), self.get_decachunk_yaml_name_from_chunk(chunky_x, chunky_z)) def get_decachunk_image_path_from_decachunk(self, decachunky_x, decachunky_z): return os.path.join(self.get_decachunk_folder_path_from_decachunk(decachunky_x, decachunky_z), self.get_decachunk_image_name_from_decachunk(decachunky_x, decachunky_z)) def get_decachunk_yaml_path_from_decachunk(self, decachunky_x, decachunky_z): return os.path.join(self.get_decachunk_folder_path_from_decachunk(decachunky_x, decachunky_z), self.get_decachunk_yaml_name_from_decachunk(decachunky_x, decachunky_z)) def get_chunk_image_path(self, chunky_x, chunky_z): return os.path.join(self.get_chunk_folder_path(chunky_x, chunky_z), self.get_chunk_image_name(chunky_x, chunky_z)) 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(self.get_chunk_genresults_tmp_folder(chunk_luid), self.get_chunk_genresult_name(chunk_luid)) def get_chunk_luid_from_yaml_name(self, yml_name): return yml_name[len(self.chunk_yaml_name_opener_string):-1*len(self.chunk_yaml_name_dotext_string)] def get_chunk_yaml_name(self, chunky_x, chunky_z): chunk_luid = self.get_chunk_luid(chunky_x, chunky_z) return self.chunk_yaml_name_opener_string+chunk_luid+self.chunk_yaml_name_dotext_string def is_chunk_yaml_present(self, chunky_x, chunky_z): return os.path.isfile(self.get_chunk_yaml_path(chunky_x, chunky_z)) def get_chunk_yaml_path(self, chunky_x, chunky_z): return os.path.join(self.get_chunk_folder_path(chunky_x, chunky_z), self.get_chunk_yaml_name(chunky_x, chunky_z)) def is_chunk_yaml_marked(self, chunky_x, chunky_z): yaml_path = self.get_chunk_yaml_path(chunky_x, chunky_z) result = False if os.path.isfile(yaml_path): result = True #ins = open(yaml_path, 'r') #line = True #while line: # line = ins.readline() # if line: # line_strip = line.strip() # if "is_empty:" in line_strip: # result = True # break #ins.close() return result def is_chunk_yaml_marked_empty(self, chunky_x, chunky_z): result = False yaml_path = self.get_chunk_yaml_path(chunky_x, chunky_z) if os.path.isfile(yaml_path): self.prepare_chunk_meta(chunky_x, chunky_z) # DOES get existing data if any file exists chunk_luid = self.get_chunk_luid(chunky_x, chunky_z) if "is_empty" in self.chunks[chunk_luid].metadata.keys(): result = self.chunks[chunk_luid].metadata["is_empty"] return result def remove_chunk_image(self, chunky_x, chunky_z): result = False tmp_png_path = self.get_chunk_image_path(chunky_x, chunky_z) if os.path.isfile(tmp_png_path): result = True os.remove(tmp_png_path) return result def remove_chunk(self, chunky_x, chunky_z): result = False chunk_luid = get_chunk_luid(chunky_x, chunky_z) out_path = self.get_chunk_genresult_tmp_path(chunk_luid) tmp_png_path = self.get_chunk_image_path(chunky_x, chunky_z) yml_path = self.get_chunk_yaml_path(chunky_x, chunky_z) 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, chunky_x, chunky_z): #formerly is_chunk_empty_on_dest (reversed) is_rendered = False dest_png_path = self.get_chunk_image_path(chunky_x, chunky_z) if os.path.isfile(dest_png_path): is_rendered = True return is_rendered def prepare_decachunk_meta_from_chunk(self, chunky_x, chunky_z): chunk_luid = self.get_decachunk_luid_from_chunk(chunky_x, chunky_z) if chunk_luid not in self.decachunks.keys(): self.decachunks[chunk_luid] = MTDecaChunk() #self.chunks[chunk_luid].luid = chunk_luid yaml_path = self.get_decachunk_yaml_path_from_chunk(chunky_x, chunky_z) if os.path.isfile(yaml_path): self.decachunks[chunk_luid].load_yaml(yaml_path) def prepare_decachunk_meta_from_decachunk(self, decachunky_x, decachunky_z): chunk_luid = self.get_decachunk_luid_from_decachunk(decachunky_x, decachunky_z) if chunk_luid not in self.decachunks.keys(): self.decachunks[chunk_luid] = MTDecaChunk() #self.chunks[chunk_luid].luid = chunk_luid yaml_path = self.get_decachunk_yaml_path_from_decachunk(decachunky_x, decachunky_z) if os.path.isfile(yaml_path): self.decachunks[chunk_luid].load_yaml(yaml_path) def prepare_chunk_meta(self, chunky_x, chunky_z): chunk_luid = self.get_chunk_luid(chunky_x, chunky_z) if chunk_luid not in self.chunks.keys(): self.chunks[chunk_luid] = MTChunk() #self.chunks[chunk_luid].luid = chunk_luid yaml_path = self.get_chunk_yaml_path(chunky_x, chunky_z) if os.path.isfile(yaml_path): self.chunks[chunk_luid].load_yaml(yaml_path) # normally call check_chunk instead, which renders chunk only if necessary def _render_chunk(self, chunky_x, chunky_z): min_indent = " " # increased below result = False chunk_luid = self.get_chunk_luid(chunky_x, chunky_z) png_name = self.get_chunk_image_name(chunky_x, chunky_z) tmp_png_path = self.get_chunk_image_tmp_path(chunky_x, chunky_z) 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) x_min = chunky_x * self.mapvars["chunk_size"] x_max = chunky_x * self.mapvars["chunk_size"] + self.mapvars["chunk_size"] - 1 z_min = chunky_z * self.mapvars["chunk_size"] z_max = chunky_z * self.mapvars["chunk_size"] + self.mapvars["chunk_size"] - 1 #print (min_indent+"generating chunky_x = " + str(x_min) + " to " + str(x_max) + " , chunky_z = " + str(z_min) + " to " + str(z_max)) geometry_value_string = str(x_min)+":"+str(z_min)+"+"+str(int(x_max)-int(x_min)+1)+"+"+str(int(z_max)-int(z_min)+1) # +1 since max-min is exclusive and width must be inclusive for minetestmapper.py cmd_suffix = "" cmd_suffix = " > \""+genresult_path+"\"" #self.mapper_id = "minetestmapper-region" cmd_no_out_string = self.python_exe_path + " \""+self.minetestmapper_py_path + "\" --region " + str(x_min) + " " + str(x_max) + " " + str(z_min) + " " + str(z_max) + " --maxheight "+str(self.mapvars["maxheight"])+" --minheight "+str(self.mapvars["minheight"])+" --pixelspernode "+str(self.mapvars["pixelspernode"])+" \""+self.config["world_path"]+"\" \""+tmp_png_path+"\"" cmd_string = cmd_no_out_string + cmd_suffix if self.minetestmapper_py_path==self.minetestmapper_custom_path:#if self.backend_string!="sqlite3": #if self.mapper_id=="minetestmapper-region": # Since minetestmapper-numpy has trouble with leveldb: # such as sudo minetest-mapper --input "/home/owner/.minetest/worlds/FCAGameAWorld" --geometry -32:-32+64+64 --output /var/www/html/minetest/try1.png # where geometry option is like --geometry x:y+w+h # mapper_id = "minetest-mapper" # NOTE: minetest-mapper is part of the minetest-data package, which can be installed alongside the git version of minetestserver # BUT *buntu Trusty version of it does NOT have geometry option # cmd_string = "/usr/games/minetest-mapper --input \""+self.config["world_path"]+"\" --draworigin --geometry "+geometry_value_string+" --output \""+tmp_png_path+"\""+cmd_suffix # such as sudo python minetestmapper --input "/home/owner/.minetest/worlds/FCAGameAWorld" --geometry -32:-32+64+64 --output /var/www/html/minetest/try1.png # OR try PYTHON version (looks for expertmm fork which has geometry option like C++ version does): #script_path = "/home/owner/minetest/util/minetestmapper.py" #region_capable_script_path = "/home/owner/minetest/util/chunkymap/minetestmapper.py" # region_capable_script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "minetestmapper.py") # if os.path.isfile(region_capable_script_path): # script_path=region_capable_script_path #if os.path.isfile(region_capable_script_path): #script_path = region_capable_script_path geometry_string = str(x_min)+":"+str(z_min)+"+"+str(int(x_max)-int(x_min)+1)+"+"+str(int(z_max)-int(z_min)+1) # +1 since max-min is exclusive and width must be inclusive for minetestmapper.py #expertmm_region_string = str(x_min) + ":" + str(x_max) + "," + str(z_min) + ":" + str(z_max) #cmd_string="sudo python "+script_path+" --input \""+self.config["world_path"]+"\" --geometry "+geometry_value_string+" --output \""+tmp_png_path+"\""+cmd_suffix cmd_no_out_string = self.python_exe_path+" "+self.minetestmapper_py_path+" --bgcolor '"+self.FLAG_EMPTY_HEXCOLOR+"' --input \""+self.config["world_path"]+"\" --geometry "+geometry_string+" --output \""+tmp_png_path+"\"" cmd_string = cmd_no_out_string + cmd_suffix #sudo python /home/owner/minetest/util/minetestmapper.py --bgcolor '#010000' --input "/home/owner/.minetest/worlds/FCAGameAWorld" --output /var/www/html/minetest/chunkymapdata/entire.png > entire-mtmresult.txt #sudo python /home/owner/minetest/util/chunkymap/minetestmapper.py --input "/home/owner/.minetest/worlds/FCAGameAWorld" --geometry 0:0+16+16 --output /var/www/html/minetest/chunkymapdata/chunk_x0z0.png > /home/owner/minetest/util/chunkymap-genresults/chunk_x0z0_mapper_result.txt # sudo mv entire-mtmresult.txt /home/owner/minetest/util/chunkymap-genresults/ dest_png_path = self.get_chunk_image_path(chunky_x, chunky_z) #is_empty_chunk = is_chunk_yaml_marked(chunky_x, chunky_z) and is_chunk_yaml_marked_empty(chunky_x, chunky_z) #if self.verbose_enable: # #print(min_indent+"") # print(min_indent+"Running '"+cmd_string+"'...") #else: print (min_indent+"Calling map tile renderer for: "+str((chunky_x, chunky_z))) min_indent += " " try: if os.path.isfile(tmp_png_path): os.remove(tmp_png_path) subprocess.call(cmd_string, shell=True) # TODO: remember not to allow arbitrary command execution, which could happen if input contains ';' when using shell=True is_empty_before = True is_marked_before = False if chunk_luid in self.chunks.keys(): is_marked_before = True if (self.chunks[chunk_luid].metadata is not None) and ("is_empty" in self.chunks[chunk_luid].metadata): is_empty_before = self.chunks[chunk_luid].metadata["is_empty"] self.prepare_chunk_meta(chunky_x, chunky_z) # DOES load existing yml if exists this_chunk = self.chunks[chunk_luid] if os.path.isfile(tmp_png_path): result = True this_chunk.metadata["is_empty"] = False try: if (os.path.isfile(dest_png_path)): os.remove(dest_png_path) except: print (min_indent+"Could not finish deleting '"+dest_png_path+"'") try: self.create_chunk_folder(chunky_x, chunky_z) os.rename(tmp_png_path, dest_png_path) print(min_indent+"(moved to '"+dest_png_path+"')") self.total_newly_rendered += 1 self.prepare_chunk_meta(chunky_x, chunky_z) # DOES load existing yml if exists self.chunks[chunk_luid].is_fresh = True self.chunks[chunk_luid].metadata["is_empty"] = False except: print (min_indent+"Could not finish moving '"+tmp_png_path+"' to '"+dest_png_path+"'") else: if self.is_chunk_traversed_by_player(chunk_luid): print (min_indent+"WARNING: no chunk data though traversed by player:") print(min_indent+"standard output stream:") line_count = print_file(genresult_path, min_indent+" ") if line_count>0: print(min_indent+" #EOF: "+str(line_count)+" line(s) in '"+genresult_path+"'") pass else: print(min_indent+" #EOF: "+str(line_count)+" line(s) in '"+genresult_path+"'") subprocess.call(cmd_no_out_string+" 2> \""+genresult_path+"\"", shell=True) print(min_indent+"standard error stream:") line_count = print_file(genresult_path, min_indent+" ") if (line_count<1): print(min_indent+" #EOF: "+str(line_count)+" line(s) in '"+genresult_path+"'") print(min_indent+" (done output of '"+cmd_no_out_string+"')") try: if os.path.exists(tmp_png_path): os.rename(tmp_png_path, dest_png_path) except: pass try: this_chunk.set_from_genresult(genresult_path) if is_marked_before: if (not is_empty_before) and this_chunk.metadata["is_empty"]: print("ERROR: chunk changed from nonempty to empty (may happen if output of mapper was not recognized)") elif this_chunk.metadata["is_empty"] and os.path.isfile(dest_png_path): print("ERROR: chunk marked empty though has data (may happen if output of mapper was not recognized)") chunk_yaml_path = self.get_chunk_yaml_path(chunky_x, chunky_z) self.create_chunk_folder(chunky_x, chunky_z) this_chunk.save_yaml(chunk_yaml_path) print(min_indent+"(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 (min_indent+"Could not finish deleting/moving output") view_traceback() except: print(min_indent+"Could not finish deleting/moving temp files") view_traceback() return result def check_players(self): print("PROCESSING PLAYERS") players_path = os.path.join(self.config["world_path"], "players") player_count = 0 player_written_count = 0 players_moved_count = 0 players_didntmove_count = 0 players_saved_count = 0 for base_path, dirnames, filenames in os.walk(players_path): for filename in filenames: file_fullname = os.path.join(players_path,filename) #print (" EXAMINING "+filename) badstart_string = "." player_name = None player_position = None if (filename[:len(badstart_string)]!=badstart_string): ins = open(file_fullname, 'r') line = True is_enough_data = False while line: line = ins.readline() if line: ao_index = line.find("=") if ao_index > 0: found_name = line[:ao_index].strip() found_value = line[ao_index+1:].strip() if found_name=="name": player_name = found_value elif found_name=="position": player_position = found_value if (player_name is not None) and (player_position is not None): is_enough_data = True break ins.close() player_dest_path = os.path.join(self.chunkymap_players_path,filename+".yml") player_x = None player_y = None player_z = None chunk_x = None chunk_y = None chunk_z = None player_position_tuple = get_tuple_from_notation(player_position, filename) if player_position_tuple is not None: #Divide by 10 because I don't know why (minetest issue, maybe to avoid float rounding errors upon save/load) player_position_tuple = player_position_tuple[0]/10.0, player_position_tuple[1]/10.0, player_position_tuple[2]/10.0 player_x, player_y, player_z = player_position_tuple player_x = float(player_x) player_y = float(player_y) player_z = float(player_z) chunky_x = int((int(player_x)/self.mapvars["chunk_size"])) chunky_y = int((int(player_y)/self.mapvars["chunk_size"])) chunky_z = int((int(player_z)/self.mapvars["chunk_size"])) chunk_luid = self.get_chunk_luid(chunky_x, chunky_z) self.prepare_chunk_meta(chunky_x, chunky_z) # DOES load existing yml if exists if not self.chunks[chunk_luid].metadata["is_traversed"]: self.chunks[chunk_luid].metadata["is_traversed"] = True chunk_yaml_path = self.get_chunk_yaml_path(chunky_x, chunky_z) self.create_chunk_folder(chunky_x, chunky_z) self.chunks[chunk_luid].save_yaml(chunk_yaml_path) #if is_enough_data: #if player_name!="singleplayer": map_player_dict = 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)): # 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: #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)) players_moved_count += 1 else: 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() player_written_count += 1 else: #if self.verbose_enable: #print("DIDN'T MOVE: "+str(player_name)) players_didntmove_count += 1 player_count += 1 #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)) def is_chunk_traversed_by_player(self, chunk_luid): result = False if chunk_luid in self.chunks.keys(): result = self.chunks[chunk_luid].metadata["is_traversed"] return result def is_chunk_fresh(self, chunk_luid): result = False if chunk_luid in self.chunks.keys(): result = self.chunks[chunk_luid].is_fresh return result #Returns: (boolean) whether the chunk image is present on dest (rendered now or earlier)--only possible if there is chunk data at the given location def check_chunk(self, chunky_x, chunky_z): min_indent = " " result = [False,""] chunk_luid = self.get_chunk_luid(chunky_x, chunky_z) #if (is_different_world): #instead, see above where all chunk files and player files are deleted # self.remove_chunk(chunky_x, chunky_z) is_traversed_by_player = self.is_chunk_traversed_by_player(chunk_luid) #ok if stale, since is only used for whether empty chunk should be regenerated is_render_needed = False if not self.is_chunk_fresh(chunk_luid): if is_traversed_by_player: if self.is_chunk_yaml_marked(chunky_x, chunky_z): if self.is_chunk_yaml_marked_empty(chunky_x, chunky_z): is_render_needed = True result[1] = "RENDERING since nonfresh empty traversed" if self.verbose_enable: print (min_indent+chunk_luid+": "+result[1]) #else: #sys.stdout.write('.') else: if self.is_chunk_rendered_on_dest(chunky_x, chunky_z): result[1] = "SKIPPING since RENDERED nonfresh nonempty traversed" if self.verbose_enable: print (min_indent+chunk_luid+": "+result[1]) else: is_render_needed = True result[1] = "RENDERING since NONRENDERED nonfresh nonempty traversed" if self.verbose_enable: theoretical_path = self.get_chunk_image_path(chunky_x, chunky_z) print (min_indent+chunk_luid+": "+result[1]) print (min_indent+" {dest_png_path:"+theoretical_path+"}") #end if marked else: is_render_needed = True result[1] = "RENDERING since nonfresh unmarked traversed" if self.verbose_enable: print (min_indent+chunk_luid+": "+result[1]) #else: #sys.stdout.write('.') #end if traversed else: if (self.is_chunk_yaml_marked(chunky_x, chunky_z)): if (self.is_chunk_yaml_marked_empty(chunky_x, chunky_z)): result[1] = "SKIPPING since nonfresh empty nontraversed" if self.verbose_enable: print (min_indent+chunk_luid+": "+result[1]) else: if (self.is_chunk_rendered_on_dest(chunky_x, chunky_z)): result[1] = "SKIPPING since RENDERED nonfresh nonempty nontraversed (delete png to re-render)" if self.verbose_enable: print (min_indent+chunk_luid+":"+result[1]) else: is_render_needed = True theoretical_path = self.get_chunk_image_path(chunky_x, chunky_z) result[1] = "RENDERING since NONRENDRERED nonfresh nonempty nontraversed" if self.verbose_enable: print (min_indent+chunk_luid+": "+result[1]) print (min_indent+" {dest_png_path:"+theoretical_path+"}") else: is_render_needed = True result[1] = "RENDERING since nonfresh unmarked nontraversed" if self.verbose_enable: print (min_indent+chunk_luid+": "+result[1]) #else: #sys.stdout.write('.') else: result[1] = "SKIPPING since RENDERED fresh" if self.verbose_enable: print (min_indent+chunk_luid+": "+result[1]+" (rendered after starting "+__file__+")") #if (not self.is_chunk_yaml_marked(chunky_x, chunky_z)): #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(chunky_x, chunky_z): # tmp_chunk = MTChunk() # tmp_chunk.luid = 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(chunky_x, chunky_z) # self.create_chunk_folder(chunky_x, chunky_z) # tmp_chunk.save_yaml(chunk_yaml_path) # print(min_indent+"(saved yaml to '"+chunk_yaml_path+"')") if is_render_needed: self.rendered_count += 1 if not self.verbose_enable: print(min_indent+chunk_luid+": "+result[1]) if (self._render_chunk(chunky_x, chunky_z)): result[0] = True else: if self.is_chunk_rendered_on_dest(chunky_x, chunky_z): result[0] = True tmp_png_path = self.get_chunk_image_path(chunky_x, chunky_z) #NOTE: do NOT set result[1] since specific reason was already set above if self.verbose_enable: print(min_indent+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(min_indent+chunk_luid+": Not rendered on dest.") return result def _check_map_pseudorecursion_branchfrom(self, chunky_x, chunky_z): chunk_luid = self.get_chunk_luid(chunky_x, chunky_z) branched_pos = chunky_x-1, chunky_z #only add if not in list already, to prevent infinite re-branching if branched_pos not in self.todo_positions: self.todo_positions.append(branched_pos) branched_pos = chunky_x+1, chunky_z if branched_pos not in self.todo_positions: self.todo_positions.append(branched_pos) branched_pos = chunky_x, chunky_z-1 if branched_pos not in self.todo_positions: self.todo_positions.append(branched_pos) branched_pos = chunky_x, chunky_z+1 if branched_pos not in self.todo_positions: self.todo_positions.append(branched_pos) def check_map_pseudorecursion_iterate(self): # , redo_empty_enable=False): min_indent = "" if self.todo_index<0: self.check_map_pseudorecursion_start() if self.verbose_enable: print(min_indent+"(initialized "+str(len(self.todo_positions))+" branche(s))") if self.todo_index>=0: if self.todo_indexself.mapvars["chunkx_max"]: self.mapvars["chunkx_max"]=chunky_x if chunky_zself.mapvars["chunkz_max"]: self.mapvars["chunkz_max"]=chunky_z #end while square outline (1-chunk-thick outline) generated any png files self.save_mapvars_if_changed() prev_len = len(self.todo_positions) self._check_map_pseudorecursion_branchfrom(chunky_x, chunky_z) #must check_decachunk_containing_chunk AFTER _check_map_pseudorecursion_branchfrom so check_decachunk_containing_chunk can see if there are more to do before rendering superchunk if self.total_newly_rendered>prev_total_newly_rendered: self.check_decachunk_containing_chunk(chunky_x, chunky_z) if self.verbose_enable: print(min_indent+"["+str(self.todo_index)+"] branching from "+str((chunky_x, chunky_z))+" (added "+str(len(self.todo_positions)-prev_len)+")") else: if self.verbose_enable: print(min_indent+"["+str(self.todo_index)+"] not branching from "+str((chunky_x, chunky_z))) self.todo_index += 1 if self.todo_index>=len(self.todo_positions): # check again since may have branched above, making this untrue self.save_mapvars_if_changed() self.todo_index = -1 else: if self.verbose_enable: print(min_indent+"(no branches)") def get_coords_from_luid(self,chunk_luid): result = None if chunk_luid is not None: xopener_index = chunk_luid.find("x") zopener_index = chunk_luid.find("z") if xopener_index>=0 and zopener_index>xopener_index: x_string = chunk_luid[xopener_index+1:zopener_index] z_string = chunk_luid[zopener_index+1:] try: chunky_x = int(x_string) try: chunky_z = int(z_string) result = chunky_x, chunky_z except: pass except: pass return result def check_map_pseudorecursion_start(self): worldgen_mod_list = list() worldgen_mod_list.append("technic_worldgen") worldgen_mod_list.append("mg") worldgen_mod_list.append("moreores") worldgen_mod_list.append("lapis") worldgen_mod_list.append("sea") worldgen_mod_list.append("moretrees") worldgen_mod_list.append("caverealms") worldgen_mod_list.append("nature_classic") # NOTE: plantlife_modpack has this and other stuff, but just mention this one in tags since it is unique to the modpack if self.todo_index<0: print("PROCESSING MAP DATA (BRANCH PATTERN)") if os.path.isfile(self.minetestmapper_py_path) and os.path.isfile(self.colors_path): self.rendered_count = 0 self.todo_positions = list() self.todo_positions.append((0,0)) #self.mapvars = get_dict_from_conf_file(self.world_yaml_path,":") self.verify_correct_map() decachunk_luid_list = list() if self.preload_all_enable: self.preload_all_enable = False minlen=len(self.chunk_yaml_name_opener_string)+4+len(self.chunk_yaml_name_dotext_string) # +4 for luid, such as x1z2 (ok since just a minimum) #for base_path, dirnames, filenames in os.walk(self.data_16px_path): #for dirname in dirnames: #for decachunk_x_basepath, decachunk_x_dirnames, decachunk_x_filenames in os.walk(self.data_16px_path): for decachunk_x_name in os.listdir(self.data_16px_path): decachunk_x_path = os.path.join(self.data_16px_path, decachunk_x_name) #for decachunk_z_basepath, decachunk_z_dirnames, decachunk_z_filenames in os.walk(decachunk_x_dirnames): if decachunk_x_path[:1]!="." and os.path.isdir(decachunk_x_path): for decachunk_z_name in os.listdir(decachunk_x_path): decachunk_z_path = os.path.join(decachunk_x_path, decachunk_z_name) if decachunk_z_path[:1]!="." and os.path.isdir(decachunk_z_path): #for chunk_filename in decachunk_z_filenames: for chunk_filename in os.listdir(decachunk_z_path): chunk_path = os.path.join(decachunk_z_path, chunk_filename) #file_fullname = os.path.join(self.chunkymap_thisworld_data_path,filename) if chunk_filename[:1]!="." and os.path.isfile(chunk_path): #print (" EXAMINING "+filename) #badstart_string = "." #if (filename[:len(badstart_string)]!=badstart_string): if len(chunk_filename) > minlen: chunk_luid = self.get_chunk_luid_from_yaml_name(chunk_filename) coords = self.get_coords_from_luid(chunk_luid) if coords is not None: chunky_x, chunky_z = coords decachunk_luid = self.get_decachunk_luid_from_chunk(chunky_x, chunky_z) if decachunk_luid not in decachunk_luid_list: decachunk_luid_list.append(decachunk_luid) if "chunk_size" not in self.mapvars: print("ERROR: '"+chunk_luid+"' has missing mapvars among {"+str(self.mapvars)+"}") break print("Checking chunk "+str(coords)+" *"+str(self.mapvars["chunk_size"])+"") self.prepare_chunk_meta(chunky_x, chunky_z) #if ("tags" not in self.chunks[chunk_luid].metadata): self.chunks[chunk_luid].metadata["tags"] = "moreores,caverealms" self.chunks[chunk_luid].save_yaml(chunk_path) print(" saved tags to '"+chunk_path+"'") for decachunk_luid in decachunk_luid_list: coords = self.get_coords_from_luid(decachunk_luid) if coords is not None: decachunky_x, decachunky_z = coords chunky_x = decachunky_x*10 chunky_z = decachunky_z*10 if not os.path.isfile(self.get_decachunk_image_path_from_chunk(chunky_x, chunky_z)): self.check_decachunk_containing_chunk(chunky_x, chunky_z) else: print("ERROR: could not get coords from decachunk luid "+decachunk_luid) for chunk_luid in self.chunks.keys(): coords = self.get_coords_from_luid(chunk_luid) chunky_x, chunky_z = coords if self.chunks[chunk_luid].metadata["is_traversed"] and not self.is_chunk_rendered_on_dest(chunky_x, chunky_z): if self.chunks[chunk_luid].metadata["is_empty"]: self.chunks[chunk_luid].metadata["is_empty"] = False self.create_chunk_folder(chunky_x, chunky_z) self.chunks[chunk_luid].save_yaml(self.get_chunk_yaml_path(chunky_x, chunky_z)) coords = self.get_coords_from_luid(chunk_luid) if coords is not None: self.todo_positions.append(coords) else: print("ERROR: could not get coords from luid '"+chunk_luid+"'") #ins = open(file_fullname, 'r') #line = True #while line: #line = ins.readline() #if line: #ins.close() self.todo_index = 0 #while (todo_index= len(badstart_string)) and (filename[:len(badstart_string)]==badstart_string): #os.remove(file_fullname) #elif filename==self.yaml_name: #os.remove(file_fullname) #players_path = os.path.join(self.chunkymap_thisworld_data_path, "players") #if os.path.isdir(players_path): #for base_path, dirnames, filenames in os.walk(players_path): #for filename in filenames: #if filename[0] != ".": #file_fullname = os.path.join(self.chunkymap_thisworld_data_path,filename) #if self.verbose_enable: #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) #self.mapvars["chunkx_min"]=0 #self.mapvars["chunkx_max"]=0 #self.mapvars["chunkz_min"]=0 #self.mapvars["chunkz_max"]=0 #self.save_mapvars_if_changed() ##do not neet to run self.save_mapvars_if_changed() since already removed the yml def save_mapvars_if_changed(self): is_changed = False #is_different_world = False if self.saved_mapvars is None: print ("SAVING '" + self.world_yaml_path + "' since nothing was loaded or it did not exist") is_changed = True else: for this_key in self.mapvars.iterkeys(): if this_key != "total_generated_count": # don't care if generated count changed since may have been regenerated if (this_key not in self.saved_mapvars.keys()): is_changed = True print ("SAVING '" + self.world_yaml_path + "' since " + str(this_key) + " not in saved_mapvars") break elif (str(self.saved_mapvars[this_key]) != str(self.mapvars[this_key])): is_changed = True print ("SAVING '" + self.world_yaml_path + "' since new " + this_key + " value " + str(self.mapvars[this_key]) + " not same as saved value " + str(self.saved_mapvars[this_key]) + "") break if is_changed: save_conf_from_dict(self.world_yaml_path,self.mapvars,":") self.saved_mapvars = get_dict_from_conf_file(self.world_yaml_path,":") #self.mapvars = get_dict_from_conf_file(self.world_yaml_path,":") else: if self.verbose_enable: print (" (Not saving '"+self.world_yaml_path+"' since same value of each current variable is already in file as loaded)") def check_map_inefficient_squarepattern(self): if os.path.isfile(self.minetestmapper_py_path) and os.path.isfile(self.colors_path): self.rendered_count = 0 self.mapvars = get_dict_from_conf_file(self.world_yaml_path,":") self.verify_correct_map() self.mapvars["chunkx_min"] = 0 self.mapvars["chunkz_min"] = 0 self.mapvars["chunkx_max"] = 0 self.mapvars["chunkz_max"] = 0 if self.saved_mapvars is not None: if "chunkx_min" in self.saved_mapvars.keys(): self.mapvars["chunkx_min"] = self.saved_mapvars["chunkx_min"] if "chunkx_max" in self.saved_mapvars.keys(): self.mapvars["chunkx_max"] = self.saved_mapvars["chunkx_max"] if "chunkz_min" in self.saved_mapvars.keys(): self.mapvars["chunkz_min"] = self.saved_mapvars["chunkz_min"] if "chunkz_max" in self.saved_mapvars.keys(): self.mapvars["chunkz_max"] = self.saved_mapvars["chunkz_max"] self.mapvars["total_generated_count"] = 0 newchunk_luid_list = list() this_iteration_generates_count = 1 #if str(self.config["world_name"]) != str(self.config["world_name"]): # is_different_world = True # print("FULL RENDER since chosen world name '"+self.config["world_name"]+"' does not match previously rendered world name '"+self.config["world_name"]+"'") print("PROCESSING MAP DATA (SQUARE)") while this_iteration_generates_count > 0: this_iteration_generates_count = 0 self.read_then_remove_signals() if not self.refresh_map_enable: break for chunky_z in range (self.mapvars["chunkz_min"],self.mapvars["chunkz_max"]+1): self.read_then_remove_signals() if not self.refresh_map_enable: break for chunky_x in range(self.mapvars["chunkx_min"],self.mapvars["chunkx_max"]+1): self.read_then_remove_signals() if not self.refresh_map_enable: break #python ~/minetest/util/minetestmapper-numpy.py --region -1200 800 -1200 800 --drawscale --maxheight 100 --minheight -50 --pixelspernode 1 ~/.minetest/worlds/FCAGameAWorld ~/map.png #sudo mv ~/map.png /var/www/html/minetest/images/map.png #only generate the edges (since started with region 0 0 0 0) and expanding from there until no png is created: is_outline = (chunky_x==self.mapvars["chunkx_min"]) or (chunky_x==self.mapvars["chunkx_max"]) or (chunky_z==self.mapvars["chunkz_min"]) or (chunky_z==self.mapvars["chunkz_max"]) if is_outline: is_present, reason_string = self.check_chunk(chunky_x, chunky_z) if is_present: this_iteration_generates_count += 1 self.mapvars["total_generated_count"] += 1 if self.verbose_enable: print ("") # blank line before next chunky_z so output is more readable self.mapvars["chunkx_min"] -= 1 self.mapvars["chunkz_min"] -= 1 self.mapvars["chunkx_max"] += 1 self.mapvars["chunkz_max"] += 1 #end while square outline (1-chunk-thick outline) generated any png files self.save_mapvars_if_changed() if not self.verbose_enable: print(" rendered: "+str(self.rendered_count)+" (only checks for new chunks)") else: 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: print("ANALYZING "+str(len(signals))+" signal(s)") for this_key in signals.keys(): is_signal_ok = True if this_key=="loop_enable": if not signals[this_key]: self.loop_enable = False else: is_signal_ok = False 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: is_signal_ok = False 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: is_signal_ok = False print("ERROR: expected >=1 seconds for refresh_map_seconds (int or float)") else: is_signal_ok = False 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: is_signal_ok = False print("ERROR: expected int for "+this_key) elif this_key=="recheck_rendered": if type(signals[this_key]) is bool: if signals[this_key]: for chunk_luid in self.chunks.keys(): self.chunks[chunk_luid].is_fresh = False else: is_signal_ok = False print("ERROR: expected bool for "+this_key) elif this_key=="refresh_map_enable": if type(signals[this_key]) is bool: self.refresh_map_enable = signals[this_key] else: is_signal_ok = False print("ERROR: expected bool for "+this_key) elif this_key=="verbose_enable": if type(signals[this_key]) is bool: self.verbose_enable = signals[this_key] else: is_signal_ok = False print("ERROR: expected true or false after colon for "+this_key) else: is_signal_ok = False print("ERROR: unknown signal '"+this_key+"'") if is_signal_ok: print("RECEIVED SIGNAL "+str(this_key)+":"+str(signals[this_key])) else: print("WARNING: blank '"+signal_path+"'") try: os.remove(signal_path) except: print("ERROR: "+__file__+" must have permission to remove '"+signal_path+"'. Commands will be repeated unless command was loop_enable:false.") # 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.verbose_enable = False is_first_iteration = True 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("") print("Ran "+str(self.run_count)+" time(s)") 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 ): #if self.last_players_refresh_second is not None: #print ("waited "+str(best_timer()-self.last_players_refresh_second)+"s for map update") self.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: is_first_run = True map_render_latency = 0.3 is_done_iterating = self.todo_index<0 if (not is_first_iteration) or (self.last_map_refresh_second is None) or (best_timer()-self.last_map_refresh_second > self.refresh_map_seconds) or (not is_done_iterating): while is_first_run or ( ((best_timer()+map_render_latency)-self.last_players_refresh_second) < self.refresh_players_seconds ): self.read_then_remove_signals() if not self.refresh_map_enable: break is_first_run = False is_first_iteration = self.todo_index<0 #if (self.last_map_refresh_second is None) or (best_timer()-self.last_map_refresh_second > self.refresh_map_seconds): #if self.last_map_refresh_second is not None: #print ("waited "+str(best_timer()-self.last_map_refresh_second)+"s for map update") self.last_map_refresh_second = best_timer() self.check_map_pseudorecursion_iterate() if self.todo_index<0: # if done iterating break map_render_latency = best_timer() - self.last_map_refresh_second #self.check_map_inefficient_squarepattern() else: print("waiting before doing map update") else: print("map update is not enabled") run_wait_seconds -= (best_timer()-before_second) is_done_iterating = self.todo_index<0 if ( (float(run_wait_seconds)>0.0) and (is_done_iterating)): print ("sleeping for "+str(run_wait_seconds)+"s") time.sleep(run_wait_seconds) self.run_count += 1 else: self.verbose_enable = True def run(self): if self.refresh_players_enable: self.check_players() if self.refresh_map_enable: self.check_map_inefficient_squarepattern() #self.check_map_pseudorecursion_iterate() 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-players', type = bool, metavar = ('skip_players'), default = False, help = 'update player YAML files for chunkymap.php to use') parser.add_argument('--no-loop', type = bool, metavar = ('no_loop'), default = False, help = 'keep running until "'+signal_path+'" contains the line '+stop_line) args = parser.parse_args() if not args.skip_players: if not args.skip_map: print("Drawing players and map") else: mtchunks.refresh_map_enable = False print("Drawing players only") else: if not args.skip_map: 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)) if mtchunks.refresh_players_enable or mtchunks.refresh_map_enable: if args.no_loop: mtchunks.run() else: print("To stop chunkymap-regen loop, save a line '"+stop_line+"' to '"+signal_path+"'") mtchunks.run_loop()