mirror of
https://gitlab.com/GameFeverOnline/py-mmlv.git
synced 2025-12-17 17:27:42 -04:00
394 lines
13 KiB
Python
394 lines
13 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
|
|
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
|
|
"""
|