Skip to content

Commit

Permalink
Update mapmerge2
Browse files Browse the repository at this point in the history
  • Loading branch information
PsiOmegaDelta committed Apr 8, 2020
1 parent ea9b20f commit 490b0c7
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 33 deletions.
2 changes: 2 additions & 0 deletions tools/hooks/install.bat
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ for %%f in (*.merge) do (

driver = tools/hooks/%%f %%P %%O %%A %%B %%L >> ..\..\.git\config
)
echo Installing Python dependencies
python -m pip install -r ..\mapmerge2\requirements.txt
echo Done
pause
5 changes: 5 additions & 0 deletions tools/hooks/install.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/bin/bash
set -e
shopt -s nullglob
cd "$(dirname "$0")"
for f in *.hook; do
Expand All @@ -9,4 +10,8 @@ for f in *.merge; do
echo Installing merge driver: ${f%.merge}
git config --replace-all merge.${f%.merge}.driver "tools/hooks/$f %P %O %A %B %L"
done

echo "Installing Python dependencies"
./python.sh -m pip install -r ../mapmerge2/requirements.txt

echo "Done"
23 changes: 5 additions & 18 deletions tools/mapmerge2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,15 @@ contains the desired changes.

## Installation

To install Python dependencies, run `requirements-install.bat`, OR run
`python -m pip install -r requirements.txt` directly. Make sure you have Python 3.5
or higher before doing so.
To install Python dependencies, run `requirements-install.bat`, or run
`python -m pip install -r requirements.txt` directly. See the [Git hooks]
documentation to install the Git pre-commit hook which runs the map merger
automatically, or use `tools/mapmerge/Prepare Maps.bat` to save backups before
running `mapmerge.bat`.

For up-to-date installation and detailed troubleshooting instructions, visit
the [Map Merger] wiki article.

## Usage

Make sure you've performed the installation steps above! You have two choices for using this tool: Either view the [Git hooks] documentation to install the hook to automate this process,
or to perform merges manually, follow the steps below.

1. Run Prepare Maps.bat as this provides backups before making changes to a map. It also makes sure the mapmerger actually works.

2. Edit your map and save. Close DM.**

3. Run mapmerge.bat

4. Commit your changes and you're done!

**Note: Do not open the map in dreammaker before committing the results of mapmerger - this can cause dreammaker to save back into the default dmm format. If you're having issues with your map getting stuck in dmm mode, try committing and pushing the mapmerger changes before reopening in dreammaker.

## Code Structure

Frontend scripts are meant to be run directly. They obey the environment
Expand Down
18 changes: 6 additions & 12 deletions tools/mapmerge2/dmm.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ def _parse(map_raw_text):
in_map_block = False
in_coord_block = False
in_map_string = False
iter_x = 0
base_x = 0
adjust_y = True

curr_num = ""
Expand Down Expand Up @@ -487,7 +487,7 @@ def _parse(map_raw_text):
curr_x = int(curr_num)
if curr_x > maxx:
maxx = curr_x
iter_x = 0
base_x = curr_x
curr_num = ""
reading_coord = "y"
elif reading_coord == "y":
Expand Down Expand Up @@ -521,21 +521,15 @@ def _parse(map_raw_text):
adjust_y = False
else:
curr_y += 1
if curr_x > maxx:
maxx = curr_x
if iter_x > 1:
curr_x = 1
iter_x = 0

curr_x = base_x
else:
curr_key = BASE * curr_key + base52_r[char]
curr_key_len += 1
if curr_key_len == key_length:
iter_x += 1
if iter_x > 1:
curr_x += 1

grid[curr_x, curr_y, curr_z] = duplicate_keys.get(curr_key, curr_key)
if curr_x > maxx:
maxx = curr_x
curr_x += 1
curr_key = 0
curr_key_len = 0

Expand Down
6 changes: 3 additions & 3 deletions tools/mapmerge2/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pygit2>=0.27.2
bidict>=0.13.1
Pillow>=5.1.0
pygit2==1.0.1
bidict==0.13.1
Pillow==7.0.0
176 changes: 176 additions & 0 deletions tools/mapmerge2/update_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# A script and syntax for applying path updates to maps.
import re
import os
import argparse
import frontend
from dmm import *

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
"""

default_map_directory = "../../_maps"
replacement_re = re.compile(r'\s*(?P<path>[^{]*)\s*(\{(?P<props>.*)\})?')

#urgent todo: replace with actual parser, this is slow as janitor in crit
split_re = re.compile(r'((?:[A-Za-z0-9_\-$]+)\s*=\s*(?:"(?:.+?)"|[^";]*)|@OLD)')


def props_to_string(props):
return "{{{}}}".format(";".join([f"{k} = {v}" for k, v in props.items()]))


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['path']
props = match['props']
if props:
prop_dict = string_to_props(props, verbose)
else:
prop_dict = dict()
return path.strip(), prop_dict


def update_path(dmm_data, 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 = list()
for replacement_def in new_path_part.split(','):
new_path, new_path_props = parse_rep_string(replacement_def, verbose)
new_paths.append((new_path, new_path_props))

subtypes = ""
if old_path.endswith("/@SUBTYPES"):
old_path = old_path[:-len("/@SUBTYPES")]
if verbose:
print("Looking for subtypes of", old_path)
subtypes = r"(?:/\w+)*"

replacement_pattern = re.compile(rf"(?P<path>{re.escape(old_path)}{subtypes})\s*(:?{{(?P<props>.*)}})?$")

def replace_def(match):
if match['props']:
old_props = string_to_props(match['props'], 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:
if new_path == "@OLD":
out = match.group('path')
else:
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):
match = replacement_pattern.match(element)
if match:
return replace_def(match)
else:
return [element]

bad_keys = {}
keys = list(dmm_data.dictionary.keys())
for definition_key in keys:
def_value = dmm_data.dictionary[definition_key]
new_value = tuple(y for x in def_value for y in get_result(x))
if new_value != def_value:
dmm_data.overwrite_key(definition_key, new_value, bad_keys)
dmm_data.reassign_bad_keys(bad_keys)


def update_map(map_filepath, updates, verbose=False):
print("Updating: {0}".format(map_filepath))
dmm_data = DMM.from_file(map_filepath)
for update_string in updates:
update_path(dmm_data, update_string, verbose)
dmm_data.to_file(map_filepath, True)


def update_all_maps(map_directory, updates, verbose=False):
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, updates, verbose)


def main(args):
if args.inline:
print("Using replacement:", args.update_source)
updates = [args.update_source]
else:
with open(args.update_source) as f:
updates = [line for line in f if line and not line.startswith("#") and not line.isspace()]
print(f"Using {len(updates)} replacements from file:", args.update_source)

if args.map:
update_map(args.map, updates, verbose=args.verbose)
else:
map_directory = args.directory or frontend.read_settings().map_folder
update_all_maps(map_directory, updates, verbose=args.verbose)


if __name__ == "__main__":
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")
main(parser.parse_args())

0 comments on commit 490b0c7

Please sign in to comment.