py-mmlv/mmlv_library.py
2023-10-25 21:00:31 -04:00

378 lines
12 KiB
Python

# 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
import csv
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()
safe_config = configparser.SafeConfigParser()
alias = definitions.aliases
max_x = 799
max_y = 279
max_room_x = 49
max_room_y = 19
###
# 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]
#done. writes the changes to file specified in global variable filename.
def read_file(filename="blank.mmlv"):
config.read(filename)
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
###----------------------------------------------------------------------------------------------------------------###
def set_values(key, x="", y="", value=""):
if value != "":
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):
## frozen
def optimize_mmlv(verbose=False):
for key in config[section]:
value_string = config[section][key]
try:
value = float(value_string.replace('"', ""))
floatified = float(value)
config[section][key] = str(floatified)
except ValueError:
if verbose == True:
print("this was a float or a string",value_string)
else:
continue
def write_changes(filename="sample.mmlv", optimize_enable=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", [set_apos(4480), set_apos(0), set_apos(12800), set_apos(0)]):
config.set(section,f"1{key}", value)
optimize_mmlv()
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 is_block_used(x, y):
#this needs a check to see if the coordinates are already taken, and deal with object-tile conflicts accordingly.
# opt = get_options()
#WIP
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)
### should check if there is another "1t" (and maybe "r" and "e" idk) and delete everything on that tile.
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
#this needs the same calculations of the room active function
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
"""
# A problem that has to be fixed some day is that EVERY TILE WILL NOT APPEAR ON EDITOR TILL A PLAY TEST. It has something to do with the 1s, 1q,1p and 1r entries. I set the max value that works but on MMLV Editor the value changes if a tile is more torward the top left. Maybe an algorithm is needed to count every tile from the level and update those values accordingly.