# A Mega Man Maker MMLV Library/Module By Timothy J. (GameFeverOnline, www.youtube.com/@GameFeverOnline) """ The goal is to create a library that reads a mmlv file, and then creates variables for each object, tile, background, etc... So that you could on another program import this as a module and just give it for example, mm1iceman = [1, 2, 3] , the 1 meaning that it's active and the 2 and 3 it's position in space. In the future there will be more inputs for settings of objects. It could also work by calling a function, say place_tile(tilename, x, y) and then do that for every tile placement call. That would separate the placing of tiles from the placing of objects and more. """ ### check if dependencies are met try: import configparser, os, definitions import requests except ModuleNotFoundError: print("module not found, please install configparser, requests, os and have the definitions.py in the same folder as mmlv_library.py") Exception(ModuleNotFoundError) ### init global variables section = "Level" config = configparser.ConfigParser(allow_no_value=True) safe_config = configparser.SafeConfigParser() alias = definitions.aliases max_x = 799 max_y = 279 max_room_x = 49 max_room_y = 19 used_blocks = set() ### # converts from multiples of 16 to normal x and y values. for use in reading def conv_coordinates(x16, y16): if x16 < 1 or y16 < 1: x = x16 / 16 y = y16 / 16 else: x = 0 y = 0 return [x, y] #done. converts from normal x and y values to multiples of 16. for use in writing def conv_coordinates_x16(x, y): x16 = x * 16 y16 = y * 16 return [x16, y16] #adds apostrofes to the string def set_apos(object): apos = '"'+ str(object) + '"' return apos def room_coordinates(roomX, roomY): x = int(roomX) * 256 y = int(roomY) * 224 x = str(x) y = str(y) return [x, y] def mark_block_as_used(coord_tuple): global used_blocks if type(coord_tuple) is tuple: if len(coord_tuple) == 2: if all([isinstance(item, int) and item < max_x and item >= 0 for item in coord_tuple]): if coord_tuple not in used_blocks: print("worked") used_blocks.add(coord_tuple) else: print("ERROR: mark_block_as_used did not recieve a tuple") #done. writes the changes to file specified in global variable filename. def read_file(filename="blank.mmlv"): config.read(filename) for x in range(max_x+1): for y in range(max_y+1): if is_block_used(x, y): mark_block_as_used((x,y)) def get_options(): options = config.options(section) return options #so far unneeded def get_items(): items = config.items(section) return items #done. def get_user(): if config.has_option(section, alias["user_name"]): current_username = config.get(section, alias["user_name"]) return current_username def is_block_used(x, y, details=False, alt_algorithm=False): conv_list = conv_coordinates_x16(x, y) x = conv_list[0] y = conv_list[1] if alt_algorithm: ans = False config_dict = config[section] print(type(config_dict)) for key in config_dict: if f"{x},{y}" in key: ans = True break # else: # ans = False return ans for letter in definitions.block_with_coordinates: current = f"{letter}{x},{y}" if current in config[section]: return True else: return False #todo: filter list before doing anything def get_block_contents(x, y, full_scan=False): conv_list = conv_coordinates_x16(x, y) x = conv_list[0] y = conv_list[1] result = [] if full_scan: coordinates = f"{x},{y}" options = get_options() for key in options: #for every key in the file if key.endswith(coordinates): #if each key ends with the correct coordinates index = key.find(coordinates) for character in key: if character.isdigit(): test = key.find(character) if index != test: break result.append([key,config.get(section, key)]) else: #same logic as is_block_used for letter in definitions.block_with_coordinates: current = f"{letter}{x},{y}" if current in config[section]: result.append({current : config.get(section, current)}) return result def string_to_number(value): try: value = int(value) except ValueError: try: value = float(value) if value.is_integer(): value = int(value) except ValueError: value = None return value ###----------------------------------------------------------------------------------------------------------------### #I need to add the "mark_block_as_used" here def set_values(key, x=0, y=0, value=False): if value != False: value = str(value) keys_with_string_values = ("4a", "1a", "0v") if key in value: value = set_apos(value) config[section][f"{key}{x},{y}"] = value else: Exception("no value specified in set_values. that is ironic.") def check_overlap(x,y): if (x,y) in used_blocks: return true ## frozen def optimize_mmlv(verbose=False): for key in config[section]: value_string = config[section][key] try: value = float(value_string.strip('"')) if value.is_integer(): value = int(value) config[section][key] = str(value) except ValueError: if verbose == True: print("this was a float or a string",value_string) else: continue #frozen def write_changes(filename="sample.mmlv", optimize_enable=True, splash_message=True): #Well well well... this for loop is here to make every thing work. I'm not sure for what the 1s, 1q,1r and 1p keys are, but if they are the same as a blank mmlv created by mmm, it just doesn't work. It's weird.' for key,value in zip("srqp", [4480, 0, 12800, 0]): config.set(section,f"1{key}", str(value)) if optimize_enable: optimize_mmlv() if splash_message: if not config.has_section("Py-MMLV"): config.add_section("Py-MMLV") config.set("Py-MMLV","; This level was modified using Py-MMLV by Timothy GFO (https://gitlab.com/gamefeveronline/py-mmlv)", None) with open(filename, 'w') as configfile: config.write(configfile, space_around_delimiters=False) # inspected _____ frozen def set_setting(alias_or_code,value,code=False): if not code: #if the code argument is False alias_or_code = alias[alias_or_code] #var alias_or_code is given value from the dictionary alias with the key being the original alias_or_code argument else: #but if code is True, just check if it's a string alias_or_code = str(alias_or_code) value = set_apos(value) config.set(section, alias_or_code, value) # inspected _____ frozen *** Sets level creator's user name def set_user(username): config.set(section, alias["user_name"],set_apos(username)) # inspected _____ frozen def set_level_name(levelname): config.set(section, alias["level_name"],set_apos(levelname)) #Needs work for choosing specific version of tilename (subtypes?), other versions of tiles (e.g. mm1electile1), and it also needs optional automatic room activator. #def set_spike: #def set_ladder def set_tile(x, y, tilename="mm1cuttile", tile_type="normal", tile_subtype=False, room_active=1): by16 = conv_coordinates_x16(x, y) x16 = str(by16[0]) y16 = str(by16[1]) if (x,y) in used_blocks: del_tile(x,y) option = 0 error = False tile_types = { "normal" : "1", "spike" : "2", "ladder" : "3" } if tile_type == "2": direction = tile_subtype try: tilename = int(tilename) tilename_value = set_apos(tilename) except ValueError: if tilename in definitions.tile_names: tilename_value = definitions.tile_names[tilename] else: error = True for key,value in zip("kjiea", ['"106"', '"71"', tile_types[tile_type], tilename_value, '"1"']): if error == False: config.set(section,key + x16 + "," + y16, value) else: return "error in " + str(letter) # inspected _____ frozen def set_room_active(roomX, roomY,status=True, quadrant="IV"): #Mega Man Maker's coordinates start from top left. Quadrant IV you could say. I'll add this on set tile as a argument. roomX = int(roomX) * 256 roomY = int(roomY) * 224 roomX = str(roomX) roomY = str(roomY) if status == True: status = set_apos(1) else: status = set_apos(0) code = alias["room_active"] config.set(section,f"{code}{roomX},{roomY}", status) # inspected _____ frozen def del_tile(x,y,times_16=False): if not times_16: by16 = conv_coordinates_x16(x, y) x = str(by16[0]) y = str(by16[1]) for letter in definitions.block_with_coordinates: current = f"{letter}{x},{y}" config.remove_option(section, current) ## finally frozen def set_player(x, y, player_type="mm",direction="right", allow_duplicates=False): by16 = conv_coordinates_x16(x, y) x16 = str(by16[0]) y16 = str(by16[1]) if is_block_used(x, y): del_tile(x16, y16) # checks if there is another "player" instance and deletes it if allow_duplicates == False: for dup_x in range(max_x+1): for dup_y in range(max_y+1): for letter in get_block_contents(dup_x,dup_y): by16 = conv_coordinates_x16(dup_x, dup_y) dup_x16 = str(by16[0]) dup_y16 = str(by16[1]) if f"d{dup_x16},{dup_y16}" in letter: value = letter[f"d{dup_x16},{dup_y16}"].strip('"') value = string_to_number(value) if value == 4: del_tile(dup_x,dup_y) type_lookup = { "mm":{"1t":"0"}, "pm":{"r":"2","1t":"1","e":"1"}, "b":{"r":"3", "1t":"2","e":"2"}, "r":{"r":"4", "1t":"3","e":"3"} } player_dictionary = type_lookup[player_type] player_dictionary["a"] = "1" player_dictionary["d"] = "4" #this should reset after function ends (so remove b from dictionary) if direction == "left": player_dictionary["b"] = "-1" for key in player_dictionary: value = player_dictionary[key] full_key = f"{key}{x16},{y16}" #this is needed because 1t doesn't have coordinate values in the key' if key == "1t": full_key = key print("spawning player", full_key, value) config.set(section,full_key, value) # inspected _____ frozen #sets version variable in mmlv file. If 'version' argument is False it will reach for MMM's api to get current version.' def set_ver(version=False): if not version: api_version = "https://api.megamanmaker.com/version" ver = requests.get(api_version) ver_num = ver.json()["version"] else: ver_num = version set_setting(alias["game_version"], ver_num, True) # inspected _____ frozen def connect_screen(h_or_v, roomX, roomY): coord = room_coordinates(roomX, roomY) x = coord[0] y = coord[1] if h_or_v == "h".lower(): config.remove_option(section, f"2b{x},{y}") else: config.set(section, f"2c{x},{y}", "1") # inspected _____ frozen def disconnect_screen(h_or_v, x, y): coord = room_coordinates(roomX, roomY) x = coord[0] y = coord[1] if h_or_v == "h".lower(): config.set(section, f"2b{x},{y}", "0") else: config.remove_option(section, f"2c{x},{y}") def set_object(x, y, object_name="energy_element", setting="9999", object_type="8"): by16 = conv_coordinates_x16(x, y) x16 = str(by16[0]) y16 = str(by16[1]) #maybe the e only applies if object type is 8, I think if object type is 7, e is replaced with p for key,value in zip("oeda", [setting, definitions.object_names[object_name], object_type, "1"]): config.set(section, f"{key}{x16},{y16}", value) #< I'll maybe add the set_player function here, because they are sort of objects. That way everything can be made through here.' # inspected _____ frozen def set_background(roomX, roomY, bg_id): bg_id = str(bg_id) coor = room_coordinates(roomX, roomY) x = str(coor[0]) y = str(coor[1]) bg = alias["bg"] if bg_id in definitions.background_names: bg_id = definitions.background_names[bg_id] config.set(section, f"{bg}{x},{y}", bg_id) """ def list_tiles < this will return a list of tiles already on the level as a list each one with every of the 5 values for the 5 letters def set_weapons """