def _read_node_data_lod(reader: BinReader) -> LevelOfDetail: ( level, # 00 range_near_sq, # 04 range_far, # 08 range_far_sq, # 12 zero16, # 44 zero bytes unk60, # 60 unk64, # 64 one68, # 68 zero72, # 72 unk76, # 76 ) = reader.read(LEVEL_OF_DETAIL) assert_in("level", (0, 1), level, reader.prev + 0) assert_between( "range near sq", 0.0, 1000.0 * 1000.0, range_near_sq, reader.prev + 4, ) range_near = sqrt(range_near_sq) assert_ge("range far", 0.0, range_far, reader.prev + 8) expected = force_single_prec(range_far * range_far) assert_eq("range far sq", expected, range_far_sq, reader.prev + 12) assert_all_zero("field 16", zero16, reader.prev + 16) # TODO: assert_ge("field 60", 0.0, unk60, reader.prev + 60) expected = force_single_prec(unk60 * unk60) assert_eq("field 64", expected, unk64, reader.prev + 64) assert_eq("field 68", 1, one68, reader.prev + 68) # TODO: assert_in("field 72", (0, 1), zero72, reader.prev + 72) if zero72 == 0: assert_eq("field 76", 0, unk76, reader.prev + 76) else: assert_ne("field 76", 0, unk76, reader.prev + 76) # object 3d nodes always have an action priority of 6 return LevelOfDetail( type="LOD", level=level == 1, range=(range_near, range_far), unk60=unk60, unk76=unk76, )
def _read_anim_info(reader: BinReader) -> Tuple[int, int, int]: LOG.debug("Reading anim info at %d", reader.offset) ( zero00, ptr04, zero08, count, anim_ptr, loc_count, loc_ptr, world_ptr, gravity, zero32, zero36, zero40, zero44, zero48, zero52, zero56, one60, zero64, ) = reader.read(ANIM_INFO) assert_eq("field 00", 0, zero00, reader.prev + 0) assert_eq("field 04", 0, ptr04, reader.prev + 4) assert_eq("field 08", 0, zero08, reader.prev + 8) assert_gt("count", 0, count, reader.prev + 10) assert_ne("anim ptr", 0, anim_ptr, reader.prev + 12) # the localisation isn't used assert_eq("loc count", 0, loc_count, reader.prev + 16) assert_eq("loc ptr", 0, loc_ptr, reader.prev + 20) assert_ne("world ptr", 0, world_ptr, reader.prev + 24) # the gravity is always the same assert_eq("gravity", GRAVITY, gravity, reader.prev + 28) assert_eq("field 32", 0, zero32, reader.prev + 32) assert_eq("field 36", 0, zero36, reader.prev + 36) assert_eq("field 40", 0, zero40, reader.prev + 40) assert_eq("field 44", 0, zero44, reader.prev + 44) assert_eq("field 48", 0, zero48, reader.prev + 48) assert_eq("field 52", 0, zero52, reader.prev + 52) assert_eq("field 56", 0, zero56, reader.prev + 56) assert_eq("field 60", 1, one60, reader.prev + 60) # this is probably a float assert_eq("field 64", 0, zero64, reader.prev + 64) LOG.debug("Anim count is %d", count) return (count, anim_ptr, world_ptr)
def _assert_node_info_lod(node: Node, offset: int, _mesh_count: int) -> None: # cannot assert name # these values are always set flag_base = node.flag & NODE_FLAG_BASE assert_eq("flag base", NODE_FLAG_BASE, flag_base, offset + 36) # TODO: # flag_lod_base = node.flag & NODE_FLAG_LOD_BASE # assert_eq("flag lod base", NODE_FLAG_LOD_BASE, flag_lod_base, offset + 36) # # the only variable flag is Unk15 # flag_lod_mask = node.flag & ~NODE_FLAG_LOD_BASE # assert_in("flag lod mask", (0, NodeFlag.Unk15), flag_lod_mask, offset + 36) assert_eq("field 044", 1, node.unk044, offset + 44) if node.zone_id != ZONE_DEFAULT: assert_between("zone id", 1, 80, node.zone_id, offset + 48) assert_ne("data ptr", 0, node.data_ptr, offset + 56) assert_eq("mesh index", -1, node.mesh_index, offset + 60) # assert area partition properly once we have read the world data assert_between("area partition x", -1, 64, node.area_partition_x, offset + 76) assert_between("area partition y", -1, 64, node.area_partition_y, offset + 80) # must have one parent assert_eq("parent count", 1, node.parent_count, offset + 84) assert_ne("parent array ptr", 0, node.parent_array_ptr, offset + 88) # always has at least one child assert_between("children count", 1, 32, node.children_count, offset + 92) assert_ne("children array ptr", 0, node.children_array_ptr, offset + 96) assert_ne("block 1", BLOCK_EMPTY, node.block1, offset + 116) assert_eq("block 2", BLOCK_EMPTY, node.block2, offset + 140) assert_eq("block 3", node.block1, node.block3, offset + 164) assert_eq("field 196", 160, node.unk196, offset + 196)
def read_materials(reader: BinReader, texture_count: int) -> Tuple[int, List[Material]]: (array_size, mat_count, index_max, mat_unknown) = reader.read(MATERIAL_HEADER) assert_eq("index max", mat_count, index_max, reader.prev + 8) assert_eq("field 12", mat_count - 1, mat_unknown, reader.prev + 12) materials_and_cycle = _read_materials(reader, mat_count, texture_count) _read_materials_zero(reader, mat_count, array_size) materials = [] for material, cycle_info_ptr in materials_and_cycle: if cycle_info_ptr: ( unk00, unk04, zero08, unk12, cycle_count1, cycle_count2, data_ptr, ) = reader.read(CYCLE_HEADER) assert_in("field 00", (0, 1), unk00, reader.prev + 0) # field 04 assert_eq("field 08", 0, zero08, reader.prev + 8) assert_between("field 12", 2.0, 16.0, unk12, reader.prev + 12) assert_eq("cycle count", cycle_count1, cycle_count2, reader.prev + 20) assert_ne("field 24", 0, data_ptr, reader.prev + 24) cycle_textures = reader.read(Struct(f"<{cycle_count1}I")) for i, cycle_texture in enumerate(cycle_textures): # the texture should be in range assert_between( "texture", 0, texture_count - 1, cycle_texture, reader.prev + i * 4 ) material.cycle = Cycle( textures=list(cycle_textures), unk00=unk00 == 1, unk04=unk04, unk12=unk12, info_ptr=cycle_info_ptr, data_ptr=data_ptr, ) materials.append(material) return array_size, materials
def _read_nodes(reader: BinReader, count: int) -> List[NamePtrFlag]: # the first entry is always zero name_raw, zero, ptr = reader.read(NODE) assert_all_zero("name", name_raw, reader.prev + 0) assert_eq("field 32", 0, zero, reader.prev + 32) assert_eq("field 36", 0, ptr, reader.prev + 36) nodes = [] for _ in range(1, count): name_raw, zero, ptr = reader.read(NODE) with assert_ascii("name", name_raw, reader.prev + 0): name = ascii_zterm_node_name(name_raw) assert_eq("field 32", 0, zero, reader.prev + 32) assert_ne("field 36", 0, ptr, reader.prev + 36) nodes.append(NamePtrFlag(name=name, ptr=ptr)) return nodes
def _assert_node_info_window(node: Node, offset: int, _mesh_count: int) -> None: assert_eq("name", "window1", node.name, offset + 0) assert_eq("flag", NODE_FLAG_DEFAULT, node.flag, offset + 36) assert_eq("field 044", 0, node.unk044, offset + 44) assert_eq("zone id", ZONE_DEFAULT, node.zone_id, offset + 48) assert_ne("data ptr", 0, node.data_ptr, offset + 56) assert_eq("mesh index", -1, node.mesh_index, offset + 60) assert_eq("area partition x", -1, node.area_partition_x, offset + 76) assert_eq("area partition y", -1, node.area_partition_y, offset + 80) assert_eq("parent count", 0, node.parent_count, offset + 84) assert_eq("parent array ptr", 0, node.parent_array_ptr, offset + 88) assert_eq("children count", 0, node.children_count, offset + 92) assert_eq("children array ptr", 0, node.children_array_ptr, offset + 96) assert_eq("block 1", BLOCK_EMPTY, node.block1, offset + 116) assert_eq("block 2", BLOCK_EMPTY, node.block2, offset + 140) assert_eq("block 3", BLOCK_EMPTY, node.block3, offset + 164) assert_eq("field 196", 0, node.unk196, offset + 196)
def _read_dynamic_sounds(reader: BinReader, count: int) -> List[NamePtrFlag]: # the first entry is always zero name_raw, flag, ptr, zero = reader.read(READER_LOOKUP) assert_all_zero("name", name_raw, reader.prev + 0) assert_eq("field 32", 0, flag, reader.prev + 32) assert_eq("field 36", 0, ptr, reader.prev + 36) assert_eq("field 40", 0, zero, reader.prev + 40) sounds = [] for _ in range(1, count): name_raw, flag, ptr, zero = reader.read(READER_LOOKUP) with assert_ascii("name", name_raw, reader.prev + 0): name = ascii_zterm_node_name(name_raw) assert_eq("field 32", 0, flag, reader.prev + 32) assert_ne("field 36", 0, ptr, reader.prev + 36) assert_eq("field 40", 0, zero, reader.prev + 40) sounds.append(NamePtrFlag(name=name, ptr=ptr)) return sounds
def _read_lights(reader: BinReader, count: int) -> List[NamePtrFlag]: # the first entry is always zero name_raw, flag, ptr, zero = reader.read(READER_LOOKUP) assert_all_zero("name", name_raw, reader.prev + 0) assert_eq("field 32", 0, flag, reader.prev + 32) assert_eq("field 36", 0, ptr, reader.prev + 36) assert_eq("field 40", 0, zero, reader.prev + 40) lights = [] for _ in range(1, count): name_raw, flag, ptr, zero = reader.read(READER_LOOKUP) with assert_ascii("name", name_raw, reader.prev + 0): name = ascii_zterm_node_name(name_raw) assert_eq("field 32", 0, flag, reader.prev + 32) assert_ne("field 36", 0, ptr, reader.prev + 36) # if this were non-zero, it would cause the light to be removed instead # of added (???) assert_eq("field 40", 0, zero, reader.prev + 40) lights.append(NamePtrFlag(name=name, ptr=ptr)) return lights
def _read_sequence_definitions(reader: BinReader, anim_def: AnimDef, count: int) -> List[SeqDef]: sequences = [] for _ in range(count): name_raw, flag, zero, seqdef_ptr, seqdef_len = reader.read(SEQDEF_INFO) with assert_ascii("name", name_raw, reader.prev + 0): name = ascii_zterm_padded(name_raw) assert_in("activation", (0x0, 0x303), flag, reader.prev + 32) activation: SeqActivation = "ON_CALL" if flag == 0x303 else "NONE" assert_all_zero("field 36", zero, reader.prev + 36) assert_gt("seqdef length", 0, seqdef_len, reader.prev + 56) assert_ne("seqdef ptr", 0, seqdef_ptr, reader.prev + 60) script = _parse_script(reader, anim_def, seqdef_len) sequences.append( SeqDef(name=name, ptr=seqdef_ptr, activation=activation, script=script)) return sequences
def _read_reset_state( reader: BinReader, anim_def: AnimDef, length: int, ptr: int, offset: int, ) -> Optional[SeqDef]: reset_raw, reset_ptr, reset_len = reader.read(RESET_STATE) with assert_ascii("reset end", reset_raw, reader.prev + 0): reset_end = ascii_zterm_padded(reset_raw) # this is always "RESET_SEQUENCE" assert_eq("reset end", "RESET_SEQUENCE", reset_end, reader.prev + 0) assert_eq("reset ptr", ptr, reset_ptr, reader.prev + 56) assert_eq("reset len", length, reset_len, reader.prev + 60) if not length: assert_eq("reset ptr", 0, ptr, offset) return None assert_ne("reset ptr", 0, ptr, offset) script = _parse_script(reader, anim_def, length) return SeqDef(name="RESET_SEQUENCE", ptr=ptr, script=script)
def _read_puffers(reader: BinReader, count: int) -> List[NamePtrFlag]: # the first entry is always zero name_raw, flag, ptr, zero = reader.read(READER_LOOKUP) assert_all_zero("name", name_raw, reader.prev + 0) assert_eq("field 32", 0, flag, reader.prev + 32) assert_eq("field 36", 0, ptr, reader.prev + 36) assert_eq("field 40", 0, zero, reader.prev + 40) puffers = [] for _ in range(1, count): name_raw, flag, ptr, zero = reader.read(READER_LOOKUP) with assert_ascii("name", name_raw, reader.prev + 0): name = ascii_zterm_padded(name_raw) assert_eq("field 32", 0, (flag & 0x00FFFFFF), reader.prev + 32) # TODO: what does this flag mean? # this is something the code does, but i'm not sure why # some of these values make decent floating point numbers flag = flag >> 24 assert_ne("field 36", 0, ptr, reader.prev + 36) assert_eq("field 40", 0, zero, reader.prev + 40) puffers.append(NamePtrFlag(name=name, ptr=ptr, flag=flag)) return puffers
def read_activation_prereq_obj( reader: BinReader, required: bool, prereq_type: int, prev: OptPrereqObj) -> Tuple[OptPrereqObj, OptPrereqObj]: active, name_raw, ptr = reader.read(ACTIV_PREREQ_OBJ) with assert_ascii("activ prereq name", name_raw, reader.prev + 4): name = ascii_zterm_padded(name_raw) assert_ne("activ prereq ptr", 0, ptr, reader.prev + 36) if prereq_type == 3: assert_eq("activ prereq active", 0, active, reader.prev + 0) # remember the current node as the previous one prev = PrereqObject(required=required, active=False, name=name, ptr=ptr) return prev, None assert_in("activ prereq active", (0, 1), active, reader.prev + 0) if prev: assert_eq("activ prereq required", prev.required, required, reader.prev + 0) parent_name = prev.name parent_ptr = prev.ptr else: parent_name = "" parent_ptr = 0 obj = PrereqObject( required=required, active=active == 1, name=name, ptr=ptr, parent_name=parent_name, parent_ptr=parent_ptr, ) # set the previous node to NULL return None, obj
def _assert_node_info_object3d(node: Node, offset: int, mesh_count: int) -> None: # cannot assert name # these values are always set flag_base = node.flag & NODE_FLAG_BASE assert_eq("flag base", NODE_FLAG_BASE, flag_base, offset + 36) # TODO: flag? assert_eq("field 044", 1, node.unk044, offset + 44) if node.zone_id != ZONE_DEFAULT: assert_between("zone id", 1, 80, node.zone_id, offset + 48) assert_ne("data ptr", 0, node.data_ptr, offset + 56) if node.flag & NodeFlag.HasMesh != 0: assert_between("mesh index", 0, mesh_count, node.mesh_index, offset + 60) else: assert_eq("mesh index", -1, node.mesh_index, offset + 60) # assert area partition properly once we have read the world data assert_between("area partition x", -1, 64, node.area_partition_x, offset + 76) assert_between("area partition y", -1, 64, node.area_partition_y, offset + 80) # can only have one parent assert_in("parent count", (0, 1), node.parent_count, offset + 84) if node.parent_count: assert_ne("parent array ptr", 0, node.parent_array_ptr, offset + 88) else: assert_eq("parent array ptr", 0, node.parent_array_ptr, offset + 88) assert_between("children count", 0, 64, node.children_count, offset + 92) if node.children_count: assert_ne("children array ptr", 0, node.children_array_ptr, offset + 96) else: assert_eq("children array ptr", 0, node.children_array_ptr, offset + 96) # unknowns are not 0.0 assert_eq("field 196", 160, node.unk196, offset + 196)
def read( # pylint: disable=too-many-locals,too-many-branches,too-many-statements cls, reader: BinReader, anim_def: AnimDef) -> ObjectMotion: ( flag_raw, # 000, 012 node_index, # 004, 016 zero008, # 008, 020 gravity_value, # 012, 024 zero016, # 016, 028 trans_range_min_1, # 020, 032 trans_range_max_1, # 024, 036 trans_range_min_2, # 028, 040 trans_range_max_2, # 032, 044 trans_range_min_3, # 036, 048 trans_range_max_3, # 040, 052 trans_range_min_4, # 044, 056 trans_range_max_4, # 048, 060 translation_1, # 052, 064 translation_2, # 056, 068 translation_3, # 060, 072 translation_4, # 064, 076 translation_5, # 068, 080 translation_6, # 072, 084 # used for translation calculations zero076, # 076, 088 zero080, # 080, 092 zero084, # 084, 096 zero088, # 088, 100 zero092, # 092, 104 zero096, # 096, 108 # used for translation calculations unk100, # 100, 112 unk104, # 104, 116 unk108, # 108, 120 # FORWARD_ROTATION forward_rotation_1, # 112, 124 forward_rotation_2, # 116, 128 zero120, # 120, 132 # XYZ_ROTATION xyz_rotation_1, # 124, 136 xyz_rotation_2, # 128, 140 xyz_rotation_3, # 132, 144 xyz_rotation_4, # 136, 148 xyz_rotation_5, # 140, 152 xyz_rotation_6, # 144, 156 # used for xyz rotation calculations zero148, # 148, 160 zero152, # 152, 164 zero156, # 156, 168 scale_1, # 160, 172 scale_2, # 164, 176 scale_3, # 168, 180 scale_4, # 172, 184 scale_5, # 176, 188 scale_6, # 180, 192 # used for scale calculations zero184, # 184, 196 zero188, # 188, 200 zero192, # 192, 204 bounce_seq0_name_raw, # 196, 208 bounce_seq0_sentinel, # 228, 240 bounce_snd0_index, # 230, 242 bounce_snd0_volume, # 232, 244 bounce_seq1_name_raw, # 236, 248 bounce_seq1_sentinel, # 268, 280 bounce_snd1_index, # 270, 282 bounce_snd1_volume, # 272, 284 bounce_seq2_name_raw, # 276, 288 bounce_seq2_sentinel, # 308, 320 bounce_snd2_index, # 310, 322 bounce_snd2_volume, # 312, 324 run_time, # 316, 328 ) = reader.read(cls._STRUCT) assert_lt("flag", 0x7FFF, flag_raw, reader.prev + 0) with assert_flag("flag", flag_raw, reader.prev + 0): flag = MotionFlag.check(flag_raw) node = anim_def.get_node(node_index - 1, reader.prev + 4) assert_eq("field 008", 0.0, zero008, reader.prev + 8) assert_eq("field 016", 0.0, zero016, reader.prev + 16) gravity_no_alt = MotionFlag.GravityNoAltitude(flag) gravity_complex = MotionFlag.GravityComplex(flag) if not MotionFlag.Gravity(flag): assert_eq("gravity", 0.0, gravity_value, reader.prev + 12) assert_eq("gravity no alt", False, gravity_no_alt, reader.prev + 0) assert_eq("gravity complex", False, gravity_complex, reader.prev + 0) gravity: Gravity = None elif gravity_no_alt: assert_eq("gravity complex", False, gravity_complex, reader.prev + 0) gravity = ("NO_ALTITUDE", gravity_value) elif gravity_complex: gravity = ("COMPLEX", gravity_value) else: gravity = ("LOCAL", gravity_value) if MotionFlag.TranslationMin(flag): translation_range_min: Optional[Vec4] = ( trans_range_min_1, trans_range_min_2, trans_range_min_3, trans_range_min_4, ) else: assert_eq("trans range min 1", 0.0, trans_range_min_1, reader.prev + 20) assert_eq("trans range min 2", 0.0, trans_range_min_2, reader.prev + 28) assert_eq("trans range min 3", 0.0, trans_range_min_3, reader.prev + 36) assert_eq("trans range min 4", 0.0, trans_range_min_4, reader.prev + 44) translation_range_min = None if MotionFlag.TranslationMax(flag): translation_range_max: Optional[Vec4] = ( trans_range_max_1, trans_range_max_2, trans_range_max_3, trans_range_max_4, ) else: assert_eq("trans range max 1", 0.0, trans_range_max_1, reader.prev + 24) assert_eq("trans range max 2", 0.0, trans_range_max_2, reader.prev + 32) assert_eq("trans range max 3", 0.0, trans_range_max_3, reader.prev + 40) assert_eq("trans range max 4", 0.0, trans_range_max_4, reader.prev + 48) translation_range_max = None if MotionFlag.Translation(flag): translation: Optional[Vec9] = ( translation_1, translation_2, translation_3, translation_4, translation_5, translation_6, unk100, unk104, unk108, ) else: assert_eq("translation 1", 0.0, translation_1, reader.prev + 52) assert_eq("translation 2", 0.0, translation_2, reader.prev + 56) assert_eq("translation 3", 0.0, translation_3, reader.prev + 60) assert_eq("translation 4", 0.0, translation_4, reader.prev + 64) assert_eq("translation 5", 0.0, translation_5, reader.prev + 68) assert_eq("translation 6", 0.0, translation_6, reader.prev + 72) assert_eq("field 100", 0.0, unk100, reader.prev + 100) assert_eq("field 104", 0.0, unk104, reader.prev + 104) assert_eq("field 108", 0.0, unk108, reader.prev + 108) translation = None assert_eq("field 076", 0.0, zero076, reader.prev + 76) assert_eq("field 080", 0.0, zero080, reader.prev + 80) assert_eq("field 084", 0.0, zero084, reader.prev + 84) assert_eq("field 088", 0.0, zero088, reader.prev + 88) assert_eq("field 092", 0.0, zero092, reader.prev + 92) assert_eq("field 096", 0.0, zero096, reader.prev + 96) if MotionFlag.ForwardRotationTime(flag): forward_rotation: ForwardRotation = ( "TIME", forward_rotation_1, forward_rotation_2, ) elif MotionFlag.ForwardRotationDistance(flag): assert_eq("fwd rot 2", 0.0, forward_rotation_2, reader.prev + 116) forward_rotation = ( "DISTANCE", forward_rotation_1, 0.0, ) else: assert_eq("fwd rot 1", 0.0, forward_rotation_1, reader.prev + 112) assert_eq("fwd rot 2", 0.0, forward_rotation_2, reader.prev + 116) forward_rotation = None assert_eq("field 120", 0.0, zero120, reader.prev + 120) if MotionFlag.XYZRotation(flag): xyz_rotation: Optional[Vec6] = ( xyz_rotation_1, xyz_rotation_2, xyz_rotation_3, xyz_rotation_4, xyz_rotation_5, xyz_rotation_6, ) else: assert_eq("xyz rot 1", 0.0, xyz_rotation_1, reader.prev + 124) assert_eq("xyz rot 2", 0.0, xyz_rotation_2, reader.prev + 128) assert_eq("xyz rot 3", 0.0, xyz_rotation_3, reader.prev + 132) assert_eq("xyz rot 4", 0.0, xyz_rotation_4, reader.prev + 136) assert_eq("xyz rot 5", 0.0, xyz_rotation_5, reader.prev + 140) assert_eq("xyz rot 6", 0.0, xyz_rotation_6, reader.prev + 144) xyz_rotation = None assert_eq("field 148", 0.0, zero148, reader.prev + 148) assert_eq("field 152", 0.0, zero152, reader.prev + 152) assert_eq("field 156", 0.0, zero156, reader.prev + 156) if MotionFlag.Scale(flag): scale: Optional[Vec6] = ( scale_1, scale_2, scale_3, scale_4, scale_5, scale_6, ) else: assert_eq("scale 1", 0.0, scale_1, reader.prev + 160) assert_eq("scale 2", 0.0, scale_2, reader.prev + 164) assert_eq("scale 3", 0.0, scale_3, reader.prev + 168) assert_eq("scale 4", 0.0, scale_4, reader.prev + 172) assert_eq("scale 5", 0.0, scale_5, reader.prev + 176) assert_eq("scale 6", 0.0, scale_6, reader.prev + 180) scale = None assert_eq("field 184", 0.0, zero184, reader.prev + 184) assert_eq("field 188", 0.0, zero188, reader.prev + 188) assert_eq("field 192", 0.0, zero192, reader.prev + 192) assert_eq("bounce seq 0 sentinel", -1, bounce_seq0_sentinel, reader.prev + 228) assert_eq("bounce seq 1 sentinel", -1, bounce_seq1_sentinel, reader.prev + 268) assert_eq("bounce seq 2 sentinel", -1, bounce_seq2_sentinel, reader.prev + 308) bounce_sequence = [] if MotionFlag.BounceSeq(flag): with assert_ascii("bounce seq 0 name", bounce_seq0_name_raw, reader.prev + 196): bounce_seq0_name = ascii_zterm_padded(bounce_seq0_name_raw) # should have at least one value assert_ne("bounce seq 0 name", "", bounce_seq0_name, reader.prev + 196) bounce_sequence.append(bounce_seq0_name) with assert_ascii("bounce seq 1 name", bounce_seq1_name_raw, reader.prev + 236): bounce_seq1_name = ascii_zterm_padded(bounce_seq1_name_raw) if bounce_seq1_name: bounce_sequence.append(bounce_seq1_name) with assert_ascii("bounce seq 2 name", bounce_seq2_name_raw, reader.prev + 276): bounce_seq2_name = ascii_zterm_padded(bounce_seq2_name_raw) if bounce_seq2_name: bounce_sequence.append(bounce_seq2_name) else: assert_all_zero("bounce seq 0 name", bounce_seq0_name_raw, reader.prev + 196) assert_all_zero("bounce seq 1 name", bounce_seq1_name_raw, reader.prev + 236) assert_all_zero("bounce seq 2 name", bounce_seq2_name_raw, reader.prev + 276) bounce_sounds = [] if MotionFlag.BounceSound(flag): # should have at least one value assert_gt("bounce snd 0 volume", 0.0, bounce_snd0_volume, reader.prev + 232) sound = anim_def.get_sound(bounce_snd0_index - 1, reader.prev + 230) bounce_sounds.append((sound, bounce_snd0_volume)) else: assert_eq("bounce snd 0 sound", 0, bounce_snd0_index, reader.prev + 230) assert_eq("bounce snd 0 volume", 0.0, bounce_snd0_volume, reader.prev + 232) # these are never set, regardless of the flag assert_eq("bounce snd 1 sound", 0, bounce_snd1_index, reader.prev + 270) assert_eq("bounce snd 1 volume", 0.0, bounce_snd1_volume, reader.prev + 272) assert_eq("bounce snd 2 sound", 0, bounce_snd2_index, reader.prev + 310) assert_eq("bounce snd 2 volume", 0.0, bounce_snd2_volume, reader.prev + 312) if MotionFlag.RunTime(flag): assert_gt("run time", 0.0, run_time, reader.prev + 316) else: assert_eq("run time", 0.0, run_time, reader.prev + 316) run_time = None return cls( node=node, gravity=gravity, impact_force=MotionFlag.ImpactForce(flag), translation_range_min=translation_range_min, translation_range_max=translation_range_max, translation=translation, forward_rotation=forward_rotation, xyz_rotation=xyz_rotation, scale=scale, bounce_sequence=bounce_sequence, bounce_sounds=bounce_sounds, run_time=run_time, )
def _read_partitions( # pylint: disable=too-many-locals reader: BinReader, area_x: Sequence[int], area_y: Sequence[int]) -> List[List[Partition]]: partitions = [] for y in area_y: subpartitions = [] for x in area_x: ( flag_raw, mone04, part_x, part_y, unk16, unk20, unk24, unk28, unk32, unk36, unk40, unk44, unk48, unk52, zero56, count, # 58 ptr, # 60 zero64, zero68, ) = reader.read(PARTITION) assert_eq("partition field 00", 0x100, flag_raw, reader.prev + 0) assert_eq("partition field 04", -1, mone04, reader.prev + 4) assert_eq("partition field 56", 0, zero56, reader.prev + 56) assert_eq("partition field 64", 0, zero64, reader.prev + 64) assert_eq("partition field 68", 0, zero68, reader.prev + 68) assert_eq("partition x", x, part_x, reader.prev + 8) assert_eq("partition y", y, part_y, reader.prev + 12) assert_eq("partition field 16", x, unk16, reader.prev + 16) # unk20 assert_eq("partition field 24", y - 256, unk24, reader.prev + 24) assert_eq("partition field 28", x + 256, unk28, reader.prev + 28) # unk32 assert_eq("partition field 36", y, unk36, reader.prev + 36) # this is set through an extremely convoluted calculation starting with: # unk40 = unk16 + (unk28 - unk16) * 0.5 # which simplifies to: # unk40 = x + 128.0 assert_eq("partition field 40", x + 128, unk40, reader.prev + 40) # this is set through an extremely convoluted calculation starting with: # unk44 = unk20 + (unk32 - unk20) * 0.5 # ... at least initially, although unk20 and unk32 would be 0.0 because # calloc zeros memory. i can get this calculation to work with almost # all values, but some are ever so slightly off (lowest bits in the # single precision floating point numbers differ), so i suspect this # calculation is more complicated. # assert_eq("partition field 44", expected, unk44, reader.prev + 44) # this is set through an extremely convoluted calculation starting with: # unk48 = unk24 + (unk36 - unk24) * 0.5 # which simplifies to: # unk48 = y - 128.0 assert_eq("partition field 48", y - 128, unk48, reader.prev + 48) # two[2] # this is set through an extremely convoluted calculation starting with: # temp1 = (unk28 - unk16) * 0.5 # temp2 = (unk32 - unk20) * 0.5 # temp3 = (unk36 - unk24) * 0.5 # which simplifies to: # temp1 = 128.0 # (does not simplify without knowing unk32 and unk20) # temp3 = 128.0 # approx_sqrt automatically converts to single precision temp = (unk32 - unk20) * 0.5 expected = approx_sqrt(128 * 128 + temp * temp + 128 * 128) assert_eq("partition field 52", expected, unk52, reader.prev + 52) if count: assert_ne("partition ptr", 0, ptr, reader.prev + 60) nodes = [reader.read_u32() for _ in range(count)] else: assert_eq("partition ptr", 0, ptr, reader.prev + 60) nodes = [] partition = Partition( x=x, y=y, nodes=nodes, unk=(unk20, unk32, unk44), ptr=ptr, ) subpartitions.append(partition) partitions.append(subpartitions) return partitions
def _read_node_data_world( # pylint: disable=too-many-locals,too-many-statements reader: BinReader, ) -> World: ( flag_raw, # 000 area_partition_used, # 004 area_partition_count, # 008 area_partition_ptr, # 012 fog_state_raw, # 016 fog_color_r, # 020 fog_color_g, # 024 fog_color_b, # 028 fog_range_near, # 032 fog_range_far, # 036 fog_alti_high, # 040 fog_alti_low, # 044 fog_density, # 048 area_left_f, # 052 area_bottom_f, # 056 area_width, # 060 area_height, # 064 area_right_f, # 068 area_top_f, # 072 partition_max_dec_feature_count, # 076 virtual_partition, # 080 virt_partition_x_min, # 084 virt_partition_y_min, # 088 virt_partition_x_max, # 092 virt_partition_y_max, # 096 virt_partition_x_size, # 100 virt_partition_y_size, # 104 virt_partition_x_half, # 108 virt_partition_y_half, # 112 virt_partition_x_inv, # 116 virt_partition_y_inv, # 124 virt_partition_diag, # 128 partition_inclusion_tol_low, # 128 partition_inclusion_tol_high, # 132 virt_partition_x_count, # 136 virt_partition_y_count, # 140 virt_partition_ptr, # 144 one148, one152, one156, children_count, # 160 children_ptr, # 164 lights_ptr, # 168 zero172, zero176, zero180, zero184, ) = reader.read(WORLD) assert_eq("flag", 0, flag_raw, reader.prev + 0) # LINEAR = 1, EXPONENTIAL = 2 (never set) assert_eq("fog state", 1, fog_state_raw, reader.prev + 16) # not set assert_eq("fog color r", 0.0, fog_color_r, reader.prev + 20) assert_eq("fog color g", 0.0, fog_color_g, reader.prev + 24) assert_eq("fog color b", 0.0, fog_color_b, reader.prev + 28) assert_eq("fog range near", 0.0, fog_range_near, reader.prev + 32) assert_eq("fog range far", 0.0, fog_range_far, reader.prev + 36) assert_eq("fog alti high", 0.0, fog_alti_high, reader.prev + 40) assert_eq("fog alti low", 0.0, fog_alti_low, reader.prev + 44) assert_eq("fog density", 0.0, fog_density, reader.prev + 48) # we need these values to be integers for the partition logic area_left = int(area_left_f) area_bottom = int(area_bottom_f) area_right = int(area_right_f) area_top = int(area_top_f) assert_eq("area left", area_left, area_left_f, reader.prev + 52) assert_eq("area bottom", area_bottom, area_bottom_f, reader.prev + 56) assert_eq("area right", area_right, area_right_f, reader.prev + 68) assert_eq("area top", area_top, area_top_f, reader.prev + 72) # validate rect assert_gt("area right", area_left, area_right, reader.prev + 68) assert_gt("area bottom", area_top, area_bottom, reader.prev + 72) width = area_right - area_left height = area_top - area_bottom assert_eq("area width", width, area_width, reader.prev + 60) assert_eq("area height", height, area_height, reader.prev + 64) assert_eq("partition max feat", 16, partition_max_dec_feature_count, reader.prev + 76) assert_eq("virtual partition", 1, virtual_partition, reader.prev + 80) assert_eq("vp x min", 1, virt_partition_x_min, reader.prev + 84) assert_eq("vp y min", 1, virt_partition_y_min, reader.prev + 88) assert_eq("vp x size", 256.0, virt_partition_x_size, reader.prev + 100) assert_eq("vp y size", -256.0, virt_partition_y_size, reader.prev + 104) assert_eq("vp x half", 128.0, virt_partition_x_half, reader.prev + 108) assert_eq("vp y half", -128.0, virt_partition_y_half, reader.prev + 112) assert_eq("vp x inv", 1.0 / 256.0, virt_partition_x_inv, reader.prev + 116) assert_eq("vp y inv", 1.0 / -256.0, virt_partition_y_inv, reader.prev + 120) # this is sqrt(x_size * x_size + y_size * y_size) * -0.5, but because of the # (poor) sqrt approximation used, it comes out as -192.0 instead of -181.0 assert_eq("vp diagonal", -192.0, virt_partition_diag, reader.prev + 124) assert_eq("vp inc tol low", 3, partition_inclusion_tol_low, reader.prev + 128) assert_eq("vp inc tol high", 3, partition_inclusion_tol_high, reader.prev + 132) area_x = range(area_left, area_right, 256) # because the virtual partition y size is negative, this is inverted! area_y = range(area_bottom, area_top, -256) assert_eq("vp x count", len(area_x), virt_partition_x_count, reader.prev + 136) assert_eq("vp y count", len(area_y), virt_partition_y_count, reader.prev + 140) assert_eq("ap used", 0, area_partition_used, reader.prev + 4) assert_eq( "vp x max", virt_partition_x_count - 1, virt_partition_x_max, reader.prev + 92, ) assert_eq( "vp y max", virt_partition_y_count - 1, virt_partition_y_max, reader.prev + 96, ) # TODO: why isn't this a perfect fit for T1? virt_partition_count = virt_partition_x_count * virt_partition_y_count assert_between( "ap count", virt_partition_count - 1, virt_partition_count, area_partition_count, reader.prev + 8, ) fudge_count = area_partition_count != virt_partition_count assert_ne("ap ptr", 0, area_partition_ptr, reader.prev + 12) assert_ne("vp ptr", 0, virt_partition_ptr, reader.prev + 144) assert_eq("field 148", 1, one148, reader.prev + 148) assert_eq("field 152", 1, one152, reader.prev + 152) assert_eq("field 156", 1, one156, reader.prev + 156) assert_eq("children count", 1, children_count, reader.prev + 160) assert_ne("children ptr", 0, children_ptr, reader.prev + 164) assert_ne("lights ptr", 0, lights_ptr, reader.prev + 168) assert_eq("field 172", 0, zero172, reader.prev + 172) assert_eq("field 176", 0, zero176, reader.prev + 176) assert_eq("field 180", 0, zero180, reader.prev + 180) assert_eq("field 184", 0, zero184, reader.prev + 184) # read as a result of children_count child = reader.read_u32() # read as a result of zero172 (i.e. nothing to do) partitions = _read_partitions(reader, area_x, area_y) # world nodes always have an action priority of 13 return World( type="World", area=(area_left, area_top, area_right, area_bottom), partitions=partitions, children=[child], area_partition_x_count=virt_partition_x_count, area_partition_y_count=virt_partition_y_count, fudge_count=fudge_count, area_partition_ptr=area_partition_ptr, virt_partition_ptr=virt_partition_ptr, children_ptr=children_ptr, lights_ptr=lights_ptr, )
def read_anim_def( # pylint: disable=too-many-locals,too-many-branches,too-many-statements reader: BinReader, ) -> Tuple[AnimDef, AnimDefPointers]: ( anim_name_raw, name_raw, base_node_ptr, # 064 anim_root_raw, anim_root_ptr, zero104, # 44 zero bytes flag_raw, # 148 zero152, activation_value, action_prio, byte155, exec_by_range_min, exec_by_range_max, reset_time, zero168, max_health, cur_health, zero180, zero184, zero188, zero192, seq_defs_ptr, # 196 reset_state_ptr, # 200 int204, int208, int212, zero216, # 40 zero bytes reset_state_events_ptr, # 256 reset_state_events_len, # 260 seq_def_count, # 264 object_count, # 265 node_count, # 266 light_count, # 267 puffer_count, # 268 dynamic_sound_count, # 269 static_sound_count, # 270 unknown_count, # 271 activ_prereq_count, # 272 activ_prereq_min_to_satisfy, # 273 anim_ref_count, # 274 zero275, objects_ptr, # 276 nodes_ptr, # 280 lights_ptr, # 284 puffers_ptr, # 288 dynamic_sounds_ptr, # 292 static_sounds_ptr, # 296 unknown_ptr, # 300 activ_prereqs_ptr, # 304 anim_refs_ptr, # 308 zero312, ) = reader.read(ANIM_DEF) # save this so we can output accurate offsets after doing further reads data_offset = reader.prev with assert_ascii("anim name", anim_name_raw, reader.prev + 0): anim_name, _anim_name_pad = ascii_zterm_partition(anim_name_raw) with assert_ascii("name", name_raw, reader.prev + 32): name = ascii_zterm_padded(name_raw) assert_ne("base node ptr", 0, base_node_ptr, data_offset + 64) with assert_ascii("anim root", anim_root_raw, reader.prev + 68): anim_root, _anim_root_pad = ascii_zterm_partition(anim_root_raw) if name != anim_root: assert_ne("anim root ptr", base_node_ptr, anim_root_ptr, data_offset + 100) else: assert_eq("anim root ptr", base_node_ptr, anim_root_ptr, data_offset + 100) assert_all_zero("field 104", zero104, data_offset + 104) with assert_flag("flag", flag_raw, data_offset + 148): flag = AnimDefFlag.check(flag_raw) network_log: Optional[bool] = None if AnimDefFlag.NetworkLogSet(flag): network_log = AnimDefFlag.NetworkLogOn(flag) save_log: Optional[bool] = None if AnimDefFlag.SaveLogSet(flag): save_log = AnimDefFlag.SaveLogOn(flag) assert_eq("field 152", 0, zero152, data_offset + 152) assert_in("field 153", (0, 1, 2, 3, 4), activation_value, data_offset + 153) assert_eq("field 154", 4, action_prio, data_offset + 154) assert_eq("field 155", 2, byte155, data_offset + 155) exec_by_zone = AnimDefFlag.ExecutionByZone(flag) if not AnimDefFlag.ExecutionByRange(flag): assert_eq("exec by range min", 0.0, exec_by_range_min, data_offset + 156) assert_eq("exec by range max", 0.0, exec_by_range_max, data_offset + 156) exec_by_range = None else: assert_eq("exec by zone", False, exec_by_zone, data_offset + 148) assert_ge("exec by range min", 0.0, exec_by_range_min, data_offset + 156) assert_ge("exec by range max", exec_by_range_max, exec_by_range_max, data_offset + 156) exec_by_range = (exec_by_range_min, exec_by_range_max) if not AnimDefFlag.ResetUnk(flag): assert_eq("reset time", -1.0, reset_time, data_offset + 164) reset_time = None assert_eq("field 168", 0.0, zero168, data_offset + 168) assert_ge("health", 0.0, max_health, data_offset + 172) assert_eq("health", max_health, cur_health, data_offset + 176) assert_eq("field 180", 0, zero180, data_offset + 180) assert_eq("field 184", 0, zero184, data_offset + 184) assert_eq("field 188", 0, zero188, data_offset + 188) assert_eq("field 192", 0, zero192, data_offset + 192) # WTF??? assert_eq("field 200", 0x45534552, int200, data_offset + 200) assert_eq("field 204", 0x45535F54, int204, data_offset + 204) assert_eq("field 208", 0x4E455551, int208, data_offset + 208) assert_eq("field 212", 0x00004543, int212, data_offset + 212) assert_all_zero("field 216", zero216, data_offset + 216) assert_eq("field 275", 0, zero275, data_offset + 275) assert_eq("field 312", 0, zero312, data_offset + 312) activation = ANIM_ACTIVATION[activation_value] if object_count: assert_ne("object ptr", 0, objects_ptr, data_offset + 276) objects = _read_objects(reader, object_count) else: assert_eq("object ptr", 0, objects_ptr, data_offset + 276) objects = [] if node_count: assert_ne("node ptr", 0, nodes_ptr, data_offset + 280) nodes = _read_nodes(reader, node_count) else: assert_eq("node ptr", 0, nodes_ptr, data_offset + 280) nodes = [] if light_count: assert_ne("light ptr", 0, lights_ptr, data_offset + 284) lights = _read_lights(reader, light_count) else: assert_eq("light ptr", 0, lights_ptr, data_offset + 284) lights = [] if puffer_count: assert_ne("puffer ptr", 0, puffers_ptr, data_offset + 288) puffers = _read_puffers(reader, puffer_count) else: assert_eq("puffer ptr", 0, puffers_ptr, data_offset + 288) puffers = [] if dynamic_sound_count: assert_ne("dynamic sound ptr", 0, dynamic_sounds_ptr, data_offset + 292) dynamic_sounds = _read_dynamic_sounds(reader, dynamic_sound_count) else: assert_eq("dynamic sound ptr", 0, dynamic_sounds_ptr, data_offset + 292) dynamic_sounds = [] if static_sound_count: assert_ne("static sound ptr", 0, static_sounds_ptr, data_offset + 296) static_sounds = _read_static_sounds(reader, static_sound_count) else: assert_eq("static sound ptr", 0, static_sounds_ptr, data_offset + 296) static_sounds = [] # this isn't set in any file i have (it is read like the static sound data) assert_eq("unknown count", 0, unknown_count, data_offset + 271) assert_eq("unknown ptr", 0, unknown_ptr, data_offset + 300) if activ_prereq_count: assert_ne("activ prereq ptr", 0, activ_prereqs_ptr, data_offset + 304) assert_in( "activ prereq min", (0, 1, 2), activ_prereq_min_to_satisfy, data_offset + 273, ) activ_prereq = read_activation_prereq( reader, activ_prereq_count, activ_prereq_min_to_satisfy, ) else: assert_eq("activ prereq ptr", 0, activ_prereqs_ptr, data_offset + 304) assert_eq("activ prereq min", 0, activ_prereq_min_to_satisfy, data_offset + 273) activ_prereq = None if anim_ref_count: assert_ne("anim ref ptr", 0, anim_refs_ptr, data_offset + 308) anim_refs = _read_anim_refs(reader, anim_ref_count) else: assert_eq("anim ref ptr", 0, anim_refs_ptr, data_offset + 308) anim_refs = [] base_name = name.replace(".flt", "") if name == anim_root: file_name = f"{base_name}-{anim_name}.json" else: file_name = f"{base_name}-{anim_name}-{anim_root}.json" anim_def = AnimDef( name=name, anim_name=anim_name, anim_root=anim_root, file_name=file_name, # --- auto_reset_node_states=AnimDefFlag.AutoResetNodeStates(flag), activation=activation, execution_by_range=exec_by_range, execution_by_zone=exec_by_zone, network_log=network_log, save_log=save_log, has_callback=AnimDefFlag.HasCallback(flag), callback_count=0, reset_time=reset_time, health=max_health, proximity_damage=AnimDefFlag.ProximityDamage(flag), # --- objects=objects, nodes=nodes, lights=lights, puffers=puffers, dynamic_sounds=dynamic_sounds, static_sounds=static_sounds, activation_prereq=activ_prereq, anim_refs=anim_refs, # skip reset_sequence and sequences, as they need to look up other items ) # unconditional read anim_def.reset_sequence = _read_reset_state( reader, anim_def, reset_state_events_len, reset_state_events_ptr, data_offset + 256, ) # this could be zero, in which case the pointer would also be NULL (but never is) assert_gt("seq count", 0, seq_def_count, data_offset + 264) assert_ne("seq ptr", 0, seq_defs_ptr, data_offset + 196) anim_def.sequences = _read_sequence_definitions(reader, anim_def, seq_def_count) # the Callback script object checks if callbacks are allowed, but i also # want to catch the case where the flag might've been set, but no callbacks # were in the scripts if anim_def.has_callback: assert_gt("callbacks", 0, anim_def.callback_count, data_offset + 148) # don't need this value any more anim_def.callback_count = 0 pointers = AnimDefPointers( file_name=file_name, objects_ptr=objects_ptr, nodes_ptr=nodes_ptr, lights_ptr=lights_ptr, puffers_ptr=puffers_ptr, dynamic_sounds_ptr=dynamic_sounds_ptr, static_sounds_ptr=static_sounds_ptr, activ_prereqs_ptr=activ_prereqs_ptr, anim_refs_ptr=anim_refs_ptr, reset_state_ptr=reset_state_ptr, reset_state_events_ptr=reset_state_events_ptr, seq_defs_ptr=seq_defs_ptr, ) return anim_def, pointers
def _read_node_data_light( # pylint: disable=too-many-locals reader: BinReader, ) -> Light: ( direction_x, # 000 direction_y, # 004 direction_z, # 008 trans_x, # 012 trans_y, # 016 trans_z, # 020 zero024, # 112 zero bytes one136, zero140, zero144, zero148, zero152, diffuse, # 156 ambient, # 160 color_r, # 164 color_g, # 168 color_b, # 172 flag_raw, # 176 range_min, # 180 range_max, # 184 range_min_sq, # 188 range_max_sq, # 192 range_inv, # 196 parent_count, # 200 parent_ptr, # 204 zero208, ) = reader.read(LIGHT) # translation is never set assert_eq("trans x", 0.0, trans_x, reader.prev + 12) assert_eq("trans y", 0.0, trans_y, reader.prev + 16) assert_eq("trans z", 0.0, trans_z, reader.prev + 20) assert_all_zero("field 024", zero024, reader.prev + 24) assert_eq("field 136", 1.0, one136, reader.prev + 136) assert_eq("field 140", 0.0, zero140, reader.prev + 140) assert_eq("field 144", 0.0, zero144, reader.prev + 144) assert_eq("field 148", 0.0, zero148, reader.prev + 148) assert_eq("field 152", 0.0, zero152, reader.prev + 152) assert_between("diffuse", 0.0, 1.0, diffuse, reader.prev + 156) assert_between("ambient", 0.0, 1.0, ambient, reader.prev + 160) assert_eq("color r", 1.0, color_r, reader.prev + 164) assert_eq("color g", 1.0, color_g, reader.prev + 168) assert_eq("color b", 1.0, color_b, reader.prev + 172) with assert_flag("flag", flag_raw, reader.prev + 176): flag = LightFlag.check(flag_raw) assert_eq("flag", LIGHT_FLAG, flag, reader.prev + 176) assert_gt("range min", 0.0, range_min, reader.prev + 180) assert_gt("range max", range_min, range_max, reader.prev + 184) expected = range_min * range_min assert_eq("range min sq", expected, range_min_sq, reader.prev + 188) expected = range_max * range_max assert_eq("range max sq", expected, range_max_sq, reader.prev + 192) expected = force_single_prec(1.0 / (range_max - range_min)) assert_eq("range inv", expected, range_inv, reader.prev + 196) # if this was ever zero, field 208 wouldn't be read assert_eq("parent count", 1, parent_count, reader.prev + 200) assert_ne("parent ptr", 0, parent_ptr, reader.prev + 204) assert_eq("field 208", 0, zero208, reader.prev + 208) # light nodes always have an action priority of 9 return Light( type="Light", direction=(direction_x, direction_y, direction_z), diffuse=diffuse, ambient=ambient, color=(color_r, color_g, color_b), range=(range_min, range_max), parent_ptr=parent_ptr, )
def _read_materials( # pylint: disable=too-many-locals reader: BinReader, mat_count: int, texture_count: int ) -> List[Tuple[Material, int]]: materials = [] for i in range(0, mat_count): # very similar to materials in the mechlib ( unk00, flag_raw, rgb, red, green, blue, texture, unk20, unk24, unk28, unk32, cycle_ptr, index1, index2, ) = reader.read(MATERIAL_INFO) with assert_flag("flag", flag_raw, reader.prev + 1): flag = MaterialFlag.check(flag_raw) assert_eq("flag always", True, MaterialFlag.Always(flag), reader.prev + 1) assert_eq("flag free", False, MaterialFlag.Free(flag), reader.prev + 1) cycled = MaterialFlag.Cycled(flag) if MaterialFlag.Textured(flag): assert_eq("field 00", 255, unk00, reader.prev + 0) # if the material is textured, it should not have an RGB value assert_eq("rgb", 0x7FFF, rgb, reader.prev + 2) assert_eq("red", 255.0, red, reader.prev + 4) assert_eq("green", 255.0, green, reader.prev + 8) assert_eq("blue", 255.0, blue, reader.prev + 12) color: Optional[Vec3] = None # the texture should be in range assert_between("texture", 0, texture_count - 1, texture, reader.prev + 16) else: # value distribution: # 24 0 # 2 51 # 2 76 # 2 89 # 3 102 # 1 127 # 1 153 # 2629 255 (includes textured) values_00 = (0, 51, 76, 89, 102, 127, 153, 255) assert_in("field 00", values_00, unk00, reader.prev + 0) # this is never true for untextured materials assert_eq("flag unk", False, MaterialFlag.Unknown(flag), reader.prev + 1) # if the material is not textured, it can't be cycled assert_eq("texture cycled", False, cycled, reader.prev + 1) # this is calculated from the floating point values, since this short # representation depends on the hardware RGB565 or RGB555 support assert_eq("rgb", 0x0, rgb, reader.prev + 2) color = (red, green, blue) assert_eq("texture", 0, texture, reader.prev + 16) texture = None # not sure what these are? assert_eq("field 20", 0.0, unk20, reader.prev + 20) assert_eq("field 24", 0.5, unk24, reader.prev + 24) assert_eq("field 28", 0.5, unk28, reader.prev + 28) # value distribution: # 2480 0 # 12 1 # 1 4 # 61 6 # 17 7 # 3 8 # 72 9 # 4 10 # 11 12 # 3 13 # bit field? # 0 0b0000 # 1 0b0001 # 4 0b0100 # 6 0b0110 # 7 0b0111 # 8 0b1000 # 9 0b1001 # 10 0b1010 # 12 0b1100 # 13 0b1101 assert_in( "field 32", (0, 1, 4, 6, 7, 8, 9, 10, 12, 13), unk32, reader.prev + 32 ) if cycled: assert_ne("cycle pointer", 0, cycle_ptr, reader.prev + 36) else: assert_eq("cycle pointer", 0, cycle_ptr, reader.prev + 36) expected1 = i + 1 if expected1 >= mat_count: expected1 = -1 assert_eq("index 1", expected1, index1, reader.prev + 40) expected2 = i - 1 if expected2 < 0: expected2 = -1 assert_eq("index 2", expected2, index2, reader.prev + 42) material = Material( texture=texture, color=color, unk00=unk00, unk32=unk32, unknown=MaterialFlag.Unknown(flag), ) materials.append((material, cycle_ptr)) return materials