Integrating mapmerge2 checks into Travis (#5616)

Okay, this update is kinda big. Summary:

- Trimmed unused keys in Exodus telecomms

- Adds script that will run mapmerge2 on Travis to check branch for unused keys or key overflow, etc.

- Fixes matching indentation style in tag-matcher and converts it to use Python 3.6

- Converts mapmerge2 to be used by Python 3.4 and above. Instead of 3.6

- Removes Windows 1252 characters from Communication-blackout.dm that were not able to be seen in UTF-8 format.

Note: the last commit will fail because currently main level is broken

Example of no map issues:
![2018-11-15_li](https://user-images.githubusercontent.com/25555314/48592180-0fd7be80-e8fc-11e8-80b9-cd5af32540e3.jpg)

Example of issues:
![2018-11-15_li 2](https://user-images.githubusercontent.com/25555314/48592190-15cd9f80-e8fc-11e8-99bd-6da4b4c2b9d8.jpg)
This commit is contained in:
Mykhailo Bykhovtsev
2018-11-20 11:14:21 -08:00
committed by Werner
parent d230a60a20
commit 779f8a0733
9 changed files with 162 additions and 284 deletions

View File

@@ -31,6 +31,8 @@ addons:
- libssl-dev:i386 - libssl-dev:i386
- pkg-config:i386 - pkg-config:i386
- gcc-multilib - gcc-multilib
- "python3"
- "python3-pip"
node_js: node_js:
- "10" - "10"
@@ -67,6 +69,7 @@ jobs:
install: install:
- pip install --user PyYaml -q - pip install --user PyYaml -q
- pip install --user beautifulsoup4 -q - pip install --user beautifulsoup4 -q
- pip3 install --user bidict -q
- chmod +x ./vueui/install - chmod +x ./vueui/install
- ./vueui/install - ./vueui/install
- chmod +x ./scripts/rust_g.sh - chmod +x ./scripts/rust_g.sh
@@ -84,3 +87,4 @@ jobs:
- md5sum -c - <<< "51704c822012212faa577079db2e11fd *vueui/template.html" - md5sum -c - <<< "51704c822012212faa577079db2e11fd *vueui/template.html"
- python tools/TagMatcher/tag-matcher.py ../.. - python tools/TagMatcher/tag-matcher.py ../..
- python tools/GenerateChangelog/ss13_genchangelog.py html/changelog.html html/changelogs --dry-run - python tools/GenerateChangelog/ss13_genchangelog.py html/changelog.html html/changelogs --dry-run
- python3 tools/mapmerge2/travis_mapcheck.py

View File

@@ -3,11 +3,11 @@
/datum/event/communications_blackout/announce() /datum/event/communications_blackout/announce()
var/alert = pick( "Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you*%fj00)`5vc-BZZT", \ var/alert = pick( "Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you*%fj00)`5vc-BZZT", \
"Ionospheric anomalies detected. Temporary telecommunication failu*3mga;b4;'1v<EFBFBD>-BZZZT", \ "Ionospheric anomalies detected. Temporary telecommunication failu*3mga;b4;'1v-BZZZT", \
"Ionospheric anomalies detected. Temporary telec#MCi46:5.;@63-BZZZZT", \ "Ionospheric anomalies detected. Temporary telec#MCi46:5.;@63-BZZZZT", \
"Ionospheric anomalies dete'fZ\\kg5_0-BZZZZZT", \ "Ionospheric anomalies dete'fZ\\kg5_0-BZZZZZT", \
"Ionospheri:%<EFBFBD> MCayj^j<.3-BZZZZZZT", \ "Ionospheri:% MCayj^j<.3-BZZZZZZT", \
"#4nd%;f4y6,><EFBFBD>%-BZZZZZZZT") "#4nd%;f4y6,>%-BZZZZZZZT")
for(var/mob/living/silicon/ai/A in player_list) //AIs are always aware of communication blackouts. for(var/mob/living/silicon/ai/A in player_list) //AIs are always aware of communication blackouts.
A << "<br>" A << "<br>"

View File

@@ -5,33 +5,6 @@
"ab" = ( "ab" = (
/turf/simulated/shuttle/wall, /turf/simulated/shuttle/wall,
/area/derelict/ship) /area/derelict/ship)
"ac" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_straight";
dir = 4
},
/area/derelict/ship)
"ad" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_t";
dir = 1
},
/area/derelict/ship)
"ae" = (
/turf/template_noop,
/turf/simulated/shuttle/wall{
icon_state = "swall_c"
},
/area/derelict/ship)
"af" = (
/turf/simulated/shuttle/floor{
icon_state = "floor3"
},
/turf/simulated/shuttle/wall{
icon_state = "swall_c";
dir = 8
},
/area/derelict/ship)
"ag" = ( "ag" = (
/turf/simulated/shuttle/floor{ /turf/simulated/shuttle/floor{
icon_state = "floor3" icon_state = "floor3"
@@ -47,26 +20,6 @@
icon_state = "floor3" icon_state = "floor3"
}, },
/area/derelict/ship) /area/derelict/ship)
"ai" = (
/turf/simulated/shuttle/floor{
icon_state = "floor3"
},
/turf/simulated/shuttle/wall{
icon_state = "swall_c";
dir = 1
},
/area/derelict/ship)
"aj" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_t"
},
/area/derelict/ship)
"ak" = (
/turf/simulated/shuttle/wall{
icon_state = "swall15";
dir = 2
},
/area/derelict/ship)
"al" = ( "al" = (
/obj/structure/shuttle/engine/propulsion{ /obj/structure/shuttle/engine/propulsion{
icon_state = "propulsion_r"; icon_state = "propulsion_r";
@@ -74,12 +27,6 @@
}, },
/turf/template_noop, /turf/template_noop,
/area/derelict/ship) /area/derelict/ship)
"am" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_t";
dir = 4
},
/area/derelict/ship)
"an" = ( "an" = (
/obj/machinery/computer/med_data, /obj/machinery/computer/med_data,
/turf/simulated/shuttle/floor{ /turf/simulated/shuttle/floor{
@@ -96,13 +43,6 @@
icon_state = "floor3" icon_state = "floor3"
}, },
/area/derelict/ship) /area/derelict/ship)
"ap" = (
/turf/simulated/shuttle/plating,
/turf/simulated/shuttle/wall{
icon_state = "swall_c";
dir = 8
},
/area/derelict/ship)
"aq" = ( "aq" = (
/obj/structure/shuttle/engine/heater{ /obj/structure/shuttle/engine/heater{
icon_state = "heater"; icon_state = "heater";
@@ -120,26 +60,12 @@
}, },
/turf/template_noop, /turf/template_noop,
/area/derelict/ship) /area/derelict/ship)
"as" = (
/turf/simulated/shuttle/floor{
icon_state = "floor3"
},
/turf/simulated/shuttle/wall{
icon_state = "swall_c"
},
/area/derelict/ship)
"at" = ( "at" = (
/obj/item/weapon/scalpel, /obj/item/weapon/scalpel,
/turf/simulated/shuttle/floor{ /turf/simulated/shuttle/floor{
icon_state = "floor3" icon_state = "floor3"
}, },
/area/derelict/ship) /area/derelict/ship)
"au" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_t";
dir = 8
},
/area/derelict/ship)
"av" = ( "av" = (
/turf/simulated/shuttle/plating, /turf/simulated/shuttle/plating,
/area/derelict/ship) /area/derelict/ship)
@@ -203,11 +129,6 @@
icon_state = "floor3" icon_state = "floor3"
}, },
/area/derelict/ship) /area/derelict/ship)
"aD" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_straight"
},
/area/derelict/ship)
"aE" = ( "aE" = (
/obj/item/device/multitool, /obj/item/device/multitool,
/turf/simulated/shuttle/floor{ /turf/simulated/shuttle/floor{
@@ -227,13 +148,6 @@
/obj/machinery/door/unpowered/shuttle, /obj/machinery/door/unpowered/shuttle,
/turf/simulated/floor/plating, /turf/simulated/floor/plating,
/area/derelict/ship) /area/derelict/ship)
"aH" = (
/turf/simulated/shuttle/plating,
/turf/simulated/shuttle/wall{
icon_state = "swall_c";
dir = 4
},
/area/derelict/ship)
"aI" = ( "aI" = (
/obj/structure/shuttle/engine/propulsion{ /obj/structure/shuttle/engine/propulsion{
icon_state = "propulsion_l"; icon_state = "propulsion_l";
@@ -241,11 +155,6 @@
}, },
/turf/template_noop, /turf/template_noop,
/area/derelict/ship) /area/derelict/ship)
"aJ" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_s"
},
/area/derelict/ship)
"aK" = ( "aK" = (
/obj/structure/grille, /obj/structure/grille,
/obj/structure/window/reinforced{ /obj/structure/window/reinforced{
@@ -268,15 +177,6 @@
icon_state = "floor3" icon_state = "floor3"
}, },
/area/derelict/ship) /area/derelict/ship)
"aM" = (
/turf/simulated/shuttle/floor{
icon_state = "floor3"
},
/turf/simulated/shuttle/wall{
icon_state = "swall_c";
dir = 4
},
/area/derelict/ship)
"aN" = ( "aN" = (
/obj/machinery/door/airlock/glass, /obj/machinery/door/airlock/glass,
/turf/simulated/shuttle/floor{ /turf/simulated/shuttle/floor{
@@ -398,24 +298,6 @@
"bb" = ( "bb" = (
/turf/simulated/shuttle/wall, /turf/simulated/shuttle/wall,
/area/template_noop) /area/template_noop)
"bc" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_t";
dir = 1
},
/area/template_noop)
"bd" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_straight";
dir = 4
},
/area/template_noop)
"be" = (
/turf/template_noop,
/turf/simulated/shuttle/wall{
icon_state = "swall_c"
},
/area/template_noop)
"bf" = ( "bf" = (
/obj/structure/table/standard, /obj/structure/table/standard,
/obj/item/device/analyzer, /obj/item/device/analyzer,
@@ -446,41 +328,11 @@
icon_state = "floor3" icon_state = "floor3"
}, },
/area/derelict/ship) /area/derelict/ship)
"bj" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_t";
dir = 8
},
/area/template_noop)
"bk" = (
/turf/simulated/shuttle/floor{
icon_state = "floor3"
},
/turf/simulated/shuttle/wall{
icon_state = "swall_c";
dir = 8
},
/area/template_noop)
"bl" = ( "bl" = (
/turf/simulated/shuttle/floor{ /turf/simulated/shuttle/floor{
icon_state = "floor3" icon_state = "floor3"
}, },
/area/template_noop) /area/template_noop)
"bm" = (
/turf/simulated/shuttle/floor{
icon_state = "floor3"
},
/turf/simulated/shuttle/wall{
icon_state = "swall_c";
dir = 1
},
/area/template_noop)
"bn" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_t";
dir = 4
},
/area/template_noop)
"bo" = ( "bo" = (
/obj/machinery/door/window/northright, /obj/machinery/door/window/northright,
/obj/effect/decal/remains/human, /obj/effect/decal/remains/human,
@@ -513,35 +365,12 @@
icon_state = "floor3" icon_state = "floor3"
}, },
/area/template_noop) /area/template_noop)
"bs" = (
/turf/template_noop,
/turf/simulated/shuttle/wall{
icon_state = "swall_c";
dir = 8
},
/area/derelict/ship)
"bt" = (
/turf/template_noop,
/turf/simulated/shuttle/wall{
icon_state = "swall_c";
dir = 1
},
/area/derelict/ship)
"bu" = ( "bu" = (
/obj/structure/table, /obj/structure/table,
/turf/simulated/shuttle/floor{ /turf/simulated/shuttle/floor{
icon_state = "floor3" icon_state = "floor3"
}, },
/area/template_noop) /area/template_noop)
"bv" = (
/turf/simulated/shuttle/floor{
icon_state = "floor3"
},
/turf/simulated/shuttle/wall{
icon_state = "swall_c";
dir = 4
},
/area/template_noop)
"bw" = ( "bw" = (
/obj/structure/grille, /obj/structure/grille,
/obj/structure/window/reinforced, /obj/structure/window/reinforced,
@@ -564,25 +393,6 @@
icon_state = "floor3" icon_state = "floor3"
}, },
/area/derelict/ship) /area/derelict/ship)
"by" = (
/turf/template_noop,
/turf/simulated/shuttle/wall{
icon_state = "swall_c";
dir = 1
},
/area/template_noop)
"bz" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_t"
},
/area/template_noop)
"bA" = (
/turf/template_noop,
/turf/simulated/shuttle/wall{
icon_state = "swall_c";
dir = 8
},
/area/template_noop)
"bB" = ( "bB" = (
/obj/item/weapon/material/shard{ /obj/item/weapon/material/shard{
icon_state = "medium" icon_state = "medium"
@@ -679,13 +489,6 @@
icon_state = "floor3" icon_state = "floor3"
}, },
/area/derelict/ship) /area/derelict/ship)
"bM" = (
/turf/simulated/shuttle/plating,
/turf/simulated/shuttle/wall{
icon_state = "swall_c";
dir = 1
},
/area/derelict/ship)
"bN" = ( "bN" = (
/obj/item/trash/cheesie, /obj/item/trash/cheesie,
/turf/template_noop, /turf/template_noop,
@@ -723,12 +526,6 @@
icon_state = "floor3" icon_state = "floor3"
}, },
/area/derelict/ship) /area/derelict/ship)
"bT" = (
/turf/simulated/shuttle/plating,
/turf/simulated/shuttle/wall{
icon_state = "swall_c"
},
/area/derelict/ship)
"bU" = ( "bU" = (
/obj/machinery/computer/pod{ /obj/machinery/computer/pod{
id = "oldship_gun" id = "oldship_gun"
@@ -737,12 +534,6 @@
icon_state = "floor3" icon_state = "floor3"
}, },
/area/derelict/ship) /area/derelict/ship)
"bV" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_s";
dir = 1
},
/area/derelict/ship)
"bW" = ( "bW" = (
/obj/structure/table/standard, /obj/structure/table/standard,
/obj/item/weapon/screwdriver, /obj/item/weapon/screwdriver,

View File

@@ -19,6 +19,7 @@ THE SOFTWARE.
''' '''
import argparse, re, sys import argparse, re, sys
from os import path, walk from os import path, walk
import io
opt = argparse.ArgumentParser() opt = argparse.ArgumentParser()
opt.add_argument('dir', help='The directory to scan for *.dm files with non-matching spans') opt.add_argument('dir', help='The directory to scan for *.dm files with non-matching spans')
@@ -30,11 +31,11 @@ if(not path.isdir(args.dir)):
# These tuples are expected to be ordered as: # These tuples are expected to be ordered as:
# A unique human readable name (henceforth referred to as tuple name), a regex pattern matching an opening tag, a regex pattern matching a closing tag # A unique human readable name (henceforth referred to as tuple name), a regex pattern matching an opening tag, a regex pattern matching a closing tag
tag_tuples = [ ('<span>', re.compile('<span(.*?)>', re.IGNORECASE), re.compile('</span>', re.IGNORECASE)), tag_tuples = [('<span>', re.compile('<span(.*?)>', re.IGNORECASE), re.compile('</span>', re.IGNORECASE)),
('<font>', re.compile('<font(.*?)>', re.IGNORECASE), re.compile('</font>', re.IGNORECASE)), ('<font>', re.compile('<font(.*?)>', re.IGNORECASE), re.compile('</font>', re.IGNORECASE)),
('<center>', re.compile('<center>', re.IGNORECASE), re.compile('</center>', re.IGNORECASE)), ('<center>', re.compile('<center>', re.IGNORECASE), re.compile('</center>', re.IGNORECASE)),
('<b>', re.compile('<b>', re.IGNORECASE), re.compile('</b>', re.IGNORECASE)), ('<b>', re.compile('<b>', re.IGNORECASE), re.compile('</b>', re.IGNORECASE)),
('<i>', re.compile('<i>', re.IGNORECASE), re.compile('</i>', re.IGNORECASE))] ('<i>', re.compile('<i>', re.IGNORECASE), re.compile('</i>', re.IGNORECASE))]
# The keys of this dictionary will be the file path of each parsed *.dm file # The keys of this dictionary will be the file path of each parsed *.dm file
# The values of this dictionary will be in the format provided by populate_match_list(). # The values of this dictionary will be in the format provided by populate_match_list().
@@ -43,60 +44,60 @@ matches = { }
# Support def for setting up a dictionary, populating it with all defined tuple names as key with each being assigned the value 0. # Support def for setting up a dictionary, populating it with all defined tuple names as key with each being assigned the value 0.
# One such dictionary is created for each parsed file. # One such dictionary is created for each parsed file.
def populate_match_list(): def populate_match_list():
match_list = { } match_list = { }
for tag_tuple in tag_tuples: for tag_tuple in tag_tuples:
match_list[tag_tuple[0]] = 0 match_list[tag_tuple[0]] = 0
return match_list return match_list
# This def shall be provided by a dictionary in the format given by populate_match_list() and a line of text. # This def shall be provided by a dictionary in the format given by populate_match_list() and a line of text.
# It loops over all defined tag tuples, adding the number of open tags found and subtracting the number of close tag found to the corresponding tuple name in the match list. # It loops over all defined tag tuples, adding the number of open tags found and subtracting the number of close tag found to the corresponding tuple name in the match list.
# This def is currently run with the same match_list for a given file and for all lines of that file. # This def is currently run with the same match_list for a given file and for all lines of that file.
def get_tag_matches(match_list, line): def get_tag_matches(match_list, line):
for tag_tuple in tag_tuples: for tag_tuple in tag_tuples:
match_list[tag_tuple[0]] += len(tag_tuple[1].findall(line)) match_list[tag_tuple[0]] += len(tag_tuple[1].findall(line))
match_list[tag_tuple[0]] -= len(tag_tuple[2].findall(line)) match_list[tag_tuple[0]] -= len(tag_tuple[2].findall(line))
return return
# Support def that simply checks if a given dictionary in the format given by populate_match_list() contains any value that is non-zero. # Support def that simply checks if a given dictionary in the format given by populate_match_list() contains any value that is non-zero.
# That is, a tag which had a non-equal amount of open/closing tags. # That is, a tag which had a non-equal amount of open/closing tags.
def has_mismatch(match_list): def has_mismatch(match_list):
for tag, match_number in match_list.iteritems(): for tag, match_number in match_list.items():
if(match_number != 0): if(match_number != 0):
return 1 return 1
return 0 return 0
# This section parses all *.dm files in the given directory, recursively. # This section parses all *.dm files in the given directory, recursively.
for root, subdirs, files in walk(args.dir): for root, subdirs, files in walk(args.dir):
for filename in files: for filename in files:
if filename.endswith('.dm'): if filename.endswith('.dm'):
file_path = path.join(root, filename) file_path = path.join(root, filename)
with open(file_path, 'r') as f: with io.open(file_path, 'r') as f:
# For each file, generate the match dictionary. # For each file, generate the match dictionary.
matches[file_path] = populate_match_list() matches[file_path] = populate_match_list()
for x in f: for x in f:
# Then for each line in the file, conduct the tuple open/close matching. # Then for each line in the file, conduct the tuple open/close matching.
get_tag_matches(matches[file_path], x) get_tag_matches(matches[file_path], x)
# Pretty printing section. # Pretty printing section.
# Loops over all matches and checks if there is a mismatch of tags. # Loops over all matches and checks if there is a mismatch of tags.
# If so, then and only then is the corresponding file path printed along with the number of unmatched open/close tags. # If so, then and only then is the corresponding file path printed along with the number of unmatched open/close tags.
total_mismatch = 0 total_mismatch = 0
for file, match_list in matches.iteritems(): for file, match_list in matches.items():
if(has_mismatch(match_list)): if(has_mismatch(match_list)):
print(file) print(file)
for tag, match_number in match_list.iteritems(): for tag, match_number in match_list.items():
# A positive number means an excess of opening tag, a negative number means an excess of closing tags. # A positive number means an excess of opening tag, a negative number means an excess of closing tags.
if(match_number > 0): if(match_number > 0):
total_mismatch += match_number total_mismatch += match_number
print('\t{0} - Excess of {1} opening tag(s)'.format(tag, match_number)) print('\t{0} - Excess of {1} opening tag(s)'.format(tag, match_number))
elif (match_number < 0): elif (match_number < 0):
total_mismatch -= match_number total_mismatch -= match_number
print('\t{0} - Excess of {1} closing tag(s)'.format(tag, -match_number)) print('\t{0} - Excess of {1} closing tag(s)'.format(tag, -match_number))
# Simply prints the total number of mismatches found and if so returns 1 to, for example, fail Travis builds. # Simply prints the total number of mismatches found and if so returns 1 to, for example, fail Travis builds.
if(total_mismatch == 0): if(total_mismatch == 0):
print('No mismatches found.') print('No mismatches found.')
else: else:
print('') print('')
print('Total number of mismatches: {0}'.format(total_mismatch)) print('Total number of mismatches: {0}'.format(total_mismatch))
sys.exit(1) sys.exit(1)

View File

@@ -64,12 +64,12 @@ class DMM:
max_key = max_key_for(self.key_length) max_key = max_key_for(self.key_length)
bad_keys = {key: 0 for key in self.dictionary.keys() if key > max_key} bad_keys = {key: 0 for key in self.dictionary.keys() if key > max_key}
if bad_keys: if bad_keys:
print(f"Warning: fixing {len(bad_keys)} overflowing keys") print("Warning: fixing {} overflowing keys".format(len(bad_keys)))
for k in bad_keys: for k in bad_keys:
# create a new non-bogus key and transfer that value to it # create a new non-bogus key and transfer that value to it
new_key = bad_keys[k] = self.generate_new_key() new_key = bad_keys[k] = self.generate_new_key()
self.dictionary.forceput(new_key, self.dictionary[k]) self.dictionary.forceput(new_key, self.dictionary[k])
print(f" {num_to_key(k, self.key_length, True)} -> {num_to_key(new_key, self.key_length)}") print(" {} -> {}".format(num_to_key(k, self.key_length, True), num_to_key(new_key, self.key_length)))
for k, v in self.grid.items(): for k, v in self.grid.items():
# reassign the grid entries which used the old key # reassign the grid entries which used the old key
self.grid[k] = bad_keys.get(v, v) self.grid[k] = bad_keys.get(v, v)
@@ -79,7 +79,7 @@ class DMM:
free_keys = max_key_for(self.key_length) - len(self.dictionary) free_keys = max_key_for(self.key_length) - len(self.dictionary)
while free_keys < desired: while free_keys < desired:
if self.key_length >= MAX_KEY_LENGTH: if self.key_length >= MAX_KEY_LENGTH:
raise KeyTooLarge(f"can't expand beyond key length {MAX_KEY_LENGTH} ({len(self.dictionary)} keys)") raise KeyTooLarge("can't expand beyond key length {} ({} keys)".format(MAX_KEY_LENGTH, len(self.dictionary)))
self.key_length += 1 self.key_length += 1
free_keys = max_key_for(self.key_length) - len(self.dictionary) free_keys = max_key_for(self.key_length) - len(self.dictionary)
return free_keys return free_keys
@@ -119,7 +119,7 @@ def key_to_num(key):
def num_to_key(num, key_length, allow_overflow=False): def num_to_key(num, key_length, allow_overflow=False):
if num >= (BASE ** key_length if allow_overflow else max_key_for(key_length)): if num >= (BASE ** key_length if allow_overflow else max_key_for(key_length)):
raise KeyTooLarge(f"num={num} does not fit in key_length={key_length}") raise KeyTooLarge("num={} does not fit in key_length={}".format(num, key_length))
result = '' result = ''
while num: while num:
@@ -183,13 +183,13 @@ def parse_map_atom(atom):
# TGM writer # TGM writer
def save_tgm(dmm, output): def save_tgm(dmm, output):
output.write(f"{TGM_HEADER}\n") output.write("{}\n".format(TGM_HEADER))
if dmm.header: if dmm.header:
output.write(f"{dmm.header}\n") output.write("{}\n".format(dmm.header))
# write dictionary in tgm format # write dictionary in tgm format
for key, value in sorted(dmm.dictionary.items()): for key, value in sorted(dmm.dictionary.items()):
output.write(f'"{num_to_key(key, dmm.key_length)}" = (\n') output.write('"{}" = (\n'.format(num_to_key(key, dmm.key_length)))
for idx, thing in enumerate(value): for idx, thing in enumerate(value):
in_quote_block = False in_quote_block = False
in_varedit_block = False in_varedit_block = False
@@ -223,9 +223,9 @@ def save_tgm(dmm, output):
for z in range(1, max_z + 1): for z in range(1, max_z + 1):
output.write("\n") output.write("\n")
for x in range(1, max_x + 1): for x in range(1, max_x + 1):
output.write(f"({x},{1},{z}) = {{\"\n") output.write("({},{},{}) = {{\"\n".format(x, 1, z))
for y in range(1, max_y + 1): for y in range(1, max_y + 1):
output.write(f"{num_to_key(dmm.grid[x, y, z], dmm.key_length)}\n") output.write("{}\n".format(num_to_key(dmm.grid[x, y, z], dmm.key_length)))
output.write("\"}\n") output.write("\"}\n")
# ---------- # ----------
@@ -233,25 +233,25 @@ def save_tgm(dmm, output):
def save_dmm(dmm, output): def save_dmm(dmm, output):
if dmm.header: if dmm.header:
output.write(f"{dmm.header}\n") output.write("{}\n".format(dmm.header))
# writes a tile dictionary the same way Dreammaker does # writes a tile dictionary the same way Dreammaker does
for key, value in sorted(dmm.dictionary.items()): for key, value in sorted(dmm.dictionary.items()):
output.write(f'"{num_to_key(key, dmm.key_length)}" = ({",".join(value)})\n') output.write('"{}" = ({})\n'.format(num_to_key(key, dmm.key_length), ",".join(value)))
output.write("\n") output.write("\n")
# writes a map grid the same way Dreammaker does # writes a map grid the same way Dreammaker does
max_x, max_y, max_z = dmm.size max_x, max_y, max_z = dmm.size
for z in range(1, max_z + 1): for z in range(1, max_z + 1):
output.write(f"(1,1,{z}) = {{\"\n") output.write("(1,1,{}) = {{\"\n".format(z))
for y in range(1, max_y + 1): for y in range(1, max_y + 1):
for x in range(1, max_x + 1): for x in range(1, max_x + 1):
try: try:
output.write(num_to_key(dmm.grid[x, y, z], dmm.key_length)) output.write(num_to_key(dmm.grid[x, y, z], dmm.key_length))
except KeyError: except KeyError:
print(f"Key error: ({x}, {y}, {z})") print("Key error: ({}, {}, {})".format(x, y, z))
output.write("\n") output.write("\n")
output.write("\"}\n") output.write("\"}\n")

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
# Common code for the frontend interface of map tools # Common code for the frontend interface of map tools
import sys import sys
import os import os
@@ -93,26 +94,26 @@ def process(settings, verb, *, modify=True, backup=None):
return return
if modify: if modify:
print(f"Maps WILL{'' if settings.tgm else ' NOT'} be converted to tgm.") print("Maps WILL{} be converted to tgm.".format('' if settings.tgm else ' NOT'))
if backup: if backup:
print("Backups will be created with a \".before\" extension.") print("Backups will be created with a \".before\" extension.")
else: else:
print("Warning: backups are NOT being taken.") print("Warning: backups are NOT being taken.")
print(f"\nWill {verb} these maps:") print("\nWill {} these maps:".format(verb))
for path_str in maps: for path_str in maps:
print(pretty_path(settings, path_str)) print(pretty_path(settings, path_str))
try: try:
confirm = input(f"\nPress Enter to {verb}...\n") confirm = input("\nPress Enter to {}...\n".format(verb))
except KeyboardInterrupt: except KeyboardInterrupt:
confirm = "^C" confirm = "^C"
if confirm != "": if confirm != "":
print(f"\nAborted.") print("\nAborted.")
return return
for path_str in maps: for path_str in maps:
print(f' - {pretty_path(settings, path_str)}') print(" - {}".format(pretty_path(settings, path_str)))
if backup: if backup:
shutil.copyfile(path_str, path_str + ".before") shutil.copyfile(path_str, path_str + ".before")
@@ -120,7 +121,7 @@ def process(settings, verb, *, modify=True, backup=None):
try: try:
yield path_str yield path_str
except Exception as e: except Exception as e:
print(f"Error: {e}") print("Error: {}".format(e))
else: else:
print("Succeeded.") print("Succeeded.")

View File

@@ -7,14 +7,14 @@ from collections import defaultdict
def merge_map(new_map, old_map, delete_unused=False): def merge_map(new_map, old_map, delete_unused=False):
if new_map.key_length != old_map.key_length: if new_map.key_length != old_map.key_length:
print("Warning: Key lengths differ, taking new map") print("Warning: Key lengths differ, taking new map")
print(f" Old: {old_map.key_length}") print(" Old: {}".format(old_map.key_length))
print(f" New: {new_map.key_length}") print(" New: {}".format(new_map.key_length))
return new_map return new_map
if new_map.size != old_map.size: if new_map.size != old_map.size:
print("Warning: Map dimensions differ, taking new map") print("Warning: Map dimensions differ, taking new map")
print(f" Old: {old_map.size}") print(" Old: {}".format(old_map.size))
print(f" New: {new_map.size}") print(" New: {}".format(new_map.size))
return new_map return new_map
key_length, size = old_map.key_length, old_map.size key_length, size = old_map.key_length, old_map.size
@@ -66,7 +66,7 @@ def merge_map(new_map, old_map, delete_unused=False):
# step two: delete unused keys # step two: delete unused keys
if unused_keys: if unused_keys:
print(f"Notice: Trimming {len(unused_keys)} unused dictionary keys.") print("Notice: Trimming {} unused dictionary keys.".format(len(unused_keys)))
for key in unused_keys: for key in unused_keys:
del merged.dictionary[key] del merged.dictionary[key]
@@ -75,10 +75,10 @@ def merge_map(new_map, old_map, delete_unused=False):
new_tile = new_map.dictionary[new_map.grid[x, y, z]] new_tile = new_map.dictionary[new_map.grid[x, y, z]]
merged_tile = merged.dictionary[merged.grid[x, y, z]] merged_tile = merged.dictionary[merged.grid[x, y, z]]
if new_tile != merged_tile: if new_tile != merged_tile:
print(f"Error: the map has been mangled! This is a mapmerge bug!") print("Error: the map has been mangled! This is a mapmerge bug!")
print(f"At {x},{y},{z}.") print("At {},{},{}.".format(x, y, z))
print(f"Should be {new_tile}") print("Should be {}".format(new_tile))
print(f"Instead is {merged_tile}") print("Instead is {}".format(merged_tile))
raise RuntimeError() raise RuntimeError()
return merged return merged

View File

@@ -20,12 +20,12 @@ def main(repo):
head_blob = repo[repo[repo.head.target].tree[path].id] head_blob = repo[repo[repo.head.target].tree[path].id]
except KeyError: except KeyError:
# New map, no entry in HEAD # New map, no entry in HEAD
print(f"Converting new map: {path}") print("Converting new map: {}".format(path))
assert (status & pygit2.GIT_STATUS_INDEX_NEW) assert (status & pygit2.GIT_STATUS_INDEX_NEW)
merged_map = index_map merged_map = index_map
else: else:
# Entry in HEAD, merge the index over it # Entry in HEAD, merge the index over it
print(f"Merging map: {path}") print("Merging map: {}".format(path))
assert not (status & pygit2.GIT_STATUS_INDEX_NEW) assert not (status & pygit2.GIT_STATUS_INDEX_NEW)
head_map = dmm.DMM.from_bytes(head_blob.read_raw()) head_map = dmm.DMM.from_bytes(head_blob.read_raw())
merged_map = merge_map(index_map, head_map) merged_map = merge_map(index_map, head_map)
@@ -37,13 +37,13 @@ def main(repo):
# write to the working directory if that's clean # write to the working directory if that's clean
if status & (pygit2.GIT_STATUS_WT_DELETED | pygit2.GIT_STATUS_WT_MODIFIED): if status & (pygit2.GIT_STATUS_WT_DELETED | pygit2.GIT_STATUS_WT_MODIFIED):
print(f"Warning: {path} has unindexed changes, not overwriting them") print("Warning: {} has unindexed changes, not overwriting them".format(path))
else: else:
merged_map.to_file(os.path.join(repo.workdir, path)) merged_map.to_file(os.path.join(repo.workdir, path))
if changed: if changed:
repo.index.write() repo.index.write()
print(f"Merged {changed} maps.") print("Merged {} maps.".format(changed))
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -0,0 +1,81 @@
#!/usr/bin/env python3
import os
from collections import defaultdict
import shutil
import pathlib
from dmm import *
def map_check(map, name):
key_length, size = map.key_length, map.size
merged = DMM(key_length, size)
merged.dictionary = map.dictionary.copy()
known_keys = dict() # mapping fron 'new' key to 'merged' key
unused_keys = set(map.dictionary.keys()) # keys going unused
# step one: parse the new version, compare it to the old version, merge both
for z, y, x in map.coords_zyx:
new_key = map.grid[x, y, z]
# if this key has been processed before, it can immediately be merged
try:
merged.grid[x, y, z] = known_keys[new_key]
continue
except KeyError:
pass
def select_key(assigned):
merged.grid[x, y, z] = known_keys[new_key] = assigned
old_key = map.grid[x, y, z]
old_tile = map.dictionary[old_key]
new_tile = map.dictionary[new_key]
# this tile is the exact same as before, so the old key is used
if new_tile == old_tile:
select_key(old_key)
unused_keys.remove(old_key)
# the tile is different here, but if it exists in the merged dictionary, that key can be used
elif new_tile in merged.dictionary.inv:
newold_key = merged.dictionary.inv[new_tile]
select_key(newold_key)
unused_keys.remove(newold_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 old_tile not in map.dictionary.inv and old_key in unused_keys:
merged.dictionary[old_key] = new_tile
select_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 = merged.generate_new_key()
merged.dictionary[fresh_key] = new_tile
select_key(fresh_key)
# step two: delete unused keys
if unused_keys:
print("Error: {} unused dictionary keys. Please run mapmerge2 on {} locally to trim them.".format(len(unused_keys), name[42:len(name)]))
exit(1)
try:
merged._ensure_free_keys(0)
max_key = max_key_for(merged.key_length)
except KeyTooLarge:
print("\nKey is too large, run mapmerge2 on {} locally and fix errors.".format(fname[42:len(fname)]))
exit(1)
bad_keys = {key: 0 for key in merged.dictionary.keys() if key > max_key}
if bad_keys:
print("\nBad keys detected, please run mapmerge2 on {} locally and fix errors.".format(fname[42:len(fname)]))
exit(1)
if __name__ == '__main__':
list_of_files = list()
for root, directories, filenames in os.walk("/home/travis/build/Aurorastation/Aurora.3/maps/"):
for filename in [f for f in filenames if f.endswith(".dmm")]:
list_of_files.append(str(pathlib.Path(root, filename)))
for fname in list_of_files:
map = DMM.from_file(fname)
map_check(map, fname)
print("Maps scanning complete, no issues were found.")