perfect_dark/tools/assetmgr/mkpads

332 lines
11 KiB
Python
Executable File

#!/usr/bin/env python3
import assetmgr
import json
import os
import re
import struct
import sys
class App():
def run(self):
self.load_data()
self.pad_names = self.make_names(self.json['pads'])
self.waypoint_names = self.make_names(self.json['waypoints'])
self.waygroup_names = self.make_names(self.json['waygroups'])
self.populate_waygroup_waypoints()
self.make_header()
self.make_object()
def load_data(self):
fd = open(sys.argv[1], 'r')
data = fd.read()
fd.close()
self.json = json.loads(data)
self.shortname = os.path.basename(sys.argv[1]).replace('.json', '')
def make_names(self, items):
names = {}
for index, item in enumerate(items):
names[item['id']] = index
return names
def populate_waygroup_waypoints(self):
for index, waygroup in enumerate(self.json['waygroups']):
self.json['waygroups'][index]['waypoints'] = []
for waypoint in self.json['waypoints']:
index = self.waygroup_names[waypoint['waygroup']]
self.json['waygroups'][index]['waypoints'].append(waypoint['id'])
def make_header(self):
typename = 'pad_%s' % self.shortname
enums = [row['id'] for row in self.json['pads']]
filename = 'pads/%s.h' % self.shortname
terminator = 'PAD_%s_END' % self.shortname.upper()
assetmgr.write_enums(typename, enums, filename, terminator)
def make_object(self):
binary = self.make_binary()
zipped = assetmgr.zip(binary)
# Write the zipped file, purely so `make test` can verify it
assetmgr.writefile('build/%s/assets/files/bgdata/bg_%s_padsZ' % (os.environ['ROMID'], self.shortname), zipped)
filename = 'files/bgdata/bg_%s_padsZ.o' % self.shortname
assetmgr.write_object(zipped, filename)
# Order of stuff:
# - 0x00: header
# - 0x14: array of pad offsets (variable length)
# - Pads
# - Waypoint list
# - Waypoint neighbours
# - Waygroup list
# - Waygroup neighbours
# - Waygroup waypoints
# - Cover
def make_binary(self):
pads = self.make_pads()
waypoints_start = 0x14 + len(pads)
waypoints = self.make_waypoints(waypoints_start)
waygroups_start = waypoints_start + len(waypoints)
waygroups = self.make_waygroups(waygroups_start)
cover_start = waygroups_start + len(waygroups)
cover = self.make_cover()
output = bytes()
output += len(self.json['pads']).to_bytes(4, 'big')
output += len(self.json['cover']).to_bytes(4, 'big')
output += waypoints_start.to_bytes(4, 'big')
output += waygroups_start.to_bytes(4, 'big')
output += cover_start.to_bytes(4, 'big')
output += pads
output += waypoints
output += waygroups
output += cover
output = assetmgr.pad16(output)
return output
def make_pads(self):
output = bytes()
offsets = bytes()
start = pos = assetmgr.align4(0x14 + len(self.json['pads']) * 2)
for pad in self.json['pads']:
# flags, room and liftnum
# ffffffff ffffffff ffrrrrrr rrrrllll
flags = self.make_pad_flags(pad)
word = (flags << 14) | 0x3ff0 | (pad['liftnum'] & 0x0f)
output += word.to_bytes(4, 'big')
if flags & PADFLAG_INTPOS:
output += self.make_unsigned(pad['pos'][0]).to_bytes(2, 'big')
output += self.make_unsigned(pad['pos'][1]).to_bytes(2, 'big')
output += self.make_unsigned(pad['pos'][2]).to_bytes(2, 'big')
output += b'\x00\x00'
else:
output += self.make_float(pad['pos'][0])
output += self.make_float(pad['pos'][1])
output += self.make_float(pad['pos'][2])
if (flags & 0x0e) == 0:
output += self.make_float(pad['up'][0])
output += self.make_float(pad['up'][1])
output += self.make_float(pad['up'][2])
if (flags & 0xe0) == 0:
output += self.make_float(pad['dir'][0])
output += self.make_float(pad['dir'][1])
output += self.make_float(pad['dir'][2])
if flags & PADFLAG_HASBBOXDATA:
output += self.make_float(pad['xmin'])
output += self.make_float(pad['xmax'])
output += self.make_float(pad['ymin'])
output += self.make_float(pad['ymax'])
output += self.make_float(pad['zmin'])
output += self.make_float(pad['zmax'])
output = assetmgr.pad4(output)
offsets += pos.to_bytes(2, 'big')
pos = start + len(output)
offsets = assetmgr.pad4(offsets)
return offsets + output
# Some pads need to store 0x80000000 (negative 0), but this isn't supported
# in JSON, nor does it appear to be a thing in Python, hence this hack.
# For -0 values, the JSON uses a string which we check for here.
def make_float(self, value):
if value == '-0':
return b'\x80\x00\x00\x00'
return struct.pack('>f', value)
def make_pad_flags(self, pad):
flags = 0
if isinstance(pad['pos'][0], int) and isinstance(pad['pos'][1], int) and isinstance(pad['pos'][2], int):
flags |= PADFLAG_INTPOS
if abs(pad['up'][0]) == 1 and pad['up'][1] == 0 and pad['up'][2] == 0:
flags |= PADFLAG_UPALIGNTOX
if pad['up'][0] == 0 and abs(pad['up'][1]) == 1 and pad['up'][2] == 0:
flags |= PADFLAG_UPALIGNTOY
if pad['up'][0] == 0 and pad['up'][1] == 0 and abs(pad['up'][2]) == 1:
flags |= PADFLAG_UPALIGNTOZ
if (flags & 0x0e) and sum(pad['up']) == -1:
flags |= PADFLAG_UPALIGNINVERT
if abs(pad['dir'][0]) == 1 and pad['dir'][1] == 0 and pad['dir'][2] == 0:
flags |= PADFLAG_LOOKALIGNTOX
if pad['dir'][0] == 0 and abs(pad['dir'][1]) == 1 and pad['dir'][2] == 0:
flags |= PADFLAG_LOOKALIGNTOY
if pad['dir'][0] == 0 and pad['dir'][1] == 0 and abs(pad['dir'][2]) == 1:
flags |= PADFLAG_LOOKALIGNTOZ
if (flags & 0xe0) and sum(pad['dir']) == -1:
flags |= PADFLAG_LOOKALIGNINVERT
if pad['xmin'] != -100 or pad['xmax'] != 100 or pad['ymin'] != -100 or pad['ymax'] != 100 or pad['zmin'] != -100 or pad['zmax'] != 100:
flags |= PADFLAG_HASBBOXDATA
if pad['aiwaitlift']:
flags |= PADFLAG_AIWAITLIFT
if pad['aionlift']:
flags |= PADFLAG_AIONLIFT
if pad['aiwalkdirect']:
flags |= PADFLAG_AIWALKDIRECT
if pad['aidrop']:
flags |= PADFLAG_AIDROP
if pad['aiduck']:
flags |= PADFLAG_AIDUCK
if pad['flag00008000']:
flags |= PADFLAG_8000
if pad['flag00010000']:
flags |= PADFLAG_10000
return flags
def make_unsigned(self, value):
if value < 0:
value = 0x10000 - abs(value)
return value
def make_waypoints(self, start):
output = bytes()
neighbours = bytes()
pos = start + len(self.json['waypoints']) * 0x10 + 0x10
for row in self.json['waypoints']:
output += self.pad_names[row['pad']].to_bytes(4, 'big')
output += pos.to_bytes(4, 'big')
output += self.waygroup_names[row['waygroup']].to_bytes(4, 'big')
output += b'\x00\x00\x00\x00'
neighbours += self.make_waypoint_neighbours_list(row['neighbours'])
pos += len(row['neighbours']) * 4 + 4
# Terminator record
output += b'\xff\xff\xff\xff'
output += b'\x00\x00\x00\x00'
output += b'\x00\x00\x00\x00'
output += b'\x00\x00\x00\x00'
return output + neighbours
def make_waygroups(self, start):
neighbours = bytes()
waypoints = bytes()
for row in self.json['waygroups']:
neighbours += self.make_waygroup_neighbours_list(row['neighbours'])
waypoints += self.make_list(row['waypoints'])
output = bytes()
wpos = start + len(self.json['waygroups']) * 0x0c + 0x0c
npos = wpos + len(waypoints)
for row in self.json['waygroups']:
output += npos.to_bytes(4, 'big')
npos += len(row['neighbours']) * 4 + 4
output += wpos.to_bytes(4, 'big')
wpos += len(row['waypoints']) * 4 + 4
output += b'\x00\x00\x00\x00'
# Terminator record
output += b'\x00\x00\x00\x00'
output += b'\x00\x00\x00\x00'
output += b'\x00\x00\x00\x00'
return output + waypoints + neighbours
def make_cover(self):
output = bytes()
for row in self.json['cover']:
output += self.make_float(row['pos'][0])
output += self.make_float(row['pos'][1])
output += self.make_float(row['pos'][2])
output += self.make_float(row['dir'][0])
output += self.make_float(row['dir'][1])
output += self.make_float(row['dir'][2])
output += row['unk18'].to_bytes(4, 'big')
return output
def make_list(self, items):
output = bytes()
for value in items:
if 'WAYPOINT_' in value:
output += self.waypoint_names[value].to_bytes(4, 'big')
elif 'WAYGROUP_' in value:
output += self.waygroup_names[value].to_bytes(4, 'big')
output += b'\xff\xff\xff\xff'
return output
def make_waypoint_neighbours_list(self, items):
output = bytes()
for item in items:
value = self.waypoint_names[item['waypoint']]
if item['flag8000']: value |= 0x8000
if item['flag4000']: value |= 0x4000
output += value.to_bytes(4, 'big')
output += b'\xff\xff\xff\xff'
return output
def make_waygroup_neighbours_list(self, items):
output = bytes()
for item in items:
value = self.waygroup_names[item['waygroup']]
if item['flag8000']: value |= 0x8000
if item['flag4000']: value |= 0x4000
output += value.to_bytes(4, 'big')
output += b'\xff\xff\xff\xff'
return output
PADFLAG_INTPOS = 0x0001
PADFLAG_UPALIGNTOX = 0x0002
PADFLAG_UPALIGNTOY = 0x0004
PADFLAG_UPALIGNTOZ = 0x0008
PADFLAG_UPALIGNINVERT = 0x0010
PADFLAG_LOOKALIGNTOX = 0x0020
PADFLAG_LOOKALIGNTOY = 0x0040
PADFLAG_LOOKALIGNTOZ = 0x0080
PADFLAG_LOOKALIGNINVERT = 0x0100
PADFLAG_HASBBOXDATA = 0x0200
PADFLAG_AIWAITLIFT = 0x0400
PADFLAG_AIONLIFT = 0x0800
PADFLAG_AIWALKDIRECT = 0x1000
PADFLAG_AIDROP = 0x2000
PADFLAG_AIDUCK = 0x4000
PADFLAG_8000 = 0x8000
PADFLAG_10000 = 0x10000
app = App()
app.run()