mirror of
https://github.com/MapMakersAndProgrammers/io_scene_a3d.git
synced 2025-10-26 01:49:13 -07:00
Add initial BattleMap code
This commit is contained in:
192
io_scene_a3d/AlternativaProtocol.py
Normal file
192
io_scene_a3d/AlternativaProtocol.py
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
'''
|
||||||
|
Copyright (c) 2024 Pyogenics
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
'''
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
from struct import unpack
|
||||||
|
from array import array
|
||||||
|
from zlib import decompress
|
||||||
|
|
||||||
|
class OptionalMask:
|
||||||
|
def __init__(self):
|
||||||
|
self.optionalMask = []
|
||||||
|
|
||||||
|
def read(self, stream):
|
||||||
|
print("Read optional mask")
|
||||||
|
# Read "Null-mask" field
|
||||||
|
nullMask = b""
|
||||||
|
nullMaskOffset = 0
|
||||||
|
|
||||||
|
nullMaskField = int.from_bytes(stream.read(1), "little")
|
||||||
|
nullMaskType = nullMaskField & 0b10000000
|
||||||
|
if nullMaskType == 0:
|
||||||
|
# Short null-mask: 5-29 bits
|
||||||
|
nullMaskLength = nullMaskField & 0b01100000
|
||||||
|
|
||||||
|
nullMask += bytes(nullMaskField & 0b00011111)
|
||||||
|
nullMask += stream.read(nullMaskLength) # 1,2 or 3 bytes
|
||||||
|
nullMaskOffset = 3
|
||||||
|
else:
|
||||||
|
# Long null-mask: 64 - 4194304 bytes
|
||||||
|
nullMaskLengthSize = nullMaskField & 0b01000000
|
||||||
|
nullMaskLength = nullMaskField & 0b00111111
|
||||||
|
if nullMaskLengthSize > 0:
|
||||||
|
# Long length: 22 bits
|
||||||
|
nullMaskLength = nullMaskLength << 16
|
||||||
|
nullMaskLength += int.from_bytes(stream.read(2), "big")
|
||||||
|
else:
|
||||||
|
# Short length: 6 bits
|
||||||
|
pass
|
||||||
|
|
||||||
|
nullMask += stream.read(nullMaskLength)
|
||||||
|
nullMaskOffset = 0
|
||||||
|
|
||||||
|
nullMask = BytesIO(nullMask)
|
||||||
|
# Process first byte (the first byte is missing some bits on some nullmask configs)
|
||||||
|
maskByte = int.from_bytes(nullMask.read(1))
|
||||||
|
for bitI in range(7 - nullMaskOffset, -1, -1):
|
||||||
|
self.optionalMask.append(
|
||||||
|
not bool(maskByte & (2**bitI))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Process the rest of the bytes
|
||||||
|
for maskByte in nullMask.read():
|
||||||
|
for bitI in range(7, -1, -1):
|
||||||
|
self.optionalMask.append(
|
||||||
|
not bool(maskByte & (2**bitI))
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Optional mask flags: {len(self.optionalMask)}")
|
||||||
|
|
||||||
|
def getOptional(self):
|
||||||
|
optional = self.optionalMask.pop(0)
|
||||||
|
return optional
|
||||||
|
|
||||||
|
def getOptionals(self, count):
|
||||||
|
optionals = ()
|
||||||
|
for _ in range(count):
|
||||||
|
optionals += (self.optionalMask.pop(0),)
|
||||||
|
return optionals
|
||||||
|
|
||||||
|
def getLength(self):
|
||||||
|
return len(self.optionalMask)
|
||||||
|
|
||||||
|
def readPacket(stream):
|
||||||
|
print("Reading packet")
|
||||||
|
|
||||||
|
# Read "Package Length" field
|
||||||
|
packageLength = 0
|
||||||
|
packageGzip = False
|
||||||
|
|
||||||
|
packageLengthField = int.from_bytes(stream.read(1), "little")
|
||||||
|
packageLengthSize = packageLengthField & 0b10000000
|
||||||
|
if packageLengthSize == 0:
|
||||||
|
# Short package: 14 bits
|
||||||
|
packageLength += (packageLengthField & 0b00111111) << 8
|
||||||
|
packageLength += int.from_bytes(stream.read(1), "little")
|
||||||
|
|
||||||
|
packageGzip = packageLengthField & 0b01000000
|
||||||
|
else:
|
||||||
|
# Long package: 31 bits
|
||||||
|
packageLength += (packageLengthField & 0b00111111) << 24
|
||||||
|
packageLength += int.from_bytes(stream.read(3), "little")
|
||||||
|
|
||||||
|
packageGzip = packageLengthField & 0b01000000
|
||||||
|
|
||||||
|
# Decompress gzip data
|
||||||
|
package = stream.read()
|
||||||
|
if packageGzip:
|
||||||
|
print("Decompressing packet")
|
||||||
|
package = decompress(package)
|
||||||
|
package = BytesIO(package)
|
||||||
|
|
||||||
|
return package
|
||||||
|
|
||||||
|
'''
|
||||||
|
Array
|
||||||
|
'''
|
||||||
|
def readArrayLength(package):
|
||||||
|
arrayLength = 0
|
||||||
|
|
||||||
|
arrayField = int.from_bytes(package.read(1), "little")
|
||||||
|
arrayLengthType = arrayField & 0b10000000
|
||||||
|
# Short array length
|
||||||
|
if arrayLengthType == 0:
|
||||||
|
# Length of the array is contained in the last 7 bits of this byte
|
||||||
|
arrayLength = arrayField & 0b01111111
|
||||||
|
else: # Must be large array length
|
||||||
|
longArrayLengthType = arrayField & 0b01000000
|
||||||
|
# Length in last 6 bits + next byte
|
||||||
|
if longArrayLengthType == 0:
|
||||||
|
lengthByte = int.from_bytes(package.read(1), "little")
|
||||||
|
arrayLength = (arrayField & 0b00111111) << 8
|
||||||
|
arrayLength += lengthByte
|
||||||
|
else: # Length in last 6 bits + next 2 bytes
|
||||||
|
lengthBytes = int.from_bytes(package.read(2), "big")
|
||||||
|
arrayLength = (arrayField & 0b00111111) << 16
|
||||||
|
arrayLength += lengthBytes
|
||||||
|
|
||||||
|
return arrayLength
|
||||||
|
|
||||||
|
def readObjectArray(package, objReader, optionalMask):
|
||||||
|
length = readArrayLength(package)
|
||||||
|
objects = []
|
||||||
|
for _ in range(length):
|
||||||
|
obj = objReader()
|
||||||
|
obj.read(package, optionalMask)
|
||||||
|
objects.append(obj)
|
||||||
|
|
||||||
|
return objects
|
||||||
|
|
||||||
|
def readString(package):
|
||||||
|
stringLength = readArrayLength(package)
|
||||||
|
string = package.read(stringLength)
|
||||||
|
string = string.decode("utf-8")
|
||||||
|
|
||||||
|
return string
|
||||||
|
|
||||||
|
def readInt16Array(package):
|
||||||
|
length = readArrayLength(package)
|
||||||
|
integers = unpack(f"{length}h", package.read(length*2))
|
||||||
|
integers = array("h", integers)
|
||||||
|
|
||||||
|
return integers
|
||||||
|
|
||||||
|
def readIntArray(package):
|
||||||
|
length = readArrayLength(package)
|
||||||
|
integers = unpack(f"{length}i", package.read(length*4))
|
||||||
|
integers = array("i", integers)
|
||||||
|
|
||||||
|
return integers
|
||||||
|
|
||||||
|
def readInt64Array(package):
|
||||||
|
length = readArrayLength(package)
|
||||||
|
integers = unpack(f"{length}q", package.read(length*8))
|
||||||
|
integers = array("q", integers)
|
||||||
|
|
||||||
|
return integers
|
||||||
|
|
||||||
|
def readFloatArray(package):
|
||||||
|
length = readArrayLength(package)
|
||||||
|
floats = unpack(f">{length}f", package.read(length*4))
|
||||||
|
floats = array("f", floats)
|
||||||
|
|
||||||
|
return floats
|
||||||
328
io_scene_a3d/BattleMap.py
Normal file
328
io_scene_a3d/BattleMap.py
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
'''
|
||||||
|
Copyright (c) 2024 Pyogenics
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
'''
|
||||||
|
|
||||||
|
from .IOTools import unpackStream
|
||||||
|
from . import AlternativaProtocol
|
||||||
|
|
||||||
|
'''
|
||||||
|
Objects
|
||||||
|
'''
|
||||||
|
class AtlasRect:
|
||||||
|
def __init__(self):
|
||||||
|
self.height = 0
|
||||||
|
self.libraryName = ""
|
||||||
|
self.name = ""
|
||||||
|
self.width = 0
|
||||||
|
self.x = 0
|
||||||
|
self.y = 0
|
||||||
|
|
||||||
|
def read(self, stream, optionalMask):
|
||||||
|
print("Read AtlasRect")
|
||||||
|
self.height, = unpackStream(">I", stream)
|
||||||
|
self.libraryName = AlternativaProtocol.readString(stream)
|
||||||
|
self.name = AlternativaProtocol.readString(stream)
|
||||||
|
self.width, self.x, self.y = unpackStream(">3I", stream)
|
||||||
|
|
||||||
|
class CollisionBox:
|
||||||
|
def __init__(self):
|
||||||
|
self.position = (0.0, 0.0, 0.0)
|
||||||
|
self.rotation = (0.0, 0.0, 0.0)
|
||||||
|
self.size = (0.0, 0.0, 0.0)
|
||||||
|
|
||||||
|
def read(self, stream, optionalMask):
|
||||||
|
print("Read CollisionBox")
|
||||||
|
self.position = unpackStream(">3f", stream)
|
||||||
|
self.rotation = unpackStream(">3f", stream)
|
||||||
|
self.size = unpackStream(">3f", stream)
|
||||||
|
|
||||||
|
class CollisionPlane:
|
||||||
|
def __init__(self):
|
||||||
|
self.length = 0.0
|
||||||
|
self.position = (0.0, 0.0, 0.0)
|
||||||
|
self.rotation = (0.0, 0.0, 0.0)
|
||||||
|
self.width = 0.0
|
||||||
|
|
||||||
|
def read(self, stream, optionalMask):
|
||||||
|
print("Read CollisionPlane")
|
||||||
|
self.length, = unpackStream(">d", stream)
|
||||||
|
self.position = unpackStream(">3f", stream)
|
||||||
|
self.rotation = unpackStream(">3f", stream)
|
||||||
|
self.width, = unpackStream(">d", stream)
|
||||||
|
|
||||||
|
class CollisionTriangle:
|
||||||
|
def __init__(self):
|
||||||
|
self.length = 0.0
|
||||||
|
self.position = (0.0, 0.0, 0.0)
|
||||||
|
self.rotation = (0.0, 0.0, 0.0)
|
||||||
|
self.v0 = (0.0, 0.0, 0.0)
|
||||||
|
self.v1 = (0.0, 0.0, 0.0)
|
||||||
|
self.v2 = (0.0, 0.0, 0.0)
|
||||||
|
|
||||||
|
def read(self, stream, optionalMask):
|
||||||
|
print("Read CollisionTriangle")
|
||||||
|
self.length, = unpackStream(">d", stream)
|
||||||
|
self.position = unpackStream(">3f", stream)
|
||||||
|
self.rotation = unpackStream(">3f", stream)
|
||||||
|
self.v0 = unpackStream(">3f", stream)
|
||||||
|
self.v1 = unpackStream(">3f", stream)
|
||||||
|
self.v2 = unpackStream(">3f", stream)
|
||||||
|
|
||||||
|
class ScalarParameter:
|
||||||
|
def __init__(self):
|
||||||
|
self.name = ""
|
||||||
|
self.value = 0.0
|
||||||
|
|
||||||
|
def read(self, stream, optionalMask):
|
||||||
|
print("Read ScalarParameters")
|
||||||
|
self.name = AlternativaProtocol.readString(stream)
|
||||||
|
self.value, = unpackStream(">f", stream)
|
||||||
|
|
||||||
|
class TextureParameter:
|
||||||
|
def __init__(self):
|
||||||
|
self.name = ""
|
||||||
|
self.textureName = ""
|
||||||
|
|
||||||
|
# Optional
|
||||||
|
self.libraryName = None
|
||||||
|
|
||||||
|
def read(self, stream, optionalMask):
|
||||||
|
print("Read TextureParameter")
|
||||||
|
if optionalMask.getOptional():
|
||||||
|
self.libraryName = AlternativaProtocol.readString(stream)
|
||||||
|
self.name = AlternativaProtocol.readString(stream)
|
||||||
|
self.textureName = AlternativaProtocol.readString(stream)
|
||||||
|
|
||||||
|
class Vector2Parameter:
|
||||||
|
def __init__(self):
|
||||||
|
self.name = ""
|
||||||
|
self.value = (0.0, 0.0)
|
||||||
|
|
||||||
|
def __init__(self, stream, optionalMask):
|
||||||
|
print("Read Vector2Parameters")
|
||||||
|
self.name = AlternativaProtocol.readString(stream)
|
||||||
|
self.value = unpackStream(">2f", stream)
|
||||||
|
|
||||||
|
class Vector3Parameter:
|
||||||
|
def __init__(self):
|
||||||
|
self.name = ""
|
||||||
|
self.value = (0.0, 0.0, 0.0)
|
||||||
|
|
||||||
|
def __init__(self, stream, optionalMask):
|
||||||
|
print("Read Vector3Parameters")
|
||||||
|
self.name = AlternativaProtocol.readString(stream)
|
||||||
|
self.value = unpackStream(">3f", stream)
|
||||||
|
|
||||||
|
class Vector4Parameter:
|
||||||
|
def __init__(self):
|
||||||
|
self.name = ""
|
||||||
|
self.value = (0.0, 0.0, 0.0, 0.0)
|
||||||
|
|
||||||
|
def read(self, stream, optionalMask):
|
||||||
|
print("Read Vector4Parameters")
|
||||||
|
self.name = AlternativaProtocol.readString(stream)
|
||||||
|
self.value = unpackStream(">4f", stream)
|
||||||
|
|
||||||
|
'''
|
||||||
|
Main objects
|
||||||
|
'''
|
||||||
|
class Atlas:
|
||||||
|
def __init__(self):
|
||||||
|
self.height = 0
|
||||||
|
self.name = ""
|
||||||
|
self.padding = 0
|
||||||
|
self.rects = []
|
||||||
|
self.width = 0
|
||||||
|
|
||||||
|
# Get the rect's texture from an atlas
|
||||||
|
# XXX: Handle padding?
|
||||||
|
def resolveRectImage(self, rectName, atlasImage):
|
||||||
|
rect = None
|
||||||
|
for childRect in self.rects:
|
||||||
|
if childRect.name == rectName:
|
||||||
|
rect = childRect
|
||||||
|
if rect == None:
|
||||||
|
raise RuntimeError(f"Couldn't find rect with name: {rectName}")
|
||||||
|
|
||||||
|
# Cut the texture out
|
||||||
|
rectTexture = atlasImage.crop(
|
||||||
|
(rect.x, rect.y, rect.x+rect.width, rect.y+rect.height)
|
||||||
|
)
|
||||||
|
return rectTexture
|
||||||
|
|
||||||
|
def read(self, stream, optionalMask):
|
||||||
|
print("Read Atlas")
|
||||||
|
self.height, unpackStream(">i", stream)
|
||||||
|
self.name = AlternativaProtocol.readString(stream)
|
||||||
|
self.padding = unpackStream(">I", stream)
|
||||||
|
self.rects = AlternativaProtocol.readObjectArray(stream, AtlasRect, optionalMask)
|
||||||
|
self.width, = unpackStream(">I", stream)
|
||||||
|
|
||||||
|
class Batch:
|
||||||
|
def __init__(self):
|
||||||
|
self.materialID = 0
|
||||||
|
self.name = ""
|
||||||
|
self.position = (0.0, 0.0, 0.0)
|
||||||
|
self.propIDs = ""
|
||||||
|
|
||||||
|
def read(self, stream, optionalMask):
|
||||||
|
print("Read Batch")
|
||||||
|
self.materialID, = unpackStream(">I", stream)
|
||||||
|
self.name = AlternativaProtocol.readString(stream)
|
||||||
|
self.position = unpackStream(">3f", stream)
|
||||||
|
self.propIDs = AlternativaProtocol.readString(stream)
|
||||||
|
|
||||||
|
class CollisionGeometry:
|
||||||
|
def __init__(self):
|
||||||
|
self.boxes = []
|
||||||
|
self.planes = []
|
||||||
|
self.triangles = []
|
||||||
|
|
||||||
|
def read(self, stream, optionalMask):
|
||||||
|
print("Read CollisionGeometry")
|
||||||
|
self.boxes = AlternativaProtocol.readObjectArray(stream, CollisionBox, optionalMask)
|
||||||
|
self.planes = AlternativaProtocol.readObjectArray(stream, CollisionPlane, optionalMask)
|
||||||
|
self.triangles = AlternativaProtocol.readObjectArray(stream, CollisionTriangle, optionalMask)
|
||||||
|
|
||||||
|
class Material:
|
||||||
|
def __init__(self):
|
||||||
|
self.ID = 0
|
||||||
|
self.name = ""
|
||||||
|
self.shader = ""
|
||||||
|
self.textureParameters = None
|
||||||
|
|
||||||
|
# Optional
|
||||||
|
self.scalarParameters = None
|
||||||
|
self.vector2Parameters = None
|
||||||
|
self.vector3Parameters = None
|
||||||
|
self.vector4Parameters = None
|
||||||
|
|
||||||
|
def getTextureParameterByName(self, name):
|
||||||
|
for textureParameter in self.textureParameters:
|
||||||
|
if textureParameter.name == name: return textureParameter
|
||||||
|
|
||||||
|
raise RuntimeError(f"Couldn't find texture parameter with name: {name}")
|
||||||
|
|
||||||
|
def read(self, stream, optionalMask):
|
||||||
|
print(f"Read Material")
|
||||||
|
self.ID, = unpackStream(">I", stream)
|
||||||
|
self.name = AlternativaProtocol.readString(stream)
|
||||||
|
if optionalMask.getOptional():
|
||||||
|
self.scalarParameters = AlternativaProtocol.readObjectArray(stream, ScalarParameter, optionalMask)
|
||||||
|
self.shader = AlternativaProtocol.readString(stream)
|
||||||
|
self.textureParameters = AlternativaProtocol.readObjectArray(stream, TextureParameter, optionalMask)
|
||||||
|
if optionalMask.getOptional():
|
||||||
|
self.vector2Parameters = AlternativaProtocol.readObjectArray(stream, Vector2Parameter, optionalMask)
|
||||||
|
if optionalMask.getOptional():
|
||||||
|
self.vector3Parameters = AlternativaProtocol.readObjectArray(stream, Vector3Parameter, optionalMask)
|
||||||
|
if optionalMask.getOptional():
|
||||||
|
self.vector4Parameters = AlternativaProtocol.readObjectArray(stream, Vector4Parameter, optionalMask)
|
||||||
|
|
||||||
|
class SpawnPoint:
|
||||||
|
def __init__(self):
|
||||||
|
self.position = (0.0, 0.0, 0.0)
|
||||||
|
self.rotation = (0.0, 0.0, 0.0)
|
||||||
|
self.type = 0
|
||||||
|
|
||||||
|
def read(self, stream, optionalMask):
|
||||||
|
print("Read SpawnPoint")
|
||||||
|
self.position = unpackStream(">3f", stream)
|
||||||
|
self.rotation = unpackStream(">3f", stream)
|
||||||
|
self.type, = unpackStream(">I", stream)
|
||||||
|
|
||||||
|
class Prop:
|
||||||
|
def __init__(self):
|
||||||
|
self.ID = 0
|
||||||
|
self.libraryName = ""
|
||||||
|
self.materialID = 0
|
||||||
|
self.name = ""
|
||||||
|
self.position = (0.0, 0.0, 0.0)
|
||||||
|
|
||||||
|
# Optional
|
||||||
|
self.groupName = ""
|
||||||
|
self.rotation = (0.0, 0.0, 0.0)
|
||||||
|
self.scale = (0.0, 0.0, 0.0)
|
||||||
|
|
||||||
|
def read(self, stream, optionalMask):
|
||||||
|
print(f"Read Prop")
|
||||||
|
if optionalMask.getOptional():
|
||||||
|
self.groupName = AlternativaProtocol.readString(stream)
|
||||||
|
self.ID, = unpackStream(">I", stream)
|
||||||
|
self.libraryName = AlternativaProtocol.readString(stream)
|
||||||
|
self.materialID, = unpackStream(">I", stream)
|
||||||
|
self.name = AlternativaProtocol.readString(stream)
|
||||||
|
self.position = unpackStream(">3f", stream)
|
||||||
|
if optionalMask.getOptional():
|
||||||
|
self.rotation = unpackStream(">3f", stream)
|
||||||
|
if optionalMask.getOptional():
|
||||||
|
self.scale = unpackStream(">3f", stream)
|
||||||
|
|
||||||
|
'''
|
||||||
|
Main
|
||||||
|
'''
|
||||||
|
class BattleMap:
|
||||||
|
def __init__(self):
|
||||||
|
self.atlases = []
|
||||||
|
self.batches = []
|
||||||
|
self.collisionGeometry = []
|
||||||
|
self.collisionGeometryOutsideGamingZone = []
|
||||||
|
self.materials = []
|
||||||
|
self.spawnPoints = []
|
||||||
|
self.staticGeometry = []
|
||||||
|
|
||||||
|
'''
|
||||||
|
Getters
|
||||||
|
'''
|
||||||
|
def getMaterialByID(self, materialID):
|
||||||
|
for material in self.materials:
|
||||||
|
if material.ID == materialID: return material
|
||||||
|
|
||||||
|
raise RuntimeError(f"Couldn't find material with ID: {materialID}")
|
||||||
|
|
||||||
|
'''
|
||||||
|
IO
|
||||||
|
'''
|
||||||
|
def read(self, stream):
|
||||||
|
print("Reading BIN map")
|
||||||
|
|
||||||
|
# Read packet
|
||||||
|
packet = AlternativaProtocol.readPacket(stream)
|
||||||
|
with open("packet.bin", "wb") as packetFile:
|
||||||
|
packetFile.write(
|
||||||
|
packet.read()
|
||||||
|
)
|
||||||
|
packet.seek(0)
|
||||||
|
optionalMask = AlternativaProtocol.OptionalMask()
|
||||||
|
optionalMask.read(packet)
|
||||||
|
|
||||||
|
# Read data
|
||||||
|
if optionalMask.getOptional():
|
||||||
|
self.atlases = AlternativaProtocol.readObjectArray(packet, Atlas, optionalMask)
|
||||||
|
if optionalMask.getOptional():
|
||||||
|
self.batches = AlternativaProtocol.readObjectArray(packet, Batch, optionalMask)
|
||||||
|
self.collisionGeometry = CollisionGeometry()
|
||||||
|
self.collisionGeometry.read(packet, optionalMask)
|
||||||
|
self.collisionGeometryOutsideGamingZone = CollisionGeometry()
|
||||||
|
self.collisionGeometryOutsideGamingZone.read(packet, optionalMask)
|
||||||
|
self.materials = AlternativaProtocol.readObjectArray(packet, Material, optionalMask)
|
||||||
|
if optionalMask.getOptional():
|
||||||
|
self.spawnPoints = AlternativaProtocol.readObjectArray(packet, SpawnPoint, optionalMask)
|
||||||
|
self.staticGeometry = AlternativaProtocol.readObjectArray(packet, Prop, optionalMask)
|
||||||
@@ -27,6 +27,7 @@ from bpy_extras.io_utils import ImportHelper
|
|||||||
|
|
||||||
from .A3D import A3D
|
from .A3D import A3D
|
||||||
from .A3DBlenderImporter import A3DBlenderImporter
|
from .A3DBlenderImporter import A3DBlenderImporter
|
||||||
|
from .BattleMap import BattleMap
|
||||||
|
|
||||||
from glob import glob
|
from glob import glob
|
||||||
|
|
||||||
@@ -40,8 +41,8 @@ class ImportA3D(Operator, ImportHelper):
|
|||||||
bl_options = {'PRESET', 'UNDO'}
|
bl_options = {'PRESET', 'UNDO'}
|
||||||
|
|
||||||
filter_glob: StringProperty(default="*.a3d", options={'HIDDEN'})
|
filter_glob: StringProperty(default="*.a3d", options={'HIDDEN'})
|
||||||
directory: StringProperty(subtype='DIR_PATH', options={'HIDDEN'})
|
directory: StringProperty(subtype="DIR_PATH", options={'HIDDEN'})
|
||||||
files: CollectionProperty(type=OperatorFileListElement, options={"HIDDEN", "SKIP_SAVE"})
|
files: CollectionProperty(type=OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'})
|
||||||
|
|
||||||
# User options
|
# User options
|
||||||
create_collection: BoolProperty(name="Create collection", description="Create a collection to hold all the model objects", default=False)
|
create_collection: BoolProperty(name="Create collection", description="Create a collection to hold all the model objects", default=False)
|
||||||
@@ -78,6 +79,29 @@ class ImportA3D(Operator, ImportHelper):
|
|||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
class ImportBattleMap(Operator, ImportHelper):
|
||||||
|
bl_idname = "import_scene.tanki_battlemap"
|
||||||
|
bl_label = "Import map"
|
||||||
|
bl_description = "Import a BIN format Tanki Online map file"
|
||||||
|
bl_options = {'PRESET', 'UNDO'}
|
||||||
|
|
||||||
|
filter_glob: StringProperty(default="*.bin", options={'HIDDEN'})
|
||||||
|
directory: StringProperty(subtype="DIR_PATH", options={'HIDDEN'})
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return ImportHelper.invoke(self, context, event)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
print(f"Reading BattleMap data from {self.filepath}")
|
||||||
|
mapData = BattleMap()
|
||||||
|
with open(self.filepath, "rb") as file:
|
||||||
|
mapData.read(file)
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Menu
|
Menu
|
||||||
'''
|
'''
|
||||||
@@ -92,22 +116,28 @@ def import_panel_options(layout, operator):
|
|||||||
def menu_func_import_a3d(self, context):
|
def menu_func_import_a3d(self, context):
|
||||||
self.layout.operator(ImportA3D.bl_idname, text="Alternativa3D HTML5 (.a3d)")
|
self.layout.operator(ImportA3D.bl_idname, text="Alternativa3D HTML5 (.a3d)")
|
||||||
|
|
||||||
|
def menu_func_import_battlemap(self, context):
|
||||||
|
self.layout.operator(ImportBattleMap.bl_idname, text="Tanki Online BattleMap (.bin)")
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Registration
|
Registration
|
||||||
'''
|
'''
|
||||||
classes = [
|
classes = [
|
||||||
ImportA3D
|
ImportA3D,
|
||||||
|
ImportBattleMap
|
||||||
]
|
]
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
for c in classes:
|
for c in classes:
|
||||||
bpy.utils.register_class(c)
|
bpy.utils.register_class(c)
|
||||||
bpy.types.TOPBAR_MT_file_import.append(menu_func_import_a3d)
|
bpy.types.TOPBAR_MT_file_import.append(menu_func_import_a3d)
|
||||||
|
bpy.types.TOPBAR_MT_file_import.append(menu_func_import_battlemap)
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
for c in classes:
|
for c in classes:
|
||||||
bpy.utils.unregister_class(c)
|
bpy.utils.unregister_class(c)
|
||||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import_a3d)
|
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import_a3d)
|
||||||
|
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import_battlemap)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
register()
|
register()
|
||||||
Reference in New Issue
Block a user