#  • ▌ ▄ ·. ▄▄▄▄·     ▄▄▄ .·▄▄▄▄  ▪  ▄▄▄▄▄      ▄▄▄       ▄▄▄·▄▄▄       
#  ·██ ▐███▪▐█ ▀█▪    ▀▄.▀·██▪ ██ ██ •██  ▪     ▀▄ █·    ▐█ ▄█▀▄ █·▪    
#  ▐█ ▌▐▌▐█·▐█▀▀█▄    ▐▀▀▪▄▐█· ▐█▌▐█· ▐█.▪ ▄█▀▄ ▐▀▀▄      ██▀·▐▀▀▄  ▄█▀▄
#  ██ ██▌▐█▌██▄▪▐█    ▐█▄▄▌██. ██ ▐█▌ ▐█▌·▐█▌.▐▌▐█•█▌    ▐█▪·•▐█•█▌▐█▌.▐▌
#  ▀▀  █▪▀▀▀·▀▀▀▀      ▀▀▀ ▀▀▀▀▀• ▀▀▀ ▀▀▀  ▀█▄▀▪.▀  ▀    .▀   .▀  ▀ ▀█▄▀▪
#                Magicbane Emulator Project © 2013 - 2022
#                           www.magicbane.com

from collections import OrderedDict

from arcane.util import ResStream

TRACKER_TO_STRING = {
    0: 'NONE',
    1: 'XY',
    2: 'Y',
}
STRING_TO_TRACKER = {value: key for key, value in TRACKER_TO_STRING.items()}

TRANSPARENT_TO_STRING = {
    0: 'NONE',
    1: 'PINK',
    2: 'BLACK',
    3: 'WHITE',
    4: 'SEMI',
    6: 'ALPHA',
}
STRING_TO_TRANSPARENT = {value: key for key, value in TRANSPARENT_TO_STRING.items()}

TEXTURE_TO_STRING = {
    0: 'SINGLE_TEXTURE',
    1: 'COLOR_TEXTURE',
    3: 'ANIMATED_TEXTURE',
}
STRING_TO_TEXTURE = {value: key for key, value in TEXTURE_TO_STRING.items()}

LIGHT_TYPE_TO_STRING = {
    0xb6787258: 'ArcLightPoint',
    0x54e8ff1d: 'ArcLightAffectorAttach',
    0xa73bd9d4: 'ArcLightAffectorFlicker',
}
STRING_TO_LIGHT_TYPE = {value: key for key, value in LIGHT_TYPE_TO_STRING.items()}


class ArcSinglePolyMesh:
    def load_binary(self, stream: ResStream):
        self.polymesh_id = stream.read_qword()
        self.polymesh_decal = stream.read_bool()
        self.polymesh_double_sided = stream.read_bool()

    def save_binary(self, stream: ResStream):
        stream.write_qword(self.polymesh_id)
        stream.write_bool(self.polymesh_decal)
        stream.write_bool(self.polymesh_double_sided)

    def load_json(self, data):
        self.polymesh_id = data['polymesh_id']
        self.polymesh_decal = data['polymesh_decal']
        self.polymesh_double_sided = data['polymesh_double_sided']

    def save_json(self):
        data = OrderedDict()
        data['polymesh_id'] = self.polymesh_id
        data['polymesh_decal'] = self.polymesh_decal
        data['polymesh_double_sided'] = self.polymesh_double_sided
        return data


class ArcMeshSet:
    def load_binary(self, stream: ResStream):
        num = stream.read_dword()
        self.mesh_set = [ArcSinglePolyMesh() for _ in range(num)]
        for mesh in self.mesh_set:
            mesh.load_binary(stream)

    def save_binary(self, stream: ResStream):
        stream.write_dword(len(self.mesh_set))
        for mesh in self.mesh_set:
            mesh.save_binary(stream)

    def load_json(self, data):
        self.mesh_set = []
        for mesh_data in data['mesh_set']:
            mesh = ArcSinglePolyMesh()
            mesh.load_json(mesh_data)
            self.mesh_set.append(mesh)

    def save_json(self):
        data = OrderedDict()
        data['mesh_set'] = []
        for mesh in self.mesh_set:
            data['mesh_set'].append(mesh.save_json())
        return data


class ArcRenderTemplate:
    def load_binary(self, stream: ResStream):
        self.template_object_can_fade = stream.read_bool()
        self.template_tracker = stream.read_dword()
        self.template_illuminated = stream.read_bool()
        self.template_bone_length = stream.read_float()
        self.template_clip_map = stream.read_dword()
        self.template_light_two_side = stream.read_dword()
        self.template_cull_face = stream.read_dword()
        self.template_specular_map = stream.read_qword()
        self.template_shininess = stream.read_float()
        self.template_has_mesh = stream.read_bool()
        if self.template_has_mesh:
            self.template_mesh = ArcMeshSet()
            self.template_mesh.load_binary(stream)

    def save_binary(self, stream: ResStream):
        stream.write_bool(self.template_object_can_fade)
        stream.write_dword(self.template_tracker)
        stream.write_bool(self.template_illuminated)
        stream.write_float(self.template_bone_length)
        stream.write_dword(self.template_clip_map)
        stream.write_dword(self.template_light_two_side)
        stream.write_dword(self.template_cull_face)
        stream.write_qword(self.template_specular_map)
        stream.write_float(self.template_shininess)
        stream.write_bool(self.template_has_mesh)
        if self.template_has_mesh:
            self.template_mesh.save_binary(stream)

    def load_json(self, data):
        self.template_object_can_fade = data['template_object_can_fade']
        self.template_tracker = STRING_TO_TRACKER[data['template_tracker']]
        self.template_illuminated = data['template_illuminated']
        self.template_bone_length = data['template_bone_length']
        self.template_clip_map = data['template_clip_map']
        self.template_light_two_side = data['template_light_two_side']
        self.template_cull_face = data['template_cull_face']
        self.template_specular_map = data['template_specular_map']
        self.template_shininess = data['template_shininess']
        self.template_has_mesh = data['template_has_mesh']
        if self.template_has_mesh:
            self.template_mesh = ArcMeshSet()
            self.template_mesh.load_json(data['template_mesh'])

    def save_json(self):
        data = OrderedDict()
        data['template_object_can_fade'] = self.template_object_can_fade
        data['template_tracker'] = TRACKER_TO_STRING[self.template_tracker]
        data['template_illuminated'] = self.template_illuminated
        data['template_bone_length'] = self.template_bone_length
        data['template_clip_map'] = self.template_clip_map
        data['template_light_two_side'] = self.template_light_two_side
        data['template_cull_face'] = self.template_cull_face
        data['template_specular_map'] = self.template_specular_map
        data['template_shininess'] = self.template_shininess
        data['template_has_mesh'] = self.template_has_mesh
        if self.template_has_mesh:
            data['template_mesh'] = self.template_mesh.save_json()
        return data


class ArcSingleTexture:
    def load_binary(self, stream: ResStream):
        self.texture_id = stream.read_qword()
        self.texture_transparent = stream.read_dword()
        self.texture_compress = stream.read_bool()
        self.texture_normal_map = stream.read_bool()
        self.texture_detail_normal_map = stream.read_bool()
        self.texture_create_mip_maps = stream.read_bool()
        self.texture_x0 = stream.read_string()
        self.texture_x1 = stream.read_string()
        self.texture_x2 = stream.read_dword()
        self.texture_x3 = stream.read_dword()
        self.texture_x4 = stream.read_bool()
        self.texture_wrap = stream.read_bool()

    def save_binary(self, stream: ResStream):
        stream.write_qword(self.texture_id)
        stream.write_dword(self.texture_transparent)
        stream.write_bool(self.texture_compress)
        stream.write_bool(self.texture_normal_map)
        stream.write_bool(self.texture_detail_normal_map)
        stream.write_bool(self.texture_create_mip_maps)
        stream.write_string(self.texture_x0)
        stream.write_string(self.texture_x1)
        stream.write_dword(self.texture_x2)
        stream.write_dword(self.texture_x3)
        stream.write_bool(self.texture_x4)
        stream.write_bool(self.texture_wrap)

    def load_json(self, data):
        self.texture_id = data['texture_id']
        self.texture_transparent = STRING_TO_TRANSPARENT[data['texture_transparent']]
        self.texture_compress = data['texture_compress']
        self.texture_normal_map = data['texture_normal_map']
        self.texture_detail_normal_map = data['texture_detail_normal_map']
        self.texture_create_mip_maps = data['texture_create_mip_maps']
        self.texture_x0 = ''
        self.texture_x1 = ''
        self.texture_x2 = 255
        self.texture_x3 = 0
        self.texture_x4 = False
        self.texture_wrap = data['texture_wrap']

    def save_json(self):
        data = OrderedDict()
        data['texture_id'] = self.texture_id
        data['texture_transparent'] = TRANSPARENT_TO_STRING[self.texture_transparent]
        data['texture_compress'] = self.texture_compress
        data['texture_normal_map'] = self.texture_normal_map
        data['texture_detail_normal_map'] = self.texture_detail_normal_map
        data['texture_create_mip_maps'] = self.texture_create_mip_maps
        data['texture_wrap'] = self.texture_wrap
        return data


class ArcColorTexture(ArcSingleTexture):
    pass


class ArcAnimatedTexture:
    def load_binary(self, stream: ResStream):
        self.animated_texture_id = stream.read_qword()
        self.animated_texture_transparent = stream.read_dword()
        self.animated_texture_compress = stream.read_bool()
        self.animated_texture_normal_map = stream.read_bool()
        self.animated_texture_detail_normal_map = stream.read_bool()
        self.animated_texture_create_mip_maps = stream.read_bool()
        self.animated_texture_frame_timer = stream.read_float()
        self.animated_texture_x0 = stream.read_float()
        self.animated_texture_frame_rand = stream.read_dword()

        num = stream.read_dword()
        self.animated_texture_sets = [ArcTextureSet() for _ in range(num)]
        for texture in self.animated_texture_sets:
            texture.load_binary(stream)

    def save_binary(self, stream: ResStream):
        stream.write_qword(self.animated_texture_id)
        stream.write_dword(self.animated_texture_transparent)
        stream.write_bool(self.animated_texture_compress)
        stream.write_bool(self.animated_texture_normal_map)
        stream.write_bool(self.animated_texture_detail_normal_map)
        stream.write_bool(self.animated_texture_create_mip_maps)
        stream.write_float(self.animated_texture_frame_timer)
        stream.write_float(self.animated_texture_x0)
        stream.write_dword(self.animated_texture_frame_rand)

        stream.write_dword(len(self.animated_texture_sets))
        for texture in self.animated_texture_sets:
            texture.save_binary(stream)

    def load_json(self, data):
        self.animated_texture_id = data['animated_texture_id']
        self.animated_texture_transparent = STRING_TO_TRANSPARENT[data['animated_texture_transparent']]
        self.animated_texture_compress = data['animated_texture_compress']
        self.animated_texture_normal_map = data['animated_texture_normal_map']
        self.animated_texture_detail_normal_map = data['animated_texture_detail_normal_map']
        self.animated_texture_create_mip_maps = data['animated_texture_create_mip_maps']
        self.animated_texture_frame_timer = data['animated_texture_frame_timer']
        self.animated_texture_x0 = 0.0
        self.animated_texture_frame_rand = data['animated_texture_frame_rand']
        self.animated_texture_sets = []
        for texture_data in data['animated_texture_sets']:
            texture = ArcTextureSet()
            texture.load_json(texture_data)
            self.animated_texture_sets.append(texture)

    def save_json(self):
        data = OrderedDict()
        data['animated_texture_id'] = self.animated_texture_id
        data['animated_texture_transparent'] = TRANSPARENT_TO_STRING[self.animated_texture_transparent]
        data['animated_texture_compress'] = self.animated_texture_compress
        data['animated_texture_normal_map'] = self.animated_texture_normal_map
        data['animated_texture_detail_normal_map'] = self.animated_texture_detail_normal_map
        data['animated_texture_create_mip_maps'] = self.animated_texture_create_mip_maps
        data['animated_texture_frame_timer'] = self.animated_texture_frame_timer
        data['animated_texture_frame_rand'] = self.animated_texture_frame_rand
        data['animated_texture_sets'] = []
        for texture in self.animated_texture_sets:
            data['animated_texture_sets'].append(texture.save_json())
        return data


class ArcTextureSet:
    def load_binary(self, stream: ResStream):
        self.texture_type = stream.read_dword()
        if self.texture_type == 0:
            self.texture_data = ArcSingleTexture()
        elif self.texture_type == 1:
            self.texture_data = ArcColorTexture()
        elif self.texture_type == 3:
            self.texture_data = ArcAnimatedTexture()
        self.texture_data.load_binary(stream)

    def save_binary(self, stream: ResStream):
        stream.write_dword(self.texture_type)
        self.texture_data.save_binary(stream)

    def load_json(self, data):
        self.texture_type = STRING_TO_TEXTURE[data['texture_type']]
        if self.texture_type == 0:
            self.texture_data = ArcSingleTexture()
        elif self.texture_type == 1:
            self.texture_data = ArcColorTexture()
        elif self.texture_type == 3:
            self.texture_data = ArcAnimatedTexture()
        self.texture_data.load_json(data['texture_data'])

    def save_json(self):
        data = OrderedDict()
        data['texture_type'] = TEXTURE_TO_STRING[self.texture_type]
        data['texture_data'] = self.texture_data.save_json()
        return data


class ArcLightPoint:
    def load_binary(self, stream: ResStream):
        self.lightpoint_x0 = stream.read_dword()
        self.lightpoint_x1 = stream.read_bool()
        self.lightpoint_shader = stream.read_bool()
        self.lightpoint_update_offscreen = stream.read_bool()
        self.lightpoint_radius = stream.read_float()
        self.lightpoint_position = stream.read_tuple()
        self.lightpoint_diffuse_color = [stream.read_float() for _ in range(4)]
        self.lightpoint_x2 = stream.read_dword()
        self.lightpoint_orientation = [stream.read_float() for _ in range(4)]
        self.lightpoint_cubemap = stream.read_dword()
        self.lightpoint_x3 = stream.read_bool()

    def save_binary(self, stream: ResStream):
        stream.write_dword(self.lightpoint_x0)
        stream.write_bool(self.lightpoint_x1)
        stream.write_bool(self.lightpoint_shader)
        stream.write_bool(self.lightpoint_update_offscreen)
        stream.write_float(self.lightpoint_radius)
        stream.write_tuple(self.lightpoint_position)
        for i in range(4):
            stream.write_float(self.lightpoint_diffuse_color[i])
        stream.write_dword(self.lightpoint_x2)
        for i in range(4):
            stream.write_float(self.lightpoint_orientation[i])
        stream.write_dword(self.lightpoint_cubemap)
        stream.write_bool(self.lightpoint_x3)

    def load_json(self, data):
        self.lightpoint_x0 = 1
        self.lightpoint_x1 = True
        self.lightpoint_shader = data['lightpoint_shader']
        self.lightpoint_update_offscreen = data['lightpoint_update_offscreen']
        self.lightpoint_radius = data['lightpoint_radius']
        self.lightpoint_position = data['lightpoint_position']
        self.lightpoint_diffuse_color = data['lightpoint_diffuse_color']
        self.lightpoint_x2 = 1
        self.lightpoint_orientation = data['lightpoint_orientation']
        self.lightpoint_cubemap = data['lightpoint_cubemap']
        self.lightpoint_x3 = False

    def save_json(self):
        data = OrderedDict()
        data['lightpoint_shader'] = self.lightpoint_shader
        data['lightpoint_update_offscreen'] = self.lightpoint_update_offscreen
        data['lightpoint_radius'] = self.lightpoint_radius
        data['lightpoint_position'] = self.lightpoint_position
        data['lightpoint_diffuse_color'] = self.lightpoint_diffuse_color
        data['lightpoint_orientation'] = self.lightpoint_orientation
        data['lightpoint_cubemap'] = self.lightpoint_cubemap
        return data


class ArcLightAffectorAttach:
    def load_binary(self, stream: ResStream):
        self.attach_x0 = stream.read_dword()
        self.attach_offset = stream.read_tuple()

    def save_binary(self, stream: ResStream):
        stream.write_dword(self.attach_x0)
        stream.write_tuple(self.attach_offset)

    def load_json(self, data):
        self.attach_x0 = 1
        self.attach_offset = data['attach_offset']

    def save_json(self):
        data = OrderedDict()
        data['attach_offset'] = self.attach_offset
        return data


class ArcLightAffectorFlicker:
    def load_binary(self, stream: ResStream):
        self.flicker_x0 = stream.read_dword()
        self.flicker_avg_period = stream.read_float()
        self.flicker_std_dev_radius = stream.read_float()
        self.flicker_std_dev_period = stream.read_float()
        self.flicker_falloff = stream.read_float()

    def save_binary(self, stream: ResStream):
        stream.write_dword(self.flicker_x0)
        stream.write_float(self.flicker_avg_period)
        stream.write_float(self.flicker_std_dev_radius)
        stream.write_float(self.flicker_std_dev_period)
        stream.write_float(self.flicker_falloff)

    def load_json(self, data):
        self.flicker_x0 = 1
        self.flicker_avg_period = data['flicker_avg_period']
        self.flicker_std_dev_radius = data['flicker_std_dev_radius']
        self.flicker_std_dev_period = data['flicker_std_dev_period']
        self.flicker_falloff = data['flicker_falloff']

    def save_json(self):
        data = OrderedDict()
        data['flicker_avg_period'] = self.flicker_avg_period
        data['flicker_std_dev_radius'] = self.flicker_std_dev_radius
        data['flicker_std_dev_period'] = self.flicker_std_dev_period
        data['flicker_falloff'] = self.flicker_falloff
        return data


class ArcLightAffectors:
    def load_binary(self, stream: ResStream):
        self.light_affector_type = stream.read_dword()

        if self.light_affector_type == 0x54e8ff1d:
            self.light_affector_data = ArcLightAffectorAttach()
        elif self.light_affector_type == 0xa73bd9d4:
            self.light_affector_data = ArcLightAffectorFlicker()
        self.light_affector_data.load_binary(stream)

        self.light_affector_0xdaed = stream.read_dword()

    def save_binary(self, stream: ResStream):
        stream.write_dword(self.light_affector_type)
        self.light_affector_data.save_binary(stream)
        stream.write_dword(self.light_affector_0xdaed)

    def load_json(self, data):
        self.light_affector_type = STRING_TO_LIGHT_TYPE[data['light_affector_type']]
        if self.light_affector_type == 0x54e8ff1d:
            self.light_affector_data = ArcLightAffectorAttach()
        elif self.light_affector_type == 0xa73bd9d4:
            self.light_affector_data = ArcLightAffectorFlicker()
        self.light_affector_data.load_json(data['light_affector_data'])
        self.light_affector_0xdaed = 0xddaaeedd

    def save_json(self):
        data = OrderedDict()
        data['light_affector_type'] = LIGHT_TYPE_TO_STRING[self.light_affector_type]
        data['light_affector_data'] = self.light_affector_data.save_json()
        return data


class ArcLight:
    def load_binary(self, stream: ResStream):
        self.light_x0 = stream.read_dword()
        self.light_x1 = stream.read_bool()
        self.light_type = stream.read_dword()

        if self.light_type == 0xb6787258:
            self.light_data = ArcLightPoint()
        self.light_data.load_binary(stream)

        self.light_0xdaed = stream.read_dword()

        num = stream.read_dword()
        self.light_affectors = [ArcLightAffectors() for _ in range(num)]
        for extra in self.light_affectors:
            extra.load_binary(stream)

    def save_binary(self, stream: ResStream):
        stream.write_dword(self.light_x0)
        stream.write_bool(self.light_x1)
        stream.write_dword(self.light_type)
        self.light_data.save_binary(stream)
        stream.write_dword(self.light_0xdaed)

        stream.write_dword(len(self.light_affectors))
        for extra in self.light_affectors:
            extra.save_binary(stream)

    def load_json(self, data):
        self.light_x0 = 1
        self.light_x1 = True
        self.light_type = STRING_TO_LIGHT_TYPE[data['light_type']]
        if self.light_type == 0xb6787258:
            self.light_data = ArcLightPoint()
        self.light_data.load_json(data['light_data'])
        self.light_0xdaed = 0xddaaeedd

        self.light_affectors = []
        for extra_data in data['light_affectors']:
            extra = ArcLightAffectors()
            extra.load_json(extra_data)
            self.light_affectors.append(extra)

    def save_json(self):
        data = OrderedDict()
        data['light_type'] = LIGHT_TYPE_TO_STRING[self.light_type]
        data['light_data'] = self.light_data.save_json()

        data['light_affectors'] = []
        for extra in self.light_affectors:
            data['light_affectors'].append(extra.save_json())
        return data


class ArcRender:
    def load_binary(self, stream: ResStream):
        self.render_template = ArcRenderTemplate()
        self.render_template.load_binary(stream)
        self.render_target_bone = stream.read_string()
        self.render_scale = stream.read_tuple()
        self.render_has_loc = stream.read_dword()
        if self.render_has_loc:
            self.render_loc = stream.read_tuple()
            num_children = stream.read_dword()
            self.render_children = [stream.read_qword() for _ in range(num_children)]
        self.render_has_texture_set = stream.read_bool()
        if self.render_has_texture_set:
            num = stream.read_dword()
            self.render_texture_set = [ArcTextureSet() for _ in range(num)]
            for texture in self.render_texture_set:
                texture.load_binary(stream)
        self.render_collides = stream.read_bool()
        self.render_calculate_bounding_box = stream.read_bool()
        self.render_nation_crest = stream.read_bool()
        self.render_guild_crest = stream.read_bool()
        self.render_bumped = stream.read_bool()
        self.render_vp_active = stream.read_bool()
        if self.render_vp_active:
            self.render_vp_name = stream.read_string()
            num_params = stream.read_dword()
            self.render_vp_params = [
                [
                    stream.read_dword(),
                    stream.read_float(),
                    stream.read_float(),
                    stream.read_float(),
                    stream.read_float(),
                ] for _ in range(num_params)
            ]
        self.render_has_light_effects = stream.read_bool()
        if self.render_has_light_effects:
            num_effects = stream.read_dword()
            self.render_light_effects = [ArcLight() for _ in range(num_effects)]
            for effect in self.render_light_effects:
                effect.load_binary(stream)

    def save_binary(self, stream: ResStream):
        self.render_template.save_binary(stream)
        stream.write_string(self.render_target_bone)
        stream.write_tuple(self.render_scale)
        stream.write_dword(self.render_has_loc)
        if self.render_has_loc:
            stream.write_tuple(self.render_loc)
            stream.write_dword(len(self.render_children))
            for child in self.render_children:
                stream.write_qword(child)
        stream.write_bool(self.render_has_texture_set)
        if self.render_has_texture_set:
            stream.write_dword(len(self.render_texture_set))
            for texture in self.render_texture_set:
                texture.save_binary(stream)
        stream.write_bool(self.render_collides)
        stream.write_bool(self.render_calculate_bounding_box)
        stream.write_bool(self.render_nation_crest)
        stream.write_bool(self.render_guild_crest)
        stream.write_bool(self.render_bumped)
        stream.write_bool(self.render_vp_active)
        if self.render_vp_active:
            stream.write_string(self.render_vp_name)

            stream.write_dword(len(self.render_vp_params))
            for param in self.render_vp_params:
                stream.write_dword(param[0])
                stream.write_float(param[1])
                stream.write_float(param[2])
                stream.write_float(param[3])
                stream.write_float(param[4])
        stream.write_bool(self.render_has_light_effects)
        if self.render_has_light_effects:
            stream.write_dword(len(self.render_light_effects))
            for effect in self.render_light_effects:
                effect.save_binary(stream)

    def load_json(self, data):
        self.render_template = ArcRenderTemplate()
        self.render_template.load_json(data['render_template'])
        self.render_target_bone = data['render_target_bone']
        self.render_scale = data['render_scale']
        self.render_has_loc = data['render_has_loc']
        if self.render_has_loc:
            self.render_loc = data['render_loc']
            self.render_children = data['render_children']
        self.render_has_texture_set = data['render_has_texture_set']
        if self.render_has_texture_set:
            self.render_texture_set = []
            for texture_data in data['render_texture_set']:
                texture = ArcTextureSet()
                texture.load_json(texture_data)
                self.render_texture_set.append(texture)
        self.render_collides = data['render_collides']
        self.render_calculate_bounding_box = data['render_calculate_bounding_box']
        self.render_nation_crest = data['render_nation_crest']
        self.render_guild_crest = data['render_guild_crest']
        self.render_bumped = data['render_bumped']
        self.render_vp_active = data['render_vp_active']
        if self.render_vp_active:
            self.render_vp_name = data['render_vp_name']
            self.render_vp_params = data['render_vp_params']
        self.render_has_light_effects = data['render_has_light_effects']
        if self.render_has_light_effects:
            self.render_light_effects = []
            for effect_data in data['render_light_effects']:
                effect = ArcLight()
                effect.load_json(effect_data)
                self.render_light_effects.append(effect)

    def save_json(self):
        data = OrderedDict()
        data['render_template'] = self.render_template.save_json()
        data['render_target_bone'] = self.render_target_bone
        data['render_scale'] = self.render_scale
        data['render_has_loc'] = self.render_has_loc
        if self.render_has_loc:
            data['render_loc'] = self.render_loc
            data['render_children'] = self.render_children
        data['render_has_texture_set'] = self.render_has_texture_set
        if self.render_has_texture_set:
            data['render_texture_set'] = []
            for texture in self.render_texture_set:
                data['render_texture_set'].append(texture.save_json())
        data['render_collides'] = self.render_collides
        data['render_calculate_bounding_box'] = self.render_calculate_bounding_box
        data['render_nation_crest'] = self.render_nation_crest
        data['render_guild_crest'] = self.render_guild_crest
        data['render_bumped'] = self.render_bumped
        data['render_vp_active'] = self.render_vp_active
        if self.render_vp_active:
            data['render_vp_name'] = self.render_vp_name
            data['render_vp_params'] = self.render_vp_params
        data['render_has_light_effects'] = self.render_has_light_effects
        if self.render_has_light_effects:
            data['render_light_effects'] = []
            for effect in self.render_light_effects:
                data['render_light_effects'].append(effect.save_json())
        return data