
__doc__=\
'''Object model for the ESP/ESM file format.

This is all hand-written at the moment. It forms the substructure for
the actual records and fields that are currently generated using sniptopy.py.
'''

import game as game_
import struct
from struct import unpack as st_unpack

RECORD_TYPES = {}
KNOWN_SUBGROUPS = '''REFR ACHR NAVM PGRE PHZD LAND INFO'''.strip().split()
EMPTY_GROUP_RECORDS = '''CLDC HAIR RGDL SCPT SCOL PWAT'''.strip().split()

def record_type(rec_tag):
    def tag_record_class(klazz):
        if rec_tag in RECORD_TYPES:
            print AssertionError,  \
                ("trying to register %s for existing tag %s, mapped to %s" %
                            (rec_tag, klazz, RECORD_TYPES[rec_tag]))
        else:
            RECORD_TYPES[rec_tag] = klazz
            klazz.___REC___ = rec_tag

        return klazz
    return tag_record_class


##########################################
# Manage smart, ordered attr descriptors.
#
# When new subclasses of BaseRecord are created, any declared OrderedDescriptor
# subclasses are ordered into a "__order__" class attribute, based on the
# "declaration_order" attribute, which is expected to be an integer set by the
# descriptor's constructor.

DEFINITION_COUNTER = 0

class OrderedDescriptor(object):
    def __init__(self):
        global DEFINITION_COUNTER
        self.declaration_order = DEFINITION_COUNTER     # will be used by __new__ to get order
        DEFINITION_COUNTER += 1
        self.attribute_name = None          # will be set by __new__

        ####

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.attribute_name)

    def __set__(self, obj, value):
        raise NotImplementedError, ("set %s.%s to %s" % (self, self.attribute_name, value))

    def __get__(self, obj):
        raise NotImplementedError

    @classmethod
    def lookup_type(cls, type_name):
        '''Helps to resolve text type name to class once everything is defined.
        '''
        raise NotImplementedError


class OrderedAttributes(type):
    def __new__(cls, name, supers, class_dict):

        sub_cls = super(OrderedAttributes, cls).__new__(cls, name, supers, class_dict)

        # configure __order__ class attribute to match declaration order of descriptors.
        attr_order = []
        for attr_name, descriptor in class_dict.iteritems():
            if isinstance(descriptor, OrderedDescriptor):
                attr_order.append((descriptor.declaration_order, descriptor))
                descriptor.attribute_name = attr_name

        attr_order.sort()

        old_order = getattr(sub_cls, '__order__', [])
        sub_cls.__order__ = tuple(list(old_order) + [desc for (n, desc) in attr_order])

        return sub_cls


##################################################

class BaseRecord(object):
    '''Core superclass for TES ESP/ESM records and subrecords.

    There's a great deal of magic going on in here.

    Descriptors of type OrderedDescriptor get registered by their declaration order
    in the class, so that the __order__ class tuple can be used for reading and writing
    to the plugin file.

    Subclass descriptors get ordered AFTER their superclass descriptors.
    '''
    def __init__(self, *args, **kwargs):
        pass

    __metaclass__ = OrderedAttributes

    # Read constructor for all record subclasses
    @staticmethod
    def read_from(fd):
        record_offset = fd.tell()
        typed = fd.read(4)
        if not typed: return None

        try:
            rec_type, = st_unpack("4s", typed)
        except:
            print "Fail read header at:", repr(typed)
            raise

        rc_class = RECORD_TYPES[rec_type]
        rec = rc_class()
        rec._tag = rec_type
        rec._file_offset = record_offset
        rec.read_header(fd)
        return rec

    ## Concrete subclasses should override
    def read_header(self, fd):  raise NotImplementedError
    def has_subrecords(self):   return False
    def record_size(self):      return self.HEADER_SIZE + self._dataSize
    def data_size(self):        return self._dataSize
    def data_offset(self):      return self._file_offset + self.HEADER_SIZE
    def data_end_offset(self):  return self._file_offset + self.record_size()

    ## Convenience functions - common

    def seek_data(self, fd):
        fd.seek(self.data_offset())
        return self.data_size()

    def read_data(self, fd):
        self.seek_data(fd)
        return fd.read(self.data_size())

    def skip(self, fd):
        fd.seek(self.data_end_offset())



##########################################


class Record(BaseRecord):
    HEADER_STRUCT = struct.Struct("<4I2H")
    HEADER_SIZE = HEADER_STRUCT.size + 4

    def read_header(self, fd):
        (self._dataSize,
         self._flags,
         self._record_id,
         self._revision,
         self._version,
         self._unknown) = self.HEADER_STRUCT.unpack(
                fd.read(self.HEADER_STRUCT.size))

    def has_subrecords(self):   return True

    def read_subrecords(self, fd):
        if self._flags & self.F_COMPRESSED:
            raise Warning, "No reading of compressed data yet."
            self.skip(fd)
            return

        end_offset = self.data_end_offset()
        fd.seek(self.data_offset())

        prev_subrecord = None
        while fd.tell() < end_offset:
            subrecord = Subrecord.read(fd)
            #if prev_subrecord
            yield subrecord
            prev_subrecord = subrecord
            subrecord.skip(fd)

    F_MASTER    = 0x01
    F_DELETED   = 0x20
    F_SHIELDS   = 0x40
    F_LOCALIZED = 0x80
    F_HIDDEN    = 0x200 # REFR
    F_DEAD      = 0x200 # ACHR
    F_QUEST_ITEM= 0x400
    F_MAIN_MENU_DISPLAY= F_QUEST_ITEM # # LSCR
    F_INITIALLY_DISABLED = 0x800
    F_IGNORED   = 0x1000
    F_DANGEROUS = 0x20000 # CELL
    F_COMPRESSED =0x40000
    F_NO_WAIT   = 0x80000

    def __repr__(self):
        return "<%s %s at %08X>" % (self.__class__.__name__,
                                getattr(self, "type", "None"),
                                getattr(self, "record_id", 0))

    def flag_list(self):
        return [fname for (fname, mask) in self.__FLAGS__.items()
                        if self._flags & mask]


Record.__FLAGS__ = dict((fk,fv) for (fk,fv) in Record.__dict__.items() if fk.startswith('F_'))

@record_type('GRUP')
class Group(BaseRecord):
    HEADER_STRUCT = struct.Struct("<I4BI4H")
    HEADER_SIZE = HEADER_STRUCT.size + 4

    def read_header(self, fd):
        self.label = [0, 0, 0, 0]
        (self._groupSize,
        self.label[0], self.label[1], self.label[2], self.label[3],
        self.groupType,
        self.stamp,
        self._unknown1,
        self._version,
        self._unknown2) = self.HEADER_STRUCT.unpack(fd.read(self.HEADER_STRUCT.size))

    def record_size(self):  return self._groupSize
    def data_size(self):    return self._groupSize - self.HEADER_SIZE
    def flag_list(self):    return [] # TODO
    def has_subrecords(self): return True
    def has_subrecords(self): return False
    def read_subrecords(self, fd): return []

    def __repr__(self):
        return "<Group %s>" % (''.join(chr(c) for c in getattr(self, "label", [])))



############################################################

class Subrecord(BaseRecord):
    HEADER_STRUCT = struct.Struct("<H")
    HEADER_SIZE = HEADER_STRUCT.size + 4

    @classmethod
    def read(cls, fd):
        rec = cls()
        rec._file_offset = fd.tell()
        rec._tag, = st_unpack("4s", fd.read(4))
        rec.read_header(fd)
        return rec

    def read_header(self, fd):
        self._dataSize, = self.HEADER_STRUCT.unpack(
            fd.read(self.HEADER_STRUCT.size))

    def __repr__(self):
        return "<Subrecord %s>" % (getattr(self, "type", "None"))


class SubrecordGroup:
    '''These are abstract groups of Subrecords.

    They are not themselves rendered as subrecords themselves, but their children
    are rendered, in the declared order.
    '''
    pass


###########################################################

class Scalar(Subrecord):
    # single data value
    pass

class Struct(Subrecord):
    # named sequence of data values of individual type
    pass

class Sequence(Subrecord):
    # sequence of homogenuous data values
    pass

## Data types
class DataValue(object): pass
class Blob(DataValue): pass

class NoValue(DataValue): pass # Null/zero size data

class CharSequence(DataValue): pass
class Str4(CharSequence): pass
class String(CharSequence): pass
class LString(CharSequence): pass
class Text(String): pass

### Numbers
class Number(DataValue):
    pass

class Byte(Number):
    size = 8
    python_type = int
    binary_format = '<b'

class UnsignedByte(Number):
    size = 8
    python_type = int
    binary_format = '<B'

class UnsignedInteger(Number):
    size = 32
    python_type = int
    binary_format = '<I'


class Integer(Number):
    size = 32
    python_type = int
    binary_format = '<i'


class UnsignedShort(Number):
    size = 16
    python_type = int
    binary_format = '<H'

class Short(Number):
    size = 16
    python_type = int
    binary_format = '<h'

class Float(Number):
    size = 32
    python_type = float
    binary_format = 'f'

class Long(Number):
    size = 64
    python_type = int
    binary_format = '<q'

class UnsignedLong(Number):
    size = 64
    python_type = int
    binary_format = '<Q'

class FormId(DataValue):
    size = 32
    python_type = 'int'
    binary_format = '<I'

class Reference(FormId): pass
class OwnedReference(FormId): pass


#########

class FilePath(String): pass

class Color: pass # RGBA
class Point3D: pass
class Line3D: pass
class Box3D: pass

#########


class AttributeBase(object):
    def __init__(self, field_tag, desc, data_type, size,
                    null = False): pass

class Attribute(AttributeBase): pass

class SingleSelect(Attribute): pass
class MultiSelect(Attribute): pass

class AttributeSequence(AttributeBase): pass

class SingleSelectSequence(AttributeSequence): pass
class MultiSelectSequence(AttributeSequence): pass

class ReferenceBase(AttributeBase): pass
class Reference(ReferenceBase): pass
class ReferenceSequence(ReferenceBase): pass


####################
# Descriptors - TODO

# this should be incremented by 1 and assigned to each new descriptor so that we
# can define field order when the enclosing class is finally created (in __new__)


class subrecord(OrderedDescriptor):
    def __init__(self, class_name, tag, null = False, size = None, default_value = None,
                fixed_value = None):
        super(subrecord, self).__init__()

        self.class_name = class_name
        self.tag = tag
        self.null = null
        self.size = size
        self.default_value = None
        self.fixed_value = None

subrecord_set = subrecord

class scalar(OrderedDescriptor):
    def __init__(self, tag, data_type, null = False, size = None, default_value = None,
                fixed_value = None):
        super(scalar, self).__init__()

        self.tag = tag
        self.data_type = data_type
        self.null = null
        self.size = size
        self.default_value = None
        self.fixed_value = None

scalar_set = scalar

class structure(OrderedDescriptor):
    def __init__(self, struct_type, tag, null = False, size = None, default_value = None,
                fixed_value = None):
        super(structure, self).__init__()

        self.struct_type = struct_type
        self.tag = tag
        self.null = null
        self.size = size
        self.default_value = None
        self.fixed_value = None

structure_set = structure


class field(OrderedDescriptor):
    def __init__(self, data_type, null = False, default_value = True, fixed_value = None):
        super(field, self).__init__()

        self.data_type = data_type
        self.null = null
        self.default_value = None
        self.fixed_value = None


field_set = field

class reference_field(OrderedDescriptor):
    def __init__(self, refers_to, null = False):
        super(reference_field, self).__init__()

        self.refers_to = refers_to
        self.null = null

reference_field_set = reference_field


class reference(OrderedDescriptor):
    def __init__(self, tag, refers_to, null = True):
        super(reference, self).__init__()

        self.refers_to = refers_to
        self.tag = tag
        self.null = null

reference_set = reference


class subrecord_group(OrderedDescriptor):
    def __init__(self, subrecord_type, null = True):
        super(subrecord_group, self).__init__()

        self.subrecord_type = subrecord_type
        self.null = null

subrecord_group_set = subrecord_group





__doc__=\
'''Object model for the ESP/ESM file format.

This is all hand-written at the moment. It forms the substructure for
the actual records and fields that are currently generated using sniptopy.py.
'''

import game as game_
import struct
from struct import unpack as st_unpack

RECORD_TYPES = {}
KNOWN_SUBGROUPS = '''REFR ACHR NAVM PGRE PHZD LAND INFO'''.strip().split()
EMPTY_GROUP_RECORDS = '''CLDC HAIR RGDL SCPT SCOL PWAT'''.strip().split()

def record_type(rec_tag):
    def tag_record_class(klazz):
        if rec_tag in RECORD_TYPES:
            print AssertionError,  \
                ("trying to register %s for existing tag %s, mapped to %s" %
                            (rec_tag, klazz, RECORD_TYPES[rec_tag]))
        else:
            RECORD_TYPES[rec_tag] = klazz
            klazz.___REC___ = rec_tag

        return klazz
    return tag_record_class


##########################################
# Manage smart, ordered attr descriptors.
#
# When new subclasses of BaseRecord are created, any declared OrderedDescriptor
# subclasses are ordered into a "__order__" class attribute, based on the
# "declaration_order" attribute, which is expected to be an integer set by the
# descriptor's constructor.

DEFINITION_COUNTER = 0

class OrderedDescriptor(object):
    def __init__(self):
        global DEFINITION_COUNTER
        self.declaration_order = DEFINITION_COUNTER     # will be used by __new__ to get order
        DEFINITION_COUNTER += 1
        self.attribute_name = None          # will be set by __new__

        ####

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.attribute_name)

    def __set__(self, obj, value):
        raise NotImplementedError, ("set %s.%s to %s" % (self, self.attribute_name, value))

    def __get__(self, obj):
        raise NotImplementedError

    @classmethod
    def lookup_type(cls, type_name):
        '''Helps to resolve text type name to class once everything is defined.
        '''
        raise NotImplementedError


class OrderedAttributes(type):
    def __new__(cls, name, supers, class_dict):

        sub_cls = super(OrderedAttributes, cls).__new__(cls, name, supers, class_dict)

        # configure __order__ class attribute to match declaration order of descriptors.
        attr_order = []
        for attr_name, descriptor in class_dict.iteritems():
            if isinstance(descriptor, OrderedDescriptor):
                attr_order.append((descriptor.declaration_order, descriptor))
                descriptor.attribute_name = attr_name

        attr_order.sort()

        old_order = getattr(sub_cls, '__order__', [])
        sub_cls.__order__ = tuple(list(old_order) + [desc for (n, desc) in attr_order])

        return sub_cls


##################################################

class BaseRecord(object):
    '''Core superclass for TES ESP/ESM records and subrecords.

    There's a great deal of magic going on in here.

    Descriptors of type OrderedDescriptor get registered by their declaration order
    in the class, so that the __order__ class tuple can be used for reading and writing
    to the plugin file.

    Subclass descriptors get ordered AFTER their superclass descriptors.
    '''
    def __init__(self, *args, **kwargs):
        pass

    __metaclass__ = OrderedAttributes

    # Read constructor for all record subclasses
    @staticmethod
    def read_from(fd):
        record_offset = fd.tell()
        typed = fd.read(4)
        if not typed: return None

        try:
            rec_type, = st_unpack("4s", typed)
        except:
            print "Fail read header at:", repr(typed)
            raise

        rc_class = RECORD_TYPES[rec_type]
        rec = rc_class()
        rec._tag = rec_type
        rec._file_offset = record_offset
        rec.read_header(fd)
        return rec

    ## Concrete subclasses should override
    def read_header(self, fd):  raise NotImplementedError
    def has_subrecords(self):   return False
    def record_size(self):      return self.HEADER_SIZE + self._dataSize
    def data_size(self):        return self._dataSize
    def data_offset(self):      return self._file_offset + self.HEADER_SIZE
    def data_end_offset(self):  return self._file_offset + self.record_size()

    ## Convenience functions - common

    def seek_data(self, fd):
        fd.seek(self.data_offset())
        return self.data_size()

    def read_data(self, fd):
        self.seek_data(fd)
        return fd.read(self.data_size())

    def skip(self, fd):
        fd.seek(self.data_end_offset())



##########################################


class Record(BaseRecord):
    HEADER_STRUCT = struct.Struct("<4I2H")
    HEADER_SIZE = HEADER_STRUCT.size + 4

    def read_header(self, fd):
        (self._dataSize,
         self._flags,
         self._record_id,
         self._revision,
         self._version,
         self._unknown) = self.HEADER_STRUCT.unpack(
                fd.read(self.HEADER_STRUCT.size))

    def has_subrecords(self):   return True

    def read_subrecords(self, fd):
        if self._flags & self.F_COMPRESSED:
            raise Warning, "No reading of compressed data yet."
            self.skip(fd)
            return

        end_offset = self.data_end_offset()
        fd.seek(self.data_offset())

        prev_subrecord = None
        while fd.tell() < end_offset:
            subrecord = Subrecord.read(fd)
            #if prev_subrecord
            yield subrecord
            prev_subrecord = subrecord
            subrecord.skip(fd)

    F_MASTER    = 0x01
    F_DELETED   = 0x20
    F_SHIELDS   = 0x40
    F_LOCALIZED = 0x80
    F_HIDDEN    = 0x200 # REFR
    F_DEAD      = 0x200 # ACHR
    F_QUEST_ITEM= 0x400
    F_MAIN_MENU_DISPLAY= F_QUEST_ITEM # # LSCR
    F_INITIALLY_DISABLED = 0x800
    F_IGNORED   = 0x1000
    F_DANGEROUS = 0x20000 # CELL
    F_COMPRESSED =0x40000
    F_NO_WAIT   = 0x80000

    def __repr__(self):
        return "<%s %s at %08X>" % (self.__class__.__name__,
                                getattr(self, "type", "None"),
                                getattr(self, "record_id", 0))

    def flag_list(self):
        return [fname for (fname, mask) in self.__FLAGS__.items()
                        if self._flags & mask]


Record.__FLAGS__ = dict((fk,fv) for (fk,fv) in Record.__dict__.items() if fk.startswith('F_'))

@record_type('GRUP')
class Group(BaseRecord):
    HEADER_STRUCT = struct.Struct("<I4BI4H")
    HEADER_SIZE = HEADER_STRUCT.size + 4

    def read_header(self, fd):
        self.label = [0, 0, 0, 0]
        (self._groupSize,
        self.label[0], self.label[1], self.label[2], self.label[3],
        self.groupType,
        self.stamp,
        self._unknown1,
        self._version,
        self._unknown2) = self.HEADER_STRUCT.unpack(fd.read(self.HEADER_STRUCT.size))

    def record_size(self):  return self._groupSize
    def data_size(self):    return self._groupSize - self.HEADER_SIZE
    def flag_list(self):    return [] # TODO
    def has_subrecords(self): return True
    def has_subrecords(self): return False
    def read_subrecords(self, fd): return []

    def __repr__(self):
        return "<Group %s>" % (''.join(chr(c) for c in getattr(self, "label", [])))



############################################################

class Subrecord(BaseRecord):
    HEADER_STRUCT = struct.Struct("<H")
    HEADER_SIZE = HEADER_STRUCT.size + 4

    @classmethod
    def read(cls, fd):
        rec = cls()
        rec._file_offset = fd.tell()
        rec._tag, = st_unpack("4s", fd.read(4))
        rec.read_header(fd)
        return rec

    def read_header(self, fd):
        self._dataSize, = self.HEADER_STRUCT.unpack(
            fd.read(self.HEADER_STRUCT.size))

    def __repr__(self):
        return "<Subrecord %s>" % (getattr(self, "type", "None"))


class SubrecordGroup:
    '''These are abstract groups of Subrecords.

    They are not themselves rendered as subrecords themselves, but their children
    are rendered, in the declared order.
    '''
    pass


###########################################################

class Scalar(Subrecord):
    # single data value
    pass

class Struct(Subrecord):
    # named sequence of data values of individual type
    pass

class Sequence(Subrecord):
    # sequence of homogenuous data values
    pass

## Data types
class DataValue(object): pass
class Blob(DataValue): pass

class NoValue(DataValue): pass # Null/zero size data

class CharSequence(DataValue): pass
class Str4(CharSequence): pass
class String(CharSequence): pass
class LString(CharSequence): pass
class Text(String): pass

### Numbers
class Number(DataValue):
    pass

class Byte(Number):
    size = 8
    python_type = int
    binary_format = '<b'

class UnsignedByte(Number):
    size = 8
    python_type = int
    binary_format = '<B'

class UnsignedInteger(Number):
    size = 32
    python_type = int
    binary_format = '<I'


class Integer(Number):
    size = 32
    python_type = int
    binary_format = '<i'


class UnsignedShort(Number):
    size = 16
    python_type = int
    binary_format = '<H'

class Short(Number):
    size = 16
    python_type = int
    binary_format = '<h'

class Float(Number):
    size = 32
    python_type = float
    binary_format = 'f'

class Long(Number):
    size = 64
    python_type = int
    binary_format = '<q'

class UnsignedLong(Number):
    size = 64
    python_type = int
    binary_format = '<Q'

class FormId(DataValue):
    size = 32
    python_type = 'int'
    binary_format = '<I'

class Reference(FormId): pass
class OwnedReference(FormId): pass


#########

class FilePath(String): pass

class Color: pass # RGBA
class Point3D: pass
class Line3D: pass
class Box3D: pass

#########


class AttributeBase(object):
    def __init__(self, field_tag, desc, data_type, size,
                    null = False): pass

class Attribute(AttributeBase): pass

class SingleSelect(Attribute): pass
class MultiSelect(Attribute): pass

class AttributeSequence(AttributeBase): pass

class SingleSelectSequence(AttributeSequence): pass
class MultiSelectSequence(AttributeSequence): pass

class ReferenceBase(AttributeBase): pass
class Reference(ReferenceBase): pass
class ReferenceSequence(ReferenceBase): pass


####################
# Descriptors - TODO

# this should be incremented by 1 and assigned to each new descriptor so that we
# can define field order when the enclosing class is finally created (in __new__)


class subrecord(OrderedDescriptor):
    def __init__(self, class_name, tag, null = False, size = None, default_value = None,
                fixed_value = None):
        super(subrecord, self).__init__()

        self.class_name = class_name
        self.tag = tag
        self.null = null
        self.size = size
        self.default_value = None
        self.fixed_value = None

subrecord_set = subrecord

class scalar(OrderedDescriptor):
    def __init__(self, tag, data_type, null = False, size = None, default_value = None,
                fixed_value = None):
        super(scalar, self).__init__()

        self.tag = tag
        self.data_type = data_type
        self.null = null
        self.size = size
        self.default_value = None
        self.fixed_value = None

scalar_set = scalar

class structure(OrderedDescriptor):
    def __init__(self, struct_type, tag, null = False, size = None, default_value = None,
                fixed_value = None):
        super(structure, self).__init__()

        self.struct_type = struct_type
        self.tag = tag
        self.null = null
        self.size = size
        self.default_value = None
        self.fixed_value = None

structure_set = structure


class field(OrderedDescriptor):
    def __init__(self, data_type, null = False, default_value = True, fixed_value = None):
        super(field, self).__init__()

        self.data_type = data_type
        self.null = null
        self.default_value = None
        self.fixed_value = None


field_set = field

class reference_field(OrderedDescriptor):
    def __init__(self, refers_to, null = False):
        super(reference_field, self).__init__()

        self.refers_to = refers_to
        self.null = null

reference_field_set = reference_field


class reference(OrderedDescriptor):
    def __init__(self, tag, refers_to, null = True):
        super(reference, self).__init__()

        self.refers_to = refers_to
        self.tag = tag
        self.null = null

reference_set = reference


class subrecord_group(OrderedDescriptor):
    def __init__(self, subrecord_type, null = True):
        super(subrecord_group, self).__init__()

        self.subrecord_type = subrecord_type
        self.null = null

subrecord_group_set = subrecord_group




Object model for the ESP/ESM file format.
 
 This is all hand-written at the moment. It forms the substructure for
 the actual records and fields that are currently generated using sniptopy.py. REFR ACHR NAVM PGRE PHZD LAND INFO CLDC HAIR RGDL SCPT SCOL PWAT ########################################## # Manage smart, ordered attr descriptors. # # When new subclasses of BaseRecord are created, any declared OrderedDescriptor # subclasses are ordered into a "__order__" class attribute, based on the # "declaration_order" attribute, which is expected to be an integer set by the # descriptor's constructor. # will be used by __new__ to get order # will be set by __new__ #### Helps to resolve text type name to class once everything is defined. # configure __order__ class attribute to match declaration order of descriptors. ################################################## Core superclass for TES ESP/ESM records and subrecords.
 
     There's a great deal of magic going on in here.
 
     Descriptors of type OrderedDescriptor get registered by their declaration order
     in the class, so that the __order__ class tuple can be used for reading and writing
     to the plugin file.
 
     Subclass descriptors get ordered AFTER their superclass descriptors. # Read constructor for all record subclasses ## Concrete subclasses should override ## Convenience functions - common ########################################## #if prev_subrecord # REFR # ACHR # # LSCR # CELL # TODO ############################################################ These are abstract groups of Subrecords.
 
     They are not themselves rendered as subrecords themselves, but their children
     are rendered, in the declared order. ########################################################### # single data value # named sequence of data values of individual type # sequence of homogenuous data values ## Data types # Null/zero size data ### Numbers ######### # RGBA ######### #################### # Descriptors - TODO # this should be incremented by 1 and assigned to each new descriptor so that we # can define field order when the enclosing class is finally created (in __new__)