Added get_block_contents and updated readme

This commit is contained in:
Timothy GFO 2023-10-05 01:56:25 -04:00
parent c6be529e06
commit 45b6344f28
7 changed files with 172 additions and 260 deletions

View file

@ -1,7 +1,14 @@
# Py-MMLV, a MMLV Format Python Library by Timothy GFO # Py-MMLV, a MMLV Format Python Library by Timothy GFO
## The Goal ## The Goal
The goal of this project is to create a single, easy to use, and flexible Python Library for the MMLV format used in Mega Man Maker. This project takes advantage of the common ConfigParser Module, and uses it to read and write the files, for use in the Py-MMLV. The goal of this project is to create a single, easy to use, and flexible Python Library for the MMLV format used in Mega Man Maker. This project takes advantage of the common ConfigParser Module, and uses it to read and write the files, for use in Py-MMLV.
This includes:
* Files generated with Py-MMLV will generally be *smaller* in file size, this could in theory improve load times. The reason is that Mega Man Maker stores integers as "1.000", while Py-MMLV just does "1". Py-MMLV will also be smart enough to automatically remove redundant entries in the file. (in the future)
* Every function that spawns something in the level should have intuitive naming and arguments.
* A stable base to improve upon: Every function will lookup in the definitions.py file.
* Be able to generate from scratch a valid mmlv file, as well as modifying a file (valid or not).
## Installation ## Installation
Dependencies: Dependencies:
@ -18,7 +25,9 @@ When in beta, you should be able to import as a module in another python program
If you find a bug, remember to create an issue about it. But don't do it right now, there is more bug than code. If you find a bug, remember to create an issue about it. But don't do it right now, there is more bug than code.
## Roadmap ## Roadmap
Create basic functions like set_tile, set_active_room, write_file. add get functions
add automatic room activation to set_tile
add ability to all functions to accept raw x, y values (the times 16 numbers in mmlv) and raw codes (like "a" instead of "active")
## Contributing ## Contributing
I would like to be the main developer of this project until it has some missing core functions and it reaches beta. After that feel free to leave pull requests for me to check. I would like to be the main developer of this project until it has some missing core functions and it reaches beta. After that feel free to leave pull requests for me to check.

View file

@ -1,55 +0,0 @@
[Level]
o12784,4464 = "9999.000"
e12784,4464 = "113.000"
d12784,4464 = "6.000"
a12784,4464 = "1.000"
o256,0 = "9999.000"
e256,0 = "113.000"
d256,0 = "6.000"
a256,0 = "1.000"
2a12544,4256 = "1.000"
2a256,0 = "1.000"
2b0,4256 = "0.000"
2b0,4032 = "0.000"
2b0,3808 = "0.000"
2b0,3584 = "0.000"
2b0,3360 = "0.000"
2b0,3136 = "0.000"
2b0,2912 = "0.000"
2b0,2688 = "0.000"
2b0,2464 = "0.000"
2b0,2240 = "0.000"
2b0,2016 = "0.000"
2b0,1792 = "0.000"
2b0,1568 = "0.000"
2b0,1344 = "0.000"
2b0,1120 = "0.000"
2b0,896 = "0.000"
2b0,672 = "0.000"
2b0,448 = "0.000"
2b0,224 = "0.000"
2b0,0 = "0.000"
1s = "4464.000"
1r = "0.000"
1q = "12784.000"
1p = "0.000"
1m = "3.000"
1l = "10.000"
1k0 = "0.000"
1bc = "0.000"
1f = "-1.000"
1e = "29.000"
1d = "6.000"
1cc = "1.000"
1cb = "1.000"
1bb = "0.000"
1ca = "0.000"
1ba = "0.000"
1c = "1.000"
1b = "1.000"
4b = "75.000"
4a = "py-mmlv-custom"
1a = "MmLV-PARSER-Test1Default"
0v = "1.8.4.1"
0a = "386743.000"

View file

@ -30,7 +30,7 @@ aliases = {
"weapon_slot_10" : "1k10", "weapon_slot_10" : "1k10",
"weapon_slot_11" : "1k11", "weapon_slot_11" : "1k11",
"level_music" : "1m", "level_music" : "1m",
"room_active" : "2a", #2aX,Y="1.000" "room_active" : "2a",
"game_version" : "0v", "game_version" : "0v",
"charge_broom" : "1cc", "charge_broom" : "1cc",
"dodge_roll" : "1cb", "dodge_roll" : "1cb",
@ -41,20 +41,23 @@ aliases = {
"track_id" : "1l", "track_id" : "1l",
"spike_subtype" : "l", "spike_subtype" : "l",
"object_type" : "d", "object_type" : "d",
"bg" : "2d" "bg" : "2d",
"lava_status" : "q"
} }
#weapons definition. currently it's a dictionary with each weapon_name being it's ID number.
weapons = { weapons = {
"M. Buster" : "0", "M. Buster" : "0",
# 1 = S. Tomahawk "S. Tomahawk" : "1",
# 2 = W. Storm "W. Storm" : "2",
# 3 = P. Shot "P. Shot" : "3",
# 4 = Metal Blade "Metal Blade" : "4",
# 5 = Rush Coil "Rush Coil" : "5",
# 6 = S. Crash "S. Crash" : "6",
# 7 = Rush Jet "Rush Jet" : "7",
# 8 = Shine "Shine" : "8",
# 9 = Gravity Hold "Gravity Hold" : "9"
# 10 = Top Spin # 10 = Top Spin
# 11 = Magnet Beam # 11 = Magnet Beam
# 12 = C. Bomber # 12 = C. Bomber
@ -90,18 +93,13 @@ weapons = {
# 42 = S. Arrow # 42 = S. Arrow
# 43 = Power Stone # 43 = Power Stone
# 44 = W. Wave # 44 = W. Wave
# 45 = C. Eye # 45 = C. EyePy-MMLV
} }
supported_values = {
"weapon_slot" : weapons,
}
tile_names = { tile_names = {
"mm1cuttile" : '"3.000"' "mm1cuttile" : "3"
} }
object_names = { object_names = {
@ -112,7 +110,10 @@ object_names = {
background_names = { background_names = {
"cut" : "115" "cut" : "115"
} }
block_with_coordinates = ["a", "k", "j", "i","e"] block_with_coordinates = ["a", "k", "j", "i","e", "o", "d", "q"]
tile_identifiers = [["a", "enable"], ["k", "sub_type"], "j", "i","e"]
spike_identifiers = [["l", "direction"],"i","e","a"]
spike_direction = [["1" ,"point_down"], ["0", "point_up"], ["2", "point_left"], ["3", "point_right"], ["4", "center"]]

View file

@ -1,28 +0,0 @@
1s="1600"
1r="0"
1q="1600"
1p="0"
1m="10"
1l="6"
1k11="-1"
1k10="-1"
1k9="-1"
1k8="-1"
1k7="-1"
1k6="-1"
1k5="-1"
1k4="-1"
1k3="-1"
1k2="-1"
1k1="-1"
1k0="0"
1bc="0"
1f="-1"
1e="29"
1d="6"
1bb="0"
1ca="0"
1ba="0"
1c="1"
1b="1"
4b="75"

View file

@ -16,7 +16,6 @@ except ModuleNotFoundError:
section = "Level" section = "Level"
config = configparser.ConfigParser() config = configparser.ConfigParser()
alias = definitions.aliases alias = definitions.aliases
supported_values = definitions.supported_values
max_x = 799 max_x = 799
max_y = 279 max_y = 279
max_room_x = 49 max_room_x = 49
@ -44,29 +43,18 @@ def set_apos(object):
apos = '"'+ str(object) + '"' apos = '"'+ str(object) + '"'
return apos 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. #done. writes the changes to file specified in global variable filename.
def read_file(filename="blank.mmlv"): def read_file(filename="blank.mmlv"):
config.read(filename) config.read(filename)
def write_changes(filename="sample.mmlv"):
#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)
# for letter in "srqp":
# if letter == "q":
# config.set(section,"1"+letter, set_apos(12800) )
# elif letter == "s":
# config.set(section,"1"+letter, set_apos(4480) )
# else:
# config.set(section,"1"+letter, set_apos(0) )
with open(filename, 'w') as configfile:
config.write(configfile, space_around_delimiters=False)
def set_global(option, value):
config.set(section, option, value)
#done. returns what is available in the file. without values. So far unneeded
def get_options(): def get_options():
options = config.options(section) options = config.options(section)
return options return options
@ -76,28 +64,13 @@ def get_items():
items = config.items(section) items = config.items(section)
return items return items
def set_setting(alias_or_code,value,code=False):
if not code:
alias_or_code = alias[alias_or_code]
else:
alias_or_code = str(alias_or_code)
value = set_apos(value)
config.set(section, alias_or_code, value)
#done. Sets level creator's user name
def set_user(username):
if config.has_option(section, alias["user_name"]):
username = '"' + str(username) + '"'
config.set(section, alias["user_name"],username.lower())
#done. Not tested #done. Not tested
def get_user(): def get_user():
if config.has_option(section, alias["user_name"]): if config.has_option(section, alias["user_name"]):
current_username = config.get(section, alias["user_name"]) current_username = config.get(section, alias["user_name"])
return current_username return current_username
# IS NOT WORKING NEEDS OPTIMIZATION
def is_block_used(x, y, details=False): def is_block_used(x, y, details=False):
conv_list = conv_coordinates_x16(x, y) conv_list = conv_coordinates_x16(x, y)
x = conv_list[0] x = conv_list[0]
@ -109,16 +82,87 @@ def is_block_used(x, y, details=False):
return True return True
except KeyError: except KeyError:
return False return False
#this will optionally report WHAT is actually in there.
def set_tile_value(k_value, j_value, i_value, e_value, a_value, posX_16, posY_16): #todo: filter list before doing anything, And SPEED IT UP! (maybe by using this algorithm in the "is block used" or just using the "for letter in definitions.block_with_coordinates. maybe by using a full_scan=True)
config.set(section,letter + posX_16 + "," + posY_16, k_value) def get_block_contents(x, y, full_scan=True):
config.set(section,letter + posX_16 + "," + posY_16, j_value) conv_list = conv_coordinates_x16(x, y)
config.set(section,letter + posX_16 + "," + posY_16, i_value) x = conv_list[0]
config.set(section,letter + posX_16 + "," + posY_16, e_value) y = conv_list[1]
config.set(section,letter + posX_16 + "," + posY_16, a_value) result = []
if full_scan and True:
coordinates = f"{x},{y}"
options = get_options()
#print(options)
for key in options:
#print(key.endswith(coordinates))
if key.endswith(coordinates):
index = key.find(coordinates)
#print("index", index)
for character in key:
if character.isdigit():
test = key.find(character)
#print("character", character)
if index != test:
break
result.append([key,config.get(section, key)])
# else:
# for letter in definitions.block_with_coordinates:
# current = f"{letter}{x},{y}"
# try:
# result.append([current,config[section,current]])
# except KeyError:
# print("skipping")
return result
#print(key)
# if coordinates in key:
# print(key)
#config.get(section, f"{}")
###----------------------------------------------------------------------------------------------------------------###
def write_changes(filename="sample.mmlv"):
#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)
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. #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): def set_tile(x, y, tilename="mm1cuttile", tile_type="normal", tile_subtype=False, room_active=1):
by16 = conv_coordinates_x16(x, y) by16 = conv_coordinates_x16(x, y)
x16 = str(by16[0]) x16 = str(by16[0])
@ -131,11 +175,15 @@ def set_tile(x, y, tilename="mm1cuttile", tile_type="normal", tile_subtype=False
option = 0 option = 0
error = False error = False
tile_types = { tile_types = {
"normal" : '"1"', "normal" : "1",
"spike" : '"2"', "spike" : "2",
"ladder" : '"3"' "ladder" : "3"
} }
if tile_type == "2":
direction = tile_subtype
try: try:
tilename = int(tilename) tilename = int(tilename)
tilename_value = set_apos(tilename) tilename_value = set_apos(tilename)
@ -146,66 +194,28 @@ def set_tile(x, y, tilename="mm1cuttile", tile_type="normal", tile_subtype=False
error = True error = True
for key,value in zip("kjiea", ['"106"', '"71"', tile_types[tile_type], tilename_value, '"1"']): for key,value in zip("kjiea", ['"106"', '"71"', tile_types[tile_type], tilename_value, '"1"']):
# for letter in "kjiea":
# if letter == "k":
# #unknown, so sample for now. Related to tile sub type
# #print("working k")
# option = '"106.000"'
# elif letter == "j":
# #unknown Related to tile subtype. maybe this and k are x and y? This is x, not y.
# #print("working j")
# option = '"71.000"'
# elif letter == "i":
# #according to https://gist.github.com/freem/03d6c1832299e8d8a32f64361380920d this is block type
# if block_type == "ladder":
# option = '"3.000"'
# elif block_type == "spike":
# option = '"2.000"'
# else:
# option = '"1.000"'
# elif letter == "e":
# #supposedly this is item sub-type. Leaving default for now. Need to implement a method to have an index of all the tiles.
# try:
# tilename = int(tilename)
# option = set_apos(tilename)
# except ValueError:
# if tilename in definitions.tile_names:
# option = definitions.tile_names[tilename]
# else:
# error = True
#print("working e")
#option = '"3.000"'
# else:
# #enabled? Well, of course! Cheap way to remove a tile. Not ideal though, increases load time, proper removing of the keys will be implemented. ALERT, THIS APPLIES TO EVERYTHING NOT JUST TILES(objects, etc...)
# #print("working a")
# option = '"1.000"'
if error == False: if error == False:
config.set(section,key + x16 + "," + y16, value) config.set(section,key + x16 + "," + y16, value)
else: else:
return "error in " + str(letter) return "error in " + str(letter)
def set_level_name(levelname):
config.set(section, alias["level_name"],set_apos(levelname))
def room_coordinates(roomX, roomY):
x = int(roomX) * 256
y = int(roomY) * 224
x = str(x)
y = str(y)
return [x, y]
def set_room_active(roomX, roomY,status=True): #the x and y values have to be the 0,0 relative to the room, so just the one where the tile on the top left corner of a room goes. I'll add this on set tile as a argument.
# 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 roomX = int(roomX) * 256
roomY = int(roomY) * 224 roomY = int(roomY) * 224
roomX = str(roomX) roomX = str(roomX)
roomY = str(roomY) roomY = str(roomY)
room_active = str(alias["room_active"])
if status == True: if status == True:
status = '"1.000"' status = set_apos(1)
else: else:
status = '"0.000"' status = set_apos(0)
config.set(section,room_active + roomX + "," + roomY, status) code = alias["room_active"]
config.set(section,f"{code}{roomX},{roomY}", status)
def set_player(x, y,type="mm", allow_duplicates=0): def set_player(x, y,type="mm", allow_duplicates=0):
@ -213,23 +223,13 @@ def set_player(x, y,type="mm", allow_duplicates=0):
x16 = str(by16[0]) x16 = str(by16[0])
y16 = str(by16[1]) #Every function that has something like all this should be a funcion. y16 = str(by16[1]) #Every function that has something like all this should be a funcion.
for key,value in zip("oda", ['"9999"', '"4"', '"1"']): for key,value in zip("oda", ['"9999"', '"4"', '"1"']):
# for letter in "oda":
# option = 0
# if letter == "o":
# #Unknown
# option = '"9999.000"'
# elif letter == "d":
# #unknown
# option = '"4.000"'
# else:
# option = '"1.000"'
config.set(section,key + x16 + "," + y16, value) config.set(section,key + x16 + "," + y16, value)
# inspected _____ frozen
def del_tile(x,y,disable=False): def del_tile(x,y,disable=False):
for letter in definitions.block_with_coordinates: for letter in definitions.block_with_coordinates:
current = letter + str(x) + ',' + str(y) current = letter + str(x) + ',' + str(y)
config.remove_option(section, current) config.remove_option(section, current)
# inspected _____ frozen
#sets version variable in mmlv file. If 'version' argument is False it will reach for MMM's api to get current version.' #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): def set_ver(version=False):
if not version: if not version:
@ -240,6 +240,8 @@ def set_ver(version=False):
ver_num = version ver_num = version
set_setting(alias["game_version"], ver_num, True) set_setting(alias["game_version"], ver_num, True)
# inspected _____ frozen
#this needs the same calculations of the room active function #this needs the same calculations of the room active function
def connect_screen(h_or_v, roomX, roomY): def connect_screen(h_or_v, roomX, roomY):
coord = room_coordinates(roomX, roomY) coord = room_coordinates(roomX, roomY)
@ -250,6 +252,7 @@ def connect_screen(h_or_v, roomX, roomY):
else: else:
config.set(section, f"2c{x},{y}", "1") config.set(section, f"2c{x},{y}", "1")
# inspected _____ frozen
def disconnect_screen(h_or_v, x, y): def disconnect_screen(h_or_v, x, y):
coord = room_coordinates(roomX, roomY) coord = room_coordinates(roomX, roomY)
x = coord[0] x = coord[0]
@ -263,20 +266,12 @@ def set_object(x, y, object_name="energy_element", setting="9999", object_type="
by16 = conv_coordinates_x16(x, y) by16 = conv_coordinates_x16(x, y)
x16 = str(by16[0]) x16 = str(by16[0])
y16 = str(by16[1]) 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"]): for key,value in zip("oeda", [setting, definitions.object_names[object_name], object_type, "1"]):
# for key in "oeda":
# if key == "o":
# #mysteries continue. Maybe setting?
# value = setting
# elif key == "e":
# value = definitions.object_names[object_name]
# elif key == "d":
# #this is like the object_type?
# value = object_type
# else:
# value = "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.' 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): def set_background(roomX, roomY, bg_id):
bg_id = str(bg_id) bg_id = str(bg_id)
coor = room_coordinates(roomX, roomY) coor = room_coordinates(roomX, roomY)
@ -286,10 +281,10 @@ def set_background(roomX, roomY, bg_id):
if bg_id in definitions.background_names: if bg_id in definitions.background_names:
bg_id = definitions.background_names[bg_id] bg_id = definitions.background_names[bg_id]
config.set(section, f"{bg}{x},{y}", 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 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
def set_background
""" """
# 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. # 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.

View file

@ -1,4 +1,18 @@
[Level] [Level]
k16,16="71.000"
j16,16="141.000"
i16,16="1.000"
e16,16="3.000"
a16,16="1.000"
k0,0="71.000"
j0,0="71.000"
i0,0="1.000"
e0,0="3.000"
a0,0="1.000"
2c512,224="1.000"
2d256,4256="115.000"
2d0,0="115.000"
2a0,0="1.000"
1s="4480" 1s="4480"
1r="0" 1r="0"
1q="12800" 1q="12800"
@ -18,20 +32,8 @@
1c="1.000" 1c="1.000"
1b="1.000" 1b="1.000"
4b="75.000" 4b="75.000"
4a="python" 4a="game fever online yt"
1a="sample" 1a="sample"
0v="1.8.4.1" 0v="1.8.5.2"
0a="386743.000" 0a="386743.000"
2c512,224=1
2d0,0=115
2d256,0=115
o16,16=9999
e16,16=15
d16,16=8
a16,16=1
k16,32="106"
j16,32="71"
i16,32="1"
e16,32="3.000"
a16,32="1"

View file

@ -8,29 +8,17 @@ _|_ _ __|_o._ _ _ .__ ._ _| _
_| _| _| _|
""" """
mmlv.read_file() mmlv.read_file("4 hour level PyMMLV.mmlv")
print(mmlv.get_user()) #
mmlv.set_level_name("sample") # # print(mmlv.get_user())
mmlv.set_user("Python") # #
# count = 0 # # mmlv.set_level_name("sample")
# four = 0 # # mmlv.set_user("Python")
# for column in range(mmlv.max_y+1): # # print(count)
# four += 1 # # mmlv.connect_screen("v", 2, 1)
# count += 1 # # mmlv.set_background(0,0, 115)
# print(column) # # mmlv.set_background(1,mmlv.max_room_y, "cut")
# if four == 4: print(mmlv.get_block_contents(1,2))
# four = 0 # #mmlv.write_changes("sample.mmlv")
# for tile in range(mmlv.max_x+1):
# count += 1
# #print(str(tile)+"tile")
# mmlv.set_tile(tile,column)
#mmlv.set_room_active(mmlv.max_room_x,mmlv.max_room_y)
# print(count)
mmlv.connect_screen("v", 2, 1)
mmlv.set_background(0,0, 115)
mmlv.set_background(1,0, "cut")
mmlv.set_object(1,1, "energy_element")
mmlv.set_tile(1,2)
mmlv.write_changes("sample.mmlv")
#del_tile(1,1) #del_tile(1,1)