From 354cef0de18559805be85bee7a7fad5df960f494 Mon Sep 17 00:00:00 2001 From: Crazylemon64 Date: Wed, 18 Jan 2017 20:18:31 -0800 Subject: [PATCH 1/6] Add's -tg-'s mapfixconflict icon --- icons/mob/screen_gen.dmi | Bin 133514 -> 133702 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/mob/screen_gen.dmi b/icons/mob/screen_gen.dmi index 54d39e581dbaac2b17a378b92464faca136a9efc..8ca8456ea398c9a6a61ebcf1c979c92e00d13975 100644 GIT binary patch delta 9098 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 VDs!)W8Y04@uWhXL{gGqL{{xpDUIqXF delta 8894 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 O?$ObBq~4%v9rZs{Xp4XV From d945229fcb270e4b71419d26b3785a1afefca426 Mon Sep 17 00:00:00 2001 From: Crazylemon64 Date: Wed, 18 Jan 2017 20:27:00 -0800 Subject: [PATCH 2/6] Ports over tg's map conflict helping script --- code/modules/admin/verbs/mapping.dm | 6 + tools/map_conflict_fixer/README.md | 29 + .../map_conflict_fixer/map_conflict_fixer.py | 103 +++ tools/map_conflict_fixer/map_helpers.py | 683 ++++++++++++++++++ 4 files changed, 821 insertions(+) create mode 100644 tools/map_conflict_fixer/README.md create mode 100644 tools/map_conflict_fixer/map_conflict_fixer.py create mode 100644 tools/map_conflict_fixer/map_helpers.py diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm index bfc4f7a8887..5a53b0201b3 100644 --- a/code/modules/admin/verbs/mapping.dm +++ b/code/modules/admin/verbs/mapping.dm @@ -30,6 +30,12 @@ var/intercom_range_display_status = 0 src.pixel_x = -224 src.pixel_y = -224 +/obj/effect/debugging/mapfix_marker + name = "map fix marker" + icon = 'icons/mob/screen_gen.dmi' + icon_state = "mapfixmarker" + desc = "I am a mappers mistake." + /obj/effect/debugging/marker icon = 'icons/turf/areas.dmi' icon_state = "yellow" diff --git a/tools/map_conflict_fixer/README.md b/tools/map_conflict_fixer/README.md new file mode 100644 index 00000000000..5af8d3aadf0 --- /dev/null +++ b/tools/map_conflict_fixer/README.md @@ -0,0 +1,29 @@ +#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 successfuly the map should be ready to be commited. + +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/map_conflict_fixer/map_conflict_fixer.py b/tools/map_conflict_fixer/map_conflict_fixer.py new file mode 100644 index 00000000000..c3dc378bf1c --- /dev/null +++ b/tools/map_conflict_fixer/map_conflict_fixer.py @@ -0,0 +1,103 @@ +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/map_conflict_fixer/map_helpers.py b/tools/map_conflict_fixer/map_helpers.py new file mode 100644 index 00000000000..70f1f35089b --- /dev/null +++ b/tools/map_conflict_fixer/map_helpers.py @@ -0,0 +1,683 @@ +import sys +import subprocess + +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"]) + 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"]) + 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 + unused_keys.remove(old_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: + 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 + +## #step two: clean the dictionary if it has too many unused keys +## if len(unused_keys) > min(100, (len(old_dict) * 0.5)): +## print("NOTICE: Trimming the dictionary.") +## old_dict = trim_dictionary(old_dict) + + #step three: write the map to file + if tgm: + write_dictionary_tgm(newfile, old_dict) + write_grid_coord_small(newfile, merged_grid, maxx, maxy) + else: + write_dictionary(newfile, old_dict) + write_grid(newfile, merged_grid, maxx, maxy) + return 0 + +####################### +#write to file helpers# +def write_dictionary_tgm(filename, dictionary): #write dictionary in tgm format + with open(filename, "w") as output: + output.write("{}\n".format(tgm_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") 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): #writes a tile dictionary the same way Dreammaker does + with open(filename, "w") as output: + for key, value in dictionary.items(): + 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") 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# +def search_key(dictionary, data): + for key, value in dictionary.items(): + if value == data: + return key + return None + +def get_key(keys, values, data): + try: + return keys[values.index(data)] + except: + return None + +def trim_dictionary(dictionary): #rewrites dictionary into an ordered dictionary with no unused keys + trimmed_dict = collections.OrderedDict() + key_length = len(list(dictionary.keys())[0]) + key = "" + for tile in dictionary.values(): + key = get_next_key(key, key_length) + trimmed_dict[key] = tile + return trimmed_dict + +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_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" or char == "\t": + continue + + 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 From 16723535cd355a7eeb2aec34acd9a0ca479d4e67 Mon Sep 17 00:00:00 2001 From: Crazylemon64 Date: Wed, 18 Jan 2017 21:04:25 -0800 Subject: [PATCH 3/6] Adds my own tweaks to the map conflict fixer No longer always converts to tgm --- .../map_conflict_fixer/map_conflict_fixer.py | 20 +++++--- tools/map_conflict_fixer/map_helpers.py | 46 ++++++++++--------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/tools/map_conflict_fixer/map_conflict_fixer.py b/tools/map_conflict_fixer/map_conflict_fixer.py index c3dc378bf1c..5121bde7175 100644 --- a/tools/map_conflict_fixer/map_conflict_fixer.py +++ b/tools/map_conflict_fixer/map_conflict_fixer.py @@ -3,7 +3,9 @@ import sys import os import time -def main(relative_root): +# Credits to tg for this + +def main(relative_root, tgm=0): 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.") @@ -12,7 +14,7 @@ def main(relative_root): 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"] @@ -78,11 +80,11 @@ def main(relative_root): 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)) @@ -96,8 +98,14 @@ def main(relative_root): 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): + if map_helpers.fix_map_git_conflicts(base_map, ours_map, theirs_map, mode, marker, priority, relative_root+path, tgm): print("{}: {}".format(ed, path)) print(".") -main(sys.argv[1]) +output_tgm = None +try: + output_tgm = int(sys.argv[2]) +except ValueError: + output_tgm = 0 + +main(sys.argv[1], output_tgm) diff --git a/tools/map_conflict_fixer/map_helpers.py b/tools/map_conflict_fixer/map_helpers.py index 70f1f35089b..2d289e591b6 100644 --- a/tools/map_conflict_fixer/map_helpers.py +++ b/tools/map_conflict_fixer/map_helpers.py @@ -69,7 +69,7 @@ def merge_map(newfile, backupfile, tgm): 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 @@ -77,7 +77,7 @@ def merge_map(newfile, backupfile, tgm): 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) + 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 @@ -94,7 +94,7 @@ def merge_map(newfile, backupfile, tgm): unused_keys.remove(old_key) #all other options ruled out, a brand new key is generated for the brand new tile - else: + else: fresh_key = generate_new_key(old_dict) old_dict[fresh_key] = new_tile merged_grid[x,y] = fresh_key @@ -126,7 +126,7 @@ def write_dictionary_tgm(filename, dictionary): #write dictionary in tgm format in_quote_block = False in_varedit_block = False for char in thing: - + if in_quote_block: if char == "\"": in_quote_block = False @@ -152,11 +152,11 @@ def write_dictionary_tgm(filename, dictionary): #write dictionary in tgm format continue buffer = buffer + char - + if list_.index(thing) != len(list_) - 1: buffer = buffer + ",\n" output.write(buffer) - + output.write(")\n") @@ -253,10 +253,10 @@ def parse_map(map_raw_text): #still does not support more than one z level per f reading_coord = "x" key_length = 0 - + maxx = 0 maxy = 0 - + curr_x = 0 curr_y = 0 curr_z = 1 @@ -283,7 +283,7 @@ def parse_map(map_raw_text): #still does not support more than one z level per f curr_datum = curr_datum + char escaping = False continue - + if char == "\"": curr_datum = curr_datum + char in_quote_block = False @@ -296,7 +296,7 @@ def parse_map(map_raw_text): #still does not support more than one z level per f skip_whitespace = False continue skip_whitespace = False - + if char == "\"": curr_datum = curr_datum + char in_quote_block = True @@ -337,14 +337,14 @@ def parse_map(map_raw_text): #still does not support more than one z level per f 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 + continue #else we're looking for a key block, a data block or the map block if char == "\"": @@ -363,7 +363,7 @@ def parse_map(map_raw_text): #still does not support more than one z level per f in_data_block = True after_data_block = False continue - + else: if in_coord_block: @@ -415,7 +415,7 @@ def parse_map(map_raw_text): #still does not support more than one z level per f iter_x = 0 continue - + curr_key = curr_key + char if len(curr_key) == key_length: iter_x += 1 @@ -425,7 +425,7 @@ def parse_map(map_raw_text): #still does not support more than one z level per f grid[curr_x, curr_y] = curr_key curr_key = "" continue - + #else look for coordinate block or a map string @@ -552,7 +552,7 @@ 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 +def fix_map_git_conflicts(base_map, ours_map, theirs_map, mode, marker, priority, path, tgm): #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 @@ -560,11 +560,11 @@ def fix_map_git_conflicts(base_map, ours_map, theirs_map, mode, marker, priority 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 @@ -639,8 +639,12 @@ def fix_map_git_conflicts(base_map, ours_map, theirs_map, mode, marker, priority 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(tgm) + write_dictionary_tgm(path+".fixed.dmm", merged_dict) + write_grid_coord_small(path+".fixed.dmm", merged_grid, maxx, maxy) + else + write_dictionary(path+".fixed.dmm", old_dict) + write_grid(path+".fixed.dmm", merged_grid, maxx, maxy) if grid_conflict_counter > 0: print("Counted {} conflicts.".format(grid_conflict_counter)) return True @@ -677,7 +681,7 @@ def combine_tiles(tile_A, tile_B, priority, marker): 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 From b9ad3116eb7a8baf06fd5834a43394015e7e74ff Mon Sep 17 00:00:00 2001 From: Crazylemon64 Date: Wed, 18 Jan 2017 21:07:00 -0800 Subject: [PATCH 4/6] Adds a slightly modified batch script For running it in DMM mode --- tools/map_conflict_fixer/Run Map Conflict Fixer - DMM.bat | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 tools/map_conflict_fixer/Run Map Conflict Fixer - DMM.bat diff --git a/tools/map_conflict_fixer/Run Map Conflict Fixer - DMM.bat b/tools/map_conflict_fixer/Run Map Conflict Fixer - DMM.bat new file mode 100644 index 00000000000..d66102a3295 --- /dev/null +++ b/tools/map_conflict_fixer/Run Map Conflict Fixer - DMM.bat @@ -0,0 +1,4 @@ +@echo off +SET RELATIVEROOT="../../" +python map_conflict_fixer.py %1 %RELATIVEROOT% 0 +pause From a6c092f1382a6768d80948ae9d10a1d333810e5c Mon Sep 17 00:00:00 2001 From: Crazylemon64 Date: Wed, 18 Jan 2017 21:49:26 -0800 Subject: [PATCH 5/6] Updates gitignore to be python friendly --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9a9bbe0aafd..861b69b8be0 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ data/ /_maps/map_files/**/*.dmm.backup /_maps/quicksave/* /nano/debug.html +**/__pycache__/* *.db stddef.dm .atom-build.json From cfac90590aa649b43af4762198ca9b95d30a7d9d Mon Sep 17 00:00:00 2001 From: Crazylemon64 Date: Wed, 18 Jan 2017 21:49:45 -0800 Subject: [PATCH 6/6] One last bit to make it all work --- tools/map_conflict_fixer/map_conflict_fixer.py | 4 ++-- tools/map_conflict_fixer/map_helpers.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/map_conflict_fixer/map_conflict_fixer.py b/tools/map_conflict_fixer/map_conflict_fixer.py index 5121bde7175..5e64657ad89 100644 --- a/tools/map_conflict_fixer/map_conflict_fixer.py +++ b/tools/map_conflict_fixer/map_conflict_fixer.py @@ -57,9 +57,9 @@ def main(relative_root, tgm=0): mode = map_helpers.MAP_FIX_DICTIONARY print("DICTIONARY mode selected.") else: - marker = input("FULL mode selected. Input a marker [/obj/effect/debugging/marker]: ") + marker = input("FULL mode selected. Input a marker [/obj/effect/debugging/mapfix_marker]: ") if not marker: - marker = "/obj/effect/debugging/marker" + marker = "/obj/effect/debugging/mapfix_marker" print("Marker selected: {}".format(marker)) print("\nVersion priorities:") diff --git a/tools/map_conflict_fixer/map_helpers.py b/tools/map_conflict_fixer/map_helpers.py index 2d289e591b6..45f27e8c92c 100644 --- a/tools/map_conflict_fixer/map_helpers.py +++ b/tools/map_conflict_fixer/map_helpers.py @@ -639,11 +639,11 @@ def fix_map_git_conflicts(base_map, ours_map, theirs_map, mode, marker, priority grid_conflict_counter += 1 merged_dict = sort_dictionary(merged_dict) - if(tgm) - write_dictionary_tgm(path+".fixed.dmm", merged_dict) - write_grid_coord_small(path+".fixed.dmm", merged_grid, maxx, maxy) - else - write_dictionary(path+".fixed.dmm", old_dict) + if tgm: + write_dictionary_tgm(path+".fixed.dmm", merged_dict) + write_grid_coord_small(path+".fixed.dmm", merged_grid, maxx, maxy) + else: + write_dictionary(path+".fixed.dmm", merged_dict) write_grid(path+".fixed.dmm", merged_grid, maxx, maxy) if grid_conflict_counter > 0: print("Counted {} conflicts.".format(grid_conflict_counter))