Last active
August 24, 2024 20:54
-
-
Save jasonsperske/31324a0cdffd8edf9683 to your computer and use it in GitHub Desktop.
Like my WADParser.py but for Duke Nukem 3D MAP files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import struct | |
import re | |
import io | |
from collections import namedtuple | |
GrpFileDefinition = namedtuple('GrpFileDefinition', 'name size') | |
Line = namedtuple('Line', 'a b is_one_sided') | |
class Grp(object): | |
"""Encapsulates the data found inside a GRP file""" | |
def __init__(self, grpFile): | |
"""Each GRP files contains the contents of several files""" | |
self.levels = [] | |
with open(grpFile, "rb") as f: | |
header_size = 12 | |
self.ken = f.read(12) | |
self.num_files = struct.unpack("<l", f.read(4))[0] | |
self.points = [] | |
file_defs = [] | |
for _ in range(self.num_files): | |
lump = f.read(16) | |
file_name = lump[0:12].decode('UTF-8').rstrip('\0') | |
file_size = struct.unpack("<l", lump[12:16])[0] | |
file_defs.append(GrpFileDefinition(file_name, file_size)) | |
for file_def in file_defs: | |
if(re.match('E\dL\d+\.MAP', file_def.name)): | |
self.levels.append(Level(file_def.name[:-4], io.BytesIO(f.read(file_def.size)))) | |
else: | |
#Skip over file because it's not a map | |
#print("Skipping %s (%d bytes)" % (file_def.name, file_def.size)) | |
f.read(file_def.size) | |
class Sector(object): | |
def __init__(self, data): | |
self.wallptr, self.wallnum = struct.unpack("<hh", data[0:4]) | |
def lines(self, level): | |
for index in range(self.wallptr, self.wallptr+self.wallnum): | |
a = level.points[index] | |
b = level.points[a.point2] | |
yield Line(a, b, a.nextsector == -1) | |
return | |
class Point(object): | |
def __init__(self, data): | |
self.x, self.y = struct.unpack("<ll", data[0:8]) | |
self.point2, self.nextwall, self.nextsector = struct.unpack("<hhh", data[8:14]) | |
class Level(object): | |
def __init__(self, name, f): | |
self.name = name | |
self.version = struct.unpack("<l", f.read(4))[0] | |
self.x, self.y, self.z = struct.unpack("<lll", f.read(12)) | |
self.ang, self.cursectnum = struct.unpack("<hh", f.read(4)) | |
self.sectors = [] | |
numsectors = struct.unpack("<h", f.read(2))[0] | |
for _ in range(numsectors): | |
self.sectors.append(Sector(f.read(40))) | |
self.points = [] | |
numpoints = struct.unpack("<h", f.read(2))[0] | |
for _ in range(numpoints): | |
self.points.append(Point(f.read(32))) | |
numsprites = struct.unpack("<h", f.read(2))[0] | |
f.read(numsprites*44) #Skip over sprites (not needed for mapping) | |
self.lower_left = None | |
self.upper_right = None | |
for sector in self.sectors: | |
for line in sector.lines(self): | |
if self.lower_left is None: | |
self.lower_left = (min(line.a.x, line.b.x), min(line.a.y, line.b.y)) | |
else: | |
self.lower_left = (min(self.lower_left[0], line.a.x, line.b.x), min(self.lower_left[1], line.a.y, line.b.y)) | |
if self.upper_right is None: | |
self.upper_right = (max(line.a.x, line.b.x), max(line.a.y, line.b.y)) | |
else: | |
self.upper_right = (max(self.upper_right[0], line.a.x, line.b.x), max(self.upper_right[1], line.a.y, line.b.y)) | |
self.shift = (100-self.lower_left[0],100-self.lower_left[1]) | |
# Scale the drawing to fit inside a 1024x1024 canvas (iPhones don't like really large SVGs even if they have the same detail) | |
self.view_box_size = ((self.shift[0]+self.upper_right[0]+200),(self.shift[1]+self.upper_right[1]+200)) | |
if self.view_box_size[0] > self.view_box_size[1]: | |
self.canvas_size = (1024, int(1024*(float(self.view_box_size[1])/self.view_box_size[0]))) | |
else: | |
self.canvas_size = (int(1024*(float(self.view_box_size[0])/self.view_box_size[1])), 1024) | |
self.scale_x = 1/8 | |
self.scale_y = 1/8 | |
self.view_box_size = (int(self.view_box_size[0]*self.scale_x), int(self.view_box_size[1]*self.scale_y)) | |
def normalize(self, point): | |
return (int((self.shift[0]+point[0])*self.scale_x), int((self.shift[1]+point[1])*self.scale_y)) | |
def save_svg(self): | |
import svgwrite | |
dwg = svgwrite.Drawing(self.name+'.svg', profile='tiny', size=self.canvas_size, viewBox=('0 0 %d %d' % self.view_box_size)) | |
for sector in self.sectors: | |
for line in sector.lines(self): | |
a = self.normalize((line.a.x, line.a.y)) | |
b = self.normalize((line.b.x, line.b.y)) | |
if line.is_one_sided: | |
dwg.add(dwg.line(a, b, stroke='#333', stroke_width=10)) | |
else: | |
dwg.add(dwg.line(a, b, stroke='#999', stroke_width=3)) | |
dwg.save() | |
if __name__ == "__main__": | |
import sys | |
if len(sys.argv) > 1: | |
grp = Grp(sys.argv[1]) | |
for level in grp.levels: | |
print("Saving %s.svg (map ver. %d)" % (level.name, level.version)) | |
level.save_svg() | |
else: | |
print('You need to pass a GRP file as the only argument') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Fixed with help from Ken Silverman himself