def read_activation_prereq( reader: BinReader, count: int, min_to_satisfy: int, ) -> Optional[ActivationPrereq]: LOG.debug("Reading activation prerequisites at %d", reader.offset) anim_list = [] obj_list = [] prev: OptPrereqObj = None for _ in range(count): optional, prereq_type = reader.read(ACTIV_PREREQ_HEADER) # this is actually a byte in the code, but this way we also validate the padding assert_in("activ prereq type", (1, 2, 3), prereq_type, reader.prev + 4) if prereq_type == 1: # ANIMATION_LIST (these are always required) assert_eq("activ prereq optional", 0, optional, reader.prev + 0) anim_list.append(read_activation_prereq_anim(reader)) else: # OBJECT_INACTIVE_LIST / OBJECT_ACTIVE_LIST assert_in("activ prereq optional", (0, 1), optional, reader.prev + 0) prev, obj = read_activation_prereq_obj(reader, optional == 0, prereq_type, prev) if obj: obj_list.append(obj) LOG.debug("Read activation prerequisites") return ActivationPrereq(min_to_satisfy=min_to_satisfy, anim_list=anim_list, obj_list=obj_list)
def get_comparison( condition: int, value: bytes, offset: int ) -> Tuple[str, Discriminator, RightHandSide]: assert_in("condition", IF_COND.keys(), condition, offset) lhs, function = IF_COND[condition] discriminator, rhs = function(value) return lhs, discriminator, rhs
def read(cls, reader: BinReader, anim_def: AnimDef) -> ObjectOpacityFromTo: ( node_index, from_state, to_state, from_value, to_value, delta, run_time, ) = reader.read(cls._STRUCT) node = anim_def.get_node(node_index - 1, reader.prev + 0) assert_in("from state", (-1, 0, 1), from_state, reader.prev + 4) assert_in("to state", (-1, 0, 1), to_state, reader.prev + 6) assert_between("from opacity", 0.0, 1.0, from_value, reader.prev + 8) assert_between("to opacity", 0.0, 1.0, to_value, reader.prev + 12) # delta is roughly: (to_value - from_value) / run_time assert_gt("run time", 0.0, run_time, reader.prev + 20) return cls( node=node, opacity_from=(from_value, from_state), opacity_to=(to_value, to_state), run_time=run_time, delta=delta, )
def read(cls, reader: BinReader, anim_def: AnimDef) -> ObjectRotateState: flag, rx, ry, rz, node_index, at_index = reader.read(cls._STRUCT) # FLAG (mutually exclusive) # if this is a camera: # 0 -> absolute rotation # 1 -> relative rotation # if this is a 3d object: # 0 -> absolute rotation # 1 -> relative rotation # 2 -> AT_NODE_XYZ # 4 -> AT_NODE_MATRIX assert_in("flag", (0, 2, 4), flag, reader.prev + 0) node = anim_def.get_node(node_index - 1, reader.prev + 16) if flag == 0: assert_between("rot x", -PI2, PI2, rx, reader.prev + 4) assert_between("rot y", -PI2, PI2, ry, reader.prev + 8) assert_between("rot z", -PI2, PI2, rz, reader.prev + 12) state = (degrees(rx), degrees(ry), degrees(rz)) assert_eq("at node", 0, at_index, reader.prev + 18) mode: RotationMode = "ABSOLUTE" else: assert_eq("rot x", 0.0, rx, reader.prev + 4) assert_eq("rot y", 0.0, ry, reader.prev + 8) assert_eq("rot z", 0.0, rz, reader.prev + 12) state = (0.0, 0.0, 0.0) assert_eq("at node", -200, at_index, reader.prev + 18) if flag == 2: mode = "AT_NODE_XYZ" else: mode = "AT_NODE_MATRIX" return cls(node=node, mode=mode, state=state)
def _parse_script(reader: BinReader, anim_def: AnimDef, rel_end: int) -> List[ScriptItem]: LOG.debug("Reading script with length %d at %d", rel_end, reader.offset) abs_end = rel_end + reader.offset script = [] while reader.offset < abs_end: stype, start_offset, pad, size, start_time = reader.read(SCRIPT_HEADER) assert_in("type", OBJECT_REGISTRY_NUM.keys(), stype, reader.prev + 0) assert_in("start offset", (1, 2, 3), start_offset, reader.prev + 1) assert_eq("field 02", 0, pad, reader.prev + 2) start_offset = StartOffset(start_offset) if start_time == 0.0: assert_eq("start offset", StartOffset.Animation, start_offset, reader.prev + 1) start_offset = StartOffset.Unset actual_length = size - SCRIPT_HEADER.size base_model = OBJECT_REGISTRY_NUM[stype] obj = base_model.validate_and_read(reader, anim_def, actual_length) item = ScriptItem( name=base_model._NAME, # pylint: disable=protected-access item=obj, start_offset=start_offset, start_time=start_time, ) script.append(item) assert_eq("script end", abs_end, reader.offset, reader.offset) LOG.debug("Read script") return script
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(cls, reader: BinReader, anim_def: AnimDef) -> ObjectOpacityState: is_set_raw, state_raw, opacity, node_index = reader.read(cls._STRUCT) # this could be another value (e.g. -1), in which case is set is not changed assert_in("is set", (0, 1), is_set_raw, reader.prev + 0) # the state does not seem to depend on is_set? assert_in("state", (0, 1), state_raw, reader.prev + 2) state = state_raw == 1 if state: assert_between("opacity", 0.0, 1.0, opacity, reader.prev + 4) else: assert_eq("opacity", 0.0, opacity, reader.prev + 4) node = anim_def.get_node(node_index - 1, reader.prev + 8) return cls(node=node, is_set=is_set_raw == 1, state=state, opacity=opacity)
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 _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 _assert_node_info_empty(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) # flag? assert_in("field 044", (1, 3, 5, 7), node.unk044, offset + 44) assert_in("zone id", (1, ZONE_DEFAULT), node.zone_id, offset + 48) assert_eq("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) # unknowns are not 0.0 assert_eq("field 196", 160, node.unk196, offset + 196)
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_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 read(cls, reader: BinReader, anim_def: AnimDef) -> SoundNode: ( name_raw, one32, inherit_trans, active_state, node_index, tx, ty, tz, ) = reader.read(cls._STRUCT) with assert_ascii("name", name_raw, reader.prev + 0): name = ascii_zterm_padded(name_raw) # this would cause the sound node to be dynamically allocated assert_eq("field 32", 1, one32, reader.prev + 32) # 44 assert_in("active state", (0, 1), active_state, reader.prev + 40) # 52 # this is actually a bit field. # if it's 1, then the translation would be applied directly to the node # if it's 2 and AT_NODE is given, then the translation is applied relative # to the node assert_in("field 36", (0, 2), inherit_trans, reader.prev + 36) # 48 if inherit_trans == 0: at_node = None assert_eq("at node", 0, node_index, reader.prev + 44) assert_eq("at tx", 0.0, tx, reader.prev + 48) assert_eq("at ty", 0.0, ty, reader.prev + 52) assert_eq("at tz", 0.0, tz, reader.prev + 56) else: node = anim_def.get_node(node_index - 1, reader.prev + 44) at_node = AtNodeShort(node=node, tx=tx, ty=ty, tz=tz) return cls(name=name, active_state=active_state == 1, at_node=at_node)
def read_node_data_object3d( # pylint: disable=too-many-locals reader: BinReader, ) -> Object3d: ( flag_raw, # 000 opacity, # 004 zero008, zero012, zero016, zero020, rot_x, # 024 rot_y, # 028 rot_z, # 032 scale_x, # 036 scale_y, # 040 scale_z, # 044 matrix00, # 048 matrix01, # 052 matrix02, # 056 matrix10, # 060 matrix11, # 064 matrix12, # 068 matrix20, # 072 matrix21, # 076 matrix22, # 080 trans_x, # 084 trans_y, # 088 trans_z, # 092 zero096, # 42 zero bytes ) = reader.read(OBJECT3D) assert_in("flag", (32, 40), flag_raw, reader.prev + 0) assert_eq("opacity", 0.0, opacity, reader.prev + 4) assert_eq("field 008", 0.0, zero008, reader.prev + 8) assert_eq("field 012", 0.0, zero012, reader.prev + 12) assert_eq("field 016", 0.0, zero016, reader.prev + 16) assert_eq("field 020", 0.0, zero020, reader.prev + 20) assert_eq("scale x", 1.0, scale_x, reader.prev + 36) assert_eq("scale y", 1.0, scale_y, reader.prev + 40) assert_eq("scale z", 1.0, scale_z, reader.prev + 44) assert_all_zero("field 096", zero096, reader.prev + 96) def _assert_matrix(expected: Matrix) -> None: assert_eq("matrix 00", expected[0], matrix00, reader.prev + 48) assert_eq("matrix 01", expected[1], matrix01, reader.prev + 52) assert_eq("matrix 02", expected[2], matrix02, reader.prev + 56) assert_eq("matrix 10", expected[3], matrix10, reader.prev + 60) assert_eq("matrix 11", expected[4], matrix11, reader.prev + 64) assert_eq("matrix 12", expected[5], matrix12, reader.prev + 68) assert_eq("matrix 20", expected[6], matrix20, reader.prev + 72) assert_eq("matrix 21", expected[7], matrix21, reader.prev + 76) assert_eq("matrix 22", expected[8], matrix22, reader.prev + 80) matrix_sign = extract_zero_signs( matrix00, matrix01, matrix02, matrix10, matrix11, matrix12, matrix20, matrix21, matrix22, ) if flag_raw == 40: assert_eq("rot x", 0.0, rot_x, reader.prev + 24) assert_eq("rot y", 0.0, rot_y, reader.prev + 28) assert_eq("rot z", 0.0, rot_z, reader.prev + 32) assert_eq("trans x", 0.0, trans_x, reader.prev + 84) assert_eq("trans y", 0.0, trans_y, reader.prev + 88) assert_eq("trans z", 0.0, trans_z, reader.prev + 92) _assert_matrix(IDENTITY_MATRIX) rotation = None translation = None matrix = None else: # all values between PI assert_between("rot x", -PI, PI, rot_x, reader.prev + 24) assert_between("rot y", -PI, PI, rot_y, reader.prev + 28) assert_between("rot z", -PI, PI, rot_z, reader.prev + 32) rotation = (rot_x, rot_y, rot_z) translation = (trans_x, trans_y, trans_z) expected = euler_to_matrix(rot_x, rot_y, rot_z) # in most cases, the calculated matrix is correct :/ # for 58 out of 2729 Object3D nodes, this fails though try: _assert_matrix(expected) except Mech3ParseError: matrix = ( matrix00, matrix01, matrix02, matrix10, matrix11, matrix12, matrix20, matrix21, matrix22, ) matrix_sign = 0 else: matrix = None # object 3d nodes always have an action priority of 6 return Object3d( type="Object3D", rotation=rotation, translation=translation, matrix=matrix, matrix_sign=matrix_sign, )
def read( # pylint: disable=too-many-locals,too-many-statements cls, reader: BinReader, anim_def: AnimDef) -> ObjectMotionFromTo: ( flag_raw, # 000 node_index, # 004 morph_from, # 008 morph_to, # 012 morph_delta, # 016 translate_from_x, # 020 translate_from_y, # 024 translate_from_z, # 028 translate_to_x, # 032 translate_to_y, # 036 translate_to_z, # 040 translate_delta_x, # 044 translate_delta_y, # 048 translate_delta_z, # 052 rotate_from_x, # 056 rotate_from_y, # 060 rotate_from_z, # 064 rotate_to_x, # 068 rotate_to_y, # 072 rotate_to_z, # 076 rotate_delta_x, # 080 rotate_delta_y, # 084 rotate_delta_z, # 088 scale_from_x, # 092 scale_from_y, # 096 scale_from_z, # 100 scale_to_x, # 104 scale_to_y, # 108 scale_to_z, # 112 scale_delta_x, # 116 scale_delta_y, # 120 scale_delta_z, # 124 run_time, # 128 ) = reader.read(cls._STRUCT) # these only appear in mutually exclusive combinations, but it really is # a flag in the game code assert_in("flag", (1, 2, 4, 8), flag_raw, reader.prev + 0) node = anim_def.get_node(node_index - 1, reader.prev + 4) if flag_raw == 8: morph: Optional[Vec3] = (morph_from, morph_to, morph_delta) else: assert_eq("morph from", 0.0, morph_from, reader.prev + 8) assert_eq("morph to", 0.0, morph_to, reader.prev + 12) assert_eq("morph delta", 0.0, morph_delta, reader.prev + 16) morph = None if flag_raw == 1: translate: Optional[Vec3FromTo] = Vec3FromTo( from_=(translate_from_x, translate_from_y, translate_from_z), to_=(translate_to_x, translate_to_y, translate_to_z), delta=(translate_delta_x, translate_delta_y, translate_delta_z), ) else: assert_eq("translate from x", 0.0, translate_from_x, reader.prev + 20) assert_eq("translate from y", 0.0, translate_from_y, reader.prev + 24) assert_eq("translate from z", 0.0, translate_from_z, reader.prev + 28) assert_eq("translate to x", 0.0, translate_to_x, reader.prev + 32) assert_eq("translate to y", 0.0, translate_to_y, reader.prev + 36) assert_eq("translate to z", 0.0, translate_to_z, reader.prev + 40) assert_eq("translate delta x", 0.0, translate_delta_x, reader.prev + 44) assert_eq("translate delta y", 0.0, translate_delta_y, reader.prev + 48) assert_eq("translate delta z", 0.0, translate_delta_z, reader.prev + 52) translate = None if flag_raw == 2: rotate: Optional[Vec3FromTo] = Vec3FromTo( from_=( degrees(rotate_from_x), degrees(rotate_from_y), degrees(rotate_from_z), ), to_=(degrees(rotate_to_x), degrees(rotate_to_y), degrees(rotate_to_z)), delta=( degrees(rotate_delta_x), degrees(rotate_delta_y), degrees(rotate_delta_z), ), ) else: assert_eq("rotate from x", 0.0, rotate_from_x, reader.prev + 56) assert_eq("rotate from y", 0.0, rotate_from_y, reader.prev + 60) assert_eq("rotate from z", 0.0, rotate_from_z, reader.prev + 64) assert_eq("rotate to x", 0.0, rotate_to_x, reader.prev + 68) assert_eq("rotate to y", 0.0, rotate_to_y, reader.prev + 72) assert_eq("rotate to z", 0.0, rotate_to_z, reader.prev + 76) assert_eq("rotate delta x", 0.0, rotate_delta_x, reader.prev + 80) assert_eq("rotate delta y", 0.0, rotate_delta_y, reader.prev + 84) assert_eq("rotate delta z", 0.0, rotate_delta_z, reader.prev + 88) rotate = None if flag_raw == 4: scale: Optional[Vec3FromTo] = Vec3FromTo( from_=(scale_from_x, scale_from_y, scale_from_z), to_=(scale_to_x, scale_to_y, scale_to_z), delta=(scale_delta_x, scale_delta_y, scale_delta_z), ) else: assert_eq("scale from x", 0.0, scale_from_x, reader.prev + 92) assert_eq("scale from y", 0.0, scale_from_y, reader.prev + 96) assert_eq("scale from z", 0.0, scale_from_z, reader.prev + 100) assert_eq("scale to x", 0.0, scale_to_x, reader.prev + 104) assert_eq("scale to y", 0.0, scale_to_y, reader.prev + 108) assert_eq("scale to z", 0.0, scale_to_z, reader.prev + 112) assert_eq("scale delta x", 0.0, scale_delta_x, reader.prev + 116) assert_eq("scale delta y", 0.0, scale_delta_y, reader.prev + 120) assert_eq("scale delta z", 0.0, scale_delta_z, reader.prev + 124) scale = None assert_gt("run time", 0.0, run_time, reader.prev + 128) return cls( node=node, morph=morph, translate=translate, rotate=rotate, scale=scale, run_time=run_time, )
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
def read(cls, reader: BinReader, anim_def: AnimDef) -> ObjectActiveState: state, node_index = reader.read(cls._STRUCT) assert_in("state", (0, 1), state, reader.prev + 0) node = anim_def.get_node(node_index - 1, reader.prev + 4) return cls(node=node, state=state == 1)
def read( # pylint: disable=too-many-locals,too-many-branches,too-many-statements cls, reader: BinReader, anim_def: AnimDef) -> LightState: ( name_raw, light_index, flag_raw, active_state_raw, point_source_raw, directional_raw, saturated_raw, subdivide_raw, static_raw, node_index, tx, ty, tz, rx, ry, rz, range_min, range_max, color_r, color_g, color_b, ambient_value, diffuse_value, ) = reader.read(cls._STRUCT) with assert_ascii("name", name_raw, reader.prev + 0): name = ascii_zterm_padded(name_raw) expected_name = anim_def.get_light(light_index - 1, reader.prev + 32) assert_eq("index name", expected_name, name, reader.prev + 32) # 44 with assert_flag("flag", flag_raw, reader.prev + 36): flag = LightFlag.check(flag_raw) # 0 = directed (never set), 1 = point source assert_eq("point source", 1, point_source_raw, reader.prev + 44) # 56 active_state = flag != LightFlag.Inactive if anim_def.anim_name in ("impact_ppc_mech", "exp_flash", "exp_flash_small"): # unfortunately, in these few cases, the active state doesn't line up assert_in("active state", (0, 1), active_state_raw, reader.prev + 40) # 52 else: expected = 1 if active_state else 0 assert_eq("active state", expected, active_state_raw, reader.prev + 40) # WARNING: the values are the state, and the flag indicates whether this # state should be set or not if LightFlag.Directional(flag): assert_in("directional", (0, 1), directional_raw, reader.prev + 48) # 60 directional = directional_raw == 1 else: assert_eq("directional", 0, directional_raw, reader.prev + 48) directional = None if LightFlag.Saturated(flag): assert_in("saturated", (0, 1), saturated_raw, reader.prev + 52) # 64 saturated = saturated_raw == 1 else: assert_eq("saturated", 0, saturated_raw, reader.prev + 52) saturated = None if LightFlag.Subdivide(flag): assert_in("subdivide", (0, 1), subdivide_raw, reader.prev + 56) # 68 subdivide = subdivide_raw == 1 else: assert_eq("subdivide", 0, subdivide_raw, reader.prev + 56) subdivide = None if LightFlag.Static(flag): assert_in("static", (0, 1), static_raw, reader.prev + 60) # 72 static = static_raw == 1 else: assert_eq("static", 0, static_raw, reader.prev + 60) static = None if not LightFlag.Translation(flag): assert_eq("at node", 0, node_index, reader.prev + 64) assert_eq("trans x", 0.0, tx, reader.prev + 68) assert_eq("trans y", 0.0, ty, reader.prev + 72) assert_eq("trans z", 0.0, tz, reader.prev + 76) assert_eq("rotation", False, LightFlag.Rotation(flag), reader.prev + 36) assert_eq("rot x", 0.0, rx, reader.prev + 80) assert_eq("rot y", 0.0, ry, reader.prev + 84) assert_eq("rot z", 0.0, rz, reader.prev + 88) at_node = None else: if node_index == -200: node = INPUT_NODE else: node = anim_def.get_node(node_index - 1, reader.prev + 64) # this is never set assert_eq("rotation", False, LightFlag.Rotation(flag), reader.prev + 36) assert_eq("rot x", 0.0, rx, reader.prev + 80) assert_eq("rot y", 0.0, ry, reader.prev + 84) assert_eq("rot z", 0.0, rz, reader.prev + 88) at_node = AtNodeShort(node=node, tx=tx, ty=ty, tz=tz) if LightFlag.Range(flag): assert_ge("range min", 0.0, range_min, reader.prev + 92) assert_ge("range max", range_min, range_max, reader.prev + 96) range_: Range = (range_min, range_max) else: assert_eq("range min", 0.0, range_min, reader.prev + 92) assert_eq("range max", 0.0, range_max, reader.prev + 96) range_ = None if LightFlag.Color(flag): assert_between("red", 0.0, 1.0, color_r, reader.prev + 100) assert_between("green", 0.0, 1.0, color_g, reader.prev + 104) assert_between("blue", 0.0, 1.0, color_b, reader.prev + 108) color: Color = (color_r, color_g, color_b) else: assert_eq("red", 0.0, color_r, reader.prev + 100) assert_eq("green", 0.0, color_g, reader.prev + 104) assert_eq("blue", 0.0, color_b, reader.prev + 108) color = None if LightFlag.Ambient(flag): assert_between("ambient", 0.0, 1.0, ambient_value, reader.prev + 112) ambient = ambient_value else: assert_eq("ambient", 0.0, ambient_value, reader.prev + 112) ambient = None if LightFlag.Diffuse(flag): assert_between("diffuse", 0.0, 1.0, diffuse_value, reader.prev + 116) diffuse = diffuse_value else: assert_eq("diffuse", 0.0, diffuse_value, reader.prev + 116) diffuse = None return cls( name=name, active_state=active_state, directional=directional, saturated=saturated, subdivide=subdivide, static=static, at_node=at_node, range=range_, color=color, ambient=ambient, diffuse=diffuse, )
def read( # pylint: disable=too-many-locals,too-many-branches,too-many-statements cls, reader: BinReader, anim_def: AnimDef) -> CallAnimation: ( name_raw, operand_index, flag_raw, anim_index, wait_for_completion, node_index, tx, ty, tz, rx, ry, rz, ) = reader.read(cls._STRUCT) with assert_ascii("name", name_raw, reader.prev + 0): name = ascii_zterm_padded(name_raw) # not all combinations are present assert_in("flag", (0, 1, 3, 7, 8, 10, 16), flag_raw, reader.prev + 34) # 46 with assert_flag("flag", flag_raw, reader.prev + 34): flag = CallAnimationFlag.check(flag_raw) # this is used to store the index of the animation to call once loaded assert_eq("anim index", 0, anim_index, reader.prev + 36) # 48 if CallAnimationFlag.WaitFor(flag): # since multiple animation with the same name may be called, translating # this to a name would lose information max_prev_ref = len(anim_def.anim_refs) - 1 assert_between("wait for", 0, max_prev_ref, wait_for_completion, reader.prev + 38) else: assert_eq("wait for", -1, wait_for_completion, reader.prev + 38) # 50 has_at_node = CallAnimationFlag.AtNode(flag) has_with_node = CallAnimationFlag.WithNode(flag) has_translation = CallAnimationFlag.Translation(flag) has_rotation = CallAnimationFlag.Rotation(flag) if not has_translation: assert_eq("tx", 0.0, tx, reader.prev + 44) assert_eq("ty", 0.0, ty, reader.prev + 48) assert_eq("tz", 0.0, tz, reader.prev + 52) if not has_rotation: assert_eq("rx", 0.0, rx, reader.prev + 56) assert_eq("ry", 0.0, ry, reader.prev + 60) assert_eq("rz", 0.0, rz, reader.prev + 64) at_node: AtNodeFlex = None with_node = None if has_at_node: # when using AT_NODE, OPERAND_NODE can't be used assert_eq("operand node", 0, operand_index, reader.prev + 32) operand_node = None assert_eq("with node", False, has_with_node, reader.prev + 34) if node_index == 65336: node = INPUT_NODE else: node = anim_def.get_node(node_index - 1, reader.prev + 40) if has_rotation: at_node = AtNodeLong(node=node, tx=tx, ty=ty, tz=tz, rx=rx, ry=ry, rz=rz) else: at_node = AtNodeShort(node=node, tx=tx, ty=ty, tz=tz) elif has_with_node: # when using WITH_NODE, OPERAND_NODE can't be used assert_eq("operand node", 0, operand_index, reader.prev + 32) operand_node = None assert_eq("has rotation", False, has_rotation, reader.prev + 34) # WITH_NODE doesn't seem to use INPUT_NODE node = anim_def.get_node(node_index - 1, reader.prev + 40) with_node = AtNodeShort(node=node, tx=tx, ty=ty, tz=tz) else: # otherwise, OPERAND_NODE may be used but doesn't need to be if operand_index < 1: assert_eq("operand node", 0, operand_index, reader.prev + 32) operand_node = None else: operand_node = anim_def.get_node(operand_index - 1, reader.prev + 32) assert_eq("node index", 0, node_index, reader.prev + 40) assert_eq("has translation", False, has_translation, reader.prev + 34) assert_eq("has rotation", False, has_rotation, reader.prev + 34) return cls( name=name, at_node=at_node, with_node=with_node, operand_node=operand_node, wait_for_completion=wait_for_completion, )
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