diff --git a/tools/mapmerge/Convert Maps to TGM.bat b/tools/mapmerge/Convert Maps to TGM.bat deleted file mode 100644 index 0b88dfaf1d..0000000000 --- a/tools/mapmerge/Convert Maps to TGM.bat +++ /dev/null @@ -1,3 +0,0 @@ -@echo off -set MAPROOT="../../_maps/" -python dmm2tgm.py %1 %MAPROOT% \ No newline at end of file diff --git a/tools/mapmerge/README.md b/tools/mapmerge/README.md deleted file mode 100644 index 08190019e4..0000000000 --- a/tools/mapmerge/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# Map Merger - -Before any change to a map, it is good to use the Map Merger tools. In a nutshell, it rewrites the map to minimize differences between different versions of the map (DreamMakers map editor rewrites a lot of the tile keys). This makes the git diff between different map changes much smaller. More recently a new way of laying out the map was invented by Remie, called TGM, this helps to further reduce conflicts in the map files. - -This is good for a few reasons - -- Maintainers can actually verify the changes you are making are what you say they are by simply viewing the diff (For small changes at least) - -- The less changes there are in any given map diff, the easier it is for git to merge it without running into unexpected conflicts, which in most cases you have to either manually resolve or require you to remap your changes - -However - to do all this is going to require you to put some elbow grease into understanding the map merger tool. - -If you have difficulty using these tools, ask for help in #coderbus - -## Using the tools - -1. **Install Python 3.5 or greater** - If you don't have Python already installed it can be downloaded from: https://www.python.org/downloads/ - make sure you grab the latest python 3, again, it must be 3.5 or greater - -2. **PATH Python** - This step is mostly applicable to windows users, you must make sure you ask the windows installer to add python to your path. If you have already installed python you may need to manually add it to your path as indicated in this guide - -3. **Prepare Maps** - Run "Prepare Maps.bat" in the tools/mapmerge/ directory. -4. **Edit your map** - Make your changes to the map here. Remember to save them! -5. **Clean map** - Run "Run Map Merge - TGM.bat" in the tools/mapmerge/ directory. -6. **Check differences** - Use your git application of choice to look at the differences between revisions of your code and commit the result. -7. **Commit** - Your map is now ready to be committed, rejoice and wait for conflicts. - -## Common pitfalls - -Do *not* open the map in dreameditor before committing the results of the mapmerger - this can cause dreameditor to resave the map back to dmm, if you're having issues with your map getting stuck in dmm mode, try committing and pushing the mapmerger changes before reopening in dreameditor. - -## Map Conflict Fixer/Helper - -The map conflict fixer is a script that can help you fix map conflicts easier and faster. Here's how it works: - -### Before using - -You need git for this, of course. Make sure your development branch is up to date before starting a map edit to ensure the script outputs a correct fix. - -### Dictionary mode - -Dictionary conflicts are the easiest to fix, you simply need to create more models to accommodate your changes and everyone elses. - -When you run in this mode, if the script finishes successfully the map should be ready to be committed. - -If the script fails in dictionary mode, you can run it again in full fix mode. - -### Full Fix mode - -When you and someone else edit the same coordinate, there is no easy way to fix the conflict. You need to get your hands dirty. - -The script will mark every tile with a marker type to help you identify what needs fixing in the map editor. - -After you edit and fix a marked map, you should run it through the map merger. The .backup file should be the same you used before. - -#### Priorities - -In Full Fix mode, the script needs to know which map version has higher priority, yours or someone elses. This important so tiles with multiple area and turf types aren't created. - -Your version has priority - In each conflicted coordinate, your floor type and your area type will be used Their version has priority - In each conflicted coordinate, your floor type and your area type will not be used - -### IMPORTANT - -This script is in a testing phase and you should not consider any output to be safe. Always verify the maps this script produced to make sure nothing is out of place. diff --git a/tools/mapmerge/Run Map Conflict Fixer.bat b/tools/mapmerge/Run Map Conflict Fixer.bat deleted file mode 100644 index 34eac701bb..0000000000 --- a/tools/mapmerge/Run Map Conflict Fixer.bat +++ /dev/null @@ -1,4 +0,0 @@ -@echo off -SET RELATIVEROOT="../../" -python map_conflict_fixer.py %1 %RELATIVEROOT% -pause \ No newline at end of file diff --git a/tools/mapmerge/Run Map Merge - DMM.bat b/tools/mapmerge/Run Map Merge - DMM.bat deleted file mode 100644 index 77eedfbff7..0000000000 --- a/tools/mapmerge/Run Map Merge - DMM.bat +++ /dev/null @@ -1,5 +0,0 @@ -@echo off -set MAPROOT="../../_maps/" -set TGM=0 -python mapmerger.py %1 %MAPROOT% %TGM% -pause \ No newline at end of file diff --git a/tools/mapmerge/Run Map Merge - TGM.bat b/tools/mapmerge/Run Map Merge - TGM.bat deleted file mode 100644 index 1c7ff90311..0000000000 --- a/tools/mapmerge/Run Map Merge - TGM.bat +++ /dev/null @@ -1,5 +0,0 @@ -@echo off -set MAPROOT="../../_maps/" -set TGM=1 -python mapmerger.py %1 %MAPROOT% %TGM% -pause \ No newline at end of file diff --git a/tools/mapmerge/dmm2tgm.py b/tools/mapmerge/dmm2tgm.py deleted file mode 100644 index 4ed4d8ffe6..0000000000 --- a/tools/mapmerge/dmm2tgm.py +++ /dev/null @@ -1,39 +0,0 @@ -import map_helpers -import sys -import shutil - -#main("../../_maps/") -def main(map_folder): - tgm = "1" - maps = map_helpers.prompt_maps(map_folder, "convert", tgm) - - print("\nConverting these maps:") - for i in maps.indices: - print(str(maps.files[i])[len(map_folder):]) - - convert = input("\nPress Enter to convert...\n") - if convert == "abort": - print("\nAborted map convert.") - sys.exit() - else: - for i in maps.indices: - path_str = str(maps.files[i]) - path_str_pretty = path_str[len(map_folder):] - error = map_helpers.merge_map(path_str, path_str, tgm) - if error > 1: - print(map_helpers.error[error]) - continue - if error == 1: - print(map_helpers.error[1]) - print("CONVERTED: {}".format(path_str_pretty)) - print(" - ") - - print("\nFinished converting.") - -def string_to_num(s): - try: - return int(s) - except ValueError: - return -1 - -main(sys.argv[1]) diff --git a/tools/mapmerge/map_conflict_fixer.py b/tools/mapmerge/map_conflict_fixer.py deleted file mode 100644 index c3dc378bf1..0000000000 --- a/tools/mapmerge/map_conflict_fixer.py +++ /dev/null @@ -1,103 +0,0 @@ -import map_helpers -import sys -import os -import time - -def main(relative_root): - git_version = map_helpers.run_shell_command("git version") - if not git_version: - print("ERROR: Failed to run git. Make sure it is installed and in your PATH.") - return False - - print("--- DISCLAIMER ---") - print("This script is in a testing phase. Verify all the results yourself to make sure you got what you expected. Make sure to read the readme to learn how to use this.") - input("Press Enter to GO\n") - - file_conflicts = map_helpers.run_shell_command("git diff --name-only --diff-filter=U").split("\n") - map_conflicts = [path for path in file_conflicts if path[len(path)-3::] == "dmm"] - - for i in range(0, len(map_conflicts)): - print("[{}]: {}".format(i, map_conflicts[i])) - selection = input("Choose maps you want to fix (example: 1,3-5,12):\n") - selection = selection.replace(" ", "") - selection = selection.split(",") - - #shamelessly copied from mapmerger cli - valid_indices = list() - for m in selection: - index_range = m.split("-") - if len(index_range) == 1: - index = map_helpers.string_to_num(index_range[0]) - if index >= 0 and index < len(map_conflicts): - valid_indices.append(index) - elif len(index_range) == 2: - index0 = map_helpers.string_to_num(index_range[0]) - index1 = map_helpers.string_to_num(index_range[1]) - if index0 >= 0 and index0 <= index1 and index1 < len(map_conflicts): - valid_indices.extend(range(index0, index1 + 1)) - - if not len(valid_indices): - print("No map selected, exiting.") - sys.exit() - - print("Attempting to fix the following maps:") - for i in valid_indices: - print(map_conflicts[i]) - - - marker = None - priority = 0 - print("\nFixing modes:") - print("[{}]: Dictionary conflict fixing mode".format(map_helpers.MAP_FIX_DICTIONARY)) - print("[{}]: Full map conflict fixing mode".format(map_helpers.MAP_FIX_FULL)) - mode = map_helpers.string_to_num(input("Select fixing mode [Dictionary]: ")) - if mode != map_helpers.MAP_FIX_FULL: - mode = map_helpers.MAP_FIX_DICTIONARY - print("DICTIONARY mode selected.") - else: - marker = input("FULL mode selected. Input a marker [/obj/effect/debugging/marker]: ") - if not marker: - marker = "/obj/effect/debugging/marker" - print("Marker selected: {}".format(marker)) - - print("\nVersion priorities:") - print("[{}]: Your version".format(map_helpers.MAP_FIX_PRIORITY_OURS)) - print("[{}]: Their version".format(map_helpers.MAP_FIX_PRIORITY_THEIRS)) - priority = map_helpers.string_to_num(input("Select priority [Yours]: ")) - if priority != map_helpers.MAP_FIX_PRIORITY_THEIRS: - priority = map_helpers.MAP_FIX_PRIORITY_OURS - print("Your version will be prioritized.") - else: - print("Their version will be prioritized.") - - ed = "FIXED" if mode == map_helpers.MAP_FIX_DICTIONARY else "MARKED" - ing = "FIXING" if mode == map_helpers.MAP_FIX_DICTIONARY else "MARKING" - - print("\nMaps will be converted to TGM.") - print("Writing maps to 'file_path/file_name.fixed.dmm'. Please verify the results before commiting.") - if mode == map_helpers.MAP_FIX_FULL: - print("After editing the marked maps, run them through the map merger!") - input("Press Enter to start.") - - print(".") - time.sleep(0.3) - print(".") - - for i in valid_indices: - path = map_conflicts[i] - print("{}: {}".format(ing, path)) - ours_map_raw_text = map_helpers.run_shell_command("git show HEAD:{}".format(path)) - theirs_map_raw_text = map_helpers.run_shell_command("git show MERGE_HEAD:{}".format(path)) - - common_ancestor_hash = map_helpers.run_shell_command("git merge-base HEAD MERGE_HEAD").strip() - base_map_raw_text = map_helpers.run_shell_command("git show {}:{}".format(common_ancestor_hash, path)) - - ours_map = map_helpers.parse_map(ours_map_raw_text) - theirs_map = map_helpers.parse_map(theirs_map_raw_text) - base_map = map_helpers.parse_map(base_map_raw_text) - - if map_helpers.fix_map_git_conflicts(base_map, ours_map, theirs_map, mode, marker, priority, relative_root+path): - print("{}: {}".format(ed, path)) - print(".") - -main(sys.argv[1]) diff --git a/tools/mapmerge/map_helpers.py b/tools/mapmerge/map_helpers.py deleted file mode 100644 index e1eb7e875c..0000000000 --- a/tools/mapmerge/map_helpers.py +++ /dev/null @@ -1,774 +0,0 @@ -import sys -import subprocess -import os -import pathlib -import collections -from datetime import datetime - -tgm_header = "//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE" - -try: - version = sys.version_info - if version.major < 3 or (version.major == 3 and version.minor < 5): - print("ERROR: You are running an incompatible version of Python. The current minimum version required is [3.5].\nYour version: {}".format(sys.version)) - sys.exit() -except: - print("ERROR: Something went wrong, you might be running an incompatible version of Python. The current minimum version required is [3.5].\nYour version: {}".format(sys.version)) - sys.exit() - -import collections - -error = {0:"OK", 1:"WARNING: Detected key length difference, not merging.", 2:"WARNING: Detected map size difference, not merging."} - -def merge_map(newfile, backupfile, tgm): - key_length = 1 - maxx = 1 - maxy = 1 - - new_map = parse_map(get_map_raw_text(newfile)) - old_map = parse_map(get_map_raw_text(backupfile)) - - if new_map["key_length"] != old_map["key_length"]: - if tgm: - write_dictionary_tgm(newfile, new_map["dictionary"]) - write_grid_coord_small(newfile, new_map["grid"], new_map["maxx"], new_map["maxy"]) - return 1 - else: - key_length = old_map["key_length"] - - if new_map["maxx"] != old_map["maxx"] or new_map["maxy"] != old_map["maxy"]: - if tgm: - write_dictionary_tgm(newfile, new_map["dictionary"]) - write_grid_coord_small(newfile, new_map["grid"], new_map["maxx"], new_map["maxy"]) - return 2 - else: - maxx = old_map["maxx"] - maxy = old_map["maxy"] - - new_dict = new_map["dictionary"] - new_grid = new_map["grid"] - old_dict = sort_dictionary(old_map["dictionary"]) #impose order; old_dict is used in the end as the merged dictionary - old_grid = old_map["grid"] - - merged_grid = dict() - known_keys = dict() - unused_keys = list(old_dict.keys()) - - #both new and old dictionary in lists, for faster key lookup by tile tuple - old_dict_keys = list(old_dict.keys()) - old_dict_values = list(old_dict.values()) - new_dict_keys = list(new_dict.keys()) - new_dict_values = list(new_dict.values()) - - #step one: parse the new version, compare it to the old version, merge both - for y in range(1, maxy+1): - for x in range(1, maxx+1): - - new_key = new_grid[x,y] - #if this key has been processed before, it can immediately be merged - if new_key in known_keys: - merged_grid[x,y] = known_keys[new_key] - continue - - old_key = old_grid[x,y] - old_tile = old_dict[old_key] - new_tile = new_dict[new_key] - - if new_tile == old_tile: #this tile is the exact same as before, so the old key is used - merged_grid[x,y] = old_key - known_keys[new_key] = old_key - try: - unused_keys.remove(old_key) - except ValueError as ve_exception: - print("NOTICE: Correcting duplicate dictionary entry. ({})".format(new_key)) - continue - - #the tile is different here, but if it exists in the old dictionary, its old key can be used - newold_key = get_key(old_dict_keys, old_dict_values, new_tile) - if newold_key != None: - merged_grid[x,y] = newold_key - known_keys[new_key] = newold_key - try: - unused_keys.remove(newold_key) - except ValueError as ve_exception: - print("NOTICE: Correcting duplicate dictionary entry. ({})".format(new_key)) - - #the tile is brand new and it needs a new key, but if the old key isn't being used any longer it can be used instead - elif get_key(new_dict_keys, new_dict_values, old_tile) == None: - merged_grid[x,y] = old_key - old_dict[old_key] = new_tile - known_keys[new_key] = old_key - unused_keys.remove(old_key) - - #all other options ruled out, a brand new key is generated for the brand new tile - else: - fresh_key = generate_new_key(old_dict) - old_dict[fresh_key] = new_tile - merged_grid[x,y] = fresh_key - - header = False - #step two: clean the dictionary if it has too many unused keys - if len(unused_keys) > min(1600, (len(old_dict) * 0.5)): - print("NOTICE: Trimming the dictionary.") - trimmed_dict_map = trim_dictionary({"dictionary": old_dict, "grid": merged_grid}) - old_dict = trimmed_dict_map["dictionary"] - merged_grid = trimmed_dict_map["grid"] - print("NOTICE: Trimmed out {} unused dictionary keys.".format(len(unused_keys))) - header = "//Model dictionary trimmed on: {}".format(datetime.utcnow().strftime("%d-%m-%Y %H:%M (UTC)")) - - #step three: write the map to file - if tgm: - write_dictionary_tgm(newfile, old_dict, header) - write_grid_coord_small(newfile, merged_grid, maxx, maxy) - else: - write_dictionary(newfile, old_dict, header) - write_grid(newfile, merged_grid, maxx, maxy) - return 0 - -####################### -#write to file helpers# -def write_dictionary_tgm(filename, dictionary, header = None): #write dictionary in tgm format - with open(filename, "w", newline='\n') as output: - output.write("{}\n".format(tgm_header)) - if header: - output.write("{}\n".format(header)) - for key, list_ in dictionary.items(): - output.write("\"{}\" = (\n".format(key)) - - for thing in list_: - buffer = "" - in_quote_block = False - in_varedit_block = False - for char in thing: - - if in_quote_block: - if char == "\"": - in_quote_block = False - buffer = buffer + char - continue - elif char == "\"": - in_quote_block = True - buffer = buffer + char - continue - - if not in_varedit_block: - if char == "{": - in_varedit_block = True - buffer = buffer + "{\n\t" - continue - else: - if char == ";": - buffer = buffer + ";\n\t" - continue - elif char == "}": - buffer = buffer + "\n\t}" - in_varedit_block = False - continue - - buffer = buffer + char - - if list_.index(thing) != len(list_) - 1: - buffer = buffer + ",\n" - output.write(buffer) - - output.write(")\n") - - -def write_grid_coord_small(filename, grid, maxx, maxy): #thanks to YotaXP for finding out about this one - with open(filename, "a", newline='\n') as output: - output.write("\n") - - for x in range(1, maxx+1): - output.write("({},{},1) = {{\"\n".format(x, 1, 1)) - for y in range(1, maxy): - output.write("{}\n".format(grid[x,y])) - output.write("{}\n\"}}\n".format(grid[x,maxy])) - - -def write_dictionary(filename, dictionary, header = None): #writes a tile dictionary the same way Dreammaker does - with open(filename, "w", newline='\n') as output: - for key, value in dictionary.items(): - if header: - output.write("{}\n".format(header)) - output.write("\"{}\" = ({})\n".format(key, ",".join(value))) - - -def write_grid(filename, grid, maxx, maxy): #writes a map grid the same way Dreammaker does - with open(filename, "a", newline='\n') as output: - output.write("\n") - output.write("(1,1,1) = {\"\n") - - for y in range(1, maxy+1): - for x in range(1, maxx+1): - try: - output.write(grid[x,y]) - except KeyError: - print("Key error: ({},{})".format(x,y)) - output.write("\n") - output.write("\"}") - output.write("\n") - -#################### -#dictionary helpers# - -#faster than get_key() on smaller dictionaries -def search_key(dictionary, data): - for key, value in dictionary.items(): - if value == data: - return key - return None - -#faster than search_key() on bigger dictionaries -def get_key(keys, values, data): - try: - return keys[values.index(data)] - except: - return None - -def trim_dictionary(unclean_map): #rewrites dictionary into an ordered dictionary with no unused keys - trimmed_dict = collections.OrderedDict() - adjusted_grid = dict() - key_length = len(list(unclean_map["dictionary"].keys())[0]) - key = "" - old_to_new = dict() - used_keys = set(unclean_map["grid"].values()) - - for old_key, tile in unclean_map["dictionary"].items(): - if old_key in used_keys: - key = get_next_key(key, key_length) - trimmed_dict[key] = tile - old_to_new[old_key] = key - - for coord, old_key in unclean_map["grid"].items(): - adjusted_grid[coord] = old_to_new[old_key] - - return {"dictionary": trimmed_dict, "grid": adjusted_grid} - -def sort_dictionary(dictionary): - sorted_dict = collections.OrderedDict() - keys = list(dictionary.keys()) - keys.sort(key=key_int_value) - for key in keys: - sorted_dict[key] = dictionary[key] - return sorted_dict - -############ -#map parser# -def get_map_raw_text(mapfile): - with open(mapfile, "r") as reading: - return reading.read() - -def parse_map(map_raw_text): #still does not support more than one z level per file, but should parse any format - in_comment_line = False - comment_trigger = False - - in_quote_block = False - in_key_block = False - in_data_block = False - in_varedit_block = False - after_data_block = False - escaping = False - skip_whitespace = False - - dictionary = collections.OrderedDict() - curr_key = "" - curr_datum = "" - curr_data = list() - - in_map_block = False - in_coord_block = False - in_map_string = False - iter_x = 0 - adjust_y = True - - curr_num = "" - reading_coord = "x" - - key_length = 0 - - maxx = 0 - maxy = 0 - - curr_x = 0 - curr_y = 0 - curr_z = 1 - grid = dict() - - for char in map_raw_text: - - if not in_map_block: - - if char == "\n": - in_comment_line = False - comment_trigger = False - continue - - if in_comment_line: - continue - - if char == "\t": - continue - - if char == "/" and not in_quote_block: - if comment_trigger: - in_comment_line = True - continue - else: - comment_trigger = True - else: - comment_trigger = False - - if in_data_block: - - if in_varedit_block: - - if in_quote_block: - if char == "\\": - curr_datum = curr_datum + char - escaping = True - continue - - if escaping: - curr_datum = curr_datum + char - escaping = False - continue - - if char == "\"": - curr_datum = curr_datum + char - in_quote_block = False - continue - - curr_datum = curr_datum + char - continue - - if skip_whitespace and char == " ": - skip_whitespace = False - continue - skip_whitespace = False - - if char == "\"": - curr_datum = curr_datum + char - in_quote_block = True - continue - - if char == ";": - skip_whitespace = True - curr_datum = curr_datum + char - continue - - if char == "}": - curr_datum = curr_datum + char - in_varedit_block = False - continue - - curr_datum = curr_datum + char - continue - - if char == "{": - curr_datum = curr_datum + char - in_varedit_block = True - continue - - if char == ",": - curr_data.append(curr_datum) - curr_datum = "" - continue - - if char == ")": - curr_data.append(curr_datum) - dictionary[curr_key] = tuple(curr_data) - curr_data = list() - curr_datum = "" - curr_key = "" - in_data_block = False - after_data_block = True - continue - - curr_datum = curr_datum + char - continue - - if in_key_block: - if char == "\"": - in_key_block = False - key_length = len(curr_key) - else: - curr_key = curr_key + char - continue - #else we're looking for a key block, a data block or the map block - - if char == "\"": - in_key_block = True - after_data_block = False - continue - - if char == "(": - if after_data_block: - in_map_block = True - in_coord_block = True - after_data_block = False - curr_key = "" - continue - else: - in_data_block = True - after_data_block = False - continue - - else: - - if in_coord_block: - if char == ",": - if reading_coord == "x": - curr_x = string_to_num(curr_num) - if curr_x > maxx: - maxx = curr_x - iter_x = 0 - curr_num = "" - reading_coord = "y" - elif reading_coord == "y": - curr_y = string_to_num(curr_num) - if curr_y > maxy: - maxy = curr_y - curr_num = "" - reading_coord = "z" - else: - pass - continue - - if char == ")": - in_coord_block = False - reading_coord = "x" - curr_num = "" - #read z here if needed - continue - - curr_num = curr_num + char - continue - - if in_map_string: - - if char == "\"": - in_map_string = False - adjust_y = True - curr_y -= 1 - continue - - if char == "\n": - if adjust_y: - adjust_y = False - else: - curr_y += 1 - if curr_x > maxx: - maxx = curr_x - if iter_x > 1: - curr_x = 1 - iter_x = 0 - continue - - - curr_key = curr_key + char - if len(curr_key) == key_length: - iter_x += 1 - if iter_x > 1: - curr_x += 1 - - grid[curr_x, curr_y] = curr_key - curr_key = "" - continue - - - #else look for coordinate block or a map string - - if char == "(": - in_coord_block = True - continue - if char == "\"": - in_map_string = True - continue - - if curr_y > maxy: - maxy = curr_y - - data = dict() - data["dictionary"] = dictionary - data["grid"] = grid - data["key_length"] = key_length - data["maxx"] = maxx - data["maxy"] = maxy - return data - -############# -#key helpers# -def generate_new_key(dictionary): - last_key = next(reversed(dictionary)) - return get_next_key(last_key, len(last_key)) - -def get_next_key(key, key_length): - if key == "": - return "".join("a" for _ in range(key_length)) - - length = len(key) - new_key = "" - carry = 1 - for char in key[::-1]: - if carry <= 0: - new_key = new_key + char - continue - if char == 'Z': - new_key = new_key + 'a' - carry += 1 - length -= 1 - if length <= 0: - return "OVERFLOW" - elif char == 'z': - new_key = new_key + 'A' - else: - new_key = new_key + chr(ord(char) + 1) - if carry > 0: - carry -= 1 - return new_key[::-1] - -def key_int_value(key): - value = 0 - b = 0 - for digit in reversed(key): - value += base52.index(digit) * (52 ** b) - b += 1 - return value - -def key_compare(keyA, keyB): #thanks byond for not respecting ascii - pos = 0 - for a in keyA: - pos += 1 - count = pos - for b in keyB: - if(count > 1): - count -= 1 - continue - if a.islower() and b.islower(): - if(a < b): - return -1 - if(a > b): - return 1 - break - if a.islower() and b.isupper(): - return -1 - if a.isupper() and b.islower(): - return 1 - if a.isupper() and b.isupper(): - if(a < b): - return -1 - if(a > b): - return 1 - break - return 0 - - -def key_difference(keyA, keyB): #subtract keyB from keyA - if len(keyA) != len(keyB): - return "you fucked up" - - Ayek = keyA[::-1] - Byek = keyB[::-1] - - result = 0 - for i in range(0, len(keyA)): - base = 52**i - A = 26 if Ayek[i].isupper() else 0 - B = 26 if Byek[i].isupper() else 0 - result += ( (ord(Byek[i].lower()) + B) - (ord(Ayek[i].lower()) + A) ) * base - return result - -############# -#other stuff# - -#Base 52 a-z A-Z dictionary (it's a python list) for fast conversion -base52 = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o', - 'p','q','r','s','t','u','v','w','x','y','z','A','B','C','D', - 'E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S', - 'T','U','V','W','X','Y','Z'] - -def string_to_num(s): - try: - return int(s) - except ValueError: - return -1 - -#################### -#map conflict fixer# - -MAP_FIX_DICTIONARY = 0 -MAP_FIX_FULL = 1 -MAP_FIX_PRIORITY_OURS = 0 -MAP_FIX_PRIORITY_THEIRS = 1 - -def fix_map_git_conflicts(base_map, ours_map, theirs_map, mode, marker, priority, path): #attempts to fix git map conflicts automagically - #mode values: - # 0 - Dictionary mode, only fixes dictionary conflicts and fails at coordinate conflicts - # 1 - Full mode, fixes dictionary conflicts and will join both versions of a coordinate and adds a marker to each conflicted coordinate - - if mode == MAP_FIX_FULL and not marker: - print("ERROR: Full fix mode selected but a marker wasn't provided.") - return False - - key_length = 1 - maxx = 1 - maxy = 1 - - if ours_map["key_length"] != theirs_map["key_length"] or ours_map["key_length"] != base_map["key_length"]: - print("ERROR: Key length is different, I am not smart enough for this yet.") - return False - else: - key_length = ours_map["key_length"] - - if ours_map["maxx"] != theirs_map["maxx"] or ours_map["maxy"] != theirs_map["maxy"] or ours_map["maxx"] != base_map["maxx"] or ours_map["maxy"] != base_map["maxy"]: - print("ERROR: Map size is different, I am not smart enough for this yet.") - return False - else: - maxx = ours_map["maxx"] - maxy = ours_map["maxy"] - - base_dict = base_map["dictionary"] - base_grid = base_map["grid"] - ours_dict = ours_map["dictionary"] - ours_grid = ours_map["grid"] - theirs_dict = theirs_map["dictionary"] - theirs_grid = theirs_map["grid"] - - merged_dict = collections.OrderedDict() - merged_grid = dict() - - new_key = generate_new_key(base_dict) - - grid_conflict_counter = 0 - - for y in range(1, maxy+1): - for x in range(1, maxx+1): - base_key = base_grid[x,y] - ours_key = ours_grid[x,y] - theirs_key = theirs_grid[x,y] - - #case 1: everything is the same - if base_dict[base_key] == ours_dict[ours_key] and base_dict[base_key] == theirs_dict[theirs_key]: - merged_dict[base_key] = base_dict[base_key] - merged_grid[x,y] = base_key - continue - - #case 2: ours is unchanged, theirs is different - if base_dict[base_key] == ours_dict[ours_key]: - some_key = search_key(base_dict, theirs_dict[theirs_key]) - if some_key != None: - merged_dict[some_key] = theirs_dict[theirs_key] - merged_grid[x,y] = some_key - else: - merged_dict[new_key] = theirs_dict[theirs_key] - merged_grid[x,y] = new_key - new_key = get_next_key(new_key, key_length) - - #case 3: ours is different, theirs is unchanged - elif base_dict[base_key] == theirs_dict[theirs_key]: - some_key = search_key(base_dict, ours_dict[ours_key]) - if some_key != None: - merged_dict[some_key] = ours_dict[ours_key] - merged_grid[x,y] = some_key - else: - merged_dict[new_key] = ours_dict[ours_key] - merged_grid[x,y] = new_key - new_key = get_next_key(new_key, key_length) - - #case 4: everything is different, grid conflict - else: - if mode != MAP_FIX_FULL: - print("FAILED: Map fixing failed due to grid conflicts. Try fixing in full fix mode.") - return False - else: - combined_tile = combine_tiles(ours_dict[ours_key], theirs_dict[theirs_key], priority, marker) - merged_dict[new_key] = combined_tile - merged_grid[x,y] = new_key - new_key = get_next_key(new_key, key_length) - grid_conflict_counter += 1 - - merged_dict = sort_dictionary(merged_dict) - write_dictionary_tgm(path+".fixed.dmm", merged_dict) - write_grid_coord_small(path+".fixed.dmm", merged_grid, maxx, maxy) - if grid_conflict_counter > 0: - print("Counted {} conflicts.".format(grid_conflict_counter)) - return True - -def combine_tiles(tile_A, tile_B, priority, marker): - new_tile = list() - turf_atom = None - area_atom = None - - low_tile = None - high_tile = None - if priority == MAP_FIX_PRIORITY_OURS: - high_tile = tile_A - low_tile = tile_B - else: - high_tile = tile_B - low_tile = tile_A - - new_tile.append(marker) - for atom in high_tile: - if atom[0:5] == "/turf": - turf_atom = atom - elif atom[0:5] == "/area": - area_atom = atom - else: - new_tile.append(atom) - - for atom in low_tile: - if atom[0:5] == "/turf" or atom[0:5] == "/area": - continue - else: - new_tile.append(atom) - - new_tile.append(turf_atom) - new_tile.append(area_atom) - return tuple(new_tile) - - -def run_shell_command(command): - return subprocess.run(command, shell=True, stdout=subprocess.PIPE, universal_newlines=True).stdout - -def prompt_maps(map_folder, verb, tgm): - list_of_files = list() - for root, directories, filenames in os.walk(map_folder): - for filename in [f for f in filenames if f.endswith(".dmm")]: - list_of_files.append(pathlib.Path(root, filename)) - - last_dir = "" - for i in range(0, len(list_of_files)): - this_dir = list_of_files[i].parent - if last_dir != this_dir: - print("--------------------------------") - last_dir = this_dir - print("[{}]: {}".format(i, str(list_of_files[i])[len(map_folder):])) - - print("--------------------------------") - in_list = input("List the maps you want to " + verb + " (example: 1,3-5,12):\n") - in_list = in_list.replace(" ", "") - in_list = in_list.split(",") - - valid_indices = list() - for m in in_list: - index_range = m.split("-") - if len(index_range) == 1: - index = string_to_num(index_range[0]) - if index >= 0 and index < len(list_of_files): - valid_indices.append(index) - elif len(index_range) == 2: - index0 = string_to_num(index_range[0]) - index1 = string_to_num(index_range[1]) - if index0 >= 0 and index0 <= index1 and index1 < len(list_of_files): - valid_indices.extend(range(index0, index1 + 1)) - - if tgm == "1": - print("\nMaps will be converted to tgm.") - tgm = True - else: - print("\nMaps will not be converted to tgm.") - tgm = False - - maps_to_run = collections.namedtuple('maps_to_run', ['files', 'indices']) - return maps_to_run(list_of_files, valid_indices) \ No newline at end of file diff --git a/tools/mapmerge/mapmerger.py b/tools/mapmerge/mapmerger.py deleted file mode 100644 index f0206a9b6c..0000000000 --- a/tools/mapmerge/mapmerger.py +++ /dev/null @@ -1,46 +0,0 @@ -import map_helpers -import sys -import shutil - -#main("../../_maps/") -def main(map_folder, tgm=0): - maps = map_helpers.prompt_maps(map_folder, "merge", tgm) - - print("\nMerging these maps:") - for i in maps.indices: - print(str(maps.files[i])[len(map_folder):]) - - merge = input("\nPress Enter to merge...\n") - if merge == "abort": - print("\nAborted map merge.") - sys.exit() - else: - for i in maps.indices: - path_str = str(maps.files[i]) - shutil.copyfile(path_str, path_str + ".before") - path_str_pretty = path_str[len(map_folder):] - try: - error = map_helpers.merge_map(path_str, path_str + ".backup", tgm) - if error > 1: - print(map_helpers.error[error]) - os.remove(path_str + ".before") - continue - if error == 1: - print(map_helpers.error[1]) - print("MERGED: {}".format(path_str_pretty)) - print(" - ") - except FileNotFoundError: - print("ERROR: File not found! Make sure you run 'Prepare Maps.bat' before merging.") - print("MISSING BACKUP FILE: " + path_str_pretty + ".backup") - print(" - ") - - print("\nFinished merging.") - print("\nNOTICE: A version of the map files from before merging have been created for debug purposes.\nDo not delete these files until it is sure your map edits have no undesirable changes.") - -def string_to_num(s): - try: - return int(s) - except ValueError: - return -1 - -main(sys.argv[1], sys.argv[2]) diff --git a/tools/mapmerge/path_update.py b/tools/mapmerge/path_update.py deleted file mode 100644 index 2713de2d8b..0000000000 --- a/tools/mapmerge/path_update.py +++ /dev/null @@ -1,183 +0,0 @@ -import re -import os -import argparse -import map_helpers #why we don't have some generic package for these reee - -default_map_directory = "../../_maps" -replacement_re = re.compile('\s*([^{]*)\s*(\{(.*)\})?') - -def tgm_check(map_file): - with open(map_file) as f: - firstline = f.readline() - #why some maps have trailing spaces in this ??? - if firstline.startswith(map_helpers.tgm_header): - return True - return False - -def save_map(map_data,filepath,tgm=False): - map_data['dictionary'] = map_helpers.sort_dictionary(map_data['dictionary']) - if tgm: - map_helpers.write_dictionary_tgm(filepath, map_data['dictionary'],None) - map_helpers.write_grid_coord_small(filepath, map_data['grid'], map_data['maxx'], map_data['maxy']) - else: - map_helpers.write_dictionary(filepath, map_data['dictionary'],None) - map_helpers.write_grid(filepath, map_data['grid'], map_data['maxx'], map_data['maxy']) - -def update_all_maps(update_file=None, update_string=None, map_directory=None, verbose=False): - if map_directory is None: - map_directory = os.path.normpath(os.path.join(os.path.dirname(__file__), default_map_directory)) - for root, _, files in os.walk(map_directory): - for filepath in files: - if filepath.endswith(".dmm"): - path = os.path.join(root, filepath) - update_map(path, update_file, update_string, verbose) - -def update_map(map_filepath, update_file=None, update_string=None, verbose=False): - print("Updating: {0}".format(map_filepath)) - map_data = map_helpers.parse_map(map_helpers.get_map_raw_text(map_filepath)) - tgm = tgm_check(map_filepath) - if update_file: - with open(update_file) as update_source: - for line in update_source: - if line.startswith("#") or line.isspace(): - continue - map_data = update_path(map_data, line, verbose) - save_map(map_data, map_filepath, tgm) - if update_string: - map_data = update_path(map_data, update_string, verbose) - save_map(map_data, map_filepath, tgm) - -def props_to_string(props): - return "{{{0}}}".format(";".join([k+" = "+props[k] for k in props])) - -#urgent todo: replace with actual parser, this is slow as janitor in crit -split_re = re.compile('((?:[A-Za-z0-9_\-$]+)\s*=\s*(?:"(?:.+?)"|[^";]*)|@OLD)') - -def string_to_props(propstring,verbose = False): - props = dict() - for raw_prop in re.split(split_re,propstring): - if not raw_prop or raw_prop.strip() == ';': - continue - prop = raw_prop.split('=', maxsplit=1) - props[prop[0].strip()] = prop[1].strip() if len(prop) > 1 else None - if verbose: - print("{0} to {1}".format(propstring,props)) - return props - -def parse_rep_string(replacement_string,verbose = False): - # translates /blah/blah {meme = "test",} into path,prop dictionary tuple - match = re.match(replacement_re, replacement_string) - path = match.group(1) - props = match.group(3) - if props: - prop_dict = string_to_props(props, verbose) - else: - prop_dict = dict() - return path.strip(), prop_dict - -def update_path(mapdata, replacement_string, verbose=False): - old_path_part, new_path_part = replacement_string.split(':', maxsplit=1) - old_path, old_path_props = parse_rep_string(old_path_part,verbose) - new_paths = dict() - for replacement_def in new_path_part.split(','): - new_path, new_path_props = parse_rep_string(replacement_def,verbose) - new_paths[new_path] = new_path_props - - def replace_def(match): - if match.group(2): - old_props = string_to_props(match.group(2),verbose) - else: - old_props = dict() - for filter_prop in old_path_props: - if filter_prop not in old_props: - if old_path_props[filter_prop] == "@UNSET": - continue - else: - return [match.group(0)] - else: - if old_props[filter_prop] != old_path_props[filter_prop] or old_path_props[filter_prop] == "@UNSET": - return [match.group(0)] #does not match current filter, skip the change. - if verbose: - print("Found match : {0}".format(match.group(0))) - out_paths = [] - for new_path, new_props in new_paths.items(): - out = new_path - out_props = dict() - for prop_name, prop_value in new_props.items(): - if prop_name == "@OLD": - out_props = dict(old_props) - continue - if prop_value == "@SKIP": - out_props.pop(prop_name, None) - continue - if prop_value.startswith("@OLD"): - params = prop_value.split(":") - if prop_name in old_props: - out_props[prop_name] = old_props[params[1]] if len(params) > 1 else old_props[prop_name] - continue - out_props[prop_name] = prop_value - if out_props: - out += props_to_string(out_props) - out_paths.append(out) - if verbose: - print("Replacing with: {0}".format(out_paths)) - return out_paths - - def get_result(element): - p = re.compile("{0}\s*({{(.*)}})?$".format(re.escape(old_path))) - match = p.match(element) - if match: - return replace_def(match) # = re.sub(p,replace_def,element) - else: - return [element] - - for definition_key in mapdata['dictionary']: - def_value = mapdata['dictionary'][definition_key] - start = list(def_value) - changed = [y for x in start for y in get_result(x)] - new_value = tuple(changed) - if new_value != def_value: - mapdata['dictionary'][definition_key] = new_value - - return mapdata - -if __name__ == "__main__": - desc = """ - Update dmm files given update file/string. - - Replacement syntax example: - /turf/open/floor/plasteel/warningline : /obj/effect/turf_decal {dir = @OLD ;tag = @SKIP;icon_state = @SKIP} - /turf/open/floor/plasteel/warningline : /obj/effect/turf_decal {@OLD} , /obj/thing {icon_state = @OLD:name; name = "meme"} - /turf/open/floor/plasteel/warningline{dir=2} : /obj/thing - New paths properties: - @OLD - if used as property name copies all modified properties from original path to this one - property = @SKIP - will not copy this property through when global @OLD is used. - property = @OLD - will copy this modified property from original object even if global @OLD is not used - property = @OLD:name - will copy [name] property from original object even if global @OLD is not used - Anything else is copied as written. - Old paths properties: - Will be used as a filter. - property = @UNSET - will apply the rule only if the property is not mapedited - """ - - parser = argparse.ArgumentParser(description=desc, formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument("update_source", help="update file path / line of update notation") - parser.add_argument("--map", "-m", help="path to update, defaults to all maps in maps directory") - parser.add_argument("--directory", "-d", help="path to maps directory, defaults to ../../_maps") - parser.add_argument("--inline", "-i", help="treat update source as update string instead of path", action="store_true") - parser.add_argument("--verbose", "-v", help="toggle detailed update information", action="store_true") - - args = parser.parse_args() - if args.directory: - default_map_directory = args.directory - - if args.map: - if args.inline: - update_map(args.map, update_string=args.update_source, verbose=args.verbose) - else: - update_map(args.map, update_file=args.update_source, verbose=args.verbose) - else: - if args.inline: - update_all_maps(update_string=args.update_source, verbose=args.verbose) - else: - update_all_maps(update_file=args.update_source, verbose=args.verbose) diff --git a/tools/mapmerge/Prepare Maps.bat b/tools/mapmerge2/Prepare Maps.bat similarity index 100% rename from tools/mapmerge/Prepare Maps.bat rename to tools/mapmerge2/Prepare Maps.bat