def read( # pylint: disable=too-many-locals cls, reader: BinReader, anim_def: AnimDef) -> LightAnimation: ( name_raw, light_index, range_min, range_max, zero44, zero48, zero52, zero56, color_r, color_g, color_b, zero72, zero76, zero80, zero84, zero88, zero92, run_time, ) = 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) if range_min >= 0.0: assert_ge("range max", range_min, range_max, reader.prev + 40) else: assert_lt("range max", range_min, range_max, 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_between("red", -5.0, 5.0, color_r, reader.prev + 60) assert_between("green", -5.0, 5.0, color_g, reader.prev + 64) assert_between("blue", -5.0, 5.0, color_b, reader.prev + 68) assert_eq("field 72", 0, zero72, reader.prev + 72) assert_eq("field 76", 0, zero76, reader.prev + 76) assert_eq("field 80", 0, zero80, reader.prev + 80) assert_eq("field 84", 0, zero84, reader.prev + 84) assert_eq("field 88", 0, zero88, reader.prev + 88) assert_eq("field 92", 0, zero92, reader.prev + 92) assert_gt("run time", 0.0, run_time, reader.prev + 96) return cls( name=name, range=(range_min, range_max), color=(color_r, color_g, color_b), run_time=run_time, )
def validate_and_read(cls, reader: BinReader, _anim_def: AnimDef, actual_length: int) -> ObjectMotionSIScript: abs_end = reader.offset + actual_length ( index, count, zero08, zero12, zero16, zero20, ) = reader.read(cls._STRUCT) assert_between("index", 0, 20, index, reader.prev + 0) # 12 assert_eq("field 08", 0.0, zero08, reader.prev + 8) # 20 assert_eq("field 12", 0.0, zero12, reader.prev + 12) # 24 assert_eq("field 16", 0, zero16, reader.prev + 16) # 28 assert_eq("field 20", 0, zero20, reader.prev + 20) # 32 frames = [] for i in range(count): flag, start_time, end_time = reader.read(SI_SCRIPT_FRAME) assert_between(f"motion {i} flag", 1, 7, flag, reader.prev + 0) assert_ge(f"motion {i} start", 0.0, start_time, reader.prev + 4) if end_time != 0.0: assert_ge(f"motion {i} end", start_time, end_time, reader.prev + 8) if (flag & 1) == 0: translate: Optional[Vector] = None else: values = cast(Values, reader.read(SI_SCRIPT_DATA)) translate = parse_vec_data(values, start_time) if (flag & 2) == 0: rotate: Optional[Quaternion] = None else: values = cast(Values, reader.read(SI_SCRIPT_DATA)) rotate = parse_quat_data(values, start_time) if (flag & 4) == 0: scale: Optional[Vector] = None else: values = cast(Values, reader.read(SI_SCRIPT_DATA)) scale = parse_vec_data(values, start_time) frame = MotionFrame( start=start_time, end=end_time, translate=translate, rotate=rotate, scale=scale, ) frames.append(frame) assert_eq("motion script end", abs_end, reader.offset, reader.offset) return cls(index=index, frames=frames)
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) -> FogState: ( name_raw, flag_raw, fog_type, color_r, color_g, color_b, altitude_min, altitude_max, range_min, range_max, ) = reader.read(cls._STRUCT) assert_eq("name", DEFAULT_FOG_NAME, name_raw, reader.prev + 0) # see LIGHT_STATE - in this case, all FOG_STATEs use the same flags # 1 << 0 set fog state # 1 << 1 set fog color # 1 << 2 set fog altitude # 1 << 3 set fog range assert_eq("flag", 14, flag_raw, reader.prev + 32) # OFF = 0, LINEAR = 1, EXPONENTIAL = 2 assert_eq("fog type", 1, fog_type, reader.prev + 36) assert_between("red", 0.0, 1.0, color_r, reader.prev + 40) assert_between("green", 0.0, 1.0, color_g, reader.prev + 44) assert_between("blue", 0.0, 1.0, color_b, reader.prev + 48) # altitude is always ordered this way even negative (unlike range) assert_ge("altitude max", altitude_min, altitude_max, reader.prev + 56) assert_ge("range min", 0.0, range_min, reader.prev + 60) assert_ge("range max", range_min, range_max, reader.prev + 64) return cls( fog_type="LINEAR", color=(color_r, color_g, color_b), altitude=(altitude_min, altitude_max), range=(range_min, range_max), )
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( # 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, )