Files
CHOMPStation2/tools/mapmerge2/merge_driver.py
2025-02-21 11:48:45 +04:00

130 lines
5.3 KiB
Python

#!/usr/bin/env python3
import sys
import collections
from . import dmm, mapmerge
from hooks.merge_frontend import MergeDriver
debug_stats = collections.defaultdict(int)
def select(base, left, right, *, debug=None):
if left == right:
# whether or not it's in the base, both sides agree
if debug:
debug_stats[f"select {debug} both"] += 1
return left
elif base == left:
# base == left, but right is different: accept right
if debug:
debug_stats[f"select {debug} right"] += 1
return right
elif base == right:
# base == right, but left is different: accept left
if debug:
debug_stats[f"select {debug} left"] += 1
return left
else:
# all three versions are different
if debug:
debug_stats[f"select {debug} fail"] += 1
return None
def three_way_merge(base, left, right):
if base.size != left.size or base.size != right.size:
print("Dimensions have changed:")
print(f" Base: {base.size}")
print(f" Ours: {left.size}")
print(f" Theirs: {right.size}")
return True, None
trouble = False
merged = dmm.DMM(base.key_length, base.size)
merged.dictionary = base.dictionary.copy()
for (z, y, x) in base.coords_zyx:
coord = x, y, z
base_tile = base.get_tile(coord)
left_tile = left.get_tile(coord)
right_tile = right.get_tile(coord)
# try to merge the whole tiles
whole_tile_merge = select(base_tile, left_tile, right_tile, debug='tile')
if whole_tile_merge is not None:
merged.set_tile(coord, whole_tile_merge)
continue
# try to merge each group independently (movables, turfs, areas)
base_movables, base_turfs, base_areas = dmm.split_atom_groups(base_tile)
left_movables, left_turfs, left_areas = dmm.split_atom_groups(left_tile)
right_movables, right_turfs, right_areas = dmm.split_atom_groups(right_tile)
merged_movables = select(base_movables, left_movables, right_movables, debug='movable')
merged_turfs = select(base_turfs, left_turfs, right_turfs, debug='turf')
merged_areas = select(base_areas, left_areas, right_areas, debug='area')
if merged_movables is not None and merged_turfs is not None and merged_areas is not None:
merged.set_tile(coord, merged_movables + merged_turfs + merged_areas)
continue
# TODO: more advanced strategies?
# fall back to requiring manual conflict resolution
trouble = True
print(f" C: Both sides touch the tile at {coord}")
if merged_movables is None:
# Note that if you do not have an object that matches this path in your DME, the invalid path may be discarded when the map is loaded into a map editor.
# To rectify this, either add an object with this same path, or create a new object/denote an existing object in the obj_path define.
obj_path = "/obj/merge_conflict_marker"
obj_name = "---Merge Conflict Marker---"
obj_desc = "A best-effort merge was performed. You must resolve this conflict yourself (manually) and remove this object once complete."
merged_movables = left_movables + [f'{obj_path}{{name = "{obj_name}";\n\tdesc = "{obj_desc}"}}'] + right_movables
print(f" Left and right movable groups are split by an `{obj_path}` named \"{obj_name}\"")
if merged_turfs is None:
merged_turfs = left_turfs
print(f" Saving turf: {', '.join(left_turfs)}")
print(f" Alternative: {', '.join(right_turfs)}")
print(f" Original: {', '.join(base_turfs)}")
if merged_areas is None:
merged_areas = left_areas
print(f" Saving area: {', '.join(left_areas)}")
print(f" Alternative: {', '.join(right_areas)}")
print(f" Original: {', '.join(base_areas)}")
merged.set_tile(coord, merged_movables + merged_turfs + merged_areas)
merged = mapmerge.merge_map(merged, base) # is this necessary or correct?
merged = mapmerge.merge_map(merged, right)
return trouble, merged
class DmmDriver(MergeDriver):
driver_id = 'dmm'
def merge(self, base, left, right):
map_base = dmm.DMM.from_bytes(base.read())
map_left = dmm.DMM.from_bytes(left.read())
map_right = dmm.DMM.from_bytes(right.read())
trouble, merge_result = three_way_merge(map_base, map_left, map_right)
return not trouble, merge_result
def to_file(self, outfile, merge_result):
outfile.write(merge_result.to_bytes())
def post_announce(self, success, merge_result):
if not success:
print("!!! Manual merge required!")
if merge_result:
print(" A best-effort merge was performed. You must edit the map and confirm")
print(" that all coordinates mentioned above are as desired.")
else:
print(" The map was totally unable to be merged; you must start with one version")
print(" or the other and manually resolve the conflict. Information about the")
print(" conflicting tiles is listed above.")
if __name__ == '__main__':
exit(DmmDriver().main())