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
- pkg-config:i386
- gcc-multilib
- "python3"
- "python3-pip"
node_js:
- "10"
@@ -67,6 +69,7 @@ jobs:
install:
- pip install --user PyYaml -q
- pip install --user beautifulsoup4 -q
- pip3 install --user bidict -q
- chmod +x ./vueui/install
- ./vueui/install
- chmod +x ./scripts/rust_g.sh
@@ -84,3 +87,4 @@ jobs:
- md5sum -c - <<< "51704c822012212faa577079db2e11fd *vueui/template.html"
- python tools/TagMatcher/tag-matcher.py ../..
- 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()
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 dete'fZ\\kg5_0-BZZZZZT", \
"Ionospheri:%<EFBFBD> MCayj^j<.3-BZZZZZZT", \
"#4nd%;f4y6,><EFBFBD>%-BZZZZZZZT")
"Ionospheri:% MCayj^j<.3-BZZZZZZT", \
"#4nd%;f4y6,>%-BZZZZZZZT")
for(var/mob/living/silicon/ai/A in player_list) //AIs are always aware of communication blackouts.
A << "<br>"

View File

@@ -5,33 +5,6 @@
"ab" = (
/turf/simulated/shuttle/wall,
/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" = (
/turf/simulated/shuttle/floor{
icon_state = "floor3"
@@ -47,26 +20,6 @@
icon_state = "floor3"
},
/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" = (
/obj/structure/shuttle/engine/propulsion{
icon_state = "propulsion_r";
@@ -74,12 +27,6 @@
},
/turf/template_noop,
/area/derelict/ship)
"am" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_t";
dir = 4
},
/area/derelict/ship)
"an" = (
/obj/machinery/computer/med_data,
/turf/simulated/shuttle/floor{
@@ -96,13 +43,6 @@
icon_state = "floor3"
},
/area/derelict/ship)
"ap" = (
/turf/simulated/shuttle/plating,
/turf/simulated/shuttle/wall{
icon_state = "swall_c";
dir = 8
},
/area/derelict/ship)
"aq" = (
/obj/structure/shuttle/engine/heater{
icon_state = "heater";
@@ -120,26 +60,12 @@
},
/turf/template_noop,
/area/derelict/ship)
"as" = (
/turf/simulated/shuttle/floor{
icon_state = "floor3"
},
/turf/simulated/shuttle/wall{
icon_state = "swall_c"
},
/area/derelict/ship)
"at" = (
/obj/item/weapon/scalpel,
/turf/simulated/shuttle/floor{
icon_state = "floor3"
},
/area/derelict/ship)
"au" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_t";
dir = 8
},
/area/derelict/ship)
"av" = (
/turf/simulated/shuttle/plating,
/area/derelict/ship)
@@ -203,11 +129,6 @@
icon_state = "floor3"
},
/area/derelict/ship)
"aD" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_straight"
},
/area/derelict/ship)
"aE" = (
/obj/item/device/multitool,
/turf/simulated/shuttle/floor{
@@ -227,13 +148,6 @@
/obj/machinery/door/unpowered/shuttle,
/turf/simulated/floor/plating,
/area/derelict/ship)
"aH" = (
/turf/simulated/shuttle/plating,
/turf/simulated/shuttle/wall{
icon_state = "swall_c";
dir = 4
},
/area/derelict/ship)
"aI" = (
/obj/structure/shuttle/engine/propulsion{
icon_state = "propulsion_l";
@@ -241,11 +155,6 @@
},
/turf/template_noop,
/area/derelict/ship)
"aJ" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_s"
},
/area/derelict/ship)
"aK" = (
/obj/structure/grille,
/obj/structure/window/reinforced{
@@ -268,15 +177,6 @@
icon_state = "floor3"
},
/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" = (
/obj/machinery/door/airlock/glass,
/turf/simulated/shuttle/floor{
@@ -398,24 +298,6 @@
"bb" = (
/turf/simulated/shuttle/wall,
/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" = (
/obj/structure/table/standard,
/obj/item/device/analyzer,
@@ -446,41 +328,11 @@
icon_state = "floor3"
},
/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" = (
/turf/simulated/shuttle/floor{
icon_state = "floor3"
},
/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" = (
/obj/machinery/door/window/northright,
/obj/effect/decal/remains/human,
@@ -513,35 +365,12 @@
icon_state = "floor3"
},
/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" = (
/obj/structure/table,
/turf/simulated/shuttle/floor{
icon_state = "floor3"
},
/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" = (
/obj/structure/grille,
/obj/structure/window/reinforced,
@@ -564,25 +393,6 @@
icon_state = "floor3"
},
/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" = (
/obj/item/weapon/material/shard{
icon_state = "medium"
@@ -679,13 +489,6 @@
icon_state = "floor3"
},
/area/derelict/ship)
"bM" = (
/turf/simulated/shuttle/plating,
/turf/simulated/shuttle/wall{
icon_state = "swall_c";
dir = 1
},
/area/derelict/ship)
"bN" = (
/obj/item/trash/cheesie,
/turf/template_noop,
@@ -723,12 +526,6 @@
icon_state = "floor3"
},
/area/derelict/ship)
"bT" = (
/turf/simulated/shuttle/plating,
/turf/simulated/shuttle/wall{
icon_state = "swall_c"
},
/area/derelict/ship)
"bU" = (
/obj/machinery/computer/pod{
id = "oldship_gun"
@@ -737,12 +534,6 @@
icon_state = "floor3"
},
/area/derelict/ship)
"bV" = (
/turf/simulated/shuttle/wall{
icon_state = "swall_s";
dir = 1
},
/area/derelict/ship)
"bW" = (
/obj/structure/table/standard,
/obj/item/weapon/screwdriver,

View File

@@ -19,6 +19,7 @@ THE SOFTWARE.
'''
import argparse, re, sys
from os import path, walk
import io
opt = argparse.ArgumentParser()
opt.add_argument('dir', help='The directory to scan for *.dm files with non-matching spans')
@@ -60,7 +61,7 @@ def get_tag_matches(match_list, line):
# 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.
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):
return 1
return 0
@@ -70,7 +71,7 @@ for root, subdirs, files in walk(args.dir):
for filename in files:
if filename.endswith('.dm'):
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.
matches[file_path] = populate_match_list()
for x in f:
@@ -81,10 +82,10 @@ for root, subdirs, files in walk(args.dir):
# 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.
total_mismatch = 0
for file, match_list in matches.iteritems():
for file, match_list in matches.items():
if(has_mismatch(match_list)):
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.
if(match_number > 0):
total_mismatch += match_number

View File

@@ -64,12 +64,12 @@ class DMM:
max_key = max_key_for(self.key_length)
bad_keys = {key: 0 for key in self.dictionary.keys() if key > max_key}
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:
# create a new non-bogus key and transfer that value to it
new_key = bad_keys[k] = self.generate_new_key()
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():
# reassign the grid entries which used the old key
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)
while free_keys < desired:
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
free_keys = max_key_for(self.key_length) - len(self.dictionary)
return free_keys
@@ -119,7 +119,7 @@ def key_to_num(key):
def num_to_key(num, key_length, allow_overflow=False):
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 = ''
while num:
@@ -183,13 +183,13 @@ def parse_map_atom(atom):
# TGM writer
def save_tgm(dmm, output):
output.write(f"{TGM_HEADER}\n")
output.write("{}\n".format(TGM_HEADER))
if dmm.header:
output.write(f"{dmm.header}\n")
output.write("{}\n".format(dmm.header))
# write dictionary in tgm format
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):
in_quote_block = False
in_varedit_block = False
@@ -223,9 +223,9 @@ def save_tgm(dmm, output):
for z in range(1, max_z + 1):
output.write("\n")
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):
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")
# ----------
@@ -233,25 +233,25 @@ def save_tgm(dmm, output):
def save_dmm(dmm, output):
if dmm.header:
output.write(f"{dmm.header}\n")
output.write("{}\n".format(dmm.header))
# writes a tile dictionary the same way Dreammaker does
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")
# writes a map grid the same way Dreammaker does
max_x, max_y, max_z = dmm.size
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 x in range(1, max_x + 1):
try:
output.write(num_to_key(dmm.grid[x, y, z], dmm.key_length))
except KeyError:
print(f"Key error: ({x}, {y}, {z})")
print("Key error: ({}, {}, {})".format(x, y, z))
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
import sys
import os
@@ -93,26 +94,26 @@ def process(settings, verb, *, modify=True, backup=None):
return
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:
print("Backups will be created with a \".before\" extension.")
else:
print("Warning: backups are NOT being taken.")
print(f"\nWill {verb} these maps:")
print("\nWill {} these maps:".format(verb))
for path_str in maps:
print(pretty_path(settings, path_str))
try:
confirm = input(f"\nPress Enter to {verb}...\n")
confirm = input("\nPress Enter to {}...\n".format(verb))
except KeyboardInterrupt:
confirm = "^C"
if confirm != "":
print(f"\nAborted.")
print("\nAborted.")
return
for path_str in maps:
print(f' - {pretty_path(settings, path_str)}')
print(" - {}".format(pretty_path(settings, path_str)))
if backup:
shutil.copyfile(path_str, path_str + ".before")
@@ -120,7 +121,7 @@ def process(settings, verb, *, modify=True, backup=None):
try:
yield path_str
except Exception as e:
print(f"Error: {e}")
print("Error: {}".format(e))
else:
print("Succeeded.")

View File

@@ -7,14 +7,14 @@ from collections import defaultdict
def merge_map(new_map, old_map, delete_unused=False):
if new_map.key_length != old_map.key_length:
print("Warning: Key lengths differ, taking new map")
print(f" Old: {old_map.key_length}")
print(f" New: {new_map.key_length}")
print(" Old: {}".format(old_map.key_length))
print(" New: {}".format(new_map.key_length))
return new_map
if new_map.size != old_map.size:
print("Warning: Map dimensions differ, taking new map")
print(f" Old: {old_map.size}")
print(f" New: {new_map.size}")
print(" Old: {}".format(old_map.size))
print(" New: {}".format(new_map.size))
return new_map
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
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:
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]]
merged_tile = merged.dictionary[merged.grid[x, y, z]]
if new_tile != merged_tile:
print(f"Error: the map has been mangled! This is a mapmerge bug!")
print(f"At {x},{y},{z}.")
print(f"Should be {new_tile}")
print(f"Instead is {merged_tile}")
print("Error: the map has been mangled! This is a mapmerge bug!")
print("At {},{},{}.".format(x, y, z))
print("Should be {}".format(new_tile))
print("Instead is {}".format(merged_tile))
raise RuntimeError()
return merged

View File

@@ -20,12 +20,12 @@ def main(repo):
head_blob = repo[repo[repo.head.target].tree[path].id]
except KeyError:
# 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)
merged_map = index_map
else:
# 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)
head_map = dmm.DMM.from_bytes(head_blob.read_raw())
merged_map = merge_map(index_map, head_map)
@@ -37,13 +37,13 @@ def main(repo):
# write to the working directory if that's clean
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:
merged_map.to_file(os.path.join(repo.workdir, path))
if changed:
repo.index.write()
print(f"Merged {changed} maps.")
print("Merged {} maps.".format(changed))
return 0
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.")