Files
VOREStation/tools/maplint/source/dmm.py
T
Drathek 9f124e5b14 Tooling Update and Maplint Port (#17199)
* Initial

* Remove corrupt dmis

* Fixup maps in TGM format

4e5a32721f: maps/_templates_and_guidance/Public Event Templates/Maze_Reward_-_Copy.dmm
4e5a32721f: maps/_templates_and_guidance/Templates/shelter_Medical.dmm
4e5a32721f: maps/expedition_vr/aerostat/aerostat.dmm
4e5a32721f: maps/expedition_vr/aerostat/aerostat_science_outpost.dmm
4e5a32721f: maps/expedition_vr/beach/submaps/deadBeacon.dmm
4e5a32721f: maps/expedition_vr/wild/tether_wild-crash-alt.dmm
4e5a32721f: maps/expedition_vr/wild/tether_wild-crash.dmm
4e5a32721f: maps/expedition_vr/wild/tether_wild-surface.dmm
4e5a32721f: maps/expedition_vr/wild/tether_wild-temple.dmm
4e5a32721f: maps/gateway_vr/lucky_7.dmm
4e5a32721f: maps/gateway_vr/snow_outpost.dmm
4e5a32721f: maps/overmap/_map.dmm
4e5a32721f: maps/overmap/bearcat/bearcat.dmm
4e5a32721f: maps/overmap/example_sector1.dmm
4e5a32721f: maps/overmap/example_sector2.dmm
4e5a32721f: maps/redgate/falls/falls.dmm
4e5a32721f: maps/submaps/pois_vr/aerostat/CaveS.dmm
4e5a32721f: maps/submaps/pois_vr/aerostat/DeadSettlers1.dmm
4e5a32721f: maps/submaps/pois_vr/aerostat/DeadSettlers2.dmm
4e5a32721f: maps/submaps/pois_vr/aerostat/DoomP.dmm
4e5a32721f: maps/submaps/pois_vr/aerostat/Lab1.dmm
4e5a32721f: maps/submaps/pois_vr/aerostat/Rockybase.dmm
4e5a32721f: maps/submaps/pois_vr/debris_field/debris14.dmm
4e5a32721f: maps/submaps/pois_vr/debris_field/derelict.dmm
4e5a32721f: maps/submaps/pois_vr/debris_field/new_escapepod_xeno.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/BlastMine1.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/CaveTrench.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/Cavelake.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/Cliff1.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/CrashedMedShuttle1.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/Geyser1.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/Geyser2.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/Geyser3.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/Mineshaft1.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/Scave1.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/SupplyDrop1.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/crashed_ufo.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/crashed_ufo_frigate.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/crashedcontainmentshuttle.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/crystal1.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/crystal2.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/crystal3.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/deadBeacon.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/deadly_rabbit_vr.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/deadspy.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/digsite.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/excavation1.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/lava_trench.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/prepper1.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/ritual.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/spatial_anomaly.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/speakeasy_vr.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/vault1.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/vault2.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/vault3.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/vault4.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/vault5.dmm
4e5a32721f: maps/submaps/surface_submaps/mountains/vault6.dmm
4e5a32721f: maps/submaps/surface_submaps/plains/Boathouse.dmm
4e5a32721f: maps/submaps/surface_submaps/plains/BuriedTreasure.dmm
4e5a32721f: maps/submaps/surface_submaps/plains/BuriedTreasure2.dmm
4e5a32721f: maps/submaps/surface_submaps/plains/BuriedTreasure3.dmm
4e5a32721f: maps/submaps/surface_submaps/plains/Oldhouse.dmm
4e5a32721f: maps/submaps/surface_submaps/plains/PooledR.dmm
4e5a32721f: maps/submaps/surface_submaps/plains/Rocky5.dmm
4e5a32721f: maps/submaps/surface_submaps/plains/Shakden.dmm
4e5a32721f: maps/submaps/surface_submaps/plains/Thiefc.dmm
4e5a32721f: maps/submaps/surface_submaps/plains/beacons.dmm
4e5a32721f: maps/submaps/surface_submaps/plains/chemspill1.dmm
4e5a32721f: maps/submaps/surface_submaps/plains/farm1.dmm
4e5a32721f: maps/submaps/surface_submaps/plains/house1.dmm
4e5a32721f: maps/submaps/surface_submaps/plains/lonehome.dmm
4e5a32721f: maps/submaps/surface_submaps/plains/smol2.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Blackshuttledown.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Blueshuttledown.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Boombase.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/CaveS.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Chapel.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Cragzone1.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/DJOutpost1.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/DJOutpost2.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/DJOutpost3.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/DJOutpost4.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/DecoupledEngine.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/DoomP.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Drugden.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Epod3.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Epod4.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Flake.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/FrostflyNest.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/MCamp1.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/MHR.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Manor1.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Mudpit.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Rocky1.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Rocky3.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Rocky4.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Rockybase.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Shack1.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Shelter.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Smol1.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/Snowrock1.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/borglab.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/butchershack.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/chasm.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/chemspill2.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/deathden.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/derelictengine.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/frostoasis.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/kururakden.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/spider1.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/wolfden.dmm
4e5a32721f: maps/submaps/surface_submaps/wilderness/xenohive.dmm
4e5a32721f: maps/tether/tether-02-surface2.dmm
4e5a32721f: maps/virgo_minitest/virgo_minitest-sector-2.dmm

Automatically commited by: tools\mapmerge2\fixup.py

* Remove unnecessary whitespace edits from mapmerger

* Cable dirs update path

* Fix area var edits

* Put the area over there

* Ignore archive maps folder

* Forgot to port multivar support too

* A few changes I forgot about for hook install

* restore multivar support that chomp doesn't have yet

* ban those

* Forgot to add code for the marker too

* Couple more of these invalid cables were added in master

* Update multiple_blood_effects.yml

* Update multiple_blood_effects.yml

* Fixup maps in TGM format

612ca9cbb9: maps/tether/submaps/tether_misc.dmm

Automatically commited by: tools\mapmerge2\fixup.py

* Fixup now logs the map its currently checking

* Final fixes?

* Fixup maps in TGM format

3078e5cd0a: maps/expedition_vr/beach/submaps/crashedcontainmentshuttle.dmm
3078e5cd0a: maps/redgate/fantasy_dungeon.dmm
3078e5cd0a: maps/submaps/pois_vr/aerostat/Rockybase.dmm
3078e5cd0a: maps/submaps/surface_submaps/mountains/crashedcontainmentshuttle_vr.dmm
3078e5cd0a: maps/submaps/surface_submaps/plains/Oldhouse_vr.dmm
3078e5cd0a: maps/submaps/surface_submaps/plains/dogbase.dmm
3078e5cd0a: maps/submaps/surface_submaps/plains/greatwolfden.dmm
3078e5cd0a: maps/submaps/surface_submaps/plains/lonehome_vr.dmm
3078e5cd0a: maps/submaps/surface_submaps/plains/methlab.dmm
3078e5cd0a: maps/submaps/surface_submaps/plains/oldhotel.dmm
3078e5cd0a: maps/submaps/surface_submaps/plains/priderock.dmm
3078e5cd0a: maps/submaps/surface_submaps/wilderness/Rockybase.dmm
3078e5cd0a: maps/submaps/surface_submaps/wilderness/demonpool.dmm
3078e5cd0a: maps/submaps/surface_submaps/wilderness/dogbase.dmm
3078e5cd0a: maps/submaps/surface_submaps/wilderness/greatwolfden.dmm
3078e5cd0a: maps/tether/submaps/underdark_pois/abandonded_outpost.dmm
3078e5cd0a: maps/tether/submaps/underdark_pois/phoron_rat_den.dmm

Automatically commited by: tools\mapmerge2\fixup.py

* Fix tether_misc error

* Remap reused solar farm area

* Fix erroneous bearcat entries

* Fix weird whitespace (most archive maps also affected but didn't bother)

* misc mdb cleanup

* moar

* grr

---------

Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
2025-03-16 01:06:55 +01:00

196 lines
6.2 KiB
Python

# I know we already have one in mapmerge, but this one can afford to be significantly simpler to interface with
# by virtue of being read-only.
import re
from dataclasses import dataclass, field
from typing import IO
from .common import Constant, Filename, Null, Typepath
from .error import MapParseError, MaplintError
REGEX_POP_ID = re.compile(r'^"(?P<key>.+)" = \($')
REGEX_POP_CONTENT_HEADER = re.compile(r'^(?P<path>[/\w]+?)(?P<end>[{,)])$')
REGEX_ROW_BEGIN = re.compile(r'^\((?P<x>\d+),(?P<y>\d+),(?P<z>\d+)\) = {"$')
REGEX_VAR_EDIT = re.compile(r'^\t(?P<name>.+?) = (?P<definition>.+?);?$')
@dataclass
class Content:
path: Typepath
filename: str
starting_line: int
var_edits: dict[str, Constant] = field(default_factory = dict)
@dataclass
class DMM:
pops: dict[str, list[Content]] = field(default_factory = dict)
# Z -> X -> Y -> Pop
turfs: list[list[list[str]]] = field(default_factory = list)
def size(self):
return (len(self.turfs[0]), len(self.turfs[0][0]))
def turfs_for_pop(self, key: str):
for z, z_level in enumerate(self.turfs):
for x, x_level in enumerate(z_level):
for y, turf in enumerate(x_level):
if turf == key:
yield (x, y, z)
class DMMParser:
dmm: DMM
line = 0
def __init__(self, reader: IO):
self.dmm = DMM()
self.reader = reader
def parse(self):
if "dmm2tgm" not in self.next_line():
self.raise_error("Map isn't in TGM format. Consider using StrongDMM instead of Dream Maker.\n Please also consider installing the map merge tools, found through Install.bat in the tools/hooks folder.")
try:
while self.parse_pop():
pass
while self.parse_row():
pass
except MapParseError as error:
raise self.raise_error(error)
return self.dmm
def next_line(self):
self.line += 1
try:
return next(self.reader).removesuffix("\n")
except StopIteration:
return None
def parse_pop(self):
line = self.next_line()
if line == "":
return False
pop_match = REGEX_POP_ID.match(line)
if pop_match is None:
self.raise_error("Pops ended too early, expected a newline in between.")
pop_key = pop_match.group("key")
contents = []
while next_line := self.next_line():
next_line = next_line.rstrip()
content_match = REGEX_POP_CONTENT_HEADER.match(next_line)
if content_match is None:
self.raise_error("Pop content didn't lead to a path")
content = Content(Typepath(content_match.group("path")), self.reader.name, self.line)
contents.append(content)
content_end = content_match.group("end")
if content_end == ")":
break
elif content_end == "{":
while (var_edit := self.parse_var_edit()) is not None:
if var_edit[0] == None and var_edit[1] == None:
break
content.var_edits[var_edit[0]] = var_edit[1]
else:
continue # inner loop didn't break
break # inner loop did break indicating a })
elif content_end == ",":
continue
self.dmm.pops[pop_key] = contents
return True
def parse_var_edit(self):
line = self.next_line()
if line == "\t},":
return None
if line == "\t})":
return None, None
var_edit_match = REGEX_VAR_EDIT.match(line)
self.expect(var_edit_match is not None, "Var edits ended too early, expected a newline in between.")
return (var_edit_match.group("name"), self.parse_constant(var_edit_match.group("definition")))
def parse_constant(self, constant):
if (float_constant := self.safe_float(constant)) is not None:
if(constant.isdigit()):
return int(constant)
return float_constant
elif re.match(r'^/[/\w]+$', constant):
return Typepath(constant)
elif re.match(r'^".*"$', constant):
# This should do escaping in the future
return constant[1:-1]
elif re.match(r'^null$', constant):
return Null()
elif re.match(r"^'.*'$", constant):
return Filename(constant[1:-1])
elif (list_match := re.match(r'^list\((?P<contents>.*)\)$', constant)):
return ["NYI: list"]
elif (list_match := re.match(r'^newlist\((?P<contents>.*)\)$', constant)):
return ["NYI: newlist"]
else:
self.raise_error(f"Unknown constant type: {constant}")
def parse_row(self):
line = self.next_line()
if line is None:
return False
if line == "":
# Starting a new z level
return True
row_match = REGEX_ROW_BEGIN.match(line)
self.expect(row_match is not None, "Rows ended too early, expected a newline in between.")
self.expect(row_match.group("y") == "1", "TGM should only be producing individual rows.")
x = int(row_match.group("x")) - 1
z = int(row_match.group("z")) - 1
if len(self.dmm.turfs) <= z:
self.dmm.turfs.append([])
self.expect(len(self.dmm.turfs) == z + 1, "Z coordinate is not sequential")
z_level = self.dmm.turfs[z]
self.expect(len(z_level) == x, "X coordinate is not sequential")
contents = []
while (next_line := self.next_line()) is not None:
next_line = next_line.rstrip()
if next_line == '"}':
break
self.expect(next_line in self.dmm.pops, f"Pop {next_line} is not defined")
contents.append(next_line)
z_level.append(contents)
return True
def safe_float(self, value):
try:
return float(value)
except ValueError:
return None
def expect(self, condition, message):
if not condition:
self.raise_error(message)
def raise_error(self, message):
raise MaplintError(message, self.reader.name, self.line)
def parse_dmm(reader: IO):
return DMMParser(reader).parse()