From 7aba9492bdbb48572aaf4a943302289a17227743 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Tue, 26 Dec 2023 02:16:56 -0600 Subject: [PATCH 01/51] Duplicate H1EK defs for MCC purposes --- reclaimer/mcc_hek/__init__.py | 0 reclaimer/mcc_hek/defs/DeLa.py | 481 +++++++++++ reclaimer/mcc_hek/defs/Soul.py | 34 + reclaimer/mcc_hek/defs/__init__.py | 22 + reclaimer/mcc_hek/defs/actr.py | 270 ++++++ reclaimer/mcc_hek/defs/actv.py | 207 +++++ reclaimer/mcc_hek/defs/ant_.py | 56 ++ reclaimer/mcc_hek/defs/antr.py | 304 +++++++ reclaimer/mcc_hek/defs/bipd.py | 167 ++++ reclaimer/mcc_hek/defs/bitm.py | 292 +++++++ reclaimer/mcc_hek/defs/boom.py | 26 + reclaimer/mcc_hek/defs/cdmg.py | 88 ++ reclaimer/mcc_hek/defs/coll.py | 310 +++++++ reclaimer/mcc_hek/defs/colo.py | 34 + reclaimer/mcc_hek/defs/cont.py | 121 +++ reclaimer/mcc_hek/defs/ctrl.py | 58 ++ reclaimer/mcc_hek/defs/deca.py | 109 +++ reclaimer/mcc_hek/defs/devc.py | 36 + reclaimer/mcc_hek/defs/devi.py | 69 ++ reclaimer/mcc_hek/defs/dobc.py | 59 ++ reclaimer/mcc_hek/defs/effe.py | 181 ++++ reclaimer/mcc_hek/defs/elec.py | 77 ++ reclaimer/mcc_hek/defs/eqip.py | 56 ++ reclaimer/mcc_hek/defs/flag.py | 60 ++ reclaimer/mcc_hek/defs/fog_.py | 92 ++ reclaimer/mcc_hek/defs/font.py | 63 ++ reclaimer/mcc_hek/defs/foot.py | 40 + reclaimer/mcc_hek/defs/garb.py | 37 + reclaimer/mcc_hek/defs/glw_.py | 99 +++ reclaimer/mcc_hek/defs/grhi.py | 249 ++++++ reclaimer/mcc_hek/defs/hmt_.py | 60 ++ reclaimer/mcc_hek/defs/hud_.py | 34 + reclaimer/mcc_hek/defs/hudg.py | 224 +++++ reclaimer/mcc_hek/defs/item.py | 61 ++ reclaimer/mcc_hek/defs/itmc.py | 38 + reclaimer/mcc_hek/defs/jpt_.py | 158 ++++ reclaimer/mcc_hek/defs/lens.py | 127 +++ reclaimer/mcc_hek/defs/lifi.py | 38 + reclaimer/mcc_hek/defs/ligh.py | 108 +++ reclaimer/mcc_hek/defs/lsnd.py | 98 +++ reclaimer/mcc_hek/defs/mach.py | 62 ++ reclaimer/mcc_hek/defs/matg.py | 382 +++++++++ reclaimer/mcc_hek/defs/metr.py | 57 ++ reclaimer/mcc_hek/defs/mgs2.py | 88 ++ reclaimer/mcc_hek/defs/mod2.py | 330 +++++++ reclaimer/mcc_hek/defs/mode.py | 173 ++++ reclaimer/mcc_hek/defs/mply.py | 37 + reclaimer/mcc_hek/defs/ngpr.py | 36 + reclaimer/mcc_hek/defs/obje.py | 151 ++++ reclaimer/mcc_hek/defs/objs/__init__.py | 0 reclaimer/mcc_hek/defs/objs/actr.py | 40 + reclaimer/mcc_hek/defs/objs/ant_.py | 31 + reclaimer/mcc_hek/defs/objs/antr.py | 13 + reclaimer/mcc_hek/defs/objs/bipd.py | 34 + reclaimer/mcc_hek/defs/objs/bitm.py | 483 +++++++++++ reclaimer/mcc_hek/defs/objs/coll.py | 21 + reclaimer/mcc_hek/defs/objs/ctrl.py | 17 + reclaimer/mcc_hek/defs/objs/devi.py | 49 ++ reclaimer/mcc_hek/defs/objs/effe.py | 36 + reclaimer/mcc_hek/defs/objs/lens.py | 21 + reclaimer/mcc_hek/defs/objs/lifi.py | 17 + reclaimer/mcc_hek/defs/objs/ligh.py | 22 + reclaimer/mcc_hek/defs/objs/mach.py | 19 + reclaimer/mcc_hek/defs/objs/mod2.py | 96 +++ reclaimer/mcc_hek/defs/objs/mode.py | 124 +++ reclaimer/mcc_hek/defs/objs/obje.py | 52 ++ reclaimer/mcc_hek/defs/objs/phys.py | 151 ++++ reclaimer/mcc_hek/defs/objs/pphy.py | 31 + reclaimer/mcc_hek/defs/objs/sbsp.py | 64 ++ reclaimer/mcc_hek/defs/objs/shdr.py | 46 + reclaimer/mcc_hek/defs/objs/snd_.py | 20 + reclaimer/mcc_hek/defs/objs/str_.py | 21 + reclaimer/mcc_hek/defs/objs/tag.py | 70 ++ reclaimer/mcc_hek/defs/objs/ustr.py | 13 + reclaimer/mcc_hek/defs/objs/weap.py | 39 + reclaimer/mcc_hek/defs/objs/wphi.py | 20 + reclaimer/mcc_hek/defs/part.py | 105 +++ reclaimer/mcc_hek/defs/pctl.py | 163 ++++ reclaimer/mcc_hek/defs/phys.py | 115 +++ reclaimer/mcc_hek/defs/plac.py | 35 + reclaimer/mcc_hek/defs/pphy.py | 67 ++ reclaimer/mcc_hek/defs/proj.py | 141 +++ reclaimer/mcc_hek/defs/rain.py | 112 +++ reclaimer/mcc_hek/defs/sbsp.py | 561 ++++++++++++ reclaimer/mcc_hek/defs/scen.py | 35 + reclaimer/mcc_hek/defs/scex.py | 62 ++ reclaimer/mcc_hek/defs/schi.py | 90 ++ reclaimer/mcc_hek/defs/scnr.py | 1044 +++++++++++++++++++++++ reclaimer/mcc_hek/defs/senv.py | 239 ++++++ reclaimer/mcc_hek/defs/sgla.py | 84 ++ reclaimer/mcc_hek/defs/shdr.py | 73 ++ reclaimer/mcc_hek/defs/sky_.py | 89 ++ reclaimer/mcc_hek/defs/smet.py | 68 ++ reclaimer/mcc_hek/defs/snd_.py | 175 ++++ reclaimer/mcc_hek/defs/snde.py | 41 + reclaimer/mcc_hek/defs/soso.py | 168 ++++ reclaimer/mcc_hek/defs/sotr.py | 269 ++++++ reclaimer/mcc_hek/defs/spla.py | 76 ++ reclaimer/mcc_hek/defs/ssce.py | 35 + reclaimer/mcc_hek/defs/str_.py | 31 + reclaimer/mcc_hek/defs/swat.py | 80 ++ reclaimer/mcc_hek/defs/tagc.py | 34 + reclaimer/mcc_hek/defs/trak.py | 36 + reclaimer/mcc_hek/defs/udlg.py | 247 ++++++ reclaimer/mcc_hek/defs/unhi.py | 209 +++++ reclaimer/mcc_hek/defs/unit.py | 210 +++++ reclaimer/mcc_hek/defs/ustr.py | 31 + reclaimer/mcc_hek/defs/vcky.py | 67 ++ reclaimer/mcc_hek/defs/vehi.py | 107 +++ reclaimer/mcc_hek/defs/weap.py | 325 +++++++ reclaimer/mcc_hek/defs/wind.py | 32 + reclaimer/mcc_hek/defs/wphi.py | 354 ++++++++ reclaimer/mcc_hek/handler.py | 275 ++++++ 113 files changed, 13394 insertions(+) create mode 100644 reclaimer/mcc_hek/__init__.py create mode 100644 reclaimer/mcc_hek/defs/DeLa.py create mode 100644 reclaimer/mcc_hek/defs/Soul.py create mode 100644 reclaimer/mcc_hek/defs/__init__.py create mode 100644 reclaimer/mcc_hek/defs/actr.py create mode 100644 reclaimer/mcc_hek/defs/actv.py create mode 100644 reclaimer/mcc_hek/defs/ant_.py create mode 100644 reclaimer/mcc_hek/defs/antr.py create mode 100644 reclaimer/mcc_hek/defs/bipd.py create mode 100644 reclaimer/mcc_hek/defs/bitm.py create mode 100644 reclaimer/mcc_hek/defs/boom.py create mode 100644 reclaimer/mcc_hek/defs/cdmg.py create mode 100644 reclaimer/mcc_hek/defs/coll.py create mode 100644 reclaimer/mcc_hek/defs/colo.py create mode 100644 reclaimer/mcc_hek/defs/cont.py create mode 100644 reclaimer/mcc_hek/defs/ctrl.py create mode 100644 reclaimer/mcc_hek/defs/deca.py create mode 100644 reclaimer/mcc_hek/defs/devc.py create mode 100644 reclaimer/mcc_hek/defs/devi.py create mode 100644 reclaimer/mcc_hek/defs/dobc.py create mode 100644 reclaimer/mcc_hek/defs/effe.py create mode 100644 reclaimer/mcc_hek/defs/elec.py create mode 100644 reclaimer/mcc_hek/defs/eqip.py create mode 100644 reclaimer/mcc_hek/defs/flag.py create mode 100644 reclaimer/mcc_hek/defs/fog_.py create mode 100644 reclaimer/mcc_hek/defs/font.py create mode 100644 reclaimer/mcc_hek/defs/foot.py create mode 100644 reclaimer/mcc_hek/defs/garb.py create mode 100644 reclaimer/mcc_hek/defs/glw_.py create mode 100644 reclaimer/mcc_hek/defs/grhi.py create mode 100644 reclaimer/mcc_hek/defs/hmt_.py create mode 100644 reclaimer/mcc_hek/defs/hud_.py create mode 100644 reclaimer/mcc_hek/defs/hudg.py create mode 100644 reclaimer/mcc_hek/defs/item.py create mode 100644 reclaimer/mcc_hek/defs/itmc.py create mode 100644 reclaimer/mcc_hek/defs/jpt_.py create mode 100644 reclaimer/mcc_hek/defs/lens.py create mode 100644 reclaimer/mcc_hek/defs/lifi.py create mode 100644 reclaimer/mcc_hek/defs/ligh.py create mode 100644 reclaimer/mcc_hek/defs/lsnd.py create mode 100644 reclaimer/mcc_hek/defs/mach.py create mode 100644 reclaimer/mcc_hek/defs/matg.py create mode 100644 reclaimer/mcc_hek/defs/metr.py create mode 100644 reclaimer/mcc_hek/defs/mgs2.py create mode 100644 reclaimer/mcc_hek/defs/mod2.py create mode 100644 reclaimer/mcc_hek/defs/mode.py create mode 100644 reclaimer/mcc_hek/defs/mply.py create mode 100644 reclaimer/mcc_hek/defs/ngpr.py create mode 100644 reclaimer/mcc_hek/defs/obje.py create mode 100644 reclaimer/mcc_hek/defs/objs/__init__.py create mode 100644 reclaimer/mcc_hek/defs/objs/actr.py create mode 100644 reclaimer/mcc_hek/defs/objs/ant_.py create mode 100644 reclaimer/mcc_hek/defs/objs/antr.py create mode 100644 reclaimer/mcc_hek/defs/objs/bipd.py create mode 100644 reclaimer/mcc_hek/defs/objs/bitm.py create mode 100644 reclaimer/mcc_hek/defs/objs/coll.py create mode 100644 reclaimer/mcc_hek/defs/objs/ctrl.py create mode 100644 reclaimer/mcc_hek/defs/objs/devi.py create mode 100644 reclaimer/mcc_hek/defs/objs/effe.py create mode 100644 reclaimer/mcc_hek/defs/objs/lens.py create mode 100644 reclaimer/mcc_hek/defs/objs/lifi.py create mode 100644 reclaimer/mcc_hek/defs/objs/ligh.py create mode 100644 reclaimer/mcc_hek/defs/objs/mach.py create mode 100644 reclaimer/mcc_hek/defs/objs/mod2.py create mode 100644 reclaimer/mcc_hek/defs/objs/mode.py create mode 100644 reclaimer/mcc_hek/defs/objs/obje.py create mode 100644 reclaimer/mcc_hek/defs/objs/phys.py create mode 100644 reclaimer/mcc_hek/defs/objs/pphy.py create mode 100644 reclaimer/mcc_hek/defs/objs/sbsp.py create mode 100644 reclaimer/mcc_hek/defs/objs/shdr.py create mode 100644 reclaimer/mcc_hek/defs/objs/snd_.py create mode 100644 reclaimer/mcc_hek/defs/objs/str_.py create mode 100644 reclaimer/mcc_hek/defs/objs/tag.py create mode 100644 reclaimer/mcc_hek/defs/objs/ustr.py create mode 100644 reclaimer/mcc_hek/defs/objs/weap.py create mode 100644 reclaimer/mcc_hek/defs/objs/wphi.py create mode 100644 reclaimer/mcc_hek/defs/part.py create mode 100644 reclaimer/mcc_hek/defs/pctl.py create mode 100644 reclaimer/mcc_hek/defs/phys.py create mode 100644 reclaimer/mcc_hek/defs/plac.py create mode 100644 reclaimer/mcc_hek/defs/pphy.py create mode 100644 reclaimer/mcc_hek/defs/proj.py create mode 100644 reclaimer/mcc_hek/defs/rain.py create mode 100644 reclaimer/mcc_hek/defs/sbsp.py create mode 100644 reclaimer/mcc_hek/defs/scen.py create mode 100644 reclaimer/mcc_hek/defs/scex.py create mode 100644 reclaimer/mcc_hek/defs/schi.py create mode 100644 reclaimer/mcc_hek/defs/scnr.py create mode 100644 reclaimer/mcc_hek/defs/senv.py create mode 100644 reclaimer/mcc_hek/defs/sgla.py create mode 100644 reclaimer/mcc_hek/defs/shdr.py create mode 100644 reclaimer/mcc_hek/defs/sky_.py create mode 100644 reclaimer/mcc_hek/defs/smet.py create mode 100644 reclaimer/mcc_hek/defs/snd_.py create mode 100644 reclaimer/mcc_hek/defs/snde.py create mode 100644 reclaimer/mcc_hek/defs/soso.py create mode 100644 reclaimer/mcc_hek/defs/sotr.py create mode 100644 reclaimer/mcc_hek/defs/spla.py create mode 100644 reclaimer/mcc_hek/defs/ssce.py create mode 100644 reclaimer/mcc_hek/defs/str_.py create mode 100644 reclaimer/mcc_hek/defs/swat.py create mode 100644 reclaimer/mcc_hek/defs/tagc.py create mode 100644 reclaimer/mcc_hek/defs/trak.py create mode 100644 reclaimer/mcc_hek/defs/udlg.py create mode 100644 reclaimer/mcc_hek/defs/unhi.py create mode 100644 reclaimer/mcc_hek/defs/unit.py create mode 100644 reclaimer/mcc_hek/defs/ustr.py create mode 100644 reclaimer/mcc_hek/defs/vcky.py create mode 100644 reclaimer/mcc_hek/defs/vehi.py create mode 100644 reclaimer/mcc_hek/defs/weap.py create mode 100644 reclaimer/mcc_hek/defs/wind.py create mode 100644 reclaimer/mcc_hek/defs/wphi.py create mode 100644 reclaimer/mcc_hek/handler.py diff --git a/reclaimer/mcc_hek/__init__.py b/reclaimer/mcc_hek/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/reclaimer/mcc_hek/defs/DeLa.py b/reclaimer/mcc_hek/defs/DeLa.py new file mode 100644 index 00000000..8a68085a --- /dev/null +++ b/reclaimer/mcc_hek/defs/DeLa.py @@ -0,0 +1,481 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +# Typing these up took FOREVER +game_data_input_functions = ( + 'NULL', + 'player_settings_menu_update', + 'unused', + 'playlist_settings_menu_update', + 'gametype_select_menu_update', + 'multiplayer_type_menu_update', + 'solo_level_select_update', + 'difficulty_menu_update', + 'build_number', # textbox only for build_number + 'server_list_update', + 'network_pregame_status_update', + 'splitscreen_pregame_update', + 'net_splitscreen_prejoin_players', + 'mp_profile_list_update', + 'wide_3_player_profile_list_update', + 'player_profile_edit_select_menu_update', + 'player_profile_small_menu_update', + 'game_settings_lists_text_update', + 'solo_game_objective_text', + 'color_picker_update', + 'game_setings_lists_pic_update', + 'main_menu_fake_animate', + 'mp_level_select_update', + 'get_active_player_profile_name', + 'get_edit_player_profile_name', + 'get_edit_game_settings_name', + 'get_active_player_profile_color', + 'mp_set_textbox_map_name', + 'mp_set_textbox_game_rules', + 'mp_set_textbox_teams_no_teams', + 'mp_set_textbox_score_limit', + 'mp_set_textbox_score_limit_type', + 'mp_set_bitmap_for_map', + 'mp_set_bitmap_for_ruleset', + 'mp_set_textbox_player_count', + 'mp_edit_profile_set_rule_text', + 'system_link_status_check', + 'mp_game_directions', + 'teams_no_teams_bitmap_update', + 'warn_if_diff_will_nuke_saved_game', + 'dim_if_no_net_cable', + 'pause_game_set_textbox_inverted', + 'dim_unless_two_controllers', + 'controls_update_menu', + 'video_menu_update', + 'gamespy_screen_update', + 'common_button_bar_update', + 'gamepad_update_menu', + 'server_settings_update', + 'audio_menu_update', + 'mp_profile_vehicles_update', + 'solo_map_list_update', + 'mp_map_list_update', + 'gametype_select_list_update', + 'gametype_edit_list_update', + 'load_game_list_update', + 'checking_for_updates', + 'direct_ip_connect_update', + 'network_settings_update', + ) +event_types = ( + 'A_button', + 'B_button', + 'X_button', + 'Y_button', + 'black_button', + 'white_button', + 'left_trigger', + 'right_trigger', + 'dpad_up', + 'dpad_down', + 'dpad_left', + 'dpad_right', + 'start_button', + 'back_button', + 'left_thumb', + 'right_thumb', + 'left_analog_stick_up', + 'left_analog_stick_down', + 'left_analog_stick_left', + 'left_analog_stick_right', + 'right_analog_stick_up', + 'right_analog_stick_down', + 'right_analog_stick_left', + 'right_analog_stick_right', + 'created', + 'deleted', + 'get_focus', + 'lose_focus', + 'left_mouse', + 'middle_mouse', + 'right_mouse', + 'double_click', + 'custom_activation', + 'post_render' + ) +event_functions = ( + 'NULL', + 'list_goto_next_item', + 'list_goto_previous_item', + 'unused1', + 'unused2', + 'initialize_sp_level_list_solo', + 'initialize_sp_level_list_coop', + 'dispose_sp_level_list', + 'solo_level_set_map', + 'set_difficulty', + 'start_new_game', + 'pause_game_restart_at_checkpoint', + 'pause_game_restart_level', + 'pause_game_rturn_to_main_menu', + 'clear_multiplayer_player_joins', + 'join_controller_to_mp_game', + 'initialize_net_game_server_list', + 'start_net_game_server', + 'dispose_net_game_server_list', + 'shutdown_net_game_server', + 'net_game_join_from_server_list', + 'split_screen_game_initialize', + 'coop_game_initialize', + 'main_menu_initialize', + 'mp_type_menu_initialize', + 'pick_play_stage_for_quick_start', + 'mp_level_list_initialize', + 'mp_level_list_dispose', + 'mp_level_select', + 'mp_profiles_list_initialize', + 'mp_profiles_list_dispose', + 'mp_profile_set_for_game', + 'swap_player_team', + 'net_game_join_player', + 'player_profile_list_initialize', + 'player_profile_list_dispose', + 'wide_3_player_profile_set_for_game', + 'wide_1_player_profile_set_for_game', + 'mp_profile_begin_editing', + 'mp_profile_end_editing', + 'mp_profile_set_game_engine', + 'mp_profile_change_name', + 'mp_profile_set_ctf_rules', + 'mp_profile_set_koth_rules', + 'mp_profile_set_slayer_rules', + 'mp_profile_set_oddball_rules', + 'mp_profile_set_racing_rules', + 'mp_profile_set_player_options', + 'mp_profile_set_item_options', + 'mp_profile_set_indicator_options', + 'mp_profile_init_game_engine', + 'mp_profile_init_name', + 'mp_profile_init_ctf_rules', + 'mp_profile_init_koth_rules', + 'mp_profile_init_slayer_rules', + 'mp_profile_init_oddball_rules', + 'mp_profile_init_racing_rules', + 'mp_profile_init_player_options', + 'mp_profile_init_item_options', + 'mp_profile_init_indicator_options', + 'mp_profile_save_changes', + 'color_picker_menu_initialize', + 'color_picker_menu_dispose', + 'color_picker_select_color', + 'player_prof_begin_editing', + 'player_prof_end_editing', + 'player_prof_change_name', + 'player_prof_save_changes', + 'player_prof_init_control_settings', + 'player_prof_init_adv_ctrl_settings', + 'player_prof_save_control_settings', + 'player_prof_save_adv_ctrl_settings', + 'mp_game_player_quit', + 'main_menu_switch_to_solo_game', + 'request_del_player_profile', + 'request_del_playlist_profile', + 'final_del_player_profile', + 'final_del_playlist_profile', + 'cancel_profile_delete', + 'create_and_edit_playlist_profile', + 'create_and_edit_player_profile', + 'net_game_speed_start', + 'net_game_delay_start', + 'net_server_accept_connection', + 'net_server_defer_start', + 'net_server_allow_start', + 'disable_if_no_xdemos', + 'run_xdemos', + 'sp_reset_controller_choices', + 'sp_set_p1_controller_choices', + 'sp_set_p2_controller_choices', + 'error_if_no_network_connection', + 'start_server_if_none_advertised', + 'net_game_unjoin_player', + 'close_if_not_editing_profile', + 'exit_to_xbox_dashboard', + 'new_campaign_chosen', + 'new_campaign_decision', + 'pop_history_stack_once', + 'difficulty_menu_init', + 'begin_music_fade_out', + 'new_game_if_no_player_profile', + 'exit_gracefully_to_xbox_dashboard', + 'pause_game_invert_pitch', + 'start_new_coop_game', + 'pause_game_invert_spinner_set', + 'pause_game_invert_spinner_get', + 'main_menu_quit_game', + 'mouse_emit_ACCEPT_event', + 'mouse_emit_BACK_event', + 'mouse_emit_DPAD_LEFT_event', + 'mouse_emit_DPAD_RIGHT_event', + 'mouse_spinner_3wide_click', + 'controls_screen_init', + 'video_screen_init', + 'controls_begin_binding', + 'gamespy_screen_init', + 'gamespy_screen_dispose', + 'gamespy_select_header', + 'gamespy_select_item', + 'gamespy_select_button', + 'player_prof_init_mouse_set', + 'player_prof_change_mouse_set', + 'player_prof_init_audio_set', + 'player_prof_change_audio_set', + 'player_prof_change_video_set', + 'controls_screen_dispose', + 'controls_screen_change_set', + 'mouse_emit_X_event', + 'gamepad_screen_init', + 'gamepad_screen_dispose', + 'gamepad_screen_change_gamepads', + 'gamepad_screen_select_item', + 'mouse_screen_defaults', + 'audio_screen_defaults', + 'video_screen_defaults', + 'controls_screen_defaults', + 'profile_set_edit_begin', + 'profile_manager_delete', + 'profile_manager_select', + 'gamespy_dismiiss_error', + 'server_settings_init', + 'server_set_edit_server_name', + 'server_set_edit_server_password', + 'server_set_start_game', + 'video_test_dialog_init', + 'video_test_dialog_dispose', + 'video_test_dialog_accept', + 'gamespy_dismiss_filters', + 'gamespy_update_filter_settings', + 'gamespy_dismiss_back_handler', + 'mouse_spinner_1wide_click', + 'controls_back_handler', + 'controls_advanced_launch', + 'controls_advanced_ok', + 'mp_pause_menu_open', + 'mp_game_options_open', + 'mp_choose_team', + 'mp_prof_init_vehicle_options', + 'mp_prof_save_vehicle_options', + 'single_prev_cl_item_active', + 'mp_prof_init_teamplay_options', + 'mp_prof_save_teamplay_options', + 'mp_game_options_choose', + 'emit_custom_activation_event', + 'player_prof_cancel_audio_set', + 'player_prof_init_network_options', + 'player_prof_save_network_options', + 'credits_post_render', + 'difficulty_item_select', + 'credits_initialize', + 'credits_dispose', + 'gamespy_get_patch', + 'video_screen_dispose', + 'campaign_menu_init', + 'campaign_menu_continue', + 'load_game_menu_init', + 'load_game_menu_dispose', + 'load_game_menu_activated', + 'solo_menu_save_checkpoint', + 'mp_type_set_mode', + 'checking_for_updates_ok', + 'checking_for_updates_dismiss', + 'direct_ip_connect_init', + 'direct_ip_connect_go', + 'direct_ip_edit_field', + 'network_settings_edit_a_port', + 'network_settings_defaults', + 'load_game_menu_delete_request', + 'load_game_menu_delete_finish' + ) + +widget_bounds = QStruct("", + SInt16("t"), SInt16("l"), SInt16("b"), SInt16("r"), + ORIENT='h', SIZE=8 + ) + +game_data_input = Struct("game_data_input", + SEnum16("function", *game_data_input_functions), + SIZE=36 + ) + +event_handler = Struct("event_handler", + Bool32('flags', + "close_current_widget", + "close_other_widget", + "close_all_widgets", + "open_widget", + "reload_self", + "reload_other_widget", + "give_focus_to_widget", + "run_function", + "replace_self_with_widget", + "go_back_to_previous_widget", + "run_scenario_script", + "try_to_branch_on_failure", + ), + SEnum16("event_type", *event_types), + SEnum16("function", *event_functions), + dependency("widget_tag", "DeLa"), + dependency("sound_effect", "snd!"), + ascii_str32("script"), + SIZE=72 + ) + +s_and_r_reference = Struct("search_and_replace_reference", + ascii_str32("search_string"), + SEnum16("replace_function", + "NULL", + "widgets_controller", + "build_number", + "pid", + ), + SIZE=34 + ) + +conditional_widget = Struct("conditional_widget", + dependency("widget_tag", "DeLa"), + ascii_str32("name"), # UNUSED + Bool32("flags", + "load_if_event_handler_function_fails", + ), + SInt16("custom_controller_index"), # UNUSED + SIZE=80 + ) + +child_widget = Struct("child_widget", + dependency("widget_tag", "DeLa"), + ascii_str32("name"), # UNUSED + Bool32("flags", + "use_custom_controller_index", + ), + SInt16("custom_controller_index"), + SInt16("vertical_offset"), + SInt16("horizontal_offset"), + SIZE=80 + ) + +DeLa_body = Struct("tagdata", + SEnum16("widget_type", + "container", + "text_box", + "spinner_list", + "column_list", + "game_model", # not implemented + "movie", # not implemented + "custom" # not implemented + ), + SEnum16("controller_index", + "player_1", + "player_2", + "player_3", + "player_4", + "any_player" + ), + ascii_str32("name"), + QStruct("bounds", INCLUDE=widget_bounds), + Bool32('flags', + "pass_unhandled_events_to_focused_child", + "pause_game_time", + "flash_background_bitmap", + "dpad_up_down_tabs_thru_children", + "dpad_left_right_tabs_thru_children", + "dpad_up_down_tabs_thru_list_items", + "dpad_left_right_tabs_thru_list_items", + "dont_focus_a_specific_child_widget", + "pass_unhandled_events_to_all_children", + "return_to_main_menu_if_no_history", + "always_use_tag_controller_index", + "always_use_nifty_render_fx", + "dont_push_history", + "force_handle_mouse" + ), + SInt32("auto_close_time", SIDETIP="milliseconds"), + SInt32("auto_close_fade_time", SIDETIP="milliseconds"), + dependency("background_bitmap", "bitm"), + + reflexive("game_data_inputs", game_data_input, 64), + reflexive("event_handlers", event_handler, 32), + reflexive("search_and_replace_references", + s_and_r_reference, 32, DYN_NAME_PATH='.search_string'), + + Pad(128), + Struct("text_box", + dependency("text_label_unicode_strings_list", "ustr"), + dependency("text_font", "font"), + QStruct("text_color", INCLUDE=argb_float), + SEnum16("justification", + "left", + "right", + "center", + ), + # as weird as it sounds, these flags are off alignment by 2 + Bool32("flags", + "editable", + "password", + "flashing", + "dont_do_that_weird_focus_test", + ), + BytesRaw("unknown2", SIZE=10, VISIBLE=False), + + FlSInt16("unknown3", VISIBLE=False), + SInt16("string_list_index"), + SInt16("horizontal_offset"), + SInt16("vertical_offset") + ), + + Pad(28), + Struct("list_items", + Bool32("flags", + "list_items_generated_in_code", + "list_items_from_string_list_tag", + "list_items_only_one_tooltip", + "list_single_preview_no_scroll" + ) + ), + + Struct("spinner_list", + dependency("list_header_bitmap", "bitm"), + dependency("list_footer_bitmap", "bitm"), + QStruct("header_bounds", INCLUDE=widget_bounds), + QStruct("footer_bounds", INCLUDE=widget_bounds) + ), + + Pad(32), + Struct("column_list", + dependency("extended_description_widget", "DeLa") + ), + + Pad(288), + reflexive("conditional_widgets", conditional_widget, 32, + DYN_NAME_PATH='.widget_tag.filepath'), + + Pad(256), + reflexive("child_widgets", child_widget, 32, + DYN_NAME_PATH='.widget_tag.filepath'), + + SIZE=1004 + ) + +def get(): + return DeLa_def + +DeLa_def = TagDef("DeLa", + blam_header('DeLa'), + DeLa_body, + + ext=".ui_widget_definition", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/Soul.py b/reclaimer/mcc_hek/defs/Soul.py new file mode 100644 index 00000000..c0c7c202 --- /dev/null +++ b/reclaimer/mcc_hek/defs/Soul.py @@ -0,0 +1,34 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +ui_widget_def = Struct("ui_widget_definition", + dependency("ui_widget_definition", 'DeLa'), + SIZE=16 + ) + +soul_body = Struct("tagdata", + reflexive("ui_widget_definitions", ui_widget_def, 32, + DYN_NAME_PATH='.ui_widget_definition.filepath'), + SIZE=12, + ) + + +def get(): + return Soul_def + +Soul_def = TagDef("Soul", + blam_header('Soul'), + soul_body,#lol Megaman X4 + + ext=".ui_widget_collection", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/__init__.py b/reclaimer/mcc_hek/defs/__init__.py new file mode 100644 index 00000000..2f706142 --- /dev/null +++ b/reclaimer/mcc_hek/defs/__init__.py @@ -0,0 +1,22 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +# DO NOT REMOVE! __all__ is used to locate frozen definitions +__all__ = ( + "actr", "actv", "ant_", "antr", "bipd", "bitm", "boom", "cdmg", "coll", + "colo", "cont", "ctrl", "deca", "DeLa", "devc", "devi", "dobc", "effe", + "elec", "eqip", "flag", "fog_", "font", "foot", "garb", "glw_", "grhi", + "hmt_", "hud_", "hudg", "item", "itmc", "jpt_", "lens", "lifi", "ligh", + "mach", "matg", "metr", "mgs2", "mod2", "mode", "mply", "ngpr", "obje", + "part", "pctl", "phys", "plac", "pphy", "proj", "rain", "sbsp", "scen", + "scex", "schi", "scnr", "senv", "sgla", "shdr", "sky_", "smet", "snd_", + "snde", "lsnd", "soso", "sotr", "Soul", "spla", "ssce", "str_", "swat", + "tagc", "trak", "udlg", "unhi", "unit", "ustr", "vcky", "vehi", "weap", + "wind", "wphi", + ) diff --git a/reclaimer/mcc_hek/defs/actr.py b/reclaimer/mcc_hek/defs/actr.py new file mode 100644 index 00000000..fd7b047f --- /dev/null +++ b/reclaimer/mcc_hek/defs/actr.py @@ -0,0 +1,270 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.actr import ActrTag +from supyr_struct.defs.tag_def import TagDef + +danger_triggers = ( + "never", + "visible", + "shooting", + "shooting_near_us", + "damaging_us", + "unused1", + "unused2", + "unused3", + "unused4", + "unused5", + ) + +actr_body = Struct("tagdata", + Bool32('flags', + "can_see_in_darkness", + "sneak_uncovering_target", + "sneak_uncovering_pursuit_location", + "unused1", + "shoot_at_targets_last_location", + "try_to_stay_still_when_crouched", + "crouch_when_not_in_combat", + "crouch_when_guarding", + "unused2", + "must_crouch_to_shoot", + "panic_when_surprised", + "always_charge_at_enemies", + "gets_in_vehicles_with_player", + "starts_firing_before_aligned", + "standing_must_move_forward", + "crouching_must_move_forward", + "defensive_crouch_while_charging", + "use_stalking_behavior", + "stalking_freeze_if_exposed", + "always_berserk_in_attacking_mode", + "berserking_uses_panicked_movement", + "flying", + "panicked_by_unopposable_enemy", + "crouch_when_hiding_from_unopposable", + "always_charge_in_attacking_mode", + "dive_off_ledges", + "swarm", + "suicidal_melee_attack", + "cannot_move_while_crouching", + "fixed_crouch_facing", + "crouch_when_in_line_of_fire", + "avoid_friends_line_of_fire" + ), + Bool32('more_flags', + "avoid_all_enemy_attack_vectors", + "must_stand_to_fire", + "must_stop_to_fire", + "disallow_vehicle_combat", + "pathfinding_ignores_danger", + "panic_in_groups", + "no_corpse_shooting" + ), + + Pad(12), + SEnum16("type", *actor_types), + + Pad(2), + Struct("perception", + float_wu("max_vision_distance"), # world units + float_rad("central_vision_angle"), # radians + float_rad("max_vision_angle"), # radians + + Pad(4), + float_rad("peripheral_vision_angle"), # radians + float_wu("peripheral_distance"), # world units + + Pad(4), + QStruct("standing_gun_offset", INCLUDE=ijk_float), + QStruct("crouching_gun_offset", INCLUDE=ijk_float), + float_wu("hearing_distance"), # world units + float_zero_to_one("notice_projectile_chance"), + float_zero_to_one("notice_vehicle_chance"), + + Pad(8), + float_sec("combat_perception_time", + UNIT_SCALE=sec_unit_scale), # seconds + float_sec("guard_perception_time", + UNIT_SCALE=sec_unit_scale), # seconds + float_sec("non_combat_perception_time", + UNIT_SCALE=sec_unit_scale), # seconds + + float_sec("inv_combat_perception_time", + UNIT_SCALE=sec_unit_scale, VISIBLE=False), + float_sec("inv_guard_perception_time", + UNIT_SCALE=sec_unit_scale, VISIBLE=False), + float_sec("inv_non_combat_perception_time", + UNIT_SCALE=sec_unit_scale, VISIBLE=False), + ), + + Pad(8), + Struct("movement", + float_zero_to_one("dive_into_cover_chance"), + float_zero_to_one("emerge_from_cover_chance"), + float_zero_to_one("dive_from_grenade_cover_chance"), + float_wu("pathfinding_radius"), # world units + float_zero_to_one("glass_ignorance_chance"), + float_wu("stationary_movement_dist"), # world units + float_wu("free_flying_sidestep"), # world units + float_rad("begin_moving_angle"), # radians + ), + + Pad(4), + Struct("looking", + yp_float_rad("maximum_aiming_deviation"), # radians + yp_float_rad("maximum_looking_deviation"), # radians + float_rad("noncombat_look_delta_l"), # radians + float_rad("noncombat_look_delta_r"), # radians + float_rad("combat_look_delta_l"), # radians + float_rad("combat_look_delta_r"), # radians + from_to_rad("idle_aiming_range"), # radians + from_to_rad("idle_looking_range"), # radians + QStruct("event_look_time_modifier", INCLUDE=from_to), + from_to_sec("noncombat_idle_facing"), # seconds + from_to_sec("noncombat_idle_aiming"), # seconds + from_to_sec("noncombat_idle_looking"), # seconds + from_to_sec("guard_idle_facing"), # seconds + from_to_sec("guard_idle_aiming"), # seconds + from_to_sec("guard_idle_looking"), # seconds + from_to_sec("combat_idle_facing"), # seconds + from_to_sec("combat_idle_aiming"), # seconds + from_to_sec("combat_idle_looking"), # seconds + Pad(8), + from_to_neg_one_to_one("cosine_maximum_aiming_deviation", VISIBLE=False), + from_to_neg_one_to_one("cosine_maximum_looking_deviation", VISIBLE=False), + + dependency("DO_NOT_USE_1", "weap"), + + Pad(268), + dependency("DO_NOT_USE_2", "proj") + ), + + Struct("unopposable", + SEnum16("unreachable_danger_trigger", *danger_triggers), + SEnum16("vehicle_danger_trigger", *danger_triggers), + SEnum16("player_danger_trigger", *danger_triggers), + + Pad(2), + from_to_sec("danger_trigger_time"), # seconds + SInt16("friends_killed_trigger"), + SInt16("friends_retreating_trigger"), + + Pad(12), + from_to_sec("retreat_time"), # seconds + ), + + Pad(8), + Struct("panic", + from_to_sec("cowering_time"), # seconds + float_zero_to_one("friend_killed_panic_chance"), + SEnum16("leader_type", *actor_types), + + Pad(2), + float_zero_to_one("leader_killed_panic_chance"), + float_zero_to_one("panic_damage_threshold"), + float_wu("surprise_distance"), # world units + ), + + Pad(28), + Struct("defensive", + from_to_sec("hide_behind_cover_time"), # seconds + float_sec("hide_target_not_visible_time", + UNIT_SCALE=sec_unit_scale), # seconds + float_zero_to_one("hide_shield_fraction"), + float_zero_to_one("attack_shield_fraction"), + float_zero_to_one("pursue_shield_fraction"), + + Pad(16), + SEnum16("defensive_crouch_type", + "never", + "danger", + "low_shields", + "hide_behind_shield", + "any_target", + "flood_shamble" + ), + + Pad(2), + Float("attacking_crouch_threshold"), + Float("defending_crouch_threshold"), + float_sec("mim_stand_time", UNIT_SCALE=sec_unit_scale), # seconds + float_sec("mim_crouch_time", UNIT_SCALE=sec_unit_scale), # seconds + Float("defending_hide_time_modifier"), + Float("attacking_evasion_threshold"), + Float("defending_evasion_threshold"), + float_zero_to_one("evasion_seek_cover_chance"), + float_sec("evasion_delay_time", UNIT_SCALE=sec_unit_scale), # seconds + float_wu("max_seek_cover_distance"), # world units + float_zero_to_one("cover_damage_threshold"), + float_sec("stalking_discovery_time", + UNIT_SCALE=sec_unit_scale), # seconds + float_wu("stalking_max_distance"), # world units + float_rad("stationary_facing_angle"), # radians + float_sec("change_facing_stand_time", + UNIT_SCALE=sec_unit_scale), # seconds + ), + + Pad(4), + Struct("pursuit", + from_to_sec("uncover_delay_time"), # seconds + from_to_sec("target_search_time"), # seconds + from_to_sec("pursuit_position_time"), # seconds + SInt16("coordinated_position_count", MIN=0), + SInt16("normal_position_count", MIN=0), + ), + + Pad(32), + QStruct("berserk", + float_sec("melee_attack_delay", UNIT_SCALE=sec_unit_scale), # seconds + float_wu("melee_fudge_factor"), # world units + float_sec("melee_charge_time", UNIT_SCALE=sec_unit_scale), # seconds + float_wu("melee_leap_range_lower_bound"), # world units + float_wu("melee_leap_range_upper_bound"), # world units + Float("melee_leap_velocity", SIDETIP="world units/tick", + UNIT_SCALE=per_sec_unit_scale), # world units/tick + float_zero_to_one("melee_leap_chance"), + float_zero_to_one("melee_leap_ballistic"), + float_zero_to_one("berserk_damage_amount"), + float_zero_to_one("berserk_damage_threshold"), + float_wu("berserk_proximity"), # world units + float_wu("suicide_sensing_dist"), # world units + float_zero_to_one("berserk_grenade_chance"), + ), + + Pad(12), + Struct("firing_positions", + from_to_sec("guard_position_time"), # seconds + from_to_sec("combat_position_time"), # seconds + Float("old_position_avoid_dist"), # world units + Float("friend_avoid_dist"), # world units + ), + + Pad(40), + Struct("communication", + from_to_sec("noncombat_idle_speech_time"), # seconds + from_to_sec("combat_idle_speech_time"), # seconds + + Pad(176), + dependency("DO_NOT_USE_3", "actr"), + ), + SIZE=1272 + ) + + +def get(): + return actr_def + +actr_def = TagDef("actr", + blam_header('actr', 2), + actr_body, + + ext=".actor", endian=">", tag_cls=ActrTag + ) diff --git a/reclaimer/mcc_hek/defs/actv.py b/reclaimer/mcc_hek/defs/actv.py new file mode 100644 index 00000000..571f988e --- /dev/null +++ b/reclaimer/mcc_hek/defs/actv.py @@ -0,0 +1,207 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +change_color = Struct("change_color", + QStruct("color_lower_bound", INCLUDE=rgb_float), + QStruct("color_upper_bound", INCLUDE=rgb_float), + SIZE=32 + ) + +actv_grenades = Struct("grenades", + Pad(8), + SEnum16("grenade_type", *grenade_types), + SEnum16("trajectory_type", + "toss", + "lob", + "bounce", + ), + SEnum16("grenade_stimulus", + "never", + "visible_target", + "seek_cover", + ), + SInt16("minimum_enemy_count"), + float_wu("enemy_radius"), + + Pad(4), + float_wu_sec("grenade_velocity", UNIT_SCALE=per_sec_unit_scale), + from_to_wu("grenade_ranges"), + float_wu("collateral_damage_radius"), + float_zero_to_one("grenade_chance"), + float_sec("grenade_check_time", UNIT_SCALE=sec_unit_scale), + float_sec("encounter_grenade_timeout", UNIT_SCALE=sec_unit_scale) + ) + +actv_body = Struct("tagdata", + Bool32('flags', + "can_shoot_while_flying", + "blend_color_in_hsv", + "has_unlimited_grenades", + "moveswitch_stay_with_friends", + "active_camouflage", + "super_active_camouflage", + "cannot_use_ranged_weapons", + "prefer_passenger_seat", + ), + dependency("actor_definition", "actr"), + dependency("unit", valid_units), + dependency("major_variant", "actv"), + SEnum16("mcc_scoring_type", TOOLTIP="Used to determine score in MCC", *mcc_actor_types), + + + #Movement switching + Struct("movement_switching", + Pad(22), + SEnum16("movement_type", + "always_run", + "always_crouch", + "switch_types", + ), + Pad(2), + float_zero_to_one("initial_crouch_chance"), + from_to_sec("crouch_time", UNIT_SCALE=sec_unit_scale), # seconds + from_to_sec("run_time", UNIT_SCALE=sec_unit_scale) # seconds + ), + + #Ranged combat + Struct("ranged_combat", + dependency("weapon", "weap"), + float_wu("maximum_firing_distance"), + Float("rate_of_fire", UNIT_SCALE=per_sec_unit_scale), # rounds/sec + float_rad("projectile_error"), # radians + from_to_sec("first_burst_delay_time", + UNIT_SCALE=sec_unit_scale), # seconds + Float("new_target_firing_pattern_time", + UNIT_SCALE=sec_unit_scale), # seconds + Float("surprise_delay_time", UNIT_SCALE=sec_unit_scale), # seconds + Float("surprise_fire_wildly_time", + UNIT_SCALE=sec_unit_scale), # seconds + float_zero_to_one("death_fire_wildly_chance"), + float_sec("death_fire_wildly_time", + UNIT_SCALE=sec_unit_scale), # seconds + from_to_wu("desired_combat_range"), + QStruct("custom_stand_gun_offset", INCLUDE=ijk_float), + QStruct("custom_crouch_gun_offset", INCLUDE=ijk_float), + float_zero_to_one("target_tracking"), + float_zero_to_one("target_leading"), + Float("weapon_damage_modifier"), + Float("damage_per_second", UNIT_SCALE=per_sec_unit_scale), # seconds + ), + + #Burst geometry + Struct("burst_geometry", + float_wu("burst_origin_radius"), + float_rad("burst_origin_angle"), # radians + from_to_wu("burst_return_length"), + float_rad("burst_return_angle"), # radians + from_to_sec("burst_duration", UNIT_SCALE=sec_unit_scale), # seconds + from_to_sec("burst_separation"), + Float("burst_angular_velocity", SIDETIP="degrees/sec", + UNIT_SCALE=irad_per_sec_unit_scale), # radians/second + Pad(4), + float_zero_to_one("special_damage_modifier"), + float_rad("special_projectile_error") # radians + ), + + #Firing patterns" + Struct("firing_patterns", + Float("new_target_burst_duration"), + Float("new_target_burst_separation"), + Float("new_target_rate_of_fire", UNIT_SCALE=per_sec_unit_scale), + Float("new_target_projectile_error"), + + Pad(8), + Float("moving_burst_duration"), + Float("moving_burst_separation"), + Float("moving_rate_of_fire", UNIT_SCALE=per_sec_unit_scale), + Float("moving_projectile_error"), + + Pad(8), + Float("berserk_burst_duration"), + Float("berserk_burst_separation"), + Float("berserk_rate_of_fire", UNIT_SCALE=per_sec_unit_scale), + Float("berserk_projectile_error") + ), + + #Special-case firing patterns + Struct("special_case_firing_patterns", + Pad(8), + Float("super_ballistic_range"), + Float("bombardment_range"), + Float("modified_vision_range"), + SEnum16("special_fire_mode", + "none", + "overcharge", + "secondary_trigger", + ), + SEnum16("special_fire_situation", + "never", + "enemy_visible", + "enemy_out_of_sight", + "strafing", + ), + float_zero_to_one("special_fire_chance"), + float_sec("special_fire_delay", UNIT_SCALE=sec_unit_scale) + ), + + #Berserking and melee + Struct("berserking_and_melee", + float_wu("melee_range"), + float_wu("melee_abort_range"), + from_to_wu("berserk_firing_ranges", INCLUDE=from_to), + float_wu("berserk_melee_range"), + float_wu("berserk_melee_abort_range") + ), + + #Grenades + actv_grenades, + + #Items + Struct("items", + Pad(20), + dependency("equipment", "eqip"), + QStruct("grenade_count", + SInt16("from", GUI_NAME=""), SInt16("to"), ORIENT='h' + ), + float_zero_to_one("dont_drop_grenades_chance"), + QStruct("drop_weapon_loaded", INCLUDE=from_to), + QStruct("drop_weapon_ammo", + SInt16("from", GUI_NAME=""), + SInt16("to"), ORIENT='h' + ) + ), + + #Unit properties + Struct("unit_properties", + Pad(28), + Float("body_vitality"), + Float("shield_vitality"), + float_wu("shield_sapping_radius"), + SInt16("forced_shader_permutation"), + ), + + Pad(30), + reflexive("change_colors", change_color, 4), + SIZE=568 + ) + + +def get(): + return actv_def + +actv_def = TagDef("actv", + blam_header('actv'), + actv_body, + + ext=".actor_variant", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/ant_.py b/reclaimer/mcc_hek/defs/ant_.py new file mode 100644 index 00000000..dfe950a8 --- /dev/null +++ b/reclaimer/mcc_hek/defs/ant_.py @@ -0,0 +1,56 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.ant_ import Ant_Tag +from supyr_struct.defs.tag_def import TagDef + +vertex = Struct("vertex", + Float("spring_strength_coefficient"), + + Pad(24), + yp_float_rad("angles"), # radians + float_wu("length"), + SInt16("sequence_index"), + + Pad(2), + QStruct("color", INCLUDE=argb_float), + QStruct("lod_color", INCLUDE=argb_float), + Pad(40), + QStruct('offset', INCLUDE=xyz_float, VISIBLE=False), + + SIZE=128 + ) + +ant__body = Struct("tagdata", + ascii_str32("attachment_marker_name"), + dependency("bitmaps", "bitm"), + dependency("physics", "pphy"), + + Pad(80), + Float("spring_strength_coefficient"), + Float("falloff_pixels"), + Float("cutoff_pixels"), + Float("length"), + + Pad(36), + reflexive("vertices", vertex, 20), + SIZE=208 + ) + + +def get(): + return ant__def + +ant__def = TagDef("ant!", + blam_header('ant!'), + ant__body, + + ext=".antenna", endian=">", tag_cls=Ant_Tag + ) diff --git a/reclaimer/mcc_hek/defs/antr.py b/reclaimer/mcc_hek/defs/antr.py new file mode 100644 index 00000000..9399a550 --- /dev/null +++ b/reclaimer/mcc_hek/defs/antr.py @@ -0,0 +1,304 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.antr import AntrTag +from supyr_struct.defs.tag_def import TagDef + +frame_info_dxdy_node = QStruct("frame_info_node", + Float("dx"), + Float("dy"), ORIENT='h' + ) + +frame_info_dxdydyaw_node = QStruct("frame_info_node", + Float("dx"), + Float("dy"), + Float("dyaw"), ORIENT='h' + ) + +frame_info_dxdydzdyaw_node = QStruct("frame_info_node", + Float("dx"), + Float("dy"), + Float("dz"), + Float("dyaw"), ORIENT='h' + ) + +default_node = Struct("default_node", + # each of these structs exists ONLY if the corrosponding flag + # for that node it NOT set in the animation it is located in. + QStruct("rotation", + SInt16("i", UNIT_SCALE=1/32767), + SInt16("j", UNIT_SCALE=1/32767), + SInt16("k", UNIT_SCALE=1/32767), + SInt16("w", UNIT_SCALE=1/32767), + ORIENT="h" + ), + QStruct("translation", INCLUDE=xyz_float), + Float("scale"), + SIZE=24 + ) + + +dyn_anim_path = "tagdata.animations.STEPTREE[DYN_I].name" + +object_desc = Struct("object", + dyn_senum16("animation", DYN_NAME_PATH=dyn_anim_path), + SEnum16("function", + "A_out", + "B_out", + "C_out", + "D_out" + ), + SEnum16("function_controls", + "frame", + "scale", + ), + SIZE=20, + ) + +anim_enum_desc = QStruct("animation", + dyn_senum16("animation", DYN_NAME_PATH=dyn_anim_path) + ) + +ik_point_desc = Struct("ik_point", + ascii_str32("marker"), + ascii_str32("attach_to_marker"), + SIZE=64, + ) + +weapon_types_desc = Struct("weapon_types", + ascii_str32("label"), + Pad(16), + reflexive("animations", anim_enum_desc, 10, + *unit_weapon_type_animation_names + ), + SIZE=60, + ) + +unit_weapon_desc = Struct("weapon", + ascii_str32("name"), + ascii_str32("grip_marker"), + ascii_str32("hand_marker"), + #Aiming screen bounds + + #pitch and yaw are saved in radians. + float_rad("right_yaw_per_frame"), + float_rad("left_yaw_per_frame"), + SInt16("right_frame_count"), + SInt16("left_frame_count"), + + float_rad("down_pitch_per_frame"), + float_rad("up_pitch_per_frame"), + SInt16("down_frame_count"), + SInt16("up_frame_count"), + + Pad(32), + reflexive("animations", anim_enum_desc, 55, + *unit_weapon_animation_names + ), + reflexive("ik_points", ik_point_desc, 4, DYN_NAME_PATH=".marker"), + reflexive("weapon_types", weapon_types_desc, 10, DYN_NAME_PATH=".label"), + SIZE=188, + ) + +unit_desc = Struct("unit", + ascii_str32("label"), + #pitch and yaw are saved in radians. + + #Looking screen bounds + float_rad("right_yaw_per_frame"), + float_rad("left_yaw_per_frame"), + SInt16("right_frame_count"), + SInt16("left_frame_count"), + + float_rad("down_pitch_per_frame"), + float_rad("up_pitch_per_frame"), + SInt16("down_frame_count"), + SInt16("up_frame_count"), + + Pad(8), + reflexive("animations", anim_enum_desc, 30, + *unit_animation_names + ), + reflexive("ik_points", ik_point_desc, 4, DYN_NAME_PATH=".marker"), + reflexive("weapons", unit_weapon_desc, 16, DYN_NAME_PATH=".name"), + SIZE=100, + ) + +weapon_desc = Struct("weapon", + Pad(16), + reflexive("animations", anim_enum_desc, 11, + *weapon_animation_names + ), + SIZE=28, + ) + +suspension_desc = QStruct("suspension_animation", + SInt16("mass_point_index"), + dyn_senum16("animation", DYN_NAME_PATH=dyn_anim_path), + Float("full_extension_ground_depth"), + Float("full_compression_ground_depth"), + SIZE=20, + ) + +vehicle_desc = Struct("vehicle", + #pitch and yaw are saved in radians. + + #Steering screen bounds + float_rad("right_yaw_per_frame"), + float_rad("left_yaw_per_frame"), + SInt16("right_frame_count"), + SInt16("left_frame_count"), + + float_rad("down_pitch_per_frame"), + float_rad("up_pitch_per_frame"), + SInt16("down_frame_count"), + SInt16("up_frame_count"), + + Pad(68), + reflexive("animations", anim_enum_desc, 8, + *vehicle_animation_names + ), + reflexive("suspension_animations", suspension_desc, 8), + SIZE=116, + ) + +device_desc = Struct("device", + Pad(84), + reflexive("animations", anim_enum_desc, 2, + *device_animation_names + ), + SIZE=96, + ) + +fp_animation_desc = Struct("fp_animation", + Pad(16), + reflexive("animations", anim_enum_desc, 28, + *fp_animation_names + ), + SIZE=28, + ) + +sound_reference_desc = Struct("sound_reference", + dependency('sound', "snd!"), + SIZE=20, + ) + +nodes_desc = Struct("node", + ascii_str32("name"), + dyn_senum16("next_sibling_node_index", DYN_NAME_PATH="..[DYN_I].name"), + dyn_senum16("first_child_node_index", DYN_NAME_PATH="..[DYN_I].name"), + dyn_senum16("parent_node_index", DYN_NAME_PATH="..[DYN_I].name"), + Pad(2), + Bool32("node_joint_flags", + "ball_socket", + "hinge", + "no_movement", + ), + QStruct("base_vector", INCLUDE=ijk_float), + float_rad("vector_range"), + Pad(4), + SIZE=64, + ) + +animation_desc = Struct("animation", + ascii_str32("name"), + SEnum16("type", *anim_types), + SInt16("frame_count"), + SInt16("frame_size"), + SEnum16("frame_info_type", *anim_frame_info_types), + SInt32("node_list_checksum"), + SInt16("node_count"), + SInt16("loop_frame_index"), + + Float("weight"), + SInt16("key_frame_index"), + SInt16("second_key_frame_index"), + + dyn_senum16("next_animation", + DYN_NAME_PATH="..[DYN_I].name"), + Bool16("flags", + "compressed_data", + "world_relative", + { NAME:"pal", GUI_NAME:"25Hz(PAL)" }, + ), + dyn_senum16("sound", + DYN_NAME_PATH="tagdata.sound_references." + + "sound_references_array[DYN_I].sound.filepath"), + SInt16("sound_frame_index"), + SInt8("left_foot_frame_index"), + SInt8("right_foot_frame_index"), + FlSInt16("first_permutation_index", VISIBLE=False, + TOOLTIP="The index of the first animation in the permutation chain."), + FlFloat("chance_to_play", VISIBLE=False, + MIN=0.0, MAX=1.0, SIDETIP="[0,1]", + TOOLTIP=("Seems to be the chance range to select this permutation.\n" + "Random number in the range [0,1] is rolled. The permutation\n" + "chain is looped until the number is higher than or equal\n" + "to that permutations chance to play. This chance to play\n" + "is likely influenced by the animations 'weight' field.\n" + "All permutation chains should have the last one end with\n" + "a chance to play of 1.0")), + + rawdata_ref("frame_info", max_size=32768), + + # each of the bits in these flags determines whether + # or not the frame data stores info for each nodes + # translation, rotation, and scale. + # This info was discovered by looking at TheGhost's + # animation importer script, so thank him for that. + UInt32("trans_flags0", EDITABLE=False), + UInt32("trans_flags1", EDITABLE=False), + Pad(8), + UInt32("rot_flags0", EDITABLE=False), + UInt32("rot_flags1", EDITABLE=False), + Pad(8), + UInt32("scale_flags0", EDITABLE=False), + UInt32("scale_flags1", EDITABLE=False), + Pad(4), + SInt32("offset_to_compressed_data", EDITABLE=False), + rawdata_ref("default_data", max_size=16384), + rawdata_ref("frame_data", max_size=1048576), + SIZE=180, + ) + +antr_body = Struct("tagdata", + reflexive("objects", object_desc, 4), + reflexive("units", unit_desc, 32, DYN_NAME_PATH=".label"), + reflexive("weapons", weapon_desc, 1), + reflexive("vehicles", vehicle_desc, 1), + reflexive("devices", device_desc, 1), + reflexive("unit_damages", anim_enum_desc, 176, + *unit_damage_animation_names + ), + reflexive("fp_animations", fp_animation_desc, 1), + #i have no idea why they decided to cap it at 257 instead of 256.... + reflexive("sound_references", sound_reference_desc, 257, + DYN_NAME_PATH=".sound.filepath"), + Float("limp_body_node_radius"), + Bool16("flags", + "compress_all_animations", + "force_idle_compression", + ), + Pad(2), + reflexive("nodes", nodes_desc, 64, DYN_NAME_PATH=".name"), + reflexive("animations", animation_desc, 256, DYN_NAME_PATH=".name"), + SIZE=128, + ) + + +def get(): + return antr_def + +antr_def = TagDef("antr", + blam_header('antr', 4), + antr_body, + + ext=".model_animations", endian=">", tag_cls=AntrTag + ) diff --git a/reclaimer/mcc_hek/defs/bipd.py b/reclaimer/mcc_hek/defs/bipd.py new file mode 100644 index 00000000..18d22720 --- /dev/null +++ b/reclaimer/mcc_hek/defs/bipd.py @@ -0,0 +1,167 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +''' +Names for the "physics" struct in the biped tag are courtesy of Sparky. +The source files where the information was taken from are here: + +https://github.com/LiquidLightning/infernal/blob/master/infernal/inf_bipd.h + +EDIT: updates physics struct with more accurate names determined by Kavawuvi +''' +from math import sqrt +from .objs.bipd import BipdTag +from .obje import * +from .unit import * +from supyr_struct.util import desc_variant + +# replace the object_type enum one that uses +# the correct default value for this object +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(0)) + ) + +contact_point = Struct("contact_point", + Pad(32), + ascii_str32('marker_name'), + SIZE=64 + ) + +bipd_attrs = Struct("bipd_attrs", + Float("moving_turning_speed", + SIDETIP="degrees/sec", UNIT_SCALE=180/pi), # radians + Bool32("flags", + "turns_without_aiming", + "uses_player_physics", + "flying", + "physics_pill_centered_at_origin", + "spherical", + "passes_through_other_bipeds", + "can_climb_any_surface", + "immune_to_falling_damage", + "rotate_while_airborne", + "uses_limp_body_physics", + "has_no_dying_airborne", + "random_speed_increase", + "uses_old_player_physics", + ), + float_rad("stationary_turning_threshold"), # radians + + Pad(16), + SEnum16('A_in', *biped_inputs), + SEnum16('B_in', *biped_inputs), + SEnum16('C_in', *biped_inputs), + SEnum16('D_in', *biped_inputs), + dependency('DONT_USE', "jpt!"), + + QStruct("flying", + float_rad("bank_angle"), # radians + float_sec("bank_apply_time", UNIT_SCALE=sec_unit_scale), # seconds + float_sec("bank_decay_time", UNIT_SCALE=sec_unit_scale), # seconds + Float("pitch_ratio"), + float_wu_sec("max_velocity", + UNIT_SCALE=per_sec_unit_scale), # world units/second + float_wu_sec("max_sidestep_velocity", + UNIT_SCALE=per_sec_unit_scale), # world units/second + float_wu_sec_sq("acceleration", + UNIT_SCALE=per_sec_unit_scale), # world units/second^2 + float_wu_sec_sq("deceleration", + UNIT_SCALE=per_sec_unit_scale), # world units/second^2 + float_rad_sec("angular_velocity_maximum"), # radians/second + float_rad_sec_sq("angular_acceleration_maximum"), # radians/second^2 + float_zero_to_one("crouch_velocity_modifier"), + ), + + Pad(8), + Struct("movement", + float_rad("maximum_slope_angle"), # radians + float_rad("downhill_falloff_angle"), # radians + float_rad("downhill_cutoff_angle"), # radians + Float("downhill_velocity_scale"), + float_rad("uphill_falloff_angle"), # radians + float_rad("uphill_cutoff_angle"), # radians + Float("uphill_velocity_scale"), + + Pad(24), + dependency('footsteps', "foot"), + ), + + Pad(24), + QStruct("jumping_and_landing", + float_wu_sec("jump_velocity", UNIT_SCALE=per_sec_unit_scale), + Pad(28), + float_sec("maximum_soft_landing_time", UNIT_SCALE=sec_unit_scale), + float_sec("maximum_hard_landing_time", UNIT_SCALE=sec_unit_scale), + float_wu_sec("minimum_soft_landing_velocity", + UNIT_SCALE=per_sec_unit_scale), # world units/second + float_wu_sec("minimum_hard_landing_velocity", + UNIT_SCALE=per_sec_unit_scale), # world units/second + float_wu_sec("maximum_hard_landing_velocity", + UNIT_SCALE=per_sec_unit_scale), # world units/second + float_wu_sec("death_hard_landing_velocity", + UNIT_SCALE=per_sec_unit_scale), # world units/second + ), + + Pad(20), + QStruct("camera_collision_and_autoaim", + float_wu("standing_camera_height"), + float_wu("crouching_camera_height"), + float_sec("crouch_transition_time", UNIT_SCALE=sec_unit_scale), + + Pad(24), + float_wu("standing_collision_height"), + float_wu("crouching_collision_height"), + float_wu("collision_radius"), + + Pad(40), + float_wu("autoaim_width"), + ), + + Pad(108), + QStruct("physics", + # the default values below that aren't commented out are taken + # from the cyborg.biped tag after saving it with guerilla. + FlFloat("cosine_stationary_turning_threshold"), + FlFloat("crouch_camera_velocity"), + FlFloat("cosine_maximum_slope_angle", + TOOLTIP=("negative is walking on walls.\n > 0.707107 is " + + "floating with contact points off the ground")), + FlFloat("neg_sine_downhill_falloff_angle"), + FlFloat("neg_sine_downhill_cutoff_angle"), + FlFloat("sine_uphill_falloff_angle"), + FlFloat("sine_uphill_cutoff_angle", + TOOLTIP="does the same thing as the fp accel modifier?"), + FlSInt16("root_node_index", DEFAULT=-1), + FlSInt16("head_node_index", DEFAULT=-1), + VISIBLE=False, SIZE=32 + ), + + reflexive("contact_points", contact_point, 2, + DYN_NAME_PATH='.marker_name'), + + SIZE=516 + ) + +bipd_body = Struct("tagdata", + obje_attrs, + unit_attrs, + bipd_attrs, + SIZE=1268, + ) + + +def get(): + return bipd_def + +bipd_def = TagDef("bipd", + blam_header('bipd', 3), + bipd_body, + + ext=".biped", endian=">", tag_cls=BipdTag + ) diff --git a/reclaimer/mcc_hek/defs/bitm.py b/reclaimer/mcc_hek/defs/bitm.py new file mode 100644 index 00000000..7cfec1c6 --- /dev/null +++ b/reclaimer/mcc_hek/defs/bitm.py @@ -0,0 +1,292 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from array import array +from ...common_descs import * +from supyr_struct.defs.tag_def import TagDef +from .objs.bitm import BitmTag + +type_comment = """Type controls bitmap 'geometry'. +All dimensions must be a power-of-two except for SPRITES and INTERFACE: + +*2D TEXTURES: Ordinary, 2D textures will be generated. + +*3D TEXTURES: Volume textures will be generated from each + sequence of 2D texture 'slices'. + +*CUBE MAPS: Cube maps will be generated from each consecutive + set of six 2D textures in each sequence. All faces + of a cubemap must be square and have the same dimensions. + +*SPRITES: Sprite texture pages will be generated. + +*INTERFACE BITMAPS: Similar to 2D TEXTURES, but without mipmaps or + the power-of-two restriction on their dimensions.""" + +format_comment = """Format controls how pixels will be stored internally. + +*COMPRESSED WITH COLOR-KEY TRANSPARENCY: DXT1 compression(4-bits per pixel). + For each 4x4 blocks of pixels, two colors are chosen that best represent + the range of the colors in that block(c0 and c1). The colors are reduced + to 16-bit and each of the 16 pixels is given a blending code(0 to 3). + 0 means the pixels color is c0 and 1 means its color is c1, with + 2 meaning to use 1/3(c1) + 2/3(c2), and 3 meaning 2/3(c1) + 1/3(c2). + + If an alpha exists, it is reduced to 1-bit(black or white). + If a 4x4 block contains transparency, the blending codes are changed so + that 2 means to use 1/2(c1) + 1/2(c2) and 3 means solid black(a=r=g=b=0). + +*COMPRESSED WITH EXPLICIT ALPHA: DXT3 compression(8-bits per pixel). + Same method as DXT1, but without the color-key transparency stuff. + Alpha channel is quantized down to 4 bits per pixel(16 shades of gray). + This format is best used where smooth gradients are not required in the + alpha channel, and consistancy in shades between 4x4 chunks is important. + +*COMPRESSED WITH INTERPOLATED ALPHA: DXT5 compression(8-bits per pixel). + Same method as DXT1, but without the color-key transparency stuff. + Alpha channel uses a method similar to DXT1. For each 4x4 block of + pixels, two 8-bit shades of gray are chosen that best represent the + range of values in that block(v0, v1). Each of the 16 pixels is given a + blending code(0 to 7), with 0 meaning to use v0 and 1 meaning to use v1. + The rest of the codes blend the v0 and v1 shades as shown: + 2 = (v0*6 + v1)/7 3 = (v0*5 + v1*2)/7 + 4 = (v0*4 + v1*3)/7 5 = (v0*3 + v1*4)/7 + 6 = (v0*2 + v1*5)/7 7 = (v0 + v1*6)/7 + If 100% white and 100% black are in the 4x4 block, these are used instead. + 2 = (v0*4 + v1)/5 3 = (v0*3 + v1*2)/5 + 4 = (v0*2 + v1*3)/5 5 = (v0 + v1*4)/5 + 6 = black 7 = white + This allows very smooth gradients in the alpha, but if two neighboring + 4x4 blocks do not use the same v0 and v1 shades, it can be very noticible. + +*16-BIT COLOR: Uses 16 bits per pixel. Depending on the alpha channel + bitmaps are quantized to one of 3 different formats: + r5g6b5(no alpha), a1r5g5b5(1-bit alpha), or a4r4g4b4(>1-bit alpha) + +*32-BIT COLOR: Uses 32 bits per pixel. Very high quality and can have an alpha + channel at no added cost. This format takes up the most memory, however. + Bitmap formats are x8r8g8b8 and a8r8g8b8. + +*MONOCHROME: Uses either 8 or 16 bits per pixel. This is an Xbox-only format. + There are 4 different formats, each using an intensity and alpha channel. + An intensity channel is essentially a monochrome rgb channel: + a8: 8-bits per pixel. Intensity channel is solid black, with the + pixel data being used for the alpha channel. + y8: 8-bits per pixel. Alpha channel is solid white, with the pixel + data being used for the intensity channel. + ay8: 8-bits per pixel. Pixel data is used for both intensity and alpha. + a8y8: 16-bits per pixel. Intensity and alpha channels each use their + own separate pixel data, with 8 bits for each channel. + +NOTE: Normal maps(a.k.a. bump maps) usually use 32-bit color. + This is costly, and if there is no alpha you can usually use 16-bit + r5g6b5 to save space without any noticible drop in quality ingame.""" + +usage_comment = """Usage controls how mipmaps are generated: + +*ALPHA BLEND: Pixels with zero alpha are ignored in mipmaps, to prevent + bleeding the transparent color. + +*DEFAULT: Downsampling works normally, as in Photoshop. + +*HEIGHT MAP: The bitmap is a height map, which will get converted to a bump map. + Uses the 'bump height' below. Alpha is 1-bit. This is an Xbox-only format. + +*DETAIL MAP: Mipmap color fades to gray and alpha fades to white. + Color fading is controlled by the 'detail fade factor' below. + +*LIGHT MAP: Generates no mipmaps. Do not use! + +*VECTOR MAP: Used mostly for special effects; pixels are treated as XYZ vectors + and are normalized after downsampling. Alpha is passed though unmodified.""" + +post_processing_comment = """ +These properties control how mipmaps are processed.""" + +sprite_processing_comment = """ +When creating a sprite group, specify the number and size of the textures +that the group is allowed to occupy. During importing, you will recieve +feedback about how well the alloted space was used.""" + +def get(): return bitm_def + +def pixel_block_size(node, *a, **kwa): + if isinstance(node, array): + return node.itemsize*len(node) + return len(node) + +pixel_root = WhileArray('pixel_root', + SUB_STRUCT=WhileArray('bitmap_pixels', + SUB_STRUCT=UInt8Array('pixels', SIZE=pixel_block_size) + ) + ) + +sprite = QStruct("sprite", + SInt16("bitmap_index"), + Pad(6), + Float("left_side"), + Float("right_side"), + Float("top_side"), + Float("bottom_side"), + Float("registration_point_x"), + Float("registration_point_y"), + SIZE=32, + ) + +sequence = Struct("sequence", + ascii_str32("sequence_name"), + SInt16("first_bitmap_index"), + SInt16("bitmap_count"), + Pad(16), + reflexive("sprites", sprite, 64), + SIZE=64, + ) + +bitmap = Struct("bitmap", + UEnum32('bitm_id', ('bitm', 'bitm'), DEFAULT='bitm', EDITABLE=False), + UInt16("width", SIDETIP="pixels", EDITABLE=False), + UInt16("height", SIDETIP="pixels", EDITABLE=False), + UInt16("depth", SIDETIP="pixels", EDITABLE=False), + SEnum16("type", + "texture_2d", + "texture_3d", + "cubemap", + "white", + EDITABLE=False + ), + SEnum16("format", + "a8", + "y8", + "ay8", + "a8y8", + #"-unused1-", + #"-unused2-", + ("r5g6b5", 6), + #"-unused3-", + ("a1r5g5b5", 8), + ("a4r4g4b4", 9), + ("x8r8g8b8", 10), + ("a8r8g8b8", 11), + #"-unused4-", + #"-unused5-", + ("dxt1", 14), + ("dxt3", 15), + ("dxt5", 16), + ("p8_bump", 17), + ), + Bool16("flags", + "power_of_2_dim", + "compressed", + "palletized", + "swizzled", + "linear", + "v16u16", + "unknown", + "prefer_low_detail", + "data_in_resource_map", + ), + UInt16("registration_point_x"), + UInt16("registration_point_y"), + UInt16("mipmaps"), + FlUInt16("pixels", VISIBLE=False, EDITABLE=False), + + # this is the non-magic pointer into the map that the pixel data + # is located at. if flags.data_in_resource_map is True and the + # map is halo ce/pc/trial, the offset is into the bitmaps.map + UInt32("pixels_offset", VISIBLE=False, EDITABLE=False), + UInt32("pixels_meta_size", VISIBLE=False, EDITABLE=False), + UInt32("bitmap_id_unknown1", VISIBLE=False, EDITABLE=False), + UInt32("bitmap_data_pointer", VISIBLE=False, EDITABLE=False), + UInt32("bitmap_id_unknown2", VISIBLE=False, EDITABLE=False), + UInt32("base_address", VISIBLE=False, EDITABLE=False), + SIZE=48, + ) + +bitm_body = Struct("tagdata", + SEnum16("type", + "textures_2d", + "textures_3d", + "cubemaps", + "sprites", + "interface_bitmaps", + COMMENT=type_comment + ), + SEnum16("format", + "color_key_transparency", + "explicit_alpha", + "interpolated_alpha", + "color_16bit", + "color_32bit", + "monochrome", + COMMENT=format_comment + ), + SEnum16("usage", + "alpha-blend", + "default", + "height_map", + "detail_map", + "light_map", + "vector_map", + COMMENT=usage_comment, DEFAULT=1 + ), + Bool16("flags", + "enable_diffusion_dithering", + "disable_height_map_compression", + "uniform_sprite_sequences", + "sprite_bug_fix", + ), + QStruct("post_processing", + float_zero_to_one("detail_fade_factor"), + float_zero_to_one("sharpen_amount"), + Float("bump_height", SIDETIP="repeats"), + COMMENT=post_processing_comment + ), + Struct("sprite_processing", + SEnum16("sprite_budget_size", + {NAME: "x32", VALUE: 0, GUI_NAME: "32x32"}, + {NAME: "x64", VALUE: 1, GUI_NAME: "64x64"}, + {NAME: "x128", VALUE: 2, GUI_NAME: "128x128"}, + {NAME: "x256", VALUE: 3, GUI_NAME: "256x256"}, + {NAME: "x512", VALUE: 4, GUI_NAME: "512x512"}, + ), + UInt16("sprite_budget_count"), + COMMENT=sprite_processing_comment + ), + UInt16("color_plate_width", SIDETIP="pixels", EDITABLE=False), + UInt16("color_plate_height", SIDETIP="pixels", EDITABLE=False), + + rawdata_ref("compressed_color_plate_data", max_size=16777216), + rawdata_ref("processed_pixel_data", max_size=16777216), + + Float("blur_filter_size", MIN=0.0, MAX=10.0, SIDETIP="[0,10] pixels"), + float_neg_one_to_one("alpha_bias"), + UInt16("mipmap_levels", MIN=0, SIDETIP="levels"), + SEnum16("sprite_usage", + "blend/add/subtract/max", + "multiply/min", + "double_multiply", + ), + UInt16("sprite_spacing", SIDETIP="pixels"), + Pad(2), + reflexive("sequences", sequence, 256, + DYN_NAME_PATH='.sequence_name', IGNORE_SAFE_MODE=True), + reflexive("bitmaps", bitmap, 2048, IGNORE_SAFE_MODE=True), + SIZE=108, WIDGET=HaloBitmapTagFrame + ) + +def get(): + return bitm_def + +bitm_def = TagDef("bitm", + blam_header('bitm', 7), + bitm_body, + + ext=".bitmap", endian=">", tag_cls=BitmTag, + subdefs = {'pixel_root':pixel_root} + ) diff --git a/reclaimer/mcc_hek/defs/boom.py b/reclaimer/mcc_hek/defs/boom.py new file mode 100644 index 00000000..6e1ddaf4 --- /dev/null +++ b/reclaimer/mcc_hek/defs/boom.py @@ -0,0 +1,26 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +def get(): + return boom_def + +boom_def = TagDef("boom", + blam_header('boom'), + QStruct('tagdata', + #this is just a guess. This could just as easily + #be 4 bytes of padding. effing useless tag type + Float('radius') + ), + + ext=".spheroid", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/cdmg.py b/reclaimer/mcc_hek/defs/cdmg.py new file mode 100644 index 00000000..dd6f27b3 --- /dev/null +++ b/reclaimer/mcc_hek/defs/cdmg.py @@ -0,0 +1,88 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +cdmg_body = Struct("tagdata", + from_to_wu("radius"), + float_zero_to_one("cutoff_scale"), + Pad(24), + + QStruct("vibrate_parameters", + float_zero_to_one("low_frequency"), + float_zero_to_one("high_frequency"), + Pad(24), + ), + + Struct("camera_shaking", + float_wu("random_translation"), + float_rad("random_rotation"), # radians + Pad(12), + + SEnum16("wobble_function", *animation_functions), + Pad(2), + float_sec("wobble_function_period"), + Float("wobble_weight"), + Pad(192), + ), + + Struct("damage", + SEnum16("priority", + "none", + "harmless", + {NAME: "backstab", GUI_NAME: "lethal to the unsuspecting"}, + "emp", + ), + SEnum16("category", *damage_category), + Bool32("flags", + "does_not_hurt_owner", + {NAME: "headshot", GUI_NAME: "can cause headshots"}, + "pings_resistant_units", + "does_not_hurt_friends", + "does_not_ping_shields", + "detonates_explosives", + "only_hurts_shields", + "causes_flaming_death", + {NAME: "indicator_points_down", + GUI_NAME: "damage indicator always points down"}, + "skips_shields", + "only_hurts_one_infection_form", + {NAME: "multiplayer_headshot", + GUI_NAME: "can cause multiplayer headshots"}, + "infection_form_pop", + ), + Pad(4), + Float("damage_lower_bound"), + QStruct("damage_upper_bound", INCLUDE=from_to), + float_zero_to_one("vehicle_passthrough_penalty"), + Pad(4), + float_zero_to_one("stun"), + float_zero_to_one("maximum_stun"), + float_sec("stun_time"), + Pad(4), + float_zero_to_inf("instantaneous_acceleration"), + Pad(8), + ), + + damage_modifiers, + SIZE=512, + ) + + +def get(): + return cdmg_def + +cdmg_def = TagDef("cdmg", + blam_header('cdmg'), + cdmg_body, + + ext=".continuous_damage_effect", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/coll.py b/reclaimer/mcc_hek/defs/coll.py new file mode 100644 index 00000000..8728ae38 --- /dev/null +++ b/reclaimer/mcc_hek/defs/coll.py @@ -0,0 +1,310 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.coll import CollTag +from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant + +bsp_node_tooltip = ( + "Refers to a leaf node if negative.\n" + "Add 0x80000000 to get leaf node index." + ) + +modifier = Struct("modifier", + Pad(52), + ) + +body = Struct("body", + Float("maximum_body_vitality"), + Float("body_system_shock"), + + Pad(52), + float_zero_to_one("friendly_damage_resistance"), + + Pad(40), + dependency("localized_damage_effect", "effe"), + + float_zero_to_one("area_damage_effect_threshold"), + dependency("area_damage_effect", "effe"), + + Float("body_damaged_threshold"), + dependency("body_damaged_effect", "effe"), + dependency("body_depleted_effect", "effe"), + Float("body_destroyed_threshold"), + dependency("body_destroyed_effect", "effe"), + ) + +shield = Struct("shield", + Float("maximum_shield_vitality"), + + Pad(2), + SEnum16("shield_material_type", *materials_list), + + Pad(24), + SEnum16("shield_failure_function", *fade_functions), + + Pad(2), + Float("shield_failure_threshold"), + Float("shield_failing_leak_fraction"), + + Pad(16), + Float("minimum_stun_damage"), + float_sec("stun_time"), + float_sec("recharge_time"), + + Pad(112), + Float("shield_damaged_threshold"), + dependency("shield_damaged_effect", "effe"), + dependency("shield_depleted_effect", "effe"), + dependency("shield_recharging_effect", "effe"), + Pad(8), + Float("shield_recharge_rate", VISIBLE=False), + ) + +bsp3d_node = QStruct("bsp3d_node", + SInt32("plane"), + SInt32("back_child", TOOLTIP=bsp_node_tooltip), + SInt32("front_child", TOOLTIP=bsp_node_tooltip), + SIZE=12 + ) + +plane = QStruct("plane", + # i, j, and k form a unit vector where d specifies + # the location of a point a "distance" along it + Float("i"), Float("j"), Float("k"), Float("d"), + SIZE=16, ORIENT='h' + ) + +leaf = Struct("leaf", + Bool16("flags", + "contains_double_sided_surfaces" + ), + SInt16("bsp2d_reference_count"), + SInt32("first_bsp2d_reference"), + SIZE=8 + ) + +bsp2d_reference = QStruct("bsp2d_reference", + SInt32("plane"), + SInt32("bsp2d_node", TOOLTIP=bsp_node_tooltip), + SIZE=8 + ) + +bsp2d_node = QStruct("bsp2d_node", + Float("plane_i"), + Float("plane_j"), + Float("plane_d"), + SInt32("left_child", TOOLTIP=bsp_node_tooltip), + SInt32("right_child", TOOLTIP=bsp_node_tooltip), + SIZE=20 + ) + +surface = Struct("surface", + SInt32("plane"), + SInt32("first_edge"), + Bool8("flags", + "two_sided", + "invisible", + "climbable", + "breakable", + ), + SInt8("breakable_surface"), + SInt16("material"), + SIZE=12 + ) + +edge = QStruct("edge", + SInt32("start_vertex"), + SInt32("end_vertex"), + SInt32("forward_edge"), + SInt32("reverse_edge"), + SInt32("left_surface"), + SInt32("right_surface"), + SIZE=24 + ) + +vertex = QStruct("vertex", + Float("x"), + Float("y"), + Float("z"), + SInt32("first_edge"), + SIZE=16 + ) + +permutation_bsp = Struct("permutation_bsp", + reflexive("bsp3d_nodes", bsp3d_node, 131072), + reflexive("planes", plane, 65535), + reflexive("leaves", leaf, 65535), + reflexive("bsp2d_references", bsp2d_reference, 131072), + reflexive("bsp2d_nodes", bsp2d_node, 65535), + reflexive("surfaces", surface, 131072), + reflexive("edges", edge, 262144), + reflexive("vertices", vertex, 131072), + SIZE=96 + ) + +node = Struct("node", + ascii_str32("name"), + dyn_senum16("region", + DYN_NAME_PATH=".....regions.regions_array[DYN_I].name"), + dyn_senum16("parent_node", + DYN_NAME_PATH="..[DYN_I].name"), + dyn_senum16("next_sibling_node", + DYN_NAME_PATH="..[DYN_I].name"), + dyn_senum16("first_child_node", + DYN_NAME_PATH="..[DYN_I].name"), + + Pad(8), + FlSInt16("unknown0", VISIBLE=False), + FlSInt16("unknown1", VISIBLE=False), + reflexive("bsps", permutation_bsp, 32), + SIZE=64 + ) + +pathfinding_sphere = Struct("pathfinding_sphere", + dyn_senum16("node", + DYN_NAME_PATH=".....nodes.nodes_array[DYN_I].name"), + + Pad(14), + QStruct("center", INCLUDE=xyz_float), + Float("radius"), + SIZE=32 + ) + +permutation = Struct("permutation", + ascii_str32("name"), + SIZE=32 + ) + +region = Struct("region", + ascii_str32("name"), + Bool32("flags", + "lives_until_object_dies", + "forces_object_to_die", + "dies_when_object_dies", + "dies_when_object_is_damaged", + "disappears_when_shield_is_off", + "inhibits_melee_attack", + "inhibits_weapon_attack", + "inhibits_walking", + "forces_drop_weapon", + "causes_head_maimed_scream", + ), + Pad(4), + Float("damage_threshold"), + + Pad(12), + dependency("destroyed_effect", "effe"), + reflexive("permutations", permutation, 32, DYN_NAME_PATH='.name'), + SIZE=84 + ) + +material = Struct("material", + ascii_str32("name"), + Bool32("flags", + "head" + ), + SEnum16("material_type", *materials_list), + Pad(2), + Float("shield_leak_percentage"), + Float("shield_damage_multiplier"), + + Pad(12), + Float("body_damage_multiplier"), + SIZE=72 + ) + +coll_body = Struct("tagdata", + Bool32("flags", + "takes_shield_damage_for_children", + "takes_body_damage_for_children", + "always_shields_friendly_damage", + "passes_area_damage_to_children", + "parent_never_takes_body_damage_for_us", + "only_damaged_by_explosives", + "only_damaged_while_occupied", + ), + dyn_senum16("indirect_damage_material", + DYN_NAME_PATH=".materials.materials_array[DYN_I].name"), + Pad(2), + + body, + shield, + + Pad(112), + reflexive("materials", material, 32, DYN_NAME_PATH='.name'), + reflexive("regions", region, 8, DYN_NAME_PATH='.name'), + reflexive("modifiers", modifier, 0, VISIBLE=False), + + Pad(16), + Struct("pathfinding_box", + QStruct("x", INCLUDE=from_to), + QStruct("y", INCLUDE=from_to), + QStruct("z", INCLUDE=from_to), + ), + + reflexive("pathfinding_spheres", pathfinding_sphere, 32), + reflexive("nodes", node, 64, DYN_NAME_PATH='.name'), + + SIZE=664, + ) + + +fast_permutation_bsp = Struct("permutation_bsp", + raw_reflexive("bsp3d_nodes", bsp3d_node, 131072), + raw_reflexive("planes", plane, 65535), + raw_reflexive("leaves", leaf, 65535), + raw_reflexive("bsp2d_references", bsp2d_reference, 131072), + raw_reflexive("bsp2d_nodes", bsp2d_node, 65535), + raw_reflexive("surfaces", surface, 131072), + raw_reflexive("edges", edge, 262144), + raw_reflexive("vertices", vertex, 131072), + SIZE=96 + ) + +fast_node = Struct("node", + ascii_str32("name"), + dyn_senum16("region", + DYN_NAME_PATH=".....regions.regions_array[DYN_I].name"), + dyn_senum16("parent_node", + DYN_NAME_PATH="..[DYN_I].name"), + dyn_senum16("next_sibling_node", + DYN_NAME_PATH="..[DYN_I].name"), + dyn_senum16("first_child_node", + DYN_NAME_PATH="..[DYN_I].name"), + + Pad(8), + FlSInt16("unknown0", VISIBLE=False), + FlSInt16("unknown1", VISIBLE=False), + reflexive("bsps", fast_permutation_bsp, 32), + SIZE=64 + ) + +fast_coll_body = desc_variant(coll_body, + ("nodes", reflexive("nodes", fast_node, 64, DYN_NAME_PATH='.name')), + ) + + +def get(): + return coll_def + +coll_def = TagDef("coll", + blam_header("coll", 10), + coll_body, + + ext=".model_collision_geometry", endian=">", tag_cls=CollTag, + ) + +fast_coll_def = TagDef("coll", + blam_header("coll", 10), + fast_coll_body, + + ext=".model_collision_geometry", endian=">", tag_cls=CollTag, + ) diff --git a/reclaimer/mcc_hek/defs/colo.py b/reclaimer/mcc_hek/defs/colo.py new file mode 100644 index 00000000..e5bd6892 --- /dev/null +++ b/reclaimer/mcc_hek/defs/colo.py @@ -0,0 +1,34 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + + +color = Struct("color", + ascii_str32('name'), + QStruct("color", INCLUDE=argb_float), + SIZE=48, + ) + +colo_body = Struct("tagdata", + reflexive("colors", color, 512, DYN_NAME_PATH='.name'), + SIZE=12 + ) + +def get(): + return colo_def + +colo_def = TagDef("colo", + blam_header('colo'), + colo_body, + + ext=".color_table", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/cont.py b/reclaimer/mcc_hek/defs/cont.py new file mode 100644 index 00000000..df29fa6e --- /dev/null +++ b/reclaimer/mcc_hek/defs/cont.py @@ -0,0 +1,121 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +def get(): return cont_def + +point_state = Struct("point_state", + from_to_sec("state_duration"), + from_to_sec("state_transition_duration"), + dependency("physics", "pphy"), + Pad(32), + float_wu("width"), + QStruct("color_lower_bound", INCLUDE=argb_float), + QStruct("color_upper_bound", INCLUDE=argb_float), + Bool32("scale_flags", + "duration", + "duration_delta", + "transition_duration", + "transition_duration_delta", + "color", + ), + SIZE=104 + ) + +cont_body = Struct("tagdata", + Bool16("flags", + "first_point_unfaded", + "last_point_unfaded", + "points_start_pinned_to_media", + "points_start_pinned_to_ground", + "points_always_pinned_to_media", + "points_always_pinned_to_ground", + "edge_effect_fades_slowly", + ), + Bool16("scale_flags", + "point_generation_rate", + "point_velocity", + "point_velocity_delta", + "point_velocity_cone_angle", + "inherited_velocity_fraction", + "sequence_animation_rate", + "texture_scale_u", + "texture_scale_v", + "texture_animation_u", + "texture_animation_v", + ), + + Struct("point_creation", + Float("generation_rate", + SIDETIP="points/sec", UNIT_SCALE=per_sec_unit_scale), + from_to_wu_sec("velocity"), + float_rad("velocity_cone_angle"), + Float("inherited_velocity_fraction"), + ), + + Struct("rendering", + SEnum16("render_type", + "vertical_orientation", + "horizontal_orientation", + "media_mapped", + "ground_mapped", + "viewer_facing", + "double_marker_linked", + ), + Pad(2), + Float("texture_repeats_u"), + Float("texture_repeats_v"), + + Float("texture_animation_u", SIDETIP="repeats/sec"), + Float("texture_animation_v", SIDETIP="repeats/sec"), + Float("animation_rate", SIDETIP="frames/sec"), + dependency("bitmap", "bitm"), + SInt16("first_sequence_index"), + SInt16("sequence_count"), + Pad(100), + + FlUInt32("unknown0", VISIBLE=False), + Bool16("shader_flags", *shader_flags), + SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), + SEnum16("framebuffer_fade_mode", *render_fade_mode), + Bool16("map_flags", + "unfiltered", + ), + ), + Pad(12), # OS v4 shader extension padding + Pad(16), + + Struct("secondary_map", + dependency("bitmap", "bitm"), + SEnum16("anchor", *render_anchor), + Bool16("map_flags", + "unfiltered", + ), + + Struct("u_animation", INCLUDE=anim_src_func_per_pha_sca), + Struct("v_animation", INCLUDE=anim_src_func_per_pha_sca), + Struct("rotation_animation", INCLUDE=anim_src_func_per_pha_sca_rot), + QStruct("rotation_center", INCLUDE=xy_float), + Pad(4), + Float("zsprite_radius_scale"), + Pad(20), + ), + reflexive("point_states", point_state, 16), + SIZE=324, + ) + +cont_def = TagDef("cont", + blam_header('cont', 3), + cont_body, + + ext=".contrail", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/ctrl.py b/reclaimer/mcc_hek/defs/ctrl.py new file mode 100644 index 00000000..6ce72336 --- /dev/null +++ b/reclaimer/mcc_hek/defs/ctrl.py @@ -0,0 +1,58 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .obje import * +from .devi import * +from .objs.ctrl import CtrlTag +from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant + +# replace the object_type enum one that uses +# the correct default value for this object +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(8)) + ) + +ctrl_attrs = Struct("ctrl_attrs", + SEnum16('type', + 'toggle_switch', + 'on_button', + 'off_button', + 'call_button' + ), + SEnum16('triggers_when', + 'touched_by_player', + 'destroyed' + ), + float_zero_to_one('call_value'), + + Pad(80), + dependency("on", valid_event_effects), + dependency("off", valid_event_effects), + dependency("deny", valid_event_effects), + ) + +ctrl_body = Struct("tagdata", + obje_attrs, + devi_attrs, + ctrl_attrs, + + SIZE=792, + ) + + +def get(): + return ctrl_def + +ctrl_def = TagDef("ctrl", + blam_header('ctrl'), + ctrl_body, + + ext=".device_control", endian=">", tag_cls=CtrlTag + ) diff --git a/reclaimer/mcc_hek/defs/deca.py b/reclaimer/mcc_hek/defs/deca.py new file mode 100644 index 00000000..c8a779cd --- /dev/null +++ b/reclaimer/mcc_hek/defs/deca.py @@ -0,0 +1,109 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +decal_comment = """COMPOUND DECALS: +A 'compound decal' is a chain of decals which are instantiated simultaneously. +Compound decals are created by choosing a below. +NOTE: Do not attempt to create a circularly linked decal chain, i.e. A->B->C->A! +Also, do not reference a decal from an effect if it is not the 'head' of the chain; +for example an effect should not instantiate decal B if the chain was A->B->C. +Compound decals can have seperate bitmaps, seperate framebuffer blend functions, +and can be drawn in seperate layers. In addition, each decal in the chain can either +inherit its parent's , rotation, , , and - +or it can randomly choose its own. This behavior is controlled by the +'geometry_inherited_by_next_decal_in_chain' flag, below. + +DECAL TYPING AND LAYERING: +The decal (or layer) determines the drawing order of the decal with respect +to the rest of the environment. Decals in the primary layer are drawn after the +environment diffuse texture, hence they affect the already-lit texture of the surface. +Decals in the secondary layer are drawn immediately after decals in the primary layer, +so they 'cover up' the primary decals. Decals in the 'light' layer are drawn before +the environment diffuse texture, hence they affect the accumulated diffuse light and +only indirectly affect the lit texture.""" + +deca_body = Struct("tagdata", + #Decal Properties + Bool16("flags", + "geometry_inherited_by_next_decal_in_chain", + "interpolate_color_in_hsv", + "more_colors", + "no_random_rotation", + "water_effect", + "SAPIEN_snap_to_axis", + "SAPIEN_incremental_counter", + "animation_loop", + "preserve_aspect", + COMMENT=decal_comment + ), + SEnum16("type", + "scratch", + "splatter", + "burn", + "painted_sign", + ), + SEnum16("layer", + "primary", + "secondary", + "light", + "alpha_tested", + "water" + ), + Pad(2), + dependency("next_decal_in_chain", "deca"), + from_to_wu("radius"), # world units + Pad(12), + + Struct("color", + from_to_zero_to_one("intensity"), # [0,1] + Struct("lower_bounds", INCLUDE=rgb_float), + Struct("upper_bounds", INCLUDE=rgb_float), + Pad(12), + ), + + #Animation + Struct("animation", + SInt16("loop_frame"), + SInt16("speed", MIN=1, MAX=120, + SIDETIP="[1,120] ticks/frame", UNIT_SCALE=per_sec_unit_scale), + Pad(28), + from_to_sec("lifetime"), # seconds + from_to_sec("decay_time"), # seconds + Pad(56), + ), + + #Shader + Struct("shader", + SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), + Pad(22), + dependency("shader_map", "bitm"), + ), + + #Sprite info + Pad(20), + Float("maximum_sprite_extent", SIDETIP="pixels"), + + SIZE=268, + ) + + + +def get(): + return deca_def + +deca_def = TagDef("deca", + blam_header('deca'), + deca_body, + + ext=".decal", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/devc.py b/reclaimer/mcc_hek/defs/devc.py new file mode 100644 index 00000000..8b78faf1 --- /dev/null +++ b/reclaimer/mcc_hek/defs/devc.py @@ -0,0 +1,36 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +devc_body = Struct("tagdata", + SEnum16("device_type", + "mouse_and_keyboard", + "joysticks_joypads_etc", + "full_profile_definition", + ), + Bool16("flags", + "unused", + ), + rawdata_ref("device_id", max_size=16), + rawdata_ref("profile", max_size=41984), + SIZE=44, + ) + +def get(): + return devc_def + +devc_def = TagDef("devc", + blam_header('devc'), + devc_body, + + ext=".input_device_defaults", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/devi.py b/reclaimer/mcc_hek/defs/devi.py new file mode 100644 index 00000000..81478bcd --- /dev/null +++ b/reclaimer/mcc_hek/defs/devi.py @@ -0,0 +1,69 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.devi import DeviTag +from supyr_struct.defs.tag_def import TagDef + +devi_attrs = Struct("devi_attrs", + Bool32("flags", + "position_loops", + "position_not_interpolated", + ), + + float_sec("power_transition_time"), + float_sec("power_acceleration_time"), + float_sec("position_transition_time"), + float_sec("position_acceleration_time"), + float_sec("depowered_position_transition_time"), + float_sec("depowered_position_acceleration_time"), + + SEnum16("A_in", *device_functions), + SEnum16("B_in", *device_functions), + SEnum16("C_in", *device_functions), + SEnum16("D_in", *device_functions), + + dependency("open", valid_event_effects), + dependency("close", valid_event_effects), + dependency("opened", valid_event_effects), + dependency("closed", valid_event_effects), + dependency("depowered", valid_event_effects), + dependency("repowered", valid_event_effects), + + float_sec("delay_time"), + Pad(8), + dependency("delay_effect", valid_event_effects), + float_wu("automatic_activation_radius"), + + Pad(84), + FlFloat("inv_power_acceleration_time"), + FlFloat("inv_power_transition_time"), + FlFloat("inv_depowered_acceleration_time"), + FlFloat("inv_depowered_transition_time"), + FlFloat("inv_position_acceleration_time"), + FlFloat("inv_position_transition_time"), + Pad(4), + + SIZE=276, + ) + +devi_body = Struct('tagdata', + devi_attrs, + SIZE=276 + ) + +def get(): + return devi_def + +devi_def = TagDef("devi", + blam_header('devi'), + devi_body, + + ext=".device", endian=">", tag_cls=DeviTag + ) diff --git a/reclaimer/mcc_hek/defs/dobc.py b/reclaimer/mcc_hek/defs/dobc.py new file mode 100644 index 00000000..6086a765 --- /dev/null +++ b/reclaimer/mcc_hek/defs/dobc.py @@ -0,0 +1,59 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +def get(): return dobc_def + +detail_object_type = Struct("detail_object_type", + ascii_str32("name"), + SInt8("sequence_index", SIDETIP="[0,15]"), + Bool8("scale_flags", + ("interpolate_color_in_hsv", 4), + ("more_colors", 8), + ), + #UInt8("unknown0", VISIBLE=False), + Pad(1), + UInt8("sequence_sprite_count", VISIBLE=False), + float_zero_to_one("color_override_factor"), + Pad(8), + float_wu("near_fade_distance"), + float_wu("far_fade_distance"), + Float("size", SIDETIP="world units/pixel"), + Pad(4), + QStruct("minimum_color", INCLUDE=rgb_float), + QStruct("maximum_color", INCLUDE=rgb_float), + #QStruct("ambient_color", INCLUDE=argb_byte), + UInt32("ambient_color", INCLUDE=argb_uint32), + SIZE=96 + ) + +dobc_body = Struct("tagdata", + SEnum16("anchor", + "screen-facing", + "viewer-facing", + ), + Pad(2), + Float("global_z_offset", + SIDETIP="applied to all these detail object so they dont float"), + Pad(44), + dependency("sprite_plate", "bitm"), + reflexive("detail_object_types", detail_object_type, 16, + DYN_NAME_PATH='.name'), + SIZE=128, + ) + +dobc_def = TagDef("dobc", + blam_header('dobc'), + dobc_body, + + ext=".detail_object_collection", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/effe.py b/reclaimer/mcc_hek/defs/effe.py new file mode 100644 index 00000000..32340e93 --- /dev/null +++ b/reclaimer/mcc_hek/defs/effe.py @@ -0,0 +1,181 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.effe import EffeTag +from supyr_struct.defs.tag_def import TagDef + +part_scale_modifiers = ( + "velocity", + "velocity_delta", + "velocity_cone_angle", + "angular_velocity", + "angular_velocity_delta", + "type_specific_scale" + ) + +particle_scale_modifiers = ( + "velocity", + "velocity_delta", + "velocity_cone_angle", + "angular_velocity", + "angular_velocity_delta", + "count", + "count_delta", + "distribution_radius", + "distribution_radius_delta", + "particle_radius", + "particle_radius_delta", + "tint" + ) + +create_in_env = SEnum16("create_in_env", + "any_environment", + "air_only", + "water_only", + "space_only", + ) + +create_in_mode = SEnum16("create_in_mode", + "either_mode", + "violent_mode_only", + "nonviolent_mode_only", + ) + +part = Struct("part", + create_in_env, + create_in_mode, + dyn_senum16("location", + DYN_NAME_PATH="........locations.locations_array[DYN_I].marker_name"), + Bool16("flags", + {NAME:"face_down", GUI_NAME:"face down regardless of location(decals)"} + ), + Pad(12), + + UEnum32("effect_class", INCLUDE=valid_tags_os, VISIBLE=False), + dependency("type", valid_effect_events), + Pad(24), + + from_to_wu_sec("velocity_bounds"), # world units/sec + float_rad("velocity_cone_angle"), # radians + from_to_rad_sec("angular_velocity_bounds"), # radians/sec + QStruct("radius_modifier_bounds"), + + Bool32("A_scales_values", *part_scale_modifiers), + Bool32("B_scales_values", *part_scale_modifiers), + SIZE=104, + ) + +particle = Struct("particle", + create_in_env, + create_in_mode, + SEnum16("create_in_camera", + "either", + "first_person_only", + "third_person_only", + "first_person_if_possible", + ), + FlSInt16("unknown0", VISIBLE=False), + dyn_senum16("location", + DYN_NAME_PATH="........locations.locations_array[DYN_I].marker_name"), + FlSInt16("unknown1", VISIBLE=False), + + yp_float_rad("relative_direction"), # radians + QStruct("relative_offset", INCLUDE=ijk_float), + QStruct("relative_direction_vector", INCLUDE=xyz_float, VISIBLE=False), + Pad(40), + + dependency("particle_type", "part"), + Bool32("flags", + "stay_attached_to_marker", + "random_initial_angle", + "tint_from_object_color", + {NAME: "tint_as_hsv", GUI_NAME: "interpolate tint as hsv"}, + {NAME: "use_long_hue_path", GUI_NAME: "...across the long hue path"}, + ), + SEnum16("distribution_function", + "start", + "end", + "constant", + "buildup", + "falloff", + "buildup_and_falloff", + ), + Pad(2), + + QStruct("created_count", + SInt16("from", GUI_NAME=""), + SInt16("to"), ORIENT='h' + ), + from_to_wu("distribution_radius"), + Pad(12), + + from_to_wu_sec("velocity"), + float_rad("velocity_cone_angle"), # radians + from_to_rad_sec("angular_velocity"), # radians + Pad(8), + + from_to_wu("radius"), + Pad(8), + + QStruct("tint_lower_bound", INCLUDE=argb_float), + QStruct("tint_upper_bound", INCLUDE=argb_float), + Pad(16), + + Bool32("A_scales_values", *particle_scale_modifiers), + Bool32("B_scales_values", *particle_scale_modifiers), + SIZE=232 + ) + + +location = Struct("location", + ascii_str32("marker_name"), + ) + +event = Struct("event", + Pad(4), + Float("skip_fraction"), + from_to_sec("delay_bounds"), + from_to_sec("duration_bounds"), + + Pad(20), + reflexive("parts", part, 32, DYN_NAME_PATH='.type.filepath'), + reflexive("particles", particle, 32, DYN_NAME_PATH='.particle_type.filepath'), + SIZE=68 + ) + + +effe_body = Struct("tagdata", + Bool32("flags", + {NAME: "deleted_when_inactive", GUI_NAME: "deleted when attachment deactivates"}, + {NAME: "required", GUI_NAME: "required for gameplay (cannot optimize out)"}, + {NAME: "never_cull", VISIBLE: VISIBILITY_HIDDEN} + ), + dyn_senum16("loop_start_event", + DYN_NAME_PATH=".events.events_array[DYN_I].NAME"), + dyn_senum16("loop_stop_event", + DYN_NAME_PATH=".events.events_array[DYN_I].NAME"), + + Pad(32), + reflexive("locations", location, 32, DYN_NAME_PATH='.marker_name'), + reflexive("events", event, 32), + + SIZE=64, + ) + + +def get(): + return effe_def + +effe_def = TagDef("effe", + blam_header("effe", 4), + effe_body, + + ext=".effect", endian=">", tag_cls=EffeTag, + ) diff --git a/reclaimer/mcc_hek/defs/elec.py b/reclaimer/mcc_hek/defs/elec.py new file mode 100644 index 00000000..9ab0695c --- /dev/null +++ b/reclaimer/mcc_hek/defs/elec.py @@ -0,0 +1,77 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +shader = Struct("shader", + Pad(36), + FlUInt32("unknown0"), + Bool16("shader_flags", *shader_flags), + SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), + SEnum16("framebuffer_fade_mode", *render_fade_mode), + Bool16("map_flags", + "unfiltered" + ), + Pad(40), + FlUInt32("unknown1"), + Pad(88), + SIZE=180 + ) + +marker = Struct("marker", + ascii_str32("attachment_marker"), + Bool16("flags", + "not_connected_to_next_marker" + ), + + Pad(2), + SInt16("octaves_to_next_marker"), + + Pad(78), + QStruct("random_position_bounds", INCLUDE=ijk_float, SIDETIP="world units"), + float_wu("random_jitter"), + float_wu("thickness"), + QStruct("tint", INCLUDE=argb_float), + SIZE=228 + ) + +elec_body = Struct("tagdata", + Pad(2), + SInt16("effects_count"), + + Pad(16), + float_wu("near_fade_distance"), + float_wu("far_fade_distance"), + + Pad(16), + SEnum16("jitter_scale_source", *function_outputs), + SEnum16("thickness_scale_source", *function_outputs), + SEnum16("tint_modulation_source", *function_names), + SEnum16("brightness_scale_source", *function_outputs), + dependency("bitmap", "bitm"), + + Pad(84), + reflexive("markers", marker, 16, DYN_NAME_PATH='.attachment_marker'), + reflexive("shaders", shader, 1), + + SIZE=264, + ) + + +def get(): + return elec_def + +elec_def = TagDef("elec", + blam_header("elec"), + elec_body, + + ext=".lightning", endian=">", tag_cls=HekTag, + ) diff --git a/reclaimer/mcc_hek/defs/eqip.py b/reclaimer/mcc_hek/defs/eqip.py new file mode 100644 index 00000000..cd8b091b --- /dev/null +++ b/reclaimer/mcc_hek/defs/eqip.py @@ -0,0 +1,56 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .obje import * +from .item import * +from .objs.obje import ObjeTag +from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant + +# replace the object_type enum one that uses +# the correct default value for this object +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(3)) + ) + +eqip_attrs = Struct("eqip_attrs", + SEnum16('powerup_type', + 'none', + 'double_speed', + 'overshield', + 'active_camo', + 'full_spectrum_vision', + 'health', + 'grenade', + ), + SEnum16('grenade_type', *grenade_types), + float_sec('powerup_time'), + dependency('pickup_sound', "snd!"), + + SIZE=168 + ) + +eqip_body = Struct("tagdata", + obje_attrs, + item_attrs, + eqip_attrs, + + SIZE=944, + ) + + +def get(): + return eqip_def + +eqip_def = TagDef("eqip", + blam_header('eqip', 2), + eqip_body, + + ext=".equipment", endian=">", tag_cls=ObjeTag + ) diff --git a/reclaimer/mcc_hek/defs/flag.py b/reclaimer/mcc_hek/defs/flag.py new file mode 100644 index 00000000..ccb6e478 --- /dev/null +++ b/reclaimer/mcc_hek/defs/flag.py @@ -0,0 +1,60 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +def get(): return flag_def + +attachment_point = Struct("attachment_point", + SInt16("height_to_next_attachment", SIDETIP="vertices"), + Pad(18), + ascii_str32("marker_name"), + ) + +flag_body = Struct("tagdata", + Pad(4), + SEnum16("trailing_edge_shape", + "flat", + "concave_triangular", + "convex_triangular", + "trapezoid_short_top", + "trapezoid_short_bottom", + ), + + SInt16("trailing_edge_shape_offset", SIDETIP="vertices"), + SEnum16("attached_edge_shape", + "flat", + "concave_triangular", + ), + Pad(2), + SInt16("width", SIDETIP="vertices"), + SInt16("height", SIDETIP="vertices"), + + float_wu("cell_width"), + float_wu("cell_height"), + + dependency("red_flag_shader", valid_shaders), + dependency("physics", "pphy"), + + float_wu_sec("wind_noise"), + Pad(8), + dependency("blue_flag_shader", valid_shaders), + reflexive("attachment_points", attachment_point, 4, + DYN_NAME_PATH='.marker_name'), + SIZE=96, + ) + +flag_def = TagDef("flag", + blam_header('flag'), + flag_body, + + ext=".flag", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/fog_.py b/reclaimer/mcc_hek/defs/fog_.py new file mode 100644 index 00000000..b47a1ca6 --- /dev/null +++ b/reclaimer/mcc_hek/defs/fog_.py @@ -0,0 +1,92 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +fog_comment = """FLAGS +Setting prevents polygon popping when the atmospheric fog maximum +density (in the sky tag) is 1 and the atmospheric fog opaque distance is less than the +diameter of the map. However, this flag will cause artifacts when the camera goes below +the fog plane - so it should only be used when the fog plane is close to the ground.""" + +fog__body = Struct("tagdata", + #fog flags + Bool32("flags", + "is_water", + "atmospheric_dominant", + "fog_screen_only", + COMMENT=fog_comment + ), + + Pad(84), + #Density + float_zero_to_one("maximum_density"), + Pad(4), + float_wu("opaque_distance"), + Pad(4), + float_wu("opaque_depth"), + Pad(8), + float_wu("distance_to_water_plane"), + + #Color + QStruct("fog_color", INCLUDE=rgb_float), + + #Screen Layers + Struct("screen_layers", + Bool16("flags", + "no_environment_multipass", + "no_model_multipass", + "no_texture_based_falloff", + ), + UInt16("layer_count", SIDETIP="[0,4]", MIN=0, MAX=4), + + from_to_wu("distance_gradient"), + from_to_zero_to_one("density_gradient"), + + float_wu("start_distance_from_fog_plane"), + Pad(4), + + #QStruct("color", INCLUDE=xrgb_byte), + UInt32("color", INCLUDE=xrgb_uint32), + float_zero_to_one("rotation_multiplier"), + float_zero_to_one("strafing_multiplier"), + float_zero_to_one("zoom_multiplier"), + Pad(8), + Float("map_scale"), + dependency("fog_map", "bitm") + ), + + #Screen Layer Animation + Struct("screen_layer_animation", + float_sec("animation_period"), + Pad(4), + from_to_wu_sec("wind_velocity"), + from_to_sec("wind_period"), + float_zero_to_one("wind_acceleration_weight"), + float_zero_to_one("wind_perpendicular_weight") + ), + + Pad(8), + #Sound + dependency("background_sound", "lsnd"), + dependency("sound_environment", "snde"), + SIZE=396, + ) + +def get(): + return fog__def + +fog__def = TagDef("fog ", + blam_header('fog '), + fog__body, + + ext=".fog", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/font.py b/reclaimer/mcc_hek/defs/font.py new file mode 100644 index 00000000..cc4e50a7 --- /dev/null +++ b/reclaimer/mcc_hek/defs/font.py @@ -0,0 +1,63 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +def get(): return font_def + + +character_table = QStruct("character_table", + SInt16("character_index"), + SIZE=2 + ) + +character_tables = Struct("character_tables", + reflexive("character_table", character_table, 256), + SIZE=12 + ) + +character = QStruct("character", + UInt16("character"), + SInt16("character_width"), + SInt16("bitmap_width", EDITABLE=False), + SInt16("bitmap_height", EDITABLE=False), + SInt16("bitmap_origin_x"), + SInt16("bitmap_origin_y"), + SInt16("hardware_character_index"), + Pad(2), + SInt32("pixels_offset", EDITABLE=False), + SIZE=20, WIDGET=FontCharacterFrame + ) + +font_body = Struct("tagdata", + SInt32("flags"), + SInt16("ascending_height"), + SInt16("decending_height"), + SInt16("leading_height"), + SInt16("leading_width"), + Pad(36), + + reflexive("character_tables", character_tables, 256), + dependency("bold", "font"), + dependency("italic", "font"), + dependency("condense", "font"), + dependency("underline", "font"), + reflexive("characters", character, 65535), + rawdata_ref("pixels", max_size=8388608), + SIZE=156, + ) + +font_def = TagDef("font", + blam_header('font'), + font_body, + + ext=".font", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/foot.py b/reclaimer/mcc_hek/defs/foot.py new file mode 100644 index 00000000..a59bb98e --- /dev/null +++ b/reclaimer/mcc_hek/defs/foot.py @@ -0,0 +1,40 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +material = Struct("material", + dependency("effect", "effe"), + dependency("sound", "snd!"), + SIZE=48, + ) + +effect = Struct("effect", + reflexive("materials", material, len(materials_list), *materials_list), + SIZE=28, + ) + +foot_body = Struct("tagdata", + reflexive("effects", effect, 13, *material_effect_types), + SIZE=140, + ) + + + +def get(): + return foot_def + +foot_def = TagDef("foot", + blam_header('foot'), + foot_body, + + ext=".material_effects", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/garb.py b/reclaimer/mcc_hek/defs/garb.py new file mode 100644 index 00000000..039e4a46 --- /dev/null +++ b/reclaimer/mcc_hek/defs/garb.py @@ -0,0 +1,37 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .obje import * +from .item import * +from .objs.obje import ObjeTag +from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant + +# replace the object_type enum one that uses +# the correct default value for this object +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(4)) + ) + +garb_body = Struct("tagdata", + obje_attrs, + item_attrs, + SIZE=944, + ) + + +def get(): + return garb_def + +garb_def = TagDef("garb", + blam_header('garb'), + garb_body, + + ext=".garbage", endian=">", tag_cls=ObjeTag + ) diff --git a/reclaimer/mcc_hek/defs/glw_.py b/reclaimer/mcc_hek/defs/glw_.py new file mode 100644 index 00000000..cba1b5e8 --- /dev/null +++ b/reclaimer/mcc_hek/defs/glw_.py @@ -0,0 +1,99 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +velocity_properties = Struct("velocity_properties", + SEnum16("attachment", *function_outputs), + Pad(2), + Float("velocity", UNIT_SCALE=per_sec_unit_scale), + Float("low_multiplier"), + Float("high_multiplier") + ) + +glw__body = Struct("tagdata", + ascii_str32("attachment_marker"), + SInt16("number_of_particles"), + SEnum16("boundary_effect", + "bounce", + "wrap", + ), + SEnum16("normal_particle_distribution", + "random", + "uniform", + ), + SEnum16("trailing_particle_distribution", + "vertically", + "normal", + "randomly" + ), + Bool32("glow_flags" , + "modify_particle_color_in_range", + "particles_move_backwards", + "particles_move_in_both_directions", + "trailing_particles_fade_over_time", + "trailing_particles_shrink_over_time", + "trailing_particles_slow_over_time", + ), + + Pad(36), + Struct("particle_rotational_velocity", INCLUDE=velocity_properties), + Struct("effect_rotational_velocity", INCLUDE=velocity_properties), + Struct("effect_translational_velocity", INCLUDE=velocity_properties), + Struct("particle_distance_to_object", + SEnum16("attachment", *function_outputs), + Pad(2), + Float("min_distance"), + Float("max_distance"), + Float("low_multiplier"), + Float("high_multiplier") + ), + + Pad(8), + Struct("particle_size", + SEnum16("attachment", *function_outputs), + Pad(2), + from_to_wu("size_bounds"), # world units + QStruct("size_attachment_multiplier", INCLUDE=from_to), + ), + + Struct("particle_color", + SEnum16("attachment", *function_outputs), + Pad(2), + QStruct("lower_bound", INCLUDE=argb_float), + QStruct("upper_bound", INCLUDE=argb_float), + QStruct("lower_scale", INCLUDE=argb_float), + QStruct("upper_scale", INCLUDE=argb_float) + ), + + Float("color_rate_of_change"), + Float("fading_percentage_of_glow"), + Float("particle_generation_frequency", + SIDETIP="Hz", UNIT_SCALE=per_sec_unit_scale), + float_sec("lifetime_of_trailing_particles"), + float_wu_sec("velocity_of_trailing_particles"), + float_sec("trailing_particle_min_time"), + float_sec("trailing_particle_max_time"), + + Pad(52), + dependency("texture", "bitm"), + SIZE=340, + ) + +def get(): + return glw__def + +glw__def = TagDef("glw!", + blam_header('glw!'), + glw__body, + + ext=".glow", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/grhi.py b/reclaimer/mcc_hek/defs/grhi.py new file mode 100644 index 00000000..20484b14 --- /dev/null +++ b/reclaimer/mcc_hek/defs/grhi.py @@ -0,0 +1,249 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +messaging_information = Struct("messaging_information", + SInt16("sequence_index"), + SInt16("width_offset"), + QStruct("offset_from_reference_corner", + SInt16("x"), SInt16("y"), ORIENT='h' + ), + #QStruct("override_icon_color", INCLUDE=argb_byte), + UInt32("override_icon_color", INCLUDE=argb_uint32), + SInt8("frame_rate", MIN=0, MAX=30, UNIT_SCALE=per_sec_unit_scale), + Bool8("flags", + "use_text_from_string_list_instead", + "override_default_color", + "width_offset_is_absolute_icon_width", + ), + SInt16("text_index"), + SIZE=64 + ) + +effector = Struct("effector", + Pad(64), + SEnum16("destination_type", + "tint_0_to_1", + "horizontal_offset", + "vertical_offset", + "fade_0_to_1", + ), + SEnum16("destination", + "geometry_offset", + "primary_map", + "secondary_map", + "tertiary_map", + ), + SEnum16("source", + "player_pitch", + "player_tangent", + "player_yaw", + "weapon_total_ammo", + "weapon_loaded_ammo", + "weapon_heat", + "explicit", # use low bound + "weapon_zoom_level", + ), + + Pad(2), + QStruct("in_bounds", INCLUDE=from_to, + SIDETIP="source_units"), # source units + QStruct("out_bounds", INCLUDE=from_to, SIDETIP="pixels"), # pixels + + Pad(64), + QStruct("tint_color_lower_bound", INCLUDE=rgb_float), + QStruct("tint_color_upper_bound", INCLUDE=rgb_float), + Struct("periodic_functions", INCLUDE=anim_func_per_pha), + SIZE=220 + ) + +multitex_overlay = Struct("multitex_overlay", + Pad(2), + SInt16("type"), + SEnum16("framebuffer_blend_func", *framebuffer_blend_functions), + + # Anchors + Pad(34), + SEnum16("primary_anchor", *multitex_anchors), + SEnum16("secondary_anchor", *multitex_anchors), + SEnum16("tertiary_anchor", *multitex_anchors), + # Blending function + SEnum16("zero_to_one_blend_func", *blending_funcs), + SEnum16("one_to_two_blend_func", *blending_funcs), + + # Map scales + Pad(2), + QStruct("primary_scale", INCLUDE=xy_float), + QStruct("secondary_scale", INCLUDE=xy_float), + QStruct("tertiary_scale", INCLUDE=xy_float), + # Map offsets + QStruct("primary_offset", INCLUDE=xy_float), + QStruct("secondary_offset", INCLUDE=xy_float), + QStruct("tertiary_offset", INCLUDE=xy_float), + + # Maps + dependency("primary_map", "bitm"), + dependency("secondary_map", "bitm"), + dependency("tertiary_map", "bitm"), + SEnum16("primary_wrap_mode", *multitex_wrap_modes), + SEnum16("secondary_wrap_mode", *multitex_wrap_modes), + SEnum16("tertiary_wrap_mode", *multitex_wrap_modes), + + Pad(186), + reflexive("effectors", effector, 30), + SIZE=480, + ) + +total_grenades_numbers = Struct("total_grenades_numbers", + QStruct("anchor_offset", + SInt16("x"), SInt16("y"), ORIENT='h' + ), + Float("width_scale"), + Float("height_scale"), + Bool16("scaling_flags", *hud_scaling_flags), + + Pad(22), + #QStruct("default_color", INCLUDE=argb_byte), + #QStruct("flashing_color", INCLUDE=argb_byte), + UInt32("default_color", INCLUDE=argb_uint32), + UInt32("flashing_color", INCLUDE=argb_uint32), + float_sec("flash_period"), + float_sec("flash_delay"), + SInt16("number_of_flashes"), + Bool16("flash_flags", *hud_flash_flags), + float_sec("flash_length"), + #QStruct("disabled_color", INCLUDE=argb_byte), + UInt32("disabled_color", INCLUDE=argb_uint32), + + Pad(4), + SInt8("maximum_number_of_digits"), + Bool8("flags", + "show_leading_zeros", + "only_show_when_zoomed", + "draw_a_trailing_m", + ), + SInt8("number_of_fractional_digits"), + + Pad(13), + SInt16("flash_cutoff"), + SIZE=88 + ) + +overlay = Struct("overlay", + QStruct("anchor_offset", + SInt16("x"), SInt16("y"), ORIENT='h' + ), + Float("width_scale"), + Float("height_scale"), + Bool16("scaling_flags", *hud_scaling_flags), + + Pad(22), + #QStruct("default_color", INCLUDE=argb_byte), + #QStruct("flashing_color", INCLUDE=argb_byte), + UInt32("default_color", INCLUDE=argb_uint32), + UInt32("flashing_color", INCLUDE=argb_uint32), + float_sec("flash_period"), + float_sec("flash_delay"), + SInt16("number_of_flashes"), + Bool16("flash_flags", *hud_flash_flags), + float_sec("flash_length"), + #QStruct("disabled_color", INCLUDE=argb_byte), + UInt32("disabled_color", INCLUDE=argb_uint32), + + Pad(4), + float_sec("frame_rate"), + SInt16("sequence_index"), + Bool16("type", + "show_on_flashing", + "show_on_empty", + "show_on_default", + "show_always", + ), + Bool32("flags", + "flashes_when_active", + ), + + SIZE=136 + ) + +warning_sound = Struct("warning_sound", + dependency("sound", ('lsnd', 'snd!')), + Bool32("latched_to", + "low_grenade_sound", + "no_grenades_left", + "throw_on_no_grenades", + ), + Float("scale"), + SIZE=56 + ) + +# Use this with INCLUDE keywords since it will need to be named +hud_background = Struct("", + QStruct("anchor_offset", + SInt16("x"), SInt16("y"), ORIENT='h' + ), + Float("width_scale"), + Float("height_scale"), + Bool16("scaling_flags", *hud_scaling_flags), + + Pad(22), + dependency("interface_bitmap", "bitm"), + #QStruct("default_color", INCLUDE=argb_byte), + #QStruct("flashing_color", INCLUDE=argb_byte), + UInt32("default_color", INCLUDE=argb_uint32), + UInt32("flashing_color", INCLUDE=argb_uint32), + float_sec("flash_period"), + float_sec("flash_delay"), + SInt16("number_of_flashes"), + Bool16("flash_flags", *hud_flash_flags), + float_sec("flash_length"), + #QStruct("disabled_color", INCLUDE=argb_byte), + UInt32("disabled_color", INCLUDE=argb_uint32), + + Pad(4), + SInt16("sequence_index"), + + Pad(2), + reflexive("multitex_overlays", multitex_overlay, 30), + Pad(4), + SIZE=104 + ) + +grhi_body = Struct("tagdata", + SEnum16("anchor", *hud_anchors), + + Pad(34), + Struct("grenade_hud_background", INCLUDE=hud_background), + Struct("total_grenades", + Struct("background", INCLUDE=hud_background), + Struct("numbers", INCLUDE=total_grenades_numbers), + dependency("overlay_bitmap", "bitm"), + reflexive("overlays", overlay, 16), + ), + reflexive("warning_sounds", warning_sound, 12, + DYN_NAME_PATH='.sound.filepath'), + + Pad(68), + messaging_information, + SIZE=504, + ) + + +def get(): + return grhi_def + +grhi_def = TagDef("grhi", + blam_header("grhi"), + grhi_body, + + ext=".grenade_hud_interface", endian=">", tag_cls=HekTag, + ) diff --git a/reclaimer/mcc_hek/defs/hmt_.py b/reclaimer/mcc_hek/defs/hmt_.py new file mode 100644 index 00000000..97c63844 --- /dev/null +++ b/reclaimer/mcc_hek/defs/hmt_.py @@ -0,0 +1,60 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +message_element = Struct("message_element", + UEnum8("type", + "text", + "icon", + EDITABLE=False + ), + Union("data", + CASE=".type.enum_name", + CASES=dict( + text=UEnum8("length", + *({GUI_NAME: str(i), NAME: "_%s" % i} for i in range(256))), + icon=UEnum8("icon_type", + *({GUI_NAME: name, NAME: name} for name in hmt_icon_types) + ), + ), + EDITABLE=False + ), + SIZE=2, + ) + +message = Struct("message", + ascii_str32("name"), + SInt16("text_start", GUI_NAME="start index into text blob"), + SInt16("element_index", GUI_NAME="start index of message block"), + SInt8("element_count"), + Computed("message_preview", WIDGET=HaloHudMessageTextFrame), + SIZE=64 + ) + +hmt__body = Struct("tagdata", + rawtext_ref("string", FlStrUTF16Data, max_size=65536, + VISIBLE=False, EDITABLE=False), + reflexive("message_elements", message_element, 8192), + reflexive("messages", message, 1024, DYN_NAME_PATH='.name'), + SIZE=128, + ) + + +def get(): + return hmt__def + +hmt__def = TagDef("hmt ", + blam_header('hmt '), + hmt__body, + + ext=".hud_message_text", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/hud_.py b/reclaimer/mcc_hek/defs/hud_.py new file mode 100644 index 00000000..df50869e --- /dev/null +++ b/reclaimer/mcc_hek/defs/hud_.py @@ -0,0 +1,34 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +hud__body = Struct("tagdata", + dependency("digits_bitmap", "bitm"), + SInt8("bitmap_digit_width"), + SInt8("screen_digit_width"), + SInt8("x_offset"), + SInt8("y_offset"), + SInt8("decimal_point_width"), + SInt8("colon_width"), + SIZE=100, + ) + + +def get(): + return hud__def + +hud__def = TagDef("hud#", + blam_header('hud#'), + hud__body, + + ext=".hud_number", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/hudg.py b/reclaimer/mcc_hek/defs/hudg.py new file mode 100644 index 00000000..4990b261 --- /dev/null +++ b/reclaimer/mcc_hek/defs/hudg.py @@ -0,0 +1,224 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +button_icon = Struct("button_icon", + SInt16("sequence_index"), + SInt16("width_offset"), + QStruct("offset_from_reference_corner", + SInt16("x"), SInt16("y"), ORIENT='h' + ), + #QStruct("override_icon_color", INCLUDE=argb_byte), + UInt32("override_icon_color", INCLUDE=argb_uint32), + SInt8("frame_rate", MIN=0, MAX=30, UNIT_SCALE=per_sec_unit_scale), + Bool8("flags", + "use_text_from_string_list_instead", + "override_default_color", + "width_offset_is_absolute_icon_width", + ), + SInt16("text_index"), + SIZE=16 + ) + +waypoint_arrow = Struct("waypoint_arrow", + ascii_str32("name"), + + Pad(8), + #QStruct("color", INCLUDE=xrgb_byte), + UInt32("color", INCLUDE=xrgb_uint32), + Float("opacity"), + Float("translucency"), + SInt16("on_screen_sequence_index"), + SInt16("off_screen_sequence_index"), + SInt16("occluded_sequence_index"), + + Pad(18), + Bool32("flags", + "dont_rotate_when_pointing_offscreen" + ), + SIZE=104 + ) + + +messaging_parameters = Struct("messaging_parameters", + SEnum16("anchor", *hud_anchors), + + Pad(34), + QStruct("anchor_offset", + SInt16("x"), SInt16("y"), ORIENT='h' + ), + Float("width_scale"), + Float("height_scale"), + Bool16("scaling_flags", *hud_scaling_flags), + + Pad(22), + dependency("single_player_font", "font"), + dependency("multi_player_font", "font"), + float_sec("up_time"), + float_sec("fade_time"), + QStruct("icon_color", INCLUDE=argb_float), + QStruct("text_color", INCLUDE=argb_float), + Float("text_spacing"), + dependency("item_message_text", "ustr"), + dependency("icon_bitmap", "bitm"), + dependency("alternate_icon_text", "ustr"), + SIZE=196 + ) + +hud_help_text_color = Struct("hud_help_text_color", + #QStruct("default_color", INCLUDE=argb_byte), + #QStruct("flashing_color", INCLUDE=argb_byte), + UInt32("default_color", INCLUDE=argb_uint32), + UInt32("flashing_color", INCLUDE=argb_uint32), + float_sec("flash_period"), + float_sec("flash_delay"), + SInt16("number_of_flashes"), + Bool16("flash_flags", *hud_flash_flags), + float_sec("flash_length"), + #QStruct("disabled_color", INCLUDE=argb_byte), + UInt32("disabled_color", INCLUDE=argb_uint32), + SIZE=32 + ) + +objective_colors = Struct("objective_colors", + #QStruct("default_color", INCLUDE=argb_byte), + #QStruct("flashing_color", INCLUDE=argb_byte), + UInt32("default_color", INCLUDE=argb_uint32), + UInt32("flashing_color", INCLUDE=argb_uint32), + float_sec("flash_period"), + float_sec("flash_delay"), + SInt16("number_of_flashes"), + Bool16("flash_flags", *hud_flash_flags), + float_sec("flash_length"), + #QStruct("disabled_color", INCLUDE=argb_byte), + UInt32("disabled_color", INCLUDE=argb_uint32), + SInt16("uptime_ticks", UNIT_SCALE=sec_unit_scale), + SInt16("fade_ticks", UNIT_SCALE=sec_unit_scale), + SIZE=32 + ) + +waypoint_parameters = Struct("waypoint_parameters", + Float("top_offset"), + Float("bottom_offset"), + Float("left_offset"), + Float("right_offset"), + + Pad(32), + dependency("arrow_bitmaps", "bitm"), + SIZE=64 + ) + +hud_globals = Struct("hud_globals", + Float("hud_scale_in_multiplayer"), + + Pad(256), + dependency("default_weapon_hud", "wphi"), + Float("motion_sensor_range"), + Float("motion_sensor_velocity_sensitivity"), + Float("motion_sensor_scale", DEFAULT=32.0), # DONT TOUCH(why?) + QStruct("default_chapter_title_bounds", + SInt16("t"), SInt16("l"), SInt16("b"), SInt16("r"), ORIENT='h' + ), + SIZE=340 + ) + +hud_damage_indicators = Struct("hud_damage_indicators", + SInt16("top_offset"), + SInt16("bottom_offset"), + SInt16("left_offset"), + SInt16("right_offset"), + + Pad(32), + dependency("indicator_bitmap", "bitm"), + SInt16("sequence_index"), + SInt16("multiplayer_sequence_index"), + #QStruct("color", INCLUDE=argb_byte), + UInt32("color", INCLUDE=argb_uint32), + SIZE=80 + ) + +time_running_out = Struct("time_running_out_flash_color", + #QStruct("default_color", INCLUDE=argb_byte), + #QStruct("flashing_color", INCLUDE=argb_byte), + UInt32("default_color", INCLUDE=argb_uint32), + UInt32("flashing_color", INCLUDE=argb_uint32), + float_sec("flash_period"), + float_sec("flash_delay"), + SInt16("number_of_flashes"), + Bool16("flash_flags", *hud_flash_flags), + float_sec("flash_length"), + #QStruct("disabled_color", INCLUDE=argb_byte), + UInt32("disabled_color", INCLUDE=argb_uint32), + SIZE=32 + ) + +time_out = Struct("time_out_flash_color", + #QStruct("default_color", INCLUDE=argb_byte), + #QStruct("flashing_color", INCLUDE=argb_byte), + UInt32("default_color", INCLUDE=argb_uint32), + UInt32("flashing_color", INCLUDE=argb_uint32), + float_sec("flash_period"), + float_sec("flash_delay"), + SInt16("number_of_flashes"), + Bool16("flash_flags", *hud_flash_flags), + float_sec("flash_length"), + #QStruct("disabled_color", INCLUDE=argb_byte), + UInt32("disabled_color", INCLUDE=argb_uint32), + SIZE=32 + ) + +misc_hud_crap = Struct("misc_hud_crap", + SInt16("loading_begin_text"), + SInt16("loading_end_text"), + SInt16("checkpoint_begin_text"), + SInt16("checkpoint_end_text"), + dependency("checkpoint", "snd!"), + BytearrayRaw("unknown", SIZE=96, VISIBLE=False), + SIZE=120 + ) + +hudg_body = Struct("tagdata", + messaging_parameters, + reflexive("button_icons", button_icon, 18, + "a button", "b button", "x button", "y button", + "black button", "white button", "left trigger", "right trigger", + "dpad up", "dpad down", "dpad left", "dpad right", "start", "back", + "left thumb button", "right thumb button", "left stick", "right stick" + ), + hud_help_text_color, + dependency("hud_messages", "hmt "), + objective_colors, + waypoint_parameters, + reflexive("waypoint_arrows", waypoint_arrow, 16, DYN_NAME_PATH='.name'), + + Pad(80), + hud_globals, + hud_damage_indicators, + time_running_out, + time_out, + + Pad(40), + dependency("carnage_report_bitmap", "bitm"), + misc_hud_crap, + SIZE=1104 + ) + + +def get(): + return hudg_def + +hudg_def = TagDef("hudg", + blam_header("hudg"), + hudg_body, + + ext=".hud_globals", endian=">", tag_cls=HekTag, + ) diff --git a/reclaimer/mcc_hek/defs/item.py b/reclaimer/mcc_hek/defs/item.py new file mode 100644 index 00000000..9b4c8d6e --- /dev/null +++ b/reclaimer/mcc_hek/defs/item.py @@ -0,0 +1,61 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +message_index_comment = """MESSAGE INDEX +This sets which string from tags\\ui\\hud\\hud_item_messages.unicode_string_list to display.""" + +item_attrs = Struct('item_attrs', + Bool32("flags", + "always_maintains_z_up", + "destroyed_by_explosions", + "unaffected_by_gravity", + ), + SInt16("message_index", COMMENT=message_index_comment), + SInt16("sort_order"), + Float("scale"), + SInt16("hud_message_value_scale"), + + Pad(18), + + SEnum16("A_in", *device_functions), + SEnum16("B_in", *device_functions), + SEnum16("C_in", *device_functions), + SEnum16("D_in", *device_functions), + + Pad(164), + + dependency("material_effects", "foot"), + dependency("collision_sound", "snd!"), + + Pad(120), + + from_to_sec("detonation_delay"), + dependency("detonating_effect", "effe"), + dependency("detonation_effect", "effe"), + SIZE=396, + ) + +item_body = Struct('tagdata', + item_attrs, + SIZE=396 + ) + +def get(): + return item_def + +item_def = TagDef("item", + blam_header('item', 2), + item_body, + + ext=".item", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/itmc.py b/reclaimer/mcc_hek/defs/itmc.py new file mode 100644 index 00000000..8a88c5ed --- /dev/null +++ b/reclaimer/mcc_hek/defs/itmc.py @@ -0,0 +1,38 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +item_permutation = Struct("permutation", + Pad(32), + Float("weight"), + dependency("item", valid_items), + SIZE=84, + ) + +itmc_body = Struct("tagdata", + reflexive("item_permutations", item_permutation, 32767, + DYN_NAME_PATH='.item.filepath'), + SInt16("spawn_time", SIDETIP="seconds(0 = default)", + UNIT_SCALE=per_sec_unit_scale), + SIZE=92, + ) + + +def get(): + return itmc_def + +itmc_def = TagDef("itmc", + blam_header('itmc',0), + itmc_body, + + ext=".item_collection", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/jpt_.py b/reclaimer/mcc_hek/defs/jpt_.py new file mode 100644 index 00000000..bb37426f --- /dev/null +++ b/reclaimer/mcc_hek/defs/jpt_.py @@ -0,0 +1,158 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + + +frequency_vibration = Struct("", + float_zero_to_one("frequency"), + float_sec("duration"), + SEnum16("fade_function", *fade_functions), + ) + +jpt__body = Struct("tagdata", + from_to_wu("radius"), + float_zero_to_one("cutoff_scale"), + Bool32("flags", + "dont_scale_by_distance", + ), + Pad(20), + + #Screen_Flash + Struct("screen_flash", + SEnum16("type", + "none", + "lighten", + "darken", + "max", + "min", + "invert", + "tint", + ), + SEnum16("priority", + "low", + "medium", + "high", + ), + Pad(12), + + float_sec("duration"), + SEnum16("fade_function", *fade_functions), + Pad(10), + + float_zero_to_one("maximum_intensity"), + Pad(4), + + QStruct("tint_lower_bound", INCLUDE=argb_float), + ), + + Struct("low_frequency_vibrate", INCLUDE=frequency_vibration), + Pad(10), + Struct("high_frequency_vibrate", INCLUDE=frequency_vibration), + Pad(30), + + Struct("temporary_camera_impulse", + float_sec("duration"), + SEnum16("fade_function", *fade_functions), + Pad(2), + + float_rad("rotation"), # radians + float_wu("pushback"), + from_to_wu("jitter"), + Pad(8), + ), + + float_rad("permanent_camera_impulse_angle"), + Pad(16), + + Struct("camera_shaking", + float_sec("duration"), + SEnum16("fade_function", *fade_functions), + Pad(2), + + float_wu("random_translation"), + float_rad("random_rotation"), # radians + Pad(12), + + SEnum16("wobble_function", *animation_functions), + Pad(2), + float_sec("wobble_function_period"), + Float("wobble_weight"), + Pad(32), + ), + + dependency("sound", "snd!"), + Pad(112), + + QStruct("breaking_effect", + float_wu_sec("forward_velocity"), + float_wu("forward_radius"), + Float("forward_exponent"), + Pad(12), + + float_wu_sec("outward_velocity"), + float_wu("outward_radius"), + Float("outward_exponent"), + Pad(12), + ), + + Struct("damage", + SEnum16("priority", + "none", + "harmless", + {NAME: "backstab", GUI_NAME: "lethal to the unsuspecting"}, + "emp", + ), + SEnum16("category", *damage_category), + Bool32("flags", + "does_not_hurt_owner", + {NAME: "headshot", GUI_NAME: "causes headshots"}, + "pings_resistant_units", + "does_not_hurt_friends", + "does_not_ping_units", + "detonates_explosives", + "only_hurts_shields", + "causes_flaming_death", + {NAME: "indicator_points_down", + GUI_NAME: "damage indicator always points down"}, + "skips_shields", + "only_hurts_one_infection_form", + {NAME: "multiplayer_headshot", + GUI_NAME: "causes multiplayer headshots"}, + "infection_form_pop", + ), + float_wu("aoe_core_radius"), + Float("damage_lower_bound"), + QStruct("damage_upper_bound", INCLUDE=from_to), + float_zero_to_one("vehicle_passthrough_penalty"), + float_zero_to_one("active_camouflage_damage"), + float_zero_to_one("stun"), + float_zero_to_one("maximum_stun"), + float_sec("stun_time"), + Pad(4), + float_zero_to_inf("instantaneous_acceleration"), + Pad(8), + ), + + damage_modifiers, + SIZE=672, + ) + + +def get(): + return jpt__def + +jpt__def = TagDef("jpt!", + blam_header('jpt!', 6), + jpt__body, + + ext=".damage_effect", endian=">", tag_cls=HekTag, + ) diff --git a/reclaimer/mcc_hek/defs/lens.py b/reclaimer/mcc_hek/defs/lens.py new file mode 100644 index 00000000..15cc1bf6 --- /dev/null +++ b/reclaimer/mcc_hek/defs/lens.py @@ -0,0 +1,127 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.lens import LensTag +from supyr_struct.defs.tag_def import TagDef + +occlusion_comment = """Occlusion factor affects overall lens flare brightness and can also affect scale. +Occlusion never affects rotation.""" + +corona_rotation_comment = """Controls how corona rotation is affected by the viewer and light angles.""" + +reflection = Struct("reflection", + Bool16("flags", + "align_rotation_with_screen_center", + "radius_not_scaled_by_distance", + "radius_scaled_by_occlusion_factor", + "occluded_by_solid_objects", + ), + Pad(2), + SInt16("bitmap_index"), + Pad(22), + Float("position", SIDETIP="along flare axis"), # along flare axis + float_deg("rotation_offset"), # degrees + Pad(4), + from_to_wu("radius"), # world units + SEnum16("radius_scaled_by", + "none", + "rotation", + "rotation_and_strafing", + "distance_from_center", + ), + Pad(2), + from_to_zero_to_one("brightness"), # [0,1] + SEnum16("brightness_scaled_by", + "none", + "rotation", + "rotation_and_strafing", + "distance_from_center", + ), + Pad(2), + + #Tint color + QStruct("tint_color", INCLUDE=argb_float), + + #Animation + Struct("animation", + QStruct("color_lower_bound", INCLUDE=argb_float), + QStruct("color_upper_bound", INCLUDE=argb_float), + Bool16("flags", *blend_flags), + SEnum16("function", *animation_functions), + float_sec("period"), # seconds + float_sec("phase"), # seconds + ), + + SIZE=128 + ) + + +lens_body = Struct("tagdata", + float_rad("falloff_angle"), # radians + float_rad("cutoff_angle"), # radians + FlFloat("cosine_falloff_angle", VISIBLE=False), + FlFloat("cosine_cutoff_angle", VISIBLE=False), + Struct("occlusion", + float_wu("radius"), + SEnum16("offset_direction", + "toward_viewer", + "marker_forward", + "none", + ), + Pad(2), + float_wu("near_fade_distance"), + float_wu("far_fade_distance"), + COMMENT=occlusion_comment + ), + + Struct("bitmaps", + dependency("bitmap", "bitm"), + Bool16("flags", + "sun", + ), + Pad(78), + ), + + Struct("corona_rotation", + SEnum16("function", + "none", + "rotation_a", + "rotation_b", + "rotation_translation", + "translation", + ), + Pad(2), + float_rad("function_scale"), # radians + COMMENT=corona_rotation_comment + ), + + Struct("corona_radius_scale", + Pad(24), + Float("horizontal_scale"), + Float("vertical_scale"), + ), + + Pad(28), + + reflexive("reflections", reflection, 32), + + SIZE=240, + ) + + +def get(): + return lens_def + +lens_def = TagDef("lens", + blam_header("lens", 2), + lens_body, + + ext=".lens_flare", endian=">", tag_cls=LensTag, + ) diff --git a/reclaimer/mcc_hek/defs/lifi.py b/reclaimer/mcc_hek/defs/lifi.py new file mode 100644 index 00000000..f4addf61 --- /dev/null +++ b/reclaimer/mcc_hek/defs/lifi.py @@ -0,0 +1,38 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .obje import * +from .devi import * +from .objs.lifi import LifiTag +from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant + +# replace the object_type enum one that uses +# the correct default value for this object +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(9)) + ) + +lifi_body = Struct("tagdata", + obje_attrs, + devi_attrs, + + SIZE=720, + ) + + +def get(): + return lifi_def + +lifi_def = TagDef("lifi", + blam_header('lifi'), + lifi_body, + + ext=".device_light_fixture", endian=">", tag_cls=LifiTag + ) diff --git a/reclaimer/mcc_hek/defs/ligh.py b/reclaimer/mcc_hek/defs/ligh.py new file mode 100644 index 00000000..cf9ec696 --- /dev/null +++ b/reclaimer/mcc_hek/defs/ligh.py @@ -0,0 +1,108 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.ligh import LighTag +from supyr_struct.defs.tag_def import TagDef + +gel_comment = """The map tints the light per-pixel of cubemap.""" + +lens_flare_comment = """LENS FLARE +Optional lens flare associated with this light.""" + +radiosity_comment = """Controls how the light affects the lightmaps (ignored for dynamic lights).""" + +effect_parameters_comment = """If the light is created by an effect, it will animate itself as follows.""" + +ligh_body = Struct("tagdata", + Bool32("flags", + "dynamic", + "no_specular", + "dont_light_own_object", + "supersize_in_first_person", + "first_person_flashlight", + "dont_fade_active_camouflage", + ), + + #Shape + Struct("shape", + Float("radius"), + QStruct("radius_modifier", INCLUDE=from_to), + float_rad("falloff_angle"), # radians + float_rad("cutoff_angle"), # radians + Float("lens_flare_only_radius"), + Float("cosine_falloff_angle", VISIBLE=False), + Float("cosine_cutoff_angle", VISIBLE=False), + Float("unknown", VISIBLE=False), + Float("sine_cutoff_angle", VISIBLE=False), + Pad(8), + ), + + #Color + Struct("color", + Bool32("interpolation_flags", *blend_flags), + QStruct("color_lower_bound", INCLUDE=argb_float), + QStruct("color_upper_bound", INCLUDE=argb_float), + Pad(12), + ), + + #Gel + Struct("gel_map", + dependency("primary_cube_map", "bitm"), + Pad(2), + SEnum16("texture_animation_function", *animation_functions), + float_sec("texture_animation_period"), + + dependency("secondary_cube_map", "bitm"), + Pad(2), + SEnum16("yaw_animation_function", *animation_functions), + float_sec("yaw_animation_period"), + Pad(2), + SEnum16("roll_animation_function", *animation_functions), + float_sec("roll_animation_period"), + Pad(2), + SEnum16("pitch_animation_function", *animation_functions), + float_sec("pitch_animation_period"), + Pad(8), + COMMENT=gel_comment + ), + + #Lens flare + dependency("lens_flare", "lens", COMMENT=lens_flare_comment), + Pad(24), + + #Radiosity + Struct("radiosity", + Float("intensity"), + QStruct("color", INCLUDE=rgb_float), + Pad(16), + COMMENT=radiosity_comment + ), + + #Effect parameters + Struct("effect_parameters", + float_sec("duration"), + Pad(2), + SEnum16("falloff_function", *fade_functions), + COMMENT=effect_parameters_comment + ), + + SIZE=352, + ) + + +def get(): + return ligh_def + +ligh_def = TagDef("ligh", + blam_header("ligh", 3), + ligh_body, + + ext=".light", endian=">", tag_cls=LighTag, + ) diff --git a/reclaimer/mcc_hek/defs/lsnd.py b/reclaimer/mcc_hek/defs/lsnd.py new file mode 100644 index 00000000..0914ebbf --- /dev/null +++ b/reclaimer/mcc_hek/defs/lsnd.py @@ -0,0 +1,98 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +scale_comment = """DETAIL SOUND PERIOD SCALING +As the sound's input scale changes from zero to one, these modifiers move between +the two values specified here. The sound will play using the current scale modifier +multiplied by the value specified below. (0 values are ignored.)""" + +random_spatialisation_comment = """RANDOM SPATIALIZATION +If the sound specified above is not stereo it will be randomly spatialized according +to the following contraints. If both lower and upper bounds are zero for any of +the following fields, the sound's position will be randomly selected from +all the possible directions or distances.""" + +detail_sound = Struct("detail_sound", + dependency("sound", "snd!"), + from_to_sec('random_period_bounds'), + Float("gain"), + Bool32("flags", + "dont_play_with_alternate", + "dont_play_without_alternate", + ), + + Pad(48), + from_to_rad('yaw_bounds', COMMENT=random_spatialisation_comment), # radians + from_to_rad('pitch_bounds'), # radians + from_to_wu('distance_bounds'), # world units + + SIZE=104 + ) + +track = Struct("track", + Bool32("flags", + "fade_in_at_start", + "fade_out_at_stop", + "fade_in_alternate", + ), + Float("gain"), + float_sec("fade_in_duration"), + float_sec("fade_out_duration"), + + Pad(32), + dependency("start", "snd!"), + dependency("loop", "snd!"), + dependency("end", "snd!"), + + Pad(32), + dependency("alternate_loop", "snd!"), + dependency("alternate_end", "snd!"), + SIZE=160 + ) + + +lsnd_body = Struct("tagdata", + Bool32("flags", + "deafening_to_ai", + "not_a_loop", + "stops_music", + ), + Float("detail_sound_period_at_zero", COMMENT=scale_comment), + FlFloat("unknown0", DEFAULT=1.0, VISIBLE=False), + FlFloat("unknown1", DEFAULT=1.0, VISIBLE=False), + Float("detail_sound_period_at_one"), + FlFloat("unknown2", DEFAULT=1.0, VISIBLE=False), + FlFloat("unknown3", DEFAULT=1.0, VISIBLE=False), + FlSInt16("unknown4", DEFAULT=-1, VISIBLE=False), + FlSInt16("unknown5", DEFAULT=-1, VISIBLE=False), + FlFloat("unknown6", DEFAULT=1.0, VISIBLE=False), + Pad(8), + dependency("continuous_damage_effect", "cdmg"), + + reflexive("tracks", track, 4), + reflexive("detail_sounds", detail_sound, 32, + DYN_NAME_PATH='.sound.filepath'), + + SIZE=84, + ) + + +def get(): + return lsnd_def + +lsnd_def = TagDef("lsnd", + blam_header("lsnd", 3), + lsnd_body, + + ext=".sound_looping", endian=">", tag_cls=HekTag, + ) diff --git a/reclaimer/mcc_hek/defs/mach.py b/reclaimer/mcc_hek/defs/mach.py new file mode 100644 index 00000000..c532f018 --- /dev/null +++ b/reclaimer/mcc_hek/defs/mach.py @@ -0,0 +1,62 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .obje import * +from .devi import * +from .objs.mach import MachTag +from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant + +# replace the object_type enum one that uses +# the correct default value for this object +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(7)) + ) + +mach_attrs = Struct("mach_attrs", + SEnum16('type', + 'door', + 'platform', + 'gear', + ), + Bool16('flags', + 'pathfinding_obstable', + 'except_when_open', + 'elevator', + ), + float_sec('door_open_time'), # seconds + + Pad(80), + SEnum16('triggers_when', + 'pause_until_crushed', + 'reverse_directions' + ), + SInt16('elevator_node'), + Pad(52), + UInt32("door_open_time_ticks") + ) + +mach_body = Struct("tagdata", + obje_attrs, + devi_attrs, + mach_attrs, + + SIZE=804, + ) + + +def get(): + return mach_def + +mach_def = TagDef("mach", + blam_header('mach'), + mach_body, + + ext=".device_machine", endian=">", tag_cls=MachTag + ) diff --git a/reclaimer/mcc_hek/defs/matg.py b/reclaimer/mcc_hek/defs/matg.py new file mode 100644 index 00000000..15319bea --- /dev/null +++ b/reclaimer/mcc_hek/defs/matg.py @@ -0,0 +1,382 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +def get(): + return matg_def + +camera = Struct("camera", + dependency('camera', "trak"), + SIZE=16 + ) + +sound = Struct("sound", + dependency('sound', "snd!"), + SIZE=16 + ) + +look_function = Struct("look_function", + Float('scale'), + SIZE=4 + ) + +player_control = Struct("player_control", + Float('magnetism_friction'), + Float('magnetism_adhesion'), + Float('inconsequential_target_scale'), + + Pad(52), + float_sec('look_acceleration_time'), + Float('look_acceleration_scale'), + float_zero_to_one('look_peg_threshold'), + float_deg('look_default_pitch_rate'), # degrees + float_deg('look_default_yaw_rate'), # degrees + Float('look_autolevelling_scale'), + + Pad(20), + SInt16('minimum_weapon_swap_ticks'), + SInt16('minimum_autolevelling_ticks'), + float_rad('minimum_angle_for_vehicle_flip'), # radians + reflexive("look_functions", look_function, 16), + + SIZE=128 + ) + +difficulty_base = QStruct("", + Float("easy"), + Float("normal"), + Float("hard"), + Float("imposs"), + ORIENT='h' + ) + +difficulty = Struct("difficulty", + # Health + Struct("enemy_scales", + QStruct("damage", INCLUDE=difficulty_base), + QStruct("vitality", INCLUDE=difficulty_base), + QStruct("shield", INCLUDE=difficulty_base), + QStruct("recharge", INCLUDE=difficulty_base), + ), + Struct("friend_scales", + QStruct("damage", INCLUDE=difficulty_base), + QStruct("vitality", INCLUDE=difficulty_base), + QStruct("shield", INCLUDE=difficulty_base), + QStruct("recharge", INCLUDE=difficulty_base), + ), + QStruct("infection_form_vitality_scales", INCLUDE=difficulty_base), + + Pad(16), + # Enemy ranged fire + Struct("ranged_combat_scales", + QStruct("rate_of_fire", INCLUDE=difficulty_base), + QStruct("projectile_error", INCLUDE=difficulty_base), + QStruct("burst_error", INCLUDE=difficulty_base), + QStruct("new_target_delay", INCLUDE=difficulty_base), + QStruct("burst_separation", INCLUDE=difficulty_base), + QStruct("target_tracking", INCLUDE=difficulty_base), + QStruct("target_leading", INCLUDE=difficulty_base), + QStruct("overcharge_chance", INCLUDE=difficulty_base), + QStruct("special_fire_delay", INCLUDE=difficulty_base), + QStruct("guidance_vs_player", INCLUDE=difficulty_base), + ), + + Struct("close_combat_scales", + QStruct("melee_delay_base", + GUI_NAME="melee delay base(not a scale)", INCLUDE=difficulty_base + ), + QStruct("melee_delay", INCLUDE=difficulty_base), + + Pad(16), + # Grenades + QStruct("grenade_chance", INCLUDE=difficulty_base), + QStruct("grenade_timer", INCLUDE=difficulty_base), + ), + + Pad(48), + # Placement + Struct("major_upgrade_fractions", + QStruct("normal", INCLUDE=difficulty_base), + QStruct("few", INCLUDE=difficulty_base), + QStruct("many", INCLUDE=difficulty_base), + ), + + SIZE=644 + ) + +grenade = Struct("grenade", + SInt16('maximum_count'), + SInt16('mp_spawn_default'), + dependency('throwing_effect', "effe"), + dependency('hud_interface', "grhi"), + dependency('equipment', "eqip"), + dependency('projectile', "proj"), + SIZE=68 + ) + +rasterizer_data = Struct("rasterizer_data", + Struct("function_textures", + dependency('distance_attenuation', "bitm"), + dependency('vector_normalization', "bitm"), + dependency('atmospheric_fog_density', "bitm"), + dependency('planar_fog_density', "bitm"), + dependency('linear_corner_fade', "bitm"), + dependency('active_camouflage_distortion', "bitm"), + dependency('glow', "bitm"), + Pad(60), + ), + + # Default textures + Struct("default_textures", + dependency('default_2d', "bitm"), + dependency('default_3d', "bitm"), + dependency('default_cubemap', "bitm"), + ), + + # Experimental textures + Struct("experimental_textures", + dependency('test0', "bitm"), + dependency('test1', "bitm"), + dependency('test2', "bitm"), + dependency('test3', "bitm"), + ), + + # video effect textures + Struct("video_effect_textures", + dependency('video_scanline_map', "bitm"), + dependency('video_noise_map', "bitm"), + Pad(52), + ), + + # Active camouflage + Struct("active_camouflage", + Bool16("flags", + "tint_edge_density" + ), + Pad(2), + Float('refraction_amount', SIDETIP="pixels"), # pixels + Float('distance_falloff'), + QStruct('tint_color', INCLUDE=rgb_float), + Float('hyper_stealth_refraction_amount', SIDETIP="pixels"), # pixels + Float('hyper_stealth_distance_falloff'), + QStruct('hyper_stealth_tint_color', INCLUDE=rgb_float), + ), + + # PC textures + dependency('distance_attenuation_2d', "bitm"), + + SIZE=428 + ) + +interface_bitmaps = Struct("interface_bitmaps", + dependency('font_system', "font"), + dependency('font_terminal', "font"), + dependency('screen_color_table', "colo"), + dependency('hud_color_table', "colo"), + dependency('editor_color_table', "colo"), + dependency('dialog_color_table', "colo"), + dependency('hud_globals', "hudg"), + dependency('motion_sensor_sweep_bitmap', "bitm"), + dependency('motion_sensor_sweep_bitmap_mask', "bitm"), + dependency('multiplayer_hud_bitmap', "bitm"), + dependency('localization', "str#"), + dependency('hud_digits_definition', "hud#"), + dependency('motion_sensor_blip', "bitm"), + dependency('interface_goo_map1', "bitm"), + dependency('interface_goo_map2', "bitm"), + dependency('interface_goo_map3', "bitm"), + SIZE=304 + ) + +cheat_weapon = Struct("weapon", + dependency('weapon', valid_items), + SIZE=16 + ) + +cheat_powerup = Struct("powerup", + dependency('powerup', "eqip"), + SIZE=16 + ) + +vehicle = Struct("vehicle", + dependency('vehicle', "vehi"), + SIZE=16 + ) + +multiplayer_information = Struct("multiplayer_information", + dependency('flag', valid_items), + dependency('unit', valid_units), + reflexive('vehicles', vehicle, 20, DYN_NAME_PATH='.vehicle.filepath'), + dependency('hill_shader', valid_shaders), + dependency('flag_shader', valid_shaders), + dependency('ball', valid_items), + reflexive('sounds', sound, 60, DYN_NAME_PATH='.sound.filepath'), + SIZE=160 + ) + +player_information = Struct("player_information", + dependency('unit', valid_units), + + Pad(28), + float_wu_sec("walking_speed"), # world units/second + Float("double_speed_multiplier", SIDETIP="[1.0,+inf]", MIN=1.0), + float_wu_sec("run_forward"), # world units/second + float_wu_sec("run_backward"), # world units/second + float_wu_sec("run_sideways"), # world units/second + float_wu_sec_sq("run_acceleration", + UNIT_SCALE=per_sec_unit_scale), # world units/second^2 + float_wu_sec("sneak_forward"), # world units/second + float_wu_sec("sneak_backward"), # world units/second + float_wu_sec("sneak_sideways"), # world units/second + float_wu_sec_sq("sneak_acceleration", + UNIT_SCALE=per_sec_unit_scale), # world units/second^2 + float_wu_sec_sq("airborne_acceleration", + UNIT_SCALE=per_sec_unit_scale), # world units/second^2 + Float("speed_multiplier", SIDETIP="multiplayer only"), # multiplayer only + + Pad(12), + QStruct("grenade_origin", INCLUDE=xyz_float), + + Pad(12), + float_zero_to_one("stun_movement_penalty"), + float_zero_to_one("stun_turning_penalty"), + float_zero_to_one("stun_jumping_penalty"), + Float("minimum_stun_time"), + Float("maximum_stun_time"), + + Pad(8), + from_to_sec("first_person_idle_time"), + float_zero_to_one("first_person_skip_fraction"), + + Pad(16), + dependency('coop_respawn_effect', "effe"), + SIZE=244 + ) + +first_person_interface = Struct("first_person_interface", + dependency('first_person_hands', valid_models), + dependency('base_bitmap', "bitm"), + dependency('shield_meter', "metr"), + QStruct('shield_meter_origin', + SInt16('x'), SInt16('y'), ORIENT='h' + ), + dependency('body_meter', "metr"), + QStruct('body_meter_origin', + SInt16('x'), SInt16('y'), ORIENT='h' + ), + dependency('night_vision_toggle_on_effect', "effe"), + dependency('night_vision_toggle_off_effect', "effe"), + + SIZE=192 + ) + +falling_damage = Struct("falling_damage", + Pad(8), + QStruct("harmful_falling_distance", INCLUDE=from_to), + dependency('falling_damage', "jpt!"), + + Pad(8), + Float("maximum_falling_distance"), + dependency('distance_damage', "jpt!"), + dependency('vehicle_environment_collision_damage', "jpt!"), + dependency('vehicle_killed_unit_damage', "jpt!"), + dependency('vehicle_collision_damage', "jpt!"), + dependency('flaming_death_damage', "jpt!"), + SIZE=152 + ) + +particle_effect = Struct("particle_effect", + dependency('particle_type', "part"), + Bool32("flags", *blend_flags), + float_wu("density"), + QStruct("velocity_scale", INCLUDE=from_to), + + Pad(4), + from_to_rad_sec("angular_velocity"), # radians/second + + Pad(8), + from_to_wu("radius"), # world units + + Pad(8), + QStruct("tint_lower_bound", INCLUDE=argb_float), + QStruct("tint_upper_bound", INCLUDE=argb_float), + SIZE=128 + ) + +material = Struct("material", + Pad(148), + # Vehicle terrain parameters + Float("ground_friction_scale"), + Float("ground_friction_normal_k1_scale"), + Float("ground_friction_normal_k0_scale"), + Float("ground_depth_scale"), + Float("ground_damp_fraction_scale"), + + # Breakable surface parameters + Pad(556), + Float("maximum_vitality"), + + Pad(12), + dependency('effect', "effe"), + dependency('sound', "snd!"), + + Pad(24), + reflexive("particle_effects", particle_effect, 8, + DYN_NAME_PATH='.particle_type.filepath'), + + Pad(60), + dependency('melee_hit_sound', "snd!"), + SIZE=884 + ) + +playlist_member = Struct("playlist_member", + ascii_str32("map_name"), + ascii_str32("game_variant"), + SInt32('minimum_experience'), + SInt32('maximum_experience'), + SInt32('minimum_player_count'), + SInt32('maximum_player_count'), + SInt32('rating'), + SIZE=148 + ) + +matg_body = Struct('tagdata', + Pad(248), + reflexive("sounds", sound, 2, + "enter water", "exit water"), + reflexive("cameras", camera, 1), + reflexive("player_controls", player_control, 1), + reflexive("difficulties", difficulty, 1), + reflexive("grenades", grenade, 2, *grenade_types), + reflexive("rasterizer_datas", rasterizer_data, 1), + reflexive("interface_bitmaps", interface_bitmaps, 1), + reflexive("cheat_weapons", cheat_weapon, 20, + DYN_NAME_PATH='.weapon.filepath'), + reflexive("cheat_powerups", cheat_powerup, 20, + DYN_NAME_PATH='.powerup.filepath'), + reflexive("multiplayer_informations", multiplayer_information, 1), + reflexive("player_informations", player_information, 1), + reflexive("first_person_interfaces", first_person_interface, 1), + reflexive("falling_damages", falling_damage, 1), + reflexive("materials", material, len(materials_list), *materials_list), + reflexive("playlist_members", playlist_member, 20, + DYN_NAME_PATH='.map_name'), + + SIZE=428 + ) + +matg_def = TagDef("matg", + blam_header('matg', 3), + matg_body, + + ext=".globals", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/metr.py b/reclaimer/mcc_hek/defs/metr.py new file mode 100644 index 00000000..88c12a70 --- /dev/null +++ b/reclaimer/mcc_hek/defs/metr.py @@ -0,0 +1,57 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + + +meter_body = Struct("tagdata", + Pad(4), + dependency("stencil_bitmap", "bitm"), + dependency("source_bitmap", "bitm"), + + SInt16("stencil_sequence_index"), + SInt16("source_sequence_index"), + Pad(20), + SEnum16("interpolate_colors", + "linearly", + "faster_near_empty", + "faster_near_full", + "through_random_noise" + ), + SEnum16("anchor_colors" , + "at_both_ends", + "at_empty", + "at_full" + ), + Pad(8), + QStruct("empty_color", INCLUDE=argb_float), + QStruct("full_color", INCLUDE=argb_float), + Pad(20), + Float("unmask_distance", SIDETIP="meter units"), + Float("mask_distance", SIDETIP="meter units"), + Pad(12), + FlUInt16("screen_x_pos", SIDETIP="pixels"), + FlUInt16("screen_y_pos", SIDETIP="pixels"), + FlUInt16("width", SIDETIP="pixels"), + FlUInt16("height", SIDETIP="pixels"), + + rawdata_ref("meter_data", max_size=65536), + SIZE=172, WIDGET=MeterImageFrame + ) + +def get(): + return metr_def + +metr_def = TagDef("metr", + blam_header('metr'), + meter_body, + ext=".meter", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/mgs2.py b/reclaimer/mcc_hek/defs/mgs2.py new file mode 100644 index 00000000..076a3290 --- /dev/null +++ b/reclaimer/mcc_hek/defs/mgs2.py @@ -0,0 +1,88 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +light_volume_comment = """LIGHT VOLUME +Draws a sequence of glow bitmaps along a line. Can be used for contrail-type effects +as well as volumetric lights.""" + +brightness_scale_comment = """BRIGHTNESS SCALE +Fades the effect in and out with distance, viewer angle and external source.""" + +bitmaps_comment = """BITMAPS +Bitmap tag used to draw the light volume, repeated times. Default is +'tags\\rasterizer_textures\\glow'. +Note: Sprite plates are not valid for light volumes.""" + +frame_animation_comment = """FRAME ANIMATION +Frames are descriptions of the light volume at a particular point in time, +interpolated by an external source. For example: A bolt of energy can be made +to stretch out and grow thinner as it is fired from a weapon.""" + +frame = Struct("frame", + Pad(16), + float_wu("offset_from_marker"), + Float("offset_exponent"), + float_wu("length"), + + Pad(32), + float_wu("radius_hither"), + float_wu("radius_yon"), + Float("radius_exponent"), + + Pad(32), + QStruct("tint_color_hither", INCLUDE=argb_float), + QStruct("tint_color_yon", INCLUDE=argb_float), + Float("tint_color_exponent"), + Float("brightness_exponent"), + SIZE=176 + ) + +mgs2_body = Struct("tagdata", + #Light volume + ascii_str32("attachment_marker", COMMENT=light_volume_comment), + Bool32("flags", *blend_flags), + Pad(16), + + #Brightness scale + float_wu("near_fade_distance", COMMENT=brightness_scale_comment), + float_wu("far_fade_distance"), + float_zero_to_one("perpendicular_brightness_scale"), + float_zero_to_one("parallel_brightness_scale"), + SEnum16("brightness_scale_source", *function_outputs), + Pad(22), + + #Bitmaps + dependency("map", "bitm", COMMENT=bitmaps_comment), + SInt16("map_sequence_index"), + SInt16("map_count"), + Pad(72), + + #Frame animation + SEnum16("frame_animation_source", *function_outputs, COMMENT=frame_animation_comment), + Pad(102), + + reflexive("frames", frame, 2), + + SIZE=332, + ) + + +def get(): + return mgs2_def + +mgs2_def = TagDef("mgs2", + blam_header("mgs2"), + mgs2_body, + + ext=".light_volume", endian=">", tag_cls=HekTag, + ) diff --git a/reclaimer/mcc_hek/defs/mod2.py b/reclaimer/mcc_hek/defs/mod2.py new file mode 100644 index 00000000..42264d95 --- /dev/null +++ b/reclaimer/mcc_hek/defs/mod2.py @@ -0,0 +1,330 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.mod2 import Mod2Tag +from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant + +def get(): + return mod2_def + +local_marker = Struct('local_marker', + ascii_str32("name"), + dyn_senum16('node_index', + DYN_NAME_PATH="tagdata.nodes.nodes_array[DYN_I].name"), + Pad(2), + + QStruct('rotation', INCLUDE=ijkw_float), + QStruct('translation', INCLUDE=xyz_float), + SIZE=80 + ) + +fast_uncompressed_vertex = QStruct('uncompressed_vertex', + Float('position_x'), Float('position_y'), Float('position_z'), + Float('normal_i'), Float('normal_j'), Float('normal_k'), + Float('binormal_i'), Float('binormal_j'), Float('binormal_k'), + Float('tangent_i'), Float('tangent_j'), Float('tangent_k'), + + Float('u'), Float('v'), + + SInt16('node_0_index'), SInt16('node_1_index'), + Float('node_0_weight'), Float('node_1_weight'), + SIZE=68 + ) + +fast_compressed_vertex = QStruct('compressed_vertex', + Float('position_x'), Float('position_y'), Float('position_z'), + UInt32('normal'), + UInt32('binormal'), + UInt32('tangent'), + + SInt16('u', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), + SInt16('v', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), + + SInt8('node_0_index', UNIT_SCALE=1/3, MIN=0, WIDGET_WIDTH=10), + SInt8('node_1_index', UNIT_SCALE=1/3, MIN=0, WIDGET_WIDTH=10), + SInt16('node_0_weight', UNIT_SCALE=1/32767, MIN=0, WIDGET_WIDTH=10), + SIZE=32 + ) + +uncompressed_vertex = Struct('uncompressed_vertex', + QStruct("position", INCLUDE=xyz_float), + QStruct("normal", INCLUDE=ijk_float), + QStruct("binormal", INCLUDE=ijk_float), + QStruct("tangent", INCLUDE=ijk_float), + + Float('u'), + Float('v'), + + SInt16('node_0_index', + TOOLTIP="If local nodes are used, this is a local index"), + SInt16('node_1_index', + TOOLTIP="If local nodes are used, this is a local index"), + Float('node_0_weight'), + Float('node_1_weight'), + SIZE=68 + ) + +compressed_vertex = Struct('compressed_vertex', + QStruct("position", INCLUDE=xyz_float), + + BBitStruct('normal', INCLUDE=compressed_normal_32, SIZE=4), + BBitStruct('binormal', INCLUDE=compressed_normal_32, SIZE=4), + BBitStruct('tangent', INCLUDE=compressed_normal_32, SIZE=4), + + SInt16('u', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), + SInt16('v', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), + + SInt8('node_0_index', UNIT_SCALE=1/3, MIN=0, WIDGET_WIDTH=10), + SInt8('node_1_index', UNIT_SCALE=1/3, MIN=0, WIDGET_WIDTH=10), + SInt16('node_0_weight', UNIT_SCALE=1/32767, MIN=0, WIDGET_WIDTH=10), + SIZE=32 + ) + +triangle = QStruct('triangle', + SInt16('v0_index'), SInt16('v1_index'), SInt16('v2_index'), + SIZE=6, ORIENT='h' + ) + +uncompressed_vertex_union = Union('uncompressed_vertex', + CASES={'uncompressed_vertex': uncompressed_vertex}, + ) + +compressed_vertex_union = Union('compressed_vertex', + CASES={'compressed_vertex': compressed_vertex}, + ) + +triangle_union = Union('triangle', + CASES={'triangle': triangle}, + ) + + +marker_instance = Struct('marker_instance', + dyn_senum8('region_index', + DYN_NAME_PATH="tagdata.regions.regions_array[DYN_I].name"), + SInt8('permutation_index', MIN=-1), + dyn_senum8('node_index', + DYN_NAME_PATH="tagdata.nodes.nodes_array[DYN_I].name"), + Pad(1), + + QStruct('translation', INCLUDE=xyz_float), + QStruct('rotation', INCLUDE=ijkw_float), + SIZE=32 + ) + +permutation = Struct('permutation', + ascii_str32("name"), + Bool32('flags', + 'cannot_be_chosen_randomly' + ), + # permutations ending with -XXX where XXX is a number will belong to + # the permutation set XXX. Trailing non-numeric characters are ignored. + # Example: + # head_marcus_cap-101asdf + # Will have its permutation_set be set to 101 + # For all parts of a permutation to be properly chosen across + # all regions, they must share a permutation set number. + # Anything not ending in -XXX will have this set to 0. + FlUInt16("permutation_set", VISIBLE=False), # meta only field + Pad(26), + + dyn_senum16('superlow_geometry_block', + DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), + dyn_senum16('low_geometry_block', + DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), + dyn_senum16('medium_geometry_block', + DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), + dyn_senum16('high_geometry_block', + DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), + dyn_senum16('superhigh_geometry_block', + DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), + Pad(2), + + reflexive("local_markers", local_marker, 32, DYN_NAME_PATH=".name"), + SIZE=88 + ) + +part = Struct('part', + Bool32('flags', + 'stripped', + 'ZONER', + ), + dyn_senum16('shader_index', + DYN_NAME_PATH="tagdata.shaders.shaders_array[DYN_I].shader.filepath"), + SInt8('previous_part_index'), + SInt8('next_part_index'), + + SInt16('centroid_primary_node'), + SInt16('centroid_secondary_node'), + Float('centroid_primary_weight'), + Float('centroid_secondary_weight'), + + QStruct('centroid_translation', INCLUDE=xyz_float), + + #reflexive("uncompressed_vertices", uncompressed_vertex_union, 32767), + #reflexive("compressed_vertices", compressed_vertex_union, 32767), + #reflexive("triangles", triangle_union, 32767), + reflexive("uncompressed_vertices", fast_uncompressed_vertex, 32767), + reflexive("compressed_vertices", fast_compressed_vertex, 32767), + reflexive("triangles", triangle, 32767), + #Pad(36), + Struct("model_meta_info", + UEnum16("index_type", # name is a guess. always 1? + ("uncompressed", 1), + ), + Pad(2), + UInt32("index_count"), + # THESE TWO VALUES ARE DIFFERENT THAN ON XBOX IT SEEMS + UInt32("indices_magic_offset"), + UInt32("indices_offset"), + + UEnum16("vertex_type", # name is a guess + ("uncompressed", 4), + ("compressed", 5), + ), + Pad(2), + UInt32("vertex_count"), + Pad(4), # always 0? + # THESE TWO VALUES ARE DIFFERENT THAN ON XBOX IT SEEMS + UInt32("vertices_magic_offset"), + UInt32("vertices_offset"), + VISIBLE=False, SIZE=36 + ), + + Pad(3), + SInt8('local_node_count', MIN=0, MAX=22), + #UInt8Array('local_nodes', SIZE=22), + Array("local_nodes", SUB_STRUCT=UInt8("local_node_index"), SIZE=22), + + # this COULD be 2 more potential local nodes, but I've seen tool + # split models when they reach 22 nodes, so im assuming 22 is the max + Pad(2), + SIZE=132 + ) + +fast_part = desc_variant(part, + ("uncompressed_vertices", raw_reflexive("uncompressed_vertices", fast_uncompressed_vertex, 65535)), + ("compressed_vertices", raw_reflexive("compressed_vertices", fast_compressed_vertex, 65535)), + ("triangles", raw_reflexive("triangles", triangle, 65535)), + ) + +marker = Struct('marker', + ascii_str32("name"), + UInt16('magic_identifier'), + Pad(18), + + reflexive("marker_instances", marker_instance, 32), + SIZE=64 + ) + +node = Struct('node', + ascii_str32("name"), + dyn_senum16('next_sibling_node', DYN_NAME_PATH="..[DYN_I].name"), + dyn_senum16('first_child_node', DYN_NAME_PATH="..[DYN_I].name"), + dyn_senum16('parent_node', DYN_NAME_PATH="..[DYN_I].name"), + Pad(2), + + QStruct('translation', INCLUDE=xyz_float), + QStruct('rotation', INCLUDE=ijkw_float), + Float('distance_from_parent'), + Pad(32), + + # xbox specific values + LFloat('scale', ENDIAN='<', DEFAULT=1.0, VISIBLE=False), + QStruct("rot_jj_kk", GUI_NAME="[1-2j^2-2k^2] 2[ij+kw] 2[ik-jw]", + INCLUDE=ijk_float, ENDIAN='<', VISIBLE=False), + QStruct("rot_kk_ii", GUI_NAME="2[ij-kw] [1-2k^2-2i^2] 2[jk+iw]", + INCLUDE=ijk_float, ENDIAN='<', VISIBLE=False), + QStruct("rot_ii_jj", GUI_NAME="2[ik+jw] 2[jk-iw] [1-2i^2-2j^2]", + INCLUDE=ijk_float, ENDIAN='<', VISIBLE=False), + QStruct('translation_to_root', + INCLUDE=xyz_float, ENDIAN='<', VISIBLE=False), + SIZE=156, + ) + +region = Struct('region', + ascii_str32("name"), + Pad(32), + reflexive("permutations", permutation, 32, DYN_NAME_PATH=".name"), + SIZE=76 + ) + +geometry = Struct('geometry', + Pad(36), + reflexive("parts", part, 32), + SIZE=48 + ) + +fast_geometry = Struct('geometry', + Pad(36), + reflexive("parts", fast_part, 32), + SIZE=48 + ) + +shader = Struct('shader', + dependency("shader", valid_shaders), + SInt16('permutation_index'), + SIZE=32, + ) + + +mod2_body = Struct('tagdata', + Bool32('flags', + 'blend_shared_normals', + 'parts_have_local_nodes', + 'ignore_skinning' + ), + SInt32('node_list_checksum'), + + Float('superhigh_lod_cutoff', SIDETIP="pixels"), + Float('high_lod_cutoff', SIDETIP="pixels"), + Float('medium_lod_cutoff', SIDETIP="pixels"), + Float('low_lod_cutoff', SIDETIP="pixels"), + Float('superlow_lod_cutoff', SIDETIP="pixels"), + + SInt16('superlow_lod_nodes', SIDETIP="nodes"), + SInt16('low_lod_nodes', SIDETIP="nodes"), + SInt16('medium_lod_nodes', SIDETIP="nodes"), + SInt16('high_lod_nodes', SIDETIP="nodes"), + SInt16('superhigh_lod_nodes', SIDETIP="nodes"), + + Pad(10), + + Float('base_map_u_scale'), + Float('base_map_v_scale'), + + Pad(116), + + reflexive("markers", marker, 256, DYN_NAME_PATH=".name"), + reflexive("nodes", node, 64, DYN_NAME_PATH=".name"), + reflexive("regions", region, 32, DYN_NAME_PATH=".name"), + reflexive("geometries", geometry, 256), + reflexive("shaders", shader, 256, DYN_NAME_PATH=".shader.filepath"), + + SIZE=232 + ) + +fast_mod2_body = desc_variant(mod2_body, + ("geometries", reflexive("geometries", fast_geometry, 256)), + ) + +mod2_def = TagDef("mod2", + blam_header('mod2', 5), + mod2_body, + + ext=".gbxmodel", endian=">", tag_cls=Mod2Tag + ) + +fast_mod2_def = TagDef("mod2", + blam_header('mod2', 5), + fast_mod2_body, + + ext=".gbxmodel", endian=">", tag_cls=Mod2Tag + ) diff --git a/reclaimer/mcc_hek/defs/mode.py b/reclaimer/mcc_hek/defs/mode.py new file mode 100644 index 00000000..2b67917c --- /dev/null +++ b/reclaimer/mcc_hek/defs/mode.py @@ -0,0 +1,173 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .mod2 import * +from .objs.mode import ModeTag +from supyr_struct.util import desc_variant + +def get(): + return mode_def + +permutation = Struct('permutation', + ascii_str32("name"), + Bool32('flags', + 'cannot_be_chosen_randomly' + ), + # permutations ending with -XXX where XXX is a number will belong to + # the permutation set XXX. Trailing non-numeric characters are ignored. + # Example: + # head_marcus_cap-101asdf + # Will have its permutation_set be set to 101 + # For all parts of a permutation to be properly chosen across + # all regions, they must share a permutation set number. + # Anything not ending in -XXX will have this set to 0. + FlUInt16("permutation_set", VISIBLE=False), # meta only field + Pad(26), + + dyn_senum16('superlow_geometry_block', + DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), + dyn_senum16('low_geometry_block', + DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), + dyn_senum16('medium_geometry_block', + DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), + dyn_senum16('high_geometry_block', + DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), + dyn_senum16('superhigh_geometry_block', + DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), + Pad(2), + + reflexive("local_markers", local_marker, 32, DYN_NAME_PATH=".name"), + SIZE=88 + ) + +part = Struct('part', + Bool32('flags', + 'stripped', + ), + dyn_senum16('shader_index', + DYN_NAME_PATH="tagdata.shaders.shaders_array[DYN_I].shader.filepath"), + SInt8('previous_part_index'), + SInt8('next_part_index'), + + SInt16('centroid_primary_node'), + SInt16('centroid_secondary_node'), + Float('centroid_primary_weight'), + Float('centroid_secondary_weight'), + + QStruct('centroid_translation', INCLUDE=xyz_float), + + #reflexive("uncompressed_vertices", uncompressed_vertex_union, 65535), + #reflexive("compressed_vertices", compressed_vertex_union, 65535), + #reflexive("triangles", triangle_union, 65535), + reflexive("uncompressed_vertices", fast_uncompressed_vertex, 65535), + reflexive("compressed_vertices", fast_compressed_vertex, 65535), + reflexive("triangles", triangle, 65535), + + #Pad(36), + Struct("model_meta_info", + UEnum16("index_type", # name is a guess. always 1? + ("uncompressed", 1), + ), + Pad(2), + UInt32("index_count"), + UInt32("indices_offset"), + UInt32("indices_reflexive_offset"), + + UEnum16("vertex_type", # name is a guess + ("uncompressed", 4), + ("compressed", 5), + ), + Pad(2), + UInt32("vertex_count"), + Pad(4), # always 0? + UInt32("vertices_offset"), + UInt32("vertices_reflexive_offset"), + VISIBLE=False, SIZE=36 + ), + + SIZE=104 + ) + +fast_part = desc_variant(part, + ("uncompressed_vertices", raw_reflexive("uncompressed_vertices", fast_uncompressed_vertex, 65535)), + ("compressed_vertices", raw_reflexive("compressed_vertices", fast_compressed_vertex, 65535)), + ("triangles", raw_reflexive("triangles", triangle, 65535)), + ) + +region = Struct('region', + ascii_str32("name"), + Pad(32), + reflexive("permutations", permutation, 32, DYN_NAME_PATH=".name"), + SIZE=76 + ) + +geometry = Struct('geometry', + Pad(36), + reflexive("parts", part, 32), + SIZE=48 + ) + +fast_geometry = Struct('geometry', + Pad(36), + reflexive("parts", fast_part, 32), + SIZE=48 + ) + +mode_body = Struct('tagdata', + Bool32('flags', + 'blend_shared_normals', + ), + SInt32('node_list_checksum'), + + # xbox has these values swapped around in order + Float('superlow_lod_cutoff', SIDETIP="pixels"), + Float('low_lod_cutoff', SIDETIP="pixels"), + Float('medium_lod_cutoff', SIDETIP="pixels"), + Float('high_lod_cutoff', SIDETIP="pixels"), + Float('superhigh_lod_cutoff', SIDETIP="pixels"), + + SInt16('superlow_lod_nodes', SIDETIP="nodes"), + SInt16('low_lod_nodes', SIDETIP="nodes"), + SInt16('medium_lod_nodes', SIDETIP="nodes"), + SInt16('high_lod_nodes', SIDETIP="nodes"), + SInt16('superhigh_lod_nodes', SIDETIP="nodes"), + + Pad(10), + + Float('base_map_u_scale'), + Float('base_map_v_scale'), + + Pad(116), + + reflexive("markers", marker, 256, DYN_NAME_PATH=".name"), + reflexive("nodes", node, 64, DYN_NAME_PATH=".name"), + reflexive("regions", region, 32, DYN_NAME_PATH=".name"), + reflexive("geometries", geometry, 256), + reflexive("shaders", shader, 256, DYN_NAME_PATH=".shader.filepath"), + + SIZE=232 + ) + +fast_mode_body = desc_variant(mode_body, + ("geometries", reflexive("geometries", fast_geometry, 256)), + ) + +mode_def = TagDef("mode", + blam_header('mode', 4), + mode_body, + + ext=".model", endian=">", tag_cls=ModeTag + ) + +fast_mode_def = TagDef("mode", + blam_header('mode', 4), + fast_mode_body, + + ext=".model", endian=">", tag_cls=ModeTag + ) diff --git a/reclaimer/mcc_hek/defs/mply.py b/reclaimer/mcc_hek/defs/mply.py new file mode 100644 index 00000000..0f52302e --- /dev/null +++ b/reclaimer/mcc_hek/defs/mply.py @@ -0,0 +1,37 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + + +scenario_description = Struct("scenario_description", + dependency("descriptive_bitmap", "bitm"), + dependency("displayed_map_name", "ustr"), + ascii_str32("scenario_tag_directory_path"), + SIZE=68 + ) + +mply_body = Struct("tagdata", + reflexive("multiplayer_scenario_descriptions", + scenario_description, 32, DYN_NAME_PATH='.scenario_tag_directory_path'), + SIZE=12, + ) + + +def get(): + return mply_def + +mply_def = TagDef("mply", + blam_header('mply'), + mply_body, + + ext=".multiplayer_scenario_description", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/ngpr.py b/reclaimer/mcc_hek/defs/ngpr.py new file mode 100644 index 00000000..0c2a99f2 --- /dev/null +++ b/reclaimer/mcc_hek/defs/ngpr.py @@ -0,0 +1,36 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +ngpr_body = Struct("tagdata", + ascii_str32("name"), + QStruct("primary_color", INCLUDE=rgb_float), + QStruct("secondary_color", INCLUDE=rgb_float), + + dependency("pattern", "bitm"), + SInt16("pattern_bitmap_index"), + Pad(2), + dependency("decal", "bitm"), + SInt16("decal_bitmap_index"), + SIZE=896 + ) + + +def get(): + return ngpr_def + +ngpr_def = TagDef("ngpr", + blam_header('ngpr', 2), + ngpr_body, + + ext=".preferences_network_game", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/obje.py b/reclaimer/mcc_hek/defs/obje.py new file mode 100644 index 00000000..7cbf0ef6 --- /dev/null +++ b/reclaimer/mcc_hek/defs/obje.py @@ -0,0 +1,151 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.obje import ObjeTag +from supyr_struct.defs.tag_def import TagDef + +def get(): + return obje_def + +attachment = Struct('attachment', + dependency('type', valid_attachments), + ascii_str32('marker'), + SEnum16('primary_scale', *function_outputs), + SEnum16('secondary_scale', *function_outputs), + SEnum16('change_color', *function_names), + + SIZE=72 + ) + +widget = Struct('widget', + dependency('reference', valid_widgets), + SIZE=32 + ) + +function = Struct('function', + Bool32('flags', + 'invert', + 'additive', + 'always_active', + ), + float_sec('period', UNIT_SCALE=sec_unit_scale), # seconds + SEnum16('scale_period_by', *function_inputs_outputs), + SEnum16('function', *animation_functions), + SEnum16('scale_function_by', *function_inputs_outputs), + SEnum16('wobble_function', *animation_functions), + float_sec('wobble_period', UNIT_SCALE=sec_unit_scale), # seconds + Float('wobble_magnitude', SIDETIP="%"), # percent + + Float('square_wave_threshold'), + SInt16('step_count'), + SEnum16('map_to', *fade_functions), + SInt16('sawtooth_count'), + SEnum16('add', *function_inputs_outputs), + SEnum16('scale_result_by', *function_inputs_outputs), + SEnum16('bounds_mode', + 'clip', + 'clip_and_normalize', + 'scale_to_fit', + ), + QStruct('bounds', INCLUDE=from_to), + + Pad(6), + dyn_senum16('turn_off_with', DYN_NAME_PATH="..[DYN_I].usage"), + Float('scale_by'), + + Pad(268), + ascii_str32('usage'), + + SIZE=360 + ) + +permutation = Struct('permutation', + Float('weight'), + QStruct('color_lower_bound', INCLUDE=rgb_float), + QStruct('color_upper_bound', INCLUDE=rgb_float), + + SIZE=28 + ) + +change_color = Struct('change_color', + SEnum16('darken_by', *function_inputs_outputs), + SEnum16('scale_by', *function_inputs_outputs), + Bool32('flags', + 'blend_in_hsv', + 'more_colors', + ), + QStruct('color_lower_bound', INCLUDE=rgb_float), + QStruct('color_upper_bound', INCLUDE=rgb_float), + reflexive("permutations", permutation, 8), + + SIZE=44 + ) + +obje_attrs = Struct('obje_attrs', + FlSEnum16("object_type", + *((object_types[i], i - 1) for i in + range(len(object_types))), + VISIBLE=False, DEFAULT=-1 + ), + Bool16('flags', + 'does_not_cast_shadow', + 'transparent_self_occlusion', + 'brighter_than_it_should_be', + 'not_a_pathfinding_obstacle', + {NAME: 'xbox_unknown_bit_8', VALUE: 1<<8, VISIBLE: False}, + {NAME: 'xbox_unknown_bit_11', VALUE: 1<<11, VISIBLE: False}, + ), + float_wu('bounding_radius'), + QStruct('bounding_offset', INCLUDE=xyz_float), + QStruct('origin_offset', INCLUDE=xyz_float), + float_zero_to_inf('acceleration_scale', UNIT_SCALE=per_sec_unit_scale), + + Pad(4), + dependency('model', valid_models), + dependency('animation_graph', "antr"), + + Pad(40), + dependency('collision_model', "coll"), + dependency('physics', "phys"), + dependency('modifier_shader', valid_shaders), + dependency('creation_effect', "effe"), + Pad(84), + float_wu('render_bounding_radius'), + + #Export to functions + SEnum16('A_in', *object_export_to), + SEnum16('B_in', *object_export_to), + SEnum16('C_in', *object_export_to), + SEnum16('D_in', *object_export_to), + + Pad(44), + SInt16('hud_text_message_index'), + SInt16('forced_shader_permutation_index'), + reflexive("attachments", attachment, 8, DYN_NAME_PATH='.type.filepath'), + reflexive("widgets", widget, 4, DYN_NAME_PATH='.reference.filepath'), + reflexive("functions", function, 4, DYN_NAME_PATH='.usage'), + reflexive("change_colors", change_color, 4, + 'A', 'B', 'C', 'D'), + reflexive("predicted_resources", predicted_resource, 1024, VISIBLE=False), + + SIZE=380 + ) + +obje_body = Struct('tagdata', + obje_attrs, + SIZE=380 + ) + +obje_def = TagDef("obje", + blam_header('obje'), + obje_body, + + ext=".object", endian=">", tag_cls=ObjeTag + ) diff --git a/reclaimer/mcc_hek/defs/objs/__init__.py b/reclaimer/mcc_hek/defs/objs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/reclaimer/mcc_hek/defs/objs/actr.py b/reclaimer/mcc_hek/defs/objs/actr.py new file mode 100644 index 00000000..762f03b1 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/actr.py @@ -0,0 +1,40 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from math import cos + +from reclaimer.hek.defs.objs.tag import HekTag + +class ActrTag(HekTag): + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + perception = self.data.tagdata.perception + looking = self.data.tagdata.looking + + perception.inv_combat_perception_time = 0 + perception.inv_guard_perception_time = 0 + perception.inv_non_combat_perception_time = 0 + + if perception.combat_perception_time: + perception.inv_combat_perception_time = 1 / perception.combat_perception_time + + if perception.guard_perception_time: + perception.inv_guard_perception_time = 1 / perception.guard_perception_time + + if perception.non_combat_perception_time: + perception.inv_non_combat_perception_time = 1 / perception.non_combat_perception_time + + perception.inv_combat_perception_time /= 30 + perception.inv_guard_perception_time /= 30 + perception.inv_non_combat_perception_time /= 30 + + for i in range(2): + looking.cosine_maximum_aiming_deviation[i] = cos(looking.maximum_aiming_deviation[i]) + looking.cosine_maximum_looking_deviation[i] = cos(looking.maximum_looking_deviation[i]) diff --git a/reclaimer/mcc_hek/defs/objs/ant_.py b/reclaimer/mcc_hek/defs/objs/ant_.py new file mode 100644 index 00000000..6a343b98 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/ant_.py @@ -0,0 +1,31 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from math import sin, cos + +from reclaimer.hek.defs.objs.tag import HekTag + +class Ant_Tag(HekTag): + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + + tagdata = self.data.tagdata + tagdata.length = 0 + for vertex in tagdata.vertices.STEPTREE: + sin_y = sin(vertex.angles.y) + sin_p = sin(vertex.angles.p) + cos_y = cos(vertex.angles.y) + cos_p = cos(vertex.angles.p) + + vertex.offset.x = vertex.length * sin_p * cos_y + vertex.offset.y = vertex.length * sin_y * sin_p + vertex.offset.z = vertex.length * cos_p + + tagdata.length += vertex.length diff --git a/reclaimer/mcc_hek/defs/objs/antr.py b/reclaimer/mcc_hek/defs/objs/antr.py new file mode 100644 index 00000000..62eaa987 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/antr.py @@ -0,0 +1,13 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.tag import HekTag + +class AntrTag(HekTag): + jma_nodes = () diff --git a/reclaimer/mcc_hek/defs/objs/bipd.py b/reclaimer/mcc_hek/defs/objs/bipd.py new file mode 100644 index 00000000..3c4c2e6a --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/bipd.py @@ -0,0 +1,34 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from math import sin, cos + +from reclaimer.hek.defs.objs.obje import ObjeTag + +class BipdTag(ObjeTag): + + def calc_internal_data(self): + ObjeTag.calc_internal_data(self) + bipd_attrs = self.data.tagdata.bipd_attrs + movement = bipd_attrs.movement + physics = bipd_attrs.physics + + physics.cosine_stationary_turning_threshold = cos(bipd_attrs.stationary_turning_threshold) + + physics.cosine_maximum_slope_angle = cos(movement.maximum_slope_angle) + physics.neg_sine_downhill_falloff_angle = -sin(movement.downhill_falloff_angle) + physics.neg_sine_downhill_cutoff_angle = -sin(movement.downhill_cutoff_angle) + physics.sine_uphill_falloff_angle = sin(movement.uphill_falloff_angle) + physics.sine_uphill_cutoff_angle = sin(movement.uphill_cutoff_angle) + + physics.crouch_camera_velocity = 0 + if physics.crouch_camera_velocity: + physics.crouch_camera_velocity /= bipd_attrs.camera_collision_and_autoaim.crouch_transition_time + + physics.crouch_camera_velocity /= 30 diff --git a/reclaimer/mcc_hek/defs/objs/bitm.py b/reclaimer/mcc_hek/defs/objs/bitm.py new file mode 100644 index 00000000..cc1b5b0f --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/bitm.py @@ -0,0 +1,483 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from array import array +from reclaimer.constants import TYPE_CUBEMAP, CUBEMAP_PADDING, BITMAP_PADDING,\ + FORMAT_NAME_MAP, TYPE_NAME_MAP, FORMAT_P8_BUMP +from reclaimer.bitmaps.p8_palette import HALO_P8_PALETTE +from reclaimer.hek.defs.objs.tag import HekTag + +try: + import arbytmap as ab + + if not hasattr(ab, "FORMAT_P8_BUMP"): + ab.FORMAT_P8_BUMP = "P8-BUMP" + + """ADD THE P8 FORMAT TO THE BITMAP CONVERTER""" + ab.register_format(format_id=ab.FORMAT_P8_BUMP, depths=(8,8,8,8)) +except (ImportError, AttributeError): + ab = None + + +class BitmTag(HekTag): + tex_infos = () + p8_palette = None + + def __init__(self, *args, **kwargs): + HekTag.__init__(self, *args, **kwargs) + self.p8_palette = HALO_P8_PALETTE + + def bitmap_count(self, new_value=None): + if new_value is None: + return self.data.tagdata.bitmaps.size + self.data.tagdata.bitmaps.size = new_value + + def bitmap_width(self, b_index=0, new_value=None): + if new_value is None: + return self.data.tagdata.bitmaps.bitmaps_array[b_index].width + self.data.tagdata.bitmaps.bitmaps_array[b_index].width = new_value + + def bitmap_height(self, b_index=0, new_value=None): + if new_value is None: + return self.data.tagdata.bitmaps.bitmaps_array[b_index].height + self.data.tagdata.bitmaps.bitmaps_array[b_index].height = new_value + + def bitmap_depth(self, b_index=0, new_value=None): + if new_value is None: + return self.data.tagdata.bitmaps.bitmaps_array[b_index].depth + self.data.tagdata.bitmaps.bitmaps_array[b_index].depth = new_value + + def bitmap_mipmaps_count(self, b_index=0, new_value=None): + if new_value is None: + return self.data.tagdata.bitmaps.bitmaps_array[b_index].mipmaps + self.data.tagdata.bitmaps.bitmaps_array[b_index].mipmaps = new_value + + def bitmap_type(self, b_index=0, new_value=None): + if new_value is None: + return self.data.tagdata.bitmaps.bitmaps_array[b_index].type.data + self.data.tagdata.bitmaps.bitmaps_array[b_index].type.data = new_value + + def bitmap_format(self, b_index=0, new_value=None): + if new_value is None: + return self.data.tagdata.bitmaps.bitmaps_array[b_index].format.data + self.data.tagdata.bitmaps.bitmaps_array[b_index].format.data = new_value + + def fix_top_format(self): + if len(self.data.tagdata.bitmaps.bitmaps_array) <= 0: + self.data.tagdata.format.data = "color_key_transparency" + + # Why can't get_name get the name of the current option? + pixel_format = self.data.tagdata.bitmaps.bitmaps_array[0].format.get_name( + self.data.tagdata.bitmaps.bitmaps_array[0].format.data) + + top_format = "color_key_transparency" + if pixel_format in ("a8", "y8", "ay8", "a8y8"): + top_format = "monochrome" + elif pixel_format in ("r5g6b5", "a1r5g5b5", "a4r4g4b4"): + top_format = "color_16bit" + elif pixel_format in ("x8r8g8b8", "a8r8g8b8", "p8_bump"): + top_format = "color_32bit" + elif pixel_format == "dxt1": + top_format = "color_key_transparency" + elif pixel_format == "dxt3": + top_format = "explicit_alpha" + elif pixel_format == "dxt5": + top_format = "interpolated_alpha" + + self.data.tagdata.format.set_to(top_format) + + def bitmap_width_height_depth(self, b_index=0, new_value=None): + bitmap = self.data.tagdata.bitmaps.bitmaps_array[b_index] + if new_value is None: + return(bitmap.width, bitmap.height, bitmap.depth) + bitmap.width, bitmap.height, bitmap.depth = ( + new_value[0], new_value[1], new_value[2]) + + def bitmap_flags(self, b_index=0, new_value=None): + if new_value is None: + return self.data.tagdata.bitmaps.bitmaps_array[b_index].flags + self.data.tagdata.bitmaps.bitmaps_array[b_index].flags = new_value + + def bitmap_base_address(self, b_index=0, new_value=None): + bitm = self.data.tagdata.bitmaps.bitmaps_array[b_index] + if new_value is None: + return(bitm.base_address) + bitm.base_address=new_value + + def bitmap_data_offset(self, b_index=0, new_value=None): + bitm = self.data.tagdata.bitmaps.bitmaps_array[b_index] + if new_value is None: + return(bitm.pixels_offset) + bitm.pixels_offset=new_value + + def registration_point_x(self, b_index=0, new_value=None): + bitm = self.data.tagdata.bitmaps.bitmaps_array[b_index] + if new_value is None: + return bitm.registration_point_x + bitm.registration_point_x = new_value + + def registration_point_y(self, b_index=0, new_value=None): + bitm = self.data.tagdata.bitmaps.bitmaps_array[b_index] + if new_value is None: + return bitm.registration_point_y + bitm.registration_point_y = new_value + + def registration_point_xy(self, b_index=0, new_value=None): + bitm = self.data.tagdata.bitmaps.bitmaps_array[b_index] + if new_value is None: + return(bitm.registration_point_x, + bitm.registration_point_y) + bitm.registration_point_x, bitm.registration_point_y = new_value[0],\ + new_value[1] + + @property + def is_xbox_bitmap(self): + # we only need to check the first bitmap + if not self.bitmap_count(): return False + return self.bitmap_base_address() == 1073751810 + + def is_power_of_2_bitmap(self, b_index=0): + if not self.bitmap_count(): return False + return self.bitmap_flags(b_index).power_of_2_dim + + def is_compressed_bitmap(self, b_index=0): + if not self.bitmap_count(): return False + return self.bitmap_flags(b_index).compressed + + def swizzled(self, b_index=0, new_flag=None): + if new_flag is None: + if not self.bitmap_count(): return False + return self.bitmap_flags(b_index).swizzled + if not self.bitmap_count(): return + self.bitmap_flags(b_index).swizzled = new_flag + + def color_plate_data_bytes_size(self, new_value=None): + if new_value is None: + return(self.data.tagdata.compressed_color_plate_data.size) + self.data.tagdata.compressed_color_plate_data.size = new_value + + def pixel_data_bytes_size(self, new_value=None): + if new_value is None: + return self.data.tagdata.processed_pixel_data.size + self.data.tagdata.processed_pixel_data.size = new_value + + def set_platform(self, saveasxbox): + '''changes different things to set the platform to either PC or Xbox''' + # read each of the bitmap blocks + for b_index in range(self.bitmap_count()): + bitmap = self.data.tagdata.bitmaps.bitmaps_array[b_index] + + bitmap.flags.prefer_low_detail = saveasxbox + + '''base_address is the ONLY discernable difference + between a bitmap made by arsenic from a PC map, and + a bitmap made by arsenic from an original XBOX map''' + if saveasxbox: + # change some miscellaneous variables + bitmap.pixels = 18 + bitmap.bitmap_data_pointer = 0xFFFFFFFF + bitmap.base_address = 1073751810 + else: + bitmap.base_address = 0 + + if not saveasxbox: + return + + # if Xbox, reset these structure variable's all to 0 + # since xbox doesn't like them being non-zero + tagdata = self.data.tagdata + tagdata.compressed_color_plate_data.flags.data = 0 + tagdata.processed_pixel_data.flags.data = 0 + for i in (2,3): + tagdata.compressed_color_plate_data[i] = 0 + tagdata.processed_pixel_data[i] = 0 + + for i in (1,2): + tagdata.sequences[i] = 0 + tagdata.bitmaps[i] = 0 + + # swap the order of the cubemap faces + # and mipmaps if saving to xbox format + self.change_sub_bitmap_ordering(saveasxbox) + + def change_sub_bitmap_ordering(self, saveasxbox): + '''Used to change the mipmap and cube face ordering. + On pc all highest resolution faces are first, then + the next highest resolution mipmap set. On xbox it's + all of a face's mipmaps before any of the other faces. + + DO NOT UNDER ANY CIRCUMSTANCES CALL THIS FUNCTION + IF PADDING HAS ALREADY BEEN ADDED TO A BITMAP''' + + raw_bitmap_data = self.data.tagdata.processed_pixel_data.data + + # loop over each of the bitmap blocks + for b_index in range(self.bitmap_count()): + if self.bitmap_type(b_index) == TYPE_CUBEMAP: + mipmap_count = self.bitmap_mipmaps_count(b_index) + 1 + tex_block = raw_bitmap_data[b_index] + + # this will be used to copy values from + template = tex_block.__copy__() + + # this is used to keep track of which index + # we're placing the new pixel array into + i = 0 + + '''since we also want to swap the second and third + cubemap faces we can do that easily like this. + xbox has the second and third cubemap faces swapped + with each other compared to pc. IDFKY''' + for face in (0, 2, 1, 3, 4, 5): + for mip in range(0, mipmap_count*6, 6): + '''get the block we want from the original + layout and place it in its new position''' + if saveasxbox: + tex_block[i] = template[mip + face] + else: + tex_block[mip + face] = template[i] + i += 1 + + def add_bitmap_padding(self, save_as_xbox): + '''This function will create and apply padding to each of the + bitmaps in the tag to make it XBOX compatible. This function will + also add the number of bytes of padding to the internal offsets''' + + """The offset of each bitmap's pixel data needs to be increased by + the padding of all the bitmaps before it. This variable will be + used for knowing the total amount of padding before each bitmap. + + DO NOT RUN IF A BITMAP ALREADY HAS PADDING.""" + total_data_size = 0 + if ab is None: + raise NotImplementedError( + "Arbytmap is not loaded. Cannot add padding.") + + for i in range(self.bitmap_count()): + sub_bitmap_count = 1 + if self.bitmap_type(i) == TYPE_CUBEMAP: + sub_bitmap_count = 6 + + pixel_data_block = self.data.tagdata.processed_pixel_data.data[i] + + # apply the offset to the tag + self.bitmap_data_offset(i, total_data_size) + + if save_as_xbox or self.bitmap_format(i) == FORMAT_P8_BUMP: + # calculate how much padding to add to the xbox bitmaps + bitmap_pad, cubemap_pad = self.get_padding_size(i) + + # if this bitmap has padding on each of the sub-bitmaps + if cubemap_pad: + mipmap_count = self.bitmap_mipmaps_count(i) + 1 + for j in range(0, 6*(mipmap_count + 1), mipmap_count + 1): + pad = bytearray(cubemap_pad) + if isinstance(pixel_data_block[0], array): + pad = array('B', pad) + pixel_data_block.insert(j + mipmap_count, pad) + + # add the main padding to the end of the bitmap block + pad = bytearray(bitmap_pad) + if isinstance(pixel_data_block[0], array): + pad = array('B', pad) + pixel_data_block.append(pad) + + # add the number of bytes this bitmap is to the + # total bytes so far(multiple by sub-bitmap count) + for pixel_data in pixel_data_block: + if isinstance(pixel_data, array): + total_data_size += len(pixel_data) * pixel_data.itemsize + else: + total_data_size += len(pixel_data) + + # update the total number of bytes of pixel data + # in the tag by all the padding that was added + self.pixel_data_bytes_size(total_data_size) + + def get_bitmap_size(self, b_index): + '''Given a bitmap index, this function will + calculate how many bytes the data takes up. + THIS FUNCTION WILL NOT TAKE INTO ACCOUNT THE NUMBER OF SUB-BITMAPS''' + if ab is None: + raise NotImplementedError( + "Arbytmap is not loaded. Cannot get bitmap size.") + + w, h, d, = self.bitmap_width_height_depth(b_index) + fmt = FORMAT_NAME_MAP[self.bitmap_format(b_index)] + + bytes_count = 0 + for mipmap in range(self.bitmap_mipmaps_count(b_index) + 1): + mw, mh, md = ab.get_mipmap_dimensions(w, h, d, mipmap) + if fmt == ab.FORMAT_P8_BUMP: + bytes_count += mw*mh*md + else: + bytes_count += ab.bitmap_io.get_pixel_bytes_size(fmt, mw, mh, md) + + return bytes_count + + def get_padding_size(self, b_index): + bytes_count = self.get_bitmap_size(b_index) + cubemap_pad = 0 + + if self.bitmap_type(b_index) == TYPE_CUBEMAP: + cubemap_pad = ((CUBEMAP_PADDING - (bytes_count % CUBEMAP_PADDING)) + % CUBEMAP_PADDING) + bytes_count = (bytes_count + cubemap_pad) * 6 + + bitmap_pad = (BITMAP_PADDING - + (bytes_count%BITMAP_PADDING)) % BITMAP_PADDING + + return bitmap_pad, cubemap_pad + + def sanitize_mipmap_counts(self): + '''Some original xbox bitmaps have fudged up mipmap counts + and cause issues. This function will scan through all a + bitmap's bitmaps and check that they fit within their + calculated pixel data bounds. This is done by checking if a + bitmap's calculated size is both within the side of the total + pixel data and less than the next bitmap's pixel data start''' + + bad_bitmap_index_list = [] + bitmap_count = self.bitmap_count() + + for i in range(bitmap_count): + # if this is the last bitmap + if i + 1 == bitmap_count: + # this is how many bytes of texture data there is total + max_size = self.pixel_data_bytes_size() + else: + # this is the start of the next bitmap's pixel data + max_size = self.bitmap_data_offset(i+1) + + while True: + mipmap_count = self.bitmap_mipmaps_count(i) + curr_size = self.get_bitmap_size(i) + self.bitmap_data_offset(i) + + if curr_size <= max_size: + break + + self.bitmap_mipmaps_count(i, mipmap_count - 1) + + # the mipmap count is zero and the bitmap still will + # not fit within the space provided. Something's wrong + if mipmap_count == 0: + bad_bitmap_index_list.append(i) + break + + return bad_bitmap_index_list + + def sanitize_bitmaps(self): + if ab is None: + raise NotImplementedError( + "Arbytmap is not loaded. Cannot sanitize bitmaps.") + tex_infos = self.tex_infos + + for i in range(self.bitmap_count()): + format = FORMAT_NAME_MAP[self.bitmap_format(i)] + flags = self.bitmap_flags(i) + old_w, old_h, _ = self.bitmap_width_height_depth(i) + + reg_point_x, reg_point_y = self.registration_point_xy(i) + texinfo = tex_infos[i] + + # set the flags to the new value + flags.palletized = (format == ab.FORMAT_P8_BUMP) + flags.compressed = (format in ab.COMPRESSED_FORMATS) + + self.bitmap_width_height_depth( + i, (texinfo["width"], texinfo["height"], texinfo["depth"])) + self.bitmap_mipmaps_count(i, texinfo["mipmap_count"]) + self.registration_point_xy(i, (texinfo["width"]*reg_point_x//old_w, + texinfo["height"]*reg_point_y//old_h)) + + def parse_bitmap_blocks(self): + '''converts the raw pixel data into arrays of pixel + data and replaces the raw data in the tag with them''' + if ab is None: + raise NotImplementedError( + "Arbytmap is not loaded. Cannot parse bitmaps.") + + pixel_data = self.data.tagdata.processed_pixel_data + rawdata = pixel_data.data + + tex_infos = self.tex_infos = [] + + # this is the block that will hold all of the bitmap blocks + root_tex_block = self.definition.subdefs['pixel_root'].build() + + is_xbox = self.is_xbox_bitmap + get_mip_dims = ab.get_mipmap_dimensions + bytes_to_array = ab.bitmap_io.bitmap_bytes_to_array + + # read the pixel data blocks for each bitmap + for i in range(self.bitmap_count()): + # since we need this information to read the bitmap we extract it + mw, mh, md, = self.bitmap_width_height_depth(i) + type = self.bitmap_type(i) + format = FORMAT_NAME_MAP[self.bitmap_format(i)] + mipmap_count = self.bitmap_mipmaps_count(i) + 1 + sub_bitmap_count = ab.SUB_BITMAP_COUNTS[TYPE_NAME_MAP[type]] + + # Get the offset of the pixel data for + # this bitmap within the raw pixel data + off = self.bitmap_data_offset(i) + + # this texture info is used in manipulating the texture data + tex_infos.append(dict( + width=mw, height=mh, depth=md, format=format, + mipmap_count=mipmap_count-1, sub_bitmap_count=sub_bitmap_count, + swizzled=self.swizzled(), texture_type=TYPE_NAME_MAP[type])) + + if format == ab.FORMAT_P8_BUMP: + tex_infos[-1]["palette"] = [ + self.p8_palette.p8_palette_32bit_packed]*mipmap_count + + # set it to packed since if we need to drop channels + # then it needs to be unpacked with channels dropped + tex_infos[-1]["palette_packed"] = True + tex_infos[-1]["indexing_size"] = 8 + + # this is the block that will hold each mipmap, + # texture slice, and cube face of the bitmap + root_tex_block.append() + tex_block = root_tex_block[-1] + + # xbox bitmaps are stored all mip level faces first, then + # the next mip level, whereas pc is the other way. Xbox + # bitmaps also have padding between each mipmap and bitmap. + dim0 = sub_bitmap_count if is_xbox else mipmap_count + dim1 = mipmap_count if is_xbox else sub_bitmap_count + for j in range(dim0): + if not is_xbox: w, h, d = get_mip_dims(mw, mh, md, j) + + for k in range(dim1): + if is_xbox: w, h, d = get_mip_dims(mw, mh, md, k) + + if format == ab.FORMAT_P8_BUMP: + pixel_count = w*h + tex_block.append(array('B', rawdata[off: off+pixel_count])) + off += pixel_count + continue + + off = bytes_to_array(rawdata, off, tex_block, format, w, h, d) + + # skip the xbox alignment padding to get to the next texture + if is_xbox: + tex_pad, sub_tex_pad = self.get_padding_size(i) + off += sub_tex_pad + if j + 1 == dim0: + off += tex_pad + + pixel_data.data = root_tex_block + # now that we've successfully built the bitmap + # blocks from the raw data we replace the raw data + if is_xbox: + # it's easier to work with bitmaps in one format so + # we'll switch the mipmaps from XBOX to PC ordering + self.change_sub_bitmap_ordering(False) diff --git a/reclaimer/mcc_hek/defs/objs/coll.py b/reclaimer/mcc_hek/defs/objs/coll.py new file mode 100644 index 00000000..f829ac6c --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/coll.py @@ -0,0 +1,21 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.tag import HekTag + +class CollTag(HekTag): + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + + shield = self.data.tagdata.shield + shield.shield_recharge_rate = 0 + if shield.recharge_time: + shield.shield_recharge_rate = 1 / shield.recharge_time + shield.shield_recharge_rate /= 30 diff --git a/reclaimer/mcc_hek/defs/objs/ctrl.py b/reclaimer/mcc_hek/defs/objs/ctrl.py new file mode 100644 index 00000000..10fa9888 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/ctrl.py @@ -0,0 +1,17 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.obje import ObjeTag +from reclaimer.hek.defs.objs.devi import DeviTag + +class CtrlTag(DeviTag, ObjeTag): + + def calc_internal_data(self): + ObjeTag.calc_internal_data(self) + DeviTag.calc_internal_data(self) diff --git a/reclaimer/mcc_hek/defs/objs/devi.py b/reclaimer/mcc_hek/defs/objs/devi.py new file mode 100644 index 00000000..f5bf5acc --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/devi.py @@ -0,0 +1,49 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from math import cos + +from reclaimer.hek.defs.objs.tag import HekTag + +class DeviTag(HekTag): + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + devi_attrs = self.data.tagdata.devi_attrs + + devi_attrs.inv_power_acceleration_time = 0 + devi_attrs.inv_power_transition_time = 0 + devi_attrs.inv_position_acceleration_time = 0 + devi_attrs.inv_position_transition_time = 0 + devi_attrs.inv_depowered_acceleration_time = 0 + devi_attrs.inv_depowered_transition_time = 0 + + if devi_attrs.power_acceleration_time: + devi_attrs.inv_power_acceleration_time = 1 / ( + 30 * devi_attrs.power_acceleration_time) + + if devi_attrs.power_transition_time: + devi_attrs.inv_power_transition_time = 1 / ( + 30 * devi_attrs.power_transition_time) + + if devi_attrs.depowered_position_acceleration_time: + devi_attrs.inv_depowered_acceleration_time = 1 / ( + 30 * devi_attrs.depowered_position_acceleration_time) + + if devi_attrs.depowered_position_transition_time: + devi_attrs.inv_depowered_transition_time = 1 / ( + 30 * devi_attrs.depowered_position_transition_time) + + if devi_attrs.position_acceleration_time: + devi_attrs.inv_position_acceleration_time = 1 / ( + 30 * devi_attrs.position_acceleration_time) + + if devi_attrs.position_transition_time: + devi_attrs.inv_position_transition_time = 1 / ( + 30 * devi_attrs.position_transition_time) diff --git a/reclaimer/mcc_hek/defs/objs/effe.py b/reclaimer/mcc_hek/defs/objs/effe.py new file mode 100644 index 00000000..84cee398 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/effe.py @@ -0,0 +1,36 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.tag import HekTag +from reclaimer.util.matrices import euler_2d_to_vector_3d +#from reclaimer.common_descs import valid_objects + +class EffeTag(HekTag): + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + + never_cull = False + for event in self.data.tagdata.events.STEPTREE: + for part in event.parts.STEPTREE: + if part.type.tag_class.enum_name == 'light': + never_cull = True + + part.effect_class = part.type.tag_class + + #TODO: There is no good way to do this right now + #object_types = valid_objects('b').desc[0]['NAME_MAP'].keys() + #if part.effect_class.enum_name in object_types: + # part.effect_class.enum_name = 'object' + + for particle in event.particles.STEPTREE: + particle.relative_direction_vector[:] = euler_2d_to_vector_3d( + *particle.relative_direction + ) + self.data.tagdata.flags.never_cull = never_cull diff --git a/reclaimer/mcc_hek/defs/objs/lens.py b/reclaimer/mcc_hek/defs/objs/lens.py new file mode 100644 index 00000000..5d9e6c41 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/lens.py @@ -0,0 +1,21 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from math import cos + +from reclaimer.hek.defs.objs.tag import HekTag + +class LensTag(HekTag): + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + + tagdata = self.data.tagdata + tagdata.cosine_falloff_angle = cos(tagdata.falloff_angle) + tagdata.cosine_cutoff_angle = cos(tagdata.cutoff_angle) diff --git a/reclaimer/mcc_hek/defs/objs/lifi.py b/reclaimer/mcc_hek/defs/objs/lifi.py new file mode 100644 index 00000000..11105fd0 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/lifi.py @@ -0,0 +1,17 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.obje import ObjeTag +from reclaimer.hek.defs.objs.devi import DeviTag + +class LifiTag(DeviTag, ObjeTag): + + def calc_internal_data(self): + ObjeTag.calc_internal_data(self) + DeviTag.calc_internal_data(self) diff --git a/reclaimer/mcc_hek/defs/objs/ligh.py b/reclaimer/mcc_hek/defs/objs/ligh.py new file mode 100644 index 00000000..f11f3e87 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/ligh.py @@ -0,0 +1,22 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from math import sin, cos + +from reclaimer.hek.defs.objs.tag import HekTag + +class LighTag(HekTag): + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + + shape = self.data.tagdata.shape + shape.cosine_falloff_angle = cos(shape.falloff_angle) + shape.cosine_cutoff_angle = cos(shape.cutoff_angle) + shape.sine_cutoff_angle = sin(shape.cutoff_angle) diff --git a/reclaimer/mcc_hek/defs/objs/mach.py b/reclaimer/mcc_hek/defs/objs/mach.py new file mode 100644 index 00000000..84e6a697 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/mach.py @@ -0,0 +1,19 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.obje import ObjeTag +from reclaimer.hek.defs.objs.devi import DeviTag + +class MachTag(DeviTag, ObjeTag): + + def calc_internal_data(self): + ObjeTag.calc_internal_data(self) + DeviTag.calc_internal_data(self) + mach_attrs = self.data.tagdata.mach_attrs + mach_attrs.door_open_time_ticks = int(mach_attrs.door_open_time * 30) diff --git a/reclaimer/mcc_hek/defs/objs/mod2.py b/reclaimer/mcc_hek/defs/objs/mod2.py new file mode 100644 index 00000000..c264ecdb --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/mod2.py @@ -0,0 +1,96 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.mode import ModeTag + +class Mod2Tag(ModeTag): + + def globalize_local_markers(self): + tagdata = self.data.tagdata + all_global_markers = tagdata.markers.STEPTREE + all_global_markers_by_name = {b.name: b.marker_instances.STEPTREE + for b in all_global_markers} + + for i in range(len(tagdata.regions.STEPTREE)): + region = tagdata.regions.STEPTREE[i] + for j in range(len(region.permutations.STEPTREE)): + perm = region.permutations.STEPTREE[j] + + for k in range(len(perm.local_markers.STEPTREE)): + local_marker = perm.local_markers.STEPTREE[k] + global_markers = all_global_markers_by_name.get( + local_marker.name, None) + + if global_markers is None or len(global_markers) >= 32: + all_global_markers.append() + all_global_markers[-1].name = local_marker.name + global_markers = all_global_markers[-1].marker_instances.STEPTREE + all_global_markers_by_name[local_marker.name] = global_markers + + global_markers.append() + global_marker = global_markers[-1] + + global_marker.region_index = i + global_marker.permutation_index = j + global_marker.node_index = local_marker.node_index + global_marker.rotation[:] = local_marker.rotation[:] + global_marker.translation[:] = local_marker.translation[:] + + del perm.local_markers.STEPTREE[:] + + # sort the markers how Halo's picky ass wants them + name_map = {all_global_markers[i].name: i + for i in range(len(all_global_markers))} + all_global_markers[:] = list(all_global_markers[name_map[name]] + for name in sorted(name_map)) + + def delocalize_part_nodes(self, geometry_index, part_index): + part = self.data.tagdata.geometries.STEPTREE\ + [geometry_index].parts.STEPTREE[part_index] + local_nodes = part.local_nodes[: part.local_node_count] + + delocalize_compressed_verts(part.compressed_vertices.STEPTREE, + local_nodes) + delocalize_uncompressed_verts(part.uncompressed_vertices.STEPTREE, + local_nodes) + + part.flags.ZONER = False + part.local_node_count = 0 + + +def delocalize_compressed_verts(comp_verts, local_nodes): + '''TODO: Update this function to also work on parsed vert data.''' + local_node_ct = len(local_nodes) * 3 + # 28 is the offset to the first verts first node index + for i in range(28, len(comp_verts), 32): + if comp_verts[i] < local_node_ct: + comp_verts[i] = local_nodes[comp_verts[i] // 3] * 3 + + i += 1 + if comp_verts[i] < local_node_ct: + comp_verts[i] = local_nodes[comp_verts[i] // 3] * 3 + + +def delocalize_uncompressed_verts(uncomp_verts, local_nodes): + '''TODO: Update this function to also work on parsed vert data.''' + local_node_ct = len(local_nodes) + # 57 is the offset to the least-significant half of + # the first verts first node index(in big endian). + for i in range(57, len(uncomp_verts), 68): + # literally no reason to use 2 bytes for the node index since + # a max of 63 nodes can be used, so we'll only edit the least + # significant byte of the node indices and make it absolute. + # if the value is 0xFF, the other byte should be 0xFF as well, + # meaning the node is -1, which should stay as -1(no node). + if uncomp_verts[i] < local_node_ct: + uncomp_verts[i] = local_nodes[uncomp_verts[i]] + + i += 2 + if uncomp_verts[i] < local_node_ct: + uncomp_verts[i] = local_nodes[uncomp_verts[i]] diff --git a/reclaimer/mcc_hek/defs/objs/mode.py b/reclaimer/mcc_hek/defs/objs/mode.py new file mode 100644 index 00000000..a7d98cf6 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/mode.py @@ -0,0 +1,124 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from math import sqrt +from struct import unpack, pack_into +from types import MethodType + +from reclaimer.hek.defs.objs.tag import HekTag +from reclaimer.util.compression import compress_normal32, decompress_normal32 +from reclaimer.util.matrices import quaternion_to_matrix, Matrix + +# TODO: Make calc_internal_data recalculate the lod nodes, and remove that +# same function from model.model_compilation.compile_gbxmodel and replace +# it with a call to calc_internal_data. lod nodes are recalculated when +# tags are compiled into maps, but the functionality should still be here. +class ModeTag(HekTag): + + def calc_internal_data(self): + ''' + For each node, this method recalculates the rotation matrix + from the quaternion, and the translation to the root bone. + ''' + HekTag.calc_internal_data(self) + + nodes = self.data.tagdata.nodes.STEPTREE + for node in nodes: + rotation = quaternion_to_matrix(*node.rotation) + trans = Matrix([node.translation])*-1 + parent = None + + # add the parents translation to this ones + if node.parent_node > -1: + trans += Matrix([nodes[node.parent_node].translation_to_root]) + + # rotate the trans_to_root by this node's rotation + trans *= rotation + + # combine this nodes rotation with its parents rotation + if parent is not None: + this_trans = node.translation + node.distance_from_parent = sqrt( + this_trans.x**2 + this_trans.y**2 + this_trans.z**2) + + parent_rot = Matrix([parent.rot_jj_kk, + parent.rot_kk_ii, + parent.rot_ii_jj]) + rotation *= parent_rot + + # apply the changes to the node + node.translation_to_root[:] = trans[0][:] + node.rot_jj_kk[:] = rotation[0] + node.rot_kk_ii[:] = rotation[1] + node.rot_ii_jj[:] = rotation[2] + + def compress_part_verts(self, geometry_index, part_index): + part = self.data.tagdata.geometries.STEPTREE\ + [geometry_index].parts.STEPTREE[part_index] + uncomp_verts_reflexive = part.uncompressed_vertices + comp_verts_reflexive = part.compressed_vertices + + comp_norm = compress_normal32 + unpack_vert = MethodType(unpack, ">11f2hf") + pack_vert_into = MethodType(pack_into, ">12s3I2h2bh") + + comp_verts = bytearray(b'\x00' * 32 * uncomp_verts_reflexive.size) + uncomp_verts = uncomp_verts_reflexive.STEPTREE + + in_off = out_off = 0 + # compress each of the verts and write them to the buffer + for i in range(uncomp_verts_reflexive.size): + ni, nj, nk, bi, bj, bk, ti, tj, tk,\ + u, v, ni_0, ni_1, nw = unpack_vert( + uncomp_verts[in_off + 12: in_off + 64]) + + # write the compressed data + pack_vert_into( + comp_verts, out_off, + uncomp_verts[in_off: in_off + 12], + comp_norm(ni, nj, nk), + comp_norm(bi, bj, bk), + comp_norm(ti, tj, tk), + int(max(0, min(1, u))*32767.5), + int(max(0, min(1, v))*32767.5), + ni_0*3, ni_1*3, int(max(0, min(1, nw))*32767.5)) + in_off += 68 + out_off += 32 + + comp_verts_reflexive.STEPTREE = comp_verts + + def decompress_part_verts(self, geometry_index, part_index): + part = self.data.tagdata.geometries.STEPTREE\ + [geometry_index].parts.STEPTREE[part_index] + uncomp_verts_reflexive = part.uncompressed_vertices + comp_verts_reflexive = part.compressed_vertices + + decomp_norm = decompress_normal32 + unpack_vert = MethodType(unpack, ">3I2h2bh") + pack_vert_into = MethodType(pack_into, ">12s11f2h2f") + + uncomp_verts = bytearray(b'\x00' * 68 * comp_verts_reflexive.size) + comp_verts = comp_verts_reflexive.STEPTREE + + in_off = out_off = 0 + # uncompress each of the verts and write them to the buffer + for i in range(comp_verts_reflexive.size): + n, b, t, u, v, ni_0, ni_1, nw = unpack_vert( + comp_verts[in_off + 12: in_off + 32]) + # write the uncompressed data + pack_vert_into( + uncomp_verts, out_off, + comp_verts[in_off: in_off + 12], + *decomp_norm(n), *decomp_norm(b), *decomp_norm(t), + u/32767.5, v/32767.5, ni_0 // 3, ni_1 // 3, + nw/32767.5, 1.0 - nw/32767.5) + in_off += 32 + out_off += 68 + + uncomp_verts_reflexive.STEPTREE = uncomp_verts diff --git a/reclaimer/mcc_hek/defs/objs/obje.py b/reclaimer/mcc_hek/defs/objs/obje.py new file mode 100644 index 00000000..0adcac71 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/obje.py @@ -0,0 +1,52 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +import os + +from reclaimer.hek.defs.objs.tag import HekTag + +class ObjeTag(HekTag): + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + + full_class_name = self.data.blam_header.tag_class.enum_name + + self.ext = '.' + full_class_name + self.filepath = os.path.splitext(str(self.filepath))[0] + self.ext + + object_type = self.data.tagdata.obje_attrs.object_type + if full_class_name == "object": + object_type.data = -1 + elif full_class_name == "biped": + object_type.data = 0 + elif full_class_name == "vehicle": + object_type.data = 1 + elif full_class_name == "weapon": + object_type.data = 2 + elif full_class_name == "equipment": + object_type.data = 3 + elif full_class_name == "garbage": + object_type.data = 4 + elif full_class_name == "projectile": + object_type.data = 5 + elif full_class_name == "scenery": + object_type.data = 6 + elif full_class_name == "device_machine": + object_type.data = 7 + elif full_class_name == "device_control": + object_type.data = 8 + elif full_class_name == "device_light_fixture": + object_type.data = 9 + elif full_class_name == "placeholder": + object_type.data = 10 + elif full_class_name == "sound_scenery": + object_type.data = 11 + else: + raise ValueError("Unknown object type '%s'" % full_class_name) diff --git a/reclaimer/mcc_hek/defs/objs/phys.py b/reclaimer/mcc_hek/defs/objs/phys.py new file mode 100644 index 00000000..dd64cd8f --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/phys.py @@ -0,0 +1,151 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from math import log + +from reclaimer.hek.defs.objs.tag import HekTag +from reclaimer.util.matrices import Matrix + + +class PhysTag(HekTag): + + def calc_masses(self): + data = self.data.tagdata + mass_points = data.mass_points.STEPTREE + mass_center = [0,0,0] + + if not len(mass_points): + data.center_of_mass[:] = mass_center + return + + total_mass = data.mass + total_rel_mass = sum([mp.relative_mass for mp in mass_points]) + + for mp in mass_points: + rel_mass = mp.relative_mass + position = mp.position + + mp.mass = total_mass*(rel_mass/total_rel_mass) + mass_center[0] += position[0]*mp.mass + mass_center[1] += position[1]*mp.mass + mass_center[2] += position[2]*mp.mass + + mass_center[0] /= total_mass + mass_center[1] /= total_mass + mass_center[2] /= total_mass + + data.center_of_mass[:] = mass_center + + def calc_densities(self): + data = self.data.tagdata + mass_points = data.mass_points.STEPTREE + + if not len(mass_points): + return + + total_mass = data.mass + total_density = data.density + + densities = [mp.relative_density for mp in + mass_points if mp.relative_density] + masses = [mp.relative_mass for mp in + mass_points if mp.relative_mass] + if not densities or not masses: + for mp in mass_points: + mp.density = 0 + return + + total_rel_mass = sum(mp.relative_mass for mp in mass_points) + average_rel_mass = total_rel_mass / len(mass_points) + # total_density total_density + # density_scale = ------------------ = ---------------- + # avg(mass/density) avg( m^3 kg ) + # ( --- * -- ) + # ( kg 1 ) + den_scale = total_density / len(mass_points) + den_scale *= sum(mp.relative_mass / mp.relative_density + for mp in mass_points if mp.relative_density) + mass_scale = 1 / average_rel_mass + + for mp in mass_points: + mp.density = den_scale * mass_scale * mp.relative_density + + def calc_intertia_matrices(self): + data = self.data.tagdata + scale = data.moment_scale + matrices = data.inertia_matrices.STEPTREE + com = data.center_of_mass + mass_points = data.mass_points.STEPTREE + + # make sure the matrix array is only 2 long + matrices.extend(2 - len(matrices)) + del matrices[2:] + + reg = matrices.regular + inv = matrices.inverse + + reg_yy_zz, reg_zz_xx, reg_xx_yy = reg.yy_zz, reg.zz_xx, reg.xx_yy + xx = yy = zz = float('1e-30') # prevent division by 0 + neg_zx = neg_xy = neg_yz = 0 + + # calculate the moments for each mass point and add them up + for mp in mass_points: + pos = mp.position + + dist_xx = (com[1] - pos[1])**2 + (com[2] - pos[2])**2 + dist_yy = (com[0] - pos[0])**2 + (com[2] - pos[2])**2 + dist_zz = (com[0] - pos[0])**2 + (com[1] - pos[1])**2 + + dist_zx = (com[0] - pos[0])*(com[2] - pos[2]) + dist_xy = (com[0] - pos[0])*(com[1] - pos[1]) + dist_yz = (com[1] - pos[1])*(com[2] - pos[2]) + + if mp.radius > 0: + radius_term = 4*pow(10, (2*log(mp.radius, 10) - 1)) + else: + radius_term = 0 + + xx += (dist_xx + radius_term) * mp.mass + yy += (dist_yy + radius_term) * mp.mass + zz += (dist_zz + radius_term) * mp.mass + neg_zx -= dist_zx * mp.mass + neg_xy -= dist_xy * mp.mass + neg_yz -= dist_yz * mp.mass + + xx, yy, zz = xx*scale, yy*scale, zz*scale + neg_zx, neg_xy, neg_yz = neg_zx*scale, neg_xy*scale, neg_yz*scale + + # place the calculated values into the matrix + reg_yy_zz[:] = xx, neg_xy, neg_zx + reg_zz_xx[:] = neg_xy, yy, neg_yz + reg_xx_yy[:] = neg_zx, neg_yz, zz + + # calculate the inverse inertia matrix + regular = Matrix((reg_yy_zz, reg_zz_xx, reg_xx_yy)) + try: + inverse = regular.inverse + except ZeroDivisionError: + inverse = Matrix((1, 0, 0), (0, 1, 0), (0, 0, 1)) + print("Could not calculate inertia matrix inverse.") + + # place the inverse matrix into the tag + inv.yy_zz[:] = inverse[0][:] + inv.zz_xx[:] = inverse[1][:] + inv.xx_yy[:] = inverse[2][:] + + # copy the xx, yy, and zz moments form the matrix into the tag body + data.xx_moment = reg_yy_zz[0] + data.yy_moment = reg_zz_xx[1] + data.zz_moment = reg_xx_yy[2] + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + self.calc_masses() + self.calc_densities() + self.calc_intertia_matrices() diff --git a/reclaimer/mcc_hek/defs/objs/pphy.py b/reclaimer/mcc_hek/defs/objs/pphy.py new file mode 100644 index 00000000..859bbaf9 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/pphy.py @@ -0,0 +1,31 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.tag import HekTag + +class PphyTag(HekTag): + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + tagdata = self.data.tagdata + + d = tagdata.density + tagdata.scaled_density = d * 355840 / 3 + + try: + tagdata.water_gravity_scale = -((d - 1) / (d + 1)) + except ZeroDivisionError: + tagdata.water_gravity_scale = float("-inf") + + # 0.0011 is the density of air relative to water + try: + tagdata.air_gravity_scale = -((d / 0.0011 - 1) / + (d / 0.0011 + 1)) + except ZeroDivisionError: + tagdata.air_gravity_scale = float("-inf") diff --git a/reclaimer/mcc_hek/defs/objs/sbsp.py b/reclaimer/mcc_hek/defs/objs/sbsp.py new file mode 100644 index 00000000..863c2524 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/sbsp.py @@ -0,0 +1,64 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.tag import HekTag + + +class SbspTag(HekTag): + @staticmethod + def point_in_front_of_plane(plane, x, y, z): + ''' + Takes a plane ex: + ```py + collision_bsp = self.data.tagdata.collision_bsp.STEPTREE[0] + collision_bsp.planes.STEPTREE[i] + ``` + And dots the plane and coordinates and compares against d to + see if the xyz are in front of it. + ''' + return (x*plane.i + y*plane.j + z*plane.k) >= plane.d + + def get_leaf_index_of_point(self, x, y, z): + ''' + Returns the leaf node the point is in in the bsp. + If point is not in the bsp, returns None. + ''' + if len(self.data.tagdata.collision_bsp.STEPTREE) < 1: + raise ValueError("No collision_bsp structure found in sbsp tag.") + + collision_bsp = self.data.tagdata.collision_bsp.STEPTREE[0] + bsp3d_nodes = collision_bsp.bsp3d_nodes.STEPTREE + bsp_planes = collision_bsp.planes.STEPTREE + if not bsp3d_nodes: + return None + + node_index = 0 + # Go through the tree until we get a negative number (leaf or null) + while node_index >= 0: + node = bsp3d_nodes[node_index] + if self.point_in_front_of_plane(bsp_planes[node.plane], x, y, z): + node_index = node.front_child + else: + node_index = node.back_child + + # -1 = null (not found); otherwise it's a leaf (found) + return None if node_index == -1 else node_index + 0x80000000 + + def get_cluster_index_of_point(self, x, y, z): + ''' + Returns the cluster the point is in in the bsp. + If point is not in the bsp, returns None. + ''' + leaf = self.get_leaf_index_of_point(x, y, z) + leaves = self.data.tagdata.leaves.STEPTREE + return leaves[leaf].cluster if leaf in range(len(leaves)) else None + + def is_point_in_bsp(self, x, y, z): + '''Returns if given point in 3d space is inside of this BSP''' + return self.get_leaf_index_of_point(x, y, z) is not None diff --git a/reclaimer/mcc_hek/defs/objs/shdr.py b/reclaimer/mcc_hek/defs/objs/shdr.py new file mode 100644 index 00000000..290b73fb --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/shdr.py @@ -0,0 +1,46 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +import os + +from reclaimer.hek.defs.objs.tag import HekTag + +class ShdrTag(HekTag): + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + + full_class_name = self.data.blam_header.tag_class.enum_name + + self.ext = '.' + full_class_name + self.filepath = os.path.splitext(str(self.filepath))[0] + self.ext + + shader_type = self.data.tagdata.shdr_attrs.shader_type + if full_class_name == "shader": + shader_type.data = -1 + elif full_class_name == "shader_environment": + shader_type.data = 3 + elif full_class_name == "shader_model": + shader_type.data = 4 + elif full_class_name == "shader_transparent_generic": + shader_type.data = 5 + elif full_class_name == "shader_transparent_chicago": + shader_type.data = 6 + elif full_class_name == "shader_transparent_chicago_extended": + shader_type.data = 7 + elif full_class_name == "shader_transparent_water": + shader_type.data = 8 + elif full_class_name == "shader_transparent_glass": + shader_type.data = 9 + elif full_class_name == "shader_transparent_meter": + shader_type.data = 10 + elif full_class_name == "shader_transparent_plasma": + shader_type.data = 11 + else: + raise ValueError("Unknown shader type '%s'" % full_class_name) diff --git a/reclaimer/mcc_hek/defs/objs/snd_.py b/reclaimer/mcc_hek/defs/objs/snd_.py new file mode 100644 index 00000000..1dd5a730 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/snd_.py @@ -0,0 +1,20 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.tag import HekTag + +class Snd_Tag(HekTag): + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + + for pitch_range in self.data.tagdata.pitch_ranges.STEPTREE: + pitch_range.playback_rate = 1 + if pitch_range.natural_pitch: + pitch_range.playback_rate = 1 / pitch_range.natural_pitch diff --git a/reclaimer/mcc_hek/defs/objs/str_.py b/reclaimer/mcc_hek/defs/objs/str_.py new file mode 100644 index 00000000..17582773 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/str_.py @@ -0,0 +1,21 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.tag import HekTag +from reclaimer.util import convert_newlines_to_windows + +class Str_Tag(HekTag): + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + strings = self.data.tagdata.strings.STEPTREE + + for i in range(len(strings)): + # Replace all newlines with \r\n. + strings[i].data = convert_newlines_to_windows(strings[i].data) diff --git a/reclaimer/mcc_hek/defs/objs/tag.py b/reclaimer/mcc_hek/defs/objs/tag.py new file mode 100644 index 00000000..889037c6 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/tag.py @@ -0,0 +1,70 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# +from pathlib import Path + +from supyr_struct.defs.constants import DEFAULT +from supyr_struct.tag import Tag + +from reclaimer.util import calc_halo_crc32 + +class HekTag(Tag): + def __init__(self, **kwargs): + self.calc_pointers = False + Tag.__init__(self, **kwargs) + + def serialize(self, **kwargs): + ''' + Overload of the supyr serialization function that retroactively adds + a CRC to the tag. + ''' + head = self.data.blam_header + filepath = kwargs.get('filepath', self.filepath) + buffer = kwargs.get('buffer', None) + + # Run the normal serialization. + result = Tag.serialize(self, **kwargs) + + # If there is neither a buffer or filepath just return the result. + if (buffer is None) and (not filepath): + return result + + # Prefer to use the buffer as that is how Tag.serialize does it. + f = buffer + if buffer is None: + f = Path(filepath).open('rb+') + + # Calculate the crc from after the header to the end. + crc = calc_halo_crc32(f, offset=head.get_desc('SIZE')) + # Write the crc to the offset of the checksum value in the header. + # The way we retrieve this offset from supyr is insane. + attr_index = head.get_desc('NAME_MAP')['checksum'] + f.seek(head.get_desc('ATTR_OFFS')[attr_index]) + f.write(crc.to_bytes(4, byteorder='big', signed=False)) + # Flush the stream. + f.flush() + # Only close if it is a file. Because the only case where we own + # this buffer is if there was no buffer kwarg. + if not buffer: + f.close() + + # Update the tag object so it won't have to be deserialized again. + head.checksum = crc + return result + + def calc_internal_data(self): + # recalculate the header data + head = self.data.blam_header + + head.tag_class.data = head.tag_class.get_desc(DEFAULT) + head.flags.edited_with_mozz = True + head.header_size = head.get_desc(DEFAULT, 'header_size') + head.version = head.get_desc(DEFAULT, 'version') + head.integrity0 = head.get_desc(DEFAULT, 'integrity0') + head.integrity1 = head.get_desc(DEFAULT, 'integrity1') + head.engine_id.data = head.engine_id.get_desc(DEFAULT) diff --git a/reclaimer/mcc_hek/defs/objs/ustr.py b/reclaimer/mcc_hek/defs/objs/ustr.py new file mode 100644 index 00000000..c9cc0a65 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/ustr.py @@ -0,0 +1,13 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.str_ import Str_Tag + +class UstrTag(Str_Tag): + pass diff --git a/reclaimer/mcc_hek/defs/objs/weap.py b/reclaimer/mcc_hek/defs/objs/weap.py new file mode 100644 index 00000000..9572dca4 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/weap.py @@ -0,0 +1,39 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.obje import ObjeTag + +class WeapTag(ObjeTag): + + def calc_internal_data(self): + ObjeTag.calc_internal_data(self) + for trigger in self.data.tagdata.weap_attrs.triggers.STEPTREE: + firing = trigger.firing + misc = trigger.misc + rates = trigger.misc_rates + for i in range(len(rates)): + rates[i] = 0 + + if misc.ejection_port_recovery_time: + rates.ejection_port_recovery_rate = 1 / misc.ejection_port_recovery_time + if misc.illumination_recovery_time: + rates.illumination_recovery_rate = 1 / misc.illumination_recovery_time + + if firing.acceleration_time: + rates.acceleration_rate = 1 / firing.acceleration_time + if firing.deceleration_time: + rates.deceleration_rate = 1 / firing.deceleration_time + + if firing.error_acceleration_time: + rates.error_acceleration_rate = 1 / firing.error_acceleration_time + if firing.error_deceleration_time: + rates.error_deceleration_rate = 1 / firing.error_deceleration_time + + for i in range(len(rates)): + rates[i] /= 30 diff --git a/reclaimer/mcc_hek/defs/objs/wphi.py b/reclaimer/mcc_hek/defs/objs/wphi.py new file mode 100644 index 00000000..d759eaa5 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/wphi.py @@ -0,0 +1,20 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.tag import HekTag + +class WphiTag(HekTag): + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + tagdata = self.data.tagdata + tagdata.crosshair_types.data = 0 + + for crosshair in tagdata.crosshairs.STEPTREE: + tagdata.crosshair_types.data |= 1 << crosshair.crosshair_type.data diff --git a/reclaimer/mcc_hek/defs/part.py b/reclaimer/mcc_hek/defs/part.py new file mode 100644 index 00000000..9ee519f7 --- /dev/null +++ b/reclaimer/mcc_hek/defs/part.py @@ -0,0 +1,105 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +part_body = Struct("tagdata", + Bool32("flags", + "can_animate_backwards", + "animation_stops_at_rest", + "animation_starts_on_random_frame", + "animate_once_per_frame", + "dies_at_rest", + "dies_on_contact_with_structure", + "tint_from_diffuse_texture", + "dies_on_contact_with_water", + "dies_on_contact_with_air", + "self_illuminated", + "random_horizontal_mirroring", + "random_vertical_mirroring", + ), + dependency("bitmap", "bitm"), + dependency("physics", "pphy"), + dependency("impact_effect", "foot", + TOOLTIP="Marty traded his kids for this"), + + Pad(4), + from_to_sec("lifespan"), + float_sec("fade_in_time"), + float_sec("fade_out_time"), + + dependency("collision_effect", valid_event_effects), + dependency("death_effect", valid_event_effects), + + Struct("rendering", + Float("minimum_size", SIDETIP="pixels"), + FlSInt32("unknown0", VISIBLE=False), + FlFloat("unknown1", VISIBLE=False), + QStruct("radius_animation", INCLUDE=from_to), + Pad(4), + QStruct("animation_rate", + Float("from", UNIT_SCALE=per_sec_unit_scale, GUI_NAME=''), + Float("to", UNIT_SCALE=per_sec_unit_scale), + ORIENT='h', SIDETIP='frames/sec' + ), + Float("contact_deterioration"), + Float("fade_start_size", SIDETIP="pixels"), + Float("fade_end_size", SIDETIP="pixels"), + + Pad(4), + SInt16("first_sequence_index"), + SInt16("initial_sequence_count"), + SInt16("looping_sequence_count"), + SInt16("final_sequence_count"), + + Pad(12), + SEnum16("orientation", *render_mode), + + Pad(38), + FlUInt32("unknown2", VISIBLE=False), + Bool16("shader_flags", *shader_flags), + SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), + SEnum16("framebuffer_fade_mode", *render_fade_mode), + Bool16("map_flags", + "unfiltered" + ), + ), + Pad(12), # OS v4 shader extension padding + Pad(16), + + #Secondary map + Struct("secondary_map", + dependency("bitmap", "bitm"), + SEnum16("anchor", *render_anchor), + Bool16("flags", + "unfiltered" + ), + Struct("u_animation", INCLUDE=anim_src_func_per_pha_sca), + Struct("v_animation", INCLUDE=anim_src_func_per_pha_sca), + Struct("rotation_animation", INCLUDE=anim_src_func_per_pha_sca_rot), + QStruct("rotation_center", INCLUDE=xy_float), + Pad(4), + Float("zsprite_radius_scale"), + ), + + SIZE=356, + ) + + +def get(): + return part_def + +part_def = TagDef("part", + blam_header("part", 2), + part_body, + + ext=".particle", endian=">", tag_cls=HekTag, + ) diff --git a/reclaimer/mcc_hek/defs/pctl.py b/reclaimer/mcc_hek/defs/pctl.py new file mode 100644 index 00000000..e2fcb1c9 --- /dev/null +++ b/reclaimer/mcc_hek/defs/pctl.py @@ -0,0 +1,163 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +particle_creation_physics = ( + "default", + "explosion", + "jet" + ) + +physics_constant = QStruct("physics_constant", + Float("k", UNIT_SCALE=per_sec_unit_scale), + ) + +state = Struct("state", + ascii_str32("name"), + from_to_sec("duration_bounds"), + from_to_sec("transition_time_bounds"), + + Pad(4), + Float("scale_multiplier"), + Float("animation_rate_multiplier"), + Float("rotation_rate_multiplier"), + QStruct("color_multiplier", INCLUDE=argb_float), + Float("radius_multiplier"), + Float("minimum_particle_count"), + Float("particle_creation_rate", + SIDETIP="particles/sec", UNIT_SCALE=per_sec_unit_scale), + + Pad(84), + SEnum16("particle_creation_physics", *particle_creation_physics), + Pad(2), # SEnum16("particle_update_physics", "default"), + reflexive("physics_constants", physics_constant, 16), + SIZE=192 + ) + +particle_state = Struct("particle_state", + ascii_str32("name"), + from_to_sec("duration_bounds"), + from_to_sec("transition_time_bounds"), + + dependency("bitmaps", "bitm"), + SInt16("sequence_index"), + Pad(6), + QStruct("scale", INCLUDE=from_to, SIDETIP="world units/pixel"), + QStruct("animation_rate", + Float("from", UNIT_SCALE=per_sec_unit_scale, GUI_NAME=''), + Float("to", UNIT_SCALE=per_sec_unit_scale), + ORIENT='h', SIDETIP="frames/sec" + ), + from_to_rad_sec("rotation_rate"), # radians/sec + QStruct("color_1", INCLUDE=argb_float), + QStruct("color_2", INCLUDE=argb_float), + Float("radius_multiplier"), + dependency("physics", "pphy"), + + Pad(72), + FlUInt32("unknown0", VISIBLE=False), + Bool16("shader_flags", *shader_flags), + SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), + SEnum16("framebuffer_fade_mode", *render_fade_mode), + Bool16("map_flags", + "unfiltered" + ), + + Pad(12), # open sauce particle shader extension + Pad(16), + #Secondary map + Struct("secondary_map", + dependency("bitmap", "bitm"), + SEnum16("anchor", *render_anchor), + Bool16("flags", + "unfiltered" + ), + Struct("u_animation", INCLUDE=anim_src_func_per_pha_sca), + Struct("v_animation", INCLUDE=anim_src_func_per_pha_sca), + Struct("rotation_animation", INCLUDE=anim_src_func_per_pha_sca_rot), + QStruct("rotation_center", INCLUDE=xy_float), + Pad(4), + Float("zsprite_radius_scale"), + ), + + Pad(20), + reflexive("physics_constants", physics_constant, 16), + SIZE=376 + ) + + +particle_type = Struct("particle_type", + ascii_str32("name"), + Bool32("flags", + "type_states_loop", + "type_states_loops_forward_backward", + "particle_states_loop", + "particle_states_loops_forward_backward", + "particles_die_in_water", + "particles_die_in_air", + "particles_die_on_ground", + "rotational_sprites_animate_sideways", + "disabled", + "tint_by_effect_color", + "initial_count_scales_with_effect", + "minimum_count_scales_with_effect", + "creation_rate_scales_with_effect", + "radius_scales_with_effect", + "animation_rate_scales_with_effect", + "rotation_rate_scales_with_effect", + "dont_draw_in_first_person", + "dont_draw_in_third_person", + ), + SInt16("initial_particle_count"), + + Pad(2), + SEnum16("complex_sprite_render_modes", + "simple", + "rotational" + ), + + Pad(2), + float_wu("radius"), + + Pad(36), + SEnum16("particle_creation_physics", *particle_creation_physics), + + Pad(6), + reflexive("physics_constants", physics_constant, 16), + reflexive("states", state, 8, DYN_NAME_PATH='.name'), + reflexive("particle_states", particle_state, 8, DYN_NAME_PATH='.name'), + SIZE=128 + ) + +pctl_body = Struct("tagdata", + Pad(56), + dependency("point_physics", "pphy"), + SEnum16("system_update_physics", + "default", + "explosion" + ), + Pad(6), + reflexive("physics_constants", physics_constant, 16), + reflexive("particle_types", particle_type, 4, DYN_NAME_PATH='.name'), + SIZE=104, + ) + + +def get(): + return pctl_def + +pctl_def = TagDef("pctl", + blam_header("pctl", 4), + pctl_body, + + ext=".particle_system", endian=">", tag_cls=HekTag, + ) diff --git a/reclaimer/mcc_hek/defs/phys.py b/reclaimer/mcc_hek/defs/phys.py new file mode 100644 index 00000000..1cee3e38 --- /dev/null +++ b/reclaimer/mcc_hek/defs/phys.py @@ -0,0 +1,115 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.phys import PhysTag +from supyr_struct.defs.tag_def import TagDef + +inertial_matrix = Struct("inertial_matrix", + QStruct("yy_zz", GUI_NAME="yy+zz -xy -zx", INCLUDE=ijk_float), + QStruct("zz_xx", GUI_NAME="-xy zz+xx -yz", INCLUDE=ijk_float), + QStruct("xx_yy", GUI_NAME="-zx -yz xx+yy", INCLUDE=ijk_float), + SIZE=36, + ) + +powered_mass_point = Struct("powered_mass_point", + ascii_str32("name"), + Bool32('flags', + 'ground_friction', + 'water_friction', + 'air_friction', + 'water_lift', + 'air_lift', + 'thrust', + 'antigrav', + ), + Float("antigrav_strength"), + Float("antigrav_offset"), + Float("antigrav_height"), + Float("antigrav_damp_fraction"), + Float("antigrav_normal_k1"), + Float("antigrav_normal_k0"), + SIZE=128, + ) + +mass_point = Struct("mass_point", + ascii_str32("name"), + dyn_senum16("powered_mass_point", + DYN_NAME_PATH="tagdata.powered_mass_points.STEPTREE[DYN_I].name"), + SInt16("model_node"), + Bool32('flags', + 'metallic', + ), + Float("relative_mass", MIN=0.0), + Float("mass", VISIBLE=False, MIN=0.0), + Float("relative_density", MIN=0.0), + Float("density", VISIBLE=False, MIN=0.0), + QStruct("position", INCLUDE=xyz_float), + QStruct("forward", INCLUDE=ijk_float), + QStruct("up", INCLUDE=ijk_float), + SEnum16('friction_type', + 'point', + 'forward', + 'left', + 'up', + ), + Pad(2), + Float("friction_parallel_scale"), + Float("friction_perpendicular_scale"), + Float("radius", MIN=0.0, ALLOW_MIN=False), + SIZE=128, + ) + +phys_body = Struct("tagdata", + Float("radius"), + Float("moment_scale"), + Float("mass", MIN=0.0), + QStruct("center_of_mass", INCLUDE=xyz_float, VISIBLE=False), + Float("density", MIN=0.0), + Float("gravity_scale"), + Float("ground_friction", UNIT_SCALE=per_sec_unit_scale), + Float("ground_depth"), + Float("ground_damp_fraction"), + Float("ground_normal_k1"), + Float("ground_normal_k0"), + Pad(4), + Float("water_friction", UNIT_SCALE=per_sec_unit_scale), + Float("water_depth"), + Float("water_density"), + Pad(4), + Float("air_friction", UNIT_SCALE=per_sec_unit_scale), + Pad(4), + Float("xx_moment", VISIBLE=False), + Float("yy_moment", VISIBLE=False), + Float("zz_moment", VISIBLE=False), + + reflexive("inertia_matrices", inertial_matrix, 2, + "regular", "inverse", MIN=2, MAX=2, VISIBLE=False), + reflexive("powered_mass_points", powered_mass_point, 32, + DYN_NAME_PATH='.name'), + reflexive("mass_points", mass_point, 32, + DYN_NAME_PATH='.name'), + SIZE=128, + COMMENT="\ +Some fields have been hidden because you cant edit them.\n\n\ +This is because they are recalculated when you hit save.\n\ +The inertial matrices and xx/yy/zz moments are hidden as\n\ +well as the mass and density of the individual mass points." + ) + + +def get(): + return phys_def + +phys_def = TagDef("phys", + blam_header('phys', 4), + phys_body, + + ext=".physics", endian=">", tag_cls=PhysTag + ) diff --git a/reclaimer/mcc_hek/defs/plac.py b/reclaimer/mcc_hek/defs/plac.py new file mode 100644 index 00000000..d9befd91 --- /dev/null +++ b/reclaimer/mcc_hek/defs/plac.py @@ -0,0 +1,35 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .obje import * +from .objs.obje import ObjeTag +from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant + +# replace the object_type enum one that uses +# the correct default value for this object +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(10)) + ) + +plac_body = Struct("tagdata", + obje_attrs, + SIZE=508, + ) + + +def get(): + return plac_def + +plac_def = TagDef("plac", + blam_header('plac', 2), + plac_body, + + ext=".placeholder", endian=">", tag_cls=ObjeTag + ) diff --git a/reclaimer/mcc_hek/defs/pphy.py b/reclaimer/mcc_hek/defs/pphy.py new file mode 100644 index 00000000..7d335328 --- /dev/null +++ b/reclaimer/mcc_hek/defs/pphy.py @@ -0,0 +1,67 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.pphy import PphyTag +from supyr_struct.defs.tag_def import TagDef + +density_comment = """Densities(grams per milliliter)\n +air 0.0011 +snow 0.128 +cork 0.24 +cedar 0.43 +oak 0.866 +ice 0.897 +water 1.0 +soil 1.1 +cotton 1.491 +dry earth 1.52 +sand 1.7 +granite 2.4 +glass 2.5 +iron 7.65 +steel 7.77 +lead 11.37 +uranium 18.74 +gold 19.3""" + +pphy_body = Struct("tagdata", + Bool32("flags", + "flamethrower_particle_collision", + "collides_with_structures", + "collides_with_water_surface", + "uses_simple_wind", + "uses_damped_wind", + "no_gravity" + ), + # these next three are courtesy of Sparky. I had + # no idea these existed till I looked in Eschaton. + # kavawuvi figured out how to calculate them(see PphyTag) + FlFloat("scaled_density", VISIBLE=False), + FlFloat("water_gravity_scale", VISIBLE=False), + FlFloat("air_gravity_scale", VISIBLE=False), + Pad(16), + Float("density", SIDETIP="g/mL", COMMENT=density_comment),#g/mL + Float("air_friction"), + Float("water_friction"), + Float("surface_friction"), + Float("elasticity"), + + SIZE=64 + ) + +def get(): + return pphy_def + +pphy_def = TagDef("pphy", + blam_header('pphy'), + pphy_body, + + ext=".point_physics", endian=">", tag_cls=PphyTag + ) diff --git a/reclaimer/mcc_hek/defs/proj.py b/reclaimer/mcc_hek/defs/proj.py new file mode 100644 index 00000000..baf1f83b --- /dev/null +++ b/reclaimer/mcc_hek/defs/proj.py @@ -0,0 +1,141 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .obje import * +from .objs.obje import ObjeTag +from supyr_struct.util import desc_variant + +# replace the object_type enum one that uses +# the correct default value for this object +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(5)) + ) + +responses = ( + "disappear", + "detonate", + "reflect", + "overpenetrate", + "attach" + ) + +material_response = Struct("material_response", + Bool16("flags", + "cannot_be_overpenetrated", + ), + SEnum16('response', *responses), + dependency('effect', "effe"), + + Pad(16), + Struct("potential_response", + SEnum16('response', *responses), + Bool16("flags", + "only_against_units", + ), + float_zero_to_one("skip_fraction"), + from_to_rad("impact_angle"), # radians + from_to_wu_sec("impact_velocity"), # world units/second + dependency('effect', "effe"), + ), + + Pad(16), + SEnum16("scale_effects_by", + "damage", + "angle", + ), + Pad(2), + float_rad("angular_noise"), + float_wu_sec("velocity_noise"), + dependency('detonation_effect', "effe"), + + Pad(24), + # Penetration + Float("initial_friction", UNIT_SCALE=per_sec_unit_scale), + Float("maximum_distance"), + + # Reflection + Float("parallel_refriction", UNIT_SCALE=per_sec_unit_scale), + Float("perpendicular_friction", UNIT_SCALE=per_sec_unit_scale), + + SIZE=160 + ) + +proj_attrs = Struct("proj_attrs", + Bool32("flags", + "oriented_along_velocity", + "ai_must_use_ballistic_aiming", + "detonation_max_time_if_attached", + "has_super_combining_explosion", + "add_parent_velocity_to_initial_velocity", + "random_attached_detonation_time", + "minimum_unattached_detonation_time", + ), + SEnum16('detonation_timer_starts', + "immediately", + "on_first_bounce", + "when_at_rest", + ), + SEnum16('impact_noise', *sound_volumes), + SEnum16('A_in', *projectile_inputs), + SEnum16('B_in', *projectile_inputs), + SEnum16('C_in', *projectile_inputs), + SEnum16('D_in', *projectile_inputs), + dependency('super_detonation', "effe"), + float_wu("ai_perception_radius"), + float_wu("collision_radius"), + + Struct("detonation", + float_sec("arming_time"), + float_wu("danger_radius"), + dependency('effect', "effe"), + from_to_sec("timer"), + float_wu_sec("minimum_velocity"), + float_wu("maximum_range"), + ), + + Struct("physics", + Float("air_gravity_scale"), + from_to_wu("air_damage_range"), + Float("water_gravity_scale"), + from_to_wu("water_damage_range"), + float_wu_sec("initial_velocity"), # world units/sec + float_wu_sec("final_velocity"), # world units/sec + float_rad_sec("guided_angular_velocity"), # radians/second + SEnum16('detonation_noise', *sound_volumes), + + Pad(2), + dependency('detonation_started', "effe"), + dependency('flyby_sound', "snd!"), + dependency('attached_detonation_damage', "jpt!"), + dependency('impact_damage', "jpt!"), + ), + + Pad(12), + reflexive("material_responses", material_response, + len(materials_list), *materials_list), + + SIZE=208 + ) + +proj_body = Struct("tagdata", + obje_attrs, + proj_attrs, + SIZE=588, + ) + + +def get(): + return proj_def + +proj_def = TagDef("proj", + blam_header('proj', 5), + proj_body, + + ext=".projectile", endian=">", tag_cls=ObjeTag + ) diff --git a/reclaimer/mcc_hek/defs/rain.py b/reclaimer/mcc_hek/defs/rain.py new file mode 100644 index 00000000..39372bda --- /dev/null +++ b/reclaimer/mcc_hek/defs/rain.py @@ -0,0 +1,112 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +particle_type = Struct("particle_type", + ascii_str32("name"), + Bool32("flags", + "blend_colors_in_hsv", + "along_long_hue_path", + "random_rotation", + ), + QStruct("fade", + float_wu("in_start_distance"), + float_wu("in_end_distance"), + float_wu("out_start_distance"), + float_wu("out_end_distance"), + float_wu("in_start_height"), + float_wu("in_end_height"), + float_wu("out_start_height"), + float_wu("out_end_height"), + ), + + Pad(96), + QStruct("particle_count", INCLUDE=from_to, + SIDETIP="particles/(world unit^3)"), + dependency("physics", "pphy"), + + Pad(16), + Struct("acceleration", + QStruct("magnitude", INCLUDE=from_to), + float_rad("turning_rate"), # radians + Float("change_rate"), + ), + + Pad(32), + from_to_wu("particle_radius"), + QStruct("animation_rate", + Float("from", UNIT_SCALE=per_sec_unit_scale, GUI_NAME=''), + Float("to", UNIT_SCALE=per_sec_unit_scale), + ORIENT='h', SIDETIP="frames/sec" + ), + from_to_rad_sec("rotation_rate"), # radians/sec + + Pad(32), + QStruct("color_lower_bound", INCLUDE=argb_float), + QStruct("color_upper_bound", INCLUDE=argb_float), + + #Shader + Struct("shader", + Pad(64), + dependency("sprite_bitmap", "bitm"), + SEnum16("render_mode", *render_mode), + SEnum16("render_direction_source", + "from_velocity", + "from_acceleration" + ), + + Pad(40), + Bool16("shader_flags", *shader_flags), + SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), + SEnum16("framebuffer_fade_mode", *render_fade_mode), + Bool16("map_flags", + "unfiltered" + ) + ), + + #Secondary bitmap + Struct("secondary_bitmap", + Pad(28), + dependency("bitmap", "bitm"), + SEnum16("anchor", *render_anchor), + Bool16("secondary_map_flags", + "unfiltered" + ), + Struct("u_animation", INCLUDE=anim_src_func_per_pha_sca), + Struct("v_animation", INCLUDE=anim_src_func_per_pha_sca), + Struct("rotation_animation", INCLUDE=anim_src_func_per_pha_sca_rot), + QStruct("rotation_center", INCLUDE=xy_float) + ), + + Pad(4), + Float("zsprite_radius_scale"), + + SIZE=604 + ) + +rain_body = Struct("tagdata", + Pad(36), + reflexive("particle_types", particle_type, 8, DYN_NAME_PATH='.name'), + + SIZE=48, + ) + + +def get(): + return rain_def + +rain_def = TagDef("rain", + blam_header("rain"), + rain_body, + + ext=".weather_particle_system", endian=">", tag_cls=HekTag, + ) diff --git a/reclaimer/mcc_hek/defs/sbsp.py b/reclaimer/mcc_hek/defs/sbsp.py new file mode 100644 index 00000000..42d29085 --- /dev/null +++ b/reclaimer/mcc_hek/defs/sbsp.py @@ -0,0 +1,561 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .coll import * +from .objs.sbsp import SbspTag +from supyr_struct.defs.block_def import BlockDef +from supyr_struct.util import desc_variant + +cluster_fog_tooltip = ( + "Unknown flag is set if negative.\n" + "Add 0x8000 to get fog index." + ) + + +# the order is an array of vertices first, then an array of lightmap vertices. +# +uncompressed_vertex = QStruct("uncompressed_vertex", + Float('position_x'), Float('position_y'), Float('position_z'), + Float('normal_i'), Float('normal_j'), Float('normal_k'), + Float('binormal_i'), Float('binormal_j'), Float('binormal_k'), + Float('tangent_i'), Float('tangent_j'), Float('tangent_k'), + + Float('tex_coord_u'), Float('tex_coord_v'), + SIZE=56 + ) + +compressed_vertex = QStruct("compressed_vertex", + Float('position_x'), Float('position_y'), Float('position_z'), + UInt32('normal'), + UInt32('binormal'), + UInt32('tangent'), + + Float('tex_coord_u'), Float('tex_coord_v'), + SIZE=32 + ) + +uncompressed_lightmap_vertex = QStruct("uncompressed_lightmap_vertex", + # this normal is the direction the light is hitting from, and + # is used for calculating dynamic shadows on dynamic objects + Float('normal_i'), Float('normal_j'), Float('normal_k'), + Float('u'), Float('v'), + SIZE=20 + ) + +compressed_lightmap_vertex = QStruct("compressed_lightmap_vertex", + # this normal is the direction the light is hitting from, and + # is used for calculating dynamic shadows on dynamic objects + UInt32('normal'), + SInt16('u', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), + SInt16('v', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), + SIZE=8 + ) + +plane = QStruct("plane", + Float("i", MIN=-1.0, MAX=1.0), + Float("j", MIN=-1.0, MAX=1.0), + Float("k", MIN=-1.0, MAX=1.0), + Float("d"), + SIZE=16, ORIENT='h' + ) + +vertex = QStruct("vertex", INCLUDE=xyz_float, SIZE=12) + +collision_material = Struct("collision_material", + dependency("shader", valid_shaders), + FlUInt32("unknown", VISIBLE=False), + SIZE=20 + ) + +collision_bsp = Struct("collision_bsp", INCLUDE=permutation_bsp) +fast_collision_bsp = Struct("collision_bsp", INCLUDE=fast_permutation_bsp) + +node = Struct("node", + # these dont get byteswapped going from meta to tag + BytesRaw("unknown", SIZE=6), + #QStruct("unknown_0", UInt8("val0"), SInt8("val1"), ORIENT="h"), + #QStruct("unknown_1", UInt8("val0"), SInt8("val1"), ORIENT="h"), + #QStruct("unknown_2", UInt8("val0"), SInt8("val1"), ORIENT="h"), + SIZE=6 + ) + +leaf = QStruct("leaf", + # these unknowns are in the tag and are preserved in the meta + FlSInt16("unknown0", VISIBLE=False), + FlSInt16("unknown1", VISIBLE=False), + FlSInt16("unknown2", VISIBLE=False), + FlSInt16("unknown3", VISIBLE=False), + + SInt16("cluster"), + SInt16("surface_reference_count"), + SInt32("surface_references"), + SIZE=16, + ) + +leaf_surface = QStruct("leaf_surface", + SInt32("surface"), + SInt32("node"), + SIZE=8, ORIENT='h' + ) + +surface = QStruct("surface", + SInt16("a"), + SInt16("b"), + SInt16("c"), + SIZE=6, ORIENT='h', COMMENT=""" +This is a renderable surface(visible geometry and lightmap geometry)""" + ) + +material = Struct("material", + dependency("shader", valid_shaders), + SInt16("shader_permutation"), + Bool16("flags", + "coplanar", + "fog_plane", + ), + SInt32("surfaces", EDITABLE=False, + TOOLTIP=("The offset into the surfaces array that this\n" + "lightmap materials surfaces are located at.") + ), + SInt32("surface_count", EDITABLE=False, + TOOLTIP=("The number of surfaces in the array belonging\n" + "to this lightmap material.") + ), + QStruct("centroid", INCLUDE=xyz_float), + QStruct("ambient_color", INCLUDE=rgb_float), + SInt16("distant_light_count"), + Pad(2), + + QStruct("distant_light_0_color", INCLUDE=rgb_float), + QStruct("distant_light_0_direction", INCLUDE=ijk_float), + QStruct("distant_light_1_color", INCLUDE=rgb_float), + QStruct("distant_light_1_direction", INCLUDE=ijk_float), + Pad(12), + + QStruct("reflection_tint", INCLUDE=argb_float), + QStruct("shadow_vector", INCLUDE=ijk_float), + QStruct("shadow_color", INCLUDE=rgb_float), + QStruct("plane", INCLUDE=plane), + SInt16("breakable_surface", EDITABLE=False), + Pad(6), + + SInt32("vertices_count", EDITABLE=False), + SInt32("vertices_offset", EDITABLE=False, VISIBLE=False), + + FlUInt32("unknown_meta_offset0", VISIBLE=False), + FlUInt32("vertices_meta_offset", + TOOLTIP=("In xbox maps this is a bspmagic relative pointer that\n" + "points to a reflexive. The reflexive contains only a\n" + "bspmagic relative pointer to the vertices."), + VISIBLE=False + ), + FlUEnum16("vertex_type", # name is a guess + ("unknown", 0), + ("uncompressed", 2), + ("compressed", 3), + VISIBLE=False, + ), + Pad(2), + SInt32("lightmap_vertices_count", EDITABLE=False), + SInt32("lightmap_vertices_offset", EDITABLE=False, VISIBLE=False), + + FlUInt32("unknown_meta_offset1", VISIBLE=False), + FlUInt32("lightmap_vertices_meta_offset", + TOOLTIP=("In xbox maps this is a bspmagic relative pointer that\n" + "points to a reflexive. The reflexive contains only a\n" + "bspmagic relative pointer to the lightmap vertices."), + VISIBLE=False + ), + + rawdata_ref("uncompressed_vertices", max_size=4864000), + rawdata_ref("compressed_vertices", max_size=2560000), + SIZE=256 + ) + +lightmap = Struct("lightmap", + SInt16("bitmap_index"), + Pad(18), + reflexive("materials", material, 2048, + DYN_NAME_PATH='.shader.filepath'), + SIZE=32 + ) + +lens_flare = Struct("lens_flare", + dependency("shader", 'lens'), + SIZE=16 + ) + +lens_flare_marker = Struct("lens_flare_marker", + QStruct("position", INCLUDE=xyz_float), + QStruct("direction", + SInt8('i'), SInt8('j'), SInt8('k'), ORIENT='h' + ), + # While guerilla treats this like a signed int, there is no way that it + # is gonna be able to reference one of the 256 lens flares if its signed + UInt8('lens_flare_index'), + SIZE=16 + ) + +surface_index = QStruct("surface_index", + SInt32("surface_index"), + SIZE=4 + ) + +mirror = Struct("mirror", + QStruct("plane", INCLUDE=plane), + # might be padding, might not be + BytesRaw("unknown", VISIBLE=False, SIZE=20), + #Pad(20), + dependency("shader", valid_shaders), + reflexive("vertices", vertex, 512), + SIZE=64 + ) + +portal = QStruct("portal", + SInt16("portal"), + SIZE=2 + ) + +subcluster = Struct("subcluster", + QStruct('world_bounds_x', INCLUDE=from_to), + QStruct('world_bounds_y', INCLUDE=from_to), + QStruct('world_bounds_z', INCLUDE=from_to), + reflexive("surface_indices", surface_index, 128), + SIZE=36, COMMENT=""" +Subclusters define areas to render in square chunks. Surfaces indices are +the renderable surfaces(not collision) to render in this subcluster. This is +how Halo's renderer knows what surfaces to render in each cluster.""" + ) + +cluster = Struct("cluster", + SInt16('sky'), + SInt16('fog', TOOLTIP=cluster_fog_tooltip), + dyn_senum16('background_sound', + DYN_NAME_PATH="tagdata.background_sounds_palette.STEPTREE[DYN_I].name"), + dyn_senum16('sound_environment', + DYN_NAME_PATH="tagdata.sound_environments_palette." + + "STEPTREE[DYN_I].name"), + dyn_senum16('weather', + DYN_NAME_PATH="tagdata.weather_palettes.STEPTREE[DYN_I].name"), + + UInt16("transition_structure_bsp", VISIBLE=False), + UInt16("first_decal_index", VISIBLE=False), + UInt16("decal_count", VISIBLE=False), + + # almost certain this is padding, though a value in the third + # and fourth bytes is non-zero in meta, but not in a tag, so idk. + Pad(24), + + reflexive("predicted_resources", predicted_resource, 1024, VISIBLE=False), + reflexive("subclusters", subcluster, 4096), + SInt16("first_lens_flare_marker_index"), + SInt16("lens_flare_marker_count"), + reflexive("surface_indices", surface_index, 32768), + reflexive("mirrors", mirror, 16, DYN_NAME_PATH=".shader.filepath"), + reflexive("portals", portal, 128), + SIZE=104 + ) + +cluster_portal = Struct("cluster_portal", + SInt16("front_cluster"), + SInt16("back_cluster"), + SInt32("plane_index"), + QStruct("centroid", INCLUDE=xyz_float), + Float("bounding_radius"), + Bool32("flags", + "ai_cant_hear_through_this", + ), + + # might be padding, might not be + BytesRaw("unknown", VISIBLE=False, SIZE=24), + #Pad(24), + reflexive("vertices", vertex, 128), + SIZE=64 + ) + +breakable_surface = Struct("breakable_surface", + QStruct("centroid", INCLUDE=xyz_float), + Float("radius"), + SInt32("collision_surface_index"), + Pad(28), + SIZE=48 + ) + +fog_plane = Struct("fog_plane", + SInt16("front_region"), + FlSEnum16("material_type", + *(tuple((materials_list[i], i) for i in + range(len(materials_list))) + (("NONE", -1), )), + VISIBLE=False), # calculated when compiled into map + QStruct("plane", INCLUDE=plane), + reflexive("vertices", vertex, 4096), + SIZE=32 + ) + +fog_region = Struct("fog_region", + Pad(36), + dyn_senum16("fog_palette", + DYN_NAME_PATH="tagdata.fog_palettes.STEPTREE[DYN_I].name"), + dyn_senum16("weather_palette", + DYN_NAME_PATH="tagdata.weather_palettes.STEPTREE[DYN_I].name"), + SIZE=40 + ) + +fog_palette = Struct("fog_palette", + ascii_str32("name"), + dependency("fog", "fog "), + Pad(4), + ascii_str32("fog_scale_function"), + SIZE=136 + ) + +weather_palette = Struct("weather_palette", + ascii_str32("name"), + dependency("particle_system", "rain"), + Pad(4), + ascii_str32("particle_system_scale_function"), + + Pad(44), + dependency("wind", "wind"), + QStruct("wind_direction", INCLUDE=ijk_float), + Float("wind_magnitude"), + Pad(4), + ascii_str32("wind_scale_function"), + SIZE=240 + ) + +weather_polyhedra = Struct("weather_polyhedra", + QStruct("bounding_sphere_center", INCLUDE=xyz_float), + Float("bounding_sphere_radius"), + Pad(4), + reflexive("planes", plane, 16), + SIZE=32 + ) + +pathfinding_surface = QStruct("pathfinding_surface", + # this is actually a 3bit width index, 3bit height + # index, and 2 flags(is_walkable and is_breakable/is_broken) + # stored in a single byte(in that order) + UInt8("data"), + SIZE=1 + ) + +pathfinding_edge = QStruct("pathfinding_edge", UInt8("midpoint"), SIZE=1) + +background_sound_palette = Struct("background_sound_palette", + ascii_str32("name"), + dependency("background_sound", "lsnd"), + Pad(4), + ascii_str32("scale_function"), + SIZE=116 + ) + +sound_environment_palette = Struct("sound_environment_palette", + ascii_str32("name"), + dependency("sound_environment", "snde"), + SIZE=80 + ) + +marker = Struct("marker", + ascii_str32("name"), + QStruct("rotation", INCLUDE=ijkw_float), + QStruct("position", INCLUDE=xyz_float), + SIZE=60 + ) + + +detail_object_cell = QStruct("detail_object_cell", + SInt16("unknown1"), SInt16("unknown2"), + SInt16("unknown3"), SInt16("unknown4"), + SInt32("unknown5"), SInt32("unknown6"), SInt32("unknown7"), + SIZE=32 + ) + +detail_object_instance = QStruct("detail_object_instance", + SInt8("unknown1"), SInt8("unknown2"), + SInt8("unknown3"), SInt8("unknown4"), SInt16("unknown5"), + SIZE=6 + ) + +detail_object_count = QStruct("detail_object_count", + SInt16("unknown"), + SIZE=2 + ) + +detail_object_z_reference_vector = QStruct("detail_object_z_reference_vector", + Float("unknown1"), Float("unknown2"), + Float("unknown3"), Float("unknown4"), + SIZE=16 + ) + +detail_object = Struct("detail_object", + reflexive("cells", detail_object_cell, 262144), + reflexive("instances", detail_object_instance, 2097152), + reflexive("counts", detail_object_count, 8388608), + reflexive("z_reference_vectors", detail_object_z_reference_vector, 262144), + Bool8("flags", + "enabled", # required to be set on map compile. + # set to "parent.instances.size != 0" + VISIBLE=False + ), + SIZE=64 + ) + +runtime_decal = BytesRaw("unknown", SIZE=16) + + +face_vertex = QStruct("vertex", Float("x"), Float("y"), SIZE=8) +portal_index = Struct("portal_index", SInt32("portal_index"), SIZE=4) + +face = Struct("face", + SInt32("node_index"), + reflexive("vertices", face_vertex, 64), + SIZE=16 + ) + +leaf_map_leaf = Struct("leaf_map_leaf", + reflexive("faces", face, 256), + reflexive("portal_indices", portal_index, 256), + SIZE=24 + ) + +leaf_map_portal = Struct("leaf_map_portal", + SInt32("plane_index"), + SInt32("back_leaf_index"), + SInt32("front_leaf_index"), + reflexive("vertices", face_vertex, 64), + SIZE=24 + ) + +raw_cluster_data = QStruct("raw_cluster_data", + UInt16("unknown0"), + UInt16("unknown1"), + UInt16("unknown2"), + UInt16("unknown3"), + SIZE=8 + ) + +sbsp_body = Struct("tagdata", + dependency("lightmap_bitmaps", 'bitm'), + float_wu("vehicle_floor"), # world units + float_wu("vehicle_ceiling"), # world units + + Pad(20), + QStruct("default_ambient_color", INCLUDE=rgb_float), + Pad(4), + QStruct("default_distant_light_0_color", INCLUDE=rgb_float), + QStruct("default_distant_light_0_direction", INCLUDE=ijk_float), + QStruct("default_distant_light_1_color", INCLUDE=rgb_float), + QStruct("default_distant_light_1_direction", INCLUDE=ijk_float), + + Pad(12), + QStruct("default_reflection_tint", INCLUDE=argb_float), + QStruct("default_shadow_vector", INCLUDE=ijk_float), + QStruct("default_shadow_color", INCLUDE=rgb_float), + + Pad(4), + reflexive("collision_materials", collision_material, 512, + DYN_NAME_PATH='.shader.filepath'), + reflexive("collision_bsp", collision_bsp, 1), + reflexive("nodes", node, 131072, VISIBLE=False), + QStruct("world_bounds_x", INCLUDE=from_to), + QStruct("world_bounds_y", INCLUDE=from_to), + QStruct("world_bounds_z", INCLUDE=from_to), + reflexive("leaves", leaf, 65535), + reflexive("leaf_surfaces", leaf_surface, 262144), + reflexive("surfaces", surface, 131072), + reflexive("lightmaps", lightmap, 128), + + Pad(12), + reflexive("lens_flares", lens_flare, 256, + DYN_NAME_PATH='.shader.filepath'), + reflexive("lens_flare_markers", lens_flare_marker, 65535), + reflexive("clusters", cluster, 8192), + + # this is an array of 8 byte structs for each cluster + rawdata_ref("cluster_data", max_size=65536), + reflexive("cluster_portals", cluster_portal, 512), + + Pad(12), + reflexive("breakable_surfaces", breakable_surface, 256), + reflexive("fog_planes", fog_plane, 32), + reflexive("fog_regions", fog_region, 32), + reflexive("fog_palettes", fog_palette, 32, + DYN_NAME_PATH='.name'), + + Pad(24), + reflexive("weather_palettes", weather_palette, 32, + DYN_NAME_PATH='.name'), + reflexive("weather_polyhedras", weather_polyhedra, 32), + + Pad(24), + reflexive("pathfinding_surfaces", pathfinding_surface, 131072, VISIBLE=False), + reflexive("pathfinding_edges", pathfinding_edge, 262144, VISIBLE=False), + reflexive("background_sounds_palette", background_sound_palette, 64, + DYN_NAME_PATH='.name'), + reflexive("sound_environments_palette", sound_environment_palette, 64, + DYN_NAME_PATH='.name'), + rawdata_ref("sound_pas_data", max_size=131072), + + UInt32("unknown", VISIBLE=False), + Pad(20), + reflexive("markers", marker, 1024, DYN_NAME_PATH='.name'), + reflexive("detail_objects", detail_object, 1, VISIBLE=False), + + # the runtime decals reflexive is populated ONLY by the + # engine while it is running(I'm making an educated guess) + reflexive("runtime_decals", runtime_decal, 6144, VISIBLE=False), + + Pad(12), + reflexive("leaf_map_leaves", leaf_map_leaf, 65536, VISIBLE=False), + reflexive("leaf_map_portals", leaf_map_portal, 524288, VISIBLE=False), + SIZE=648, + ) + +fast_sbsp_body = desc_variant(sbsp_body, + ("collision_bsp", reflexive("collision_bsp", fast_collision_bsp, 1)), + ("nodes", raw_reflexive("nodes", node, 131072)), + ("leaves", raw_reflexive("leaves", leaf, 65535)), + ("leaf_surfaces", raw_reflexive("leaf_surfaces", leaf_surface, 262144)), + ("surfaces", raw_reflexive("surface", surface, 131072)), + ("lens_flare_markers", raw_reflexive("lens_flare_markers", lens_flare_marker, 65535)), + ("breakable_surfaces", raw_reflexive("breakable_surfaces", breakable_surface, 256)), + ("pathfinding_surfaces", raw_reflexive("pathfinding_surfaces", pathfinding_surface, 131072)), + ("pathfinding_edges", raw_reflexive("pathfinding_edges", pathfinding_edge, 262144)), + ("markers", raw_reflexive("markers", marker, 1024, DYN_NAME_PATH='.name')), + ) + +sbsp_meta_header_def = BlockDef("sbsp_meta_header", + # to convert these pointers to offsets, do: pointer - bsp_magic + UInt32("meta_pointer"), + UInt32("uncompressed_lightmap_materials_count"), + UInt32("uncompressed_lightmap_materials_pointer"), # name is a guess + UInt32("compressed_lightmap_materials_count"), + UInt32("compressed_lightmap_materials_pointer"), # name is a guess + UInt32("sig", DEFAULT="sbsp"), + SIZE=24, TYPE=QStruct + ) + + +def get(): + return sbsp_def + +sbsp_def = TagDef("sbsp", + blam_header("sbsp", 5), + sbsp_body, + + ext=".scenario_structure_bsp", endian=">", tag_cls=SbspTag, + ) + +fast_sbsp_def = TagDef("sbsp", + blam_header("sbsp", 5), + fast_sbsp_body, + + ext=".scenario_structure_bsp", endian=">", tag_cls=SbspTag, + ) diff --git a/reclaimer/mcc_hek/defs/scen.py b/reclaimer/mcc_hek/defs/scen.py new file mode 100644 index 00000000..24238809 --- /dev/null +++ b/reclaimer/mcc_hek/defs/scen.py @@ -0,0 +1,35 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .obje import * +from .objs.obje import ObjeTag +from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant + +# replace the object_type enum one that uses +# the correct default value for this object +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(6)) + ) + +scen_body = Struct("tagdata", + obje_attrs, + SIZE=508, + ) + + +def get(): + return scen_def + +scen_def = TagDef("scen", + blam_header('scen'), + scen_body, + + ext=".scenery", endian=">", tag_cls=ObjeTag + ) diff --git a/reclaimer/mcc_hek/defs/scex.py b/reclaimer/mcc_hek/defs/scex.py new file mode 100644 index 00000000..fa88bd9d --- /dev/null +++ b/reclaimer/mcc_hek/defs/scex.py @@ -0,0 +1,62 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .schi import * +from supyr_struct.defs.tag_def import TagDef +from .objs.shdr import ShdrTag + +chicago_2_stage_maps = Struct("two_stage_map", INCLUDE=chicago_4_stage_maps) + +scex_attrs = Struct("scex_attrs", + # Shader Properties + Struct("chicago_shader_extended", + UInt8("numeric_counter_limit", + MIN=0, MAX=255, SIDETIP="[0,255]"), # [0,255] + + Bool8("chicago_shader_flags", *trans_shdr_properties), + SEnum16("first_map_type", *trans_shdr_first_map_type), + SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), + SEnum16("framebuffer_fade_mode", *render_fade_mode), + SEnum16("framebuffer_fade_source", *function_outputs), + Pad(2), + ), + + #Lens Flare + float_wu("lens_flare_spacing"), # world units + dependency("lens_flare", "lens"), + reflexive("extra_layers", extra_layers_block, 4, + DYN_NAME_PATH='.filepath'), + reflexive("four_stage_maps", chicago_4_stage_maps, 4, + DYN_NAME_PATH='.bitmap.filepath'), + reflexive("two_stage_maps", chicago_2_stage_maps, 2, + DYN_NAME_PATH='.bitmap.filepath'), + Bool32("extra_flags", + "dont_fade_active_camouflage", + "numeric_countdown_timer" + ), + SIZE=80 + ) + +scex_body = Struct("tagdata", + shdr_attrs, + scex_attrs, + SIZE=120 + ) + + +def get(): + return scex_def + +scex_def = TagDef("scex", + blam_header('scex'), + scex_body, + + ext=".shader_transparent_chicago_extended", + endian=">", tag_cls=ShdrTag + ) diff --git a/reclaimer/mcc_hek/defs/schi.py b/reclaimer/mcc_hek/defs/schi.py new file mode 100644 index 00000000..55b626c5 --- /dev/null +++ b/reclaimer/mcc_hek/defs/schi.py @@ -0,0 +1,90 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .shdr import * +from supyr_struct.defs.tag_def import TagDef +from .objs.shdr import ShdrTag + +chicago_4_stage_maps = Struct("four_stage_map", + Bool16("flags" , + "unfiltered", + "alpha_replicate", + "u_clamped", + "v_clamped", + ), + + Pad(42), + SEnum16("color_function", *blend_functions), + SEnum16("alpha_function", *blend_functions), + + Pad(36), + #shader transformations + Float("map_u_scale"), + Float("map_v_scale"), + Float("map_u_offset"), + Float("map_v_offset"), + float_deg("map_rotation"), # degrees + float_zero_to_one("map_bias"), # [0,1] + dependency("bitmap", "bitm"), + + #shader animations + Pad(40), + Struct("u_animation", INCLUDE=anim_src_func_per_pha_sca), + Struct("v_animation", INCLUDE=anim_src_func_per_pha_sca), + Struct("rotation_animation", INCLUDE=anim_src_func_per_pha_sca_rot), + + QStruct("rotation_center", INCLUDE=xy_float), + SIZE=220, + ) + +schi_attrs = Struct("schi_attrs", + # Shader Properties + Struct("chicago_shader", + UInt8("numeric_counter_limit", + MIN=0, MAX=255, SIDETIP="[0,255]"), # [0,255] + + Bool8("chicago_shader_flags", *trans_shdr_properties), + SEnum16("first_map_type", *trans_shdr_first_map_type), + SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), + SEnum16("framebuffer_fade_mode", *render_fade_mode), + SEnum16("framebuffer_fade_source", *function_outputs), + Pad(2), + ), + + #Lens Flare + float_wu("lens_flare_spacing"), # world units + dependency("lens_flare", "lens"), + reflexive("extra_layers", extra_layers_block, 4, + DYN_NAME_PATH='.filepath'), + reflexive("maps", chicago_4_stage_maps, 4, + DYN_NAME_PATH='.bitmap.filepath'), + Bool32("extra_flags", + "dont_fade_active_camouflage", + "numeric_countdown_timer" + ), + SIZE=68 + ) + +schi_body = Struct("tagdata", + shdr_attrs, + schi_attrs, + SIZE=108 + ) + + +def get(): + return schi_def + +schi_def = TagDef("schi", + blam_header('schi'), + schi_body, + + ext=".shader_transparent_chicago", + endian=">", tag_cls=ShdrTag + ) diff --git a/reclaimer/mcc_hek/defs/scnr.py b/reclaimer/mcc_hek/defs/scnr.py new file mode 100644 index 00000000..78f11b6f --- /dev/null +++ b/reclaimer/mcc_hek/defs/scnr.py @@ -0,0 +1,1044 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant + + +def object_reference(name, *args, **kwargs): + "Macro to cut down on a lot of code" + block_name = kwargs.pop('block_name', name + 's').replace(' ', '_') + + dyn_type_path = ".....%s_palette.STEPTREE[DYN_I].name.filepath" % block_name + return Struct(name, + dyn_senum16('type', DYN_NAME_PATH=dyn_type_path), + dyn_senum16('name', + DYN_NAME_PATH=".....object_names.STEPTREE[DYN_I].name"), + Bool16('not_placed', + "automatically", + "on_easy", + "on_normal", + "on_hard", + ), + SInt16('desired_permutation'), + QStruct("position", INCLUDE=xyz_float), + ypr_float_rad("rotation"), + *args, + **kwargs + ) + +def object_swatch(name, def_id, size=48): + "Macro to cut down on a lot of code" + return Struct(name, + dependency("name", def_id), + SIZE=size + ) + +fl_float_xyz = QStruct("", + FlFloat("x"), + FlFloat("y"), + FlFloat("z"), + ORIENT="h" + ) + +stance_flags = FlBool16("stance", + "walk", + "look_only", + "primary_fire", + "secondary_fire", + "jump", + "crouch", + "melee", + "flashlight", + "action1", + "action2", + "action_hold", + ) + +unit_control_packet = Struct("unit_control_packet", + + ) + +r_a_stream_header = Struct("r_a_stream_header", + UInt8("move_index", DEFAULT=3, MAX=6), + UInt8("bool_index"), + stance_flags, + FlSInt16("weapon", DEFAULT=-1), + QStruct("speed", FlFloat("x"), FlFloat("y"), ORIENT="h"), + QStruct("feet", INCLUDE=fl_float_xyz), + QStruct("body", INCLUDE=fl_float_xyz), + QStruct("head", INCLUDE=fl_float_xyz), + QStruct("change", INCLUDE=fl_float_xyz), + FlUInt16("unknown1"), + FlUInt16("unknown2"), + FlUInt16("unknown3", DEFAULT=0xFFFF), + FlUInt16("unknown4", DEFAULT=0xFFFF), + SIZE=60 + ) + +device_flags = ( + "initially_open", # value of 1.0 + "initially_off", # value of 0.0 + "can_change_only_once", + "position_reversed", + "not_usable_from_any_side" + ) + +location_types = ( + "none", + "ctf", + "slayer", + "oddball", + "king", + "race", + "terminator", + "stub", + "ignored1", + "ignored2", + "ignored3", + "ignored4", + "all_games", + "all_games_except_ctf", + "all_games_except_ctf_and_race" + ) + +group_indices = tuple("ABCDEFGHIJKLMNOPQRSTUVWXYZ") + +squad_states = ( + "none", + "sleeping", + "alert", + "moving_repeat_same_position", + "moving_loop", + "moving_loop_back_and_forth", + "moving_loop_randomly", + "moving_randomly", + "guarding", + "guarding_at_position", + "searching", + "fleeing" + ) + +maneuver_when_states = ( + "never", + {NAME: "strength_at_75_percent", GUI_NAME: "< 75% strength"}, + {NAME: "strength_at_50_percent", GUI_NAME: "< 50% strength"}, + {NAME: "strength_at_25_percent", GUI_NAME: "< 25% strength"}, + "anybody_dead", + {NAME: "dead_at_25_percent", GUI_NAME: "25% dead"}, + {NAME: "dead_at_50_percent", GUI_NAME: "50% dead"}, + {NAME: "dead_at_75_percent", GUI_NAME: "75% dead"}, + "all_but_one_dead", + "all_dead" + ) + +atom_types = ( + "pause", + "goto", + "goto_and_face", + "move_in_direction", + "look", + "animation_mode", + "crouch", + "shoot", + "grenade", + "vehicle", + "running_jump", + "targeted_jump", + "script", + "animate", + "recording", + "action", + "vocalize", + "targeting", + "initiative", + "wait", + "loop", + "die", + "move_immediate", + "look_random", + "look_player", + "look_object", + "set_radius", + "teleport" + ) + + +sky = Struct("sky", + dependency("sky", "sky "), + SIZE=16 + ) + +child_scenario = Struct("child_scenario", + dependency("child_scenario", "scnr"), + SIZE=32 + ) + +function = Struct('function', + Bool32('flags', + 'scripted', + 'invert', + 'additive', + 'always_active', + ), + ascii_str32('name'), + float_sec('period'), # seconds + dyn_senum16('scale_period_by', DYN_NAME_PATH="..[DYN_I].name"), + SEnum16('function', *animation_functions), + dyn_senum16('scale_function_by', DYN_NAME_PATH="..[DYN_I].name"), + SEnum16('wobble_function', *animation_functions), + float_sec('wobble_period'), # seconds + Float('wobble_magnitude', SIDETIP="%"), # percent + Float('square_wave_threshold'), + SInt16('step_count'), + SEnum16('map_to', *fade_functions), + SInt16('sawtooth_count'), + + Pad(2), + dyn_senum16('scale_result_by', DYN_NAME_PATH="..[DYN_I].name"), + SEnum16('bounds_mode', + 'clip', + 'clip_and_normalize', + 'scale_to_fit', + ), + QStruct('bounds', INCLUDE=from_to), + + Pad(6), + dyn_senum16('turn_off_with', DYN_NAME_PATH="..[DYN_I].name"), + + SIZE=120 + ) + +comment = Struct("comment", + QStruct("position", INCLUDE=xyz_float), + Pad(16), + rawtext_ref("comment_data", StrLatin1, max_size=16384), + SIZE=48 + ) + +object_name = Struct("object_name", + ascii_str32("name"), + FlSEnum16("object_type", + *((object_types[i], i - 1) for i in + range(len(object_types))), + VISIBLE=False, EDITABLE=False, DEFAULT=-1 + ), + FlSInt16("reflexive_index", VISIBLE=False, EDITABLE=False), + SIZE=36 + ) + +# Object references +scenery = object_reference("scenery", SIZE=72, block_name="sceneries") + +biped = object_reference("biped", + Pad(40), + float_zero_to_one("body_vitality"), + Bool32("flags", + "dead", + ), + SIZE=120 + ) + +vehicle = object_reference("vehicle", + Pad(40), + float_zero_to_one("body_vitality"), + Bool32("flags", + "dead", + ), + + Pad(8), + SInt8("multiplayer_team_index"), + Pad(1), + Bool16("multiplayer_spawn_flags", + "slayer_default", + "ctf_default", + "king_default", + "oddball_default", + #"unused1", + #"unused2", + #"unused3", + #"unused4", + ("slayer_allowed", 1<<8), + ("ctf_allowed", 1<<9), + ("king_allowed", 1<<10), + ("oddball_allowed", 1<<11), + #"unused5", + #"unused6", + #"unused7", + #"unused8", + ), + SIZE=120 + ) + +equipment = object_reference("equipment", + Pad(2), + Bool16("flags", + "initially_at_rest", + "obsolete", + {NAME: "can_accelerate", GUI_NAME:"moves due to explosions"}, + ), + SIZE=40 + ) + +weapon = object_reference("weapon", + Pad(40), + SInt16("rounds_left"), + SInt16("rounds_loaded"), + Bool16("flags", + "initially_at_rest", + "obsolete", + {NAME: "can_accelerate", GUI_NAME:"moves due to explosions"}, + ), + SIZE=92 + ) + +device_group = Struct("device_group", + ascii_str32("name"), + float_zero_to_one("initial_value"), + Bool32("flags", + "can_change_only_once" + ), + SIZE=52 + ) + +machine = object_reference("machine", + Pad(8), + dyn_senum16("power_group", + DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), + dyn_senum16("position_group", + DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), + Bool32("flags", *device_flags), + Bool32("more_flags", + "does_not_operate_automatically", + "one_sided", + "never_appears_locked", + "opened_by_melee_attack", + ), + SIZE=64 + ) + +control = object_reference("control", + Pad(8), + dyn_senum16("power_group", + DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), + dyn_senum16("position_group", + DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), + Bool32("flags", *device_flags), + Bool32("more_flags", + "usable_from_both_sides", + ), + SInt16("DONT_TOUCH_THIS"), # why? + SIZE=64 + ) + +light_fixture = object_reference("light_fixture", + Pad(8), + dyn_senum16("power_group", + DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), + dyn_senum16("position_group", + DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), + Bool32("flags", *device_flags), + QStruct("color", INCLUDE=rgb_float), + Float("intensity"), + Float("falloff_angle"), # radians + Float("cutoff_angle"), # radians + SIZE=88 + ) + +sound_scenery = object_reference("sound_scenery", SIZE=40, block_name="sound_sceneries") + +# Object swatches +scenery_swatch = object_swatch("scenery_swatch", "scen") +biped_swatch = object_swatch("biped_swatch", "bipd") +vehicle_swatch = object_swatch("vehicle_swatch", "vehi") +equipment_swatch = object_swatch("equipment_swatch", "eqip") +weapon_swatch = object_swatch("weapon_swatch", "weap") +machine_swatch = object_swatch("machine_swatch", "mach") +control_swatch = object_swatch("control_swatch", "ctrl") +light_fixture_swatch = object_swatch("light_fixture_swatch", "lifi") +sound_scenery_swatch = object_swatch("sound_scenery_swatch", "ssce") + +player_starting_profile = Struct("player_starting_profile", + ascii_str32("name"), + float_zero_to_one("starting_health_modifier"), + float_zero_to_one("starting_shield_modifier"), + dependency("primary_weapon", "weap"), + SInt16("primary_rounds_loaded"), + SInt16("primary_rounds_total"), + dependency("secondary_weapon", "weap"), + SInt16("secondary_rounds_loaded"), + SInt16("secondary_rounds_total"), + SInt8("starting_frag_grenade_count", MIN=0), + SInt8("starting_plasma_grenade_count", MIN=0), + SIZE=104 + ) + +player_starting_location = Struct("player_starting_location", + QStruct("position", INCLUDE=xyz_float), + float_rad("facing"), # radians + SInt16("team_index"), + dyn_senum16("bsp_index", + DYN_NAME_PATH=".....structure_bsps.STEPTREE[DYN_I].structure_bsp.filepath"), + SEnum16("type_0", *location_types), + SEnum16("type_1", *location_types), + SEnum16("type_2", *location_types), + SEnum16("type_3", *location_types), + SIZE=52 + ) + +player_starting_location2 = desc_variant(player_starting_location, + ("bsp_index", dyn_senum16("bsp_index", + DYN_NAME_PATH="........structure_bsps.STEPTREE[DYN_I].structure_bsp.filepath" + ) + ) + ) + + +trigger_volume = Struct("trigger_volume", + # if this unknown != 1, the trigger volume is disabled + FlUInt16("unknown0", DEFAULT=1, EDITABLE=False, VISIBLE=False), + Pad(2), + ascii_str32("name"), + BytesRaw("unknown1", SIZE=12, VISIBLE=False), + QStruct("binormal", INCLUDE=ijk_float), + QStruct("normal", INCLUDE=ijk_float), + QStruct("position", INCLUDE=xyz_float, + TOOLTIP=("Volume sides extend in one direction from this position.\n" + "This position is the origin corner for the trigger volume.")), + QStruct("sides", + Float("w", TOOLTIP="Along local y axis"), + Float("h", TOOLTIP="Along local z axis"), + Float("l", TOOLTIP="Along local x axis"), + ORIENT='h' + ), + SIZE=96, + COMMENT=( + "To make the trigger volumes rotation be zero, the normal and\n" + "binormal should be (1, 0, 0) and (0, 1, 0) respectively.") + ) + +recorded_animation = Struct("recorded_animation", + ascii_str32("name"), + SInt8("version"), + SInt8("raw_animation_data"), + SInt8("unit_control_data_version"), + Pad(1), + SInt16("length_of_animation", SIDETIP="ticks"), # ticks + Pad(6), + rawdata_ref("recorded_animation_event_stream", max_size=2097152), + SIZE=64 + ) + +netgame_flag = Struct("netgame_flag", + QStruct("position", INCLUDE=xyz_float), + float_rad("facing"), # radians + SEnum16("type", + "ctf_flag", + "ctf_vehicle", + "oddball_ball_spawn", + "race_track", + "race_vehicle", + "vegas_bank", + "teleport_from", + "teleport_to", + "hill_flag", + ), + SInt16("team_index"), + dependency("weapon_group", "itmc"), + SIZE=148 + ) + +netgame_equipment = Struct("netgame_equipment", + Bool32("flags", + "levitate" + ), + SEnum16("type_0", *location_types), + SEnum16("type_1", *location_types), + SEnum16("type_2", *location_types), + SEnum16("type_3", *location_types), + SInt16("team_index"), + SInt16("spawn_time", SIDETIP="seconds(0 = default)", + UNIT_SCALE=sec_unit_scale), # seconds + + Pad(48), + QStruct("position", INCLUDE=xyz_float), + float_rad("facing"), # radians + dependency("item_collection", "itmc"), + SIZE=144 + ) + +starting_equipment = Struct("starting_equipment", + Bool32("flags", + "no_grenades", + "plasma_grenades", + ), + SEnum16("type_0", *location_types), + SEnum16("type_1", *location_types), + SEnum16("type_2", *location_types), + SEnum16("type_3", *location_types), + + Pad(48), + dependency("item_collection_1", "itmc"), + dependency("item_collection_2", "itmc"), + dependency("item_collection_3", "itmc"), + dependency("item_collection_4", "itmc"), + dependency("item_collection_5", "itmc"), + dependency("item_collection_6", "itmc"), + SIZE=204 + ) + +bsp_switch_trigger_volume = Struct("bsp_switch_trigger_volume", + dyn_senum16("trigger_volume", + DYN_NAME_PATH=".....trigger_volumes.STEPTREE[DYN_I].name"), + dyn_senum16("source", + DYN_NAME_PATH=".....structure_bsps.STEPTREE[DYN_I].structure_bsp.filepath"), + dyn_senum16("destination", + DYN_NAME_PATH=".....structure_bsps.STEPTREE[DYN_I].structure_bsp.filepath"), + FlUInt16("unknown", EDITABLE=False), + SIZE=8 + ) + +decal = Struct("decal", + dyn_senum16("decal_type", + DYN_NAME_PATH=".....decals_palette.STEPTREE[DYN_I].name.filepath"), + SInt8("yaw"), + SInt8("pitch"), + QStruct("position", INCLUDE=xyz_float), + SIZE=16 + ) + +decal_swatch = object_swatch("decal_swatch", "deca", 16) +detail_object_collection_swatch = object_swatch( + "detail_object_collection_swatch", "dobc") +actor_swatch = object_swatch("actor_swatch", "actv", 16) + +ai_anim_reference = Struct("ai_animation_reference", + ascii_str32("animation_name"), + dependency("animation_graph", "antr"), + SIZE=60 + ) + +ai_script_reference = Struct("ai_script_reference", + ascii_str32("script_name"), + SIZE=40 + ) + +ai_recording_reference = Struct("ai_recording_reference", + ascii_str32("recording_name"), + SIZE=40 + ) + +halo_script = Struct("script", + ascii_str32("name", EDITABLE=False), + SEnum16("type", *script_types), + SEnum16("return_type", *script_object_types, EDITABLE=False), + UInt32("root_expression_index", EDITABLE=False), + Computed("decompiled_script", WIDGET=HaloScriptTextFrame), + SIZE=92, + ) + +halo_global = Struct("global", + ascii_str32("name", EDITABLE=False), + SEnum16("type", *script_object_types, EDITABLE=False), + Pad(6), + UInt32("initialization_expression_index", EDITABLE=False), + Computed("decompiled_script", WIDGET=HaloScriptTextFrame), + SIZE=92, + ) + +reference = Struct("tag_reference", + Pad(24), + dependency("reference"), + SIZE=40 + ) + +source_file = Struct("source_file", + ascii_str32("source_name"), + rawdata_ref("source", max_size=262144, widget=HaloScriptSourceFrame), + SIZE=52 + ) + +cutscene_flag = Struct("cutscene_flag", + Pad(4), + ascii_str32("name"), + QStruct("position", INCLUDE=xyz_float), + yp_float_rad("facing"), # radians + SIZE=92 + ) + +cutscene_camera_point = Struct("cutscene_camera_point", + Pad(4), + ascii_str32("name"), + Pad(4), + QStruct("position", INCLUDE=xyz_float), + ypr_float_rad("orientation"), # radians + float_rad("field_of_view"), # radians + SIZE=104 + ) + +cutscene_title = Struct("cutscene_title", + Pad(4), + ascii_str32("name"), + Pad(4), + QStruct("text_bounds", + SInt16("t"), SInt16("l"), SInt16("b"), SInt16("r"), + ORIENT='h', + ), + SInt16("string_index"), + SEnum16("text_style", + "plain", + "bold", + "italic", + "condense", + "underline", + ), + SEnum16("justification", + "left", + "right", + "center", + ), + + Pad(6), + #QStruct("text_color", INCLUDE=argb_byte), + #QStruct("shadow_color", INCLUDE=argb_byte), + UInt32("text_color", INCLUDE=argb_uint32), + UInt32("shadow_color", INCLUDE=argb_uint32), + float_sec("fade_in_time"), # seconds + float_sec("up_time"), # seconds + float_sec("fade_out_time"), # seconds + SIZE=96 + ) + + +move_position = Struct("move_position", + QStruct("position", INCLUDE=xyz_float), + float_rad("facing"), # radians + Float("weight"), + from_to_sec("time"), + dyn_senum16("animation", + DYN_NAME_PATH="tagdata.ai_animation_references.STEPTREE[DYN_I].animation_name"), + SInt8("sequence_id"), + + Pad(45), + SInt32("surface_index"), + SIZE=80 + ) + +actor_starting_location = Struct("starting_location", + QStruct("position", INCLUDE=xyz_float), + float_rad("facing"), # radians + Pad(2), + SInt8("sequence_id"), + Bool8("flags", + "required", + ), + SEnum16("return_state", *(dict(NAME=n, GUI_NAME=n) for n in squad_states)), + SEnum16("initial_state", *(dict(NAME=n, GUI_NAME=n) for n in squad_states)), + dyn_senum16("actor_type", + DYN_NAME_PATH="tagdata.actors_palette.STEPTREE[DYN_I].name.filepath"), + dyn_senum16("command_list", + DYN_NAME_PATH="tagdata.command_lists.STEPTREE[DYN_I].name"), + SIZE=28 + ) + +squad = Struct("squad", + ascii_str32("name"), + dyn_senum16("actor_type", + DYN_NAME_PATH="tagdata.actors_palette.STEPTREE[DYN_I].name.filepath"), + dyn_senum16("platoon", + DYN_NAME_PATH=".....platoons.STEPTREE[DYN_I].name"), + SEnum16("initial_state", *(dict(NAME=n, GUI_NAME=n) for n in squad_states)), + SEnum16("return_state", *(dict(NAME=n, GUI_NAME=n) for n in squad_states)), + Bool32("flags", + "unused", + "never_search", + "start_timer_immediately", + "no_timer_delay_forever", + "magic_sight_after_timer", + "automatic_migration", + ), + SEnum16("unique_leader_type", + "normal", + "none", + "random", + "sgt_johnson", + "sgt_lehto", + ), + + Pad(32), + dyn_senum16("maneuver_to_squad", DYN_NAME_PATH="..[DYN_I].name"), + float_sec("squad_delay_time"), # seconds + Bool32("attacking", *group_indices), + Bool32("attacking_search", *group_indices), + Bool32("attacking_guard", *group_indices), + Bool32("defending", *group_indices), + Bool32("defending_search", *group_indices), + Bool32("defending_guard", *group_indices), + Bool32("pursuing", *group_indices), + + Pad(12), + SInt16("normal_diff_count"), + SInt16("insane_diff_count"), + SEnum16("major_upgrade", + "normal", + "few", + "many", + "none", + "all", + ), + Pad(2), + + SInt16("respawn_min_actors"), + SInt16("respawn_max_actors"), + SInt16("respawn_total"), + Pad(2), + + from_to_sec("respawn_delay"), + + Pad(48), + reflexive("move_positions", move_position, 31), + reflexive("starting_locations", actor_starting_location, 31), + SIZE=232 + ) + +platoon = Struct("platoon", + ascii_str32("name"), + Bool32("flags", + "flee_when_maneuvering", + "say_advancing_when_maneuvering", + "start_in_defending_state", + ), + + Pad(12), + SEnum16("change_attacking_defending_state", *maneuver_when_states), + dyn_senum16("change_happens_to", DYN_NAME_PATH="..[DYN_I].name"), + + Pad(8), + SEnum16("maneuver_when", *maneuver_when_states), + dyn_senum16("maneuver_happens_to", DYN_NAME_PATH="..[DYN_I].name"), + SIZE=172 + ) + +firing_position = Struct("firing_position", + QStruct("position", INCLUDE=xyz_float), + SEnum16("group_index", *group_indices), + FlUInt16('bsp_cluster', VISIBLE=False), # calculated on map compile + Pad(4), + FlSInt32('bsp_surface', VISIBLE=False), # calculated on map compile + SIZE=24 + ) + +encounter = Struct("encounter", + ascii_str32("name"), + Bool32("flags", + "not_initially_created", + "respawn_enabled", + "initially_blind", + "initially_deaf", + "initially_braindead", + "firing_positions_are_3d", + "manual_bsp_index_specified", + ), + SEnum16("team_index", + {NAME: "default", GUI_NAME: "0 / default_by_unit"}, + {NAME: "player", GUI_NAME: "1 / player"}, + {NAME: "human", GUI_NAME: "2 / human"}, + {NAME: "covenant", GUI_NAME: "3 / covenant"}, + {NAME: "flood", GUI_NAME: "4 / flood"}, + {NAME: "sentinel", GUI_NAME: "5 / sentinel"}, + {NAME: "unused6", GUI_NAME: "6 / unused6"}, + {NAME: "unused7", GUI_NAME: "7 / unused7"}, + {NAME: "unused8", GUI_NAME: "8 / unused8"}, + {NAME: "unused9", GUI_NAME: "9 / unused9"} + ), + SInt16('unknown', VISIBLE=False), + SEnum16("search_behavior", + "normal", + "never", + "tenacious" + ), + dyn_senum16("manual_bsp_index", + DYN_NAME_PATH=".....structure_bsps.STEPTREE[DYN_I].structure_bsp.filepath"), + from_to_sec("respawn_delay"), + + Pad(74), + FlSInt16('bsp_index', VISIBLE=False), # calculated on map compile + reflexive("squads", squad, 64), + reflexive("platoons", platoon, 32, DYN_NAME_PATH='.name'), + reflexive("firing_positions", firing_position, 512), + reflexive("player_starting_locations", player_starting_location2, 256), + + SIZE=176 + ) + +command = Struct("command", + SEnum16("atom_type", *atom_types), + SInt16("atom_modifier"), + Float("parameter_1"), + Float("parameter_2"), + dyn_senum16("point_1", DYN_NAME_PATH=".....points.STEPTREE[DYN_I]"), + dyn_senum16("point_2", DYN_NAME_PATH=".....points.STEPTREE[DYN_I]"), + dyn_senum16("animation", + DYN_NAME_PATH="tagdata.ai_animation_references.STEPTREE[DYN_I].animation_name"), + dyn_senum16("script", + DYN_NAME_PATH="tagdata.scripts.STEPTREE[DYN_I].name"), + dyn_senum16("recording", + DYN_NAME_PATH="tagdata.ai_recording_references.STEPTREE[DYN_I].recording_name"), + dyn_senum16("command", DYN_NAME_PATH="..[DYN_I].atom_type.enum_name"), + dyn_senum16("object_name", DYN_NAME_PATH="tagdata.object_names.STEPTREE[DYN_I].name"), + SIZE=32 + ) + +point = Struct("point", + QStruct("position", INCLUDE=xyz_float), + SIZE=20 + ) + +command_list = Struct("command_list", + ascii_str32("name"), + Bool32("flags", + "allow_initiative", + "allow_targeting", + "disable_looking", + "disable_communication", + "disable_falling_damage", + "manual_bsp_index", + ), + + Pad(8), + dyn_senum16("manual_bsp_index", + DYN_NAME_PATH=".....structure_bsps.STEPTREE[DYN_I].structure_bsp.filepath"), + dyn_senum16("bsp_index", + DYN_NAME_PATH=".....structure_bsps.STEPTREE[DYN_I].structure_bsp.filepath", + VISIBLE=False + ), + reflexive("commands", command, 64), + reflexive("points", point, 64), + SIZE=96 + ) + +participant = Struct("participant", + Pad(2), + Bool16("flags", + "optional", + "has_alternate", + "is_alternate", + ), + SEnum16("selection_type", + "friendly_actor", + "disembodied", + "in_players_vehicle", + "not_in_a_vehicle", + "prefer_sergeant", + "any_actor", + "radio_unit", + "radio_sergeant", + ), + SEnum16("actor_type", *actor_types), + dyn_senum16("use_this_object", DYN_NAME_PATH="tagdata.object_names.STEPTREE[DYN_I].name"), + dyn_senum16("set_new_name", DYN_NAME_PATH="tagdata.object_names.STEPTREE[DYN_I].name"), + + Pad(12), + BytesRaw("unknown", DEFAULT=b"\xFF"*12, SIZE=12, VISIBLE=False), + ascii_str32("encounter_name"), + SIZE=84 + ) + +line = Struct("line", + Bool16("flags", + "addressee_look_at_speaker", + "everyone_look_at_speaker", + "everyone_look_at_addressee", + "wait_until_told_to_advance", + "wait_until_speaker_nearby", + "wait_until_everyone_nearby", + ), + dyn_senum16("participant", + DYN_NAME_PATH=".....participants.STEPTREE[DYN_I].encounter_name"), + SEnum16("addressee", + "none", + "player", + "participant", + ), + dyn_senum16("addressee_participant", + DYN_NAME_PATH=".....participants.STEPTREE[DYN_I].encounter_name"), + + Pad(4), + Float("line_delay_time"), + + Pad(12), + dependency("variant_1", "snd!"), + dependency("variant_2", "snd!"), + dependency("variant_3", "snd!"), + dependency("variant_4", "snd!"), + dependency("variant_5", "snd!"), + dependency("variant_6", "snd!"), + SIZE=124 + ) + +ai_conversation = Struct("ai_conversation", + ascii_str32("name"), + Bool16("flags", + "stop_if_death", + "stop_if_damaged", + "stop_if_visible_enemy", + "stop_if_alerted_to_enemy", + "player_must_be_visible", + "stop_other_actions", + "keep_trying_to_play", + "player_must_be_looking", + ), + + Pad(2), + float_wu("trigger_distance"), + float_wu("run_to_player_distance"), + + Pad(36), + reflexive("participants", participant, 8, + DYN_NAME_PATH='.encounter_name'), + reflexive("lines", line, 32), + SIZE=116 + ) + +structure_bsp = Struct("structure_bsp", + UInt32("bsp_pointer", VISIBLE=False), + UInt32("bsp_size", VISIBLE=False), + UInt32("bsp_magic", VISIBLE=False), + Pad(4), + dependency("structure_bsp", "sbsp"), + SIZE=32 + ) + +scnr_body = Struct("tagdata", + dependency("DONT_USE", 'sbsp'), + dependency("WONT_USE", 'sbsp'), + dependency("CANT_USE", 'sky '), + reflexive("skies", sky, 8, DYN_NAME_PATH='.sky.filepath'), + SEnum16("type", + "singleplayer", + "multiplayer", + "main_menu" + ), + Bool16("flags", + "cortana_hack", + "use_demo_ui" + ), + reflexive("child_scenarios", child_scenario, 16, + DYN_NAME_PATH='.child_scenario.filepath'), + float_rad("local_north"), # radians + + Pad(156), + reflexive("predicted_resources", predicted_resource, 1024, VISIBLE=False), + reflexive("functions", function, 32, + DYN_NAME_PATH='.name'), + rawdata_ref("scenario_editor_data", max_size=65536), + reflexive("comments", comment, 1024), + + Pad(224), + reflexive("object_names", object_name, 512, + DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), + reflexive("sceneries", scenery, 2000, IGNORE_SAFE_MODE=True), + reflexive("sceneries_palette", scenery_swatch, 100, + DYN_NAME_PATH='.name.filepath'), + reflexive("bipeds", biped, 128, IGNORE_SAFE_MODE=True), + reflexive("bipeds_palette", biped_swatch, 100, + DYN_NAME_PATH='.name.filepath'), + reflexive("vehicles", vehicle, 80, IGNORE_SAFE_MODE=True), + reflexive("vehicles_palette", vehicle_swatch, 100, + DYN_NAME_PATH='.name.filepath'), + reflexive("equipments", equipment, 256, IGNORE_SAFE_MODE=True), + reflexive("equipments_palette", equipment_swatch, 100, + DYN_NAME_PATH='.name.filepath'), + reflexive("weapons", weapon, 128, IGNORE_SAFE_MODE=True), + reflexive("weapons_palette", weapon_swatch, 100, + DYN_NAME_PATH='.name.filepath'), + + reflexive("device_groups", device_group, 128, + DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), + reflexive("machines", machine, 400, IGNORE_SAFE_MODE=True), + reflexive("machines_palette", machine_swatch, 100, + DYN_NAME_PATH='.name.filepath'), + reflexive("controls", control, 100, IGNORE_SAFE_MODE=True), + reflexive("controls_palette", control_swatch, 100, + DYN_NAME_PATH='.name.filepath'), + reflexive("light_fixtures", light_fixture, 500, IGNORE_SAFE_MODE=True), + reflexive("light_fixtures_palette", light_fixture_swatch, 100, + DYN_NAME_PATH='.name.filepath'), + reflexive("sound_sceneries", sound_scenery, 256, IGNORE_SAFE_MODE=True), + reflexive("sound_sceneries_palette", sound_scenery_swatch, 100, + DYN_NAME_PATH='.name.filepath'), + + Pad(84), + reflexive("player_starting_profiles", player_starting_profile, 256, + DYN_NAME_PATH='.name'), + reflexive("player_starting_locations", player_starting_location, 256), + reflexive("trigger_volumes", trigger_volume, 256, + DYN_NAME_PATH='.name'), + reflexive("recorded_animations", recorded_animation, 1024, + DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), + reflexive("netgame_flags", netgame_flag, 200, + DYN_NAME_PATH='.type.enum_name'), + reflexive("netgame_equipments", netgame_equipment, 200, + DYN_NAME_PATH='.item_collection.filepath'), + reflexive("starting_equipments", starting_equipment, 200), + reflexive("bsp_switch_trigger_volumes", bsp_switch_trigger_volume, 256), + reflexive("decals", decal, 65535), + reflexive("decals_palette", decal_swatch, 128, + DYN_NAME_PATH='.name.filepath'), + reflexive("detail_object_collection_palette", + detail_object_collection_swatch, 32, DYN_NAME_PATH='.name.filepath'), + + Pad(84), + reflexive("actors_palette", actor_swatch, 64, + DYN_NAME_PATH='.name.filepath'), + reflexive("encounters", encounter, 128, DYN_NAME_PATH='.name'), + reflexive("command_lists", command_list, 256, DYN_NAME_PATH='.name'), + reflexive("ai_animation_references", ai_anim_reference, 128, + DYN_NAME_PATH='.animation_name'), + reflexive("ai_script_references", ai_script_reference, 128, + DYN_NAME_PATH='.script_name'), + reflexive("ai_recording_references", ai_recording_reference, 128, + DYN_NAME_PATH='.recording_name'), + reflexive("ai_conversations", ai_conversation, 128, + DYN_NAME_PATH='.name'), + rawdata_ref("script_syntax_data", max_size=380076, IGNORE_SAFE_MODE=True), + rawdata_ref("script_string_data", max_size=262144, IGNORE_SAFE_MODE=True), + reflexive("scripts", halo_script, 512, DYN_NAME_PATH='.name'), + reflexive("globals", halo_global, 128, DYN_NAME_PATH='.name'), + reflexive("references", reference, 256, + DYN_NAME_PATH='.reference.filepath'), + reflexive("source_files", source_file, 8, DYN_NAME_PATH='.source_name'), + + Pad(24), + reflexive("cutscene_flags", cutscene_flag, 512, DYN_NAME_PATH='.name'), + reflexive("cutscene_camera_points", cutscene_camera_point, 512, + DYN_NAME_PATH='.name'), + reflexive("cutscene_titles", cutscene_title, 64, DYN_NAME_PATH='.name'), + Pad(12), # OS bsp_modifiers reflexive + + Pad(96), + dependency("custom_object_names", 'ustr'), + dependency("ingame_help_text", 'ustr'), + dependency("hud_messages", 'hmt '), + reflexive("structure_bsps", structure_bsp, 16, + DYN_NAME_PATH='.structure_bsp.filepath'), + SIZE=1456, + ) + +def get(): + return scnr_def + +scnr_def = TagDef("scnr", + blam_header('scnr', 2), + scnr_body, + + ext=".scenario", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/senv.py b/reclaimer/mcc_hek/defs/senv.py new file mode 100644 index 00000000..42aabecb --- /dev/null +++ b/reclaimer/mcc_hek/defs/senv.py @@ -0,0 +1,239 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .shdr import * +from .objs.shdr import ShdrTag +from supyr_struct.defs.tag_def import TagDef + +environment_shader_comment = """ENVIRONMENT SHADER +Setting enables per-pixel atmospheric fog (for models) but disables +point/spot lights, planar fog, and the ability to control the atmospheric fog density for +this shader. + +Alpha-tested controls if the shader is masked by the diffuse alpha (or depending on +the environment shader type's setting) the bump map alpha. The transparancy is only 1-bit""" + +environment_shader_type_comment = """ENVIRONMENT SHADER TYPE +Controls how diffuse maps are combined: + +NORMAL: +Secondary detail map alpha controls blend between primary and secondary detail map. +Specular mask is alpha of blended primary and secondary detail map alpha multiplied by +alpha of micro detail map. + +BLENDED: +Base map alpha controls blend between primary and secondary detail map. +Specular mask is alpha of blended primary and secondary detail map alpha multiplied by +alpha of micro detail map. + +BLENDED BASE SPECULAR: +Same as BLENDED, except specular mask is alpha is base map multiplied with +the alpha of micro detail map.""" + +bump_properties_comment = """BUMP PROPERTIES +Perforated (alpha-tested) shaders use alpha in bump map.""" + +tex_scroll_anim_comment = """TEXTURE SCROLLING ANIMATION +Scrolls all 2D maps simultaneously.""" + +self_illum_comment = """SELF-ILLUMINATION PROPERTIES +There are three self-illumination effects which are added together. +Each effect has an , used when the shader is active, and an , used when +the shader is not active. The self-illumination map is used as follows: +* RED: primary mask +* GREEN: secondary mask +* BLUE: plasma mask +* ALPHA: plasma animation reference + +Each effect also has an animation , and , used when the shader is +active. The primary and secondary effects simply modulate the by the animation +value to produce an animation color, and then blend between the animation color and the + based on the shader's activation level, and finally modulate by the mask. + +The plasma shader compares the animation value with the alpha channel of the map (the plasma +animation reference) and produces a high value when they are similar and a dark value when +they are different. This value modulates the to produce a plasma animation +color, and the rest proceeds just like the primary and secondary effects.""" + +specular_properties_comment = """SPECULAR PROPERTIES +Controls the dynamic specular highlights. The highlight is modulated by brightness +and a blend between perpendicular and parrallel color. + +These color values also affect the colour of the reflection in REFLECTION PROPERTIES""" + +reflection_properties_comment = """REFLECTION PROPERTIES +Controls cube map reflections. The color of the cubemap is tinted by the color settings +in the SPECULAR PROPERTIES and the brightness in the REFLECTION PROPERTIES. + +BUMPED CUBE MAP: Makes it so that the reflection and fresnel is affected by the bump map. + +FLAT CUBE MAP: The reflection is not affected by the cubemap, the fresnel still is though.""" + +environment_shader = Struct("environment_shader", + Bool16("flags", + "alpha_tested", + "bump_map_is_specular_mask", + "true_atmospheric_fog", + COMMENT=environment_shader_comment + ), + SEnum16("type", + "normal", + "blended", + "blended_base_specular", + COMMENT=environment_shader_type_comment + ), + ) + +diffuse = Struct("diffuse", + Bool16("diffuse_flags", + "rescale_detail_maps", + "rescale_bump_maps", + ), + Pad(26), + dependency("base_map", "bitm"), + + Pad(24), + SEnum16("detail_map_function", *detail_map_functions), + Pad(2), + + Float("primary_detail_map_scale"), + dependency("primary_detail_map", "bitm"), + Float("secondary_detail_map_scale"), + dependency("secondary_detail_map", "bitm"), + + Pad(24), + SEnum16("micro_detail_map_function", *detail_map_functions), + + Pad(2), + Float("micro_detail_map_scale"), + dependency("micro_detail_map", "bitm"), + QStruct("material_color", INCLUDE=rgb_float), + ) + +bump_properties = Struct("bump_properties", + Float("map_scale"), + dependency("map", "bitm"), + FlFloat("map_scale_x", VISIBLE=False), + FlFloat("map_scale_y", VISIBLE=False), + COMMENT=bump_properties_comment + ) + +texture_scrolling = Struct("texture_scrolling", + anim_func_per_sca_macro("u_animation"), + anim_func_per_sca_macro("v_animation"), + COMMENT=tex_scroll_anim_comment + ) + +self_illumination = Struct("self_illumination", + Bool16("flags", + "unfiltered", + ), + Pad(2), + Pad(24), + + QStruct("primary_on_color", INCLUDE=rgb_float), + QStruct("primary_off_color", INCLUDE=rgb_float), + anim_func_per_pha_macro("primary_animation"), + + Pad(24), + QStruct("secondary_on_color", INCLUDE=rgb_float), + QStruct("secondary_off_color", INCLUDE=rgb_float), + anim_func_per_pha_macro("secondary_animation"), + + Pad(24), + QStruct("plasma_on_color", INCLUDE=rgb_float), + QStruct("plasma_off_color", INCLUDE=rgb_float), + anim_func_per_pha_macro("plasma_animation"), + + Pad(24), + Float("map_scale"), + dependency("map", "bitm"), + COMMENT=self_illum_comment + ) + +specular = Struct("specular", + Bool16("specular_flags", + "overbright", + "extra_shiny", + "lightmap_is_specular" + ), + Pad(18), + float_zero_to_one("brightness"), # [0,1] + + Pad(20), + QStruct("perpendicular_tint_color", INCLUDE=rgb_float), + QStruct("parallel_tint_color", INCLUDE=rgb_float), + COMMENT=specular_properties_comment + ) + +reflection = Struct("reflection", + Bool16("reflection_flags", + "dynamic_mirror", + ), + SEnum16("reflection_type", + "bumped_cubemap", + "flat_cubemap", + "bumped_radiosity", + ), + + float_zero_to_one("lightmap_brightness_scale"), # [0,1] + Pad(28), + float_zero_to_one("perpendicular_brightness"), # [0,1] + float_zero_to_one("parallel_brightness"), # [0,1] + + Pad(40), + dependency("cube_map", "bitm"), + COMMENT=reflection_properties_comment + ) + +senv_attrs = Struct("senv_attrs", + environment_shader, + + float_wu("lens_flare_spacing"), # world units + dependency("lens_flare", "lens"), + + # this padding is the reflexive for the OS shader environment extension + Pad(12), + + Pad(32), + diffuse, + + Pad(12), + bump_properties, + + Pad(16), + texture_scrolling, + + Pad(24), + self_illumination, + + Pad(24), + specular, + + Pad(16), + reflection, + SIZE=796 + ) + +senv_body = Struct("tagdata", + shdr_attrs, + senv_attrs, + SIZE=836, + ) + + +def get(): + return senv_def + +senv_def = TagDef("senv", + blam_header('senv', 2), + senv_body, + + ext=".shader_environment", endian=">", tag_cls=ShdrTag + ) diff --git a/reclaimer/mcc_hek/defs/sgla.py b/reclaimer/mcc_hek/defs/sgla.py new file mode 100644 index 00000000..eeaac1e8 --- /dev/null +++ b/reclaimer/mcc_hek/defs/sgla.py @@ -0,0 +1,84 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .shdr import * +from .objs.shdr import ShdrTag +from supyr_struct.defs.tag_def import TagDef + +sgla_attrs = Struct("sgla_attrs", + #Glass Shader Properties + Bool16("glass_shader_flags", + "alpha_tested", + "decal", + "two_sided", + "bump_map_is_specular_mask", + ), + + Pad(42), + #Background Tint Properties + Struct("background_tint_properties", + QStruct("color", INCLUDE=rgb_float), + Float("map_scale"), + dependency("map", "bitm"), + ), + + Pad(22), + #Reflection Properties + Struct("reflection_properties", + SEnum16("type", + "bumped_cubemap", + "flat_cubemap", + "dynamic_mirror", + ), + float_zero_to_one("perpendicular_brightness"), # [0,1] + QStruct("perpendicular_tint_color", INCLUDE=rgb_float), + float_zero_to_one("parallel_brightness"), # [0,1] + QStruct("parallel_tint_color", INCLUDE=rgb_float), + dependency("map", "bitm"), + + Float("bump_map_scale"), + dependency("bump_map", "bitm"), + ), + + Pad(132), + #Diffuse Properties + Struct("diffuse_properties", + Float("map_scale"), + dependency("map", "bitm"), + Float("detail_map_scale"), + dependency("detail_map", "bitm"), + ), + + Pad(32), + #Specular Properties + Struct("specular_properties", + Float("map_scale"), + dependency("map", "bitm"), + Float("detail_map_scale"), + dependency("detail_map", "bitm"), + ), + SIZE=440 + ) + +sgla_body = Struct("tagdata", + shdr_attrs, + sgla_attrs, + SIZE=480, + ) + + +def get(): + return sgla_def + +sgla_def = TagDef("sgla", + blam_header('sgla'), + sgla_body, + + ext=".shader_transparent_glass", endian=">", tag_cls=ShdrTag + ) diff --git a/reclaimer/mcc_hek/defs/shdr.py b/reclaimer/mcc_hek/defs/shdr.py new file mode 100644 index 00000000..26c22d78 --- /dev/null +++ b/reclaimer/mcc_hek/defs/shdr.py @@ -0,0 +1,73 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.shdr import ShdrTag +from supyr_struct.defs.tag_def import TagDef + +radiosity_comment = """RADIOSITY/LIGHTMAPPING +The simple parameterisation flag is used for shaders that are on surfaces with more curved +shapes like grass hills. It avoids lightmap uv problems by using the uvs from the bsp geometry. +The ignore normals flag helps with shading bugs on double sided polygons by making the +light independent of normals.(Suggested use: tree leaves). + +The detail level controls the relative quality of the lightmaps/lightmap uvs for this shader.""" + +lighting_comment = """LIGHT +Light power controls the brightness and reach of the light emitted by this shader +during lightmap rendering. +""" + +material_type_comment = """MATERIAL TYPE +The material type is used to determine what material effects should be used for impacts on +BSP geometry that uses this shader.""" + +shdr_attrs = Struct("shdr_attrs", + Bool16("radiosity_flags", + { NAME: "simple_parameterization", GUI_NAME: "simple parameterization(lightmap fix)" }, + "ignore_normals", + "transparent_lit", + COMMENT=radiosity_comment + ), + SEnum16("radiosity_detail_level" , + "high", + "medium", + "low", + "turd", + ), + Float("radiosity_light_power", COMMENT=lighting_comment), + QStruct("radiosity_light_color", INCLUDE=rgb_float), + QStruct("radiosity_tint_color", INCLUDE=rgb_float), + + Pad(2), + SEnum16("material_type", *materials_list, COMMENT=material_type_comment), + # THIS FIELD IS OFTEN INCORRECT ON STOCK TAGS. + FlSEnum16("shader_type", + *((shader_types[i], i - 1) for i in + range(len(shader_types))), + VISIBLE=False, DEFAULT=-1 + ), + Pad(2), + SIZE=40 + ) + +shader_body = Struct("tagdata", + shdr_attrs, + SIZE=40 + ) + +def get(): + return shdr_def + +shdr_def = TagDef("shdr", + blam_header('shdr'), + shader_body, + + ext=".shader", endian=">", tag_cls=ShdrTag + ) diff --git a/reclaimer/mcc_hek/defs/sky_.py b/reclaimer/mcc_hek/defs/sky_.py new file mode 100644 index 00000000..72d501fa --- /dev/null +++ b/reclaimer/mcc_hek/defs/sky_.py @@ -0,0 +1,89 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +shader_function = Struct('shader_function', + Pad(4), + ascii_str32("global_function_name"), + SIZE=36 + ) + +animation = Struct('animation', + SInt16('animation_index'), + Pad(2), + float_sec("period"), + SIZE=36 + ) + +light = Struct('light', + dependency("lens_flare", "lens"), + ascii_str32("global_function_name"), + Pad(28), + Bool32('flags', + 'affects_exteriors', + 'affects_interiors', + ), + QStruct("color", INCLUDE=rgb_float), + Float("power"), + Float("test_distance"), + Pad(4), + yp_float_rad("direction"), # radians + float_rad("diameter"), # radians (yeah, it sounds weird, but this + # value is stored as a radian coefficient) + SIZE=116 + ) + + +sky__body = Struct("tagdata", + dependency("model", valid_models), + dependency("animation_graph", "antr"), + Pad(24), + + QStruct("indoor_ambient_radiosity_color", INCLUDE=rgb_float), + Float("indoor_ambient_radiosity_power"), + + QStruct("outdoor_ambient_radiosity_color", INCLUDE=rgb_float), + Float("outdoor_ambient_radiosity_power"), + + QStruct("outdoor_fog_color", INCLUDE=rgb_float), + Pad(8), + float_zero_to_one("outdoor_fog_maximum_density"), + float_wu("outdoor_fog_start_distance"), + float_wu("outdoor_fog_opaque_distance"), + + QStruct("indoor_fog_color", INCLUDE=rgb_float), + Pad(8), + float_zero_to_one("indoor_fog_maximum_density"), + float_wu("indoor_fog_start_distance"), + float_wu("indoor_fog_opaque_distance"), + + dependency("indoor_fog_screen", "fog "), + Pad(4), + reflexive("shader_functions", shader_function, 8, + DYN_NAME_PATH='.global_function_name'), + reflexive("animations", animation, 8), + reflexive("lights", light, 8, + DYN_NAME_PATH='.lens_flare.filepath'), + + SIZE=208, + ) + + +def get(): + return sky__def + +sky__def = TagDef("sky ", + blam_header('sky '), + sky__body, + + ext=".sky", endian=">", tag_cls=HekTag, + ) diff --git a/reclaimer/mcc_hek/defs/smet.py b/reclaimer/mcc_hek/defs/smet.py new file mode 100644 index 00000000..2ad01d6a --- /dev/null +++ b/reclaimer/mcc_hek/defs/smet.py @@ -0,0 +1,68 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .shdr import * +from .objs.shdr import ShdrTag +from supyr_struct.defs.tag_def import TagDef + +smet_attrs = Struct("smet_attrs", + #Meter Shader Properties + Struct("meter_shader", + Bool16("meter_shader_flags", + "decal", + "two_sided", + "flash_color_is_negative", + "tint_mode_2", + "unfiltered" + ), + Pad(34), + dependency("map", "bitm"), + Pad(32), + ), + + #Colors + Struct("colors", + Struct("gadient_min", INCLUDE=rgb_float), + Struct("gadient_max", INCLUDE=rgb_float), + Struct("background", INCLUDE=rgb_float), + Struct("flash", INCLUDE=rgb_float), + Struct("tint", INCLUDE=rgb_float), + float_zero_to_one("meter_transparency"), + float_zero_to_one("background_transparency"), + ), + + Pad(24), + #External Function Sources + Struct("external_function_sources", + SEnum16("meter_brightness", *function_outputs), + SEnum16("flash_brightness", *function_outputs), + SEnum16("value", *function_outputs), + SEnum16("gradient", *function_outputs), + SEnum16("flash_extension", *function_outputs), + ), + SIZE=220, + ) + +smet_body = Struct("tagdata", + shdr_attrs, + smet_attrs, + SIZE=260, + ) + + + +def get(): + return smet_def + +smet_def = TagDef("smet", + blam_header('smet'), + smet_body, + + ext=".shader_transparent_meter", endian=">", tag_cls=ShdrTag + ) diff --git a/reclaimer/mcc_hek/defs/snd_.py b/reclaimer/mcc_hek/defs/snd_.py new file mode 100644 index 00000000..ab29cd73 --- /dev/null +++ b/reclaimer/mcc_hek/defs/snd_.py @@ -0,0 +1,175 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.snd_ import Snd_Tag +from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant + +sound_classes = ( + ("projectile_impact", 0), + ("projectile_detonation", 1), + + ("weapon_fire", 4), + ("weapon_ready", 5), + ("weapon_reload", 6), + ("weapon_empty", 7), + ("weapon_charge", 8), + ("weapon_overheat", 9), + ("weapon_idle", 10), + + ("object_impacts", 13), + ("particle_impacts", 14), + ("slow_particle_impacts", 15), + + ("unit_footsteps", 18), + ("unit_dialog", 19), + + ("vehicle_collision", 22), + ("vehicle_engine", 23), + + ("device_door", 26), + ("device_force_field", 27), + ("device_machinery", 28), + ("device_nature", 29), + ("device_computers", 30), + + ("music", 32), + ("ambient_nature", 33), + ("ambient_machinery", 34), + ("ambient_computers", 35), + + ("first_person_damage", 39), + + ("scripted_dialog_player", 44), + ("scripted_effect", 45), + ("scripted_dialog_other", 46), + ("scripted_dialog_force_unspatialized", 47), + + ("game_event", 50), + ) + +compression = SEnum16("compression", + 'none', + 'xbox_adpcm', + 'ima_adpcm', + 'ogg', + TOOLTIP=""" +IMA ADPCM is unsupported on pc, and is actually treated as +MS ADPCM, with an additional header on the data stream. +For all intents and purposes, "ima adpcm" is useless. +""" + ) + + +permutation = Struct('permutation', + ascii_str32("name"), + Float("skip_fraction"), + Float("gain", DEFAULT=1.0), + compression, + SInt16("next_permutation_index", DEFAULT=-1), + FlSInt32("unknown0", VISIBLE=False), + FlUInt32("unknown1", VISIBLE=False), # always zero? + FlUInt32("unknown2", VISIBLE=False), + # this is actually the required length of the ogg + # decompression buffer. For "none" compression, this + # mirrors samples.size, so a more appropriate name + # for this field should be pcm_buffer_size + FlUInt32("ogg_sample_count", EDITABLE=False), + FlUInt32("unknown3", VISIBLE=False), # seems to always be == unknown2 + rawdata_ref("samples", max_size=4194304, widget=SoundSampleFrame), + rawdata_ref("mouth_data", max_size=8192), + rawdata_ref("subtitle_data", max_size=512), + SIZE=124 + ) + +pitch_range = Struct('pitch_range', + ascii_str32("name"), + + Float("natural_pitch"), + QStruct("bend_bounds", INCLUDE=from_to), + SInt16("actual_permutation_count"), + Pad(2), + Float("playback_rate", VISIBLE=False), + SInt32("unknown1", VISIBLE=False, DEFAULT=-1), + SInt32("unknown2", VISIBLE=False, DEFAULT=-1), + + reflexive("permutations", permutation, 256, + DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), + SIZE=72, + ) + + +snd__body = Struct("tagdata", + Bool32("flags", + "fit_to_adpcm_blocksize", + "split_long_sound_into_permutations" + ), + SEnum16("sound_class", *sound_classes), + SEnum16("sample_rate", + {NAME: "khz_22", GUI_NAME: "22kHz"}, + {NAME: "khz_44", GUI_NAME: "44kHz"}, + ), + float_wu("minimum_distance"), + float_wu("maximum_distance"), + float_zero_to_one("skip_fraction"), + + #Randomization + QStruct("random_pitch_bounds", INCLUDE=from_to), + float_rad("inner_cone_angle"), # radians + float_rad("outer_cone_angle"), # radians + float_zero_to_one("outer_cone_gain"), + Float("gain_modifier"), + Float("maximum_bend_per_second"), + Pad(12), + + QStruct("modifiers_when_scale_is_zero", + Float("skip_fraction"), + Float("gain"), + Float("pitch"), + ), + Pad(12), + + QStruct("modifiers_when_scale_is_one", + Float("skip_fraction"), + Float("gain"), + Float("pitch"), + ), + Pad(12), + + SEnum16("encoding", + 'mono', + 'stereo' + ), + compression, + dependency("promotion_sound", "snd!"), + SInt16("promotion_count"), + SInt16("unknown1", VISIBLE=False), + Struct("unknown2", INCLUDE=rawdata_ref_struct, VISIBLE=False), + reflexive("pitch_ranges", pitch_range, 8, + DYN_NAME_PATH='.name'), + + SIZE=164, + ) + + +def get(): + return snd__def + +snd__def = TagDef("snd!", + blam_header('snd!', 4), + snd__body, + + ext=".sound", endian=">", tag_cls=Snd_Tag, + ) + +snd__meta_stub = desc_variant( + snd__body, ("pitch_ranges", Pad(12)) + ) +snd__meta_stub_blockdef = BlockDef(snd__meta_stub) diff --git a/reclaimer/mcc_hek/defs/snde.py b/reclaimer/mcc_hek/defs/snde.py new file mode 100644 index 00000000..1a5ad2dc --- /dev/null +++ b/reclaimer/mcc_hek/defs/snde.py @@ -0,0 +1,41 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +snde_body = QStruct("tagdata", + Pad(4), + UInt16("priority"), + Pad(2), + Float("room_intensity"), + Float("room_intensity_hf"), + Float("room_rolloff", MIN=0.0, MAX=10.0), + float_sec("decay_time", MIN=0.1, MAX=20.0), + Float("decay_hf_ratio", MIN=0.1, MAX=2.0), + Float("reflections_intensity"), + float_sec("reflections_delay", MIN=0.0, MAX=0.3), + Float("reverb_intensity"), + float_sec("reverb_delay", MIN=0.0, MAX=0.1), + Float("diffusion"), + Float("density"), + Float("hf_reference", MIN=20.0, MAX=20000.0, SIDETIP="Hz"), + SIZE=72, + ) + +def get(): + return snde_def + +snde_def = TagDef("snde", + blam_header('snde'), + snde_body, + + ext=".sound_environment", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/soso.py b/reclaimer/mcc_hek/defs/soso.py new file mode 100644 index 00000000..ca8f22de --- /dev/null +++ b/reclaimer/mcc_hek/defs/soso.py @@ -0,0 +1,168 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .shdr import * +from .objs.shdr import ShdrTag +from supyr_struct.defs.tag_def import TagDef + +soso_comment = """MODEL SHADER +Setting enables per-pixel atmospheric fog but disables point/spot +lights, planar fog, and the ability to control the atmospheric fog density for this shader.""" + +cc_comment = """CHANGE COLOR +Change color is used to recolor the diffuse map, it affects pixels +based on the ALPHA channel (BLUE on XBOX) of the multipurpose map.""" + +self_illum_comment = """SELF-ILLUMINATION +Self-illumination adds diffuse light to pixels based on the GREEN channel +of the multipurpose map. The external self-illumination color referenced by + is modulated by the self-illumination animation.""" + +maps_comment = """MAPS +On PC, the multipurpose map channels are used for: +* RED: auxiliary mask (usually used for detail) +* GREEN: self-illumination mask (adds to diffuse light) +* BLUE: specular reflection mask (modulates reflections) +* ALPHA: color change mask (recolors diffuse map) + +On XBOX, the channels are used for: +* RED: specular reflection +* GREEN: self-illumination +* BLUE: color change +* ALPHA: auxiliary + +Note: When DXT1 compressed color-key textures are used for the +multipurpose map (as they should be normally), the alpha channel is 1-bit +and any non-zero alpha pixels must have zero-color, therefore on PC if we +need colorchange we use DXT3 (explicit alpha) or DXT5 (interpolated alpha). + +Detail map affects diffuse map, and optionally affects reflection +if flag is set.""" + +tex_scroll_comment = """TEXTURE SCROLLING ANIMATIONS +Scrolls all 2D maps simultaneously.""" + +reflection_prop_comment = """REFLECTION PROPERTIES""" + +model_shader = Struct("model_shader", + Bool16("flags", + "detail_after_reflection", + "two_sided", + "not_alpha_tested", + "alpha_blended_decal", + "true_atmospheric_fog", + "disable_two_sided_culling", + ), + Pad(14), + Float("translucency"), + COMMENT=soso_comment + ) + +self_illumination = Struct("self_illumination", + Bool16("flags", + "no_random_phase" + ), + Pad(2), + SEnum16("color_source", *function_names), + SEnum16("animation_function", *animation_functions), + float_sec("animation_period"), # seconds + QStruct("color_lower_bound", INCLUDE=rgb_float), + QStruct("color_upper_bound", INCLUDE=rgb_float), + COMMENT=self_illum_comment + ) + +maps = Struct("maps", + Float("map_u_scale"), + Float("map_v_scale"), + dependency("diffuse_map", "bitm"), + + Pad(8), + dependency("multipurpose_map", "bitm"), + + Pad(8), + SEnum16("detail_function", *detail_map_functions), + SEnum16("detail_mask", *detail_mask), + + Float("detail_map_scale"), + dependency("detail_map", "bitm"), + Float("detail_map_v_scale"), + COMMENT=maps_comment + ) + +texture_scrolling = Struct("texture_scrolling", + Struct("u_animation", INCLUDE=anim_src_func_per_pha_sca), + Struct("v_animation", INCLUDE=anim_src_func_per_pha_sca), + Struct("rotation_animation", INCLUDE=anim_src_func_per_pha_sca_rot), + QStruct("rot_animation_center", INCLUDE=xy_float), + COMMENT=tex_scroll_comment + ) + +reflection = Struct("reflection", + float_wu("falloff_distance"), # world units + float_wu("cutoff_distance"), # world units + + float_zero_to_one("perpendicular_brightness"), + QStruct("perpendicular_tint_color", INCLUDE=rgb_float), + float_zero_to_one("parallel_brightness"), + QStruct("parallel_tint_color", INCLUDE=rgb_float), + + dependency("cube_map", "bitm"), + #COMMENT=reflection_prop_comment + ) + +soso_attrs = Struct("soso_attrs", + #Model Shader Properties + model_shader, + + Pad(16), + #Color-Change + SEnum16("color_change_source", *function_names, COMMENT=cc_comment), + + + Pad(30), + #Self-Illumination + self_illumination, + + Pad(12), + #Diffuse, Multipurpose, and Detail Maps + maps, + + # this padding is the reflexive for the OS shader model extension + Pad(12), + + #Texture Scrolling Animation + texture_scrolling, + + Pad(8), + #Reflection Properties + reflection, + Pad(16), + + Float("unknown0", VISIBLE=False), + BytesRaw("unknown1", SIZE=16, VISIBLE=False), # little endian dependency + + SIZE=400 + ) + + +soso_body = Struct("tagdata", + shdr_attrs, + soso_attrs + ) + + +def get(): + return soso_def + +soso_def = TagDef("soso", + blam_header('soso', 2), + soso_body, + + ext=".shader_model", endian=">", tag_cls=ShdrTag + ) diff --git a/reclaimer/mcc_hek/defs/sotr.py b/reclaimer/mcc_hek/defs/sotr.py new file mode 100644 index 00000000..c3945c61 --- /dev/null +++ b/reclaimer/mcc_hek/defs/sotr.py @@ -0,0 +1,269 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .shdr import * +from .objs.shdr import ShdrTag +from supyr_struct.defs.tag_def import TagDef + +sotr_input_mappings = ( + {NAME: 'clamp_x', GUI_NAME: 'clamp(x)'}, + {NAME: 'one_minus_clamp_x', GUI_NAME: '1 - clamp(x)'}, + {NAME: 'clamp_x_times_two_minus_one', GUI_NAME: '2*clamp(x) - 1'}, + {NAME: 'one_minus_clamp_x_times_two', GUI_NAME: '1 - 2*clamp(x)'}, + {NAME: 'clamp_x_minus_one_half', GUI_NAME: 'clamp(x) - 1/2'}, + {NAME: 'one_half_minus_clamp_x', GUI_NAME: '1/2 - clamp(x)'}, + {NAME: 'x', GUI_NAME: 'x'}, + {NAME: 'minus_x', GUI_NAME: '-x'}, + ) +sotr_output_mappings = ( + 'identity', + {NAME: 'scale_by_one_half', GUI_NAME: 'scale by 1/2'}, + {NAME: 'scale_by_two', GUI_NAME: 'scale by 2'}, + {NAME: 'scale_by_four', GUI_NAME: 'scale by 4'}, + {NAME: 'bias_by_minus_one_half', GUI_NAME: 'bias by -1/2'}, + 'expand_normal', + ) + + +sotr_color_inputs = ( + 'zero', + 'one', + 'one_half', + 'negative_one', + 'negative_one_half', + + 'map_color_0', + 'map_color_1', + 'map_color_2', + 'map_color_3', + {NAME: 'vertex_color_0', GUI_NAME: 'vertex color 0 / diffuse light'}, + {NAME: 'vertex_color_1', GUI_NAME: 'vertex color 1 / fade(perpendicular)'}, + 'scratch_color_0', + 'scratch_color_1', + 'constant_color_0', + 'constant_color_1', + + 'map_alpha_0', + 'map_alpha_1', + 'map_alpha_2', + 'map_alpha_3', + {NAME: 'vertex_alpha_0', GUI_NAME: 'vertex alpha 0 / diffuse light'}, + {NAME: 'vertex_alpha_1', GUI_NAME: 'vertex alpha 1 / fade(perpendicular)'}, + 'scratch_alpha_0', + 'scratch_alpha_1', + 'constant_alpha_0', + 'constant_alpha_1', + ) +sotr_color_outputs = ( + 'discard', + {NAME:'scratch_color_0', GUI_NAME: 'scratch color 0 / final color'}, + 'scratch_color_1', + 'vertex_color_0', + 'vertex_color_1', + 'map_color_0', + 'map_color_1', + 'map_color_2', + 'map_color_3', + ) +sotr_color_output_functions = ( + 'multiply', + 'dot_product', + ) + + +sotr_alpha_inputs = ( + 'zero', + 'one', + 'one_half', + 'negative_one', + 'negative_one_half', + + 'map_blue_0', + 'map_blue_1', + 'map_blue_2', + 'map_blue_3', + {NAME: 'vertex_blue_0', GUI_NAME: 'vertex blue 0 / diffuse light'}, + {NAME: 'vertex_blue_1', GUI_NAME: 'vertex blue 1 / fade(perpendicular)'}, + 'scratch_blue_0', + 'scratch_blue_1', + 'constant_blue_0', + 'constant_blue_1', + + 'map_alpha_0', + 'map_alpha_1', + 'map_alpha_2', + 'map_alpha_3', + {NAME: 'vertex_alpha_0', GUI_NAME: 'vertex alpha 0 / diffuse light'}, + {NAME: 'vertex_alpha_1', GUI_NAME: 'vertex alpha 1 / fade(perpendicular)'}, + 'scratch_alpha_0', + 'scratch_alpha_1', + 'constant_alpha_0', + 'constant_alpha_1', + ) +sotr_alpha_outputs = ( + 'discard', + {NAME: 'scratch_alpha_0', GUI_NAME: 'scratch alpha 0 / final alpha'}, + 'scratch_alpha_1', + {NAME: 'vertex_alpha_0', GUI_NAME: 'vertex alpha 0 / fog'}, + 'vertex_alpha_1', + 'map_alpha_0', + 'map_alpha_1', + 'map_alpha_2', + 'map_alpha_3', + ) + + +stage = Struct("stage", + Bool16("flags" , + "color_mux", + "alpha_mux", + "a_out_controls_color0_animation", + ), + Pad(2), + + SEnum16("color0_source", *function_names, + COMMENT="If set to 'none', color0 is calculated by blending the\n" + "two bounds below based on the 'color0 anim function'."), + SEnum16("color0_anim_function", *animation_functions), + float_sec("color0_anim_period"), + QStruct("color0_anim_lower_bound", INCLUDE=argb_float), + QStruct("color0_anim_upper_bound", INCLUDE=argb_float), + QStruct("color1", INCLUDE=argb_float), + + Struct('color', + Struct('input_A', + SEnum16('input', GUI_NAME='', *sotr_color_inputs), + SEnum16('mapped_to', *sotr_input_mappings), + ORIENT='h' + ), + Struct('input_B', + SEnum16('input', GUI_NAME='', *sotr_color_inputs), + SEnum16('mapped_to', *sotr_input_mappings), + ORIENT='h' + ), + Struct('input_C', + SEnum16('input', GUI_NAME='', *sotr_color_inputs), + SEnum16('mapped_to', *sotr_input_mappings), + ORIENT='h' + ), + Struct('input_D', + SEnum16('input', GUI_NAME='', *sotr_color_inputs), + SEnum16('mapped_to', *sotr_input_mappings), + ORIENT='h' + ), + + Struct('output_AB', + SEnum16('output', GUI_NAME='', *sotr_color_outputs), + SEnum16('function', *sotr_color_output_functions), + ORIENT='h' + ), + Struct('output_CD', + SEnum16('output', GUI_NAME='', *sotr_color_outputs), + SEnum16('function', *sotr_color_output_functions), + ORIENT='h' + ), + SEnum16('output_AB_CD_mux_sum', *sotr_color_outputs), + SEnum16('output_mapping', *sotr_output_mappings) + ), + + Struct('alpha', + Struct('input_A', + SEnum16('input', GUI_NAME='', *sotr_alpha_inputs), + SEnum16('mapped_to', *sotr_input_mappings), + ORIENT='h' + ), + Struct('input_B', + SEnum16('input', GUI_NAME='', *sotr_alpha_inputs), + SEnum16('mapped_to', *sotr_input_mappings), + ORIENT='h' + ), + Struct('input_C', + SEnum16('input', GUI_NAME='', *sotr_alpha_inputs), + SEnum16('mapped_to', *sotr_input_mappings), + ORIENT='h' + ), + Struct('input_D', + SEnum16('input', GUI_NAME='', *sotr_alpha_inputs), + SEnum16('mapped_to', *sotr_input_mappings), + ORIENT='h' + ), + + SEnum16('output_AB', *sotr_alpha_outputs), + SEnum16('output_CD', *sotr_alpha_outputs), + SEnum16('output_AB_CD_mux_sum', *sotr_alpha_outputs), + SEnum16('output_mapping', *sotr_output_mappings) + ), + + SIZE=112 + ) + +map = Struct("map", + Bool16("flags" , + "unfiltered", + "u_clamped", + "v_clamped", + ), + Pad(2), + #shader_transformations + Float("map_u_scale"), + Float("map_v_scale"), + Float("map_u_offset"), + Float("map_v_offset"), + float_deg("map_rotation"), # degrees + float_zero_to_one("map_bias"), + dependency("bitmap", "bitm"), + + #shader animations + Struct("u_animation", INCLUDE=anim_src_func_per_pha_sca), + Struct("v_animation", INCLUDE=anim_src_func_per_pha_sca), + Struct("rotation_animation", INCLUDE=anim_src_func_per_pha_sca_rot), + + QStruct("rotation_center", INCLUDE=xy_float), + SIZE=100, + ) + +sotr_attrs = Struct("sotr_attrs", + #Generic Transparent Shader + Struct("generic_transparent_shader", + UInt8("numeric_counter_limit", MIN=0, MAX=255, SIDETIP="[0,255]"), + Bool8("flags", *trans_shdr_properties), + SEnum16("first_map_type", *trans_shdr_first_map_type), + SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), + SEnum16("framebuffer_fade_mode", *render_fade_mode), + SEnum16("framebuffer_fade_source", *function_outputs), + Pad(2), + ), + + #Lens Flare + float_wu("lens_flare_spacing"), # world units + dependency("lens_flare", "lens"), + reflexive("extra_layers", extra_layers_block, 4, + DYN_NAME_PATH='.filepath'), + reflexive("maps", map, 4, DYN_NAME_PATH='.bitmap.filepath'), + reflexive("stages", stage, 7), + SIZE=68 + ) + +sotr_body = Struct("tagdata", + shdr_attrs, + sotr_attrs, + + SIZE=108, + ) + + +def get(): + return sotr_def + +sotr_def = TagDef("sotr", + blam_header("sotr"), + sotr_body, + + ext=".shader_transparent_generic", endian=">", tag_cls=ShdrTag, + ) diff --git a/reclaimer/mcc_hek/defs/spla.py b/reclaimer/mcc_hek/defs/spla.py new file mode 100644 index 00000000..a9828cdc --- /dev/null +++ b/reclaimer/mcc_hek/defs/spla.py @@ -0,0 +1,76 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .shdr import * +from .objs.shdr import ShdrTag +from supyr_struct.defs.tag_def import TagDef + +noise_map = Struct("noise_map", + FlUInt16("unknown0", VISIBLE=False), + FlUInt16("unknown1", VISIBLE=False), + float_sec("animation_period"), + QStruct("animation_direction", INCLUDE=ijk_float), + Float("noise_map_scale"), + dependency("noise_map", "bitm"), + Pad(32), + ) + +spla_attrs = Struct("spla_attrs", + Pad(4), + #Intensity + Struct("intensity", + SEnum16("source", *function_outputs), + Pad(2), + Float("exponent"), + ), + + #Offset + Struct("offset", + SEnum16("source", *function_outputs), + Pad(2), + float_wu("amount"), + Float("exponent"), + ), + + Pad(32), + + #Color + Struct("color", + float_zero_to_one("perpendicular_brightness"), + QStruct("perpendicular_tint_color", INCLUDE=rgb_float), + float_zero_to_one("parallel_brightness"), + QStruct("parallel_tint_color", INCLUDE=rgb_float), + SEnum16("tint_color_source", *function_names), + ), + + Pad(58), + #Primary Noise Map + Struct("primary_noise_map", INCLUDE=noise_map), + + #Secondary Noise Map + Struct("secondary_noise_map", INCLUDE=noise_map), + SIZE=292 + ) + +spla_body = Struct("tagdata", + shdr_attrs, + spla_attrs, + SIZE=332, + ) + + +def get(): + return spla_def + +spla_def = TagDef("spla", + blam_header('spla'), + spla_body, + + ext=".shader_transparent_plasma", endian=">", tag_cls=ShdrTag + ) diff --git a/reclaimer/mcc_hek/defs/ssce.py b/reclaimer/mcc_hek/defs/ssce.py new file mode 100644 index 00000000..78591bd8 --- /dev/null +++ b/reclaimer/mcc_hek/defs/ssce.py @@ -0,0 +1,35 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .obje import * +from .objs.obje import ObjeTag +from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant + +# replace the object_type enum one that uses +# the correct default value for this object +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(11)) + ) + +ssce_body = Struct("tagdata", + obje_attrs, + SIZE=508, + ) + + +def get(): + return ssce_def + +ssce_def = TagDef("ssce", + blam_header('ssce'), + ssce_body, + + ext=".sound_scenery", endian=">", tag_cls=ObjeTag + ) diff --git a/reclaimer/mcc_hek/defs/str_.py b/reclaimer/mcc_hek/defs/str_.py new file mode 100644 index 00000000..6d449708 --- /dev/null +++ b/reclaimer/mcc_hek/defs/str_.py @@ -0,0 +1,31 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.str_ import Str_Tag +from supyr_struct.defs.tag_def import TagDef + +string_data_struct = rawtext_ref("string", StrLatin1, max_size=4096) + +str__body = Struct("tagdata", + reflexive("strings", string_data_struct, 32767, widget=TextFrame, + DYN_NAME_PATH='.data'), + SIZE=12, + ) + + +def get(): + return str__def + +str__def = TagDef("str#", + blam_header('str#'), + str__body, + + ext=".string_list", endian=">", tag_cls=Str_Tag + ) diff --git a/reclaimer/mcc_hek/defs/swat.py b/reclaimer/mcc_hek/defs/swat.py new file mode 100644 index 00000000..4550259d --- /dev/null +++ b/reclaimer/mcc_hek/defs/swat.py @@ -0,0 +1,80 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from math import pi + +from .shdr import * +from .objs.shdr import ShdrTag +from supyr_struct.defs.tag_def import TagDef + +ripple = Struct("ripple", + Pad(4), + float_zero_to_one("contribution_factor"), + Pad(32), + Float("animation_angle", + MIN=0.0, MAX=2*pi, UNIT_SCALE=180/pi, SIDETIP="[0,360]"), # radians + Float("animation_velocity", UNIT_SCALE=per_sec_unit_scale), + Struct("map_offset", INCLUDE=ij_float), + UInt16("map_repeats"), + UInt16("map_index"), + SIZE=76 + ) + + +swat_attrs = Struct("swat_attrs", + #Water Shader Properties + Struct("water_shader", + Bool16("flags", + "base_map_alpha_modulates_reflection", + "base_map_color_modulates_background", + "atmospheric_fog", + "draw_before_fog", + ), + Pad(34), + dependency("base_map", "bitm"), + Pad(16), + float_zero_to_one("perpendicular_brightness"), + Struct("perpendicular_tint_color", INCLUDE=rgb_float), + float_zero_to_one("parallel_brightness"), + Struct("parallel_tint_color", INCLUDE=rgb_float), + Pad(16), + dependency("reflection_map", "bitm"), + + Pad(16), + Float("ripple_animation_angle", + MIN=0.0, MAX=2*pi, UNIT_SCALE=180/pi, SIDETIP="[0,360]"), # radians + Float("ripple_animation_velocity", UNIT_SCALE=per_sec_unit_scale), + Float("ripple_scale"), + dependency("ripple_maps", "bitm"), + UInt16("ripple_mipmap_levels"), + Pad(2), + float_zero_to_one("ripple_mipmap_fade_factor"), + Float("ripple_mipmap_detail_bias"), + ), + + Pad(64), + reflexive("ripples", ripple, 4), + SIZE=280 + ) + +swat_body = Struct("tagdata", + shdr_attrs, + swat_attrs, + SIZE=320, + ) + +def get(): + return swat_def + +swat_def = TagDef("swat", + blam_header('swat', 2), + swat_body, + + ext=".shader_transparent_water", endian=">", tag_cls=ShdrTag + ) diff --git a/reclaimer/mcc_hek/defs/tagc.py b/reclaimer/mcc_hek/defs/tagc.py new file mode 100644 index 00000000..f5976efb --- /dev/null +++ b/reclaimer/mcc_hek/defs/tagc.py @@ -0,0 +1,34 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +tag_reference = Struct("tag_reference", + dependency("tag"), + SIZE=16 + ) + +tagc_body = Struct("tagdata", + reflexive("tag_references", tag_reference, 200, + DYN_NAME_PATH='.tag.filepath'), + SIZE=12, + ) + + +def get(): + return tagc_def + +tagc_def = TagDef("tagc", + blam_header('tagc'), + tagc_body, + + ext=".tag_collection", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/trak.py b/reclaimer/mcc_hek/defs/trak.py new file mode 100644 index 00000000..5e5fbf4b --- /dev/null +++ b/reclaimer/mcc_hek/defs/trak.py @@ -0,0 +1,36 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + + +control_point = Struct("control_point", + QStruct("position", INCLUDE=ijk_float), + QStruct("orientation", INCLUDE=ijkw_float), + SIZE=60, + ) + +trak_body = Struct("tagdata", + Pad(4), + reflexive("control_points", control_point, 16), + SIZE=48, + ) + + +def get(): + return trak_def + +trak_def = TagDef("trak", + blam_header('trak', 2), + trak_body, + + ext=".camera_track", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/udlg.py b/reclaimer/mcc_hek/defs/udlg.py new file mode 100644 index 00000000..0595c2bd --- /dev/null +++ b/reclaimer/mcc_hek/defs/udlg.py @@ -0,0 +1,247 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +def get(): return udlg_def + +def snd_dependency(name): + return dependency(name, "snd!") + +udlg_body = Struct("tagdata", + Pad(16), + Struct("idle", + snd_dependency("noncombat"), + snd_dependency("combat"), + snd_dependency("flee"), + Pad(48), + ), + + Struct("involuntary", + snd_dependency("pain_body_minor"), + snd_dependency("pain_body_maior"), + snd_dependency("pain_shield"), + snd_dependency("pain_falling"), + snd_dependency("scream_fear"), + snd_dependency("scream_pain"), + snd_dependency("maimed_limb"), + snd_dependency("maimed_head"), + snd_dependency("death_quiet"), + snd_dependency("death_violent"), + snd_dependency("death_falling"), + snd_dependency("death_agonizing"), + snd_dependency("death_instant"), + snd_dependency("death_flying"), + Pad(16), + ), + + Struct("hurting_people", + snd_dependency("damaged_friend"), + snd_dependency("damaged_friend_player"), + snd_dependency("damaged_enemy"), + snd_dependency("damaged_enemy_cm"), + Pad(64), + ), + + Struct("being_hurt", + snd_dependency("hurt_friend"), + snd_dependency("hurt_friend_re"), + snd_dependency("hurt_friend_player"), + snd_dependency("hurt_enemy"), + snd_dependency("hurt_enemy_re"), + snd_dependency("hurt_enemy_cm"), + snd_dependency("hurt_enemy_bullet"), + snd_dependency("hurt_enemy_needler"), + snd_dependency("hurt_enemy_plasma"), + snd_dependency("hurt_enemy_sniper"), + snd_dependency("hurt_enemy_grenade"), + snd_dependency("hurt_enemy_explosion"), + snd_dependency("hurt_enemy_melee"), + snd_dependency("hurt_enemy_flame"), + snd_dependency("hurt_enemy_shotgun"), + snd_dependency("hurt_enemy_vehicle"), + snd_dependency("hurt_enemy_mounted_weapon"), + Pad(48), + ), + + Struct("killing_people", + snd_dependency("killed_friend"), + snd_dependency("killed_friend_cm"), + snd_dependency("killed_friend_player"), + snd_dependency("killed_friend_player_cm"), + snd_dependency("killed_enemy"), + snd_dependency("killed_enemy_cm"), + snd_dependency("killed_enemy_player"), + snd_dependency("killed_enemy_player_cm"), + snd_dependency("killed_enemy_covenant"), + snd_dependency("killed_enemy_covenant_cm"), + snd_dependency("killed_enemy_floodcombat"), + snd_dependency("killed_enemy_floodcombat_cm"), + snd_dependency("killed_enemy_floodcarrier"), + snd_dependency("killed_enemy_floodcarrier_cm"), + snd_dependency("killed_enemy_sentinel"), + snd_dependency("killed_enemy_sentinel_cm"), + + snd_dependency("killed_enemy_bullet"), + snd_dependency("killed_enemy_needler"), + snd_dependency("killed_enemy_plasma"), + snd_dependency("killed_enemy_sniper"), + snd_dependency("killed_enemy_grenade"), + snd_dependency("killed_enemy_explosion"), + snd_dependency("killed_enemy_melee"), + snd_dependency("killed_enemy_flame"), + snd_dependency("killed_enemy_shotgun"), + snd_dependency("killed_enemy_vehicle"), + snd_dependency("killed_enemy_mounted_weapon"), + snd_dependency("killing_spree"), + Pad(48), + ), + + Struct("player_kill_responses", + snd_dependency("player_kill_cm"), + snd_dependency("player_kill_bullet_cm"), + snd_dependency("player_kill_needler_cm"), + snd_dependency("player_kill_plasma_cm"), + snd_dependency("player_kill_sniper_cm"), + snd_dependency("anyone_kill_grenade_cm"), + snd_dependency("player_kill_explosion_cm"), + snd_dependency("player_kill_melee_cm"), + snd_dependency("player_kill_flame_cm"), + snd_dependency("player_kill_shotgun_cm"), + snd_dependency("player_kill_vehicle_cm"), + snd_dependency("player_kill_mounted_weapon_cm"), + snd_dependency("player_killing_spree_cm"), + Pad(48), + ), + + Struct("friends_dying", + snd_dependency("friend_died"), + snd_dependency("friend_player_died"), + snd_dependency("friend_killed_by_friend"), + snd_dependency("friend_killed_by_friendly_player"), + snd_dependency("friend_enemy"), + snd_dependency("friend_enemy_player"), + snd_dependency("friend_covenant"), + snd_dependency("friend_flood"), + snd_dependency("friend_sentinel"), + snd_dependency("friend_betrayed"), + Pad(32), + ), + + Struct("shouting", + snd_dependency("new_combat_alone"), + snd_dependency("new_enemy_recent_combat"), + snd_dependency("old_enemy_sighted"), + snd_dependency("unexpected_enemy"), + snd_dependency("dead_friend_found"), + snd_dependency("alliance_broken"), + snd_dependency("alliance_reformed"), + snd_dependency("grenade_throwing"), + snd_dependency("grenade_sighted"), + snd_dependency("grenade_startle"), + snd_dependency("grenade_danger_enemy"), + snd_dependency("grenade_danger_self"), + snd_dependency("grenade_danger_friend"), + Pad(32), + ), + + Struct("group_communication", + snd_dependency("new_combat_group_re"), + snd_dependency("new_combat_nearby_re"), + snd_dependency("alert_friend"), + snd_dependency("alert_friend_re"), + snd_dependency("alert_lost_contact"), + snd_dependency("alert_lost_contact_re"), + snd_dependency("blocked"), + snd_dependency("blocked_re"), + snd_dependency("search_start"), + snd_dependency("search_query"), + snd_dependency("search_query_re"), + snd_dependency("search_report"), + snd_dependency("search_abandon"), + snd_dependency("search_group_abandon"), + snd_dependency("group_uncover"), + snd_dependency("group_uncover_re"), + snd_dependency("advance"), + snd_dependency("advance_re"), + snd_dependency("retreat"), + snd_dependency("retreat_re"), + snd_dependency("cover"), + Pad(64), + ), + + Struct("actions", + snd_dependency("sighted_friend_player"), + snd_dependency("shooting"), + snd_dependency("shooting_vehicle"), + snd_dependency("shooting_berserk"), + snd_dependency("shooting_group"), + snd_dependency("shooting_traitor"), + snd_dependency("taunt"), + snd_dependency("taunt_re"), + snd_dependency("flee"), + snd_dependency("flee_re"), + snd_dependency("free_leader_died"), + snd_dependency("attempted_flee"), + snd_dependency("attempted_flee_re"), + snd_dependency("lost_contact"), + snd_dependency("hiding_finished"), + snd_dependency("vehicle_entry"), + snd_dependency("vehicle_exit"), + snd_dependency("vehicle_woohoo"), + snd_dependency("vehicle_scared"), + snd_dependency("vehicle_collision"), + snd_dependency("partially_sighted"), + snd_dependency("nothing_there"), + snd_dependency("pleading"), + Pad(96), + ), + + Struct("exclamations", + snd_dependency("surprise"), + snd_dependency("berserk"), + snd_dependency("melee_attack"), + snd_dependency("dive"), + snd_dependency("uncover_exclamation"), + snd_dependency("leap_attack"), + snd_dependency("resurrection"), + Pad(64), + ), + + Struct("post_combat_actions", + snd_dependency("celebration"), + snd_dependency("check_body_enemy"), + snd_dependency("check_body_friend"), + snd_dependency("shooting_dead_enemy"), + snd_dependency("shooting_dead_enemy_player"), + Pad(64), + ), + + Struct("post_combat_chatter", + snd_dependency("alone"), + snd_dependency("unscathed"), + snd_dependency("seriously_wounded"), + snd_dependency("seriously_wounded_re"), + snd_dependency("massacre"), + snd_dependency("massacre_re"), + snd_dependency("rout"), + snd_dependency("rout_re"), + ), + + SIZE=4112, + ) + +udlg_def = TagDef("udlg", + blam_header('udlg'), + udlg_body, + + ext=".dialogue", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/unhi.py b/reclaimer/mcc_hek/defs/unhi.py new file mode 100644 index 00000000..097c352a --- /dev/null +++ b/reclaimer/mcc_hek/defs/unhi.py @@ -0,0 +1,209 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef +from .grhi import hud_background + +warning_sound = Struct("warning_sound", + dependency("sound", ('lsnd', 'snd!')), + Bool32("latched_to", + "shield_recharging", + "shield_recharged", + "shield_low", + "shield_empty", + "health_low", + "health_empty", + "health_minor_damage", + "health_major_damage", + ), + Float("scale"), + SIZE=56 + ) + +shield_panel_meter = Struct("shield_panel_meter", + QStruct("anchor_offset", + SInt16("x"), SInt16("y"), ORIENT='h', + ), + Float("width_scale"), + Float("height_scale"), + Bool16("scaling_flags", *hud_scaling_flags), + + Pad(22), + dependency("meter_bitmap", "bitm"), + #QStruct("color_at_meter_minimum", INCLUDE=xrgb_byte), + #QStruct("color_at_meter_maximum", INCLUDE=xrgb_byte), + #QStruct("flash_color", INCLUDE=xrgb_byte), + #QStruct("empty_color", INCLUDE=argb_byte), + UInt32("color_at_meter_minimum", INCLUDE=xrgb_uint32), + UInt32("color_at_meter_maximum", INCLUDE=xrgb_uint32), + UInt32("flash_color", INCLUDE=xrgb_uint32), + UInt32("empty_color", INCLUDE=argb_uint32), + Bool8("flags", *hud_panel_meter_flags), + SInt8("minimum_meter_value"), + SInt16("sequence_index"), + SInt8("alpha_multiplier"), + SInt8("alpha_bias"), + SInt16("value_scale"), + Float("opacity"), + Float("translucency"), + #QStruct("disabled_color", INCLUDE=argb_byte), + UInt32("disabled_color", INCLUDE=argb_uint32), + Pad(16), + #QStruct("overcharge_minimum_color", INCLUDE=xrgb_byte), + #QStruct("overcharge_maximum_color", INCLUDE=xrgb_byte), + #QStruct("overcharge_flash_color", INCLUDE=xrgb_byte), + #QStruct("overcharge_empty_color", INCLUDE=xrgb_byte), + UInt32("overcharge_minimum_color", INCLUDE=xrgb_uint32), + UInt32("overcharge_maximum_color", INCLUDE=xrgb_uint32), + UInt32("overcharge_flash_color", INCLUDE=xrgb_uint32), + UInt32("overcharge_empty_color", INCLUDE=xrgb_uint32), + SIZE=136 + ) + +health_panel_meter = Struct("health_panel_meter", + QStruct("anchor_offset", + SInt16("x"), SInt16("y"), ORIENT='h', + ), + Float("width_scale"), + Float("height_scale"), + Bool16("scaling_flags", *hud_scaling_flags), + + Pad(22), + dependency("meter_bitmap", "bitm"), + #QStruct("color_at_meter_minimum", INCLUDE=xrgb_byte), + #QStruct("color_at_meter_maximum", INCLUDE=xrgb_byte), + #QStruct("flash_color", INCLUDE=xrgb_byte), + #QStruct("empty_color", INCLUDE=argb_byte), + UInt32("color_at_meter_minimum", INCLUDE=xrgb_uint32), + UInt32("color_at_meter_maximum", INCLUDE=xrgb_uint32), + UInt32("flash_color", INCLUDE=xrgb_uint32), + UInt32("empty_color", INCLUDE=argb_uint32), + Bool8("flags", *hud_panel_meter_flags), + SInt8("minimum_meter_value"), + SInt16("sequence_index"), + SInt8("alpha_multiplier"), + SInt8("alpha_bias"), + SInt16("value_scale"), + Float("opacity"), + Float("translucency"), + #QStruct("disabled_color", INCLUDE=argb_byte), + UInt32("disabled_color", INCLUDE=argb_uint32), + Pad(16), + #QStruct("medium_health_left_color", INCLUDE=xrgb_byte), + UInt32("medium_health_left_color", INCLUDE=xrgb_uint32), + Float("max_color_health_fraction_cutoff"), + Float("min_color_health_fraction_cutoff"), + SIZE=136 + ) + +motion_sensor_center = Struct("motion_sensor_center", + QStruct("anchor_offset", + SInt16("x"), SInt16("y"), ORIENT='h', + ), + Float("width_scale"), + Float("height_scale"), + Bool16("scaling_flags", *hud_scaling_flags), + SIZE=16 + ) + +auxilary_overlay = Struct("auxilary_overlay", + Struct("background", INCLUDE=hud_background), + SEnum16("type", + "team_icon" + ), + Bool16("flags", + "use_team_color" + ), + SIZE=132 + ) + +auxilary_meter = Struct("auxilary_meter", + Pad(18), + SEnum16("type", "integrated_light", VISIBLE=False), + Struct("background", INCLUDE=hud_background), + + QStruct("anchor_offset", + SInt16("x"), SInt16("y"), ORIENT='h', + ), + Float("width_scale"), + Float("height_scale"), + Bool16("scaling_flags", *hud_scaling_flags), + + Pad(22), + dependency("meter_bitmap", "bitm"), + #QStruct("color_at_meter_minimum", INCLUDE=xrgb_byte), + #QStruct("color_at_meter_maximum", INCLUDE=xrgb_byte), + #QStruct("flash_color", INCLUDE=xrgb_byte), + #QStruct("empty_color", INCLUDE=argb_byte), + UInt32("color_at_meter_minimum", INCLUDE=xrgb_uint32), + UInt32("color_at_meter_maximum", INCLUDE=xrgb_uint32), + UInt32("flash_color", INCLUDE=xrgb_uint32), + UInt32("empty_color", INCLUDE=argb_uint32), + Bool8("flags", *hud_panel_meter_flags), + SInt8("minimum_meter_value"), + SInt16("sequence_index"), + SInt8("alpha_multiplier"), + SInt8("alpha_bias"), + SInt16("value_scale"), + Float("opacity"), + Float("translucency"), + #QStruct("disabled_color", INCLUDE=argb_byte), + UInt32("disabled_color", INCLUDE=argb_uint32), + + Pad(16), + Float("minimum_fraction_cutoff"), + Bool32("overlay_flags", + "show_only_when_active", + "flash_once_if_activated_while_disabled", + ), + + SIZE=324, + COMMENT="\nThis auxilary meter is meant for the flashlight.\n" + ) + +unhi_body = Struct("tagdata", + SEnum16("anchor", *hud_anchors), + + Pad(34), + Struct("unit_hud_background", INCLUDE=hud_background), + Struct("shield_panel_background", INCLUDE=hud_background), + shield_panel_meter, + Struct("health_panel_background", INCLUDE=hud_background), + health_panel_meter, + Struct("motion_sensor_background", INCLUDE=hud_background), + Struct("motion_sensor_foreground", INCLUDE=hud_background), + + Pad(32), + motion_sensor_center, + + Pad(20), + SEnum16("auxilary_overlay_anchor", *hud_anchors), + Pad(34), + reflexive("auxilary_overlays", auxilary_overlay, 16), + + Pad(16), + reflexive("warning_sounds", warning_sound, 12, + DYN_NAME_PATH='.sound.filepath'), + reflexive("auxilary_meters", auxilary_meter, 16), + + SIZE=1388 + ) + + +def get(): + return unhi_def + +unhi_def = TagDef("unhi", + blam_header("unhi"), + unhi_body, + + ext=".unit_hud_interface", endian=">", tag_cls=HekTag, + ) diff --git a/reclaimer/mcc_hek/defs/unit.py b/reclaimer/mcc_hek/defs/unit.py new file mode 100644 index 00000000..949e9a98 --- /dev/null +++ b/reclaimer/mcc_hek/defs/unit.py @@ -0,0 +1,210 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +def get(): + return unit_def + +camera_track = Struct('camera_track', + dependency('track', "trak"), + SIZE=28 + ) + +new_hud_interface = Struct('new_hud_interface', + dependency('unit_hud_interface', "unhi"), + SIZE=48 + ) + +dialogue_variant = Struct('dialogue_variant', + SInt16('variant_number'), + Pad(6), + dependency('dialogue', "udlg"), + SIZE=24 + ) + +powered_seat = Struct('powered_seat', + Pad(4), + float_sec('driver_powerup_time'), + float_sec('driver_powerdown_time'), + SIZE=68 + ) + +weapon = Struct('weapon', + dependency('weapon', "weap"), + SIZE=36 + ) + +seat = Struct('seat', + Bool32("flags", + "invisible", + "locked", + "driver", + "gunner", + "third_person_camera", + "allows_weapons", + "third_person_on_enter", + "first_person_slaved_to_gun", + "allow_vehicle_communcation_animation", + "not_valid_without_driver", + "allow_ai_noncombatants" + ), + ascii_str32('label'), + ascii_str32('marker_name'), + + Pad(32), + QStruct("acceleration_scale", INCLUDE=ijk_float), + + Pad(12), + float_deg_sec('yaw_rate', UNIT_SCALE=per_sec_unit_scale), # degrees/sec + float_deg_sec('pitch_rate', UNIT_SCALE=per_sec_unit_scale), # degrees/sec + ascii_str32('camera_marker_name'), + ascii_str32('camera_submerged_marker_name'), + float_rad('pitch_auto_level'), # radians + from_to_rad('pitch_range'), # radians + + reflexive("camera_tracks", camera_track, 2, 'loose', 'tight'), + reflexive("new_hud_interfaces", new_hud_interface, 2, + 'default/solo', 'multiplayer'), + + Pad(4), + SInt16("hud_text_message_index"), + + Pad(2), + float_rad('yaw_minimum'), # radians + float_rad('yaw_maximum'), # radians + dependency('built_in_gunner', "actv"), + Pad(12), # open sauce seat extension padding + SIZE=284 + ) + +unit_attrs = Struct("unit_attrs", + Bool32("flags", + "circular_aiming", + "destroyed_after_dying", + "half_speed_interpolation", + "fires_from_camera", + "entrance_inside_bounding_sphere", + "unused", + "causes_passenger_dialogue", + "resists_pings", + "melee_attack_is_fatal", + "dont_reface_during_pings", + "has_no_aiming", + "simple_creature", + "impact_melee_attaches_to_unit", + "impact_melee_dies_on_shields", + "cannot_open_doors_automatically", + "melee_attackers_cannot_attach", + "not_instantly_killed_by_melee", + "shield_sapping", + "runs_around_flaming", + "inconsequential", + "special_cinematic_unit", + "ignored_by_autoaiming", + "shields_fry_infection_forms", + "integrated_light_controls_weapon", + "integrated_light_lasts_forever", + ), + SEnum16('default_team', *unit_teams), + SEnum16('constant_sound_volume', *sound_volumes), + float_zero_to_inf('rider_damage_fraction'), + dependency('integrated_light_toggle', "effe"), + SEnum16('A_in', *unit_inputs), + SEnum16('B_in', *unit_inputs), + SEnum16('C_in', *unit_inputs), + SEnum16('D_in', *unit_inputs), + float_rad('camera_field_of_view'), # radians + Float('camera_stiffness'), + ascii_str32('camera_marker_name'), + ascii_str32('camera_submerged_marker_name'), + float_rad('pitch_auto_level'), # radians + from_to_rad('pitch_range'), # radians + reflexive("camera_tracks", camera_track, 2, + 'loose', 'tight'), + + #Miscellaneous + QStruct("seat_acceleration_scale", INCLUDE=ijk_float), + Pad(12), + float_zero_to_one('soft_ping_threshold'), # [0,1] + float_sec('soft_ping_interrupt_time', UNIT_SCALE=sec_unit_scale), # seconds + float_zero_to_one('hard_ping_threshold'), # [0,1] + float_sec('hard_ping_interrupt_time', UNIT_SCALE=sec_unit_scale), # seconds + float_zero_to_one('hard_death_threshold'), # [0,1] + float_zero_to_one('feign_death_threshold'), # [0,1] + float_sec('feign_death_time', UNIT_SCALE=sec_unit_scale), # seconds + float_wu('distance_of_evade_aim'), # world units + float_wu('distance_of_dive_aim'), # world units + + Pad(4), + float_zero_to_one('stunned_movement_threshold'), # [0,1] + float_zero_to_one('feign_death_chance'), # [0,1] + float_zero_to_one('feign_repeat_chance'), # [0,1] + dependency('spawned_actor', "actv"), + QStruct("spawned_actor_count", + SInt16("from", GUI_NAME=""), SInt16("to"), ORIENT='h', + ), + float_wu_sec('spawned_velocity'), + float_rad_sec('aiming_velocity_maximum', + UNIT_SCALE=irad_per_sec_unit_scale), # radians/sec + float_rad_sec_sq('aiming_acceleration_maximum', + UNIT_SCALE=irad_per_sec_sq_unit_scale), # radians/sec^2 + float_zero_to_one('casual_aiming_modifier'), + float_rad_sec('looking_velocity_maximum', + UNIT_SCALE=irad_per_sec_unit_scale), # radians/sec + float_rad_sec_sq('looking_acceleration_maximum', + UNIT_SCALE=irad_per_sec_sq_unit_scale), # radians/sec^2 + + Pad(8), + Float('ai_vehicle_radius'), + Float('ai_danger_radius'), + dependency('melee_damage', "jpt!"), + SEnum16('motion_sensor_blip_size', + "medium", + "small", + "large", + ), + Pad(2), + + Struct("mcc_additions", # replaced with opensauce unit extension in os_v4 + SEnum16("mcc_scoring_type", TOOLTIP="Used to determine score in MCC", *mcc_actor_types), + Pad(10), + ), + reflexive("new_hud_interfaces", new_hud_interface, 2, + 'default/solo', 'multiplayer'), + reflexive("dialogue_variants", dialogue_variant, 16, + DYN_NAME_PATH='.dialogue.filepath'), + + #Grenades + float_wu_sec('grenade_velocity'), + SEnum16('grenade_type', *grenade_types), + SInt16('grenade_count', MIN=0), + + Pad(4), + reflexive("powered_seats", powered_seat, 2, + "driver", "gunner"), + reflexive("weapons", weapon, 4, DYN_NAME_PATH='.weapon.filepath'), + reflexive("seats", seat, 16, DYN_NAME_PATH='.label'), + + SIZE=372 + ) + +unit_body = Struct('tagdata', + unit_attrs, + SIZE=372 + ) + +unit_def = TagDef("unit", + blam_header('unit', 2), + unit_body, + + ext=".unit", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/ustr.py b/reclaimer/mcc_hek/defs/ustr.py new file mode 100644 index 00000000..585214bb --- /dev/null +++ b/reclaimer/mcc_hek/defs/ustr.py @@ -0,0 +1,31 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.ustr import UstrTag +from supyr_struct.defs.tag_def import TagDef + +string_data_struct = rawtext_ref("string", FlStrUTF16, max_size=32768) + +ustr_body = Struct("tagdata", + reflexive("strings", string_data_struct, 32767, + DYN_NAME_PATH='.data'), + SIZE=12, + ) + + +def get(): + return ustr_def + +ustr_def = TagDef("ustr", + blam_header('ustr'), + ustr_body, + + ext=".unicode_string_list", endian=">", tag_cls=UstrTag + ) diff --git a/reclaimer/mcc_hek/defs/vcky.py b/reclaimer/mcc_hek/defs/vcky.py new file mode 100644 index 00000000..347915de --- /dev/null +++ b/reclaimer/mcc_hek/defs/vcky.py @@ -0,0 +1,67 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +def get(): return vcky_def + +virtual_key = Struct("virtual_key", + SEnum16("keyboard_key", + {NAME:"one", GUI_NAME:"1"}, + {NAME:"two", GUI_NAME:"2"}, + {NAME:"three", GUI_NAME:"3"}, + {NAME:"four", GUI_NAME:"4"}, + {NAME:"five", GUI_NAME:"5"}, + {NAME:"six", GUI_NAME:"6"}, + {NAME:"seven", GUI_NAME:"7"}, + {NAME:"eight", GUI_NAME:"8"}, + {NAME:"nine", GUI_NAME:"9"}, + {NAME:"zero", GUI_NAME:"0"}, + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", + "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "done", + "shift", + "capslock", + "symbols", + "backspace", + "left", + "right", + "space", + ), + SInt16("lowercase_character"), + SInt16("shift_character"), + SInt16("caps_character"), + SInt16("symbols_character"), + SInt16("shift_caps_character", GUI_NAME="shift+caps character"), + SInt16("shift_symbols_character", GUI_NAME="shift+symbols character"), + SInt16("caps_symbols_character", GUI_NAME="caps+symbols character"), + dependency("unselected_background_bitmap", "bitm"), + dependency("selected_background_bitmap", "bitm"), + dependency("active_background_bitmap", "bitm"), + dependency("sticky_background_bitmap", "bitm"), + SIZE=80 + ) + +vcky_body = Struct("tagdata", + dependency("display_font", "font"), + dependency("background_bitmap", "bitm"), + dependency("special_key_labels_string_list", "ustr"), + reflexive("virtual_keys", virtual_key, 44, + DYN_NAME_PATH='.keyboard_key.enum_name'), + SIZE=60, + ) + +vcky_def = TagDef("vcky", + blam_header('vcky', 2), + vcky_body, + + ext=".virtual_keyboard", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/vehi.py b/reclaimer/mcc_hek/defs/vehi.py new file mode 100644 index 00000000..4e7174e0 --- /dev/null +++ b/reclaimer/mcc_hek/defs/vehi.py @@ -0,0 +1,107 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .obje import * +from .unit import * +from .objs.obje import ObjeTag +from supyr_struct.util import desc_variant + +# replace the object_type enum one that uses +# the correct default value for this object +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(1)) + ) + +vehi_attrs = Struct("vehi_attrs", + Bool32("flags", + "speed_wakes_physics", + "turn_wakes_physics", + "driver_power_wakes_physics", + "gunner_power_wakes_physics", + "control_opposite_sets_brake", + "slide_wakes_physics", + "kills_riders_at_terminal_velocity", + "causes_collision_damage", + "ai_weapon_cannot_rotate", + "ai_does_not_require_driver", + "ai_unused", + "ai_driver_enable", + "ai_driver_flying", + "ai_driver_can_sidestep", + "ai_driver_hovering", + ), + SEnum16('type', *vehicle_types), + + Pad(2), + float_wu_sec("maximum_forward_speed"), + float_wu_sec("maximum_reverse_speed"), + float_wu_sec_sq("speed_acceleration", UNIT_SCALE=per_sec_unit_scale), + float_wu_sec_sq("speed_deceleration", UNIT_SCALE=per_sec_unit_scale), + Float("maximum_left_turn"), + Float("maximum_right_turn", SIDETIP="(should be negative)"), + float_wu("wheel_circumference"), # world units + Float("turn_rate", UNIT_SCALE=per_sec_unit_scale), + Float("blur_speed", UNIT_SCALE=per_sec_unit_scale), + SEnum16('A_in', *vehicle_inputs), + SEnum16('B_in', *vehicle_inputs), + SEnum16('C_in', *vehicle_inputs), + SEnum16('D_in', *vehicle_inputs), + + Pad(12), + Float("maximum_left_slide"), + Float("maximum_right_slide"), + Float("slide_acceleration", UNIT_SCALE=per_sec_unit_scale), + Float("slide_deceleration", UNIT_SCALE=per_sec_unit_scale), + Float("minimum_flipping_angular_velocity", UNIT_SCALE=per_sec_unit_scale), + Float("maximum_flipping_angular_velocity", UNIT_SCALE=per_sec_unit_scale), + + Pad(24), + float_deg("fixed_gun_yaw"), # degrees + float_deg("fixed_gun_pitch"), # degrees + + Pad(24), + Struct("ai", + Float("sidestep_distance"), + Float("destination_radius"), + Float("avoidance_distance"), + Float("pathfinding_radius"), + float_sec("charge_repeat_timeout"), + Float("strafing_abort_range"), + from_to_rad("oversteering_bounds"), # radians + float_rad("steering_maximum"), # radians + Float("throttle_maximum"), + float_sec("move_position_time"), + ), + + Pad(4), + dependency('suspension_sound', "snd!"), + dependency('crash_sound', "snd!"), + dependency('material_effect', "foot"), + dependency('effect', "effe"), + + SIZE=256 + ) + +vehi_body = Struct("tagdata", + obje_attrs, + unit_attrs, + vehi_attrs, + SIZE=1008, + ) + + +def get(): + return vehi_def + +vehi_def = TagDef("vehi", + blam_header('vehi'), + vehi_body, + + ext=".vehicle", endian=">", tag_cls=ObjeTag + ) diff --git a/reclaimer/mcc_hek/defs/weap.py b/reclaimer/mcc_hek/defs/weap.py new file mode 100644 index 00000000..27afab29 --- /dev/null +++ b/reclaimer/mcc_hek/defs/weap.py @@ -0,0 +1,325 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from .obje import * +from .item import * +from .objs.weap import WeapTag +from supyr_struct.util import desc_variant + +# replace the object_type enum one that uses +# the correct default value for this object +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(2)) + ) + +magazine_item = Struct("magazine_item", + SInt16("rounds"), + + Pad(10), + dependency('equipment', "eqip"), + SIZE=28 + ) + +magazine = Struct("magazine", + Bool32("flags", + "wastes_rounds_when_reloaded", + "every_round_must_be_chambered" + ), + SInt16("rounds_recharged", SIDETIP="per second"), + SInt16("rounds_total_initial"), + SInt16("rounds_total_maximum"), + SInt16("rounds_loaded_maximum"), + + Pad(8), + float_sec("reload_time"), + SInt16("rounds_reloaded"), + + Pad(2), + float_sec("chamber_time"), + + Pad(24), + dependency('reloading_effect', valid_event_effects), + dependency('chambering_effect', valid_event_effects), + + Pad(12), + reflexive("magazine_items", magazine_item, 2, + "primary", "secondary"), + SIZE=112 + ) + +firing_effect = Struct("firing_effect", + SInt16("shot_count_lower_bound"), + SInt16("shot_count_upper_bound"), + + Pad(32), + dependency('firing_effect', valid_event_effects), + dependency('misfire_effect', valid_event_effects), + dependency('empty_effect', valid_event_effects), + dependency('firing_damage', "jpt!"), + dependency('misfire_damage', "jpt!"), + dependency('empty_damage', "jpt!"), + SIZE=132 + ) + +trigger = Struct("trigger", + Bool32("flags", + "tracks_fired_projectile", + "random_firing_effects", + "can_fire_with_partial_ammo", + "does_not_repeat_automatically", + "locks_in_on_off_state", + "projectiles_use_weapon_origin", + "sticks_when_dropped", + "ejects_during_chamber", + "discharging_spews", + "analog_rate_of_fire", + "use_error_when_unzoomed", + "projectile_vector_cannot_be_adjusted", + "projectiles_have_identical_error", + "projectile_is_client_side_only", + ), + Struct("firing", + QStruct("rounds_per_second", + Float("from", UNIT_SCALE=per_sec_unit_scale, GUI_NAME=''), + Float("to", UNIT_SCALE=per_sec_unit_scale), + ORIENT='h' + ), + float_sec("acceleration_time"), + float_sec("deceleration_time"), + Float("blurred_rate_of_fire", UNIT_SCALE=per_sec_unit_scale), + + Pad(8), + SEnum16("magazine", + 'primary', + 'secondary', + ('NONE', -1), + DEFAULT=-1 + ), + SInt16("rounds_per_shot"), + SInt16("minimum_rounds_loaded"), + SInt16("rounds_between_tracers"), + + Pad(6), + SEnum16("firing_noise", *sound_volumes), + from_to_zero_to_one("error"), + float_sec("error_acceleration_time"), + float_sec("error_deceleration_time"), + ), + + Pad(8), + Struct("charging", + float_sec("charging_time"), + float_sec("charge_hold_time"), + SEnum16("overcharged_action", + 'none', + 'explode', + 'discharge', + ), + + Pad(2), + float_zero_to_one("charged_illumination"), + float_sec("spew_time"), + dependency('charging_effect', valid_event_effects), + ), + + Struct("projectile", + SEnum16("distribution_function", + 'point', + 'horizontal_fan', + ), + SInt16("projectiles_per_shot"), + float_deg("distribution_angle"), # degrees + + Pad(4), + float_rad("minimum_error"), # radians + from_to_rad("error_angle"), # radians + QStruct("first_person_offset", INCLUDE=xyz_float), + + Pad(4), + dependency('projectile', valid_objects), + ), + + Struct("misc", + float_sec("ejection_port_recovery_time"), + float_sec("illumination_recovery_time"), + + Pad(12), + float_zero_to_one("heat_generated_per_round"), + float_zero_to_one("age_generated_per_round"), + + Pad(4), + float_sec("overload_time"), + + Pad(40), + ), + + QStruct("misc_rates", + Float("ejection_port_recovery_rate"), + Float("illumination_recovery_rate"), + Float("acceleration_rate"), + Float("deceleration_rate"), + Float("error_acceleration_rate"), + Float("error_deceleration_rate"), + VISIBLE=False, + COMMENT="\ +These are various rates that are calculated when the weapon is compiled into a map." + ), + reflexive("firing_effects", firing_effect, 8), + + SIZE=276 + ) + +weap_attrs = Struct("weap_attrs", + Bool32("flags", + "vertical_heat_display", + "mutually_exclusive_triggers", + "attacks_automatically_on_bump", + "must_be_readied", + "doesnt_count_toward_maximum", + "aim_assists_only_when_zoomed", + "prevents_grenade_throwing", + "must_be_picked_up", + "holds_triggers_when_dropped", + "prevents_melee_attack", + "detonates_when_dropped", + "cannot_fire_at_maximum_age", + "secondary_trigger_overrides_grenades", + "does_not_depower_active_camo_in_multiplayer", # obsolete + "enables_integrated_night_vision", + "ai_uses_weapon_melee_damage" + ), + ascii_str32('label'), + SEnum16('secondary_trigger_mode', + "normal", + "slaved_to_primary", + "inhibits_primary", + "loads_alternate_ammunition", + "loads_multiple_primary_ammunition", + ), + SInt16("max_alternate_shots_loaded"), + SEnum16('A_in', *weapon_export_to), + SEnum16('B_in', *weapon_export_to), + SEnum16('C_in', *weapon_export_to), + SEnum16('D_in', *weapon_export_to), + float_sec("ready_time"), + dependency('ready_effect', valid_event_effects), + + Struct("heat", + float_zero_to_one("recovery_threshold"), + float_zero_to_one("overheated_threshold"), + float_zero_to_one("detonation_threshold"), + float_zero_to_one("detonation_fraction"), + float_zero_to_inf("loss_per_second", UNIT_SCALE=per_sec_unit_scale), + float_zero_to_one("illumination"), + + Pad(16), + dependency('overheated', valid_event_effects), + dependency('detonation', valid_event_effects), + ), + + Struct("melee", + dependency('player_damage', "jpt!"), + dependency('player_response', "jpt!"), + ), + + Pad(8), + Struct("aiming", + dependency("actor_firing_parameters", "actv"), + float_wu("near_reticle_range"), # world units + float_wu("far_reticle_range"), # world units + float_wu("intersection_reticle_range"), # world units + + Pad(2), + SInt16("zoom_levels"), + QStruct("zoom_ranges", INCLUDE=from_to), + float_rad("autoaim_angle"), # radians + float_wu("autoaim_range"), # world units + float_rad("magnetism_angle"), # radians + float_wu("magnetism_range"), # world units + float_rad("deviation_angle"), # radians + ), + + Pad(4), + Struct("movement", + SEnum16('penalized', + "always", + "when_zoomed", + "when_zoomed_or_reloading", + ), + Pad(2), + Float("forward_penalty"), + Float("sideways_penalty"), + ), + + Pad(4), + Struct("ai_targeting", + Float("minimum_target_range"), + Float("looking_time_modifier") + ), + + Pad(4), + Struct("light", + float_sec('power_on_time', UNIT_SCALE=sec_unit_scale), + float_sec('power_off_time', UNIT_SCALE=sec_unit_scale), + dependency('power_on_effect', valid_event_effects), + dependency('power_off_effect', valid_event_effects) + ), + + Struct("age", + Float("heat_penalty"), + Float("rate_of_fire_penalty"), + float_zero_to_one("misfire_start"), + float_zero_to_one("misfire_chance") + ), + + Pad(12), + Struct("interface", + dependency('first_person_model', valid_models), + dependency('first_person_animations', "antr"), + + Pad(4), + dependency('hud_interface', "wphi"), + dependency('pickup_sound', "snd!"), + dependency('zoom_in_sound', "snd!"), + dependency('zoom_out_sound', "snd!"), + + Pad(12), + Float('active_camo_ding'), + Float('active_camo_regrowth_rate', UNIT_SCALE=per_sec_unit_scale), + ), + + Pad(14), + SEnum16('weapon_type', *weapon_types), + + reflexive("predicted_resources", predicted_resource, 1024, VISIBLE=False), + reflexive("magazines", magazine, 2, + "primary", "secondary"), + reflexive("triggers", trigger, 2, + "primary", "secondary"), + + SIZE=512 + ) + +weap_body = Struct("tagdata", + obje_attrs, + item_attrs, + weap_attrs, + SIZE=1288, + ) + + +def get(): + return weap_def + +weap_def = TagDef("weap", + blam_header('weap', 2), + weap_body, + + ext=".weapon", endian=">", tag_cls=WeapTag + ) diff --git a/reclaimer/mcc_hek/defs/wind.py b/reclaimer/mcc_hek/defs/wind.py new file mode 100644 index 00000000..f194b77d --- /dev/null +++ b/reclaimer/mcc_hek/defs/wind.py @@ -0,0 +1,32 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.tag import HekTag +from supyr_struct.defs.tag_def import TagDef + +wind_body = Struct("tagdata", + from_to_wu("velocity"), + yp_float_rad("variation_area"), + Float("local_variation_weight"), + Float("local_variation_rate"), + Float("damping"), + SIZE=64, + ) + + +def get(): + return wind_def + +wind_def = TagDef("wind", + blam_header('wind'), + wind_body, + + ext=".wind", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/mcc_hek/defs/wphi.py b/reclaimer/mcc_hek/defs/wphi.py new file mode 100644 index 00000000..9a76abfb --- /dev/null +++ b/reclaimer/mcc_hek/defs/wphi.py @@ -0,0 +1,354 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from ...common_descs import * +from .objs.wphi import WphiTag +from supyr_struct.defs.tag_def import TagDef +from .grhi import messaging_information, multitex_overlay, hud_background + +crosshair_types = ( + "aim", + "zoom", + "charge", + "should_reload", + "flash_heat", + "flash_total_ammo", + "flash_battery", + "reload_overheat", + "flash_when_firing_and_no_ammo", + "flash_when_throwing_grenade_and_no_grenade", + "low_ammo_and_none_left_to_reload", + "should_reload_secondary_trigger", + "flash_secondary_total_ammo", + "flash_secondary_reload", + "flash_when_firing_secondary_and_no_ammo", + "low_secondary_ammo_and_none_left_to_reload", + "primary_trigger_ready", + "secondary_trigger_ready", + "flash_when_firing_with_depleted_battery", + ) + +attached_state = SEnum16("state_attached_to", + "total_ammo", + "loaded_ammo", + "heat", + "age", + "secondary_weapon_total_ammo", + "secondary_weapon_loaded_ammo", + "distance_to_target", + "elevation_to_target", + ) + +use_on_map_type = SEnum16("can_use_on_map_type", + "any", + "solo", + "multiplayer", + ) + +static_element = Struct("static_element", + attached_state, + Pad(2), + use_on_map_type, + + Pad(30), + QStruct("anchor_offset", + SInt16("x"), SInt16("y"), ORIENT='h', + ), + Float("width_scale"), + Float("height_scale"), + Bool16("scaling_flags", *hud_scaling_flags), + + Pad(22), + dependency("interface_bitmap", "bitm"), + #QStruct("default_color", INCLUDE=argb_byte), + #QStruct("flashing_color", INCLUDE=argb_byte), + UInt32("default_color", INCLUDE=argb_uint32), + UInt32("flashing_color", INCLUDE=argb_uint32), + float_sec("flash_period"), + float_sec("flash_delay"), + SInt16("number_of_flashes"), + Bool16("flash_flags", *hud_flash_flags), + float_sec("flash_length"), + #QStruct("disabled_color", INCLUDE=argb_byte), + UInt32("disabled_color", INCLUDE=argb_uint32), + + Pad(4), + SInt16("sequence_index"), + + Pad(2), + reflexive("multitex_overlays", multitex_overlay, 30), + SIZE=180 + ) + +meter_element = Struct("meter_element", + attached_state, + Pad(2), + use_on_map_type, + + Pad(30), + QStruct("anchor_offset", + SInt16("x"), SInt16("y"), ORIENT='h', + ), + Float("width_scale"), + Float("height_scale"), + Bool16("scaling_flags", *hud_scaling_flags), + + Pad(22), + dependency("meter_bitmap", "bitm"), + #QStruct("color_at_meter_minimum", INCLUDE=xrgb_byte), + #QStruct("color_at_meter_maximum", INCLUDE=xrgb_byte), + #QStruct("flash_color", INCLUDE=xrgb_byte), + #QStruct("empty_color", INCLUDE=argb_byte), + UInt32("color_at_meter_minimum", INCLUDE=xrgb_uint32), + UInt32("color_at_meter_maximum", INCLUDE=xrgb_uint32), + UInt32("flash_color", INCLUDE=xrgb_uint32), + UInt32("empty_color", INCLUDE=argb_uint32), + Bool8("flags", *hud_panel_meter_flags), + SInt8("minimum_meter_value"), + SInt16("sequence_index"), + SInt8("alpha_multiplier"), + SInt8("alpha_bias"), + SInt16("value_scale"), + Float("opacity"), + Float("translucency"), + #QStruct("disabled_color", INCLUDE=argb_byte), + UInt32("disabled_color", INCLUDE=argb_uint32), + SIZE=180 + ) + +number_element = Struct("number_element", + attached_state, + Pad(2), + use_on_map_type, + + Pad(30), + QStruct("anchor_offset", + SInt16("x"), SInt16("y"), ORIENT='h', + ), + Float("width_scale"), + Float("height_scale"), + Bool16("scaling_flags", *hud_scaling_flags), + + Pad(22), + #QStruct("default_color", INCLUDE=argb_byte), + #QStruct("flashing_color", INCLUDE=argb_byte), + UInt32("default_color", INCLUDE=argb_uint32), + UInt32("flashing_color", INCLUDE=argb_uint32), + float_sec("flash_period"), + float_sec("flash_delay"), + SInt16("number_of_flashes"), + Bool16("flash_flags", *hud_flash_flags), + float_sec("flash_length"), + #QStruct("disabled_color", INCLUDE=argb_byte), + UInt32("disabled_color", INCLUDE=argb_uint32), + + Pad(4), + SInt8("maximum_number_of_digits"), + Bool8("flags", + "show_leading_zeros", + "only_show_when_zoomed", + "draw_a_trailing_m", + ), + SInt8("number_of_fractional_digits"), + Pad(1), + + Pad(12), + Bool16("weapon_specific_flags", + "divide_number_by_magazine_size" + ), + SIZE=160 + ) + +crosshair_overlay = Struct("crosshair_overlay", + QStruct("anchor_offset", + SInt16("x"), SInt16("y"), ORIENT='h', + ), + Float("width_scale"), + Float("height_scale"), + Bool16("scaling_flags", *hud_scaling_flags), + + Pad(22), + #QStruct("default_color", INCLUDE=argb_byte), + #QStruct("flashing_color", INCLUDE=argb_byte), + UInt32("default_color", INCLUDE=argb_uint32), + UInt32("flashing_color", INCLUDE=argb_uint32), + float_sec("flash_period"), + float_sec("flash_delay"), + SInt16("number_of_flashes"), + Bool16("flash_flags", *hud_flash_flags), + float_sec("flash_length"), + #QStruct("disabled_color", INCLUDE=argb_byte), + UInt32("disabled_color", INCLUDE=argb_uint32), + + Pad(4), + SInt16("frame_rate", UNIT_SCALE=per_sec_unit_scale), + SInt16("sequence_index"), + Bool32("type", + "flashes_when_active", + "not_a_sprite", + "show_only_when_zoomed", + "show_sniper_data", + "hide_area_outside_reticle", + "one_zoom_level", + "dont_show_when_zoomed", + ), + SIZE=108 + ) + +overlay = Struct("overlay", + QStruct("anchor_offset", + SInt16("x"), SInt16("y"), ORIENT='h', + ), + Float("width_scale"), + Float("height_scale"), + Bool16("scaling_flags", *hud_scaling_flags), + + Pad(22), + #QStruct("default_color", INCLUDE=argb_byte), + #QStruct("flashing_color", INCLUDE=argb_byte), + UInt32("default_color", INCLUDE=argb_uint32), + UInt32("flashing_color", INCLUDE=argb_uint32), + float_sec("flash_period"), + float_sec("flash_delay"), + SInt16("number_of_flashes"), + Bool16("flash_flags", *hud_flash_flags), + float_sec("flash_length"), + #QStruct("disabled_color", INCLUDE=argb_byte), + UInt32("disabled_color", INCLUDE=argb_uint32), + + Pad(4), + SInt16("frame_rate", UNIT_SCALE=per_sec_unit_scale), + Pad(2), + SInt16("sequence_index"), + Bool16("type", + "show_on_flashing", + "show_on_empty", + "show_on_reload_overheating", + "show_on_default", + "show_always", + ), + Bool32("flags", + "flashes_when_active", + ), + SIZE=136 + ) + +crosshair = Struct("crosshair", + SEnum16("crosshair_type", *crosshair_types), + Pad(2), + use_on_map_type, + + Pad(30), + dependency("crosshair_bitmap", "bitm"), + reflexive("crosshair_overlays", crosshair_overlay, 16), + SIZE=104 + ) + +overlay_element = Struct("overlay_element", + attached_state, + Pad(2), + use_on_map_type, + + Pad(30), + dependency("overlay_bitmap", "bitm"), + reflexive("overlays", overlay, 16), + SIZE=104 + ) + +screen_effect = Struct("screen_effect", + Pad(4), + Struct("mask", + Bool16("flags", + "only_when_zoomed" + ), + Pad(18), + dependency("fullscreen_mask", "bitm"), + dependency("splitscreen_mask", "bitm") + ), + + Pad(8), + Struct("convolution", + Bool16("flags", + "only_when_zoomed" + ), + Pad(2), + from_to_rad("fov_in_bounds"), # radians + QStruct("radius_out_bounds", + INCLUDE=from_to, SIDETIP="pixels") # pixels + ), + + Pad(24), + Struct("night_vision", + Bool16("flags", + "only_when_zoomed", + "connect_to_flashlight", + "masked" + ), + SInt16("script_source", MIN=0, MAX=3, SIDETIP="[0,3]"), + Float("intensity", MIN=0.0, MAX=1.0, SIDETIP="[0,1]") + ), + + Pad(24), + Struct("desaturation", + Bool16("flags", + "only_when_zoomed", + "connect_to_flashlight", + "additive", + "masked" + ), + SInt16("script_source", MIN=0, MAX=3, SIDETIP="[0,3]"), + Float("intensity", MIN=0.0, MAX=1.0, SIDETIP="[0,1]"), + QStruct("tint", INCLUDE=rgb_float) + ), + SIZE=184 + ) + +wphi_body = Struct("tagdata", + dependency("child_hud", "wphi"), + Struct("flash_cutoffs", + Bool16("flags", + "use_parent_hud_flashing_parameters" + ), + Pad(2), + SInt16("total_ammo_cutoff"), + SInt16("loaded_ammo_cutoff"), + SInt16("heat_cutoff"), + SInt16("age_cutoff"), + ), + + Pad(32), + SEnum16("anchor", *hud_anchors), + + Pad(34), + reflexive("static_elements", static_element, 16), + reflexive("meter_elements", meter_element, 16), + reflexive("number_elements", number_element, 16), + reflexive("crosshairs", crosshair, 19), + reflexive("overlay_elements", overlay_element, 16), + FlBool32("crosshair_types", *crosshair_types, VISIBLE=False), + + # necessary for reticles to show up sometimes + Pad(12), + reflexive("screen_effect", screen_effect, 1), + + Pad(132), + messaging_information, + SIZE=380, + ) + + +def get(): + return wphi_def + +wphi_def = TagDef("wphi", + blam_header("wphi", 2), + wphi_body, + + ext=".weapon_hud_interface", endian=">", tag_cls=WphiTag, + ) diff --git a/reclaimer/mcc_hek/handler.py b/reclaimer/mcc_hek/handler.py new file mode 100644 index 00000000..3414faf5 --- /dev/null +++ b/reclaimer/mcc_hek/handler.py @@ -0,0 +1,275 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +import os + +from datetime import datetime +from time import time +from traceback import format_exc +from pathlib import Path, PureWindowsPath + +from binilla.handler import Handler + +from reclaimer.data_extraction import h1_data_extractors +from reclaimer.field_types import TagRef, Reflexive, RawdataRef +from reclaimer.hek.defs.objs.tag import HekTag +from reclaimer.hek.defs import __all__ as all_def_names + +from supyr_struct.buffer import BytearrayBuffer +from supyr_struct.util import tagpath_to_fullpath, is_path_empty +from supyr_struct.field_types import FieldType + + +class NodepathRef(dict): + __slots__ = ("is_ref",) + def __init__(self, is_ref, *a, **kw): + self.is_ref = is_ref + dict.__init__(self, *a, **kw) + + +NO_LOC_REFS = NodepathRef(False) + + +class HaloHandler(Handler): + frozen_imp_paths = all_def_names + tag_header_engine_id = "blam" + default_defs_path = "reclaimer.hek.defs" + tag_fcc_match_set = frozenset() + tag_filepath_match_set = frozenset() + + _tagsdir = Path.cwd().joinpath("tags") + _datadir = Path.cwd().joinpath("data") + + case_sensitive = False + tagsdir_relative = True + + treat_mode_as_mod2 = True + tag_ref_cache = None + reflexive_cache = None + raw_data_cache = None + + tag_data_extractors = h1_data_extractors + + def __init__(self, *args, **kwargs): + if not kwargs.pop("build_tag_ref_cache", True): + self.tag_ref_cache = NO_LOC_REFS + if not kwargs.pop("build_reflexive_cache", True): + self.reflexive_cache = NO_LOC_REFS + if not kwargs.pop("build_raw_data_cache", True): + self.raw_data_cache = NO_LOC_REFS + + Handler.__init__(self, *args, **kwargs) + + self.tag_fcc_match_set = set() + self.tag_filepath_match_set = set() + + self.ext_id_map = {} + for key in self.id_ext_map.keys(): + self.ext_id_map[self.id_ext_map[key]] = key + + if "default_conversion_flags" in kwargs: + self.default_conversion_flags = kwargs["default_conversion_flags"] + else: + self.default_conversion_flags = {} + for def_id in self.tags: + self.default_conversion_flags[def_id] = {} + + self.datadir = Path( + kwargs.get("datadir", self.tagsdir.parent.joinpath("data"))) + + # These break on Python 3.9 + + if self.tag_ref_cache is None: + self.tag_ref_cache = self.build_loc_caches(TagRef) + + if self.reflexive_cache is None: + self.reflexive_cache = self.build_loc_caches(Reflexive) + + if self.raw_data_cache is None: + self.raw_data_cache = self.build_loc_caches(RawdataRef) + + @property + def datadir(self): + return self._datadir + @datadir.setter + def datadir(self, new_val): + if not isinstance(new_val, Path): + new_val = Path(new_val) + self._datadir = new_val + + def _build_loc_cache(self, cond, desc={}): + try: + f_type = desc['TYPE'] + except Exception: + f_type = None + + if f_type is None: + return NO_LOC_REFS + + # python 3.9 band-aid + + try: + nodepath_ref = NodepathRef(cond(desc)) + except Exception: + print("Ignore me if you're not a developer") + print(format_exc()) + return NO_LOC_REFS + + for key in desc: + sub_nodepath_ref = self._build_loc_cache(cond, desc[key]) + if sub_nodepath_ref.is_ref or sub_nodepath_ref: + nodepath_ref[key] = sub_nodepath_ref + + return nodepath_ref + + def build_loc_caches(self, cond): + # if we are looking for only one specific FieldType, make it a tuple + if isinstance(cond, FieldType): + cond = (cond,) + + # if we are looking for FieldTypes, make it into a function + if isinstance(cond, (tuple, list)): + cond = lambda desc, f_types=cond: desc.get('TYPE') in f_types + + cache = {} + + for def_id in sorted(self.defs): + definition = self.defs[def_id].descriptor + + nodepath_ref = self._build_loc_cache(cond, definition) + if nodepath_ref.is_ref or nodepath_ref: + cache[def_id] = nodepath_ref + + return cache + + def _get_nodes_by_paths(self, paths, coll, cond, parent, key): + node = parent[key] + if paths.is_ref and cond(parent, key): + # this node is a node we are looking for; add it to the collection + if hasattr(node, "desc"): + coll.append(node) + else: + coll.append((parent, key)) + + if 'SUB_STRUCT' in paths: + paths = paths['SUB_STRUCT'] + for i in range(len(node)): + self._get_nodes_by_paths(paths, coll, cond, node, i) + return coll + + for i in paths: + self._get_nodes_by_paths(paths[i], coll, cond, node, i) + + return coll + + def get_nodes_by_paths(self, paths, node, cond=lambda x, y: True): + if paths: + return self._get_nodes_by_paths(paths, [], cond, (node,), 0) + + return () + + def get_def_id(self, filepath): + filepath = Path(filepath) + if self.tagsdir_relative and not filepath.is_absolute(): + filepath = self.tagsdir.joinpath(filepath) + + # It is more reliable to determine a Halo tag + # based on its 4CC def_id than by file extension + try: + with filepath.open('rb') as f: + f.seek(36) + def_id = str(f.read(4), 'latin-1') + f.seek(60) + engine_id = f.read(4).decode(encoding='latin-1') + if def_id in self.defs and engine_id == self.tag_header_engine_id: + return def_id + except Exception: + print(format_exc()); + + return self.ext_id_map.get(filepath.suffix.lower()) + + def get_tagref_invalid(self, parent, attr_index): + ''' + Checks if filepath of a tag reference is invalid. + Returns False if file exists. + Returns True if does not or if the reference is empty. + ''' + node = parent[attr_index] + #if the string is empty, there is no reference, can't be invalid. + if not node.filepath: + return False + + return not self.get_tagref_exists(parent, attr_index) + + def get_tagref_exists(self, parent, attr_index): + '''Returns whether or not a tag reference is valid.''' + node = parent[attr_index] + if not node.filepath: + return False + + ext = '.' + node.tag_class.enum_name + + # Get full path with proper capitalization if it points to a file. + filepath = tagpath_to_fullpath( + self.tagsdir, PureWindowsPath(node.filepath), extension=ext) + + if filepath is None and (self.treat_mode_as_mod2 and + ext == '.model'): + filepath = tagpath_to_fullpath( + self.tagsdir, PureWindowsPath(node.filepath), extension='.gbxmodel') + + return filepath is not None + + def get_tagref_matches(self, parent, attr_index): + ''' + Returns whether or not the int fcc of the nodes tag_class + matches any of the int fccs in self.tag_fcc_match_set and + if the nodes filepath is in self.tag_filepath_match_set. + + Returns True if both matchs are found AND the filepath is valid. + Returns False otherwise. + ''' + node = parent[attr_index] + return (bool(node.filepath) and ( + node.filepath in self.tag_filepath_match_set and + node.tag_class.data in self.tag_fcc_match_set)) + + def make_log_file(self, logstr, logpath=None): + ''' + Writes the supplied string to a log file. + + Required arguments: + logstr(str) + + If self.log_filename is a non-blank string it will be used as the + log filename. Otherwise the current timestamp will be used as the + filename in the format "YY-MM-DD HH:MM SS". + If the file already exists it will be appended to with the current + timestamp separating each write. Otherwise the file will be created. + ''' + # get the timestamp for the debug log's name + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + if not is_path_empty(logpath): + pass + elif isinstance(self.log_filename, str) and self.log_filename: + logpath = self.tagsdir.joinpath(self.log_filename) + logstr = '\n' + '-'*80 + '\n' + timestamp + '\n' + logstr + else: + logpath = self.tagsdir.joinpath(timestamp.replace(':', '.') + ".log") + + logpath = Path(logpath) + + mode = 'w' + if logpath.is_file(): + mode = 'a' + + # open a debug file and write the debug string to it + with logpath.open(mode) as logfile: + logfile.write(logstr) From 0e89283fd7203d06a9c5874db58111d17cac4e43 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Tue, 26 Dec 2023 02:18:56 -0600 Subject: [PATCH 02/51] MCC defs --- reclaimer/mcc_hek/defs/actr.py | 8 +- reclaimer/mcc_hek/defs/actv.py | 12 +- reclaimer/mcc_hek/defs/antr.py | 22 +-- reclaimer/mcc_hek/defs/bitm.py | 12 +- reclaimer/mcc_hek/defs/cdmg.py | 8 +- reclaimer/mcc_hek/defs/coll.py | 2 +- reclaimer/mcc_hek/defs/deca.py | 1 + reclaimer/mcc_hek/defs/effe.py | 4 +- reclaimer/mcc_hek/defs/eqip.py | 2 +- reclaimer/mcc_hek/defs/font.py | 6 +- reclaimer/mcc_hek/defs/grhi.py | 2 +- reclaimer/mcc_hek/defs/hudg.py | 36 ++++- reclaimer/mcc_hek/defs/jpt_.py | 8 +- reclaimer/mcc_hek/defs/lens.py | 6 + reclaimer/mcc_hek/defs/lsnd.py | 1 + reclaimer/mcc_hek/defs/matg.py | 2 +- reclaimer/mcc_hek/defs/obje.py | 5 +- reclaimer/mcc_hek/defs/proj.py | 1 + reclaimer/mcc_hek/defs/scen.py | 4 + reclaimer/mcc_hek/defs/scex.py | 1 + reclaimer/mcc_hek/defs/schi.py | 3 +- reclaimer/mcc_hek/defs/scnr.py | 112 ++++++++++---- reclaimer/mcc_hek/defs/senv.py | 1 + reclaimer/mcc_hek/defs/snd_.py | 3 +- reclaimer/mcc_hek/defs/soso.py | 1 + reclaimer/mcc_hek/defs/unhi.py | 19 ++- reclaimer/mcc_hek/defs/unit.py | 9 +- reclaimer/mcc_hek/defs/vehi.py | 7 + reclaimer/mcc_hek/defs/weap.py | 24 ++- reclaimer/mcc_hek/defs/wphi.py | 93 ++++++++++-- reclaimer/mcc_hek/handler.py | 268 +-------------------------------- 31 files changed, 317 insertions(+), 366 deletions(-) diff --git a/reclaimer/mcc_hek/defs/actr.py b/reclaimer/mcc_hek/defs/actr.py index fd7b047f..2cde09bd 100644 --- a/reclaimer/mcc_hek/defs/actr.py +++ b/reclaimer/mcc_hek/defs/actr.py @@ -109,7 +109,7 @@ Struct("movement", float_zero_to_one("dive_into_cover_chance"), float_zero_to_one("emerge_from_cover_chance"), - float_zero_to_one("dive_from_grenade_cover_chance"), + float_zero_to_one("dive_from_grenade_chance"), float_wu("pathfinding_radius"), # world units float_zero_to_one("glass_ignorance_chance"), float_wu("stationary_movement_dist"), # world units @@ -141,10 +141,10 @@ from_to_neg_one_to_one("cosine_maximum_aiming_deviation", VISIBLE=False), from_to_neg_one_to_one("cosine_maximum_looking_deviation", VISIBLE=False), - dependency("DO_NOT_USE_1", "weap"), + dependency("DO_NOT_USE_weapon", "weap"), Pad(268), - dependency("DO_NOT_USE_2", "proj") + dependency("DO_NOT_USE_projectile", "proj") ), Struct("unopposable", @@ -253,7 +253,7 @@ from_to_sec("combat_idle_speech_time"), # seconds Pad(176), - dependency("DO_NOT_USE_3", "actr"), + dependency("DO_NOT_USE_major_upgrade", "actr"), ), SIZE=1272 ) diff --git a/reclaimer/mcc_hek/defs/actv.py b/reclaimer/mcc_hek/defs/actv.py index 571f988e..ac93cdaf 100644 --- a/reclaimer/mcc_hek/defs/actv.py +++ b/reclaimer/mcc_hek/defs/actv.py @@ -19,7 +19,7 @@ actv_grenades = Struct("grenades", Pad(8), - SEnum16("grenade_type", *grenade_types), + SEnum16("grenade_type", *grenade_types_mcc), SEnum16("trajectory_type", "toss", "lob", @@ -45,9 +45,9 @@ actv_body = Struct("tagdata", Bool32('flags', "can_shoot_while_flying", - "blend_color_in_hsv", + "interpolate_color_in_hsv", "has_unlimited_grenades", - "moveswitch_stay_with_friends", + "movement_switching_try_to_stay_with_friends", "active_camouflage", "super_active_camouflage", "cannot_use_ranged_weapons", @@ -56,12 +56,12 @@ dependency("actor_definition", "actr"), dependency("unit", valid_units), dependency("major_variant", "actv"), - SEnum16("mcc_scoring_type", TOOLTIP="Used to determine score in MCC", *mcc_actor_types), - + SEnum16("metagame_type", TOOLTIP="Used to determine score in MCC", *mcc_actor_types), + SEnum16("metagame_class", TOOLTIP="Used to determine score in MCC", *mcc_actor_classes), #Movement switching Struct("movement_switching", - Pad(22), + Pad(20), SEnum16("movement_type", "always_run", "always_crouch", diff --git a/reclaimer/mcc_hek/defs/antr.py b/reclaimer/mcc_hek/defs/antr.py index 9399a550..8e01644a 100644 --- a/reclaimer/mcc_hek/defs/antr.py +++ b/reclaimer/mcc_hek/defs/antr.py @@ -102,8 +102,8 @@ reflexive("animations", anim_enum_desc, 55, *unit_weapon_animation_names ), - reflexive("ik_points", ik_point_desc, 4, DYN_NAME_PATH=".marker"), - reflexive("weapon_types", weapon_types_desc, 10, DYN_NAME_PATH=".label"), + reflexive("ik_points", ik_point_desc, 8, DYN_NAME_PATH=".marker"), + reflexive("weapon_types", weapon_types_desc, 64, DYN_NAME_PATH=".label"), SIZE=188, ) @@ -126,8 +126,8 @@ reflexive("animations", anim_enum_desc, 30, *unit_animation_names ), - reflexive("ik_points", ik_point_desc, 4, DYN_NAME_PATH=".marker"), - reflexive("weapons", unit_weapon_desc, 16, DYN_NAME_PATH=".name"), + reflexive("ik_points", ik_point_desc, 8, DYN_NAME_PATH=".marker"), + reflexive("weapons", unit_weapon_desc, 64, DYN_NAME_PATH=".name"), SIZE=100, ) @@ -165,7 +165,7 @@ reflexive("animations", anim_enum_desc, 8, *vehicle_animation_names ), - reflexive("suspension_animations", suspension_desc, 8), + reflexive("suspension_animations", suspension_desc, 32), SIZE=116, ) @@ -179,8 +179,8 @@ fp_animation_desc = Struct("fp_animation", Pad(16), - reflexive("animations", anim_enum_desc, 28, - *fp_animation_names + reflexive("animations", anim_enum_desc, 30, + *fp_animation_names_mcc ), SIZE=28, ) @@ -264,13 +264,13 @@ Pad(4), SInt32("offset_to_compressed_data", EDITABLE=False), rawdata_ref("default_data", max_size=16384), - rawdata_ref("frame_data", max_size=1048576), + rawdata_ref("frame_data", max_size=4194304), SIZE=180, ) antr_body = Struct("tagdata", reflexive("objects", object_desc, 4), - reflexive("units", unit_desc, 32, DYN_NAME_PATH=".label"), + reflexive("units", unit_desc, 2048, DYN_NAME_PATH=".label"), reflexive("weapons", weapon_desc, 1), reflexive("vehicles", vehicle_desc, 1), reflexive("devices", device_desc, 1), @@ -279,7 +279,7 @@ ), reflexive("fp_animations", fp_animation_desc, 1), #i have no idea why they decided to cap it at 257 instead of 256.... - reflexive("sound_references", sound_reference_desc, 257, + reflexive("sound_references", sound_reference_desc, 512, DYN_NAME_PATH=".sound.filepath"), Float("limp_body_node_radius"), Bool16("flags", @@ -288,7 +288,7 @@ ), Pad(2), reflexive("nodes", nodes_desc, 64, DYN_NAME_PATH=".name"), - reflexive("animations", animation_desc, 256, DYN_NAME_PATH=".name"), + reflexive("animations", animation_desc, 2048, DYN_NAME_PATH=".name"), SIZE=128, ) diff --git a/reclaimer/mcc_hek/defs/bitm.py b/reclaimer/mcc_hek/defs/bitm.py index 7cfec1c6..c7cce72a 100644 --- a/reclaimer/mcc_hek/defs/bitm.py +++ b/reclaimer/mcc_hek/defs/bitm.py @@ -179,6 +179,7 @@ def pixel_block_size(node, *a, **kwa): ("dxt3", 15), ("dxt5", 16), ("p8_bump", 17), + ("bc7", 18), ), Bool16("flags", "power_of_2_dim", @@ -224,6 +225,7 @@ def pixel_block_size(node, *a, **kwa): "color_16bit", "color_32bit", "monochrome", + "high_quality_compression", COMMENT=format_comment ), SEnum16("usage", @@ -240,6 +242,9 @@ def pixel_block_size(node, *a, **kwa): "disable_height_map_compression", "uniform_sprite_sequences", "sprite_bug_fix", + "hud_scale_0.5", + "invert_detail_fade", + "use_average_color_for_detail_fade" ), QStruct("post_processing", float_zero_to_one("detail_fade_factor"), @@ -254,6 +259,7 @@ def pixel_block_size(node, *a, **kwa): {NAME: "x128", VALUE: 2, GUI_NAME: "128x128"}, {NAME: "x256", VALUE: 3, GUI_NAME: "256x256"}, {NAME: "x512", VALUE: 4, GUI_NAME: "512x512"}, + {NAME: "x1024", VALUE: 5, GUI_NAME: "1024x1024"}, ), UInt16("sprite_budget_count"), COMMENT=sprite_processing_comment @@ -261,8 +267,8 @@ def pixel_block_size(node, *a, **kwa): UInt16("color_plate_width", SIDETIP="pixels", EDITABLE=False), UInt16("color_plate_height", SIDETIP="pixels", EDITABLE=False), - rawdata_ref("compressed_color_plate_data", max_size=16777216), - rawdata_ref("processed_pixel_data", max_size=16777216), + rawdata_ref("compressed_color_plate_data", max_size=1073741824), + rawdata_ref("processed_pixel_data", max_size=1073741824), Float("blur_filter_size", MIN=0.0, MAX=10.0, SIDETIP="[0,10] pixels"), float_neg_one_to_one("alpha_bias"), @@ -276,7 +282,7 @@ def pixel_block_size(node, *a, **kwa): Pad(2), reflexive("sequences", sequence, 256, DYN_NAME_PATH='.sequence_name', IGNORE_SAFE_MODE=True), - reflexive("bitmaps", bitmap, 2048, IGNORE_SAFE_MODE=True), + reflexive("bitmaps", bitmap, 65536, IGNORE_SAFE_MODE=True), SIZE=108, WIDGET=HaloBitmapTagFrame ) diff --git a/reclaimer/mcc_hek/defs/cdmg.py b/reclaimer/mcc_hek/defs/cdmg.py index dd6f27b3..faed0dfd 100644 --- a/reclaimer/mcc_hek/defs/cdmg.py +++ b/reclaimer/mcc_hek/defs/cdmg.py @@ -58,6 +58,11 @@ {NAME: "multiplayer_headshot", GUI_NAME: "can cause multiplayer headshots"}, "infection_form_pop", + "ignore_seat_scale_for_dir_dmg", + "forces_hard_ping", + "does_not_hurt_players", + "use_3d_instantaneous_acceleration", + "allow_any_non_zero_acceleration_value", ), Pad(4), Float("damage_lower_bound"), @@ -68,8 +73,7 @@ float_zero_to_one("maximum_stun"), float_sec("stun_time"), Pad(4), - float_zero_to_inf("instantaneous_acceleration"), - Pad(8), + QStruct("instantaneous_acceleration", INCLUDE=ijk_float), ), damage_modifiers, diff --git a/reclaimer/mcc_hek/defs/coll.py b/reclaimer/mcc_hek/defs/coll.py index 8728ae38..c5702be0 100644 --- a/reclaimer/mcc_hek/defs/coll.py +++ b/reclaimer/mcc_hek/defs/coll.py @@ -250,7 +250,7 @@ QStruct("z", INCLUDE=from_to), ), - reflexive("pathfinding_spheres", pathfinding_sphere, 32), + reflexive("pathfinding_spheres", pathfinding_sphere, 256), reflexive("nodes", node, 64, DYN_NAME_PATH='.name'), SIZE=664, diff --git a/reclaimer/mcc_hek/defs/deca.py b/reclaimer/mcc_hek/defs/deca.py index c8a779cd..131ca82d 100644 --- a/reclaimer/mcc_hek/defs/deca.py +++ b/reclaimer/mcc_hek/defs/deca.py @@ -44,6 +44,7 @@ "SAPIEN_incremental_counter", "animation_loop", "preserve_aspect", + "disabled in remastered by blood setting", COMMENT=decal_comment ), SEnum16("type", diff --git a/reclaimer/mcc_hek/defs/effe.py b/reclaimer/mcc_hek/defs/effe.py index 32340e93..4bdef38e 100644 --- a/reclaimer/mcc_hek/defs/effe.py +++ b/reclaimer/mcc_hek/defs/effe.py @@ -154,8 +154,8 @@ effe_body = Struct("tagdata", Bool32("flags", {NAME: "deleted_when_inactive", GUI_NAME: "deleted when attachment deactivates"}, - {NAME: "required", GUI_NAME: "required for gameplay (cannot optimize out)"}, - {NAME: "never_cull", VISIBLE: VISIBILITY_HIDDEN} + {NAME: "must_be_deterministic", GUI_NAME: "must_be_deterministic"}, + {NAME: "disabled_in_remastered_by_blood_setting"} ), dyn_senum16("loop_start_event", DYN_NAME_PATH=".events.events_array[DYN_I].NAME"), diff --git a/reclaimer/mcc_hek/defs/eqip.py b/reclaimer/mcc_hek/defs/eqip.py index cd8b091b..e4cfe6f0 100644 --- a/reclaimer/mcc_hek/defs/eqip.py +++ b/reclaimer/mcc_hek/defs/eqip.py @@ -29,7 +29,7 @@ 'health', 'grenade', ), - SEnum16('grenade_type', *grenade_types), + SEnum16('grenade_type', *grenade_types_mcc), float_sec('powerup_time'), dependency('pickup_sound', "snd!"), diff --git a/reclaimer/mcc_hek/defs/font.py b/reclaimer/mcc_hek/defs/font.py index cc4e50a7..e2367ac8 100644 --- a/reclaimer/mcc_hek/defs/font.py +++ b/reclaimer/mcc_hek/defs/font.py @@ -38,9 +38,11 @@ def get(): return font_def ) font_body = Struct("tagdata", - SInt32("flags"), + Bool32("flags", + "never_override_with_remastered_font_under_MCC", + ), SInt16("ascending_height"), - SInt16("decending_height"), + SInt16("descending_height"), SInt16("leading_height"), SInt16("leading_width"), Pad(36), diff --git a/reclaimer/mcc_hek/defs/grhi.py b/reclaimer/mcc_hek/defs/grhi.py index 20484b14..e20fe702 100644 --- a/reclaimer/mcc_hek/defs/grhi.py +++ b/reclaimer/mcc_hek/defs/grhi.py @@ -219,7 +219,7 @@ ) grhi_body = Struct("tagdata", - SEnum16("anchor", *hud_anchors), + SEnum16("anchor", *hud_anchors_mcc), Pad(34), Struct("grenade_hud_background", INCLUDE=hud_background), diff --git a/reclaimer/mcc_hek/defs/hudg.py b/reclaimer/mcc_hek/defs/hudg.py index 4990b261..8c5f0a1c 100644 --- a/reclaimer/mcc_hek/defs/hudg.py +++ b/reclaimer/mcc_hek/defs/hudg.py @@ -50,7 +50,7 @@ messaging_parameters = Struct("messaging_parameters", - SEnum16("anchor", *hud_anchors), + SEnum16("anchor", *hud_anchors_mcc), Pad(34), QStruct("anchor_offset", @@ -182,8 +182,36 @@ SInt16("checkpoint_begin_text"), SInt16("checkpoint_end_text"), dependency("checkpoint", "snd!"), - BytearrayRaw("unknown", SIZE=96, VISIBLE=False), - SIZE=120 + SIZE=24 + ) + +targets = Struct("targets", + dependency("target_bitmap", "bitm"), + SEnum16("language", + "english", + "french", + "spanish", + "italian", + "german", + "tchinese", + "japanese", + "korean", + "portuguese", + "latam_spanish", + "polish", + "russian", + "schinese" + ), + Bool16("flags", + "legacy_mode" + ), + SIZE=20 + ) + +remaps = Struct("remaps", + dependency("original_bitmap", "bitm"), + reflexive("targets", targets, 26, DYN_NAME_PATH='.target_bitmap.filepath'), + SIZE=28 ) hudg_body = Struct("tagdata", @@ -209,6 +237,8 @@ Pad(40), dependency("carnage_report_bitmap", "bitm"), misc_hud_crap, + reflexive("remaps", remaps, 32, DYN_NAME_PATH='.original_bitmap.filepath'), + Pad(84), SIZE=1104 ) diff --git a/reclaimer/mcc_hek/defs/jpt_.py b/reclaimer/mcc_hek/defs/jpt_.py index bb37426f..9b4dac20 100644 --- a/reclaimer/mcc_hek/defs/jpt_.py +++ b/reclaimer/mcc_hek/defs/jpt_.py @@ -128,6 +128,11 @@ {NAME: "multiplayer_headshot", GUI_NAME: "causes multiplayer headshots"}, "infection_form_pop", + "ignore_seat_scale_for_dir_dmg", + "forces_hard_ping", + "does_not_hurt_players", + "use_3d_instantaneous_acceleration", + "allow_any_non_zero_acceleration_value", ), float_wu("aoe_core_radius"), Float("damage_lower_bound"), @@ -138,8 +143,7 @@ float_zero_to_one("maximum_stun"), float_sec("stun_time"), Pad(4), - float_zero_to_inf("instantaneous_acceleration"), - Pad(8), + QStruct("instantaneous_acceleration", INCLUDE=ijk_float), ), damage_modifiers, diff --git a/reclaimer/mcc_hek/defs/lens.py b/reclaimer/mcc_hek/defs/lens.py index 15cc1bf6..cde68ad2 100644 --- a/reclaimer/mcc_hek/defs/lens.py +++ b/reclaimer/mcc_hek/defs/lens.py @@ -85,6 +85,12 @@ dependency("bitmap", "bitm"), Bool16("flags", "sun", + "no_occlusion_test", + "only_render_in_first_person", + "only_render_in_third_person", + "fade_in_more_quickly", + "fade_out_more_quickly", + "scale_by_marker", ), Pad(78), ), diff --git a/reclaimer/mcc_hek/defs/lsnd.py b/reclaimer/mcc_hek/defs/lsnd.py index 0914ebbf..74bebd1b 100644 --- a/reclaimer/mcc_hek/defs/lsnd.py +++ b/reclaimer/mcc_hek/defs/lsnd.py @@ -66,6 +66,7 @@ "deafening_to_ai", "not_a_loop", "stops_music", + "siege_of_the_madrigal", ), Float("detail_sound_period_at_zero", COMMENT=scale_comment), FlFloat("unknown0", DEFAULT=1.0, VISIBLE=False), diff --git a/reclaimer/mcc_hek/defs/matg.py b/reclaimer/mcc_hek/defs/matg.py index 15319bea..a488f7e5 100644 --- a/reclaimer/mcc_hek/defs/matg.py +++ b/reclaimer/mcc_hek/defs/matg.py @@ -356,7 +356,7 @@ def get(): reflexive("cameras", camera, 1), reflexive("player_controls", player_control, 1), reflexive("difficulties", difficulty, 1), - reflexive("grenades", grenade, 2, *grenade_types), + reflexive("grenades", grenade, 4, *grenade_types_mcc), reflexive("rasterizer_datas", rasterizer_data, 1), reflexive("interface_bitmaps", interface_bitmaps, 1), reflexive("cheat_weapons", cheat_weapon, 20, diff --git a/reclaimer/mcc_hek/defs/obje.py b/reclaimer/mcc_hek/defs/obje.py index 7cbf0ef6..1b51dd11 100644 --- a/reclaimer/mcc_hek/defs/obje.py +++ b/reclaimer/mcc_hek/defs/obje.py @@ -99,8 +99,9 @@ def get(): 'transparent_self_occlusion', 'brighter_than_it_should_be', 'not_a_pathfinding_obstacle', - {NAME: 'xbox_unknown_bit_8', VALUE: 1<<8, VISIBLE: False}, - {NAME: 'xbox_unknown_bit_11', VALUE: 1<<11, VISIBLE: False}, + "extension of parent", + "cast shadow by default", + "does not have remastered geometry", ), float_wu('bounding_radius'), QStruct('bounding_offset', INCLUDE=xyz_float), diff --git a/reclaimer/mcc_hek/defs/proj.py b/reclaimer/mcc_hek/defs/proj.py index baf1f83b..63e03464 100644 --- a/reclaimer/mcc_hek/defs/proj.py +++ b/reclaimer/mcc_hek/defs/proj.py @@ -37,6 +37,7 @@ SEnum16('response', *responses), Bool16("flags", "only_against_units", + "never against units" ), float_zero_to_one("skip_fraction"), from_to_rad("impact_angle"), # radians diff --git a/reclaimer/mcc_hek/defs/scen.py b/reclaimer/mcc_hek/defs/scen.py index 24238809..e86fc344 100644 --- a/reclaimer/mcc_hek/defs/scen.py +++ b/reclaimer/mcc_hek/defs/scen.py @@ -20,6 +20,10 @@ scen_body = Struct("tagdata", obje_attrs, + Pad(2), + Bool32('flags', + 'unused', + ), SIZE=508, ) diff --git a/reclaimer/mcc_hek/defs/scex.py b/reclaimer/mcc_hek/defs/scex.py index fa88bd9d..491812e8 100644 --- a/reclaimer/mcc_hek/defs/scex.py +++ b/reclaimer/mcc_hek/defs/scex.py @@ -39,6 +39,7 @@ Bool32("extra_flags", "dont_fade_active_camouflage", "numeric_countdown_timer" + "custom_edition_blending", ), SIZE=80 ) diff --git a/reclaimer/mcc_hek/defs/schi.py b/reclaimer/mcc_hek/defs/schi.py index 55b626c5..4c7f7fa3 100644 --- a/reclaimer/mcc_hek/defs/schi.py +++ b/reclaimer/mcc_hek/defs/schi.py @@ -66,7 +66,8 @@ DYN_NAME_PATH='.bitmap.filepath'), Bool32("extra_flags", "dont_fade_active_camouflage", - "numeric_countdown_timer" + "numeric_countdown_timer", + "custom_edition_blending", ), SIZE=68 ) diff --git a/reclaimer/mcc_hek/defs/scnr.py b/reclaimer/mcc_hek/defs/scnr.py index 78f11b6f..cc3df953 100644 --- a/reclaimer/mcc_hek/defs/scnr.py +++ b/reclaimer/mcc_hek/defs/scnr.py @@ -27,6 +27,7 @@ def object_reference(name, *args, **kwargs): "on_easy", "on_normal", "on_hard", + "use_player_appearance", ), SInt16('desired_permutation'), QStruct("position", INCLUDE=xyz_float), @@ -236,10 +237,16 @@ def object_swatch(name, def_id, size=48): ) # Object references -scenery = object_reference("scenery", SIZE=72, block_name="sceneries") +scenery = object_reference("scenery", + Pad(4), + SInt8("appearance_player_index"), + SIZE=72, + block_name="sceneries") biped = object_reference("biped", - Pad(40), + Pad(4), + SInt8("appearance_player_index"), + Pad(35), float_zero_to_one("body_vitality"), Bool32("flags", "dead", @@ -248,7 +255,9 @@ def object_swatch(name, def_id, size=48): ) vehicle = object_reference("vehicle", - Pad(40), + Pad(4), + SInt8("appearance_player_index"), + Pad(35), float_zero_to_one("body_vitality"), Bool32("flags", "dead", @@ -285,11 +294,14 @@ def object_swatch(name, def_id, size=48): "obsolete", {NAME: "can_accelerate", GUI_NAME:"moves due to explosions"}, ), + SInt8("appearance_player_index"), SIZE=40 ) weapon = object_reference("weapon", - Pad(40), + Pad(4), + SInt8("appearance_player_index"), + Pad(35), SInt16("rounds_left"), SInt16("rounds_loaded"), Bool16("flags", @@ -310,7 +322,9 @@ def object_swatch(name, def_id, size=48): ) machine = object_reference("machine", - Pad(8), + Pad(4), + SInt8("appearance_player_index"), + Pad(3), dyn_senum16("power_group", DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), dyn_senum16("position_group", @@ -326,7 +340,9 @@ def object_swatch(name, def_id, size=48): ) control = object_reference("control", - Pad(8), + Pad(4), + SInt8("appearance_player_index"), + Pad(3), dyn_senum16("power_group", DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), dyn_senum16("position_group", @@ -340,7 +356,9 @@ def object_swatch(name, def_id, size=48): ) light_fixture = object_reference("light_fixture", - Pad(8), + Pad(4), + SInt8("appearance_player_index"), + Pad(3), dyn_senum16("power_group", DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), dyn_senum16("position_group", @@ -353,7 +371,11 @@ def object_swatch(name, def_id, size=48): SIZE=88 ) -sound_scenery = object_reference("sound_scenery", SIZE=40, block_name="sound_sceneries") +sound_scenery = object_reference("sound_scenery", + Pad(4), + SInt8("appearance_player_index"), + SIZE=40, + block_name="sound_sceneries") # Object swatches scenery_swatch = object_swatch("scenery_swatch", "scen") @@ -378,6 +400,8 @@ def object_swatch(name, def_id, size=48): SInt16("secondary_rounds_total"), SInt8("starting_frag_grenade_count", MIN=0), SInt8("starting_plasma_grenade_count", MIN=0), + SInt8("starting_grenade_type2_count", MIN=0), + SInt8("starting_grenade_type3_count", MIN=0), SIZE=104 ) @@ -464,7 +488,7 @@ def object_swatch(name, def_id, size=48): SEnum16("type_1", *location_types), SEnum16("type_2", *location_types), SEnum16("type_3", *location_types), - SInt16("team_index"), + SInt16("usage_id"), SInt16("spawn_time", SIDETIP="seconds(0 = default)", UNIT_SCALE=sec_unit_scale), # seconds @@ -478,7 +502,9 @@ def object_swatch(name, def_id, size=48): starting_equipment = Struct("starting_equipment", Bool32("flags", "no_grenades", - "plasma_grenades", + "plasma_grenades_only", + "type2_grenades_only", + "type3_grenades_only", ), SEnum16("type_0", *location_types), SEnum16("type_1", *location_types), @@ -536,12 +562,20 @@ def object_swatch(name, def_id, size=48): SIZE=40 ) +parameters = Struct("parameters", + ascii_str32("name", EDITABLE=False), + SEnum16("return_type", *script_object_types, EDITABLE=False), + SIZE=36, + ) + halo_script = Struct("script", ascii_str32("name", EDITABLE=False), SEnum16("type", *script_types), SEnum16("return_type", *script_object_types, EDITABLE=False), UInt32("root_expression_index", EDITABLE=False), Computed("decompiled_script", WIDGET=HaloScriptTextFrame), + Pad(40), + reflexive("parameters", parameters, 16), SIZE=92, ) @@ -562,7 +596,7 @@ def object_swatch(name, def_id, size=48): source_file = Struct("source_file", ascii_str32("source_name"), - rawdata_ref("source", max_size=262144, widget=HaloScriptSourceFrame), + rawdata_ref("source", max_size=1048576, widget=HaloScriptSourceFrame), SIZE=52 ) @@ -606,7 +640,13 @@ def object_swatch(name, def_id, size=48): "center", ), - Pad(6), + Pad(2), + Bool32("flags", + "wrap horizontally", + "wrap vertically", + "center vertically", + "bottom justify", + ), #QStruct("text_color", INCLUDE=argb_byte), #QStruct("shadow_color", INCLUDE=argb_byte), UInt32("text_color", INCLUDE=argb_uint32), @@ -917,6 +957,13 @@ def object_swatch(name, def_id, size=48): SIZE=32 ) +scavenger_hunt_objects = Struct("scavenger_hunt_objects", + ascii_str32("exported_name"), + dyn_senum16("scenario_object_name_index", DYN_NAME_PATH="tagdata.object_names.STEPTREE[DYN_I].name"), + Pad(2), + SIZE=36 + ) + scnr_body = Struct("tagdata", dependency("DONT_USE", 'sbsp'), dependency("WONT_USE", 'sbsp'), @@ -929,7 +976,9 @@ def object_swatch(name, def_id, size=48): ), Bool16("flags", "cortana_hack", - "use_demo_ui" + "use_demo_ui", + "color correction (ntsc->srgb)", + "DO NOT apply bungie campaign tag patches", ), reflexive("child_scenarios", child_scenario, 16, DYN_NAME_PATH='.child_scenario.filepath'), @@ -941,39 +990,40 @@ def object_swatch(name, def_id, size=48): DYN_NAME_PATH='.name'), rawdata_ref("scenario_editor_data", max_size=65536), reflexive("comments", comment, 1024), + reflexive("scavenger_hunt_objects", scavenger_hunt_objects, 16), - Pad(224), - reflexive("object_names", object_name, 512, + Pad(212), + reflexive("object_names", object_name, 640, DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), reflexive("sceneries", scenery, 2000, IGNORE_SAFE_MODE=True), - reflexive("sceneries_palette", scenery_swatch, 100, + reflexive("sceneries_palette", scenery_swatch, 256, DYN_NAME_PATH='.name.filepath'), reflexive("bipeds", biped, 128, IGNORE_SAFE_MODE=True), - reflexive("bipeds_palette", biped_swatch, 100, + reflexive("bipeds_palette", biped_swatch, 256, DYN_NAME_PATH='.name.filepath'), - reflexive("vehicles", vehicle, 80, IGNORE_SAFE_MODE=True), - reflexive("vehicles_palette", vehicle_swatch, 100, + reflexive("vehicles", vehicle, 256, IGNORE_SAFE_MODE=True), + reflexive("vehicles_palette", vehicle_swatch, 256, DYN_NAME_PATH='.name.filepath'), reflexive("equipments", equipment, 256, IGNORE_SAFE_MODE=True), - reflexive("equipments_palette", equipment_swatch, 100, + reflexive("equipments_palette", equipment_swatch, 256, DYN_NAME_PATH='.name.filepath'), reflexive("weapons", weapon, 128, IGNORE_SAFE_MODE=True), - reflexive("weapons_palette", weapon_swatch, 100, + reflexive("weapons_palette", weapon_swatch, 256, DYN_NAME_PATH='.name.filepath'), reflexive("device_groups", device_group, 128, DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), reflexive("machines", machine, 400, IGNORE_SAFE_MODE=True), - reflexive("machines_palette", machine_swatch, 100, + reflexive("machines_palette", machine_swatch, 256, DYN_NAME_PATH='.name.filepath'), reflexive("controls", control, 100, IGNORE_SAFE_MODE=True), - reflexive("controls_palette", control_swatch, 100, + reflexive("controls_palette", control_swatch, 256, DYN_NAME_PATH='.name.filepath'), reflexive("light_fixtures", light_fixture, 500, IGNORE_SAFE_MODE=True), - reflexive("light_fixtures_palette", light_fixture_swatch, 100, + reflexive("light_fixtures_palette", light_fixture_swatch, 256, DYN_NAME_PATH='.name.filepath'), reflexive("sound_sceneries", sound_scenery, 256, IGNORE_SAFE_MODE=True), - reflexive("sound_sceneries_palette", sound_scenery_swatch, 100, + reflexive("sound_sceneries_palette", sound_scenery_swatch, 256, DYN_NAME_PATH='.name.filepath'), Pad(84), @@ -1009,13 +1059,13 @@ def object_swatch(name, def_id, size=48): DYN_NAME_PATH='.recording_name'), reflexive("ai_conversations", ai_conversation, 128, DYN_NAME_PATH='.name'), - rawdata_ref("script_syntax_data", max_size=380076, IGNORE_SAFE_MODE=True), - rawdata_ref("script_string_data", max_size=262144, IGNORE_SAFE_MODE=True), - reflexive("scripts", halo_script, 512, DYN_NAME_PATH='.name'), - reflexive("globals", halo_global, 128, DYN_NAME_PATH='.name'), - reflexive("references", reference, 256, + rawdata_ref("script_syntax_data", max_size=655396, IGNORE_SAFE_MODE=True), + rawdata_ref("script_string_data", max_size=819200, IGNORE_SAFE_MODE=True), + reflexive("scripts", halo_script, 1024, DYN_NAME_PATH='.name'), + reflexive("globals", halo_global, 512, DYN_NAME_PATH='.name'), + reflexive("references", reference, 512, DYN_NAME_PATH='.reference.filepath'), - reflexive("source_files", source_file, 8, DYN_NAME_PATH='.source_name'), + reflexive("source_files", source_file, 16, DYN_NAME_PATH='.source_name'), Pad(24), reflexive("cutscene_flags", cutscene_flag, 512, DYN_NAME_PATH='.name'), diff --git a/reclaimer/mcc_hek/defs/senv.py b/reclaimer/mcc_hek/defs/senv.py index 42aabecb..10addf48 100644 --- a/reclaimer/mcc_hek/defs/senv.py +++ b/reclaimer/mcc_hek/defs/senv.py @@ -80,6 +80,7 @@ "alpha_tested", "bump_map_is_specular_mask", "true_atmospheric_fog", + "use_variant_2_for_calculation_bump_attention", COMMENT=environment_shader_comment ), SEnum16("type", diff --git a/reclaimer/mcc_hek/defs/snd_.py b/reclaimer/mcc_hek/defs/snd_.py index ab29cd73..a0498c89 100644 --- a/reclaimer/mcc_hek/defs/snd_.py +++ b/reclaimer/mcc_hek/defs/snd_.py @@ -109,7 +109,8 @@ snd__body = Struct("tagdata", Bool32("flags", "fit_to_adpcm_blocksize", - "split_long_sound_into_permutations" + "split_long_sound_into_permutations", + "thirsty_grunt", ), SEnum16("sound_class", *sound_classes), SEnum16("sample_rate", diff --git a/reclaimer/mcc_hek/defs/soso.py b/reclaimer/mcc_hek/defs/soso.py index ca8f22de..08cbe16c 100644 --- a/reclaimer/mcc_hek/defs/soso.py +++ b/reclaimer/mcc_hek/defs/soso.py @@ -58,6 +58,7 @@ "alpha_blended_decal", "true_atmospheric_fog", "disable_two_sided_culling", + "multipurpose_map_uses_og_xbox_channel_order", ), Pad(14), Float("translucency"), diff --git a/reclaimer/mcc_hek/defs/unhi.py b/reclaimer/mcc_hek/defs/unhi.py index 097c352a..a244ce36 100644 --- a/reclaimer/mcc_hek/defs/unhi.py +++ b/reclaimer/mcc_hek/defs/unhi.py @@ -46,7 +46,7 @@ UInt32("color_at_meter_maximum", INCLUDE=xrgb_uint32), UInt32("flash_color", INCLUDE=xrgb_uint32), UInt32("empty_color", INCLUDE=argb_uint32), - Bool8("flags", *hud_panel_meter_flags), + Bool8("flags", *hud_panel_meter_mcc_flags), SInt8("minimum_meter_value"), SInt16("sequence_index"), SInt8("alpha_multiplier"), @@ -56,7 +56,8 @@ Float("translucency"), #QStruct("disabled_color", INCLUDE=argb_byte), UInt32("disabled_color", INCLUDE=argb_uint32), - Pad(16), + Float("min_alpha"), + Pad(12), #QStruct("overcharge_minimum_color", INCLUDE=xrgb_byte), #QStruct("overcharge_maximum_color", INCLUDE=xrgb_byte), #QStruct("overcharge_flash_color", INCLUDE=xrgb_byte), @@ -86,7 +87,7 @@ UInt32("color_at_meter_maximum", INCLUDE=xrgb_uint32), UInt32("flash_color", INCLUDE=xrgb_uint32), UInt32("empty_color", INCLUDE=argb_uint32), - Bool8("flags", *hud_panel_meter_flags), + Bool8("flags", *hud_panel_meter_mcc_flags), SInt8("minimum_meter_value"), SInt16("sequence_index"), SInt8("alpha_multiplier"), @@ -96,7 +97,8 @@ Float("translucency"), #QStruct("disabled_color", INCLUDE=argb_byte), UInt32("disabled_color", INCLUDE=argb_uint32), - Pad(16), + Float("min_alpha"), + Pad(12), #QStruct("medium_health_left_color", INCLUDE=xrgb_byte), UInt32("medium_health_left_color", INCLUDE=xrgb_uint32), Float("max_color_health_fraction_cutoff"), @@ -147,7 +149,7 @@ UInt32("color_at_meter_maximum", INCLUDE=xrgb_uint32), UInt32("flash_color", INCLUDE=xrgb_uint32), UInt32("empty_color", INCLUDE=argb_uint32), - Bool8("flags", *hud_panel_meter_flags), + Bool8("flags", *hud_panel_meter_mcc_flags), SInt8("minimum_meter_value"), SInt16("sequence_index"), SInt8("alpha_multiplier"), @@ -157,8 +159,9 @@ Float("translucency"), #QStruct("disabled_color", INCLUDE=argb_byte), UInt32("disabled_color", INCLUDE=argb_uint32), + Float("min_alpha"), - Pad(16), + Pad(12), Float("minimum_fraction_cutoff"), Bool32("overlay_flags", "show_only_when_active", @@ -170,7 +173,7 @@ ) unhi_body = Struct("tagdata", - SEnum16("anchor", *hud_anchors), + SEnum16("anchor", *hud_anchors_mcc), Pad(34), Struct("unit_hud_background", INCLUDE=hud_background), @@ -185,7 +188,7 @@ motion_sensor_center, Pad(20), - SEnum16("auxilary_overlay_anchor", *hud_anchors), + SEnum16("auxilary_overlay_anchor", *hud_anchors_mcc), Pad(34), reflexive("auxilary_overlays", auxilary_overlay, 16), diff --git a/reclaimer/mcc_hek/defs/unit.py b/reclaimer/mcc_hek/defs/unit.py index 949e9a98..ae62edb3 100644 --- a/reclaimer/mcc_hek/defs/unit.py +++ b/reclaimer/mcc_hek/defs/unit.py @@ -46,7 +46,7 @@ def get(): seat = Struct('seat', Bool32("flags", "invisible", - "locked", + "unused", "driver", "gunner", "third_person_camera", @@ -175,8 +175,9 @@ def get(): Pad(2), Struct("mcc_additions", # replaced with opensauce unit extension in os_v4 - SEnum16("mcc_scoring_type", TOOLTIP="Used to determine score in MCC", *mcc_actor_types), - Pad(10), + SEnum16("metagame_type", TOOLTIP="Used to determine score in MCC", *mcc_actor_types), + SEnum16("metagame_class", TOOLTIP="Used to determine score in MCC", *mcc_actor_classes), + Pad(2), ), reflexive("new_hud_interfaces", new_hud_interface, 2, 'default/solo', 'multiplayer'), @@ -185,7 +186,7 @@ def get(): #Grenades float_wu_sec('grenade_velocity'), - SEnum16('grenade_type', *grenade_types), + SEnum16('grenade_type', *grenade_types_mcc), SInt16('grenade_count', MIN=0), Pad(4), diff --git a/reclaimer/mcc_hek/defs/vehi.py b/reclaimer/mcc_hek/defs/vehi.py index 4e7174e0..3f7ed630 100644 --- a/reclaimer/mcc_hek/defs/vehi.py +++ b/reclaimer/mcc_hek/defs/vehi.py @@ -35,6 +35,13 @@ "ai_driver_flying", "ai_driver_can_sidestep", "ai_driver_hovering", + "vehicle_steers_directly", + "unused", + "has_e_brake", + "noncombat_vehicle", + "no_friction_with_driver", + "can_trigger_automatic_opening_doors", + "autoaim_when_teamless" ), SEnum16('type', *vehicle_types), diff --git a/reclaimer/mcc_hek/defs/weap.py b/reclaimer/mcc_hek/defs/weap.py index 27afab29..cd56be5d 100644 --- a/reclaimer/mcc_hek/defs/weap.py +++ b/reclaimer/mcc_hek/defs/weap.py @@ -83,6 +83,7 @@ "projectile_vector_cannot_be_adjusted", "projectiles_have_identical_error", "projectile_is_client_side_only", + "use_unit_adjust_projectile_ray_from_halo1", ), Struct("firing", QStruct("rounds_per_second", @@ -105,7 +106,12 @@ SInt16("minimum_rounds_loaded"), SInt16("rounds_between_tracers"), - Pad(6), + Pad(4), + SEnum16("prediction_type", + 'none', + 'continuous', + 'instant', + ), SEnum16("firing_noise", *sound_volumes), from_to_zero_to_one("error"), float_sec("error_acceleration_time"), @@ -177,22 +183,24 @@ weap_attrs = Struct("weap_attrs", Bool32("flags", - "vertical_heat_display", - "mutually_exclusive_triggers", - "attacks_automatically_on_bump", + "unused0", + "unused1", + "unused2", "must_be_readied", "doesnt_count_toward_maximum", "aim_assists_only_when_zoomed", "prevents_grenade_throwing", - "must_be_picked_up", - "holds_triggers_when_dropped", + "unused7", + "unused8", "prevents_melee_attack", "detonates_when_dropped", "cannot_fire_at_maximum_age", "secondary_trigger_overrides_grenades", - "does_not_depower_active_camo_in_multiplayer", # obsolete + "unused13", # obsolete "enables_integrated_night_vision", "ai_uses_weapon_melee_damage" + "prevents crouching", + "uses_3rd_person_camera" ), ascii_str32('label'), SEnum16('secondary_trigger_mode', @@ -295,7 +303,7 @@ ), Pad(14), - SEnum16('weapon_type', *weapon_types), + SEnum16('weapon_type', *weapon_types_mcc), reflexive("predicted_resources", predicted_resource, 1024, VISIBLE=False), reflexive("magazines", magazine, 2, diff --git a/reclaimer/mcc_hek/defs/wphi.py b/reclaimer/mcc_hek/defs/wphi.py index 9a76abfb..91a993bd 100644 --- a/reclaimer/mcc_hek/defs/wphi.py +++ b/reclaimer/mcc_hek/defs/wphi.py @@ -34,6 +34,28 @@ "flash_when_firing_with_depleted_battery", ) +crosshair_types_mcc = ( + "aim", + "zoom_overlay", + "charge", + "should_reload", + "flash_heat", + "flash_total_ammo", + "flash_battery", + "reload_overheat", + "flash_when_firing_and_no_ammo", + "flash_when_throwing_grenade_and_no_grenade", + "low_ammo_and_none_left_to_reload", + "should_reload_secondary_trigger", + "flash_secondary_total_ammo", + "flash_secondary_reload", + "flash_when_firing_secondary_and_no_ammo", + "low_secondary_ammo_and_none_left_to_reload", + "primary_trigger_ready", + "secondary_trigger_ready", + "flash_when_firing_with_depleted_battery", + ) + attached_state = SEnum16("state_attached_to", "total_ammo", "loaded_ammo", @@ -47,8 +69,8 @@ use_on_map_type = SEnum16("can_use_on_map_type", "any", - "solo", - "multiplayer", + "fullscreen", + "splitscreen", ) static_element = Struct("static_element", @@ -56,7 +78,20 @@ Pad(2), use_on_map_type, - Pad(30), + SEnum16("anchor", + "from_parent" + "top_left" + "top_right" + "bottom_left" + "bottom_right" + "center" + "top_center" + "bottom_center" + "left_center" + "right_center" + ), + + Pad(28), QStruct("anchor_offset", SInt16("x"), SInt16("y"), ORIENT='h', ), @@ -91,7 +126,20 @@ Pad(2), use_on_map_type, - Pad(30), + SEnum16("anchor", + "from_parent" + "top_left" + "top_right" + "bottom_left" + "bottom_right" + "center" + "top_center" + "bottom_center" + "left_center" + "right_center" + ), + + Pad(28), QStruct("anchor_offset", SInt16("x"), SInt16("y"), ORIENT='h', ), @@ -109,7 +157,7 @@ UInt32("color_at_meter_maximum", INCLUDE=xrgb_uint32), UInt32("flash_color", INCLUDE=xrgb_uint32), UInt32("empty_color", INCLUDE=argb_uint32), - Bool8("flags", *hud_panel_meter_flags), + Bool8("flags", *hud_panel_meter_mcc_flags), SInt8("minimum_meter_value"), SInt16("sequence_index"), SInt8("alpha_multiplier"), @@ -119,6 +167,7 @@ Float("translucency"), #QStruct("disabled_color", INCLUDE=argb_byte), UInt32("disabled_color", INCLUDE=argb_uint32), + Float("min_alpha"), SIZE=180 ) @@ -127,7 +176,20 @@ Pad(2), use_on_map_type, - Pad(30), + SEnum16("anchor", + "from_parent" + "top_left" + "top_right" + "bottom_left" + "bottom_right" + "center" + "top_center" + "bottom_center" + "left_center" + "right_center" + ), + + Pad(28), QStruct("anchor_offset", SInt16("x"), SInt16("y"), ORIENT='h', ), @@ -240,7 +302,7 @@ ) crosshair = Struct("crosshair", - SEnum16("crosshair_type", *crosshair_types), + SEnum16("crosshair_type", *crosshair_types_mcc), Pad(2), use_on_map_type, @@ -255,7 +317,20 @@ Pad(2), use_on_map_type, - Pad(30), + SEnum16("anchor", + "from_parent" + "top_left" + "top_right" + "bottom_left" + "bottom_right" + "center" + "top_center" + "bottom_center" + "left_center" + "right_center" + ), + + Pad(28), dependency("overlay_bitmap", "bitm"), reflexive("overlays", overlay, 16), SIZE=104 @@ -323,7 +398,7 @@ ), Pad(32), - SEnum16("anchor", *hud_anchors), + SEnum16("anchor", *hud_anchors_mcc), Pad(34), reflexive("static_elements", static_element, 16), diff --git a/reclaimer/mcc_hek/handler.py b/reclaimer/mcc_hek/handler.py index 3414faf5..3d838a86 100644 --- a/reclaimer/mcc_hek/handler.py +++ b/reclaimer/mcc_hek/handler.py @@ -7,269 +7,11 @@ # See LICENSE for more information. # -import os +from pathlib import Path -from datetime import datetime -from time import time -from traceback import format_exc -from pathlib import Path, PureWindowsPath +from reclaimer.hek.handler import HaloHandler +from reclaimer.mcc_hek.defs import __all__ as all_def_names -from binilla.handler import Handler - -from reclaimer.data_extraction import h1_data_extractors -from reclaimer.field_types import TagRef, Reflexive, RawdataRef -from reclaimer.hek.defs.objs.tag import HekTag -from reclaimer.hek.defs import __all__ as all_def_names - -from supyr_struct.buffer import BytearrayBuffer -from supyr_struct.util import tagpath_to_fullpath, is_path_empty -from supyr_struct.field_types import FieldType - - -class NodepathRef(dict): - __slots__ = ("is_ref",) - def __init__(self, is_ref, *a, **kw): - self.is_ref = is_ref - dict.__init__(self, *a, **kw) - - -NO_LOC_REFS = NodepathRef(False) - - -class HaloHandler(Handler): +class MCCHaloHandler(HaloHandler): frozen_imp_paths = all_def_names - tag_header_engine_id = "blam" - default_defs_path = "reclaimer.hek.defs" - tag_fcc_match_set = frozenset() - tag_filepath_match_set = frozenset() - - _tagsdir = Path.cwd().joinpath("tags") - _datadir = Path.cwd().joinpath("data") - - case_sensitive = False - tagsdir_relative = True - - treat_mode_as_mod2 = True - tag_ref_cache = None - reflexive_cache = None - raw_data_cache = None - - tag_data_extractors = h1_data_extractors - - def __init__(self, *args, **kwargs): - if not kwargs.pop("build_tag_ref_cache", True): - self.tag_ref_cache = NO_LOC_REFS - if not kwargs.pop("build_reflexive_cache", True): - self.reflexive_cache = NO_LOC_REFS - if not kwargs.pop("build_raw_data_cache", True): - self.raw_data_cache = NO_LOC_REFS - - Handler.__init__(self, *args, **kwargs) - - self.tag_fcc_match_set = set() - self.tag_filepath_match_set = set() - - self.ext_id_map = {} - for key in self.id_ext_map.keys(): - self.ext_id_map[self.id_ext_map[key]] = key - - if "default_conversion_flags" in kwargs: - self.default_conversion_flags = kwargs["default_conversion_flags"] - else: - self.default_conversion_flags = {} - for def_id in self.tags: - self.default_conversion_flags[def_id] = {} - - self.datadir = Path( - kwargs.get("datadir", self.tagsdir.parent.joinpath("data"))) - - # These break on Python 3.9 - - if self.tag_ref_cache is None: - self.tag_ref_cache = self.build_loc_caches(TagRef) - - if self.reflexive_cache is None: - self.reflexive_cache = self.build_loc_caches(Reflexive) - - if self.raw_data_cache is None: - self.raw_data_cache = self.build_loc_caches(RawdataRef) - - @property - def datadir(self): - return self._datadir - @datadir.setter - def datadir(self, new_val): - if not isinstance(new_val, Path): - new_val = Path(new_val) - self._datadir = new_val - - def _build_loc_cache(self, cond, desc={}): - try: - f_type = desc['TYPE'] - except Exception: - f_type = None - - if f_type is None: - return NO_LOC_REFS - - # python 3.9 band-aid - - try: - nodepath_ref = NodepathRef(cond(desc)) - except Exception: - print("Ignore me if you're not a developer") - print(format_exc()) - return NO_LOC_REFS - - for key in desc: - sub_nodepath_ref = self._build_loc_cache(cond, desc[key]) - if sub_nodepath_ref.is_ref or sub_nodepath_ref: - nodepath_ref[key] = sub_nodepath_ref - - return nodepath_ref - - def build_loc_caches(self, cond): - # if we are looking for only one specific FieldType, make it a tuple - if isinstance(cond, FieldType): - cond = (cond,) - - # if we are looking for FieldTypes, make it into a function - if isinstance(cond, (tuple, list)): - cond = lambda desc, f_types=cond: desc.get('TYPE') in f_types - - cache = {} - - for def_id in sorted(self.defs): - definition = self.defs[def_id].descriptor - - nodepath_ref = self._build_loc_cache(cond, definition) - if nodepath_ref.is_ref or nodepath_ref: - cache[def_id] = nodepath_ref - - return cache - - def _get_nodes_by_paths(self, paths, coll, cond, parent, key): - node = parent[key] - if paths.is_ref and cond(parent, key): - # this node is a node we are looking for; add it to the collection - if hasattr(node, "desc"): - coll.append(node) - else: - coll.append((parent, key)) - - if 'SUB_STRUCT' in paths: - paths = paths['SUB_STRUCT'] - for i in range(len(node)): - self._get_nodes_by_paths(paths, coll, cond, node, i) - return coll - - for i in paths: - self._get_nodes_by_paths(paths[i], coll, cond, node, i) - - return coll - - def get_nodes_by_paths(self, paths, node, cond=lambda x, y: True): - if paths: - return self._get_nodes_by_paths(paths, [], cond, (node,), 0) - - return () - - def get_def_id(self, filepath): - filepath = Path(filepath) - if self.tagsdir_relative and not filepath.is_absolute(): - filepath = self.tagsdir.joinpath(filepath) - - # It is more reliable to determine a Halo tag - # based on its 4CC def_id than by file extension - try: - with filepath.open('rb') as f: - f.seek(36) - def_id = str(f.read(4), 'latin-1') - f.seek(60) - engine_id = f.read(4).decode(encoding='latin-1') - if def_id in self.defs and engine_id == self.tag_header_engine_id: - return def_id - except Exception: - print(format_exc()); - - return self.ext_id_map.get(filepath.suffix.lower()) - - def get_tagref_invalid(self, parent, attr_index): - ''' - Checks if filepath of a tag reference is invalid. - Returns False if file exists. - Returns True if does not or if the reference is empty. - ''' - node = parent[attr_index] - #if the string is empty, there is no reference, can't be invalid. - if not node.filepath: - return False - - return not self.get_tagref_exists(parent, attr_index) - - def get_tagref_exists(self, parent, attr_index): - '''Returns whether or not a tag reference is valid.''' - node = parent[attr_index] - if not node.filepath: - return False - - ext = '.' + node.tag_class.enum_name - - # Get full path with proper capitalization if it points to a file. - filepath = tagpath_to_fullpath( - self.tagsdir, PureWindowsPath(node.filepath), extension=ext) - - if filepath is None and (self.treat_mode_as_mod2 and - ext == '.model'): - filepath = tagpath_to_fullpath( - self.tagsdir, PureWindowsPath(node.filepath), extension='.gbxmodel') - - return filepath is not None - - def get_tagref_matches(self, parent, attr_index): - ''' - Returns whether or not the int fcc of the nodes tag_class - matches any of the int fccs in self.tag_fcc_match_set and - if the nodes filepath is in self.tag_filepath_match_set. - - Returns True if both matchs are found AND the filepath is valid. - Returns False otherwise. - ''' - node = parent[attr_index] - return (bool(node.filepath) and ( - node.filepath in self.tag_filepath_match_set and - node.tag_class.data in self.tag_fcc_match_set)) - - def make_log_file(self, logstr, logpath=None): - ''' - Writes the supplied string to a log file. - - Required arguments: - logstr(str) - - If self.log_filename is a non-blank string it will be used as the - log filename. Otherwise the current timestamp will be used as the - filename in the format "YY-MM-DD HH:MM SS". - If the file already exists it will be appended to with the current - timestamp separating each write. Otherwise the file will be created. - ''' - # get the timestamp for the debug log's name - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - if not is_path_empty(logpath): - pass - elif isinstance(self.log_filename, str) and self.log_filename: - logpath = self.tagsdir.joinpath(self.log_filename) - logstr = '\n' + '-'*80 + '\n' + timestamp + '\n' + logstr - else: - logpath = self.tagsdir.joinpath(timestamp.replace(':', '.') + ".log") - - logpath = Path(logpath) - - mode = 'w' - if logpath.is_file(): - mode = 'a' - - # open a debug file and write the debug string to it - with logpath.open(mode) as logfile: - logfile.write(logstr) + default_defs_path = "reclaimer.mcc_hek.defs" From 8ee2ed4569f4589348a81f9fc3c6946c71043a2a Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Tue, 26 Dec 2023 03:56:12 -0600 Subject: [PATCH 03/51] Fix extraction bug with projectile velocity --- reclaimer/meta/wrappers/halo1_map.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/reclaimer/meta/wrappers/halo1_map.py b/reclaimer/meta/wrappers/halo1_map.py index bd15ac93..3dd21a61 100644 --- a/reclaimer/meta/wrappers/halo1_map.py +++ b/reclaimer/meta/wrappers/halo1_map.py @@ -1118,6 +1118,10 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): # need to scale velocities by 30 meta.proj_attrs.physics.initial_velocity *= 30 meta.proj_attrs.physics.final_velocity *= 30 + meta.proj_attrs.detonation.minimum_velocity *= 30 + for material_response in meta.proj_attrs.material_responses.STEPTREE: + material_response.potential_response.impact_velocity[0] *= 30 + material_response.potential_response.impact_velocity[1] *= 30 elif tag_cls == "sbsp": if byteswap: From cff6e7fb6a2292b0d7bc691a1072d256f2144b9b Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Fri, 5 Jan 2024 11:02:33 -0600 Subject: [PATCH 04/51] Import HEK definitions in unchanged files --- reclaimer/mcc_hek/defs/DeLa.py | 473 +--------------------------- reclaimer/mcc_hek/defs/Soul.py | 26 +- reclaimer/mcc_hek/defs/ant_.py | 48 +-- reclaimer/mcc_hek/defs/bipd.py | 139 +-------- reclaimer/mcc_hek/defs/boom.py | 18 +- reclaimer/mcc_hek/defs/colo.py | 26 +- reclaimer/mcc_hek/defs/ctrl.py | 41 +-- reclaimer/mcc_hek/defs/devc.py | 28 +- reclaimer/mcc_hek/defs/devi.py | 61 +--- reclaimer/mcc_hek/defs/dobc.py | 51 +-- reclaimer/mcc_hek/defs/elec.py | 69 +--- reclaimer/mcc_hek/defs/flag.py | 52 +--- reclaimer/mcc_hek/defs/fog_.py | 84 +---- reclaimer/mcc_hek/defs/foot.py | 32 +- reclaimer/mcc_hek/defs/garb.py | 19 +- reclaimer/mcc_hek/defs/glw_.py | 91 +----- reclaimer/mcc_hek/defs/hmt_.py | 52 +--- reclaimer/mcc_hek/defs/hud_.py | 26 +- reclaimer/mcc_hek/defs/item.py | 53 +--- reclaimer/mcc_hek/defs/itmc.py | 30 +- reclaimer/mcc_hek/defs/lifi.py | 21 +- reclaimer/mcc_hek/defs/ligh.py | 100 +----- reclaimer/mcc_hek/defs/mach.py | 45 +-- reclaimer/mcc_hek/defs/metr.py | 49 +-- reclaimer/mcc_hek/defs/mgs2.py | 80 +---- reclaimer/mcc_hek/defs/mod2.py | 322 +------------------ reclaimer/mcc_hek/defs/mode.py | 165 +--------- reclaimer/mcc_hek/defs/mply.py | 29 +- reclaimer/mcc_hek/defs/ngpr.py | 28 +- reclaimer/mcc_hek/defs/part.py | 97 +----- reclaimer/mcc_hek/defs/pctl.py | 155 +-------- reclaimer/mcc_hek/defs/phys.py | 107 +------ reclaimer/mcc_hek/defs/plac.py | 18 +- reclaimer/mcc_hek/defs/pphy.py | 59 +--- reclaimer/mcc_hek/defs/rain.py | 104 +------ reclaimer/mcc_hek/defs/sbsp.py | 553 +-------------------------------- reclaimer/mcc_hek/defs/sgla.py | 76 +---- reclaimer/mcc_hek/defs/shdr.py | 65 +--- reclaimer/mcc_hek/defs/sky_.py | 81 +---- reclaimer/mcc_hek/defs/smet.py | 60 +--- reclaimer/mcc_hek/defs/snde.py | 33 +- reclaimer/mcc_hek/defs/sotr.py | 261 +--------------- reclaimer/mcc_hek/defs/spla.py | 68 +--- reclaimer/mcc_hek/defs/ssce.py | 18 +- reclaimer/mcc_hek/defs/str_.py | 23 +- reclaimer/mcc_hek/defs/swat.py | 72 +---- reclaimer/mcc_hek/defs/tagc.py | 26 +- reclaimer/mcc_hek/defs/trak.py | 28 +- reclaimer/mcc_hek/defs/udlg.py | 239 +------------- reclaimer/mcc_hek/defs/ustr.py | 23 +- reclaimer/mcc_hek/defs/vcky.py | 59 +--- reclaimer/mcc_hek/defs/wind.py | 24 +- 52 files changed, 91 insertions(+), 4416 deletions(-) diff --git a/reclaimer/mcc_hek/defs/DeLa.py b/reclaimer/mcc_hek/defs/DeLa.py index 8a68085a..a0762da4 100644 --- a/reclaimer/mcc_hek/defs/DeLa.py +++ b/reclaimer/mcc_hek/defs/DeLa.py @@ -7,475 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -# Typing these up took FOREVER -game_data_input_functions = ( - 'NULL', - 'player_settings_menu_update', - 'unused', - 'playlist_settings_menu_update', - 'gametype_select_menu_update', - 'multiplayer_type_menu_update', - 'solo_level_select_update', - 'difficulty_menu_update', - 'build_number', # textbox only for build_number - 'server_list_update', - 'network_pregame_status_update', - 'splitscreen_pregame_update', - 'net_splitscreen_prejoin_players', - 'mp_profile_list_update', - 'wide_3_player_profile_list_update', - 'player_profile_edit_select_menu_update', - 'player_profile_small_menu_update', - 'game_settings_lists_text_update', - 'solo_game_objective_text', - 'color_picker_update', - 'game_setings_lists_pic_update', - 'main_menu_fake_animate', - 'mp_level_select_update', - 'get_active_player_profile_name', - 'get_edit_player_profile_name', - 'get_edit_game_settings_name', - 'get_active_player_profile_color', - 'mp_set_textbox_map_name', - 'mp_set_textbox_game_rules', - 'mp_set_textbox_teams_no_teams', - 'mp_set_textbox_score_limit', - 'mp_set_textbox_score_limit_type', - 'mp_set_bitmap_for_map', - 'mp_set_bitmap_for_ruleset', - 'mp_set_textbox_player_count', - 'mp_edit_profile_set_rule_text', - 'system_link_status_check', - 'mp_game_directions', - 'teams_no_teams_bitmap_update', - 'warn_if_diff_will_nuke_saved_game', - 'dim_if_no_net_cable', - 'pause_game_set_textbox_inverted', - 'dim_unless_two_controllers', - 'controls_update_menu', - 'video_menu_update', - 'gamespy_screen_update', - 'common_button_bar_update', - 'gamepad_update_menu', - 'server_settings_update', - 'audio_menu_update', - 'mp_profile_vehicles_update', - 'solo_map_list_update', - 'mp_map_list_update', - 'gametype_select_list_update', - 'gametype_edit_list_update', - 'load_game_list_update', - 'checking_for_updates', - 'direct_ip_connect_update', - 'network_settings_update', - ) -event_types = ( - 'A_button', - 'B_button', - 'X_button', - 'Y_button', - 'black_button', - 'white_button', - 'left_trigger', - 'right_trigger', - 'dpad_up', - 'dpad_down', - 'dpad_left', - 'dpad_right', - 'start_button', - 'back_button', - 'left_thumb', - 'right_thumb', - 'left_analog_stick_up', - 'left_analog_stick_down', - 'left_analog_stick_left', - 'left_analog_stick_right', - 'right_analog_stick_up', - 'right_analog_stick_down', - 'right_analog_stick_left', - 'right_analog_stick_right', - 'created', - 'deleted', - 'get_focus', - 'lose_focus', - 'left_mouse', - 'middle_mouse', - 'right_mouse', - 'double_click', - 'custom_activation', - 'post_render' - ) -event_functions = ( - 'NULL', - 'list_goto_next_item', - 'list_goto_previous_item', - 'unused1', - 'unused2', - 'initialize_sp_level_list_solo', - 'initialize_sp_level_list_coop', - 'dispose_sp_level_list', - 'solo_level_set_map', - 'set_difficulty', - 'start_new_game', - 'pause_game_restart_at_checkpoint', - 'pause_game_restart_level', - 'pause_game_rturn_to_main_menu', - 'clear_multiplayer_player_joins', - 'join_controller_to_mp_game', - 'initialize_net_game_server_list', - 'start_net_game_server', - 'dispose_net_game_server_list', - 'shutdown_net_game_server', - 'net_game_join_from_server_list', - 'split_screen_game_initialize', - 'coop_game_initialize', - 'main_menu_initialize', - 'mp_type_menu_initialize', - 'pick_play_stage_for_quick_start', - 'mp_level_list_initialize', - 'mp_level_list_dispose', - 'mp_level_select', - 'mp_profiles_list_initialize', - 'mp_profiles_list_dispose', - 'mp_profile_set_for_game', - 'swap_player_team', - 'net_game_join_player', - 'player_profile_list_initialize', - 'player_profile_list_dispose', - 'wide_3_player_profile_set_for_game', - 'wide_1_player_profile_set_for_game', - 'mp_profile_begin_editing', - 'mp_profile_end_editing', - 'mp_profile_set_game_engine', - 'mp_profile_change_name', - 'mp_profile_set_ctf_rules', - 'mp_profile_set_koth_rules', - 'mp_profile_set_slayer_rules', - 'mp_profile_set_oddball_rules', - 'mp_profile_set_racing_rules', - 'mp_profile_set_player_options', - 'mp_profile_set_item_options', - 'mp_profile_set_indicator_options', - 'mp_profile_init_game_engine', - 'mp_profile_init_name', - 'mp_profile_init_ctf_rules', - 'mp_profile_init_koth_rules', - 'mp_profile_init_slayer_rules', - 'mp_profile_init_oddball_rules', - 'mp_profile_init_racing_rules', - 'mp_profile_init_player_options', - 'mp_profile_init_item_options', - 'mp_profile_init_indicator_options', - 'mp_profile_save_changes', - 'color_picker_menu_initialize', - 'color_picker_menu_dispose', - 'color_picker_select_color', - 'player_prof_begin_editing', - 'player_prof_end_editing', - 'player_prof_change_name', - 'player_prof_save_changes', - 'player_prof_init_control_settings', - 'player_prof_init_adv_ctrl_settings', - 'player_prof_save_control_settings', - 'player_prof_save_adv_ctrl_settings', - 'mp_game_player_quit', - 'main_menu_switch_to_solo_game', - 'request_del_player_profile', - 'request_del_playlist_profile', - 'final_del_player_profile', - 'final_del_playlist_profile', - 'cancel_profile_delete', - 'create_and_edit_playlist_profile', - 'create_and_edit_player_profile', - 'net_game_speed_start', - 'net_game_delay_start', - 'net_server_accept_connection', - 'net_server_defer_start', - 'net_server_allow_start', - 'disable_if_no_xdemos', - 'run_xdemos', - 'sp_reset_controller_choices', - 'sp_set_p1_controller_choices', - 'sp_set_p2_controller_choices', - 'error_if_no_network_connection', - 'start_server_if_none_advertised', - 'net_game_unjoin_player', - 'close_if_not_editing_profile', - 'exit_to_xbox_dashboard', - 'new_campaign_chosen', - 'new_campaign_decision', - 'pop_history_stack_once', - 'difficulty_menu_init', - 'begin_music_fade_out', - 'new_game_if_no_player_profile', - 'exit_gracefully_to_xbox_dashboard', - 'pause_game_invert_pitch', - 'start_new_coop_game', - 'pause_game_invert_spinner_set', - 'pause_game_invert_spinner_get', - 'main_menu_quit_game', - 'mouse_emit_ACCEPT_event', - 'mouse_emit_BACK_event', - 'mouse_emit_DPAD_LEFT_event', - 'mouse_emit_DPAD_RIGHT_event', - 'mouse_spinner_3wide_click', - 'controls_screen_init', - 'video_screen_init', - 'controls_begin_binding', - 'gamespy_screen_init', - 'gamespy_screen_dispose', - 'gamespy_select_header', - 'gamespy_select_item', - 'gamespy_select_button', - 'player_prof_init_mouse_set', - 'player_prof_change_mouse_set', - 'player_prof_init_audio_set', - 'player_prof_change_audio_set', - 'player_prof_change_video_set', - 'controls_screen_dispose', - 'controls_screen_change_set', - 'mouse_emit_X_event', - 'gamepad_screen_init', - 'gamepad_screen_dispose', - 'gamepad_screen_change_gamepads', - 'gamepad_screen_select_item', - 'mouse_screen_defaults', - 'audio_screen_defaults', - 'video_screen_defaults', - 'controls_screen_defaults', - 'profile_set_edit_begin', - 'profile_manager_delete', - 'profile_manager_select', - 'gamespy_dismiiss_error', - 'server_settings_init', - 'server_set_edit_server_name', - 'server_set_edit_server_password', - 'server_set_start_game', - 'video_test_dialog_init', - 'video_test_dialog_dispose', - 'video_test_dialog_accept', - 'gamespy_dismiss_filters', - 'gamespy_update_filter_settings', - 'gamespy_dismiss_back_handler', - 'mouse_spinner_1wide_click', - 'controls_back_handler', - 'controls_advanced_launch', - 'controls_advanced_ok', - 'mp_pause_menu_open', - 'mp_game_options_open', - 'mp_choose_team', - 'mp_prof_init_vehicle_options', - 'mp_prof_save_vehicle_options', - 'single_prev_cl_item_active', - 'mp_prof_init_teamplay_options', - 'mp_prof_save_teamplay_options', - 'mp_game_options_choose', - 'emit_custom_activation_event', - 'player_prof_cancel_audio_set', - 'player_prof_init_network_options', - 'player_prof_save_network_options', - 'credits_post_render', - 'difficulty_item_select', - 'credits_initialize', - 'credits_dispose', - 'gamespy_get_patch', - 'video_screen_dispose', - 'campaign_menu_init', - 'campaign_menu_continue', - 'load_game_menu_init', - 'load_game_menu_dispose', - 'load_game_menu_activated', - 'solo_menu_save_checkpoint', - 'mp_type_set_mode', - 'checking_for_updates_ok', - 'checking_for_updates_dismiss', - 'direct_ip_connect_init', - 'direct_ip_connect_go', - 'direct_ip_edit_field', - 'network_settings_edit_a_port', - 'network_settings_defaults', - 'load_game_menu_delete_request', - 'load_game_menu_delete_finish' - ) - -widget_bounds = QStruct("", - SInt16("t"), SInt16("l"), SInt16("b"), SInt16("r"), - ORIENT='h', SIZE=8 - ) - -game_data_input = Struct("game_data_input", - SEnum16("function", *game_data_input_functions), - SIZE=36 - ) - -event_handler = Struct("event_handler", - Bool32('flags', - "close_current_widget", - "close_other_widget", - "close_all_widgets", - "open_widget", - "reload_self", - "reload_other_widget", - "give_focus_to_widget", - "run_function", - "replace_self_with_widget", - "go_back_to_previous_widget", - "run_scenario_script", - "try_to_branch_on_failure", - ), - SEnum16("event_type", *event_types), - SEnum16("function", *event_functions), - dependency("widget_tag", "DeLa"), - dependency("sound_effect", "snd!"), - ascii_str32("script"), - SIZE=72 - ) - -s_and_r_reference = Struct("search_and_replace_reference", - ascii_str32("search_string"), - SEnum16("replace_function", - "NULL", - "widgets_controller", - "build_number", - "pid", - ), - SIZE=34 - ) - -conditional_widget = Struct("conditional_widget", - dependency("widget_tag", "DeLa"), - ascii_str32("name"), # UNUSED - Bool32("flags", - "load_if_event_handler_function_fails", - ), - SInt16("custom_controller_index"), # UNUSED - SIZE=80 - ) - -child_widget = Struct("child_widget", - dependency("widget_tag", "DeLa"), - ascii_str32("name"), # UNUSED - Bool32("flags", - "use_custom_controller_index", - ), - SInt16("custom_controller_index"), - SInt16("vertical_offset"), - SInt16("horizontal_offset"), - SIZE=80 - ) - -DeLa_body = Struct("tagdata", - SEnum16("widget_type", - "container", - "text_box", - "spinner_list", - "column_list", - "game_model", # not implemented - "movie", # not implemented - "custom" # not implemented - ), - SEnum16("controller_index", - "player_1", - "player_2", - "player_3", - "player_4", - "any_player" - ), - ascii_str32("name"), - QStruct("bounds", INCLUDE=widget_bounds), - Bool32('flags', - "pass_unhandled_events_to_focused_child", - "pause_game_time", - "flash_background_bitmap", - "dpad_up_down_tabs_thru_children", - "dpad_left_right_tabs_thru_children", - "dpad_up_down_tabs_thru_list_items", - "dpad_left_right_tabs_thru_list_items", - "dont_focus_a_specific_child_widget", - "pass_unhandled_events_to_all_children", - "return_to_main_menu_if_no_history", - "always_use_tag_controller_index", - "always_use_nifty_render_fx", - "dont_push_history", - "force_handle_mouse" - ), - SInt32("auto_close_time", SIDETIP="milliseconds"), - SInt32("auto_close_fade_time", SIDETIP="milliseconds"), - dependency("background_bitmap", "bitm"), - - reflexive("game_data_inputs", game_data_input, 64), - reflexive("event_handlers", event_handler, 32), - reflexive("search_and_replace_references", - s_and_r_reference, 32, DYN_NAME_PATH='.search_string'), - - Pad(128), - Struct("text_box", - dependency("text_label_unicode_strings_list", "ustr"), - dependency("text_font", "font"), - QStruct("text_color", INCLUDE=argb_float), - SEnum16("justification", - "left", - "right", - "center", - ), - # as weird as it sounds, these flags are off alignment by 2 - Bool32("flags", - "editable", - "password", - "flashing", - "dont_do_that_weird_focus_test", - ), - BytesRaw("unknown2", SIZE=10, VISIBLE=False), - - FlSInt16("unknown3", VISIBLE=False), - SInt16("string_list_index"), - SInt16("horizontal_offset"), - SInt16("vertical_offset") - ), - - Pad(28), - Struct("list_items", - Bool32("flags", - "list_items_generated_in_code", - "list_items_from_string_list_tag", - "list_items_only_one_tooltip", - "list_single_preview_no_scroll" - ) - ), - - Struct("spinner_list", - dependency("list_header_bitmap", "bitm"), - dependency("list_footer_bitmap", "bitm"), - QStruct("header_bounds", INCLUDE=widget_bounds), - QStruct("footer_bounds", INCLUDE=widget_bounds) - ), - - Pad(32), - Struct("column_list", - dependency("extended_description_widget", "DeLa") - ), - - Pad(288), - reflexive("conditional_widgets", conditional_widget, 32, - DYN_NAME_PATH='.widget_tag.filepath'), - - Pad(256), - reflexive("child_widgets", child_widget, 32, - DYN_NAME_PATH='.widget_tag.filepath'), - - SIZE=1004 - ) - -def get(): - return DeLa_def - -DeLa_def = TagDef("DeLa", - blam_header('DeLa'), - DeLa_body, - - ext=".ui_widget_definition", endian=">", tag_cls=HekTag - ) +from ...hek.defs.DeLa import * diff --git a/reclaimer/mcc_hek/defs/Soul.py b/reclaimer/mcc_hek/defs/Soul.py index c0c7c202..f5e0804d 100644 --- a/reclaimer/mcc_hek/defs/Soul.py +++ b/reclaimer/mcc_hek/defs/Soul.py @@ -7,28 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -ui_widget_def = Struct("ui_widget_definition", - dependency("ui_widget_definition", 'DeLa'), - SIZE=16 - ) - -soul_body = Struct("tagdata", - reflexive("ui_widget_definitions", ui_widget_def, 32, - DYN_NAME_PATH='.ui_widget_definition.filepath'), - SIZE=12, - ) - - -def get(): - return Soul_def - -Soul_def = TagDef("Soul", - blam_header('Soul'), - soul_body,#lol Megaman X4 - - ext=".ui_widget_collection", endian=">", tag_cls=HekTag - ) +from ...hek.defs.Soul import * diff --git a/reclaimer/mcc_hek/defs/ant_.py b/reclaimer/mcc_hek/defs/ant_.py index dfe950a8..a03db3c2 100644 --- a/reclaimer/mcc_hek/defs/ant_.py +++ b/reclaimer/mcc_hek/defs/ant_.py @@ -7,50 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.ant_ import Ant_Tag -from supyr_struct.defs.tag_def import TagDef - -vertex = Struct("vertex", - Float("spring_strength_coefficient"), - - Pad(24), - yp_float_rad("angles"), # radians - float_wu("length"), - SInt16("sequence_index"), - - Pad(2), - QStruct("color", INCLUDE=argb_float), - QStruct("lod_color", INCLUDE=argb_float), - Pad(40), - QStruct('offset', INCLUDE=xyz_float, VISIBLE=False), - - SIZE=128 - ) - -ant__body = Struct("tagdata", - ascii_str32("attachment_marker_name"), - dependency("bitmaps", "bitm"), - dependency("physics", "pphy"), - - Pad(80), - Float("spring_strength_coefficient"), - Float("falloff_pixels"), - Float("cutoff_pixels"), - Float("length"), - - Pad(36), - reflexive("vertices", vertex, 20), - SIZE=208 - ) - - -def get(): - return ant__def - -ant__def = TagDef("ant!", - blam_header('ant!'), - ant__body, - - ext=".antenna", endian=">", tag_cls=Ant_Tag - ) +from ...hek.defs.ant_ import * diff --git a/reclaimer/mcc_hek/defs/bipd.py b/reclaimer/mcc_hek/defs/bipd.py index 18d22720..ab9c544f 100644 --- a/reclaimer/mcc_hek/defs/bipd.py +++ b/reclaimer/mcc_hek/defs/bipd.py @@ -7,146 +7,16 @@ # See LICENSE for more information. # -''' -Names for the "physics" struct in the biped tag are courtesy of Sparky. -The source files where the information was taken from are here: +from ...hek.defs.bipd import * -https://github.com/LiquidLightning/infernal/blob/master/infernal/inf_bipd.h - -EDIT: updates physics struct with more accurate names determined by Kavawuvi -''' -from math import sqrt -from .objs.bipd import BipdTag +#import and use the mcc obje and unit attrs from .obje import * from .unit import * -from supyr_struct.util import desc_variant # replace the object_type enum one that uses # the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(0)) - ) - -contact_point = Struct("contact_point", - Pad(32), - ascii_str32('marker_name'), - SIZE=64 - ) - -bipd_attrs = Struct("bipd_attrs", - Float("moving_turning_speed", - SIDETIP="degrees/sec", UNIT_SCALE=180/pi), # radians - Bool32("flags", - "turns_without_aiming", - "uses_player_physics", - "flying", - "physics_pill_centered_at_origin", - "spherical", - "passes_through_other_bipeds", - "can_climb_any_surface", - "immune_to_falling_damage", - "rotate_while_airborne", - "uses_limp_body_physics", - "has_no_dying_airborne", - "random_speed_increase", - "uses_old_player_physics", - ), - float_rad("stationary_turning_threshold"), # radians - - Pad(16), - SEnum16('A_in', *biped_inputs), - SEnum16('B_in', *biped_inputs), - SEnum16('C_in', *biped_inputs), - SEnum16('D_in', *biped_inputs), - dependency('DONT_USE', "jpt!"), - - QStruct("flying", - float_rad("bank_angle"), # radians - float_sec("bank_apply_time", UNIT_SCALE=sec_unit_scale), # seconds - float_sec("bank_decay_time", UNIT_SCALE=sec_unit_scale), # seconds - Float("pitch_ratio"), - float_wu_sec("max_velocity", - UNIT_SCALE=per_sec_unit_scale), # world units/second - float_wu_sec("max_sidestep_velocity", - UNIT_SCALE=per_sec_unit_scale), # world units/second - float_wu_sec_sq("acceleration", - UNIT_SCALE=per_sec_unit_scale), # world units/second^2 - float_wu_sec_sq("deceleration", - UNIT_SCALE=per_sec_unit_scale), # world units/second^2 - float_rad_sec("angular_velocity_maximum"), # radians/second - float_rad_sec_sq("angular_acceleration_maximum"), # radians/second^2 - float_zero_to_one("crouch_velocity_modifier"), - ), - - Pad(8), - Struct("movement", - float_rad("maximum_slope_angle"), # radians - float_rad("downhill_falloff_angle"), # radians - float_rad("downhill_cutoff_angle"), # radians - Float("downhill_velocity_scale"), - float_rad("uphill_falloff_angle"), # radians - float_rad("uphill_cutoff_angle"), # radians - Float("uphill_velocity_scale"), - - Pad(24), - dependency('footsteps', "foot"), - ), - - Pad(24), - QStruct("jumping_and_landing", - float_wu_sec("jump_velocity", UNIT_SCALE=per_sec_unit_scale), - Pad(28), - float_sec("maximum_soft_landing_time", UNIT_SCALE=sec_unit_scale), - float_sec("maximum_hard_landing_time", UNIT_SCALE=sec_unit_scale), - float_wu_sec("minimum_soft_landing_velocity", - UNIT_SCALE=per_sec_unit_scale), # world units/second - float_wu_sec("minimum_hard_landing_velocity", - UNIT_SCALE=per_sec_unit_scale), # world units/second - float_wu_sec("maximum_hard_landing_velocity", - UNIT_SCALE=per_sec_unit_scale), # world units/second - float_wu_sec("death_hard_landing_velocity", - UNIT_SCALE=per_sec_unit_scale), # world units/second - ), - - Pad(20), - QStruct("camera_collision_and_autoaim", - float_wu("standing_camera_height"), - float_wu("crouching_camera_height"), - float_sec("crouch_transition_time", UNIT_SCALE=sec_unit_scale), - - Pad(24), - float_wu("standing_collision_height"), - float_wu("crouching_collision_height"), - float_wu("collision_radius"), - - Pad(40), - float_wu("autoaim_width"), - ), - - Pad(108), - QStruct("physics", - # the default values below that aren't commented out are taken - # from the cyborg.biped tag after saving it with guerilla. - FlFloat("cosine_stationary_turning_threshold"), - FlFloat("crouch_camera_velocity"), - FlFloat("cosine_maximum_slope_angle", - TOOLTIP=("negative is walking on walls.\n > 0.707107 is " + - "floating with contact points off the ground")), - FlFloat("neg_sine_downhill_falloff_angle"), - FlFloat("neg_sine_downhill_cutoff_angle"), - FlFloat("sine_uphill_falloff_angle"), - FlFloat("sine_uphill_cutoff_angle", - TOOLTIP="does the same thing as the fp accel modifier?"), - FlSInt16("root_node_index", DEFAULT=-1), - FlSInt16("head_node_index", DEFAULT=-1), - VISIBLE=False, SIZE=32 - ), - - reflexive("contact_points", contact_point, 2, - DYN_NAME_PATH='.marker_name'), - - SIZE=516 - ) +obje_attrs = dict(obje_attrs) +obje_attrs[0] = dict(obje_attrs[0], DEFAULT=0) bipd_body = Struct("tagdata", obje_attrs, @@ -155,7 +25,6 @@ SIZE=1268, ) - def get(): return bipd_def diff --git a/reclaimer/mcc_hek/defs/boom.py b/reclaimer/mcc_hek/defs/boom.py index 6e1ddaf4..9f928092 100644 --- a/reclaimer/mcc_hek/defs/boom.py +++ b/reclaimer/mcc_hek/defs/boom.py @@ -7,20 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -def get(): - return boom_def - -boom_def = TagDef("boom", - blam_header('boom'), - QStruct('tagdata', - #this is just a guess. This could just as easily - #be 4 bytes of padding. effing useless tag type - Float('radius') - ), - - ext=".spheroid", endian=">", tag_cls=HekTag - ) +from ...hek.defs.boom import * diff --git a/reclaimer/mcc_hek/defs/colo.py b/reclaimer/mcc_hek/defs/colo.py index e5bd6892..d124ebc1 100644 --- a/reclaimer/mcc_hek/defs/colo.py +++ b/reclaimer/mcc_hek/defs/colo.py @@ -7,28 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - - -color = Struct("color", - ascii_str32('name'), - QStruct("color", INCLUDE=argb_float), - SIZE=48, - ) - -colo_body = Struct("tagdata", - reflexive("colors", color, 512, DYN_NAME_PATH='.name'), - SIZE=12 - ) - -def get(): - return colo_def - -colo_def = TagDef("colo", - blam_header('colo'), - colo_body, - - ext=".color_table", endian=">", tag_cls=HekTag - ) +from ...hek.defs.colo import * diff --git a/reclaimer/mcc_hek/defs/ctrl.py b/reclaimer/mcc_hek/defs/ctrl.py index 6ce72336..42107817 100644 --- a/reclaimer/mcc_hek/defs/ctrl.py +++ b/reclaimer/mcc_hek/defs/ctrl.py @@ -7,45 +7,18 @@ # See LICENSE for more information. # +from ...hek.defs.ctrl import * + +#import and use the mcc obje attrs from .obje import * -from .devi import * -from .objs.ctrl import CtrlTag -from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant # replace the object_type enum one that uses # the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(8)) - ) - -ctrl_attrs = Struct("ctrl_attrs", - SEnum16('type', - 'toggle_switch', - 'on_button', - 'off_button', - 'call_button' - ), - SEnum16('triggers_when', - 'touched_by_player', - 'destroyed' - ), - float_zero_to_one('call_value'), - - Pad(80), - dependency("on", valid_event_effects), - dependency("off", valid_event_effects), - dependency("deny", valid_event_effects), - ) - -ctrl_body = Struct("tagdata", - obje_attrs, - devi_attrs, - ctrl_attrs, - - SIZE=792, - ) +obje_attrs = dict(obje_attrs) +obje_attrs[0] = dict(obje_attrs[0], DEFAULT=8) +ctrl_body = dict(ctrl_body) +ctrl_body[0] = obje_attrs def get(): return ctrl_def diff --git a/reclaimer/mcc_hek/defs/devc.py b/reclaimer/mcc_hek/defs/devc.py index 8b78faf1..a32a44d5 100644 --- a/reclaimer/mcc_hek/defs/devc.py +++ b/reclaimer/mcc_hek/defs/devc.py @@ -7,30 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -devc_body = Struct("tagdata", - SEnum16("device_type", - "mouse_and_keyboard", - "joysticks_joypads_etc", - "full_profile_definition", - ), - Bool16("flags", - "unused", - ), - rawdata_ref("device_id", max_size=16), - rawdata_ref("profile", max_size=41984), - SIZE=44, - ) - -def get(): - return devc_def - -devc_def = TagDef("devc", - blam_header('devc'), - devc_body, - - ext=".input_device_defaults", endian=">", tag_cls=HekTag - ) +from ...hek.defs.devc import * diff --git a/reclaimer/mcc_hek/defs/devi.py b/reclaimer/mcc_hek/defs/devi.py index 81478bcd..48429a52 100644 --- a/reclaimer/mcc_hek/defs/devi.py +++ b/reclaimer/mcc_hek/defs/devi.py @@ -7,63 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.devi import DeviTag -from supyr_struct.defs.tag_def import TagDef - -devi_attrs = Struct("devi_attrs", - Bool32("flags", - "position_loops", - "position_not_interpolated", - ), - - float_sec("power_transition_time"), - float_sec("power_acceleration_time"), - float_sec("position_transition_time"), - float_sec("position_acceleration_time"), - float_sec("depowered_position_transition_time"), - float_sec("depowered_position_acceleration_time"), - - SEnum16("A_in", *device_functions), - SEnum16("B_in", *device_functions), - SEnum16("C_in", *device_functions), - SEnum16("D_in", *device_functions), - - dependency("open", valid_event_effects), - dependency("close", valid_event_effects), - dependency("opened", valid_event_effects), - dependency("closed", valid_event_effects), - dependency("depowered", valid_event_effects), - dependency("repowered", valid_event_effects), - - float_sec("delay_time"), - Pad(8), - dependency("delay_effect", valid_event_effects), - float_wu("automatic_activation_radius"), - - Pad(84), - FlFloat("inv_power_acceleration_time"), - FlFloat("inv_power_transition_time"), - FlFloat("inv_depowered_acceleration_time"), - FlFloat("inv_depowered_transition_time"), - FlFloat("inv_position_acceleration_time"), - FlFloat("inv_position_transition_time"), - Pad(4), - - SIZE=276, - ) - -devi_body = Struct('tagdata', - devi_attrs, - SIZE=276 - ) - -def get(): - return devi_def - -devi_def = TagDef("devi", - blam_header('devi'), - devi_body, - - ext=".device", endian=">", tag_cls=DeviTag - ) +from ...hek.defs.devi import * diff --git a/reclaimer/mcc_hek/defs/dobc.py b/reclaimer/mcc_hek/defs/dobc.py index 6086a765..d97d047c 100644 --- a/reclaimer/mcc_hek/defs/dobc.py +++ b/reclaimer/mcc_hek/defs/dobc.py @@ -7,53 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -def get(): return dobc_def - -detail_object_type = Struct("detail_object_type", - ascii_str32("name"), - SInt8("sequence_index", SIDETIP="[0,15]"), - Bool8("scale_flags", - ("interpolate_color_in_hsv", 4), - ("more_colors", 8), - ), - #UInt8("unknown0", VISIBLE=False), - Pad(1), - UInt8("sequence_sprite_count", VISIBLE=False), - float_zero_to_one("color_override_factor"), - Pad(8), - float_wu("near_fade_distance"), - float_wu("far_fade_distance"), - Float("size", SIDETIP="world units/pixel"), - Pad(4), - QStruct("minimum_color", INCLUDE=rgb_float), - QStruct("maximum_color", INCLUDE=rgb_float), - #QStruct("ambient_color", INCLUDE=argb_byte), - UInt32("ambient_color", INCLUDE=argb_uint32), - SIZE=96 - ) - -dobc_body = Struct("tagdata", - SEnum16("anchor", - "screen-facing", - "viewer-facing", - ), - Pad(2), - Float("global_z_offset", - SIDETIP="applied to all these detail object so they dont float"), - Pad(44), - dependency("sprite_plate", "bitm"), - reflexive("detail_object_types", detail_object_type, 16, - DYN_NAME_PATH='.name'), - SIZE=128, - ) - -dobc_def = TagDef("dobc", - blam_header('dobc'), - dobc_body, - - ext=".detail_object_collection", endian=">", tag_cls=HekTag - ) +from ...hek.defs.dobc import * diff --git a/reclaimer/mcc_hek/defs/elec.py b/reclaimer/mcc_hek/defs/elec.py index 9ab0695c..78521eea 100644 --- a/reclaimer/mcc_hek/defs/elec.py +++ b/reclaimer/mcc_hek/defs/elec.py @@ -7,71 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -shader = Struct("shader", - Pad(36), - FlUInt32("unknown0"), - Bool16("shader_flags", *shader_flags), - SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), - SEnum16("framebuffer_fade_mode", *render_fade_mode), - Bool16("map_flags", - "unfiltered" - ), - Pad(40), - FlUInt32("unknown1"), - Pad(88), - SIZE=180 - ) - -marker = Struct("marker", - ascii_str32("attachment_marker"), - Bool16("flags", - "not_connected_to_next_marker" - ), - - Pad(2), - SInt16("octaves_to_next_marker"), - - Pad(78), - QStruct("random_position_bounds", INCLUDE=ijk_float, SIDETIP="world units"), - float_wu("random_jitter"), - float_wu("thickness"), - QStruct("tint", INCLUDE=argb_float), - SIZE=228 - ) - -elec_body = Struct("tagdata", - Pad(2), - SInt16("effects_count"), - - Pad(16), - float_wu("near_fade_distance"), - float_wu("far_fade_distance"), - - Pad(16), - SEnum16("jitter_scale_source", *function_outputs), - SEnum16("thickness_scale_source", *function_outputs), - SEnum16("tint_modulation_source", *function_names), - SEnum16("brightness_scale_source", *function_outputs), - dependency("bitmap", "bitm"), - - Pad(84), - reflexive("markers", marker, 16, DYN_NAME_PATH='.attachment_marker'), - reflexive("shaders", shader, 1), - - SIZE=264, - ) - - -def get(): - return elec_def - -elec_def = TagDef("elec", - blam_header("elec"), - elec_body, - - ext=".lightning", endian=">", tag_cls=HekTag, - ) +from ...hek.defs.elec import * diff --git a/reclaimer/mcc_hek/defs/flag.py b/reclaimer/mcc_hek/defs/flag.py index ccb6e478..705bcc63 100644 --- a/reclaimer/mcc_hek/defs/flag.py +++ b/reclaimer/mcc_hek/defs/flag.py @@ -7,54 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -def get(): return flag_def - -attachment_point = Struct("attachment_point", - SInt16("height_to_next_attachment", SIDETIP="vertices"), - Pad(18), - ascii_str32("marker_name"), - ) - -flag_body = Struct("tagdata", - Pad(4), - SEnum16("trailing_edge_shape", - "flat", - "concave_triangular", - "convex_triangular", - "trapezoid_short_top", - "trapezoid_short_bottom", - ), - - SInt16("trailing_edge_shape_offset", SIDETIP="vertices"), - SEnum16("attached_edge_shape", - "flat", - "concave_triangular", - ), - Pad(2), - SInt16("width", SIDETIP="vertices"), - SInt16("height", SIDETIP="vertices"), - - float_wu("cell_width"), - float_wu("cell_height"), - - dependency("red_flag_shader", valid_shaders), - dependency("physics", "pphy"), - - float_wu_sec("wind_noise"), - Pad(8), - dependency("blue_flag_shader", valid_shaders), - reflexive("attachment_points", attachment_point, 4, - DYN_NAME_PATH='.marker_name'), - SIZE=96, - ) - -flag_def = TagDef("flag", - blam_header('flag'), - flag_body, - - ext=".flag", endian=">", tag_cls=HekTag - ) +from ...hek.defs.flag import * diff --git a/reclaimer/mcc_hek/defs/fog_.py b/reclaimer/mcc_hek/defs/fog_.py index b47a1ca6..da390e5f 100644 --- a/reclaimer/mcc_hek/defs/fog_.py +++ b/reclaimer/mcc_hek/defs/fog_.py @@ -7,86 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -fog_comment = """FLAGS -Setting prevents polygon popping when the atmospheric fog maximum -density (in the sky tag) is 1 and the atmospheric fog opaque distance is less than the -diameter of the map. However, this flag will cause artifacts when the camera goes below -the fog plane - so it should only be used when the fog plane is close to the ground.""" - -fog__body = Struct("tagdata", - #fog flags - Bool32("flags", - "is_water", - "atmospheric_dominant", - "fog_screen_only", - COMMENT=fog_comment - ), - - Pad(84), - #Density - float_zero_to_one("maximum_density"), - Pad(4), - float_wu("opaque_distance"), - Pad(4), - float_wu("opaque_depth"), - Pad(8), - float_wu("distance_to_water_plane"), - - #Color - QStruct("fog_color", INCLUDE=rgb_float), - - #Screen Layers - Struct("screen_layers", - Bool16("flags", - "no_environment_multipass", - "no_model_multipass", - "no_texture_based_falloff", - ), - UInt16("layer_count", SIDETIP="[0,4]", MIN=0, MAX=4), - - from_to_wu("distance_gradient"), - from_to_zero_to_one("density_gradient"), - - float_wu("start_distance_from_fog_plane"), - Pad(4), - - #QStruct("color", INCLUDE=xrgb_byte), - UInt32("color", INCLUDE=xrgb_uint32), - float_zero_to_one("rotation_multiplier"), - float_zero_to_one("strafing_multiplier"), - float_zero_to_one("zoom_multiplier"), - Pad(8), - Float("map_scale"), - dependency("fog_map", "bitm") - ), - - #Screen Layer Animation - Struct("screen_layer_animation", - float_sec("animation_period"), - Pad(4), - from_to_wu_sec("wind_velocity"), - from_to_sec("wind_period"), - float_zero_to_one("wind_acceleration_weight"), - float_zero_to_one("wind_perpendicular_weight") - ), - - Pad(8), - #Sound - dependency("background_sound", "lsnd"), - dependency("sound_environment", "snde"), - SIZE=396, - ) - -def get(): - return fog__def - -fog__def = TagDef("fog ", - blam_header('fog '), - fog__body, - - ext=".fog", endian=">", tag_cls=HekTag - ) +from ...hek.defs.fog_ import * diff --git a/reclaimer/mcc_hek/defs/foot.py b/reclaimer/mcc_hek/defs/foot.py index a59bb98e..e359bc3a 100644 --- a/reclaimer/mcc_hek/defs/foot.py +++ b/reclaimer/mcc_hek/defs/foot.py @@ -7,34 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -material = Struct("material", - dependency("effect", "effe"), - dependency("sound", "snd!"), - SIZE=48, - ) - -effect = Struct("effect", - reflexive("materials", material, len(materials_list), *materials_list), - SIZE=28, - ) - -foot_body = Struct("tagdata", - reflexive("effects", effect, 13, *material_effect_types), - SIZE=140, - ) - - - -def get(): - return foot_def - -foot_def = TagDef("foot", - blam_header('foot'), - foot_body, - - ext=".material_effects", endian=">", tag_cls=HekTag - ) +from ...hek.defs.foot import * diff --git a/reclaimer/mcc_hek/defs/garb.py b/reclaimer/mcc_hek/defs/garb.py index 039e4a46..69805ad5 100644 --- a/reclaimer/mcc_hek/defs/garb.py +++ b/reclaimer/mcc_hek/defs/garb.py @@ -7,23 +7,18 @@ # See LICENSE for more information. # +from ...hek.defs.garb import * + +#import and use the mcc obje attrs from .obje import * -from .item import * -from .objs.obje import ObjeTag -from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant # replace the object_type enum one that uses # the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(4)) - ) +obje_attrs = dict(obje_attrs) +obje_attrs[0] = dict(obje_attrs[0], DEFAULT=4) -garb_body = Struct("tagdata", - obje_attrs, - item_attrs, - SIZE=944, - ) +garb_body = dict(garb_body) +garb_body[0] = obje_attrs def get(): diff --git a/reclaimer/mcc_hek/defs/glw_.py b/reclaimer/mcc_hek/defs/glw_.py index cba1b5e8..057d76e1 100644 --- a/reclaimer/mcc_hek/defs/glw_.py +++ b/reclaimer/mcc_hek/defs/glw_.py @@ -7,93 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -velocity_properties = Struct("velocity_properties", - SEnum16("attachment", *function_outputs), - Pad(2), - Float("velocity", UNIT_SCALE=per_sec_unit_scale), - Float("low_multiplier"), - Float("high_multiplier") - ) - -glw__body = Struct("tagdata", - ascii_str32("attachment_marker"), - SInt16("number_of_particles"), - SEnum16("boundary_effect", - "bounce", - "wrap", - ), - SEnum16("normal_particle_distribution", - "random", - "uniform", - ), - SEnum16("trailing_particle_distribution", - "vertically", - "normal", - "randomly" - ), - Bool32("glow_flags" , - "modify_particle_color_in_range", - "particles_move_backwards", - "particles_move_in_both_directions", - "trailing_particles_fade_over_time", - "trailing_particles_shrink_over_time", - "trailing_particles_slow_over_time", - ), - - Pad(36), - Struct("particle_rotational_velocity", INCLUDE=velocity_properties), - Struct("effect_rotational_velocity", INCLUDE=velocity_properties), - Struct("effect_translational_velocity", INCLUDE=velocity_properties), - Struct("particle_distance_to_object", - SEnum16("attachment", *function_outputs), - Pad(2), - Float("min_distance"), - Float("max_distance"), - Float("low_multiplier"), - Float("high_multiplier") - ), - - Pad(8), - Struct("particle_size", - SEnum16("attachment", *function_outputs), - Pad(2), - from_to_wu("size_bounds"), # world units - QStruct("size_attachment_multiplier", INCLUDE=from_to), - ), - - Struct("particle_color", - SEnum16("attachment", *function_outputs), - Pad(2), - QStruct("lower_bound", INCLUDE=argb_float), - QStruct("upper_bound", INCLUDE=argb_float), - QStruct("lower_scale", INCLUDE=argb_float), - QStruct("upper_scale", INCLUDE=argb_float) - ), - - Float("color_rate_of_change"), - Float("fading_percentage_of_glow"), - Float("particle_generation_frequency", - SIDETIP="Hz", UNIT_SCALE=per_sec_unit_scale), - float_sec("lifetime_of_trailing_particles"), - float_wu_sec("velocity_of_trailing_particles"), - float_sec("trailing_particle_min_time"), - float_sec("trailing_particle_max_time"), - - Pad(52), - dependency("texture", "bitm"), - SIZE=340, - ) - -def get(): - return glw__def - -glw__def = TagDef("glw!", - blam_header('glw!'), - glw__body, - - ext=".glow", endian=">", tag_cls=HekTag - ) +from ...hek.defs.glw_ import * diff --git a/reclaimer/mcc_hek/defs/hmt_.py b/reclaimer/mcc_hek/defs/hmt_.py index 97c63844..5bc951be 100644 --- a/reclaimer/mcc_hek/defs/hmt_.py +++ b/reclaimer/mcc_hek/defs/hmt_.py @@ -7,54 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -message_element = Struct("message_element", - UEnum8("type", - "text", - "icon", - EDITABLE=False - ), - Union("data", - CASE=".type.enum_name", - CASES=dict( - text=UEnum8("length", - *({GUI_NAME: str(i), NAME: "_%s" % i} for i in range(256))), - icon=UEnum8("icon_type", - *({GUI_NAME: name, NAME: name} for name in hmt_icon_types) - ), - ), - EDITABLE=False - ), - SIZE=2, - ) - -message = Struct("message", - ascii_str32("name"), - SInt16("text_start", GUI_NAME="start index into text blob"), - SInt16("element_index", GUI_NAME="start index of message block"), - SInt8("element_count"), - Computed("message_preview", WIDGET=HaloHudMessageTextFrame), - SIZE=64 - ) - -hmt__body = Struct("tagdata", - rawtext_ref("string", FlStrUTF16Data, max_size=65536, - VISIBLE=False, EDITABLE=False), - reflexive("message_elements", message_element, 8192), - reflexive("messages", message, 1024, DYN_NAME_PATH='.name'), - SIZE=128, - ) - - -def get(): - return hmt__def - -hmt__def = TagDef("hmt ", - blam_header('hmt '), - hmt__body, - - ext=".hud_message_text", endian=">", tag_cls=HekTag - ) +from ...hek.defs.hmt_ import * diff --git a/reclaimer/mcc_hek/defs/hud_.py b/reclaimer/mcc_hek/defs/hud_.py index df50869e..2b759083 100644 --- a/reclaimer/mcc_hek/defs/hud_.py +++ b/reclaimer/mcc_hek/defs/hud_.py @@ -7,28 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -hud__body = Struct("tagdata", - dependency("digits_bitmap", "bitm"), - SInt8("bitmap_digit_width"), - SInt8("screen_digit_width"), - SInt8("x_offset"), - SInt8("y_offset"), - SInt8("decimal_point_width"), - SInt8("colon_width"), - SIZE=100, - ) - - -def get(): - return hud__def - -hud__def = TagDef("hud#", - blam_header('hud#'), - hud__body, - - ext=".hud_number", endian=">", tag_cls=HekTag - ) +from ...hek.defs.hud_ import * diff --git a/reclaimer/mcc_hek/defs/item.py b/reclaimer/mcc_hek/defs/item.py index 9b4c8d6e..3c7c221f 100644 --- a/reclaimer/mcc_hek/defs/item.py +++ b/reclaimer/mcc_hek/defs/item.py @@ -7,55 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -message_index_comment = """MESSAGE INDEX -This sets which string from tags\\ui\\hud\\hud_item_messages.unicode_string_list to display.""" - -item_attrs = Struct('item_attrs', - Bool32("flags", - "always_maintains_z_up", - "destroyed_by_explosions", - "unaffected_by_gravity", - ), - SInt16("message_index", COMMENT=message_index_comment), - SInt16("sort_order"), - Float("scale"), - SInt16("hud_message_value_scale"), - - Pad(18), - - SEnum16("A_in", *device_functions), - SEnum16("B_in", *device_functions), - SEnum16("C_in", *device_functions), - SEnum16("D_in", *device_functions), - - Pad(164), - - dependency("material_effects", "foot"), - dependency("collision_sound", "snd!"), - - Pad(120), - - from_to_sec("detonation_delay"), - dependency("detonating_effect", "effe"), - dependency("detonation_effect", "effe"), - SIZE=396, - ) - -item_body = Struct('tagdata', - item_attrs, - SIZE=396 - ) - -def get(): - return item_def - -item_def = TagDef("item", - blam_header('item', 2), - item_body, - - ext=".item", endian=">", tag_cls=HekTag - ) +from ...hek.defs.item import * diff --git a/reclaimer/mcc_hek/defs/itmc.py b/reclaimer/mcc_hek/defs/itmc.py index 8a88c5ed..e067cdab 100644 --- a/reclaimer/mcc_hek/defs/itmc.py +++ b/reclaimer/mcc_hek/defs/itmc.py @@ -7,32 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -item_permutation = Struct("permutation", - Pad(32), - Float("weight"), - dependency("item", valid_items), - SIZE=84, - ) - -itmc_body = Struct("tagdata", - reflexive("item_permutations", item_permutation, 32767, - DYN_NAME_PATH='.item.filepath'), - SInt16("spawn_time", SIDETIP="seconds(0 = default)", - UNIT_SCALE=per_sec_unit_scale), - SIZE=92, - ) - - -def get(): - return itmc_def - -itmc_def = TagDef("itmc", - blam_header('itmc',0), - itmc_body, - - ext=".item_collection", endian=">", tag_cls=HekTag - ) +from ...hek.defs.itmc import * diff --git a/reclaimer/mcc_hek/defs/lifi.py b/reclaimer/mcc_hek/defs/lifi.py index f4addf61..442889de 100644 --- a/reclaimer/mcc_hek/defs/lifi.py +++ b/reclaimer/mcc_hek/defs/lifi.py @@ -7,25 +7,18 @@ # See LICENSE for more information. # +from ...hek.defs.lifi import * + +#import and use the mcc obje attrs from .obje import * -from .devi import * -from .objs.lifi import LifiTag -from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant # replace the object_type enum one that uses # the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(9)) - ) - -lifi_body = Struct("tagdata", - obje_attrs, - devi_attrs, - - SIZE=720, - ) +obje_attrs = dict(obje_attrs) +obje_attrs[0] = dict(obje_attrs[0], DEFAULT=9) +lifi_body = dict(lifi_body) +lifi_body[0] = obje_attrs def get(): return lifi_def diff --git a/reclaimer/mcc_hek/defs/ligh.py b/reclaimer/mcc_hek/defs/ligh.py index cf9ec696..ac32dcef 100644 --- a/reclaimer/mcc_hek/defs/ligh.py +++ b/reclaimer/mcc_hek/defs/ligh.py @@ -7,102 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.ligh import LighTag -from supyr_struct.defs.tag_def import TagDef - -gel_comment = """The map tints the light per-pixel of cubemap.""" - -lens_flare_comment = """LENS FLARE -Optional lens flare associated with this light.""" - -radiosity_comment = """Controls how the light affects the lightmaps (ignored for dynamic lights).""" - -effect_parameters_comment = """If the light is created by an effect, it will animate itself as follows.""" - -ligh_body = Struct("tagdata", - Bool32("flags", - "dynamic", - "no_specular", - "dont_light_own_object", - "supersize_in_first_person", - "first_person_flashlight", - "dont_fade_active_camouflage", - ), - - #Shape - Struct("shape", - Float("radius"), - QStruct("radius_modifier", INCLUDE=from_to), - float_rad("falloff_angle"), # radians - float_rad("cutoff_angle"), # radians - Float("lens_flare_only_radius"), - Float("cosine_falloff_angle", VISIBLE=False), - Float("cosine_cutoff_angle", VISIBLE=False), - Float("unknown", VISIBLE=False), - Float("sine_cutoff_angle", VISIBLE=False), - Pad(8), - ), - - #Color - Struct("color", - Bool32("interpolation_flags", *blend_flags), - QStruct("color_lower_bound", INCLUDE=argb_float), - QStruct("color_upper_bound", INCLUDE=argb_float), - Pad(12), - ), - - #Gel - Struct("gel_map", - dependency("primary_cube_map", "bitm"), - Pad(2), - SEnum16("texture_animation_function", *animation_functions), - float_sec("texture_animation_period"), - - dependency("secondary_cube_map", "bitm"), - Pad(2), - SEnum16("yaw_animation_function", *animation_functions), - float_sec("yaw_animation_period"), - Pad(2), - SEnum16("roll_animation_function", *animation_functions), - float_sec("roll_animation_period"), - Pad(2), - SEnum16("pitch_animation_function", *animation_functions), - float_sec("pitch_animation_period"), - Pad(8), - COMMENT=gel_comment - ), - - #Lens flare - dependency("lens_flare", "lens", COMMENT=lens_flare_comment), - Pad(24), - - #Radiosity - Struct("radiosity", - Float("intensity"), - QStruct("color", INCLUDE=rgb_float), - Pad(16), - COMMENT=radiosity_comment - ), - - #Effect parameters - Struct("effect_parameters", - float_sec("duration"), - Pad(2), - SEnum16("falloff_function", *fade_functions), - COMMENT=effect_parameters_comment - ), - - SIZE=352, - ) - - -def get(): - return ligh_def - -ligh_def = TagDef("ligh", - blam_header("ligh", 3), - ligh_body, - - ext=".light", endian=">", tag_cls=LighTag, - ) +from ...hek.defs.ligh import * diff --git a/reclaimer/mcc_hek/defs/mach.py b/reclaimer/mcc_hek/defs/mach.py index c532f018..3fad1d55 100644 --- a/reclaimer/mcc_hek/defs/mach.py +++ b/reclaimer/mcc_hek/defs/mach.py @@ -7,49 +7,18 @@ # See LICENSE for more information. # +from ...hek.defs.mach import * + +#import and use the mcc obje attrs from .obje import * -from .devi import * -from .objs.mach import MachTag -from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant # replace the object_type enum one that uses # the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(7)) - ) - -mach_attrs = Struct("mach_attrs", - SEnum16('type', - 'door', - 'platform', - 'gear', - ), - Bool16('flags', - 'pathfinding_obstable', - 'except_when_open', - 'elevator', - ), - float_sec('door_open_time'), # seconds - - Pad(80), - SEnum16('triggers_when', - 'pause_until_crushed', - 'reverse_directions' - ), - SInt16('elevator_node'), - Pad(52), - UInt32("door_open_time_ticks") - ) - -mach_body = Struct("tagdata", - obje_attrs, - devi_attrs, - mach_attrs, - - SIZE=804, - ) +obje_attrs = dict(obje_attrs) +obje_attrs[0] = dict(obje_attrs[0], DEFAULT=7) +mach_body = dict(mach_body) +mach_body[0] = obje_attrs def get(): return mach_def diff --git a/reclaimer/mcc_hek/defs/metr.py b/reclaimer/mcc_hek/defs/metr.py index 88c12a70..0f59bdcc 100644 --- a/reclaimer/mcc_hek/defs/metr.py +++ b/reclaimer/mcc_hek/defs/metr.py @@ -7,51 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - - -meter_body = Struct("tagdata", - Pad(4), - dependency("stencil_bitmap", "bitm"), - dependency("source_bitmap", "bitm"), - - SInt16("stencil_sequence_index"), - SInt16("source_sequence_index"), - Pad(20), - SEnum16("interpolate_colors", - "linearly", - "faster_near_empty", - "faster_near_full", - "through_random_noise" - ), - SEnum16("anchor_colors" , - "at_both_ends", - "at_empty", - "at_full" - ), - Pad(8), - QStruct("empty_color", INCLUDE=argb_float), - QStruct("full_color", INCLUDE=argb_float), - Pad(20), - Float("unmask_distance", SIDETIP="meter units"), - Float("mask_distance", SIDETIP="meter units"), - Pad(12), - FlUInt16("screen_x_pos", SIDETIP="pixels"), - FlUInt16("screen_y_pos", SIDETIP="pixels"), - FlUInt16("width", SIDETIP="pixels"), - FlUInt16("height", SIDETIP="pixels"), - - rawdata_ref("meter_data", max_size=65536), - SIZE=172, WIDGET=MeterImageFrame - ) - -def get(): - return metr_def - -metr_def = TagDef("metr", - blam_header('metr'), - meter_body, - ext=".meter", endian=">", tag_cls=HekTag - ) +from ...hek.defs.metr import * diff --git a/reclaimer/mcc_hek/defs/mgs2.py b/reclaimer/mcc_hek/defs/mgs2.py index 076a3290..516735cc 100644 --- a/reclaimer/mcc_hek/defs/mgs2.py +++ b/reclaimer/mcc_hek/defs/mgs2.py @@ -7,82 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -light_volume_comment = """LIGHT VOLUME -Draws a sequence of glow bitmaps along a line. Can be used for contrail-type effects -as well as volumetric lights.""" - -brightness_scale_comment = """BRIGHTNESS SCALE -Fades the effect in and out with distance, viewer angle and external source.""" - -bitmaps_comment = """BITMAPS -Bitmap tag used to draw the light volume, repeated times. Default is -'tags\\rasterizer_textures\\glow'. -Note: Sprite plates are not valid for light volumes.""" - -frame_animation_comment = """FRAME ANIMATION -Frames are descriptions of the light volume at a particular point in time, -interpolated by an external source. For example: A bolt of energy can be made -to stretch out and grow thinner as it is fired from a weapon.""" - -frame = Struct("frame", - Pad(16), - float_wu("offset_from_marker"), - Float("offset_exponent"), - float_wu("length"), - - Pad(32), - float_wu("radius_hither"), - float_wu("radius_yon"), - Float("radius_exponent"), - - Pad(32), - QStruct("tint_color_hither", INCLUDE=argb_float), - QStruct("tint_color_yon", INCLUDE=argb_float), - Float("tint_color_exponent"), - Float("brightness_exponent"), - SIZE=176 - ) - -mgs2_body = Struct("tagdata", - #Light volume - ascii_str32("attachment_marker", COMMENT=light_volume_comment), - Bool32("flags", *blend_flags), - Pad(16), - - #Brightness scale - float_wu("near_fade_distance", COMMENT=brightness_scale_comment), - float_wu("far_fade_distance"), - float_zero_to_one("perpendicular_brightness_scale"), - float_zero_to_one("parallel_brightness_scale"), - SEnum16("brightness_scale_source", *function_outputs), - Pad(22), - - #Bitmaps - dependency("map", "bitm", COMMENT=bitmaps_comment), - SInt16("map_sequence_index"), - SInt16("map_count"), - Pad(72), - - #Frame animation - SEnum16("frame_animation_source", *function_outputs, COMMENT=frame_animation_comment), - Pad(102), - - reflexive("frames", frame, 2), - - SIZE=332, - ) - - -def get(): - return mgs2_def - -mgs2_def = TagDef("mgs2", - blam_header("mgs2"), - mgs2_body, - - ext=".light_volume", endian=">", tag_cls=HekTag, - ) +from ...hek.defs.mgs2 import * diff --git a/reclaimer/mcc_hek/defs/mod2.py b/reclaimer/mcc_hek/defs/mod2.py index 42264d95..a3aa0bbd 100644 --- a/reclaimer/mcc_hek/defs/mod2.py +++ b/reclaimer/mcc_hek/defs/mod2.py @@ -7,324 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.mod2 import Mod2Tag -from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant - -def get(): - return mod2_def - -local_marker = Struct('local_marker', - ascii_str32("name"), - dyn_senum16('node_index', - DYN_NAME_PATH="tagdata.nodes.nodes_array[DYN_I].name"), - Pad(2), - - QStruct('rotation', INCLUDE=ijkw_float), - QStruct('translation', INCLUDE=xyz_float), - SIZE=80 - ) - -fast_uncompressed_vertex = QStruct('uncompressed_vertex', - Float('position_x'), Float('position_y'), Float('position_z'), - Float('normal_i'), Float('normal_j'), Float('normal_k'), - Float('binormal_i'), Float('binormal_j'), Float('binormal_k'), - Float('tangent_i'), Float('tangent_j'), Float('tangent_k'), - - Float('u'), Float('v'), - - SInt16('node_0_index'), SInt16('node_1_index'), - Float('node_0_weight'), Float('node_1_weight'), - SIZE=68 - ) - -fast_compressed_vertex = QStruct('compressed_vertex', - Float('position_x'), Float('position_y'), Float('position_z'), - UInt32('normal'), - UInt32('binormal'), - UInt32('tangent'), - - SInt16('u', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), - SInt16('v', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), - - SInt8('node_0_index', UNIT_SCALE=1/3, MIN=0, WIDGET_WIDTH=10), - SInt8('node_1_index', UNIT_SCALE=1/3, MIN=0, WIDGET_WIDTH=10), - SInt16('node_0_weight', UNIT_SCALE=1/32767, MIN=0, WIDGET_WIDTH=10), - SIZE=32 - ) - -uncompressed_vertex = Struct('uncompressed_vertex', - QStruct("position", INCLUDE=xyz_float), - QStruct("normal", INCLUDE=ijk_float), - QStruct("binormal", INCLUDE=ijk_float), - QStruct("tangent", INCLUDE=ijk_float), - - Float('u'), - Float('v'), - - SInt16('node_0_index', - TOOLTIP="If local nodes are used, this is a local index"), - SInt16('node_1_index', - TOOLTIP="If local nodes are used, this is a local index"), - Float('node_0_weight'), - Float('node_1_weight'), - SIZE=68 - ) - -compressed_vertex = Struct('compressed_vertex', - QStruct("position", INCLUDE=xyz_float), - - BBitStruct('normal', INCLUDE=compressed_normal_32, SIZE=4), - BBitStruct('binormal', INCLUDE=compressed_normal_32, SIZE=4), - BBitStruct('tangent', INCLUDE=compressed_normal_32, SIZE=4), - - SInt16('u', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), - SInt16('v', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), - - SInt8('node_0_index', UNIT_SCALE=1/3, MIN=0, WIDGET_WIDTH=10), - SInt8('node_1_index', UNIT_SCALE=1/3, MIN=0, WIDGET_WIDTH=10), - SInt16('node_0_weight', UNIT_SCALE=1/32767, MIN=0, WIDGET_WIDTH=10), - SIZE=32 - ) - -triangle = QStruct('triangle', - SInt16('v0_index'), SInt16('v1_index'), SInt16('v2_index'), - SIZE=6, ORIENT='h' - ) - -uncompressed_vertex_union = Union('uncompressed_vertex', - CASES={'uncompressed_vertex': uncompressed_vertex}, - ) - -compressed_vertex_union = Union('compressed_vertex', - CASES={'compressed_vertex': compressed_vertex}, - ) - -triangle_union = Union('triangle', - CASES={'triangle': triangle}, - ) - - -marker_instance = Struct('marker_instance', - dyn_senum8('region_index', - DYN_NAME_PATH="tagdata.regions.regions_array[DYN_I].name"), - SInt8('permutation_index', MIN=-1), - dyn_senum8('node_index', - DYN_NAME_PATH="tagdata.nodes.nodes_array[DYN_I].name"), - Pad(1), - - QStruct('translation', INCLUDE=xyz_float), - QStruct('rotation', INCLUDE=ijkw_float), - SIZE=32 - ) - -permutation = Struct('permutation', - ascii_str32("name"), - Bool32('flags', - 'cannot_be_chosen_randomly' - ), - # permutations ending with -XXX where XXX is a number will belong to - # the permutation set XXX. Trailing non-numeric characters are ignored. - # Example: - # head_marcus_cap-101asdf - # Will have its permutation_set be set to 101 - # For all parts of a permutation to be properly chosen across - # all regions, they must share a permutation set number. - # Anything not ending in -XXX will have this set to 0. - FlUInt16("permutation_set", VISIBLE=False), # meta only field - Pad(26), - - dyn_senum16('superlow_geometry_block', - DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), - dyn_senum16('low_geometry_block', - DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), - dyn_senum16('medium_geometry_block', - DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), - dyn_senum16('high_geometry_block', - DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), - dyn_senum16('superhigh_geometry_block', - DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), - Pad(2), - - reflexive("local_markers", local_marker, 32, DYN_NAME_PATH=".name"), - SIZE=88 - ) - -part = Struct('part', - Bool32('flags', - 'stripped', - 'ZONER', - ), - dyn_senum16('shader_index', - DYN_NAME_PATH="tagdata.shaders.shaders_array[DYN_I].shader.filepath"), - SInt8('previous_part_index'), - SInt8('next_part_index'), - - SInt16('centroid_primary_node'), - SInt16('centroid_secondary_node'), - Float('centroid_primary_weight'), - Float('centroid_secondary_weight'), - - QStruct('centroid_translation', INCLUDE=xyz_float), - - #reflexive("uncompressed_vertices", uncompressed_vertex_union, 32767), - #reflexive("compressed_vertices", compressed_vertex_union, 32767), - #reflexive("triangles", triangle_union, 32767), - reflexive("uncompressed_vertices", fast_uncompressed_vertex, 32767), - reflexive("compressed_vertices", fast_compressed_vertex, 32767), - reflexive("triangles", triangle, 32767), - #Pad(36), - Struct("model_meta_info", - UEnum16("index_type", # name is a guess. always 1? - ("uncompressed", 1), - ), - Pad(2), - UInt32("index_count"), - # THESE TWO VALUES ARE DIFFERENT THAN ON XBOX IT SEEMS - UInt32("indices_magic_offset"), - UInt32("indices_offset"), - - UEnum16("vertex_type", # name is a guess - ("uncompressed", 4), - ("compressed", 5), - ), - Pad(2), - UInt32("vertex_count"), - Pad(4), # always 0? - # THESE TWO VALUES ARE DIFFERENT THAN ON XBOX IT SEEMS - UInt32("vertices_magic_offset"), - UInt32("vertices_offset"), - VISIBLE=False, SIZE=36 - ), - - Pad(3), - SInt8('local_node_count', MIN=0, MAX=22), - #UInt8Array('local_nodes', SIZE=22), - Array("local_nodes", SUB_STRUCT=UInt8("local_node_index"), SIZE=22), - - # this COULD be 2 more potential local nodes, but I've seen tool - # split models when they reach 22 nodes, so im assuming 22 is the max - Pad(2), - SIZE=132 - ) - -fast_part = desc_variant(part, - ("uncompressed_vertices", raw_reflexive("uncompressed_vertices", fast_uncompressed_vertex, 65535)), - ("compressed_vertices", raw_reflexive("compressed_vertices", fast_compressed_vertex, 65535)), - ("triangles", raw_reflexive("triangles", triangle, 65535)), - ) - -marker = Struct('marker', - ascii_str32("name"), - UInt16('magic_identifier'), - Pad(18), - - reflexive("marker_instances", marker_instance, 32), - SIZE=64 - ) - -node = Struct('node', - ascii_str32("name"), - dyn_senum16('next_sibling_node', DYN_NAME_PATH="..[DYN_I].name"), - dyn_senum16('first_child_node', DYN_NAME_PATH="..[DYN_I].name"), - dyn_senum16('parent_node', DYN_NAME_PATH="..[DYN_I].name"), - Pad(2), - - QStruct('translation', INCLUDE=xyz_float), - QStruct('rotation', INCLUDE=ijkw_float), - Float('distance_from_parent'), - Pad(32), - - # xbox specific values - LFloat('scale', ENDIAN='<', DEFAULT=1.0, VISIBLE=False), - QStruct("rot_jj_kk", GUI_NAME="[1-2j^2-2k^2] 2[ij+kw] 2[ik-jw]", - INCLUDE=ijk_float, ENDIAN='<', VISIBLE=False), - QStruct("rot_kk_ii", GUI_NAME="2[ij-kw] [1-2k^2-2i^2] 2[jk+iw]", - INCLUDE=ijk_float, ENDIAN='<', VISIBLE=False), - QStruct("rot_ii_jj", GUI_NAME="2[ik+jw] 2[jk-iw] [1-2i^2-2j^2]", - INCLUDE=ijk_float, ENDIAN='<', VISIBLE=False), - QStruct('translation_to_root', - INCLUDE=xyz_float, ENDIAN='<', VISIBLE=False), - SIZE=156, - ) - -region = Struct('region', - ascii_str32("name"), - Pad(32), - reflexive("permutations", permutation, 32, DYN_NAME_PATH=".name"), - SIZE=76 - ) - -geometry = Struct('geometry', - Pad(36), - reflexive("parts", part, 32), - SIZE=48 - ) - -fast_geometry = Struct('geometry', - Pad(36), - reflexive("parts", fast_part, 32), - SIZE=48 - ) - -shader = Struct('shader', - dependency("shader", valid_shaders), - SInt16('permutation_index'), - SIZE=32, - ) - - -mod2_body = Struct('tagdata', - Bool32('flags', - 'blend_shared_normals', - 'parts_have_local_nodes', - 'ignore_skinning' - ), - SInt32('node_list_checksum'), - - Float('superhigh_lod_cutoff', SIDETIP="pixels"), - Float('high_lod_cutoff', SIDETIP="pixels"), - Float('medium_lod_cutoff', SIDETIP="pixels"), - Float('low_lod_cutoff', SIDETIP="pixels"), - Float('superlow_lod_cutoff', SIDETIP="pixels"), - - SInt16('superlow_lod_nodes', SIDETIP="nodes"), - SInt16('low_lod_nodes', SIDETIP="nodes"), - SInt16('medium_lod_nodes', SIDETIP="nodes"), - SInt16('high_lod_nodes', SIDETIP="nodes"), - SInt16('superhigh_lod_nodes', SIDETIP="nodes"), - - Pad(10), - - Float('base_map_u_scale'), - Float('base_map_v_scale'), - - Pad(116), - - reflexive("markers", marker, 256, DYN_NAME_PATH=".name"), - reflexive("nodes", node, 64, DYN_NAME_PATH=".name"), - reflexive("regions", region, 32, DYN_NAME_PATH=".name"), - reflexive("geometries", geometry, 256), - reflexive("shaders", shader, 256, DYN_NAME_PATH=".shader.filepath"), - - SIZE=232 - ) - -fast_mod2_body = desc_variant(mod2_body, - ("geometries", reflexive("geometries", fast_geometry, 256)), - ) - -mod2_def = TagDef("mod2", - blam_header('mod2', 5), - mod2_body, - - ext=".gbxmodel", endian=">", tag_cls=Mod2Tag - ) - -fast_mod2_def = TagDef("mod2", - blam_header('mod2', 5), - fast_mod2_body, - - ext=".gbxmodel", endian=">", tag_cls=Mod2Tag - ) +from ...hek.defs.mod2 import * diff --git a/reclaimer/mcc_hek/defs/mode.py b/reclaimer/mcc_hek/defs/mode.py index 2b67917c..5c8d9d1c 100644 --- a/reclaimer/mcc_hek/defs/mode.py +++ b/reclaimer/mcc_hek/defs/mode.py @@ -7,167 +7,4 @@ # See LICENSE for more information. # -from .mod2 import * -from .objs.mode import ModeTag -from supyr_struct.util import desc_variant - -def get(): - return mode_def - -permutation = Struct('permutation', - ascii_str32("name"), - Bool32('flags', - 'cannot_be_chosen_randomly' - ), - # permutations ending with -XXX where XXX is a number will belong to - # the permutation set XXX. Trailing non-numeric characters are ignored. - # Example: - # head_marcus_cap-101asdf - # Will have its permutation_set be set to 101 - # For all parts of a permutation to be properly chosen across - # all regions, they must share a permutation set number. - # Anything not ending in -XXX will have this set to 0. - FlUInt16("permutation_set", VISIBLE=False), # meta only field - Pad(26), - - dyn_senum16('superlow_geometry_block', - DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), - dyn_senum16('low_geometry_block', - DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), - dyn_senum16('medium_geometry_block', - DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), - dyn_senum16('high_geometry_block', - DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), - dyn_senum16('superhigh_geometry_block', - DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), - Pad(2), - - reflexive("local_markers", local_marker, 32, DYN_NAME_PATH=".name"), - SIZE=88 - ) - -part = Struct('part', - Bool32('flags', - 'stripped', - ), - dyn_senum16('shader_index', - DYN_NAME_PATH="tagdata.shaders.shaders_array[DYN_I].shader.filepath"), - SInt8('previous_part_index'), - SInt8('next_part_index'), - - SInt16('centroid_primary_node'), - SInt16('centroid_secondary_node'), - Float('centroid_primary_weight'), - Float('centroid_secondary_weight'), - - QStruct('centroid_translation', INCLUDE=xyz_float), - - #reflexive("uncompressed_vertices", uncompressed_vertex_union, 65535), - #reflexive("compressed_vertices", compressed_vertex_union, 65535), - #reflexive("triangles", triangle_union, 65535), - reflexive("uncompressed_vertices", fast_uncompressed_vertex, 65535), - reflexive("compressed_vertices", fast_compressed_vertex, 65535), - reflexive("triangles", triangle, 65535), - - #Pad(36), - Struct("model_meta_info", - UEnum16("index_type", # name is a guess. always 1? - ("uncompressed", 1), - ), - Pad(2), - UInt32("index_count"), - UInt32("indices_offset"), - UInt32("indices_reflexive_offset"), - - UEnum16("vertex_type", # name is a guess - ("uncompressed", 4), - ("compressed", 5), - ), - Pad(2), - UInt32("vertex_count"), - Pad(4), # always 0? - UInt32("vertices_offset"), - UInt32("vertices_reflexive_offset"), - VISIBLE=False, SIZE=36 - ), - - SIZE=104 - ) - -fast_part = desc_variant(part, - ("uncompressed_vertices", raw_reflexive("uncompressed_vertices", fast_uncompressed_vertex, 65535)), - ("compressed_vertices", raw_reflexive("compressed_vertices", fast_compressed_vertex, 65535)), - ("triangles", raw_reflexive("triangles", triangle, 65535)), - ) - -region = Struct('region', - ascii_str32("name"), - Pad(32), - reflexive("permutations", permutation, 32, DYN_NAME_PATH=".name"), - SIZE=76 - ) - -geometry = Struct('geometry', - Pad(36), - reflexive("parts", part, 32), - SIZE=48 - ) - -fast_geometry = Struct('geometry', - Pad(36), - reflexive("parts", fast_part, 32), - SIZE=48 - ) - -mode_body = Struct('tagdata', - Bool32('flags', - 'blend_shared_normals', - ), - SInt32('node_list_checksum'), - - # xbox has these values swapped around in order - Float('superlow_lod_cutoff', SIDETIP="pixels"), - Float('low_lod_cutoff', SIDETIP="pixels"), - Float('medium_lod_cutoff', SIDETIP="pixels"), - Float('high_lod_cutoff', SIDETIP="pixels"), - Float('superhigh_lod_cutoff', SIDETIP="pixels"), - - SInt16('superlow_lod_nodes', SIDETIP="nodes"), - SInt16('low_lod_nodes', SIDETIP="nodes"), - SInt16('medium_lod_nodes', SIDETIP="nodes"), - SInt16('high_lod_nodes', SIDETIP="nodes"), - SInt16('superhigh_lod_nodes', SIDETIP="nodes"), - - Pad(10), - - Float('base_map_u_scale'), - Float('base_map_v_scale'), - - Pad(116), - - reflexive("markers", marker, 256, DYN_NAME_PATH=".name"), - reflexive("nodes", node, 64, DYN_NAME_PATH=".name"), - reflexive("regions", region, 32, DYN_NAME_PATH=".name"), - reflexive("geometries", geometry, 256), - reflexive("shaders", shader, 256, DYN_NAME_PATH=".shader.filepath"), - - SIZE=232 - ) - -fast_mode_body = desc_variant(mode_body, - ("geometries", reflexive("geometries", fast_geometry, 256)), - ) - -mode_def = TagDef("mode", - blam_header('mode', 4), - mode_body, - - ext=".model", endian=">", tag_cls=ModeTag - ) - -fast_mode_def = TagDef("mode", - blam_header('mode', 4), - fast_mode_body, - - ext=".model", endian=">", tag_cls=ModeTag - ) +from ...hek.defs.mode import * diff --git a/reclaimer/mcc_hek/defs/mply.py b/reclaimer/mcc_hek/defs/mply.py index 0f52302e..ba79de26 100644 --- a/reclaimer/mcc_hek/defs/mply.py +++ b/reclaimer/mcc_hek/defs/mply.py @@ -7,31 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - - -scenario_description = Struct("scenario_description", - dependency("descriptive_bitmap", "bitm"), - dependency("displayed_map_name", "ustr"), - ascii_str32("scenario_tag_directory_path"), - SIZE=68 - ) - -mply_body = Struct("tagdata", - reflexive("multiplayer_scenario_descriptions", - scenario_description, 32, DYN_NAME_PATH='.scenario_tag_directory_path'), - SIZE=12, - ) - - -def get(): - return mply_def - -mply_def = TagDef("mply", - blam_header('mply'), - mply_body, - - ext=".multiplayer_scenario_description", endian=">", tag_cls=HekTag - ) +from ...hek.defs.mply import * diff --git a/reclaimer/mcc_hek/defs/ngpr.py b/reclaimer/mcc_hek/defs/ngpr.py index 0c2a99f2..e0d01cd8 100644 --- a/reclaimer/mcc_hek/defs/ngpr.py +++ b/reclaimer/mcc_hek/defs/ngpr.py @@ -7,30 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -ngpr_body = Struct("tagdata", - ascii_str32("name"), - QStruct("primary_color", INCLUDE=rgb_float), - QStruct("secondary_color", INCLUDE=rgb_float), - - dependency("pattern", "bitm"), - SInt16("pattern_bitmap_index"), - Pad(2), - dependency("decal", "bitm"), - SInt16("decal_bitmap_index"), - SIZE=896 - ) - - -def get(): - return ngpr_def - -ngpr_def = TagDef("ngpr", - blam_header('ngpr', 2), - ngpr_body, - - ext=".preferences_network_game", endian=">", tag_cls=HekTag - ) +from ...hek.defs.ngpr import * diff --git a/reclaimer/mcc_hek/defs/part.py b/reclaimer/mcc_hek/defs/part.py index 9ee519f7..1125f046 100644 --- a/reclaimer/mcc_hek/defs/part.py +++ b/reclaimer/mcc_hek/defs/part.py @@ -7,99 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -part_body = Struct("tagdata", - Bool32("flags", - "can_animate_backwards", - "animation_stops_at_rest", - "animation_starts_on_random_frame", - "animate_once_per_frame", - "dies_at_rest", - "dies_on_contact_with_structure", - "tint_from_diffuse_texture", - "dies_on_contact_with_water", - "dies_on_contact_with_air", - "self_illuminated", - "random_horizontal_mirroring", - "random_vertical_mirroring", - ), - dependency("bitmap", "bitm"), - dependency("physics", "pphy"), - dependency("impact_effect", "foot", - TOOLTIP="Marty traded his kids for this"), - - Pad(4), - from_to_sec("lifespan"), - float_sec("fade_in_time"), - float_sec("fade_out_time"), - - dependency("collision_effect", valid_event_effects), - dependency("death_effect", valid_event_effects), - - Struct("rendering", - Float("minimum_size", SIDETIP="pixels"), - FlSInt32("unknown0", VISIBLE=False), - FlFloat("unknown1", VISIBLE=False), - QStruct("radius_animation", INCLUDE=from_to), - Pad(4), - QStruct("animation_rate", - Float("from", UNIT_SCALE=per_sec_unit_scale, GUI_NAME=''), - Float("to", UNIT_SCALE=per_sec_unit_scale), - ORIENT='h', SIDETIP='frames/sec' - ), - Float("contact_deterioration"), - Float("fade_start_size", SIDETIP="pixels"), - Float("fade_end_size", SIDETIP="pixels"), - - Pad(4), - SInt16("first_sequence_index"), - SInt16("initial_sequence_count"), - SInt16("looping_sequence_count"), - SInt16("final_sequence_count"), - - Pad(12), - SEnum16("orientation", *render_mode), - - Pad(38), - FlUInt32("unknown2", VISIBLE=False), - Bool16("shader_flags", *shader_flags), - SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), - SEnum16("framebuffer_fade_mode", *render_fade_mode), - Bool16("map_flags", - "unfiltered" - ), - ), - Pad(12), # OS v4 shader extension padding - Pad(16), - - #Secondary map - Struct("secondary_map", - dependency("bitmap", "bitm"), - SEnum16("anchor", *render_anchor), - Bool16("flags", - "unfiltered" - ), - Struct("u_animation", INCLUDE=anim_src_func_per_pha_sca), - Struct("v_animation", INCLUDE=anim_src_func_per_pha_sca), - Struct("rotation_animation", INCLUDE=anim_src_func_per_pha_sca_rot), - QStruct("rotation_center", INCLUDE=xy_float), - Pad(4), - Float("zsprite_radius_scale"), - ), - - SIZE=356, - ) - - -def get(): - return part_def - -part_def = TagDef("part", - blam_header("part", 2), - part_body, - - ext=".particle", endian=">", tag_cls=HekTag, - ) +from ...hek.defs.part import * diff --git a/reclaimer/mcc_hek/defs/pctl.py b/reclaimer/mcc_hek/defs/pctl.py index e2fcb1c9..9bed8582 100644 --- a/reclaimer/mcc_hek/defs/pctl.py +++ b/reclaimer/mcc_hek/defs/pctl.py @@ -7,157 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -particle_creation_physics = ( - "default", - "explosion", - "jet" - ) - -physics_constant = QStruct("physics_constant", - Float("k", UNIT_SCALE=per_sec_unit_scale), - ) - -state = Struct("state", - ascii_str32("name"), - from_to_sec("duration_bounds"), - from_to_sec("transition_time_bounds"), - - Pad(4), - Float("scale_multiplier"), - Float("animation_rate_multiplier"), - Float("rotation_rate_multiplier"), - QStruct("color_multiplier", INCLUDE=argb_float), - Float("radius_multiplier"), - Float("minimum_particle_count"), - Float("particle_creation_rate", - SIDETIP="particles/sec", UNIT_SCALE=per_sec_unit_scale), - - Pad(84), - SEnum16("particle_creation_physics", *particle_creation_physics), - Pad(2), # SEnum16("particle_update_physics", "default"), - reflexive("physics_constants", physics_constant, 16), - SIZE=192 - ) - -particle_state = Struct("particle_state", - ascii_str32("name"), - from_to_sec("duration_bounds"), - from_to_sec("transition_time_bounds"), - - dependency("bitmaps", "bitm"), - SInt16("sequence_index"), - Pad(6), - QStruct("scale", INCLUDE=from_to, SIDETIP="world units/pixel"), - QStruct("animation_rate", - Float("from", UNIT_SCALE=per_sec_unit_scale, GUI_NAME=''), - Float("to", UNIT_SCALE=per_sec_unit_scale), - ORIENT='h', SIDETIP="frames/sec" - ), - from_to_rad_sec("rotation_rate"), # radians/sec - QStruct("color_1", INCLUDE=argb_float), - QStruct("color_2", INCLUDE=argb_float), - Float("radius_multiplier"), - dependency("physics", "pphy"), - - Pad(72), - FlUInt32("unknown0", VISIBLE=False), - Bool16("shader_flags", *shader_flags), - SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), - SEnum16("framebuffer_fade_mode", *render_fade_mode), - Bool16("map_flags", - "unfiltered" - ), - - Pad(12), # open sauce particle shader extension - Pad(16), - #Secondary map - Struct("secondary_map", - dependency("bitmap", "bitm"), - SEnum16("anchor", *render_anchor), - Bool16("flags", - "unfiltered" - ), - Struct("u_animation", INCLUDE=anim_src_func_per_pha_sca), - Struct("v_animation", INCLUDE=anim_src_func_per_pha_sca), - Struct("rotation_animation", INCLUDE=anim_src_func_per_pha_sca_rot), - QStruct("rotation_center", INCLUDE=xy_float), - Pad(4), - Float("zsprite_radius_scale"), - ), - - Pad(20), - reflexive("physics_constants", physics_constant, 16), - SIZE=376 - ) - - -particle_type = Struct("particle_type", - ascii_str32("name"), - Bool32("flags", - "type_states_loop", - "type_states_loops_forward_backward", - "particle_states_loop", - "particle_states_loops_forward_backward", - "particles_die_in_water", - "particles_die_in_air", - "particles_die_on_ground", - "rotational_sprites_animate_sideways", - "disabled", - "tint_by_effect_color", - "initial_count_scales_with_effect", - "minimum_count_scales_with_effect", - "creation_rate_scales_with_effect", - "radius_scales_with_effect", - "animation_rate_scales_with_effect", - "rotation_rate_scales_with_effect", - "dont_draw_in_first_person", - "dont_draw_in_third_person", - ), - SInt16("initial_particle_count"), - - Pad(2), - SEnum16("complex_sprite_render_modes", - "simple", - "rotational" - ), - - Pad(2), - float_wu("radius"), - - Pad(36), - SEnum16("particle_creation_physics", *particle_creation_physics), - - Pad(6), - reflexive("physics_constants", physics_constant, 16), - reflexive("states", state, 8, DYN_NAME_PATH='.name'), - reflexive("particle_states", particle_state, 8, DYN_NAME_PATH='.name'), - SIZE=128 - ) - -pctl_body = Struct("tagdata", - Pad(56), - dependency("point_physics", "pphy"), - SEnum16("system_update_physics", - "default", - "explosion" - ), - Pad(6), - reflexive("physics_constants", physics_constant, 16), - reflexive("particle_types", particle_type, 4, DYN_NAME_PATH='.name'), - SIZE=104, - ) - - -def get(): - return pctl_def - -pctl_def = TagDef("pctl", - blam_header("pctl", 4), - pctl_body, - - ext=".particle_system", endian=">", tag_cls=HekTag, - ) +from ...hek.defs.pctl import * diff --git a/reclaimer/mcc_hek/defs/phys.py b/reclaimer/mcc_hek/defs/phys.py index 1cee3e38..e4b8d637 100644 --- a/reclaimer/mcc_hek/defs/phys.py +++ b/reclaimer/mcc_hek/defs/phys.py @@ -7,109 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.phys import PhysTag -from supyr_struct.defs.tag_def import TagDef - -inertial_matrix = Struct("inertial_matrix", - QStruct("yy_zz", GUI_NAME="yy+zz -xy -zx", INCLUDE=ijk_float), - QStruct("zz_xx", GUI_NAME="-xy zz+xx -yz", INCLUDE=ijk_float), - QStruct("xx_yy", GUI_NAME="-zx -yz xx+yy", INCLUDE=ijk_float), - SIZE=36, - ) - -powered_mass_point = Struct("powered_mass_point", - ascii_str32("name"), - Bool32('flags', - 'ground_friction', - 'water_friction', - 'air_friction', - 'water_lift', - 'air_lift', - 'thrust', - 'antigrav', - ), - Float("antigrav_strength"), - Float("antigrav_offset"), - Float("antigrav_height"), - Float("antigrav_damp_fraction"), - Float("antigrav_normal_k1"), - Float("antigrav_normal_k0"), - SIZE=128, - ) - -mass_point = Struct("mass_point", - ascii_str32("name"), - dyn_senum16("powered_mass_point", - DYN_NAME_PATH="tagdata.powered_mass_points.STEPTREE[DYN_I].name"), - SInt16("model_node"), - Bool32('flags', - 'metallic', - ), - Float("relative_mass", MIN=0.0), - Float("mass", VISIBLE=False, MIN=0.0), - Float("relative_density", MIN=0.0), - Float("density", VISIBLE=False, MIN=0.0), - QStruct("position", INCLUDE=xyz_float), - QStruct("forward", INCLUDE=ijk_float), - QStruct("up", INCLUDE=ijk_float), - SEnum16('friction_type', - 'point', - 'forward', - 'left', - 'up', - ), - Pad(2), - Float("friction_parallel_scale"), - Float("friction_perpendicular_scale"), - Float("radius", MIN=0.0, ALLOW_MIN=False), - SIZE=128, - ) - -phys_body = Struct("tagdata", - Float("radius"), - Float("moment_scale"), - Float("mass", MIN=0.0), - QStruct("center_of_mass", INCLUDE=xyz_float, VISIBLE=False), - Float("density", MIN=0.0), - Float("gravity_scale"), - Float("ground_friction", UNIT_SCALE=per_sec_unit_scale), - Float("ground_depth"), - Float("ground_damp_fraction"), - Float("ground_normal_k1"), - Float("ground_normal_k0"), - Pad(4), - Float("water_friction", UNIT_SCALE=per_sec_unit_scale), - Float("water_depth"), - Float("water_density"), - Pad(4), - Float("air_friction", UNIT_SCALE=per_sec_unit_scale), - Pad(4), - Float("xx_moment", VISIBLE=False), - Float("yy_moment", VISIBLE=False), - Float("zz_moment", VISIBLE=False), - - reflexive("inertia_matrices", inertial_matrix, 2, - "regular", "inverse", MIN=2, MAX=2, VISIBLE=False), - reflexive("powered_mass_points", powered_mass_point, 32, - DYN_NAME_PATH='.name'), - reflexive("mass_points", mass_point, 32, - DYN_NAME_PATH='.name'), - SIZE=128, - COMMENT="\ -Some fields have been hidden because you cant edit them.\n\n\ -This is because they are recalculated when you hit save.\n\ -The inertial matrices and xx/yy/zz moments are hidden as\n\ -well as the mass and density of the individual mass points." - ) - - -def get(): - return phys_def - -phys_def = TagDef("phys", - blam_header('phys', 4), - phys_body, - - ext=".physics", endian=">", tag_cls=PhysTag - ) +from ...hek.defs.phys import * diff --git a/reclaimer/mcc_hek/defs/plac.py b/reclaimer/mcc_hek/defs/plac.py index d9befd91..2f9bc5c5 100644 --- a/reclaimer/mcc_hek/defs/plac.py +++ b/reclaimer/mcc_hek/defs/plac.py @@ -7,22 +7,18 @@ # See LICENSE for more information. # +from ...hek.defs.plac import * + +#import and use the mcc obje attrs from .obje import * -from .objs.obje import ObjeTag -from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant # replace the object_type enum one that uses # the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(10)) - ) - -plac_body = Struct("tagdata", - obje_attrs, - SIZE=508, - ) +obje_attrs = dict(obje_attrs) +obje_attrs[0] = dict(obje_attrs[0], DEFAULT=10) +plac_body = dict(plac_body) +plac_body[0] = obje_attrs def get(): return plac_def diff --git a/reclaimer/mcc_hek/defs/pphy.py b/reclaimer/mcc_hek/defs/pphy.py index 7d335328..8e02981c 100644 --- a/reclaimer/mcc_hek/defs/pphy.py +++ b/reclaimer/mcc_hek/defs/pphy.py @@ -7,61 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.pphy import PphyTag -from supyr_struct.defs.tag_def import TagDef - -density_comment = """Densities(grams per milliliter)\n -air 0.0011 -snow 0.128 -cork 0.24 -cedar 0.43 -oak 0.866 -ice 0.897 -water 1.0 -soil 1.1 -cotton 1.491 -dry earth 1.52 -sand 1.7 -granite 2.4 -glass 2.5 -iron 7.65 -steel 7.77 -lead 11.37 -uranium 18.74 -gold 19.3""" - -pphy_body = Struct("tagdata", - Bool32("flags", - "flamethrower_particle_collision", - "collides_with_structures", - "collides_with_water_surface", - "uses_simple_wind", - "uses_damped_wind", - "no_gravity" - ), - # these next three are courtesy of Sparky. I had - # no idea these existed till I looked in Eschaton. - # kavawuvi figured out how to calculate them(see PphyTag) - FlFloat("scaled_density", VISIBLE=False), - FlFloat("water_gravity_scale", VISIBLE=False), - FlFloat("air_gravity_scale", VISIBLE=False), - Pad(16), - Float("density", SIDETIP="g/mL", COMMENT=density_comment),#g/mL - Float("air_friction"), - Float("water_friction"), - Float("surface_friction"), - Float("elasticity"), - - SIZE=64 - ) - -def get(): - return pphy_def - -pphy_def = TagDef("pphy", - blam_header('pphy'), - pphy_body, - - ext=".point_physics", endian=">", tag_cls=PphyTag - ) +from ...hek.defs.pphy import * diff --git a/reclaimer/mcc_hek/defs/rain.py b/reclaimer/mcc_hek/defs/rain.py index 39372bda..eeff8261 100644 --- a/reclaimer/mcc_hek/defs/rain.py +++ b/reclaimer/mcc_hek/defs/rain.py @@ -7,106 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -particle_type = Struct("particle_type", - ascii_str32("name"), - Bool32("flags", - "blend_colors_in_hsv", - "along_long_hue_path", - "random_rotation", - ), - QStruct("fade", - float_wu("in_start_distance"), - float_wu("in_end_distance"), - float_wu("out_start_distance"), - float_wu("out_end_distance"), - float_wu("in_start_height"), - float_wu("in_end_height"), - float_wu("out_start_height"), - float_wu("out_end_height"), - ), - - Pad(96), - QStruct("particle_count", INCLUDE=from_to, - SIDETIP="particles/(world unit^3)"), - dependency("physics", "pphy"), - - Pad(16), - Struct("acceleration", - QStruct("magnitude", INCLUDE=from_to), - float_rad("turning_rate"), # radians - Float("change_rate"), - ), - - Pad(32), - from_to_wu("particle_radius"), - QStruct("animation_rate", - Float("from", UNIT_SCALE=per_sec_unit_scale, GUI_NAME=''), - Float("to", UNIT_SCALE=per_sec_unit_scale), - ORIENT='h', SIDETIP="frames/sec" - ), - from_to_rad_sec("rotation_rate"), # radians/sec - - Pad(32), - QStruct("color_lower_bound", INCLUDE=argb_float), - QStruct("color_upper_bound", INCLUDE=argb_float), - - #Shader - Struct("shader", - Pad(64), - dependency("sprite_bitmap", "bitm"), - SEnum16("render_mode", *render_mode), - SEnum16("render_direction_source", - "from_velocity", - "from_acceleration" - ), - - Pad(40), - Bool16("shader_flags", *shader_flags), - SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), - SEnum16("framebuffer_fade_mode", *render_fade_mode), - Bool16("map_flags", - "unfiltered" - ) - ), - - #Secondary bitmap - Struct("secondary_bitmap", - Pad(28), - dependency("bitmap", "bitm"), - SEnum16("anchor", *render_anchor), - Bool16("secondary_map_flags", - "unfiltered" - ), - Struct("u_animation", INCLUDE=anim_src_func_per_pha_sca), - Struct("v_animation", INCLUDE=anim_src_func_per_pha_sca), - Struct("rotation_animation", INCLUDE=anim_src_func_per_pha_sca_rot), - QStruct("rotation_center", INCLUDE=xy_float) - ), - - Pad(4), - Float("zsprite_radius_scale"), - - SIZE=604 - ) - -rain_body = Struct("tagdata", - Pad(36), - reflexive("particle_types", particle_type, 8, DYN_NAME_PATH='.name'), - - SIZE=48, - ) - - -def get(): - return rain_def - -rain_def = TagDef("rain", - blam_header("rain"), - rain_body, - - ext=".weather_particle_system", endian=">", tag_cls=HekTag, - ) +from ...hek.defs.rain import * diff --git a/reclaimer/mcc_hek/defs/sbsp.py b/reclaimer/mcc_hek/defs/sbsp.py index 42d29085..2e017cb4 100644 --- a/reclaimer/mcc_hek/defs/sbsp.py +++ b/reclaimer/mcc_hek/defs/sbsp.py @@ -7,555 +7,4 @@ # See LICENSE for more information. # -from .coll import * -from .objs.sbsp import SbspTag -from supyr_struct.defs.block_def import BlockDef -from supyr_struct.util import desc_variant - -cluster_fog_tooltip = ( - "Unknown flag is set if negative.\n" - "Add 0x8000 to get fog index." - ) - - -# the order is an array of vertices first, then an array of lightmap vertices. -# -uncompressed_vertex = QStruct("uncompressed_vertex", - Float('position_x'), Float('position_y'), Float('position_z'), - Float('normal_i'), Float('normal_j'), Float('normal_k'), - Float('binormal_i'), Float('binormal_j'), Float('binormal_k'), - Float('tangent_i'), Float('tangent_j'), Float('tangent_k'), - - Float('tex_coord_u'), Float('tex_coord_v'), - SIZE=56 - ) - -compressed_vertex = QStruct("compressed_vertex", - Float('position_x'), Float('position_y'), Float('position_z'), - UInt32('normal'), - UInt32('binormal'), - UInt32('tangent'), - - Float('tex_coord_u'), Float('tex_coord_v'), - SIZE=32 - ) - -uncompressed_lightmap_vertex = QStruct("uncompressed_lightmap_vertex", - # this normal is the direction the light is hitting from, and - # is used for calculating dynamic shadows on dynamic objects - Float('normal_i'), Float('normal_j'), Float('normal_k'), - Float('u'), Float('v'), - SIZE=20 - ) - -compressed_lightmap_vertex = QStruct("compressed_lightmap_vertex", - # this normal is the direction the light is hitting from, and - # is used for calculating dynamic shadows on dynamic objects - UInt32('normal'), - SInt16('u', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), - SInt16('v', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), - SIZE=8 - ) - -plane = QStruct("plane", - Float("i", MIN=-1.0, MAX=1.0), - Float("j", MIN=-1.0, MAX=1.0), - Float("k", MIN=-1.0, MAX=1.0), - Float("d"), - SIZE=16, ORIENT='h' - ) - -vertex = QStruct("vertex", INCLUDE=xyz_float, SIZE=12) - -collision_material = Struct("collision_material", - dependency("shader", valid_shaders), - FlUInt32("unknown", VISIBLE=False), - SIZE=20 - ) - -collision_bsp = Struct("collision_bsp", INCLUDE=permutation_bsp) -fast_collision_bsp = Struct("collision_bsp", INCLUDE=fast_permutation_bsp) - -node = Struct("node", - # these dont get byteswapped going from meta to tag - BytesRaw("unknown", SIZE=6), - #QStruct("unknown_0", UInt8("val0"), SInt8("val1"), ORIENT="h"), - #QStruct("unknown_1", UInt8("val0"), SInt8("val1"), ORIENT="h"), - #QStruct("unknown_2", UInt8("val0"), SInt8("val1"), ORIENT="h"), - SIZE=6 - ) - -leaf = QStruct("leaf", - # these unknowns are in the tag and are preserved in the meta - FlSInt16("unknown0", VISIBLE=False), - FlSInt16("unknown1", VISIBLE=False), - FlSInt16("unknown2", VISIBLE=False), - FlSInt16("unknown3", VISIBLE=False), - - SInt16("cluster"), - SInt16("surface_reference_count"), - SInt32("surface_references"), - SIZE=16, - ) - -leaf_surface = QStruct("leaf_surface", - SInt32("surface"), - SInt32("node"), - SIZE=8, ORIENT='h' - ) - -surface = QStruct("surface", - SInt16("a"), - SInt16("b"), - SInt16("c"), - SIZE=6, ORIENT='h', COMMENT=""" -This is a renderable surface(visible geometry and lightmap geometry)""" - ) - -material = Struct("material", - dependency("shader", valid_shaders), - SInt16("shader_permutation"), - Bool16("flags", - "coplanar", - "fog_plane", - ), - SInt32("surfaces", EDITABLE=False, - TOOLTIP=("The offset into the surfaces array that this\n" - "lightmap materials surfaces are located at.") - ), - SInt32("surface_count", EDITABLE=False, - TOOLTIP=("The number of surfaces in the array belonging\n" - "to this lightmap material.") - ), - QStruct("centroid", INCLUDE=xyz_float), - QStruct("ambient_color", INCLUDE=rgb_float), - SInt16("distant_light_count"), - Pad(2), - - QStruct("distant_light_0_color", INCLUDE=rgb_float), - QStruct("distant_light_0_direction", INCLUDE=ijk_float), - QStruct("distant_light_1_color", INCLUDE=rgb_float), - QStruct("distant_light_1_direction", INCLUDE=ijk_float), - Pad(12), - - QStruct("reflection_tint", INCLUDE=argb_float), - QStruct("shadow_vector", INCLUDE=ijk_float), - QStruct("shadow_color", INCLUDE=rgb_float), - QStruct("plane", INCLUDE=plane), - SInt16("breakable_surface", EDITABLE=False), - Pad(6), - - SInt32("vertices_count", EDITABLE=False), - SInt32("vertices_offset", EDITABLE=False, VISIBLE=False), - - FlUInt32("unknown_meta_offset0", VISIBLE=False), - FlUInt32("vertices_meta_offset", - TOOLTIP=("In xbox maps this is a bspmagic relative pointer that\n" - "points to a reflexive. The reflexive contains only a\n" - "bspmagic relative pointer to the vertices."), - VISIBLE=False - ), - FlUEnum16("vertex_type", # name is a guess - ("unknown", 0), - ("uncompressed", 2), - ("compressed", 3), - VISIBLE=False, - ), - Pad(2), - SInt32("lightmap_vertices_count", EDITABLE=False), - SInt32("lightmap_vertices_offset", EDITABLE=False, VISIBLE=False), - - FlUInt32("unknown_meta_offset1", VISIBLE=False), - FlUInt32("lightmap_vertices_meta_offset", - TOOLTIP=("In xbox maps this is a bspmagic relative pointer that\n" - "points to a reflexive. The reflexive contains only a\n" - "bspmagic relative pointer to the lightmap vertices."), - VISIBLE=False - ), - - rawdata_ref("uncompressed_vertices", max_size=4864000), - rawdata_ref("compressed_vertices", max_size=2560000), - SIZE=256 - ) - -lightmap = Struct("lightmap", - SInt16("bitmap_index"), - Pad(18), - reflexive("materials", material, 2048, - DYN_NAME_PATH='.shader.filepath'), - SIZE=32 - ) - -lens_flare = Struct("lens_flare", - dependency("shader", 'lens'), - SIZE=16 - ) - -lens_flare_marker = Struct("lens_flare_marker", - QStruct("position", INCLUDE=xyz_float), - QStruct("direction", - SInt8('i'), SInt8('j'), SInt8('k'), ORIENT='h' - ), - # While guerilla treats this like a signed int, there is no way that it - # is gonna be able to reference one of the 256 lens flares if its signed - UInt8('lens_flare_index'), - SIZE=16 - ) - -surface_index = QStruct("surface_index", - SInt32("surface_index"), - SIZE=4 - ) - -mirror = Struct("mirror", - QStruct("plane", INCLUDE=plane), - # might be padding, might not be - BytesRaw("unknown", VISIBLE=False, SIZE=20), - #Pad(20), - dependency("shader", valid_shaders), - reflexive("vertices", vertex, 512), - SIZE=64 - ) - -portal = QStruct("portal", - SInt16("portal"), - SIZE=2 - ) - -subcluster = Struct("subcluster", - QStruct('world_bounds_x', INCLUDE=from_to), - QStruct('world_bounds_y', INCLUDE=from_to), - QStruct('world_bounds_z', INCLUDE=from_to), - reflexive("surface_indices", surface_index, 128), - SIZE=36, COMMENT=""" -Subclusters define areas to render in square chunks. Surfaces indices are -the renderable surfaces(not collision) to render in this subcluster. This is -how Halo's renderer knows what surfaces to render in each cluster.""" - ) - -cluster = Struct("cluster", - SInt16('sky'), - SInt16('fog', TOOLTIP=cluster_fog_tooltip), - dyn_senum16('background_sound', - DYN_NAME_PATH="tagdata.background_sounds_palette.STEPTREE[DYN_I].name"), - dyn_senum16('sound_environment', - DYN_NAME_PATH="tagdata.sound_environments_palette." + - "STEPTREE[DYN_I].name"), - dyn_senum16('weather', - DYN_NAME_PATH="tagdata.weather_palettes.STEPTREE[DYN_I].name"), - - UInt16("transition_structure_bsp", VISIBLE=False), - UInt16("first_decal_index", VISIBLE=False), - UInt16("decal_count", VISIBLE=False), - - # almost certain this is padding, though a value in the third - # and fourth bytes is non-zero in meta, but not in a tag, so idk. - Pad(24), - - reflexive("predicted_resources", predicted_resource, 1024, VISIBLE=False), - reflexive("subclusters", subcluster, 4096), - SInt16("first_lens_flare_marker_index"), - SInt16("lens_flare_marker_count"), - reflexive("surface_indices", surface_index, 32768), - reflexive("mirrors", mirror, 16, DYN_NAME_PATH=".shader.filepath"), - reflexive("portals", portal, 128), - SIZE=104 - ) - -cluster_portal = Struct("cluster_portal", - SInt16("front_cluster"), - SInt16("back_cluster"), - SInt32("plane_index"), - QStruct("centroid", INCLUDE=xyz_float), - Float("bounding_radius"), - Bool32("flags", - "ai_cant_hear_through_this", - ), - - # might be padding, might not be - BytesRaw("unknown", VISIBLE=False, SIZE=24), - #Pad(24), - reflexive("vertices", vertex, 128), - SIZE=64 - ) - -breakable_surface = Struct("breakable_surface", - QStruct("centroid", INCLUDE=xyz_float), - Float("radius"), - SInt32("collision_surface_index"), - Pad(28), - SIZE=48 - ) - -fog_plane = Struct("fog_plane", - SInt16("front_region"), - FlSEnum16("material_type", - *(tuple((materials_list[i], i) for i in - range(len(materials_list))) + (("NONE", -1), )), - VISIBLE=False), # calculated when compiled into map - QStruct("plane", INCLUDE=plane), - reflexive("vertices", vertex, 4096), - SIZE=32 - ) - -fog_region = Struct("fog_region", - Pad(36), - dyn_senum16("fog_palette", - DYN_NAME_PATH="tagdata.fog_palettes.STEPTREE[DYN_I].name"), - dyn_senum16("weather_palette", - DYN_NAME_PATH="tagdata.weather_palettes.STEPTREE[DYN_I].name"), - SIZE=40 - ) - -fog_palette = Struct("fog_palette", - ascii_str32("name"), - dependency("fog", "fog "), - Pad(4), - ascii_str32("fog_scale_function"), - SIZE=136 - ) - -weather_palette = Struct("weather_palette", - ascii_str32("name"), - dependency("particle_system", "rain"), - Pad(4), - ascii_str32("particle_system_scale_function"), - - Pad(44), - dependency("wind", "wind"), - QStruct("wind_direction", INCLUDE=ijk_float), - Float("wind_magnitude"), - Pad(4), - ascii_str32("wind_scale_function"), - SIZE=240 - ) - -weather_polyhedra = Struct("weather_polyhedra", - QStruct("bounding_sphere_center", INCLUDE=xyz_float), - Float("bounding_sphere_radius"), - Pad(4), - reflexive("planes", plane, 16), - SIZE=32 - ) - -pathfinding_surface = QStruct("pathfinding_surface", - # this is actually a 3bit width index, 3bit height - # index, and 2 flags(is_walkable and is_breakable/is_broken) - # stored in a single byte(in that order) - UInt8("data"), - SIZE=1 - ) - -pathfinding_edge = QStruct("pathfinding_edge", UInt8("midpoint"), SIZE=1) - -background_sound_palette = Struct("background_sound_palette", - ascii_str32("name"), - dependency("background_sound", "lsnd"), - Pad(4), - ascii_str32("scale_function"), - SIZE=116 - ) - -sound_environment_palette = Struct("sound_environment_palette", - ascii_str32("name"), - dependency("sound_environment", "snde"), - SIZE=80 - ) - -marker = Struct("marker", - ascii_str32("name"), - QStruct("rotation", INCLUDE=ijkw_float), - QStruct("position", INCLUDE=xyz_float), - SIZE=60 - ) - - -detail_object_cell = QStruct("detail_object_cell", - SInt16("unknown1"), SInt16("unknown2"), - SInt16("unknown3"), SInt16("unknown4"), - SInt32("unknown5"), SInt32("unknown6"), SInt32("unknown7"), - SIZE=32 - ) - -detail_object_instance = QStruct("detail_object_instance", - SInt8("unknown1"), SInt8("unknown2"), - SInt8("unknown3"), SInt8("unknown4"), SInt16("unknown5"), - SIZE=6 - ) - -detail_object_count = QStruct("detail_object_count", - SInt16("unknown"), - SIZE=2 - ) - -detail_object_z_reference_vector = QStruct("detail_object_z_reference_vector", - Float("unknown1"), Float("unknown2"), - Float("unknown3"), Float("unknown4"), - SIZE=16 - ) - -detail_object = Struct("detail_object", - reflexive("cells", detail_object_cell, 262144), - reflexive("instances", detail_object_instance, 2097152), - reflexive("counts", detail_object_count, 8388608), - reflexive("z_reference_vectors", detail_object_z_reference_vector, 262144), - Bool8("flags", - "enabled", # required to be set on map compile. - # set to "parent.instances.size != 0" - VISIBLE=False - ), - SIZE=64 - ) - -runtime_decal = BytesRaw("unknown", SIZE=16) - - -face_vertex = QStruct("vertex", Float("x"), Float("y"), SIZE=8) -portal_index = Struct("portal_index", SInt32("portal_index"), SIZE=4) - -face = Struct("face", - SInt32("node_index"), - reflexive("vertices", face_vertex, 64), - SIZE=16 - ) - -leaf_map_leaf = Struct("leaf_map_leaf", - reflexive("faces", face, 256), - reflexive("portal_indices", portal_index, 256), - SIZE=24 - ) - -leaf_map_portal = Struct("leaf_map_portal", - SInt32("plane_index"), - SInt32("back_leaf_index"), - SInt32("front_leaf_index"), - reflexive("vertices", face_vertex, 64), - SIZE=24 - ) - -raw_cluster_data = QStruct("raw_cluster_data", - UInt16("unknown0"), - UInt16("unknown1"), - UInt16("unknown2"), - UInt16("unknown3"), - SIZE=8 - ) - -sbsp_body = Struct("tagdata", - dependency("lightmap_bitmaps", 'bitm'), - float_wu("vehicle_floor"), # world units - float_wu("vehicle_ceiling"), # world units - - Pad(20), - QStruct("default_ambient_color", INCLUDE=rgb_float), - Pad(4), - QStruct("default_distant_light_0_color", INCLUDE=rgb_float), - QStruct("default_distant_light_0_direction", INCLUDE=ijk_float), - QStruct("default_distant_light_1_color", INCLUDE=rgb_float), - QStruct("default_distant_light_1_direction", INCLUDE=ijk_float), - - Pad(12), - QStruct("default_reflection_tint", INCLUDE=argb_float), - QStruct("default_shadow_vector", INCLUDE=ijk_float), - QStruct("default_shadow_color", INCLUDE=rgb_float), - - Pad(4), - reflexive("collision_materials", collision_material, 512, - DYN_NAME_PATH='.shader.filepath'), - reflexive("collision_bsp", collision_bsp, 1), - reflexive("nodes", node, 131072, VISIBLE=False), - QStruct("world_bounds_x", INCLUDE=from_to), - QStruct("world_bounds_y", INCLUDE=from_to), - QStruct("world_bounds_z", INCLUDE=from_to), - reflexive("leaves", leaf, 65535), - reflexive("leaf_surfaces", leaf_surface, 262144), - reflexive("surfaces", surface, 131072), - reflexive("lightmaps", lightmap, 128), - - Pad(12), - reflexive("lens_flares", lens_flare, 256, - DYN_NAME_PATH='.shader.filepath'), - reflexive("lens_flare_markers", lens_flare_marker, 65535), - reflexive("clusters", cluster, 8192), - - # this is an array of 8 byte structs for each cluster - rawdata_ref("cluster_data", max_size=65536), - reflexive("cluster_portals", cluster_portal, 512), - - Pad(12), - reflexive("breakable_surfaces", breakable_surface, 256), - reflexive("fog_planes", fog_plane, 32), - reflexive("fog_regions", fog_region, 32), - reflexive("fog_palettes", fog_palette, 32, - DYN_NAME_PATH='.name'), - - Pad(24), - reflexive("weather_palettes", weather_palette, 32, - DYN_NAME_PATH='.name'), - reflexive("weather_polyhedras", weather_polyhedra, 32), - - Pad(24), - reflexive("pathfinding_surfaces", pathfinding_surface, 131072, VISIBLE=False), - reflexive("pathfinding_edges", pathfinding_edge, 262144, VISIBLE=False), - reflexive("background_sounds_palette", background_sound_palette, 64, - DYN_NAME_PATH='.name'), - reflexive("sound_environments_palette", sound_environment_palette, 64, - DYN_NAME_PATH='.name'), - rawdata_ref("sound_pas_data", max_size=131072), - - UInt32("unknown", VISIBLE=False), - Pad(20), - reflexive("markers", marker, 1024, DYN_NAME_PATH='.name'), - reflexive("detail_objects", detail_object, 1, VISIBLE=False), - - # the runtime decals reflexive is populated ONLY by the - # engine while it is running(I'm making an educated guess) - reflexive("runtime_decals", runtime_decal, 6144, VISIBLE=False), - - Pad(12), - reflexive("leaf_map_leaves", leaf_map_leaf, 65536, VISIBLE=False), - reflexive("leaf_map_portals", leaf_map_portal, 524288, VISIBLE=False), - SIZE=648, - ) - -fast_sbsp_body = desc_variant(sbsp_body, - ("collision_bsp", reflexive("collision_bsp", fast_collision_bsp, 1)), - ("nodes", raw_reflexive("nodes", node, 131072)), - ("leaves", raw_reflexive("leaves", leaf, 65535)), - ("leaf_surfaces", raw_reflexive("leaf_surfaces", leaf_surface, 262144)), - ("surfaces", raw_reflexive("surface", surface, 131072)), - ("lens_flare_markers", raw_reflexive("lens_flare_markers", lens_flare_marker, 65535)), - ("breakable_surfaces", raw_reflexive("breakable_surfaces", breakable_surface, 256)), - ("pathfinding_surfaces", raw_reflexive("pathfinding_surfaces", pathfinding_surface, 131072)), - ("pathfinding_edges", raw_reflexive("pathfinding_edges", pathfinding_edge, 262144)), - ("markers", raw_reflexive("markers", marker, 1024, DYN_NAME_PATH='.name')), - ) - -sbsp_meta_header_def = BlockDef("sbsp_meta_header", - # to convert these pointers to offsets, do: pointer - bsp_magic - UInt32("meta_pointer"), - UInt32("uncompressed_lightmap_materials_count"), - UInt32("uncompressed_lightmap_materials_pointer"), # name is a guess - UInt32("compressed_lightmap_materials_count"), - UInt32("compressed_lightmap_materials_pointer"), # name is a guess - UInt32("sig", DEFAULT="sbsp"), - SIZE=24, TYPE=QStruct - ) - - -def get(): - return sbsp_def - -sbsp_def = TagDef("sbsp", - blam_header("sbsp", 5), - sbsp_body, - - ext=".scenario_structure_bsp", endian=">", tag_cls=SbspTag, - ) - -fast_sbsp_def = TagDef("sbsp", - blam_header("sbsp", 5), - fast_sbsp_body, - - ext=".scenario_structure_bsp", endian=">", tag_cls=SbspTag, - ) +from ...hek.defs.sbsp import * diff --git a/reclaimer/mcc_hek/defs/sgla.py b/reclaimer/mcc_hek/defs/sgla.py index eeaac1e8..7b93274a 100644 --- a/reclaimer/mcc_hek/defs/sgla.py +++ b/reclaimer/mcc_hek/defs/sgla.py @@ -7,78 +7,4 @@ # See LICENSE for more information. # -from .shdr import * -from .objs.shdr import ShdrTag -from supyr_struct.defs.tag_def import TagDef - -sgla_attrs = Struct("sgla_attrs", - #Glass Shader Properties - Bool16("glass_shader_flags", - "alpha_tested", - "decal", - "two_sided", - "bump_map_is_specular_mask", - ), - - Pad(42), - #Background Tint Properties - Struct("background_tint_properties", - QStruct("color", INCLUDE=rgb_float), - Float("map_scale"), - dependency("map", "bitm"), - ), - - Pad(22), - #Reflection Properties - Struct("reflection_properties", - SEnum16("type", - "bumped_cubemap", - "flat_cubemap", - "dynamic_mirror", - ), - float_zero_to_one("perpendicular_brightness"), # [0,1] - QStruct("perpendicular_tint_color", INCLUDE=rgb_float), - float_zero_to_one("parallel_brightness"), # [0,1] - QStruct("parallel_tint_color", INCLUDE=rgb_float), - dependency("map", "bitm"), - - Float("bump_map_scale"), - dependency("bump_map", "bitm"), - ), - - Pad(132), - #Diffuse Properties - Struct("diffuse_properties", - Float("map_scale"), - dependency("map", "bitm"), - Float("detail_map_scale"), - dependency("detail_map", "bitm"), - ), - - Pad(32), - #Specular Properties - Struct("specular_properties", - Float("map_scale"), - dependency("map", "bitm"), - Float("detail_map_scale"), - dependency("detail_map", "bitm"), - ), - SIZE=440 - ) - -sgla_body = Struct("tagdata", - shdr_attrs, - sgla_attrs, - SIZE=480, - ) - - -def get(): - return sgla_def - -sgla_def = TagDef("sgla", - blam_header('sgla'), - sgla_body, - - ext=".shader_transparent_glass", endian=">", tag_cls=ShdrTag - ) +from ...hek.defs.sgla import * diff --git a/reclaimer/mcc_hek/defs/shdr.py b/reclaimer/mcc_hek/defs/shdr.py index 26c22d78..152ef509 100644 --- a/reclaimer/mcc_hek/defs/shdr.py +++ b/reclaimer/mcc_hek/defs/shdr.py @@ -7,67 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.shdr import ShdrTag -from supyr_struct.defs.tag_def import TagDef - -radiosity_comment = """RADIOSITY/LIGHTMAPPING -The simple parameterisation flag is used for shaders that are on surfaces with more curved -shapes like grass hills. It avoids lightmap uv problems by using the uvs from the bsp geometry. -The ignore normals flag helps with shading bugs on double sided polygons by making the -light independent of normals.(Suggested use: tree leaves). - -The detail level controls the relative quality of the lightmaps/lightmap uvs for this shader.""" - -lighting_comment = """LIGHT -Light power controls the brightness and reach of the light emitted by this shader -during lightmap rendering. -""" - -material_type_comment = """MATERIAL TYPE -The material type is used to determine what material effects should be used for impacts on -BSP geometry that uses this shader.""" - -shdr_attrs = Struct("shdr_attrs", - Bool16("radiosity_flags", - { NAME: "simple_parameterization", GUI_NAME: "simple parameterization(lightmap fix)" }, - "ignore_normals", - "transparent_lit", - COMMENT=radiosity_comment - ), - SEnum16("radiosity_detail_level" , - "high", - "medium", - "low", - "turd", - ), - Float("radiosity_light_power", COMMENT=lighting_comment), - QStruct("radiosity_light_color", INCLUDE=rgb_float), - QStruct("radiosity_tint_color", INCLUDE=rgb_float), - - Pad(2), - SEnum16("material_type", *materials_list, COMMENT=material_type_comment), - # THIS FIELD IS OFTEN INCORRECT ON STOCK TAGS. - FlSEnum16("shader_type", - *((shader_types[i], i - 1) for i in - range(len(shader_types))), - VISIBLE=False, DEFAULT=-1 - ), - Pad(2), - SIZE=40 - ) - -shader_body = Struct("tagdata", - shdr_attrs, - SIZE=40 - ) - -def get(): - return shdr_def - -shdr_def = TagDef("shdr", - blam_header('shdr'), - shader_body, - - ext=".shader", endian=">", tag_cls=ShdrTag - ) +from ...hek.defs.shdr import * diff --git a/reclaimer/mcc_hek/defs/sky_.py b/reclaimer/mcc_hek/defs/sky_.py index 72d501fa..eff94d12 100644 --- a/reclaimer/mcc_hek/defs/sky_.py +++ b/reclaimer/mcc_hek/defs/sky_.py @@ -7,83 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -shader_function = Struct('shader_function', - Pad(4), - ascii_str32("global_function_name"), - SIZE=36 - ) - -animation = Struct('animation', - SInt16('animation_index'), - Pad(2), - float_sec("period"), - SIZE=36 - ) - -light = Struct('light', - dependency("lens_flare", "lens"), - ascii_str32("global_function_name"), - Pad(28), - Bool32('flags', - 'affects_exteriors', - 'affects_interiors', - ), - QStruct("color", INCLUDE=rgb_float), - Float("power"), - Float("test_distance"), - Pad(4), - yp_float_rad("direction"), # radians - float_rad("diameter"), # radians (yeah, it sounds weird, but this - # value is stored as a radian coefficient) - SIZE=116 - ) - - -sky__body = Struct("tagdata", - dependency("model", valid_models), - dependency("animation_graph", "antr"), - Pad(24), - - QStruct("indoor_ambient_radiosity_color", INCLUDE=rgb_float), - Float("indoor_ambient_radiosity_power"), - - QStruct("outdoor_ambient_radiosity_color", INCLUDE=rgb_float), - Float("outdoor_ambient_radiosity_power"), - - QStruct("outdoor_fog_color", INCLUDE=rgb_float), - Pad(8), - float_zero_to_one("outdoor_fog_maximum_density"), - float_wu("outdoor_fog_start_distance"), - float_wu("outdoor_fog_opaque_distance"), - - QStruct("indoor_fog_color", INCLUDE=rgb_float), - Pad(8), - float_zero_to_one("indoor_fog_maximum_density"), - float_wu("indoor_fog_start_distance"), - float_wu("indoor_fog_opaque_distance"), - - dependency("indoor_fog_screen", "fog "), - Pad(4), - reflexive("shader_functions", shader_function, 8, - DYN_NAME_PATH='.global_function_name'), - reflexive("animations", animation, 8), - reflexive("lights", light, 8, - DYN_NAME_PATH='.lens_flare.filepath'), - - SIZE=208, - ) - - -def get(): - return sky__def - -sky__def = TagDef("sky ", - blam_header('sky '), - sky__body, - - ext=".sky", endian=">", tag_cls=HekTag, - ) +from ...hek.defs.sky_ import * diff --git a/reclaimer/mcc_hek/defs/smet.py b/reclaimer/mcc_hek/defs/smet.py index 2ad01d6a..b079796d 100644 --- a/reclaimer/mcc_hek/defs/smet.py +++ b/reclaimer/mcc_hek/defs/smet.py @@ -7,62 +7,4 @@ # See LICENSE for more information. # -from .shdr import * -from .objs.shdr import ShdrTag -from supyr_struct.defs.tag_def import TagDef - -smet_attrs = Struct("smet_attrs", - #Meter Shader Properties - Struct("meter_shader", - Bool16("meter_shader_flags", - "decal", - "two_sided", - "flash_color_is_negative", - "tint_mode_2", - "unfiltered" - ), - Pad(34), - dependency("map", "bitm"), - Pad(32), - ), - - #Colors - Struct("colors", - Struct("gadient_min", INCLUDE=rgb_float), - Struct("gadient_max", INCLUDE=rgb_float), - Struct("background", INCLUDE=rgb_float), - Struct("flash", INCLUDE=rgb_float), - Struct("tint", INCLUDE=rgb_float), - float_zero_to_one("meter_transparency"), - float_zero_to_one("background_transparency"), - ), - - Pad(24), - #External Function Sources - Struct("external_function_sources", - SEnum16("meter_brightness", *function_outputs), - SEnum16("flash_brightness", *function_outputs), - SEnum16("value", *function_outputs), - SEnum16("gradient", *function_outputs), - SEnum16("flash_extension", *function_outputs), - ), - SIZE=220, - ) - -smet_body = Struct("tagdata", - shdr_attrs, - smet_attrs, - SIZE=260, - ) - - - -def get(): - return smet_def - -smet_def = TagDef("smet", - blam_header('smet'), - smet_body, - - ext=".shader_transparent_meter", endian=">", tag_cls=ShdrTag - ) +from ...hek.defs.smet import * diff --git a/reclaimer/mcc_hek/defs/snde.py b/reclaimer/mcc_hek/defs/snde.py index 1a5ad2dc..f0767f1b 100644 --- a/reclaimer/mcc_hek/defs/snde.py +++ b/reclaimer/mcc_hek/defs/snde.py @@ -7,35 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -snde_body = QStruct("tagdata", - Pad(4), - UInt16("priority"), - Pad(2), - Float("room_intensity"), - Float("room_intensity_hf"), - Float("room_rolloff", MIN=0.0, MAX=10.0), - float_sec("decay_time", MIN=0.1, MAX=20.0), - Float("decay_hf_ratio", MIN=0.1, MAX=2.0), - Float("reflections_intensity"), - float_sec("reflections_delay", MIN=0.0, MAX=0.3), - Float("reverb_intensity"), - float_sec("reverb_delay", MIN=0.0, MAX=0.1), - Float("diffusion"), - Float("density"), - Float("hf_reference", MIN=20.0, MAX=20000.0, SIDETIP="Hz"), - SIZE=72, - ) - -def get(): - return snde_def - -snde_def = TagDef("snde", - blam_header('snde'), - snde_body, - - ext=".sound_environment", endian=">", tag_cls=HekTag - ) +from ...hek.defs.snde import * diff --git a/reclaimer/mcc_hek/defs/sotr.py b/reclaimer/mcc_hek/defs/sotr.py index c3945c61..ae77c445 100644 --- a/reclaimer/mcc_hek/defs/sotr.py +++ b/reclaimer/mcc_hek/defs/sotr.py @@ -7,263 +7,4 @@ # See LICENSE for more information. # -from .shdr import * -from .objs.shdr import ShdrTag -from supyr_struct.defs.tag_def import TagDef - -sotr_input_mappings = ( - {NAME: 'clamp_x', GUI_NAME: 'clamp(x)'}, - {NAME: 'one_minus_clamp_x', GUI_NAME: '1 - clamp(x)'}, - {NAME: 'clamp_x_times_two_minus_one', GUI_NAME: '2*clamp(x) - 1'}, - {NAME: 'one_minus_clamp_x_times_two', GUI_NAME: '1 - 2*clamp(x)'}, - {NAME: 'clamp_x_minus_one_half', GUI_NAME: 'clamp(x) - 1/2'}, - {NAME: 'one_half_minus_clamp_x', GUI_NAME: '1/2 - clamp(x)'}, - {NAME: 'x', GUI_NAME: 'x'}, - {NAME: 'minus_x', GUI_NAME: '-x'}, - ) -sotr_output_mappings = ( - 'identity', - {NAME: 'scale_by_one_half', GUI_NAME: 'scale by 1/2'}, - {NAME: 'scale_by_two', GUI_NAME: 'scale by 2'}, - {NAME: 'scale_by_four', GUI_NAME: 'scale by 4'}, - {NAME: 'bias_by_minus_one_half', GUI_NAME: 'bias by -1/2'}, - 'expand_normal', - ) - - -sotr_color_inputs = ( - 'zero', - 'one', - 'one_half', - 'negative_one', - 'negative_one_half', - - 'map_color_0', - 'map_color_1', - 'map_color_2', - 'map_color_3', - {NAME: 'vertex_color_0', GUI_NAME: 'vertex color 0 / diffuse light'}, - {NAME: 'vertex_color_1', GUI_NAME: 'vertex color 1 / fade(perpendicular)'}, - 'scratch_color_0', - 'scratch_color_1', - 'constant_color_0', - 'constant_color_1', - - 'map_alpha_0', - 'map_alpha_1', - 'map_alpha_2', - 'map_alpha_3', - {NAME: 'vertex_alpha_0', GUI_NAME: 'vertex alpha 0 / diffuse light'}, - {NAME: 'vertex_alpha_1', GUI_NAME: 'vertex alpha 1 / fade(perpendicular)'}, - 'scratch_alpha_0', - 'scratch_alpha_1', - 'constant_alpha_0', - 'constant_alpha_1', - ) -sotr_color_outputs = ( - 'discard', - {NAME:'scratch_color_0', GUI_NAME: 'scratch color 0 / final color'}, - 'scratch_color_1', - 'vertex_color_0', - 'vertex_color_1', - 'map_color_0', - 'map_color_1', - 'map_color_2', - 'map_color_3', - ) -sotr_color_output_functions = ( - 'multiply', - 'dot_product', - ) - - -sotr_alpha_inputs = ( - 'zero', - 'one', - 'one_half', - 'negative_one', - 'negative_one_half', - - 'map_blue_0', - 'map_blue_1', - 'map_blue_2', - 'map_blue_3', - {NAME: 'vertex_blue_0', GUI_NAME: 'vertex blue 0 / diffuse light'}, - {NAME: 'vertex_blue_1', GUI_NAME: 'vertex blue 1 / fade(perpendicular)'}, - 'scratch_blue_0', - 'scratch_blue_1', - 'constant_blue_0', - 'constant_blue_1', - - 'map_alpha_0', - 'map_alpha_1', - 'map_alpha_2', - 'map_alpha_3', - {NAME: 'vertex_alpha_0', GUI_NAME: 'vertex alpha 0 / diffuse light'}, - {NAME: 'vertex_alpha_1', GUI_NAME: 'vertex alpha 1 / fade(perpendicular)'}, - 'scratch_alpha_0', - 'scratch_alpha_1', - 'constant_alpha_0', - 'constant_alpha_1', - ) -sotr_alpha_outputs = ( - 'discard', - {NAME: 'scratch_alpha_0', GUI_NAME: 'scratch alpha 0 / final alpha'}, - 'scratch_alpha_1', - {NAME: 'vertex_alpha_0', GUI_NAME: 'vertex alpha 0 / fog'}, - 'vertex_alpha_1', - 'map_alpha_0', - 'map_alpha_1', - 'map_alpha_2', - 'map_alpha_3', - ) - - -stage = Struct("stage", - Bool16("flags" , - "color_mux", - "alpha_mux", - "a_out_controls_color0_animation", - ), - Pad(2), - - SEnum16("color0_source", *function_names, - COMMENT="If set to 'none', color0 is calculated by blending the\n" - "two bounds below based on the 'color0 anim function'."), - SEnum16("color0_anim_function", *animation_functions), - float_sec("color0_anim_period"), - QStruct("color0_anim_lower_bound", INCLUDE=argb_float), - QStruct("color0_anim_upper_bound", INCLUDE=argb_float), - QStruct("color1", INCLUDE=argb_float), - - Struct('color', - Struct('input_A', - SEnum16('input', GUI_NAME='', *sotr_color_inputs), - SEnum16('mapped_to', *sotr_input_mappings), - ORIENT='h' - ), - Struct('input_B', - SEnum16('input', GUI_NAME='', *sotr_color_inputs), - SEnum16('mapped_to', *sotr_input_mappings), - ORIENT='h' - ), - Struct('input_C', - SEnum16('input', GUI_NAME='', *sotr_color_inputs), - SEnum16('mapped_to', *sotr_input_mappings), - ORIENT='h' - ), - Struct('input_D', - SEnum16('input', GUI_NAME='', *sotr_color_inputs), - SEnum16('mapped_to', *sotr_input_mappings), - ORIENT='h' - ), - - Struct('output_AB', - SEnum16('output', GUI_NAME='', *sotr_color_outputs), - SEnum16('function', *sotr_color_output_functions), - ORIENT='h' - ), - Struct('output_CD', - SEnum16('output', GUI_NAME='', *sotr_color_outputs), - SEnum16('function', *sotr_color_output_functions), - ORIENT='h' - ), - SEnum16('output_AB_CD_mux_sum', *sotr_color_outputs), - SEnum16('output_mapping', *sotr_output_mappings) - ), - - Struct('alpha', - Struct('input_A', - SEnum16('input', GUI_NAME='', *sotr_alpha_inputs), - SEnum16('mapped_to', *sotr_input_mappings), - ORIENT='h' - ), - Struct('input_B', - SEnum16('input', GUI_NAME='', *sotr_alpha_inputs), - SEnum16('mapped_to', *sotr_input_mappings), - ORIENT='h' - ), - Struct('input_C', - SEnum16('input', GUI_NAME='', *sotr_alpha_inputs), - SEnum16('mapped_to', *sotr_input_mappings), - ORIENT='h' - ), - Struct('input_D', - SEnum16('input', GUI_NAME='', *sotr_alpha_inputs), - SEnum16('mapped_to', *sotr_input_mappings), - ORIENT='h' - ), - - SEnum16('output_AB', *sotr_alpha_outputs), - SEnum16('output_CD', *sotr_alpha_outputs), - SEnum16('output_AB_CD_mux_sum', *sotr_alpha_outputs), - SEnum16('output_mapping', *sotr_output_mappings) - ), - - SIZE=112 - ) - -map = Struct("map", - Bool16("flags" , - "unfiltered", - "u_clamped", - "v_clamped", - ), - Pad(2), - #shader_transformations - Float("map_u_scale"), - Float("map_v_scale"), - Float("map_u_offset"), - Float("map_v_offset"), - float_deg("map_rotation"), # degrees - float_zero_to_one("map_bias"), - dependency("bitmap", "bitm"), - - #shader animations - Struct("u_animation", INCLUDE=anim_src_func_per_pha_sca), - Struct("v_animation", INCLUDE=anim_src_func_per_pha_sca), - Struct("rotation_animation", INCLUDE=anim_src_func_per_pha_sca_rot), - - QStruct("rotation_center", INCLUDE=xy_float), - SIZE=100, - ) - -sotr_attrs = Struct("sotr_attrs", - #Generic Transparent Shader - Struct("generic_transparent_shader", - UInt8("numeric_counter_limit", MIN=0, MAX=255, SIDETIP="[0,255]"), - Bool8("flags", *trans_shdr_properties), - SEnum16("first_map_type", *trans_shdr_first_map_type), - SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), - SEnum16("framebuffer_fade_mode", *render_fade_mode), - SEnum16("framebuffer_fade_source", *function_outputs), - Pad(2), - ), - - #Lens Flare - float_wu("lens_flare_spacing"), # world units - dependency("lens_flare", "lens"), - reflexive("extra_layers", extra_layers_block, 4, - DYN_NAME_PATH='.filepath'), - reflexive("maps", map, 4, DYN_NAME_PATH='.bitmap.filepath'), - reflexive("stages", stage, 7), - SIZE=68 - ) - -sotr_body = Struct("tagdata", - shdr_attrs, - sotr_attrs, - - SIZE=108, - ) - - -def get(): - return sotr_def - -sotr_def = TagDef("sotr", - blam_header("sotr"), - sotr_body, - - ext=".shader_transparent_generic", endian=">", tag_cls=ShdrTag, - ) +from ...hek.defs.sotr import * diff --git a/reclaimer/mcc_hek/defs/spla.py b/reclaimer/mcc_hek/defs/spla.py index a9828cdc..b05fef80 100644 --- a/reclaimer/mcc_hek/defs/spla.py +++ b/reclaimer/mcc_hek/defs/spla.py @@ -7,70 +7,4 @@ # See LICENSE for more information. # -from .shdr import * -from .objs.shdr import ShdrTag -from supyr_struct.defs.tag_def import TagDef - -noise_map = Struct("noise_map", - FlUInt16("unknown0", VISIBLE=False), - FlUInt16("unknown1", VISIBLE=False), - float_sec("animation_period"), - QStruct("animation_direction", INCLUDE=ijk_float), - Float("noise_map_scale"), - dependency("noise_map", "bitm"), - Pad(32), - ) - -spla_attrs = Struct("spla_attrs", - Pad(4), - #Intensity - Struct("intensity", - SEnum16("source", *function_outputs), - Pad(2), - Float("exponent"), - ), - - #Offset - Struct("offset", - SEnum16("source", *function_outputs), - Pad(2), - float_wu("amount"), - Float("exponent"), - ), - - Pad(32), - - #Color - Struct("color", - float_zero_to_one("perpendicular_brightness"), - QStruct("perpendicular_tint_color", INCLUDE=rgb_float), - float_zero_to_one("parallel_brightness"), - QStruct("parallel_tint_color", INCLUDE=rgb_float), - SEnum16("tint_color_source", *function_names), - ), - - Pad(58), - #Primary Noise Map - Struct("primary_noise_map", INCLUDE=noise_map), - - #Secondary Noise Map - Struct("secondary_noise_map", INCLUDE=noise_map), - SIZE=292 - ) - -spla_body = Struct("tagdata", - shdr_attrs, - spla_attrs, - SIZE=332, - ) - - -def get(): - return spla_def - -spla_def = TagDef("spla", - blam_header('spla'), - spla_body, - - ext=".shader_transparent_plasma", endian=">", tag_cls=ShdrTag - ) +from ...hek.defs.spla import * diff --git a/reclaimer/mcc_hek/defs/ssce.py b/reclaimer/mcc_hek/defs/ssce.py index 78591bd8..5619d014 100644 --- a/reclaimer/mcc_hek/defs/ssce.py +++ b/reclaimer/mcc_hek/defs/ssce.py @@ -7,22 +7,18 @@ # See LICENSE for more information. # +from ...hek.defs.ssce import * + +#import and use the mcc obje attrs from .obje import * -from .objs.obje import ObjeTag -from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant # replace the object_type enum one that uses # the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(11)) - ) - -ssce_body = Struct("tagdata", - obje_attrs, - SIZE=508, - ) +obje_attrs = dict(obje_attrs) +obje_attrs[0] = dict(obje_attrs[0], DEFAULT=11) +ssce_body = dict(ssce_body) +ssce_body[0] = obje_attrs def get(): return ssce_def diff --git a/reclaimer/mcc_hek/defs/str_.py b/reclaimer/mcc_hek/defs/str_.py index 6d449708..6a1411ae 100644 --- a/reclaimer/mcc_hek/defs/str_.py +++ b/reclaimer/mcc_hek/defs/str_.py @@ -7,25 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.str_ import Str_Tag -from supyr_struct.defs.tag_def import TagDef - -string_data_struct = rawtext_ref("string", StrLatin1, max_size=4096) - -str__body = Struct("tagdata", - reflexive("strings", string_data_struct, 32767, widget=TextFrame, - DYN_NAME_PATH='.data'), - SIZE=12, - ) - - -def get(): - return str__def - -str__def = TagDef("str#", - blam_header('str#'), - str__body, - - ext=".string_list", endian=">", tag_cls=Str_Tag - ) +from ...hek.defs.str_ import * diff --git a/reclaimer/mcc_hek/defs/swat.py b/reclaimer/mcc_hek/defs/swat.py index 4550259d..b35bb169 100644 --- a/reclaimer/mcc_hek/defs/swat.py +++ b/reclaimer/mcc_hek/defs/swat.py @@ -7,74 +7,4 @@ # See LICENSE for more information. # -from math import pi - -from .shdr import * -from .objs.shdr import ShdrTag -from supyr_struct.defs.tag_def import TagDef - -ripple = Struct("ripple", - Pad(4), - float_zero_to_one("contribution_factor"), - Pad(32), - Float("animation_angle", - MIN=0.0, MAX=2*pi, UNIT_SCALE=180/pi, SIDETIP="[0,360]"), # radians - Float("animation_velocity", UNIT_SCALE=per_sec_unit_scale), - Struct("map_offset", INCLUDE=ij_float), - UInt16("map_repeats"), - UInt16("map_index"), - SIZE=76 - ) - - -swat_attrs = Struct("swat_attrs", - #Water Shader Properties - Struct("water_shader", - Bool16("flags", - "base_map_alpha_modulates_reflection", - "base_map_color_modulates_background", - "atmospheric_fog", - "draw_before_fog", - ), - Pad(34), - dependency("base_map", "bitm"), - Pad(16), - float_zero_to_one("perpendicular_brightness"), - Struct("perpendicular_tint_color", INCLUDE=rgb_float), - float_zero_to_one("parallel_brightness"), - Struct("parallel_tint_color", INCLUDE=rgb_float), - Pad(16), - dependency("reflection_map", "bitm"), - - Pad(16), - Float("ripple_animation_angle", - MIN=0.0, MAX=2*pi, UNIT_SCALE=180/pi, SIDETIP="[0,360]"), # radians - Float("ripple_animation_velocity", UNIT_SCALE=per_sec_unit_scale), - Float("ripple_scale"), - dependency("ripple_maps", "bitm"), - UInt16("ripple_mipmap_levels"), - Pad(2), - float_zero_to_one("ripple_mipmap_fade_factor"), - Float("ripple_mipmap_detail_bias"), - ), - - Pad(64), - reflexive("ripples", ripple, 4), - SIZE=280 - ) - -swat_body = Struct("tagdata", - shdr_attrs, - swat_attrs, - SIZE=320, - ) - -def get(): - return swat_def - -swat_def = TagDef("swat", - blam_header('swat', 2), - swat_body, - - ext=".shader_transparent_water", endian=">", tag_cls=ShdrTag - ) +from ...hek.defs.swat import * diff --git a/reclaimer/mcc_hek/defs/tagc.py b/reclaimer/mcc_hek/defs/tagc.py index f5976efb..6e3f05af 100644 --- a/reclaimer/mcc_hek/defs/tagc.py +++ b/reclaimer/mcc_hek/defs/tagc.py @@ -7,28 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -tag_reference = Struct("tag_reference", - dependency("tag"), - SIZE=16 - ) - -tagc_body = Struct("tagdata", - reflexive("tag_references", tag_reference, 200, - DYN_NAME_PATH='.tag.filepath'), - SIZE=12, - ) - - -def get(): - return tagc_def - -tagc_def = TagDef("tagc", - blam_header('tagc'), - tagc_body, - - ext=".tag_collection", endian=">", tag_cls=HekTag - ) +from ...hek.defs.tagc import * diff --git a/reclaimer/mcc_hek/defs/trak.py b/reclaimer/mcc_hek/defs/trak.py index 5e5fbf4b..c5ed1bb6 100644 --- a/reclaimer/mcc_hek/defs/trak.py +++ b/reclaimer/mcc_hek/defs/trak.py @@ -7,30 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - - -control_point = Struct("control_point", - QStruct("position", INCLUDE=ijk_float), - QStruct("orientation", INCLUDE=ijkw_float), - SIZE=60, - ) - -trak_body = Struct("tagdata", - Pad(4), - reflexive("control_points", control_point, 16), - SIZE=48, - ) - - -def get(): - return trak_def - -trak_def = TagDef("trak", - blam_header('trak', 2), - trak_body, - - ext=".camera_track", endian=">", tag_cls=HekTag - ) +from ...hek.defs.trak import * diff --git a/reclaimer/mcc_hek/defs/udlg.py b/reclaimer/mcc_hek/defs/udlg.py index 0595c2bd..9a6059b1 100644 --- a/reclaimer/mcc_hek/defs/udlg.py +++ b/reclaimer/mcc_hek/defs/udlg.py @@ -7,241 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -def get(): return udlg_def - -def snd_dependency(name): - return dependency(name, "snd!") - -udlg_body = Struct("tagdata", - Pad(16), - Struct("idle", - snd_dependency("noncombat"), - snd_dependency("combat"), - snd_dependency("flee"), - Pad(48), - ), - - Struct("involuntary", - snd_dependency("pain_body_minor"), - snd_dependency("pain_body_maior"), - snd_dependency("pain_shield"), - snd_dependency("pain_falling"), - snd_dependency("scream_fear"), - snd_dependency("scream_pain"), - snd_dependency("maimed_limb"), - snd_dependency("maimed_head"), - snd_dependency("death_quiet"), - snd_dependency("death_violent"), - snd_dependency("death_falling"), - snd_dependency("death_agonizing"), - snd_dependency("death_instant"), - snd_dependency("death_flying"), - Pad(16), - ), - - Struct("hurting_people", - snd_dependency("damaged_friend"), - snd_dependency("damaged_friend_player"), - snd_dependency("damaged_enemy"), - snd_dependency("damaged_enemy_cm"), - Pad(64), - ), - - Struct("being_hurt", - snd_dependency("hurt_friend"), - snd_dependency("hurt_friend_re"), - snd_dependency("hurt_friend_player"), - snd_dependency("hurt_enemy"), - snd_dependency("hurt_enemy_re"), - snd_dependency("hurt_enemy_cm"), - snd_dependency("hurt_enemy_bullet"), - snd_dependency("hurt_enemy_needler"), - snd_dependency("hurt_enemy_plasma"), - snd_dependency("hurt_enemy_sniper"), - snd_dependency("hurt_enemy_grenade"), - snd_dependency("hurt_enemy_explosion"), - snd_dependency("hurt_enemy_melee"), - snd_dependency("hurt_enemy_flame"), - snd_dependency("hurt_enemy_shotgun"), - snd_dependency("hurt_enemy_vehicle"), - snd_dependency("hurt_enemy_mounted_weapon"), - Pad(48), - ), - - Struct("killing_people", - snd_dependency("killed_friend"), - snd_dependency("killed_friend_cm"), - snd_dependency("killed_friend_player"), - snd_dependency("killed_friend_player_cm"), - snd_dependency("killed_enemy"), - snd_dependency("killed_enemy_cm"), - snd_dependency("killed_enemy_player"), - snd_dependency("killed_enemy_player_cm"), - snd_dependency("killed_enemy_covenant"), - snd_dependency("killed_enemy_covenant_cm"), - snd_dependency("killed_enemy_floodcombat"), - snd_dependency("killed_enemy_floodcombat_cm"), - snd_dependency("killed_enemy_floodcarrier"), - snd_dependency("killed_enemy_floodcarrier_cm"), - snd_dependency("killed_enemy_sentinel"), - snd_dependency("killed_enemy_sentinel_cm"), - - snd_dependency("killed_enemy_bullet"), - snd_dependency("killed_enemy_needler"), - snd_dependency("killed_enemy_plasma"), - snd_dependency("killed_enemy_sniper"), - snd_dependency("killed_enemy_grenade"), - snd_dependency("killed_enemy_explosion"), - snd_dependency("killed_enemy_melee"), - snd_dependency("killed_enemy_flame"), - snd_dependency("killed_enemy_shotgun"), - snd_dependency("killed_enemy_vehicle"), - snd_dependency("killed_enemy_mounted_weapon"), - snd_dependency("killing_spree"), - Pad(48), - ), - - Struct("player_kill_responses", - snd_dependency("player_kill_cm"), - snd_dependency("player_kill_bullet_cm"), - snd_dependency("player_kill_needler_cm"), - snd_dependency("player_kill_plasma_cm"), - snd_dependency("player_kill_sniper_cm"), - snd_dependency("anyone_kill_grenade_cm"), - snd_dependency("player_kill_explosion_cm"), - snd_dependency("player_kill_melee_cm"), - snd_dependency("player_kill_flame_cm"), - snd_dependency("player_kill_shotgun_cm"), - snd_dependency("player_kill_vehicle_cm"), - snd_dependency("player_kill_mounted_weapon_cm"), - snd_dependency("player_killing_spree_cm"), - Pad(48), - ), - - Struct("friends_dying", - snd_dependency("friend_died"), - snd_dependency("friend_player_died"), - snd_dependency("friend_killed_by_friend"), - snd_dependency("friend_killed_by_friendly_player"), - snd_dependency("friend_enemy"), - snd_dependency("friend_enemy_player"), - snd_dependency("friend_covenant"), - snd_dependency("friend_flood"), - snd_dependency("friend_sentinel"), - snd_dependency("friend_betrayed"), - Pad(32), - ), - - Struct("shouting", - snd_dependency("new_combat_alone"), - snd_dependency("new_enemy_recent_combat"), - snd_dependency("old_enemy_sighted"), - snd_dependency("unexpected_enemy"), - snd_dependency("dead_friend_found"), - snd_dependency("alliance_broken"), - snd_dependency("alliance_reformed"), - snd_dependency("grenade_throwing"), - snd_dependency("grenade_sighted"), - snd_dependency("grenade_startle"), - snd_dependency("grenade_danger_enemy"), - snd_dependency("grenade_danger_self"), - snd_dependency("grenade_danger_friend"), - Pad(32), - ), - - Struct("group_communication", - snd_dependency("new_combat_group_re"), - snd_dependency("new_combat_nearby_re"), - snd_dependency("alert_friend"), - snd_dependency("alert_friend_re"), - snd_dependency("alert_lost_contact"), - snd_dependency("alert_lost_contact_re"), - snd_dependency("blocked"), - snd_dependency("blocked_re"), - snd_dependency("search_start"), - snd_dependency("search_query"), - snd_dependency("search_query_re"), - snd_dependency("search_report"), - snd_dependency("search_abandon"), - snd_dependency("search_group_abandon"), - snd_dependency("group_uncover"), - snd_dependency("group_uncover_re"), - snd_dependency("advance"), - snd_dependency("advance_re"), - snd_dependency("retreat"), - snd_dependency("retreat_re"), - snd_dependency("cover"), - Pad(64), - ), - - Struct("actions", - snd_dependency("sighted_friend_player"), - snd_dependency("shooting"), - snd_dependency("shooting_vehicle"), - snd_dependency("shooting_berserk"), - snd_dependency("shooting_group"), - snd_dependency("shooting_traitor"), - snd_dependency("taunt"), - snd_dependency("taunt_re"), - snd_dependency("flee"), - snd_dependency("flee_re"), - snd_dependency("free_leader_died"), - snd_dependency("attempted_flee"), - snd_dependency("attempted_flee_re"), - snd_dependency("lost_contact"), - snd_dependency("hiding_finished"), - snd_dependency("vehicle_entry"), - snd_dependency("vehicle_exit"), - snd_dependency("vehicle_woohoo"), - snd_dependency("vehicle_scared"), - snd_dependency("vehicle_collision"), - snd_dependency("partially_sighted"), - snd_dependency("nothing_there"), - snd_dependency("pleading"), - Pad(96), - ), - - Struct("exclamations", - snd_dependency("surprise"), - snd_dependency("berserk"), - snd_dependency("melee_attack"), - snd_dependency("dive"), - snd_dependency("uncover_exclamation"), - snd_dependency("leap_attack"), - snd_dependency("resurrection"), - Pad(64), - ), - - Struct("post_combat_actions", - snd_dependency("celebration"), - snd_dependency("check_body_enemy"), - snd_dependency("check_body_friend"), - snd_dependency("shooting_dead_enemy"), - snd_dependency("shooting_dead_enemy_player"), - Pad(64), - ), - - Struct("post_combat_chatter", - snd_dependency("alone"), - snd_dependency("unscathed"), - snd_dependency("seriously_wounded"), - snd_dependency("seriously_wounded_re"), - snd_dependency("massacre"), - snd_dependency("massacre_re"), - snd_dependency("rout"), - snd_dependency("rout_re"), - ), - - SIZE=4112, - ) - -udlg_def = TagDef("udlg", - blam_header('udlg'), - udlg_body, - - ext=".dialogue", endian=">", tag_cls=HekTag - ) +from ...hek.defs.udlg import * diff --git a/reclaimer/mcc_hek/defs/ustr.py b/reclaimer/mcc_hek/defs/ustr.py index 585214bb..c012a53c 100644 --- a/reclaimer/mcc_hek/defs/ustr.py +++ b/reclaimer/mcc_hek/defs/ustr.py @@ -7,25 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.ustr import UstrTag -from supyr_struct.defs.tag_def import TagDef - -string_data_struct = rawtext_ref("string", FlStrUTF16, max_size=32768) - -ustr_body = Struct("tagdata", - reflexive("strings", string_data_struct, 32767, - DYN_NAME_PATH='.data'), - SIZE=12, - ) - - -def get(): - return ustr_def - -ustr_def = TagDef("ustr", - blam_header('ustr'), - ustr_body, - - ext=".unicode_string_list", endian=">", tag_cls=UstrTag - ) +from ...hek.defs.ustr import * diff --git a/reclaimer/mcc_hek/defs/vcky.py b/reclaimer/mcc_hek/defs/vcky.py index 347915de..4d56b87f 100644 --- a/reclaimer/mcc_hek/defs/vcky.py +++ b/reclaimer/mcc_hek/defs/vcky.py @@ -7,61 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -def get(): return vcky_def - -virtual_key = Struct("virtual_key", - SEnum16("keyboard_key", - {NAME:"one", GUI_NAME:"1"}, - {NAME:"two", GUI_NAME:"2"}, - {NAME:"three", GUI_NAME:"3"}, - {NAME:"four", GUI_NAME:"4"}, - {NAME:"five", GUI_NAME:"5"}, - {NAME:"six", GUI_NAME:"6"}, - {NAME:"seven", GUI_NAME:"7"}, - {NAME:"eight", GUI_NAME:"8"}, - {NAME:"nine", GUI_NAME:"9"}, - {NAME:"zero", GUI_NAME:"0"}, - "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", - "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", - "done", - "shift", - "capslock", - "symbols", - "backspace", - "left", - "right", - "space", - ), - SInt16("lowercase_character"), - SInt16("shift_character"), - SInt16("caps_character"), - SInt16("symbols_character"), - SInt16("shift_caps_character", GUI_NAME="shift+caps character"), - SInt16("shift_symbols_character", GUI_NAME="shift+symbols character"), - SInt16("caps_symbols_character", GUI_NAME="caps+symbols character"), - dependency("unselected_background_bitmap", "bitm"), - dependency("selected_background_bitmap", "bitm"), - dependency("active_background_bitmap", "bitm"), - dependency("sticky_background_bitmap", "bitm"), - SIZE=80 - ) - -vcky_body = Struct("tagdata", - dependency("display_font", "font"), - dependency("background_bitmap", "bitm"), - dependency("special_key_labels_string_list", "ustr"), - reflexive("virtual_keys", virtual_key, 44, - DYN_NAME_PATH='.keyboard_key.enum_name'), - SIZE=60, - ) - -vcky_def = TagDef("vcky", - blam_header('vcky', 2), - vcky_body, - - ext=".virtual_keyboard", endian=">", tag_cls=HekTag - ) +from ...hek.defs.vcky import * diff --git a/reclaimer/mcc_hek/defs/wind.py b/reclaimer/mcc_hek/defs/wind.py index f194b77d..721a2615 100644 --- a/reclaimer/mcc_hek/defs/wind.py +++ b/reclaimer/mcc_hek/defs/wind.py @@ -7,26 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -wind_body = Struct("tagdata", - from_to_wu("velocity"), - yp_float_rad("variation_area"), - Float("local_variation_weight"), - Float("local_variation_rate"), - Float("damping"), - SIZE=64, - ) - - -def get(): - return wind_def - -wind_def = TagDef("wind", - blam_header('wind'), - wind_body, - - ext=".wind", endian=">", tag_cls=HekTag - ) +from ...hek.defs.wind import * From 1bab7d9f1c19862661faba9c59cca77dbaa8189b Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Mon, 15 Jan 2024 00:22:37 -0600 Subject: [PATCH 05/51] Simplify MCC changes further --- reclaimer/enums.py | 124 ++-- reclaimer/hek/defs/font.py | 2 +- reclaimer/hek/defs/unit.py | 2 +- reclaimer/mcc_hek/defs/__init__.py | 2 +- reclaimer/mcc_hek/defs/actr.py | 262 +------- reclaimer/mcc_hek/defs/actv.py | 204 +----- reclaimer/mcc_hek/defs/antr.py | 297 +-------- reclaimer/mcc_hek/defs/bipd.py | 6 +- reclaimer/mcc_hek/defs/bitm.py | 339 ++-------- reclaimer/mcc_hek/defs/cdmg.py | 107 ++-- reclaimer/mcc_hek/defs/coll.py | 281 +-------- reclaimer/mcc_hek/defs/cont.py | 113 +--- reclaimer/mcc_hek/defs/ctrl.py | 17 +- reclaimer/mcc_hek/defs/deca.py | 107 +--- reclaimer/mcc_hek/defs/effe.py | 166 +---- reclaimer/mcc_hek/defs/eqip.py | 26 +- reclaimer/mcc_hek/defs/font.py | 53 +- reclaimer/mcc_hek/defs/garb.py | 15 +- reclaimer/mcc_hek/defs/grhi.py | 233 +------ reclaimer/mcc_hek/defs/hudg.py | 214 +------ reclaimer/mcc_hek/defs/jpt_.py | 146 +---- reclaimer/mcc_hek/defs/lens.py | 124 +--- reclaimer/mcc_hek/defs/lifi.py | 16 +- reclaimer/mcc_hek/defs/lsnd.py | 87 +-- reclaimer/mcc_hek/defs/mach.py | 17 +- reclaimer/mcc_hek/defs/matg.py | 369 +---------- reclaimer/mcc_hek/defs/obje.py | 145 +---- reclaimer/mcc_hek/defs/plac.py | 14 +- reclaimer/mcc_hek/defs/proj.py | 132 +--- reclaimer/mcc_hek/defs/scen.py | 10 +- reclaimer/mcc_hek/defs/scex.py | 38 +- reclaimer/mcc_hek/defs/schi.py | 69 +-- reclaimer/mcc_hek/defs/scnr.py | 957 ++--------------------------- reclaimer/mcc_hek/defs/senv.py | 219 +------ reclaimer/mcc_hek/defs/snd_.py | 156 +---- reclaimer/mcc_hek/defs/soso.py | 152 +---- reclaimer/mcc_hek/defs/ssce.py | 13 +- reclaimer/mcc_hek/defs/tagc.py | 2 +- reclaimer/mcc_hek/defs/unhi.py | 169 +---- reclaimer/mcc_hek/defs/unit.py | 198 +----- reclaimer/mcc_hek/defs/vehi.py | 105 +--- reclaimer/mcc_hek/defs/weap.py | 338 ++-------- reclaimer/mcc_hek/defs/wphi.py | 386 +----------- 43 files changed, 674 insertions(+), 5758 deletions(-) diff --git a/reclaimer/enums.py b/reclaimer/enums.py index eda27995..9721e9c1 100644 --- a/reclaimer/enums.py +++ b/reclaimer/enums.py @@ -1227,52 +1227,6 @@ def TEST_PRINT_HSC_BUILT_IN_FUNCTIONS(): 'throw-overheated', 'overheating', 'overheating-again', 'enter', 'exit-empty', 'exit-full', 'o-h-exit', 'o-h-s-enter' ) -mcc_actor_types = ( # Used to determine score for killing different actor types. - "brute", - "grunt", - "jackal", - "skirmisher", - "marine", - "spartan", - "drone", - "hunter", - "flood infection", - "flood carrier", - "flood combat", - "flood pure", - "sentinel", - "elite", - "huragok", - "mule", - "turret", - "mongoose", - "warthog", - "scorpion", - "hornet", - "pelican", - "revenant", - "seraph", - "shade", - "watchtower", - "ghost", - "chopper", - "prowler", - "wraith", - "banshee", - "phantom", - "scarab", - "guntower", - "spirit", - "broadsword", - "mammoth", - "lich", - "mantis", - "wasp", - "phaeton", - "watcher", - "knight", - "crawler" - ) unit_damage_animation_names = [] for typ in ("s-ping", "h-ping", "s-kill", "h-kill"): @@ -1292,8 +1246,7 @@ def TEST_PRINT_HSC_BUILT_IN_FUNCTIONS(): #Shared Enumerator options grenade_types_os = ( - 'human_frag', - 'covenant_plasma', + *grenade_types, 'custom_2', 'custom_3', ) @@ -1312,3 +1265,78 @@ def TEST_PRINT_HSC_BUILT_IN_FUNCTIONS(): 'searching', 'fleeing' ) + +# MCC Shared Enumerator options +grenade_types_mcc = grenade_types_os # they're the same +hud_anchors_mcc = ( + "from_parent", + *hud_anchors, + "top_center", + "bottom_center", + "left_center", + "right_center", + ) +actor_types_mcc = ( # Used to determine score for killing different actor types. + "brute", + "grunt", + "jackal", + "skirmisher", + "marine", + "spartan", + "bugger", + "hunter", + "flood_infection", + "flood_carrier", + "flood_combat", + "flood_pure", + "sentinel", + "elite", + "engineer", + "mule", + "turret", + "mongoose", + "warthog", + "scorpion", + "hornet", + "pelican", + "revenant", + "seraph", + "shade", + "watchtower", + "ghost", + "chopper", + "mauler", + "wraith", + "banshee", + "phantom", + "scarab", + "guntower", + "tuning_fork", + "broadsword", + "mammoth", + "lich", + "mantis", + "wasp", + "phaeton", + "bishop", + "knight", + "pawn" + ) +actor_classes_mcc = ( + "infantry", + "leader", + "hero", + "specialist", + "light_vehicle", + "heavy_vehicle", + "giant_vehicle", + "standard_vehicle" + ) +fp_animation_names_mcc = ( + *fp_animation_names, + 'reload-empty-2', 'reload-full-2', + ) +weapon_types_mcc = ( + *weapon_types, + "rocket_launcher" + ) \ No newline at end of file diff --git a/reclaimer/hek/defs/font.py b/reclaimer/hek/defs/font.py index cc4e50a7..28cc3605 100644 --- a/reclaimer/hek/defs/font.py +++ b/reclaimer/hek/defs/font.py @@ -40,7 +40,7 @@ def get(): return font_def font_body = Struct("tagdata", SInt32("flags"), SInt16("ascending_height"), - SInt16("decending_height"), + SInt16("descending_height"), SInt16("leading_height"), SInt16("leading_width"), Pad(36), diff --git a/reclaimer/hek/defs/unit.py b/reclaimer/hek/defs/unit.py index 949e9a98..d20617eb 100644 --- a/reclaimer/hek/defs/unit.py +++ b/reclaimer/hek/defs/unit.py @@ -175,7 +175,7 @@ def get(): Pad(2), Struct("mcc_additions", # replaced with opensauce unit extension in os_v4 - SEnum16("mcc_scoring_type", TOOLTIP="Used to determine score in MCC", *mcc_actor_types), + SEnum16("mcc_scoring_type", TOOLTIP="Used to determine score in MCC", *actor_types_mcc), Pad(10), ), reflexive("new_hud_interfaces", new_hud_interface, 2, diff --git a/reclaimer/mcc_hek/defs/__init__.py b/reclaimer/mcc_hek/defs/__init__.py index 2f706142..50ea1ec1 100644 --- a/reclaimer/mcc_hek/defs/__init__.py +++ b/reclaimer/mcc_hek/defs/__init__.py @@ -19,4 +19,4 @@ "snde", "lsnd", "soso", "sotr", "Soul", "spla", "ssce", "str_", "swat", "tagc", "trak", "udlg", "unhi", "unit", "ustr", "vcky", "vehi", "weap", "wind", "wphi", - ) + ) \ No newline at end of file diff --git a/reclaimer/mcc_hek/defs/actr.py b/reclaimer/mcc_hek/defs/actr.py index 2cde09bd..71f1409c 100644 --- a/reclaimer/mcc_hek/defs/actr.py +++ b/reclaimer/mcc_hek/defs/actr.py @@ -7,264 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.actr import ActrTag -from supyr_struct.defs.tag_def import TagDef - -danger_triggers = ( - "never", - "visible", - "shooting", - "shooting_near_us", - "damaging_us", - "unused1", - "unused2", - "unused3", - "unused4", - "unused5", - ) - -actr_body = Struct("tagdata", - Bool32('flags', - "can_see_in_darkness", - "sneak_uncovering_target", - "sneak_uncovering_pursuit_location", - "unused1", - "shoot_at_targets_last_location", - "try_to_stay_still_when_crouched", - "crouch_when_not_in_combat", - "crouch_when_guarding", - "unused2", - "must_crouch_to_shoot", - "panic_when_surprised", - "always_charge_at_enemies", - "gets_in_vehicles_with_player", - "starts_firing_before_aligned", - "standing_must_move_forward", - "crouching_must_move_forward", - "defensive_crouch_while_charging", - "use_stalking_behavior", - "stalking_freeze_if_exposed", - "always_berserk_in_attacking_mode", - "berserking_uses_panicked_movement", - "flying", - "panicked_by_unopposable_enemy", - "crouch_when_hiding_from_unopposable", - "always_charge_in_attacking_mode", - "dive_off_ledges", - "swarm", - "suicidal_melee_attack", - "cannot_move_while_crouching", - "fixed_crouch_facing", - "crouch_when_in_line_of_fire", - "avoid_friends_line_of_fire" - ), - Bool32('more_flags', - "avoid_all_enemy_attack_vectors", - "must_stand_to_fire", - "must_stop_to_fire", - "disallow_vehicle_combat", - "pathfinding_ignores_danger", - "panic_in_groups", - "no_corpse_shooting" - ), - - Pad(12), - SEnum16("type", *actor_types), - - Pad(2), - Struct("perception", - float_wu("max_vision_distance"), # world units - float_rad("central_vision_angle"), # radians - float_rad("max_vision_angle"), # radians - - Pad(4), - float_rad("peripheral_vision_angle"), # radians - float_wu("peripheral_distance"), # world units - - Pad(4), - QStruct("standing_gun_offset", INCLUDE=ijk_float), - QStruct("crouching_gun_offset", INCLUDE=ijk_float), - float_wu("hearing_distance"), # world units - float_zero_to_one("notice_projectile_chance"), - float_zero_to_one("notice_vehicle_chance"), - - Pad(8), - float_sec("combat_perception_time", - UNIT_SCALE=sec_unit_scale), # seconds - float_sec("guard_perception_time", - UNIT_SCALE=sec_unit_scale), # seconds - float_sec("non_combat_perception_time", - UNIT_SCALE=sec_unit_scale), # seconds - - float_sec("inv_combat_perception_time", - UNIT_SCALE=sec_unit_scale, VISIBLE=False), - float_sec("inv_guard_perception_time", - UNIT_SCALE=sec_unit_scale, VISIBLE=False), - float_sec("inv_non_combat_perception_time", - UNIT_SCALE=sec_unit_scale, VISIBLE=False), - ), - - Pad(8), - Struct("movement", - float_zero_to_one("dive_into_cover_chance"), - float_zero_to_one("emerge_from_cover_chance"), - float_zero_to_one("dive_from_grenade_chance"), - float_wu("pathfinding_radius"), # world units - float_zero_to_one("glass_ignorance_chance"), - float_wu("stationary_movement_dist"), # world units - float_wu("free_flying_sidestep"), # world units - float_rad("begin_moving_angle"), # radians - ), - - Pad(4), - Struct("looking", - yp_float_rad("maximum_aiming_deviation"), # radians - yp_float_rad("maximum_looking_deviation"), # radians - float_rad("noncombat_look_delta_l"), # radians - float_rad("noncombat_look_delta_r"), # radians - float_rad("combat_look_delta_l"), # radians - float_rad("combat_look_delta_r"), # radians - from_to_rad("idle_aiming_range"), # radians - from_to_rad("idle_looking_range"), # radians - QStruct("event_look_time_modifier", INCLUDE=from_to), - from_to_sec("noncombat_idle_facing"), # seconds - from_to_sec("noncombat_idle_aiming"), # seconds - from_to_sec("noncombat_idle_looking"), # seconds - from_to_sec("guard_idle_facing"), # seconds - from_to_sec("guard_idle_aiming"), # seconds - from_to_sec("guard_idle_looking"), # seconds - from_to_sec("combat_idle_facing"), # seconds - from_to_sec("combat_idle_aiming"), # seconds - from_to_sec("combat_idle_looking"), # seconds - Pad(8), - from_to_neg_one_to_one("cosine_maximum_aiming_deviation", VISIBLE=False), - from_to_neg_one_to_one("cosine_maximum_looking_deviation", VISIBLE=False), - - dependency("DO_NOT_USE_weapon", "weap"), - - Pad(268), - dependency("DO_NOT_USE_projectile", "proj") - ), - - Struct("unopposable", - SEnum16("unreachable_danger_trigger", *danger_triggers), - SEnum16("vehicle_danger_trigger", *danger_triggers), - SEnum16("player_danger_trigger", *danger_triggers), - - Pad(2), - from_to_sec("danger_trigger_time"), # seconds - SInt16("friends_killed_trigger"), - SInt16("friends_retreating_trigger"), - - Pad(12), - from_to_sec("retreat_time"), # seconds - ), - - Pad(8), - Struct("panic", - from_to_sec("cowering_time"), # seconds - float_zero_to_one("friend_killed_panic_chance"), - SEnum16("leader_type", *actor_types), - - Pad(2), - float_zero_to_one("leader_killed_panic_chance"), - float_zero_to_one("panic_damage_threshold"), - float_wu("surprise_distance"), # world units - ), - - Pad(28), - Struct("defensive", - from_to_sec("hide_behind_cover_time"), # seconds - float_sec("hide_target_not_visible_time", - UNIT_SCALE=sec_unit_scale), # seconds - float_zero_to_one("hide_shield_fraction"), - float_zero_to_one("attack_shield_fraction"), - float_zero_to_one("pursue_shield_fraction"), - - Pad(16), - SEnum16("defensive_crouch_type", - "never", - "danger", - "low_shields", - "hide_behind_shield", - "any_target", - "flood_shamble" - ), - - Pad(2), - Float("attacking_crouch_threshold"), - Float("defending_crouch_threshold"), - float_sec("mim_stand_time", UNIT_SCALE=sec_unit_scale), # seconds - float_sec("mim_crouch_time", UNIT_SCALE=sec_unit_scale), # seconds - Float("defending_hide_time_modifier"), - Float("attacking_evasion_threshold"), - Float("defending_evasion_threshold"), - float_zero_to_one("evasion_seek_cover_chance"), - float_sec("evasion_delay_time", UNIT_SCALE=sec_unit_scale), # seconds - float_wu("max_seek_cover_distance"), # world units - float_zero_to_one("cover_damage_threshold"), - float_sec("stalking_discovery_time", - UNIT_SCALE=sec_unit_scale), # seconds - float_wu("stalking_max_distance"), # world units - float_rad("stationary_facing_angle"), # radians - float_sec("change_facing_stand_time", - UNIT_SCALE=sec_unit_scale), # seconds - ), - - Pad(4), - Struct("pursuit", - from_to_sec("uncover_delay_time"), # seconds - from_to_sec("target_search_time"), # seconds - from_to_sec("pursuit_position_time"), # seconds - SInt16("coordinated_position_count", MIN=0), - SInt16("normal_position_count", MIN=0), - ), - - Pad(32), - QStruct("berserk", - float_sec("melee_attack_delay", UNIT_SCALE=sec_unit_scale), # seconds - float_wu("melee_fudge_factor"), # world units - float_sec("melee_charge_time", UNIT_SCALE=sec_unit_scale), # seconds - float_wu("melee_leap_range_lower_bound"), # world units - float_wu("melee_leap_range_upper_bound"), # world units - Float("melee_leap_velocity", SIDETIP="world units/tick", - UNIT_SCALE=per_sec_unit_scale), # world units/tick - float_zero_to_one("melee_leap_chance"), - float_zero_to_one("melee_leap_ballistic"), - float_zero_to_one("berserk_damage_amount"), - float_zero_to_one("berserk_damage_threshold"), - float_wu("berserk_proximity"), # world units - float_wu("suicide_sensing_dist"), # world units - float_zero_to_one("berserk_grenade_chance"), - ), - - Pad(12), - Struct("firing_positions", - from_to_sec("guard_position_time"), # seconds - from_to_sec("combat_position_time"), # seconds - Float("old_position_avoid_dist"), # world units - Float("friend_avoid_dist"), # world units - ), - - Pad(40), - Struct("communication", - from_to_sec("noncombat_idle_speech_time"), # seconds - from_to_sec("combat_idle_speech_time"), # seconds - - Pad(176), - dependency("DO_NOT_USE_major_upgrade", "actr"), - ), - SIZE=1272 - ) - - -def get(): - return actr_def - -actr_def = TagDef("actr", - blam_header('actr', 2), - actr_body, - - ext=".actor", endian=">", tag_cls=ActrTag - ) +from ...hek.defs.actr import * diff --git a/reclaimer/mcc_hek/defs/actv.py b/reclaimer/mcc_hek/defs/actv.py index ac93cdaf..20205b0d 100644 --- a/reclaimer/mcc_hek/defs/actv.py +++ b/reclaimer/mcc_hek/defs/actv.py @@ -7,194 +7,32 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef +from ...hek.defs.actv import * +from supyr_struct.util import desc_variant -change_color = Struct("change_color", - QStruct("color_lower_bound", INCLUDE=rgb_float), - QStruct("color_upper_bound", INCLUDE=rgb_float), - SIZE=32 +actv_grenades = desc_variant(actv_grenades, + ("grenade_type", SEnum16("grenade_type", *grenade_types_mcc)), ) - -actv_grenades = Struct("grenades", - Pad(8), - SEnum16("grenade_type", *grenade_types_mcc), - SEnum16("trajectory_type", - "toss", - "lob", - "bounce", - ), - SEnum16("grenade_stimulus", - "never", - "visible_target", - "seek_cover", - ), - SInt16("minimum_enemy_count"), - float_wu("enemy_radius"), - - Pad(4), - float_wu_sec("grenade_velocity", UNIT_SCALE=per_sec_unit_scale), - from_to_wu("grenade_ranges"), - float_wu("collateral_damage_radius"), - float_zero_to_one("grenade_chance"), - float_sec("grenade_check_time", UNIT_SCALE=sec_unit_scale), - float_sec("encounter_grenade_timeout", UNIT_SCALE=sec_unit_scale) +metagame_scoring = Struct("metagame_scoring", + SEnum16("metagame_type", *actor_types_mcc), + SEnum16("metagame_class", *actor_classes_mcc), + ORIENT="H", COMMENT="Used to determine score in MCC", ) - -actv_body = Struct("tagdata", - Bool32('flags', - "can_shoot_while_flying", - "interpolate_color_in_hsv", - "has_unlimited_grenades", - "movement_switching_try_to_stay_with_friends", - "active_camouflage", - "super_active_camouflage", - "cannot_use_ranged_weapons", - "prefer_passenger_seat", - ), - dependency("actor_definition", "actr"), - dependency("unit", valid_units), - dependency("major_variant", "actv"), - SEnum16("metagame_type", TOOLTIP="Used to determine score in MCC", *mcc_actor_types), - SEnum16("metagame_class", TOOLTIP="Used to determine score in MCC", *mcc_actor_classes), - - #Movement switching - Struct("movement_switching", - Pad(20), - SEnum16("movement_type", - "always_run", - "always_crouch", - "switch_types", - ), - Pad(2), - float_zero_to_one("initial_crouch_chance"), - from_to_sec("crouch_time", UNIT_SCALE=sec_unit_scale), # seconds - from_to_sec("run_time", UNIT_SCALE=sec_unit_scale) # seconds - ), - - #Ranged combat - Struct("ranged_combat", - dependency("weapon", "weap"), - float_wu("maximum_firing_distance"), - Float("rate_of_fire", UNIT_SCALE=per_sec_unit_scale), # rounds/sec - float_rad("projectile_error"), # radians - from_to_sec("first_burst_delay_time", - UNIT_SCALE=sec_unit_scale), # seconds - Float("new_target_firing_pattern_time", - UNIT_SCALE=sec_unit_scale), # seconds - Float("surprise_delay_time", UNIT_SCALE=sec_unit_scale), # seconds - Float("surprise_fire_wildly_time", - UNIT_SCALE=sec_unit_scale), # seconds - float_zero_to_one("death_fire_wildly_chance"), - float_sec("death_fire_wildly_time", - UNIT_SCALE=sec_unit_scale), # seconds - from_to_wu("desired_combat_range"), - QStruct("custom_stand_gun_offset", INCLUDE=ijk_float), - QStruct("custom_crouch_gun_offset", INCLUDE=ijk_float), - float_zero_to_one("target_tracking"), - float_zero_to_one("target_leading"), - Float("weapon_damage_modifier"), - Float("damage_per_second", UNIT_SCALE=per_sec_unit_scale), # seconds - ), - - #Burst geometry - Struct("burst_geometry", - float_wu("burst_origin_radius"), - float_rad("burst_origin_angle"), # radians - from_to_wu("burst_return_length"), - float_rad("burst_return_angle"), # radians - from_to_sec("burst_duration", UNIT_SCALE=sec_unit_scale), # seconds - from_to_sec("burst_separation"), - Float("burst_angular_velocity", SIDETIP="degrees/sec", - UNIT_SCALE=irad_per_sec_unit_scale), # radians/second - Pad(4), - float_zero_to_one("special_damage_modifier"), - float_rad("special_projectile_error") # radians - ), - - #Firing patterns" - Struct("firing_patterns", - Float("new_target_burst_duration"), - Float("new_target_burst_separation"), - Float("new_target_rate_of_fire", UNIT_SCALE=per_sec_unit_scale), - Float("new_target_projectile_error"), - - Pad(8), - Float("moving_burst_duration"), - Float("moving_burst_separation"), - Float("moving_rate_of_fire", UNIT_SCALE=per_sec_unit_scale), - Float("moving_projectile_error"), - - Pad(8), - Float("berserk_burst_duration"), - Float("berserk_burst_separation"), - Float("berserk_rate_of_fire", UNIT_SCALE=per_sec_unit_scale), - Float("berserk_projectile_error") - ), - - #Special-case firing patterns - Struct("special_case_firing_patterns", - Pad(8), - Float("super_ballistic_range"), - Float("bombardment_range"), - Float("modified_vision_range"), - SEnum16("special_fire_mode", - "none", - "overcharge", - "secondary_trigger", - ), - SEnum16("special_fire_situation", - "never", - "enemy_visible", - "enemy_out_of_sight", - "strafing", - ), - float_zero_to_one("special_fire_chance"), - float_sec("special_fire_delay", UNIT_SCALE=sec_unit_scale) - ), - - #Berserking and melee - Struct("berserking_and_melee", - float_wu("melee_range"), - float_wu("melee_abort_range"), - from_to_wu("berserk_firing_ranges", INCLUDE=from_to), - float_wu("berserk_melee_range"), - float_wu("berserk_melee_abort_range") - ), - - #Grenades - actv_grenades, - - #Items - Struct("items", - Pad(20), - dependency("equipment", "eqip"), - QStruct("grenade_count", - SInt16("from", GUI_NAME=""), SInt16("to"), ORIENT='h' - ), - float_zero_to_one("dont_drop_grenades_chance"), - QStruct("drop_weapon_loaded", INCLUDE=from_to), - QStruct("drop_weapon_ammo", - SInt16("from", GUI_NAME=""), - SInt16("to"), ORIENT='h' - ) - ), - - #Unit properties - Struct("unit_properties", - Pad(28), - Float("body_vitality"), - Float("shield_vitality"), - float_wu("shield_sapping_radius"), - SInt16("forced_shader_permutation"), - ), - - Pad(30), - reflexive("change_colors", change_color, 4), - SIZE=568 +movement_switching_descs = [ + desc for desc in actv_body.values() + if isinstance(desc, dict) and desc.get("NAME") == "movement_switching" + ] +if not movement_switching_descs: + raise ValueError("Could not locate descriptor 'movement_switching' in actv_body") +movement_switching = desc_variant(movement_switching_descs[0], + ("pad_0", Pad(20)), ) +actv_body = desc_variant(actv_body, + ("grenades", actv_grenades), + ("mcc_scoring_type", metagame_scoring), + ("movement_switching", movement_switching), + ) def get(): return actv_def diff --git a/reclaimer/mcc_hek/defs/antr.py b/reclaimer/mcc_hek/defs/antr.py index 8e01644a..1daed4eb 100644 --- a/reclaimer/mcc_hek/defs/antr.py +++ b/reclaimer/mcc_hek/defs/antr.py @@ -7,292 +7,35 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.antr import AntrTag -from supyr_struct.defs.tag_def import TagDef +from ...hek.defs.antr import * +from supyr_struct.util import desc_variant -frame_info_dxdy_node = QStruct("frame_info_node", - Float("dx"), - Float("dy"), ORIENT='h' +unit_weapon_desc = desc_variant(unit_weapon_desc, + ("ik_points", reflexive("ik_points", ik_point_desc, 8, DYN_NAME_PATH=".marker")), + ("weapon_types", reflexive("weapon_types", weapon_types_desc, 8, DYN_NAME_PATH=".label")), ) - -frame_info_dxdydyaw_node = QStruct("frame_info_node", - Float("dx"), - Float("dy"), - Float("dyaw"), ORIENT='h' - ) - -frame_info_dxdydzdyaw_node = QStruct("frame_info_node", - Float("dx"), - Float("dy"), - Float("dz"), - Float("dyaw"), ORIENT='h' - ) - -default_node = Struct("default_node", - # each of these structs exists ONLY if the corrosponding flag - # for that node it NOT set in the animation it is located in. - QStruct("rotation", - SInt16("i", UNIT_SCALE=1/32767), - SInt16("j", UNIT_SCALE=1/32767), - SInt16("k", UNIT_SCALE=1/32767), - SInt16("w", UNIT_SCALE=1/32767), - ORIENT="h" - ), - QStruct("translation", INCLUDE=xyz_float), - Float("scale"), - SIZE=24 - ) - - -dyn_anim_path = "tagdata.animations.STEPTREE[DYN_I].name" - -object_desc = Struct("object", - dyn_senum16("animation", DYN_NAME_PATH=dyn_anim_path), - SEnum16("function", - "A_out", - "B_out", - "C_out", - "D_out" - ), - SEnum16("function_controls", - "frame", - "scale", - ), - SIZE=20, - ) - -anim_enum_desc = QStruct("animation", - dyn_senum16("animation", DYN_NAME_PATH=dyn_anim_path) - ) - -ik_point_desc = Struct("ik_point", - ascii_str32("marker"), - ascii_str32("attach_to_marker"), - SIZE=64, - ) - -weapon_types_desc = Struct("weapon_types", - ascii_str32("label"), - Pad(16), - reflexive("animations", anim_enum_desc, 10, - *unit_weapon_type_animation_names - ), - SIZE=60, - ) - -unit_weapon_desc = Struct("weapon", - ascii_str32("name"), - ascii_str32("grip_marker"), - ascii_str32("hand_marker"), - #Aiming screen bounds - - #pitch and yaw are saved in radians. - float_rad("right_yaw_per_frame"), - float_rad("left_yaw_per_frame"), - SInt16("right_frame_count"), - SInt16("left_frame_count"), - - float_rad("down_pitch_per_frame"), - float_rad("up_pitch_per_frame"), - SInt16("down_frame_count"), - SInt16("up_frame_count"), - - Pad(32), - reflexive("animations", anim_enum_desc, 55, - *unit_weapon_animation_names - ), - reflexive("ik_points", ik_point_desc, 8, DYN_NAME_PATH=".marker"), - reflexive("weapon_types", weapon_types_desc, 64, DYN_NAME_PATH=".label"), - SIZE=188, - ) - -unit_desc = Struct("unit", - ascii_str32("label"), - #pitch and yaw are saved in radians. - - #Looking screen bounds - float_rad("right_yaw_per_frame"), - float_rad("left_yaw_per_frame"), - SInt16("right_frame_count"), - SInt16("left_frame_count"), - - float_rad("down_pitch_per_frame"), - float_rad("up_pitch_per_frame"), - SInt16("down_frame_count"), - SInt16("up_frame_count"), - - Pad(8), - reflexive("animations", anim_enum_desc, 30, - *unit_animation_names - ), - reflexive("ik_points", ik_point_desc, 8, DYN_NAME_PATH=".marker"), - reflexive("weapons", unit_weapon_desc, 64, DYN_NAME_PATH=".name"), - SIZE=100, - ) - -weapon_desc = Struct("weapon", - Pad(16), - reflexive("animations", anim_enum_desc, 11, - *weapon_animation_names - ), - SIZE=28, - ) - -suspension_desc = QStruct("suspension_animation", - SInt16("mass_point_index"), - dyn_senum16("animation", DYN_NAME_PATH=dyn_anim_path), - Float("full_extension_ground_depth"), - Float("full_compression_ground_depth"), - SIZE=20, +unit_desc = desc_variant(unit_desc, + ("ik_points", reflexive("ik_points", ik_point_desc, 8, DYN_NAME_PATH=".marker")), + ("weapons", reflexive("weapons", unit_weapon_desc, 64, DYN_NAME_PATH=".name")), ) - -vehicle_desc = Struct("vehicle", - #pitch and yaw are saved in radians. - - #Steering screen bounds - float_rad("right_yaw_per_frame"), - float_rad("left_yaw_per_frame"), - SInt16("right_frame_count"), - SInt16("left_frame_count"), - - float_rad("down_pitch_per_frame"), - float_rad("up_pitch_per_frame"), - SInt16("down_frame_count"), - SInt16("up_frame_count"), - - Pad(68), - reflexive("animations", anim_enum_desc, 8, - *vehicle_animation_names - ), - reflexive("suspension_animations", suspension_desc, 32), - SIZE=116, +vehicle_desc = desc_variant(vehicle_desc, + ("suspension_animations", reflexive("suspension_animations", suspension_desc, 32)), ) - -device_desc = Struct("device", - Pad(84), - reflexive("animations", anim_enum_desc, 2, - *device_animation_names - ), - SIZE=96, +fp_animation_desc = desc_variant(fp_animation_desc, + ("animations", reflexive("animations", anim_enum_desc, 30, *fp_animation_names_mcc)), ) - -fp_animation_desc = Struct("fp_animation", - Pad(16), - reflexive("animations", anim_enum_desc, 30, - *fp_animation_names_mcc - ), - SIZE=28, +animation_desc = desc_variant(animation_desc, + ("frame_data", rawdata_ref("frame_data", max_size=4194304)), ) -sound_reference_desc = Struct("sound_reference", - dependency('sound', "snd!"), - SIZE=20, +antr_body = desc_variant(antr_body, + ("units", reflexive("units", unit_desc, 2048, DYN_NAME_PATH=".label")), + ("vehicles", reflexive("vehicles", vehicle_desc, 1)), + ("fp_animations", reflexive("fp_animations", fp_animation_desc, 1)), + ("sound_references", reflexive("sound_references", sound_reference_desc, 512, DYN_NAME_PATH=".sound.filepath")), + ("animations", reflexive("animations", animation_desc, 2048, DYN_NAME_PATH=".name")), ) -nodes_desc = Struct("node", - ascii_str32("name"), - dyn_senum16("next_sibling_node_index", DYN_NAME_PATH="..[DYN_I].name"), - dyn_senum16("first_child_node_index", DYN_NAME_PATH="..[DYN_I].name"), - dyn_senum16("parent_node_index", DYN_NAME_PATH="..[DYN_I].name"), - Pad(2), - Bool32("node_joint_flags", - "ball_socket", - "hinge", - "no_movement", - ), - QStruct("base_vector", INCLUDE=ijk_float), - float_rad("vector_range"), - Pad(4), - SIZE=64, - ) - -animation_desc = Struct("animation", - ascii_str32("name"), - SEnum16("type", *anim_types), - SInt16("frame_count"), - SInt16("frame_size"), - SEnum16("frame_info_type", *anim_frame_info_types), - SInt32("node_list_checksum"), - SInt16("node_count"), - SInt16("loop_frame_index"), - - Float("weight"), - SInt16("key_frame_index"), - SInt16("second_key_frame_index"), - - dyn_senum16("next_animation", - DYN_NAME_PATH="..[DYN_I].name"), - Bool16("flags", - "compressed_data", - "world_relative", - { NAME:"pal", GUI_NAME:"25Hz(PAL)" }, - ), - dyn_senum16("sound", - DYN_NAME_PATH="tagdata.sound_references." + - "sound_references_array[DYN_I].sound.filepath"), - SInt16("sound_frame_index"), - SInt8("left_foot_frame_index"), - SInt8("right_foot_frame_index"), - FlSInt16("first_permutation_index", VISIBLE=False, - TOOLTIP="The index of the first animation in the permutation chain."), - FlFloat("chance_to_play", VISIBLE=False, - MIN=0.0, MAX=1.0, SIDETIP="[0,1]", - TOOLTIP=("Seems to be the chance range to select this permutation.\n" - "Random number in the range [0,1] is rolled. The permutation\n" - "chain is looped until the number is higher than or equal\n" - "to that permutations chance to play. This chance to play\n" - "is likely influenced by the animations 'weight' field.\n" - "All permutation chains should have the last one end with\n" - "a chance to play of 1.0")), - - rawdata_ref("frame_info", max_size=32768), - - # each of the bits in these flags determines whether - # or not the frame data stores info for each nodes - # translation, rotation, and scale. - # This info was discovered by looking at TheGhost's - # animation importer script, so thank him for that. - UInt32("trans_flags0", EDITABLE=False), - UInt32("trans_flags1", EDITABLE=False), - Pad(8), - UInt32("rot_flags0", EDITABLE=False), - UInt32("rot_flags1", EDITABLE=False), - Pad(8), - UInt32("scale_flags0", EDITABLE=False), - UInt32("scale_flags1", EDITABLE=False), - Pad(4), - SInt32("offset_to_compressed_data", EDITABLE=False), - rawdata_ref("default_data", max_size=16384), - rawdata_ref("frame_data", max_size=4194304), - SIZE=180, - ) - -antr_body = Struct("tagdata", - reflexive("objects", object_desc, 4), - reflexive("units", unit_desc, 2048, DYN_NAME_PATH=".label"), - reflexive("weapons", weapon_desc, 1), - reflexive("vehicles", vehicle_desc, 1), - reflexive("devices", device_desc, 1), - reflexive("unit_damages", anim_enum_desc, 176, - *unit_damage_animation_names - ), - reflexive("fp_animations", fp_animation_desc, 1), - #i have no idea why they decided to cap it at 257 instead of 256.... - reflexive("sound_references", sound_reference_desc, 512, - DYN_NAME_PATH=".sound.filepath"), - Float("limp_body_node_radius"), - Bool16("flags", - "compress_all_animations", - "force_idle_compression", - ), - Pad(2), - reflexive("nodes", nodes_desc, 64, DYN_NAME_PATH=".name"), - reflexive("animations", animation_desc, 2048, DYN_NAME_PATH=".name"), - SIZE=128, - ) - - def get(): return antr_def diff --git a/reclaimer/mcc_hek/defs/bipd.py b/reclaimer/mcc_hek/defs/bipd.py index ab9c544f..0f7df180 100644 --- a/reclaimer/mcc_hek/defs/bipd.py +++ b/reclaimer/mcc_hek/defs/bipd.py @@ -8,6 +8,7 @@ # from ...hek.defs.bipd import * +from supyr_struct.util import desc_variant #import and use the mcc obje and unit attrs from .obje import * @@ -15,8 +16,9 @@ # replace the object_type enum one that uses # the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=0) +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(0)) + ) bipd_body = Struct("tagdata", obje_attrs, diff --git a/reclaimer/mcc_hek/defs/bitm.py b/reclaimer/mcc_hek/defs/bitm.py index c7cce72a..3f301d50 100644 --- a/reclaimer/mcc_hek/defs/bitm.py +++ b/reclaimer/mcc_hek/defs/bitm.py @@ -7,283 +7,76 @@ # See LICENSE for more information. # -from array import array -from ...common_descs import * -from supyr_struct.defs.tag_def import TagDef -from .objs.bitm import BitmTag - -type_comment = """Type controls bitmap 'geometry'. -All dimensions must be a power-of-two except for SPRITES and INTERFACE: - -*2D TEXTURES: Ordinary, 2D textures will be generated. - -*3D TEXTURES: Volume textures will be generated from each - sequence of 2D texture 'slices'. - -*CUBE MAPS: Cube maps will be generated from each consecutive - set of six 2D textures in each sequence. All faces - of a cubemap must be square and have the same dimensions. - -*SPRITES: Sprite texture pages will be generated. - -*INTERFACE BITMAPS: Similar to 2D TEXTURES, but without mipmaps or - the power-of-two restriction on their dimensions.""" - -format_comment = """Format controls how pixels will be stored internally. - -*COMPRESSED WITH COLOR-KEY TRANSPARENCY: DXT1 compression(4-bits per pixel). - For each 4x4 blocks of pixels, two colors are chosen that best represent - the range of the colors in that block(c0 and c1). The colors are reduced - to 16-bit and each of the 16 pixels is given a blending code(0 to 3). - 0 means the pixels color is c0 and 1 means its color is c1, with - 2 meaning to use 1/3(c1) + 2/3(c2), and 3 meaning 2/3(c1) + 1/3(c2). - - If an alpha exists, it is reduced to 1-bit(black or white). - If a 4x4 block contains transparency, the blending codes are changed so - that 2 means to use 1/2(c1) + 1/2(c2) and 3 means solid black(a=r=g=b=0). - -*COMPRESSED WITH EXPLICIT ALPHA: DXT3 compression(8-bits per pixel). - Same method as DXT1, but without the color-key transparency stuff. - Alpha channel is quantized down to 4 bits per pixel(16 shades of gray). - This format is best used where smooth gradients are not required in the - alpha channel, and consistancy in shades between 4x4 chunks is important. - -*COMPRESSED WITH INTERPOLATED ALPHA: DXT5 compression(8-bits per pixel). - Same method as DXT1, but without the color-key transparency stuff. - Alpha channel uses a method similar to DXT1. For each 4x4 block of - pixels, two 8-bit shades of gray are chosen that best represent the - range of values in that block(v0, v1). Each of the 16 pixels is given a - blending code(0 to 7), with 0 meaning to use v0 and 1 meaning to use v1. - The rest of the codes blend the v0 and v1 shades as shown: - 2 = (v0*6 + v1)/7 3 = (v0*5 + v1*2)/7 - 4 = (v0*4 + v1*3)/7 5 = (v0*3 + v1*4)/7 - 6 = (v0*2 + v1*5)/7 7 = (v0 + v1*6)/7 - If 100% white and 100% black are in the 4x4 block, these are used instead. - 2 = (v0*4 + v1)/5 3 = (v0*3 + v1*2)/5 - 4 = (v0*2 + v1*3)/5 5 = (v0 + v1*4)/5 - 6 = black 7 = white - This allows very smooth gradients in the alpha, but if two neighboring - 4x4 blocks do not use the same v0 and v1 shades, it can be very noticible. - -*16-BIT COLOR: Uses 16 bits per pixel. Depending on the alpha channel - bitmaps are quantized to one of 3 different formats: - r5g6b5(no alpha), a1r5g5b5(1-bit alpha), or a4r4g4b4(>1-bit alpha) - -*32-BIT COLOR: Uses 32 bits per pixel. Very high quality and can have an alpha - channel at no added cost. This format takes up the most memory, however. - Bitmap formats are x8r8g8b8 and a8r8g8b8. - -*MONOCHROME: Uses either 8 or 16 bits per pixel. This is an Xbox-only format. - There are 4 different formats, each using an intensity and alpha channel. - An intensity channel is essentially a monochrome rgb channel: - a8: 8-bits per pixel. Intensity channel is solid black, with the - pixel data being used for the alpha channel. - y8: 8-bits per pixel. Alpha channel is solid white, with the pixel - data being used for the intensity channel. - ay8: 8-bits per pixel. Pixel data is used for both intensity and alpha. - a8y8: 16-bits per pixel. Intensity and alpha channels each use their - own separate pixel data, with 8 bits for each channel. - -NOTE: Normal maps(a.k.a. bump maps) usually use 32-bit color. - This is costly, and if there is no alpha you can usually use 16-bit - r5g6b5 to save space without any noticible drop in quality ingame.""" - -usage_comment = """Usage controls how mipmaps are generated: - -*ALPHA BLEND: Pixels with zero alpha are ignored in mipmaps, to prevent - bleeding the transparent color. - -*DEFAULT: Downsampling works normally, as in Photoshop. - -*HEIGHT MAP: The bitmap is a height map, which will get converted to a bump map. - Uses the 'bump height' below. Alpha is 1-bit. This is an Xbox-only format. - -*DETAIL MAP: Mipmap color fades to gray and alpha fades to white. - Color fading is controlled by the 'detail fade factor' below. - -*LIGHT MAP: Generates no mipmaps. Do not use! - -*VECTOR MAP: Used mostly for special effects; pixels are treated as XYZ vectors - and are normalized after downsampling. Alpha is passed though unmodified.""" - -post_processing_comment = """ -These properties control how mipmaps are processed.""" - -sprite_processing_comment = """ -When creating a sprite group, specify the number and size of the textures -that the group is allowed to occupy. During importing, you will recieve -feedback about how well the alloted space was used.""" - -def get(): return bitm_def - -def pixel_block_size(node, *a, **kwa): - if isinstance(node, array): - return node.itemsize*len(node) - return len(node) - -pixel_root = WhileArray('pixel_root', - SUB_STRUCT=WhileArray('bitmap_pixels', - SUB_STRUCT=UInt8Array('pixels', SIZE=pixel_block_size) - ) +from ...hek.defs.bitm import * +from supyr_struct.util import desc_variant + +format_comment_parts = format_comment.split("NOTE: ", 1) +format_comment = "".join(( + format_comment_parts[0], + """\ +HIGH QUALITY COMPRESSION: ???? + +NOTE:""", + format_comment_parts[1], + )) + +bitmap_format = SEnum16("format", + "a8", + "y8", + "ay8", + "a8y8", + ("r5g6b5", 6), + ("a1r5g5b5", 8), + ("a4r4g4b4", 9), + ("x8r8g8b8", 10), + ("a8r8g8b8", 11), + ("dxt1", 14), + ("dxt3", 15), + ("dxt5", 16), + ("p8_bump", 17), + ("bc7", 18), ) - -sprite = QStruct("sprite", - SInt16("bitmap_index"), - Pad(6), - Float("left_side"), - Float("right_side"), - Float("top_side"), - Float("bottom_side"), - Float("registration_point_x"), - Float("registration_point_y"), - SIZE=32, +bitmap = desc_variant(bitmap, + ("format", bitmap_format), ) - -sequence = Struct("sequence", - ascii_str32("sequence_name"), - SInt16("first_bitmap_index"), - SInt16("bitmap_count"), - Pad(16), - reflexive("sprites", sprite, 64), - SIZE=64, +body_format = SEnum16("format", + "color_key_transparency", + "explicit_alpha", + "interpolated_alpha", + "color_16bit", + "color_32bit", + "monochrome", + "high_quality_compression", + COMMENT=format_comment ) - -bitmap = Struct("bitmap", - UEnum32('bitm_id', ('bitm', 'bitm'), DEFAULT='bitm', EDITABLE=False), - UInt16("width", SIDETIP="pixels", EDITABLE=False), - UInt16("height", SIDETIP="pixels", EDITABLE=False), - UInt16("depth", SIDETIP="pixels", EDITABLE=False), - SEnum16("type", - "texture_2d", - "texture_3d", - "cubemap", - "white", - EDITABLE=False - ), - SEnum16("format", - "a8", - "y8", - "ay8", - "a8y8", - #"-unused1-", - #"-unused2-", - ("r5g6b5", 6), - #"-unused3-", - ("a1r5g5b5", 8), - ("a4r4g4b4", 9), - ("x8r8g8b8", 10), - ("a8r8g8b8", 11), - #"-unused4-", - #"-unused5-", - ("dxt1", 14), - ("dxt3", 15), - ("dxt5", 16), - ("p8_bump", 17), - ("bc7", 18), - ), - Bool16("flags", - "power_of_2_dim", - "compressed", - "palletized", - "swizzled", - "linear", - "v16u16", - "unknown", - "prefer_low_detail", - "data_in_resource_map", - ), - UInt16("registration_point_x"), - UInt16("registration_point_y"), - UInt16("mipmaps"), - FlUInt16("pixels", VISIBLE=False, EDITABLE=False), - - # this is the non-magic pointer into the map that the pixel data - # is located at. if flags.data_in_resource_map is True and the - # map is halo ce/pc/trial, the offset is into the bitmaps.map - UInt32("pixels_offset", VISIBLE=False, EDITABLE=False), - UInt32("pixels_meta_size", VISIBLE=False, EDITABLE=False), - UInt32("bitmap_id_unknown1", VISIBLE=False, EDITABLE=False), - UInt32("bitmap_data_pointer", VISIBLE=False, EDITABLE=False), - UInt32("bitmap_id_unknown2", VISIBLE=False, EDITABLE=False), - UInt32("base_address", VISIBLE=False, EDITABLE=False), - SIZE=48, +body_flags = Bool16("flags", + "enable_diffusion_dithering", + "disable_height_map_compression", + "uniform_sprite_sequences", + "sprite_bug_fix", + "hud_scale_0.5", + "invert_detail_fade", + "use_average_color_for_detail_fade" ) - -bitm_body = Struct("tagdata", - SEnum16("type", - "textures_2d", - "textures_3d", - "cubemaps", - "sprites", - "interface_bitmaps", - COMMENT=type_comment - ), - SEnum16("format", - "color_key_transparency", - "explicit_alpha", - "interpolated_alpha", - "color_16bit", - "color_32bit", - "monochrome", - "high_quality_compression", - COMMENT=format_comment - ), - SEnum16("usage", - "alpha-blend", - "default", - "height_map", - "detail_map", - "light_map", - "vector_map", - COMMENT=usage_comment, DEFAULT=1 - ), - Bool16("flags", - "enable_diffusion_dithering", - "disable_height_map_compression", - "uniform_sprite_sequences", - "sprite_bug_fix", - "hud_scale_0.5", - "invert_detail_fade", - "use_average_color_for_detail_fade" - ), - QStruct("post_processing", - float_zero_to_one("detail_fade_factor"), - float_zero_to_one("sharpen_amount"), - Float("bump_height", SIDETIP="repeats"), - COMMENT=post_processing_comment - ), - Struct("sprite_processing", - SEnum16("sprite_budget_size", - {NAME: "x32", VALUE: 0, GUI_NAME: "32x32"}, - {NAME: "x64", VALUE: 1, GUI_NAME: "64x64"}, - {NAME: "x128", VALUE: 2, GUI_NAME: "128x128"}, - {NAME: "x256", VALUE: 3, GUI_NAME: "256x256"}, - {NAME: "x512", VALUE: 4, GUI_NAME: "512x512"}, - {NAME: "x1024", VALUE: 5, GUI_NAME: "1024x1024"}, - ), - UInt16("sprite_budget_count"), - COMMENT=sprite_processing_comment - ), - UInt16("color_plate_width", SIDETIP="pixels", EDITABLE=False), - UInt16("color_plate_height", SIDETIP="pixels", EDITABLE=False), - - rawdata_ref("compressed_color_plate_data", max_size=1073741824), - rawdata_ref("processed_pixel_data", max_size=1073741824), - - Float("blur_filter_size", MIN=0.0, MAX=10.0, SIDETIP="[0,10] pixels"), - float_neg_one_to_one("alpha_bias"), - UInt16("mipmap_levels", MIN=0, SIDETIP="levels"), - SEnum16("sprite_usage", - "blend/add/subtract/max", - "multiply/min", - "double_multiply", - ), - UInt16("sprite_spacing", SIDETIP="pixels"), - Pad(2), - reflexive("sequences", sequence, 256, - DYN_NAME_PATH='.sequence_name', IGNORE_SAFE_MODE=True), - reflexive("bitmaps", bitmap, 65536, IGNORE_SAFE_MODE=True), - SIZE=108, WIDGET=HaloBitmapTagFrame +sprite_processing = Struct("sprite_processing", + SEnum16("sprite_budget_size", + {NAME: "x32", VALUE: 0, GUI_NAME: "32x32"}, + {NAME: "x64", VALUE: 1, GUI_NAME: "64x64"}, + {NAME: "x128", VALUE: 2, GUI_NAME: "128x128"}, + {NAME: "x256", VALUE: 3, GUI_NAME: "256x256"}, + {NAME: "x512", VALUE: 4, GUI_NAME: "512x512"}, + {NAME: "x1024", VALUE: 5, GUI_NAME: "1024x1024"}, + ), + UInt16("sprite_budget_count"), + COMMENT=sprite_processing_comment + ) +bitm_body = desc_variant(bitm_body, + ("format", body_format), + ("flags", body_flags), + ("sprite_processing", sprite_processing), + ("compressed_color_plate_data", rawdata_ref("compressed_color_plate_data", max_size=1073741824)), + ("processed_pixel_data", rawdata_ref("processed_pixel_data", max_size=1073741824)), + ("bitmaps", reflexive("bitmaps", bitmap, 65536, IGNORE_SAFE_MODE=True)), ) def get(): diff --git a/reclaimer/mcc_hek/defs/cdmg.py b/reclaimer/mcc_hek/defs/cdmg.py index faed0dfd..12fd9168 100644 --- a/reclaimer/mcc_hek/defs/cdmg.py +++ b/reclaimer/mcc_hek/defs/cdmg.py @@ -7,79 +7,46 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -cdmg_body = Struct("tagdata", - from_to_wu("radius"), - float_zero_to_one("cutoff_scale"), - Pad(24), - - QStruct("vibrate_parameters", - float_zero_to_one("low_frequency"), - float_zero_to_one("high_frequency"), - Pad(24), - ), - - Struct("camera_shaking", - float_wu("random_translation"), - float_rad("random_rotation"), # radians - Pad(12), - - SEnum16("wobble_function", *animation_functions), - Pad(2), - float_sec("wobble_function_period"), - Float("wobble_weight"), - Pad(192), - ), - - Struct("damage", - SEnum16("priority", - "none", - "harmless", - {NAME: "backstab", GUI_NAME: "lethal to the unsuspecting"}, - "emp", - ), - SEnum16("category", *damage_category), - Bool32("flags", - "does_not_hurt_owner", - {NAME: "headshot", GUI_NAME: "can cause headshots"}, - "pings_resistant_units", - "does_not_hurt_friends", - "does_not_ping_shields", - "detonates_explosives", - "only_hurts_shields", - "causes_flaming_death", - {NAME: "indicator_points_down", - GUI_NAME: "damage indicator always points down"}, - "skips_shields", - "only_hurts_one_infection_form", - {NAME: "multiplayer_headshot", - GUI_NAME: "can cause multiplayer headshots"}, - "infection_form_pop", - "ignore_seat_scale_for_dir_dmg", - "forces_hard_ping", - "does_not_hurt_players", - "use_3d_instantaneous_acceleration", - "allow_any_non_zero_acceleration_value", - ), - Pad(4), - Float("damage_lower_bound"), - QStruct("damage_upper_bound", INCLUDE=from_to), - float_zero_to_one("vehicle_passthrough_penalty"), - Pad(4), - float_zero_to_one("stun"), - float_zero_to_one("maximum_stun"), - float_sec("stun_time"), - Pad(4), - QStruct("instantaneous_acceleration", INCLUDE=ijk_float), - ), +from ...hek.defs.cdmg import * +from supyr_struct.util import desc_variant + +damage_flags = Bool32("flags", + "does_not_hurt_owner", + {NAME: "headshot", GUI_NAME: "can cause headshots"}, + "pings_resistant_units", + "does_not_hurt_friends", + "does_not_ping_shields", + "detonates_explosives", + "only_hurts_shields", + "causes_flaming_death", + {NAME: "indicator_points_down", GUI_NAME: "damage indicator always points down"}, + "skips_shields", + "only_hurts_one_infection_form", + {NAME: "multiplayer_headshot", GUI_NAME: "can cause multiplayer headshots"}, + "infection_form_pop", + "ignore_seat_scale_for_dir_dmg", + "forces_hard_ping", + "does_not_hurt_players", + "use_3d_instantaneous_acceleration", + "allow_any_non_zero_acceleration_value", + ) - damage_modifiers, - SIZE=512, +damage_descs = [ + desc for desc in cdmg_body.values() + if isinstance(desc, dict) and desc.get("NAME") == "damage" + ] +if not damage_descs: + raise ValueError("Could not locate descriptor 'damage' in cdmg_body") + +damage = desc_variant(damage_descs[0], + ("flags", damage_flags), + ("instantaneous_acceleration", QStruct("instantaneous_acceleration", INCLUDE=ijk_float)), + ("pad_3", Pad(0)), ) +cdmg_body = desc_variant(cdmg_body, + ("damage", damage), + ) def get(): return cdmg_def diff --git a/reclaimer/mcc_hek/defs/coll.py b/reclaimer/mcc_hek/defs/coll.py index c5702be0..0cfc8a83 100644 --- a/reclaimer/mcc_hek/defs/coll.py +++ b/reclaimer/mcc_hek/defs/coll.py @@ -7,291 +7,16 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.coll import CollTag -from supyr_struct.defs.tag_def import TagDef +from ...hek.defs.coll import * from supyr_struct.util import desc_variant -bsp_node_tooltip = ( - "Refers to a leaf node if negative.\n" - "Add 0x80000000 to get leaf node index." +coll_body = desc_variant(coll_body, + ("pathfinding_spheres", reflexive("pathfinding_spheres", pathfinding_sphere, 256)), ) - -modifier = Struct("modifier", - Pad(52), - ) - -body = Struct("body", - Float("maximum_body_vitality"), - Float("body_system_shock"), - - Pad(52), - float_zero_to_one("friendly_damage_resistance"), - - Pad(40), - dependency("localized_damage_effect", "effe"), - - float_zero_to_one("area_damage_effect_threshold"), - dependency("area_damage_effect", "effe"), - - Float("body_damaged_threshold"), - dependency("body_damaged_effect", "effe"), - dependency("body_depleted_effect", "effe"), - Float("body_destroyed_threshold"), - dependency("body_destroyed_effect", "effe"), - ) - -shield = Struct("shield", - Float("maximum_shield_vitality"), - - Pad(2), - SEnum16("shield_material_type", *materials_list), - - Pad(24), - SEnum16("shield_failure_function", *fade_functions), - - Pad(2), - Float("shield_failure_threshold"), - Float("shield_failing_leak_fraction"), - - Pad(16), - Float("minimum_stun_damage"), - float_sec("stun_time"), - float_sec("recharge_time"), - - Pad(112), - Float("shield_damaged_threshold"), - dependency("shield_damaged_effect", "effe"), - dependency("shield_depleted_effect", "effe"), - dependency("shield_recharging_effect", "effe"), - Pad(8), - Float("shield_recharge_rate", VISIBLE=False), - ) - -bsp3d_node = QStruct("bsp3d_node", - SInt32("plane"), - SInt32("back_child", TOOLTIP=bsp_node_tooltip), - SInt32("front_child", TOOLTIP=bsp_node_tooltip), - SIZE=12 - ) - -plane = QStruct("plane", - # i, j, and k form a unit vector where d specifies - # the location of a point a "distance" along it - Float("i"), Float("j"), Float("k"), Float("d"), - SIZE=16, ORIENT='h' - ) - -leaf = Struct("leaf", - Bool16("flags", - "contains_double_sided_surfaces" - ), - SInt16("bsp2d_reference_count"), - SInt32("first_bsp2d_reference"), - SIZE=8 - ) - -bsp2d_reference = QStruct("bsp2d_reference", - SInt32("plane"), - SInt32("bsp2d_node", TOOLTIP=bsp_node_tooltip), - SIZE=8 - ) - -bsp2d_node = QStruct("bsp2d_node", - Float("plane_i"), - Float("plane_j"), - Float("plane_d"), - SInt32("left_child", TOOLTIP=bsp_node_tooltip), - SInt32("right_child", TOOLTIP=bsp_node_tooltip), - SIZE=20 - ) - -surface = Struct("surface", - SInt32("plane"), - SInt32("first_edge"), - Bool8("flags", - "two_sided", - "invisible", - "climbable", - "breakable", - ), - SInt8("breakable_surface"), - SInt16("material"), - SIZE=12 - ) - -edge = QStruct("edge", - SInt32("start_vertex"), - SInt32("end_vertex"), - SInt32("forward_edge"), - SInt32("reverse_edge"), - SInt32("left_surface"), - SInt32("right_surface"), - SIZE=24 - ) - -vertex = QStruct("vertex", - Float("x"), - Float("y"), - Float("z"), - SInt32("first_edge"), - SIZE=16 - ) - -permutation_bsp = Struct("permutation_bsp", - reflexive("bsp3d_nodes", bsp3d_node, 131072), - reflexive("planes", plane, 65535), - reflexive("leaves", leaf, 65535), - reflexive("bsp2d_references", bsp2d_reference, 131072), - reflexive("bsp2d_nodes", bsp2d_node, 65535), - reflexive("surfaces", surface, 131072), - reflexive("edges", edge, 262144), - reflexive("vertices", vertex, 131072), - SIZE=96 - ) - -node = Struct("node", - ascii_str32("name"), - dyn_senum16("region", - DYN_NAME_PATH=".....regions.regions_array[DYN_I].name"), - dyn_senum16("parent_node", - DYN_NAME_PATH="..[DYN_I].name"), - dyn_senum16("next_sibling_node", - DYN_NAME_PATH="..[DYN_I].name"), - dyn_senum16("first_child_node", - DYN_NAME_PATH="..[DYN_I].name"), - - Pad(8), - FlSInt16("unknown0", VISIBLE=False), - FlSInt16("unknown1", VISIBLE=False), - reflexive("bsps", permutation_bsp, 32), - SIZE=64 - ) - -pathfinding_sphere = Struct("pathfinding_sphere", - dyn_senum16("node", - DYN_NAME_PATH=".....nodes.nodes_array[DYN_I].name"), - - Pad(14), - QStruct("center", INCLUDE=xyz_float), - Float("radius"), - SIZE=32 - ) - -permutation = Struct("permutation", - ascii_str32("name"), - SIZE=32 - ) - -region = Struct("region", - ascii_str32("name"), - Bool32("flags", - "lives_until_object_dies", - "forces_object_to_die", - "dies_when_object_dies", - "dies_when_object_is_damaged", - "disappears_when_shield_is_off", - "inhibits_melee_attack", - "inhibits_weapon_attack", - "inhibits_walking", - "forces_drop_weapon", - "causes_head_maimed_scream", - ), - Pad(4), - Float("damage_threshold"), - - Pad(12), - dependency("destroyed_effect", "effe"), - reflexive("permutations", permutation, 32, DYN_NAME_PATH='.name'), - SIZE=84 - ) - -material = Struct("material", - ascii_str32("name"), - Bool32("flags", - "head" - ), - SEnum16("material_type", *materials_list), - Pad(2), - Float("shield_leak_percentage"), - Float("shield_damage_multiplier"), - - Pad(12), - Float("body_damage_multiplier"), - SIZE=72 - ) - -coll_body = Struct("tagdata", - Bool32("flags", - "takes_shield_damage_for_children", - "takes_body_damage_for_children", - "always_shields_friendly_damage", - "passes_area_damage_to_children", - "parent_never_takes_body_damage_for_us", - "only_damaged_by_explosives", - "only_damaged_while_occupied", - ), - dyn_senum16("indirect_damage_material", - DYN_NAME_PATH=".materials.materials_array[DYN_I].name"), - Pad(2), - - body, - shield, - - Pad(112), - reflexive("materials", material, 32, DYN_NAME_PATH='.name'), - reflexive("regions", region, 8, DYN_NAME_PATH='.name'), - reflexive("modifiers", modifier, 0, VISIBLE=False), - - Pad(16), - Struct("pathfinding_box", - QStruct("x", INCLUDE=from_to), - QStruct("y", INCLUDE=from_to), - QStruct("z", INCLUDE=from_to), - ), - - reflexive("pathfinding_spheres", pathfinding_sphere, 256), - reflexive("nodes", node, 64, DYN_NAME_PATH='.name'), - - SIZE=664, - ) - - -fast_permutation_bsp = Struct("permutation_bsp", - raw_reflexive("bsp3d_nodes", bsp3d_node, 131072), - raw_reflexive("planes", plane, 65535), - raw_reflexive("leaves", leaf, 65535), - raw_reflexive("bsp2d_references", bsp2d_reference, 131072), - raw_reflexive("bsp2d_nodes", bsp2d_node, 65535), - raw_reflexive("surfaces", surface, 131072), - raw_reflexive("edges", edge, 262144), - raw_reflexive("vertices", vertex, 131072), - SIZE=96 - ) - -fast_node = Struct("node", - ascii_str32("name"), - dyn_senum16("region", - DYN_NAME_PATH=".....regions.regions_array[DYN_I].name"), - dyn_senum16("parent_node", - DYN_NAME_PATH="..[DYN_I].name"), - dyn_senum16("next_sibling_node", - DYN_NAME_PATH="..[DYN_I].name"), - dyn_senum16("first_child_node", - DYN_NAME_PATH="..[DYN_I].name"), - - Pad(8), - FlSInt16("unknown0", VISIBLE=False), - FlSInt16("unknown1", VISIBLE=False), - reflexive("bsps", fast_permutation_bsp, 32), - SIZE=64 - ) - fast_coll_body = desc_variant(coll_body, ("nodes", reflexive("nodes", fast_node, 64, DYN_NAME_PATH='.name')), ) - def get(): return coll_def diff --git a/reclaimer/mcc_hek/defs/cont.py b/reclaimer/mcc_hek/defs/cont.py index df29fa6e..246a8f05 100644 --- a/reclaimer/mcc_hek/defs/cont.py +++ b/reclaimer/mcc_hek/defs/cont.py @@ -7,115 +7,4 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -def get(): return cont_def - -point_state = Struct("point_state", - from_to_sec("state_duration"), - from_to_sec("state_transition_duration"), - dependency("physics", "pphy"), - Pad(32), - float_wu("width"), - QStruct("color_lower_bound", INCLUDE=argb_float), - QStruct("color_upper_bound", INCLUDE=argb_float), - Bool32("scale_flags", - "duration", - "duration_delta", - "transition_duration", - "transition_duration_delta", - "color", - ), - SIZE=104 - ) - -cont_body = Struct("tagdata", - Bool16("flags", - "first_point_unfaded", - "last_point_unfaded", - "points_start_pinned_to_media", - "points_start_pinned_to_ground", - "points_always_pinned_to_media", - "points_always_pinned_to_ground", - "edge_effect_fades_slowly", - ), - Bool16("scale_flags", - "point_generation_rate", - "point_velocity", - "point_velocity_delta", - "point_velocity_cone_angle", - "inherited_velocity_fraction", - "sequence_animation_rate", - "texture_scale_u", - "texture_scale_v", - "texture_animation_u", - "texture_animation_v", - ), - - Struct("point_creation", - Float("generation_rate", - SIDETIP="points/sec", UNIT_SCALE=per_sec_unit_scale), - from_to_wu_sec("velocity"), - float_rad("velocity_cone_angle"), - Float("inherited_velocity_fraction"), - ), - - Struct("rendering", - SEnum16("render_type", - "vertical_orientation", - "horizontal_orientation", - "media_mapped", - "ground_mapped", - "viewer_facing", - "double_marker_linked", - ), - Pad(2), - Float("texture_repeats_u"), - Float("texture_repeats_v"), - - Float("texture_animation_u", SIDETIP="repeats/sec"), - Float("texture_animation_v", SIDETIP="repeats/sec"), - Float("animation_rate", SIDETIP="frames/sec"), - dependency("bitmap", "bitm"), - SInt16("first_sequence_index"), - SInt16("sequence_count"), - Pad(100), - - FlUInt32("unknown0", VISIBLE=False), - Bool16("shader_flags", *shader_flags), - SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), - SEnum16("framebuffer_fade_mode", *render_fade_mode), - Bool16("map_flags", - "unfiltered", - ), - ), - Pad(12), # OS v4 shader extension padding - Pad(16), - - Struct("secondary_map", - dependency("bitmap", "bitm"), - SEnum16("anchor", *render_anchor), - Bool16("map_flags", - "unfiltered", - ), - - Struct("u_animation", INCLUDE=anim_src_func_per_pha_sca), - Struct("v_animation", INCLUDE=anim_src_func_per_pha_sca), - Struct("rotation_animation", INCLUDE=anim_src_func_per_pha_sca_rot), - QStruct("rotation_center", INCLUDE=xy_float), - Pad(4), - Float("zsprite_radius_scale"), - Pad(20), - ), - reflexive("point_states", point_state, 16), - SIZE=324, - ) - -cont_def = TagDef("cont", - blam_header('cont', 3), - cont_body, - - ext=".contrail", endian=">", tag_cls=HekTag - ) +from ...hek.defs.cont import * diff --git a/reclaimer/mcc_hek/defs/ctrl.py b/reclaimer/mcc_hek/defs/ctrl.py index 42107817..2c0ffeda 100644 --- a/reclaimer/mcc_hek/defs/ctrl.py +++ b/reclaimer/mcc_hek/defs/ctrl.py @@ -8,17 +8,22 @@ # from ...hek.defs.ctrl import * - -#import and use the mcc obje attrs from .obje import * +from .devi import * # replace the object_type enum one that uses # the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=8) +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(8)) + ) -ctrl_body = dict(ctrl_body) -ctrl_body[0] = obje_attrs +ctrl_body = Struct("tagdata", + obje_attrs, + devi_attrs, + ctrl_attrs, + + SIZE=792, + ) def get(): return ctrl_def diff --git a/reclaimer/mcc_hek/defs/deca.py b/reclaimer/mcc_hek/defs/deca.py index 131ca82d..a95d13b8 100644 --- a/reclaimer/mcc_hek/defs/deca.py +++ b/reclaimer/mcc_hek/defs/deca.py @@ -7,97 +7,26 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -decal_comment = """COMPOUND DECALS: -A 'compound decal' is a chain of decals which are instantiated simultaneously. -Compound decals are created by choosing a below. -NOTE: Do not attempt to create a circularly linked decal chain, i.e. A->B->C->A! -Also, do not reference a decal from an effect if it is not the 'head' of the chain; -for example an effect should not instantiate decal B if the chain was A->B->C. -Compound decals can have seperate bitmaps, seperate framebuffer blend functions, -and can be drawn in seperate layers. In addition, each decal in the chain can either -inherit its parent's , rotation, , , and - -or it can randomly choose its own. This behavior is controlled by the -'geometry_inherited_by_next_decal_in_chain' flag, below. - -DECAL TYPING AND LAYERING: -The decal (or layer) determines the drawing order of the decal with respect -to the rest of the environment. Decals in the primary layer are drawn after the -environment diffuse texture, hence they affect the already-lit texture of the surface. -Decals in the secondary layer are drawn immediately after decals in the primary layer, -so they 'cover up' the primary decals. Decals in the 'light' layer are drawn before -the environment diffuse texture, hence they affect the accumulated diffuse light and -only indirectly affect the lit texture.""" - -deca_body = Struct("tagdata", - #Decal Properties - Bool16("flags", - "geometry_inherited_by_next_decal_in_chain", - "interpolate_color_in_hsv", - "more_colors", - "no_random_rotation", - "water_effect", - "SAPIEN_snap_to_axis", - "SAPIEN_incremental_counter", - "animation_loop", - "preserve_aspect", - "disabled in remastered by blood setting", - COMMENT=decal_comment - ), - SEnum16("type", - "scratch", - "splatter", - "burn", - "painted_sign", - ), - SEnum16("layer", - "primary", - "secondary", - "light", - "alpha_tested", - "water" - ), - Pad(2), - dependency("next_decal_in_chain", "deca"), - from_to_wu("radius"), # world units - Pad(12), - - Struct("color", - from_to_zero_to_one("intensity"), # [0,1] - Struct("lower_bounds", INCLUDE=rgb_float), - Struct("upper_bounds", INCLUDE=rgb_float), - Pad(12), - ), - - #Animation - Struct("animation", - SInt16("loop_frame"), - SInt16("speed", MIN=1, MAX=120, - SIDETIP="[1,120] ticks/frame", UNIT_SCALE=per_sec_unit_scale), - Pad(28), - from_to_sec("lifetime"), # seconds - from_to_sec("decay_time"), # seconds - Pad(56), - ), - - #Shader - Struct("shader", - SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), - Pad(22), - dependency("shader_map", "bitm"), - ), - - #Sprite info - Pad(20), - Float("maximum_sprite_extent", SIDETIP="pixels"), - - SIZE=268, +from ...hek.defs.deca import * +from supyr_struct.util import desc_variant + +flags = Bool16("flags", + "geometry_inherited_by_next_decal_in_chain", + "interpolate_color_in_hsv", + "more_colors", + "no_random_rotation", + "water_effect", + "SAPIEN_snap_to_axis", + "SAPIEN_incremental_counter", + "animation_loop", + "preserve_aspect", + "disabled_in_remastered_by_blood_setting", + COMMENT=decal_comment ) - +deca_body = desc_variant(deca_body, + ("flags", flags) + ) def get(): return deca_def diff --git a/reclaimer/mcc_hek/defs/effe.py b/reclaimer/mcc_hek/defs/effe.py index 4bdef38e..5495f73d 100644 --- a/reclaimer/mcc_hek/defs/effe.py +++ b/reclaimer/mcc_hek/defs/effe.py @@ -7,169 +7,19 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.effe import EffeTag -from supyr_struct.defs.tag_def import TagDef +from ...hek.defs.effe import * +from supyr_struct.util import desc_variant -part_scale_modifiers = ( - "velocity", - "velocity_delta", - "velocity_cone_angle", - "angular_velocity", - "angular_velocity_delta", - "type_specific_scale" +flags = Bool32("flags", + {NAME: "deleted_when_inactive", GUI_NAME: "deleted_when_attachment_deactivates"}, + "must_be_deterministic", + "disabled_in_remastered_by_blood_setting" ) -particle_scale_modifiers = ( - "velocity", - "velocity_delta", - "velocity_cone_angle", - "angular_velocity", - "angular_velocity_delta", - "count", - "count_delta", - "distribution_radius", - "distribution_radius_delta", - "particle_radius", - "particle_radius_delta", - "tint" +effe_body = desc_variant(effe_body, + ("flags", flags), ) -create_in_env = SEnum16("create_in_env", - "any_environment", - "air_only", - "water_only", - "space_only", - ) - -create_in_mode = SEnum16("create_in_mode", - "either_mode", - "violent_mode_only", - "nonviolent_mode_only", - ) - -part = Struct("part", - create_in_env, - create_in_mode, - dyn_senum16("location", - DYN_NAME_PATH="........locations.locations_array[DYN_I].marker_name"), - Bool16("flags", - {NAME:"face_down", GUI_NAME:"face down regardless of location(decals)"} - ), - Pad(12), - - UEnum32("effect_class", INCLUDE=valid_tags_os, VISIBLE=False), - dependency("type", valid_effect_events), - Pad(24), - - from_to_wu_sec("velocity_bounds"), # world units/sec - float_rad("velocity_cone_angle"), # radians - from_to_rad_sec("angular_velocity_bounds"), # radians/sec - QStruct("radius_modifier_bounds"), - - Bool32("A_scales_values", *part_scale_modifiers), - Bool32("B_scales_values", *part_scale_modifiers), - SIZE=104, - ) - -particle = Struct("particle", - create_in_env, - create_in_mode, - SEnum16("create_in_camera", - "either", - "first_person_only", - "third_person_only", - "first_person_if_possible", - ), - FlSInt16("unknown0", VISIBLE=False), - dyn_senum16("location", - DYN_NAME_PATH="........locations.locations_array[DYN_I].marker_name"), - FlSInt16("unknown1", VISIBLE=False), - - yp_float_rad("relative_direction"), # radians - QStruct("relative_offset", INCLUDE=ijk_float), - QStruct("relative_direction_vector", INCLUDE=xyz_float, VISIBLE=False), - Pad(40), - - dependency("particle_type", "part"), - Bool32("flags", - "stay_attached_to_marker", - "random_initial_angle", - "tint_from_object_color", - {NAME: "tint_as_hsv", GUI_NAME: "interpolate tint as hsv"}, - {NAME: "use_long_hue_path", GUI_NAME: "...across the long hue path"}, - ), - SEnum16("distribution_function", - "start", - "end", - "constant", - "buildup", - "falloff", - "buildup_and_falloff", - ), - Pad(2), - - QStruct("created_count", - SInt16("from", GUI_NAME=""), - SInt16("to"), ORIENT='h' - ), - from_to_wu("distribution_radius"), - Pad(12), - - from_to_wu_sec("velocity"), - float_rad("velocity_cone_angle"), # radians - from_to_rad_sec("angular_velocity"), # radians - Pad(8), - - from_to_wu("radius"), - Pad(8), - - QStruct("tint_lower_bound", INCLUDE=argb_float), - QStruct("tint_upper_bound", INCLUDE=argb_float), - Pad(16), - - Bool32("A_scales_values", *particle_scale_modifiers), - Bool32("B_scales_values", *particle_scale_modifiers), - SIZE=232 - ) - - -location = Struct("location", - ascii_str32("marker_name"), - ) - -event = Struct("event", - Pad(4), - Float("skip_fraction"), - from_to_sec("delay_bounds"), - from_to_sec("duration_bounds"), - - Pad(20), - reflexive("parts", part, 32, DYN_NAME_PATH='.type.filepath'), - reflexive("particles", particle, 32, DYN_NAME_PATH='.particle_type.filepath'), - SIZE=68 - ) - - -effe_body = Struct("tagdata", - Bool32("flags", - {NAME: "deleted_when_inactive", GUI_NAME: "deleted when attachment deactivates"}, - {NAME: "must_be_deterministic", GUI_NAME: "must_be_deterministic"}, - {NAME: "disabled_in_remastered_by_blood_setting"} - ), - dyn_senum16("loop_start_event", - DYN_NAME_PATH=".events.events_array[DYN_I].NAME"), - dyn_senum16("loop_stop_event", - DYN_NAME_PATH=".events.events_array[DYN_I].NAME"), - - Pad(32), - reflexive("locations", location, 32, DYN_NAME_PATH='.marker_name'), - reflexive("events", event, 32), - - SIZE=64, - ) - - def get(): return effe_def diff --git a/reclaimer/mcc_hek/defs/eqip.py b/reclaimer/mcc_hek/defs/eqip.py index e4cfe6f0..41c0f582 100644 --- a/reclaimer/mcc_hek/defs/eqip.py +++ b/reclaimer/mcc_hek/defs/eqip.py @@ -7,11 +7,13 @@ # See LICENSE for more information. # +from ...hek.defs.eqip import * from .obje import * from .item import * -from .objs.obje import ObjeTag -from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant + +eqip_attrs = desc_variant(eqip_attrs, + ("grenade_type", SEnum16("grenade_type", *grenade_types_mcc)), + ) # replace the object_type enum one that uses # the correct default value for this object @@ -19,23 +21,6 @@ ("object_type", object_type(3)) ) -eqip_attrs = Struct("eqip_attrs", - SEnum16('powerup_type', - 'none', - 'double_speed', - 'overshield', - 'active_camo', - 'full_spectrum_vision', - 'health', - 'grenade', - ), - SEnum16('grenade_type', *grenade_types_mcc), - float_sec('powerup_time'), - dependency('pickup_sound', "snd!"), - - SIZE=168 - ) - eqip_body = Struct("tagdata", obje_attrs, item_attrs, @@ -44,7 +29,6 @@ SIZE=944, ) - def get(): return eqip_def diff --git a/reclaimer/mcc_hek/defs/font.py b/reclaimer/mcc_hek/defs/font.py index e2367ac8..2492d68a 100644 --- a/reclaimer/mcc_hek/defs/font.py +++ b/reclaimer/mcc_hek/defs/font.py @@ -7,55 +7,18 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef +from ...hek.defs.font import * +from supyr_struct.util import desc_variant -def get(): return font_def - - -character_table = QStruct("character_table", - SInt16("character_index"), - SIZE=2 +flags = Bool32("flags", + "never_override_with_remastered_font_under_mcc", ) - -character_tables = Struct("character_tables", - reflexive("character_table", character_table, 256), - SIZE=12 +font_body = desc_variant(font_body, + ("flags", flags) ) -character = QStruct("character", - UInt16("character"), - SInt16("character_width"), - SInt16("bitmap_width", EDITABLE=False), - SInt16("bitmap_height", EDITABLE=False), - SInt16("bitmap_origin_x"), - SInt16("bitmap_origin_y"), - SInt16("hardware_character_index"), - Pad(2), - SInt32("pixels_offset", EDITABLE=False), - SIZE=20, WIDGET=FontCharacterFrame - ) - -font_body = Struct("tagdata", - Bool32("flags", - "never_override_with_remastered_font_under_MCC", - ), - SInt16("ascending_height"), - SInt16("descending_height"), - SInt16("leading_height"), - SInt16("leading_width"), - Pad(36), - - reflexive("character_tables", character_tables, 256), - dependency("bold", "font"), - dependency("italic", "font"), - dependency("condense", "font"), - dependency("underline", "font"), - reflexive("characters", character, 65535), - rawdata_ref("pixels", max_size=8388608), - SIZE=156, - ) +def get(): + return font_def font_def = TagDef("font", blam_header('font'), diff --git a/reclaimer/mcc_hek/defs/garb.py b/reclaimer/mcc_hek/defs/garb.py index 69805ad5..9bb87f3e 100644 --- a/reclaimer/mcc_hek/defs/garb.py +++ b/reclaimer/mcc_hek/defs/garb.py @@ -8,18 +8,21 @@ # from ...hek.defs.garb import * - -#import and use the mcc obje attrs from .obje import * +from .item import * # replace the object_type enum one that uses # the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=4) +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(4)) + ) -garb_body = dict(garb_body) -garb_body[0] = obje_attrs +garb_body = Struct("tagdata", + obje_attrs, + item_attrs, + SIZE=944, + ) def get(): return garb_def diff --git a/reclaimer/mcc_hek/defs/grhi.py b/reclaimer/mcc_hek/defs/grhi.py index e20fe702..5b4eb451 100644 --- a/reclaimer/mcc_hek/defs/grhi.py +++ b/reclaimer/mcc_hek/defs/grhi.py @@ -7,237 +7,16 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef +from ...hek.defs.grhi import * +from supyr_struct.util import desc_variant -messaging_information = Struct("messaging_information", - SInt16("sequence_index"), - SInt16("width_offset"), - QStruct("offset_from_reference_corner", - SInt16("x"), SInt16("y"), ORIENT='h' - ), - #QStruct("override_icon_color", INCLUDE=argb_byte), - UInt32("override_icon_color", INCLUDE=argb_uint32), - SInt8("frame_rate", MIN=0, MAX=30, UNIT_SCALE=per_sec_unit_scale), - Bool8("flags", - "use_text_from_string_list_instead", - "override_default_color", - "width_offset_is_absolute_icon_width", - ), - SInt16("text_index"), - SIZE=64 - ) - -effector = Struct("effector", - Pad(64), - SEnum16("destination_type", - "tint_0_to_1", - "horizontal_offset", - "vertical_offset", - "fade_0_to_1", - ), - SEnum16("destination", - "geometry_offset", - "primary_map", - "secondary_map", - "tertiary_map", - ), - SEnum16("source", - "player_pitch", - "player_tangent", - "player_yaw", - "weapon_total_ammo", - "weapon_loaded_ammo", - "weapon_heat", - "explicit", # use low bound - "weapon_zoom_level", - ), - - Pad(2), - QStruct("in_bounds", INCLUDE=from_to, - SIDETIP="source_units"), # source units - QStruct("out_bounds", INCLUDE=from_to, SIDETIP="pixels"), # pixels - - Pad(64), - QStruct("tint_color_lower_bound", INCLUDE=rgb_float), - QStruct("tint_color_upper_bound", INCLUDE=rgb_float), - Struct("periodic_functions", INCLUDE=anim_func_per_pha), - SIZE=220 - ) - -multitex_overlay = Struct("multitex_overlay", - Pad(2), - SInt16("type"), - SEnum16("framebuffer_blend_func", *framebuffer_blend_functions), - - # Anchors - Pad(34), - SEnum16("primary_anchor", *multitex_anchors), - SEnum16("secondary_anchor", *multitex_anchors), - SEnum16("tertiary_anchor", *multitex_anchors), - # Blending function - SEnum16("zero_to_one_blend_func", *blending_funcs), - SEnum16("one_to_two_blend_func", *blending_funcs), - - # Map scales - Pad(2), - QStruct("primary_scale", INCLUDE=xy_float), - QStruct("secondary_scale", INCLUDE=xy_float), - QStruct("tertiary_scale", INCLUDE=xy_float), - # Map offsets - QStruct("primary_offset", INCLUDE=xy_float), - QStruct("secondary_offset", INCLUDE=xy_float), - QStruct("tertiary_offset", INCLUDE=xy_float), +# NOTE: used by unhi and wphi +mcc_hud_anchor = SEnum16("anchor", *hud_anchors_mcc) - # Maps - dependency("primary_map", "bitm"), - dependency("secondary_map", "bitm"), - dependency("tertiary_map", "bitm"), - SEnum16("primary_wrap_mode", *multitex_wrap_modes), - SEnum16("secondary_wrap_mode", *multitex_wrap_modes), - SEnum16("tertiary_wrap_mode", *multitex_wrap_modes), - - Pad(186), - reflexive("effectors", effector, 30), - SIZE=480, - ) - -total_grenades_numbers = Struct("total_grenades_numbers", - QStruct("anchor_offset", - SInt16("x"), SInt16("y"), ORIENT='h' - ), - Float("width_scale"), - Float("height_scale"), - Bool16("scaling_flags", *hud_scaling_flags), - - Pad(22), - #QStruct("default_color", INCLUDE=argb_byte), - #QStruct("flashing_color", INCLUDE=argb_byte), - UInt32("default_color", INCLUDE=argb_uint32), - UInt32("flashing_color", INCLUDE=argb_uint32), - float_sec("flash_period"), - float_sec("flash_delay"), - SInt16("number_of_flashes"), - Bool16("flash_flags", *hud_flash_flags), - float_sec("flash_length"), - #QStruct("disabled_color", INCLUDE=argb_byte), - UInt32("disabled_color", INCLUDE=argb_uint32), - - Pad(4), - SInt8("maximum_number_of_digits"), - Bool8("flags", - "show_leading_zeros", - "only_show_when_zoomed", - "draw_a_trailing_m", - ), - SInt8("number_of_fractional_digits"), - - Pad(13), - SInt16("flash_cutoff"), - SIZE=88 +grhi_body = desc_variant(grhi_body, + ("anchor", mcc_hud_anchor), ) -overlay = Struct("overlay", - QStruct("anchor_offset", - SInt16("x"), SInt16("y"), ORIENT='h' - ), - Float("width_scale"), - Float("height_scale"), - Bool16("scaling_flags", *hud_scaling_flags), - - Pad(22), - #QStruct("default_color", INCLUDE=argb_byte), - #QStruct("flashing_color", INCLUDE=argb_byte), - UInt32("default_color", INCLUDE=argb_uint32), - UInt32("flashing_color", INCLUDE=argb_uint32), - float_sec("flash_period"), - float_sec("flash_delay"), - SInt16("number_of_flashes"), - Bool16("flash_flags", *hud_flash_flags), - float_sec("flash_length"), - #QStruct("disabled_color", INCLUDE=argb_byte), - UInt32("disabled_color", INCLUDE=argb_uint32), - - Pad(4), - float_sec("frame_rate"), - SInt16("sequence_index"), - Bool16("type", - "show_on_flashing", - "show_on_empty", - "show_on_default", - "show_always", - ), - Bool32("flags", - "flashes_when_active", - ), - - SIZE=136 - ) - -warning_sound = Struct("warning_sound", - dependency("sound", ('lsnd', 'snd!')), - Bool32("latched_to", - "low_grenade_sound", - "no_grenades_left", - "throw_on_no_grenades", - ), - Float("scale"), - SIZE=56 - ) - -# Use this with INCLUDE keywords since it will need to be named -hud_background = Struct("", - QStruct("anchor_offset", - SInt16("x"), SInt16("y"), ORIENT='h' - ), - Float("width_scale"), - Float("height_scale"), - Bool16("scaling_flags", *hud_scaling_flags), - - Pad(22), - dependency("interface_bitmap", "bitm"), - #QStruct("default_color", INCLUDE=argb_byte), - #QStruct("flashing_color", INCLUDE=argb_byte), - UInt32("default_color", INCLUDE=argb_uint32), - UInt32("flashing_color", INCLUDE=argb_uint32), - float_sec("flash_period"), - float_sec("flash_delay"), - SInt16("number_of_flashes"), - Bool16("flash_flags", *hud_flash_flags), - float_sec("flash_length"), - #QStruct("disabled_color", INCLUDE=argb_byte), - UInt32("disabled_color", INCLUDE=argb_uint32), - - Pad(4), - SInt16("sequence_index"), - - Pad(2), - reflexive("multitex_overlays", multitex_overlay, 30), - Pad(4), - SIZE=104 - ) - -grhi_body = Struct("tagdata", - SEnum16("anchor", *hud_anchors_mcc), - - Pad(34), - Struct("grenade_hud_background", INCLUDE=hud_background), - Struct("total_grenades", - Struct("background", INCLUDE=hud_background), - Struct("numbers", INCLUDE=total_grenades_numbers), - dependency("overlay_bitmap", "bitm"), - reflexive("overlays", overlay, 16), - ), - reflexive("warning_sounds", warning_sound, 12, - DYN_NAME_PATH='.sound.filepath'), - - Pad(68), - messaging_information, - SIZE=504, - ) - - def get(): return grhi_def diff --git a/reclaimer/mcc_hek/defs/hudg.py b/reclaimer/mcc_hek/defs/hudg.py index 8c5f0a1c..7993a3f1 100644 --- a/reclaimer/mcc_hek/defs/hudg.py +++ b/reclaimer/mcc_hek/defs/hudg.py @@ -7,183 +7,8 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -button_icon = Struct("button_icon", - SInt16("sequence_index"), - SInt16("width_offset"), - QStruct("offset_from_reference_corner", - SInt16("x"), SInt16("y"), ORIENT='h' - ), - #QStruct("override_icon_color", INCLUDE=argb_byte), - UInt32("override_icon_color", INCLUDE=argb_uint32), - SInt8("frame_rate", MIN=0, MAX=30, UNIT_SCALE=per_sec_unit_scale), - Bool8("flags", - "use_text_from_string_list_instead", - "override_default_color", - "width_offset_is_absolute_icon_width", - ), - SInt16("text_index"), - SIZE=16 - ) - -waypoint_arrow = Struct("waypoint_arrow", - ascii_str32("name"), - - Pad(8), - #QStruct("color", INCLUDE=xrgb_byte), - UInt32("color", INCLUDE=xrgb_uint32), - Float("opacity"), - Float("translucency"), - SInt16("on_screen_sequence_index"), - SInt16("off_screen_sequence_index"), - SInt16("occluded_sequence_index"), - - Pad(18), - Bool32("flags", - "dont_rotate_when_pointing_offscreen" - ), - SIZE=104 - ) - - -messaging_parameters = Struct("messaging_parameters", - SEnum16("anchor", *hud_anchors_mcc), - - Pad(34), - QStruct("anchor_offset", - SInt16("x"), SInt16("y"), ORIENT='h' - ), - Float("width_scale"), - Float("height_scale"), - Bool16("scaling_flags", *hud_scaling_flags), - - Pad(22), - dependency("single_player_font", "font"), - dependency("multi_player_font", "font"), - float_sec("up_time"), - float_sec("fade_time"), - QStruct("icon_color", INCLUDE=argb_float), - QStruct("text_color", INCLUDE=argb_float), - Float("text_spacing"), - dependency("item_message_text", "ustr"), - dependency("icon_bitmap", "bitm"), - dependency("alternate_icon_text", "ustr"), - SIZE=196 - ) - -hud_help_text_color = Struct("hud_help_text_color", - #QStruct("default_color", INCLUDE=argb_byte), - #QStruct("flashing_color", INCLUDE=argb_byte), - UInt32("default_color", INCLUDE=argb_uint32), - UInt32("flashing_color", INCLUDE=argb_uint32), - float_sec("flash_period"), - float_sec("flash_delay"), - SInt16("number_of_flashes"), - Bool16("flash_flags", *hud_flash_flags), - float_sec("flash_length"), - #QStruct("disabled_color", INCLUDE=argb_byte), - UInt32("disabled_color", INCLUDE=argb_uint32), - SIZE=32 - ) - -objective_colors = Struct("objective_colors", - #QStruct("default_color", INCLUDE=argb_byte), - #QStruct("flashing_color", INCLUDE=argb_byte), - UInt32("default_color", INCLUDE=argb_uint32), - UInt32("flashing_color", INCLUDE=argb_uint32), - float_sec("flash_period"), - float_sec("flash_delay"), - SInt16("number_of_flashes"), - Bool16("flash_flags", *hud_flash_flags), - float_sec("flash_length"), - #QStruct("disabled_color", INCLUDE=argb_byte), - UInt32("disabled_color", INCLUDE=argb_uint32), - SInt16("uptime_ticks", UNIT_SCALE=sec_unit_scale), - SInt16("fade_ticks", UNIT_SCALE=sec_unit_scale), - SIZE=32 - ) - -waypoint_parameters = Struct("waypoint_parameters", - Float("top_offset"), - Float("bottom_offset"), - Float("left_offset"), - Float("right_offset"), - - Pad(32), - dependency("arrow_bitmaps", "bitm"), - SIZE=64 - ) - -hud_globals = Struct("hud_globals", - Float("hud_scale_in_multiplayer"), - - Pad(256), - dependency("default_weapon_hud", "wphi"), - Float("motion_sensor_range"), - Float("motion_sensor_velocity_sensitivity"), - Float("motion_sensor_scale", DEFAULT=32.0), # DONT TOUCH(why?) - QStruct("default_chapter_title_bounds", - SInt16("t"), SInt16("l"), SInt16("b"), SInt16("r"), ORIENT='h' - ), - SIZE=340 - ) - -hud_damage_indicators = Struct("hud_damage_indicators", - SInt16("top_offset"), - SInt16("bottom_offset"), - SInt16("left_offset"), - SInt16("right_offset"), - - Pad(32), - dependency("indicator_bitmap", "bitm"), - SInt16("sequence_index"), - SInt16("multiplayer_sequence_index"), - #QStruct("color", INCLUDE=argb_byte), - UInt32("color", INCLUDE=argb_uint32), - SIZE=80 - ) - -time_running_out = Struct("time_running_out_flash_color", - #QStruct("default_color", INCLUDE=argb_byte), - #QStruct("flashing_color", INCLUDE=argb_byte), - UInt32("default_color", INCLUDE=argb_uint32), - UInt32("flashing_color", INCLUDE=argb_uint32), - float_sec("flash_period"), - float_sec("flash_delay"), - SInt16("number_of_flashes"), - Bool16("flash_flags", *hud_flash_flags), - float_sec("flash_length"), - #QStruct("disabled_color", INCLUDE=argb_byte), - UInt32("disabled_color", INCLUDE=argb_uint32), - SIZE=32 - ) - -time_out = Struct("time_out_flash_color", - #QStruct("default_color", INCLUDE=argb_byte), - #QStruct("flashing_color", INCLUDE=argb_byte), - UInt32("default_color", INCLUDE=argb_uint32), - UInt32("flashing_color", INCLUDE=argb_uint32), - float_sec("flash_period"), - float_sec("flash_delay"), - SInt16("number_of_flashes"), - Bool16("flash_flags", *hud_flash_flags), - float_sec("flash_length"), - #QStruct("disabled_color", INCLUDE=argb_byte), - UInt32("disabled_color", INCLUDE=argb_uint32), - SIZE=32 - ) - -misc_hud_crap = Struct("misc_hud_crap", - SInt16("loading_begin_text"), - SInt16("loading_end_text"), - SInt16("checkpoint_begin_text"), - SInt16("checkpoint_end_text"), - dependency("checkpoint", "snd!"), - SIZE=24 - ) +from ...hek.defs.hudg import * +from supyr_struct.util import desc_variant targets = Struct("targets", dependency("target_bitmap", "bitm"), @@ -208,40 +33,19 @@ SIZE=20 ) -remaps = Struct("remaps", +remap = Struct("remap", dependency("original_bitmap", "bitm"), reflexive("targets", targets, 26, DYN_NAME_PATH='.target_bitmap.filepath'), SIZE=28 ) +remaps = reflexive("remaps", remap, 32, DYN_NAME_PATH='.original_bitmap.filepath') -hudg_body = Struct("tagdata", - messaging_parameters, - reflexive("button_icons", button_icon, 18, - "a button", "b button", "x button", "y button", - "black button", "white button", "left trigger", "right trigger", - "dpad up", "dpad down", "dpad left", "dpad right", "start", "back", - "left thumb button", "right thumb button", "left stick", "right stick" - ), - hud_help_text_color, - dependency("hud_messages", "hmt "), - objective_colors, - waypoint_parameters, - reflexive("waypoint_arrows", waypoint_arrow, 16, DYN_NAME_PATH='.name'), - - Pad(80), - hud_globals, - hud_damage_indicators, - time_running_out, - time_out, - - Pad(40), - dependency("carnage_report_bitmap", "bitm"), - misc_hud_crap, - reflexive("remaps", remaps, 32, DYN_NAME_PATH='.original_bitmap.filepath'), - Pad(84), - SIZE=1104 +misc_hud_crap = desc_variant(misc_hud_crap, + ("unknown", remaps) + ) +hudg_body = desc_variant(hudg_body, + ("misc_hud_crap", misc_hud_crap) ) - def get(): return hudg_def diff --git a/reclaimer/mcc_hek/defs/jpt_.py b/reclaimer/mcc_hek/defs/jpt_.py index 9b4dac20..79f2d539 100644 --- a/reclaimer/mcc_hek/defs/jpt_.py +++ b/reclaimer/mcc_hek/defs/jpt_.py @@ -7,150 +7,14 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef +from ...hek.defs.jpt_ import * +from .cdmg import damage +from supyr_struct.util import desc_variant - -frequency_vibration = Struct("", - float_zero_to_one("frequency"), - float_sec("duration"), - SEnum16("fade_function", *fade_functions), - ) - -jpt__body = Struct("tagdata", - from_to_wu("radius"), - float_zero_to_one("cutoff_scale"), - Bool32("flags", - "dont_scale_by_distance", - ), - Pad(20), - - #Screen_Flash - Struct("screen_flash", - SEnum16("type", - "none", - "lighten", - "darken", - "max", - "min", - "invert", - "tint", - ), - SEnum16("priority", - "low", - "medium", - "high", - ), - Pad(12), - - float_sec("duration"), - SEnum16("fade_function", *fade_functions), - Pad(10), - - float_zero_to_one("maximum_intensity"), - Pad(4), - - QStruct("tint_lower_bound", INCLUDE=argb_float), - ), - - Struct("low_frequency_vibrate", INCLUDE=frequency_vibration), - Pad(10), - Struct("high_frequency_vibrate", INCLUDE=frequency_vibration), - Pad(30), - - Struct("temporary_camera_impulse", - float_sec("duration"), - SEnum16("fade_function", *fade_functions), - Pad(2), - - float_rad("rotation"), # radians - float_wu("pushback"), - from_to_wu("jitter"), - Pad(8), - ), - - float_rad("permanent_camera_impulse_angle"), - Pad(16), - - Struct("camera_shaking", - float_sec("duration"), - SEnum16("fade_function", *fade_functions), - Pad(2), - - float_wu("random_translation"), - float_rad("random_rotation"), # radians - Pad(12), - - SEnum16("wobble_function", *animation_functions), - Pad(2), - float_sec("wobble_function_period"), - Float("wobble_weight"), - Pad(32), - ), - - dependency("sound", "snd!"), - Pad(112), - - QStruct("breaking_effect", - float_wu_sec("forward_velocity"), - float_wu("forward_radius"), - Float("forward_exponent"), - Pad(12), - - float_wu_sec("outward_velocity"), - float_wu("outward_radius"), - Float("outward_exponent"), - Pad(12), - ), - - Struct("damage", - SEnum16("priority", - "none", - "harmless", - {NAME: "backstab", GUI_NAME: "lethal to the unsuspecting"}, - "emp", - ), - SEnum16("category", *damage_category), - Bool32("flags", - "does_not_hurt_owner", - {NAME: "headshot", GUI_NAME: "causes headshots"}, - "pings_resistant_units", - "does_not_hurt_friends", - "does_not_ping_units", - "detonates_explosives", - "only_hurts_shields", - "causes_flaming_death", - {NAME: "indicator_points_down", - GUI_NAME: "damage indicator always points down"}, - "skips_shields", - "only_hurts_one_infection_form", - {NAME: "multiplayer_headshot", - GUI_NAME: "causes multiplayer headshots"}, - "infection_form_pop", - "ignore_seat_scale_for_dir_dmg", - "forces_hard_ping", - "does_not_hurt_players", - "use_3d_instantaneous_acceleration", - "allow_any_non_zero_acceleration_value", - ), - float_wu("aoe_core_radius"), - Float("damage_lower_bound"), - QStruct("damage_upper_bound", INCLUDE=from_to), - float_zero_to_one("vehicle_passthrough_penalty"), - float_zero_to_one("active_camouflage_damage"), - float_zero_to_one("stun"), - float_zero_to_one("maximum_stun"), - float_sec("stun_time"), - Pad(4), - QStruct("instantaneous_acceleration", INCLUDE=ijk_float), - ), - - damage_modifiers, - SIZE=672, +jpt__body = desc_variant(jpt__body, + ("damage", damage), ) - def get(): return jpt__def diff --git a/reclaimer/mcc_hek/defs/lens.py b/reclaimer/mcc_hek/defs/lens.py index cde68ad2..3ed8027c 100644 --- a/reclaimer/mcc_hek/defs/lens.py +++ b/reclaimer/mcc_hek/defs/lens.py @@ -7,121 +7,27 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.lens import LensTag -from supyr_struct.defs.tag_def import TagDef +from ...hek.defs.lens import * +from supyr_struct.util import desc_variant -occlusion_comment = """Occlusion factor affects overall lens flare brightness and can also affect scale. -Occlusion never affects rotation.""" - -corona_rotation_comment = """Controls how corona rotation is affected by the viewer and light angles.""" - -reflection = Struct("reflection", +bitmaps = Struct("bitmaps", + dependency("bitmap", "bitm"), Bool16("flags", - "align_rotation_with_screen_center", - "radius_not_scaled_by_distance", - "radius_scaled_by_occlusion_factor", - "occluded_by_solid_objects", - ), - Pad(2), - SInt16("bitmap_index"), - Pad(22), - Float("position", SIDETIP="along flare axis"), # along flare axis - float_deg("rotation_offset"), # degrees - Pad(4), - from_to_wu("radius"), # world units - SEnum16("radius_scaled_by", - "none", - "rotation", - "rotation_and_strafing", - "distance_from_center", - ), - Pad(2), - from_to_zero_to_one("brightness"), # [0,1] - SEnum16("brightness_scaled_by", - "none", - "rotation", - "rotation_and_strafing", - "distance_from_center", - ), - Pad(2), - - #Tint color - QStruct("tint_color", INCLUDE=argb_float), - - #Animation - Struct("animation", - QStruct("color_lower_bound", INCLUDE=argb_float), - QStruct("color_upper_bound", INCLUDE=argb_float), - Bool16("flags", *blend_flags), - SEnum16("function", *animation_functions), - float_sec("period"), # seconds - float_sec("phase"), # seconds - ), - - SIZE=128 + "sun", + "no_occlusion_test", + "only_render_in_first_person", + "only_render_in_third_person", + "fade_in_more_quickly", + "fade_out_more_quickly", + "scale_by_marker", + ), + Pad(78), ) - -lens_body = Struct("tagdata", - float_rad("falloff_angle"), # radians - float_rad("cutoff_angle"), # radians - FlFloat("cosine_falloff_angle", VISIBLE=False), - FlFloat("cosine_cutoff_angle", VISIBLE=False), - Struct("occlusion", - float_wu("radius"), - SEnum16("offset_direction", - "toward_viewer", - "marker_forward", - "none", - ), - Pad(2), - float_wu("near_fade_distance"), - float_wu("far_fade_distance"), - COMMENT=occlusion_comment - ), - - Struct("bitmaps", - dependency("bitmap", "bitm"), - Bool16("flags", - "sun", - "no_occlusion_test", - "only_render_in_first_person", - "only_render_in_third_person", - "fade_in_more_quickly", - "fade_out_more_quickly", - "scale_by_marker", - ), - Pad(78), - ), - - Struct("corona_rotation", - SEnum16("function", - "none", - "rotation_a", - "rotation_b", - "rotation_translation", - "translation", - ), - Pad(2), - float_rad("function_scale"), # radians - COMMENT=corona_rotation_comment - ), - - Struct("corona_radius_scale", - Pad(24), - Float("horizontal_scale"), - Float("vertical_scale"), - ), - - Pad(28), - - reflexive("reflections", reflection, 32), - - SIZE=240, +lens_body = desc_variant(lens_body, + ("bitmaps", bitmaps), ) - def get(): return lens_def diff --git a/reclaimer/mcc_hek/defs/lifi.py b/reclaimer/mcc_hek/defs/lifi.py index 442889de..45d88732 100644 --- a/reclaimer/mcc_hek/defs/lifi.py +++ b/reclaimer/mcc_hek/defs/lifi.py @@ -8,17 +8,21 @@ # from ...hek.defs.lifi import * - -#import and use the mcc obje attrs from .obje import * +from .devi import * # replace the object_type enum one that uses # the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=9) +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(9)) + ) -lifi_body = dict(lifi_body) -lifi_body[0] = obje_attrs +lifi_body = Struct("tagdata", + obje_attrs, + devi_attrs, + + SIZE=720, + ) def get(): return lifi_def diff --git a/reclaimer/mcc_hek/defs/lsnd.py b/reclaimer/mcc_hek/defs/lsnd.py index 74bebd1b..6869b9fb 100644 --- a/reclaimer/mcc_hek/defs/lsnd.py +++ b/reclaimer/mcc_hek/defs/lsnd.py @@ -7,87 +7,20 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -scale_comment = """DETAIL SOUND PERIOD SCALING -As the sound's input scale changes from zero to one, these modifiers move between -the two values specified here. The sound will play using the current scale modifier -multiplied by the value specified below. (0 values are ignored.)""" - -random_spatialisation_comment = """RANDOM SPATIALIZATION -If the sound specified above is not stereo it will be randomly spatialized according -to the following contraints. If both lower and upper bounds are zero for any of -the following fields, the sound's position will be randomly selected from -all the possible directions or distances.""" - -detail_sound = Struct("detail_sound", - dependency("sound", "snd!"), - from_to_sec('random_period_bounds'), - Float("gain"), - Bool32("flags", - "dont_play_with_alternate", - "dont_play_without_alternate", - ), - - Pad(48), - from_to_rad('yaw_bounds', COMMENT=random_spatialisation_comment), # radians - from_to_rad('pitch_bounds'), # radians - from_to_wu('distance_bounds'), # world units - - SIZE=104 +from ...hek.defs.lsnd import * +from supyr_struct.util import desc_variant + +lsnd_flags = Bool32("flags", + "deafening_to_ai", + "not_a_loop", + "stops_music", + "siege_of_the_madrigal", ) -track = Struct("track", - Bool32("flags", - "fade_in_at_start", - "fade_out_at_stop", - "fade_in_alternate", - ), - Float("gain"), - float_sec("fade_in_duration"), - float_sec("fade_out_duration"), - - Pad(32), - dependency("start", "snd!"), - dependency("loop", "snd!"), - dependency("end", "snd!"), - - Pad(32), - dependency("alternate_loop", "snd!"), - dependency("alternate_end", "snd!"), - SIZE=160 +lsnd_body = desc_variant(lsnd_body, + ("flags", lsnd_flags), ) - -lsnd_body = Struct("tagdata", - Bool32("flags", - "deafening_to_ai", - "not_a_loop", - "stops_music", - "siege_of_the_madrigal", - ), - Float("detail_sound_period_at_zero", COMMENT=scale_comment), - FlFloat("unknown0", DEFAULT=1.0, VISIBLE=False), - FlFloat("unknown1", DEFAULT=1.0, VISIBLE=False), - Float("detail_sound_period_at_one"), - FlFloat("unknown2", DEFAULT=1.0, VISIBLE=False), - FlFloat("unknown3", DEFAULT=1.0, VISIBLE=False), - FlSInt16("unknown4", DEFAULT=-1, VISIBLE=False), - FlSInt16("unknown5", DEFAULT=-1, VISIBLE=False), - FlFloat("unknown6", DEFAULT=1.0, VISIBLE=False), - Pad(8), - dependency("continuous_damage_effect", "cdmg"), - - reflexive("tracks", track, 4), - reflexive("detail_sounds", detail_sound, 32, - DYN_NAME_PATH='.sound.filepath'), - - SIZE=84, - ) - - def get(): return lsnd_def diff --git a/reclaimer/mcc_hek/defs/mach.py b/reclaimer/mcc_hek/defs/mach.py index 3fad1d55..3c973730 100644 --- a/reclaimer/mcc_hek/defs/mach.py +++ b/reclaimer/mcc_hek/defs/mach.py @@ -8,17 +8,22 @@ # from ...hek.defs.mach import * - -#import and use the mcc obje attrs from .obje import * +from .devi import * # replace the object_type enum one that uses # the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=7) +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(7)) + ) -mach_body = dict(mach_body) -mach_body[0] = obje_attrs +mach_body = Struct("tagdata", + obje_attrs, + devi_attrs, + mach_attrs, + + SIZE=804, + ) def get(): return mach_def diff --git a/reclaimer/mcc_hek/defs/matg.py b/reclaimer/mcc_hek/defs/matg.py index a488f7e5..c4d3f593 100644 --- a/reclaimer/mcc_hek/defs/matg.py +++ b/reclaimer/mcc_hek/defs/matg.py @@ -7,372 +7,15 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef +from ...hek.defs.matg import * +from supyr_struct.util import desc_variant -def get(): - return matg_def - -camera = Struct("camera", - dependency('camera', "trak"), - SIZE=16 - ) - -sound = Struct("sound", - dependency('sound', "snd!"), - SIZE=16 - ) - -look_function = Struct("look_function", - Float('scale'), - SIZE=4 - ) - -player_control = Struct("player_control", - Float('magnetism_friction'), - Float('magnetism_adhesion'), - Float('inconsequential_target_scale'), - - Pad(52), - float_sec('look_acceleration_time'), - Float('look_acceleration_scale'), - float_zero_to_one('look_peg_threshold'), - float_deg('look_default_pitch_rate'), # degrees - float_deg('look_default_yaw_rate'), # degrees - Float('look_autolevelling_scale'), - - Pad(20), - SInt16('minimum_weapon_swap_ticks'), - SInt16('minimum_autolevelling_ticks'), - float_rad('minimum_angle_for_vehicle_flip'), # radians - reflexive("look_functions", look_function, 16), - - SIZE=128 - ) - -difficulty_base = QStruct("", - Float("easy"), - Float("normal"), - Float("hard"), - Float("imposs"), - ORIENT='h' - ) - -difficulty = Struct("difficulty", - # Health - Struct("enemy_scales", - QStruct("damage", INCLUDE=difficulty_base), - QStruct("vitality", INCLUDE=difficulty_base), - QStruct("shield", INCLUDE=difficulty_base), - QStruct("recharge", INCLUDE=difficulty_base), - ), - Struct("friend_scales", - QStruct("damage", INCLUDE=difficulty_base), - QStruct("vitality", INCLUDE=difficulty_base), - QStruct("shield", INCLUDE=difficulty_base), - QStruct("recharge", INCLUDE=difficulty_base), - ), - QStruct("infection_form_vitality_scales", INCLUDE=difficulty_base), - - Pad(16), - # Enemy ranged fire - Struct("ranged_combat_scales", - QStruct("rate_of_fire", INCLUDE=difficulty_base), - QStruct("projectile_error", INCLUDE=difficulty_base), - QStruct("burst_error", INCLUDE=difficulty_base), - QStruct("new_target_delay", INCLUDE=difficulty_base), - QStruct("burst_separation", INCLUDE=difficulty_base), - QStruct("target_tracking", INCLUDE=difficulty_base), - QStruct("target_leading", INCLUDE=difficulty_base), - QStruct("overcharge_chance", INCLUDE=difficulty_base), - QStruct("special_fire_delay", INCLUDE=difficulty_base), - QStruct("guidance_vs_player", INCLUDE=difficulty_base), - ), - - Struct("close_combat_scales", - QStruct("melee_delay_base", - GUI_NAME="melee delay base(not a scale)", INCLUDE=difficulty_base - ), - QStruct("melee_delay", INCLUDE=difficulty_base), - - Pad(16), - # Grenades - QStruct("grenade_chance", INCLUDE=difficulty_base), - QStruct("grenade_timer", INCLUDE=difficulty_base), - ), - - Pad(48), - # Placement - Struct("major_upgrade_fractions", - QStruct("normal", INCLUDE=difficulty_base), - QStruct("few", INCLUDE=difficulty_base), - QStruct("many", INCLUDE=difficulty_base), - ), - - SIZE=644 - ) - -grenade = Struct("grenade", - SInt16('maximum_count'), - SInt16('mp_spawn_default'), - dependency('throwing_effect', "effe"), - dependency('hud_interface', "grhi"), - dependency('equipment', "eqip"), - dependency('projectile', "proj"), - SIZE=68 - ) - -rasterizer_data = Struct("rasterizer_data", - Struct("function_textures", - dependency('distance_attenuation', "bitm"), - dependency('vector_normalization', "bitm"), - dependency('atmospheric_fog_density', "bitm"), - dependency('planar_fog_density', "bitm"), - dependency('linear_corner_fade', "bitm"), - dependency('active_camouflage_distortion', "bitm"), - dependency('glow', "bitm"), - Pad(60), - ), - - # Default textures - Struct("default_textures", - dependency('default_2d', "bitm"), - dependency('default_3d', "bitm"), - dependency('default_cubemap', "bitm"), - ), - - # Experimental textures - Struct("experimental_textures", - dependency('test0', "bitm"), - dependency('test1', "bitm"), - dependency('test2', "bitm"), - dependency('test3', "bitm"), - ), - - # video effect textures - Struct("video_effect_textures", - dependency('video_scanline_map', "bitm"), - dependency('video_noise_map', "bitm"), - Pad(52), - ), - - # Active camouflage - Struct("active_camouflage", - Bool16("flags", - "tint_edge_density" - ), - Pad(2), - Float('refraction_amount', SIDETIP="pixels"), # pixels - Float('distance_falloff'), - QStruct('tint_color', INCLUDE=rgb_float), - Float('hyper_stealth_refraction_amount', SIDETIP="pixels"), # pixels - Float('hyper_stealth_distance_falloff'), - QStruct('hyper_stealth_tint_color', INCLUDE=rgb_float), - ), - - # PC textures - dependency('distance_attenuation_2d', "bitm"), - - SIZE=428 +matg_body = desc_variant(matg_body, + ("grenades", reflexive("grenades", grenade, 4, *grenade_types_mcc)), ) -interface_bitmaps = Struct("interface_bitmaps", - dependency('font_system', "font"), - dependency('font_terminal', "font"), - dependency('screen_color_table', "colo"), - dependency('hud_color_table', "colo"), - dependency('editor_color_table', "colo"), - dependency('dialog_color_table', "colo"), - dependency('hud_globals', "hudg"), - dependency('motion_sensor_sweep_bitmap', "bitm"), - dependency('motion_sensor_sweep_bitmap_mask', "bitm"), - dependency('multiplayer_hud_bitmap', "bitm"), - dependency('localization', "str#"), - dependency('hud_digits_definition', "hud#"), - dependency('motion_sensor_blip', "bitm"), - dependency('interface_goo_map1', "bitm"), - dependency('interface_goo_map2', "bitm"), - dependency('interface_goo_map3', "bitm"), - SIZE=304 - ) - -cheat_weapon = Struct("weapon", - dependency('weapon', valid_items), - SIZE=16 - ) - -cheat_powerup = Struct("powerup", - dependency('powerup', "eqip"), - SIZE=16 - ) - -vehicle = Struct("vehicle", - dependency('vehicle', "vehi"), - SIZE=16 - ) - -multiplayer_information = Struct("multiplayer_information", - dependency('flag', valid_items), - dependency('unit', valid_units), - reflexive('vehicles', vehicle, 20, DYN_NAME_PATH='.vehicle.filepath'), - dependency('hill_shader', valid_shaders), - dependency('flag_shader', valid_shaders), - dependency('ball', valid_items), - reflexive('sounds', sound, 60, DYN_NAME_PATH='.sound.filepath'), - SIZE=160 - ) - -player_information = Struct("player_information", - dependency('unit', valid_units), - - Pad(28), - float_wu_sec("walking_speed"), # world units/second - Float("double_speed_multiplier", SIDETIP="[1.0,+inf]", MIN=1.0), - float_wu_sec("run_forward"), # world units/second - float_wu_sec("run_backward"), # world units/second - float_wu_sec("run_sideways"), # world units/second - float_wu_sec_sq("run_acceleration", - UNIT_SCALE=per_sec_unit_scale), # world units/second^2 - float_wu_sec("sneak_forward"), # world units/second - float_wu_sec("sneak_backward"), # world units/second - float_wu_sec("sneak_sideways"), # world units/second - float_wu_sec_sq("sneak_acceleration", - UNIT_SCALE=per_sec_unit_scale), # world units/second^2 - float_wu_sec_sq("airborne_acceleration", - UNIT_SCALE=per_sec_unit_scale), # world units/second^2 - Float("speed_multiplier", SIDETIP="multiplayer only"), # multiplayer only - - Pad(12), - QStruct("grenade_origin", INCLUDE=xyz_float), - - Pad(12), - float_zero_to_one("stun_movement_penalty"), - float_zero_to_one("stun_turning_penalty"), - float_zero_to_one("stun_jumping_penalty"), - Float("minimum_stun_time"), - Float("maximum_stun_time"), - - Pad(8), - from_to_sec("first_person_idle_time"), - float_zero_to_one("first_person_skip_fraction"), - - Pad(16), - dependency('coop_respawn_effect', "effe"), - SIZE=244 - ) - -first_person_interface = Struct("first_person_interface", - dependency('first_person_hands', valid_models), - dependency('base_bitmap', "bitm"), - dependency('shield_meter', "metr"), - QStruct('shield_meter_origin', - SInt16('x'), SInt16('y'), ORIENT='h' - ), - dependency('body_meter', "metr"), - QStruct('body_meter_origin', - SInt16('x'), SInt16('y'), ORIENT='h' - ), - dependency('night_vision_toggle_on_effect', "effe"), - dependency('night_vision_toggle_off_effect', "effe"), - - SIZE=192 - ) - -falling_damage = Struct("falling_damage", - Pad(8), - QStruct("harmful_falling_distance", INCLUDE=from_to), - dependency('falling_damage', "jpt!"), - - Pad(8), - Float("maximum_falling_distance"), - dependency('distance_damage', "jpt!"), - dependency('vehicle_environment_collision_damage', "jpt!"), - dependency('vehicle_killed_unit_damage', "jpt!"), - dependency('vehicle_collision_damage', "jpt!"), - dependency('flaming_death_damage', "jpt!"), - SIZE=152 - ) - -particle_effect = Struct("particle_effect", - dependency('particle_type', "part"), - Bool32("flags", *blend_flags), - float_wu("density"), - QStruct("velocity_scale", INCLUDE=from_to), - - Pad(4), - from_to_rad_sec("angular_velocity"), # radians/second - - Pad(8), - from_to_wu("radius"), # world units - - Pad(8), - QStruct("tint_lower_bound", INCLUDE=argb_float), - QStruct("tint_upper_bound", INCLUDE=argb_float), - SIZE=128 - ) - -material = Struct("material", - Pad(148), - # Vehicle terrain parameters - Float("ground_friction_scale"), - Float("ground_friction_normal_k1_scale"), - Float("ground_friction_normal_k0_scale"), - Float("ground_depth_scale"), - Float("ground_damp_fraction_scale"), - - # Breakable surface parameters - Pad(556), - Float("maximum_vitality"), - - Pad(12), - dependency('effect', "effe"), - dependency('sound', "snd!"), - - Pad(24), - reflexive("particle_effects", particle_effect, 8, - DYN_NAME_PATH='.particle_type.filepath'), - - Pad(60), - dependency('melee_hit_sound', "snd!"), - SIZE=884 - ) - -playlist_member = Struct("playlist_member", - ascii_str32("map_name"), - ascii_str32("game_variant"), - SInt32('minimum_experience'), - SInt32('maximum_experience'), - SInt32('minimum_player_count'), - SInt32('maximum_player_count'), - SInt32('rating'), - SIZE=148 - ) - -matg_body = Struct('tagdata', - Pad(248), - reflexive("sounds", sound, 2, - "enter water", "exit water"), - reflexive("cameras", camera, 1), - reflexive("player_controls", player_control, 1), - reflexive("difficulties", difficulty, 1), - reflexive("grenades", grenade, 4, *grenade_types_mcc), - reflexive("rasterizer_datas", rasterizer_data, 1), - reflexive("interface_bitmaps", interface_bitmaps, 1), - reflexive("cheat_weapons", cheat_weapon, 20, - DYN_NAME_PATH='.weapon.filepath'), - reflexive("cheat_powerups", cheat_powerup, 20, - DYN_NAME_PATH='.powerup.filepath'), - reflexive("multiplayer_informations", multiplayer_information, 1), - reflexive("player_informations", player_information, 1), - reflexive("first_person_interfaces", first_person_interface, 1), - reflexive("falling_damages", falling_damage, 1), - reflexive("materials", material, len(materials_list), *materials_list), - reflexive("playlist_members", playlist_member, 20, - DYN_NAME_PATH='.map_name'), - - SIZE=428 - ) +def get(): + return matg_def matg_def = TagDef("matg", blam_header('matg', 3), diff --git a/reclaimer/mcc_hek/defs/obje.py b/reclaimer/mcc_hek/defs/obje.py index 1b51dd11..75a03a27 100644 --- a/reclaimer/mcc_hek/defs/obje.py +++ b/reclaimer/mcc_hek/defs/obje.py @@ -7,136 +7,22 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.obje import ObjeTag -from supyr_struct.defs.tag_def import TagDef +from ...hek.defs.obje import * +from supyr_struct.util import desc_variant -def get(): - return obje_def - -attachment = Struct('attachment', - dependency('type', valid_attachments), - ascii_str32('marker'), - SEnum16('primary_scale', *function_outputs), - SEnum16('secondary_scale', *function_outputs), - SEnum16('change_color', *function_names), - - SIZE=72 - ) - -widget = Struct('widget', - dependency('reference', valid_widgets), - SIZE=32 - ) - -function = Struct('function', - Bool32('flags', - 'invert', - 'additive', - 'always_active', - ), - float_sec('period', UNIT_SCALE=sec_unit_scale), # seconds - SEnum16('scale_period_by', *function_inputs_outputs), - SEnum16('function', *animation_functions), - SEnum16('scale_function_by', *function_inputs_outputs), - SEnum16('wobble_function', *animation_functions), - float_sec('wobble_period', UNIT_SCALE=sec_unit_scale), # seconds - Float('wobble_magnitude', SIDETIP="%"), # percent - - Float('square_wave_threshold'), - SInt16('step_count'), - SEnum16('map_to', *fade_functions), - SInt16('sawtooth_count'), - SEnum16('add', *function_inputs_outputs), - SEnum16('scale_result_by', *function_inputs_outputs), - SEnum16('bounds_mode', - 'clip', - 'clip_and_normalize', - 'scale_to_fit', - ), - QStruct('bounds', INCLUDE=from_to), - - Pad(6), - dyn_senum16('turn_off_with', DYN_NAME_PATH="..[DYN_I].usage"), - Float('scale_by'), - - Pad(268), - ascii_str32('usage'), - - SIZE=360 - ) - -permutation = Struct('permutation', - Float('weight'), - QStruct('color_lower_bound', INCLUDE=rgb_float), - QStruct('color_upper_bound', INCLUDE=rgb_float), - - SIZE=28 - ) - -change_color = Struct('change_color', - SEnum16('darken_by', *function_inputs_outputs), - SEnum16('scale_by', *function_inputs_outputs), - Bool32('flags', - 'blend_in_hsv', - 'more_colors', - ), - QStruct('color_lower_bound', INCLUDE=rgb_float), - QStruct('color_upper_bound', INCLUDE=rgb_float), - reflexive("permutations", permutation, 8), - - SIZE=44 +obje_flags = Bool16('flags', + 'does_not_cast_shadow', + 'transparent_self_occlusion', + 'brighter_than_it_should_be', + 'not_a_pathfinding_obstacle', + "extension_of_parent", + "cast_shadow_by_default", + "does_not_have_remastered_geometry", + {NAME: 'xbox_unknown_bit_8', VALUE: 1<<8, VISIBLE: False}, + {NAME: 'xbox_unknown_bit_11', VALUE: 1<<11, VISIBLE: False}, ) - -obje_attrs = Struct('obje_attrs', - FlSEnum16("object_type", - *((object_types[i], i - 1) for i in - range(len(object_types))), - VISIBLE=False, DEFAULT=-1 - ), - Bool16('flags', - 'does_not_cast_shadow', - 'transparent_self_occlusion', - 'brighter_than_it_should_be', - 'not_a_pathfinding_obstacle', - "extension of parent", - "cast shadow by default", - "does not have remastered geometry", - ), - float_wu('bounding_radius'), - QStruct('bounding_offset', INCLUDE=xyz_float), - QStruct('origin_offset', INCLUDE=xyz_float), - float_zero_to_inf('acceleration_scale', UNIT_SCALE=per_sec_unit_scale), - - Pad(4), - dependency('model', valid_models), - dependency('animation_graph', "antr"), - - Pad(40), - dependency('collision_model', "coll"), - dependency('physics', "phys"), - dependency('modifier_shader', valid_shaders), - dependency('creation_effect', "effe"), - Pad(84), - float_wu('render_bounding_radius'), - - #Export to functions - SEnum16('A_in', *object_export_to), - SEnum16('B_in', *object_export_to), - SEnum16('C_in', *object_export_to), - SEnum16('D_in', *object_export_to), - - Pad(44), - SInt16('hud_text_message_index'), - SInt16('forced_shader_permutation_index'), - reflexive("attachments", attachment, 8, DYN_NAME_PATH='.type.filepath'), - reflexive("widgets", widget, 4, DYN_NAME_PATH='.reference.filepath'), - reflexive("functions", function, 4, DYN_NAME_PATH='.usage'), - reflexive("change_colors", change_color, 4, - 'A', 'B', 'C', 'D'), - reflexive("predicted_resources", predicted_resource, 1024, VISIBLE=False), - - SIZE=380 +obje_attrs = desc_variant(obje_attrs, + ("flags", obje_flags), ) obje_body = Struct('tagdata', @@ -144,6 +30,9 @@ def get(): SIZE=380 ) +def get(): + return obje_def + obje_def = TagDef("obje", blam_header('obje'), obje_body, diff --git a/reclaimer/mcc_hek/defs/plac.py b/reclaimer/mcc_hek/defs/plac.py index 2f9bc5c5..b1ed51e5 100644 --- a/reclaimer/mcc_hek/defs/plac.py +++ b/reclaimer/mcc_hek/defs/plac.py @@ -8,17 +8,19 @@ # from ...hek.defs.plac import * - -#import and use the mcc obje attrs from .obje import * # replace the object_type enum one that uses # the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=10) +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(10)) + ) -plac_body = dict(plac_body) -plac_body[0] = obje_attrs +plac_body = Struct("tagdata", + obje_attrs, + + SIZE=508, + ) def get(): return plac_def diff --git a/reclaimer/mcc_hek/defs/proj.py b/reclaimer/mcc_hek/defs/proj.py index 63e03464..f10f63d9 100644 --- a/reclaimer/mcc_hek/defs/proj.py +++ b/reclaimer/mcc_hek/defs/proj.py @@ -7,121 +7,38 @@ # See LICENSE for more information. # +from ...hek.defs.proj import * from .obje import * -from .objs.obje import ObjeTag from supyr_struct.util import desc_variant +potential_response_flags = Bool16("flags", + "only_against_units", + "never_against_units" + ) +potential_response_descs = [ + desc for desc in material_response.values() + if isinstance(desc, dict) and desc.get("NAME") == "potential_response" + ] +if not potential_response_descs: + raise ValueError("Could not locate descriptor 'potential_response' in material_response") + +potential_response = desc_variant(potential_response_descs[0], + ("flags", potential_response_flags) + ) +material_response = desc_variant(material_response, + ("potential_response", potential_response) + ) +material_responses = reflexive("material_responses", + material_response, len(materials_list), *materials_list + ) + # replace the object_type enum one that uses # the correct default value for this object obje_attrs = desc_variant(obje_attrs, ("object_type", object_type(5)) ) - -responses = ( - "disappear", - "detonate", - "reflect", - "overpenetrate", - "attach" - ) - -material_response = Struct("material_response", - Bool16("flags", - "cannot_be_overpenetrated", - ), - SEnum16('response', *responses), - dependency('effect', "effe"), - - Pad(16), - Struct("potential_response", - SEnum16('response', *responses), - Bool16("flags", - "only_against_units", - "never against units" - ), - float_zero_to_one("skip_fraction"), - from_to_rad("impact_angle"), # radians - from_to_wu_sec("impact_velocity"), # world units/second - dependency('effect', "effe"), - ), - - Pad(16), - SEnum16("scale_effects_by", - "damage", - "angle", - ), - Pad(2), - float_rad("angular_noise"), - float_wu_sec("velocity_noise"), - dependency('detonation_effect', "effe"), - - Pad(24), - # Penetration - Float("initial_friction", UNIT_SCALE=per_sec_unit_scale), - Float("maximum_distance"), - - # Reflection - Float("parallel_refriction", UNIT_SCALE=per_sec_unit_scale), - Float("perpendicular_friction", UNIT_SCALE=per_sec_unit_scale), - - SIZE=160 - ) - -proj_attrs = Struct("proj_attrs", - Bool32("flags", - "oriented_along_velocity", - "ai_must_use_ballistic_aiming", - "detonation_max_time_if_attached", - "has_super_combining_explosion", - "add_parent_velocity_to_initial_velocity", - "random_attached_detonation_time", - "minimum_unattached_detonation_time", - ), - SEnum16('detonation_timer_starts', - "immediately", - "on_first_bounce", - "when_at_rest", - ), - SEnum16('impact_noise', *sound_volumes), - SEnum16('A_in', *projectile_inputs), - SEnum16('B_in', *projectile_inputs), - SEnum16('C_in', *projectile_inputs), - SEnum16('D_in', *projectile_inputs), - dependency('super_detonation', "effe"), - float_wu("ai_perception_radius"), - float_wu("collision_radius"), - - Struct("detonation", - float_sec("arming_time"), - float_wu("danger_radius"), - dependency('effect', "effe"), - from_to_sec("timer"), - float_wu_sec("minimum_velocity"), - float_wu("maximum_range"), - ), - - Struct("physics", - Float("air_gravity_scale"), - from_to_wu("air_damage_range"), - Float("water_gravity_scale"), - from_to_wu("water_damage_range"), - float_wu_sec("initial_velocity"), # world units/sec - float_wu_sec("final_velocity"), # world units/sec - float_rad_sec("guided_angular_velocity"), # radians/second - SEnum16('detonation_noise', *sound_volumes), - - Pad(2), - dependency('detonation_started', "effe"), - dependency('flyby_sound', "snd!"), - dependency('attached_detonation_damage', "jpt!"), - dependency('impact_damage', "jpt!"), - ), - - Pad(12), - reflexive("material_responses", material_response, - len(materials_list), *materials_list), - - SIZE=208 +proj_attrs = desc_variant(proj_attrs, + ("material_responses", material_responses) ) proj_body = Struct("tagdata", @@ -130,7 +47,6 @@ SIZE=588, ) - def get(): return proj_def diff --git a/reclaimer/mcc_hek/defs/scen.py b/reclaimer/mcc_hek/defs/scen.py index e86fc344..68f69be8 100644 --- a/reclaimer/mcc_hek/defs/scen.py +++ b/reclaimer/mcc_hek/defs/scen.py @@ -7,10 +7,8 @@ # See LICENSE for more information. # +from ...hek.defs.scen import * from .obje import * -from .objs.obje import ObjeTag -from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant # replace the object_type enum one that uses # the correct default value for this object @@ -20,14 +18,10 @@ scen_body = Struct("tagdata", obje_attrs, - Pad(2), - Bool32('flags', - 'unused', - ), + SIZE=508, ) - def get(): return scen_def diff --git a/reclaimer/mcc_hek/defs/scex.py b/reclaimer/mcc_hek/defs/scex.py index 491812e8..aea24228 100644 --- a/reclaimer/mcc_hek/defs/scex.py +++ b/reclaimer/mcc_hek/defs/scex.py @@ -7,41 +7,12 @@ # See LICENSE for more information. # +from ...hek.defs.scex import * from .schi import * -from supyr_struct.defs.tag_def import TagDef -from .objs.shdr import ShdrTag +from supyr_struct.util import desc_variant -chicago_2_stage_maps = Struct("two_stage_map", INCLUDE=chicago_4_stage_maps) - -scex_attrs = Struct("scex_attrs", - # Shader Properties - Struct("chicago_shader_extended", - UInt8("numeric_counter_limit", - MIN=0, MAX=255, SIDETIP="[0,255]"), # [0,255] - - Bool8("chicago_shader_flags", *trans_shdr_properties), - SEnum16("first_map_type", *trans_shdr_first_map_type), - SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), - SEnum16("framebuffer_fade_mode", *render_fade_mode), - SEnum16("framebuffer_fade_source", *function_outputs), - Pad(2), - ), - - #Lens Flare - float_wu("lens_flare_spacing"), # world units - dependency("lens_flare", "lens"), - reflexive("extra_layers", extra_layers_block, 4, - DYN_NAME_PATH='.filepath'), - reflexive("four_stage_maps", chicago_4_stage_maps, 4, - DYN_NAME_PATH='.bitmap.filepath'), - reflexive("two_stage_maps", chicago_2_stage_maps, 2, - DYN_NAME_PATH='.bitmap.filepath'), - Bool32("extra_flags", - "dont_fade_active_camouflage", - "numeric_countdown_timer" - "custom_edition_blending", - ), - SIZE=80 +scex_attrs = desc_variant(scex_attrs, + ("extra_flags", extra_flags) ) scex_body = Struct("tagdata", @@ -50,7 +21,6 @@ SIZE=120 ) - def get(): return scex_def diff --git a/reclaimer/mcc_hek/defs/schi.py b/reclaimer/mcc_hek/defs/schi.py index 4c7f7fa3..d0e3d473 100644 --- a/reclaimer/mcc_hek/defs/schi.py +++ b/reclaimer/mcc_hek/defs/schi.py @@ -7,69 +7,17 @@ # See LICENSE for more information. # +from ...hek.defs.schi import * from .shdr import * -from supyr_struct.defs.tag_def import TagDef -from .objs.shdr import ShdrTag +from supyr_struct.util import desc_variant -chicago_4_stage_maps = Struct("four_stage_map", - Bool16("flags" , - "unfiltered", - "alpha_replicate", - "u_clamped", - "v_clamped", - ), - - Pad(42), - SEnum16("color_function", *blend_functions), - SEnum16("alpha_function", *blend_functions), - - Pad(36), - #shader transformations - Float("map_u_scale"), - Float("map_v_scale"), - Float("map_u_offset"), - Float("map_v_offset"), - float_deg("map_rotation"), # degrees - float_zero_to_one("map_bias"), # [0,1] - dependency("bitmap", "bitm"), - - #shader animations - Pad(40), - Struct("u_animation", INCLUDE=anim_src_func_per_pha_sca), - Struct("v_animation", INCLUDE=anim_src_func_per_pha_sca), - Struct("rotation_animation", INCLUDE=anim_src_func_per_pha_sca_rot), - - QStruct("rotation_center", INCLUDE=xy_float), - SIZE=220, +extra_flags = Bool32("extra_flags", + "dont_fade_active_camouflage", + "numeric_countdown_timer", + "custom_edition_blending", ) - -schi_attrs = Struct("schi_attrs", - # Shader Properties - Struct("chicago_shader", - UInt8("numeric_counter_limit", - MIN=0, MAX=255, SIDETIP="[0,255]"), # [0,255] - - Bool8("chicago_shader_flags", *trans_shdr_properties), - SEnum16("first_map_type", *trans_shdr_first_map_type), - SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), - SEnum16("framebuffer_fade_mode", *render_fade_mode), - SEnum16("framebuffer_fade_source", *function_outputs), - Pad(2), - ), - - #Lens Flare - float_wu("lens_flare_spacing"), # world units - dependency("lens_flare", "lens"), - reflexive("extra_layers", extra_layers_block, 4, - DYN_NAME_PATH='.filepath'), - reflexive("maps", chicago_4_stage_maps, 4, - DYN_NAME_PATH='.bitmap.filepath'), - Bool32("extra_flags", - "dont_fade_active_camouflage", - "numeric_countdown_timer", - "custom_edition_blending", - ), - SIZE=68 +schi_attrs = desc_variant(schi_attrs, + ("extra_flags", extra_flags) ) schi_body = Struct("tagdata", @@ -78,7 +26,6 @@ SIZE=108 ) - def get(): return schi_def diff --git a/reclaimer/mcc_hek/defs/scnr.py b/reclaimer/mcc_hek/defs/scnr.py index cc3df953..387d98d2 100644 --- a/reclaimer/mcc_hek/defs/scnr.py +++ b/reclaimer/mcc_hek/defs/scnr.py @@ -7,615 +7,81 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef +from ...hek.defs.scnr import * from supyr_struct.util import desc_variant - -def object_reference(name, *args, **kwargs): - "Macro to cut down on a lot of code" - block_name = kwargs.pop('block_name', name + 's').replace(' ', '_') - - dyn_type_path = ".....%s_palette.STEPTREE[DYN_I].name.filepath" % block_name - return Struct(name, - dyn_senum16('type', DYN_NAME_PATH=dyn_type_path), - dyn_senum16('name', - DYN_NAME_PATH=".....object_names.STEPTREE[DYN_I].name"), - Bool16('not_placed', - "automatically", - "on_easy", - "on_normal", - "on_hard", - "use_player_appearance", - ), - SInt16('desired_permutation'), - QStruct("position", INCLUDE=xyz_float), - ypr_float_rad("rotation"), - *args, - **kwargs - ) - -def object_swatch(name, def_id, size=48): - "Macro to cut down on a lot of code" - return Struct(name, - dependency("name", def_id), - SIZE=size - ) - -fl_float_xyz = QStruct("", - FlFloat("x"), - FlFloat("y"), - FlFloat("z"), - ORIENT="h" - ) - -stance_flags = FlBool16("stance", - "walk", - "look_only", - "primary_fire", - "secondary_fire", - "jump", - "crouch", - "melee", - "flashlight", - "action1", - "action2", - "action_hold", - ) - -unit_control_packet = Struct("unit_control_packet", - - ) - -r_a_stream_header = Struct("r_a_stream_header", - UInt8("move_index", DEFAULT=3, MAX=6), - UInt8("bool_index"), - stance_flags, - FlSInt16("weapon", DEFAULT=-1), - QStruct("speed", FlFloat("x"), FlFloat("y"), ORIENT="h"), - QStruct("feet", INCLUDE=fl_float_xyz), - QStruct("body", INCLUDE=fl_float_xyz), - QStruct("head", INCLUDE=fl_float_xyz), - QStruct("change", INCLUDE=fl_float_xyz), - FlUInt16("unknown1"), - FlUInt16("unknown2"), - FlUInt16("unknown3", DEFAULT=0xFFFF), - FlUInt16("unknown4", DEFAULT=0xFFFF), - SIZE=60 - ) - -device_flags = ( - "initially_open", # value of 1.0 - "initially_off", # value of 0.0 - "can_change_only_once", - "position_reversed", - "not_usable_from_any_side" - ) - -location_types = ( - "none", - "ctf", - "slayer", - "oddball", - "king", - "race", - "terminator", - "stub", - "ignored1", - "ignored2", - "ignored3", - "ignored4", - "all_games", - "all_games_except_ctf", - "all_games_except_ctf_and_race" - ) - -group_indices = tuple("ABCDEFGHIJKLMNOPQRSTUVWXYZ") - -squad_states = ( - "none", - "sleeping", - "alert", - "moving_repeat_same_position", - "moving_loop", - "moving_loop_back_and_forth", - "moving_loop_randomly", - "moving_randomly", - "guarding", - "guarding_at_position", - "searching", - "fleeing" - ) - -maneuver_when_states = ( - "never", - {NAME: "strength_at_75_percent", GUI_NAME: "< 75% strength"}, - {NAME: "strength_at_50_percent", GUI_NAME: "< 50% strength"}, - {NAME: "strength_at_25_percent", GUI_NAME: "< 25% strength"}, - "anybody_dead", - {NAME: "dead_at_25_percent", GUI_NAME: "25% dead"}, - {NAME: "dead_at_50_percent", GUI_NAME: "50% dead"}, - {NAME: "dead_at_75_percent", GUI_NAME: "75% dead"}, - "all_but_one_dead", - "all_dead" - ) - -atom_types = ( - "pause", - "goto", - "goto_and_face", - "move_in_direction", - "look", - "animation_mode", - "crouch", - "shoot", - "grenade", - "vehicle", - "running_jump", - "targeted_jump", - "script", - "animate", - "recording", - "action", - "vocalize", - "targeting", - "initiative", - "wait", - "loop", - "die", - "move_immediate", - "look_random", - "look_player", - "look_object", - "set_radius", - "teleport" - ) - - -sky = Struct("sky", - dependency("sky", "sky "), - SIZE=16 - ) - -child_scenario = Struct("child_scenario", - dependency("child_scenario", "scnr"), - SIZE=32 - ) - -function = Struct('function', - Bool32('flags', - 'scripted', - 'invert', - 'additive', - 'always_active', - ), - ascii_str32('name'), - float_sec('period'), # seconds - dyn_senum16('scale_period_by', DYN_NAME_PATH="..[DYN_I].name"), - SEnum16('function', *animation_functions), - dyn_senum16('scale_function_by', DYN_NAME_PATH="..[DYN_I].name"), - SEnum16('wobble_function', *animation_functions), - float_sec('wobble_period'), # seconds - Float('wobble_magnitude', SIDETIP="%"), # percent - Float('square_wave_threshold'), - SInt16('step_count'), - SEnum16('map_to', *fade_functions), - SInt16('sawtooth_count'), - - Pad(2), - dyn_senum16('scale_result_by', DYN_NAME_PATH="..[DYN_I].name"), - SEnum16('bounds_mode', - 'clip', - 'clip_and_normalize', - 'scale_to_fit', - ), - QStruct('bounds', INCLUDE=from_to), - - Pad(6), - dyn_senum16('turn_off_with', DYN_NAME_PATH="..[DYN_I].name"), - - SIZE=120 - ) - -comment = Struct("comment", - QStruct("position", INCLUDE=xyz_float), - Pad(16), - rawtext_ref("comment_data", StrLatin1, max_size=16384), - SIZE=48 - ) - -object_name = Struct("object_name", - ascii_str32("name"), - FlSEnum16("object_type", - *((object_types[i], i - 1) for i in - range(len(object_types))), - VISIBLE=False, EDITABLE=False, DEFAULT=-1 - ), - FlSInt16("reflexive_index", VISIBLE=False, EDITABLE=False), - SIZE=36 - ) +object_ref_flags = Bool16('not_placed', + "automatically", + "on_easy", + "on_normal", + "on_hard", + "use_player_appearance", + ) + +# macro to cut down on a lot of duplicated definitions +def add_player_appearance_fields(desc, lpad_size=4, insert=True): + desc = desc_variant(desc, ('not_placed', object_ref_flags)) + attr_i = (object_reference('_') if insert else desc)["ENTRIES"] + fields = [ + *(dict(desc[i]) for i in range(attr_i)), + Pad(lpad_size), SInt8("appearance_player_index"), + *(dict(desc[i]) for i in range(attr_i, desc["ENTRIES"])), + ] + if len(fields)-2 > attr_i: + # shrink the padding after the appearance_player_index + # by the lpad_size + the size of appearance_player_index + fields[attr_i+2]["SIZE"] -= lpad_size + 1 + + desc.update({i: fdesc for i, fdesc in enumerate(fields)}) + desc.update(ENTRIES=len(fields)) + return desc # Object references -scenery = object_reference("scenery", - Pad(4), - SInt8("appearance_player_index"), - SIZE=72, - block_name="sceneries") - -biped = object_reference("biped", - Pad(4), - SInt8("appearance_player_index"), - Pad(35), - float_zero_to_one("body_vitality"), - Bool32("flags", - "dead", - ), - SIZE=120 - ) - -vehicle = object_reference("vehicle", - Pad(4), - SInt8("appearance_player_index"), - Pad(35), - float_zero_to_one("body_vitality"), - Bool32("flags", - "dead", - ), - - Pad(8), - SInt8("multiplayer_team_index"), - Pad(1), - Bool16("multiplayer_spawn_flags", - "slayer_default", - "ctf_default", - "king_default", - "oddball_default", - #"unused1", - #"unused2", - #"unused3", - #"unused4", - ("slayer_allowed", 1<<8), - ("ctf_allowed", 1<<9), - ("king_allowed", 1<<10), - ("oddball_allowed", 1<<11), - #"unused5", - #"unused6", - #"unused7", - #"unused8", - ), - SIZE=120 - ) - -equipment = object_reference("equipment", - Pad(2), - Bool16("flags", - "initially_at_rest", - "obsolete", - {NAME: "can_accelerate", GUI_NAME:"moves due to explosions"}, - ), - SInt8("appearance_player_index"), - SIZE=40 - ) - -weapon = object_reference("weapon", - Pad(4), - SInt8("appearance_player_index"), - Pad(35), - SInt16("rounds_left"), - SInt16("rounds_loaded"), - Bool16("flags", - "initially_at_rest", - "obsolete", - {NAME: "can_accelerate", GUI_NAME:"moves due to explosions"}, - ), - SIZE=92 - ) - -device_group = Struct("device_group", - ascii_str32("name"), - float_zero_to_one("initial_value"), - Bool32("flags", - "can_change_only_once" - ), - SIZE=52 - ) - -machine = object_reference("machine", - Pad(4), - SInt8("appearance_player_index"), - Pad(3), - dyn_senum16("power_group", - DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), - dyn_senum16("position_group", - DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), - Bool32("flags", *device_flags), - Bool32("more_flags", - "does_not_operate_automatically", - "one_sided", - "never_appears_locked", - "opened_by_melee_attack", - ), - SIZE=64 - ) - -control = object_reference("control", - Pad(4), - SInt8("appearance_player_index"), - Pad(3), - dyn_senum16("power_group", - DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), - dyn_senum16("position_group", - DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), - Bool32("flags", *device_flags), - Bool32("more_flags", - "usable_from_both_sides", - ), - SInt16("DONT_TOUCH_THIS"), # why? - SIZE=64 - ) - -light_fixture = object_reference("light_fixture", - Pad(4), - SInt8("appearance_player_index"), - Pad(3), - dyn_senum16("power_group", - DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), - dyn_senum16("position_group", - DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), - Bool32("flags", *device_flags), - QStruct("color", INCLUDE=rgb_float), - Float("intensity"), - Float("falloff_angle"), # radians - Float("cutoff_angle"), # radians - SIZE=88 - ) - -sound_scenery = object_reference("sound_scenery", - Pad(4), - SInt8("appearance_player_index"), - SIZE=40, - block_name="sound_sceneries") - -# Object swatches -scenery_swatch = object_swatch("scenery_swatch", "scen") -biped_swatch = object_swatch("biped_swatch", "bipd") -vehicle_swatch = object_swatch("vehicle_swatch", "vehi") -equipment_swatch = object_swatch("equipment_swatch", "eqip") -weapon_swatch = object_swatch("weapon_swatch", "weap") -machine_swatch = object_swatch("machine_swatch", "mach") -control_swatch = object_swatch("control_swatch", "ctrl") -light_fixture_swatch = object_swatch("light_fixture_swatch", "lifi") -sound_scenery_swatch = object_swatch("sound_scenery_swatch", "ssce") - -player_starting_profile = Struct("player_starting_profile", - ascii_str32("name"), - float_zero_to_one("starting_health_modifier"), - float_zero_to_one("starting_shield_modifier"), - dependency("primary_weapon", "weap"), - SInt16("primary_rounds_loaded"), - SInt16("primary_rounds_total"), - dependency("secondary_weapon", "weap"), - SInt16("secondary_rounds_loaded"), - SInt16("secondary_rounds_total"), - SInt8("starting_frag_grenade_count", MIN=0), - SInt8("starting_plasma_grenade_count", MIN=0), - SInt8("starting_grenade_type2_count", MIN=0), - SInt8("starting_grenade_type3_count", MIN=0), - SIZE=104 - ) - -player_starting_location = Struct("player_starting_location", - QStruct("position", INCLUDE=xyz_float), - float_rad("facing"), # radians - SInt16("team_index"), - dyn_senum16("bsp_index", - DYN_NAME_PATH=".....structure_bsps.STEPTREE[DYN_I].structure_bsp.filepath"), - SEnum16("type_0", *location_types), - SEnum16("type_1", *location_types), - SEnum16("type_2", *location_types), - SEnum16("type_3", *location_types), - SIZE=52 - ) - -player_starting_location2 = desc_variant(player_starting_location, - ("bsp_index", dyn_senum16("bsp_index", - DYN_NAME_PATH="........structure_bsps.STEPTREE[DYN_I].structure_bsp.filepath" - ) - ) - ) - - -trigger_volume = Struct("trigger_volume", - # if this unknown != 1, the trigger volume is disabled - FlUInt16("unknown0", DEFAULT=1, EDITABLE=False, VISIBLE=False), - Pad(2), - ascii_str32("name"), - BytesRaw("unknown1", SIZE=12, VISIBLE=False), - QStruct("binormal", INCLUDE=ijk_float), - QStruct("normal", INCLUDE=ijk_float), - QStruct("position", INCLUDE=xyz_float, - TOOLTIP=("Volume sides extend in one direction from this position.\n" - "This position is the origin corner for the trigger volume.")), - QStruct("sides", - Float("w", TOOLTIP="Along local y axis"), - Float("h", TOOLTIP="Along local z axis"), - Float("l", TOOLTIP="Along local x axis"), - ORIENT='h' - ), - SIZE=96, - COMMENT=( - "To make the trigger volumes rotation be zero, the normal and\n" - "binormal should be (1, 0, 0) and (0, 1, 0) respectively.") - ) - -recorded_animation = Struct("recorded_animation", - ascii_str32("name"), - SInt8("version"), - SInt8("raw_animation_data"), - SInt8("unit_control_data_version"), - Pad(1), - SInt16("length_of_animation", SIDETIP="ticks"), # ticks - Pad(6), - rawdata_ref("recorded_animation_event_stream", max_size=2097152), - SIZE=64 - ) - -netgame_flag = Struct("netgame_flag", - QStruct("position", INCLUDE=xyz_float), - float_rad("facing"), # radians - SEnum16("type", - "ctf_flag", - "ctf_vehicle", - "oddball_ball_spawn", - "race_track", - "race_vehicle", - "vegas_bank", - "teleport_from", - "teleport_to", - "hill_flag", - ), - SInt16("team_index"), - dependency("weapon_group", "itmc"), - SIZE=148 - ) - -netgame_equipment = Struct("netgame_equipment", - Bool32("flags", - "levitate" - ), - SEnum16("type_0", *location_types), - SEnum16("type_1", *location_types), - SEnum16("type_2", *location_types), - SEnum16("type_3", *location_types), - SInt16("usage_id"), - SInt16("spawn_time", SIDETIP="seconds(0 = default)", - UNIT_SCALE=sec_unit_scale), # seconds - - Pad(48), - QStruct("position", INCLUDE=xyz_float), - float_rad("facing"), # radians - dependency("item_collection", "itmc"), - SIZE=144 - ) - -starting_equipment = Struct("starting_equipment", - Bool32("flags", - "no_grenades", - "plasma_grenades_only", - "type2_grenades_only", - "type3_grenades_only", - ), - SEnum16("type_0", *location_types), - SEnum16("type_1", *location_types), - SEnum16("type_2", *location_types), - SEnum16("type_3", *location_types), - - Pad(48), - dependency("item_collection_1", "itmc"), - dependency("item_collection_2", "itmc"), - dependency("item_collection_3", "itmc"), - dependency("item_collection_4", "itmc"), - dependency("item_collection_5", "itmc"), - dependency("item_collection_6", "itmc"), - SIZE=204 - ) - -bsp_switch_trigger_volume = Struct("bsp_switch_trigger_volume", - dyn_senum16("trigger_volume", - DYN_NAME_PATH=".....trigger_volumes.STEPTREE[DYN_I].name"), - dyn_senum16("source", - DYN_NAME_PATH=".....structure_bsps.STEPTREE[DYN_I].structure_bsp.filepath"), - dyn_senum16("destination", - DYN_NAME_PATH=".....structure_bsps.STEPTREE[DYN_I].structure_bsp.filepath"), - FlUInt16("unknown", EDITABLE=False), - SIZE=8 +scenery = add_player_appearance_fields(scenery) +equipment = add_player_appearance_fields(equipment, 0, False) +sound_scenery = add_player_appearance_fields(sound_scenery) +biped = add_player_appearance_fields(biped) +vehicle = add_player_appearance_fields(vehicle) +weapon = add_player_appearance_fields(weapon) +machine = add_player_appearance_fields(machine) +control = add_player_appearance_fields(control) +light_fixture = add_player_appearance_fields(light_fixture) + + +starting_equipment_flags = Bool32("flags", + "no_grenades", + "plasma_grenades_only", + "type2_grenades_only", + "type3_grenades_only", ) - -decal = Struct("decal", - dyn_senum16("decal_type", - DYN_NAME_PATH=".....decals_palette.STEPTREE[DYN_I].name.filepath"), - SInt8("yaw"), - SInt8("pitch"), - QStruct("position", INCLUDE=xyz_float), - SIZE=16 - ) - -decal_swatch = object_swatch("decal_swatch", "deca", 16) -detail_object_collection_swatch = object_swatch( - "detail_object_collection_swatch", "dobc") -actor_swatch = object_swatch("actor_swatch", "actv", 16) - -ai_anim_reference = Struct("ai_animation_reference", - ascii_str32("animation_name"), - dependency("animation_graph", "antr"), - SIZE=60 - ) - -ai_script_reference = Struct("ai_script_reference", - ascii_str32("script_name"), - SIZE=40 - ) - -ai_recording_reference = Struct("ai_recording_reference", - ascii_str32("recording_name"), - SIZE=40 - ) - parameters = Struct("parameters", ascii_str32("name", EDITABLE=False), SEnum16("return_type", *script_object_types, EDITABLE=False), SIZE=36, ) - halo_script = Struct("script", - ascii_str32("name", EDITABLE=False), - SEnum16("type", *script_types), - SEnum16("return_type", *script_object_types, EDITABLE=False), - UInt32("root_expression_index", EDITABLE=False), - Computed("decompiled_script", WIDGET=HaloScriptTextFrame), + # copy positional fields from halo_script descriptor + *(halo_script[i] for i in range(halo_script["ENTRIES"])), Pad(40), reflexive("parameters", parameters, 16), SIZE=92, ) -halo_global = Struct("global", - ascii_str32("name", EDITABLE=False), - SEnum16("type", *script_object_types, EDITABLE=False), - Pad(6), - UInt32("initialization_expression_index", EDITABLE=False), - Computed("decompiled_script", WIDGET=HaloScriptTextFrame), - SIZE=92, - ) - -reference = Struct("tag_reference", - Pad(24), - dependency("reference"), - SIZE=40 +player_starting_profile = Struct("player_starting_profile", + # copy positional fields from player_starting_profile descriptor + *(player_starting_profile[i] for i in range(player_starting_profile["ENTRIES"])), + SInt8("starting_grenade_type2_count", MIN=0), + SInt8("starting_grenade_type3_count", MIN=0), + SIZE=104 ) - -source_file = Struct("source_file", - ascii_str32("source_name"), - rawdata_ref("source", max_size=1048576, widget=HaloScriptSourceFrame), - SIZE=52 +netgame_equipment = desc_variant(netgame_equipment, + ("team_index", SInt16("usage_id")), ) - -cutscene_flag = Struct("cutscene_flag", - Pad(4), - ascii_str32("name"), - QStruct("position", INCLUDE=xyz_float), - yp_float_rad("facing"), # radians - SIZE=92 +starting_equipment = desc_variant(starting_equipment, + ("flags", starting_equipment_flags), ) - -cutscene_camera_point = Struct("cutscene_camera_point", - Pad(4), - ascii_str32("name"), - Pad(4), - QStruct("position", INCLUDE=xyz_float), - ypr_float_rad("orientation"), # radians - float_rad("field_of_view"), # radians - SIZE=104 +source_file = desc_variant(source_file, + ("source", rawdata_ref("source", max_size=1048576, widget=HaloScriptSourceFrame)) ) cutscene_title = Struct("cutscene_title", @@ -639,7 +105,6 @@ def object_swatch(name, def_id, size=48): "right", "center", ), - Pad(2), Bool32("flags", "wrap horizontally", @@ -647,8 +112,6 @@ def object_swatch(name, def_id, size=48): "center vertically", "bottom justify", ), - #QStruct("text_color", INCLUDE=argb_byte), - #QStruct("shadow_color", INCLUDE=argb_byte), UInt32("text_color", INCLUDE=argb_uint32), UInt32("shadow_color", INCLUDE=argb_uint32), float_sec("fade_in_time"), # seconds @@ -657,306 +120,6 @@ def object_swatch(name, def_id, size=48): SIZE=96 ) - -move_position = Struct("move_position", - QStruct("position", INCLUDE=xyz_float), - float_rad("facing"), # radians - Float("weight"), - from_to_sec("time"), - dyn_senum16("animation", - DYN_NAME_PATH="tagdata.ai_animation_references.STEPTREE[DYN_I].animation_name"), - SInt8("sequence_id"), - - Pad(45), - SInt32("surface_index"), - SIZE=80 - ) - -actor_starting_location = Struct("starting_location", - QStruct("position", INCLUDE=xyz_float), - float_rad("facing"), # radians - Pad(2), - SInt8("sequence_id"), - Bool8("flags", - "required", - ), - SEnum16("return_state", *(dict(NAME=n, GUI_NAME=n) for n in squad_states)), - SEnum16("initial_state", *(dict(NAME=n, GUI_NAME=n) for n in squad_states)), - dyn_senum16("actor_type", - DYN_NAME_PATH="tagdata.actors_palette.STEPTREE[DYN_I].name.filepath"), - dyn_senum16("command_list", - DYN_NAME_PATH="tagdata.command_lists.STEPTREE[DYN_I].name"), - SIZE=28 - ) - -squad = Struct("squad", - ascii_str32("name"), - dyn_senum16("actor_type", - DYN_NAME_PATH="tagdata.actors_palette.STEPTREE[DYN_I].name.filepath"), - dyn_senum16("platoon", - DYN_NAME_PATH=".....platoons.STEPTREE[DYN_I].name"), - SEnum16("initial_state", *(dict(NAME=n, GUI_NAME=n) for n in squad_states)), - SEnum16("return_state", *(dict(NAME=n, GUI_NAME=n) for n in squad_states)), - Bool32("flags", - "unused", - "never_search", - "start_timer_immediately", - "no_timer_delay_forever", - "magic_sight_after_timer", - "automatic_migration", - ), - SEnum16("unique_leader_type", - "normal", - "none", - "random", - "sgt_johnson", - "sgt_lehto", - ), - - Pad(32), - dyn_senum16("maneuver_to_squad", DYN_NAME_PATH="..[DYN_I].name"), - float_sec("squad_delay_time"), # seconds - Bool32("attacking", *group_indices), - Bool32("attacking_search", *group_indices), - Bool32("attacking_guard", *group_indices), - Bool32("defending", *group_indices), - Bool32("defending_search", *group_indices), - Bool32("defending_guard", *group_indices), - Bool32("pursuing", *group_indices), - - Pad(12), - SInt16("normal_diff_count"), - SInt16("insane_diff_count"), - SEnum16("major_upgrade", - "normal", - "few", - "many", - "none", - "all", - ), - Pad(2), - - SInt16("respawn_min_actors"), - SInt16("respawn_max_actors"), - SInt16("respawn_total"), - Pad(2), - - from_to_sec("respawn_delay"), - - Pad(48), - reflexive("move_positions", move_position, 31), - reflexive("starting_locations", actor_starting_location, 31), - SIZE=232 - ) - -platoon = Struct("platoon", - ascii_str32("name"), - Bool32("flags", - "flee_when_maneuvering", - "say_advancing_when_maneuvering", - "start_in_defending_state", - ), - - Pad(12), - SEnum16("change_attacking_defending_state", *maneuver_when_states), - dyn_senum16("change_happens_to", DYN_NAME_PATH="..[DYN_I].name"), - - Pad(8), - SEnum16("maneuver_when", *maneuver_when_states), - dyn_senum16("maneuver_happens_to", DYN_NAME_PATH="..[DYN_I].name"), - SIZE=172 - ) - -firing_position = Struct("firing_position", - QStruct("position", INCLUDE=xyz_float), - SEnum16("group_index", *group_indices), - FlUInt16('bsp_cluster', VISIBLE=False), # calculated on map compile - Pad(4), - FlSInt32('bsp_surface', VISIBLE=False), # calculated on map compile - SIZE=24 - ) - -encounter = Struct("encounter", - ascii_str32("name"), - Bool32("flags", - "not_initially_created", - "respawn_enabled", - "initially_blind", - "initially_deaf", - "initially_braindead", - "firing_positions_are_3d", - "manual_bsp_index_specified", - ), - SEnum16("team_index", - {NAME: "default", GUI_NAME: "0 / default_by_unit"}, - {NAME: "player", GUI_NAME: "1 / player"}, - {NAME: "human", GUI_NAME: "2 / human"}, - {NAME: "covenant", GUI_NAME: "3 / covenant"}, - {NAME: "flood", GUI_NAME: "4 / flood"}, - {NAME: "sentinel", GUI_NAME: "5 / sentinel"}, - {NAME: "unused6", GUI_NAME: "6 / unused6"}, - {NAME: "unused7", GUI_NAME: "7 / unused7"}, - {NAME: "unused8", GUI_NAME: "8 / unused8"}, - {NAME: "unused9", GUI_NAME: "9 / unused9"} - ), - SInt16('unknown', VISIBLE=False), - SEnum16("search_behavior", - "normal", - "never", - "tenacious" - ), - dyn_senum16("manual_bsp_index", - DYN_NAME_PATH=".....structure_bsps.STEPTREE[DYN_I].structure_bsp.filepath"), - from_to_sec("respawn_delay"), - - Pad(74), - FlSInt16('bsp_index', VISIBLE=False), # calculated on map compile - reflexive("squads", squad, 64), - reflexive("platoons", platoon, 32, DYN_NAME_PATH='.name'), - reflexive("firing_positions", firing_position, 512), - reflexive("player_starting_locations", player_starting_location2, 256), - - SIZE=176 - ) - -command = Struct("command", - SEnum16("atom_type", *atom_types), - SInt16("atom_modifier"), - Float("parameter_1"), - Float("parameter_2"), - dyn_senum16("point_1", DYN_NAME_PATH=".....points.STEPTREE[DYN_I]"), - dyn_senum16("point_2", DYN_NAME_PATH=".....points.STEPTREE[DYN_I]"), - dyn_senum16("animation", - DYN_NAME_PATH="tagdata.ai_animation_references.STEPTREE[DYN_I].animation_name"), - dyn_senum16("script", - DYN_NAME_PATH="tagdata.scripts.STEPTREE[DYN_I].name"), - dyn_senum16("recording", - DYN_NAME_PATH="tagdata.ai_recording_references.STEPTREE[DYN_I].recording_name"), - dyn_senum16("command", DYN_NAME_PATH="..[DYN_I].atom_type.enum_name"), - dyn_senum16("object_name", DYN_NAME_PATH="tagdata.object_names.STEPTREE[DYN_I].name"), - SIZE=32 - ) - -point = Struct("point", - QStruct("position", INCLUDE=xyz_float), - SIZE=20 - ) - -command_list = Struct("command_list", - ascii_str32("name"), - Bool32("flags", - "allow_initiative", - "allow_targeting", - "disable_looking", - "disable_communication", - "disable_falling_damage", - "manual_bsp_index", - ), - - Pad(8), - dyn_senum16("manual_bsp_index", - DYN_NAME_PATH=".....structure_bsps.STEPTREE[DYN_I].structure_bsp.filepath"), - dyn_senum16("bsp_index", - DYN_NAME_PATH=".....structure_bsps.STEPTREE[DYN_I].structure_bsp.filepath", - VISIBLE=False - ), - reflexive("commands", command, 64), - reflexive("points", point, 64), - SIZE=96 - ) - -participant = Struct("participant", - Pad(2), - Bool16("flags", - "optional", - "has_alternate", - "is_alternate", - ), - SEnum16("selection_type", - "friendly_actor", - "disembodied", - "in_players_vehicle", - "not_in_a_vehicle", - "prefer_sergeant", - "any_actor", - "radio_unit", - "radio_sergeant", - ), - SEnum16("actor_type", *actor_types), - dyn_senum16("use_this_object", DYN_NAME_PATH="tagdata.object_names.STEPTREE[DYN_I].name"), - dyn_senum16("set_new_name", DYN_NAME_PATH="tagdata.object_names.STEPTREE[DYN_I].name"), - - Pad(12), - BytesRaw("unknown", DEFAULT=b"\xFF"*12, SIZE=12, VISIBLE=False), - ascii_str32("encounter_name"), - SIZE=84 - ) - -line = Struct("line", - Bool16("flags", - "addressee_look_at_speaker", - "everyone_look_at_speaker", - "everyone_look_at_addressee", - "wait_until_told_to_advance", - "wait_until_speaker_nearby", - "wait_until_everyone_nearby", - ), - dyn_senum16("participant", - DYN_NAME_PATH=".....participants.STEPTREE[DYN_I].encounter_name"), - SEnum16("addressee", - "none", - "player", - "participant", - ), - dyn_senum16("addressee_participant", - DYN_NAME_PATH=".....participants.STEPTREE[DYN_I].encounter_name"), - - Pad(4), - Float("line_delay_time"), - - Pad(12), - dependency("variant_1", "snd!"), - dependency("variant_2", "snd!"), - dependency("variant_3", "snd!"), - dependency("variant_4", "snd!"), - dependency("variant_5", "snd!"), - dependency("variant_6", "snd!"), - SIZE=124 - ) - -ai_conversation = Struct("ai_conversation", - ascii_str32("name"), - Bool16("flags", - "stop_if_death", - "stop_if_damaged", - "stop_if_visible_enemy", - "stop_if_alerted_to_enemy", - "player_must_be_visible", - "stop_other_actions", - "keep_trying_to_play", - "player_must_be_looking", - ), - - Pad(2), - float_wu("trigger_distance"), - float_wu("run_to_player_distance"), - - Pad(36), - reflexive("participants", participant, 8, - DYN_NAME_PATH='.encounter_name'), - reflexive("lines", line, 32), - SIZE=116 - ) - -structure_bsp = Struct("structure_bsp", - UInt32("bsp_pointer", VISIBLE=False), - UInt32("bsp_size", VISIBLE=False), - UInt32("bsp_magic", VISIBLE=False), - Pad(4), - dependency("structure_bsp", "sbsp"), - SIZE=32 - ) - scavenger_hunt_objects = Struct("scavenger_hunt_objects", ascii_str32("exported_name"), dyn_senum16("scenario_object_name_index", DYN_NAME_PATH="tagdata.object_names.STEPTREE[DYN_I].name"), @@ -977,8 +140,8 @@ def object_swatch(name, def_id, size=48): Bool16("flags", "cortana_hack", "use_demo_ui", - "color correction (ntsc->srgb)", - "DO NOT apply bungie campaign tag patches", + {NAME: "color_correction", GUI_NAME: "color correction (ntsc->srgb)"}, + "DO_NOT_apply_bungie_campaign_tag_patches", ), reflexive("child_scenarios", child_scenario, 16, DYN_NAME_PATH='.child_scenario.filepath'), @@ -1072,9 +235,7 @@ def object_swatch(name, def_id, size=48): reflexive("cutscene_camera_points", cutscene_camera_point, 512, DYN_NAME_PATH='.name'), reflexive("cutscene_titles", cutscene_title, 64, DYN_NAME_PATH='.name'), - Pad(12), # OS bsp_modifiers reflexive - - Pad(96), + Pad(108), dependency("custom_object_names", 'ustr'), dependency("ingame_help_text", 'ustr'), dependency("hud_messages", 'hmt '), diff --git a/reclaimer/mcc_hek/defs/senv.py b/reclaimer/mcc_hek/defs/senv.py index 10addf48..952c556f 100644 --- a/reclaimer/mcc_hek/defs/senv.py +++ b/reclaimer/mcc_hek/defs/senv.py @@ -7,219 +7,24 @@ # See LICENSE for more information. # +from ...hek.defs.senv import * from .shdr import * -from .objs.shdr import ShdrTag -from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant -environment_shader_comment = """ENVIRONMENT SHADER -Setting enables per-pixel atmospheric fog (for models) but disables -point/spot lights, planar fog, and the ability to control the atmospheric fog density for -this shader. - -Alpha-tested controls if the shader is masked by the diffuse alpha (or depending on -the environment shader type's setting) the bump map alpha. The transparancy is only 1-bit""" - -environment_shader_type_comment = """ENVIRONMENT SHADER TYPE -Controls how diffuse maps are combined: - -NORMAL: -Secondary detail map alpha controls blend between primary and secondary detail map. -Specular mask is alpha of blended primary and secondary detail map alpha multiplied by -alpha of micro detail map. - -BLENDED: -Base map alpha controls blend between primary and secondary detail map. -Specular mask is alpha of blended primary and secondary detail map alpha multiplied by -alpha of micro detail map. - -BLENDED BASE SPECULAR: -Same as BLENDED, except specular mask is alpha is base map multiplied with -the alpha of micro detail map.""" - -bump_properties_comment = """BUMP PROPERTIES -Perforated (alpha-tested) shaders use alpha in bump map.""" - -tex_scroll_anim_comment = """TEXTURE SCROLLING ANIMATION -Scrolls all 2D maps simultaneously.""" - -self_illum_comment = """SELF-ILLUMINATION PROPERTIES -There are three self-illumination effects which are added together. -Each effect has an , used when the shader is active, and an , used when -the shader is not active. The self-illumination map is used as follows: -* RED: primary mask -* GREEN: secondary mask -* BLUE: plasma mask -* ALPHA: plasma animation reference - -Each effect also has an animation , and , used when the shader is -active. The primary and secondary effects simply modulate the by the animation -value to produce an animation color, and then blend between the animation color and the - based on the shader's activation level, and finally modulate by the mask. - -The plasma shader compares the animation value with the alpha channel of the map (the plasma -animation reference) and produces a high value when they are similar and a dark value when -they are different. This value modulates the to produce a plasma animation -color, and the rest proceeds just like the primary and secondary effects.""" - -specular_properties_comment = """SPECULAR PROPERTIES -Controls the dynamic specular highlights. The highlight is modulated by brightness -and a blend between perpendicular and parrallel color. - -These color values also affect the colour of the reflection in REFLECTION PROPERTIES""" - -reflection_properties_comment = """REFLECTION PROPERTIES -Controls cube map reflections. The color of the cubemap is tinted by the color settings -in the SPECULAR PROPERTIES and the brightness in the REFLECTION PROPERTIES. - -BUMPED CUBE MAP: Makes it so that the reflection and fresnel is affected by the bump map. - -FLAT CUBE MAP: The reflection is not affected by the cubemap, the fresnel still is though.""" - -environment_shader = Struct("environment_shader", - Bool16("flags", - "alpha_tested", - "bump_map_is_specular_mask", - "true_atmospheric_fog", - "use_variant_2_for_calculation_bump_attention", - COMMENT=environment_shader_comment - ), - SEnum16("type", - "normal", - "blended", - "blended_base_specular", - COMMENT=environment_shader_type_comment - ), +environment_shader_flags = Bool16("flags", + "alpha_tested", + "bump_map_is_specular_mask", + "true_atmospheric_fog", + "use_variant_2_for_calculation_bump_attention", + COMMENT=environment_shader_comment ) -diffuse = Struct("diffuse", - Bool16("diffuse_flags", - "rescale_detail_maps", - "rescale_bump_maps", - ), - Pad(26), - dependency("base_map", "bitm"), - - Pad(24), - SEnum16("detail_map_function", *detail_map_functions), - Pad(2), - - Float("primary_detail_map_scale"), - dependency("primary_detail_map", "bitm"), - Float("secondary_detail_map_scale"), - dependency("secondary_detail_map", "bitm"), - - Pad(24), - SEnum16("micro_detail_map_function", *detail_map_functions), - - Pad(2), - Float("micro_detail_map_scale"), - dependency("micro_detail_map", "bitm"), - QStruct("material_color", INCLUDE=rgb_float), +environment_shader = desc_variant(environment_shader, + ("flags", environment_shader_flags), ) -bump_properties = Struct("bump_properties", - Float("map_scale"), - dependency("map", "bitm"), - FlFloat("map_scale_x", VISIBLE=False), - FlFloat("map_scale_y", VISIBLE=False), - COMMENT=bump_properties_comment - ) - -texture_scrolling = Struct("texture_scrolling", - anim_func_per_sca_macro("u_animation"), - anim_func_per_sca_macro("v_animation"), - COMMENT=tex_scroll_anim_comment - ) - -self_illumination = Struct("self_illumination", - Bool16("flags", - "unfiltered", - ), - Pad(2), - Pad(24), - - QStruct("primary_on_color", INCLUDE=rgb_float), - QStruct("primary_off_color", INCLUDE=rgb_float), - anim_func_per_pha_macro("primary_animation"), - - Pad(24), - QStruct("secondary_on_color", INCLUDE=rgb_float), - QStruct("secondary_off_color", INCLUDE=rgb_float), - anim_func_per_pha_macro("secondary_animation"), - - Pad(24), - QStruct("plasma_on_color", INCLUDE=rgb_float), - QStruct("plasma_off_color", INCLUDE=rgb_float), - anim_func_per_pha_macro("plasma_animation"), - - Pad(24), - Float("map_scale"), - dependency("map", "bitm"), - COMMENT=self_illum_comment - ) - -specular = Struct("specular", - Bool16("specular_flags", - "overbright", - "extra_shiny", - "lightmap_is_specular" - ), - Pad(18), - float_zero_to_one("brightness"), # [0,1] - - Pad(20), - QStruct("perpendicular_tint_color", INCLUDE=rgb_float), - QStruct("parallel_tint_color", INCLUDE=rgb_float), - COMMENT=specular_properties_comment - ) - -reflection = Struct("reflection", - Bool16("reflection_flags", - "dynamic_mirror", - ), - SEnum16("reflection_type", - "bumped_cubemap", - "flat_cubemap", - "bumped_radiosity", - ), - - float_zero_to_one("lightmap_brightness_scale"), # [0,1] - Pad(28), - float_zero_to_one("perpendicular_brightness"), # [0,1] - float_zero_to_one("parallel_brightness"), # [0,1] - - Pad(40), - dependency("cube_map", "bitm"), - COMMENT=reflection_properties_comment - ) - -senv_attrs = Struct("senv_attrs", - environment_shader, - - float_wu("lens_flare_spacing"), # world units - dependency("lens_flare", "lens"), - - # this padding is the reflexive for the OS shader environment extension - Pad(12), - - Pad(32), - diffuse, - - Pad(12), - bump_properties, - - Pad(16), - texture_scrolling, - - Pad(24), - self_illumination, - - Pad(24), - specular, - - Pad(16), - reflection, - SIZE=796 +senv_attrs = desc_variant(senv_attrs, + ("environment_shader", environment_shader), ) senv_body = Struct("tagdata", diff --git a/reclaimer/mcc_hek/defs/snd_.py b/reclaimer/mcc_hek/defs/snd_.py index a0498c89..98f4d216 100644 --- a/reclaimer/mcc_hek/defs/snd_.py +++ b/reclaimer/mcc_hek/defs/snd_.py @@ -7,156 +7,18 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.snd_ import Snd_Tag -from supyr_struct.defs.tag_def import TagDef +from ...hek.defs.snd_ import * from supyr_struct.util import desc_variant -sound_classes = ( - ("projectile_impact", 0), - ("projectile_detonation", 1), - ("weapon_fire", 4), - ("weapon_ready", 5), - ("weapon_reload", 6), - ("weapon_empty", 7), - ("weapon_charge", 8), - ("weapon_overheat", 9), - ("weapon_idle", 10), - - ("object_impacts", 13), - ("particle_impacts", 14), - ("slow_particle_impacts", 15), - - ("unit_footsteps", 18), - ("unit_dialog", 19), - - ("vehicle_collision", 22), - ("vehicle_engine", 23), - - ("device_door", 26), - ("device_force_field", 27), - ("device_machinery", 28), - ("device_nature", 29), - ("device_computers", 30), - - ("music", 32), - ("ambient_nature", 33), - ("ambient_machinery", 34), - ("ambient_computers", 35), - - ("first_person_damage", 39), - - ("scripted_dialog_player", 44), - ("scripted_effect", 45), - ("scripted_dialog_other", 46), - ("scripted_dialog_force_unspatialized", 47), - - ("game_event", 50), - ) - -compression = SEnum16("compression", - 'none', - 'xbox_adpcm', - 'ima_adpcm', - 'ogg', - TOOLTIP=""" -IMA ADPCM is unsupported on pc, and is actually treated as -MS ADPCM, with an additional header on the data stream. -For all intents and purposes, "ima adpcm" is useless. -""" +mcc_snd__flags = Bool32("flags", + "fit_to_adpcm_blocksize", + "split_long_sound_into_permutations", + "thirsty_grunt", ) - -permutation = Struct('permutation', - ascii_str32("name"), - Float("skip_fraction"), - Float("gain", DEFAULT=1.0), - compression, - SInt16("next_permutation_index", DEFAULT=-1), - FlSInt32("unknown0", VISIBLE=False), - FlUInt32("unknown1", VISIBLE=False), # always zero? - FlUInt32("unknown2", VISIBLE=False), - # this is actually the required length of the ogg - # decompression buffer. For "none" compression, this - # mirrors samples.size, so a more appropriate name - # for this field should be pcm_buffer_size - FlUInt32("ogg_sample_count", EDITABLE=False), - FlUInt32("unknown3", VISIBLE=False), # seems to always be == unknown2 - rawdata_ref("samples", max_size=4194304, widget=SoundSampleFrame), - rawdata_ref("mouth_data", max_size=8192), - rawdata_ref("subtitle_data", max_size=512), - SIZE=124 - ) - -pitch_range = Struct('pitch_range', - ascii_str32("name"), - - Float("natural_pitch"), - QStruct("bend_bounds", INCLUDE=from_to), - SInt16("actual_permutation_count"), - Pad(2), - Float("playback_rate", VISIBLE=False), - SInt32("unknown1", VISIBLE=False, DEFAULT=-1), - SInt32("unknown2", VISIBLE=False, DEFAULT=-1), - - reflexive("permutations", permutation, 256, - DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), - SIZE=72, - ) - - -snd__body = Struct("tagdata", - Bool32("flags", - "fit_to_adpcm_blocksize", - "split_long_sound_into_permutations", - "thirsty_grunt", - ), - SEnum16("sound_class", *sound_classes), - SEnum16("sample_rate", - {NAME: "khz_22", GUI_NAME: "22kHz"}, - {NAME: "khz_44", GUI_NAME: "44kHz"}, - ), - float_wu("minimum_distance"), - float_wu("maximum_distance"), - float_zero_to_one("skip_fraction"), - - #Randomization - QStruct("random_pitch_bounds", INCLUDE=from_to), - float_rad("inner_cone_angle"), # radians - float_rad("outer_cone_angle"), # radians - float_zero_to_one("outer_cone_gain"), - Float("gain_modifier"), - Float("maximum_bend_per_second"), - Pad(12), - - QStruct("modifiers_when_scale_is_zero", - Float("skip_fraction"), - Float("gain"), - Float("pitch"), - ), - Pad(12), - - QStruct("modifiers_when_scale_is_one", - Float("skip_fraction"), - Float("gain"), - Float("pitch"), - ), - Pad(12), - - SEnum16("encoding", - 'mono', - 'stereo' - ), - compression, - dependency("promotion_sound", "snd!"), - SInt16("promotion_count"), - SInt16("unknown1", VISIBLE=False), - Struct("unknown2", INCLUDE=rawdata_ref_struct, VISIBLE=False), - reflexive("pitch_ranges", pitch_range, 8, - DYN_NAME_PATH='.name'), - - SIZE=164, +mcc_snd__body = desc_variant(snd__body, + ("flags", mcc_snd__flags), ) @@ -165,12 +27,12 @@ def get(): snd__def = TagDef("snd!", blam_header('snd!', 4), - snd__body, + mcc_snd__body, ext=".sound", endian=">", tag_cls=Snd_Tag, ) snd__meta_stub = desc_variant( - snd__body, ("pitch_ranges", Pad(12)) + mcc_snd__body, ("pitch_ranges", Pad(12)) ) snd__meta_stub_blockdef = BlockDef(snd__meta_stub) diff --git a/reclaimer/mcc_hek/defs/soso.py b/reclaimer/mcc_hek/defs/soso.py index 08cbe16c..0fc730e9 100644 --- a/reclaimer/mcc_hek/defs/soso.py +++ b/reclaimer/mcc_hek/defs/soso.py @@ -7,157 +7,33 @@ # See LICENSE for more information. # +from ...hek.defs.soso import * from .shdr import * -from .objs.shdr import ShdrTag -from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant -soso_comment = """MODEL SHADER -Setting enables per-pixel atmospheric fog but disables point/spot -lights, planar fog, and the ability to control the atmospheric fog density for this shader.""" - -cc_comment = """CHANGE COLOR -Change color is used to recolor the diffuse map, it affects pixels -based on the ALPHA channel (BLUE on XBOX) of the multipurpose map.""" - -self_illum_comment = """SELF-ILLUMINATION -Self-illumination adds diffuse light to pixels based on the GREEN channel -of the multipurpose map. The external self-illumination color referenced by - is modulated by the self-illumination animation.""" - -maps_comment = """MAPS -On PC, the multipurpose map channels are used for: -* RED: auxiliary mask (usually used for detail) -* GREEN: self-illumination mask (adds to diffuse light) -* BLUE: specular reflection mask (modulates reflections) -* ALPHA: color change mask (recolors diffuse map) - -On XBOX, the channels are used for: -* RED: specular reflection -* GREEN: self-illumination -* BLUE: color change -* ALPHA: auxiliary - -Note: When DXT1 compressed color-key textures are used for the -multipurpose map (as they should be normally), the alpha channel is 1-bit -and any non-zero alpha pixels must have zero-color, therefore on PC if we -need colorchange we use DXT3 (explicit alpha) or DXT5 (interpolated alpha). - -Detail map affects diffuse map, and optionally affects reflection -if flag is set.""" - -tex_scroll_comment = """TEXTURE SCROLLING ANIMATIONS -Scrolls all 2D maps simultaneously.""" - -reflection_prop_comment = """REFLECTION PROPERTIES""" - -model_shader = Struct("model_shader", - Bool16("flags", - "detail_after_reflection", - "two_sided", - "not_alpha_tested", - "alpha_blended_decal", - "true_atmospheric_fog", - "disable_two_sided_culling", - "multipurpose_map_uses_og_xbox_channel_order", - ), - Pad(14), - Float("translucency"), - COMMENT=soso_comment - ) - -self_illumination = Struct("self_illumination", - Bool16("flags", - "no_random_phase" - ), - Pad(2), - SEnum16("color_source", *function_names), - SEnum16("animation_function", *animation_functions), - float_sec("animation_period"), # seconds - QStruct("color_lower_bound", INCLUDE=rgb_float), - QStruct("color_upper_bound", INCLUDE=rgb_float), - COMMENT=self_illum_comment +model_shader_flags = Bool16("flags", + "detail_after_reflection", + "two_sided", + "not_alpha_tested", + "alpha_blended_decal", + "true_atmospheric_fog", + "disable_two_sided_culling", + "multipurpose_map_uses_og_xbox_channel_order", ) -maps = Struct("maps", - Float("map_u_scale"), - Float("map_v_scale"), - dependency("diffuse_map", "bitm"), - - Pad(8), - dependency("multipurpose_map", "bitm"), - - Pad(8), - SEnum16("detail_function", *detail_map_functions), - SEnum16("detail_mask", *detail_mask), - - Float("detail_map_scale"), - dependency("detail_map", "bitm"), - Float("detail_map_v_scale"), - COMMENT=maps_comment +model_shader = desc_variant(model_shader, + ("flags", model_shader_flags), ) -texture_scrolling = Struct("texture_scrolling", - Struct("u_animation", INCLUDE=anim_src_func_per_pha_sca), - Struct("v_animation", INCLUDE=anim_src_func_per_pha_sca), - Struct("rotation_animation", INCLUDE=anim_src_func_per_pha_sca_rot), - QStruct("rot_animation_center", INCLUDE=xy_float), - COMMENT=tex_scroll_comment +soso_attrs = desc_variant(soso_attrs, + ("model_shader", model_shader), ) -reflection = Struct("reflection", - float_wu("falloff_distance"), # world units - float_wu("cutoff_distance"), # world units - - float_zero_to_one("perpendicular_brightness"), - QStruct("perpendicular_tint_color", INCLUDE=rgb_float), - float_zero_to_one("parallel_brightness"), - QStruct("parallel_tint_color", INCLUDE=rgb_float), - - dependency("cube_map", "bitm"), - #COMMENT=reflection_prop_comment - ) - -soso_attrs = Struct("soso_attrs", - #Model Shader Properties - model_shader, - - Pad(16), - #Color-Change - SEnum16("color_change_source", *function_names, COMMENT=cc_comment), - - - Pad(30), - #Self-Illumination - self_illumination, - - Pad(12), - #Diffuse, Multipurpose, and Detail Maps - maps, - - # this padding is the reflexive for the OS shader model extension - Pad(12), - - #Texture Scrolling Animation - texture_scrolling, - - Pad(8), - #Reflection Properties - reflection, - Pad(16), - - Float("unknown0", VISIBLE=False), - BytesRaw("unknown1", SIZE=16, VISIBLE=False), # little endian dependency - - SIZE=400 - ) - - soso_body = Struct("tagdata", shdr_attrs, soso_attrs ) - def get(): return soso_def diff --git a/reclaimer/mcc_hek/defs/ssce.py b/reclaimer/mcc_hek/defs/ssce.py index 5619d014..6f7f8fff 100644 --- a/reclaimer/mcc_hek/defs/ssce.py +++ b/reclaimer/mcc_hek/defs/ssce.py @@ -8,17 +8,18 @@ # from ...hek.defs.ssce import * - -#import and use the mcc obje attrs from .obje import * # replace the object_type enum one that uses # the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=11) +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(11)) + ) -ssce_body = dict(ssce_body) -ssce_body[0] = obje_attrs +ssce_body = Struct("tagdata", + obje_attrs, + SIZE=508, + ) def get(): return ssce_def diff --git a/reclaimer/mcc_hek/defs/tagc.py b/reclaimer/mcc_hek/defs/tagc.py index 6e3f05af..d0be72ab 100644 --- a/reclaimer/mcc_hek/defs/tagc.py +++ b/reclaimer/mcc_hek/defs/tagc.py @@ -7,4 +7,4 @@ # See LICENSE for more information. # -from ...hek.defs.tagc import * +from ...hek.defs.tagc import * \ No newline at end of file diff --git a/reclaimer/mcc_hek/defs/unhi.py b/reclaimer/mcc_hek/defs/unhi.py index a244ce36..96edf8b9 100644 --- a/reclaimer/mcc_hek/defs/unhi.py +++ b/reclaimer/mcc_hek/defs/unhi.py @@ -7,46 +7,34 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef -from .grhi import hud_background +from ...hek.defs.unhi import * +from .grhi import hud_background, mcc_hud_anchor +from supyr_struct.util import desc_variant -warning_sound = Struct("warning_sound", - dependency("sound", ('lsnd', 'snd!')), - Bool32("latched_to", - "shield_recharging", - "shield_recharged", - "shield_low", - "shield_empty", - "health_low", - "health_empty", - "health_minor_damage", - "health_major_damage", - ), - Float("scale"), - SIZE=56 - ) - -shield_panel_meter = Struct("shield_panel_meter", +# to reduce a lot of code, these have been snipped out +meter_xform_common = ( # NOTE: used by wphi QStruct("anchor_offset", SInt16("x"), SInt16("y"), ORIENT='h', ), Float("width_scale"), Float("height_scale"), Bool16("scaling_flags", *hud_scaling_flags), - Pad(22), + ) +meter_common = ( # NOTE: used by wphi dependency("meter_bitmap", "bitm"), - #QStruct("color_at_meter_minimum", INCLUDE=xrgb_byte), - #QStruct("color_at_meter_maximum", INCLUDE=xrgb_byte), - #QStruct("flash_color", INCLUDE=xrgb_byte), - #QStruct("empty_color", INCLUDE=argb_byte), UInt32("color_at_meter_minimum", INCLUDE=xrgb_uint32), UInt32("color_at_meter_maximum", INCLUDE=xrgb_uint32), UInt32("flash_color", INCLUDE=xrgb_uint32), UInt32("empty_color", INCLUDE=argb_uint32), - Bool8("flags", *hud_panel_meter_mcc_flags), + Bool8("flags", + "use_min_max_for_state_changes", + "interpolate_between_min_max_flash_colors_as_state_changes", + "interpolate_color_along_hsv_space", + "more_colors_for_hsv_interpolation", + "invert_interpolation", + "use_xbox_shading", + ), SInt8("minimum_meter_value"), SInt16("sequence_index"), SInt8("alpha_multiplier"), @@ -54,153 +42,62 @@ SInt16("value_scale"), Float("opacity"), Float("translucency"), - #QStruct("disabled_color", INCLUDE=argb_byte), UInt32("disabled_color", INCLUDE=argb_uint32), + ) + +shield_panel_meter = Struct("shield_panel_meter", + *meter_xform_common, + *meter_common, Float("min_alpha"), Pad(12), - #QStruct("overcharge_minimum_color", INCLUDE=xrgb_byte), - #QStruct("overcharge_maximum_color", INCLUDE=xrgb_byte), - #QStruct("overcharge_flash_color", INCLUDE=xrgb_byte), - #QStruct("overcharge_empty_color", INCLUDE=xrgb_byte), UInt32("overcharge_minimum_color", INCLUDE=xrgb_uint32), UInt32("overcharge_maximum_color", INCLUDE=xrgb_uint32), UInt32("overcharge_flash_color", INCLUDE=xrgb_uint32), UInt32("overcharge_empty_color", INCLUDE=xrgb_uint32), + Pad(16), SIZE=136 ) health_panel_meter = Struct("health_panel_meter", - QStruct("anchor_offset", - SInt16("x"), SInt16("y"), ORIENT='h', - ), - Float("width_scale"), - Float("height_scale"), - Bool16("scaling_flags", *hud_scaling_flags), - - Pad(22), - dependency("meter_bitmap", "bitm"), - #QStruct("color_at_meter_minimum", INCLUDE=xrgb_byte), - #QStruct("color_at_meter_maximum", INCLUDE=xrgb_byte), - #QStruct("flash_color", INCLUDE=xrgb_byte), - #QStruct("empty_color", INCLUDE=argb_byte), - UInt32("color_at_meter_minimum", INCLUDE=xrgb_uint32), - UInt32("color_at_meter_maximum", INCLUDE=xrgb_uint32), - UInt32("flash_color", INCLUDE=xrgb_uint32), - UInt32("empty_color", INCLUDE=argb_uint32), - Bool8("flags", *hud_panel_meter_mcc_flags), - SInt8("minimum_meter_value"), - SInt16("sequence_index"), - SInt8("alpha_multiplier"), - SInt8("alpha_bias"), - SInt16("value_scale"), - Float("opacity"), - Float("translucency"), - #QStruct("disabled_color", INCLUDE=argb_byte), - UInt32("disabled_color", INCLUDE=argb_uint32), + *meter_xform_common, + *meter_common, Float("min_alpha"), Pad(12), - #QStruct("medium_health_left_color", INCLUDE=xrgb_byte), UInt32("medium_health_left_color", INCLUDE=xrgb_uint32), Float("max_color_health_fraction_cutoff"), Float("min_color_health_fraction_cutoff"), + Pad(20), SIZE=136 ) -motion_sensor_center = Struct("motion_sensor_center", - QStruct("anchor_offset", - SInt16("x"), SInt16("y"), ORIENT='h', - ), - Float("width_scale"), - Float("height_scale"), - Bool16("scaling_flags", *hud_scaling_flags), - SIZE=16 - ) - -auxilary_overlay = Struct("auxilary_overlay", - Struct("background", INCLUDE=hud_background), - SEnum16("type", - "team_icon" - ), - Bool16("flags", - "use_team_color" - ), - SIZE=132 - ) - auxilary_meter = Struct("auxilary_meter", Pad(18), SEnum16("type", "integrated_light", VISIBLE=False), Struct("background", INCLUDE=hud_background), - QStruct("anchor_offset", - SInt16("x"), SInt16("y"), ORIENT='h', - ), - Float("width_scale"), - Float("height_scale"), - Bool16("scaling_flags", *hud_scaling_flags), - - Pad(22), - dependency("meter_bitmap", "bitm"), - #QStruct("color_at_meter_minimum", INCLUDE=xrgb_byte), - #QStruct("color_at_meter_maximum", INCLUDE=xrgb_byte), - #QStruct("flash_color", INCLUDE=xrgb_byte), - #QStruct("empty_color", INCLUDE=argb_byte), - UInt32("color_at_meter_minimum", INCLUDE=xrgb_uint32), - UInt32("color_at_meter_maximum", INCLUDE=xrgb_uint32), - UInt32("flash_color", INCLUDE=xrgb_uint32), - UInt32("empty_color", INCLUDE=argb_uint32), - Bool8("flags", *hud_panel_meter_mcc_flags), - SInt8("minimum_meter_value"), - SInt16("sequence_index"), - SInt8("alpha_multiplier"), - SInt8("alpha_bias"), - SInt16("value_scale"), - Float("opacity"), - Float("translucency"), - #QStruct("disabled_color", INCLUDE=argb_byte), - UInt32("disabled_color", INCLUDE=argb_uint32), + *meter_xform_common, + *meter_common, Float("min_alpha"), - Pad(12), Float("minimum_fraction_cutoff"), Bool32("overlay_flags", "show_only_when_active", "flash_once_if_activated_while_disabled", ), + Pad(24), + Pad(64), SIZE=324, COMMENT="\nThis auxilary meter is meant for the flashlight.\n" ) -unhi_body = Struct("tagdata", - SEnum16("anchor", *hud_anchors_mcc), - - Pad(34), - Struct("unit_hud_background", INCLUDE=hud_background), - Struct("shield_panel_background", INCLUDE=hud_background), - shield_panel_meter, - Struct("health_panel_background", INCLUDE=hud_background), - health_panel_meter, - Struct("motion_sensor_background", INCLUDE=hud_background), - Struct("motion_sensor_foreground", INCLUDE=hud_background), - - Pad(32), - motion_sensor_center, - - Pad(20), - SEnum16("auxilary_overlay_anchor", *hud_anchors_mcc), - Pad(34), - reflexive("auxilary_overlays", auxilary_overlay, 16), - - Pad(16), - reflexive("warning_sounds", warning_sound, 12, - DYN_NAME_PATH='.sound.filepath'), - reflexive("auxilary_meters", auxilary_meter, 16), - - SIZE=1388 +unhi_body = desc_variant(unhi_body, + ("anchor", SEnum16("anchor", *hud_anchors_mcc)), + ("shield_panel_meter", shield_panel_meter), + ("health_panel_meter", health_panel_meter), + ("auxilary_meters", reflexive("auxilary_meters", auxilary_meter, 16)), ) - def get(): return unhi_def diff --git a/reclaimer/mcc_hek/defs/unit.py b/reclaimer/mcc_hek/defs/unit.py index ae62edb3..581a06f4 100644 --- a/reclaimer/mcc_hek/defs/unit.py +++ b/reclaimer/mcc_hek/defs/unit.py @@ -7,195 +7,18 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef - -def get(): - return unit_def - -camera_track = Struct('camera_track', - dependency('track', "trak"), - SIZE=28 - ) - -new_hud_interface = Struct('new_hud_interface', - dependency('unit_hud_interface', "unhi"), - SIZE=48 - ) - -dialogue_variant = Struct('dialogue_variant', - SInt16('variant_number'), - Pad(6), - dependency('dialogue', "udlg"), - SIZE=24 - ) - -powered_seat = Struct('powered_seat', - Pad(4), - float_sec('driver_powerup_time'), - float_sec('driver_powerdown_time'), - SIZE=68 - ) - -weapon = Struct('weapon', - dependency('weapon', "weap"), - SIZE=36 - ) - -seat = Struct('seat', - Bool32("flags", - "invisible", - "unused", - "driver", - "gunner", - "third_person_camera", - "allows_weapons", - "third_person_on_enter", - "first_person_slaved_to_gun", - "allow_vehicle_communcation_animation", - "not_valid_without_driver", - "allow_ai_noncombatants" - ), - ascii_str32('label'), - ascii_str32('marker_name'), - - Pad(32), - QStruct("acceleration_scale", INCLUDE=ijk_float), - - Pad(12), - float_deg_sec('yaw_rate', UNIT_SCALE=per_sec_unit_scale), # degrees/sec - float_deg_sec('pitch_rate', UNIT_SCALE=per_sec_unit_scale), # degrees/sec - ascii_str32('camera_marker_name'), - ascii_str32('camera_submerged_marker_name'), - float_rad('pitch_auto_level'), # radians - from_to_rad('pitch_range'), # radians - - reflexive("camera_tracks", camera_track, 2, 'loose', 'tight'), - reflexive("new_hud_interfaces", new_hud_interface, 2, - 'default/solo', 'multiplayer'), - - Pad(4), - SInt16("hud_text_message_index"), - - Pad(2), - float_rad('yaw_minimum'), # radians - float_rad('yaw_maximum'), # radians - dependency('built_in_gunner', "actv"), - Pad(12), # open sauce seat extension padding - SIZE=284 - ) - -unit_attrs = Struct("unit_attrs", - Bool32("flags", - "circular_aiming", - "destroyed_after_dying", - "half_speed_interpolation", - "fires_from_camera", - "entrance_inside_bounding_sphere", - "unused", - "causes_passenger_dialogue", - "resists_pings", - "melee_attack_is_fatal", - "dont_reface_during_pings", - "has_no_aiming", - "simple_creature", - "impact_melee_attaches_to_unit", - "impact_melee_dies_on_shields", - "cannot_open_doors_automatically", - "melee_attackers_cannot_attach", - "not_instantly_killed_by_melee", - "shield_sapping", - "runs_around_flaming", - "inconsequential", - "special_cinematic_unit", - "ignored_by_autoaiming", - "shields_fry_infection_forms", - "integrated_light_controls_weapon", - "integrated_light_lasts_forever", - ), - SEnum16('default_team', *unit_teams), - SEnum16('constant_sound_volume', *sound_volumes), - float_zero_to_inf('rider_damage_fraction'), - dependency('integrated_light_toggle', "effe"), - SEnum16('A_in', *unit_inputs), - SEnum16('B_in', *unit_inputs), - SEnum16('C_in', *unit_inputs), - SEnum16('D_in', *unit_inputs), - float_rad('camera_field_of_view'), # radians - Float('camera_stiffness'), - ascii_str32('camera_marker_name'), - ascii_str32('camera_submerged_marker_name'), - float_rad('pitch_auto_level'), # radians - from_to_rad('pitch_range'), # radians - reflexive("camera_tracks", camera_track, 2, - 'loose', 'tight'), - - #Miscellaneous - QStruct("seat_acceleration_scale", INCLUDE=ijk_float), - Pad(12), - float_zero_to_one('soft_ping_threshold'), # [0,1] - float_sec('soft_ping_interrupt_time', UNIT_SCALE=sec_unit_scale), # seconds - float_zero_to_one('hard_ping_threshold'), # [0,1] - float_sec('hard_ping_interrupt_time', UNIT_SCALE=sec_unit_scale), # seconds - float_zero_to_one('hard_death_threshold'), # [0,1] - float_zero_to_one('feign_death_threshold'), # [0,1] - float_sec('feign_death_time', UNIT_SCALE=sec_unit_scale), # seconds - float_wu('distance_of_evade_aim'), # world units - float_wu('distance_of_dive_aim'), # world units - - Pad(4), - float_zero_to_one('stunned_movement_threshold'), # [0,1] - float_zero_to_one('feign_death_chance'), # [0,1] - float_zero_to_one('feign_repeat_chance'), # [0,1] - dependency('spawned_actor', "actv"), - QStruct("spawned_actor_count", - SInt16("from", GUI_NAME=""), SInt16("to"), ORIENT='h', - ), - float_wu_sec('spawned_velocity'), - float_rad_sec('aiming_velocity_maximum', - UNIT_SCALE=irad_per_sec_unit_scale), # radians/sec - float_rad_sec_sq('aiming_acceleration_maximum', - UNIT_SCALE=irad_per_sec_sq_unit_scale), # radians/sec^2 - float_zero_to_one('casual_aiming_modifier'), - float_rad_sec('looking_velocity_maximum', - UNIT_SCALE=irad_per_sec_unit_scale), # radians/sec - float_rad_sec_sq('looking_acceleration_maximum', - UNIT_SCALE=irad_per_sec_sq_unit_scale), # radians/sec^2 +from ...hek.defs.unit import * +from supyr_struct.util import desc_variant +metagame_scoring = Struct("metagame_scoring", + SEnum16("metagame_type", TOOLTIP="Used to determine score in MCC", *actor_types_mcc), + SEnum16("metagame_class", TOOLTIP="Used to determine score in MCC", *actor_classes_mcc), Pad(8), - Float('ai_vehicle_radius'), - Float('ai_danger_radius'), - dependency('melee_damage', "jpt!"), - SEnum16('motion_sensor_blip_size', - "medium", - "small", - "large", - ), - Pad(2), - - Struct("mcc_additions", # replaced with opensauce unit extension in os_v4 - SEnum16("metagame_type", TOOLTIP="Used to determine score in MCC", *mcc_actor_types), - SEnum16("metagame_class", TOOLTIP="Used to determine score in MCC", *mcc_actor_classes), - Pad(2), - ), - reflexive("new_hud_interfaces", new_hud_interface, 2, - 'default/solo', 'multiplayer'), - reflexive("dialogue_variants", dialogue_variant, 16, - DYN_NAME_PATH='.dialogue.filepath'), - - #Grenades - float_wu_sec('grenade_velocity'), - SEnum16('grenade_type', *grenade_types_mcc), - SInt16('grenade_count', MIN=0), - - Pad(4), - reflexive("powered_seats", powered_seat, 2, - "driver", "gunner"), - reflexive("weapons", weapon, 4, DYN_NAME_PATH='.weapon.filepath'), - reflexive("seats", seat, 16, DYN_NAME_PATH='.label'), + ) - SIZE=372 +unit_attrs = desc_variant(unit_attrs, + ("mcc_additions", metagame_scoring), + ("grenade_type", SEnum16("grenade_type", *grenade_types_mcc)), ) unit_body = Struct('tagdata', @@ -203,6 +26,9 @@ def get(): SIZE=372 ) +def get(): + return unit_def + unit_def = TagDef("unit", blam_header('unit', 2), unit_body, diff --git a/reclaimer/mcc_hek/defs/vehi.py b/reclaimer/mcc_hek/defs/vehi.py index 3f7ed630..e000bfb6 100644 --- a/reclaimer/mcc_hek/defs/vehi.py +++ b/reclaimer/mcc_hek/defs/vehi.py @@ -7,10 +7,35 @@ # See LICENSE for more information. # +from ...hek.defs.vehi import * from .obje import * from .unit import * -from .objs.obje import ObjeTag from supyr_struct.util import desc_variant + +vehi_flags = Bool32("flags", + "speed_wakes_physics", + "turn_wakes_physics", + "driver_power_wakes_physics", + "gunner_power_wakes_physics", + "control_opposite_sets_brake", + "slide_wakes_physics", + "kills_riders_at_terminal_velocity", + "causes_collision_damage", + "ai_weapon_cannot_rotate", + "ai_does_not_require_driver", + "ai_unused", + "ai_driver_enable", + "ai_driver_flying", + "ai_driver_can_sidestep", + "ai_driver_hovering", + "vehicle_steers_directly", + "unused", + "has_e_brake", + "noncombat_vehicle", + "no_friction_with_driver", + "can_trigger_automatic_opening_doors", + "autoaim_when_teamless" + ) # replace the object_type enum one that uses # the correct default value for this object @@ -18,81 +43,8 @@ ("object_type", object_type(1)) ) -vehi_attrs = Struct("vehi_attrs", - Bool32("flags", - "speed_wakes_physics", - "turn_wakes_physics", - "driver_power_wakes_physics", - "gunner_power_wakes_physics", - "control_opposite_sets_brake", - "slide_wakes_physics", - "kills_riders_at_terminal_velocity", - "causes_collision_damage", - "ai_weapon_cannot_rotate", - "ai_does_not_require_driver", - "ai_unused", - "ai_driver_enable", - "ai_driver_flying", - "ai_driver_can_sidestep", - "ai_driver_hovering", - "vehicle_steers_directly", - "unused", - "has_e_brake", - "noncombat_vehicle", - "no_friction_with_driver", - "can_trigger_automatic_opening_doors", - "autoaim_when_teamless" - ), - SEnum16('type', *vehicle_types), - - Pad(2), - float_wu_sec("maximum_forward_speed"), - float_wu_sec("maximum_reverse_speed"), - float_wu_sec_sq("speed_acceleration", UNIT_SCALE=per_sec_unit_scale), - float_wu_sec_sq("speed_deceleration", UNIT_SCALE=per_sec_unit_scale), - Float("maximum_left_turn"), - Float("maximum_right_turn", SIDETIP="(should be negative)"), - float_wu("wheel_circumference"), # world units - Float("turn_rate", UNIT_SCALE=per_sec_unit_scale), - Float("blur_speed", UNIT_SCALE=per_sec_unit_scale), - SEnum16('A_in', *vehicle_inputs), - SEnum16('B_in', *vehicle_inputs), - SEnum16('C_in', *vehicle_inputs), - SEnum16('D_in', *vehicle_inputs), - - Pad(12), - Float("maximum_left_slide"), - Float("maximum_right_slide"), - Float("slide_acceleration", UNIT_SCALE=per_sec_unit_scale), - Float("slide_deceleration", UNIT_SCALE=per_sec_unit_scale), - Float("minimum_flipping_angular_velocity", UNIT_SCALE=per_sec_unit_scale), - Float("maximum_flipping_angular_velocity", UNIT_SCALE=per_sec_unit_scale), - - Pad(24), - float_deg("fixed_gun_yaw"), # degrees - float_deg("fixed_gun_pitch"), # degrees - - Pad(24), - Struct("ai", - Float("sidestep_distance"), - Float("destination_radius"), - Float("avoidance_distance"), - Float("pathfinding_radius"), - float_sec("charge_repeat_timeout"), - Float("strafing_abort_range"), - from_to_rad("oversteering_bounds"), # radians - float_rad("steering_maximum"), # radians - Float("throttle_maximum"), - float_sec("move_position_time"), - ), - - Pad(4), - dependency('suspension_sound', "snd!"), - dependency('crash_sound', "snd!"), - dependency('material_effect', "foot"), - dependency('effect', "effe"), - - SIZE=256 +vehi_attrs = desc_variant(vehi_attrs, + ('flags', vehi_flags), ) vehi_body = Struct("tagdata", @@ -102,7 +54,6 @@ SIZE=1008, ) - def get(): return vehi_def diff --git a/reclaimer/mcc_hek/defs/weap.py b/reclaimer/mcc_hek/defs/weap.py index cd56be5d..43d37537 100644 --- a/reclaimer/mcc_hek/defs/weap.py +++ b/reclaimer/mcc_hek/defs/weap.py @@ -7,311 +7,61 @@ # See LICENSE for more information. # +from ...hek.defs.weap import * from .obje import * from .item import * -from .objs.weap import WeapTag from supyr_struct.util import desc_variant -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(2)) +trigger_flags = Bool32("flags", + "tracks_fired_projectile", + "random_firing_effects", + "can_fire_with_partial_ammo", + "does_not_repeat_automatically", + "locks_in_on_off_state", + "projectiles_use_weapon_origin", + "sticks_when_dropped", + "ejects_during_chamber", + "discharging_spews", + "analog_rate_of_fire", + "use_error_when_unzoomed", + "projectile_vector_cannot_be_adjusted", + "projectiles_have_identical_error", + "projectile_is_client_side_only", + "use_unit_adjust_projectile_ray_from_halo1", ) - -magazine_item = Struct("magazine_item", - SInt16("rounds"), - - Pad(10), - dependency('equipment', "eqip"), - SIZE=28 - ) - -magazine = Struct("magazine", - Bool32("flags", - "wastes_rounds_when_reloaded", - "every_round_must_be_chambered" +mcc_upgrades = Struct("mcc_upgrades", + Pad(4), + SEnum16("prediction_type", + 'none', + 'continuous', + 'instant', ), - SInt16("rounds_recharged", SIDETIP="per second"), - SInt16("rounds_total_initial"), - SInt16("rounds_total_maximum"), - SInt16("rounds_loaded_maximum"), - - Pad(8), - float_sec("reload_time"), - SInt16("rounds_reloaded"), - - Pad(2), - float_sec("chamber_time"), - - Pad(24), - dependency('reloading_effect', valid_event_effects), - dependency('chambering_effect', valid_event_effects), - - Pad(12), - reflexive("magazine_items", magazine_item, 2, - "primary", "secondary"), - SIZE=112 + SIZE=6 ) - -firing_effect = Struct("firing_effect", - SInt16("shot_count_lower_bound"), - SInt16("shot_count_upper_bound"), - - Pad(32), - dependency('firing_effect', valid_event_effects), - dependency('misfire_effect', valid_event_effects), - dependency('empty_effect', valid_event_effects), - dependency('firing_damage', "jpt!"), - dependency('misfire_damage', "jpt!"), - dependency('empty_damage', "jpt!"), - SIZE=132 +firing_descs = [ + desc for desc in trigger.values() + if isinstance(desc, dict) and desc.get("NAME") == "firing" + ] +if not firing_descs: + raise ValueError("Could not locate descriptor 'firing' in trigger") + +firing = desc_variant(firing_descs[0], + ("pad_9", mcc_upgrades) ) - -trigger = Struct("trigger", - Bool32("flags", - "tracks_fired_projectile", - "random_firing_effects", - "can_fire_with_partial_ammo", - "does_not_repeat_automatically", - "locks_in_on_off_state", - "projectiles_use_weapon_origin", - "sticks_when_dropped", - "ejects_during_chamber", - "discharging_spews", - "analog_rate_of_fire", - "use_error_when_unzoomed", - "projectile_vector_cannot_be_adjusted", - "projectiles_have_identical_error", - "projectile_is_client_side_only", - "use_unit_adjust_projectile_ray_from_halo1", - ), - Struct("firing", - QStruct("rounds_per_second", - Float("from", UNIT_SCALE=per_sec_unit_scale, GUI_NAME=''), - Float("to", UNIT_SCALE=per_sec_unit_scale), - ORIENT='h' - ), - float_sec("acceleration_time"), - float_sec("deceleration_time"), - Float("blurred_rate_of_fire", UNIT_SCALE=per_sec_unit_scale), - - Pad(8), - SEnum16("magazine", - 'primary', - 'secondary', - ('NONE', -1), - DEFAULT=-1 - ), - SInt16("rounds_per_shot"), - SInt16("minimum_rounds_loaded"), - SInt16("rounds_between_tracers"), - - Pad(4), - SEnum16("prediction_type", - 'none', - 'continuous', - 'instant', - ), - SEnum16("firing_noise", *sound_volumes), - from_to_zero_to_one("error"), - float_sec("error_acceleration_time"), - float_sec("error_deceleration_time"), - ), - - Pad(8), - Struct("charging", - float_sec("charging_time"), - float_sec("charge_hold_time"), - SEnum16("overcharged_action", - 'none', - 'explode', - 'discharge', - ), - - Pad(2), - float_zero_to_one("charged_illumination"), - float_sec("spew_time"), - dependency('charging_effect', valid_event_effects), - ), - - Struct("projectile", - SEnum16("distribution_function", - 'point', - 'horizontal_fan', - ), - SInt16("projectiles_per_shot"), - float_deg("distribution_angle"), # degrees - - Pad(4), - float_rad("minimum_error"), # radians - from_to_rad("error_angle"), # radians - QStruct("first_person_offset", INCLUDE=xyz_float), - - Pad(4), - dependency('projectile', valid_objects), - ), - - Struct("misc", - float_sec("ejection_port_recovery_time"), - float_sec("illumination_recovery_time"), - - Pad(12), - float_zero_to_one("heat_generated_per_round"), - float_zero_to_one("age_generated_per_round"), - - Pad(4), - float_sec("overload_time"), - - Pad(40), - ), - - QStruct("misc_rates", - Float("ejection_port_recovery_rate"), - Float("illumination_recovery_rate"), - Float("acceleration_rate"), - Float("deceleration_rate"), - Float("error_acceleration_rate"), - Float("error_deceleration_rate"), - VISIBLE=False, - COMMENT="\ -These are various rates that are calculated when the weapon is compiled into a map." - ), - reflexive("firing_effects", firing_effect, 8), - - SIZE=276 +trigger = desc_variant(trigger, + ("flags", trigger_flags), + ("firing", firing) ) -weap_attrs = Struct("weap_attrs", - Bool32("flags", - "unused0", - "unused1", - "unused2", - "must_be_readied", - "doesnt_count_toward_maximum", - "aim_assists_only_when_zoomed", - "prevents_grenade_throwing", - "unused7", - "unused8", - "prevents_melee_attack", - "detonates_when_dropped", - "cannot_fire_at_maximum_age", - "secondary_trigger_overrides_grenades", - "unused13", # obsolete - "enables_integrated_night_vision", - "ai_uses_weapon_melee_damage" - "prevents crouching", - "uses_3rd_person_camera" - ), - ascii_str32('label'), - SEnum16('secondary_trigger_mode', - "normal", - "slaved_to_primary", - "inhibits_primary", - "loads_alternate_ammunition", - "loads_multiple_primary_ammunition", - ), - SInt16("max_alternate_shots_loaded"), - SEnum16('A_in', *weapon_export_to), - SEnum16('B_in', *weapon_export_to), - SEnum16('C_in', *weapon_export_to), - SEnum16('D_in', *weapon_export_to), - float_sec("ready_time"), - dependency('ready_effect', valid_event_effects), - - Struct("heat", - float_zero_to_one("recovery_threshold"), - float_zero_to_one("overheated_threshold"), - float_zero_to_one("detonation_threshold"), - float_zero_to_one("detonation_fraction"), - float_zero_to_inf("loss_per_second", UNIT_SCALE=per_sec_unit_scale), - float_zero_to_one("illumination"), - - Pad(16), - dependency('overheated', valid_event_effects), - dependency('detonation', valid_event_effects), - ), - - Struct("melee", - dependency('player_damage', "jpt!"), - dependency('player_response', "jpt!"), - ), - - Pad(8), - Struct("aiming", - dependency("actor_firing_parameters", "actv"), - float_wu("near_reticle_range"), # world units - float_wu("far_reticle_range"), # world units - float_wu("intersection_reticle_range"), # world units - - Pad(2), - SInt16("zoom_levels"), - QStruct("zoom_ranges", INCLUDE=from_to), - float_rad("autoaim_angle"), # radians - float_wu("autoaim_range"), # world units - float_rad("magnetism_angle"), # radians - float_wu("magnetism_range"), # world units - float_rad("deviation_angle"), # radians - ), - - Pad(4), - Struct("movement", - SEnum16('penalized', - "always", - "when_zoomed", - "when_zoomed_or_reloading", - ), - Pad(2), - Float("forward_penalty"), - Float("sideways_penalty"), - ), - - Pad(4), - Struct("ai_targeting", - Float("minimum_target_range"), - Float("looking_time_modifier") - ), - - Pad(4), - Struct("light", - float_sec('power_on_time', UNIT_SCALE=sec_unit_scale), - float_sec('power_off_time', UNIT_SCALE=sec_unit_scale), - dependency('power_on_effect', valid_event_effects), - dependency('power_off_effect', valid_event_effects) - ), - - Struct("age", - Float("heat_penalty"), - Float("rate_of_fire_penalty"), - float_zero_to_one("misfire_start"), - float_zero_to_one("misfire_chance") - ), - - Pad(12), - Struct("interface", - dependency('first_person_model', valid_models), - dependency('first_person_animations', "antr"), - - Pad(4), - dependency('hud_interface', "wphi"), - dependency('pickup_sound', "snd!"), - dependency('zoom_in_sound', "snd!"), - dependency('zoom_out_sound', "snd!"), - - Pad(12), - Float('active_camo_ding'), - Float('active_camo_regrowth_rate', UNIT_SCALE=per_sec_unit_scale), - ), - - Pad(14), - SEnum16('weapon_type', *weapon_types_mcc), - - reflexive("predicted_resources", predicted_resource, 1024, VISIBLE=False), - reflexive("magazines", magazine, 2, - "primary", "secondary"), - reflexive("triggers", trigger, 2, - "primary", "secondary"), +# replace the object_type enum one that uses +# the correct default value for this object +obje_attrs = desc_variant(obje_attrs, + ("object_type", object_type(2)) + ) - SIZE=512 +weap_attrs = desc_variant(weap_attrs, + ("triggers", reflexive("triggers", trigger, 2, "primary", "secondary")), + ("weapon_type", SEnum16('weapon_type', *weapon_types_mcc)) ) weap_body = Struct("tagdata", diff --git a/reclaimer/mcc_hek/defs/wphi.py b/reclaimer/mcc_hek/defs/wphi.py index 91a993bd..6b6f90ae 100644 --- a/reclaimer/mcc_hek/defs/wphi.py +++ b/reclaimer/mcc_hek/defs/wphi.py @@ -7,102 +7,20 @@ # See LICENSE for more information. # -from ...common_descs import * -from .objs.wphi import WphiTag -from supyr_struct.defs.tag_def import TagDef -from .grhi import messaging_information, multitex_overlay, hud_background +from ...hek.defs.wphi import * +from .grhi import multitex_overlay, mcc_hud_anchor +from .unhi import meter_xform_common, meter_common +from supyr_struct.util import desc_variant -crosshair_types = ( - "aim", - "zoom", - "charge", - "should_reload", - "flash_heat", - "flash_total_ammo", - "flash_battery", - "reload_overheat", - "flash_when_firing_and_no_ammo", - "flash_when_throwing_grenade_and_no_grenade", - "low_ammo_and_none_left_to_reload", - "should_reload_secondary_trigger", - "flash_secondary_total_ammo", - "flash_secondary_reload", - "flash_when_firing_secondary_and_no_ammo", - "low_secondary_ammo_and_none_left_to_reload", - "primary_trigger_ready", - "secondary_trigger_ready", - "flash_when_firing_with_depleted_battery", - ) - -crosshair_types_mcc = ( - "aim", - "zoom_overlay", - "charge", - "should_reload", - "flash_heat", - "flash_total_ammo", - "flash_battery", - "reload_overheat", - "flash_when_firing_and_no_ammo", - "flash_when_throwing_grenade_and_no_grenade", - "low_ammo_and_none_left_to_reload", - "should_reload_secondary_trigger", - "flash_secondary_total_ammo", - "flash_secondary_reload", - "flash_when_firing_secondary_and_no_ammo", - "low_secondary_ammo_and_none_left_to_reload", - "primary_trigger_ready", - "secondary_trigger_ready", - "flash_when_firing_with_depleted_battery", - ) - -attached_state = SEnum16("state_attached_to", - "total_ammo", - "loaded_ammo", - "heat", - "age", - "secondary_weapon_total_ammo", - "secondary_weapon_loaded_ammo", - "distance_to_target", - "elevation_to_target", - ) - -use_on_map_type = SEnum16("can_use_on_map_type", - "any", - "fullscreen", - "splitscreen", - ) - -static_element = Struct("static_element", +# to reduce a lot of code, these have been snipped out +element_common = ( attached_state, Pad(2), use_on_map_type, - - SEnum16("anchor", - "from_parent" - "top_left" - "top_right" - "bottom_left" - "bottom_right" - "center" - "top_center" - "bottom_center" - "left_center" - "right_center" - ), - + mcc_hud_anchor, Pad(28), - QStruct("anchor_offset", - SInt16("x"), SInt16("y"), ORIENT='h', - ), - Float("width_scale"), - Float("height_scale"), - Bool16("scaling_flags", *hud_scaling_flags), - - Pad(22), - dependency("interface_bitmap", "bitm"), - #QStruct("default_color", INCLUDE=argb_byte), - #QStruct("flashing_color", INCLUDE=argb_byte), + ) +element_flash_common = ( UInt32("default_color", INCLUDE=argb_uint32), UInt32("flashing_color", INCLUDE=argb_uint32), float_sec("flash_period"), @@ -110,8 +28,14 @@ SInt16("number_of_flashes"), Bool16("flash_flags", *hud_flash_flags), float_sec("flash_length"), - #QStruct("disabled_color", INCLUDE=argb_byte), UInt32("disabled_color", INCLUDE=argb_uint32), + ) + +static_element = Struct("static_element", + *element_common, + *meter_xform_common, + dependency("interface_bitmap", "bitm"), + *element_flash_common, Pad(4), SInt16("sequence_index"), @@ -122,93 +46,16 @@ ) meter_element = Struct("meter_element", - attached_state, - Pad(2), - use_on_map_type, - - SEnum16("anchor", - "from_parent" - "top_left" - "top_right" - "bottom_left" - "bottom_right" - "center" - "top_center" - "bottom_center" - "left_center" - "right_center" - ), - - Pad(28), - QStruct("anchor_offset", - SInt16("x"), SInt16("y"), ORIENT='h', - ), - Float("width_scale"), - Float("height_scale"), - Bool16("scaling_flags", *hud_scaling_flags), - - Pad(22), - dependency("meter_bitmap", "bitm"), - #QStruct("color_at_meter_minimum", INCLUDE=xrgb_byte), - #QStruct("color_at_meter_maximum", INCLUDE=xrgb_byte), - #QStruct("flash_color", INCLUDE=xrgb_byte), - #QStruct("empty_color", INCLUDE=argb_byte), - UInt32("color_at_meter_minimum", INCLUDE=xrgb_uint32), - UInt32("color_at_meter_maximum", INCLUDE=xrgb_uint32), - UInt32("flash_color", INCLUDE=xrgb_uint32), - UInt32("empty_color", INCLUDE=argb_uint32), - Bool8("flags", *hud_panel_meter_mcc_flags), - SInt8("minimum_meter_value"), - SInt16("sequence_index"), - SInt8("alpha_multiplier"), - SInt8("alpha_bias"), - SInt16("value_scale"), - Float("opacity"), - Float("translucency"), - #QStruct("disabled_color", INCLUDE=argb_byte), - UInt32("disabled_color", INCLUDE=argb_uint32), - Float("min_alpha"), + *element_common, + *meter_xform_common, + *meter_common, SIZE=180 ) number_element = Struct("number_element", - attached_state, - Pad(2), - use_on_map_type, - - SEnum16("anchor", - "from_parent" - "top_left" - "top_right" - "bottom_left" - "bottom_right" - "center" - "top_center" - "bottom_center" - "left_center" - "right_center" - ), - - Pad(28), - QStruct("anchor_offset", - SInt16("x"), SInt16("y"), ORIENT='h', - ), - Float("width_scale"), - Float("height_scale"), - Bool16("scaling_flags", *hud_scaling_flags), - - Pad(22), - #QStruct("default_color", INCLUDE=argb_byte), - #QStruct("flashing_color", INCLUDE=argb_byte), - UInt32("default_color", INCLUDE=argb_uint32), - UInt32("flashing_color", INCLUDE=argb_uint32), - float_sec("flash_period"), - float_sec("flash_delay"), - SInt16("number_of_flashes"), - Bool16("flash_flags", *hud_flash_flags), - float_sec("flash_length"), - #QStruct("disabled_color", INCLUDE=argb_byte), - UInt32("disabled_color", INCLUDE=argb_uint32), + *element_common, + *meter_xform_common, + *element_flash_common, Pad(4), SInt8("maximum_number_of_digits"), @@ -227,194 +74,19 @@ SIZE=160 ) -crosshair_overlay = Struct("crosshair_overlay", - QStruct("anchor_offset", - SInt16("x"), SInt16("y"), ORIENT='h', - ), - Float("width_scale"), - Float("height_scale"), - Bool16("scaling_flags", *hud_scaling_flags), - - Pad(22), - #QStruct("default_color", INCLUDE=argb_byte), - #QStruct("flashing_color", INCLUDE=argb_byte), - UInt32("default_color", INCLUDE=argb_uint32), - UInt32("flashing_color", INCLUDE=argb_uint32), - float_sec("flash_period"), - float_sec("flash_delay"), - SInt16("number_of_flashes"), - Bool16("flash_flags", *hud_flash_flags), - float_sec("flash_length"), - #QStruct("disabled_color", INCLUDE=argb_byte), - UInt32("disabled_color", INCLUDE=argb_uint32), - - Pad(4), - SInt16("frame_rate", UNIT_SCALE=per_sec_unit_scale), - SInt16("sequence_index"), - Bool32("type", - "flashes_when_active", - "not_a_sprite", - "show_only_when_zoomed", - "show_sniper_data", - "hide_area_outside_reticle", - "one_zoom_level", - "dont_show_when_zoomed", - ), - SIZE=108 - ) - -overlay = Struct("overlay", - QStruct("anchor_offset", - SInt16("x"), SInt16("y"), ORIENT='h', - ), - Float("width_scale"), - Float("height_scale"), - Bool16("scaling_flags", *hud_scaling_flags), - - Pad(22), - #QStruct("default_color", INCLUDE=argb_byte), - #QStruct("flashing_color", INCLUDE=argb_byte), - UInt32("default_color", INCLUDE=argb_uint32), - UInt32("flashing_color", INCLUDE=argb_uint32), - float_sec("flash_period"), - float_sec("flash_delay"), - SInt16("number_of_flashes"), - Bool16("flash_flags", *hud_flash_flags), - float_sec("flash_length"), - #QStruct("disabled_color", INCLUDE=argb_byte), - UInt32("disabled_color", INCLUDE=argb_uint32), - - Pad(4), - SInt16("frame_rate", UNIT_SCALE=per_sec_unit_scale), - Pad(2), - SInt16("sequence_index"), - Bool16("type", - "show_on_flashing", - "show_on_empty", - "show_on_reload_overheating", - "show_on_default", - "show_always", - ), - Bool32("flags", - "flashes_when_active", - ), - SIZE=136 - ) - -crosshair = Struct("crosshair", - SEnum16("crosshair_type", *crosshair_types_mcc), - Pad(2), - use_on_map_type, - - Pad(30), - dependency("crosshair_bitmap", "bitm"), - reflexive("crosshair_overlays", crosshair_overlay, 16), - SIZE=104 - ) - overlay_element = Struct("overlay_element", - attached_state, - Pad(2), - use_on_map_type, - - SEnum16("anchor", - "from_parent" - "top_left" - "top_right" - "bottom_left" - "bottom_right" - "center" - "top_center" - "bottom_center" - "left_center" - "right_center" - ), - - Pad(28), + *element_common, dependency("overlay_bitmap", "bitm"), reflexive("overlays", overlay, 16), SIZE=104 ) -screen_effect = Struct("screen_effect", - Pad(4), - Struct("mask", - Bool16("flags", - "only_when_zoomed" - ), - Pad(18), - dependency("fullscreen_mask", "bitm"), - dependency("splitscreen_mask", "bitm") - ), - - Pad(8), - Struct("convolution", - Bool16("flags", - "only_when_zoomed" - ), - Pad(2), - from_to_rad("fov_in_bounds"), # radians - QStruct("radius_out_bounds", - INCLUDE=from_to, SIDETIP="pixels") # pixels - ), - - Pad(24), - Struct("night_vision", - Bool16("flags", - "only_when_zoomed", - "connect_to_flashlight", - "masked" - ), - SInt16("script_source", MIN=0, MAX=3, SIDETIP="[0,3]"), - Float("intensity", MIN=0.0, MAX=1.0, SIDETIP="[0,1]") - ), - - Pad(24), - Struct("desaturation", - Bool16("flags", - "only_when_zoomed", - "connect_to_flashlight", - "additive", - "masked" - ), - SInt16("script_source", MIN=0, MAX=3, SIDETIP="[0,3]"), - Float("intensity", MIN=0.0, MAX=1.0, SIDETIP="[0,1]"), - QStruct("tint", INCLUDE=rgb_float) - ), - SIZE=184 - ) - -wphi_body = Struct("tagdata", - dependency("child_hud", "wphi"), - Struct("flash_cutoffs", - Bool16("flags", - "use_parent_hud_flashing_parameters" - ), - Pad(2), - SInt16("total_ammo_cutoff"), - SInt16("loaded_ammo_cutoff"), - SInt16("heat_cutoff"), - SInt16("age_cutoff"), - ), - - Pad(32), - SEnum16("anchor", *hud_anchors_mcc), - - Pad(34), - reflexive("static_elements", static_element, 16), - reflexive("meter_elements", meter_element, 16), - reflexive("number_elements", number_element, 16), - reflexive("crosshairs", crosshair, 19), - reflexive("overlay_elements", overlay_element, 16), - FlBool32("crosshair_types", *crosshair_types, VISIBLE=False), - - # necessary for reticles to show up sometimes - Pad(12), - reflexive("screen_effect", screen_effect, 1), - - Pad(132), - messaging_information, - SIZE=380, +wphi_body = desc_variant(wphi_body, + ("anchor", mcc_hud_anchor), + ("static_elements", reflexive("static_elements", static_element, 16)), + ("meter_elements", reflexive("meter_elements", meter_element, 16)), + ("number_elements", reflexive("number_elements", number_element, 16)), + ("overlay_elements", reflexive("overlay_elements", overlay_element, 16)), ) From eb62916ac7d94c2ecfafdb772559ded897d97674 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Mon, 15 Jan 2024 00:28:23 -0600 Subject: [PATCH 06/51] More padding changes for MCC defs --- reclaimer/hek/defs/actv.py | 4 +- reclaimer/hek/defs/cdmg.py | 78 ++++---- reclaimer/hek/defs/mod2.py | 10 +- reclaimer/hek/defs/mode.py | 19 +- reclaimer/hek/defs/proj.py | 23 ++- reclaimer/hek/defs/scnr.py | 43 ++++- reclaimer/hek/defs/unit.py | 6 +- reclaimer/hek/defs/weap.py | 57 +++--- reclaimer/mcc_hek/defs/actv.py | 18 +- reclaimer/mcc_hek/defs/bitm.py | 2 +- reclaimer/mcc_hek/defs/cdmg.py | 12 +- reclaimer/mcc_hek/defs/proj.py | 9 +- reclaimer/mcc_hek/defs/scnr.py | 269 +++++++-------------------- reclaimer/mcc_hek/defs/unit.py | 2 +- reclaimer/mcc_hek/defs/weap.py | 8 +- reclaimer/meta/wrappers/halo1_map.py | 86 ++++----- reclaimer/stubbs/defs/mode.py | 10 +- 17 files changed, 261 insertions(+), 395 deletions(-) diff --git a/reclaimer/hek/defs/actv.py b/reclaimer/hek/defs/actv.py index 571f988e..498c2a77 100644 --- a/reclaimer/hek/defs/actv.py +++ b/reclaimer/hek/defs/actv.py @@ -56,12 +56,12 @@ dependency("actor_definition", "actr"), dependency("unit", valid_units), dependency("major_variant", "actv"), - SEnum16("mcc_scoring_type", TOOLTIP="Used to determine score in MCC", *mcc_actor_types), + Pad(4), # replaced with metagame_scoring in mcc_hek #Movement switching Struct("movement_switching", - Pad(22), + Pad(20), SEnum16("movement_type", "always_run", "always_crouch", diff --git a/reclaimer/hek/defs/cdmg.py b/reclaimer/hek/defs/cdmg.py index dd6f27b3..dc764e6a 100644 --- a/reclaimer/hek/defs/cdmg.py +++ b/reclaimer/hek/defs/cdmg.py @@ -11,6 +11,45 @@ from .objs.tag import HekTag from supyr_struct.defs.tag_def import TagDef +# split out to be reused in mcc_hek +damage = Struct("damage", + SEnum16("priority", + "none", + "harmless", + {NAME: "backstab", GUI_NAME: "lethal to the unsuspecting"}, + "emp", + ), + SEnum16("category", *damage_category), + Bool32("flags", + "does_not_hurt_owner", + {NAME: "headshot", GUI_NAME: "can cause headshots"}, + "pings_resistant_units", + "does_not_hurt_friends", + "does_not_ping_shields", + "detonates_explosives", + "only_hurts_shields", + "causes_flaming_death", + {NAME: "indicator_points_down", + GUI_NAME: "damage indicator always points down"}, + "skips_shields", + "only_hurts_one_infection_form", + {NAME: "multiplayer_headshot", + GUI_NAME: "can cause multiplayer headshots"}, + "infection_form_pop", + ), + Pad(4), + Float("damage_lower_bound"), + QStruct("damage_upper_bound", INCLUDE=from_to), + float_zero_to_one("vehicle_passthrough_penalty"), + Pad(4), + float_zero_to_one("stun"), + float_zero_to_one("maximum_stun"), + float_sec("stun_time"), + Pad(4), + float_zero_to_inf("instantaneous_acceleration"), + Pad(8), + ) + cdmg_body = Struct("tagdata", from_to_wu("radius"), float_zero_to_one("cutoff_scale"), @@ -34,44 +73,7 @@ Pad(192), ), - Struct("damage", - SEnum16("priority", - "none", - "harmless", - {NAME: "backstab", GUI_NAME: "lethal to the unsuspecting"}, - "emp", - ), - SEnum16("category", *damage_category), - Bool32("flags", - "does_not_hurt_owner", - {NAME: "headshot", GUI_NAME: "can cause headshots"}, - "pings_resistant_units", - "does_not_hurt_friends", - "does_not_ping_shields", - "detonates_explosives", - "only_hurts_shields", - "causes_flaming_death", - {NAME: "indicator_points_down", - GUI_NAME: "damage indicator always points down"}, - "skips_shields", - "only_hurts_one_infection_form", - {NAME: "multiplayer_headshot", - GUI_NAME: "can cause multiplayer headshots"}, - "infection_form_pop", - ), - Pad(4), - Float("damage_lower_bound"), - QStruct("damage_upper_bound", INCLUDE=from_to), - float_zero_to_one("vehicle_passthrough_penalty"), - Pad(4), - float_zero_to_one("stun"), - float_zero_to_one("maximum_stun"), - float_sec("stun_time"), - Pad(4), - float_zero_to_inf("instantaneous_acceleration"), - Pad(8), - ), - + damage, damage_modifiers, SIZE=512, ) diff --git a/reclaimer/hek/defs/mod2.py b/reclaimer/hek/defs/mod2.py index 42264d95..a040735a 100644 --- a/reclaimer/hek/defs/mod2.py +++ b/reclaimer/hek/defs/mod2.py @@ -289,11 +289,11 @@ def get(): Float('low_lod_cutoff', SIDETIP="pixels"), Float('superlow_lod_cutoff', SIDETIP="pixels"), - SInt16('superlow_lod_nodes', SIDETIP="nodes"), - SInt16('low_lod_nodes', SIDETIP="nodes"), - SInt16('medium_lod_nodes', SIDETIP="nodes"), - SInt16('high_lod_nodes', SIDETIP="nodes"), - SInt16('superhigh_lod_nodes', SIDETIP="nodes"), + SInt16('superlow_lod_nodes', SIDETIP="nodes", VISIBLE=False), + SInt16('low_lod_nodes', SIDETIP="nodes", VISIBLE=False), + SInt16('medium_lod_nodes', SIDETIP="nodes", VISIBLE=False), + SInt16('high_lod_nodes', SIDETIP="nodes", VISIBLE=False), + SInt16('superhigh_lod_nodes', SIDETIP="nodes", VISIBLE=False), Pad(10), diff --git a/reclaimer/hek/defs/mode.py b/reclaimer/hek/defs/mode.py index 2b67917c..76f96292 100644 --- a/reclaimer/hek/defs/mode.py +++ b/reclaimer/hek/defs/mode.py @@ -125,18 +125,17 @@ def get(): ), SInt32('node_list_checksum'), - # xbox has these values swapped around in order - Float('superlow_lod_cutoff', SIDETIP="pixels"), - Float('low_lod_cutoff', SIDETIP="pixels"), - Float('medium_lod_cutoff', SIDETIP="pixels"), - Float('high_lod_cutoff', SIDETIP="pixels"), Float('superhigh_lod_cutoff', SIDETIP="pixels"), + Float('high_lod_cutoff', SIDETIP="pixels"), + Float('medium_lod_cutoff', SIDETIP="pixels"), + Float('low_lod_cutoff', SIDETIP="pixels"), + Float('superlow_lod_cutoff', SIDETIP="pixels"), - SInt16('superlow_lod_nodes', SIDETIP="nodes"), - SInt16('low_lod_nodes', SIDETIP="nodes"), - SInt16('medium_lod_nodes', SIDETIP="nodes"), - SInt16('high_lod_nodes', SIDETIP="nodes"), - SInt16('superhigh_lod_nodes', SIDETIP="nodes"), + SInt16('superlow_lod_nodes', SIDETIP="nodes", VISIBLE=False), + SInt16('low_lod_nodes', SIDETIP="nodes", VISIBLE=False), + SInt16('medium_lod_nodes', SIDETIP="nodes", VISIBLE=False), + SInt16('high_lod_nodes', SIDETIP="nodes", VISIBLE=False), + SInt16('superhigh_lod_nodes', SIDETIP="nodes", VISIBLE=False), Pad(10), diff --git a/reclaimer/hek/defs/proj.py b/reclaimer/hek/defs/proj.py index baf1f83b..01fe916d 100644 --- a/reclaimer/hek/defs/proj.py +++ b/reclaimer/hek/defs/proj.py @@ -25,6 +25,18 @@ "attach" ) +# split out to be reused in mcc_hek +potential_response = Struct("potential_response", + SEnum16('response', *responses), + Bool16("flags", + "only_against_units", + ), + float_zero_to_one("skip_fraction"), + from_to_rad("impact_angle"), # radians + from_to_wu_sec("impact_velocity"), # world units/second + dependency('effect', "effe"), + ) + material_response = Struct("material_response", Bool16("flags", "cannot_be_overpenetrated", @@ -33,16 +45,7 @@ dependency('effect', "effe"), Pad(16), - Struct("potential_response", - SEnum16('response', *responses), - Bool16("flags", - "only_against_units", - ), - float_zero_to_one("skip_fraction"), - from_to_rad("impact_angle"), # radians - from_to_wu_sec("impact_velocity"), # world units/second - dependency('effect', "effe"), - ), + potential_response, Pad(16), SEnum16("scale_effects_by", diff --git a/reclaimer/hek/defs/scnr.py b/reclaimer/hek/defs/scnr.py index 78f11b6f..1df3d5e3 100644 --- a/reclaimer/hek/defs/scnr.py +++ b/reclaimer/hek/defs/scnr.py @@ -235,11 +235,21 @@ def object_swatch(name, def_id, size=48): SIZE=36 ) +# 8 bytes of padding, with the 5th byte being defined separately +# so it can be replaced with the appearance_player_index in mcc_hek +_object_ref_pad_fields = tuple( + Pad(n) for n in (2, 2, 1, 3) + ) + # Object references -scenery = object_reference("scenery", SIZE=72, block_name="sceneries") +scenery = object_reference("scenery", + *_object_ref_pad_fields, + SIZE=72, block_name="sceneries" + ) biped = object_reference("biped", - Pad(40), + *_object_ref_pad_fields, + Pad(32), float_zero_to_one("body_vitality"), Bool32("flags", "dead", @@ -248,7 +258,8 @@ def object_swatch(name, def_id, size=48): ) vehicle = object_reference("vehicle", - Pad(40), + *_object_ref_pad_fields, + Pad(32), float_zero_to_one("body_vitality"), Bool32("flags", "dead", @@ -285,11 +296,14 @@ def object_swatch(name, def_id, size=48): "obsolete", {NAME: "can_accelerate", GUI_NAME:"moves due to explosions"}, ), + Pad(1), # replaced with appearance_player_index in mcc_hek + Pad(3), SIZE=40 ) weapon = object_reference("weapon", - Pad(40), + *_object_ref_pad_fields, + Pad(32), SInt16("rounds_left"), SInt16("rounds_loaded"), Bool16("flags", @@ -310,7 +324,7 @@ def object_swatch(name, def_id, size=48): ) machine = object_reference("machine", - Pad(8), + *_object_ref_pad_fields, dyn_senum16("power_group", DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), dyn_senum16("position_group", @@ -326,7 +340,7 @@ def object_swatch(name, def_id, size=48): ) control = object_reference("control", - Pad(8), + *_object_ref_pad_fields, dyn_senum16("power_group", DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), dyn_senum16("position_group", @@ -340,7 +354,7 @@ def object_swatch(name, def_id, size=48): ) light_fixture = object_reference("light_fixture", - Pad(8), + *_object_ref_pad_fields, dyn_senum16("power_group", DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), dyn_senum16("position_group", @@ -353,7 +367,10 @@ def object_swatch(name, def_id, size=48): SIZE=88 ) -sound_scenery = object_reference("sound_scenery", SIZE=40, block_name="sound_sceneries") +sound_scenery = object_reference("sound_scenery", + *_object_ref_pad_fields, + SIZE=40, block_name="sound_sceneries" + ) # Object swatches scenery_swatch = object_swatch("scenery_swatch", "scen") @@ -378,6 +395,8 @@ def object_swatch(name, def_id, size=48): SInt16("secondary_rounds_total"), SInt8("starting_frag_grenade_count", MIN=0), SInt8("starting_plasma_grenade_count", MIN=0), + Pad(1), # replaced with starting_grenade_type2_count in mcc_hek + Pad(1), # replaced with starting_grenade_type3_count in mcc_hek SIZE=104 ) @@ -542,6 +561,8 @@ def object_swatch(name, def_id, size=48): SEnum16("return_type", *script_object_types, EDITABLE=False), UInt32("root_expression_index", EDITABLE=False), Computed("decompiled_script", WIDGET=HaloScriptTextFrame), + Pad(40), + Pad(12), # replaced with parameters in mcc_hek SIZE=92, ) @@ -606,7 +627,8 @@ def object_swatch(name, def_id, size=48): "center", ), - Pad(6), + Pad(2), + Pad(4), # replaced with flags in mcc_hek #QStruct("text_color", INCLUDE=argb_byte), #QStruct("shadow_color", INCLUDE=argb_byte), UInt32("text_color", INCLUDE=argb_uint32), @@ -942,7 +964,8 @@ def object_swatch(name, def_id, size=48): rawdata_ref("scenario_editor_data", max_size=65536), reflexive("comments", comment, 1024), - Pad(224), + Pad(12), # replaced with scavenger_hunt_objects in mcc_hek + Pad(212), reflexive("object_names", object_name, 512, DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), reflexive("sceneries", scenery, 2000, IGNORE_SAFE_MODE=True), diff --git a/reclaimer/hek/defs/unit.py b/reclaimer/hek/defs/unit.py index d20617eb..9e556ef5 100644 --- a/reclaimer/hek/defs/unit.py +++ b/reclaimer/hek/defs/unit.py @@ -174,10 +174,8 @@ def get(): ), Pad(2), - Struct("mcc_additions", # replaced with opensauce unit extension in os_v4 - SEnum16("mcc_scoring_type", TOOLTIP="Used to determine score in MCC", *actor_types_mcc), - Pad(10), - ), + Pad(12), # replaced with opensauce unit extension in os_v4 and mcc_additions in mcc_hek + reflexive("new_hud_interfaces", new_hud_interface, 2, 'default/solo', 'multiplayer'), reflexive("dialogue_variants", dialogue_variant, 16, diff --git a/reclaimer/hek/defs/weap.py b/reclaimer/hek/defs/weap.py index 27afab29..7d2896d9 100644 --- a/reclaimer/hek/defs/weap.py +++ b/reclaimer/hek/defs/weap.py @@ -67,6 +67,35 @@ SIZE=132 ) +# split out to be reused in mcc_hek +firing = Struct("firing", + QStruct("rounds_per_second", + Float("from", UNIT_SCALE=per_sec_unit_scale, GUI_NAME=''), + Float("to", UNIT_SCALE=per_sec_unit_scale), + ORIENT='h' + ), + float_sec("acceleration_time"), + float_sec("deceleration_time"), + Float("blurred_rate_of_fire", UNIT_SCALE=per_sec_unit_scale), + + Pad(8), + SEnum16("magazine", + 'primary', + 'secondary', + ('NONE', -1), + DEFAULT=-1 + ), + SInt16("rounds_per_shot"), + SInt16("minimum_rounds_loaded"), + SInt16("rounds_between_tracers"), + + Pad(6), + SEnum16("firing_noise", *sound_volumes), + from_to_zero_to_one("error"), + float_sec("error_acceleration_time"), + float_sec("error_deceleration_time"), + ) + trigger = Struct("trigger", Bool32("flags", "tracks_fired_projectile", @@ -84,33 +113,7 @@ "projectiles_have_identical_error", "projectile_is_client_side_only", ), - Struct("firing", - QStruct("rounds_per_second", - Float("from", UNIT_SCALE=per_sec_unit_scale, GUI_NAME=''), - Float("to", UNIT_SCALE=per_sec_unit_scale), - ORIENT='h' - ), - float_sec("acceleration_time"), - float_sec("deceleration_time"), - Float("blurred_rate_of_fire", UNIT_SCALE=per_sec_unit_scale), - - Pad(8), - SEnum16("magazine", - 'primary', - 'secondary', - ('NONE', -1), - DEFAULT=-1 - ), - SInt16("rounds_per_shot"), - SInt16("minimum_rounds_loaded"), - SInt16("rounds_between_tracers"), - - Pad(6), - SEnum16("firing_noise", *sound_volumes), - from_to_zero_to_one("error"), - float_sec("error_acceleration_time"), - float_sec("error_deceleration_time"), - ), + firing, Pad(8), Struct("charging", diff --git a/reclaimer/mcc_hek/defs/actv.py b/reclaimer/mcc_hek/defs/actv.py index 20205b0d..74eab55e 100644 --- a/reclaimer/mcc_hek/defs/actv.py +++ b/reclaimer/mcc_hek/defs/actv.py @@ -10,28 +10,18 @@ from ...hek.defs.actv import * from supyr_struct.util import desc_variant -actv_grenades = desc_variant(actv_grenades, - ("grenade_type", SEnum16("grenade_type", *grenade_types_mcc)), - ) metagame_scoring = Struct("metagame_scoring", SEnum16("metagame_type", *actor_types_mcc), SEnum16("metagame_class", *actor_classes_mcc), ORIENT="H", COMMENT="Used to determine score in MCC", ) -movement_switching_descs = [ - desc for desc in actv_body.values() - if isinstance(desc, dict) and desc.get("NAME") == "movement_switching" - ] -if not movement_switching_descs: - raise ValueError("Could not locate descriptor 'movement_switching' in actv_body") -movement_switching = desc_variant(movement_switching_descs[0], - ("pad_0", Pad(20)), - ) +actv_grenades = desc_variant(actv_grenades, + ("grenade_type", SEnum16("grenade_type", *grenade_types_mcc)), + ) actv_body = desc_variant(actv_body, ("grenades", actv_grenades), - ("mcc_scoring_type", metagame_scoring), - ("movement_switching", movement_switching), + ("pad_4", metagame_scoring), ) def get(): diff --git a/reclaimer/mcc_hek/defs/bitm.py b/reclaimer/mcc_hek/defs/bitm.py index 3f301d50..bc000799 100644 --- a/reclaimer/mcc_hek/defs/bitm.py +++ b/reclaimer/mcc_hek/defs/bitm.py @@ -14,7 +14,7 @@ format_comment = "".join(( format_comment_parts[0], """\ -HIGH QUALITY COMPRESSION: ???? +*HIGH QUALITY COMPRESSION: ???? NOTE:""", format_comment_parts[1], diff --git a/reclaimer/mcc_hek/defs/cdmg.py b/reclaimer/mcc_hek/defs/cdmg.py index 12fd9168..202a7bdd 100644 --- a/reclaimer/mcc_hek/defs/cdmg.py +++ b/reclaimer/mcc_hek/defs/cdmg.py @@ -31,19 +31,11 @@ "allow_any_non_zero_acceleration_value", ) -damage_descs = [ - desc for desc in cdmg_body.values() - if isinstance(desc, dict) and desc.get("NAME") == "damage" - ] -if not damage_descs: - raise ValueError("Could not locate descriptor 'damage' in cdmg_body") - -damage = desc_variant(damage_descs[0], +damage = desc_variant(damage, ("flags", damage_flags), ("instantaneous_acceleration", QStruct("instantaneous_acceleration", INCLUDE=ijk_float)), - ("pad_3", Pad(0)), + ("pad_13", Pad(0)), ) - cdmg_body = desc_variant(cdmg_body, ("damage", damage), ) diff --git a/reclaimer/mcc_hek/defs/proj.py b/reclaimer/mcc_hek/defs/proj.py index f10f63d9..2ef9e71f 100644 --- a/reclaimer/mcc_hek/defs/proj.py +++ b/reclaimer/mcc_hek/defs/proj.py @@ -15,14 +15,7 @@ "only_against_units", "never_against_units" ) -potential_response_descs = [ - desc for desc in material_response.values() - if isinstance(desc, dict) and desc.get("NAME") == "potential_response" - ] -if not potential_response_descs: - raise ValueError("Could not locate descriptor 'potential_response' in material_response") - -potential_response = desc_variant(potential_response_descs[0], +potential_response = desc_variant(potential_response, ("flags", potential_response_flags) ) material_response = desc_variant(material_response, diff --git a/reclaimer/mcc_hek/defs/scnr.py b/reclaimer/mcc_hek/defs/scnr.py index 387d98d2..b61f81b4 100644 --- a/reclaimer/mcc_hek/defs/scnr.py +++ b/reclaimer/mcc_hek/defs/scnr.py @@ -18,36 +18,6 @@ "use_player_appearance", ) -# macro to cut down on a lot of duplicated definitions -def add_player_appearance_fields(desc, lpad_size=4, insert=True): - desc = desc_variant(desc, ('not_placed', object_ref_flags)) - attr_i = (object_reference('_') if insert else desc)["ENTRIES"] - fields = [ - *(dict(desc[i]) for i in range(attr_i)), - Pad(lpad_size), SInt8("appearance_player_index"), - *(dict(desc[i]) for i in range(attr_i, desc["ENTRIES"])), - ] - if len(fields)-2 > attr_i: - # shrink the padding after the appearance_player_index - # by the lpad_size + the size of appearance_player_index - fields[attr_i+2]["SIZE"] -= lpad_size + 1 - - desc.update({i: fdesc for i, fdesc in enumerate(fields)}) - desc.update(ENTRIES=len(fields)) - return desc - -# Object references -scenery = add_player_appearance_fields(scenery) -equipment = add_player_appearance_fields(equipment, 0, False) -sound_scenery = add_player_appearance_fields(sound_scenery) -biped = add_player_appearance_fields(biped) -vehicle = add_player_appearance_fields(vehicle) -weapon = add_player_appearance_fields(weapon) -machine = add_player_appearance_fields(machine) -control = add_player_appearance_fields(control) -light_fixture = add_player_appearance_fields(light_fixture) - - starting_equipment_flags = Bool32("flags", "no_grenades", "plasma_grenades_only", @@ -59,20 +29,38 @@ def add_player_appearance_fields(desc, lpad_size=4, insert=True): SEnum16("return_type", *script_object_types, EDITABLE=False), SIZE=36, ) -halo_script = Struct("script", - # copy positional fields from halo_script descriptor - *(halo_script[i] for i in range(halo_script["ENTRIES"])), - Pad(40), - reflexive("parameters", parameters, 16), - SIZE=92, +cutscene_title_flags = Bool32("flags", + "wrap_horizontally", + "wrap_vertically", + "center_vertically", + "bottom_justify", + ) +scavenger_hunt_objects = Struct("scavenger_hunt_objects", + ascii_str32("exported_name"), + dyn_senum16("scenario_object_name_index", DYN_NAME_PATH="tagdata.object_names.STEPTREE[DYN_I].name"), + Pad(2), + SIZE=36 + ) + +# Object references +object_ref_replacements = ( + ('not_placed', object_ref_flags), + ("pad_8", SInt8("appearance_player_index")), ) -player_starting_profile = Struct("player_starting_profile", - # copy positional fields from player_starting_profile descriptor - *(player_starting_profile[i] for i in range(player_starting_profile["ENTRIES"])), - SInt8("starting_grenade_type2_count", MIN=0), - SInt8("starting_grenade_type3_count", MIN=0), - SIZE=104 +scenery = desc_variant(scenery, *object_ref_replacements) +equipment = desc_variant(equipment, *object_ref_replacements) +sound_scenery = desc_variant(sound_scenery, *object_ref_replacements) +biped = desc_variant(biped, *object_ref_replacements) +vehicle = desc_variant(vehicle, *object_ref_replacements) +weapon = desc_variant(weapon, *object_ref_replacements) +machine = desc_variant(machine, *object_ref_replacements) +control = desc_variant(control, *object_ref_replacements) +light_fixture = desc_variant(light_fixture, *object_ref_replacements) + +player_starting_profile = desc_variant(player_starting_profile, + ("pad_11", SInt8("starting_grenade_type2_count", MIN=0)), + ("pad_12", SInt8("starting_grenade_type3_count", MIN=0)), ) netgame_equipment = desc_variant(netgame_equipment, ("team_index", SInt16("usage_id")), @@ -80,168 +68,55 @@ def add_player_appearance_fields(desc, lpad_size=4, insert=True): starting_equipment = desc_variant(starting_equipment, ("flags", starting_equipment_flags), ) +halo_script = desc_variant(halo_script, + ("pad_6", reflexive("parameters", parameters, 16)) + ) source_file = desc_variant(source_file, ("source", rawdata_ref("source", max_size=1048576, widget=HaloScriptSourceFrame)) ) - -cutscene_title = Struct("cutscene_title", - Pad(4), - ascii_str32("name"), - Pad(4), - QStruct("text_bounds", - SInt16("t"), SInt16("l"), SInt16("b"), SInt16("r"), - ORIENT='h', - ), - SInt16("string_index"), - SEnum16("text_style", - "plain", - "bold", - "italic", - "condense", - "underline", - ), - SEnum16("justification", - "left", - "right", - "center", - ), - Pad(2), - Bool32("flags", - "wrap horizontally", - "wrap vertically", - "center vertically", - "bottom justify", - ), - UInt32("text_color", INCLUDE=argb_uint32), - UInt32("shadow_color", INCLUDE=argb_uint32), - float_sec("fade_in_time"), # seconds - float_sec("up_time"), # seconds - float_sec("fade_out_time"), # seconds - SIZE=96 +cutscene_title = desc_variant(cutscene_title, + ("pad_8", cutscene_title_flags), ) -scavenger_hunt_objects = Struct("scavenger_hunt_objects", - ascii_str32("exported_name"), - dyn_senum16("scenario_object_name_index", DYN_NAME_PATH="tagdata.object_names.STEPTREE[DYN_I].name"), - Pad(2), - SIZE=36 +scnr_flags = Bool16("flags", + "cortana_hack", + "use_demo_ui", + {NAME: "color_correction", GUI_NAME: "color correction (ntsc->srgb)"}, + "DO_NOT_apply_bungie_campaign_tag_patches", ) -scnr_body = Struct("tagdata", - dependency("DONT_USE", 'sbsp'), - dependency("WONT_USE", 'sbsp'), - dependency("CANT_USE", 'sky '), - reflexive("skies", sky, 8, DYN_NAME_PATH='.sky.filepath'), - SEnum16("type", - "singleplayer", - "multiplayer", - "main_menu" - ), - Bool16("flags", - "cortana_hack", - "use_demo_ui", - {NAME: "color_correction", GUI_NAME: "color correction (ntsc->srgb)"}, - "DO_NOT_apply_bungie_campaign_tag_patches", - ), - reflexive("child_scenarios", child_scenario, 16, - DYN_NAME_PATH='.child_scenario.filepath'), - float_rad("local_north"), # radians - - Pad(156), - reflexive("predicted_resources", predicted_resource, 1024, VISIBLE=False), - reflexive("functions", function, 32, - DYN_NAME_PATH='.name'), - rawdata_ref("scenario_editor_data", max_size=65536), - reflexive("comments", comment, 1024), - reflexive("scavenger_hunt_objects", scavenger_hunt_objects, 16), - - Pad(212), - reflexive("object_names", object_name, 640, - DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), - reflexive("sceneries", scenery, 2000, IGNORE_SAFE_MODE=True), - reflexive("sceneries_palette", scenery_swatch, 256, - DYN_NAME_PATH='.name.filepath'), - reflexive("bipeds", biped, 128, IGNORE_SAFE_MODE=True), - reflexive("bipeds_palette", biped_swatch, 256, - DYN_NAME_PATH='.name.filepath'), - reflexive("vehicles", vehicle, 256, IGNORE_SAFE_MODE=True), - reflexive("vehicles_palette", vehicle_swatch, 256, - DYN_NAME_PATH='.name.filepath'), - reflexive("equipments", equipment, 256, IGNORE_SAFE_MODE=True), - reflexive("equipments_palette", equipment_swatch, 256, - DYN_NAME_PATH='.name.filepath'), - reflexive("weapons", weapon, 128, IGNORE_SAFE_MODE=True), - reflexive("weapons_palette", weapon_swatch, 256, - DYN_NAME_PATH='.name.filepath'), - - reflexive("device_groups", device_group, 128, - DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), - reflexive("machines", machine, 400, IGNORE_SAFE_MODE=True), - reflexive("machines_palette", machine_swatch, 256, - DYN_NAME_PATH='.name.filepath'), - reflexive("controls", control, 100, IGNORE_SAFE_MODE=True), - reflexive("controls_palette", control_swatch, 256, - DYN_NAME_PATH='.name.filepath'), - reflexive("light_fixtures", light_fixture, 500, IGNORE_SAFE_MODE=True), - reflexive("light_fixtures_palette", light_fixture_swatch, 256, - DYN_NAME_PATH='.name.filepath'), - reflexive("sound_sceneries", sound_scenery, 256, IGNORE_SAFE_MODE=True), - reflexive("sound_sceneries_palette", sound_scenery_swatch, 256, - DYN_NAME_PATH='.name.filepath'), - - Pad(84), - reflexive("player_starting_profiles", player_starting_profile, 256, - DYN_NAME_PATH='.name'), - reflexive("player_starting_locations", player_starting_location, 256), - reflexive("trigger_volumes", trigger_volume, 256, - DYN_NAME_PATH='.name'), - reflexive("recorded_animations", recorded_animation, 1024, - DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), - reflexive("netgame_flags", netgame_flag, 200, - DYN_NAME_PATH='.type.enum_name'), - reflexive("netgame_equipments", netgame_equipment, 200, - DYN_NAME_PATH='.item_collection.filepath'), - reflexive("starting_equipments", starting_equipment, 200), - reflexive("bsp_switch_trigger_volumes", bsp_switch_trigger_volume, 256), - reflexive("decals", decal, 65535), - reflexive("decals_palette", decal_swatch, 128, - DYN_NAME_PATH='.name.filepath'), - reflexive("detail_object_collection_palette", - detail_object_collection_swatch, 32, DYN_NAME_PATH='.name.filepath'), - - Pad(84), - reflexive("actors_palette", actor_swatch, 64, - DYN_NAME_PATH='.name.filepath'), - reflexive("encounters", encounter, 128, DYN_NAME_PATH='.name'), - reflexive("command_lists", command_list, 256, DYN_NAME_PATH='.name'), - reflexive("ai_animation_references", ai_anim_reference, 128, - DYN_NAME_PATH='.animation_name'), - reflexive("ai_script_references", ai_script_reference, 128, - DYN_NAME_PATH='.script_name'), - reflexive("ai_recording_references", ai_recording_reference, 128, - DYN_NAME_PATH='.recording_name'), - reflexive("ai_conversations", ai_conversation, 128, - DYN_NAME_PATH='.name'), - rawdata_ref("script_syntax_data", max_size=655396, IGNORE_SAFE_MODE=True), - rawdata_ref("script_string_data", max_size=819200, IGNORE_SAFE_MODE=True), - reflexive("scripts", halo_script, 1024, DYN_NAME_PATH='.name'), - reflexive("globals", halo_global, 512, DYN_NAME_PATH='.name'), - reflexive("references", reference, 512, - DYN_NAME_PATH='.reference.filepath'), - reflexive("source_files", source_file, 16, DYN_NAME_PATH='.source_name'), - - Pad(24), - reflexive("cutscene_flags", cutscene_flag, 512, DYN_NAME_PATH='.name'), - reflexive("cutscene_camera_points", cutscene_camera_point, 512, - DYN_NAME_PATH='.name'), - reflexive("cutscene_titles", cutscene_title, 64, DYN_NAME_PATH='.name'), - Pad(108), - dependency("custom_object_names", 'ustr'), - dependency("ingame_help_text", 'ustr'), - dependency("hud_messages", 'hmt '), - reflexive("structure_bsps", structure_bsp, 16, - DYN_NAME_PATH='.structure_bsp.filepath'), - SIZE=1456, +scnr_body = desc_variant(scnr_body, + ("flags", scnr_flags), + ("pad_13", reflexive("scavenger_hunt_objects", scavenger_hunt_objects, 16)), + ("object_names", reflexive("object_names", object_name, 640, DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True)), + ("sceneries", reflexive("sceneries", scenery, 2000, IGNORE_SAFE_MODE=True)), + ("bipeds", reflexive("bipeds", biped, 128, IGNORE_SAFE_MODE=True)), + ("vehicles", reflexive("vehicles", vehicle, 256, IGNORE_SAFE_MODE=True)), + ("equipments", reflexive("equipments", equipment, 256, IGNORE_SAFE_MODE=True)), + ("weapons", reflexive("weapons", weapon, 128, IGNORE_SAFE_MODE=True)), + ("machines", reflexive("machines", machine, 400, IGNORE_SAFE_MODE=True)), + ("controls", reflexive("controls", control, 100, IGNORE_SAFE_MODE=True)), + ("light_fixtures", reflexive("light_fixtures", light_fixture, 500, IGNORE_SAFE_MODE=True)), + ("sound_sceneries", reflexive("sound_sceneries", sound_scenery, 256, IGNORE_SAFE_MODE=True)), + ("sceneries_palette", reflexive("sceneries_palette", scenery_swatch, 256, DYN_NAME_PATH='.name.filepath')), + ("bipeds_palette", reflexive("bipeds_palette", biped_swatch, 256, DYN_NAME_PATH='.name.filepath')), + ("vehicles_palette", reflexive("vehicles_palette", vehicle_swatch, 256, DYN_NAME_PATH='.name.filepath')), + ("equipments_palette", reflexive("equipments_palette", equipment_swatch, 256, DYN_NAME_PATH='.name.filepath')), + ("weapons_palette", reflexive("weapons_palette", weapon_swatch, 256, DYN_NAME_PATH='.name.filepath')), + ("machines_palette", reflexive("machines_palette", machine_swatch, 256, DYN_NAME_PATH='.name.filepath')), + ("controls_palette", reflexive("controls_palette", control_swatch, 256, DYN_NAME_PATH='.name.filepath')), + ("light_fixtures_palette", reflexive("light_fixtures_palette", light_fixture_swatch, 256, DYN_NAME_PATH='.name.filepath')), + ("sound_sceneries_palette", reflexive("sound_sceneries_palette", sound_scenery_swatch, 256, DYN_NAME_PATH='.name.filepath')), + ("player_starting_profiles", reflexive("player_starting_profiles", player_starting_profile, 256, DYN_NAME_PATH='.name')), + ("netgame_equipments", reflexive("netgame_equipments", netgame_equipment, 200, DYN_NAME_PATH='.item_collection.filepath')), + ("starting_equipments", reflexive("starting_equipments", starting_equipment, 200)), + ("script_syntax_data", rawdata_ref("script_syntax_data", max_size=655396, IGNORE_SAFE_MODE=True)), + ("script_string_data", rawdata_ref("script_string_data", max_size=819200, IGNORE_SAFE_MODE=True)), + ("scripts", reflexive("scripts", halo_script, 1024, DYN_NAME_PATH='.name')), + ("globals", reflexive("globals", halo_global, 512, DYN_NAME_PATH='.name')), + ("references", reflexive("references", reference, 512, DYN_NAME_PATH='.reference.filepath')), + ("source_files", reflexive("source_files", source_file, 16, DYN_NAME_PATH='.source_name')), + ("cutscene_titles", reflexive("cutscene_titles", cutscene_title, 64, DYN_NAME_PATH='.name')), ) def get(): diff --git a/reclaimer/mcc_hek/defs/unit.py b/reclaimer/mcc_hek/defs/unit.py index 581a06f4..51cbbb48 100644 --- a/reclaimer/mcc_hek/defs/unit.py +++ b/reclaimer/mcc_hek/defs/unit.py @@ -17,7 +17,7 @@ ) unit_attrs = desc_variant(unit_attrs, - ("mcc_additions", metagame_scoring), + ("pad_45", metagame_scoring), ("grenade_type", SEnum16("grenade_type", *grenade_types_mcc)), ) diff --git a/reclaimer/mcc_hek/defs/weap.py b/reclaimer/mcc_hek/defs/weap.py index 43d37537..9612567f 100644 --- a/reclaimer/mcc_hek/defs/weap.py +++ b/reclaimer/mcc_hek/defs/weap.py @@ -38,14 +38,8 @@ ), SIZE=6 ) -firing_descs = [ - desc for desc in trigger.values() - if isinstance(desc, dict) and desc.get("NAME") == "firing" - ] -if not firing_descs: - raise ValueError("Could not locate descriptor 'firing' in trigger") -firing = desc_variant(firing_descs[0], +firing = desc_variant(firing, ("pad_9", mcc_upgrades) ) trigger = desc_variant(trigger, diff --git a/reclaimer/meta/wrappers/halo1_map.py b/reclaimer/meta/wrappers/halo1_map.py index 3dd21a61..104313c8 100644 --- a/reclaimer/meta/wrappers/halo1_map.py +++ b/reclaimer/meta/wrappers/halo1_map.py @@ -985,57 +985,51 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): verts_attr_name = "uncompressed_vertices" byteswap_verts = byteswap_uncomp_verts vert_size = 68 - - if engine != "stubbspc": - # need to swap the lod cutoff and nodes values around - cutoffs = (meta.superlow_lod_cutoff, meta.low_lod_cutoff, - meta.high_lod_cutoff, meta.superhigh_lod_cutoff) - meta.superlow_lod_cutoff = cutoffs[3] - meta.low_lod_cutoff = cutoffs[2] - meta.high_lod_cutoff = cutoffs[1] - meta.superhigh_lod_cutoff = cutoffs[0] - else: verts_attr_name = "compressed_vertices" byteswap_verts = byteswap_comp_verts vert_size = 32 - # If this is a gbxmodel localize the markers. - # We skip this for xbox models for arsenic. - - if tag_cls == "mod2": - # ensure all local marker arrays are empty - for region in meta.regions.STEPTREE: - for perm in region.permutations.STEPTREE: - del perm.local_markers.STEPTREE[:] - - # localize the global markers - for g_marker in meta.markers.STEPTREE: - for g_marker_inst in g_marker.marker_instances.STEPTREE: - try: - region = meta.regions.STEPTREE[g_marker_inst.region_index] - except IndexError: - print("Model marker instance for", g_marker.name, "has invalid region index", g_marker_inst.region_index, "and is skipped.") - continue - - try: - perm = region.permutations.STEPTREE[g_marker_inst.permutation_index] - except IndexError: - print("Model marker instance for", g_marker.name, "has invalid permutation index", g_marker_inst.permutation_index, "and is skipped.") - continue - - # make a new local marker - perm.local_markers.STEPTREE.append() - l_marker = perm.local_markers.STEPTREE[-1] - - # copy the global marker into the local - l_marker.name = g_marker.name - l_marker.node_index = g_marker_inst.node_index - l_marker.translation[:] = g_marker_inst.translation[:] - l_marker.rotation[:] = g_marker_inst.rotation[:] - - # clear the global markers - del meta.markers.STEPTREE[:] + # lod cutoffs are swapped between tag and cache form + cutoffs = (meta.superlow_lod_cutoff, meta.low_lod_cutoff, + meta.high_lod_cutoff, meta.superhigh_lod_cutoff) + meta.superlow_lod_cutoff = cutoffs[3] + meta.low_lod_cutoff = cutoffs[2] + meta.high_lod_cutoff = cutoffs[1] + meta.superhigh_lod_cutoff = cutoffs[0] + + # localize the global markers + # ensure all local marker arrays are empty + for region in meta.regions.STEPTREE: + for perm in region.permutations.STEPTREE: + del perm.local_markers.STEPTREE[:] + + for g_marker in meta.markers.STEPTREE: + for g_marker_inst in g_marker.marker_instances.STEPTREE: + try: + region = meta.regions.STEPTREE[g_marker_inst.region_index] + except IndexError: + print("Model marker instance for", g_marker.name, "has invalid region index", g_marker_inst.region_index, "and is skipped.") + continue + + try: + perm = region.permutations.STEPTREE[g_marker_inst.permutation_index] + except IndexError: + print("Model marker instance for", g_marker.name, "has invalid permutation index", g_marker_inst.permutation_index, "and is skipped.") + continue + + # make a new local marker + perm.local_markers.STEPTREE.append() + l_marker = perm.local_markers.STEPTREE[-1] + + # copy the global marker into the local + l_marker.name = g_marker.name + l_marker.node_index = g_marker_inst.node_index + l_marker.translation[:] = g_marker_inst.translation[:] + l_marker.rotation[:] = g_marker_inst.rotation[:] + + # clear the global markers + del meta.markers.STEPTREE[:] # grab vertices and indices from the map for geom in meta.geometries.STEPTREE: diff --git a/reclaimer/stubbs/defs/mode.py b/reclaimer/stubbs/defs/mode.py index cbf65558..84ad26c7 100644 --- a/reclaimer/stubbs/defs/mode.py +++ b/reclaimer/stubbs/defs/mode.py @@ -109,11 +109,11 @@ def get(): Float('low_lod_cutoff', SIDETIP="pixels"), Float('superlow_lod_cutoff', SIDETIP="pixels"), - SInt16('superhigh_lod_nodes', SIDETIP="nodes"), - SInt16('high_lod_nodes', SIDETIP="nodes"), - SInt16('medium_lod_nodes', SIDETIP="nodes"), - SInt16('low_lod_nodes', SIDETIP="nodes"), - SInt16('superlow_lod_nodes', SIDETIP="nodes"), + SInt16('superlow_lod_nodes', SIDETIP="nodes", VISIBLE=False), + SInt16('low_lod_nodes', SIDETIP="nodes", VISIBLE=False), + SInt16('medium_lod_nodes', SIDETIP="nodes", VISIBLE=False), + SInt16('high_lod_nodes', SIDETIP="nodes", VISIBLE=False), + SInt16('superhigh_lod_nodes', SIDETIP="nodes", VISIBLE=False), Pad(10), From f02f42aa4e63a9c6b450a889c89f633c69d50414 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Mon, 15 Jan 2024 13:48:58 -0600 Subject: [PATCH 07/51] Removed unneeded files --- reclaimer/mcc_hek/defs/objs/__init__.py | 0 reclaimer/mcc_hek/defs/objs/actr.py | 40 -- reclaimer/mcc_hek/defs/objs/ant_.py | 31 -- reclaimer/mcc_hek/defs/objs/antr.py | 13 - reclaimer/mcc_hek/defs/objs/bipd.py | 34 -- reclaimer/mcc_hek/defs/objs/bitm.py | 483 ------------------------ reclaimer/mcc_hek/defs/objs/coll.py | 21 -- reclaimer/mcc_hek/defs/objs/ctrl.py | 17 - reclaimer/mcc_hek/defs/objs/devi.py | 49 --- reclaimer/mcc_hek/defs/objs/effe.py | 36 -- reclaimer/mcc_hek/defs/objs/lens.py | 21 -- reclaimer/mcc_hek/defs/objs/lifi.py | 17 - reclaimer/mcc_hek/defs/objs/ligh.py | 22 -- reclaimer/mcc_hek/defs/objs/mach.py | 19 - reclaimer/mcc_hek/defs/objs/mod2.py | 96 ----- reclaimer/mcc_hek/defs/objs/mode.py | 124 ------ reclaimer/mcc_hek/defs/objs/obje.py | 52 --- reclaimer/mcc_hek/defs/objs/phys.py | 151 -------- reclaimer/mcc_hek/defs/objs/pphy.py | 31 -- reclaimer/mcc_hek/defs/objs/sbsp.py | 64 ---- reclaimer/mcc_hek/defs/objs/shdr.py | 46 --- reclaimer/mcc_hek/defs/objs/snd_.py | 20 - reclaimer/mcc_hek/defs/objs/str_.py | 21 -- reclaimer/mcc_hek/defs/objs/tag.py | 70 ---- reclaimer/mcc_hek/defs/objs/ustr.py | 13 - reclaimer/mcc_hek/defs/objs/weap.py | 39 -- reclaimer/mcc_hek/defs/objs/wphi.py | 20 - 27 files changed, 1550 deletions(-) delete mode 100644 reclaimer/mcc_hek/defs/objs/__init__.py delete mode 100644 reclaimer/mcc_hek/defs/objs/actr.py delete mode 100644 reclaimer/mcc_hek/defs/objs/ant_.py delete mode 100644 reclaimer/mcc_hek/defs/objs/antr.py delete mode 100644 reclaimer/mcc_hek/defs/objs/bipd.py delete mode 100644 reclaimer/mcc_hek/defs/objs/bitm.py delete mode 100644 reclaimer/mcc_hek/defs/objs/coll.py delete mode 100644 reclaimer/mcc_hek/defs/objs/ctrl.py delete mode 100644 reclaimer/mcc_hek/defs/objs/devi.py delete mode 100644 reclaimer/mcc_hek/defs/objs/effe.py delete mode 100644 reclaimer/mcc_hek/defs/objs/lens.py delete mode 100644 reclaimer/mcc_hek/defs/objs/lifi.py delete mode 100644 reclaimer/mcc_hek/defs/objs/ligh.py delete mode 100644 reclaimer/mcc_hek/defs/objs/mach.py delete mode 100644 reclaimer/mcc_hek/defs/objs/mod2.py delete mode 100644 reclaimer/mcc_hek/defs/objs/mode.py delete mode 100644 reclaimer/mcc_hek/defs/objs/obje.py delete mode 100644 reclaimer/mcc_hek/defs/objs/phys.py delete mode 100644 reclaimer/mcc_hek/defs/objs/pphy.py delete mode 100644 reclaimer/mcc_hek/defs/objs/sbsp.py delete mode 100644 reclaimer/mcc_hek/defs/objs/shdr.py delete mode 100644 reclaimer/mcc_hek/defs/objs/snd_.py delete mode 100644 reclaimer/mcc_hek/defs/objs/str_.py delete mode 100644 reclaimer/mcc_hek/defs/objs/tag.py delete mode 100644 reclaimer/mcc_hek/defs/objs/ustr.py delete mode 100644 reclaimer/mcc_hek/defs/objs/weap.py delete mode 100644 reclaimer/mcc_hek/defs/objs/wphi.py diff --git a/reclaimer/mcc_hek/defs/objs/__init__.py b/reclaimer/mcc_hek/defs/objs/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/reclaimer/mcc_hek/defs/objs/actr.py b/reclaimer/mcc_hek/defs/objs/actr.py deleted file mode 100644 index 762f03b1..00000000 --- a/reclaimer/mcc_hek/defs/objs/actr.py +++ /dev/null @@ -1,40 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from math import cos - -from reclaimer.hek.defs.objs.tag import HekTag - -class ActrTag(HekTag): - - def calc_internal_data(self): - HekTag.calc_internal_data(self) - perception = self.data.tagdata.perception - looking = self.data.tagdata.looking - - perception.inv_combat_perception_time = 0 - perception.inv_guard_perception_time = 0 - perception.inv_non_combat_perception_time = 0 - - if perception.combat_perception_time: - perception.inv_combat_perception_time = 1 / perception.combat_perception_time - - if perception.guard_perception_time: - perception.inv_guard_perception_time = 1 / perception.guard_perception_time - - if perception.non_combat_perception_time: - perception.inv_non_combat_perception_time = 1 / perception.non_combat_perception_time - - perception.inv_combat_perception_time /= 30 - perception.inv_guard_perception_time /= 30 - perception.inv_non_combat_perception_time /= 30 - - for i in range(2): - looking.cosine_maximum_aiming_deviation[i] = cos(looking.maximum_aiming_deviation[i]) - looking.cosine_maximum_looking_deviation[i] = cos(looking.maximum_looking_deviation[i]) diff --git a/reclaimer/mcc_hek/defs/objs/ant_.py b/reclaimer/mcc_hek/defs/objs/ant_.py deleted file mode 100644 index 6a343b98..00000000 --- a/reclaimer/mcc_hek/defs/objs/ant_.py +++ /dev/null @@ -1,31 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from math import sin, cos - -from reclaimer.hek.defs.objs.tag import HekTag - -class Ant_Tag(HekTag): - - def calc_internal_data(self): - HekTag.calc_internal_data(self) - - tagdata = self.data.tagdata - tagdata.length = 0 - for vertex in tagdata.vertices.STEPTREE: - sin_y = sin(vertex.angles.y) - sin_p = sin(vertex.angles.p) - cos_y = cos(vertex.angles.y) - cos_p = cos(vertex.angles.p) - - vertex.offset.x = vertex.length * sin_p * cos_y - vertex.offset.y = vertex.length * sin_y * sin_p - vertex.offset.z = vertex.length * cos_p - - tagdata.length += vertex.length diff --git a/reclaimer/mcc_hek/defs/objs/antr.py b/reclaimer/mcc_hek/defs/objs/antr.py deleted file mode 100644 index 62eaa987..00000000 --- a/reclaimer/mcc_hek/defs/objs/antr.py +++ /dev/null @@ -1,13 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from reclaimer.hek.defs.objs.tag import HekTag - -class AntrTag(HekTag): - jma_nodes = () diff --git a/reclaimer/mcc_hek/defs/objs/bipd.py b/reclaimer/mcc_hek/defs/objs/bipd.py deleted file mode 100644 index 3c4c2e6a..00000000 --- a/reclaimer/mcc_hek/defs/objs/bipd.py +++ /dev/null @@ -1,34 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from math import sin, cos - -from reclaimer.hek.defs.objs.obje import ObjeTag - -class BipdTag(ObjeTag): - - def calc_internal_data(self): - ObjeTag.calc_internal_data(self) - bipd_attrs = self.data.tagdata.bipd_attrs - movement = bipd_attrs.movement - physics = bipd_attrs.physics - - physics.cosine_stationary_turning_threshold = cos(bipd_attrs.stationary_turning_threshold) - - physics.cosine_maximum_slope_angle = cos(movement.maximum_slope_angle) - physics.neg_sine_downhill_falloff_angle = -sin(movement.downhill_falloff_angle) - physics.neg_sine_downhill_cutoff_angle = -sin(movement.downhill_cutoff_angle) - physics.sine_uphill_falloff_angle = sin(movement.uphill_falloff_angle) - physics.sine_uphill_cutoff_angle = sin(movement.uphill_cutoff_angle) - - physics.crouch_camera_velocity = 0 - if physics.crouch_camera_velocity: - physics.crouch_camera_velocity /= bipd_attrs.camera_collision_and_autoaim.crouch_transition_time - - physics.crouch_camera_velocity /= 30 diff --git a/reclaimer/mcc_hek/defs/objs/bitm.py b/reclaimer/mcc_hek/defs/objs/bitm.py deleted file mode 100644 index cc1b5b0f..00000000 --- a/reclaimer/mcc_hek/defs/objs/bitm.py +++ /dev/null @@ -1,483 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from array import array -from reclaimer.constants import TYPE_CUBEMAP, CUBEMAP_PADDING, BITMAP_PADDING,\ - FORMAT_NAME_MAP, TYPE_NAME_MAP, FORMAT_P8_BUMP -from reclaimer.bitmaps.p8_palette import HALO_P8_PALETTE -from reclaimer.hek.defs.objs.tag import HekTag - -try: - import arbytmap as ab - - if not hasattr(ab, "FORMAT_P8_BUMP"): - ab.FORMAT_P8_BUMP = "P8-BUMP" - - """ADD THE P8 FORMAT TO THE BITMAP CONVERTER""" - ab.register_format(format_id=ab.FORMAT_P8_BUMP, depths=(8,8,8,8)) -except (ImportError, AttributeError): - ab = None - - -class BitmTag(HekTag): - tex_infos = () - p8_palette = None - - def __init__(self, *args, **kwargs): - HekTag.__init__(self, *args, **kwargs) - self.p8_palette = HALO_P8_PALETTE - - def bitmap_count(self, new_value=None): - if new_value is None: - return self.data.tagdata.bitmaps.size - self.data.tagdata.bitmaps.size = new_value - - def bitmap_width(self, b_index=0, new_value=None): - if new_value is None: - return self.data.tagdata.bitmaps.bitmaps_array[b_index].width - self.data.tagdata.bitmaps.bitmaps_array[b_index].width = new_value - - def bitmap_height(self, b_index=0, new_value=None): - if new_value is None: - return self.data.tagdata.bitmaps.bitmaps_array[b_index].height - self.data.tagdata.bitmaps.bitmaps_array[b_index].height = new_value - - def bitmap_depth(self, b_index=0, new_value=None): - if new_value is None: - return self.data.tagdata.bitmaps.bitmaps_array[b_index].depth - self.data.tagdata.bitmaps.bitmaps_array[b_index].depth = new_value - - def bitmap_mipmaps_count(self, b_index=0, new_value=None): - if new_value is None: - return self.data.tagdata.bitmaps.bitmaps_array[b_index].mipmaps - self.data.tagdata.bitmaps.bitmaps_array[b_index].mipmaps = new_value - - def bitmap_type(self, b_index=0, new_value=None): - if new_value is None: - return self.data.tagdata.bitmaps.bitmaps_array[b_index].type.data - self.data.tagdata.bitmaps.bitmaps_array[b_index].type.data = new_value - - def bitmap_format(self, b_index=0, new_value=None): - if new_value is None: - return self.data.tagdata.bitmaps.bitmaps_array[b_index].format.data - self.data.tagdata.bitmaps.bitmaps_array[b_index].format.data = new_value - - def fix_top_format(self): - if len(self.data.tagdata.bitmaps.bitmaps_array) <= 0: - self.data.tagdata.format.data = "color_key_transparency" - - # Why can't get_name get the name of the current option? - pixel_format = self.data.tagdata.bitmaps.bitmaps_array[0].format.get_name( - self.data.tagdata.bitmaps.bitmaps_array[0].format.data) - - top_format = "color_key_transparency" - if pixel_format in ("a8", "y8", "ay8", "a8y8"): - top_format = "monochrome" - elif pixel_format in ("r5g6b5", "a1r5g5b5", "a4r4g4b4"): - top_format = "color_16bit" - elif pixel_format in ("x8r8g8b8", "a8r8g8b8", "p8_bump"): - top_format = "color_32bit" - elif pixel_format == "dxt1": - top_format = "color_key_transparency" - elif pixel_format == "dxt3": - top_format = "explicit_alpha" - elif pixel_format == "dxt5": - top_format = "interpolated_alpha" - - self.data.tagdata.format.set_to(top_format) - - def bitmap_width_height_depth(self, b_index=0, new_value=None): - bitmap = self.data.tagdata.bitmaps.bitmaps_array[b_index] - if new_value is None: - return(bitmap.width, bitmap.height, bitmap.depth) - bitmap.width, bitmap.height, bitmap.depth = ( - new_value[0], new_value[1], new_value[2]) - - def bitmap_flags(self, b_index=0, new_value=None): - if new_value is None: - return self.data.tagdata.bitmaps.bitmaps_array[b_index].flags - self.data.tagdata.bitmaps.bitmaps_array[b_index].flags = new_value - - def bitmap_base_address(self, b_index=0, new_value=None): - bitm = self.data.tagdata.bitmaps.bitmaps_array[b_index] - if new_value is None: - return(bitm.base_address) - bitm.base_address=new_value - - def bitmap_data_offset(self, b_index=0, new_value=None): - bitm = self.data.tagdata.bitmaps.bitmaps_array[b_index] - if new_value is None: - return(bitm.pixels_offset) - bitm.pixels_offset=new_value - - def registration_point_x(self, b_index=0, new_value=None): - bitm = self.data.tagdata.bitmaps.bitmaps_array[b_index] - if new_value is None: - return bitm.registration_point_x - bitm.registration_point_x = new_value - - def registration_point_y(self, b_index=0, new_value=None): - bitm = self.data.tagdata.bitmaps.bitmaps_array[b_index] - if new_value is None: - return bitm.registration_point_y - bitm.registration_point_y = new_value - - def registration_point_xy(self, b_index=0, new_value=None): - bitm = self.data.tagdata.bitmaps.bitmaps_array[b_index] - if new_value is None: - return(bitm.registration_point_x, - bitm.registration_point_y) - bitm.registration_point_x, bitm.registration_point_y = new_value[0],\ - new_value[1] - - @property - def is_xbox_bitmap(self): - # we only need to check the first bitmap - if not self.bitmap_count(): return False - return self.bitmap_base_address() == 1073751810 - - def is_power_of_2_bitmap(self, b_index=0): - if not self.bitmap_count(): return False - return self.bitmap_flags(b_index).power_of_2_dim - - def is_compressed_bitmap(self, b_index=0): - if not self.bitmap_count(): return False - return self.bitmap_flags(b_index).compressed - - def swizzled(self, b_index=0, new_flag=None): - if new_flag is None: - if not self.bitmap_count(): return False - return self.bitmap_flags(b_index).swizzled - if not self.bitmap_count(): return - self.bitmap_flags(b_index).swizzled = new_flag - - def color_plate_data_bytes_size(self, new_value=None): - if new_value is None: - return(self.data.tagdata.compressed_color_plate_data.size) - self.data.tagdata.compressed_color_plate_data.size = new_value - - def pixel_data_bytes_size(self, new_value=None): - if new_value is None: - return self.data.tagdata.processed_pixel_data.size - self.data.tagdata.processed_pixel_data.size = new_value - - def set_platform(self, saveasxbox): - '''changes different things to set the platform to either PC or Xbox''' - # read each of the bitmap blocks - for b_index in range(self.bitmap_count()): - bitmap = self.data.tagdata.bitmaps.bitmaps_array[b_index] - - bitmap.flags.prefer_low_detail = saveasxbox - - '''base_address is the ONLY discernable difference - between a bitmap made by arsenic from a PC map, and - a bitmap made by arsenic from an original XBOX map''' - if saveasxbox: - # change some miscellaneous variables - bitmap.pixels = 18 - bitmap.bitmap_data_pointer = 0xFFFFFFFF - bitmap.base_address = 1073751810 - else: - bitmap.base_address = 0 - - if not saveasxbox: - return - - # if Xbox, reset these structure variable's all to 0 - # since xbox doesn't like them being non-zero - tagdata = self.data.tagdata - tagdata.compressed_color_plate_data.flags.data = 0 - tagdata.processed_pixel_data.flags.data = 0 - for i in (2,3): - tagdata.compressed_color_plate_data[i] = 0 - tagdata.processed_pixel_data[i] = 0 - - for i in (1,2): - tagdata.sequences[i] = 0 - tagdata.bitmaps[i] = 0 - - # swap the order of the cubemap faces - # and mipmaps if saving to xbox format - self.change_sub_bitmap_ordering(saveasxbox) - - def change_sub_bitmap_ordering(self, saveasxbox): - '''Used to change the mipmap and cube face ordering. - On pc all highest resolution faces are first, then - the next highest resolution mipmap set. On xbox it's - all of a face's mipmaps before any of the other faces. - - DO NOT UNDER ANY CIRCUMSTANCES CALL THIS FUNCTION - IF PADDING HAS ALREADY BEEN ADDED TO A BITMAP''' - - raw_bitmap_data = self.data.tagdata.processed_pixel_data.data - - # loop over each of the bitmap blocks - for b_index in range(self.bitmap_count()): - if self.bitmap_type(b_index) == TYPE_CUBEMAP: - mipmap_count = self.bitmap_mipmaps_count(b_index) + 1 - tex_block = raw_bitmap_data[b_index] - - # this will be used to copy values from - template = tex_block.__copy__() - - # this is used to keep track of which index - # we're placing the new pixel array into - i = 0 - - '''since we also want to swap the second and third - cubemap faces we can do that easily like this. - xbox has the second and third cubemap faces swapped - with each other compared to pc. IDFKY''' - for face in (0, 2, 1, 3, 4, 5): - for mip in range(0, mipmap_count*6, 6): - '''get the block we want from the original - layout and place it in its new position''' - if saveasxbox: - tex_block[i] = template[mip + face] - else: - tex_block[mip + face] = template[i] - i += 1 - - def add_bitmap_padding(self, save_as_xbox): - '''This function will create and apply padding to each of the - bitmaps in the tag to make it XBOX compatible. This function will - also add the number of bytes of padding to the internal offsets''' - - """The offset of each bitmap's pixel data needs to be increased by - the padding of all the bitmaps before it. This variable will be - used for knowing the total amount of padding before each bitmap. - - DO NOT RUN IF A BITMAP ALREADY HAS PADDING.""" - total_data_size = 0 - if ab is None: - raise NotImplementedError( - "Arbytmap is not loaded. Cannot add padding.") - - for i in range(self.bitmap_count()): - sub_bitmap_count = 1 - if self.bitmap_type(i) == TYPE_CUBEMAP: - sub_bitmap_count = 6 - - pixel_data_block = self.data.tagdata.processed_pixel_data.data[i] - - # apply the offset to the tag - self.bitmap_data_offset(i, total_data_size) - - if save_as_xbox or self.bitmap_format(i) == FORMAT_P8_BUMP: - # calculate how much padding to add to the xbox bitmaps - bitmap_pad, cubemap_pad = self.get_padding_size(i) - - # if this bitmap has padding on each of the sub-bitmaps - if cubemap_pad: - mipmap_count = self.bitmap_mipmaps_count(i) + 1 - for j in range(0, 6*(mipmap_count + 1), mipmap_count + 1): - pad = bytearray(cubemap_pad) - if isinstance(pixel_data_block[0], array): - pad = array('B', pad) - pixel_data_block.insert(j + mipmap_count, pad) - - # add the main padding to the end of the bitmap block - pad = bytearray(bitmap_pad) - if isinstance(pixel_data_block[0], array): - pad = array('B', pad) - pixel_data_block.append(pad) - - # add the number of bytes this bitmap is to the - # total bytes so far(multiple by sub-bitmap count) - for pixel_data in pixel_data_block: - if isinstance(pixel_data, array): - total_data_size += len(pixel_data) * pixel_data.itemsize - else: - total_data_size += len(pixel_data) - - # update the total number of bytes of pixel data - # in the tag by all the padding that was added - self.pixel_data_bytes_size(total_data_size) - - def get_bitmap_size(self, b_index): - '''Given a bitmap index, this function will - calculate how many bytes the data takes up. - THIS FUNCTION WILL NOT TAKE INTO ACCOUNT THE NUMBER OF SUB-BITMAPS''' - if ab is None: - raise NotImplementedError( - "Arbytmap is not loaded. Cannot get bitmap size.") - - w, h, d, = self.bitmap_width_height_depth(b_index) - fmt = FORMAT_NAME_MAP[self.bitmap_format(b_index)] - - bytes_count = 0 - for mipmap in range(self.bitmap_mipmaps_count(b_index) + 1): - mw, mh, md = ab.get_mipmap_dimensions(w, h, d, mipmap) - if fmt == ab.FORMAT_P8_BUMP: - bytes_count += mw*mh*md - else: - bytes_count += ab.bitmap_io.get_pixel_bytes_size(fmt, mw, mh, md) - - return bytes_count - - def get_padding_size(self, b_index): - bytes_count = self.get_bitmap_size(b_index) - cubemap_pad = 0 - - if self.bitmap_type(b_index) == TYPE_CUBEMAP: - cubemap_pad = ((CUBEMAP_PADDING - (bytes_count % CUBEMAP_PADDING)) - % CUBEMAP_PADDING) - bytes_count = (bytes_count + cubemap_pad) * 6 - - bitmap_pad = (BITMAP_PADDING - - (bytes_count%BITMAP_PADDING)) % BITMAP_PADDING - - return bitmap_pad, cubemap_pad - - def sanitize_mipmap_counts(self): - '''Some original xbox bitmaps have fudged up mipmap counts - and cause issues. This function will scan through all a - bitmap's bitmaps and check that they fit within their - calculated pixel data bounds. This is done by checking if a - bitmap's calculated size is both within the side of the total - pixel data and less than the next bitmap's pixel data start''' - - bad_bitmap_index_list = [] - bitmap_count = self.bitmap_count() - - for i in range(bitmap_count): - # if this is the last bitmap - if i + 1 == bitmap_count: - # this is how many bytes of texture data there is total - max_size = self.pixel_data_bytes_size() - else: - # this is the start of the next bitmap's pixel data - max_size = self.bitmap_data_offset(i+1) - - while True: - mipmap_count = self.bitmap_mipmaps_count(i) - curr_size = self.get_bitmap_size(i) + self.bitmap_data_offset(i) - - if curr_size <= max_size: - break - - self.bitmap_mipmaps_count(i, mipmap_count - 1) - - # the mipmap count is zero and the bitmap still will - # not fit within the space provided. Something's wrong - if mipmap_count == 0: - bad_bitmap_index_list.append(i) - break - - return bad_bitmap_index_list - - def sanitize_bitmaps(self): - if ab is None: - raise NotImplementedError( - "Arbytmap is not loaded. Cannot sanitize bitmaps.") - tex_infos = self.tex_infos - - for i in range(self.bitmap_count()): - format = FORMAT_NAME_MAP[self.bitmap_format(i)] - flags = self.bitmap_flags(i) - old_w, old_h, _ = self.bitmap_width_height_depth(i) - - reg_point_x, reg_point_y = self.registration_point_xy(i) - texinfo = tex_infos[i] - - # set the flags to the new value - flags.palletized = (format == ab.FORMAT_P8_BUMP) - flags.compressed = (format in ab.COMPRESSED_FORMATS) - - self.bitmap_width_height_depth( - i, (texinfo["width"], texinfo["height"], texinfo["depth"])) - self.bitmap_mipmaps_count(i, texinfo["mipmap_count"]) - self.registration_point_xy(i, (texinfo["width"]*reg_point_x//old_w, - texinfo["height"]*reg_point_y//old_h)) - - def parse_bitmap_blocks(self): - '''converts the raw pixel data into arrays of pixel - data and replaces the raw data in the tag with them''' - if ab is None: - raise NotImplementedError( - "Arbytmap is not loaded. Cannot parse bitmaps.") - - pixel_data = self.data.tagdata.processed_pixel_data - rawdata = pixel_data.data - - tex_infos = self.tex_infos = [] - - # this is the block that will hold all of the bitmap blocks - root_tex_block = self.definition.subdefs['pixel_root'].build() - - is_xbox = self.is_xbox_bitmap - get_mip_dims = ab.get_mipmap_dimensions - bytes_to_array = ab.bitmap_io.bitmap_bytes_to_array - - # read the pixel data blocks for each bitmap - for i in range(self.bitmap_count()): - # since we need this information to read the bitmap we extract it - mw, mh, md, = self.bitmap_width_height_depth(i) - type = self.bitmap_type(i) - format = FORMAT_NAME_MAP[self.bitmap_format(i)] - mipmap_count = self.bitmap_mipmaps_count(i) + 1 - sub_bitmap_count = ab.SUB_BITMAP_COUNTS[TYPE_NAME_MAP[type]] - - # Get the offset of the pixel data for - # this bitmap within the raw pixel data - off = self.bitmap_data_offset(i) - - # this texture info is used in manipulating the texture data - tex_infos.append(dict( - width=mw, height=mh, depth=md, format=format, - mipmap_count=mipmap_count-1, sub_bitmap_count=sub_bitmap_count, - swizzled=self.swizzled(), texture_type=TYPE_NAME_MAP[type])) - - if format == ab.FORMAT_P8_BUMP: - tex_infos[-1]["palette"] = [ - self.p8_palette.p8_palette_32bit_packed]*mipmap_count - - # set it to packed since if we need to drop channels - # then it needs to be unpacked with channels dropped - tex_infos[-1]["palette_packed"] = True - tex_infos[-1]["indexing_size"] = 8 - - # this is the block that will hold each mipmap, - # texture slice, and cube face of the bitmap - root_tex_block.append() - tex_block = root_tex_block[-1] - - # xbox bitmaps are stored all mip level faces first, then - # the next mip level, whereas pc is the other way. Xbox - # bitmaps also have padding between each mipmap and bitmap. - dim0 = sub_bitmap_count if is_xbox else mipmap_count - dim1 = mipmap_count if is_xbox else sub_bitmap_count - for j in range(dim0): - if not is_xbox: w, h, d = get_mip_dims(mw, mh, md, j) - - for k in range(dim1): - if is_xbox: w, h, d = get_mip_dims(mw, mh, md, k) - - if format == ab.FORMAT_P8_BUMP: - pixel_count = w*h - tex_block.append(array('B', rawdata[off: off+pixel_count])) - off += pixel_count - continue - - off = bytes_to_array(rawdata, off, tex_block, format, w, h, d) - - # skip the xbox alignment padding to get to the next texture - if is_xbox: - tex_pad, sub_tex_pad = self.get_padding_size(i) - off += sub_tex_pad - if j + 1 == dim0: - off += tex_pad - - pixel_data.data = root_tex_block - # now that we've successfully built the bitmap - # blocks from the raw data we replace the raw data - if is_xbox: - # it's easier to work with bitmaps in one format so - # we'll switch the mipmaps from XBOX to PC ordering - self.change_sub_bitmap_ordering(False) diff --git a/reclaimer/mcc_hek/defs/objs/coll.py b/reclaimer/mcc_hek/defs/objs/coll.py deleted file mode 100644 index f829ac6c..00000000 --- a/reclaimer/mcc_hek/defs/objs/coll.py +++ /dev/null @@ -1,21 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from reclaimer.hek.defs.objs.tag import HekTag - -class CollTag(HekTag): - - def calc_internal_data(self): - HekTag.calc_internal_data(self) - - shield = self.data.tagdata.shield - shield.shield_recharge_rate = 0 - if shield.recharge_time: - shield.shield_recharge_rate = 1 / shield.recharge_time - shield.shield_recharge_rate /= 30 diff --git a/reclaimer/mcc_hek/defs/objs/ctrl.py b/reclaimer/mcc_hek/defs/objs/ctrl.py deleted file mode 100644 index 10fa9888..00000000 --- a/reclaimer/mcc_hek/defs/objs/ctrl.py +++ /dev/null @@ -1,17 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from reclaimer.hek.defs.objs.obje import ObjeTag -from reclaimer.hek.defs.objs.devi import DeviTag - -class CtrlTag(DeviTag, ObjeTag): - - def calc_internal_data(self): - ObjeTag.calc_internal_data(self) - DeviTag.calc_internal_data(self) diff --git a/reclaimer/mcc_hek/defs/objs/devi.py b/reclaimer/mcc_hek/defs/objs/devi.py deleted file mode 100644 index f5bf5acc..00000000 --- a/reclaimer/mcc_hek/defs/objs/devi.py +++ /dev/null @@ -1,49 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from math import cos - -from reclaimer.hek.defs.objs.tag import HekTag - -class DeviTag(HekTag): - - def calc_internal_data(self): - HekTag.calc_internal_data(self) - devi_attrs = self.data.tagdata.devi_attrs - - devi_attrs.inv_power_acceleration_time = 0 - devi_attrs.inv_power_transition_time = 0 - devi_attrs.inv_position_acceleration_time = 0 - devi_attrs.inv_position_transition_time = 0 - devi_attrs.inv_depowered_acceleration_time = 0 - devi_attrs.inv_depowered_transition_time = 0 - - if devi_attrs.power_acceleration_time: - devi_attrs.inv_power_acceleration_time = 1 / ( - 30 * devi_attrs.power_acceleration_time) - - if devi_attrs.power_transition_time: - devi_attrs.inv_power_transition_time = 1 / ( - 30 * devi_attrs.power_transition_time) - - if devi_attrs.depowered_position_acceleration_time: - devi_attrs.inv_depowered_acceleration_time = 1 / ( - 30 * devi_attrs.depowered_position_acceleration_time) - - if devi_attrs.depowered_position_transition_time: - devi_attrs.inv_depowered_transition_time = 1 / ( - 30 * devi_attrs.depowered_position_transition_time) - - if devi_attrs.position_acceleration_time: - devi_attrs.inv_position_acceleration_time = 1 / ( - 30 * devi_attrs.position_acceleration_time) - - if devi_attrs.position_transition_time: - devi_attrs.inv_position_transition_time = 1 / ( - 30 * devi_attrs.position_transition_time) diff --git a/reclaimer/mcc_hek/defs/objs/effe.py b/reclaimer/mcc_hek/defs/objs/effe.py deleted file mode 100644 index 84cee398..00000000 --- a/reclaimer/mcc_hek/defs/objs/effe.py +++ /dev/null @@ -1,36 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from reclaimer.hek.defs.objs.tag import HekTag -from reclaimer.util.matrices import euler_2d_to_vector_3d -#from reclaimer.common_descs import valid_objects - -class EffeTag(HekTag): - - def calc_internal_data(self): - HekTag.calc_internal_data(self) - - never_cull = False - for event in self.data.tagdata.events.STEPTREE: - for part in event.parts.STEPTREE: - if part.type.tag_class.enum_name == 'light': - never_cull = True - - part.effect_class = part.type.tag_class - - #TODO: There is no good way to do this right now - #object_types = valid_objects('b').desc[0]['NAME_MAP'].keys() - #if part.effect_class.enum_name in object_types: - # part.effect_class.enum_name = 'object' - - for particle in event.particles.STEPTREE: - particle.relative_direction_vector[:] = euler_2d_to_vector_3d( - *particle.relative_direction - ) - self.data.tagdata.flags.never_cull = never_cull diff --git a/reclaimer/mcc_hek/defs/objs/lens.py b/reclaimer/mcc_hek/defs/objs/lens.py deleted file mode 100644 index 5d9e6c41..00000000 --- a/reclaimer/mcc_hek/defs/objs/lens.py +++ /dev/null @@ -1,21 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from math import cos - -from reclaimer.hek.defs.objs.tag import HekTag - -class LensTag(HekTag): - - def calc_internal_data(self): - HekTag.calc_internal_data(self) - - tagdata = self.data.tagdata - tagdata.cosine_falloff_angle = cos(tagdata.falloff_angle) - tagdata.cosine_cutoff_angle = cos(tagdata.cutoff_angle) diff --git a/reclaimer/mcc_hek/defs/objs/lifi.py b/reclaimer/mcc_hek/defs/objs/lifi.py deleted file mode 100644 index 11105fd0..00000000 --- a/reclaimer/mcc_hek/defs/objs/lifi.py +++ /dev/null @@ -1,17 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from reclaimer.hek.defs.objs.obje import ObjeTag -from reclaimer.hek.defs.objs.devi import DeviTag - -class LifiTag(DeviTag, ObjeTag): - - def calc_internal_data(self): - ObjeTag.calc_internal_data(self) - DeviTag.calc_internal_data(self) diff --git a/reclaimer/mcc_hek/defs/objs/ligh.py b/reclaimer/mcc_hek/defs/objs/ligh.py deleted file mode 100644 index f11f3e87..00000000 --- a/reclaimer/mcc_hek/defs/objs/ligh.py +++ /dev/null @@ -1,22 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from math import sin, cos - -from reclaimer.hek.defs.objs.tag import HekTag - -class LighTag(HekTag): - - def calc_internal_data(self): - HekTag.calc_internal_data(self) - - shape = self.data.tagdata.shape - shape.cosine_falloff_angle = cos(shape.falloff_angle) - shape.cosine_cutoff_angle = cos(shape.cutoff_angle) - shape.sine_cutoff_angle = sin(shape.cutoff_angle) diff --git a/reclaimer/mcc_hek/defs/objs/mach.py b/reclaimer/mcc_hek/defs/objs/mach.py deleted file mode 100644 index 84e6a697..00000000 --- a/reclaimer/mcc_hek/defs/objs/mach.py +++ /dev/null @@ -1,19 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from reclaimer.hek.defs.objs.obje import ObjeTag -from reclaimer.hek.defs.objs.devi import DeviTag - -class MachTag(DeviTag, ObjeTag): - - def calc_internal_data(self): - ObjeTag.calc_internal_data(self) - DeviTag.calc_internal_data(self) - mach_attrs = self.data.tagdata.mach_attrs - mach_attrs.door_open_time_ticks = int(mach_attrs.door_open_time * 30) diff --git a/reclaimer/mcc_hek/defs/objs/mod2.py b/reclaimer/mcc_hek/defs/objs/mod2.py deleted file mode 100644 index c264ecdb..00000000 --- a/reclaimer/mcc_hek/defs/objs/mod2.py +++ /dev/null @@ -1,96 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from reclaimer.hek.defs.objs.mode import ModeTag - -class Mod2Tag(ModeTag): - - def globalize_local_markers(self): - tagdata = self.data.tagdata - all_global_markers = tagdata.markers.STEPTREE - all_global_markers_by_name = {b.name: b.marker_instances.STEPTREE - for b in all_global_markers} - - for i in range(len(tagdata.regions.STEPTREE)): - region = tagdata.regions.STEPTREE[i] - for j in range(len(region.permutations.STEPTREE)): - perm = region.permutations.STEPTREE[j] - - for k in range(len(perm.local_markers.STEPTREE)): - local_marker = perm.local_markers.STEPTREE[k] - global_markers = all_global_markers_by_name.get( - local_marker.name, None) - - if global_markers is None or len(global_markers) >= 32: - all_global_markers.append() - all_global_markers[-1].name = local_marker.name - global_markers = all_global_markers[-1].marker_instances.STEPTREE - all_global_markers_by_name[local_marker.name] = global_markers - - global_markers.append() - global_marker = global_markers[-1] - - global_marker.region_index = i - global_marker.permutation_index = j - global_marker.node_index = local_marker.node_index - global_marker.rotation[:] = local_marker.rotation[:] - global_marker.translation[:] = local_marker.translation[:] - - del perm.local_markers.STEPTREE[:] - - # sort the markers how Halo's picky ass wants them - name_map = {all_global_markers[i].name: i - for i in range(len(all_global_markers))} - all_global_markers[:] = list(all_global_markers[name_map[name]] - for name in sorted(name_map)) - - def delocalize_part_nodes(self, geometry_index, part_index): - part = self.data.tagdata.geometries.STEPTREE\ - [geometry_index].parts.STEPTREE[part_index] - local_nodes = part.local_nodes[: part.local_node_count] - - delocalize_compressed_verts(part.compressed_vertices.STEPTREE, - local_nodes) - delocalize_uncompressed_verts(part.uncompressed_vertices.STEPTREE, - local_nodes) - - part.flags.ZONER = False - part.local_node_count = 0 - - -def delocalize_compressed_verts(comp_verts, local_nodes): - '''TODO: Update this function to also work on parsed vert data.''' - local_node_ct = len(local_nodes) * 3 - # 28 is the offset to the first verts first node index - for i in range(28, len(comp_verts), 32): - if comp_verts[i] < local_node_ct: - comp_verts[i] = local_nodes[comp_verts[i] // 3] * 3 - - i += 1 - if comp_verts[i] < local_node_ct: - comp_verts[i] = local_nodes[comp_verts[i] // 3] * 3 - - -def delocalize_uncompressed_verts(uncomp_verts, local_nodes): - '''TODO: Update this function to also work on parsed vert data.''' - local_node_ct = len(local_nodes) - # 57 is the offset to the least-significant half of - # the first verts first node index(in big endian). - for i in range(57, len(uncomp_verts), 68): - # literally no reason to use 2 bytes for the node index since - # a max of 63 nodes can be used, so we'll only edit the least - # significant byte of the node indices and make it absolute. - # if the value is 0xFF, the other byte should be 0xFF as well, - # meaning the node is -1, which should stay as -1(no node). - if uncomp_verts[i] < local_node_ct: - uncomp_verts[i] = local_nodes[uncomp_verts[i]] - - i += 2 - if uncomp_verts[i] < local_node_ct: - uncomp_verts[i] = local_nodes[uncomp_verts[i]] diff --git a/reclaimer/mcc_hek/defs/objs/mode.py b/reclaimer/mcc_hek/defs/objs/mode.py deleted file mode 100644 index a7d98cf6..00000000 --- a/reclaimer/mcc_hek/defs/objs/mode.py +++ /dev/null @@ -1,124 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from math import sqrt -from struct import unpack, pack_into -from types import MethodType - -from reclaimer.hek.defs.objs.tag import HekTag -from reclaimer.util.compression import compress_normal32, decompress_normal32 -from reclaimer.util.matrices import quaternion_to_matrix, Matrix - -# TODO: Make calc_internal_data recalculate the lod nodes, and remove that -# same function from model.model_compilation.compile_gbxmodel and replace -# it with a call to calc_internal_data. lod nodes are recalculated when -# tags are compiled into maps, but the functionality should still be here. -class ModeTag(HekTag): - - def calc_internal_data(self): - ''' - For each node, this method recalculates the rotation matrix - from the quaternion, and the translation to the root bone. - ''' - HekTag.calc_internal_data(self) - - nodes = self.data.tagdata.nodes.STEPTREE - for node in nodes: - rotation = quaternion_to_matrix(*node.rotation) - trans = Matrix([node.translation])*-1 - parent = None - - # add the parents translation to this ones - if node.parent_node > -1: - trans += Matrix([nodes[node.parent_node].translation_to_root]) - - # rotate the trans_to_root by this node's rotation - trans *= rotation - - # combine this nodes rotation with its parents rotation - if parent is not None: - this_trans = node.translation - node.distance_from_parent = sqrt( - this_trans.x**2 + this_trans.y**2 + this_trans.z**2) - - parent_rot = Matrix([parent.rot_jj_kk, - parent.rot_kk_ii, - parent.rot_ii_jj]) - rotation *= parent_rot - - # apply the changes to the node - node.translation_to_root[:] = trans[0][:] - node.rot_jj_kk[:] = rotation[0] - node.rot_kk_ii[:] = rotation[1] - node.rot_ii_jj[:] = rotation[2] - - def compress_part_verts(self, geometry_index, part_index): - part = self.data.tagdata.geometries.STEPTREE\ - [geometry_index].parts.STEPTREE[part_index] - uncomp_verts_reflexive = part.uncompressed_vertices - comp_verts_reflexive = part.compressed_vertices - - comp_norm = compress_normal32 - unpack_vert = MethodType(unpack, ">11f2hf") - pack_vert_into = MethodType(pack_into, ">12s3I2h2bh") - - comp_verts = bytearray(b'\x00' * 32 * uncomp_verts_reflexive.size) - uncomp_verts = uncomp_verts_reflexive.STEPTREE - - in_off = out_off = 0 - # compress each of the verts and write them to the buffer - for i in range(uncomp_verts_reflexive.size): - ni, nj, nk, bi, bj, bk, ti, tj, tk,\ - u, v, ni_0, ni_1, nw = unpack_vert( - uncomp_verts[in_off + 12: in_off + 64]) - - # write the compressed data - pack_vert_into( - comp_verts, out_off, - uncomp_verts[in_off: in_off + 12], - comp_norm(ni, nj, nk), - comp_norm(bi, bj, bk), - comp_norm(ti, tj, tk), - int(max(0, min(1, u))*32767.5), - int(max(0, min(1, v))*32767.5), - ni_0*3, ni_1*3, int(max(0, min(1, nw))*32767.5)) - in_off += 68 - out_off += 32 - - comp_verts_reflexive.STEPTREE = comp_verts - - def decompress_part_verts(self, geometry_index, part_index): - part = self.data.tagdata.geometries.STEPTREE\ - [geometry_index].parts.STEPTREE[part_index] - uncomp_verts_reflexive = part.uncompressed_vertices - comp_verts_reflexive = part.compressed_vertices - - decomp_norm = decompress_normal32 - unpack_vert = MethodType(unpack, ">3I2h2bh") - pack_vert_into = MethodType(pack_into, ">12s11f2h2f") - - uncomp_verts = bytearray(b'\x00' * 68 * comp_verts_reflexive.size) - comp_verts = comp_verts_reflexive.STEPTREE - - in_off = out_off = 0 - # uncompress each of the verts and write them to the buffer - for i in range(comp_verts_reflexive.size): - n, b, t, u, v, ni_0, ni_1, nw = unpack_vert( - comp_verts[in_off + 12: in_off + 32]) - # write the uncompressed data - pack_vert_into( - uncomp_verts, out_off, - comp_verts[in_off: in_off + 12], - *decomp_norm(n), *decomp_norm(b), *decomp_norm(t), - u/32767.5, v/32767.5, ni_0 // 3, ni_1 // 3, - nw/32767.5, 1.0 - nw/32767.5) - in_off += 32 - out_off += 68 - - uncomp_verts_reflexive.STEPTREE = uncomp_verts diff --git a/reclaimer/mcc_hek/defs/objs/obje.py b/reclaimer/mcc_hek/defs/objs/obje.py deleted file mode 100644 index 0adcac71..00000000 --- a/reclaimer/mcc_hek/defs/objs/obje.py +++ /dev/null @@ -1,52 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -import os - -from reclaimer.hek.defs.objs.tag import HekTag - -class ObjeTag(HekTag): - - def calc_internal_data(self): - HekTag.calc_internal_data(self) - - full_class_name = self.data.blam_header.tag_class.enum_name - - self.ext = '.' + full_class_name - self.filepath = os.path.splitext(str(self.filepath))[0] + self.ext - - object_type = self.data.tagdata.obje_attrs.object_type - if full_class_name == "object": - object_type.data = -1 - elif full_class_name == "biped": - object_type.data = 0 - elif full_class_name == "vehicle": - object_type.data = 1 - elif full_class_name == "weapon": - object_type.data = 2 - elif full_class_name == "equipment": - object_type.data = 3 - elif full_class_name == "garbage": - object_type.data = 4 - elif full_class_name == "projectile": - object_type.data = 5 - elif full_class_name == "scenery": - object_type.data = 6 - elif full_class_name == "device_machine": - object_type.data = 7 - elif full_class_name == "device_control": - object_type.data = 8 - elif full_class_name == "device_light_fixture": - object_type.data = 9 - elif full_class_name == "placeholder": - object_type.data = 10 - elif full_class_name == "sound_scenery": - object_type.data = 11 - else: - raise ValueError("Unknown object type '%s'" % full_class_name) diff --git a/reclaimer/mcc_hek/defs/objs/phys.py b/reclaimer/mcc_hek/defs/objs/phys.py deleted file mode 100644 index dd64cd8f..00000000 --- a/reclaimer/mcc_hek/defs/objs/phys.py +++ /dev/null @@ -1,151 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from math import log - -from reclaimer.hek.defs.objs.tag import HekTag -from reclaimer.util.matrices import Matrix - - -class PhysTag(HekTag): - - def calc_masses(self): - data = self.data.tagdata - mass_points = data.mass_points.STEPTREE - mass_center = [0,0,0] - - if not len(mass_points): - data.center_of_mass[:] = mass_center - return - - total_mass = data.mass - total_rel_mass = sum([mp.relative_mass for mp in mass_points]) - - for mp in mass_points: - rel_mass = mp.relative_mass - position = mp.position - - mp.mass = total_mass*(rel_mass/total_rel_mass) - mass_center[0] += position[0]*mp.mass - mass_center[1] += position[1]*mp.mass - mass_center[2] += position[2]*mp.mass - - mass_center[0] /= total_mass - mass_center[1] /= total_mass - mass_center[2] /= total_mass - - data.center_of_mass[:] = mass_center - - def calc_densities(self): - data = self.data.tagdata - mass_points = data.mass_points.STEPTREE - - if not len(mass_points): - return - - total_mass = data.mass - total_density = data.density - - densities = [mp.relative_density for mp in - mass_points if mp.relative_density] - masses = [mp.relative_mass for mp in - mass_points if mp.relative_mass] - if not densities or not masses: - for mp in mass_points: - mp.density = 0 - return - - total_rel_mass = sum(mp.relative_mass for mp in mass_points) - average_rel_mass = total_rel_mass / len(mass_points) - # total_density total_density - # density_scale = ------------------ = ---------------- - # avg(mass/density) avg( m^3 kg ) - # ( --- * -- ) - # ( kg 1 ) - den_scale = total_density / len(mass_points) - den_scale *= sum(mp.relative_mass / mp.relative_density - for mp in mass_points if mp.relative_density) - mass_scale = 1 / average_rel_mass - - for mp in mass_points: - mp.density = den_scale * mass_scale * mp.relative_density - - def calc_intertia_matrices(self): - data = self.data.tagdata - scale = data.moment_scale - matrices = data.inertia_matrices.STEPTREE - com = data.center_of_mass - mass_points = data.mass_points.STEPTREE - - # make sure the matrix array is only 2 long - matrices.extend(2 - len(matrices)) - del matrices[2:] - - reg = matrices.regular - inv = matrices.inverse - - reg_yy_zz, reg_zz_xx, reg_xx_yy = reg.yy_zz, reg.zz_xx, reg.xx_yy - xx = yy = zz = float('1e-30') # prevent division by 0 - neg_zx = neg_xy = neg_yz = 0 - - # calculate the moments for each mass point and add them up - for mp in mass_points: - pos = mp.position - - dist_xx = (com[1] - pos[1])**2 + (com[2] - pos[2])**2 - dist_yy = (com[0] - pos[0])**2 + (com[2] - pos[2])**2 - dist_zz = (com[0] - pos[0])**2 + (com[1] - pos[1])**2 - - dist_zx = (com[0] - pos[0])*(com[2] - pos[2]) - dist_xy = (com[0] - pos[0])*(com[1] - pos[1]) - dist_yz = (com[1] - pos[1])*(com[2] - pos[2]) - - if mp.radius > 0: - radius_term = 4*pow(10, (2*log(mp.radius, 10) - 1)) - else: - radius_term = 0 - - xx += (dist_xx + radius_term) * mp.mass - yy += (dist_yy + radius_term) * mp.mass - zz += (dist_zz + radius_term) * mp.mass - neg_zx -= dist_zx * mp.mass - neg_xy -= dist_xy * mp.mass - neg_yz -= dist_yz * mp.mass - - xx, yy, zz = xx*scale, yy*scale, zz*scale - neg_zx, neg_xy, neg_yz = neg_zx*scale, neg_xy*scale, neg_yz*scale - - # place the calculated values into the matrix - reg_yy_zz[:] = xx, neg_xy, neg_zx - reg_zz_xx[:] = neg_xy, yy, neg_yz - reg_xx_yy[:] = neg_zx, neg_yz, zz - - # calculate the inverse inertia matrix - regular = Matrix((reg_yy_zz, reg_zz_xx, reg_xx_yy)) - try: - inverse = regular.inverse - except ZeroDivisionError: - inverse = Matrix((1, 0, 0), (0, 1, 0), (0, 0, 1)) - print("Could not calculate inertia matrix inverse.") - - # place the inverse matrix into the tag - inv.yy_zz[:] = inverse[0][:] - inv.zz_xx[:] = inverse[1][:] - inv.xx_yy[:] = inverse[2][:] - - # copy the xx, yy, and zz moments form the matrix into the tag body - data.xx_moment = reg_yy_zz[0] - data.yy_moment = reg_zz_xx[1] - data.zz_moment = reg_xx_yy[2] - - def calc_internal_data(self): - HekTag.calc_internal_data(self) - self.calc_masses() - self.calc_densities() - self.calc_intertia_matrices() diff --git a/reclaimer/mcc_hek/defs/objs/pphy.py b/reclaimer/mcc_hek/defs/objs/pphy.py deleted file mode 100644 index 859bbaf9..00000000 --- a/reclaimer/mcc_hek/defs/objs/pphy.py +++ /dev/null @@ -1,31 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from reclaimer.hek.defs.objs.tag import HekTag - -class PphyTag(HekTag): - - def calc_internal_data(self): - HekTag.calc_internal_data(self) - tagdata = self.data.tagdata - - d = tagdata.density - tagdata.scaled_density = d * 355840 / 3 - - try: - tagdata.water_gravity_scale = -((d - 1) / (d + 1)) - except ZeroDivisionError: - tagdata.water_gravity_scale = float("-inf") - - # 0.0011 is the density of air relative to water - try: - tagdata.air_gravity_scale = -((d / 0.0011 - 1) / - (d / 0.0011 + 1)) - except ZeroDivisionError: - tagdata.air_gravity_scale = float("-inf") diff --git a/reclaimer/mcc_hek/defs/objs/sbsp.py b/reclaimer/mcc_hek/defs/objs/sbsp.py deleted file mode 100644 index 863c2524..00000000 --- a/reclaimer/mcc_hek/defs/objs/sbsp.py +++ /dev/null @@ -1,64 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from reclaimer.hek.defs.objs.tag import HekTag - - -class SbspTag(HekTag): - @staticmethod - def point_in_front_of_plane(plane, x, y, z): - ''' - Takes a plane ex: - ```py - collision_bsp = self.data.tagdata.collision_bsp.STEPTREE[0] - collision_bsp.planes.STEPTREE[i] - ``` - And dots the plane and coordinates and compares against d to - see if the xyz are in front of it. - ''' - return (x*plane.i + y*plane.j + z*plane.k) >= plane.d - - def get_leaf_index_of_point(self, x, y, z): - ''' - Returns the leaf node the point is in in the bsp. - If point is not in the bsp, returns None. - ''' - if len(self.data.tagdata.collision_bsp.STEPTREE) < 1: - raise ValueError("No collision_bsp structure found in sbsp tag.") - - collision_bsp = self.data.tagdata.collision_bsp.STEPTREE[0] - bsp3d_nodes = collision_bsp.bsp3d_nodes.STEPTREE - bsp_planes = collision_bsp.planes.STEPTREE - if not bsp3d_nodes: - return None - - node_index = 0 - # Go through the tree until we get a negative number (leaf or null) - while node_index >= 0: - node = bsp3d_nodes[node_index] - if self.point_in_front_of_plane(bsp_planes[node.plane], x, y, z): - node_index = node.front_child - else: - node_index = node.back_child - - # -1 = null (not found); otherwise it's a leaf (found) - return None if node_index == -1 else node_index + 0x80000000 - - def get_cluster_index_of_point(self, x, y, z): - ''' - Returns the cluster the point is in in the bsp. - If point is not in the bsp, returns None. - ''' - leaf = self.get_leaf_index_of_point(x, y, z) - leaves = self.data.tagdata.leaves.STEPTREE - return leaves[leaf].cluster if leaf in range(len(leaves)) else None - - def is_point_in_bsp(self, x, y, z): - '''Returns if given point in 3d space is inside of this BSP''' - return self.get_leaf_index_of_point(x, y, z) is not None diff --git a/reclaimer/mcc_hek/defs/objs/shdr.py b/reclaimer/mcc_hek/defs/objs/shdr.py deleted file mode 100644 index 290b73fb..00000000 --- a/reclaimer/mcc_hek/defs/objs/shdr.py +++ /dev/null @@ -1,46 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -import os - -from reclaimer.hek.defs.objs.tag import HekTag - -class ShdrTag(HekTag): - - def calc_internal_data(self): - HekTag.calc_internal_data(self) - - full_class_name = self.data.blam_header.tag_class.enum_name - - self.ext = '.' + full_class_name - self.filepath = os.path.splitext(str(self.filepath))[0] + self.ext - - shader_type = self.data.tagdata.shdr_attrs.shader_type - if full_class_name == "shader": - shader_type.data = -1 - elif full_class_name == "shader_environment": - shader_type.data = 3 - elif full_class_name == "shader_model": - shader_type.data = 4 - elif full_class_name == "shader_transparent_generic": - shader_type.data = 5 - elif full_class_name == "shader_transparent_chicago": - shader_type.data = 6 - elif full_class_name == "shader_transparent_chicago_extended": - shader_type.data = 7 - elif full_class_name == "shader_transparent_water": - shader_type.data = 8 - elif full_class_name == "shader_transparent_glass": - shader_type.data = 9 - elif full_class_name == "shader_transparent_meter": - shader_type.data = 10 - elif full_class_name == "shader_transparent_plasma": - shader_type.data = 11 - else: - raise ValueError("Unknown shader type '%s'" % full_class_name) diff --git a/reclaimer/mcc_hek/defs/objs/snd_.py b/reclaimer/mcc_hek/defs/objs/snd_.py deleted file mode 100644 index 1dd5a730..00000000 --- a/reclaimer/mcc_hek/defs/objs/snd_.py +++ /dev/null @@ -1,20 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from reclaimer.hek.defs.objs.tag import HekTag - -class Snd_Tag(HekTag): - - def calc_internal_data(self): - HekTag.calc_internal_data(self) - - for pitch_range in self.data.tagdata.pitch_ranges.STEPTREE: - pitch_range.playback_rate = 1 - if pitch_range.natural_pitch: - pitch_range.playback_rate = 1 / pitch_range.natural_pitch diff --git a/reclaimer/mcc_hek/defs/objs/str_.py b/reclaimer/mcc_hek/defs/objs/str_.py deleted file mode 100644 index 17582773..00000000 --- a/reclaimer/mcc_hek/defs/objs/str_.py +++ /dev/null @@ -1,21 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from reclaimer.hek.defs.objs.tag import HekTag -from reclaimer.util import convert_newlines_to_windows - -class Str_Tag(HekTag): - - def calc_internal_data(self): - HekTag.calc_internal_data(self) - strings = self.data.tagdata.strings.STEPTREE - - for i in range(len(strings)): - # Replace all newlines with \r\n. - strings[i].data = convert_newlines_to_windows(strings[i].data) diff --git a/reclaimer/mcc_hek/defs/objs/tag.py b/reclaimer/mcc_hek/defs/objs/tag.py deleted file mode 100644 index 889037c6..00000000 --- a/reclaimer/mcc_hek/defs/objs/tag.py +++ /dev/null @@ -1,70 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# -from pathlib import Path - -from supyr_struct.defs.constants import DEFAULT -from supyr_struct.tag import Tag - -from reclaimer.util import calc_halo_crc32 - -class HekTag(Tag): - def __init__(self, **kwargs): - self.calc_pointers = False - Tag.__init__(self, **kwargs) - - def serialize(self, **kwargs): - ''' - Overload of the supyr serialization function that retroactively adds - a CRC to the tag. - ''' - head = self.data.blam_header - filepath = kwargs.get('filepath', self.filepath) - buffer = kwargs.get('buffer', None) - - # Run the normal serialization. - result = Tag.serialize(self, **kwargs) - - # If there is neither a buffer or filepath just return the result. - if (buffer is None) and (not filepath): - return result - - # Prefer to use the buffer as that is how Tag.serialize does it. - f = buffer - if buffer is None: - f = Path(filepath).open('rb+') - - # Calculate the crc from after the header to the end. - crc = calc_halo_crc32(f, offset=head.get_desc('SIZE')) - # Write the crc to the offset of the checksum value in the header. - # The way we retrieve this offset from supyr is insane. - attr_index = head.get_desc('NAME_MAP')['checksum'] - f.seek(head.get_desc('ATTR_OFFS')[attr_index]) - f.write(crc.to_bytes(4, byteorder='big', signed=False)) - # Flush the stream. - f.flush() - # Only close if it is a file. Because the only case where we own - # this buffer is if there was no buffer kwarg. - if not buffer: - f.close() - - # Update the tag object so it won't have to be deserialized again. - head.checksum = crc - return result - - def calc_internal_data(self): - # recalculate the header data - head = self.data.blam_header - - head.tag_class.data = head.tag_class.get_desc(DEFAULT) - head.flags.edited_with_mozz = True - head.header_size = head.get_desc(DEFAULT, 'header_size') - head.version = head.get_desc(DEFAULT, 'version') - head.integrity0 = head.get_desc(DEFAULT, 'integrity0') - head.integrity1 = head.get_desc(DEFAULT, 'integrity1') - head.engine_id.data = head.engine_id.get_desc(DEFAULT) diff --git a/reclaimer/mcc_hek/defs/objs/ustr.py b/reclaimer/mcc_hek/defs/objs/ustr.py deleted file mode 100644 index c9cc0a65..00000000 --- a/reclaimer/mcc_hek/defs/objs/ustr.py +++ /dev/null @@ -1,13 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from reclaimer.hek.defs.objs.str_ import Str_Tag - -class UstrTag(Str_Tag): - pass diff --git a/reclaimer/mcc_hek/defs/objs/weap.py b/reclaimer/mcc_hek/defs/objs/weap.py deleted file mode 100644 index 9572dca4..00000000 --- a/reclaimer/mcc_hek/defs/objs/weap.py +++ /dev/null @@ -1,39 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from reclaimer.hek.defs.objs.obje import ObjeTag - -class WeapTag(ObjeTag): - - def calc_internal_data(self): - ObjeTag.calc_internal_data(self) - for trigger in self.data.tagdata.weap_attrs.triggers.STEPTREE: - firing = trigger.firing - misc = trigger.misc - rates = trigger.misc_rates - for i in range(len(rates)): - rates[i] = 0 - - if misc.ejection_port_recovery_time: - rates.ejection_port_recovery_rate = 1 / misc.ejection_port_recovery_time - if misc.illumination_recovery_time: - rates.illumination_recovery_rate = 1 / misc.illumination_recovery_time - - if firing.acceleration_time: - rates.acceleration_rate = 1 / firing.acceleration_time - if firing.deceleration_time: - rates.deceleration_rate = 1 / firing.deceleration_time - - if firing.error_acceleration_time: - rates.error_acceleration_rate = 1 / firing.error_acceleration_time - if firing.error_deceleration_time: - rates.error_deceleration_rate = 1 / firing.error_deceleration_time - - for i in range(len(rates)): - rates[i] /= 30 diff --git a/reclaimer/mcc_hek/defs/objs/wphi.py b/reclaimer/mcc_hek/defs/objs/wphi.py deleted file mode 100644 index d759eaa5..00000000 --- a/reclaimer/mcc_hek/defs/objs/wphi.py +++ /dev/null @@ -1,20 +0,0 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# - -from reclaimer.hek.defs.objs.tag import HekTag - -class WphiTag(HekTag): - - def calc_internal_data(self): - HekTag.calc_internal_data(self) - tagdata = self.data.tagdata - tagdata.crosshair_types.data = 0 - - for crosshair in tagdata.crosshairs.STEPTREE: - tagdata.crosshair_types.data |= 1 << crosshair.crosshair_type.data From 60b8d84fbed1614c746931b6b41f1b5928cf6870 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Tue, 16 Jan 2024 17:19:00 -0600 Subject: [PATCH 08/51] Fixes for Stubbs --- reclaimer/__init__.py | 4 +- reclaimer/hek/defs/mod2.py | 46 ++++---- reclaimer/hek/defs/mode.py | 46 ++++---- reclaimer/meta/wrappers/byteswapping.py | 2 +- reclaimer/meta/wrappers/halo1_map.py | 32 +++--- reclaimer/meta/wrappers/stubbs_map.py | 39 ++++--- reclaimer/model/model_decompilation.py | 104 ++++++++--------- reclaimer/stubbs/defs/mode.py | 143 ++++++------------------ 8 files changed, 183 insertions(+), 233 deletions(-) diff --git a/reclaimer/__init__.py b/reclaimer/__init__.py index 23639820..3feb1113 100644 --- a/reclaimer/__init__.py +++ b/reclaimer/__init__.py @@ -12,8 +12,8 @@ # ############## __author__ = "Sigmmma" # YYYY.MM.DD -__date__ = "2021.03.23" -__version__ = (2, 11, 2) +__date__ = "2024.01.16" +__version__ = (2, 12, 0) __website__ = "https://github.com/Sigmmma/reclaimer" __all__ = ( "animation", "bitmaps", "h2", "h3", "halo_script", "hek", "meta", "misc", diff --git a/reclaimer/hek/defs/mod2.py b/reclaimer/hek/defs/mod2.py index a040735a..d672b203 100644 --- a/reclaimer/hek/defs/mod2.py +++ b/reclaimer/hek/defs/mod2.py @@ -150,6 +150,29 @@ def get(): reflexive("local_markers", local_marker, 32, DYN_NAME_PATH=".name"), SIZE=88 ) + +model_meta_info = Struct("model_meta_info", + UEnum16("index_type", # name is a guess. always 1? + ("uncompressed", 1), + ), + Pad(2), + UInt32("index_count"), + # THESE TWO VALUES ARE DIFFERENT THAN ON XBOX IT SEEMS + UInt32("indices_magic_offset"), + UInt32("indices_offset"), + + UEnum16("vertex_type", # name is a guess + ("uncompressed", 4), + ("compressed", 5), + ), + Pad(2), + UInt32("vertex_count"), + Pad(4), # always 0? + # THESE TWO VALUES ARE DIFFERENT THAN ON XBOX IT SEEMS + UInt32("vertices_magic_offset"), + UInt32("vertices_offset"), + VISIBLE=False, SIZE=36 + ) part = Struct('part', Bool32('flags', @@ -175,28 +198,7 @@ def get(): reflexive("compressed_vertices", fast_compressed_vertex, 32767), reflexive("triangles", triangle, 32767), #Pad(36), - Struct("model_meta_info", - UEnum16("index_type", # name is a guess. always 1? - ("uncompressed", 1), - ), - Pad(2), - UInt32("index_count"), - # THESE TWO VALUES ARE DIFFERENT THAN ON XBOX IT SEEMS - UInt32("indices_magic_offset"), - UInt32("indices_offset"), - - UEnum16("vertex_type", # name is a guess - ("uncompressed", 4), - ("compressed", 5), - ), - Pad(2), - UInt32("vertex_count"), - Pad(4), # always 0? - # THESE TWO VALUES ARE DIFFERENT THAN ON XBOX IT SEEMS - UInt32("vertices_magic_offset"), - UInt32("vertices_offset"), - VISIBLE=False, SIZE=36 - ), + model_meta_info, Pad(3), SInt8('local_node_count', MIN=0, MAX=22), diff --git a/reclaimer/hek/defs/mode.py b/reclaimer/hek/defs/mode.py index 76f96292..f6b26a6d 100644 --- a/reclaimer/hek/defs/mode.py +++ b/reclaimer/hek/defs/mode.py @@ -46,6 +46,28 @@ def get(): SIZE=88 ) +# dip == double-indirect pointer +dip_model_meta_info = Struct("model_meta_info", + UEnum16("index_type", # name is a guess. always 1? + ("uncompressed", 1), + ), + Pad(2), + UInt32("index_count"), + UInt32("indices_offset"), + UInt32("indices_reflexive_offset"), + + UEnum16("vertex_type", # name is a guess + ("uncompressed", 4), + ("compressed", 5), + ), + Pad(2), + UInt32("vertex_count"), + Pad(4), # always 0? + UInt32("vertices_offset"), + UInt32("vertices_reflexive_offset"), + VISIBLE=False, SIZE=36 + ) + part = Struct('part', Bool32('flags', 'stripped', @@ -70,26 +92,7 @@ def get(): reflexive("triangles", triangle, 65535), #Pad(36), - Struct("model_meta_info", - UEnum16("index_type", # name is a guess. always 1? - ("uncompressed", 1), - ), - Pad(2), - UInt32("index_count"), - UInt32("indices_offset"), - UInt32("indices_reflexive_offset"), - - UEnum16("vertex_type", # name is a guess - ("uncompressed", 4), - ("compressed", 5), - ), - Pad(2), - UInt32("vertex_count"), - Pad(4), # always 0? - UInt32("vertices_offset"), - UInt32("vertices_reflexive_offset"), - VISIBLE=False, SIZE=36 - ), + dip_model_meta_info, SIZE=104 ) @@ -142,7 +145,8 @@ def get(): Float('base_map_u_scale'), Float('base_map_v_scale'), - Pad(116), + Pad(104), + Pad(12), # replaced with unknown reflexive in stubbs reflexive("markers", marker, 256, DYN_NAME_PATH=".name"), reflexive("nodes", node, 64, DYN_NAME_PATH=".name"), diff --git a/reclaimer/meta/wrappers/byteswapping.py b/reclaimer/meta/wrappers/byteswapping.py index 2f10c82b..89b30c72 100644 --- a/reclaimer/meta/wrappers/byteswapping.py +++ b/reclaimer/meta/wrappers/byteswapping.py @@ -174,7 +174,7 @@ def byteswap_comp_verts(verts_block): byteswap_struct_array( original, swapped, 32, None, 0, - two_byte_offs=(24, 26, 28, 30), + two_byte_offs=(24, 26, 30), four_byte_offs=(0, 4, 8, 12, 16, 20) ) diff --git a/reclaimer/meta/wrappers/halo1_map.py b/reclaimer/meta/wrappers/halo1_map.py index 104313c8..eaab8d7c 100644 --- a/reclaimer/meta/wrappers/halo1_map.py +++ b/reclaimer/meta/wrappers/halo1_map.py @@ -976,20 +976,15 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): "halo1anni", "halo1pcdemo", "stubbspc"): # model_magic seems to be the same for all pc maps verts_start = tag_index.model_data_offset - tris_start = verts_start + tag_index.vertex_data_size + tris_start = verts_start + ( + tag_index.index_parts_offset + if engine == "stubbspc" else + tag_index.vertex_data_size + ) model_magic = None else: model_magic = magic - if model_magic is None: - verts_attr_name = "uncompressed_vertices" - byteswap_verts = byteswap_uncomp_verts - vert_size = 68 - else: - verts_attr_name = "compressed_vertices" - byteswap_verts = byteswap_comp_verts - vert_size = 32 - # lod cutoffs are swapped between tag and cache form cutoffs = (meta.superlow_lod_cutoff, meta.low_lod_cutoff, meta.high_lod_cutoff, meta.superhigh_lod_cutoff) @@ -1034,9 +1029,17 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): # grab vertices and indices from the map for geom in meta.geometries.STEPTREE: for part in geom.parts.STEPTREE: - verts_block = part[verts_attr_name] tris_block = part.triangles - info = part.model_meta_info + info = part.model_meta_info + + if info.vertex_type.enum_name == "compressed": + verts_block = part.compressed_vertices + byteswap_verts = byteswap_comp_verts + vert_size = 32 + else: + verts_block = part.uncompressed_vertices + byteswap_verts = byteswap_uncomp_verts + vert_size = 68 # null out certain things in the part part.previous_part_index = part.next_part_index = 0 @@ -1050,10 +1053,7 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): tris_block.STEPTREE = raw_block_def.build() # read the offsets of the vertices and indices from the map - if engine == "stubbspc": - verts_off = verts_start + info.vertices_reflexive_offset - tris_off = tris_start + info.indices_reflexive_offset - elif model_magic is None: + if model_magic is None: verts_off = verts_start + info.vertices_offset tris_off = tris_start + info.indices_offset else: diff --git a/reclaimer/meta/wrappers/stubbs_map.py b/reclaimer/meta/wrappers/stubbs_map.py index 2f44b27b..746744cf 100644 --- a/reclaimer/meta/wrappers/stubbs_map.py +++ b/reclaimer/meta/wrappers/stubbs_map.py @@ -14,19 +14,32 @@ class StubbsMap(Halo1Map): - defs = None + xbox_defs = None + pc_defs = None handler_class = StubbsHandler tag_defs_module = StubbsHandler.default_defs_path tag_classes_to_load = tuple(sorted(stubbs_tag_class_fcc_to_ext.keys())) + + @property + def defs(self): + this_class = type(self) + return this_class.pc_defs if self.engine == "stubbspc" else this_class.xbox_defs + @defs.setter + def defs(self, val): + this_class = type(self) + if self.engine == "stubbspc": + this_class.pc_defs = val + else: + this_class.xbox_defs = val def setup_defs(self): this_class = type(self) - if not this_class.defs: + if not(this_class.xbox_defs and this_class.pc_defs): print(" Loading definitions in %s" % self.handler_class.default_defs_path) - this_class.defs = defs = {} + this_class.xbox_defs = this_class.pc_defs = {} # these imports were moved here because their defs would otherwise # be built when this module was imported, which is not good practice @@ -39,16 +52,16 @@ def setup_defs(self): this_class.handler = self.handler_class( build_reflexive_cache=False, build_raw_data_cache=False, debug=2) - this_class.defs = dict(this_class.handler.defs) - - if self.engine == "stubbspc": - this_class.defs["mode"] = pc_mode_def - else: - this_class.defs["mode"] = mode_def - this_class.defs["antr"] = antr_def - this_class.defs["coll"] = coll_def - this_class.defs["sbsp"] = sbsp_def - this_class.defs = FrozenDict(this_class.defs) + this_class.xbox_defs = dict(this_class.handler.defs) + this_class.xbox_defs.update( + antr=antr_def, coll=coll_def, sbsp=sbsp_def, mode=mode_def + ) + this_class.xbox_defs = FrozenDict(this_class.xbox_defs) + this_class.pc_defs = dict(self.xbox_defs) + this_class.pc_defs.update(mode=pc_mode_def) + + this_class.xbox_defs = FrozenDict(this_class.xbox_defs) + this_class.pc_defs = FrozenDict(this_class.pc_defs) # make a shallow copy for this instance to manipulate self.defs = dict(self.defs) diff --git a/reclaimer/model/model_decompilation.py b/reclaimer/model/model_decompilation.py index 2e1284b9..c1b2ce2e 100644 --- a/reclaimer/model/model_decompilation.py +++ b/reclaimer/model/model_decompilation.py @@ -136,12 +136,8 @@ def extract_model(tagdata, tag_path="", **kw): region_geoms.append(geom_index) last_geom_index = geom_index - try: - use_local_nodes = tagdata.flags.parts_have_local_nodes - except Exception: - use_local_nodes = False - def_node_map = list(range(128)) - def_node_map.append(-1) + can_have_local_nodes = hasattr(tagdata.flags, "parts_have_local_nodes") + def_node_map = [*range(128), -1] # use big endian since it will have been byteswapped comp_vert_unpacker = PyStruct(">3f3I2h2bh").unpack_from @@ -181,60 +177,65 @@ def extract_model(tagdata, tag_path="", **kw): v_origin = len(verts) shader_index = part.shader_index - try: - node_map = list(part.local_nodes) - node_map.append(-1) - compressed = False - except (AttributeError, KeyError): - compressed = True - - if not use_local_nodes: - node_map = def_node_map + node_map = ( + [*part.local_nodes, -1] + if can_have_local_nodes and part.flags.ZONER else + def_node_map + ) + + tris_steptree = part.triangles.STEPTREE + cverts_steptree = part.compressed_vertices.STEPTREE + ucverts_steptree = part.uncompressed_vertices.STEPTREE + unparsed = isinstance( + getattr(tris_steptree, "data", None), + (bytearray, bytes) + ) + compressed = ( + bool(unparsed and getattr(cverts_steptree, "data", None)) or + bool(not unparsed and cverts_steptree) + ) + uncompressed = ( + bool(unparsed and getattr(ucverts_steptree, "data", None)) or + bool(not unparsed and ucverts_steptree) + ) + # prefer uncompressed vertices if both exist + compressed = compressed and not uncompressed try: - unparsed = isinstance( - part.triangles.STEPTREE.data, bytearray) - except Exception: - unparsed = False - - # TODO: Make this work in meta(parse verts and tris) - try: - if compressed and unparsed: - vert_data = part.compressed_vertices.STEPTREE.data - for off in range(0, len(vert_data), 32): - v = comp_vert_unpacker(vert_data, off) - verts.append(JmsVertex( - v[8]//3, - v[0] * 100, v[1] * 100, v[2] * 100, - *decompress_normal32(v[3]), - v[9]//3, 1.0 - (v[10]/32767), - u_scale * v[6]/32767, 1.0 - v_scale * v[7]/32767)) - elif compressed: - for v in part.compressed_vertices.STEPTREE: - verts.append(JmsVertex( + vert_data = cverts_steptree if compressed else ucverts_steptree + if unparsed: + # if verts are unparsed, parse them + unpack, v_size = ( + (comp_vert_unpacker, 32) if compressed else + (uncomp_vert_unpacker, 68) + ) + data = vert_data.data + vert_data = [ + unpack(data, i) for i in range(0, len(data), v_size) + ] + + if compressed: + verts.extend( + JmsVertex( v[8]//3, v[0] * 100, v[1] * 100, v[2] * 100, *decompress_normal32(v[3]), v[9]//3, 1.0 - (v[10]/32767), - u_scale * v[6]/32767, 1.0 - v_scale * v[7]/32767)) - elif not compressed and unparsed: - vert_data = part.uncompressed_vertices.STEPTREE.data - for off in range(0, len(vert_data), 68): - v = uncomp_vert_unpacker(vert_data, off) - verts.append(JmsVertex( - node_map[v[14]], - v[0] * 100, v[1] * 100, v[2] * 100, - v[3], v[4], v[5], - node_map[v[15]], max(0, min(1, v[17])), - u_scale * v[12], 1.0 - v_scale * v[13])) + u_scale * v[6]/32767, 1.0 - v_scale * v[7]/32767 + ) + for v in vert_data + ) else: - for v in part.uncompressed_vertices.STEPTREE: - verts.append(JmsVertex( + verts.extend( + JmsVertex( node_map[v[14]], v[0] * 100, v[1] * 100, v[2] * 100, v[3], v[4], v[5], node_map[v[15]], max(0, min(1, v[17])), - u_scale * v[12], 1.0 - v_scale * v[13])) + u_scale * v[12], 1.0 - v_scale * v[13] + ) + for v in vert_data + ) except Exception: print(format_exc()) print("If you see this, tell Moses to stop " @@ -242,7 +243,7 @@ def extract_model(tagdata, tag_path="", **kw): try: if unparsed: - tri_block = part.triangles.STEPTREE.data + tri_block = tris_steptree.data tri_list = [-1] * (len(tri_block) // 2) for i in range(len(tri_list)): # assuming big endian @@ -252,9 +253,8 @@ def extract_model(tagdata, tag_path="", **kw): if tri_list[i] > 32767: tri_list[i] = -1 else: - tri_block = part.triangles.STEPTREE tri_list = [] - for triangle in tri_block: + for triangle in tris_steptree: tri_list.extend(triangle) swap = True diff --git a/reclaimer/stubbs/defs/mode.py b/reclaimer/stubbs/defs/mode.py index 84ad26c7..b1af81fd 100644 --- a/reclaimer/stubbs/defs/mode.py +++ b/reclaimer/stubbs/defs/mode.py @@ -16,8 +16,8 @@ def get(): # my guess at what the struct might be like unknown_struct = Struct("unknown", ascii_str32("name"), - LFloat('unknown1', ENDIAN='<', DEFAULT=1.0), - LFloat('unknown2', ENDIAN='<', DEFAULT=1.0), + Float('unknown1', DEFAULT=1.0), + Float('unknown2', DEFAULT=1.0), BytesRaw("unknown_data", SIZE=64-32-4*2), SIZE=64 ) @@ -27,116 +27,40 @@ def get(): # SIZE=64 # ) -pc_part = Struct('part', - Bool32('flags', - 'stripped', - ), - dyn_senum16('shader_index', - DYN_NAME_PATH="tagdata.shaders.shaders_array[DYN_I].shader.filepath"), - SInt8('previous_part_index'), - SInt8('next_part_index'), - - SInt16('centroid_primary_node'), - SInt16('centroid_secondary_node'), - Float('centroid_primary_weight'), - Float('centroid_secondary_weight'), - - QStruct('centroid_translation', INCLUDE=xyz_float), - - #reflexive("uncompressed_vertices", uncompressed_vertex_union, 65535), - #reflexive("compressed_vertices", compressed_vertex_union, 65535), - #reflexive("triangles", triangle_union, 65535), - reflexive("uncompressed_vertices", fast_uncompressed_vertex, 65535), - reflexive("compressed_vertices", fast_compressed_vertex, 65535), - reflexive("triangles", triangle, 65535), - - #Pad(36), - Struct("model_meta_info", - # the offset fields in model_meta_info struct are the only - # thing different from halo model tags. if they weren't, - # this whole new part definition wouldn't be necessary. - UEnum16("index_type", # name is a guess. always 1? - ("uncompressed", 1), - ), - Pad(2), - UInt32("index_count"), - # THESE VALUES ARE DIFFERENT THAN ON XBOX IT SEEMS - UInt32("indices_magic_offset"), - UInt32("indices_offset"), - - UEnum16("vertex_type", # name is a guess - ("uncompressed", 4), - ("compressed", 5), - ), - Pad(2), - UInt32("vertex_count"), - Pad(4), # always 0? - # THESE VALUES ARE DIFFERENT THAN ON XBOX IT SEEMS - UInt32("vertices_magic_offset"), - UInt32("vertices_offset"), - VISIBLE=False, SIZE=36 - ), - - SIZE=104 +pc_part = desc_variant(part, + ("model_meta_info", model_meta_info), ) - -fast_part = dict(part) -fast_part[9] = raw_reflexive("uncompressed_vertices", fast_uncompressed_vertex) -fast_part[10] = raw_reflexive("compressed_vertices", fast_compressed_vertex) -fast_part[11] = raw_reflexive("triangles", triangle) - -pc_geometry = Struct('geometry', - Pad(36), - reflexive("parts", pc_part, 32), - SIZE=48 +fast_part = desc_variant(part, + ("uncompressed_vertices", raw_reflexive("uncompressed_vertices", fast_uncompressed_vertex)), + ("compressed_vertices", raw_reflexive("compressed_vertices", fast_compressed_vertex)), + ("triangles", raw_reflexive("triangles", triangle)), ) - -fast_geometry = Struct('geometry', - Pad(36), - reflexive("parts", fast_part, 32), - SIZE=48 +fast_pc_part = desc_variant(fast_part, + ("model_meta_info", model_meta_info), ) -mode_body = Struct('tagdata', - Bool32('flags', - 'blend_shared_normals', - ), - SInt32('node_list_checksum'), - - Float('superhigh_lod_cutoff', SIDETIP="pixels"), - Float('high_lod_cutoff', SIDETIP="pixels"), - Float('medium_lod_cutoff', SIDETIP="pixels"), - Float('low_lod_cutoff', SIDETIP="pixels"), - Float('superlow_lod_cutoff', SIDETIP="pixels"), - - SInt16('superlow_lod_nodes', SIDETIP="nodes", VISIBLE=False), - SInt16('low_lod_nodes', SIDETIP="nodes", VISIBLE=False), - SInt16('medium_lod_nodes', SIDETIP="nodes", VISIBLE=False), - SInt16('high_lod_nodes', SIDETIP="nodes", VISIBLE=False), - SInt16('superhigh_lod_nodes', SIDETIP="nodes", VISIBLE=False), - - Pad(10), - - Float('base_map_u_scale'), - Float('base_map_v_scale'), - - Pad(104), - reflexive("unknown", unknown_struct, DYN_NAME_PATH=".name"), - - reflexive("markers", marker, 256, DYN_NAME_PATH=".name"), - reflexive("nodes", node, 64, DYN_NAME_PATH=".name"), - reflexive("regions", region, 32, DYN_NAME_PATH=".name"), - reflexive("geometries", geometry, 256), - reflexive("shaders", shader, 256, DYN_NAME_PATH=".shader.filepath"), - - SIZE=232 +pc_geometry = desc_variant(geometry, + ("parts", reflexive("parts", pc_part, 32)), + ) +fast_geometry = desc_variant(geometry, + ("parts", reflexive("parts", fast_part, 32)), + ) +fast_pc_geometry = desc_variant(geometry, + ("parts", reflexive("parts", fast_pc_part, 32)), ) -pc_mode_body = dict(mode_body) -pc_mode_body[20] = reflexive("geometries", pc_geometry, 256) - -fast_mode_body = dict(mode_body) -fast_mode_body[20] = reflexive("geometries", fast_geometry, 256) +mode_body = desc_variant(mode_body, + ("pad_16", reflexive("unknown", unknown_struct, DYN_NAME_PATH=".name")), + ) +pc_mode_body = desc_variant(mode_body, + ("geometries", reflexive("geometries", pc_geometry, 256)) + ) +fast_mode_body = desc_variant(mode_body, + ("geometries", reflexive("geometries", fast_geometry, 256)) + ) +fast_pc_mode_body = desc_variant(mode_body, + ("geometries", reflexive("geometries", fast_pc_geometry, 256)) + ) mode_def = TagDef("mode", blam_header_stubbs('mode', 6), # increment to differentiate it from mode and mod2 @@ -158,3 +82,10 @@ def get(): ext=".model", endian=">", tag_cls=ModeTag ) + +fast_pc_mode_def = TagDef("mode", + blam_header_stubbs('mode', 6), # increment to differentiate it from mode and mod2 + fast_pc_mode_body, + + ext=".model", endian=">", tag_cls=ModeTag + ) From 42e0e6b355ae29ea7a822fdfd67cbe50e149dfeb Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Wed, 17 Jan 2024 22:22:02 -0600 Subject: [PATCH 09/51] Halo 1 MCC map extraction support --- reclaimer/constants.py | 14 +++++++++----- reclaimer/hek/defs/objs/bitm.py | 10 +++++++--- reclaimer/mcc_hek/defs/bitm.py | 3 ++- reclaimer/mcc_hek/defs/objs/bitm.py | 15 +++++++++++++++ reclaimer/meta/halo1_map.py | 1 + reclaimer/meta/halo_map.py | 4 ++-- reclaimer/meta/wrappers/halo1_map.py | 8 ++++---- reclaimer/meta/wrappers/halo1_mcc_map.py | 21 +++++++++++++++++++++ reclaimer/meta/wrappers/halo1_rsrc_map.py | 8 +++++--- reclaimer/meta/wrappers/halo_map.py | 7 +++++++ 10 files changed, 73 insertions(+), 18 deletions(-) create mode 100644 reclaimer/mcc_hek/defs/objs/bitm.py create mode 100644 reclaimer/meta/wrappers/halo1_mcc_map.py diff --git a/reclaimer/constants.py b/reclaimer/constants.py index 2cceb439..7ca5d7ae 100644 --- a/reclaimer/constants.py +++ b/reclaimer/constants.py @@ -30,6 +30,7 @@ def inject_halo_constants(): PCDEMO_INDEX_MAGIC = 0x4BF10000 +MCC_INDEX_MAGIC = 0x50000000 PC_INDEX_MAGIC = 0x40440000 CE_INDEX_MAGIC = 0x40440000 ANNIVERSARY_INDEX_MAGIC = 0x004B8000 @@ -51,6 +52,7 @@ def inject_halo_constants(): "halo1yelo": "01.00.00.0609", "halo1vap": "01.00.00.0609", "halo1pc": "01.00.00.0564", + "halo1mcc": "01.03.43.0000", "halo2alpha": "02.01.07.4998", "halo2beta": "02.06.28.07902", "halo2epsilon": "02.08.28.09214", @@ -75,6 +77,7 @@ def inject_halo_constants(): "halo1pcdemo": 6, "halo1pc": 7, "halo1anni": 7, + "halo1mcc": 13, "halo1ce": 609, "halo1yelo": 609, "halo1vap": 134, @@ -95,7 +98,8 @@ def inject_halo_constants(): GEN_1_HALO_ENGINES = ("halo1xboxdemo", "halo1xbox", "halo1ce", "halo1vap", "halo1yelo", - "halo1pcdemo", "halo1pc", "halo1anni", ) + "halo1pcdemo", "halo1pc", "halo1anni", + "halo1mcc", ) GEN_1_ENGINES = GEN_1_HALO_ENGINES + ( "stubbs", "stubbspc", "shadowrun_proto", ) @@ -119,6 +123,7 @@ def inject_halo_constants(): "halo1pcdemo": PCDEMO_INDEX_MAGIC, "halo1pc": PC_INDEX_MAGIC, "halo1anni": ANNIVERSARY_INDEX_MAGIC, + "halo1mcc": MCC_INDEX_MAGIC, "halo1ce": CE_INDEX_MAGIC, "halo1yelo": CE_INDEX_MAGIC, "halo1vap": CE_INDEX_MAGIC, @@ -185,11 +190,10 @@ def inject_halo_constants(): "V8U8", "G8B8", "UNUSED6", "UNUSED7", "UNUSED8", "UNUSED9", "UNUSED10", "UNUSED11", "UNUSED12", "UNUSED13", "UNUSED14", "DXN", "CTX1", "DXT3A", "DXT3Y", "DXT5A", "DXT5Y", "DXT5AY") +MCC_FORMAT_NAME_MAP = FORMAT_NAME_MAP[:FORMAT_NAME_MAP.index("P8")] + ("BC7", ) -I_FORMAT_NAME_MAP = {} -for i in range(len(FORMAT_NAME_MAP)): - if i not in I_FORMAT_NAME_MAP: - I_FORMAT_NAME_MAP[FORMAT_NAME_MAP[i]] = i +I_FORMAT_NAME_MAP = {fmt: i for i, fmt in enumerate(FORMAT_NAME_MAP)} +I_MCC_FORMAT_NAME_MAP = {fmt: i for i, fmt in enumerate(MCC_FORMAT_NAME_MAP)} #each bitmap's number of bytes must be a multiple of 512 BITMAP_PADDING = 512 diff --git a/reclaimer/hek/defs/objs/bitm.py b/reclaimer/hek/defs/objs/bitm.py index cc1b5b0f..088a5084 100644 --- a/reclaimer/hek/defs/objs/bitm.py +++ b/reclaimer/hek/defs/objs/bitm.py @@ -28,6 +28,10 @@ class BitmTag(HekTag): tex_infos = () p8_palette = None + + @property + def format_name_map(self): + return FORMAT_NAME_MAP def __init__(self, *args, **kwargs): HekTag.__init__(self, *args, **kwargs) @@ -309,7 +313,7 @@ def get_bitmap_size(self, b_index): "Arbytmap is not loaded. Cannot get bitmap size.") w, h, d, = self.bitmap_width_height_depth(b_index) - fmt = FORMAT_NAME_MAP[self.bitmap_format(b_index)] + fmt = self.format_name_map[self.bitmap_format(b_index)] bytes_count = 0 for mipmap in range(self.bitmap_mipmaps_count(b_index) + 1): @@ -379,7 +383,7 @@ def sanitize_bitmaps(self): tex_infos = self.tex_infos for i in range(self.bitmap_count()): - format = FORMAT_NAME_MAP[self.bitmap_format(i)] + format = self.format_name_map[self.bitmap_format(i)] flags = self.bitmap_flags(i) old_w, old_h, _ = self.bitmap_width_height_depth(i) @@ -420,7 +424,7 @@ def parse_bitmap_blocks(self): # since we need this information to read the bitmap we extract it mw, mh, md, = self.bitmap_width_height_depth(i) type = self.bitmap_type(i) - format = FORMAT_NAME_MAP[self.bitmap_format(i)] + format = self.format_name_map[self.bitmap_format(i)] mipmap_count = self.bitmap_mipmaps_count(i) + 1 sub_bitmap_count = ab.SUB_BITMAP_COUNTS[TYPE_NAME_MAP[type]] diff --git a/reclaimer/mcc_hek/defs/bitm.py b/reclaimer/mcc_hek/defs/bitm.py index bc000799..749aa9a8 100644 --- a/reclaimer/mcc_hek/defs/bitm.py +++ b/reclaimer/mcc_hek/defs/bitm.py @@ -8,6 +8,7 @@ # from ...hek.defs.bitm import * +from .objs.bitm import MccBitmTag from supyr_struct.util import desc_variant format_comment_parts = format_comment.split("NOTE: ", 1) @@ -86,6 +87,6 @@ def get(): blam_header('bitm', 7), bitm_body, - ext=".bitmap", endian=">", tag_cls=BitmTag, + ext=".bitmap", endian=">", tag_cls=MccBitmTag, subdefs = {'pixel_root':pixel_root} ) diff --git a/reclaimer/mcc_hek/defs/objs/bitm.py b/reclaimer/mcc_hek/defs/objs/bitm.py new file mode 100644 index 00000000..80e9d4e5 --- /dev/null +++ b/reclaimer/mcc_hek/defs/objs/bitm.py @@ -0,0 +1,15 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# +from ....hek.defs.objs.bitm import * +from reclaimer.constants import MCC_FORMAT_NAME_MAP + +class MccBitmTag(BitmTag): + @property + def format_name_map(self): + return MCC_FORMAT_NAME_MAP \ No newline at end of file diff --git a/reclaimer/meta/halo1_map.py b/reclaimer/meta/halo1_map.py index 4ed29f6a..01c1b744 100644 --- a/reclaimer/meta/halo1_map.py +++ b/reclaimer/meta/halo1_map.py @@ -224,6 +224,7 @@ def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): ("halo2", 8), ("halo3beta", 9), ("halo3", 11), + ("halo1mcc", 13), ("halo1ce", 609), ("halo1vap", 134), ), diff --git a/reclaimer/meta/halo_map.py b/reclaimer/meta/halo_map.py index cdbc9034..c592a8bb 100644 --- a/reclaimer/meta/halo_map.py +++ b/reclaimer/meta/halo_map.py @@ -120,7 +120,7 @@ def get_map_header(map_file, header_only=False): if header_only: header_def = h2x_map_header_def - elif ver_little in (5, 6, 609): + elif ver_little in (5, 6, 13, 609): header_def = map_header_def elif ver_little == 134: @@ -227,7 +227,7 @@ def get_map_magic(header): def get_is_compressed_map(comp_data, header): if header.version.data == 134: return header.vap_header.compression_type.data != 0 - elif header.version.data not in (7, 609): + elif header.version.data not in (7, 13, 609): decomp_len = header.decomp_len if get_map_version(header) == "pcstubbs": decomp_len -= 2048 diff --git a/reclaimer/meta/wrappers/halo1_map.py b/reclaimer/meta/wrappers/halo1_map.py index eaab8d7c..a84fafef 100644 --- a/reclaimer/meta/wrappers/halo1_map.py +++ b/reclaimer/meta/wrappers/halo1_map.py @@ -149,7 +149,7 @@ def ensure_sound_maps_valid(self): return self.sound_rsrc_id = id(sounds) - if self.engine in ("halo1ce", "halo1yelo", "halo1vap"): + if self.engine in ("halo1ce", "halo1yelo", "halo1vap", "halo1mcc"): # ce resource sounds are recognized by tag_path # so we must cache their offsets by their paths rsrc_snd_map = self.ce_rsrc_sound_indexes_by_path = {} @@ -972,7 +972,7 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): meta.stencil_bitmap.filepath = meta.source_bitmap.filepath = '' elif tag_cls in ("mode", "mod2"): - if engine in ("halo1yelo", "halo1ce", "halo1pc", "halo1vap", + if engine in ("halo1yelo", "halo1ce", "halo1pc", "halo1vap", "halo1mcc", "halo1anni", "halo1pcdemo", "stubbspc"): # model_magic seems to be the same for all pc maps verts_start = tag_index.model_data_offset @@ -1371,13 +1371,13 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): return meta def get_resource_map_paths(self, maps_dir=""): - if self.is_resource or self.engine not in ("halo1pc", "halo1pcdemo", + if self.is_resource or self.engine not in ("halo1pc", "halo1pcdemo", "halo1mcc", "halo1ce", "halo1yelo", "halo1vap"): return {} map_paths = {"bitmaps": None, "sounds": None, "loc": None} - if self.engine not in ("halo1ce", "halo1yelo", "halo1vap"): + if self.engine not in ("halo1ce", "halo1yelo", "halo1vap", "halo1mcc"): map_paths.pop('loc') data_files = False diff --git a/reclaimer/meta/wrappers/halo1_mcc_map.py b/reclaimer/meta/wrappers/halo1_mcc_map.py new file mode 100644 index 00000000..a09f0d58 --- /dev/null +++ b/reclaimer/meta/wrappers/halo1_mcc_map.py @@ -0,0 +1,21 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# +from reclaimer.meta.wrappers.halo1_map import Halo1Map +from reclaimer.mcc_hek.handler import MCCHaloHandler + +class Halo1MccMap(Halo1Map): + '''Masterchief Collection Halo 1 map''' + + # Module path printed when loading the tag defs + tag_defs_module = "reclaimer.mcc_hek.defs" + # Handler that controls how to load tags, eg tag definitions + handler_class = MCCHaloHandler + + def __init__(self, maps=None): + Halo1Map.__init__(self, maps) diff --git a/reclaimer/meta/wrappers/halo1_rsrc_map.py b/reclaimer/meta/wrappers/halo1_rsrc_map.py index c5f24bb1..1c7fcfb5 100644 --- a/reclaimer/meta/wrappers/halo1_rsrc_map.py +++ b/reclaimer/meta/wrappers/halo1_rsrc_map.py @@ -224,7 +224,9 @@ def get_meta(self, tag_id, reextract=False, **kw): kwargs = dict(parsing_resource=True) desc = self.get_meta_descriptor(tag_cls) if desc is None or self.engine not in ("halo1ce", "halo1yelo", - "halo1vap"): + "halo1vap", "halo1mcc"): + return + elif self.engine == "halo1mcc" and tag_cls == "bitm": return elif tag_cls != 'snd!': # the pitch ranges pointer in resource sound tags is invalid, so @@ -310,7 +312,7 @@ def inject_rawdata(self, meta, tag_cls, tag_index_ref): except Exception: loc_data = None is_not_indexed = not self.is_indexed(tag_index_ref.id & 0xFFff) - might_be_in_rsrc = engine in ("halo1pc", "halo1pcdemo", + might_be_in_rsrc = engine in ("halo1pc", "halo1pcdemo", "halo1mcc", "halo1ce", "halo1yelo", "halo1vap") might_be_in_rsrc &= not self.is_resource @@ -365,7 +367,7 @@ def inject_rawdata(self, meta, tag_cls, tag_index_ref): meta.string.data = loc_data.read(b.size).decode('utf-16-le') elif tag_cls == "snd!": # might need to get samples and permutations from the resource map - is_pc = engine in ("halo1pc", "halo1pcdemo") + is_pc = engine in ("halo1pc", "halo1pcdemo", "halo1mcc") is_ce = engine in ("halo1ce", "halo1yelo", "halo1vap") if not(is_pc or is_ce): return meta diff --git a/reclaimer/meta/wrappers/halo_map.py b/reclaimer/meta/wrappers/halo_map.py index 7d0def3a..6759c6de 100644 --- a/reclaimer/meta/wrappers/halo_map.py +++ b/reclaimer/meta/wrappers/halo_map.py @@ -319,6 +319,13 @@ def load_resource_maps(self, maps_dir="", map_paths=(), **kw): print("Loading %s..." % map_name) new_map.load_map(map_path, **kw) + if self.engine == "halo1mcc" and ( + (new_map.engine == "halo1pc" and new_map.map_name == "bitmaps") or + new_map.engine == "halo1ce" + ): + # cant tell the difference between mcc, ce, and pc resource maps + new_map.engine = self.engine + if new_map.engine != self.engine: if do_printout: print("Incorrect engine for this map.") From bd51f35136cd29325280232f81b7777816ef302f Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Fri, 19 Jan 2024 09:31:08 -0600 Subject: [PATCH 10/51] Halo 1 MCC map pointers --- reclaimer/mcc_hek/defs/sbsp.py | 12 +++++ reclaimer/meta/halo1_map.py | 10 ++++- reclaimer/meta/halo_map.py | 9 ++-- reclaimer/meta/wrappers/halo1_map.py | 44 +++++++++++++++--- reclaimer/meta/wrappers/halo1_mcc_map.py | 55 +++++++++++++++++++++++ reclaimer/meta/wrappers/halo1_rsrc_map.py | 4 +- 6 files changed, 121 insertions(+), 13 deletions(-) diff --git a/reclaimer/mcc_hek/defs/sbsp.py b/reclaimer/mcc_hek/defs/sbsp.py index 2e017cb4..1f64cb7e 100644 --- a/reclaimer/mcc_hek/defs/sbsp.py +++ b/reclaimer/mcc_hek/defs/sbsp.py @@ -8,3 +8,15 @@ # from ...hek.defs.sbsp import * + +sbsp_meta_header_def = BlockDef("sbsp_meta_header", + # to convert the meta pointer to offsets, do: pointer - bsp_magic + UInt32("meta_pointer"), + # the rest of these are literal pointers in the map + UInt32("uncompressed_render_vertices_size"), + UInt32("uncompressed_render_vertices_pointer"), + UInt32("compressed_render_vertices_size"), # name is a guess + UInt32("compressed_render_vertices_pointer"), # name is a guess + UInt32("sig", DEFAULT="sbsp"), + SIZE=24, TYPE=QStruct + ) \ No newline at end of file diff --git a/reclaimer/meta/halo1_map.py b/reclaimer/meta/halo1_map.py index 01c1b744..d78d0489 100644 --- a/reclaimer/meta/halo1_map.py +++ b/reclaimer/meta/halo1_map.py @@ -242,12 +242,19 @@ def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): ), Pad(2), UInt32("crc32"), - Pad(8), + Pad(1), + Pad(3), + Pad(4), yelo_header, UEnum32('foot', ('foot', 'foot'), DEFAULT='foot', OFFSET=2044), SIZE=2048 ) +map_header_mcc = desc_variant( + map_header, + ("pad_12", UEnum8("enable_remastered_graphics", "yes", "no")), + ) + map_header_vap = desc_variant( map_header, ("yelo_header", Struct("vap_header", INCLUDE=vap_header, OFFSET=128)), @@ -326,3 +333,4 @@ def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): tag_index_anni_def = BlockDef(tag_index_pc, endian=">") map_header_vap_def = BlockDef(map_header_vap) +map_header_mcc_def = BlockDef(map_header_mcc) diff --git a/reclaimer/meta/halo_map.py b/reclaimer/meta/halo_map.py index c592a8bb..8c2cad23 100644 --- a/reclaimer/meta/halo_map.py +++ b/reclaimer/meta/halo_map.py @@ -15,8 +15,8 @@ from reclaimer.constants import map_build_dates, map_magics, GEN_3_ENGINES from reclaimer.meta.halo1_map import map_header_def, map_header_vap_def,\ - map_header_anni_def, map_header_demo_def, tag_index_pc_def,\ - tag_index_xbox_def, tag_index_anni_def + map_header_anni_def, map_header_demo_def, map_header_mcc_def,\ + tag_index_pc_def, tag_index_xbox_def, tag_index_anni_def from reclaimer.meta.halo2_alpha_map import h2_alpha_map_header_def,\ h2_alpha_tag_index_def from reclaimer.meta.halo2_map import h2v_map_header_full_def,\ @@ -120,9 +120,12 @@ def get_map_header(map_file, header_only=False): if header_only: header_def = h2x_map_header_def - elif ver_little in (5, 6, 13, 609): + elif ver_little in (5, 6, 609): header_def = map_header_def + elif ver_little == 13: + header_def = map_header_mcc_def + elif ver_little == 134: header_def = map_header_vap_def diff --git a/reclaimer/meta/wrappers/halo1_map.py b/reclaimer/meta/wrappers/halo1_map.py index a84fafef..9bfca9d8 100644 --- a/reclaimer/meta/wrappers/halo1_map.py +++ b/reclaimer/meta/wrappers/halo1_map.py @@ -93,6 +93,8 @@ class Halo1Map(HaloMap): bsp_headers = () bsp_header_offsets = () bsp_pointer_converters = () + + sbsp_meta_header_def = sbsp_meta_header_def data_extractors = data_extraction.h1_data_extractors @@ -273,10 +275,10 @@ def setup_sbsp_pointer_converters(self): for tag_id, offset in self.bsp_header_offsets.items(): if self.engine == "halo1anni": with FieldType.force_big: - header = sbsp_meta_header_def.build( + header = self.sbsp_meta_header_def.build( rawdata=self.map_data, offset=offset) else: - header = sbsp_meta_header_def.build( + header = self.sbsp_meta_header_def.build( rawdata=self.map_data, offset=offset) if header.sig != header.get_desc("DEFAULT", "sig"): @@ -1372,14 +1374,16 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): def get_resource_map_paths(self, maps_dir=""): if self.is_resource or self.engine not in ("halo1pc", "halo1pcdemo", "halo1mcc", - "halo1ce", "halo1yelo", - "halo1vap"): + "halo1ce", "halo1yelo", "halo1vap"): return {} map_paths = {"bitmaps": None, "sounds": None, "loc": None} - if self.engine not in ("halo1ce", "halo1yelo", "halo1vap", "halo1mcc"): + if self.engine not in ("halo1ce", "halo1yelo", "halo1vap"): map_paths.pop('loc') + if self.engine == "halo1mcc": + map_paths.pop('sounds') + data_files = False if hasattr(self.map_header, "yelo_header"): data_files = self.map_header.yelo_header.flags.uses_mod_data_files @@ -1408,6 +1412,10 @@ def get_resource_map_paths(self, maps_dir=""): def generate_map_info_string(self): string = HaloMap.generate_map_info_string(self) index, header = self.tag_index, self.map_header + + if self.engine == "halo1mcc": + string += """ + supports remastered == %s""" % header.enable_remastered_graphics.enum_name string += """ @@ -1448,14 +1456,36 @@ def generate_map_info_string(self): if header is None: continue magic = self.bsp_magics[tag_id] + offset = self.bsp_header_offsets[tag_id] string += """ %s.structure_scenario_bsp bsp base pointer == %s bsp magic == %s bsp size == %s bsp metadata pointer == %s non-magic == %s\n""" % ( - index.tag_index[tag_id].path, self.bsp_header_offsets[tag_id], + index.tag_index[tag_id].path, offset, magic, self.bsp_sizes[tag_id], header.meta_pointer, - header.meta_pointer - magic) + header.meta_pointer + offset - magic + ) + if self.engine == "halo1mcc": + string += """\ + render verts size == %s + render verts pointer == %s\n""" % ( + header.uncompressed_render_vertices_size, + header.uncompressed_render_vertices_pointer, + ) + else: + string += """\ + uncomp mats count == %s + uncomp mats pointer == %s non-magic == %s + comp mats count == %s + comp mats pointer == %s non-magic == %s\n""" % ( + header.uncompressed_lightmap_materials_count, + header.uncompressed_lightmap_materials_pointer, + header.uncompressed_lightmap_materials_pointer + offset - magic, + header.compressed_lightmap_materials_count, + header.compressed_lightmap_materials_pointer, + header.compressed_lightmap_materials_pointer + offset - magic, + ) if self.engine == "halo1yelo": string += self.generate_yelo_info_string() diff --git a/reclaimer/meta/wrappers/halo1_mcc_map.py b/reclaimer/meta/wrappers/halo1_mcc_map.py index a09f0d58..a1798944 100644 --- a/reclaimer/meta/wrappers/halo1_mcc_map.py +++ b/reclaimer/meta/wrappers/halo1_mcc_map.py @@ -8,6 +8,7 @@ # from reclaimer.meta.wrappers.halo1_map import Halo1Map from reclaimer.mcc_hek.handler import MCCHaloHandler +from reclaimer.mcc_hek.defs.sbsp import sbsp_meta_header_def class Halo1MccMap(Halo1Map): '''Masterchief Collection Halo 1 map''' @@ -16,6 +17,60 @@ class Halo1MccMap(Halo1Map): tag_defs_module = "reclaimer.mcc_hek.defs" # Handler that controls how to load tags, eg tag definitions handler_class = MCCHaloHandler + + sbsp_meta_header_def = sbsp_meta_header_def def __init__(self, maps=None): Halo1Map.__init__(self, maps) + + def inject_rawdata(self, meta, tag_cls, tag_index_ref): + if tag_cls != "sbsp": + return super().inject_rawdata(meta, tag_cls, tag_index_ref) + + tag_id = tag_index_ref.id & 0xFFFF + bsp_header = self.bsp_headers.get(tag_id, None) + if bsp_header is None: + raise ValueError("No bsp header found for tag %s of type %s" % ( + tag_id, tag_cls, + )) + + uc_sector_start = bsp_header.uncompressed_render_vertices_pointer + uc_sector_size = bsp_header.uncompressed_render_vertices_size + c_sector_start = bsp_header.compressed_render_vertices_pointer + c_sector_size = bsp_header.compressed_render_vertices_size + uc_sector_end = uc_sector_start + uc_sector_size + c_sector_end = c_sector_start + c_sector_size + map_data = self.map_data + + # mcc render geometry isn't stored the same way as custom edition/xbox. + # it's stored relative to the pointers in the sbsp meta header, and the + # size of the verts isn't calculated into the size of the sbsp sector. + for lm in meta.lightmaps.STEPTREE: + for mat in lm.materials.STEPTREE: + if mat.vertex_type.enum_name == "compressed": + data_block = mat.compressed_vertices + start, end = c_sector_start, c_sector_end + vert_size, lm_vert_size = 32, 8 + else: + data_block = mat.uncompressed_vertices + start, end = uc_sector_start, uc_sector_end + vert_size, lm_vert_size = 56, 20 + + verts_offset = start + mat.vertices_offset + lm_verts_offset = start + mat.lightmap_vertices_offset + verts_size = vert_size * mat.vertices_count + lm_verts_size = lm_vert_size * mat.lightmap_vertices_count + vert_data = b'' + if verts_size and verts_size + verts_offset > end: + print("Warning: Render vertices pointed to outside sector.") + elif verts_size: + map_data.seek(verts_offset) + vert_data += map_data.read(verts_size) + + if lm_verts_size and lm_verts_size + lm_verts_offset > end: + print("Warning: Lightmap vertices pointed to outside sector.") + elif lm_verts_size: + map_data.seek(lm_verts_offset) + vert_data += map_data.read(lm_verts_size) + + data_block.data = vert_data \ No newline at end of file diff --git a/reclaimer/meta/wrappers/halo1_rsrc_map.py b/reclaimer/meta/wrappers/halo1_rsrc_map.py index 1c7fcfb5..646077de 100644 --- a/reclaimer/meta/wrappers/halo1_rsrc_map.py +++ b/reclaimer/meta/wrappers/halo1_rsrc_map.py @@ -367,8 +367,8 @@ def inject_rawdata(self, meta, tag_cls, tag_index_ref): meta.string.data = loc_data.read(b.size).decode('utf-16-le') elif tag_cls == "snd!": # might need to get samples and permutations from the resource map - is_pc = engine in ("halo1pc", "halo1pcdemo", "halo1mcc") - is_ce = engine in ("halo1ce", "halo1yelo", "halo1vap") + is_pc = engine in ("halo1pc", "halo1pcdemo") + is_ce = engine in ("halo1ce", "halo1yelo", "halo1vap", "halo1mcc") if not(is_pc or is_ce): return meta elif sound_data is None: From e79f055d75e79971c7ff519b1c391daccd39871e Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sat, 20 Jan 2024 01:07:57 -0600 Subject: [PATCH 11/51] initial fmod work --- reclaimer/misc/defs/fmod.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 reclaimer/misc/defs/fmod.py diff --git a/reclaimer/misc/defs/fmod.py b/reclaimer/misc/defs/fmod.py new file mode 100644 index 00000000..58f95a61 --- /dev/null +++ b/reclaimer/misc/defs/fmod.py @@ -0,0 +1,24 @@ +from reclaimer.common_descs import * +from supyr_struct.defs.tag_def import TagDef + +def get(): return fmod_list_def + + +tag_ref = Container("tag_ref", + UInt32('string_len'), + StrUtf8('name', SIZE='.string_len', WIDGET_WIDTH=60), + UInt32('idx'), + UInt32('perm_count'), + ) + +header = Struct("header", + UInt32('unknown', DEFAULT=1), + UInt32("tag_count"), + ) + + +fmod_list_def = TagDef('fmod_list', + header, + Array("tag_refs", SUB_STRUCT=tag_ref, SIZE=".header.tag_count"), + ext='.bin', endian='<' + ) \ No newline at end of file From 834acd7429866c1d847431f2ffb178a3388aef39 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sat, 20 Jan 2024 01:08:40 -0600 Subject: [PATCH 12/51] More fmod work --- reclaimer/misc/defs/fmod.py | 203 +++++++++++++++++++++++++++++++++--- 1 file changed, 191 insertions(+), 12 deletions(-) diff --git a/reclaimer/misc/defs/fmod.py b/reclaimer/misc/defs/fmod.py index 58f95a61..d4386aed 100644 --- a/reclaimer/misc/defs/fmod.py +++ b/reclaimer/misc/defs/fmod.py @@ -1,24 +1,203 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + from reclaimer.common_descs import * from supyr_struct.defs.tag_def import TagDef -def get(): return fmod_list_def +FMOD_BANK_HEADER_SIZE = 60 + + +def get(): + return fmod_bank_def, fmod_list_def + + +def _get_fmod_bank(block): + while block and getattr(block, "NAME", "") != "fmod_bank": + block = block.parent + return block + + +def has_next_chunk(parent=None, **kwargs): + if parent is None: + return False + return (parent[-1] if parent else parent.parent).header.has_next_chunk + + +def sample_data_size(parent=None, root_offset=0, offset=0, **kwargs): + sample_array = getattr(parent, "parent", None) + if not sample_array: + return 0 + + next_sample_index = sample_array.index_by_id(parent) + 1 + start = sample_data_pointer(parent=parent, root_offset=root_offset, offset=offset) + if next_sample_index >= len(sample_array): + fsb_header = getattr(getattr(sample_array, "parent", None), "header", None) + data_size = getattr(fsb_header, "sample_data_size", 0) - start + else: + end = sample_data_pointer(parent=sample_array[next_sample_index]) + data_size = end - start + + return max(0, data_size) + + +def sample_data_pointer(parent=None, root_offset=0, offset=0, **kwargs): + sample_header = parent.header + bank_header = _get_fmod_bank(sample_header).header + return ( + root_offset + offset + FMOD_BANK_HEADER_SIZE + + bank_header.sample_headers_size + + bank_header.sample_names_size + + sample_header.data_qword_offset * 16 + ) + + +def sample_name_offsets_pointer(parent=None, root_offset=0, offset=0, **kwargs): + return ( + root_offset + offset + FMOD_BANK_HEADER_SIZE + + _get_fmod_bank(parent).header.sample_headers_size + ) + + +def sample_name_pointer(parent=None, root_offset=0, offset=0, **kwargs): + return sample_name_offsets_pointer( + parent, root_offset, offset + ) + parent.offset -tag_ref = Container("tag_ref", +sound_list_header = Container("sound_list_header", UInt32('string_len'), - StrUtf8('name', SIZE='.string_len', WIDGET_WIDTH=60), - UInt32('idx'), - UInt32('perm_count'), + StrUtf8('sound_name', + SIZE='.string_len', + WIDGET_WIDTH=100 + ), + UInt32('sample_id'), + UInt32('sample_count'), ) -header = Struct("header", - UInt32('unknown', DEFAULT=1), - UInt32("tag_count"), +fmod_list_header = Struct("header", + UInt32("version", DEFAULT=1), + UInt32("sound_count"), + SIZE=8 ) - fmod_list_def = TagDef('fmod_list', - header, - Array("tag_refs", SUB_STRUCT=tag_ref, SIZE=".header.tag_count"), + fmod_list_header, + Array("sample_headers", + SUB_STRUCT=sound_list_header, SIZE=".header.sound_count", + DYN_NAME_PATH=".sound_name", WIDGET=DynamicArrayFrame + ), ext='.bin', endian='<' - ) \ No newline at end of file + ) + +chunk_header = BitStruct("header", + Bit("has_next_chunk"), + UBitInt("size", SIZE=24), + UBitEnum("type", + ("channels", 1), + ("frequency", 2), + ("loop", 3), + ("xma_seek", 6), + ("dsp_coeff", 7), + ("xwma_data", 10), + ("vorbis_data", 11), + SIZE=4, + ), + Pad(3), + SIZE=4 + ) + +chunk = Container("chunk", + chunk_header, + BytesRaw("data", SIZE=".header.size"), + ) + +sample_header = BitStruct("header", + Bit("has_next_chunk"), + UBitEnum("frequency", + "invalid", + "hz_8000", + "hz_11000", + "hz_11025", + "hz_16000", + "hz_22050", + "hz_24000", + "hz_32000", + "hz_44100", + "hz_48000", + SIZE=4 + ), + Bit("channel_count"), # add 1 cause at least 1 is assumed + UBitInt("data_qword_offset", SIZE=28), + UBitInt("sample_count", SIZE=30), + SIZE=8, + ) + +sample = Container("sample", + sample_header, + WhileArray("chunks", + CASE=has_next_chunk, SUB_STRUCT=chunk + ), + STEPTREE=BytesRaw("sample_data", + SIZE=sample_data_size, POINTER=sample_data_pointer + ) + ) + +sample_name = QStruct("sample_name", + UInt32("offset"), + STEPTREE=CStrUtf8("name", + POINTER=sample_name_pointer, + WIDGET_WIDTH=100 + ), + SIZE=4, + ) + +fmod_bank_header = Struct("header", + UInt32('sig', DEFAULT=893539142), # 'FSB5' bytes as little-endian uint32 + UInt32("version", DEFAULT=1), + UInt32("sample_count"), + UInt32("sample_headers_size"), + UInt32("sample_names_size"), + UInt32("sample_data_size"), + UEnum32("mode", + "none", + "pcm8", + "pcm16", + "pcm24", + "pcm32", + "pcm_float", + "gcadpcm", + "imaadpcm", + "vag", + "hevag", + "xma", + "mpeg", + "celt", + "at9", + "xwma", + "vorbis", + ), + Pad(8), + StrHex('hash', SIZE=16), + StrHex('dummy', SIZE=8), + SIZE=FMOD_BANK_HEADER_SIZE + ) + +fmod_bank_def = TagDef('fmod_bank', + fmod_bank_header, + Array("samples", + SUB_STRUCT=sample, + SIZE=".header.sample_count" + ), + Array("names", + SUB_STRUCT=sample_name, SIZE=".header.sample_count", + POINTER=sample_name_offsets_pointer, + DYN_NAME_PATH=".name", WIDGET=DynamicArrayFrame, + ), + ext='.fsb', endian='<' + ) From e7d1894bb38f9d195d45cfe8e8259e7c80c9b470 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sat, 20 Jan 2024 01:22:36 -0600 Subject: [PATCH 13/51] fix some sizes --- reclaimer/misc/defs/fmod.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/reclaimer/misc/defs/fmod.py b/reclaimer/misc/defs/fmod.py index d4386aed..73a03a46 100644 --- a/reclaimer/misc/defs/fmod.py +++ b/reclaimer/misc/defs/fmod.py @@ -76,7 +76,7 @@ def sample_name_pointer(parent=None, root_offset=0, offset=0, **kwargs): SIZE='.string_len', WIDGET_WIDTH=100 ), - UInt32('sample_id'), + UInt32('sample_index'), UInt32('sample_count'), ) @@ -183,8 +183,8 @@ def sample_name_pointer(parent=None, root_offset=0, offset=0, **kwargs): "vorbis", ), Pad(8), - StrHex('hash', SIZE=16), - StrHex('dummy', SIZE=8), + StrHex('hash', SIZE=8), + StrHex('guid', SIZE=16), SIZE=FMOD_BANK_HEADER_SIZE ) From ee29200282572bd10c9eda21589075b2e2bb8712 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sat, 20 Jan 2024 01:58:06 -0600 Subject: [PATCH 14/51] Fix bug in reader --- reclaimer/misc/defs/fmod.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/reclaimer/misc/defs/fmod.py b/reclaimer/misc/defs/fmod.py index 73a03a46..15a28f79 100644 --- a/reclaimer/misc/defs/fmod.py +++ b/reclaimer/misc/defs/fmod.py @@ -29,16 +29,19 @@ def has_next_chunk(parent=None, **kwargs): return (parent[-1] if parent else parent.parent).header.has_next_chunk -def sample_data_size(parent=None, root_offset=0, offset=0, **kwargs): +def sample_data_size(parent=None, node=None, root_offset=0, offset=0, **kwargs): + if node is not None: + return len(node) + sample_array = getattr(parent, "parent", None) if not sample_array: return 0 next_sample_index = sample_array.index_by_id(parent) + 1 - start = sample_data_pointer(parent=parent, root_offset=root_offset, offset=offset) + start = sample_data_pointer(parent=parent) if next_sample_index >= len(sample_array): - fsb_header = getattr(getattr(sample_array, "parent", None), "header", None) - data_size = getattr(fsb_header, "sample_data_size", 0) - start + fsb_header = sample_array.parent.header + data_size = fsb_header.sample_data_size - start else: end = sample_data_pointer(parent=sample_array[next_sample_index]) data_size = end - start From 6b0bbf15b3ee98bfa47d851a476655c3bbbbde0e Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sun, 21 Jan 2024 19:14:07 -0600 Subject: [PATCH 15/51] H1 MCC sound work --- reclaimer/common_descs.py | 21 +-- reclaimer/constants.py | 2 +- reclaimer/field_type_methods.py | 5 +- reclaimer/hek/defs/snd_.py | 9 +- reclaimer/mcc_hek/defs/snd_.py | 17 ++- reclaimer/meta/halo1_map.py | 9 +- reclaimer/meta/objs/halo1_rsrc_map.py | 6 +- reclaimer/meta/wrappers/halo1_map.py | 75 ++-------- reclaimer/meta/wrappers/halo1_mcc_map.py | 168 +++++++++++++++------- reclaimer/meta/wrappers/halo1_rsrc_map.py | 125 +++++++++++----- reclaimer/meta/wrappers/halo_map.py | 5 +- reclaimer/misc/defs/fmod.py | 21 +-- 12 files changed, 280 insertions(+), 183 deletions(-) diff --git a/reclaimer/common_descs.py b/reclaimer/common_descs.py index fdd3091f..5f050c6c 100644 --- a/reclaimer/common_descs.py +++ b/reclaimer/common_descs.py @@ -57,28 +57,31 @@ def tag_class(*args, **kwargs): ) -def reflexive(name, substruct, max_count=MAX_REFLEXIVE_COUNT, *names, **desc): +def reflexive(name, substruct, max_count=MAX_REFLEXIVE_COUNT, *names, **kwargs): '''This function serves to macro the creation of a reflexive''' - desc.update( - INCLUDE=reflexive_struct, + reflexive_fields = ( + SInt32("size", VISIBLE=VISIBILITY_METADATA, EDITABLE=False, MAX=max_count), + reflexive_struct[1], + reflexive_struct[2], + ) + kwargs.update( STEPTREE=ReflexiveArray(name + "_array", - SIZE=".size", MAX=max_count, - SUB_STRUCT=substruct, WIDGET=ReflexiveFrame + SIZE=".size", SUB_STRUCT=substruct, WIDGET=ReflexiveFrame ), SIZE=12 ) - if DYN_NAME_PATH in desc: - desc[STEPTREE][DYN_NAME_PATH] = desc.pop(DYN_NAME_PATH) + if DYN_NAME_PATH in kwargs: + kwargs[STEPTREE][DYN_NAME_PATH] = kwargs.pop(DYN_NAME_PATH) if names: name_map = {} for i in range(len(names)): e_name = BlockDef.str_to_name(None, names[i]) name_map[e_name] = i - desc[STEPTREE][NAME_MAP] = name_map + kwargs[STEPTREE][NAME_MAP] = name_map - return Reflexive(name, **desc) + return Reflexive(name, *reflexive_fields, **kwargs) def get_raw_reflexive_offsets(desc, two_byte_offs, four_byte_offs, off=0): if INCLUDE in desc: diff --git a/reclaimer/constants.py b/reclaimer/constants.py index 7ca5d7ae..44932aba 100644 --- a/reclaimer/constants.py +++ b/reclaimer/constants.py @@ -192,7 +192,7 @@ def inject_halo_constants(): "UNUSED14", "DXN", "CTX1", "DXT3A", "DXT3Y", "DXT5A", "DXT5Y", "DXT5AY") MCC_FORMAT_NAME_MAP = FORMAT_NAME_MAP[:FORMAT_NAME_MAP.index("P8")] + ("BC7", ) -I_FORMAT_NAME_MAP = {fmt: i for i, fmt in enumerate(FORMAT_NAME_MAP)} +I_FORMAT_NAME_MAP = {fmt: i for i, fmt in enumerate(FORMAT_NAME_MAP)} I_MCC_FORMAT_NAME_MAP = {fmt: i for i, fmt in enumerate(MCC_FORMAT_NAME_MAP)} #each bitmap's number of bytes must be a multiple of 512 diff --git a/reclaimer/field_type_methods.py b/reclaimer/field_type_methods.py index 498a0b16..ddaa6042 100644 --- a/reclaimer/field_type_methods.py +++ b/reclaimer/field_type_methods.py @@ -230,12 +230,13 @@ def reflexive_parser(self, desc, node=None, parent=None, attr_index=None, if s_desc: pointer_converter = kwargs.get('map_pointer_converter') safe_mode = kwargs.get("safe_mode", True) and not desc.get(IGNORE_SAFE_MODE) + arr_len_max = desc[0].get(MAX, 0) # get the max value from the size field if pointer_converter is not None: file_ptr = pointer_converter.v_ptr_to_f_ptr(node[1]) if safe_mode: # make sure the reflexive sizes are within sane bounds. - node[0] = min(node[0], max(SANE_MAX_REFLEXIVE_COUNT, s_desc.get(MAX, 0))) + node[0] = min(node[0], max(SANE_MAX_REFLEXIVE_COUNT, arr_len_max)) if (file_ptr < 0 or file_ptr + node[0]*s_desc[SUB_STRUCT].get(SIZE, 0) > len(rawdata)): @@ -243,7 +244,7 @@ def reflexive_parser(self, desc, node=None, parent=None, attr_index=None, # (ex: bad hek+ extraction) node[0] = node[1] = 0 - elif node[0] > max(SANE_MAX_REFLEXIVE_COUNT, s_desc.get(MAX, 0)): + elif node[0] > max(SANE_MAX_REFLEXIVE_COUNT, arr_len_max): raise ValueError("Reflexive size is above highest allowed value.") if not node[0]: diff --git a/reclaimer/hek/defs/snd_.py b/reclaimer/hek/defs/snd_.py index ab29cd73..a5960edf 100644 --- a/reclaimer/hek/defs/snd_.py +++ b/reclaimer/hek/defs/snd_.py @@ -67,22 +67,21 @@ """ ) - permutation = Struct('permutation', ascii_str32("name"), Float("skip_fraction"), Float("gain", DEFAULT=1.0), compression, SInt16("next_permutation_index", DEFAULT=-1), - FlSInt32("unknown0", VISIBLE=False), - FlUInt32("unknown1", VISIBLE=False), # always zero? - FlUInt32("unknown2", VISIBLE=False), + UInt32("sample_data_pointer", VISIBLE=False), + UInt32("unknown", VISIBLE=False), # always zero? + UInt32("parent_tag_id", VISIBLE=False), # this is actually the required length of the ogg # decompression buffer. For "none" compression, this # mirrors samples.size, so a more appropriate name # for this field should be pcm_buffer_size FlUInt32("ogg_sample_count", EDITABLE=False), - FlUInt32("unknown3", VISIBLE=False), # seems to always be == unknown2 + UInt32("parent_tag_id2", VISIBLE=False), rawdata_ref("samples", max_size=4194304, widget=SoundSampleFrame), rawdata_ref("mouth_data", max_size=8192), rawdata_ref("subtitle_data", max_size=512), diff --git a/reclaimer/mcc_hek/defs/snd_.py b/reclaimer/mcc_hek/defs/snd_.py index 98f4d216..82f3fe76 100644 --- a/reclaimer/mcc_hek/defs/snd_.py +++ b/reclaimer/mcc_hek/defs/snd_.py @@ -16,12 +16,27 @@ "split_long_sound_into_permutations", "thirsty_grunt", ) +# found from sapien error log: +# EXCEPTION halt in c:\mcc\qfe1\h1\code\h1a2\sources\cache\pc_sound_cache.c,#157: !TEST_FLAG(sound->runtime_flags, _sound_permutation_cached_bit) +mcc_runtime_perm_flags = UInt32("runtime_flags", + # NOTES: + # this is set, even if the samples are NOT cached. not sure why + # also, seems like the above exception trips if ANY bits are set + VISIBLE=False + ) +permutation = desc_variant(permutation, + ("parent_tag_id", mcc_runtime_perm_flags), + ("parent_tag_id2", UInt32("parent_tag_id")), + ) +pitch_range = desc_variant(pitch_range, + ("permutations", reflexive("permutations", permutation, 256, DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True)), + ) mcc_snd__body = desc_variant(snd__body, ("flags", mcc_snd__flags), + ("pitch_ranges", reflexive("pitch_ranges", pitch_range, 8, DYN_NAME_PATH='.name')), ) - def get(): return snd__def diff --git a/reclaimer/meta/halo1_map.py b/reclaimer/meta/halo1_map.py index d78d0489..a30ef6bc 100644 --- a/reclaimer/meta/halo1_map.py +++ b/reclaimer/meta/halo1_map.py @@ -249,10 +249,17 @@ def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): UEnum32('foot', ('foot', 'foot'), DEFAULT='foot', OFFSET=2044), SIZE=2048 ) + +mcc_flags = Bool8("mcc_flags", + # NOTE: this is probably a set of bools, but idk what they are, and idc enough to find out + "use_classic_unk0", + "use_classic_unk1", + "use_classic_unk2", + ) map_header_mcc = desc_variant( map_header, - ("pad_12", UEnum8("enable_remastered_graphics", "yes", "no")), + ("pad_12", mcc_flags), ) map_header_vap = desc_variant( diff --git a/reclaimer/meta/objs/halo1_rsrc_map.py b/reclaimer/meta/objs/halo1_rsrc_map.py index 4624a927..37394152 100644 --- a/reclaimer/meta/objs/halo1_rsrc_map.py +++ b/reclaimer/meta/objs/halo1_rsrc_map.py @@ -333,11 +333,11 @@ def _add_sound(self, tag_path, new_tag, base_pointer, depreciate): meta_head, samp_head = rsrc_tags[-1], rsrc_tags[-2] for pr in tag_data.pitch_ranges.STEPTREE: - pr.unknown0 = 1.0 + pr.playback_rate = 1.0 pr.unknown1 = pr.unknown2 = -1 for perm in pr.permutations.STEPTREE: - perm.unknown1 = 0 - perm.unknown3 = perm.unknown2 = 0xFFffFFff + perm.unknown = 0 + perm.parent_tag_id = perm.parent_tag_id2 = 0xFFffFFff sample_data = perm.samples.data if perm.compression.enum_name == "none": sample_data = array(">h", sample_data) diff --git a/reclaimer/meta/wrappers/halo1_map.py b/reclaimer/meta/wrappers/halo1_map.py index 9bfca9d8..feb7734c 100644 --- a/reclaimer/meta/wrappers/halo1_map.py +++ b/reclaimer/meta/wrappers/halo1_map.py @@ -860,51 +860,8 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): if not byteswap: break byteswap_animation(anim) - elif tag_cls == "bitm": - # set the size of the compressed plate data to nothing - meta.compressed_color_plate_data.STEPTREE = BytearrayBuffer() - - # to enable compatibility with my bitmap converter we'll set the - # base address to a certain constant based on the console platform - is_xbox = get_is_xbox_map(engine) - - new_pixels_offset = 0 - - # uncheck the prefer_low_detail flag and - # set up the pixels_offset correctly. - for bitmap in meta.bitmaps.STEPTREE: - bitmap.flags.prefer_low_detail = is_xbox - bitmap.pixels_offset = new_pixels_offset - new_pixels_offset += bitmap.pixels_meta_size - - # clear some meta-only fields - bitmap.pixels_meta_size = 0 - bitmap.bitmap_id_unknown1 = bitmap.bitmap_id_unknown2 = 0 - bitmap.bitmap_data_pointer = 0 - - if is_xbox: - bitmap.base_address = 1073751810 - if "dxt" in bitmap.format.enum_name: - # need to correct mipmap count on xbox dxt bitmaps. - # the game seems to prune the mipmap texels for any - # mipmaps whose dimensions are 2x2 or smaller - - max_dim = max(bitmap.width, bitmap.height) - if 2 ** bitmap.mipmaps > max_dim: - # make sure the mipmap level isnt higher than the - # number of mipmaps that should be able to exist. - bitmap.mipmaps = int(log(max_dim, 2)) - - last_mip_dim = max_dim // (2 ** bitmap.mipmaps) - if last_mip_dim == 1: - bitmap.mipmaps -= 2 - elif last_mip_dim == 2: - bitmap.mipmaps -= 1 - - if bitmap.mipmaps < 0: - bitmap.mipmaps = 0 - else: - bitmap.base_address = 0 + elif tag_cls in ("bitm", "snd!"): + meta = Halo1RsrcMap.meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs) elif tag_cls == "cdmg": # divide camera shaking wobble period by 30 @@ -1128,6 +1085,9 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): for cluster in meta.clusters.STEPTREE: predicted_resources.append(cluster.predicted_resources) + + for coll_mat in meta.collision_materials.STEPTREE: + coll_mat.unknown = 0 # supposed to be 0 in tag form compressed = "xbox" in engine or engine in ("stubbs", "shadowrun_proto") @@ -1307,15 +1267,6 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): b.fade_out_time /= 30 b.up_time /= 30 - elif tag_cls == "snd!": - meta.maximum_bend_per_second = meta.maximum_bend_per_second ** 30 - for pitch_range in meta.pitch_ranges.STEPTREE: - if not byteswap: break - for permutation in pitch_range.permutations.STEPTREE: - if permutation.compression.enum_name == "none": - # byteswap pcm audio - byteswap_pcm16_samples(permutation.samples) - elif tag_cls == "shpp": predicted_resources.append(meta.predicted_resources) @@ -1373,7 +1324,7 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): return meta def get_resource_map_paths(self, maps_dir=""): - if self.is_resource or self.engine not in ("halo1pc", "halo1pcdemo", "halo1mcc", + if self.is_resource or self.engine not in ("halo1pc", "halo1pcdemo", "halo1ce", "halo1yelo", "halo1vap"): return {} @@ -1381,9 +1332,6 @@ def get_resource_map_paths(self, maps_dir=""): if self.engine not in ("halo1ce", "halo1yelo", "halo1vap"): map_paths.pop('loc') - if self.engine == "halo1mcc": - map_paths.pop('sounds') - data_files = False if hasattr(self.map_header, "yelo_header"): data_files = self.map_header.yelo_header.flags.uses_mod_data_files @@ -1412,10 +1360,15 @@ def get_resource_map_paths(self, maps_dir=""): def generate_map_info_string(self): string = HaloMap.generate_map_info_string(self) index, header = self.tag_index, self.map_header - + if self.engine == "halo1mcc": - string += """ - supports remastered == %s""" % header.enable_remastered_graphics.enum_name + # NOTE: these flags are subject to change, so + # for now they're dynamically named + string += "\n remastered flags:" + for name in header.mcc_flags.NAME_MAP: + line = "\n " + (name.replace("_", " ")) + line += " " * (29-len(line)) + "== " + string += line + str(bool(header.mcc_flags[name])) string += """ diff --git a/reclaimer/meta/wrappers/halo1_mcc_map.py b/reclaimer/meta/wrappers/halo1_mcc_map.py index a1798944..66613db6 100644 --- a/reclaimer/meta/wrappers/halo1_mcc_map.py +++ b/reclaimer/meta/wrappers/halo1_mcc_map.py @@ -6,9 +6,13 @@ # Reclaimer is free software under the GNU General Public License v3.0. # See LICENSE for more information. # +from pathlib import Path from reclaimer.meta.wrappers.halo1_map import Halo1Map from reclaimer.mcc_hek.handler import MCCHaloHandler from reclaimer.mcc_hek.defs.sbsp import sbsp_meta_header_def +from reclaimer.meta.wrappers.halo1_rsrc_map import uses_external_sounds + +from supyr_struct.util import is_path_empty class Halo1MccMap(Halo1Map): '''Masterchief Collection Halo 1 map''' @@ -22,55 +26,119 @@ class Halo1MccMap(Halo1Map): def __init__(self, maps=None): Halo1Map.__init__(self, maps) + + @property + def uses_fmod_sound_bank(self): + try: + return self.map_header.mcc_flags.data + except AttributeError: + return False + + @property + def uses_sounds_map(self): + return not self.uses_fmod_sound_bank + + def get_resource_map_paths(self, maps_dir=""): + if self.is_resource: + return {} + + map_paths = dict(bitmaps=None, sounds=None) + + # NOTE: for now we're just checking if ANY of these flags are set. + # if they ARE, we assume we're using classic resources, which + # means reading from sounds.map. if they're NOT set, we still + # expect bitmap resource data to be in bitmaps.map, but the + # external sound data is in fmod sound banks. + if self.uses_fmod_sound_bank: + map_paths.pop("sounds") + + maps_dir = self.filepath.parent if is_path_empty(maps_dir) else Path(maps_dir) + + # detect the map paths for the resource maps + for map_name in sorted(map_paths.keys()): + map_path = maps_dir.joinpath(map_name + ".map") + if self.maps.get(map_name) is not None: + map_paths[map_name] = self.maps[map_name].filepath + elif map_path.is_file(): + map_paths[map_name] = map_path + + return map_paths + + def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): + # for sounds, ensure we can extract ALL their sample data from either + # resource map or primary map before potentially overwriting local + # tag files with them. remastered sounds are stored in fmod, and + # we can't extract those tags without missing the sample data + sounds = self.maps.get("sounds") + sounds_data = getattr(sounds, "map_data", None) + if tag_cls == "snd!" and not(self.uses_sounds_map and sounds_data): + if uses_external_sounds(meta): + # no sounds.map to read sounds from, and sound + # data is specified as external. can't extract + return None + + meta = super().meta_to_tag_data(meta, tag_cls, tag_index_ref, **kwargs) + if tag_cls == "sbsp": + for lm in meta.lightmaps.STEPTREE: + for mat in lm.materials.STEPTREE: + mat.lightmap_vertices_offset = 0 + mat.vertices_offset = 0 + + return meta def inject_rawdata(self, meta, tag_cls, tag_index_ref): - if tag_cls != "sbsp": - return super().inject_rawdata(meta, tag_cls, tag_index_ref) - - tag_id = tag_index_ref.id & 0xFFFF - bsp_header = self.bsp_headers.get(tag_id, None) - if bsp_header is None: - raise ValueError("No bsp header found for tag %s of type %s" % ( - tag_id, tag_cls, - )) - - uc_sector_start = bsp_header.uncompressed_render_vertices_pointer - uc_sector_size = bsp_header.uncompressed_render_vertices_size - c_sector_start = bsp_header.compressed_render_vertices_pointer - c_sector_size = bsp_header.compressed_render_vertices_size - uc_sector_end = uc_sector_start + uc_sector_size - c_sector_end = c_sector_start + c_sector_size - map_data = self.map_data - - # mcc render geometry isn't stored the same way as custom edition/xbox. - # it's stored relative to the pointers in the sbsp meta header, and the - # size of the verts isn't calculated into the size of the sbsp sector. - for lm in meta.lightmaps.STEPTREE: - for mat in lm.materials.STEPTREE: - if mat.vertex_type.enum_name == "compressed": - data_block = mat.compressed_vertices - start, end = c_sector_start, c_sector_end - vert_size, lm_vert_size = 32, 8 - else: - data_block = mat.uncompressed_vertices - start, end = uc_sector_start, uc_sector_end - vert_size, lm_vert_size = 56, 20 - - verts_offset = start + mat.vertices_offset - lm_verts_offset = start + mat.lightmap_vertices_offset - verts_size = vert_size * mat.vertices_count - lm_verts_size = lm_vert_size * mat.lightmap_vertices_count - vert_data = b'' - if verts_size and verts_size + verts_offset > end: - print("Warning: Render vertices pointed to outside sector.") - elif verts_size: - map_data.seek(verts_offset) - vert_data += map_data.read(verts_size) - - if lm_verts_size and lm_verts_size + lm_verts_offset > end: - print("Warning: Lightmap vertices pointed to outside sector.") - elif lm_verts_size: - map_data.seek(lm_verts_offset) - vert_data += map_data.read(lm_verts_size) - - data_block.data = vert_data \ No newline at end of file + if tag_cls == "snd!" and self.uses_fmod_sound_bank and uses_external_sounds(meta): + # no sounds.map to read sounds from, and sound + # data is specified as external. can't extract + return None + elif tag_cls == "sbsp": + # mcc render geometry isn't stored the same way as custom edition/xbox. + # it's stored relative to the pointers in the sbsp meta header, and the + # size of the verts isn't calculated into the size of the sbsp sector. + + tag_id = tag_index_ref.id & 0xFFFF + bsp_header = self.bsp_headers.get(tag_id, None) + if bsp_header is None: + raise ValueError("No bsp header found for tag %s of type %s" % ( + tag_id, tag_cls, + )) + + uc_sector_start = bsp_header.uncompressed_render_vertices_pointer + uc_sector_size = bsp_header.uncompressed_render_vertices_size + c_sector_start = bsp_header.compressed_render_vertices_pointer + c_sector_size = bsp_header.compressed_render_vertices_size + uc_sector_end = uc_sector_start + uc_sector_size + c_sector_end = c_sector_start + c_sector_size + map_data = self.map_data + + for lm in meta.lightmaps.STEPTREE: + for mat in lm.materials.STEPTREE: + if mat.vertex_type.enum_name == "compressed": + data_block = mat.compressed_vertices + start, end = c_sector_start, c_sector_end + vert_size, lm_vert_size = 32, 8 + else: + data_block = mat.uncompressed_vertices + start, end = uc_sector_start, uc_sector_end + vert_size, lm_vert_size = 56, 20 + + verts_offset = start + mat.vertices_offset + lm_verts_offset = start + mat.lightmap_vertices_offset + verts_size = vert_size * mat.vertices_count + lm_verts_size = lm_vert_size * mat.lightmap_vertices_count + vert_data = b'' + if verts_size and verts_size + verts_offset > end: + print("Warning: Render vertices pointed to outside sector.") + elif verts_size: + map_data.seek(verts_offset) + vert_data += map_data.read(verts_size) + + if lm_verts_size and lm_verts_size + lm_verts_offset > end: + print("Warning: Lightmap vertices pointed to outside sector.") + elif lm_verts_size: + map_data.seek(lm_verts_offset) + vert_data += map_data.read(lm_verts_size) + + data_block.data = vert_data + else: + return super().inject_rawdata(meta, tag_cls, tag_index_ref) \ No newline at end of file diff --git a/reclaimer/meta/wrappers/halo1_rsrc_map.py b/reclaimer/meta/wrappers/halo1_rsrc_map.py index 646077de..0fd04819 100644 --- a/reclaimer/meta/wrappers/halo1_rsrc_map.py +++ b/reclaimer/meta/wrappers/halo1_rsrc_map.py @@ -58,10 +58,6 @@ def inject_sound_data(map_data, rsrc_data, rawdata_ref, map_magic): - if not rawdata_ref.size: - rawdata_ref.data = b'' - return - if rawdata_ref.flags.data_in_resource_map: data, ptr = rsrc_data, rawdata_ref.raw_pointer elif rawdata_ref.pointer == 0: @@ -69,8 +65,24 @@ def inject_sound_data(map_data, rsrc_data, rawdata_ref, map_magic): else: data, ptr = map_data, rawdata_ref.pointer + map_magic - data.seek(ptr) - rawdata_ref.data = data.read(rawdata_ref.size) + if data and rawdata_ref.size: + data.seek(ptr) + rawdata_ref.data = data.read(rawdata_ref.size) + else: + # hack to ensure the size is preserved when + # we replace the rawdata with empty bytes + size = rawdata_ref.size + rawdata_ref.data = b'' + rawdata_ref.size = size + + +def uses_external_sounds(sound_meta): + for pitches in sound_meta.pitch_ranges.STEPTREE: + for perm in pitches.permutations.STEPTREE: + for b in (perm.samples, perm.mouth_data, perm.subtitle_data): + if b.flags.data_in_resource_map: + return True + return False class Halo1RsrcMap(HaloMap): @@ -260,16 +272,20 @@ def get_meta(self, tag_id, reextract=False, **kw): return block[0] def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): - magic = self.map_magic - engine = self.engine - map_data = self.map_data - tag_index = self.tag_index - is_xbox = get_is_xbox_map(engine) + magic = self.map_magic + engine = self.engine + map_data = self.map_data + tag_index = self.tag_index + byteswap = kwargs.get("byteswap", True) if tag_cls == "bitm": # set the size of the compressed plate data to nothing meta.compressed_color_plate_data.STEPTREE = BytearrayBuffer() + # to enable compatibility with my bitmap converter we'll set the + # base address to a certain constant based on the console platform + is_xbox = get_is_xbox_map(engine) + new_pixels_offset = 0 # uncheck the prefer_low_detail flag and @@ -282,15 +298,48 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): # clear some meta-only fields bitmap.pixels_meta_size = 0 bitmap.bitmap_id_unknown1 = bitmap.bitmap_id_unknown2 = 0 - bitmap.bitmap_data_pointer = bitmap.base_address = 0 + bitmap.bitmap_data_pointer = 0 + + if is_xbox: + bitmap.base_address = 1073751810 + if "dxt" in bitmap.format.enum_name: + # need to correct mipmap count on xbox dxt bitmaps. + # the game seems to prune the mipmap texels for any + # mipmaps whose dimensions are 2x2 or smaller + + max_dim = max(bitmap.width, bitmap.height) + if 2 ** bitmap.mipmaps > max_dim: + # make sure the mipmap level isnt higher than the + # number of mipmaps that should be able to exist. + bitmap.mipmaps = int(log(max_dim, 2)) + + last_mip_dim = max_dim // (2 ** bitmap.mipmaps) + if last_mip_dim == 1: + bitmap.mipmaps -= 2 + elif last_mip_dim == 2: + bitmap.mipmaps -= 1 + + if bitmap.mipmaps < 0: + bitmap.mipmaps = 0 + else: + bitmap.base_address = 0 elif tag_cls == "snd!": meta.maximum_bend_per_second = meta.maximum_bend_per_second ** 30 for pitch_range in meta.pitch_ranges.STEPTREE: - for permutation in pitch_range.permutations.STEPTREE: - if permutation.compression.enum_name == "none": + for perm in pitch_range.permutations.STEPTREE: + if byteswap and perm.compression.enum_name == "none": # byteswap pcm audio - byteswap_pcm16_samples(permutation.samples) + byteswap_pcm16_samples(perm.samples) + + perm.sample_data_pointer = perm.parent_tag_id = perm.unknown = 0 + if hasattr(perm, "runtime_flags"): # mcc + perm.runtime_flags = 0 + else: # non-mcc + perm.parent_tag_id2 = 0 + + for b in (perm.samples, perm.mouth_data, perm.subtitle_data): + b.flags.data_in_resource_map = False return meta @@ -302,14 +351,10 @@ def inject_rawdata(self, meta, tag_cls, tag_index_ref): magic = self.map_magic engine = self.engine - map_data = self.map_data - - try: bitmap_data = bitmaps.map_data - except Exception: bitmap_data = None - try: sound_data = sounds.map_data - except Exception: sound_data = None - try: loc_data = loc.map_data - except Exception: loc_data = None + map_data = self.map_data + bitmap_data = getattr(bitmaps, "map_data", None) + sound_data = getattr(sounds, "map_data", None) + loc_data = getattr(loc, "map_data", None) is_not_indexed = not self.is_indexed(tag_index_ref.id & 0xFFff) might_be_in_rsrc = engine in ("halo1pc", "halo1pcdemo", "halo1mcc", @@ -365,30 +410,34 @@ def inject_rawdata(self, meta, tag_cls, tag_index_ref): b = meta.string loc_data.seek(b.pointer + meta_offset) meta.string.data = loc_data.read(b.size).decode('utf-16-le') + elif tag_cls == "snd!": # might need to get samples and permutations from the resource map - is_pc = engine in ("halo1pc", "halo1pcdemo") - is_ce = engine in ("halo1ce", "halo1yelo", "halo1vap", "halo1mcc") - if not(is_pc or is_ce): - return meta - elif sound_data is None: - return + is_pc = engine in ("halo1pc", "halo1pcdemo") + is_ce = engine in ("halo1ce", "halo1yelo", "halo1vap") + is_mcc = engine == "halo1mcc" # ce tagpaths are in the format: path__permutations # ex: sound\sfx\impulse\coolant\enter_water__permutations # # pc tagpaths are in the format: path__pitchrange__permutation # ex: sound\sfx\impulse\coolant\enter_water__0__0 - other_data = map_data - sound_magic = 0 - magic - # DO NOT optimize this section. The logic is like this on purpose - if is_pc: - pass - elif self.is_resource: - other_data = sound_data - sound_magic = tag_index_ref.meta_offset + meta.get_size() - elif sounds is None: + + if not(is_pc or is_ce or is_mcc): + # not pc, ce, or mcc, so sound data is read on initial tag parse return + elif self.is_resource and is_ce: + # ce sounds.map contain tagdata, not just sample data. + # HOWEVER, the pointers in the tag data are relative to + # the END of the tag(idky), so we set the magic to it. + other_data = sound_data # reading for resource, so sound map IS map data + sound_magic = tag_index_ref.meta_offset + meta.get_size() + else: + # either samples are in resource map and are pointed to with + # the raw pointer(relative to file start), or are in the main + # map and are pointed to with the magic-relative pointer + other_data = map_data + sound_magic = 0 - magic for pitches in meta.pitch_ranges.STEPTREE: for perm in pitches.permutations.STEPTREE: diff --git a/reclaimer/meta/wrappers/halo_map.py b/reclaimer/meta/wrappers/halo_map.py index 6759c6de..7f962578 100644 --- a/reclaimer/meta/wrappers/halo_map.py +++ b/reclaimer/meta/wrappers/halo_map.py @@ -319,10 +319,7 @@ def load_resource_maps(self, maps_dir="", map_paths=(), **kw): print("Loading %s..." % map_name) new_map.load_map(map_path, **kw) - if self.engine == "halo1mcc" and ( - (new_map.engine == "halo1pc" and new_map.map_name == "bitmaps") or - new_map.engine == "halo1ce" - ): + if self.engine == "halo1mcc" and new_map.engine in ("halo1pc", "halo1ce"): # cant tell the difference between mcc, ce, and pc resource maps new_map.engine = self.engine diff --git a/reclaimer/misc/defs/fmod.py b/reclaimer/misc/defs/fmod.py index 15a28f79..edf9e390 100644 --- a/reclaimer/misc/defs/fmod.py +++ b/reclaimer/misc/defs/fmod.py @@ -102,13 +102,18 @@ def sample_name_pointer(parent=None, root_offset=0, offset=0, **kwargs): Bit("has_next_chunk"), UBitInt("size", SIZE=24), UBitEnum("type", - ("channels", 1), - ("frequency", 2), - ("loop", 3), - ("xma_seek", 6), - ("dsp_coeff", 7), - ("xwma_data", 10), - ("vorbis_data", 11), + "invalid", + "channels", + "frequency", + "loop", + "unknown4", + "unknown5", + "xma_seek", + "dsp_coeff", + "unknown8", + "unknown9", + "xwma_data", + "vorbis_data", SIZE=4, ), Pad(3), @@ -122,7 +127,7 @@ def sample_name_pointer(parent=None, root_offset=0, offset=0, **kwargs): sample_header = BitStruct("header", Bit("has_next_chunk"), - UBitEnum("frequency", + UBitEnum("frequency", "invalid", "hz_8000", "hz_11000", From 5b7094e2e6c9e48a54ba9eedcfac5e79593f8359 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sun, 21 Jan 2024 22:06:01 -0600 Subject: [PATCH 16/51] Recalculate pointers in fmod files when saving --- reclaimer/field_type_methods.py | 7 ++++- reclaimer/misc/defs/fmod.py | 36 ++++++++++++++-------- reclaimer/misc/defs/objs/fmod.py | 52 ++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 reclaimer/misc/defs/objs/fmod.py diff --git a/reclaimer/field_type_methods.py b/reclaimer/field_type_methods.py index ddaa6042..1e6fc059 100644 --- a/reclaimer/field_type_methods.py +++ b/reclaimer/field_type_methods.py @@ -236,7 +236,12 @@ def reflexive_parser(self, desc, node=None, parent=None, attr_index=None, file_ptr = pointer_converter.v_ptr_to_f_ptr(node[1]) if safe_mode: # make sure the reflexive sizes are within sane bounds. - node[0] = min(node[0], max(SANE_MAX_REFLEXIVE_COUNT, arr_len_max)) + max_size = max(SANE_MAX_REFLEXIVE_COUNT, arr_len_max) + if node[0] > max_size: + print("Warning: Clipped %s reflexive size from %s to %s" % ( + desc[NAME], size, new_size + )) + node[0] = max(SANE_MAX_REFLEXIVE_COUNT, arr_len_max) if (file_ptr < 0 or file_ptr + node[0]*s_desc[SUB_STRUCT].get(SIZE, 0) > len(rawdata)): diff --git a/reclaimer/misc/defs/fmod.py b/reclaimer/misc/defs/fmod.py index edf9e390..1963418d 100644 --- a/reclaimer/misc/defs/fmod.py +++ b/reclaimer/misc/defs/fmod.py @@ -8,10 +8,10 @@ # from reclaimer.common_descs import * +from .objs.fmod import FModSoundBankTag,\ + FMOD_BANK_HEADER_SIZE, FMOD_SAMPLE_CHUNK_SIZE from supyr_struct.defs.tag_def import TagDef -FMOD_BANK_HEADER_SIZE = 60 - def get(): return fmod_bank_def, fmod_list_def @@ -29,24 +29,36 @@ def has_next_chunk(parent=None, **kwargs): return (parent[-1] if parent else parent.parent).header.has_next_chunk -def sample_data_size(parent=None, node=None, root_offset=0, offset=0, **kwargs): +def sample_data_size( + parent=None, node=None, attr_index=None, + new_val=None, root_offset=0, offset=0, rawdata=None, + **kwargs + ): + if new_val is not None: + # can't set size here + return + elif parent is not None and attr_index is not None: + node = parent[attr_index] + if node is not None: return len(node) sample_array = getattr(parent, "parent", None) - if not sample_array: + if not sample_array or not rawdata: + # NOTE: checking if rawdata is passed to indicate that + # we're actually trying to parse from something. + # if it's not, we've appended an empty block. return 0 next_sample_index = sample_array.index_by_id(parent) + 1 - start = sample_data_pointer(parent=parent) if next_sample_index >= len(sample_array): - fsb_header = sample_array.parent.header - data_size = fsb_header.sample_data_size - start + start = parent.header.data_qword_offset * FMOD_SAMPLE_CHUNK_SIZE + end = sample_array.parent.header.sample_data_size else: - end = sample_data_pointer(parent=sample_array[next_sample_index]) - data_size = end - start + start = sample_data_pointer(parent=parent) + end = sample_data_pointer(parent=sample_array[next_sample_index]) - return max(0, data_size) + return max(0, end - start) def sample_data_pointer(parent=None, root_offset=0, offset=0, **kwargs): @@ -56,7 +68,7 @@ def sample_data_pointer(parent=None, root_offset=0, offset=0, **kwargs): root_offset + offset + FMOD_BANK_HEADER_SIZE + bank_header.sample_headers_size + bank_header.sample_names_size + - sample_header.data_qword_offset * 16 + sample_header.data_qword_offset * FMOD_SAMPLE_CHUNK_SIZE ) @@ -207,5 +219,5 @@ def sample_name_pointer(parent=None, root_offset=0, offset=0, **kwargs): POINTER=sample_name_offsets_pointer, DYN_NAME_PATH=".name", WIDGET=DynamicArrayFrame, ), - ext='.fsb', endian='<' + ext='.fsb', endian='<', tag_cls=FModSoundBankTag ) diff --git a/reclaimer/misc/defs/objs/fmod.py b/reclaimer/misc/defs/objs/fmod.py new file mode 100644 index 00000000..182b29b7 --- /dev/null +++ b/reclaimer/misc/defs/objs/fmod.py @@ -0,0 +1,52 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from supyr_struct.tag import Tag + +FMOD_BANK_HEADER_SIZE = 60 +FMOD_SAMPLE_CHUNK_SIZE = 16 +FMOD_SAMPLE_DATA_ALIGN = 32 + +class FModSoundBankTag(Tag): + + def set_pointers(self, offset=0): + header = self.data.header + samples = self.data.samples + names = self.data.names + if len(samples) != len(names): + raise ValueError("Number of samples does not match number of names.") + + header.sample_count = len(samples) + sample_headers_size = sample_names_size = sample_data_size = 0 + + for sample in samples: + sample_headers_size += sample.header.binsize + sample.chunks.binsize + sample.header.data_qword_offset = sample_data_size + sample_size = ( + len(sample.sample_data) + FMOD_SAMPLE_CHUNK_SIZE - 1 + ) // FMOD_SAMPLE_CHUNK_SIZE + + sample_data_size += sample_size + + sample_names_size += 4*header.sample_count # string offset sizes + for name in names: + name.offset = sample_names_size + sample_names_size += len(name.name) + 1 # add 1 for null terminator + + sample_data_off = FMOD_BANK_HEADER_SIZE + sample_headers_size + sample_names_size + sample_name_padding = ( + (FMOD_SAMPLE_DATA_ALIGN - sample_data_off % FMOD_SAMPLE_DATA_ALIGN) + ) % FMOD_SAMPLE_DATA_ALIGN + header.sample_headers_size = sample_headers_size + header.sample_names_size = sample_names_size + sample_name_padding + header.sample_data_size = sample_data_size*FMOD_SAMPLE_CHUNK_SIZE + + def serialize(self, *args, **kwargs): + self.set_pointers() + super().serialize(*args, **kwargs) \ No newline at end of file From ed986134fc54962163f9a137e9d4d6c8ec09680d Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Mon, 22 Jan 2024 00:55:27 -0600 Subject: [PATCH 17/51] Fix MCC sound map being used improperly and filthy part indices not being set --- reclaimer/meta/wrappers/halo1_map.py | 2 +- reclaimer/meta/wrappers/halo1_mcc_map.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/reclaimer/meta/wrappers/halo1_map.py b/reclaimer/meta/wrappers/halo1_map.py index feb7734c..c07bfe57 100644 --- a/reclaimer/meta/wrappers/halo1_map.py +++ b/reclaimer/meta/wrappers/halo1_map.py @@ -1001,7 +1001,7 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): vert_size = 68 # null out certain things in the part - part.previous_part_index = part.next_part_index = 0 + #part.previous_part_index = part.next_part_index = 0 part.centroid_primary_node = 0 part.centroid_secondary_node = 0 part.centroid_primary_weight = 0.0 diff --git a/reclaimer/meta/wrappers/halo1_mcc_map.py b/reclaimer/meta/wrappers/halo1_mcc_map.py index 66613db6..af129ce0 100644 --- a/reclaimer/meta/wrappers/halo1_mcc_map.py +++ b/reclaimer/meta/wrappers/halo1_mcc_map.py @@ -30,7 +30,7 @@ def __init__(self, maps=None): @property def uses_fmod_sound_bank(self): try: - return self.map_header.mcc_flags.data + return not self.map_header.mcc_flags.data except AttributeError: return False From aeb8c44c0fb71da5f0981dad80dc492058144aea Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Tue, 23 Jan 2024 16:28:17 -0600 Subject: [PATCH 18/51] Better support for MCC resource files --- reclaimer/field_type_methods.py | 2 +- reclaimer/halo_script/hsc.py | 23 +++++- reclaimer/hek/defs/coll.py | 18 ++++- reclaimer/hek/defs/lsnd.py | 2 +- reclaimer/meta/halo1_map.py | 9 +-- reclaimer/meta/wrappers/halo1_anni_map.py | 88 ++++++++++++++-------- reclaimer/meta/wrappers/halo1_map.py | 91 ++++++++++++----------- reclaimer/meta/wrappers/halo1_mcc_map.py | 12 +-- 8 files changed, 151 insertions(+), 94 deletions(-) diff --git a/reclaimer/field_type_methods.py b/reclaimer/field_type_methods.py index 1e6fc059..cf37cc21 100644 --- a/reclaimer/field_type_methods.py +++ b/reclaimer/field_type_methods.py @@ -239,7 +239,7 @@ def reflexive_parser(self, desc, node=None, parent=None, attr_index=None, max_size = max(SANE_MAX_REFLEXIVE_COUNT, arr_len_max) if node[0] > max_size: print("Warning: Clipped %s reflexive size from %s to %s" % ( - desc[NAME], size, new_size + desc[NAME], node[0], max_size )) node[0] = max(SANE_MAX_REFLEXIVE_COUNT, arr_len_max) diff --git a/reclaimer/halo_script/hsc.py b/reclaimer/halo_script/hsc.py index c2872e7b..c9ff3aa5 100644 --- a/reclaimer/halo_script/hsc.py +++ b/reclaimer/halo_script/hsc.py @@ -20,6 +20,7 @@ script_object_types as h2_script_object_types from supyr_struct.defs.block_def import BlockDef +from supyr_struct.defs.tag_def import TagDef from supyr_struct.field_types import FieldType try: @@ -84,6 +85,8 @@ "is_script_call", "is_global", "is_garbage_collectable", + "is_parameter", # MCC only + "is_stripped", # MCC only ), UInt32("next_node"), UInt32("string_offset"), @@ -110,7 +113,7 @@ SIZE=20 ) -h1_script_syntax_data = Struct("script syntax data header", +script_syntax_data_header = Struct("header", ascii_str32('name', DEFAULT="script node"), UInt16("max_nodes", DEFAULT=19001), # this is 1 more than expected UInt16("node_size", DEFAULT=20), @@ -123,6 +126,10 @@ BytesRaw("next", SIZE=4), Pointer32("first"), SIZE=56, + ) + +h1_script_syntax_data = Struct("script_syntax_data_header", + INCLUDE=script_syntax_data_header, STEPTREE=WhileArray("nodes", SUB_STRUCT=fast_script_node) ) @@ -454,3 +461,17 @@ def hsc_bytecode_to_string(syntax_data, string_data, block_index, if block_type == "global": return "(%s%s)" % (head, body) return "(%s\n%s%s\n)" % (head, indent_str, body[1:]) + + +# for loading in binilla for debugging script data issues +def get(): + return script_syntax_data_def + + +script_syntax_data_def = TagDef('script_syntax_data', + script_syntax_data_header, + Array("nodes", + SUB_STRUCT=script_node, SIZE=".header.last_node" + ), + endian='>' + ) \ No newline at end of file diff --git a/reclaimer/hek/defs/coll.py b/reclaimer/hek/defs/coll.py index 8728ae38..8e04b0f1 100644 --- a/reclaimer/hek/defs/coll.py +++ b/reclaimer/hek/defs/coll.py @@ -162,8 +162,22 @@ DYN_NAME_PATH="..[DYN_I].name"), Pad(8), - FlSInt16("unknown0", VISIBLE=False), - FlSInt16("unknown1", VISIBLE=False), + FlSInt16("unknown", VISIBLE=False), + FlSEnum16("damage_region", + "waist", + "torso", + "head", + "l_arm", + "l_hand", + "unused0", + "l_leg", + "r_arm", + "r_hand", + "unused1", + "r_leg", + ("none", -1), + VISIBLE=False + ), reflexive("bsps", permutation_bsp, 32), SIZE=64 ) diff --git a/reclaimer/hek/defs/lsnd.py b/reclaimer/hek/defs/lsnd.py index 0914ebbf..794c7591 100644 --- a/reclaimer/hek/defs/lsnd.py +++ b/reclaimer/hek/defs/lsnd.py @@ -75,7 +75,7 @@ FlFloat("unknown3", DEFAULT=1.0, VISIBLE=False), FlSInt16("unknown4", DEFAULT=-1, VISIBLE=False), FlSInt16("unknown5", DEFAULT=-1, VISIBLE=False), - FlFloat("unknown6", DEFAULT=1.0, VISIBLE=False), + FlFloat("max_distance", DEFAULT=1.0, VISIBLE=False), Pad(8), dependency("continuous_damage_effect", "cdmg"), diff --git a/reclaimer/meta/halo1_map.py b/reclaimer/meta/halo1_map.py index a30ef6bc..3a5c20f0 100644 --- a/reclaimer/meta/halo1_map.py +++ b/reclaimer/meta/halo1_map.py @@ -249,12 +249,11 @@ def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): UEnum32('foot', ('foot', 'foot'), DEFAULT='foot', OFFSET=2044), SIZE=2048 ) - + mcc_flags = Bool8("mcc_flags", - # NOTE: this is probably a set of bools, but idk what they are, and idc enough to find out - "use_classic_unk0", - "use_classic_unk1", - "use_classic_unk2", + "use_bitmaps_map", + "use_sounds_map", + "disable_remastered_sync", ) map_header_mcc = desc_variant( diff --git a/reclaimer/meta/wrappers/halo1_anni_map.py b/reclaimer/meta/wrappers/halo1_anni_map.py index 017aa2a0..d72315a9 100644 --- a/reclaimer/meta/wrappers/halo1_anni_map.py +++ b/reclaimer/meta/wrappers/halo1_anni_map.py @@ -13,11 +13,12 @@ from reclaimer.meta.wrappers.halo_map import HaloMap from reclaimer.meta.wrappers.halo1_rsrc_map import Halo1RsrcMap -from reclaimer.meta.wrappers.halo1_map import Halo1Map +from reclaimer.meta.wrappers.halo1_mcc_map import Halo1MccMap from reclaimer import data_extraction from reclaimer.halo_script.hsc import h1_script_syntax_data_def from reclaimer.hek.defs.coll import fast_coll_def from reclaimer.hek.defs.sbsp import fast_sbsp_def +from reclaimer.mcc_hek.defs.sbsp import sbsp_meta_header_def from reclaimer.hek.handler import HaloHandler from reclaimer.util import int_to_fourcc @@ -65,17 +66,19 @@ def end_swap_uint16(v): return ((v << 8) + (v >> 8)) & 0xFFFF -class Halo1AnniMap(Halo1Map): +class Halo1AnniMap(Halo1MccMap): tag_headers = None defs = None handler_class = HaloHandler + + sbsp_meta_header_def = sbsp_meta_header_def + + @property + def uses_fmod_sound_bank(self): return True - inject_rawdata = Halo1RsrcMap.inject_rawdata - - def __init__(self, maps=None): - HaloMap.__init__(self, maps) - self.setup_tag_headers() + def get_resource_map_paths(self, maps_dir=""): + return {} def get_dependencies(self, meta, tag_id, tag_cls): if self.is_indexed(tag_id): @@ -111,6 +114,19 @@ def get_dependencies(self, meta, tag_id, tag_cls): dependencies.append(node) return dependencies + def setup_sbsp_headers(self): + with FieldType.force_big: + super().setup_sbsp_headers() + + def setup_rawdata_pages(self): + # NOTE: for some reason, anniversary maps have overlap between the + # sbsp virtual address range and the tag data address range. + # because of this, we don't setup any pages in the default + # pointer converter for sbsp + # NOTE: we also don't setup pages for the model data section, since + # it's also overlapping with the tag data address range. + pass + def get_meta(self, tag_id, reextract=False, ignore_rsrc_sounds=False, **kw): ''' Takes a tag reference id as the sole argument. @@ -138,7 +154,6 @@ def get_meta(self, tag_id, reextract=False, ignore_rsrc_sounds=False, **kw): if self.get_meta_descriptor(tag_cls) is None: return - if tag_cls is None: # couldn't determine the tag class return @@ -186,12 +201,11 @@ def get_meta(self, tag_id, reextract=False, ignore_rsrc_sounds=False, **kw): return meta def byteswap_anniversary_fields(self, meta, tag_cls): - if tag_cls == "antr": unpack_header = PyStruct("<11i").unpack for b in meta.animations.STEPTREE: - b.unknown_sint16 = end_swap_int16(b.unknown_sint16) - b.unknown_float = end_swap_float(b.unknown_float) + b.first_permutation_index = end_swap_int16(b.first_permutation_index) + b.chance_to_play = end_swap_float(b.chance_to_play) if not b.flags.compressed_data: continue @@ -234,8 +248,8 @@ def byteswap_anniversary_fields(self, meta, tag_cls): elif tag_cls == "coll": for b in meta.nodes.STEPTREE: - b.unknown0 = end_swap_int16(b.unknown0) - b.unknown1 = end_swap_int16(b.unknown1) + b.unknown = end_swap_int16(b.unknown) + b.damage_region.data = end_swap_int16(b.damage_region.data) elif tag_cls == "effe": for event in meta.events.STEPTREE: @@ -253,8 +267,8 @@ def byteswap_anniversary_fields(self, meta, tag_cls): meta.string.parse(rawdata=block_bytes) elif tag_cls == "lens": - meta.unknown0 = end_swap_float(meta.unknown0) - meta.unknown1 = end_swap_float(meta.unknown1) + meta.cosine_falloff_angle = end_swap_float(meta.cosine_falloff_angle) + meta.cosine_cutoff_angle = end_swap_float(meta.cosine_cutoff_angle) elif tag_cls == "lsnd": meta.unknown0 = end_swap_float(meta.unknown0) @@ -263,7 +277,7 @@ def byteswap_anniversary_fields(self, meta, tag_cls): meta.unknown3 = end_swap_float(meta.unknown3) meta.unknown4 = end_swap_int16(meta.unknown4) meta.unknown5 = end_swap_int16(meta.unknown5) - meta.unknown6 = end_swap_float(meta.unknown6) + meta.max_distance = end_swap_float(meta.max_distance) elif tag_cls == "metr": meta.screen_x_pos = end_swap_uint16(meta.screen_x_pos) @@ -273,7 +287,7 @@ def byteswap_anniversary_fields(self, meta, tag_cls): elif tag_cls in ("mod2", "mode"): for node in meta.nodes.STEPTREE: - node.unknown = end_swap_float(node.unknown) + node.scale = end_swap_float(node.scale) for b in (node.rot_jj_kk, node.rot_kk_ii, node.rot_ii_jj, node.translation_to_root): for i in range(len(b)): @@ -367,7 +381,7 @@ def byteswap_anniversary_fields(self, meta, tag_cls): b.reflexive_index = end_swap_int16(b.reflexive_index) for b in meta.trigger_volumes.STEPTREE: - b.unknown = end_swap_uint16(b.unknown) + b.unknown0 = end_swap_uint16(b.unknown0) for b in meta.encounters.STEPTREE: b.unknown = end_swap_int16(b.unknown) @@ -427,7 +441,6 @@ def byteswap_anniversary_fields(self, meta, tag_cls): b.parse(rawdata=block_bytes) - if tag_cls in ("bipd", "vehi", "weap", "eqip", "garb", "proj", "scen", "mach", "ctrl", "lifi", "plac", "ssce", "obje"): meta.obje_attrs.object_type.data = end_swap_int16( @@ -438,22 +451,31 @@ def byteswap_anniversary_fields(self, meta, tag_cls): meta.shdr_attrs.shader_type.data) def inject_rawdata(self, meta, tag_cls, tag_index_ref): - pass - - def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): - kwargs["byteswap"] = False - Halo1Map.meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs) + # TODO: Update this with extracting from sabre paks if + # bitmap/sound/model extraction is ever implemented + if tag_cls == "snd!": + # audio samples are ALWAYS in fmod, so fill the with empty padding + for pitches in meta.pitch_ranges.STEPTREE: + for perm in pitches.permutations.STEPTREE: + for b in (perm.samples, perm.mouth_data, perm.subtitle_data): + b.data = b"\x00"*b.size + elif tag_cls == "bitm": + # bitmap pixels are ALWAYS in saber paks, so fill the with empty padding + meta.compressed_color_plate_data.data = b"\x00"*meta.processed_pixel_data.size + meta.processed_pixel_data.data = b"\x00"*meta.processed_pixel_data.size + else: + meta = super().inject_rawdata(meta, tag_cls, tag_index_ref) + return meta - # TODO: Remove this if bitmap/sound extraction is ever implemented - if tag_cls in ("snd!", "bitm"): - # these tags don't properly extract due to missing - # pixel data and sound permutation sample data - return - elif tag_cls in ("sbsp", "scnr"): - # renderable geometry is absent from h1 anniversary bsps, and - # we don't know how to properly byteswap recorded animations, - # so scenarios can't be extracted properly either. + def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): + if tag_cls in ("bitm", "snd!", "scnr", ): + # no bitmap pixels or sound samples in map. cant extract. + # also, we don't know how to properly byteswap recorded + # animations, so scenarios can't be extracted properly. return + kwargs["byteswap"] = False + super().meta_to_tag_data(meta, tag_cls, tag_index_ref, **kwargs) + return meta diff --git a/reclaimer/meta/wrappers/halo1_map.py b/reclaimer/meta/wrappers/halo1_map.py index c07bfe57..5d2f01d0 100644 --- a/reclaimer/meta/wrappers/halo1_map.py +++ b/reclaimer/meta/wrappers/halo1_map.py @@ -271,55 +271,25 @@ def setup_sbsp_pointer_converters(self): i += 1 - # read the sbsp headers - for tag_id, offset in self.bsp_header_offsets.items(): - if self.engine == "halo1anni": - with FieldType.force_big: - header = self.sbsp_meta_header_def.build( - rawdata=self.map_data, offset=offset) - else: - header = self.sbsp_meta_header_def.build( - rawdata=self.map_data, offset=offset) - - if header.sig != header.get_desc("DEFAULT", "sig"): - print("Sbsp header is invalid for '%s'" % - self.tag_index.tag_index[tag_id].path) - self.bsp_headers[tag_id] = header - self.tag_index.tag_index[tag_id].meta_offset = header.meta_pointer + self.setup_sbsp_headers() except Exception: print(format_exc()) - def load_map(self, map_path, **kwargs): - HaloMap.load_map(self, map_path, **kwargs) - - tag_index = self.tag_index - tag_index_array = tag_index.tag_index - - # cache the original paths BEFORE running basic deprotection - self.cache_original_tag_paths() - - # make all contents of the map parseable - self.basic_deprotection() + def setup_sbsp_headers(self): + # read the sbsp headers + for tag_id, offset in self.bsp_header_offsets.items(): + header = self.sbsp_meta_header_def.build( + rawdata=self.map_data, offset=offset) - self.tag_index_manager = TagIndexManager(tag_index_array) + if header.sig != header.get_desc("DEFAULT", "sig"): + print("Sbsp header is invalid for '%s'" % + self.tag_index.tag_index[tag_id].path) + self.bsp_headers[tag_id] = header + self.tag_index.tag_index[tag_id].meta_offset = header.meta_pointer - # add the tag data section - self.map_pointer_converter.add_page_info( - self.index_magic, self.map_header.tag_index_header_offset, - self.map_header.tag_data_size - ) - - # cache the scenario meta - try: - self.scnr_meta = self.get_meta(self.tag_index.scenario_tag_id) - if self.scnr_meta is None: - print("Could not read scenario tag") - except Exception: - print(format_exc()) - print("Could not read scenario tag") - - self.setup_sbsp_pointer_converters() + def setup_rawdata_pages(self): + tag_index = self.tag_index last_bsp_end = 0 # calculate the start of the rawdata section @@ -349,6 +319,38 @@ def load_map(self, map_path, **kwargs): tag_index.model_data_offset), ) + def load_map(self, map_path, **kwargs): + HaloMap.load_map(self, map_path, **kwargs) + + tag_index = self.tag_index + tag_index_array = tag_index.tag_index + + # cache the original paths BEFORE running basic deprotection + self.cache_original_tag_paths() + + # make all contents of the map parseable + self.basic_deprotection() + + self.tag_index_manager = TagIndexManager(tag_index_array) + + # add the tag data section + self.map_pointer_converter.add_page_info( + self.index_magic, self.map_header.tag_index_header_offset, + self.map_header.tag_data_size + ) + + # cache the scenario meta + try: + self.scnr_meta = self.get_meta(self.tag_index.scenario_tag_id) + if self.scnr_meta is None: + print("Could not read scenario tag") + except Exception: + print(format_exc()) + print("Could not read scenario tag") + + self.setup_sbsp_pointer_converters() + self.setup_rawdata_pages() + # get the globals meta try: matg_id = None @@ -1001,7 +1003,6 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): vert_size = 68 # null out certain things in the part - #part.previous_part_index = part.next_part_index = 0 part.centroid_primary_node = 0 part.centroid_secondary_node = 0 part.centroid_primary_weight = 0.0 @@ -1419,7 +1420,7 @@ def generate_map_info_string(self): magic, self.bsp_sizes[tag_id], header.meta_pointer, header.meta_pointer + offset - magic ) - if self.engine == "halo1mcc": + if self.engine in ("halo1mcc", "halo1anni"): string += """\ render verts size == %s render verts pointer == %s\n""" % ( diff --git a/reclaimer/meta/wrappers/halo1_mcc_map.py b/reclaimer/meta/wrappers/halo1_mcc_map.py index af129ce0..5161f2fe 100644 --- a/reclaimer/meta/wrappers/halo1_mcc_map.py +++ b/reclaimer/meta/wrappers/halo1_mcc_map.py @@ -29,14 +29,14 @@ def __init__(self, maps=None): @property def uses_fmod_sound_bank(self): - try: - return not self.map_header.mcc_flags.data - except AttributeError: - return False + return not self.uses_sounds_map @property def uses_sounds_map(self): - return not self.uses_fmod_sound_bank + try: + return self.map_header.mcc_flags.use_sounds_map + except AttributeError: + return False def get_resource_map_paths(self, maps_dir=""): if self.is_resource: @@ -139,6 +139,6 @@ def inject_rawdata(self, meta, tag_cls, tag_index_ref): map_data.seek(lm_verts_offset) vert_data += map_data.read(lm_verts_size) - data_block.data = vert_data + data_block.data = bytearray(vert_data) else: return super().inject_rawdata(meta, tag_cls, tag_index_ref) \ No newline at end of file From aa77e56cffe36df868e67bd9c17281a4c063f616 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Tue, 23 Jan 2024 16:28:49 -0600 Subject: [PATCH 19/51] bump version --- reclaimer/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reclaimer/__init__.py b/reclaimer/__init__.py index 3feb1113..21f71f13 100644 --- a/reclaimer/__init__.py +++ b/reclaimer/__init__.py @@ -12,8 +12,8 @@ # ############## __author__ = "Sigmmma" # YYYY.MM.DD -__date__ = "2024.01.16" -__version__ = (2, 12, 0) +__date__ = "2024.01.23" +__version__ = (2, 13, 0) __website__ = "https://github.com/Sigmmma/reclaimer" __all__ = ( "animation", "bitmaps", "h2", "h3", "halo_script", "hek", "meta", "misc", From ebbf9f36066126c6c540da6280eeeee8bd5b2957 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Tue, 23 Jan 2024 17:48:26 -0600 Subject: [PATCH 20/51] Fix missing field in MCC damage_effect tags --- reclaimer/hek/defs/jpt_.py | 76 +++++++++++++++++----------------- reclaimer/mcc_hek/defs/jpt_.py | 8 +++- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/reclaimer/hek/defs/jpt_.py b/reclaimer/hek/defs/jpt_.py index bb37426f..e87a71a4 100644 --- a/reclaimer/hek/defs/jpt_.py +++ b/reclaimer/hek/defs/jpt_.py @@ -11,6 +11,43 @@ from .objs.tag import HekTag from supyr_struct.defs.tag_def import TagDef +damage = Struct("damage", + SEnum16("priority", + "none", + "harmless", + {NAME: "backstab", GUI_NAME: "lethal to the unsuspecting"}, + "emp", + ), + SEnum16("category", *damage_category), + Bool32("flags", + "does_not_hurt_owner", + {NAME: "headshot", GUI_NAME: "causes headshots"}, + "pings_resistant_units", + "does_not_hurt_friends", + "does_not_ping_units", + "detonates_explosives", + "only_hurts_shields", + "causes_flaming_death", + {NAME: "indicator_points_down", + GUI_NAME: "damage indicator always points down"}, + "skips_shields", + "only_hurts_one_infection_form", + {NAME: "multiplayer_headshot", + GUI_NAME: "causes multiplayer headshots"}, + "infection_form_pop", + ), + float_wu("aoe_core_radius"), + Float("damage_lower_bound"), + QStruct("damage_upper_bound", INCLUDE=from_to), + float_zero_to_one("vehicle_passthrough_penalty"), + float_zero_to_one("active_camouflage_damage"), + float_zero_to_one("stun"), + float_zero_to_one("maximum_stun"), + float_sec("stun_time"), + Pad(4), + float_zero_to_inf("instantaneous_acceleration"), + Pad(8), + ) frequency_vibration = Struct("", float_zero_to_one("frequency"), @@ -104,44 +141,7 @@ Pad(12), ), - Struct("damage", - SEnum16("priority", - "none", - "harmless", - {NAME: "backstab", GUI_NAME: "lethal to the unsuspecting"}, - "emp", - ), - SEnum16("category", *damage_category), - Bool32("flags", - "does_not_hurt_owner", - {NAME: "headshot", GUI_NAME: "causes headshots"}, - "pings_resistant_units", - "does_not_hurt_friends", - "does_not_ping_units", - "detonates_explosives", - "only_hurts_shields", - "causes_flaming_death", - {NAME: "indicator_points_down", - GUI_NAME: "damage indicator always points down"}, - "skips_shields", - "only_hurts_one_infection_form", - {NAME: "multiplayer_headshot", - GUI_NAME: "causes multiplayer headshots"}, - "infection_form_pop", - ), - float_wu("aoe_core_radius"), - Float("damage_lower_bound"), - QStruct("damage_upper_bound", INCLUDE=from_to), - float_zero_to_one("vehicle_passthrough_penalty"), - float_zero_to_one("active_camouflage_damage"), - float_zero_to_one("stun"), - float_zero_to_one("maximum_stun"), - float_sec("stun_time"), - Pad(4), - float_zero_to_inf("instantaneous_acceleration"), - Pad(8), - ), - + damage, damage_modifiers, SIZE=672, ) diff --git a/reclaimer/mcc_hek/defs/jpt_.py b/reclaimer/mcc_hek/defs/jpt_.py index 79f2d539..169c38e5 100644 --- a/reclaimer/mcc_hek/defs/jpt_.py +++ b/reclaimer/mcc_hek/defs/jpt_.py @@ -8,9 +8,15 @@ # from ...hek.defs.jpt_ import * -from .cdmg import damage +from .cdmg import damage_flags from supyr_struct.util import desc_variant +damage = desc_variant(damage, + ("flags", damage_flags), + ("instantaneous_acceleration", QStruct("instantaneous_acceleration", INCLUDE=ijk_float)), + ("pad_13", Pad(0)), + ) + jpt__body = desc_variant(jpt__body, ("damage", damage), ) From ef0163464d1480eb3bcff43e4c2ec9e069047589 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Tue, 23 Jan 2024 17:50:34 -0600 Subject: [PATCH 21/51] bump version --- reclaimer/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reclaimer/__init__.py b/reclaimer/__init__.py index 21f71f13..052ff5b3 100644 --- a/reclaimer/__init__.py +++ b/reclaimer/__init__.py @@ -13,7 +13,7 @@ __author__ = "Sigmmma" # YYYY.MM.DD __date__ = "2024.01.23" -__version__ = (2, 13, 0) +__version__ = (2, 13, 1) __website__ = "https://github.com/Sigmmma/reclaimer" __all__ = ( "animation", "bitmaps", "h2", "h3", "halo_script", "hek", "meta", "misc", From 5e71c391a12d861200374730e2b0aa77c2d219fe Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Fri, 26 Jan 2024 00:06:59 -0600 Subject: [PATCH 22/51] Def cleanup --- reclaimer/animation/animation_compilation.py | 2 +- reclaimer/animation/jma.py | 4 + reclaimer/common_descs.py | 5 +- reclaimer/constants.py | 10 +- reclaimer/hek/defs/actr.py | 15 ++- reclaimer/hek/defs/cdmg.py | 62 +++-------- reclaimer/hek/defs/coll.py | 6 +- reclaimer/hek/defs/cont.py | 2 +- reclaimer/hek/defs/deca.py | 6 +- reclaimer/hek/defs/dobc.py | 3 +- reclaimer/hek/defs/effe.py | 7 +- reclaimer/hek/defs/jpt_.py | 104 +++++++++---------- reclaimer/hek/defs/matg.py | 4 + reclaimer/hek/defs/mgs2.py | 4 +- reclaimer/hek/defs/mod2.py | 2 +- reclaimer/hek/defs/mode.py | 2 +- reclaimer/hek/defs/obje.py | 11 +- reclaimer/hek/defs/objs/actr.py | 3 + reclaimer/hek/defs/objs/bitm.py | 8 ++ reclaimer/hek/defs/objs/deca.py | 17 +++ reclaimer/hek/defs/objs/mgs2.py | 21 ++++ reclaimer/hek/defs/objs/mode.py | 93 ++++++++++++++++- reclaimer/hek/defs/objs/obje.py | 11 +- reclaimer/hek/defs/objs/part.py | 18 ++++ reclaimer/hek/defs/objs/scnr.py | 18 ++++ reclaimer/hek/defs/objs/snd_.py | 63 ++++++++++- reclaimer/hek/defs/part.py | 9 +- reclaimer/hek/defs/rain.py | 6 +- reclaimer/hek/defs/sbsp.py | 15 ++- reclaimer/hek/defs/scnr.py | 30 ++++-- reclaimer/hek/defs/snd_.py | 14 +-- reclaimer/hek/defs/snde.py | 2 +- reclaimer/hek/defs/soso.py | 16 ++- reclaimer/hek/defs/unit.py | 3 +- reclaimer/hek/handler.py | 4 + reclaimer/mcc_hek/defs/bitm.py | 21 +++- reclaimer/mcc_hek/defs/cdmg.py | 7 +- reclaimer/mcc_hek/defs/deca.py | 2 +- reclaimer/mcc_hek/defs/effe.py | 5 +- reclaimer/mcc_hek/defs/jpt_.py | 4 +- reclaimer/mcc_hek/defs/scnr.py | 2 +- reclaimer/mcc_hek/defs/soso.py | 2 + reclaimer/meta/halo1_map.py | 90 ++++++++-------- reclaimer/meta/halo_map.py | 41 ++++++-- reclaimer/meta/objs/halo1_rsrc_map.py | 8 +- reclaimer/meta/stubbs_map.py | 66 ++++++------ reclaimer/meta/wrappers/halo1_anni_map.py | 10 +- reclaimer/meta/wrappers/halo1_map.py | 70 ++++++++----- reclaimer/meta/wrappers/halo1_mcc_map.py | 16 ++- reclaimer/meta/wrappers/halo1_rsrc_map.py | 30 +++++- reclaimer/meta/wrappers/halo1_xbox_map.py | 21 ++++ reclaimer/meta/wrappers/halo1_yelo.py | 15 +++ reclaimer/meta/wrappers/halo_map.py | 12 ++- reclaimer/meta/wrappers/stubbs_map_64bit.py | 25 +++++ reclaimer/model/constants.py | 2 + reclaimer/model/jms/file.py | 4 + reclaimer/model/model_compilation.py | 66 ++++-------- reclaimer/model/util.py | 2 +- reclaimer/os_hek/defs/scnr.py | 5 +- reclaimer/os_hek/defs/soso.py | 39 ++----- reclaimer/os_hek/defs/unit.py | 6 +- reclaimer/os_v3_hek/defs/cdmg.py | 70 +++++-------- reclaimer/os_v3_hek/defs/jpt_.py | 54 ++-------- reclaimer/os_v4_hek/defs/part.py | 15 ++- reclaimer/os_v4_hek/defs/scnr.py | 2 +- reclaimer/os_v4_hek/defs/unit.py | 18 ++-- reclaimer/sounds/sound_compilation.py | 6 +- reclaimer/sounds/sound_decompilation.py | 2 +- reclaimer/stubbs/defs/actr.py | 18 ++-- reclaimer/stubbs/defs/cdmg.py | 28 ++--- reclaimer/stubbs/defs/coll.py | 49 ++------- reclaimer/stubbs/defs/eqip.py | 7 +- reclaimer/stubbs/defs/garb.py | 3 - reclaimer/stubbs/defs/jpt_.py | 27 ++--- reclaimer/stubbs/defs/lifi.py | 2 +- reclaimer/stubbs/defs/mach.py | 1 - reclaimer/stubbs/defs/obje.py | 9 +- reclaimer/stubbs/defs/plac.py | 2 - reclaimer/stubbs/defs/proj.py | 10 +- reclaimer/stubbs/defs/sbsp.py | 12 ++- reclaimer/stubbs/defs/scen.py | 2 - reclaimer/stubbs/defs/shdr.py | 7 +- reclaimer/stubbs/defs/soso.py | 38 +------ reclaimer/stubbs/defs/ssce.py | 2 - reclaimer/stubbs/defs/unit.py | 3 +- reclaimer/stubbs/defs/vehi.py | 7 +- reclaimer/stubbs/defs/weap.py | 7 +- 87 files changed, 928 insertions(+), 644 deletions(-) create mode 100644 reclaimer/hek/defs/objs/deca.py create mode 100644 reclaimer/hek/defs/objs/mgs2.py create mode 100644 reclaimer/hek/defs/objs/part.py create mode 100644 reclaimer/hek/defs/objs/scnr.py create mode 100644 reclaimer/meta/wrappers/halo1_xbox_map.py create mode 100644 reclaimer/meta/wrappers/stubbs_map_64bit.py diff --git a/reclaimer/animation/animation_compilation.py b/reclaimer/animation/animation_compilation.py index 3f19f2bb..35ca39f3 100644 --- a/reclaimer/animation/animation_compilation.py +++ b/reclaimer/animation/animation_compilation.py @@ -19,7 +19,7 @@ "ANIMATION_COMPILE_MODE_NEW", "ANIMATION_COMPILE_MODE_PRESERVE", "ANIMATION_COMPILE_MODE_ADDITIVE") -ANIMATION_COMPILE_MODE_NEW = 0 +ANIMATION_COMPILE_MODE_NEW = 0 ANIMATION_COMPILE_MODE_PRESERVE = 1 ANIMATION_COMPILE_MODE_ADDITIVE = 2 diff --git a/reclaimer/animation/jma.py b/reclaimer/animation/jma.py index 1e4a87bd..311d005d 100644 --- a/reclaimer/animation/jma.py +++ b/reclaimer/animation/jma.py @@ -640,6 +640,10 @@ def read_jma(jma_string, stop_at="", anim_name=""): print("Could not read node list checksum.") return jma_anim + if jma_anim.node_list_checksum >= 0x80000000: + # jma gave us an unsigned checksum.... sign it + jma_anim.node_list_checksum -= 0x100000000 + if stop_at == "nodes": continue # read the nodes diff --git a/reclaimer/common_descs.py b/reclaimer/common_descs.py index 5f050c6c..678f87bb 100644 --- a/reclaimer/common_descs.py +++ b/reclaimer/common_descs.py @@ -66,7 +66,10 @@ def reflexive(name, substruct, max_count=MAX_REFLEXIVE_COUNT, *names, **kwargs): ) kwargs.update( STEPTREE=ReflexiveArray(name + "_array", - SIZE=".size", SUB_STRUCT=substruct, WIDGET=ReflexiveFrame + SIZE=".size", SUB_STRUCT=substruct, WIDGET=ReflexiveFrame, + # NOTE: also adding max here since various things rely on it + # (i.e. compilation/mozz tag block size limit/etc) + MAX=max_count ), SIZE=12 ) diff --git a/reclaimer/constants.py b/reclaimer/constants.py index 44932aba..5f146c06 100644 --- a/reclaimer/constants.py +++ b/reclaimer/constants.py @@ -43,6 +43,7 @@ def inject_halo_constants(): map_build_dates = { "stubbs": "400", "stubbspc": "", + "stubbspc64bit": "", "shadowrun_proto": "01.12.07.0132", "halo1xboxdemo": "", "halo1xbox": "01.10.12.2276", @@ -71,6 +72,7 @@ def inject_halo_constants(): map_versions = { "stubbs": 5, "stubbspc": 5, + "stubbspc64bit": 5, "shadowrun_proto": 5, "halo1xboxdemo": 5, "halo1xbox": 5, @@ -102,7 +104,8 @@ def inject_halo_constants(): "halo1mcc", ) GEN_1_ENGINES = GEN_1_HALO_ENGINES + ( - "stubbs", "stubbspc", "shadowrun_proto", ) + "stubbs", "stubbspc", "stubbspc64bit", "shadowrun_proto", + ) GEN_2_ENGINES = ("halo2alpha", "halo2beta", "halo2epsilon", "halo2xbox", "halo2vista", ) @@ -117,6 +120,7 @@ def inject_halo_constants(): map_magics = { "stubbs": STUBBS_INDEX_MAGIC, "stubbspc": PC_INDEX_MAGIC, + "stubbspc64bit": PC_INDEX_MAGIC, "shadowrun_proto": SHADOWRUN_PROTO_INDEX_MAGIC, "halo1xboxdemo": XBOX_INDEX_MAGIC, "halo1xbox": XBOX_INDEX_MAGIC, @@ -218,6 +222,10 @@ def inject_halo_constants(): tag_class_be_int_to_fcc = {} tag_class_le_int_to_fcc = {} +LOD_NAMES = ( + "superhigh", "high", "medium", "low", "superlow" + ) + # maps tag class four character codes to the tags file extension tag_class_fcc_to_ext = { 'actr': "actor", diff --git a/reclaimer/hek/defs/actr.py b/reclaimer/hek/defs/actr.py index fd7b047f..17e84ec5 100644 --- a/reclaimer/hek/defs/actr.py +++ b/reclaimer/hek/defs/actr.py @@ -24,6 +24,18 @@ "unused5", ) +# split out to be reused in stubbs +panic = Struct("panic", + from_to_sec("cowering_time"), # seconds + float_zero_to_one("friend_killed_panic_chance"), + SEnum16("leader_type", *actor_types), + + Pad(2), + float_zero_to_one("leader_killed_panic_chance"), + float_zero_to_one("panic_damage_threshold"), + float_wu("surprise_distance"), # world units + ) + actr_body = Struct("tagdata", Bool32('flags', "can_see_in_darkness", @@ -115,9 +127,10 @@ float_wu("stationary_movement_dist"), # world units float_wu("free_flying_sidestep"), # world units float_rad("begin_moving_angle"), # radians + float_neg_one_to_one("cosine_begin_moving_angle", VISIBLE=False), ), - Pad(4), + Pad(0), Struct("looking", yp_float_rad("maximum_aiming_deviation"), # radians yp_float_rad("maximum_looking_deviation"), # radians diff --git a/reclaimer/hek/defs/cdmg.py b/reclaimer/hek/defs/cdmg.py index dc764e6a..4253816a 100644 --- a/reclaimer/hek/defs/cdmg.py +++ b/reclaimer/hek/defs/cdmg.py @@ -8,46 +8,19 @@ # from ...common_descs import * +from .jpt_ import damage, camera_shaking from .objs.tag import HekTag from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant -# split out to be reused in mcc_hek -damage = Struct("damage", - SEnum16("priority", - "none", - "harmless", - {NAME: "backstab", GUI_NAME: "lethal to the unsuspecting"}, - "emp", - ), - SEnum16("category", *damage_category), - Bool32("flags", - "does_not_hurt_owner", - {NAME: "headshot", GUI_NAME: "can cause headshots"}, - "pings_resistant_units", - "does_not_hurt_friends", - "does_not_ping_shields", - "detonates_explosives", - "only_hurts_shields", - "causes_flaming_death", - {NAME: "indicator_points_down", - GUI_NAME: "damage indicator always points down"}, - "skips_shields", - "only_hurts_one_infection_form", - {NAME: "multiplayer_headshot", - GUI_NAME: "can cause multiplayer headshots"}, - "infection_form_pop", - ), - Pad(4), - Float("damage_lower_bound"), - QStruct("damage_upper_bound", INCLUDE=from_to), - float_zero_to_one("vehicle_passthrough_penalty"), - Pad(4), - float_zero_to_one("stun"), - float_zero_to_one("maximum_stun"), - float_sec("stun_time"), - Pad(4), - float_zero_to_inf("instantaneous_acceleration"), - Pad(8), +damage = desc_variant(damage, + ("aoe_core_radius", Pad(4)), + ("active_camouflage_damage", Pad(4)), + ) + +camera_shaking = desc_variant(camera_shaking, + ("duration", Pad(4)), + ("fade_function", Pad(2)), ) cdmg_body = Struct("tagdata", @@ -58,20 +31,11 @@ QStruct("vibrate_parameters", float_zero_to_one("low_frequency"), float_zero_to_one("high_frequency"), - Pad(24), + Pad(16), ), - Struct("camera_shaking", - float_wu("random_translation"), - float_rad("random_rotation"), # radians - Pad(12), - - SEnum16("wobble_function", *animation_functions), - Pad(2), - float_sec("wobble_function_period"), - Float("wobble_weight"), - Pad(192), - ), + camera_shaking, + Pad(160), damage, damage_modifiers, diff --git a/reclaimer/hek/defs/coll.py b/reclaimer/hek/defs/coll.py index 8e04b0f1..3a4800b4 100644 --- a/reclaimer/hek/defs/coll.py +++ b/reclaimer/hek/defs/coll.py @@ -65,7 +65,7 @@ dependency("shield_depleted_effect", "effe"), dependency("shield_recharging_effect", "effe"), Pad(8), - Float("shield_recharge_rate", VISIBLE=False), + FlFloat("shield_recharge_rate", VISIBLE=False), ) bsp3d_node = QStruct("bsp3d_node", @@ -295,8 +295,8 @@ DYN_NAME_PATH="..[DYN_I].name"), Pad(8), - FlSInt16("unknown0", VISIBLE=False), - FlSInt16("unknown1", VISIBLE=False), + FlSInt16("unknown", VISIBLE=False), + FlSInt16("damage_region", VISIBLE=False), reflexive("bsps", fast_permutation_bsp, 32), SIZE=64 ) diff --git a/reclaimer/hek/defs/cont.py b/reclaimer/hek/defs/cont.py index df29fa6e..fe01049b 100644 --- a/reclaimer/hek/defs/cont.py +++ b/reclaimer/hek/defs/cont.py @@ -83,7 +83,7 @@ def get(): return cont_def SInt16("sequence_count"), Pad(100), - FlUInt32("unknown0", VISIBLE=False), + FlUInt32("unknown", VISIBLE=False), Bool16("shader_flags", *shader_flags), SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), SEnum16("framebuffer_fade_mode", *render_fade_mode), diff --git a/reclaimer/hek/defs/deca.py b/reclaimer/hek/defs/deca.py index c8a779cd..50d7b367 100644 --- a/reclaimer/hek/defs/deca.py +++ b/reclaimer/hek/defs/deca.py @@ -8,7 +8,7 @@ # from ...common_descs import * -from .objs.tag import HekTag +from .objs.deca import DecaTag from supyr_struct.defs.tag_def import TagDef decal_comment = """COMPOUND DECALS: @@ -91,7 +91,7 @@ #Sprite info Pad(20), - Float("maximum_sprite_extent", SIDETIP="pixels"), + Float("maximum_sprite_extent", SIDETIP="pixels", VISIBLE=False), SIZE=268, ) @@ -105,5 +105,5 @@ def get(): blam_header('deca'), deca_body, - ext=".decal", endian=">", tag_cls=HekTag + ext=".decal", endian=">", tag_cls=DecaTag ) diff --git a/reclaimer/hek/defs/dobc.py b/reclaimer/hek/defs/dobc.py index 6086a765..0b98f02b 100644 --- a/reclaimer/hek/defs/dobc.py +++ b/reclaimer/hek/defs/dobc.py @@ -20,8 +20,7 @@ def get(): return dobc_def ("interpolate_color_in_hsv", 4), ("more_colors", 8), ), - #UInt8("unknown0", VISIBLE=False), - Pad(1), + UInt8("first_sprite_index", VISIBLE=False), UInt8("sequence_sprite_count", VISIBLE=False), float_zero_to_one("color_override_factor"), Pad(8), diff --git a/reclaimer/hek/defs/effe.py b/reclaimer/hek/defs/effe.py index 32340e93..8dcc5f8a 100644 --- a/reclaimer/hek/defs/effe.py +++ b/reclaimer/hek/defs/effe.py @@ -154,15 +154,18 @@ effe_body = Struct("tagdata", Bool32("flags", {NAME: "deleted_when_inactive", GUI_NAME: "deleted when attachment deactivates"}, + # NOTE: on xbox the must_be_deterministic flag is in place + # of required, as the required flag didn't exist. {NAME: "required", GUI_NAME: "required for gameplay (cannot optimize out)"}, - {NAME: "never_cull", VISIBLE: VISIBILITY_HIDDEN} + {NAME: "must_be_deterministic", VISIBLE: VISIBILITY_HIDDEN} ), dyn_senum16("loop_start_event", DYN_NAME_PATH=".events.events_array[DYN_I].NAME"), dyn_senum16("loop_stop_event", DYN_NAME_PATH=".events.events_array[DYN_I].NAME"), + FlFloat("max_damage_radius", VISIBLE=False), - Pad(32), + Pad(28), reflexive("locations", location, 32, DYN_NAME_PATH='.marker_name'), reflexive("events", event, 32), diff --git a/reclaimer/hek/defs/jpt_.py b/reclaimer/hek/defs/jpt_.py index e87a71a4..f5288668 100644 --- a/reclaimer/hek/defs/jpt_.py +++ b/reclaimer/hek/defs/jpt_.py @@ -11,43 +11,58 @@ from .objs.tag import HekTag from supyr_struct.defs.tag_def import TagDef +# split out to be reused in mcc_hek damage = Struct("damage", - SEnum16("priority", - "none", - "harmless", - {NAME: "backstab", GUI_NAME: "lethal to the unsuspecting"}, - "emp", - ), - SEnum16("category", *damage_category), - Bool32("flags", - "does_not_hurt_owner", - {NAME: "headshot", GUI_NAME: "causes headshots"}, - "pings_resistant_units", - "does_not_hurt_friends", - "does_not_ping_units", - "detonates_explosives", - "only_hurts_shields", - "causes_flaming_death", - {NAME: "indicator_points_down", - GUI_NAME: "damage indicator always points down"}, - "skips_shields", - "only_hurts_one_infection_form", - {NAME: "multiplayer_headshot", - GUI_NAME: "causes multiplayer headshots"}, - "infection_form_pop", - ), - float_wu("aoe_core_radius"), - Float("damage_lower_bound"), - QStruct("damage_upper_bound", INCLUDE=from_to), - float_zero_to_one("vehicle_passthrough_penalty"), - float_zero_to_one("active_camouflage_damage"), - float_zero_to_one("stun"), - float_zero_to_one("maximum_stun"), - float_sec("stun_time"), - Pad(4), - float_zero_to_inf("instantaneous_acceleration"), - Pad(8), - ) + SEnum16("priority", + "none", + "harmless", + {NAME: "backstab", GUI_NAME: "lethal to the unsuspecting"}, + "emp", + ), + SEnum16("category", *damage_category), + Bool32("flags", + "does_not_hurt_owner", + {NAME: "headshot", GUI_NAME: "can cause headshots"}, + "pings_resistant_units", + "does_not_hurt_friends", + "does_not_ping_units", + "detonates_explosives", + "only_hurts_shields", + "causes_flaming_death", + {NAME: "indicator_points_down", GUI_NAME: "damage indicator always points down"}, + "skips_shields", + "only_hurts_one_infection_form", + {NAME: "multiplayer_headshot", GUI_NAME: "can cause multiplayer headshots"}, + "infection_form_pop", + ), + float_wu("aoe_core_radius"), + Float("damage_lower_bound"), + QStruct("damage_upper_bound", INCLUDE=from_to), + float_zero_to_one("vehicle_passthrough_penalty"), + float_zero_to_one("active_camouflage_damage"), + float_zero_to_one("stun"), + float_zero_to_one("maximum_stun"), + float_sec("stun_time"), + Pad(4), + float_zero_to_inf("instantaneous_acceleration"), + Pad(8), + ) + +camera_shaking = Struct("camera_shaking", + float_sec("duration"), + SEnum16("fade_function", *fade_functions), + Pad(2), + + float_wu("random_translation"), + float_rad("random_rotation"), # radians + Pad(12), + + SEnum16("wobble_function", *animation_functions), + Pad(2), + float_sec("wobble_function_period"), + Float("wobble_weight"), + Pad(32), + ) frequency_vibration = Struct("", float_zero_to_one("frequency"), @@ -109,22 +124,7 @@ float_rad("permanent_camera_impulse_angle"), Pad(16), - - Struct("camera_shaking", - float_sec("duration"), - SEnum16("fade_function", *fade_functions), - Pad(2), - - float_wu("random_translation"), - float_rad("random_rotation"), # radians - Pad(12), - - SEnum16("wobble_function", *animation_functions), - Pad(2), - float_sec("wobble_function_period"), - Float("wobble_weight"), - Pad(32), - ), + camera_shaking, dependency("sound", "snd!"), Pad(112), diff --git a/reclaimer/hek/defs/matg.py b/reclaimer/hek/defs/matg.py index 15319bea..c291c903 100644 --- a/reclaimer/hek/defs/matg.py +++ b/reclaimer/hek/defs/matg.py @@ -291,6 +291,10 @@ def get(): dependency('vehicle_killed_unit_damage', "jpt!"), dependency('vehicle_collision_damage', "jpt!"), dependency('flaming_death_damage', "jpt!"), + Pad(16), + FlFloat("max_falling_velocity", VISIBLE=False), + FlFloat("harmful_falling_velocity", VISIBLE=False), + Pad(4), SIZE=152 ) diff --git a/reclaimer/hek/defs/mgs2.py b/reclaimer/hek/defs/mgs2.py index 076a3290..63771c19 100644 --- a/reclaimer/hek/defs/mgs2.py +++ b/reclaimer/hek/defs/mgs2.py @@ -8,7 +8,7 @@ # from ...common_descs import * -from .objs.tag import HekTag +from .objs.mgs2 import Mgs2Tag from supyr_struct.defs.tag_def import TagDef light_volume_comment = """LIGHT VOLUME @@ -84,5 +84,5 @@ def get(): blam_header("mgs2"), mgs2_body, - ext=".light_volume", endian=">", tag_cls=HekTag, + ext=".light_volume", endian=">", tag_cls=Mgs2Tag, ) diff --git a/reclaimer/hek/defs/mod2.py b/reclaimer/hek/defs/mod2.py index d672b203..faf458ce 100644 --- a/reclaimer/hek/defs/mod2.py +++ b/reclaimer/hek/defs/mod2.py @@ -304,7 +304,7 @@ def get(): Pad(116), - reflexive("markers", marker, 256, DYN_NAME_PATH=".name"), + reflexive("markers", marker, 256, DYN_NAME_PATH=".name", VISIBLE=False), reflexive("nodes", node, 64, DYN_NAME_PATH=".name"), reflexive("regions", region, 32, DYN_NAME_PATH=".name"), reflexive("geometries", geometry, 256), diff --git a/reclaimer/hek/defs/mode.py b/reclaimer/hek/defs/mode.py index f6b26a6d..966c7d0d 100644 --- a/reclaimer/hek/defs/mode.py +++ b/reclaimer/hek/defs/mode.py @@ -148,7 +148,7 @@ def get(): Pad(104), Pad(12), # replaced with unknown reflexive in stubbs - reflexive("markers", marker, 256, DYN_NAME_PATH=".name"), + reflexive("markers", marker, 256, DYN_NAME_PATH=".name", VISIBLE=False), reflexive("nodes", node, 64, DYN_NAME_PATH=".name"), reflexive("regions", region, 32, DYN_NAME_PATH=".name"), reflexive("geometries", geometry, 256), diff --git a/reclaimer/hek/defs/obje.py b/reclaimer/hek/defs/obje.py index 7cbf0ef6..9389026d 100644 --- a/reclaimer/hek/defs/obje.py +++ b/reclaimer/hek/defs/obje.py @@ -60,7 +60,11 @@ def get(): dyn_senum16('turn_off_with', DYN_NAME_PATH="..[DYN_I].usage"), Float('scale_by'), - Pad(268), + FlFloat('bounds_range_inverse', VISIBLE=False), + FlFloat('sawtooth_count_inverse', VISIBLE=False), + FlFloat('step_count_inverse', VISIBLE=False), + FlFloat('period_inverse', VISIBLE=False), + Pad(252), ascii_str32('usage'), SIZE=360 @@ -107,7 +111,10 @@ def get(): QStruct('origin_offset', INCLUDE=xyz_float), float_zero_to_inf('acceleration_scale', UNIT_SCALE=per_sec_unit_scale), - Pad(4), + FlBool32("runtime_flags", + "functions_control_color_scale", + VISIBLE=False + ), dependency('model', valid_models), dependency('animation_graph', "antr"), diff --git a/reclaimer/hek/defs/objs/actr.py b/reclaimer/hek/defs/objs/actr.py index 762f03b1..5d3ffa8e 100644 --- a/reclaimer/hek/defs/objs/actr.py +++ b/reclaimer/hek/defs/objs/actr.py @@ -16,6 +16,7 @@ class ActrTag(HekTag): def calc_internal_data(self): HekTag.calc_internal_data(self) perception = self.data.tagdata.perception + movement = self.data.tagdata.movement looking = self.data.tagdata.looking perception.inv_combat_perception_time = 0 @@ -35,6 +36,8 @@ def calc_internal_data(self): perception.inv_guard_perception_time /= 30 perception.inv_non_combat_perception_time /= 30 + movement.cosine_begin_moving_angle = cos(movement.begin_moving_angle) + for i in range(2): looking.cosine_maximum_aiming_deviation[i] = cos(looking.maximum_aiming_deviation[i]) looking.cosine_maximum_looking_deviation[i] = cos(looking.maximum_looking_deviation[i]) diff --git a/reclaimer/hek/defs/objs/bitm.py b/reclaimer/hek/defs/objs/bitm.py index 088a5084..68767070 100644 --- a/reclaimer/hek/defs/objs/bitm.py +++ b/reclaimer/hek/defs/objs/bitm.py @@ -37,6 +37,14 @@ def __init__(self, *args, **kwargs): HekTag.__init__(self, *args, **kwargs) self.p8_palette = HALO_P8_PALETTE + def calc_internal_data(self, **kwargs): + HekTag.calc_internal_data(self) + + tagdata = self.data.tagdata + if tagdata.compressed_color_plate_data.size == 0: + tagdata.color_plate_width = 0 + tagdata.color_plate_height = 0 + def bitmap_count(self, new_value=None): if new_value is None: return self.data.tagdata.bitmaps.size diff --git a/reclaimer/hek/defs/objs/deca.py b/reclaimer/hek/defs/objs/deca.py new file mode 100644 index 00000000..4112a546 --- /dev/null +++ b/reclaimer/hek/defs/objs/deca.py @@ -0,0 +1,17 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.tag import HekTag + +class DecaTag(HekTag): + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + # why is this hardcoded, but exposed to the user? + self.data.tagdata.maximum_sprite_extend = 16.0 diff --git a/reclaimer/hek/defs/objs/mgs2.py b/reclaimer/hek/defs/objs/mgs2.py new file mode 100644 index 00000000..ef19732d --- /dev/null +++ b/reclaimer/hek/defs/objs/mgs2.py @@ -0,0 +1,21 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.tag import HekTag + +class Mgs2Tag(HekTag): + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + for frame in self.data.tagdata.frames.STEPTREE: + if frame.offset_exponent <= 0: + frame.offset_exponent = 1.0 + + if frame.radius_exponent <= 0: + frame.radius_exponent = 1.0 \ No newline at end of file diff --git a/reclaimer/hek/defs/objs/mode.py b/reclaimer/hek/defs/objs/mode.py index a7d98cf6..f243e373 100644 --- a/reclaimer/hek/defs/objs/mode.py +++ b/reclaimer/hek/defs/objs/mode.py @@ -11,20 +11,19 @@ from struct import unpack, pack_into from types import MethodType +from reclaimer.constants import LOD_NAMES from reclaimer.hek.defs.objs.tag import HekTag from reclaimer.util.compression import compress_normal32, decompress_normal32 from reclaimer.util.matrices import quaternion_to_matrix, Matrix -# TODO: Make calc_internal_data recalculate the lod nodes, and remove that -# same function from model.model_compilation.compile_gbxmodel and replace -# it with a call to calc_internal_data. lod nodes are recalculated when -# tags are compiled into maps, but the functionality should still be here. + class ModeTag(HekTag): def calc_internal_data(self): ''' For each node, this method recalculates the rotation matrix - from the quaternion, and the translation to the root bone. + from the quaternion, the translation to the root bone, and + the lod nodes. ''' HekTag.calc_internal_data(self) @@ -58,6 +57,86 @@ def calc_internal_data(self): node.rot_kk_ii[:] = rotation[1] node.rot_ii_jj[:] = rotation[2] + # calculate the highest node used by each geometry + geom_max_node_indices = [] + node_count = len(nodes) + for geometry in self.data.tagdata.geometries.STEPTREE: + geom_node_count = 0 + for part in geometry.parts.STEPTREE: + if geom_node_count == node_count: + break + elif getattr(part.flags, "ZONER", False): + # don't need to check every vert when they're all right here + geom_node_count = max(( + geom_node_count, + *(v for v in part.local_nodes[:part.local_node_count]) + )) + continue + + is_comp = not part.uncompressed_vertices.STEPTREE + verts = ( + part.compressed_vertices.STEPTREE if is_comp else + part.uncompressed_vertices.STEPTREE + ) + curr_highest = 0 + max_highest = (node_count - 1) * (3 if is_comp else 1) + if isinstance(verts, (bytes, bytearray)): + # verts are packed, so unpack what we need from it + vert_size = 32 if is_comp else 68 # vert size in bytes + unpack_vert = ( + MethodType(unpack, ">28x bbh") if is_comp else + MethodType(unpack, ">56x hhf 4x") + ) + # lazy unpack JUST the indices and weight + verts = [ + unpack_vert(verts[i: i+vert_size]) + for i in range(0, len(verts), vert_size) + ] + weight_key, node_0_key, node_1_key = 0, 1, 2 + else: + # verts aren't packed, so use as-is + weight_key = "node_0_weight" + node_0_key, node_1_key = "node_0_index", "node_1_index" + + for vert in verts: + node_0_weight = vert[weight_key] + if node_0_weight > 0 and vert[node_0_key] > curr_highest: + curr_highest = vert[node_0_key] + if curr_highest == max_highest: break + + if node_0_weight < 1 and vert[node_1_key] > curr_highest: + curr_highest = vert[node_1_key] + if curr_highest == max_highest: break + + if is_comp: + # compressed nodes use indices multiplied by 3 for some reason + curr_highest //= 3 + + geom_node_count = max(geom_node_count, curr_highest) + + geom_max_node_indices.append(max(0, geom_node_count)) + + # calculate the highest node for each lod + max_lod_nodes = {lod: 0 for lod in LOD_NAMES} + for region in self.data.tagdata.regions.STEPTREE: + for perm in region.permutations.STEPTREE: + for lod_name in LOD_NAMES: + try: + highest_node_count = geom_max_node_indices[ + perm["%s_geometry_block" % lod_name] + ] + except IndexError: + continue + + max_lod_nodes[lod_name] = max( + max_lod_nodes[lod_name], + highest_node_count + ) + + # set the node counts per lod + for lod, highest_node in max_lod_nodes.items(): + self.data.tagdata["%s_lod_nodes" % lod] = max(0, highest_node) + def compress_part_verts(self, geometry_index, part_index): part = self.data.tagdata.geometries.STEPTREE\ [geometry_index].parts.STEPTREE[part_index] @@ -70,6 +149,8 @@ def compress_part_verts(self, geometry_index, part_index): comp_verts = bytearray(b'\x00' * 32 * uncomp_verts_reflexive.size) uncomp_verts = uncomp_verts_reflexive.STEPTREE + if not isinstance(uncomp_verts, (bytes, bytearray)): + raise ValueError("Error: Uncompressed vertices must be in raw, unpacked form.") in_off = out_off = 0 # compress each of the verts and write them to the buffer @@ -105,6 +186,8 @@ def decompress_part_verts(self, geometry_index, part_index): uncomp_verts = bytearray(b'\x00' * 68 * comp_verts_reflexive.size) comp_verts = comp_verts_reflexive.STEPTREE + if not isinstance(comp_verts, (bytes, bytearray)): + raise ValueError("Error: Compressed vertices must be in raw, unpacked form.") in_off = out_off = 0 # uncompress each of the verts and write them to the buffer diff --git a/reclaimer/hek/defs/objs/obje.py b/reclaimer/hek/defs/objs/obje.py index 0adcac71..e8b5ce10 100644 --- a/reclaimer/hek/defs/objs/obje.py +++ b/reclaimer/hek/defs/objs/obje.py @@ -21,7 +21,8 @@ def calc_internal_data(self): self.ext = '.' + full_class_name self.filepath = os.path.splitext(str(self.filepath))[0] + self.ext - object_type = self.data.tagdata.obje_attrs.object_type + tag_data = self.data.tagdata + object_type = tagdata.obje_attrs.object_type if full_class_name == "object": object_type.data = -1 elif full_class_name == "biped": @@ -50,3 +51,11 @@ def calc_internal_data(self): object_type.data = 11 else: raise ValueError("Unknown object type '%s'" % full_class_name) + + # normalize color change weights + for cc in tagdata.change_colors.STEPTREE: + perms = cc.permutations.STEPTREE + total_weight = sum(max(0, perm.weight) for perm in perms) + total_weight = total_weight or len(perms) + for perm in perms: + perm.weight = (max(0, perm.weight) or 1) / total_weight \ No newline at end of file diff --git a/reclaimer/hek/defs/objs/part.py b/reclaimer/hek/defs/objs/part.py new file mode 100644 index 00000000..4f720b2a --- /dev/null +++ b/reclaimer/hek/defs/objs/part.py @@ -0,0 +1,18 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.tag import HekTag + +class PartTag(HekTag): + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + + # crash if this is nonzero? + self.data.tagdata.rendering.contact_deterioration = 0.0 \ No newline at end of file diff --git a/reclaimer/hek/defs/objs/scnr.py b/reclaimer/hek/defs/objs/scnr.py new file mode 100644 index 00000000..8183987f --- /dev/null +++ b/reclaimer/hek/defs/objs/scnr.py @@ -0,0 +1,18 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +import os + +from reclaimer.hek.defs.objs.tag import HekTag + +class ScnrTag(HekTag): + + def calc_internal_data(self): + HekTag.calc_internal_data(self) + pass diff --git a/reclaimer/hek/defs/objs/snd_.py b/reclaimer/hek/defs/objs/snd_.py index 1dd5a730..79352b87 100644 --- a/reclaimer/hek/defs/objs/snd_.py +++ b/reclaimer/hek/defs/objs/snd_.py @@ -13,8 +13,69 @@ class Snd_Tag(HekTag): def calc_internal_data(self): HekTag.calc_internal_data(self) + tagdata = self.data.tagdata - for pitch_range in self.data.tagdata.pitch_ranges.STEPTREE: + for pitch_range in tagdata.pitch_ranges.STEPTREE: pitch_range.playback_rate = 1 if pitch_range.natural_pitch: pitch_range.playback_rate = 1 / pitch_range.natural_pitch + + # default sound min and max distance by class + sound_class = tagdata.sound_class.enum_name + distance_defaults = None + if sound_class in ( + "device_door", "device_force_field", "device_machinery", + "device_nature", "music", "ambient_nature", "ambient_machinery" + ): + distance_defaults = (0.9, 5.0) + elif sound_class in ( + "weapon_ready", "weapon_reload", "weapon_empty", + "weapon_charge", "weapon_overheat", "weapon_idle" + ): + distance_defaults = (1.0, 9.0) + elif sound_class in ( + "unit_dialog", "scripted_dialog_player", + "scripted_dialog_other", + "scripted_dialog_force_unspatialized", "game_event" + ): + distance_defaults = (3.0, 20.0) + elif sound_class in ( + "object_impacts", "particle_impacts", "slow_particle_impacts", + "device_computers", "ambient_computers", "first_person_damage", + ): + distance_defaults = (0.5, 3.0) + elif sound_class in ( + "projectile_impact", "vehicle_collision", "vehicle_engine" + ): + distance_defaults = (1.4, 8.0) + elif sound_class == "weapon_fire": + distance_defaults = (4.0, 70.0) + elif sound_class == "scripted_effect": + distance_defaults = (2.0, 5.0) + elif sound_class == "projectile_detonation": + distance_defaults = (8.0, 120.0) + elif sound_class == "unit_footsteps": + distance_defaults = (0.9, 10.0) + + zero_gain_modifier_default = 1.0 + if sound_class in ( + "object_impacts", "particle_impacts", "slow_particle_impacts", + "unit_dialog", "music", "ambient_nature", "ambient_machinery", + "ambient_computers", "scripted_dialog_player", + "scripted_effect", "scripted_dialog_other", + "scripted_dialog_force_unspatialized" + ): + zero_gain_modifier_default = 0.0 + + if distance_defaults: + if not tagdata.minimum_distance: + tagdata.minimum_distance = distance_defaults[0] + + if not tagdata.maximum_distance: + tagdata.maximum_distance = distance_defaults[1] + + if (not tagdata.modifiers_when_scale_is_zero.gain and + not tagdata.modifiers_when_scale_is_one.gain + ): + tagdata.modifiers_when_scale_is_zero.gain = zero_gain_modifier_default + tagdata.modifiers_when_scale_is_one.gain = 1.0 \ No newline at end of file diff --git a/reclaimer/hek/defs/part.py b/reclaimer/hek/defs/part.py index 9ee519f7..0b1a86f5 100644 --- a/reclaimer/hek/defs/part.py +++ b/reclaimer/hek/defs/part.py @@ -8,7 +8,7 @@ # from ...common_descs import * -from .objs.tag import HekTag +from .objs.part import PartTag from supyr_struct.defs.tag_def import TagDef part_body = Struct("tagdata", @@ -50,7 +50,7 @@ Float("to", UNIT_SCALE=per_sec_unit_scale), ORIENT='h', SIDETIP='frames/sec' ), - Float("contact_deterioration"), + Float("contact_deterioration", VISIBLE=False, DEFAULT=0.0), Float("fade_start_size", SIDETIP="pixels"), Float("fade_end_size", SIDETIP="pixels"), @@ -60,7 +60,8 @@ SInt16("looping_sequence_count"), SInt16("final_sequence_count"), - Pad(12), + Pad(8), + FlFloat("sprite_size", VISIBLE=False), SEnum16("orientation", *render_mode), Pad(38), @@ -101,5 +102,5 @@ def get(): blam_header("part", 2), part_body, - ext=".particle", endian=">", tag_cls=HekTag, + ext=".particle", endian=">", tag_cls=PartTag, ) diff --git a/reclaimer/hek/defs/rain.py b/reclaimer/hek/defs/rain.py index 39372bda..4e4132c5 100644 --- a/reclaimer/hek/defs/rain.py +++ b/reclaimer/hek/defs/rain.py @@ -53,10 +53,11 @@ Pad(32), QStruct("color_lower_bound", INCLUDE=argb_float), QStruct("color_upper_bound", INCLUDE=argb_float), + FlFloat("sprite_size", VISIBLE=False), #Shader Struct("shader", - Pad(64), + Pad(60), dependency("sprite_bitmap", "bitm"), SEnum16("render_mode", *render_mode), SEnum16("render_direction_source", @@ -64,7 +65,8 @@ "from_acceleration" ), - Pad(40), + Pad(36), + FlUInt32("unknown", VISIBLE=False), Bool16("shader_flags", *shader_flags), SEnum16("framebuffer_blend_function", *framebuffer_blend_functions), SEnum16("framebuffer_fade_mode", *render_fade_mode), diff --git a/reclaimer/hek/defs/sbsp.py b/reclaimer/hek/defs/sbsp.py index 42d29085..c8afe74e 100644 --- a/reclaimer/hek/defs/sbsp.py +++ b/reclaimer/hek/defs/sbsp.py @@ -17,6 +17,13 @@ "Add 0x8000 to get fog index." ) +# calculated when compiled into map +material_type_cache_enum = FlSEnum16("material_type", + *(tuple((materials_list[i], i) for i in + range(len(materials_list))) + (("NONE", -1), )), + VISIBLE=False + ) + # the order is an array of vertices first, then an array of lightmap vertices. # @@ -69,7 +76,8 @@ collision_material = Struct("collision_material", dependency("shader", valid_shaders), - FlUInt32("unknown", VISIBLE=False), + Pad(2), + material_type_cache_enum, SIZE=20 ) @@ -289,10 +297,7 @@ fog_plane = Struct("fog_plane", SInt16("front_region"), - FlSEnum16("material_type", - *(tuple((materials_list[i], i) for i in - range(len(materials_list))) + (("NONE", -1), )), - VISIBLE=False), # calculated when compiled into map + material_type_cache_enum, QStruct("plane", INCLUDE=plane), reflexive("vertices", vertex, 4096), SIZE=32 diff --git a/reclaimer/hek/defs/scnr.py b/reclaimer/hek/defs/scnr.py index 1df3d5e3..4723f518 100644 --- a/reclaimer/hek/defs/scnr.py +++ b/reclaimer/hek/defs/scnr.py @@ -354,7 +354,10 @@ def object_swatch(name, def_id, size=48): ) light_fixture = object_reference("light_fixture", - *_object_ref_pad_fields, + FlUInt16("bsp_indices_mask", VISIBLE=False), + Pad(2), + Pad(1), + Pad(3), dyn_senum16("power_group", DYN_NAME_PATH=".....device_groups.STEPTREE[DYN_I].name"), dyn_senum16("position_group", @@ -486,8 +489,9 @@ def object_swatch(name, def_id, size=48): SInt16("team_index"), SInt16("spawn_time", SIDETIP="seconds(0 = default)", UNIT_SCALE=sec_unit_scale), # seconds + FlUInt32("unknown", VISIBLE=False), - Pad(48), + Pad(44), QStruct("position", INCLUDE=xyz_float), float_rad("facing"), # radians dependency("item_collection", "itmc"), @@ -596,7 +600,7 @@ def object_swatch(name, def_id, size=48): ) cutscene_camera_point = Struct("cutscene_camera_point", - Pad(4), + FlUInt32("unknown", VISIBLE=False), ascii_str32("name"), Pad(4), QStruct("position", INCLUDE=xyz_float), @@ -606,7 +610,7 @@ def object_swatch(name, def_id, size=48): ) cutscene_title = Struct("cutscene_title", - Pad(4), + FlUInt32("unknown", VISIBLE=False), ascii_str32("name"), Pad(4), QStruct("text_bounds", @@ -648,8 +652,11 @@ def object_swatch(name, def_id, size=48): dyn_senum16("animation", DYN_NAME_PATH="tagdata.ai_animation_references.STEPTREE[DYN_I].animation_name"), SInt8("sequence_id"), + Pad(1), + Pad(8), - Pad(45), + FlUInt16("cluster_index", VISIBLE=False), + Pad(34), SInt32("surface_index"), SIZE=80 ) @@ -657,7 +664,7 @@ def object_swatch(name, def_id, size=48): actor_starting_location = Struct("starting_location", QStruct("position", INCLUDE=xyz_float), float_rad("facing"), # radians - Pad(2), + FlUInt16("cluster_index", VISIBLE=False), SInt8("sequence_id"), Bool8("flags", "required", @@ -781,7 +788,7 @@ def object_swatch(name, def_id, size=48): {NAME: "unused8", GUI_NAME: "8 / unused8"}, {NAME: "unused9", GUI_NAME: "9 / unused9"} ), - SInt16('unknown', VISIBLE=False), + FlUInt16('unknown', VISIBLE=False, DEFAULT=1), SEnum16("search_behavior", "normal", "never", @@ -821,6 +828,7 @@ def object_swatch(name, def_id, size=48): point = Struct("point", QStruct("position", INCLUDE=xyz_float), + FlUInt32("surface_index", VISIBLE=False), SIZE=20 ) @@ -869,8 +877,14 @@ def object_swatch(name, def_id, size=48): dyn_senum16("set_new_name", DYN_NAME_PATH="tagdata.object_names.STEPTREE[DYN_I].name"), Pad(12), - BytesRaw("unknown", DEFAULT=b"\xFF"*12, SIZE=12, VISIBLE=False), + FlUInt16("variant_1_id", VISIBLE=False), + FlUInt16("variant_2_id", VISIBLE=False), + FlUInt16("variant_3_id", VISIBLE=False), + FlUInt16("variant_4_id", VISIBLE=False), + FlUInt16("variant_5_id", VISIBLE=False), + FlUInt16("variant_6_id", VISIBLE=False), ascii_str32("encounter_name"), + FlUInt32("encounter_index", VISIBLE=False), SIZE=84 ) diff --git a/reclaimer/hek/defs/snd_.py b/reclaimer/hek/defs/snd_.py index a5960edf..a5769eb6 100644 --- a/reclaimer/hek/defs/snd_.py +++ b/reclaimer/hek/defs/snd_.py @@ -76,11 +76,10 @@ UInt32("sample_data_pointer", VISIBLE=False), UInt32("unknown", VISIBLE=False), # always zero? UInt32("parent_tag_id", VISIBLE=False), - # this is actually the required length of the ogg + # for ogg vorbis, this is the required length of the # decompression buffer. For "none" compression, this - # mirrors samples.size, so a more appropriate name - # for this field should be pcm_buffer_size - FlUInt32("ogg_sample_count", EDITABLE=False), + # mirrors samples.size + FlUInt32("buffer_size", EDITABLE=False), UInt32("parent_tag_id2", VISIBLE=False), rawdata_ref("samples", max_size=4194304, widget=SoundSampleFrame), rawdata_ref("mouth_data", max_size=8192), @@ -149,8 +148,11 @@ compression, dependency("promotion_sound", "snd!"), SInt16("promotion_count"), - SInt16("unknown1", VISIBLE=False), - Struct("unknown2", INCLUDE=rawdata_ref_struct, VISIBLE=False), + Pad(2), + FlUInt32("max_play_length", VISIBLE=False), + Pad(8), + FlUInt32("unknown1", VISIBLE=False, DEFAULT=0xFFFFFFFF), + FlUInt32("unknown2", VISIBLE=False, DEFAULT=0xFFFFFFFF), reflexive("pitch_ranges", pitch_range, 8, DYN_NAME_PATH='.name'), diff --git a/reclaimer/hek/defs/snde.py b/reclaimer/hek/defs/snde.py index 1a5ad2dc..8ce07ee3 100644 --- a/reclaimer/hek/defs/snde.py +++ b/reclaimer/hek/defs/snde.py @@ -12,7 +12,7 @@ from supyr_struct.defs.tag_def import TagDef snde_body = QStruct("tagdata", - Pad(4), + FlUInt32("unknown", VISIBLE=False), UInt16("priority"), Pad(2), Float("room_intensity"), diff --git a/reclaimer/hek/defs/soso.py b/reclaimer/hek/defs/soso.py index ca8f22de..e2b03a1a 100644 --- a/reclaimer/hek/defs/soso.py +++ b/reclaimer/hek/defs/soso.py @@ -113,7 +113,9 @@ QStruct("parallel_tint_color", INCLUDE=rgb_float), dependency("cube_map", "bitm"), - #COMMENT=reflection_prop_comment + Pad(16), + + COMMENT=reflection_prop_comment ) soso_attrs = Struct("soso_attrs", @@ -124,7 +126,6 @@ #Color-Change SEnum16("color_change_source", *function_names, COMMENT=cc_comment), - Pad(30), #Self-Illumination self_illumination, @@ -142,10 +143,15 @@ Pad(8), #Reflection Properties reflection, - Pad(16), - Float("unknown0", VISIBLE=False), - BytesRaw("unknown1", SIZE=16, VISIBLE=False), # little endian dependency + # NOTE: this isn't actually used in pc, but may be usable on xbox. + Float("reflection_bump_scale", VISIBLE=False), + dependency("reflection_bump_map", "bitm", VISIBLE=False, COMMENT=""" + DO NOT USE THIS UNLESS YOU ARE MODDING XBOX HALO 1. + + It has not been tested to confirm if it works on + Xbox, but it certainly doesnt work on PC or later. + """), SIZE=400 ) diff --git a/reclaimer/hek/defs/unit.py b/reclaimer/hek/defs/unit.py index 9e556ef5..453c96cb 100644 --- a/reclaimer/hek/defs/unit.py +++ b/reclaimer/hek/defs/unit.py @@ -186,7 +186,8 @@ def get(): SEnum16('grenade_type', *grenade_types), SInt16('grenade_count', MIN=0), - Pad(4), + FlUInt16("soft_ping_stun_ticks", VISIBLE=False), # set to soft_ping_interrupt_time * 30 + FlUInt16("hard_ping_stun_ticks", VISIBLE=False), # set to hard_ping_interrupt_time * 30 reflexive("powered_seats", powered_seat, 2, "driver", "gunner"), reflexive("weapons", weapon, 4, DYN_NAME_PATH='.weapon.filepath'), diff --git a/reclaimer/hek/handler.py b/reclaimer/hek/handler.py index 3414faf5..05a3ef2b 100644 --- a/reclaimer/hek/handler.py +++ b/reclaimer/hek/handler.py @@ -176,6 +176,10 @@ def get_nodes_by_paths(self, paths, node, cond=lambda x, y: True): def get_def_id(self, filepath): filepath = Path(filepath) + if is_path_empty(filepath): + # return None instead of throwing an error about a non-existent file + return + if self.tagsdir_relative and not filepath.is_absolute(): filepath = self.tagsdir.joinpath(filepath) diff --git a/reclaimer/mcc_hek/defs/bitm.py b/reclaimer/mcc_hek/defs/bitm.py index 749aa9a8..f7b136aa 100644 --- a/reclaimer/mcc_hek/defs/bitm.py +++ b/reclaimer/mcc_hek/defs/bitm.py @@ -15,7 +15,11 @@ format_comment = "".join(( format_comment_parts[0], """\ -*HIGH QUALITY COMPRESSION: ???? +*HIGH QUALITY COMPRESSION: Block compression format similar to DXT3 and DXT5, + with same size as DXT3/DXT5(8-bits per pixel), but with higher quality + results. The format is far too complex to describe short-hand here, but + for those interested in learning about it, it is described here: + https://learn.microsoft.com/en-us/windows/win32/direct3d11/bc7-format NOTE:""", format_comment_parts[1], @@ -37,8 +41,21 @@ ("p8_bump", 17), ("bc7", 18), ) +bitmap_flags = Bool16("flags", + "power_of_2_dim", + "compressed", + "palletized", + "swizzled", + "linear", + "v16u16", + {NAME: "unknown", VISIBLE: False}, + {NAME: "prefer_low_detail", VISIBLE: False}, + {NAME: "data_in_resource_map", VISIBLE: False}, + {NAME: "environment", VISIBLE: False}, + ) bitmap = desc_variant(bitmap, ("format", bitmap_format), + ("flags", bitmap_flags), ) body_format = SEnum16("format", "color_key_transparency", @@ -55,7 +72,7 @@ "disable_height_map_compression", "uniform_sprite_sequences", "sprite_bug_fix", - "hud_scale_0.5", + {NAME: "hud_scale_half", GUI_NAME: "hud scale 50%"}, "invert_detail_fade", "use_average_color_for_detail_fade" ) diff --git a/reclaimer/mcc_hek/defs/cdmg.py b/reclaimer/mcc_hek/defs/cdmg.py index 202a7bdd..439eae53 100644 --- a/reclaimer/mcc_hek/defs/cdmg.py +++ b/reclaimer/mcc_hek/defs/cdmg.py @@ -15,7 +15,7 @@ {NAME: "headshot", GUI_NAME: "can cause headshots"}, "pings_resistant_units", "does_not_hurt_friends", - "does_not_ping_shields", + "does_not_ping_units", "detonates_explosives", "only_hurts_shields", "causes_flaming_death", @@ -33,9 +33,12 @@ damage = desc_variant(damage, ("flags", damage_flags), - ("instantaneous_acceleration", QStruct("instantaneous_acceleration", INCLUDE=ijk_float)), + ("instantaneous_acceleration", QStruct("instantaneous_acceleration", INCLUDE=ijk_float, + SIDETIP="[-inf,+inf]", ORIENT="h" + )), ("pad_13", Pad(0)), ) + cdmg_body = desc_variant(cdmg_body, ("damage", damage), ) diff --git a/reclaimer/mcc_hek/defs/deca.py b/reclaimer/mcc_hek/defs/deca.py index a95d13b8..988271e4 100644 --- a/reclaimer/mcc_hek/defs/deca.py +++ b/reclaimer/mcc_hek/defs/deca.py @@ -35,5 +35,5 @@ def get(): blam_header('deca'), deca_body, - ext=".decal", endian=">", tag_cls=HekTag + ext=".decal", endian=">", tag_cls=DecaTag ) diff --git a/reclaimer/mcc_hek/defs/effe.py b/reclaimer/mcc_hek/defs/effe.py index 5495f73d..cdc2d3f7 100644 --- a/reclaimer/mcc_hek/defs/effe.py +++ b/reclaimer/mcc_hek/defs/effe.py @@ -11,8 +11,9 @@ from supyr_struct.util import desc_variant flags = Bool32("flags", - {NAME: "deleted_when_inactive", GUI_NAME: "deleted_when_attachment_deactivates"}, - "must_be_deterministic", + {NAME: "deleted_when_inactive", GUI_NAME: "deleted when attachment deactivates"}, + {NAME: "required", GUI_NAME: "required for gameplay (cannot optimize out)"}, + {NAME: "must_be_deterministic", VISIBLE: VISIBILITY_HIDDEN}, "disabled_in_remastered_by_blood_setting" ) diff --git a/reclaimer/mcc_hek/defs/jpt_.py b/reclaimer/mcc_hek/defs/jpt_.py index 169c38e5..9ce55dc2 100644 --- a/reclaimer/mcc_hek/defs/jpt_.py +++ b/reclaimer/mcc_hek/defs/jpt_.py @@ -13,7 +13,9 @@ damage = desc_variant(damage, ("flags", damage_flags), - ("instantaneous_acceleration", QStruct("instantaneous_acceleration", INCLUDE=ijk_float)), + ("instantaneous_acceleration", QStruct("instantaneous_acceleration", INCLUDE=ijk_float, + SIDETIP="[-inf,+inf]", ORIENT="h" + )), ("pad_13", Pad(0)), ) diff --git a/reclaimer/mcc_hek/defs/scnr.py b/reclaimer/mcc_hek/defs/scnr.py index b61f81b4..5e09edcc 100644 --- a/reclaimer/mcc_hek/defs/scnr.py +++ b/reclaimer/mcc_hek/defs/scnr.py @@ -126,5 +126,5 @@ def get(): blam_header('scnr', 2), scnr_body, - ext=".scenario", endian=">", tag_cls=HekTag + ext=".scenario", endian=">", tag_cls=ScnrTag ) diff --git a/reclaimer/mcc_hek/defs/soso.py b/reclaimer/mcc_hek/defs/soso.py index 0fc730e9..3caa4368 100644 --- a/reclaimer/mcc_hek/defs/soso.py +++ b/reclaimer/mcc_hek/defs/soso.py @@ -27,6 +27,8 @@ soso_attrs = desc_variant(soso_attrs, ("model_shader", model_shader), + ("reflection_bump_scale", Pad(4)), + ("reflection_bump_map", Pad(16)), ) soso_body = Struct("tagdata", diff --git a/reclaimer/meta/halo1_map.py b/reclaimer/meta/halo1_map.py index 3a5c20f0..125504b7 100644 --- a/reclaimer/meta/halo1_map.py +++ b/reclaimer/meta/halo1_map.py @@ -179,6 +179,17 @@ def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): ) ) +map_version = UEnum32("version", + ("halo1xbox", 5), + ("halo1pcdemo", 6), + ("halo1pc", 7), + ("halo2", 8), + ("halo3beta", 9), + ("halo3", 11), + ("halo1mcc", 13), + ("halo1ce", 609), + ("halo1vap", 134), + ) # Halo Demo maps have a different header # structure with garbage filling the padding @@ -194,16 +205,7 @@ def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): UInt32("tag data size"), ascii_str32("build date", EDITABLE=False), Pad(672), - UEnum32("version", - ("halo1xbox", 5), - ("halo1pcdemo", 6), - ("halo1pc", 7), - ("halo2", 8), - ("halo3beta", 9), - ("halo3", 11), - ("halo1ce", 609), - ("halo1vap", 134), - ), + map_version, ascii_str32("map name"), UInt32("unknown"), UInt32("crc32"), @@ -217,17 +219,7 @@ def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): map_header = Struct("map header", UEnum32('head', ('head', 'head'), DEFAULT='head'), - UEnum32("version", - ("halo1xbox", 5), - ("halo1pcdemo", 6), - ("halo1pc", 7), - ("halo2", 8), - ("halo3beta", 9), - ("halo3", 11), - ("halo1mcc", 13), - ("halo1ce", 609), - ("halo1vap", 134), - ), + map_version, UInt32("decomp len"), UInt32("unknown"), UInt32("tag index header offset"), @@ -266,13 +258,13 @@ def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): ("yelo_header", Struct("vap_header", INCLUDE=vap_header, OFFSET=128)), ) -tag_header = Struct("tag header", - UEnum32("class 1", GUI_NAME="primary tag class", INCLUDE=valid_tags_os), - UEnum32("class 2", GUI_NAME="secondary tag class", INCLUDE=valid_tags_os), - UEnum32("class 3", GUI_NAME="tertiary tag class", INCLUDE=valid_tags_os), +tag_header = Struct("tag_header", + UEnum32("class_1", GUI_NAME="primary tag class", INCLUDE=valid_tags_os), + UEnum32("class_2", GUI_NAME="secondary tag class", INCLUDE=valid_tags_os), + UEnum32("class_3", GUI_NAME="tertiary tag class", INCLUDE=valid_tags_os), UInt32("id"), - UInt32("path offset"), - UInt32("meta offset"), + UInt32("path_offset"), + UInt32("meta_offset"), UInt8("indexed"), Pad(3), # if indexed is non-zero, the meta_offset is the literal index in @@ -283,42 +275,42 @@ def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): SIZE=32 ) -tag_index_array = TagIndex("tag index", +tag_index_array = TagIndex("tag_index", SIZE=".tag_count", SUB_STRUCT=tag_header, POINTER=tag_index_array_pointer ) -tag_index_xbox = Struct("tag index", - UInt32("tag index offset"), - UInt32("scenario tag id"), - UInt32("map id"), # normally unused, but can be used +tag_index_xbox = Struct("tag_index", + UInt32("tag_index_offset"), + UInt32("scenario_tag_id"), + UInt32("map_id"), # normally unused, but can be used # for spoofing the maps checksum. - UInt32("tag count"), + UInt32("tag_count"), - UInt32("vertex parts count"), - UInt32("model data offset"), + UInt32("vertex_parts_count"), + UInt32("model_data_offset"), - UInt32("index parts count"), - UInt32("index parts offset"), - UInt32("tag sig", EDITABLE=False, DEFAULT='tags'), + UInt32("index_parts_count"), + UInt32("index_parts_offset"), + UInt32("tag_sig", EDITABLE=False, DEFAULT='tags'), SIZE=36, STEPTREE=tag_index_array ) -tag_index_pc = Struct("tag index", - UInt32("tag index offset"), - UInt32("scenario tag id"), - UInt32("map id"), # normally unused, but can be used +tag_index_pc = Struct("tag_index", + UInt32("tag_index_offset"), + UInt32("scenario_tag_id"), + UInt32("map_id"), # normally unused, but can be used # for spoofing the maps checksum. - UInt32("tag count"), + UInt32("tag_count"), - UInt32("vertex parts count"), - UInt32("model data offset"), + UInt32("vertex_parts_count"), + UInt32("model_data_offset"), - UInt32("index parts count"), - UInt32("vertex data size"), - UInt32("model data size"), - UInt32("tag sig", EDITABLE=False, DEFAULT='tags'), + UInt32("index_parts_count"), + UInt32("index_parts_offset"), + UInt32("model_data_size"), + UInt32("tag_sig", EDITABLE=False, DEFAULT='tags'), SIZE=40, STEPTREE=tag_index_array diff --git a/reclaimer/meta/halo_map.py b/reclaimer/meta/halo_map.py index 8c2cad23..eb1b7a84 100644 --- a/reclaimer/meta/halo_map.py +++ b/reclaimer/meta/halo_map.py @@ -24,7 +24,8 @@ h2_tag_index_def from reclaimer.meta.halo3_map import h3_map_header_def, h3_tag_index_def from reclaimer.meta.shadowrun_map import sr_tag_index_def -from reclaimer.meta.stubbs_map import stubbs_tag_index_def +from reclaimer.meta.stubbs_map import stubbs_tag_index_def,\ + stubbs_64bit_tag_index_def from supyr_struct.defs.tag_def import TagDef from supyr_struct.buffer import get_rawdata @@ -58,8 +59,9 @@ def get_map_version(header): # Xbox maps don't have a build date, but they do have this bit of data if header.unknown in (11, 1033): version = "halo1xboxdemo" - # Stubs PC headers match xbox headers without the unknown data - else: + else: # Stubs PC headers match xbox headers without the unknown data + # unfortunately, its impossible to tell apart 32 and 64bit stubbs + # maps from just the header alone. you need to look at the tag index version = "stubbspc" elif build_date == map_build_dates["shadowrun_proto"]: version = "shadowrun_proto" @@ -92,6 +94,19 @@ def get_map_version(header): return version +def get_engine_name(map_header, map_data): + # NOTE: this is basically just get_map_version, but with the ability to + # further refine the choice by looking at the rest of the map data. + engine = get_map_version(map_header) + if engine == "stubbspc": + map_data.seek(map_header.tag_index_header_offset + 52) + # thank god for the tags sig + if map_data.read(4) == b'sgat': + engine = "stubbspc64bit" + + return engine + + def get_map_header(map_file, header_only=False): if hasattr(map_file, "read"): orig_pos = map_file.tell() @@ -172,16 +187,20 @@ def get_tag_index(map_data, header=None): base_address = header.tag_index_header_offset tag_index_def = tag_index_pc_def - version = get_map_version(header) - if "shadowrun" in version: + engine = get_engine_name(header, map_data) + if "shadowrun" in engine: tag_index_def = sr_tag_index_def - elif "stubbs" in version: - tag_index_def = stubbs_tag_index_def + elif "stubbs" in engine: + tag_index_def = ( + stubbs_64bit_tag_index_def + if engine == "stubbspc64bit" else + stubbs_tag_index_def + ) elif header.version.data < 6: tag_index_def = tag_index_xbox_def - elif version == "halo2alpha": + elif engine == "halo2alpha": tag_index_def = h2_alpha_tag_index_def - elif version == "halo1anni": + elif engine == "halo1anni": tag_index_def = tag_index_anni_def elif header.version.enum_name == "halo2": tag_index_def = h2_tag_index_def @@ -232,7 +251,7 @@ def get_is_compressed_map(comp_data, header): return header.vap_header.compression_type.data != 0 elif header.version.data not in (7, 13, 609): decomp_len = header.decomp_len - if get_map_version(header) == "pcstubbs": + if get_map_version(header) == "stubbspc": decomp_len -= 2048 return decomp_len > len(comp_data) @@ -299,7 +318,7 @@ def decompress_map_deflate(comp_data, header, decomp_path="", writable=False): decomp_start = 2048 decomp_len = header.decomp_len version = get_map_version(header) - if version == "pcstubbs": + if version == "stubbspc": decomp_len -= 2048 elif version == "halo2vista": decomp_start = 0 diff --git a/reclaimer/meta/objs/halo1_rsrc_map.py b/reclaimer/meta/objs/halo1_rsrc_map.py index 37394152..b3c188d9 100644 --- a/reclaimer/meta/objs/halo1_rsrc_map.py +++ b/reclaimer/meta/objs/halo1_rsrc_map.py @@ -336,8 +336,12 @@ def _add_sound(self, tag_path, new_tag, base_pointer, depreciate): pr.playback_rate = 1.0 pr.unknown1 = pr.unknown2 = -1 for perm in pr.permutations.STEPTREE: - perm.unknown = 0 - perm.parent_tag_id = perm.parent_tag_id2 = 0xFFffFFff + perm.sample_data_pointer = perm.parent_tag_id = perm.unknown = 0 + if hasattr(perm, "runtime_flags"): # mcc + perm.runtime_flags = 0 + else: # non-mcc + perm.parent_tag_id2 = 0 + sample_data = perm.samples.data if perm.compression.enum_name == "none": sample_data = array(">h", sample_data) diff --git a/reclaimer/meta/stubbs_map.py b/reclaimer/meta/stubbs_map.py index b8277344..79cb2cde 100644 --- a/reclaimer/meta/stubbs_map.py +++ b/reclaimer/meta/stubbs_map.py @@ -7,46 +7,54 @@ # See LICENSE for more information. # -from reclaimer.meta.halo1_map import tag_path_pointer, tag_index_array_pointer +from reclaimer.meta.halo1_map import tag_index_xbox, map_version,\ + tag_header as tag_index_header, tag_path_pointer, tag_index_array_pointer from reclaimer.stubbs.common_descs import * +from supyr_struct.util import desc_variant +stubbs_tag_index_header = desc_variant(tag_index_header, + ("class_1", UEnum32("class_1", GUI_NAME="primary tag class", INCLUDE=stubbs_valid_tags)), + ("class_2", UEnum32("class_2", GUI_NAME="secondary tag class", INCLUDE=stubbs_valid_tags)), + ("class_3", UEnum32("class_3", GUI_NAME="tertiary tag class", INCLUDE=stubbs_valid_tags)), + ) -stubbs_tag_header = Struct("tag header", - UEnum32("class 1", GUI_NAME="primary tag class", INCLUDE=stubbs_valid_tags), - UEnum32("class 2", GUI_NAME="secondary tag class", INCLUDE=stubbs_valid_tags), - UEnum32("class 3", GUI_NAME="tertiary tag class", INCLUDE=stubbs_valid_tags), - UInt32("id"), - UInt32("path offset"), - UInt32("meta offset"), - UInt32("indexed"), - # if indexed is 1, the meta_offset is the literal index in the - # bitmaps, sounds, or loc cache that the meta data is located in. - Pad(4), - STEPTREE=CStrTagRef("path", POINTER=tag_path_pointer, MAX=768), - SIZE=32 +stubbs_64bit_tag_index_header = desc_variant(stubbs_tag_index_header, + ("path_offset", Pointer64("path_offset")), + ("meta_offset", Pointer64("meta_offset")), ) +stubbs_64bit_tag_index_header.update(SIZE=40) stubbs_tag_index_array = TagIndex("tag index", - SIZE=".tag_count", SUB_STRUCT=stubbs_tag_header, + SIZE=".tag_count", SUB_STRUCT=stubbs_tag_index_header, + POINTER=tag_index_array_pointer + ) + +stubbs_64bit_tag_index_array = TagIndex("tag index", + SIZE=".tag_count", SUB_STRUCT=stubbs_64bit_tag_index_header, POINTER=tag_index_array_pointer ) -stubbs_tag_index = Struct("tag index", - UInt32("tag index offset"), - UInt32("scenario tag id"), - UInt32("map id"), # normally unused, but the scenario tag's header - # can be used for spoofing the maps checksum - UInt32("tag count"), +stubbs_tag_index = dict(tag_index_xbox) +stubbs_tag_index.update(STEPTREE=stubbs_tag_index_array) - UInt32("vertex parts count"), - UInt32("model data offset"), +stubbs_64bit_tag_index = Struct("tag_index", + Pointer64("tag_index_offset"), + UInt32("scenario_tag_id"), + UInt32("map_id"), # normally unused, but can be used + # for spoofing the maps checksum. + UInt32("tag_count"), - UInt32("index parts count"), - UInt32("index parts offset"), - UInt32("tag sig", EDITABLE=False, DEFAULT='tags'), + UInt32("vertex_parts_count"), + Pointer64("model_data_offset"), - SIZE=36, - STEPTREE=stubbs_tag_index_array + UInt32("index_parts_count"), + Pad(4), + Pointer64("index_parts_offset"), + UInt32("model_data_size"), + UInt32("tag_sig", EDITABLE=False, DEFAULT='tags'), + STEPTREE=stubbs_64bit_tag_index_array, + SIZE=56 ) -stubbs_tag_index_def = BlockDef(stubbs_tag_index) +stubbs_tag_index_def = BlockDef(stubbs_tag_index) +stubbs_64bit_tag_index_def = BlockDef(stubbs_64bit_tag_index) diff --git a/reclaimer/meta/wrappers/halo1_anni_map.py b/reclaimer/meta/wrappers/halo1_anni_map.py index d72315a9..b7be7fd6 100644 --- a/reclaimer/meta/wrappers/halo1_anni_map.py +++ b/reclaimer/meta/wrappers/halo1_anni_map.py @@ -249,7 +249,7 @@ def byteswap_anniversary_fields(self, meta, tag_cls): elif tag_cls == "coll": for b in meta.nodes.STEPTREE: b.unknown = end_swap_int16(b.unknown) - b.damage_region.data = end_swap_int16(b.damage_region.data) + b.damage_region = end_swap_int16(b.damage_region) elif tag_cls == "effe": for event in meta.events.STEPTREE: @@ -307,7 +307,7 @@ def byteswap_anniversary_fields(self, meta, tag_cls): # TODO: Might need to byteswap cluster data and sound_pas data for coll_mat in meta.collision_materials.STEPTREE: - coll_mat.unknown = end_swap_uint32(coll_mat.unknown) + coll_mat.material_type.data = end_swap_int16(coll_mat.material_type.data) node_data = meta.nodes.STEPTREE for i in range(0, len(node_data), 2): @@ -377,14 +377,14 @@ def byteswap_anniversary_fields(self, meta, tag_cls): elif tag_cls == "scnr": for b in meta.object_names.STEPTREE: - b.object_type.data = end_swap_uint16(b.object_type.data) + b.object_type.data = end_swap_int16(b.object_type.data) b.reflexive_index = end_swap_int16(b.reflexive_index) for b in meta.trigger_volumes.STEPTREE: b.unknown0 = end_swap_uint16(b.unknown0) for b in meta.encounters.STEPTREE: - b.unknown = end_swap_int16(b.unknown) + b.unknown = end_swap_uint16(b.unknown) # PROLLY GONNA HAVE TO BYTESWAP RECORDED ANIMS AND MORE SHIT syntax_data = meta.script_syntax_data.data @@ -418,7 +418,7 @@ def byteswap_anniversary_fields(self, meta, tag_cls): elif tag_cls == "snd!": for pr in meta.pitch_ranges.STEPTREE: for b in pr.permutations.STEPTREE: - b.ogg_sample_count = end_swap_uint32(b.ogg_sample_count) + b.buffer_size = end_swap_uint32(b.buffer_size) elif tag_cls == "spla": meta.spla_attrs.primary_noise_map.unknown0 = end_swap_uint16( diff --git a/reclaimer/meta/wrappers/halo1_map.py b/reclaimer/meta/wrappers/halo1_map.py index 5d2f01d0..cf66b246 100644 --- a/reclaimer/meta/wrappers/halo1_map.py +++ b/reclaimer/meta/wrappers/halo1_map.py @@ -305,7 +305,7 @@ def setup_rawdata_pages(self): ) # add the model data section - if tag_index.SIZE == 40: + if hasattr(tag_index, "model_data_size"): # PC tag index self.map_pointer_converter.add_page_info( 0, tag_index.model_data_offset, @@ -878,7 +878,10 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): elif tag_cls == "effe": # mask away the meta-only flags - meta.flags.data &= 3 + # NOTE: xbox has a cache flag in the 2nd + # bit, so it should be masked out too. + meta.flags.data &= (1 if "xbox" in engine else 3) + for event in meta.events.STEPTREE: # tool exceptions if any parts reference a damage effect # tag type, but have an empty filepath for the reference @@ -906,7 +909,11 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): elif tag_cls == "lens": # DON'T multiply corona rotation by pi/180 # reminder that this is not supposed to be changed - pass # meta.corona_rotation.function_scale *= pi/180 + + if meta.corona_rotation.function_scale == 360.0: + # fix a really old bug(i think its the + # reason the above comment was created) + meta.corona_rotation.function_scale = 0.0 elif tag_cls == "ligh": # divide light time by 30 @@ -934,14 +941,10 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): elif tag_cls in ("mode", "mod2"): if engine in ("halo1yelo", "halo1ce", "halo1pc", "halo1vap", "halo1mcc", - "halo1anni", "halo1pcdemo", "stubbspc"): + "halo1anni", "halo1pcdemo", "stubbspc", "stubbspc64bit"): # model_magic seems to be the same for all pc maps verts_start = tag_index.model_data_offset - tris_start = verts_start + ( - tag_index.index_parts_offset - if engine == "stubbspc" else - tag_index.vertex_data_size - ) + tris_start = verts_start + tag_index.index_parts_offset model_magic = None else: model_magic = magic @@ -1088,7 +1091,7 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): predicted_resources.append(cluster.predicted_resources) for coll_mat in meta.collision_materials.STEPTREE: - coll_mat.unknown = 0 # supposed to be 0 in tag form + coll_mat.material_type.data = 0 # supposed to be 0 in tag form compressed = "xbox" in engine or engine in ("stubbs", "shadowrun_proto") @@ -1315,6 +1318,11 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): # clear the merged values reflexive del shpg_attrs.merged_values.STEPTREE[:] + elif tag_cls == "soso": + if "xbox" not in engine and engine != "shadowrun_proto": + if hasattr(meta.soso_attrs.reflection, "reflection_bump_map"): + meta.soso_attrs.reflection.reflection_bump_map.filepath = "" + elif tag_cls == "weap": predicted_resources.append(meta.weap_attrs.predicted_resources) @@ -1363,13 +1371,14 @@ def generate_map_info_string(self): index, header = self.tag_index, self.map_header if self.engine == "halo1mcc": - # NOTE: these flags are subject to change, so - # for now they're dynamically named - string += "\n remastered flags:" - for name in header.mcc_flags.NAME_MAP: - line = "\n " + (name.replace("_", " ")) - line += " " * (29-len(line)) + "== " - string += line + str(bool(header.mcc_flags[name])) + string += """\n Calculated information: + use bitmaps map == %s + use sounds map == %s + no remastered sync == %s""" % ( + bool(header.mcc_flags.use_bitmaps_map), + bool(header.mcc_flags.use_sounds_map), + bool(header.mcc_flags.disable_remastered_sync), + ) string += """ @@ -1381,28 +1390,33 @@ def generate_map_info_string(self): tag count == %s scenario tag id == %s index array pointer == %s non-magic == %s - model data pointer == %s meta data length == %s vertex parts count == %s index parts count == %s""" % ( self.index_magic, self.map_magic, index.tag_count, index.scenario_tag_id & 0xFFff, index.tag_index_offset, index.tag_index_offset - self.map_magic, - index.model_data_offset, header.tag_data_size, + header.tag_data_size, index.vertex_parts_count, index.index_parts_count) - if index.SIZE == 36: + if hasattr(index, "model_data_size"): string += """ - index parts pointer == %s non-magic == %s""" % ( - index.index_parts_offset, index.index_parts_offset - self.map_magic) - else: - string += """ - vertex data size == %s + vertex data pointer == %s + index data pointer == %s index data size == %s model data size == %s""" % ( - index.vertex_data_size, - index.model_data_size - index.vertex_data_size, - index.model_data_size) + index.model_data_offset, + index.index_parts_offset, + index.model_data_size - index.index_parts_offset, + index.model_data_size + ) + else: + string += """ + vertex refs pointer == %s non-magic == %s + index refs pointer == %s non-magic == %s""" % ( + index.model_data_offset, index.model_data_offset - self.map_magic, + index.index_parts_offset, index.index_parts_offset - self.map_magic, + ) string += "\n\nSbsp magic and headers:\n" for tag_id in self.bsp_magics: diff --git a/reclaimer/meta/wrappers/halo1_mcc_map.py b/reclaimer/meta/wrappers/halo1_mcc_map.py index 5161f2fe..d896efc7 100644 --- a/reclaimer/meta/wrappers/halo1_mcc_map.py +++ b/reclaimer/meta/wrappers/halo1_mcc_map.py @@ -24,8 +24,20 @@ class Halo1MccMap(Halo1Map): sbsp_meta_header_def = sbsp_meta_header_def - def __init__(self, maps=None): - Halo1Map.__init__(self, maps) + def load_map(self, map_path, **kwargs): + super().load_map(map_path, **kwargs) + + # NOTE: mcc halo 1 resource maps MUST be handled differently than + # pc and ce because of the multiple types they may use. + # mcc maps may reference different bitmaps.map and sounds.map + # depending on what folder they're located in, so we're + # going to ignore any resource maps passed in unless + # they're coming from the same folder as this map. + if self.are_resources_in_same_directory: + print("Unlinking potentially incompatible resource maps from %s" % + self.map_name + ) + self.maps = {} @property def uses_fmod_sound_bank(self): diff --git a/reclaimer/meta/wrappers/halo1_rsrc_map.py b/reclaimer/meta/wrappers/halo1_rsrc_map.py index 0fd04819..eecbdfbe 100644 --- a/reclaimer/meta/wrappers/halo1_rsrc_map.py +++ b/reclaimer/meta/wrappers/halo1_rsrc_map.py @@ -291,6 +291,10 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): # uncheck the prefer_low_detail flag and # set up the pixels_offset correctly. for bitmap in meta.bitmaps.STEPTREE: + # clear meta-only flags + bitmap.flags.data &= 0x3F + + # TODO: convert bitmaps to pc format(swap cubemap faces and mipmaps) bitmap.flags.prefer_low_detail = is_xbox bitmap.pixels_offset = new_pixels_offset new_pixels_offset += bitmap.pixels_meta_size @@ -326,12 +330,30 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): elif tag_cls == "snd!": meta.maximum_bend_per_second = meta.maximum_bend_per_second ** 30 + meta.unknown1 = 0xFFFFFFFF + meta.unknown2 = 0xFFFFFFFF for pitch_range in meta.pitch_ranges.STEPTREE: - for perm in pitch_range.permutations.STEPTREE: - if byteswap and perm.compression.enum_name == "none": - # byteswap pcm audio - byteswap_pcm16_samples(perm.samples) + # null some meta-only fields + pitch_range.playback_rate = 0.0 + pitch_range.unknown1 = -1 + pitch_range.unknown2 = -1 + for perm in pitch_range.permutations.STEPTREE: + if perm.compression.enum_name == "none": + buffer_size = len(perm.samples) + if byteswap: + # byteswap pcm audio + byteswap_pcm16_samples(perm.samples) + elif perm.compression.enum_name == "ogg": + # TODO: find a way to calculate the buffer size here + buffer_size = perm.buffer_size + else: + buffer_size = 0 + + # fix buffer_size possibly being incorrect + perm.buffer_size = buffer_size + + # null some meta-only fields perm.sample_data_pointer = perm.parent_tag_id = perm.unknown = 0 if hasattr(perm, "runtime_flags"): # mcc perm.runtime_flags = 0 diff --git a/reclaimer/meta/wrappers/halo1_xbox_map.py b/reclaimer/meta/wrappers/halo1_xbox_map.py new file mode 100644 index 00000000..c20871f7 --- /dev/null +++ b/reclaimer/meta/wrappers/halo1_xbox_map.py @@ -0,0 +1,21 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# +from reclaimer.meta.wrappers.halo1_map import Halo1Map +from reclaimer.hek.handler import HaloHandler + +class Halo1XboxMap(Halo1Map): + '''Halo 1 Xbox map''' + + # Module path printed when loading the tag defs + tag_defs_module = "reclaimer.hek.defs" + # Handler that controls how to load tags, eg tag definitions + handler_class = HaloHandler + + def __init__(self, maps=None): + super().__init__(maps) diff --git a/reclaimer/meta/wrappers/halo1_yelo.py b/reclaimer/meta/wrappers/halo1_yelo.py index f1eb6e2d..2ab04aee 100644 --- a/reclaimer/meta/wrappers/halo1_yelo.py +++ b/reclaimer/meta/wrappers/halo1_yelo.py @@ -19,3 +19,18 @@ class Halo1YeloMap(Halo1Map): def __init__(self, maps=None): Halo1Map.__init__(self, maps) + + def load_map(self, map_path, **kwargs): + super().load_map(map_path, **kwargs) + + # NOTE: yelo halo 1 resource maps MUST be handled differently than + # pc and ce because of the multiple types they may use. + # yelo maps may reference different resource maps + # depending on what folder they're located in, so we're + # going to ignore any resource maps passed in unless + # they're coming from the same folder as this map. + if self.are_resources_in_same_directory: + print("Unlinking potentially incompatible resource maps from %s" % + self.map_name + ) + self.maps = {} \ No newline at end of file diff --git a/reclaimer/meta/wrappers/halo_map.py b/reclaimer/meta/wrappers/halo_map.py index 7f962578..c050f369 100644 --- a/reclaimer/meta/wrappers/halo_map.py +++ b/reclaimer/meta/wrappers/halo_map.py @@ -18,7 +18,7 @@ from reclaimer.meta.halo_map import get_map_version, get_map_header,\ get_tag_index, get_index_magic, get_map_magic, get_is_compressed_map,\ - decompress_map + decompress_map, get_engine_name from reclaimer.meta.wrappers.map_pointer_converter import MapPointerConverter from reclaimer.util import is_protected_tag, int_to_fourcc, path_normalize @@ -147,6 +147,13 @@ def get_writable_map_data(self): # and replace self.map_data with it return self.map_data + @property + def are_resources_in_same_directory(self): + for map_name, filepath in self.get_resource_map_paths().items(): + if filepath and filepath.parent != self.filepath.parent: + return False + return True + # wrappers around the tag index handler def get_total_dir_count(self, dir=""): return self.tag_index_manager.get_total_dir_count(dir) def get_total_file_count(self, dir=""): return self.tag_index_manager.get_total_file_count(dir) @@ -375,6 +382,9 @@ def load_map(self, map_path, **kwargs): tag_index = self.orig_tag_index = get_tag_index( self.map_data, map_header) + # calculate this more accurately now that the map data is available + self.engine = get_engine_name(map_header, self.map_data) + if tag_index is None: print(" Could not read tag index.") return diff --git a/reclaimer/meta/wrappers/stubbs_map_64bit.py b/reclaimer/meta/wrappers/stubbs_map_64bit.py new file mode 100644 index 00000000..441b30df --- /dev/null +++ b/reclaimer/meta/wrappers/stubbs_map_64bit.py @@ -0,0 +1,25 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.meta.wrappers.stubbs_map import * + +# so, turns out the steam re-release of stubbs uses 64bit pointers in most +# areas of the map(aside from the header it seems). We're going to "support" +# this enough to load and display the map header and index, but nothing else. +# extracting from 64bit stubbs can come later, but for now lets at least not +# have Reclaimer or Refinery crash when trying to load these maps. +class StubbsMap64Bit(StubbsMap): + handler_class = StubbsHandler + + tag_defs_module = StubbsHandler.default_defs_path + tag_classes_to_load = () + defs = () + + def setup_defs(self): + self.defs = {} diff --git a/reclaimer/model/constants.py b/reclaimer/model/constants.py index f8787891..ee338788 100644 --- a/reclaimer/model/constants.py +++ b/reclaimer/model/constants.py @@ -22,6 +22,8 @@ HALO_1_MAX_MARKERS = 256 +HALO_1_MAX_MARKERS_PER_PERM = 32 + # If a jms file is prefixed with this token it # cannot be randomly chosen as a permutation diff --git a/reclaimer/model/jms/file.py b/reclaimer/model/jms/file.py index 32f0ae15..328ba349 100644 --- a/reclaimer/model/jms/file.py +++ b/reclaimer/model/jms/file.py @@ -89,6 +89,10 @@ def _read_jms_8200(jms_data, stop_at="", perm_name=None): print("Could not read node list checksum.") return jms_model + if jms_model.node_list_checksum >= 0x80000000: + # jms gave us an unsigned checksum.... sign it + jms_model.node_list_checksum -= 0x100000000 + stop = (stop_at == "nodes") if not stop: # read the nodes diff --git a/reclaimer/model/model_compilation.py b/reclaimer/model/model_compilation.py index 487c7226..8ee4447e 100644 --- a/reclaimer/model/model_compilation.py +++ b/reclaimer/model/model_compilation.py @@ -12,7 +12,7 @@ from reclaimer.model.constants import ( HALO_1_MAX_MATERIALS, HALO_1_MAX_REGIONS, HALO_1_MAX_GEOMETRIES_PER_MODEL, - SCALE_INTERNAL_TO_JMS, HALO_1_NAME_MAX_LEN + SCALE_INTERNAL_TO_JMS, HALO_1_NAME_MAX_LEN, HALO_1_MAX_MARKERS_PER_PERM ) from reclaimer.model.jms import GeometryMesh from reclaimer.model.stripify import Stripifier @@ -34,7 +34,6 @@ def compile_gbxmodel(mod2_tag, merged_jms, ignore_errors=False): tagdata.node_list_checksum = merged_jms.node_list_checksum - errors = [] if len(merged_jms.materials) > HALO_1_MAX_MATERIALS: errors.append("Too many materials. Max count is %s." % (HALO_1_MAX_MATERIALS)) @@ -98,7 +97,6 @@ def compile_gbxmodel(mod2_tag, merged_jms, ignore_errors=False): global_markers = {} geom_meshes = [] - all_lod_nodes = {lod: set([0]) for lod in util.LOD_NAMES} for region_name in sorted(merged_jms.regions): region = merged_jms.regions[region_name] @@ -129,17 +127,6 @@ def compile_gbxmodel(mod2_tag, merged_jms, ignore_errors=False): lod_mesh = perm.lod_meshes[lod_name] geom_meshes.append(lod_mesh) - # figure out which nodes this mesh utilizes - this_meshes_nodes = set() - for mesh in lod_mesh.values(): - for vert in mesh.verts: - if vert.node_1_weight < 1: - this_meshes_nodes.add(vert.node_0) - if vert.node_1_weight > 0: - this_meshes_nodes.add(vert.node_1) - - all_lod_nodes[lod_name].update(this_meshes_nodes) - lods_to_set = list(range(i, 5)) if skipped_lods: lods_to_set.extend(skipped_lods) @@ -152,25 +139,24 @@ def compile_gbxmodel(mod2_tag, merged_jms, ignore_errors=False): perm_added = True - # What are we doing here? - if len(perm.markers) > 32: - for marker in perm.markers: - global_markers.setdefault( - marker.name[: HALO_1_NAME_MAX_LEN], []).append(marker) - else: - perm_added |= bool(perm.markers) - mod2_markers = mod2_perm.local_markers.STEPTREE - for marker in perm.markers: - mod2_markers.append() - mod2_marker = mod2_markers[-1] + if len(perm.markers) > HALO_1_MAX_MARKERS_PER_PERM and not ignore_errors: + return ("Cannot add more than %s markers to a permutation. " + "This model would contain %s markers." % ( + HALO_1_MAX_MARKERS_PER_PERM, len(perm.markers)), ) + + perm_added |= bool(perm.markers) + mod2_markers = mod2_perm.local_markers.STEPTREE + for marker in perm.markers: + mod2_markers.append() + mod2_marker = mod2_markers[-1] - mod2_marker.name = marker.name[: HALO_1_NAME_MAX_LEN] - mod2_marker.node_index = marker.parent - mod2_marker.translation[:] = marker.pos_x / SCALE_INTERNAL_TO_JMS,\ - marker.pos_y / SCALE_INTERNAL_TO_JMS,\ - marker.pos_z / SCALE_INTERNAL_TO_JMS - mod2_marker.rotation[:] = marker.rot_i, marker.rot_j,\ - marker.rot_k, marker.rot_w + mod2_marker.name = marker.name[: HALO_1_NAME_MAX_LEN] + mod2_marker.node_index = marker.parent + mod2_marker.translation[:] = marker.pos_x / SCALE_INTERNAL_TO_JMS,\ + marker.pos_y / SCALE_INTERNAL_TO_JMS,\ + marker.pos_z / SCALE_INTERNAL_TO_JMS + mod2_marker.rotation[:] = marker.rot_i, marker.rot_j,\ + marker.rot_k, marker.rot_w if not(perm_added or ignore_errors): @@ -217,19 +203,6 @@ def compile_gbxmodel(mod2_tag, merged_jms, ignore_errors=False): mod2_marker.rotation[:] = marker.rot_i, marker.rot_j,\ marker.rot_k, marker.rot_w - # set the node counts per lod - for lod in util.LOD_NAMES: - lod_nodes = all_lod_nodes[lod] - adding = True - node_ct = len(mod2_nodes) - - for i in range(node_ct - 1, -1, -1): - if i in lod_nodes: - break - node_ct -= 1 - - setattr(tagdata, "%s_lod_nodes" % lod, max(0, node_ct - 1)) - # calculate triangle strips stripped_geom_meshes = [] @@ -320,3 +293,6 @@ def compile_gbxmodel(mod2_tag, merged_jms, ignore_errors=False): mod2_tris[i] = tri >> 8 mod2_tris[i + 1] = tri & 0xFF i += 2 + + # calculate final bits of data before saving + mod2_tag.calc_internal_data() \ No newline at end of file diff --git a/reclaimer/model/util.py b/reclaimer/model/util.py index 8fd057e4..ddf329e2 100644 --- a/reclaimer/model/util.py +++ b/reclaimer/model/util.py @@ -9,6 +9,7 @@ import os +from reclaimer.constants import LOD_NAMES from reclaimer.model.jms import JmsVertex from reclaimer.hek.defs.scex import scex_def from reclaimer.hek.defs.schi import schi_def @@ -40,7 +41,6 @@ endian='>' ) -LOD_NAMES = ("superhigh", "high", "medium", "low", "superlow") MAX_STRIP_LEN = 32763 * 3 EMPTY_GEOM_VERTS = ( diff --git a/reclaimer/os_hek/defs/scnr.py b/reclaimer/os_hek/defs/scnr.py index bc79a10c..5097fc57 100644 --- a/reclaimer/os_hek/defs/scnr.py +++ b/reclaimer/os_hek/defs/scnr.py @@ -41,8 +41,7 @@ # copy the scnr_body and replace the descriptors for certain # fields with ones that are tweaked for use with open sauce -scnr_body = desc_variant( - scnr_body, +scnr_body = desc_variant(scnr_body, ("DONT_USE", dependency_os("project_yellow_definitions", 'yelo')), ("player_starting_profiles", reflexive("player_starting_profiles", @@ -71,5 +70,5 @@ def get(): blam_header('scnr', 2), scnr_body, - ext=".scenario", endian=">", tag_cls=HekTag + ext=".scenario", endian=">", tag_cls=ScnrTag ) diff --git a/reclaimer/os_hek/defs/soso.py b/reclaimer/os_hek/defs/soso.py index 926f2dc3..08bdee06 100644 --- a/reclaimer/os_hek/defs/soso.py +++ b/reclaimer/os_hek/defs/soso.py @@ -8,6 +8,7 @@ # from ...hek.defs.soso import * +from supyr_struct.util import desc_variant specular_map_comment = """SPECULAR (COLOR) MAP The specular color map is multiplied by the stock specular @@ -51,9 +52,8 @@ When the opensauce extension is used the tint values in here are overwritten by the ones in the os extension when the map is loaded.""" -reflection = Struct("reflection", - INCLUDE=reflection, COMMENT=os_reflection_prop_comment - ) +reflection = desc_variant(reflection) +reflection.update(COMMENT=os_reflection_prop_comment) os_soso_ext = Struct("shader_model_extension", #Specular Color @@ -102,35 +102,10 @@ SIZE=192, ) -soso_attrs = Struct("soso_attrs", - #Model Shader Properties - model_shader, - - Pad(16), - #Color-Change - SEnum16("color_change_source", *function_names, COMMENT=cc_comment), - - Pad(30), - #Self-Illumination - self_illumination, - - Pad(12), - #Diffuse, Multipurpose, and Detail Maps - maps, - - reflexive("os_shader_model_ext", os_soso_ext, 1), - - #Texture Scrolling Animation - texture_scrolling, - - Pad(8), - #Reflection Properties - reflection, - Pad(16), - - FlFloat("unknown0", VISIBLE=False), - BytesRaw("unknown1", SIZE=16, VISIBLE=False), # little endian dependency - SIZE=400 +soso_attrs = desc_variant(soso_attrs, + ("pad_7", reflexive("os_shader_model_ext", os_soso_ext, 1)), + ("reflection_bump_scale", Pad(4)), + ("reflection_bump_map", Pad(16)), ) soso_body = Struct("tagdata", diff --git a/reclaimer/os_hek/defs/unit.py b/reclaimer/os_hek/defs/unit.py index 57ff5d0b..8ae39816 100644 --- a/reclaimer/os_hek/defs/unit.py +++ b/reclaimer/os_hek/defs/unit.py @@ -8,10 +8,12 @@ # from ...hek.defs.unit import * +from supyr_struct.util import desc_variant # replace the grenade types enumerator with an open sauce one -unit_attrs = dict(unit_attrs) -unit_attrs[49] = SEnum16('grenade_type', *grenade_types_os) +unit_attrs = desc_variant(unit_attrs, + ("grenade_type", SEnum16('grenade_type', *grenade_types_os)) + ) unit_body = Struct('tagdata', unit_attrs) diff --git a/reclaimer/os_v3_hek/defs/cdmg.py b/reclaimer/os_v3_hek/defs/cdmg.py index 3212b0a0..f533762c 100644 --- a/reclaimer/os_v3_hek/defs/cdmg.py +++ b/reclaimer/os_v3_hek/defs/cdmg.py @@ -8,52 +8,36 @@ # from ...hek.defs.cdmg import * +from supyr_struct.util import desc_variant -damage = Struct("damage", - SEnum16("priority", - "none", - "harmless", - {NAME: "backstab", GUI_NAME: "lethal to the unsuspecting"}, - "emp", - ), - SEnum16("category", *damage_category), - Bool32("flags", - "does_not_hurt_owner", - {NAME: "headshot", GUI_NAME: "causes headshots"}, - "pings_resistant_units", - "does_not_hurt_friends", - "does_not_ping_shields", - "detonates_explosives", - "only_hurts_shields", - "causes_flaming_death", - {NAME: "indicator_points_down", - GUI_NAME: "damage indicator always points down"}, - "skips_shields", - "only_hurts_one_infection_form", - {NAME: "multiplayer_headshot", - GUI_NAME: "causes multiplayer headshots"}, - "infection_form_pop", - "YELO_3D_instantaneous_acceleration" - ), - Pad(4), - Float("damage_lower_bound"), - QStruct("damage_upper_bound", INCLUDE=from_to), - float_zero_to_one("vehicle_passthrough_penalty"), - Pad(4), - float_zero_to_one("stun"), - float_zero_to_one("maximum_stun"), - float_sec("stun_time"), - Pad(4), - QStruct("instantaneous_acceleration", - Float("i", UNIT_SCALE=per_sec_unit_scale), - Float("j", UNIT_SCALE=per_sec_unit_scale), - Float("k", UNIT_SCALE=per_sec_unit_scale), - SIDETIP="[-inf,+inf]", ORIENT="h" - ), +damage_flags = Bool32("flags", + "does_not_hurt_owner", + {NAME: "headshot", GUI_NAME: "can cause headshots"}, + "pings_resistant_units", + "does_not_hurt_friends", + "does_not_ping_units", + "detonates_explosives", + "only_hurts_shields", + "causes_flaming_death", + {NAME: "indicator_points_down", GUI_NAME: "damage indicator always points down"}, + "skips_shields", + "only_hurts_one_infection_form", + {NAME: "multiplayer_headshot", GUI_NAME: "can cause multiplayer headshots"}, + "infection_form_pop", + "YELO_3D_instantaneous_acceleration" ) -cdmg_body = dict(cdmg_body) -cdmg_body[5] = damage +damage = desc_variant(damage, + ("flags", damage_flags), + ("instantaneous_acceleration", QStruct("instantaneous_acceleration", + INCLUDE=ijk_float, SIDETIP="[-inf,+inf]" + )), + ("pad_13", Pad(0)), + ) + +cdmg_body = desc_variant(cdmg_body, + ("damage", damage), + ) def get(): return cdmg_def diff --git a/reclaimer/os_v3_hek/defs/jpt_.py b/reclaimer/os_v3_hek/defs/jpt_.py index 421774ed..b04a925f 100644 --- a/reclaimer/os_v3_hek/defs/jpt_.py +++ b/reclaimer/os_v3_hek/defs/jpt_.py @@ -8,52 +8,20 @@ # from ...hek.defs.jpt_ import * +from .cdmg import damage_flags +from supyr_struct.util import desc_variant -damage = Struct("damage", - SEnum16("priority", - "none", - "harmless", - {NAME: "backstab", GUI_NAME: "lethal to the unsuspecting"}, - "emp", - ), - SEnum16("category", *damage_category), - Bool32("flags", - "does_not_hurt_owner", - {NAME: "headshot", GUI_NAME: "causes headshots"}, - "pings_resistant_units", - "does_not_hurt_friends", - "does_not_ping_units", - "detonates_explosives", - "only_hurts_shields", - "causes_flaming_death", - {NAME: "indicator_points_down", - GUI_NAME: "damage indicator always points down"}, - "skips_shields", - "only_hurts_one_infection_form", - {NAME: "multiplayer_headshot", - GUI_NAME: "causes multiplayer headshots"}, - "infection_form_pop", - "YELO_3D_instantaneous_acceleration" - ), - float_wu("aoe_core_radius"), - Float("damage_lower_bound"), - QStruct("damage_upper_bound", INCLUDE=from_to), - float_zero_to_one("vehicle_passthrough_penalty"), - float_zero_to_one("active_camouflage_damage"), - float_zero_to_one("stun"), - float_zero_to_one("maximum_stun"), - float_sec("stun_time"), - Pad(4), - QStruct("instantaneous_acceleration", - Float("i", UNIT_SCALE=per_sec_unit_scale), - Float("j", UNIT_SCALE=per_sec_unit_scale), - Float("k", UNIT_SCALE=per_sec_unit_scale), - SIDETIP="[-inf,+inf]", ORIENT="h" - ), +damage = desc_variant(damage, + ("flags", damage_flags), + ("instantaneous_acceleration", QStruct("instantaneous_acceleration", + INCLUDE=ijk_float, SIDETIP="[-inf,+inf]" + )), + ("pad_13", Pad(0)), ) -jpt__body = dict(jpt__body) -jpt__body[16] = damage +jpt__body = desc_variant(jpt__body, + ("damage", damage), + ) def get(): return jpt__def diff --git a/reclaimer/os_v4_hek/defs/part.py b/reclaimer/os_v4_hek/defs/part.py index 31543beb..874acc9b 100644 --- a/reclaimer/os_v4_hek/defs/part.py +++ b/reclaimer/os_v4_hek/defs/part.py @@ -9,11 +9,16 @@ from ...hek.defs.part import * -part_body = dict(part_body) -part_body[11] = reflexive( - "particle_shader_extensions", +from supyr_struct.util import desc_variant + +particle_shader_extensions = reflexive("particle_shader_extensions", Struct("particle_shader_extension", INCLUDE=os_shader_extension), - 1) + 1 + ) + +part_body = desc_variant(part_body, + ("pad_11", particle_shader_extensions) + ) def get(): @@ -23,5 +28,5 @@ def get(): blam_header("part", 2), part_body, - ext=".particle", endian=">", tag_cls=HekTag, + ext=".particle", endian=">", tag_cls=PartTag, ) diff --git a/reclaimer/os_v4_hek/defs/scnr.py b/reclaimer/os_v4_hek/defs/scnr.py index e8e2e8c5..350c31ac 100644 --- a/reclaimer/os_v4_hek/defs/scnr.py +++ b/reclaimer/os_v4_hek/defs/scnr.py @@ -47,7 +47,7 @@ scnr_body = dict(scnr_body) -scnr_body[64] = reflexive("bsp_modifiers", bsp_modifier, 32) +scnr_body[65] = reflexive("bsp_modifiers", bsp_modifier, 32) def get(): return scnr_def diff --git a/reclaimer/os_v4_hek/defs/unit.py b/reclaimer/os_v4_hek/defs/unit.py index 063a4e02..a3f56c6f 100644 --- a/reclaimer/os_v4_hek/defs/unit.py +++ b/reclaimer/os_v4_hek/defs/unit.py @@ -220,10 +220,7 @@ SIZE=100 ) -seat = dict(seat) -unit_attrs = dict(unit_attrs) - -seat[0] = Bool32("flags", +seat_flags = Bool32("flags", "invisible", "locked", "driver", @@ -237,9 +234,16 @@ "allow_ai_noncombatants", ("allows_melee", 1<<20) ) -seat[20] = reflexive("seat_extensions", seat_extension, 1) -unit_attrs[45] = reflexive("unit_extensions", unit_extension, 1) -unit_attrs[54] = reflexive("seats", seat, 16, DYN_NAME_PATH='.label') + +seat = desc_variant(seat, + ("flags", seat_flags), + ("pad_20", reflexive("seat_extensions", seat_extension, 1)), + ) + +unit_attrs = desc_variant(unit_attrs, + ("pad_45", reflexive("unit_extensions", unit_extension, 1)), + ("seats", reflexive("seats", seat, 16, DYN_NAME_PATH='.label')), + ) unit_body = Struct('tagdata', unit_attrs) diff --git a/reclaimer/sounds/sound_compilation.py b/reclaimer/sounds/sound_compilation.py index ade119f3..b6b5f0fb 100644 --- a/reclaimer/sounds/sound_compilation.py +++ b/reclaimer/sounds/sound_compilation.py @@ -73,17 +73,17 @@ def compile_pitch_range(pitch_range, blam_pitch_range, snd__perms.append() # create the new perm block snd__perm, _ = snd__perms.pop(-1) snd__perm.name = name - snd__perm.ogg_sample_count = ( + snd__perm.buffer_size = ( channel_count * 2 * blam_samples.sample_count) snd__perm.samples.data = blam_samples.sample_data snd__perm.mouth_data.data = blam_samples.mouth_data if blam_samples.compression == constants.COMPRESSION_XBOX_ADPCM: snd__perm.compression.set_to("xbox_adpcm") - snd__perm.ogg_sample_count = 0 # adpcm has this as 0 always + snd__perm.buffer_size = 0 # adpcm has this as 0 always elif blam_samples.compression == constants.COMPRESSION_IMA_ADPCM: snd__perm.compression.set_to("ima_adpcm") - snd__perm.ogg_sample_count = 0 # adpcm has this as 0 always + snd__perm.buffer_size = 0 # adpcm has this as 0 always elif blam_samples.compression == constants.COMPRESSION_OGG: snd__perm.compression.set_to("ogg") else: diff --git a/reclaimer/sounds/sound_decompilation.py b/reclaimer/sounds/sound_decompilation.py index 9735146f..95784136 100644 --- a/reclaimer/sounds/sound_decompilation.py +++ b/reclaimer/sounds/sound_decompilation.py @@ -101,7 +101,7 @@ def extract_h1_sounds(tagdata, tag_path, **kw): if perm.compression.enum_name == "ogg": # not actually a sample count. fix struct field name compression = constants.COMPRESSION_OGG - sample_count = perm.ogg_sample_count // 2 + sample_count = perm.buffer_size // 2 elif perm.compression.enum_name == "none": compression = (constants.COMPRESSION_PCM_16_BE if pcm_is_big_endian else diff --git a/reclaimer/stubbs/defs/actr.py b/reclaimer/stubbs/defs/actr.py index be405d26..4fac7fac 100644 --- a/reclaimer/stubbs/defs/actr.py +++ b/reclaimer/stubbs/defs/actr.py @@ -9,19 +9,15 @@ from ...hek.defs.actr import * from ..common_descs import * +from supyr_struct.util import desc_variant -actr_body = dict(actr_body) -actr_body[3] = SEnum16("type", *actor_types) -actr_body[12] = dict(actr_body[12]) -actr_body[12][2] = SEnum16("leader_type", *actor_types) +panic = desc_variant(panic, + ("leader_type", SEnum16("leader_type", *actor_types)) + ) -actr_body[14] = dict(actr_body[14]) -actr_body[14][6] = SEnum16("defensive_crouch_type", - "never", - "danger", - "low_shields", - "hide_behind_shield", - "any_target", +actr_body = desc_variant(actr_body, + ("type", SEnum16("type", *actor_types)), + ("panic", panic) ) diff --git a/reclaimer/stubbs/defs/cdmg.py b/reclaimer/stubbs/defs/cdmg.py index 9383d5c3..25687be2 100644 --- a/reclaimer/stubbs/defs/cdmg.py +++ b/reclaimer/stubbs/defs/cdmg.py @@ -9,27 +9,15 @@ from ...hek.defs.cdmg import * from ..common_descs import * +from supyr_struct.util import desc_variant -cdmg_body = dict(cdmg_body) -cdmg_body[5] = dict(cdmg_body[5]) -cdmg_body[6] = damage_modifiers -cdmg_body[5][1] = SEnum16("category", *damage_category) -cdmg_body[5][2] = Bool32("flags", - "does_not_hurt_owner", - {NAME: "headshot", GUI_NAME: "causes headshots"}, - "pings_resistant_units", - "does_not_hurt_friends", - "does_not_ping_shields", - "detonates_explosives", - "only_hurts_shields", - "causes_flaming_death", - {NAME: "indicator_points_down", - GUI_NAME: "damage indicator always points down"}, - "skips_shields", - "only_hurts_one_infection_form", - {NAME: "multiplayer_headshot", - GUI_NAME: "causes multiplayer headshots"}, - "infection_form_pop", +damage = desc_variant(damage, + ("category", SEnum16("category", *damage_category)) + ) + +cdmg_body = desc_variant(cdmg_body, + ("damage", damage), + ("damage_modifiers", damage_modifiers) ) def get(): diff --git a/reclaimer/stubbs/defs/coll.py b/reclaimer/stubbs/defs/coll.py index 11be60b3..bd47f7b4 100644 --- a/reclaimer/stubbs/defs/coll.py +++ b/reclaimer/stubbs/defs/coll.py @@ -9,9 +9,11 @@ from ...hek.defs.coll import * from ..common_descs import * +from supyr_struct.util import desc_variant -shield = dict(shield) -shield[2] = SEnum16("shield_material_type", *materials_list) +shield = desc_variant(shield, + ("shield_material_type", SEnum16("shield_material_type", *materials_list)), + ) permutation = Struct("permutation", ascii_str32("name"), @@ -60,43 +62,14 @@ SIZE=144 ) -coll_body = Struct("tagdata", - Bool32("flags", - "takes_shield_damage_for_children", - "takes_body_damage_for_children", - "always_shields_friendly_damage", - "passes_area_damage_to_children", - "parent_never_takes_body_damage_for_us", - "only_damaged_by_explosives", - "only_damaged_while_occupied", - ), - dyn_senum16("indirect_damage_material", - DYN_NAME_PATH=".materials.materials_array[DYN_I].name"), - Pad(2), - - body, - shield, - - Pad(112), - reflexive("materials", material, 32, DYN_NAME_PATH='.name'), - reflexive("regions", region, 8, DYN_NAME_PATH='.name'), - reflexive("modifiers", modifier, 0, VISIBLE=False), - - Pad(16), - Struct("pathfinding_box", - QStruct("x", INCLUDE=from_to), - QStruct("y", INCLUDE=from_to), - QStruct("z", INCLUDE=from_to), - ), - - reflexive("pathfinding_spheres", pathfinding_sphere, 32), - reflexive("nodes", node, 64, DYN_NAME_PATH='.name'), - - SIZE=664, +coll_body = desc_variant(coll_body, + ("shield", shield), + ("materials", reflexive("materials", material, 32, DYN_NAME_PATH='.name')), + ("regions", reflexive("regions", region, 8, DYN_NAME_PATH='.name')), + ) +fast_coll_body = desc_variant(coll_body, + ("nodes", reflexive("nodes", fast_node, 64, DYN_NAME_PATH='.name')), ) - -fast_coll_body = dict(coll_body) -fast_coll_body[12] = reflexive("nodes", fast_node, 64, DYN_NAME_PATH='.name') def get(): diff --git a/reclaimer/stubbs/defs/eqip.py b/reclaimer/stubbs/defs/eqip.py index 61c4a9da..abd48d85 100644 --- a/reclaimer/stubbs/defs/eqip.py +++ b/reclaimer/stubbs/defs/eqip.py @@ -8,16 +8,19 @@ # from ...hek.defs.eqip import * +from .item import * from .obje import * from ..common_descs import * +from supyr_struct.util import desc_variant # replace the object_type enum one that uses # the correct default value for this object obje_attrs = dict(obje_attrs) obje_attrs[0] = dict(obje_attrs[0], DEFAULT=3) -eqip_attrs = dict(eqip_attrs) -eqip_attrs[1] = SEnum16('grenade_type', *grenade_types) +eqip_attrs = desc_variant(eqip_attrs, + ("grenade_type", SEnum16('grenade_type', *grenade_types)) + ) eqip_body = Struct("tagdata", obje_attrs, diff --git a/reclaimer/stubbs/defs/garb.py b/reclaimer/stubbs/defs/garb.py index a8fc3994..bbf969e5 100644 --- a/reclaimer/stubbs/defs/garb.py +++ b/reclaimer/stubbs/defs/garb.py @@ -8,8 +8,6 @@ # from ...hek.defs.garb import * - -#import and use the open saucified obje attrs from .obje import * # replace the object_type enum one that uses @@ -20,7 +18,6 @@ garb_body = dict(garb_body) garb_body[0] = obje_attrs - def get(): return garb_def diff --git a/reclaimer/stubbs/defs/jpt_.py b/reclaimer/stubbs/defs/jpt_.py index 5f758e17..103ec039 100644 --- a/reclaimer/stubbs/defs/jpt_.py +++ b/reclaimer/stubbs/defs/jpt_.py @@ -9,28 +9,15 @@ from ...hek.defs.jpt_ import * from ..common_descs import * +from supyr_struct.util import desc_variant -jpt__body = dict(jpt__body) -jpt__body[16] = dict(jpt__body[16]) -jpt__body[17] = damage_modifiers +damage = desc_variant(damage, + ("category", SEnum16("category", *damage_category)) + ) -jpt__body[16][1] = SEnum16("category", *damage_category) -jpt__body[16][2] = Bool32("flags", - "does_not_hurt_owner", - {NAME: "headshot", GUI_NAME: "causes headshots"}, - "pings_resistant_units", - "does_not_hurt_friends", - "does_not_ping_units", - "detonates_explosives", - "only_hurts_shields", - "causes_flaming_death", - {NAME: "indicator_points_down", - GUI_NAME: "damage indicators always points down"}, - "skips_shields", - "only_hurts_one_infection_form", - {NAME: "multiplayer_headshot", - GUI_NAME: "causes multiplayer headshots"}, - "infection_form_pop", +jpt__body = desc_variant(jpt__body, + ("damage", damage), + ("damage_modifiers", damage_modifiers) ) diff --git a/reclaimer/stubbs/defs/lifi.py b/reclaimer/stubbs/defs/lifi.py index 8250a3f2..d54afdaa 100644 --- a/reclaimer/stubbs/defs/lifi.py +++ b/reclaimer/stubbs/defs/lifi.py @@ -7,9 +7,9 @@ # See LICENSE for more information. # +from ...hek.defs.lifi import * from .obje import * from .devi import * -from ...hek.defs.lifi import * # replace the object_type enum one that uses # the correct default value for this object diff --git a/reclaimer/stubbs/defs/mach.py b/reclaimer/stubbs/defs/mach.py index ecf73697..205834dc 100644 --- a/reclaimer/stubbs/defs/mach.py +++ b/reclaimer/stubbs/defs/mach.py @@ -10,7 +10,6 @@ from ...hek.defs.mach import * from .obje import * from .devi import * -from supyr_struct.defs.tag_def import TagDef # replace the object_type enum one that uses # the correct default value for this object diff --git a/reclaimer/stubbs/defs/obje.py b/reclaimer/stubbs/defs/obje.py index 22b15007..42276461 100644 --- a/reclaimer/stubbs/defs/obje.py +++ b/reclaimer/stubbs/defs/obje.py @@ -9,12 +9,14 @@ from ...hek.defs.obje import * from ..common_descs import * +from supyr_struct.util import desc_variant def get(): return obje_def -obje_attrs = dict(obje_attrs) -obje_attrs[7] = dependency_stubbs('model', 'mode') +obje_attrs = desc_variant(obje_attrs, + ("model", dependency_stubbs('model', 'mode')), + ) obje_body = Struct('tagdata', obje_attrs, @@ -27,6 +29,3 @@ def get(): ext=".object", endian=">", tag_cls=ObjeTag ) - -def get(): - return obje_def diff --git a/reclaimer/stubbs/defs/plac.py b/reclaimer/stubbs/defs/plac.py index 46d5b915..e781221b 100644 --- a/reclaimer/stubbs/defs/plac.py +++ b/reclaimer/stubbs/defs/plac.py @@ -8,8 +8,6 @@ # from ...hek.defs.plac import * - -#import and use the open saucified obje attrs from .obje import * # replace the object_type enum one that uses diff --git a/reclaimer/stubbs/defs/proj.py b/reclaimer/stubbs/defs/proj.py index 37ce3d6e..9f45f995 100644 --- a/reclaimer/stubbs/defs/proj.py +++ b/reclaimer/stubbs/defs/proj.py @@ -11,15 +11,19 @@ from ..common_descs import * from .obje import * from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant # replace the object_type enum one that uses # the correct default value for this object obje_attrs = dict(obje_attrs) obje_attrs[0] = dict(obje_attrs[0], DEFAULT=5) -proj_attrs = dict(proj_attrs) -proj_attrs[13] = reflexive("material_responses", material_response, - len(materials_list), *materials_list) +material_responses = reflexive("material_responses", + material_response, len(materials_list), *materials_list + ) +proj_attrs = desc_variant(proj_attrs, + ("material_responses", material_responses) + ) proj_body = Struct("tagdata", obje_attrs, diff --git a/reclaimer/stubbs/defs/sbsp.py b/reclaimer/stubbs/defs/sbsp.py index 39528dfb..3816dac0 100644 --- a/reclaimer/stubbs/defs/sbsp.py +++ b/reclaimer/stubbs/defs/sbsp.py @@ -9,6 +9,7 @@ from ...hek.defs.sbsp import * from ..common_descs import * +from supyr_struct.util import desc_variant cluster = Struct("cluster", @@ -48,11 +49,12 @@ ) -sbsp_body = dict(sbsp_body) -sbsp_body[28] = reflexive("clusters", cluster, 8192) - -fast_sbsp_body = dict(fast_sbsp_body) -fast_sbsp_body[28] = reflexive("clusters", cluster, 8192) +sbsp_body = desc_variant(sbsp_body, + ("clusters", reflexive("clusters", cluster, 8192)), + ) +fast_sbsp_body = desc_variant(fast_sbsp_body, + ("clusters", reflexive("clusters", cluster, 8192)), + ) def get(): diff --git a/reclaimer/stubbs/defs/scen.py b/reclaimer/stubbs/defs/scen.py index e545302d..bf619557 100644 --- a/reclaimer/stubbs/defs/scen.py +++ b/reclaimer/stubbs/defs/scen.py @@ -8,8 +8,6 @@ # from ...hek.defs.scen import * - -#import and use the open saucified obje attrs from .obje import * # replace the object_type enum one that uses diff --git a/reclaimer/stubbs/defs/shdr.py b/reclaimer/stubbs/defs/shdr.py index fb56c4a3..f67a0bcd 100644 --- a/reclaimer/stubbs/defs/shdr.py +++ b/reclaimer/stubbs/defs/shdr.py @@ -9,10 +9,11 @@ from ...hek.defs.shdr import * from ..common_descs import * -from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant -shdr_attrs = dict(shdr_attrs) -shdr_attrs[6] = SEnum16("material_type", *materials_list) +shdr_attrs = desc_variant(shdr_attrs, + ("material_type", SEnum16("material_type", *materials_list)), + ) shader_body = Struct("tagdata", shdr_attrs, diff --git a/reclaimer/stubbs/defs/soso.py b/reclaimer/stubbs/defs/soso.py index 52769fe8..d547151e 100644 --- a/reclaimer/stubbs/defs/soso.py +++ b/reclaimer/stubbs/defs/soso.py @@ -10,41 +10,11 @@ from ...hek.defs.soso import * from .shdr import * from supyr_struct.defs.tag_def import TagDef +from supyr_struct.util import desc_variant -bump_properties = Struct("bump_properties", - Float("bump_scale"), - dependency_stubbs("bump_map", "bitm"), - ) - -soso_attrs = Struct("soso_attrs", - #Model Shader Properties - model_shader, - - Pad(16), - #Color-Change - SEnum16("color_change_source", *function_names), - - Pad(30), - #Self-Illumination - self_illumination, - - Pad(12), - #Diffuse, Multipurpose, and Detail Maps - maps, - - # this padding is the reflexive for the OS shader model extension - Pad(12), - - #Texture Scrolling Animation - texture_scrolling, - - Pad(8), - #Reflection Properties - reflection, - - Pad(16), - bump_properties, - SIZE=440 +soso_attrs = desc_variant(soso_attrs, + ("reflection_bump_scale", Float("bump_scale")), + ("reflection_bump_map", dependency_stubbs("bump_map", "bitm")), ) soso_body = Struct("tagdata", diff --git a/reclaimer/stubbs/defs/ssce.py b/reclaimer/stubbs/defs/ssce.py index 0a640b41..3fb9ac38 100644 --- a/reclaimer/stubbs/defs/ssce.py +++ b/reclaimer/stubbs/defs/ssce.py @@ -8,8 +8,6 @@ # from ...hek.defs.ssce import * - -#import and use the open saucified obje attrs from .obje import * # replace the object_type enum one that uses diff --git a/reclaimer/stubbs/defs/unit.py b/reclaimer/stubbs/defs/unit.py index 8a929067..6ee50fca 100644 --- a/reclaimer/stubbs/defs/unit.py +++ b/reclaimer/stubbs/defs/unit.py @@ -118,7 +118,8 @@ SEnum16('grenade_type', *grenade_types), SInt16('grenade_count', MIN=0), - Pad(4), + FlUInt16("soft_ping_stun_ticks", VISIBLE=False), # set to soft_ping_interrupt_time * 30 + FlUInt16("hard_ping_stun_ticks", VISIBLE=False), # set to hard_ping_interrupt_time * 30 reflexive("powered_seats", powered_seat, 2, "driver", "gunner"), reflexive("weapons", weapon, 4, DYN_NAME_PATH='.weapon.filepath'), diff --git a/reclaimer/stubbs/defs/vehi.py b/reclaimer/stubbs/defs/vehi.py index e782e05b..d45b4ce0 100644 --- a/reclaimer/stubbs/defs/vehi.py +++ b/reclaimer/stubbs/defs/vehi.py @@ -13,14 +13,17 @@ from ...hek.defs.vehi import * from .obje import * from .unit import * +from ..common_descs import * +from supyr_struct.util import desc_variant # replace the object_type enum one that uses # the correct default value for this object obje_attrs = dict(obje_attrs) obje_attrs[0] = dict(obje_attrs[0], DEFAULT=1) -vehi_attrs = dict(vehi_attrs) -vehi_attrs[1] = SEnum16('type', *vehicle_types) +vehi_attrs = desc_variant(vehi_attrs, + ("type", SEnum16('type', *vehicle_types)) + ) vehi_body = Struct("tagdata", obje_attrs, diff --git a/reclaimer/stubbs/defs/weap.py b/reclaimer/stubbs/defs/weap.py index 8ef2b974..e04bdfeb 100644 --- a/reclaimer/stubbs/defs/weap.py +++ b/reclaimer/stubbs/defs/weap.py @@ -10,6 +10,8 @@ from ...hek.defs.weap import * from .obje import * from .item import * +from ..common_descs import * +from supyr_struct.util import desc_variant # replace the object_type enum one that uses # the correct default value for this object @@ -18,8 +20,9 @@ # replace the object_type enum one that uses # the correct default value for this object -weap_attrs = dict(weap_attrs) -weap_attrs[24] = SEnum16('weapon_type', *weapon_types) +weap_attrs = desc_variant(weap_attrs, + ("weapon_type", SEnum16('weapon_type', *weapon_types)) + ) weap_body = Struct("tagdata", obje_attrs, From f65a59bc39bc9a2da44f96df10da2744984197c7 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Fri, 26 Jan 2024 00:09:16 -0600 Subject: [PATCH 23/51] bump verison --- reclaimer/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reclaimer/__init__.py b/reclaimer/__init__.py index 052ff5b3..cfcd3212 100644 --- a/reclaimer/__init__.py +++ b/reclaimer/__init__.py @@ -12,8 +12,8 @@ # ############## __author__ = "Sigmmma" # YYYY.MM.DD -__date__ = "2024.01.23" -__version__ = (2, 13, 1) +__date__ = "2024.01.26" +__version__ = (2, 14, 0) __website__ = "https://github.com/Sigmmma/reclaimer" __all__ = ( "animation", "bitmaps", "h2", "h3", "halo_script", "hek", "meta", "misc", From 15c68a7e93071fd2258d7fb86475182da8fce9c9 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sat, 10 Feb 2024 14:01:36 -0600 Subject: [PATCH 24/51] def cleanup --- reclaimer/bitmaps/bitmap_decompilation.py | 28 +- reclaimer/common_descs.py | 60 +-- reclaimer/constants.py | 7 + reclaimer/enums.py | 99 ++-- reclaimer/h2/common_descs.py | 16 + reclaimer/halo_script/defs/hsc.py | 340 +++++++++++++ reclaimer/halo_script/hsc.py | 448 ++++++++++-------- reclaimer/halo_script/hsc_decompilation.py | 287 ++++++----- reclaimer/hek/defs/actr.py | 12 +- reclaimer/hek/defs/actv.py | 3 +- reclaimer/hek/defs/antr.py | 5 +- reclaimer/hek/defs/bipd.py | 8 +- reclaimer/hek/defs/bitm.py | 2 +- reclaimer/hek/defs/cdmg.py | 1 - reclaimer/hek/defs/coll.py | 1 + reclaimer/hek/defs/ctrl.py | 9 +- reclaimer/hek/defs/eqip.py | 9 +- reclaimer/hek/defs/garb.py | 9 +- reclaimer/hek/defs/hudg.py | 3 +- reclaimer/hek/defs/item.py | 2 + reclaimer/hek/defs/jpt_.py | 1 + reclaimer/hek/defs/lens.py | 18 +- reclaimer/hek/defs/mach.py | 9 +- reclaimer/hek/defs/obje.py | 2 + reclaimer/hek/defs/objs/bitm.py | 77 ++- reclaimer/hek/defs/objs/mode.py | 2 +- reclaimer/hek/defs/objs/scnr.py | 21 +- reclaimer/hek/defs/plac.py | 9 +- reclaimer/hek/defs/proj.py | 7 +- reclaimer/hek/defs/sbsp.py | 1 + reclaimer/hek/defs/scen.py | 9 +- reclaimer/hek/defs/scnr.py | 4 +- reclaimer/hek/defs/senv.py | 1 + reclaimer/hek/defs/soso.py | 20 +- reclaimer/hek/defs/ssce.py | 9 +- reclaimer/hek/defs/unhi.py | 5 +- reclaimer/hek/defs/unit.py | 2 + reclaimer/hek/defs/weap.py | 7 +- reclaimer/mcc_hek/defs/actv.py | 7 +- reclaimer/mcc_hek/defs/antr.py | 25 +- reclaimer/mcc_hek/defs/bipd.py | 9 +- reclaimer/mcc_hek/defs/bitm.py | 20 +- reclaimer/mcc_hek/defs/cdmg.py | 13 +- reclaimer/mcc_hek/defs/coll.py | 5 +- reclaimer/mcc_hek/defs/ctrl.py | 7 +- reclaimer/mcc_hek/defs/deca.py | 5 +- reclaimer/mcc_hek/defs/effe.py | 5 +- reclaimer/mcc_hek/defs/eqip.py | 10 +- reclaimer/mcc_hek/defs/font.py | 5 +- reclaimer/mcc_hek/defs/garb.py | 7 +- reclaimer/mcc_hek/defs/grhi.py | 5 +- reclaimer/mcc_hek/defs/hudg.py | 8 +- reclaimer/mcc_hek/defs/jpt_.py | 13 +- reclaimer/mcc_hek/defs/lens.py | 12 +- reclaimer/mcc_hek/defs/lifi.py | 7 +- reclaimer/mcc_hek/defs/lsnd.py | 5 +- reclaimer/mcc_hek/defs/mach.py | 7 +- reclaimer/mcc_hek/defs/matg.py | 5 +- reclaimer/mcc_hek/defs/obje.py | 5 +- reclaimer/mcc_hek/defs/objs/bitm.py | 14 +- reclaimer/mcc_hek/defs/plac.py | 7 +- reclaimer/mcc_hek/defs/proj.py | 19 +- reclaimer/mcc_hek/defs/scen.py | 8 +- reclaimer/mcc_hek/defs/scex.py | 6 +- reclaimer/mcc_hek/defs/schi.py | 5 +- reclaimer/mcc_hek/defs/scnr.py | 70 +-- reclaimer/mcc_hek/defs/senv.py | 13 +- reclaimer/mcc_hek/defs/snd_.py | 11 +- reclaimer/mcc_hek/defs/soso.py | 12 +- reclaimer/mcc_hek/defs/ssce.py | 7 +- reclaimer/mcc_hek/defs/unhi.py | 9 +- reclaimer/mcc_hek/defs/unit.py | 5 +- reclaimer/mcc_hek/defs/vehi.py | 12 +- reclaimer/mcc_hek/defs/weap.py | 17 +- reclaimer/mcc_hek/defs/wphi.py | 11 +- reclaimer/meta/halo1_map.py | 30 +- reclaimer/meta/halo1_map_fast_functions.py | 2 +- reclaimer/meta/shadowrun_map.py | 40 +- reclaimer/meta/stubbs_map.py | 19 +- reclaimer/meta/wrappers/halo1_map.py | 70 ++- reclaimer/meta/wrappers/halo1_mcc_map.py | 2 +- reclaimer/meta/wrappers/halo1_rsrc_map.py | 123 +++-- reclaimer/meta/wrappers/halo1_yelo.py | 2 +- reclaimer/os_hek/defs/actv.py | 15 +- reclaimer/os_hek/defs/antr.py | 7 +- reclaimer/os_hek/defs/bipd.py | 10 +- reclaimer/os_hek/defs/ctrl.py | 11 +- reclaimer/os_hek/defs/eqip.py | 16 +- reclaimer/os_hek/defs/garb.py | 11 +- reclaimer/os_hek/defs/lifi.py | 11 +- reclaimer/os_hek/defs/mach.py | 11 +- reclaimer/os_hek/defs/matg.py | 5 +- reclaimer/os_hek/defs/obje.py | 13 +- reclaimer/os_hek/defs/objs/scnr.py | 13 + reclaimer/os_hek/defs/plac.py | 11 +- reclaimer/os_hek/defs/proj.py | 11 +- reclaimer/os_hek/defs/scen.py | 11 +- reclaimer/os_hek/defs/scnr.py | 61 +-- reclaimer/os_hek/defs/soso.py | 6 +- reclaimer/os_hek/defs/ssce.py | 11 +- reclaimer/os_hek/defs/tagc.py | 16 +- reclaimer/os_hek/defs/unit.py | 7 +- reclaimer/os_hek/defs/vehi.py | 9 +- reclaimer/os_hek/defs/weap.py | 11 +- reclaimer/os_v3_hek/defs/bipd.py | 8 +- reclaimer/os_v3_hek/defs/cdmg.py | 13 +- reclaimer/os_v3_hek/defs/ctrl.py | 11 +- reclaimer/os_v3_hek/defs/eqip.py | 11 +- reclaimer/os_v3_hek/defs/garb.py | 11 +- reclaimer/os_v3_hek/defs/jpt_.py | 13 +- reclaimer/os_v3_hek/defs/lifi.py | 11 +- reclaimer/os_v3_hek/defs/mach.py | 11 +- reclaimer/os_v3_hek/defs/plac.py | 11 +- reclaimer/os_v3_hek/defs/proj.py | 11 +- reclaimer/os_v3_hek/defs/scen.py | 11 +- reclaimer/os_v3_hek/defs/ssce.py | 11 +- reclaimer/os_v3_hek/defs/vehi.py | 9 +- reclaimer/os_v3_hek/defs/weap.py | 11 +- reclaimer/os_v4_hek/defs/bipd.py | 12 +- reclaimer/os_v4_hek/defs/bitm.py | 18 +- reclaimer/os_v4_hek/defs/cont.py | 12 +- reclaimer/os_v4_hek/defs/ctrl.py | 11 +- reclaimer/os_v4_hek/defs/eqip.py | 11 +- reclaimer/os_v4_hek/defs/garb.py | 11 +- reclaimer/os_v4_hek/defs/gelo.py | 29 +- reclaimer/os_v4_hek/defs/lifi.py | 11 +- reclaimer/os_v4_hek/defs/mach.py | 11 +- reclaimer/os_v4_hek/defs/obje.py | 19 +- reclaimer/os_v4_hek/defs/part.py | 2 - reclaimer/os_v4_hek/defs/pctl.py | 25 +- reclaimer/os_v4_hek/defs/plac.py | 11 +- reclaimer/os_v4_hek/defs/proj.py | 11 +- reclaimer/os_v4_hek/defs/scen.py | 11 +- reclaimer/os_v4_hek/defs/scnr.py | 11 +- reclaimer/os_v4_hek/defs/senv.py | 5 +- reclaimer/os_v4_hek/defs/ssce.py | 11 +- reclaimer/os_v4_hek/defs/unit.py | 4 +- reclaimer/os_v4_hek/defs/vehi.py | 12 +- reclaimer/os_v4_hek/defs/weap.py | 9 +- reclaimer/shadowrun_prototype/common_descs.py | 28 +- reclaimer/shadowrun_prototype/defs/bipd.py | 8 +- reclaimer/shadowrun_prototype/defs/ctrl.py | 9 +- reclaimer/shadowrun_prototype/defs/eqip.py | 9 +- reclaimer/shadowrun_prototype/defs/garb.py | 10 +- reclaimer/shadowrun_prototype/defs/lifi.py | 9 +- reclaimer/shadowrun_prototype/defs/mach.py | 9 +- .../shadowrun_prototype/defs/objs/scnr.py | 13 + reclaimer/shadowrun_prototype/defs/plac.py | 9 +- reclaimer/shadowrun_prototype/defs/proj.py | 9 +- reclaimer/shadowrun_prototype/defs/scen.py | 9 +- reclaimer/shadowrun_prototype/defs/scnr.py | 19 + reclaimer/shadowrun_prototype/defs/ssce.py | 9 +- reclaimer/shadowrun_prototype/defs/tagc.py | 20 + reclaimer/shadowrun_prototype/defs/weap.py | 9 +- reclaimer/stubbs/common_descs.py | 24 +- reclaimer/stubbs/defs/actr.py | 12 +- reclaimer/stubbs/defs/antr.py | 123 +---- reclaimer/stubbs/defs/bipd.py | 12 +- reclaimer/stubbs/defs/cdmg.py | 11 +- reclaimer/stubbs/defs/coll.py | 13 +- reclaimer/stubbs/defs/ctrl.py | 5 +- reclaimer/stubbs/defs/eqip.py | 13 +- reclaimer/stubbs/defs/foot.py | 1 - reclaimer/stubbs/defs/garb.py | 9 +- reclaimer/stubbs/defs/jpt_.py | 11 +- reclaimer/stubbs/defs/lifi.py | 13 +- reclaimer/stubbs/defs/mach.py | 14 +- reclaimer/stubbs/defs/matg.py | 22 +- reclaimer/stubbs/defs/mode.py | 66 +-- reclaimer/stubbs/defs/obje.py | 5 +- reclaimer/stubbs/defs/objs/scnr.py | 13 + reclaimer/stubbs/defs/plac.py | 9 +- reclaimer/stubbs/defs/proj.py | 11 +- reclaimer/stubbs/defs/sbsp.py | 56 +-- reclaimer/stubbs/defs/scen.py | 9 +- reclaimer/stubbs/defs/scnr.py | 19 + reclaimer/stubbs/defs/shdr.py | 6 +- reclaimer/stubbs/defs/soso.py | 5 +- reclaimer/stubbs/defs/sotr.py | 1 - reclaimer/stubbs/defs/spla.py | 1 - reclaimer/stubbs/defs/ssce.py | 9 +- reclaimer/stubbs/defs/tagc.py | 20 + reclaimer/stubbs/defs/unhi.py | 33 ++ reclaimer/stubbs/defs/vehi.py | 12 +- reclaimer/stubbs/defs/weap.py | 16 +- reclaimer/stubbs/defs/wphi.py | 19 + 186 files changed, 1789 insertions(+), 1922 deletions(-) create mode 100644 reclaimer/halo_script/defs/hsc.py create mode 100644 reclaimer/os_hek/defs/objs/scnr.py create mode 100644 reclaimer/shadowrun_prototype/defs/objs/scnr.py create mode 100644 reclaimer/stubbs/defs/objs/scnr.py diff --git a/reclaimer/bitmaps/bitmap_decompilation.py b/reclaimer/bitmaps/bitmap_decompilation.py index 2daf996e..12577e41 100644 --- a/reclaimer/bitmaps/bitmap_decompilation.py +++ b/reclaimer/bitmaps/bitmap_decompilation.py @@ -126,7 +126,6 @@ def extract_bitmaps(tagdata, tag_path, **kw): if not ext: ext = "dds" - is_xbox = get_is_xbox_map(engine) is_gen3 = hasattr(tagdata, "zone_assets_normal") if Arbytmap is None: # cant extract xbox bitmaps yet @@ -196,17 +195,10 @@ def extract_bitmaps(tagdata, tag_path, **kw): if arby_fmt is None: continue - i_max = tex_info["sub_bitmap_count"] if is_xbox else bitmap.mipmaps + 1 - j_max = bitmap.mipmaps + 1 if is_xbox else tex_info['sub_bitmap_count'] off = bitmap.pixels_offset - for i in range(i_max): - if not is_xbox: - mip_size = size_calc(arby_fmt, w, h, d, i, tiled) - - for j in range(j_max): - if is_xbox: - mip_size = size_calc(arby_fmt, w, h, d, j, tiled) - + for m in range(bitmap.mipmaps + 1): + mip_size = size_calc(arby_fmt, w, h, d, m, tiled) + for f in range(tex_info['sub_bitmap_count']): if fmt == "p8_bump": tex_block.append( array('B', pix_data[off: off + (mip_size // 4)])) @@ -216,24 +208,14 @@ def extract_bitmaps(tagdata, tag_path, **kw): pix_data, off, tex_block, arby_fmt, 1, 1, 1, mip_size) - # skip the xbox alignment padding to get to the next texture - if is_xbox and typ == "cubemap": + if typ == "cubemap": off += ((CUBEMAP_PADDING - (off % CUBEMAP_PADDING)) % CUBEMAP_PADDING) - - if is_xbox and typ == "cubemap": - template = tuple(tex_block) - i = 0 - for f in (0, 2, 1, 3, 4, 5): - for m in range(bitmap.mipmaps + 1): - tex_block[m*6 + f] = template[i] - i += 1 - if not tex_block: # nothing to extract continue arby.load_new_texture(texture_block=tex_block, texture_info=tex_info, tile_mode=False, swizzle_mode=False) - arby.save_to_file(keep_alpha=keep_alpha) + arby.save_to_file(keep_alpha=keep_alpha, overwrite=kw.get("overwrite", False)) diff --git a/reclaimer/common_descs.py b/reclaimer/common_descs.py index 678f87bb..1496d38a 100644 --- a/reclaimer/common_descs.py +++ b/reclaimer/common_descs.py @@ -27,7 +27,7 @@ from supyr_struct.defs.common_descs import * from supyr_struct.defs.block_def import BlockDef -from supyr_struct.util import desc_variant +from supyr_struct.util import desc_variant_with_verify as desc_variant from reclaimer.field_types import * from reclaimer.field_type_methods import tag_ref_str_size,\ @@ -154,18 +154,17 @@ def rawdata_ref(name, f_type=BytearrayRaw, max_size=None, if max_size is not None: ref_struct_kwargs[MAX] = max_size ref_struct = desc_variant(ref_struct, - ("size", SInt32("size", - GUI_NAME="", SIDETIP="bytes", EDITABLE=False, MAX=max_size, ) - ) + SInt32("size", GUI_NAME="", SIDETIP="bytes", + EDITABLE=False, MAX=max_size + ) ) if widget is not None: kwargs[WIDGET] = widget - return RawdataRef(name, - INCLUDE=ref_struct, + return desc_variant(ref_struct, STEPTREE=f_type("data", GUI_NAME="", SIZE=".size", **kwargs), - **ref_struct_kwargs + NAME=name, **ref_struct_kwargs ) @@ -182,10 +181,10 @@ def rawtext_ref(name, f_type=StrRawLatin1, max_size=None, if max_size is not None: ref_struct_kwargs[MAX] = max_size ref_struct = desc_variant(ref_struct, - ("size", SInt32("size", + SInt32("size", GUI_NAME="", SIDETIP="bytes", EDITABLE=False, - MAX=max_size, VISIBLE=VISIBILITY_METADATA, ) - ) + MAX=max_size, VISIBLE=VISIBILITY_METADATA + ) ) return RawdataRef(name, @@ -227,12 +226,12 @@ def dependency(name='tag_ref', valid_ids=None, **kwargs): elif valid_ids is None: valid_ids = valid_tags - return TagRef(name, + return desc_variant(tag_ref_struct, valid_ids, - INCLUDE=tag_ref_struct, STEPTREE=StrTagRef( - "filepath", SIZE=tag_ref_str_size, GUI_NAME="", MAX=254), - **kwargs + "filepath", SIZE=tag_ref_str_size, GUI_NAME="", MAX=254 + ), + NAME=name, **kwargs ) @@ -245,6 +244,9 @@ def object_type(default=-1): VISIBLE=False, DEFAULT=default ) +def obje_attrs_variant(obje_attrs, typ="", **desc): + obj_index = object_types.index(typ) if typ else 0 + return desc_variant(obje_attrs, object_type(obj_index - 1)) def zone_asset(name, **kwargs): return ZoneAsset(name, INCLUDE=zone_asset_struct, **kwargs) @@ -274,12 +276,11 @@ def string_id(name, index_bit_ct, set_bit_ct, len_bit_ct=None, **kwargs): def blam_header(tagid, version=1): '''This function serves to macro the creation of a tag header''' return desc_variant(tag_header, - ("tag_class", UEnum32("tag_class", + UEnum32("tag_class", GUI_NAME="tag_class", INCLUDE=valid_tags, EDITABLE=False, DEFAULT=tagid - ) - ), - ("version", UInt16("version", DEFAULT=version, EDITABLE=False)), + ), + UInt16("version", DEFAULT=version, EDITABLE=False), ) @@ -661,6 +662,7 @@ def anim_src_func_per_pha_sca_rot_macro(name, **desc): UInt32("raw_pointer", VISIBLE=VISIBILITY_METADATA, EDITABLE=False), # doesnt use magic UInt32("pointer", VISIBLE=VISIBILITY_METADATA, EDITABLE=False), UInt32("id", VISIBLE=VISIBILITY_METADATA, EDITABLE=False), + SIZE=20, ORIENT='h' ) @@ -669,6 +671,7 @@ def anim_src_func_per_pha_sca_rot_macro(name, **desc): SInt32("size", VISIBLE=VISIBILITY_METADATA, EDITABLE=False), UInt32("pointer", VISIBLE=VISIBILITY_METADATA, EDITABLE=False), UInt32("id", VISIBLE=VISIBILITY_METADATA, EDITABLE=False), # 0 in meta it seems + SIZE=12, ) # This is the descriptor used wherever a tag references another tag @@ -677,6 +680,7 @@ def anim_src_func_per_pha_sca_rot_macro(name, **desc): SInt32("path_pointer", VISIBLE=VISIBILITY_METADATA, EDITABLE=False), SInt32("path_length", MAX=MAX_TAG_PATH_LEN, VISIBLE=VISIBILITY_METADATA, EDITABLE=False), UInt32("id", VISIBLE=VISIBILITY_METADATA, EDITABLE=False), + SIZE=16, ORIENT='h' ) @@ -698,7 +702,10 @@ def anim_src_func_per_pha_sca_rot_macro(name, **desc): extra_layers_block = dependency("extra_layer", valid_shaders) damage_modifiers = QStruct("damage_modifiers", - *(float_zero_to_inf(material_name) for material_name in materials_list) + *(float_zero_to_inf(material_name) for material_name in materials_list), + # NOTE: there's enough allocated for 40 materials. We're assuming + # the rest of the space is all for these damage modifiers + SIZE=4*40 ) # Miscellaneous shared descriptors @@ -810,24 +817,23 @@ def dependency_os(name='tag_ref', valid_ids=None, **kwargs): elif valid_ids is None: valid_ids = valid_tags_os - return TagRef(name, + return desc_variant(tag_ref_struct, valid_ids, - INCLUDE=tag_ref_struct, STEPTREE=StrTagRef( - "filepath", SIZE=tag_ref_str_size, GUI_NAME="", MAX=254), - **kwargs + "filepath", SIZE=tag_ref_str_size, GUI_NAME="", MAX=254 + ), + NAME=name, **kwargs ) def blam_header_os(tagid, version=1): '''This function serves to macro the creation of a tag header''' return desc_variant(tag_header_os, - ("tag_class", UEnum32("tag_class", + UEnum32("tag_class", GUI_NAME="tag_class", INCLUDE=valid_tags_os, EDITABLE=False, DEFAULT=tagid - ) - ), - ("version", UInt16("version", DEFAULT=version, EDITABLE=False)), + ), + UInt16("version", DEFAULT=version, EDITABLE=False), ) diff --git a/reclaimer/constants.py b/reclaimer/constants.py index 5f146c06..5b211d49 100644 --- a/reclaimer/constants.py +++ b/reclaimer/constants.py @@ -195,6 +195,13 @@ def inject_halo_constants(): "UNUSED9", "UNUSED10", "UNUSED11", "UNUSED12", "UNUSED13", "UNUSED14", "DXN", "CTX1", "DXT3A", "DXT3Y", "DXT5A", "DXT5Y", "DXT5AY") MCC_FORMAT_NAME_MAP = FORMAT_NAME_MAP[:FORMAT_NAME_MAP.index("P8")] + ("BC7", ) +SWIZZLEABLE_FORMATS = ( + "A8", "L8", "AL8", "A8L8", + "R5G6B5", "UNUSED3", "A1R5G5B5", "A4R4G4B4", + "X8R8G8B8", "A8R8G8B8", "P8-BUMP", "P8", + "A32R32G32B32F", "R32G32B32F", "R16G16B16F", + "V8U8", "G8B8" + ) I_FORMAT_NAME_MAP = {fmt: i for i, fmt in enumerate(FORMAT_NAME_MAP)} I_MCC_FORMAT_NAME_MAP = {fmt: i for i, fmt in enumerate(MCC_FORMAT_NAME_MAP)} diff --git a/reclaimer/enums.py b/reclaimer/enums.py index 9721e9c1..28177c4c 100644 --- a/reclaimer/enums.py +++ b/reclaimer/enums.py @@ -400,6 +400,17 @@ "device_name", "scenery_name", # 48 ) +# used in determining which script object types are tag refs +script_object_tag_ref_types = ( + "sound", + "effect", + "damage", + "looping_sound", + "animation_graph", + "actor_variant", + "damage_effect", + "object_definition", + ) # DO NOT MODIFY ANY OF THESE ENUMS! # The exact lettering is important! # INCOMPLETE: Entries with None haven't been determined yet. @@ -461,9 +472,9 @@ "objects_can_see_object", "objects_can_see_flag", "objects_delete_by_definition", - None, + None, # NOTE: might be related to sound_get/set_gain # these next 5 might be shifted. a little - "sound_set_gain", # * + "sound_get_gain", # ** "sound_set_gain", # * same as previous, but extra arg "script_recompile", # * "help", # * @@ -562,100 +573,103 @@ "cheat_spawn_warthog", # * "cheat_all_vehicles", # * "cheat_teleport_to_camera", # * - "cheat_active_camouflage", # * - # no space for this command. Might not actually exist - #"cheat_active_camouflage_local_player", # * + # seems cheat_active_camouflage is a variant of + # cheat_active_camouflage_local_player with the + # local player index set to 0. + #"cheat_active_camouflage", + "cheat_active_camouflage_local_player", # * "cheats_load", # * "ai_free", "ai_free_units", "ai_attach", - "ai_attach", + "ai_attach_free", # ** "ai_detach", # 160 - "ai_place", - "ai_place", + "ai_place", # ??? + "ai_place", # ??? "ai_kill", "ai_kill_silent", "ai_erase", "ai_erase_all", "ai_select", # * - # no space for this command. Might not actually exist + # seems ai_deselect is a variant of ai_select #"ai_deselect", # * "ai_spawn_actor", - "ai_spawn_actor", + "ai_set_respawn", # ** "ai_set_deaf", # 170 "ai_set_blind", - "ai_magically_see_encounter", - "ai_magically_see_encounter", + "ai_magically_see_encounter", # ??? + "ai_magically_see_encounter", # ??? "ai_magically_see_players", "ai_magically_see_unit", # * "ai_timer_expire", "ai_attack", - "ai_attack", + "ai_defend", # ** "ai_retreat", "ai_maneuver", # 180 - "ai_maneuver", - "ai_migrate", - "ai_migrate", + "ai_maneuver_enable", # ** + "ai_migrate", # ?? + "ai_migrate", # ?? "ai_migrate_and_speak", "ai_migrate_by_unit", "ai_allegiance", + # seems ai_allegiance_remove is a variant of ai_allegiance "ai_living_count", - "ai_living_count", + "ai_living_fraction", # ** "ai_strength", - "ai_strength", # 190 - "ai_nonswarm_count", + "ai_strength", # 190 ??? + "ai_swarm_count", # ** "ai_nonswarm_count", "ai_actors", "ai_go_to_vehicle", "ai_going_to_vehicle", "ai_exit_vehicle", "ai_braindead", - "ai_braindead", + "ai_braindead", # ??? "ai_braindead_by_unit", "ai_prefer_target", # 200 "ai_teleport_to_starting_location", "ai_teleport_to_starting_location_if_unsupported", - "ai_teleport_to_starting_location_if_unsupported", + "ai_teleport_to_starting_location_if_unsupported", # ??? "ai_renew", - "ai_try_to_fight", + "ai_try_to_fight_nothing", # ** "ai_try_to_fight", "ai_try_to_fight_player", "ai_command_list", "ai_command_list_by_unit", "ai_command_list_advance", # 210 "ai_command_list_status", - "ai_command_list_status", - "ai_force_active", + "ai_is_attacking", # ** "ai_force_active", + "ai_force_active_by_unit", # ** "ai_set_return_state", # * "ai_set_current_state", # * "ai_playfight", - "ai_playfight", + "ai_playfight", # ??? "ai_status", "ai_vehicle_encounter", # 220 "ai_vehicle_enterable_distance", - "ai_vehicle_enterable_distance", + "ai_vehicle_enterable_team", # ** "ai_vehicle_enterable_actor_type", "ai_vehicle_enterable_actors", # * "ai_vehicle_enterable_disable", "ai_look_at_object", # * "ai_stop_looking", # * "ai_automatic_migration_target", - "ai_automatic_migration_target", + "ai_automatic_migration_target", # ??? "ai_follow_target_disable", # 230 "ai_follow_target_players", "ai_follow_target_ai", "ai_follow_distance", "ai_conversation", - "ai_conversation", + "ai_conversation", # ??? "ai_conversation_stop", "ai_conversation_advance", - "ai_conversation_status", + "ai_conversation_line", # ** "ai_conversation_status", "ai_link_activation", # 240 "ai_berserk", "ai_set_team", - "ai_allow_dormant", + "ai_allow_charge", # ** "ai_allow_dormant", "ai_allegiance_broken", "camera_control", @@ -663,21 +677,21 @@ "camera_set_relative", "camera_set_first_person", "camera_set_dead", # 250 - "camera_set_dead", + "camera_set_dead", # ??? "camera_time", "debug_camera_load", "debug_camera_save", "game_speed", "game_variant", # * "game_difficulty_get", - "game_difficulty_get", + "game_difficulty_get", # ??? "game_difficulty_get_real", "profile_service_clear_timers", # * 260 "profile_service_dump_timers", # * "map_reset", "map_name", - "switch_bsp", - "structure_bsp_index", + "multiplayer_map_name", # ** + "game_difficulty_set", # ** "crash", # * "switch_bsp", "structure_bsp_index", @@ -693,23 +707,26 @@ "debug_tags", # * "profile_reset", # * "profile_dump", # *280 - # no space for these commands. Might not actually exist + # seems profile_activate, profile_deactivate, and + # profile_graph_toggle are variants of profile_dump #"profile_activate", # * #"profile_deactivate", # * #"profile_graph_toggle", # * "debug_pvs", # * "radiosity_start", # * - # no space for these commands. Might not actually exist + # seems radiosity_save and radiosity_debug_point + # are variants of radiosity_start #"radiosity_save", # * #"radiosity_debug_point", # * "ai", "ai_dialogue_triggers", "ai_grenades", - None, None, - "ai", - "ai_dialogue_triggers", - "ai_grenades", # 290 - None, + "ai_lines", # * + "ai_debug_sound_point_set", # * + "ai_debug_vocalize", # * + "ai_debug_teleport_to", # * + "ai_debug_speak", # 290 * + "ai_debug_speak_list", # ** "fade_in", "fade_out", "cinematic_start", diff --git a/reclaimer/h2/common_descs.py b/reclaimer/h2/common_descs.py index cb8225d2..b574b6ad 100644 --- a/reclaimer/h2/common_descs.py +++ b/reclaimer/h2/common_descs.py @@ -213,6 +213,7 @@ def h2_blam_header(tagid, version=1): "object_definition", "shader", + # should there be a space in these 3? "render model", "structure definition", "lightmap definition", @@ -229,6 +230,21 @@ def h2_blam_header(tagid, version=1): "device_name", "scenery_name", ) +# used in determining which script object types are tag refs +# NOTE: these are a bit of a guess based on the enums above and +# the pattern displayed in the h1 script object types +script_object_tag_ref_types = ( + "effect", + "damage", + "looping_sound", + "animation_graph", + "damage_effect", + "object_definition", + "shader", + "render model", + "structure definition", + "lightmap definition", + ) #Shared Enumerator options old_materials_list = materials_list diff --git a/reclaimer/halo_script/defs/hsc.py b/reclaimer/halo_script/defs/hsc.py new file mode 100644 index 00000000..0673ea26 --- /dev/null +++ b/reclaimer/halo_script/defs/hsc.py @@ -0,0 +1,340 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.field_types import * +from reclaimer.constants import * +from reclaimer.common_descs import DynamicArrayFrame,\ + script_object_types as h1_script_object_types,\ + desc_variant +# NOTE: using this desc_variant override to ensure verify is defaulted on +from reclaimer.util import float_to_str +from reclaimer.h2.common_descs import \ + script_object_types as h2_script_object_types +from reclaimer.stubbs.common_descs import \ + script_object_types as stubbs_script_object_types +from reclaimer.shadowrun_prototype.common_descs import\ + script_object_types as sr_script_object_types + + +from supyr_struct.defs.block_def import BlockDef +from supyr_struct.defs.tag_def import TagDef + + +HSC_HEADER_LEN = 56 +HSC_STUBBS_64BIT_HEADER_LEN = 64 +HSC_NODE_SIZE = 20 +HSC_SIG_OFFSET = 40 +HSC_IS_PRIMITIVE = 1 << 0 +HSC_IS_SCRIPT_CALL = 1 << 1 +HSC_IS_GLOBAL = 1 << 2 +HSC_IS_GARBAGE_COLLECTABLE = 1 << 3 +HSC_IS_PARAMETER = 1 << 4 +HSC_IS_STRIPPED = 1 << 5 +HSC_IS_SCRIPT_OR_GLOBAL = HSC_IS_SCRIPT_CALL | HSC_IS_GLOBAL + +script_node_ref = BitStruct("node", + SBitInt("idx", SIZE=16), + SBitInt("salt", SIZE=16), + SIZE=4 + ) +script_node_data_union = Union("data", + CASES={ + "boolean": Struct("bool", UInt8("data")), + "short": Struct("int16", SInt16("data")), + "long": Struct("int32", SInt32("data")), + "real": Struct("real", Float("data")), + "node": script_node_ref, + }, + CASE=(lambda parent=None, **kw: ( + "node" if not parent else { + s: s for s in ("boolean", "short", "long", "real") + }.get(parent.type.enum_name, "node") + )), + SIZE=4 + ) + +# this is a more complete version of the fast_script_node def below +script_node = Struct("script_node", + UInt16("salt"), + Union("index_union", + CASES={ + "constant_type": Struct("value", SInt16("constant_type")), + "function_name": Struct("value", SInt16("function_index")), + "script": Struct("value", SInt16("script_index")), + }, + COMMENT=""" +For most intents and purposes, this value mirrors the 'type' field""" + ), + SEnum16("type"), + Bool16("flags", + "is_primitive", + "is_script_call", + "is_global", + "is_garbage_collectable", + "is_parameter", # MCC only + "is_stripped", # MCC only + ), + desc_variant(script_node_ref, NAME="next_node"), + UInt32("string_offset"), + script_node_data_union, + SIZE=20 + ) + +h1_script_node = desc_variant(script_node, + SEnum16("type", *h1_script_object_types) + ) +h2_script_node = desc_variant(script_node, + SEnum16("type", *h2_script_object_types) + ) +stubbs_script_node = desc_variant(script_node, + SEnum16("type", *stubbs_script_object_types) + ) +stubbs_64bit_script_node = desc_variant(script_node, + SEnum16("type", *stubbs_script_object_types), + desc_variant(script_node_data_union, + SIZE=8, verify=False + ), + SIZE=24, verify=False, + ) +sr_script_node = desc_variant(script_node, + SEnum16("type", *sr_script_object_types) + ) + +script_syntax_data_header = Struct("header", + StrLatin1('name', DEFAULT="script node", SIZE=30), + FlUInt16("total_nodes", DEFAULT=0), # always little endian + SInt16("max_nodes", DEFAULT=19001), # this is 1 more than expected + UInt16("node_size", DEFAULT=20), + UInt8("is_valid", DEFAULT=1), + UInt8("identifier_zero_invalid"), # zero? + UInt16("unused"), + UInt32("sig", DEFAULT="d@t@"), + UInt16("next_node"), # always zero? + UInt16("last_node"), + BytesRaw("next", SIZE=4), # seems to be garbage? + Pointer32("first"), + SIZE=HSC_HEADER_LEN, + ) +stubbs_64bit_script_syntax_data_header = desc_variant(script_syntax_data_header, + UInt16("node_size", DEFAULT=24), + BytesRaw("next", SIZE=8), # seems to be garbage? + Pointer64("first"), # seems to be unset + SIZE=HSC_STUBBS_64BIT_HEADER_LEN, verify=False, + ) + +fast_script_node = QStruct("script_node", + UInt16("salt"), + UInt16("index_union"), + UInt16("type"), + UInt16("flags"), + UInt32("next_node"), + UInt32("string_offset"), + UInt32("data"), + SIZE=HSC_NODE_SIZE + ) + +fast_stubbs_64bit_script_node = desc_variant(fast_script_node, + UInt64("data"), + SIZE=24, verify=False, + ) + +h1_script_syntax_data = desc_variant(script_syntax_data_header, + STEPTREE=WhileArray("nodes", SUB_STRUCT=fast_script_node) + ) +h1_script_syntax_data_os = desc_variant(h1_script_syntax_data, + SInt16("max_nodes", DEFAULT=28501) + ) +h1_script_syntax_data_mcc = desc_variant(h1_script_syntax_data, + FlSInt16("total_nodes", DEFAULT=32766), # only set in mcc? + SInt16("max_nodes", DEFAULT=32767) + ) +stubbs_64bit_script_syntax_data = desc_variant( + stubbs_64bit_script_syntax_data_header, + STEPTREE=WhileArray("nodes", SUB_STRUCT=fast_stubbs_64bit_script_node) + ) + +h1_script_syntax_data_def = BlockDef(h1_script_syntax_data) +h1_script_syntax_data_os_def = BlockDef(h1_script_syntax_data_os) +h1_script_syntax_data_mcc_def = BlockDef(h1_script_syntax_data_mcc) +stubbs_64bit_script_syntax_data_def = BlockDef(stubbs_64bit_script_syntax_data) +# NOTE: update the stubbs and shadowrun script syntax defs if +# differences are ever discovered between them and halo. +# for now, these work perfectly since the max_nodes is +# the same as halo 1, and these defs are fast(no enums) +stubbs_script_syntax_data_def = h1_script_syntax_data_def +sr_script_syntax_data_def = h1_script_syntax_data_def + + +def _generate_script_syntax_tagdef( + prefix, script_node_desc, header_desc=script_syntax_data_header + ): + def get_script_data_endianness(parent=None, rawdata=None, **kw): + try: + rawdata.seek(parent.header_pointer + HSC_SIG_OFFSET) + sig = bytes(rawdata.read(4)) + return ">" if sig == b'd@t@' else "<" + except Exception: + asdf + pass + + def header_pointer(parent=None, rawdata=None, offset=0, root_offset=0, **kw): + if parent is None: + return + + base = offset + root_offset + ptr = -1 + try: + while ptr < base: + ptr = rawdata.find(b"script node\x00", base) + if ptr < 0: + break + + rawdata.seek(ptr + HSC_SIG_OFFSET) + if bytes(rawdata.read(4)) in (b'd@t@', b'@t@d'): + break + + base = ptr + 12 # 12 is size of "script node" string + ptr = -1 + + except Exception: + ptr = parent.base_pointer + + parent.header_pointer = ptr + + def strings_pointer(parent=None, header_size=header_desc["SIZE"], **kw): + if parent is None: + return + header = parent.header + parent.strings_pointer = ( + parent.parent.header_pointer + header_size + + header.max_nodes * header.node_size + ) + + def get_script_string_data_size(parent=None, **kw): + try: + return parent.header.max_nodes * parent.header.node_size + except Exception: + return 0 + + def get_node_string(parent=None, rawdata=None, **kw): + if parent is None: + return + + node = parent.parent + node_type = node.type.enum_name + flags = node.flags.data + data = node.data + is_global = flags & HSC_IS_GLOBAL + is_script = flags & HSC_IS_SCRIPT_CALL + is_primitive = flags & HSC_IS_PRIMITIVE + is_script_or_global = is_global or is_script + is_list = not is_primitive + is_unused = node.salt == 0 or node_type in("special_form", "unparsed") + prefixes = [] + string = None + + if node.next_node.salt != -1 and node.next_node.idx != -1: + prefixes.append(f"NEXT={node.next_node.idx}") + + if is_unused: + prefixes = ["UNUSED", *prefixes] + elif is_list: + prefixes = [f"FIRST={data.node.idx}", *prefixes] + else: + if not is_global and node_type in ("boolean", "real", "short", "long"): + string = ( + bool(data.boolean.data&1) if node_type == "boolean" else + data.real.data if node_type == "real" else + data.short.data if node_type == "short" else + data.long.data if node_type == "long" else + None + ) + else: + start = node.parent.parent.strings_pointer + node.string_offset + end = rawdata.find(b"\x00", start) + try: + rawdata.seek(start-1) + if rawdata.read(1) == b"\x00" or node.string_offset == 0: + string = rawdata.read(max(0, end-start)).decode(encoding="latin-1") + except Exception: + pass + + if string is None: + node_type, string = "undef", "" + + if is_global or is_script: + prefixes = ["GLOBAL" if is_global else "SCRIPT", *prefixes] + + if not is_unused and not is_list: + prefixes.append(f"TYPE={node_type}") + + parent.string = string + parent.description = '<%s> %s' % ( + " ".join(prefixes), + "" if string is None else f'"{string}"' + ) + + script_node_desc = desc_variant(script_node_desc, + STEPTREE=Container("computed", + Computed("description", COMPUTE_READ=get_node_string, WIDGET_WIDTH=120), + Computed("string", WIDGET_WIDTH=120), + ), + ) + script_data = Container("script_data", + desc_variant(header_desc, POINTER="..header_pointer"), + Computed("strings_pointer", COMPUTE_READ=strings_pointer, WIDGET_WIDTH=20), + Array("nodes", + SUB_STRUCT=script_node_desc, SIZE=".header.last_node", + DYN_NAME_PATH=".computed.description", WIDGET=DynamicArrayFrame + ), + ) + + return ( + TagDef('%s_%s_scripts' % (prefix, extension), + Computed("header_pointer", COMPUTE_READ=header_pointer, WIDGET_WIDTH=20), + Switch("script_data", + CASES={ + ">": desc_variant(script_data, ENDIAN=">"), + "<": desc_variant(script_data, ENDIAN="<"), + }, + CASE=get_script_data_endianness, DEFAULT=script_data + ), + ext="." + extension + ) + for extension in ("scenario", "map") + ) + +# for loading in binilla for debugging script data issues +def get(): + return ( + *h1_script_syntax_data_tagdefs, + *h2_script_syntax_data_tagdefs, + *stubbs_script_syntax_data_tagdefs, + *stubbs_64bit_script_syntax_data_tagdefs, + *sr_script_syntax_data_tagdefs + ) + +h1_script_syntax_data_tagdefs = _generate_script_syntax_tagdef( + "h1", h1_script_node + ) +h2_script_syntax_data_tagdefs = _generate_script_syntax_tagdef( + "h2", h2_script_node + ) +stubbs_script_syntax_data_tagdefs = _generate_script_syntax_tagdef( + "stubbs", stubbs_script_node + ) +stubbs_64bit_script_syntax_data_tagdefs = _generate_script_syntax_tagdef( + "stubbs_64bit", stubbs_64bit_script_node, + stubbs_64bit_script_syntax_data_header + ) +sr_script_syntax_data_tagdefs = _generate_script_syntax_tagdef( + "sr", sr_script_node + ) + +del _generate_script_syntax_tagdef # just for quick generation \ No newline at end of file diff --git a/reclaimer/halo_script/hsc.py b/reclaimer/halo_script/hsc.py index c9ff3aa5..0183051d 100644 --- a/reclaimer/halo_script/hsc.py +++ b/reclaimer/halo_script/hsc.py @@ -10,17 +10,25 @@ from struct import pack, unpack from types import MethodType -from reclaimer.field_types import * -from reclaimer.constants import * -from reclaimer.common_descs import ascii_str32,\ +from reclaimer.halo_script.defs.hsc import * + +from reclaimer.common_descs import \ script_types as h1_script_types,\ - script_object_types as h1_script_object_types + script_object_types as h1_script_object_types,\ + script_object_tag_ref_types as h1_script_object_tag_ref_types from reclaimer.util import float_to_str from reclaimer.h2.common_descs import script_types as h2_script_types,\ - script_object_types as h2_script_object_types + script_object_types as h2_script_object_types,\ + script_object_tag_ref_types as h2_script_object_tag_ref_types +from reclaimer.stubbs.common_descs import \ + script_types as stubbs_script_types,\ + script_object_types as stubbs_script_object_types,\ + script_object_tag_ref_types as stubbs_script_object_tag_ref_types +from reclaimer.shadowrun_prototype.common_descs import \ + script_types as sr_script_types,\ + script_object_types as sr_script_object_types,\ + script_object_tag_ref_types as sr_script_object_tag_ref_types -from supyr_struct.defs.block_def import BlockDef -from supyr_struct.defs.tag_def import TagDef from supyr_struct.field_types import FieldType try: @@ -28,6 +36,7 @@ except Exception: _script_built_in_functions_test = None + # in a list that begins with the keyed expression, this # is the number of nodes required before we will indent them. INDENT_LIST_MIN = { @@ -48,96 +57,23 @@ "ai_debug_communication_focus": 1, } -HSC_IS_PRIMITIVE = 1 << 0 -HSC_IS_SCRIPT_CALL = 1 << 1 -HSC_IS_GLOBAL = 1 << 2 -HSC_IS_GARBAGE_COLLECTABLE = 1 << 3 -HSC_IS_SCRIPT_OR_GLOBAL = HSC_IS_SCRIPT_CALL | HSC_IS_GLOBAL - +_script_objects = ("object", "unit", "vehicle", "weapon", "device", "scenery") SCRIPT_OBJECT_TYPES_TO_SCENARIO_REFLEXIVES = dict(( - (10, "scripts"), (11, "trigger_volumes"), (12, "cutscene_flags"), - (13, "cutscene_camera_points"), (14, "cutscene_titles"), - (15, "recorded_animations"), (16, "device_groups"), - (17, "encounters"), (18, "command_lists"), - (19, "player_starting_profiles"), (20, "ai_conversations"), - ) + tuple((i, "object_names") for i in range(37, 49))) - - -h1_script_type = SEnum16("type", *h1_script_types) -h1_return_type = SEnum16("return_type", *h1_script_object_types) - - -# this is a more complete version of the fast_script_node def below -script_node = Struct("script_node", - UInt16("salt"), - Union("index_union", - CASES={ - "constant_type": Struct("constant_type", SInt16("value")), - "function_index": Struct("function_index", SInt16("value")), - "script_index": Struct("script_index", SInt16("value")), - }, - COMMENT=""" -For most intents and purposes, this value mirrors the 'type' field""" - ), - SEnum16("type", *h1_script_object_types), - Bool16("flags", - "is_primitive", - "is_script_call", - "is_global", - "is_garbage_collectable", - "is_parameter", # MCC only - "is_stripped", # MCC only - ), - UInt32("next_node"), - UInt32("string_offset"), - Union("data", - CASES={ - "bool": Struct("bool", UInt8("data")), - "int16": Struct("int16", SInt16("data")), - "int32": Struct("int32", SInt32("data")), - "real": Struct("real", Float("data")), - "node": Struct("node", UInt32("data")), - } - ), - SIZE=20 - ) - -fast_script_node = QStruct("script_node", - UInt16("salt"), - UInt16("index_union"), - UInt16("type"), - UInt16("flags"), - UInt32("next_node"), - UInt32("string_offset"), - UInt32("data"), - SIZE=20 - ) - -script_syntax_data_header = Struct("header", - ascii_str32('name', DEFAULT="script node"), - UInt16("max_nodes", DEFAULT=19001), # this is 1 more than expected - UInt16("node_size", DEFAULT=20), - UInt8("is_valid", DEFAULT=1), - UInt8("identifier_zero_invalid"), # zero? - UInt16("unused"), - UInt32("sig", DEFAULT="d@t@"), - UInt16("next_node"), # zero? - UInt16("last_node"), - BytesRaw("next", SIZE=4), - Pointer32("first"), - SIZE=56, - ) - -h1_script_syntax_data = Struct("script_syntax_data_header", - INCLUDE=script_syntax_data_header, - STEPTREE=WhileArray("nodes", SUB_STRUCT=fast_script_node) - ) - -h1_script_syntax_data_os = dict(h1_script_syntax_data) -h1_script_syntax_data_os[1] = UInt16("max_nodes", DEFAULT=28501) - -h1_script_syntax_data_def = BlockDef(h1_script_syntax_data) -h1_script_syntax_data_os_def = BlockDef(h1_script_syntax_data_os) + ("script", "scripts"), + ("trigger_volume", "trigger_volumes"), + ("cutscene_flag", "cutscene_flags"), + ("cutscene_camera_point", "cutscene_camera_points"), + ("cutscene_title", "cutscene_titles"), + ("cutscene_recording", "recorded_animations"), + ("device_group", "device_groups"), + ("ai", "encounters"), + ("ai_command_list", "command_lists"), + ("starting_profile", "player_starting_profiles"), + ("conversation", "ai_conversations"), + *((typ, "object_names") for typ in _script_objects), + *(("%s_name" % typ, "object_names") for typ in _script_objects), + )) +del _script_objects def cast_uint32_to_float(uint32, packer=MethodType(pack, "= len(nodes): return "", False, 0 @@ -299,56 +269,64 @@ def decompile_node_bytecode(node_index, nodes, script_blocks, string_data, newl = False node = nodes[node_index] node_type = node.type - union_i = node.index_union node_str = "" + if node.flags == HSC_IS_GARBAGE_COLLECTABLE: start_node = get_first_significant_node(node, nodes, string_data) child_node, salt = start_node.data & 0xFFff, start_node.data >> 16 if salt != 0: node_str, newl, ct = decompile_node_bytecode( - child_node, nodes, script_blocks, string_data, - object_types, indent + 1, indent_size, **kwargs) + child_node, nodes, string_data, + object_types, script_types, indent + 1, indent_size, + indent_char, return_char, bool_as_int, **kwargs) if ct > 1 or newl or (node_str[:1] != "(" and node_str[-1:] != ")"): # only add a start parenthese so the end can be added # on later when we decide how to pad the list elements - node_str = "(%s" % node_str[1:] + node_str = "(" + node_str else: # reparse the node at a lesser indent node_str, newl, ct = decompile_node_bytecode( - child_node, nodes, script_blocks, string_data, - object_types, indent, indent_size, **kwargs) + child_node, nodes, string_data, + object_types, script_types, indent, indent_size, + indent_char, return_char, bool_as_int, **kwargs) + node_str = (" " + node_str) if node_str else "" elif node.flags & HSC_IS_GLOBAL: node_str = get_hsc_node_string(string_data, node, hsc_node_strings_by_type) - if "global_uses" in kwargs: + if "global_uses" in kwargs and node_str: kwargs["global_uses"].add(node_str) - elif (node.flags & HSC_IS_SCRIPT_CALL and - union_i >= 0 and union_i < len(script_blocks)): + elif node.flags & HSC_IS_SCRIPT_CALL: # is_script_call is set - block = script_blocks[union_i] - node_str = "(%s" % block.name - if "static_calls" in kwargs: - kwargs["static_calls"].add(block.name) + args_node, salt = node.data & 0xFFff, node.data >> 16 + node_str = "(" + script_call_node_str, newl, ct = decompile_node_bytecode( + args_node, nodes, string_data, + object_types, script_types, indent + 1, indent_size, + indent_char, return_char, bool_as_int, **kwargs) + + node_str += script_call_node_str elif node.flags & HSC_IS_PRIMITIVE: + # TODO: remove value hardcoding in these node_type checks # is_primitive is set if node_type in (2, 10): # function/script name node_str = get_hsc_node_string(string_data, node, hsc_node_strings_by_type) - if node_type == 10 and "static_calls" in kwargs: + if node_type == 10 and "static_calls" in kwargs and node_str: kwargs["static_calls"].add(node_str) - elif node_type in (3, 4): - # passthrough/void type + elif node_type in range(5): + # special form/unparsed/passthrough/void type pass elif node_type == 5: # bool - node_str = "true" if node.data&1 else "false" + val = node.data&1 + node_str = str(val) if bool_as_int else ["false", "true"][val] elif node_type == 6: # float node_str = float_to_str(cast_uint32_to_float(node.data)) @@ -365,7 +343,7 @@ def decompile_node_bytecode(node_index, nodes, script_blocks, string_data, node_strs.append((node_str, newl)) node_index, salt = node.next_node & 0xFFff, node.next_node >> 16 - if salt == 0: + if salt == -1 and node_index == -1: break i += 1 @@ -375,7 +353,7 @@ def decompile_node_bytecode(node_index, nodes, script_blocks, string_data, return "", False, 0 string = "" - indent_str = " " * indent_size * indent + indent_str = indent_char * indent_size * indent returned = False i = 0 first_node = node_strs[0] @@ -397,81 +375,167 @@ def decompile_node_bytecode(node_index, nodes, script_blocks, string_data, if returned: node_str = indent_str + node_str else: - node_str = "\n" + indent_str + node_str + node_str = return_char + indent_str + node_str has_newlines = True elif i > 1: + # ensure there's a space to separate parameters node_str = " " + node_str if cap_end: if local_newlines: - node_str += "\n" + indent_str + node_str += return_char + indent_str node_str += ")" - returned = node_str[-1] == "\n" + returned = node_str[-1] == return_char string += node_str - if string: - # always add a space to the beginning. - # it can be stripped off by whatever we return to - string = " " + string - return string, has_newlines, i +def get_script_types(engine="halo1"): + '''Returns the script types and script object types for this engine.''' + return ( + (h1_script_types, h1_script_object_types) if "yelo" in engine else + (h1_script_types, h1_script_object_types) if "halo1" in engine else + (h2_script_types, h2_script_object_types) if "halo2" in engine else + (sr_script_types, sr_script_object_types) if "shadowrun" in engine else + (stubbs_script_types, stubbs_script_object_types) if "stubbs" in engine else + ((), ()) + ) + + +def get_script_tag_ref_type_names(engine="halo1"): + ''' + Returns a list containing the enum name each script node tag + ref type for this engine. + ''' + # these are the names of the script object types that are tag references + return ( + h1_script_object_tag_ref_types if "yelo" in engine else + h1_script_object_tag_ref_types if "halo1" in engine else + h2_script_object_tag_ref_types if "halo2" in engine else + sr_script_object_tag_ref_types if "shadowrun" in engine else + stubbs_script_object_tag_ref_types if "stubbs" in engine else + () + ) + + +def get_script_tag_ref_types(engine="halo1"): + ''' + Returns a list containing the enum value of each script node tag + ref type for this engine. + ''' + # these are the names of the script object types that are tag references + tag_ref_script_types = get_script_tag_ref_type_names(engine) + + _, script_object_types = get_script_types(engine) + return [ + script_object_types.index(typ) + for typ in tag_ref_script_types + ] + + +def get_script_syntax_node_tag_refs(syntax_data, engine="halo1"): + '''Returns a list of all script nodes that are tag references.''' + tag_ref_type_enums = set(get_script_tag_ref_types(engine)) + tag_ref_nodes = [] + + # null all references to tags + for node in syntax_data.nodes: + if (node.flags & HSC_IS_SCRIPT_OR_GLOBAL or + node.type not in tag_ref_type_enums): + # not a tag index ref + continue + + tag_ref_nodes.append(node) + + return tag_ref_nodes + + +def clean_script_syntax_nodes(syntax_data, engine="halo1"): + ''' + Scans through script nodes and nulls tag references. + This is necessary for script syntax data in tag form. + ''' + # null all references to tags + for node in get_script_syntax_node_tag_refs(syntax_data, engine): + # null the reference + node.data = 0xFFffFFff + + def hsc_bytecode_to_string(syntax_data, string_data, block_index, script_blocks, global_blocks, block_type, - engine="halo1", indent_size=4, **kwargs): - if block_type not in ("script", "global"): + engine="halo1", indent_size=4, minify=False, + indent_char=" ", return_char="\n", **kwargs): + is_global = (block_type == "global") + is_script = (block_type == "script") + script_types, object_types = get_script_types(engine) + if not((is_global or is_script) and script_types and object_types): return "" - if ("halo1" in engine or "yelo" in engine or - "stubbs" in engine or "shadowrun" in engine): - script_types = h1_script_types - object_types = h1_script_object_types - elif "halo2" in engine: - script_types = h2_script_types - object_types = h2_script_object_types - else: - return "" + # figure out which reflexive and type enums to use + blocks = script_blocks if is_script else global_blocks + typ_names = script_types if is_script else object_types - if block_type == "global": - block = global_blocks[block_index] - script_type = "" - node_index = block.initialization_expression_index & 0xFFFF - main_type = object_types[block.type.data] - else: - block = script_blocks[block_index] - script_type = script_types[block.type.data] - node_index = block.root_expression_index & 0xFFFF - main_type = object_types[block.return_type.data] - if script_type in ("dormant", "startup", "continuous"): - # these types wont compile if a return type is specified - main_type = "" - else: - script_type += " " - - if "INVALID" in main_type: + block = blocks[block_index] + typ = block.type.data + + # invalid script/global type + if typ not in range(len(typ_names)): return "" - indent_str = " " * indent_size - head = "%s %s%s %s" % (block_type, script_type, main_type, block.name) + if minify: + indent_size = 0 + indent_char = "" + return_char = "" + + # get the index of the node in the nodes array + node_index = ( + block.root_expression_index if is_script else + block.initialization_expression_index + ) & 0xFFff + + # figure out the type of the node + node_type = typ_names[typ] + + # scripts also have a return type(except the 3 specified below) + if is_script and node_type not in ("dormant", "startup", "continuous"): + return_typ = block.return_type.data + if return_typ not in range(len(object_types)): + # invalid return type + return "" + + node_type += " " + object_types[return_typ] + + # generate the suffix of the header, which includes the function/global + # name, and any parameters the function accepts(params are MCC only) + suffix = block.name + if hasattr(block, "parameters") and block.parameters.STEPTREE: + for param in block.parameters.STEPTREE: + return_typ = param.return_type.data + if return_typ not in range(len(object_types)): + # invalid return type + print("Invalid return type '%s' in script '%s'" % + (param.name, block.name) + ) + return "" + suffix += "%s(%s %s)" % ( + indent_char, object_types[return_typ], param.name + ) + suffix = "(%s)" % suffix + + head = "%s %s %s" % (block_type, node_type, suffix) body, _, __ = decompile_node_bytecode( - node_index, syntax_data.nodes, script_blocks, string_data, - object_types, 1, indent_size, **kwargs) - if block_type == "global": - return "(%s%s)" % (head, body) - return "(%s\n%s%s\n)" % (head, indent_str, body[1:]) - - -# for loading in binilla for debugging script data issues -def get(): - return script_syntax_data_def - + node_index, syntax_data.nodes, string_data, + object_types=object_types, script_types=script_types, + indent_size=indent_size, indent_char=indent_char, + return_char=return_char, **kwargs + ) + + if is_script: + body = "".join((return_char, " " * indent_size, body, return_char)) + else: + # add a space to separate global definition from its value + body = " " + body -script_syntax_data_def = TagDef('script_syntax_data', - script_syntax_data_header, - Array("nodes", - SUB_STRUCT=script_node, SIZE=".header.last_node" - ), - endian='>' - ) \ No newline at end of file + return "(%s%s)" % (head, body) diff --git a/reclaimer/halo_script/hsc_decompilation.py b/reclaimer/halo_script/hsc_decompilation.py index cf3e8bd5..820f54b3 100644 --- a/reclaimer/halo_script/hsc_decompilation.py +++ b/reclaimer/halo_script/hsc_decompilation.py @@ -15,149 +15,176 @@ __all__ = ("extract_h1_scripts", ) - -MAX_SCRIPT_SOURCE_SIZE = 1 << 18 - - -def extract_h1_scripts(tagdata, tag_path, **kw): - dirpath = Path(kw.get("out_dir", "")).joinpath( - Path(tag_path).parent, "scripts") - - overwrite = kw.get('overwrite', True) - hsc_node_strings_by_type = kw.get("hsc_node_strings_by_type", ()) - - dirpath.mkdir(exist_ok=True, parents=True) - - engine = kw.get('engine') - if not engine and 'halo_map' in kw: - engine = kw['halo_map'].engine +def _lf_to_crlf(string): # laziness + return string.replace("\n", "\r\n") + + +SCRIPT_HEADER = _lf_to_crlf("; Extracted with Reclaimer\n\n") +MAX_SCRIPT_SOURCE_SIZE = 1 << 18 +MCC_MAX_SCRIPT_SOURCE_SIZE = 1 << 20 + + +def generate_scenario_references_comment(tagdata): + ''' + Generate a string which lists out all scenario references + that may be useful to have when editing extracted scripts. + ''' + comments = "\n; scenario names(each sorted alphabetically)\n" + comments += "\n; object names:\n" + for name in sorted(set(obj.name for obj in + tagdata.object_names.STEPTREE)): + comments += "; %s\n" % name + + comments += "\n; trigger volumes:\n" + for name in sorted(set(tv.name for tv in + tagdata.trigger_volumes.STEPTREE)): + comments += "; %s\n" % name + + comments += "\n; device groups:\n" + for name in sorted(set(dg.name for dg in + tagdata.device_groups.STEPTREE)): + comments += "; %s\n" % name + + comments += "\n; globals:\n" + return _lf_to_crlf(comments) + + +def extract_scripts( + tagdata, engine=None, halo_map=None, add_comments=True, + minify=False, max_script_size=None, default_engine=None, **kwargs + # NOTE: accepting arbitrary kwargs cause caller wont know what args we use + ): + # this will hold the decompiled script source file strings + script_sources, global_sources = [], [] + + engine = engine or getattr(halo_map, "engine", default_engine) + if max_script_size is None: + max_script_size = ( + MCC_MAX_SCRIPT_SOURCE_SIZE if engine == "halo1mcc" else + MAX_SCRIPT_SOURCE_SIZE + ) + + # unknown currently if original halo allows this, but mcc does + kwargs.setdefault("bool_as_int", engine == "halo1mcc") syntax_data = get_hsc_data_block(tagdata.script_syntax_data.data) string_data = tagdata.script_string_data.data.decode("latin-1") if not syntax_data or not string_data: - return "No script data to extract." + return script_sources, global_sources + + # generate comments string(unless we want sources as small as possible) + comments = "" if minify or not add_comments else ( + generate_scenario_references_comment(tagdata) + ) already_sorted = set() for typ, arr in (("global", tagdata.globals.STEPTREE), ("script", tagdata.scripts.STEPTREE)): - filename_base = "%ss" % typ - - header = "; Extracted with Reclaimer\n\n" - comments = "" - src_file_i = 0 global_uses = {} static_calls = {} - if typ == "global": - sort_by = global_uses - try: - comments += "; scenario names(each sorted alphabetically)\n" - comments += "\n; object names:\n" - for name in sorted(set(obj.name for obj in - tagdata.object_names.STEPTREE)): - comments += "; %s\n" % name - - comments += "\n; trigger volumes:\n" - for name in sorted(set(tv.name for tv in - tagdata.trigger_volumes.STEPTREE)): - comments += "; %s\n" % name - - comments += "\n; device groups:\n" - for name in sorted(set(dg.name for dg in - tagdata.device_groups.STEPTREE)): - comments += "; %s\n" % name - - except Exception: - pass - else: - sort_by = static_calls - - try: - sources = {} - for i in range(len(arr)): - # assemble source code for each function/global - name = arr[i].name - global_uses[name] = set() - static_calls[name] = set() - sources[name] = hsc_bytecode_to_string( - syntax_data, string_data, i, tagdata.scripts.STEPTREE, - tagdata.globals.STEPTREE, typ, engine, - global_uses=global_uses[name], - hsc_node_strings_by_type=hsc_node_strings_by_type, - static_calls=static_calls[name]) - - sorted_sources = [] - need_to_sort = sources - while need_to_sort: - # sort the functions/globals so dependencies come first - next_need_to_sort = {} - for name in sorted(need_to_sort.keys()): - source = need_to_sort[name] - if sort_by[name].issubset(already_sorted): - sorted_sources.append(source) - already_sorted.add(name) - else: - next_need_to_sort[name] = source - - if need_to_sort.keys() == next_need_to_sort.keys(): - print("Could not sort these %ss so dependencies come first:" % typ) - for name in need_to_sort.keys(): - print("\t%s" % name) - print("\t Requires: ", ", ".join(sorted(sort_by[name]))) - print() - sorted_sources.append(need_to_sort[name]) - break - need_to_sort = next_need_to_sort - - merged_sources = [] - merged_src = "" - merged_src_len = 0 - # figure out how much data we can fit in the source file - max_size = MAX_SCRIPT_SOURCE_SIZE - len(header) - len(comments) - - for src in sorted_sources: - if not src: - continue - - src += "\n\n\n" - # \n will be translated to \r\n, so the actual serialized string - # length will be incremented by the number of newline characters - src_len = len(src) + src.count("\n") - - # concatenate sources until they are too large to be compiled - if merged_src_len + src_len >= max_size: - merged_sources.append(merged_src) - merged_src = "" - merged_src_len = 0 - - merged_src += src - merged_src_len += src_len - - if merged_src: - merged_sources.append(merged_src) - - i = 0 - for out_data in merged_sources: - # write sources to hsc files - if len(merged_sources) > 1: - filename = "%s_%s.hsc" % (filename_base, i) + sort_by = global_uses if (typ == "global") else static_calls + sources = global_sources if (typ == "global") else script_sources + + decompiled_scripts = {} + for i in range(len(arr)): + # assemble source code for each function/global + name = arr[i].name + global_uses[name] = set() + static_calls[name] = set() + + decompiled_scripts[name] = hsc_bytecode_to_string( + syntax_data, string_data, i, + tagdata.scripts.STEPTREE, + tagdata.globals.STEPTREE, typ, engine, + global_uses=global_uses[name], + static_calls=static_calls[name], minify=minify, **kwargs + ) + + sorted_sources = [] + need_to_sort = decompiled_scripts + while need_to_sort: + # sort the functions/globals so dependencies come first + next_need_to_sort = {} + for name in sorted(need_to_sort.keys()): + source = need_to_sort[name] + sort_remainder = sort_by[name].difference(already_sorted) + if sort_remainder: + next_need_to_sort[name] = source + sort_by[name] = sort_remainder else: - filename = "%s.hsc" % filename_base - - filepath = dirpath.joinpath(filename) - if not overwrite and filepath.is_file(): - continue - - # apparently the scripts use latin1 encoding, who knew.... - with filepath.open("w", encoding='latin1', newline="\r\n") as f: - f.write(header) - f.write(out_data) - f.write(comments) - - i += 1 - except Exception: - return format_exc() + sorted_sources.append(source) + already_sorted.add(name) + + if need_to_sort.keys() == next_need_to_sort.keys(): + print("Could not sort these %ss so dependencies come first:" % typ) + for name in need_to_sort.keys(): + print("\t%s" % name) + print("\t Requires: ", ", ".join(sorted(sort_by[name]))) + print() + sorted_sources.append(need_to_sort[name]) + break + need_to_sort = next_need_to_sort + + # header to put before each extracted source file + header = "" if minify else SCRIPT_HEADER + ( + comments if (typ == "global") else "" + ) + + # concatenate script sources until they are too large to be compiled + sources.append(header) + for src in sorted_sources: + if not src: + continue + + # translate \n to \r\n since that's what haloscripts uses. + src = _lf_to_crlf(src + ("\n" if minify else "\n\n")) + + # if we're gonna pass our limit, append a new source file + if len(sources[-1]) + len(src) >= max_script_size: + sources.append(header) + + sources[-1] += src # TEMPORARY CODE #from reclaimer.enums import TEST_PRINT_HSC_BUILT_IN_FUNCTIONS #TEST_PRINT_HSC_BUILT_IN_FUNCTIONS() # TEMPORARY CODE + + return script_sources, global_sources + + +def extract_scripts_to_file(tagdata, tag_path, **kwargs): + out_dir = kwargs.pop("out_dir", "") + overwrite = kwargs.pop('overwrite', True) + script_sources, global_sources = extract_scripts(tagdata, **kwargs) + + if not script_sources or not global_sources: + return "No scripts to extract." + + dirpath = Path(out_dir).joinpath(Path(tag_path).parent, "scripts") + dirpath.mkdir(exist_ok=True, parents=True) + for typ, sources in ( + ("scripts", script_sources), + ("globals", global_sources), + ): + + for i in range(len(sources)): + # write sources to hsc files + filename = "%s_%s.hsc" % (typ, i) + filepath = dirpath.joinpath(filename) + if not overwrite and filepath.is_file(): + continue + + # apparently the scripts use latin1 encoding, who knew.... + with filepath.open("w", encoding='latin1', newline="") as f: + f.write(sources[i]) + + +def extract_h1_scripts(tagdata, tag_path, **kwargs): + kwargs.setdefault("default_engine", "halo1yelo") + return extract_scripts_to_file(tagdata, tag_path, **kwargs) + + +def extract_h1_mcc_scripts(tagdata, tag_path, **kwargs): + kwargs.setdefault("default_engine", "halo1mcc") + return extract_scripts_to_file(tagdata, tag_path, **kwargs) \ No newline at end of file diff --git a/reclaimer/hek/defs/actr.py b/reclaimer/hek/defs/actr.py index 17e84ec5..4e1c76d6 100644 --- a/reclaimer/hek/defs/actr.py +++ b/reclaimer/hek/defs/actr.py @@ -34,6 +34,7 @@ float_zero_to_one("leader_killed_panic_chance"), float_zero_to_one("panic_damage_threshold"), float_wu("surprise_distance"), # world units + SIZE=28 ) actr_body = Struct("tagdata", @@ -175,16 +176,7 @@ ), Pad(8), - Struct("panic", - from_to_sec("cowering_time"), # seconds - float_zero_to_one("friend_killed_panic_chance"), - SEnum16("leader_type", *actor_types), - - Pad(2), - float_zero_to_one("leader_killed_panic_chance"), - float_zero_to_one("panic_damage_threshold"), - float_wu("surprise_distance"), # world units - ), + panic, Pad(28), Struct("defensive", diff --git a/reclaimer/hek/defs/actv.py b/reclaimer/hek/defs/actv.py index 498c2a77..e687b86b 100644 --- a/reclaimer/hek/defs/actv.py +++ b/reclaimer/hek/defs/actv.py @@ -39,7 +39,8 @@ float_wu("collateral_damage_radius"), float_zero_to_one("grenade_chance"), float_sec("grenade_check_time", UNIT_SCALE=sec_unit_scale), - float_sec("encounter_grenade_timeout", UNIT_SCALE=sec_unit_scale) + float_sec("encounter_grenade_timeout", UNIT_SCALE=sec_unit_scale), + SIZE=52, ) actv_body = Struct("tagdata", diff --git a/reclaimer/hek/defs/antr.py b/reclaimer/hek/defs/antr.py index 9399a550..84d62ee2 100644 --- a/reclaimer/hek/defs/antr.py +++ b/reclaimer/hek/defs/antr.py @@ -128,6 +128,7 @@ ), reflexive("ik_points", ik_point_desc, 4, DYN_NAME_PATH=".marker"), reflexive("weapons", unit_weapon_desc, 16, DYN_NAME_PATH=".name"), + Pad(0), # replaced with unknown reflexive in stubbs SIZE=100, ) @@ -161,7 +162,8 @@ SInt16("down_frame_count"), SInt16("up_frame_count"), - Pad(68), + Pad(56), + Pad(12), # replaced with seats reflexive in stubbs reflexive("animations", anim_enum_desc, 8, *vehicle_animation_names ), @@ -220,6 +222,7 @@ Float("weight"), SInt16("key_frame_index"), SInt16("second_key_frame_index"), + Pad(0), # replaced with Pad(8) in stubbs dyn_senum16("next_animation", DYN_NAME_PATH="..[DYN_I].name"), diff --git a/reclaimer/hek/defs/bipd.py b/reclaimer/hek/defs/bipd.py index 18d22720..0b76bbf6 100644 --- a/reclaimer/hek/defs/bipd.py +++ b/reclaimer/hek/defs/bipd.py @@ -19,14 +19,8 @@ from .objs.bipd import BipdTag from .obje import * from .unit import * -from supyr_struct.util import desc_variant - -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(0)) - ) +obje_attrs = obje_attrs_variant(obje_attrs, "bipd") contact_point = Struct("contact_point", Pad(32), ascii_str32('marker_name'), diff --git a/reclaimer/hek/defs/bitm.py b/reclaimer/hek/defs/bitm.py index 7cfec1c6..00ac0300 100644 --- a/reclaimer/hek/defs/bitm.py +++ b/reclaimer/hek/defs/bitm.py @@ -256,7 +256,7 @@ def pixel_block_size(node, *a, **kwa): {NAME: "x512", VALUE: 4, GUI_NAME: "512x512"}, ), UInt16("sprite_budget_count"), - COMMENT=sprite_processing_comment + SIZE=4, COMMENT=sprite_processing_comment ), UInt16("color_plate_width", SIDETIP="pixels", EDITABLE=False), UInt16("color_plate_height", SIDETIP="pixels", EDITABLE=False), diff --git a/reclaimer/hek/defs/cdmg.py b/reclaimer/hek/defs/cdmg.py index 4253816a..7820e493 100644 --- a/reclaimer/hek/defs/cdmg.py +++ b/reclaimer/hek/defs/cdmg.py @@ -11,7 +11,6 @@ from .jpt_ import damage, camera_shaking from .objs.tag import HekTag from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant damage = desc_variant(damage, ("aoe_core_radius", Pad(4)), diff --git a/reclaimer/hek/defs/coll.py b/reclaimer/hek/defs/coll.py index 3a4800b4..ad1fb153 100644 --- a/reclaimer/hek/defs/coll.py +++ b/reclaimer/hek/defs/coll.py @@ -66,6 +66,7 @@ dependency("shield_recharging_effect", "effe"), Pad(8), FlFloat("shield_recharge_rate", VISIBLE=False), + SIZE=248 ) bsp3d_node = QStruct("bsp3d_node", diff --git a/reclaimer/hek/defs/ctrl.py b/reclaimer/hek/defs/ctrl.py index 6ce72336..619d24e2 100644 --- a/reclaimer/hek/defs/ctrl.py +++ b/reclaimer/hek/defs/ctrl.py @@ -10,15 +10,8 @@ from .obje import * from .devi import * from .objs.ctrl import CtrlTag -from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant - -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(8)) - ) +obje_attrs = obje_attrs_variant(obje_attrs, "ctrl") ctrl_attrs = Struct("ctrl_attrs", SEnum16('type', 'toggle_switch', diff --git a/reclaimer/hek/defs/eqip.py b/reclaimer/hek/defs/eqip.py index cd8b091b..6e9b504b 100644 --- a/reclaimer/hek/defs/eqip.py +++ b/reclaimer/hek/defs/eqip.py @@ -10,15 +10,8 @@ from .obje import * from .item import * from .objs.obje import ObjeTag -from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant - -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(3)) - ) +obje_attrs = obje_attrs_variant(obje_attrs, "eqip") eqip_attrs = Struct("eqip_attrs", SEnum16('powerup_type', 'none', diff --git a/reclaimer/hek/defs/garb.py b/reclaimer/hek/defs/garb.py index 039e4a46..3828202e 100644 --- a/reclaimer/hek/defs/garb.py +++ b/reclaimer/hek/defs/garb.py @@ -10,15 +10,8 @@ from .obje import * from .item import * from .objs.obje import ObjeTag -from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant - -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(4)) - ) +obje_attrs = obje_attrs_variant(obje_attrs, "garb") garb_body = Struct("tagdata", obje_attrs, item_attrs, diff --git a/reclaimer/hek/defs/hudg.py b/reclaimer/hek/defs/hudg.py index 4990b261..25ee11f8 100644 --- a/reclaimer/hek/defs/hudg.py +++ b/reclaimer/hek/defs/hudg.py @@ -182,7 +182,8 @@ SInt16("checkpoint_begin_text"), SInt16("checkpoint_end_text"), dependency("checkpoint", "snd!"), - BytearrayRaw("unknown", SIZE=96, VISIBLE=False), + BytearrayRaw("unknown0", SIZE=12, VISIBLE=False), # replaced with remaps in mcc + BytearrayRaw("unknown1", SIZE=84, VISIBLE=False), SIZE=120 ) diff --git a/reclaimer/hek/defs/item.py b/reclaimer/hek/defs/item.py index 9b4c8d6e..9e9ba123 100644 --- a/reclaimer/hek/defs/item.py +++ b/reclaimer/hek/defs/item.py @@ -10,6 +10,8 @@ from ...common_descs import * from .objs.tag import HekTag from supyr_struct.defs.tag_def import TagDef +# import here so it can be reused in all variants of item +from supyr_struct.util import desc_variant message_index_comment = """MESSAGE INDEX This sets which string from tags\\ui\\hud\\hud_item_messages.unicode_string_list to display.""" diff --git a/reclaimer/hek/defs/jpt_.py b/reclaimer/hek/defs/jpt_.py index f5288668..2cfb879c 100644 --- a/reclaimer/hek/defs/jpt_.py +++ b/reclaimer/hek/defs/jpt_.py @@ -46,6 +46,7 @@ Pad(4), float_zero_to_inf("instantaneous_acceleration"), Pad(8), + SIZE=60 ) camera_shaking = Struct("camera_shaking", diff --git a/reclaimer/hek/defs/lens.py b/reclaimer/hek/defs/lens.py index 15cc1bf6..794b910d 100644 --- a/reclaimer/hek/defs/lens.py +++ b/reclaimer/hek/defs/lens.py @@ -62,6 +62,15 @@ SIZE=128 ) +# for reuse in mcc +bitmaps = Struct("bitmaps", + dependency("bitmap", "bitm"), + Bool16("flags", + "sun", + ), + Pad(78), + SIZE=96 + ) lens_body = Struct("tagdata", float_rad("falloff_angle"), # radians @@ -81,14 +90,7 @@ COMMENT=occlusion_comment ), - Struct("bitmaps", - dependency("bitmap", "bitm"), - Bool16("flags", - "sun", - ), - Pad(78), - ), - + bitmaps, Struct("corona_rotation", SEnum16("function", "none", diff --git a/reclaimer/hek/defs/mach.py b/reclaimer/hek/defs/mach.py index c532f018..184fc548 100644 --- a/reclaimer/hek/defs/mach.py +++ b/reclaimer/hek/defs/mach.py @@ -10,15 +10,8 @@ from .obje import * from .devi import * from .objs.mach import MachTag -from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant - -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(7)) - ) +obje_attrs = obje_attrs_variant(obje_attrs, "mach") mach_attrs = Struct("mach_attrs", SEnum16('type', 'door', diff --git a/reclaimer/hek/defs/obje.py b/reclaimer/hek/defs/obje.py index 9389026d..82d8fe5d 100644 --- a/reclaimer/hek/defs/obje.py +++ b/reclaimer/hek/defs/obje.py @@ -10,6 +10,8 @@ from ...common_descs import * from .objs.obje import ObjeTag from supyr_struct.defs.tag_def import TagDef +# import here so it can be reused in all variants of object +from supyr_struct.util import desc_variant def get(): return obje_def diff --git a/reclaimer/hek/defs/objs/bitm.py b/reclaimer/hek/defs/objs/bitm.py index 68767070..a9f0beeb 100644 --- a/reclaimer/hek/defs/objs/bitm.py +++ b/reclaimer/hek/defs/objs/bitm.py @@ -9,7 +9,7 @@ from array import array from reclaimer.constants import TYPE_CUBEMAP, CUBEMAP_PADDING, BITMAP_PADDING,\ - FORMAT_NAME_MAP, TYPE_NAME_MAP, FORMAT_P8_BUMP + FORMAT_NAME_MAP, TYPE_NAME_MAP, FORMAT_P8_BUMP, SWIZZLEABLE_FORMATS from reclaimer.bitmaps.p8_palette import HALO_P8_PALETTE from reclaimer.hek.defs.objs.tag import HekTag @@ -28,10 +28,18 @@ class BitmTag(HekTag): tex_infos = () p8_palette = None + + @property + def swizzleable_formats(self): + return SWIZZLEABLE_FORMATS @property def format_name_map(self): return FORMAT_NAME_MAP + + @property + def pixel_root_definition(self): + return self.definition.subdefs['pixel_root'] def __init__(self, *args, **kwargs): HekTag.__init__(self, *args, **kwargs) @@ -81,26 +89,21 @@ def bitmap_format(self, b_index=0, new_value=None): self.data.tagdata.bitmaps.bitmaps_array[b_index].format.data = new_value def fix_top_format(self): - if len(self.data.tagdata.bitmaps.bitmaps_array) <= 0: - self.data.tagdata.format.data = "color_key_transparency" - - # Why can't get_name get the name of the current option? - pixel_format = self.data.tagdata.bitmaps.bitmaps_array[0].format.get_name( - self.data.tagdata.bitmaps.bitmaps_array[0].format.data) - top_format = "color_key_transparency" - if pixel_format in ("a8", "y8", "ay8", "a8y8"): - top_format = "monochrome" - elif pixel_format in ("r5g6b5", "a1r5g5b5", "a4r4g4b4"): - top_format = "color_16bit" - elif pixel_format in ("x8r8g8b8", "a8r8g8b8", "p8_bump"): - top_format = "color_32bit" - elif pixel_format == "dxt1": - top_format = "color_key_transparency" - elif pixel_format == "dxt3": - top_format = "explicit_alpha" - elif pixel_format == "dxt5": - top_format = "interpolated_alpha" + if self.bitmap_count() > 0: + pixel_format = self.data.tagdata.bitmaps.bitmaps_array[0].format.enum_name + if pixel_format in ("a8", "y8", "ay8", "a8y8"): + top_format = "monochrome" + elif pixel_format in ("r5g6b5", "a1r5g5b5", "a4r4g4b4"): + top_format = "color_16bit" + elif pixel_format in ("x8r8g8b8", "a8r8g8b8", "p8_bump"): + top_format = "color_32bit" + elif pixel_format == "dxt1": + top_format = "color_key_transparency" + elif pixel_format == "dxt3": + top_format = "explicit_alpha" + elif pixel_format == "dxt5": + top_format = "interpolated_alpha" self.data.tagdata.format.set_to(top_format) @@ -421,7 +424,7 @@ def parse_bitmap_blocks(self): tex_infos = self.tex_infos = [] # this is the block that will hold all of the bitmap blocks - root_tex_block = self.definition.subdefs['pixel_root'].build() + root_tex_block = self.pixel_root_definition.build() is_xbox = self.is_xbox_bitmap get_mip_dims = ab.get_mipmap_dimensions @@ -493,3 +496,35 @@ def parse_bitmap_blocks(self): # it's easier to work with bitmaps in one format so # we'll switch the mipmaps from XBOX to PC ordering self.change_sub_bitmap_ordering(False) + + def set_swizzled(self, swizzle): + '''Swizzles or unswizzles all applicable bitmap formats.''' + if ab is None: + raise NotImplementedError("Arbytmap is not loaded. Cannot swizzle.") + + pixel_data = self.data.tagdata.processed_pixel_data.data + bitmaps = self.data.tagdata.bitmaps.bitmaps_array + swizzler = ab.swizzler.Swizzler(mask_type="MORTON") + for i in range(len(bitmaps)): + bitmap = bitmaps[i] + format = self.format_name_map[bitmap.format.data] + if format not in self.swizzleable_formats: + bitmap.flags.swizzled = False + continue + elif bitmap.flags.swizzled == bool(swizzle): + continue + + w, h, d = bitmap.width, bitmap.height, bitmap.depth + faces = 6 if bitmap.type.data == TYPE_CUBEMAP else 1 + mipmaps = bitmap.mipmaps + 1 + + for f in range(faces): + for m in range(mipmaps): + j = m*faces + f + pixel_data[i][j] = swizzler.swizzle_single_array( + pixel_data[i][j], swizzle, 1, + *ab.get_mipmap_dimensions(w, h, d, m), + False + ) + + bitmap.flags.swizzled = swizzle \ No newline at end of file diff --git a/reclaimer/hek/defs/objs/mode.py b/reclaimer/hek/defs/objs/mode.py index f243e373..bf81229f 100644 --- a/reclaimer/hek/defs/objs/mode.py +++ b/reclaimer/hek/defs/objs/mode.py @@ -92,7 +92,7 @@ def calc_internal_data(self): unpack_vert(verts[i: i+vert_size]) for i in range(0, len(verts), vert_size) ] - weight_key, node_0_key, node_1_key = 0, 1, 2 + node_0_key, node_1_key, weight_key = 0, 1, 2 else: # verts aren't packed, so use as-is weight_key = "node_0_weight" diff --git a/reclaimer/hek/defs/objs/scnr.py b/reclaimer/hek/defs/objs/scnr.py index 8183987f..e7a1cd65 100644 --- a/reclaimer/hek/defs/objs/scnr.py +++ b/reclaimer/hek/defs/objs/scnr.py @@ -8,11 +8,30 @@ # import os +import traceback from reclaimer.hek.defs.objs.tag import HekTag +from reclaimer.halo_script.hsc import HSC_IS_SCRIPT_OR_GLOBAL,\ + get_hsc_data_block, clean_script_syntax_nodes class ScnrTag(HekTag): + # used to determine what syntax node block to use when parsing script data + engine = "halo1ce" def calc_internal_data(self): HekTag.calc_internal_data(self) - pass + self.clean_script_syntax_data() + + def clean_script_syntax_data(self): + try: + script_syntax_data = self.data.tagdata.script_syntax_data + script_nodes = get_hsc_data_block( + script_syntax_data.data, self.engine + ) + clean_script_syntax_nodes(script_nodes) + + # replace the sanitized data + script_syntax_data.data = script_nodes.serialize() + except Exception: + print(traceback.format_exc()) + print("Failed to sanitize script syntax data nodes.") \ No newline at end of file diff --git a/reclaimer/hek/defs/plac.py b/reclaimer/hek/defs/plac.py index d9befd91..9c1ad5a5 100644 --- a/reclaimer/hek/defs/plac.py +++ b/reclaimer/hek/defs/plac.py @@ -9,15 +9,8 @@ from .obje import * from .objs.obje import ObjeTag -from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant - -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(10)) - ) +obje_attrs = obje_attrs_variant(obje_attrs, "plac") plac_body = Struct("tagdata", obje_attrs, SIZE=508, diff --git a/reclaimer/hek/defs/proj.py b/reclaimer/hek/defs/proj.py index 01fe916d..1f73c4d3 100644 --- a/reclaimer/hek/defs/proj.py +++ b/reclaimer/hek/defs/proj.py @@ -9,13 +9,8 @@ from .obje import * from .objs.obje import ObjeTag -from supyr_struct.util import desc_variant -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(5)) - ) +obje_attrs = obje_attrs_variant(obje_attrs, "proj") responses = ( "disappear", diff --git a/reclaimer/hek/defs/sbsp.py b/reclaimer/hek/defs/sbsp.py index c8afe74e..b797e71e 100644 --- a/reclaimer/hek/defs/sbsp.py +++ b/reclaimer/hek/defs/sbsp.py @@ -258,6 +258,7 @@ # almost certain this is padding, though a value in the third # and fourth bytes is non-zero in meta, but not in a tag, so idk. + # also this is a bunch of floats in stubbs, so there's also that. Pad(24), reflexive("predicted_resources", predicted_resource, 1024, VISIBLE=False), diff --git a/reclaimer/hek/defs/scen.py b/reclaimer/hek/defs/scen.py index 24238809..87c4513f 100644 --- a/reclaimer/hek/defs/scen.py +++ b/reclaimer/hek/defs/scen.py @@ -9,15 +9,8 @@ from .obje import * from .objs.obje import ObjeTag -from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant - -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(6)) - ) +obje_attrs = obje_attrs_variant(obje_attrs, "scen") scen_body = Struct("tagdata", obje_attrs, SIZE=508, diff --git a/reclaimer/hek/defs/scnr.py b/reclaimer/hek/defs/scnr.py index 4723f518..ec736ee2 100644 --- a/reclaimer/hek/defs/scnr.py +++ b/reclaimer/hek/defs/scnr.py @@ -8,7 +8,7 @@ # from ...common_descs import * -from .objs.tag import HekTag +from .objs.scnr import ScnrTag from supyr_struct.defs.tag_def import TagDef from supyr_struct.util import desc_variant @@ -1077,5 +1077,5 @@ def get(): blam_header('scnr', 2), scnr_body, - ext=".scenario", endian=">", tag_cls=HekTag + ext=".scenario", endian=">", tag_cls=ScnrTag ) diff --git a/reclaimer/hek/defs/senv.py b/reclaimer/hek/defs/senv.py index 42aabecb..476c18d7 100644 --- a/reclaimer/hek/defs/senv.py +++ b/reclaimer/hek/defs/senv.py @@ -88,6 +88,7 @@ "blended_base_specular", COMMENT=environment_shader_type_comment ), + SIZE=4 ) diffuse = Struct("diffuse", diff --git a/reclaimer/hek/defs/soso.py b/reclaimer/hek/defs/soso.py index e2b03a1a..073ed047 100644 --- a/reclaimer/hek/defs/soso.py +++ b/reclaimer/hek/defs/soso.py @@ -61,7 +61,7 @@ ), Pad(14), Float("translucency"), - COMMENT=soso_comment + SIZE=20, COMMENT=soso_comment ) self_illumination = Struct("self_illumination", @@ -119,39 +119,29 @@ ) soso_attrs = Struct("soso_attrs", - #Model Shader Properties model_shader, Pad(16), - #Color-Change SEnum16("color_change_source", *function_names, COMMENT=cc_comment), Pad(30), - #Self-Illumination self_illumination, Pad(12), - #Diffuse, Multipurpose, and Detail Maps maps, # this padding is the reflexive for the OS shader model extension Pad(12), - #Texture Scrolling Animation texture_scrolling, Pad(8), - #Reflection Properties reflection, - # NOTE: this isn't actually used in pc, but may be usable on xbox. - Float("reflection_bump_scale", VISIBLE=False), - dependency("reflection_bump_map", "bitm", VISIBLE=False, COMMENT=""" - DO NOT USE THIS UNLESS YOU ARE MODDING XBOX HALO 1. - - It has not been tested to confirm if it works on - Xbox, but it certainly doesnt work on PC or later. - """), + # NOTE: these aren't actually used, but they were at one point in + # development. keeping these for documentation purposes. + Pad(4), #Float("reflection_bump_scale", VISIBLE=False), + Pad(16), #dependency("reflection_bump_map", "bitm", VISIBLE=False), SIZE=400 ) diff --git a/reclaimer/hek/defs/ssce.py b/reclaimer/hek/defs/ssce.py index 78591bd8..052a129e 100644 --- a/reclaimer/hek/defs/ssce.py +++ b/reclaimer/hek/defs/ssce.py @@ -9,15 +9,8 @@ from .obje import * from .objs.obje import ObjeTag -from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant - -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(11)) - ) +obje_attrs = obje_attrs_variant(obje_attrs, "ssce") ssce_body = Struct("tagdata", obje_attrs, SIZE=508, diff --git a/reclaimer/hek/defs/unhi.py b/reclaimer/hek/defs/unhi.py index 097c352a..0830f039 100644 --- a/reclaimer/hek/defs/unhi.py +++ b/reclaimer/hek/defs/unhi.py @@ -126,8 +126,11 @@ ) auxilary_meter = Struct("auxilary_meter", + SEnum16("type", + "integrated_light", + VISIBLE=False + ), Pad(18), - SEnum16("type", "integrated_light", VISIBLE=False), Struct("background", INCLUDE=hud_background), QStruct("anchor_offset", diff --git a/reclaimer/hek/defs/unit.py b/reclaimer/hek/defs/unit.py index 453c96cb..aa73c3f0 100644 --- a/reclaimer/hek/defs/unit.py +++ b/reclaimer/hek/defs/unit.py @@ -10,6 +10,8 @@ from ...common_descs import * from .objs.tag import HekTag from supyr_struct.defs.tag_def import TagDef +# import here so it can be reused in all variants of unit +from supyr_struct.util import desc_variant def get(): return unit_def diff --git a/reclaimer/hek/defs/weap.py b/reclaimer/hek/defs/weap.py index 7d2896d9..4bb2814b 100644 --- a/reclaimer/hek/defs/weap.py +++ b/reclaimer/hek/defs/weap.py @@ -10,13 +10,8 @@ from .obje import * from .item import * from .objs.weap import WeapTag -from supyr_struct.util import desc_variant -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(2)) - ) +obje_attrs = obje_attrs_variant(obje_attrs, "weap") magazine_item = Struct("magazine_item", SInt16("rounds"), diff --git a/reclaimer/mcc_hek/defs/actv.py b/reclaimer/mcc_hek/defs/actv.py index 74eab55e..ac9bc2a0 100644 --- a/reclaimer/mcc_hek/defs/actv.py +++ b/reclaimer/mcc_hek/defs/actv.py @@ -8,19 +8,18 @@ # from ...hek.defs.actv import * -from supyr_struct.util import desc_variant metagame_scoring = Struct("metagame_scoring", SEnum16("metagame_type", *actor_types_mcc), SEnum16("metagame_class", *actor_classes_mcc), - ORIENT="H", COMMENT="Used to determine score in MCC", + SIZE=4, ORIENT="H", COMMENT="Used to determine score in MCC", ) actv_grenades = desc_variant(actv_grenades, - ("grenade_type", SEnum16("grenade_type", *grenade_types_mcc)), + SEnum16("grenade_type", *grenade_types_mcc) ) actv_body = desc_variant(actv_body, - ("grenades", actv_grenades), + actv_grenades, ("pad_4", metagame_scoring), ) diff --git a/reclaimer/mcc_hek/defs/antr.py b/reclaimer/mcc_hek/defs/antr.py index 1daed4eb..045cedaf 100644 --- a/reclaimer/mcc_hek/defs/antr.py +++ b/reclaimer/mcc_hek/defs/antr.py @@ -8,32 +8,31 @@ # from ...hek.defs.antr import * -from supyr_struct.util import desc_variant unit_weapon_desc = desc_variant(unit_weapon_desc, - ("ik_points", reflexive("ik_points", ik_point_desc, 8, DYN_NAME_PATH=".marker")), - ("weapon_types", reflexive("weapon_types", weapon_types_desc, 8, DYN_NAME_PATH=".label")), + reflexive("ik_points", ik_point_desc, 8, DYN_NAME_PATH=".marker"), + reflexive("weapon_types", weapon_types_desc, 8, DYN_NAME_PATH=".label"), ) unit_desc = desc_variant(unit_desc, - ("ik_points", reflexive("ik_points", ik_point_desc, 8, DYN_NAME_PATH=".marker")), - ("weapons", reflexive("weapons", unit_weapon_desc, 64, DYN_NAME_PATH=".name")), + reflexive("ik_points", ik_point_desc, 8, DYN_NAME_PATH=".marker"), + reflexive("weapons", unit_weapon_desc, 64, DYN_NAME_PATH=".name"), ) vehicle_desc = desc_variant(vehicle_desc, - ("suspension_animations", reflexive("suspension_animations", suspension_desc, 32)), + reflexive("suspension_animations", suspension_desc, 32), ) fp_animation_desc = desc_variant(fp_animation_desc, - ("animations", reflexive("animations", anim_enum_desc, 30, *fp_animation_names_mcc)), + reflexive("animations", anim_enum_desc, 30, *fp_animation_names_mcc), ) animation_desc = desc_variant(animation_desc, - ("frame_data", rawdata_ref("frame_data", max_size=4194304)), + rawdata_ref("frame_data", max_size=4194304), ) antr_body = desc_variant(antr_body, - ("units", reflexive("units", unit_desc, 2048, DYN_NAME_PATH=".label")), - ("vehicles", reflexive("vehicles", vehicle_desc, 1)), - ("fp_animations", reflexive("fp_animations", fp_animation_desc, 1)), - ("sound_references", reflexive("sound_references", sound_reference_desc, 512, DYN_NAME_PATH=".sound.filepath")), - ("animations", reflexive("animations", animation_desc, 2048, DYN_NAME_PATH=".name")), + reflexive("units", unit_desc, 2048, DYN_NAME_PATH=".label"), + reflexive("vehicles", vehicle_desc, 1), + reflexive("fp_animations", fp_animation_desc, 1), + reflexive("sound_references", sound_reference_desc, 512, DYN_NAME_PATH=".sound.filepath"), + reflexive("animations", animation_desc, 2048, DYN_NAME_PATH=".name"), ) def get(): diff --git a/reclaimer/mcc_hek/defs/bipd.py b/reclaimer/mcc_hek/defs/bipd.py index 0f7df180..e66fa11e 100644 --- a/reclaimer/mcc_hek/defs/bipd.py +++ b/reclaimer/mcc_hek/defs/bipd.py @@ -8,17 +8,10 @@ # from ...hek.defs.bipd import * -from supyr_struct.util import desc_variant - -#import and use the mcc obje and unit attrs from .obje import * from .unit import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(0)) - ) +obje_attrs = obje_attrs_variant(obje_attrs, "bipd") bipd_body = Struct("tagdata", obje_attrs, diff --git a/reclaimer/mcc_hek/defs/bitm.py b/reclaimer/mcc_hek/defs/bitm.py index f7b136aa..2d674a07 100644 --- a/reclaimer/mcc_hek/defs/bitm.py +++ b/reclaimer/mcc_hek/defs/bitm.py @@ -9,7 +9,6 @@ from ...hek.defs.bitm import * from .objs.bitm import MccBitmTag -from supyr_struct.util import desc_variant format_comment_parts = format_comment.split("NOTE: ", 1) format_comment = "".join(( @@ -53,10 +52,7 @@ {NAME: "data_in_resource_map", VISIBLE: False}, {NAME: "environment", VISIBLE: False}, ) -bitmap = desc_variant(bitmap, - ("format", bitmap_format), - ("flags", bitmap_flags), - ) +bitmap = desc_variant(bitmap, bitmap_format, bitmap_flags) body_format = SEnum16("format", "color_key_transparency", "explicit_alpha", @@ -86,15 +82,15 @@ {NAME: "x1024", VALUE: 5, GUI_NAME: "1024x1024"}, ), UInt16("sprite_budget_count"), - COMMENT=sprite_processing_comment + SIZE=4, COMMENT=sprite_processing_comment ) bitm_body = desc_variant(bitm_body, - ("format", body_format), - ("flags", body_flags), - ("sprite_processing", sprite_processing), - ("compressed_color_plate_data", rawdata_ref("compressed_color_plate_data", max_size=1073741824)), - ("processed_pixel_data", rawdata_ref("processed_pixel_data", max_size=1073741824)), - ("bitmaps", reflexive("bitmaps", bitmap, 65536, IGNORE_SAFE_MODE=True)), + body_format, + body_flags, + sprite_processing, + rawdata_ref("compressed_color_plate_data", max_size=1073741824), + rawdata_ref("processed_pixel_data", max_size=1073741824), + reflexive("bitmaps", bitmap, 65536, IGNORE_SAFE_MODE=True), ) def get(): diff --git a/reclaimer/mcc_hek/defs/cdmg.py b/reclaimer/mcc_hek/defs/cdmg.py index 439eae53..a7b7814f 100644 --- a/reclaimer/mcc_hek/defs/cdmg.py +++ b/reclaimer/mcc_hek/defs/cdmg.py @@ -8,7 +8,6 @@ # from ...hek.defs.cdmg import * -from supyr_struct.util import desc_variant damage_flags = Bool32("flags", "does_not_hurt_owner", @@ -32,16 +31,14 @@ ) damage = desc_variant(damage, - ("flags", damage_flags), - ("instantaneous_acceleration", QStruct("instantaneous_acceleration", INCLUDE=ijk_float, - SIDETIP="[-inf,+inf]", ORIENT="h" - )), + damage_flags, + QStruct("instantaneous_acceleration", INCLUDE=ijk_float, SIDETIP="[-inf,+inf]", ORIENT="h"), ("pad_13", Pad(0)), + # we're doing some weird stuff to make this work, so we're turning off verify + verify=False ) -cdmg_body = desc_variant(cdmg_body, - ("damage", damage), - ) +cdmg_body = desc_variant(cdmg_body, damage) def get(): return cdmg_def diff --git a/reclaimer/mcc_hek/defs/coll.py b/reclaimer/mcc_hek/defs/coll.py index 0cfc8a83..0adb7301 100644 --- a/reclaimer/mcc_hek/defs/coll.py +++ b/reclaimer/mcc_hek/defs/coll.py @@ -8,13 +8,12 @@ # from ...hek.defs.coll import * -from supyr_struct.util import desc_variant coll_body = desc_variant(coll_body, - ("pathfinding_spheres", reflexive("pathfinding_spheres", pathfinding_sphere, 256)), + reflexive("pathfinding_spheres", pathfinding_sphere, 256), ) fast_coll_body = desc_variant(coll_body, - ("nodes", reflexive("nodes", fast_node, 64, DYN_NAME_PATH='.name')), + reflexive("nodes", fast_node, 64, DYN_NAME_PATH='.name'), ) def get(): diff --git a/reclaimer/mcc_hek/defs/ctrl.py b/reclaimer/mcc_hek/defs/ctrl.py index 2c0ffeda..9a8a7301 100644 --- a/reclaimer/mcc_hek/defs/ctrl.py +++ b/reclaimer/mcc_hek/defs/ctrl.py @@ -11,12 +11,7 @@ from .obje import * from .devi import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(8)) - ) - +obje_attrs = obje_attrs_variant(obje_attrs, "ctrl") ctrl_body = Struct("tagdata", obje_attrs, devi_attrs, diff --git a/reclaimer/mcc_hek/defs/deca.py b/reclaimer/mcc_hek/defs/deca.py index 988271e4..1c5983c7 100644 --- a/reclaimer/mcc_hek/defs/deca.py +++ b/reclaimer/mcc_hek/defs/deca.py @@ -8,7 +8,6 @@ # from ...hek.defs.deca import * -from supyr_struct.util import desc_variant flags = Bool16("flags", "geometry_inherited_by_next_decal_in_chain", @@ -24,9 +23,7 @@ COMMENT=decal_comment ) -deca_body = desc_variant(deca_body, - ("flags", flags) - ) +deca_body = desc_variant(deca_body, flags) def get(): return deca_def diff --git a/reclaimer/mcc_hek/defs/effe.py b/reclaimer/mcc_hek/defs/effe.py index cdc2d3f7..988adc3c 100644 --- a/reclaimer/mcc_hek/defs/effe.py +++ b/reclaimer/mcc_hek/defs/effe.py @@ -8,7 +8,6 @@ # from ...hek.defs.effe import * -from supyr_struct.util import desc_variant flags = Bool32("flags", {NAME: "deleted_when_inactive", GUI_NAME: "deleted when attachment deactivates"}, @@ -17,9 +16,7 @@ "disabled_in_remastered_by_blood_setting" ) -effe_body = desc_variant(effe_body, - ("flags", flags), - ) +effe_body = desc_variant(effe_body, flags) def get(): return effe_def diff --git a/reclaimer/mcc_hek/defs/eqip.py b/reclaimer/mcc_hek/defs/eqip.py index 41c0f582..0482b9ff 100644 --- a/reclaimer/mcc_hek/defs/eqip.py +++ b/reclaimer/mcc_hek/defs/eqip.py @@ -11,15 +11,11 @@ from .obje import * from .item import * -eqip_attrs = desc_variant(eqip_attrs, - ("grenade_type", SEnum16("grenade_type", *grenade_types_mcc)), +eqip_attrs = desc_variant(eqip_attrs, + SEnum16("grenade_type", *grenade_types_mcc) ) -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(3)) - ) +obje_attrs = obje_attrs_variant(obje_attrs, "eqip") eqip_body = Struct("tagdata", obje_attrs, diff --git a/reclaimer/mcc_hek/defs/font.py b/reclaimer/mcc_hek/defs/font.py index 2492d68a..8ab600d4 100644 --- a/reclaimer/mcc_hek/defs/font.py +++ b/reclaimer/mcc_hek/defs/font.py @@ -8,14 +8,11 @@ # from ...hek.defs.font import * -from supyr_struct.util import desc_variant flags = Bool32("flags", "never_override_with_remastered_font_under_mcc", ) -font_body = desc_variant(font_body, - ("flags", flags) - ) +font_body = desc_variant(font_body, flags) def get(): return font_def diff --git a/reclaimer/mcc_hek/defs/garb.py b/reclaimer/mcc_hek/defs/garb.py index 9bb87f3e..53b8b965 100644 --- a/reclaimer/mcc_hek/defs/garb.py +++ b/reclaimer/mcc_hek/defs/garb.py @@ -11,12 +11,7 @@ from .obje import * from .item import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(4)) - ) - +obje_attrs = obje_attrs_variant(obje_attrs, "garb") garb_body = Struct("tagdata", obje_attrs, item_attrs, diff --git a/reclaimer/mcc_hek/defs/grhi.py b/reclaimer/mcc_hek/defs/grhi.py index 5b4eb451..963427bb 100644 --- a/reclaimer/mcc_hek/defs/grhi.py +++ b/reclaimer/mcc_hek/defs/grhi.py @@ -8,14 +8,11 @@ # from ...hek.defs.grhi import * -from supyr_struct.util import desc_variant # NOTE: used by unhi and wphi mcc_hud_anchor = SEnum16("anchor", *hud_anchors_mcc) -grhi_body = desc_variant(grhi_body, - ("anchor", mcc_hud_anchor), - ) +grhi_body = desc_variant(grhi_body, mcc_hud_anchor) def get(): return grhi_def diff --git a/reclaimer/mcc_hek/defs/hudg.py b/reclaimer/mcc_hek/defs/hudg.py index 7993a3f1..47f129c9 100644 --- a/reclaimer/mcc_hek/defs/hudg.py +++ b/reclaimer/mcc_hek/defs/hudg.py @@ -8,7 +8,6 @@ # from ...hek.defs.hudg import * -from supyr_struct.util import desc_variant targets = Struct("targets", dependency("target_bitmap", "bitm"), @@ -38,14 +37,11 @@ reflexive("targets", targets, 26, DYN_NAME_PATH='.target_bitmap.filepath'), SIZE=28 ) -remaps = reflexive("remaps", remap, 32, DYN_NAME_PATH='.original_bitmap.filepath') misc_hud_crap = desc_variant(misc_hud_crap, - ("unknown", remaps) - ) -hudg_body = desc_variant(hudg_body, - ("misc_hud_crap", misc_hud_crap) + ("unknown0", reflexive("remaps", remap, 32, DYN_NAME_PATH='.original_bitmap.filepath')) ) +hudg_body = desc_variant(hudg_body, misc_hud_crap) def get(): return hudg_def diff --git a/reclaimer/mcc_hek/defs/jpt_.py b/reclaimer/mcc_hek/defs/jpt_.py index 9ce55dc2..509cfb84 100644 --- a/reclaimer/mcc_hek/defs/jpt_.py +++ b/reclaimer/mcc_hek/defs/jpt_.py @@ -9,19 +9,16 @@ from ...hek.defs.jpt_ import * from .cdmg import damage_flags -from supyr_struct.util import desc_variant damage = desc_variant(damage, - ("flags", damage_flags), - ("instantaneous_acceleration", QStruct("instantaneous_acceleration", INCLUDE=ijk_float, - SIDETIP="[-inf,+inf]", ORIENT="h" - )), + damage_flags, + QStruct("instantaneous_acceleration", INCLUDE=ijk_float, SIDETIP="[-inf,+inf]", ORIENT="h"), ("pad_13", Pad(0)), + # we're doing some weird stuff to make this work, so we're turning off verify + verify=False ) -jpt__body = desc_variant(jpt__body, - ("damage", damage), - ) +jpt__body = desc_variant(jpt__body, damage) def get(): return jpt__def diff --git a/reclaimer/mcc_hek/defs/lens.py b/reclaimer/mcc_hek/defs/lens.py index 3ed8027c..300c08e3 100644 --- a/reclaimer/mcc_hek/defs/lens.py +++ b/reclaimer/mcc_hek/defs/lens.py @@ -8,10 +8,8 @@ # from ...hek.defs.lens import * -from supyr_struct.util import desc_variant -bitmaps = Struct("bitmaps", - dependency("bitmap", "bitm"), +bitmaps = desc_variant(bitmaps, Bool16("flags", "sun", "no_occlusion_test", @@ -20,13 +18,9 @@ "fade_in_more_quickly", "fade_out_more_quickly", "scale_by_marker", - ), - Pad(78), - ) - -lens_body = desc_variant(lens_body, - ("bitmaps", bitmaps), + ) ) +lens_body = desc_variant(lens_body, bitmaps) def get(): return lens_def diff --git a/reclaimer/mcc_hek/defs/lifi.py b/reclaimer/mcc_hek/defs/lifi.py index 45d88732..a8261754 100644 --- a/reclaimer/mcc_hek/defs/lifi.py +++ b/reclaimer/mcc_hek/defs/lifi.py @@ -11,12 +11,7 @@ from .obje import * from .devi import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(9)) - ) - +obje_attrs = obje_attrs_variant(obje_attrs, "lifi") lifi_body = Struct("tagdata", obje_attrs, devi_attrs, diff --git a/reclaimer/mcc_hek/defs/lsnd.py b/reclaimer/mcc_hek/defs/lsnd.py index 6869b9fb..aafc7ba1 100644 --- a/reclaimer/mcc_hek/defs/lsnd.py +++ b/reclaimer/mcc_hek/defs/lsnd.py @@ -8,7 +8,6 @@ # from ...hek.defs.lsnd import * -from supyr_struct.util import desc_variant lsnd_flags = Bool32("flags", "deafening_to_ai", @@ -17,9 +16,7 @@ "siege_of_the_madrigal", ) -lsnd_body = desc_variant(lsnd_body, - ("flags", lsnd_flags), - ) +lsnd_body = desc_variant(lsnd_body, lsnd_flags) def get(): return lsnd_def diff --git a/reclaimer/mcc_hek/defs/mach.py b/reclaimer/mcc_hek/defs/mach.py index 3c973730..9d440179 100644 --- a/reclaimer/mcc_hek/defs/mach.py +++ b/reclaimer/mcc_hek/defs/mach.py @@ -11,12 +11,7 @@ from .obje import * from .devi import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(7)) - ) - +obje_attrs = obje_attrs_variant(obje_attrs, "mach") mach_body = Struct("tagdata", obje_attrs, devi_attrs, diff --git a/reclaimer/mcc_hek/defs/matg.py b/reclaimer/mcc_hek/defs/matg.py index c4d3f593..2e36286e 100644 --- a/reclaimer/mcc_hek/defs/matg.py +++ b/reclaimer/mcc_hek/defs/matg.py @@ -8,10 +8,9 @@ # from ...hek.defs.matg import * -from supyr_struct.util import desc_variant -matg_body = desc_variant(matg_body, - ("grenades", reflexive("grenades", grenade, 4, *grenade_types_mcc)), +matg_body = desc_variant(matg_body, + reflexive("grenades", grenade, 4, *grenade_types_mcc), ) def get(): diff --git a/reclaimer/mcc_hek/defs/obje.py b/reclaimer/mcc_hek/defs/obje.py index 75a03a27..78890b94 100644 --- a/reclaimer/mcc_hek/defs/obje.py +++ b/reclaimer/mcc_hek/defs/obje.py @@ -8,7 +8,6 @@ # from ...hek.defs.obje import * -from supyr_struct.util import desc_variant obje_flags = Bool16('flags', 'does_not_cast_shadow', @@ -21,10 +20,8 @@ {NAME: 'xbox_unknown_bit_8', VALUE: 1<<8, VISIBLE: False}, {NAME: 'xbox_unknown_bit_11', VALUE: 1<<11, VISIBLE: False}, ) -obje_attrs = desc_variant(obje_attrs, - ("flags", obje_flags), - ) +obje_attrs = desc_variant(obje_attrs, obje_flags) obje_body = Struct('tagdata', obje_attrs, SIZE=380 diff --git a/reclaimer/mcc_hek/defs/objs/bitm.py b/reclaimer/mcc_hek/defs/objs/bitm.py index 80e9d4e5..48f643c5 100644 --- a/reclaimer/mcc_hek/defs/objs/bitm.py +++ b/reclaimer/mcc_hek/defs/objs/bitm.py @@ -12,4 +12,16 @@ class MccBitmTag(BitmTag): @property def format_name_map(self): - return MCC_FORMAT_NAME_MAP \ No newline at end of file + return MCC_FORMAT_NAME_MAP + + def fix_top_format(self): + top_format = None + if self.bitmap_count() > 0: + pixel_format = self.data.tagdata.bitmaps.bitmaps_array[0].format.enum_name + if pixel_format == "bc7": + top_format = "high_quality_compression" + + if top_format is None: + return super().fix_top_format() + + self.data.tagdata.format.set_to(top_format) \ No newline at end of file diff --git a/reclaimer/mcc_hek/defs/plac.py b/reclaimer/mcc_hek/defs/plac.py index b1ed51e5..4d6bb051 100644 --- a/reclaimer/mcc_hek/defs/plac.py +++ b/reclaimer/mcc_hek/defs/plac.py @@ -10,12 +10,7 @@ from ...hek.defs.plac import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(10)) - ) - +obje_attrs = obje_attrs_variant(obje_attrs, "plac") plac_body = Struct("tagdata", obje_attrs, diff --git a/reclaimer/mcc_hek/defs/proj.py b/reclaimer/mcc_hek/defs/proj.py index 2ef9e71f..499bbe0c 100644 --- a/reclaimer/mcc_hek/defs/proj.py +++ b/reclaimer/mcc_hek/defs/proj.py @@ -9,30 +9,19 @@ from ...hek.defs.proj import * from .obje import * -from supyr_struct.util import desc_variant potential_response_flags = Bool16("flags", "only_against_units", "never_against_units" ) -potential_response = desc_variant(potential_response, - ("flags", potential_response_flags) - ) -material_response = desc_variant(material_response, - ("potential_response", potential_response) - ) +potential_response = desc_variant(potential_response, potential_response_flags) +material_response = desc_variant(material_response, potential_response) material_responses = reflexive("material_responses", material_response, len(materials_list), *materials_list ) -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(5)) - ) -proj_attrs = desc_variant(proj_attrs, - ("material_responses", material_responses) - ) +obje_attrs = obje_attrs_variant(obje_attrs, "proj") +proj_attrs = desc_variant(proj_attrs, material_responses) proj_body = Struct("tagdata", obje_attrs, diff --git a/reclaimer/mcc_hek/defs/scen.py b/reclaimer/mcc_hek/defs/scen.py index 68f69be8..15bb80e6 100644 --- a/reclaimer/mcc_hek/defs/scen.py +++ b/reclaimer/mcc_hek/defs/scen.py @@ -10,15 +10,9 @@ from ...hek.defs.scen import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(6)) - ) - +obje_attrs = obje_attrs_variant(obje_attrs, "scen") scen_body = Struct("tagdata", obje_attrs, - SIZE=508, ) diff --git a/reclaimer/mcc_hek/defs/scex.py b/reclaimer/mcc_hek/defs/scex.py index aea24228..6a771580 100644 --- a/reclaimer/mcc_hek/defs/scex.py +++ b/reclaimer/mcc_hek/defs/scex.py @@ -9,12 +9,8 @@ from ...hek.defs.scex import * from .schi import * -from supyr_struct.util import desc_variant - -scex_attrs = desc_variant(scex_attrs, - ("extra_flags", extra_flags) - ) +scex_attrs = desc_variant(scex_attrs, extra_flags) scex_body = Struct("tagdata", shdr_attrs, scex_attrs, diff --git a/reclaimer/mcc_hek/defs/schi.py b/reclaimer/mcc_hek/defs/schi.py index d0e3d473..15b08bd8 100644 --- a/reclaimer/mcc_hek/defs/schi.py +++ b/reclaimer/mcc_hek/defs/schi.py @@ -9,16 +9,13 @@ from ...hek.defs.schi import * from .shdr import * -from supyr_struct.util import desc_variant extra_flags = Bool32("extra_flags", "dont_fade_active_camouflage", "numeric_countdown_timer", "custom_edition_blending", ) -schi_attrs = desc_variant(schi_attrs, - ("extra_flags", extra_flags) - ) +schi_attrs = desc_variant(schi_attrs, extra_flags) schi_body = Struct("tagdata", shdr_attrs, diff --git a/reclaimer/mcc_hek/defs/scnr.py b/reclaimer/mcc_hek/defs/scnr.py index 5e09edcc..7c246c72 100644 --- a/reclaimer/mcc_hek/defs/scnr.py +++ b/reclaimer/mcc_hek/defs/scnr.py @@ -8,7 +8,6 @@ # from ...hek.defs.scnr import * -from supyr_struct.util import desc_variant object_ref_flags = Bool16('not_placed', "automatically", @@ -24,9 +23,10 @@ "type2_grenades_only", "type3_grenades_only", ) -parameters = Struct("parameters", +parameter = Struct("parameter", ascii_str32("name", EDITABLE=False), SEnum16("return_type", *script_object_types, EDITABLE=False), + Pad(2), SIZE=36, ) cutscene_title_flags = Bool32("flags", @@ -66,13 +66,13 @@ ("team_index", SInt16("usage_id")), ) starting_equipment = desc_variant(starting_equipment, - ("flags", starting_equipment_flags), + starting_equipment_flags ) halo_script = desc_variant(halo_script, - ("pad_6", reflexive("parameters", parameters, 16)) + ("pad_6", reflexive("parameters", parameter, 16)) ) source_file = desc_variant(source_file, - ("source", rawdata_ref("source", max_size=1048576, widget=HaloScriptSourceFrame)) + rawdata_ref("source", max_size=1048576, widget=HaloScriptSourceFrame), ) cutscene_title = desc_variant(cutscene_title, ("pad_8", cutscene_title_flags), @@ -86,37 +86,37 @@ ) scnr_body = desc_variant(scnr_body, - ("flags", scnr_flags), + scnr_flags, ("pad_13", reflexive("scavenger_hunt_objects", scavenger_hunt_objects, 16)), - ("object_names", reflexive("object_names", object_name, 640, DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True)), - ("sceneries", reflexive("sceneries", scenery, 2000, IGNORE_SAFE_MODE=True)), - ("bipeds", reflexive("bipeds", biped, 128, IGNORE_SAFE_MODE=True)), - ("vehicles", reflexive("vehicles", vehicle, 256, IGNORE_SAFE_MODE=True)), - ("equipments", reflexive("equipments", equipment, 256, IGNORE_SAFE_MODE=True)), - ("weapons", reflexive("weapons", weapon, 128, IGNORE_SAFE_MODE=True)), - ("machines", reflexive("machines", machine, 400, IGNORE_SAFE_MODE=True)), - ("controls", reflexive("controls", control, 100, IGNORE_SAFE_MODE=True)), - ("light_fixtures", reflexive("light_fixtures", light_fixture, 500, IGNORE_SAFE_MODE=True)), - ("sound_sceneries", reflexive("sound_sceneries", sound_scenery, 256, IGNORE_SAFE_MODE=True)), - ("sceneries_palette", reflexive("sceneries_palette", scenery_swatch, 256, DYN_NAME_PATH='.name.filepath')), - ("bipeds_palette", reflexive("bipeds_palette", biped_swatch, 256, DYN_NAME_PATH='.name.filepath')), - ("vehicles_palette", reflexive("vehicles_palette", vehicle_swatch, 256, DYN_NAME_PATH='.name.filepath')), - ("equipments_palette", reflexive("equipments_palette", equipment_swatch, 256, DYN_NAME_PATH='.name.filepath')), - ("weapons_palette", reflexive("weapons_palette", weapon_swatch, 256, DYN_NAME_PATH='.name.filepath')), - ("machines_palette", reflexive("machines_palette", machine_swatch, 256, DYN_NAME_PATH='.name.filepath')), - ("controls_palette", reflexive("controls_palette", control_swatch, 256, DYN_NAME_PATH='.name.filepath')), - ("light_fixtures_palette", reflexive("light_fixtures_palette", light_fixture_swatch, 256, DYN_NAME_PATH='.name.filepath')), - ("sound_sceneries_palette", reflexive("sound_sceneries_palette", sound_scenery_swatch, 256, DYN_NAME_PATH='.name.filepath')), - ("player_starting_profiles", reflexive("player_starting_profiles", player_starting_profile, 256, DYN_NAME_PATH='.name')), - ("netgame_equipments", reflexive("netgame_equipments", netgame_equipment, 200, DYN_NAME_PATH='.item_collection.filepath')), - ("starting_equipments", reflexive("starting_equipments", starting_equipment, 200)), - ("script_syntax_data", rawdata_ref("script_syntax_data", max_size=655396, IGNORE_SAFE_MODE=True)), - ("script_string_data", rawdata_ref("script_string_data", max_size=819200, IGNORE_SAFE_MODE=True)), - ("scripts", reflexive("scripts", halo_script, 1024, DYN_NAME_PATH='.name')), - ("globals", reflexive("globals", halo_global, 512, DYN_NAME_PATH='.name')), - ("references", reflexive("references", reference, 512, DYN_NAME_PATH='.reference.filepath')), - ("source_files", reflexive("source_files", source_file, 16, DYN_NAME_PATH='.source_name')), - ("cutscene_titles", reflexive("cutscene_titles", cutscene_title, 64, DYN_NAME_PATH='.name')), + reflexive("object_names", object_name, 640, DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), + reflexive("sceneries", scenery, 2000, IGNORE_SAFE_MODE=True), + reflexive("bipeds", biped, 128, IGNORE_SAFE_MODE=True), + reflexive("vehicles", vehicle, 256, IGNORE_SAFE_MODE=True), + reflexive("equipments", equipment, 256, IGNORE_SAFE_MODE=True), + reflexive("weapons", weapon, 128, IGNORE_SAFE_MODE=True), + reflexive("machines", machine, 400, IGNORE_SAFE_MODE=True), + reflexive("controls", control, 100, IGNORE_SAFE_MODE=True), + reflexive("light_fixtures", light_fixture, 500, IGNORE_SAFE_MODE=True), + reflexive("sound_sceneries", sound_scenery, 256, IGNORE_SAFE_MODE=True), + reflexive("sceneries_palette", scenery_swatch, 256, DYN_NAME_PATH='.name.filepath'), + reflexive("bipeds_palette", biped_swatch, 256, DYN_NAME_PATH='.name.filepath'), + reflexive("vehicles_palette", vehicle_swatch, 256, DYN_NAME_PATH='.name.filepath'), + reflexive("equipments_palette", equipment_swatch, 256, DYN_NAME_PATH='.name.filepath'), + reflexive("weapons_palette", weapon_swatch, 256, DYN_NAME_PATH='.name.filepath'), + reflexive("machines_palette", machine_swatch, 256, DYN_NAME_PATH='.name.filepath'), + reflexive("controls_palette", control_swatch, 256, DYN_NAME_PATH='.name.filepath'), + reflexive("light_fixtures_palette", light_fixture_swatch, 256, DYN_NAME_PATH='.name.filepath'), + reflexive("sound_sceneries_palette", sound_scenery_swatch, 256, DYN_NAME_PATH='.name.filepath'), + reflexive("player_starting_profiles", player_starting_profile, 256, DYN_NAME_PATH='.name'), + reflexive("netgame_equipments", netgame_equipment, 200, DYN_NAME_PATH='.item_collection.filepath'), + reflexive("starting_equipments", starting_equipment, 200), + rawdata_ref("script_syntax_data", max_size=655396, IGNORE_SAFE_MODE=True), + rawdata_ref("script_string_data", max_size=819200, IGNORE_SAFE_MODE=True), + reflexive("scripts", halo_script, 1024, DYN_NAME_PATH='.name'), + reflexive("globals", halo_global, 512, DYN_NAME_PATH='.name'), + reflexive("references", reference, 512, DYN_NAME_PATH='.reference.filepath'), + reflexive("source_files", source_file, 16, DYN_NAME_PATH='.source_name'), + reflexive("cutscene_titles", cutscene_title, 64, DYN_NAME_PATH='.name'), ) def get(): diff --git a/reclaimer/mcc_hek/defs/senv.py b/reclaimer/mcc_hek/defs/senv.py index 952c556f..88cde9d7 100644 --- a/reclaimer/mcc_hek/defs/senv.py +++ b/reclaimer/mcc_hek/defs/senv.py @@ -9,7 +9,6 @@ from ...hek.defs.senv import * from .shdr import * -from supyr_struct.util import desc_variant environment_shader_flags = Bool16("flags", "alpha_tested", @@ -19,15 +18,9 @@ COMMENT=environment_shader_comment ) -environment_shader = desc_variant(environment_shader, - ("flags", environment_shader_flags), - ) - -senv_attrs = desc_variant(senv_attrs, - ("environment_shader", environment_shader), - ) - -senv_body = Struct("tagdata", +environment_shader = desc_variant(environment_shader, environment_shader_flags) +senv_attrs = desc_variant(senv_attrs, environment_shader) +senv_body = Struct("tagdata", shdr_attrs, senv_attrs, SIZE=836, diff --git a/reclaimer/mcc_hek/defs/snd_.py b/reclaimer/mcc_hek/defs/snd_.py index 82f3fe76..f16321a9 100644 --- a/reclaimer/mcc_hek/defs/snd_.py +++ b/reclaimer/mcc_hek/defs/snd_.py @@ -8,7 +8,6 @@ # from ...hek.defs.snd_ import * -from supyr_struct.util import desc_variant mcc_snd__flags = Bool32("flags", @@ -29,12 +28,12 @@ ("parent_tag_id2", UInt32("parent_tag_id")), ) pitch_range = desc_variant(pitch_range, - ("permutations", reflexive("permutations", permutation, 256, DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True)), + reflexive("permutations", permutation, 256, DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), ) mcc_snd__body = desc_variant(snd__body, - ("flags", mcc_snd__flags), - ("pitch_ranges", reflexive("pitch_ranges", pitch_range, 8, DYN_NAME_PATH='.name')), + mcc_snd__flags, + reflexive("pitch_ranges", pitch_range, 8, DYN_NAME_PATH='.name'), ) def get(): @@ -47,7 +46,7 @@ def get(): ext=".sound", endian=">", tag_cls=Snd_Tag, ) -snd__meta_stub = desc_variant( - mcc_snd__body, ("pitch_ranges", Pad(12)) +snd__meta_stub = desc_variant(mcc_snd__body, + ("pitch_ranges", Pad(12)), ) snd__meta_stub_blockdef = BlockDef(snd__meta_stub) diff --git a/reclaimer/mcc_hek/defs/soso.py b/reclaimer/mcc_hek/defs/soso.py index 3caa4368..8cdf4f2e 100644 --- a/reclaimer/mcc_hek/defs/soso.py +++ b/reclaimer/mcc_hek/defs/soso.py @@ -9,7 +9,6 @@ from ...hek.defs.soso import * from .shdr import * -from supyr_struct.util import desc_variant model_shader_flags = Bool16("flags", "detail_after_reflection", @@ -21,15 +20,8 @@ "multipurpose_map_uses_og_xbox_channel_order", ) -model_shader = desc_variant(model_shader, - ("flags", model_shader_flags), - ) - -soso_attrs = desc_variant(soso_attrs, - ("model_shader", model_shader), - ("reflection_bump_scale", Pad(4)), - ("reflection_bump_map", Pad(16)), - ) +model_shader = desc_variant(model_shader, model_shader_flags) +soso_attrs = desc_variant(soso_attrs, model_shader) soso_body = Struct("tagdata", shdr_attrs, diff --git a/reclaimer/mcc_hek/defs/ssce.py b/reclaimer/mcc_hek/defs/ssce.py index 6f7f8fff..a962b2b3 100644 --- a/reclaimer/mcc_hek/defs/ssce.py +++ b/reclaimer/mcc_hek/defs/ssce.py @@ -10,12 +10,7 @@ from ...hek.defs.ssce import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(11)) - ) - +obje_attrs = obje_attrs_variant(obje_attrs, "ssce") ssce_body = Struct("tagdata", obje_attrs, SIZE=508, diff --git a/reclaimer/mcc_hek/defs/unhi.py b/reclaimer/mcc_hek/defs/unhi.py index 96edf8b9..379a2e1f 100644 --- a/reclaimer/mcc_hek/defs/unhi.py +++ b/reclaimer/mcc_hek/defs/unhi.py @@ -9,7 +9,6 @@ from ...hek.defs.unhi import * from .grhi import hud_background, mcc_hud_anchor -from supyr_struct.util import desc_variant # to reduce a lot of code, these have been snipped out meter_xform_common = ( # NOTE: used by wphi @@ -92,10 +91,10 @@ ) unhi_body = desc_variant(unhi_body, - ("anchor", SEnum16("anchor", *hud_anchors_mcc)), - ("shield_panel_meter", shield_panel_meter), - ("health_panel_meter", health_panel_meter), - ("auxilary_meters", reflexive("auxilary_meters", auxilary_meter, 16)), + SEnum16("anchor", *hud_anchors_mcc), + shield_panel_meter, + health_panel_meter, + reflexive("auxilary_meters", auxilary_meter, 16), ) def get(): diff --git a/reclaimer/mcc_hek/defs/unit.py b/reclaimer/mcc_hek/defs/unit.py index 51cbbb48..523bed71 100644 --- a/reclaimer/mcc_hek/defs/unit.py +++ b/reclaimer/mcc_hek/defs/unit.py @@ -7,8 +7,7 @@ # See LICENSE for more information. # -from ...hek.defs.unit import * -from supyr_struct.util import desc_variant +from ...hek.defs.unit import * metagame_scoring = Struct("metagame_scoring", SEnum16("metagame_type", TOOLTIP="Used to determine score in MCC", *actor_types_mcc), @@ -18,7 +17,7 @@ unit_attrs = desc_variant(unit_attrs, ("pad_45", metagame_scoring), - ("grenade_type", SEnum16("grenade_type", *grenade_types_mcc)), + SEnum16("grenade_type", *grenade_types_mcc), ) unit_body = Struct('tagdata', diff --git a/reclaimer/mcc_hek/defs/vehi.py b/reclaimer/mcc_hek/defs/vehi.py index e000bfb6..e51c6ae7 100644 --- a/reclaimer/mcc_hek/defs/vehi.py +++ b/reclaimer/mcc_hek/defs/vehi.py @@ -10,7 +10,6 @@ from ...hek.defs.vehi import * from .obje import * from .unit import * -from supyr_struct.util import desc_variant vehi_flags = Bool32("flags", "speed_wakes_physics", @@ -37,15 +36,8 @@ "autoaim_when_teamless" ) -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(1)) - ) - -vehi_attrs = desc_variant(vehi_attrs, - ('flags', vehi_flags), - ) +obje_attrs = obje_attrs_variant(obje_attrs, "vehi") +vehi_attrs = desc_variant(vehi_attrs, vehi_flags) vehi_body = Struct("tagdata", obje_attrs, diff --git a/reclaimer/mcc_hek/defs/weap.py b/reclaimer/mcc_hek/defs/weap.py index 9612567f..b7c9900b 100644 --- a/reclaimer/mcc_hek/defs/weap.py +++ b/reclaimer/mcc_hek/defs/weap.py @@ -10,7 +10,6 @@ from ...hek.defs.weap import * from .obje import * from .item import * -from supyr_struct.util import desc_variant trigger_flags = Bool32("flags", "tracks_fired_projectile", @@ -42,20 +41,12 @@ firing = desc_variant(firing, ("pad_9", mcc_upgrades) ) -trigger = desc_variant(trigger, - ("flags", trigger_flags), - ("firing", firing) - ) - -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = desc_variant(obje_attrs, - ("object_type", object_type(2)) - ) +trigger = desc_variant(trigger, trigger_flags, firing) +obje_attrs = obje_attrs_variant(obje_attrs, "weap") weap_attrs = desc_variant(weap_attrs, - ("triggers", reflexive("triggers", trigger, 2, "primary", "secondary")), - ("weapon_type", SEnum16('weapon_type', *weapon_types_mcc)) + reflexive("triggers", trigger, 2, "primary", "secondary"), + SEnum16('weapon_type', *weapon_types_mcc) ) weap_body = Struct("tagdata", diff --git a/reclaimer/mcc_hek/defs/wphi.py b/reclaimer/mcc_hek/defs/wphi.py index 6b6f90ae..2f234bfe 100644 --- a/reclaimer/mcc_hek/defs/wphi.py +++ b/reclaimer/mcc_hek/defs/wphi.py @@ -10,7 +10,6 @@ from ...hek.defs.wphi import * from .grhi import multitex_overlay, mcc_hud_anchor from .unhi import meter_xform_common, meter_common -from supyr_struct.util import desc_variant # to reduce a lot of code, these have been snipped out element_common = ( @@ -82,11 +81,11 @@ ) wphi_body = desc_variant(wphi_body, - ("anchor", mcc_hud_anchor), - ("static_elements", reflexive("static_elements", static_element, 16)), - ("meter_elements", reflexive("meter_elements", meter_element, 16)), - ("number_elements", reflexive("number_elements", number_element, 16)), - ("overlay_elements", reflexive("overlay_elements", overlay_element, 16)), + mcc_hud_anchor, + reflexive("static_elements", static_element, 16), + reflexive("meter_elements", meter_element, 16), + reflexive("number_elements", number_element, 16), + reflexive("overlay_elements", overlay_element, 16), ) diff --git a/reclaimer/meta/halo1_map.py b/reclaimer/meta/halo1_map.py index 125504b7..86b2b4a6 100644 --- a/reclaimer/meta/halo1_map.py +++ b/reclaimer/meta/halo1_map.py @@ -176,7 +176,8 @@ def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): Pad(256), STEPTREE=Array("blocks", SIZE=".block_count", SUB_STRUCT=vap_block - ) + ), + SIZE=480 ) map_version = UEnum32("version", @@ -256,6 +257,7 @@ def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): map_header_vap = desc_variant( map_header, ("yelo_header", Struct("vap_header", INCLUDE=vap_header, OFFSET=128)), + verify=False, ) tag_header = Struct("tag_header", @@ -279,24 +281,6 @@ def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): SIZE=".tag_count", SUB_STRUCT=tag_header, POINTER=tag_index_array_pointer ) -tag_index_xbox = Struct("tag_index", - UInt32("tag_index_offset"), - UInt32("scenario_tag_id"), - UInt32("map_id"), # normally unused, but can be used - # for spoofing the maps checksum. - UInt32("tag_count"), - - UInt32("vertex_parts_count"), - UInt32("model_data_offset"), - - UInt32("index_parts_count"), - UInt32("index_parts_offset"), - UInt32("tag_sig", EDITABLE=False, DEFAULT='tags'), - - SIZE=36, - STEPTREE=tag_index_array - ) - tag_index_pc = Struct("tag_index", UInt32("tag_index_offset"), UInt32("scenario_tag_id"), @@ -316,10 +300,10 @@ def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): STEPTREE=tag_index_array ) -#tag_index_pc = tipc = dict(tag_index_xbox) -#tipc['ENTRIES'] += 1; tipc['SIZE'] += 4 -#tipc[7] = UInt32("vertex data size") -#tipc[9] = tipc[8]; tipc[8] = UInt32("model data size") +tag_index_xbox = desc_variant(tag_index_pc, + ("model_data_size", Pad(0)), + SIZE=36, verify=False + ) map_header_def = BlockDef(map_header) map_header_anni_def = BlockDef(map_header, endian=">") diff --git a/reclaimer/meta/halo1_map_fast_functions.py b/reclaimer/meta/halo1_map_fast_functions.py index 61f453e5..b0c01808 100644 --- a/reclaimer/meta/halo1_map_fast_functions.py +++ b/reclaimer/meta/halo1_map_fast_functions.py @@ -174,7 +174,7 @@ def repair_dependency(index_array, map_data, tag_magic, repair, engine, cls, cls = shader_class_bytes[shader_type] elif cls in (b'2dom', b'edom'): - if "xbox" in engine: + if "xbox" in engine or "halo" not in engine: cls = b'edom' else: cls = b'2dom' diff --git a/reclaimer/meta/shadowrun_map.py b/reclaimer/meta/shadowrun_map.py index e2c81231..265d0a11 100644 --- a/reclaimer/meta/shadowrun_map.py +++ b/reclaimer/meta/shadowrun_map.py @@ -7,46 +7,20 @@ # See LICENSE for more information. # -from reclaimer.meta.halo1_map import tag_path_pointer, tag_index_array_pointer +from reclaimer.meta.halo1_map import tag_path_pointer, tag_index_xbox,\ + tag_header as tag_index_header, tag_index_array_pointer from reclaimer.shadowrun_prototype.common_descs import * - -sr_tag_header = Struct("tag header", - UEnum32("class 1", GUI_NAME="primary tag class", INCLUDE=sr_valid_tags), - UEnum32("class 2", GUI_NAME="secondary tag class", INCLUDE=sr_valid_tags), - UEnum32("class 3", GUI_NAME="tertiary tag class", INCLUDE=sr_valid_tags), - UInt32("id"), - UInt32("path offset"), - UInt32("meta offset"), - UInt32("indexed"), - # if indexed is 1, the meta_offset is the literal index in the - # bitmaps, sounds, or loc cache that the meta data is located in. - Pad(4), - STEPTREE=CStrTagRef("path", POINTER=tag_path_pointer, MAX=768), - SIZE=32 +sr_tag_header = desc_variant(tag_index_header, + UEnum32("class_1", GUI_NAME="primary tag class", INCLUDE=sr_valid_tags), + UEnum32("class_2", GUI_NAME="secondary tag class", INCLUDE=sr_valid_tags), + UEnum32("class_3", GUI_NAME="tertiary tag class", INCLUDE=sr_valid_tags), ) - sr_tag_index_array = TagIndex("tag index", SIZE=".tag_count", SUB_STRUCT=sr_tag_header, POINTER=tag_index_array_pointer ) - -sr_tag_index = Struct("tag index", - UInt32("tag index offset"), - UInt32("scenario tag id"), - UInt32("map id"), # normally unused, but the scenario tag's header - # can be used for spoofing the maps checksum - UInt32("tag count"), - - UInt32("vertex parts count"), - UInt32("model data offset"), - - UInt32("index parts count"), - UInt32("index parts offset"), - UInt32("tag sig", EDITABLE=False, DEFAULT='tags'), - - SIZE=36, +sr_tag_index = desc_variant(tag_index_xbox, STEPTREE=sr_tag_index_array ) - sr_tag_index_def = BlockDef(sr_tag_index) diff --git a/reclaimer/meta/stubbs_map.py b/reclaimer/meta/stubbs_map.py index 79cb2cde..46e5ddde 100644 --- a/reclaimer/meta/stubbs_map.py +++ b/reclaimer/meta/stubbs_map.py @@ -10,19 +10,19 @@ from reclaimer.meta.halo1_map import tag_index_xbox, map_version,\ tag_header as tag_index_header, tag_path_pointer, tag_index_array_pointer from reclaimer.stubbs.common_descs import * -from supyr_struct.util import desc_variant stubbs_tag_index_header = desc_variant(tag_index_header, - ("class_1", UEnum32("class_1", GUI_NAME="primary tag class", INCLUDE=stubbs_valid_tags)), - ("class_2", UEnum32("class_2", GUI_NAME="secondary tag class", INCLUDE=stubbs_valid_tags)), - ("class_3", UEnum32("class_3", GUI_NAME="tertiary tag class", INCLUDE=stubbs_valid_tags)), + UEnum32("class_1", GUI_NAME="primary tag class", INCLUDE=stubbs_valid_tags), + UEnum32("class_2", GUI_NAME="secondary tag class", INCLUDE=stubbs_valid_tags), + UEnum32("class_3", GUI_NAME="tertiary tag class", INCLUDE=stubbs_valid_tags), ) stubbs_64bit_tag_index_header = desc_variant(stubbs_tag_index_header, - ("path_offset", Pointer64("path_offset")), - ("meta_offset", Pointer64("meta_offset")), + Pointer64("path_offset"), + Pointer64("meta_offset"), + verify=False, + SIZE=40 ) -stubbs_64bit_tag_index_header.update(SIZE=40) stubbs_tag_index_array = TagIndex("tag index", SIZE=".tag_count", SUB_STRUCT=stubbs_tag_index_header, @@ -34,8 +34,9 @@ POINTER=tag_index_array_pointer ) -stubbs_tag_index = dict(tag_index_xbox) -stubbs_tag_index.update(STEPTREE=stubbs_tag_index_array) +stubbs_tag_index = desc_variant(tag_index_xbox, + STEPTREE=stubbs_tag_index_array + ) stubbs_64bit_tag_index = Struct("tag_index", Pointer64("tag_index_offset"), diff --git a/reclaimer/meta/wrappers/halo1_map.py b/reclaimer/meta/wrappers/halo1_map.py index cf66b246..72b509d6 100644 --- a/reclaimer/meta/wrappers/halo1_map.py +++ b/reclaimer/meta/wrappers/halo1_map.py @@ -20,8 +20,11 @@ from supyr_struct.field_types import FieldType from supyr_struct.defs.frozen_dict import FrozenDict +from reclaimer.halo_script.hsc_decompilation import extract_scripts from reclaimer.halo_script.hsc import get_hsc_data_block,\ - HSC_IS_SCRIPT_OR_GLOBAL, SCRIPT_OBJECT_TYPES_TO_SCENARIO_REFLEXIVES + get_script_syntax_node_tag_refs, clean_script_syntax_nodes,\ + get_script_types, HSC_IS_SCRIPT_OR_GLOBAL,\ + SCRIPT_OBJECT_TYPES_TO_SCENARIO_REFLEXIVES from reclaimer.common_descs import make_dependency_os_block from reclaimer.hek.defs.snd_ import snd__meta_stub_blockdef from reclaimer.hek.defs.sbsp import sbsp_meta_header_def @@ -216,12 +219,7 @@ def get_dependencies(self, meta, tag_id, tag_cls): try: seen_tag_ids = set() syntax_data = get_hsc_data_block(meta.script_syntax_data.data) - for node in syntax_data.nodes: - if (node.flags & HSC_IS_SCRIPT_OR_GLOBAL or - node.type not in range(24, 32)): - # not a tag index ref - continue - + for node in get_script_syntax_node_tag_refs(syntax_data): tag_index_id = node.data & 0xFFff if (tag_index_id in range(len(tag_index_array)) and tag_index_id not in seen_tag_ids): @@ -741,6 +739,10 @@ def clean_tag_meta(self, meta, tag_id, tag_cls): syntax_data = get_hsc_data_block(meta.script_syntax_data.data) script_nodes_modified = False + # lets not use magic numbers here + _, script_object_types = get_script_types(self.engine) + biped_node_enum = script_object_types.index("actor_type") + # clean up any fucked up palettes for pal_block, inst_block in ( (meta.sceneries_palette, meta.sceneries), @@ -763,8 +765,7 @@ def clean_tag_meta(self, meta, tag_id, tag_cls): # determine which palette indices are used by script data for i in range(len(syntax_data.nodes)): node = syntax_data.nodes[i] - # 35 == "actor_type" script type - if node.type == 35 and not(node.flags & HSC_IS_SCRIPT_OR_GLOBAL): + if node.type == biped_node_enum and not(node.flags & HSC_IS_SCRIPT_OR_GLOBAL): script_nodes_to_modify.add(i) used_pal_indices.add(node.data & 0xFFff) @@ -1233,26 +1234,34 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): string_data = meta.script_string_data.data.decode("latin-1") syntax_data = get_hsc_data_block(raw_syntax_data=meta.script_syntax_data.data) + # lets not use magic numbers here + _, script_object_types = get_script_types(engine) + trigger_volume_enum = script_object_types.index("trigger_volume") + # NOTE: For a list of all the script object types # with their corrosponding enum value, check - # reclaimer.enums.script_object_types - keep_these = {i: set() for i in + # reclaimer.halo_script.hsc.get_script_types + keep_these = {script_object_types.index(typ): set() for typ in SCRIPT_OBJECT_TYPES_TO_SCENARIO_REFLEXIVES} + + # don't de-duplicate trigger volumes for b in meta.bsp_switch_trigger_volumes.STEPTREE: - keep_these[11].add(b.trigger_volume) + keep_these[trigger_volume_enum].add(b.trigger_volume) + # for everything we're keeping, clear the upper 16bits of the data for i in range(min(syntax_data.last_node, len(syntax_data.nodes))): node = syntax_data.nodes[i] - if node.type not in keep_these: - continue - - keep_these[node.type].add(node.data & 0xFFff) + if node.type in keep_these: + keep_these[node.type].add(node.data & 0xFFff) + # for everything else, rename duplicates for script_object_type, reflexive_name in \ SCRIPT_OBJECT_TYPES_TO_SCENARIO_REFLEXIVES.items(): - keep = keep_these[script_object_type] - reflexive = meta[reflexive_name].STEPTREE - counts = {b.name.lower(): 0 for b in reflexive} + + script_object_type_enum = script_object_types.index(script_object_type) + keep = keep_these[script_object_type_enum] + reflexive = meta[reflexive_name].STEPTREE + counts = {b.name.lower(): 0 for b in reflexive} for b in reflexive: counts[b.name.lower()] += 1 @@ -1261,6 +1270,23 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): if counts[name] > 1 and i not in keep: reflexive[i].name = ("DUP%s~%s" % (i, name))[: 31] + # null tag refs after we're done with them + clean_script_syntax_nodes(syntax_data, engine) + + # decompile scripts and put them in the source_files array so + # sapien can recompile them when it opens an extracted scenario + source_files = meta.source_files.STEPTREE + del source_files[:] + script_sources, global_sources = extract_scripts( + engine=engine, tagdata=meta, add_comments=False, minify=True + ) + i = 0 + for source in (*script_sources, *global_sources): + source_files.append() + source_files[-1].source_name = "decompiled_%s.hsc" % i + source_files[-1].source.data = source.encode('latin-1') + i += 1 + # divide the cutscene times by 30(they're in ticks) and # subtract the fade-in time from the up_time(normally added # together as a total up-time in maps, but not in tag form) @@ -1319,9 +1345,9 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): del shpg_attrs.merged_values.STEPTREE[:] elif tag_cls == "soso": - if "xbox" not in engine and engine != "shadowrun_proto": - if hasattr(meta.soso_attrs.reflection, "reflection_bump_map"): - meta.soso_attrs.reflection.reflection_bump_map.filepath = "" + # set the mcc multipurpose_map_uses_og_xbox_channel_order flag + if "xbox" in engine or "stubbs" in engine or engine == "shadowrun_proto": + meta.soso_attrs.model_shader.flags.data |= 1<<6 elif tag_cls == "weap": predicted_resources.append(meta.weap_attrs.predicted_resources) diff --git a/reclaimer/meta/wrappers/halo1_mcc_map.py b/reclaimer/meta/wrappers/halo1_mcc_map.py index d896efc7..c999dc51 100644 --- a/reclaimer/meta/wrappers/halo1_mcc_map.py +++ b/reclaimer/meta/wrappers/halo1_mcc_map.py @@ -33,7 +33,7 @@ def load_map(self, map_path, **kwargs): # depending on what folder they're located in, so we're # going to ignore any resource maps passed in unless # they're coming from the same folder as this map. - if self.are_resources_in_same_directory: + if not self.are_resources_in_same_directory: print("Unlinking potentially incompatible resource maps from %s" % self.map_name ) diff --git a/reclaimer/meta/wrappers/halo1_rsrc_map.py b/reclaimer/meta/wrappers/halo1_rsrc_map.py index eecbdfbe..cc3a4362 100644 --- a/reclaimer/meta/wrappers/halo1_rsrc_map.py +++ b/reclaimer/meta/wrappers/halo1_rsrc_map.py @@ -6,11 +6,16 @@ # Reclaimer is free software under the GNU General Public License v3.0. # See LICENSE for more information. # +import math +from collections import namedtuple from struct import unpack from traceback import format_exc from reclaimer import data_extraction +from reclaimer.mcc_hek.defs.bitm import bitm_def as pixel_root_subdef +from reclaimer.mcc_hek.defs.objs.bitm import MccBitmTag, HALO_P8_PALETTE +from reclaimer.stubbs.defs.objs.bitm import StubbsBitmTag, STUBBS_P8_PALETTE from reclaimer.util import get_is_xbox_map from reclaimer.meta.halo_map import map_header_def, tag_index_pc_def from reclaimer.meta.halo1_rsrc_map import lite_halo1_rsrc_map_def as halo1_rsrc_map_def @@ -22,6 +27,9 @@ from supyr_struct.buffer import BytearrayBuffer, get_rawdata from supyr_struct.field_types import FieldType +# reassign since we only want a reference to the sub-definition +pixel_root_subdef = pixel_root_subdef.subdefs['pixel_root'] + # this is ultra hacky, but it seems to be the only # way to fix the tagid for the sounds resource map sound_rsrc_id_map = { @@ -85,6 +93,33 @@ def uses_external_sounds(sound_meta): return False +class MetaBitmTag(): + ''' + This class exists to facilitate processing bitmap tags extracted + from maps without fully converting them to tag objects first. + ''' + _fake_data_block = namedtuple('FakeDataBlock', + ("blam_header", "tagdata") + ) + def __init__(self, tagdata=None): + self.data = self._fake_data_block(None, tagdata) + + # stubed since there's nothing to calculate here + def calc_internal_data(self): pass + + @property + def pixel_root_definition(self): return pixel_root_subdef + + +class MetaHaloBitmTag(MetaBitmTag, MccBitmTag): + @property + def p8_palette(self): return HALO_P8_PALETTE + +class MetaStubbsBitmTag(MetaBitmTag, StubbsBitmTag): + @property + def p8_palette(self): return STUBBS_P8_PALETTE + + class Halo1RsrcMap(HaloMap): '''Generation 1 resource map''' @@ -279,54 +314,52 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): byteswap = kwargs.get("byteswap", True) if tag_cls == "bitm": + bitm_tag_cls = MetaStubbsBitmTag if "stubbs" in engine else MetaHaloBitmTag + bitm_tag = bitm_tag_cls(meta) + bitmaps = meta.bitmaps.STEPTREE + # set the size of the compressed plate data to nothing meta.compressed_color_plate_data.STEPTREE = BytearrayBuffer() - # to enable compatibility with my bitmap converter we'll set the - # base address to a certain constant based on the console platform - is_xbox = get_is_xbox_map(engine) - - new_pixels_offset = 0 - - # uncheck the prefer_low_detail flag and - # set up the pixels_offset correctly. - for bitmap in meta.bitmaps.STEPTREE: - # clear meta-only flags - bitmap.flags.data &= 0x3F - - # TODO: convert bitmaps to pc format(swap cubemap faces and mipmaps) - bitmap.flags.prefer_low_detail = is_xbox - bitmap.pixels_offset = new_pixels_offset - new_pixels_offset += bitmap.pixels_meta_size - - # clear some meta-only fields - bitmap.pixels_meta_size = 0 - bitmap.bitmap_id_unknown1 = bitmap.bitmap_id_unknown2 = 0 - bitmap.bitmap_data_pointer = 0 - - if is_xbox: - bitmap.base_address = 1073751810 - if "dxt" in bitmap.format.enum_name: - # need to correct mipmap count on xbox dxt bitmaps. - # the game seems to prune the mipmap texels for any - # mipmaps whose dimensions are 2x2 or smaller - - max_dim = max(bitmap.width, bitmap.height) - if 2 ** bitmap.mipmaps > max_dim: - # make sure the mipmap level isnt higher than the - # number of mipmaps that should be able to exist. - bitmap.mipmaps = int(log(max_dim, 2)) - - last_mip_dim = max_dim // (2 ** bitmap.mipmaps) - if last_mip_dim == 1: - bitmap.mipmaps -= 2 - elif last_mip_dim == 2: - bitmap.mipmaps -= 1 - - if bitmap.mipmaps < 0: - bitmap.mipmaps = 0 - else: - bitmap.base_address = 0 + # set up the pixels_offsets + pixels_offset = 0 + for bitmap in bitmaps: + bitmap.pixels_offset = pixels_offset + pixels_offset += bitmap.pixels_meta_size + + # undo xbox-specific stuff(reorder bitmaps, fix mipmap dims, unswizzle) + if get_is_xbox_map(engine): + # correct mipmap count on xbox dxt bitmaps. texels for any + # mipmaps whose dimensions are 2x2 or smaller are pruned + for bitmap in (b for b in bitmaps if "dxt" in b.format.enum_name): + # figure out largest dimension(clip to 1 to avoid log(0, 2)) + max_dim = max(1, bitmap.width, bitmap.height) + + # subtract 2 to account for width/height of 1 or 2 not having mips + maxmips = int(max(0, math.log(max_dim, 2) - 2)) + + # clip mipmap count to max and min number that can exist + bitmap.mipmaps = max(0, min(maxmips, bitmap.mipmaps)) + + # rearrange the bitmap pixels so they're in standard format + try: + bitm_tag.parse_bitmap_blocks() + bitm_tag.sanitize_bitmaps() + bitm_tag.set_swizzled(False) + bitm_tag.add_bitmap_padding(False) + except Exception: + print(format_exc()) + print("Failed to convert xbox bitmap data to pc.") + + # clear meta-only fields + for bitmap in bitmaps: + bitmap.flags.data &= 0x3F + bitmap.base_address = 0 + bitmap.pixels_meta_size = bitmap.bitmap_data_pointer = 0 + bitmap.bitmap_id_unknown1 = bitmap.bitmap_id_unknown2 = 0 + + # serialize the pixel_data and replace the parsed block with it + meta.processed_pixel_data.data = meta.processed_pixel_data.data.serialize() elif tag_cls == "snd!": meta.maximum_bend_per_second = meta.maximum_bend_per_second ** 30 diff --git a/reclaimer/meta/wrappers/halo1_yelo.py b/reclaimer/meta/wrappers/halo1_yelo.py index 2ab04aee..526b0caf 100644 --- a/reclaimer/meta/wrappers/halo1_yelo.py +++ b/reclaimer/meta/wrappers/halo1_yelo.py @@ -29,7 +29,7 @@ def load_map(self, map_path, **kwargs): # depending on what folder they're located in, so we're # going to ignore any resource maps passed in unless # they're coming from the same folder as this map. - if self.are_resources_in_same_directory: + if not self.are_resources_in_same_directory: print("Unlinking potentially incompatible resource maps from %s" % self.map_name ) diff --git a/reclaimer/os_hek/defs/actv.py b/reclaimer/os_hek/defs/actv.py index 2c47d2b0..5ac9110a 100644 --- a/reclaimer/os_hek/defs/actv.py +++ b/reclaimer/os_hek/defs/actv.py @@ -7,21 +7,10 @@ # See LICENSE for more information. # -from supyr_struct.util import desc_variant - from ...hek.defs.actv import * -# Create opensauce variant of grenade descriptor. - -os_actv_grenades = desc_variant(actv_grenades, - ("grenade_type", SEnum16("grenade_type", *grenade_types_os)), -) - -# Create os variant of actv descriptor using the new grenade descriptor. - -actv_body = desc_variant(actv_body, - ("grenades", os_actv_grenades), -) +grenades = desc_variant(actv_grenades, SEnum16("grenade_type", *grenade_types_os)) +actv_body = desc_variant(actv_body, grenades) def get(): return actv_def diff --git a/reclaimer/os_hek/defs/antr.py b/reclaimer/os_hek/defs/antr.py index e964f5bd..e483d137 100644 --- a/reclaimer/os_hek/defs/antr.py +++ b/reclaimer/os_hek/defs/antr.py @@ -8,12 +8,9 @@ # from ...hek.defs.antr import * -from supyr_struct.util import desc_variant -antr_body = desc_variant( - antr_body, - ("animations", reflexive( - "animations", animation_desc, 2048, DYN_NAME_PATH=".name")) +antr_body = desc_variant(antr_body, + reflexive("animations", animation_desc, 2048, DYN_NAME_PATH=".name") ) def get(): diff --git a/reclaimer/os_hek/defs/bipd.py b/reclaimer/os_hek/defs/bipd.py index 816a1fc3..7ca9ff98 100644 --- a/reclaimer/os_hek/defs/bipd.py +++ b/reclaimer/os_hek/defs/bipd.py @@ -8,17 +8,11 @@ # from ...hek.defs.bipd import * - -#import and use the open saucified obje and unit attrs from .obje import * from .unit import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=0) - -bipd_body = Struct("tagdata", +obje_attrs = obje_attrs_variant(obje_attrs, "bipd") +bipd_body = Struct("tagdata", obje_attrs, unit_attrs, bipd_attrs, diff --git a/reclaimer/os_hek/defs/ctrl.py b/reclaimer/os_hek/defs/ctrl.py index 2766cec6..ee9135ee 100644 --- a/reclaimer/os_hek/defs/ctrl.py +++ b/reclaimer/os_hek/defs/ctrl.py @@ -8,17 +8,10 @@ # from ...hek.defs.ctrl import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=8) - -ctrl_body = dict(ctrl_body) -ctrl_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "ctrl") +ctrl_body = desc_variant(ctrl_body, obje_attrs) def get(): return ctrl_def diff --git a/reclaimer/os_hek/defs/eqip.py b/reclaimer/os_hek/defs/eqip.py index 0db84889..74a5fd59 100644 --- a/reclaimer/os_hek/defs/eqip.py +++ b/reclaimer/os_hek/defs/eqip.py @@ -8,21 +8,11 @@ # from ...hek.defs.eqip import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=3) - -eqip_attrs = dict(eqip_attrs) -eqip_attrs[1] = SEnum16("grenade_type", *grenade_types_os) - -eqip_body = dict(eqip_body) -eqip_body[0] = obje_attrs -eqip_body[2] = eqip_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "eqip") +eqip_attrs = desc_variant(eqip_attrs, SEnum16("grenade_type", *grenade_types_os)) +eqip_body = desc_variant(eqip_body, obje_attrs, eqip_attrs) def get(): return eqip_def diff --git a/reclaimer/os_hek/defs/garb.py b/reclaimer/os_hek/defs/garb.py index 6c6a1bd4..37861ec4 100644 --- a/reclaimer/os_hek/defs/garb.py +++ b/reclaimer/os_hek/defs/garb.py @@ -8,17 +8,10 @@ # from ...hek.defs.garb import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=4) - -garb_body = dict(garb_body) -garb_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "garb") +garb_body = desc_variant(garb_body, obje_attrs) def get(): diff --git a/reclaimer/os_hek/defs/lifi.py b/reclaimer/os_hek/defs/lifi.py index 505b32d4..bcc0afdb 100644 --- a/reclaimer/os_hek/defs/lifi.py +++ b/reclaimer/os_hek/defs/lifi.py @@ -8,17 +8,10 @@ # from ...hek.defs.lifi import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=9) - -lifi_body = dict(lifi_body) -lifi_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "lifi") +lifi_body = desc_variant(lifi_body, obje_attrs) def get(): return lifi_def diff --git a/reclaimer/os_hek/defs/mach.py b/reclaimer/os_hek/defs/mach.py index 493331fc..42ba018a 100644 --- a/reclaimer/os_hek/defs/mach.py +++ b/reclaimer/os_hek/defs/mach.py @@ -8,17 +8,10 @@ # from ...hek.defs.mach import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=7) - -mach_body = dict(mach_body) -mach_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "mach") +mach_body = desc_variant(mach_body, obje_attrs) def get(): return mach_def diff --git a/reclaimer/os_hek/defs/matg.py b/reclaimer/os_hek/defs/matg.py index c403f47a..1f2c31f1 100644 --- a/reclaimer/os_hek/defs/matg.py +++ b/reclaimer/os_hek/defs/matg.py @@ -13,8 +13,9 @@ def get(): return matg_def # replace the grenades reflexive with an open sauce one -matg_body = dict(matg_body) -matg_body[5] = reflexive("grenades", grenade, 4, *grenade_types_os) +matg_body = desc_variant(matg_body, + reflexive("grenades", grenade, 4, *grenade_types_os) + ) matg_def = TagDef("matg", blam_header_os('matg', 3), diff --git a/reclaimer/os_hek/defs/obje.py b/reclaimer/os_hek/defs/obje.py index 3ca5f1fb..7792fc71 100644 --- a/reclaimer/os_hek/defs/obje.py +++ b/reclaimer/os_hek/defs/obje.py @@ -9,17 +9,14 @@ from ...hek.defs.obje import * +obje_attrs = desc_variant(obje_attrs, + dependency('animation_graph', valid_model_animations_yelo) + ) +obje_attrs = obje_attrs_variant(obje_attrs) + def get(): return obje_def -# replace the model animations dependency with an open sauce one -obje_attrs = dict(obje_attrs) -obje_attrs[8] = dependency('animation_graph', valid_model_animations_yelo) - -obje_body = Struct('tagdata', - obje_attrs - ) - obje_def = TagDef("obje", blam_header('obje'), obje_body, diff --git a/reclaimer/os_hek/defs/objs/scnr.py b/reclaimer/os_hek/defs/objs/scnr.py new file mode 100644 index 00000000..f9adf45c --- /dev/null +++ b/reclaimer/os_hek/defs/objs/scnr.py @@ -0,0 +1,13 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.scnr import ScnrTag + +class OsScnrTag(ScnrTag): + engine = "halo1yelo" diff --git a/reclaimer/os_hek/defs/plac.py b/reclaimer/os_hek/defs/plac.py index 655d8c49..95382ef4 100644 --- a/reclaimer/os_hek/defs/plac.py +++ b/reclaimer/os_hek/defs/plac.py @@ -8,17 +8,10 @@ # from ...hek.defs.plac import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=10) - -plac_body = dict(plac_body) -plac_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "plac") +plac_body = desc_variant(plac_body, obje_attrs) def get(): return plac_def diff --git a/reclaimer/os_hek/defs/proj.py b/reclaimer/os_hek/defs/proj.py index 3b01be73..cb05774a 100644 --- a/reclaimer/os_hek/defs/proj.py +++ b/reclaimer/os_hek/defs/proj.py @@ -8,17 +8,10 @@ # from ...hek.defs.proj import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=5) - -proj_body = dict(proj_body) -proj_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "proj") +proj_body = desc_variant(proj_body, obje_attrs) def get(): return proj_def diff --git a/reclaimer/os_hek/defs/scen.py b/reclaimer/os_hek/defs/scen.py index b56c75d3..d18b4285 100644 --- a/reclaimer/os_hek/defs/scen.py +++ b/reclaimer/os_hek/defs/scen.py @@ -8,17 +8,10 @@ # from ...hek.defs.scen import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=6) - -scen_body = dict(scen_body) -scen_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "scen") +scen_body = desc_variant(scen_body, obje_attrs) def get(): return scen_def diff --git a/reclaimer/os_hek/defs/scnr.py b/reclaimer/os_hek/defs/scnr.py index 5097fc57..752fc104 100644 --- a/reclaimer/os_hek/defs/scnr.py +++ b/reclaimer/os_hek/defs/scnr.py @@ -8,67 +8,36 @@ # from ...hek.defs.scnr import * -from supyr_struct.util import desc_variant +from .objs.scnr import OsScnrTag -player_starting_profile = Struct("player_starting_profile", - ascii_str32("name"), - float_zero_to_one("starting_health_modifier"), - float_zero_to_one("starting_shield_modifier"), - dependency("primary_weapon", "weap"), - SInt16("primary_rounds_loaded"), - SInt16("primary_rounds_total"), - dependency("secondary_weapon", "weap"), - SInt16("secondary_rounds_loaded"), - SInt16("secondary_rounds_total"), - SInt8("starting_frag_grenade_count", MIN=0), - SInt8("starting_plasma_grenade_count", MIN=0), - SInt8("starting_custom_2_grenade_count", MIN=0), - SInt8("starting_custom_3_grenade_count", MIN=0), - SIZE=104 +player_starting_profile = desc_variant(player_starting_profile, + ("pad_11", SInt8("starting_custom_2_grenade_count", MIN=0)), + ("pad_12", SInt8("starting_custom_3_grenade_count", MIN=0)), ) -ai_anim_reference = Struct("ai_animation_reference", - ascii_str32("animation_name"), - dependency_os("animation_graph", ("antr", "magy")), - SIZE=60 +ai_anim_reference = desc_variant(ai_anim_reference, + dependency_os("animation_graph", ("antr", "magy")) ) -reference = Struct("tag_reference", - Pad(24), - dependency_os("reference"), - SIZE=40 - ) +reference = desc_variant(reference, dependency_os("reference")) -# copy the scnr_body and replace the descriptors for certain -# fields with ones that are tweaked for use with open sauce scnr_body = desc_variant(scnr_body, ("DONT_USE", dependency_os("project_yellow_definitions", 'yelo')), - ("player_starting_profiles", - reflexive("player_starting_profiles", - player_starting_profile, 256, DYN_NAME_PATH='.name') - ), - ("ai_animation_references", - reflexive("ai_animation_references", - ai_anim_reference, 128, DYN_NAME_PATH='.animation_name') - ), - ("script_syntax_data", rawdata_ref("script_syntax_data", max_size=570076, IGNORE_SAFE_MODE=True)), - ("script_string_data", rawdata_ref("script_string_data", max_size=393216, IGNORE_SAFE_MODE=True)), - ("references", - reflexive("references", - reference, 256, DYN_NAME_PATH='.reference.filepath') - ), - ("structure_bsps", - reflexive("structure_bsps", - structure_bsp, 32, DYN_NAME_PATH='.structure_bsp.filepath') - ) + reflexive("player_starting_profiles", player_starting_profile, 256, DYN_NAME_PATH='.name'), + reflexive("ai_animation_references", ai_anim_reference, 128, DYN_NAME_PATH='.animation_name'), + rawdata_ref("script_syntax_data", max_size=570076, IGNORE_SAFE_MODE=True), + rawdata_ref("script_string_data", max_size=393216, IGNORE_SAFE_MODE=True), + reflexive("references", reference, 256, DYN_NAME_PATH='.reference.filepath'), + reflexive("structure_bsps", structure_bsp, 32, DYN_NAME_PATH='.structure_bsp.filepath'), ) def get(): return scnr_def +# TODO: update dependencies scnr_def = TagDef("scnr", blam_header('scnr', 2), scnr_body, - ext=".scenario", endian=">", tag_cls=ScnrTag + ext=".scenario", endian=">", tag_cls=OsScnrTag ) diff --git a/reclaimer/os_hek/defs/soso.py b/reclaimer/os_hek/defs/soso.py index 08bdee06..8b7e52f4 100644 --- a/reclaimer/os_hek/defs/soso.py +++ b/reclaimer/os_hek/defs/soso.py @@ -8,7 +8,6 @@ # from ...hek.defs.soso import * -from supyr_struct.util import desc_variant specular_map_comment = """SPECULAR (COLOR) MAP The specular color map is multiplied by the stock specular @@ -52,8 +51,7 @@ When the opensauce extension is used the tint values in here are overwritten by the ones in the os extension when the map is loaded.""" -reflection = desc_variant(reflection) -reflection.update(COMMENT=os_reflection_prop_comment) +reflection = desc_variant(reflection, COMMENT=os_reflection_prop_comment) os_soso_ext = Struct("shader_model_extension", #Specular Color @@ -104,8 +102,6 @@ soso_attrs = desc_variant(soso_attrs, ("pad_7", reflexive("os_shader_model_ext", os_soso_ext, 1)), - ("reflection_bump_scale", Pad(4)), - ("reflection_bump_map", Pad(16)), ) soso_body = Struct("tagdata", diff --git a/reclaimer/os_hek/defs/ssce.py b/reclaimer/os_hek/defs/ssce.py index abd90da8..d5926367 100644 --- a/reclaimer/os_hek/defs/ssce.py +++ b/reclaimer/os_hek/defs/ssce.py @@ -8,17 +8,10 @@ # from ...hek.defs.ssce import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=11) - -ssce_body = dict(ssce_body) -ssce_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "ssce") +ssce_body = desc_variant(ssce_body, obje_attrs) def get(): return ssce_def diff --git a/reclaimer/os_hek/defs/tagc.py b/reclaimer/os_hek/defs/tagc.py index bfdf97ee..d6dabd90 100644 --- a/reclaimer/os_hek/defs/tagc.py +++ b/reclaimer/os_hek/defs/tagc.py @@ -7,18 +7,14 @@ # See LICENSE for more information. # -from ...common_descs import * -from ...hek.defs.objs.tag import HekTag -from supyr_struct.defs.tag_def import TagDef +from ...hek.defs.tagc import * -tag_reference = Struct("tag_reference", - dependency_os("tag"), - SIZE=16 - ) +tag_reference = desc_variant(tag_reference, dependency_os("tag")) -tagc_body = Struct("tagdata", - reflexive("tag_references", tag_reference, 200), - SIZE=12, +tagc_body = desc_variant(tagc_body, + reflexive("tag_references", tag_reference, 200, + DYN_NAME_PATH='.tag.filepath' + ) ) diff --git a/reclaimer/os_hek/defs/unit.py b/reclaimer/os_hek/defs/unit.py index 8ae39816..c4b16198 100644 --- a/reclaimer/os_hek/defs/unit.py +++ b/reclaimer/os_hek/defs/unit.py @@ -8,13 +8,8 @@ # from ...hek.defs.unit import * -from supyr_struct.util import desc_variant - -# replace the grenade types enumerator with an open sauce one -unit_attrs = desc_variant(unit_attrs, - ("grenade_type", SEnum16('grenade_type', *grenade_types_os)) - ) +unit_attrs = desc_variant(unit_attrs, SEnum16('grenade_type', *grenade_types_os)) unit_body = Struct('tagdata', unit_attrs) def get(): diff --git a/reclaimer/os_hek/defs/vehi.py b/reclaimer/os_hek/defs/vehi.py index ded29cf0..d59f21af 100644 --- a/reclaimer/os_hek/defs/vehi.py +++ b/reclaimer/os_hek/defs/vehi.py @@ -10,19 +10,14 @@ from ...hek.defs.vehi import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=1) - -vehi_body = Struct("tagdata", +obje_attrs = obje_attrs_variant(obje_attrs, "vehi") +vehi_body = Struct("tagdata", obje_attrs, unit_attrs, vehi_attrs, SIZE=1008, ) - def get(): return vehi_def diff --git a/reclaimer/os_hek/defs/weap.py b/reclaimer/os_hek/defs/weap.py index 276bbaf3..a11a3a15 100644 --- a/reclaimer/os_hek/defs/weap.py +++ b/reclaimer/os_hek/defs/weap.py @@ -8,17 +8,10 @@ # from ...hek.defs.weap import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=2) - -weap_body = dict(weap_body) -weap_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "weap") +weap_body = desc_variant(weap_body, obje_attrs) def get(): return weap_def diff --git a/reclaimer/os_v3_hek/defs/bipd.py b/reclaimer/os_v3_hek/defs/bipd.py index a112e45f..bdc918a1 100644 --- a/reclaimer/os_v3_hek/defs/bipd.py +++ b/reclaimer/os_v3_hek/defs/bipd.py @@ -8,16 +8,10 @@ # from ...os_hek.defs.bipd import * - -#import and use the open saucified obje and unit attrs from .obje import * from .unit import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=0) - +obje_attrs = obje_attrs_variant(obje_attrs, "bipd") bipd_body = Struct("tagdata", obje_attrs, unit_attrs, diff --git a/reclaimer/os_v3_hek/defs/cdmg.py b/reclaimer/os_v3_hek/defs/cdmg.py index f533762c..fe7e2e32 100644 --- a/reclaimer/os_v3_hek/defs/cdmg.py +++ b/reclaimer/os_v3_hek/defs/cdmg.py @@ -8,7 +8,6 @@ # from ...hek.defs.cdmg import * -from supyr_struct.util import desc_variant damage_flags = Bool32("flags", "does_not_hurt_owner", @@ -28,16 +27,14 @@ ) damage = desc_variant(damage, - ("flags", damage_flags), - ("instantaneous_acceleration", QStruct("instantaneous_acceleration", - INCLUDE=ijk_float, SIDETIP="[-inf,+inf]" - )), + damage_flags, + QStruct("instantaneous_acceleration", INCLUDE=ijk_float, SIDETIP="[-inf,+inf]"), ("pad_13", Pad(0)), + # we're doing some weird stuff to make this work, so we're turning off verify + verify=False ) -cdmg_body = desc_variant(cdmg_body, - ("damage", damage), - ) +cdmg_body = desc_variant(cdmg_body, damage) def get(): return cdmg_def diff --git a/reclaimer/os_v3_hek/defs/ctrl.py b/reclaimer/os_v3_hek/defs/ctrl.py index 2766cec6..ee9135ee 100644 --- a/reclaimer/os_v3_hek/defs/ctrl.py +++ b/reclaimer/os_v3_hek/defs/ctrl.py @@ -8,17 +8,10 @@ # from ...hek.defs.ctrl import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=8) - -ctrl_body = dict(ctrl_body) -ctrl_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "ctrl") +ctrl_body = desc_variant(ctrl_body, obje_attrs) def get(): return ctrl_def diff --git a/reclaimer/os_v3_hek/defs/eqip.py b/reclaimer/os_v3_hek/defs/eqip.py index c89357d0..4c6f9537 100644 --- a/reclaimer/os_v3_hek/defs/eqip.py +++ b/reclaimer/os_v3_hek/defs/eqip.py @@ -8,17 +8,10 @@ # from ...os_hek.defs.eqip import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=3) - -eqip_body = dict(eqip_body) -eqip_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "eqip") +eqip_body = desc_variant(eqip_body, obje_attrs) def get(): return eqip_def diff --git a/reclaimer/os_v3_hek/defs/garb.py b/reclaimer/os_v3_hek/defs/garb.py index 6c6a1bd4..1958794c 100644 --- a/reclaimer/os_v3_hek/defs/garb.py +++ b/reclaimer/os_v3_hek/defs/garb.py @@ -8,17 +8,10 @@ # from ...hek.defs.garb import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=4) - -garb_body = dict(garb_body) -garb_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "garb") +garb_body = desc_variant(garb_body, obje_attrs) def get(): diff --git a/reclaimer/os_v3_hek/defs/jpt_.py b/reclaimer/os_v3_hek/defs/jpt_.py index b04a925f..d153188d 100644 --- a/reclaimer/os_v3_hek/defs/jpt_.py +++ b/reclaimer/os_v3_hek/defs/jpt_.py @@ -9,19 +9,16 @@ from ...hek.defs.jpt_ import * from .cdmg import damage_flags -from supyr_struct.util import desc_variant damage = desc_variant(damage, - ("flags", damage_flags), - ("instantaneous_acceleration", QStruct("instantaneous_acceleration", - INCLUDE=ijk_float, SIDETIP="[-inf,+inf]" - )), + damage_flags, + QStruct("instantaneous_acceleration", INCLUDE=ijk_float, SIDETIP="[-inf,+inf]"), ("pad_13", Pad(0)), + # we're doing some weird stuff to make this work, so we're turning off verify + verify=False ) -jpt__body = desc_variant(jpt__body, - ("damage", damage), - ) +jpt__body = desc_variant(jpt__body, damage) def get(): return jpt__def diff --git a/reclaimer/os_v3_hek/defs/lifi.py b/reclaimer/os_v3_hek/defs/lifi.py index 505b32d4..bcc0afdb 100644 --- a/reclaimer/os_v3_hek/defs/lifi.py +++ b/reclaimer/os_v3_hek/defs/lifi.py @@ -8,17 +8,10 @@ # from ...hek.defs.lifi import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=9) - -lifi_body = dict(lifi_body) -lifi_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "lifi") +lifi_body = desc_variant(lifi_body, obje_attrs) def get(): return lifi_def diff --git a/reclaimer/os_v3_hek/defs/mach.py b/reclaimer/os_v3_hek/defs/mach.py index 493331fc..73059163 100644 --- a/reclaimer/os_v3_hek/defs/mach.py +++ b/reclaimer/os_v3_hek/defs/mach.py @@ -8,17 +8,10 @@ # from ...hek.defs.mach import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=7) - -mach_body = dict(mach_body) -mach_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "mach") +mach_body = desc_variant(mach_body, obje_attrs) def get(): return mach_def diff --git a/reclaimer/os_v3_hek/defs/plac.py b/reclaimer/os_v3_hek/defs/plac.py index 655d8c49..cffcfd66 100644 --- a/reclaimer/os_v3_hek/defs/plac.py +++ b/reclaimer/os_v3_hek/defs/plac.py @@ -8,17 +8,10 @@ # from ...hek.defs.plac import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=10) - -plac_body = dict(plac_body) -plac_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "plac") +plac_body = desc_variant(plac_body, obje_attrs) def get(): return plac_def diff --git a/reclaimer/os_v3_hek/defs/proj.py b/reclaimer/os_v3_hek/defs/proj.py index 94303bfa..cce9ab18 100644 --- a/reclaimer/os_v3_hek/defs/proj.py +++ b/reclaimer/os_v3_hek/defs/proj.py @@ -8,17 +8,10 @@ # from ...os_hek.defs.proj import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=5) - -proj_body = dict(proj_body) -proj_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "proj") +proj_body = desc_variant(proj_body, obje_attrs) def get(): return proj_def diff --git a/reclaimer/os_v3_hek/defs/scen.py b/reclaimer/os_v3_hek/defs/scen.py index b56c75d3..2109dbf2 100644 --- a/reclaimer/os_v3_hek/defs/scen.py +++ b/reclaimer/os_v3_hek/defs/scen.py @@ -8,17 +8,10 @@ # from ...hek.defs.scen import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=6) - -scen_body = dict(scen_body) -scen_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "scen") +scen_body = desc_variant(scen_body, obje_attrs) def get(): return scen_def diff --git a/reclaimer/os_v3_hek/defs/ssce.py b/reclaimer/os_v3_hek/defs/ssce.py index abd90da8..d5926367 100644 --- a/reclaimer/os_v3_hek/defs/ssce.py +++ b/reclaimer/os_v3_hek/defs/ssce.py @@ -8,17 +8,10 @@ # from ...hek.defs.ssce import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=11) - -ssce_body = dict(ssce_body) -ssce_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "ssce") +ssce_body = desc_variant(ssce_body, obje_attrs) def get(): return ssce_def diff --git a/reclaimer/os_v3_hek/defs/vehi.py b/reclaimer/os_v3_hek/defs/vehi.py index 7481a531..37873c5e 100644 --- a/reclaimer/os_v3_hek/defs/vehi.py +++ b/reclaimer/os_v3_hek/defs/vehi.py @@ -8,16 +8,10 @@ # from ...os_hek.defs.vehi import * - -#import and use the open saucified obje and unit attrs from .obje import * from .unit import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=1) - +obje_attrs = obje_attrs_variant(obje_attrs, "vehi") vehi_body = Struct("tagdata", obje_attrs, unit_attrs, @@ -25,7 +19,6 @@ SIZE=1008, ) - def get(): return vehi_def diff --git a/reclaimer/os_v3_hek/defs/weap.py b/reclaimer/os_v3_hek/defs/weap.py index d70ee3eb..b0910a97 100644 --- a/reclaimer/os_v3_hek/defs/weap.py +++ b/reclaimer/os_v3_hek/defs/weap.py @@ -8,17 +8,10 @@ # from ...os_hek.defs.weap import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=2) - -weap_body = dict(weap_body) -weap_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "weap") +weap_body = desc_variant(weap_body, obje_attrs) def get(): return weap_def diff --git a/reclaimer/os_v4_hek/defs/bipd.py b/reclaimer/os_v4_hek/defs/bipd.py index 5dd4624e..5d3dc8c2 100644 --- a/reclaimer/os_v4_hek/defs/bipd.py +++ b/reclaimer/os_v4_hek/defs/bipd.py @@ -8,19 +8,11 @@ # from ...os_v3_hek.defs.bipd import * - -#import and use the open saucified obje and unit attrs from .obje import * from .unit import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=0) - -bipd_body = dict(bipd_body) -bipd_body[0] = obje_attrs -bipd_body[1] = unit_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "bipd") +bipd_body = desc_variant(bipd_body, obje_attrs, unit_attrs) def get(): return bipd_def diff --git a/reclaimer/os_v4_hek/defs/bitm.py b/reclaimer/os_v4_hek/defs/bitm.py index 860db917..2dce4e33 100644 --- a/reclaimer/os_v4_hek/defs/bitm.py +++ b/reclaimer/os_v4_hek/defs/bitm.py @@ -9,16 +9,14 @@ from ...os_v3_hek.defs.bitm import * -def get(): return bitm_def - -# replace the model animations dependency with an open sauce one -bitm_body = dict(bitm_body) -bitm_body[3] = Bool16("flags", - "enable_diffusion_dithering", - "disable_height_map_compression", - "uniform_sprite_sequences", - "sprite_bug_fix", - ("never_share_resources", 1<<13) +bitm_body = desc_variant(bitm_body, + Bool16("flags", + "enable_diffusion_dithering", + "disable_height_map_compression", + "uniform_sprite_sequences", + "sprite_bug_fix", + ("never_share_resources", 1<<13) + ), ) def get(): diff --git a/reclaimer/os_v4_hek/defs/cont.py b/reclaimer/os_v4_hek/defs/cont.py index 3cda3ff7..8626e058 100644 --- a/reclaimer/os_v4_hek/defs/cont.py +++ b/reclaimer/os_v4_hek/defs/cont.py @@ -9,11 +9,13 @@ from ...os_v3_hek.defs.cont import * -cont_body = dict(cont_body) -cont_body[4] = reflexive( - "shader_extensions", - Struct("shader_extension", INCLUDE=os_shader_extension), - 1) +shader_extensions = reflexive("shader_extensions", + Struct("shader_extension", INCLUDE=os_shader_extension), + 1 + ) +cont_body = desc_variant(cont_body, + ("pad_4", shader_extensions), + ) def get(): diff --git a/reclaimer/os_v4_hek/defs/ctrl.py b/reclaimer/os_v4_hek/defs/ctrl.py index f2cb6d01..ed2e2b95 100644 --- a/reclaimer/os_v4_hek/defs/ctrl.py +++ b/reclaimer/os_v4_hek/defs/ctrl.py @@ -8,17 +8,10 @@ # from ...os_v3_hek.defs.ctrl import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=8) - -ctrl_body = dict(ctrl_body) -ctrl_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "ctrl") +ctrl_body = desc_variant(ctrl_body, obje_attrs) def get(): return ctrl_def diff --git a/reclaimer/os_v4_hek/defs/eqip.py b/reclaimer/os_v4_hek/defs/eqip.py index 82f5ba66..b7390e64 100644 --- a/reclaimer/os_v4_hek/defs/eqip.py +++ b/reclaimer/os_v4_hek/defs/eqip.py @@ -8,17 +8,10 @@ # from ...os_v3_hek.defs.eqip import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=3) - -eqip_body = dict(eqip_body) -eqip_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "eqip") +eqip_body = desc_variant(eqip_body, obje_attrs) def get(): return eqip_def diff --git a/reclaimer/os_v4_hek/defs/garb.py b/reclaimer/os_v4_hek/defs/garb.py index 49127703..14c0e6ca 100644 --- a/reclaimer/os_v4_hek/defs/garb.py +++ b/reclaimer/os_v4_hek/defs/garb.py @@ -8,17 +8,10 @@ # from ...os_v3_hek.defs.garb import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=4) - -garb_body = dict(garb_body) -garb_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "garb") +garb_body = desc_variant(garb_body, obje_attrs) def get(): return garb_def diff --git a/reclaimer/os_v4_hek/defs/gelo.py b/reclaimer/os_v4_hek/defs/gelo.py index e7072604..80c9bae8 100644 --- a/reclaimer/os_v4_hek/defs/gelo.py +++ b/reclaimer/os_v4_hek/defs/gelo.py @@ -7,32 +7,11 @@ # See LICENSE for more information. # -from .yelo import * +from ...os_hek.defs.gelo import * -gelo_body = Struct("tagdata", - SInt16("version", DEFAULT=2), - Bool16("flags", - "hide_health_when_zoomed", - "hide_shield_when_zoomed", - "hide_motion_sensor_when_zoomed", - "force_game_to_use_stun_jumping_penalty" - ), - SInt32("base_address"), - ascii_str32("mod_name"), - dependency_os("global_explicit_references", "tagc"), - #dependency_os("chokin_victim_globals", "gelc"), - Pad(16), # removed_chokin_victim_globals - - Pad(16), - Pad(12), #reflexive("unknown1", void_desc), - Pad(52), - reflexive("scripted_ui_widgets", scripted_ui_widget, 128), - - Pad(12), #reflexive("unknown2", void_desc), - Pad(20), - reflexive("yelo_scripting", yelo_scripting, 1), - - SIZE=288 +gelo_body = desc_variant(gelo_body, + # was removed + ("chokin_victim_globals", Pad(16)), ) def get(): diff --git a/reclaimer/os_v4_hek/defs/lifi.py b/reclaimer/os_v4_hek/defs/lifi.py index 1ca371c9..a85fcb15 100644 --- a/reclaimer/os_v4_hek/defs/lifi.py +++ b/reclaimer/os_v4_hek/defs/lifi.py @@ -8,17 +8,10 @@ # from ...os_v3_hek.defs.lifi import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=9) - -lifi_body = dict(lifi_body) -lifi_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "lifi") +lifi_body = desc_variant(lifi_body, obje_attrs) def get(): return lifi_def diff --git a/reclaimer/os_v4_hek/defs/mach.py b/reclaimer/os_v4_hek/defs/mach.py index c6b78a65..2d25cbdb 100644 --- a/reclaimer/os_v4_hek/defs/mach.py +++ b/reclaimer/os_v4_hek/defs/mach.py @@ -8,17 +8,10 @@ # from ...os_v3_hek.defs.mach import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=7) - -mach_body = dict(mach_body) -mach_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "mach") +mach_body = desc_variant(mach_body, obje_attrs) def get(): return mach_def diff --git a/reclaimer/os_v4_hek/defs/obje.py b/reclaimer/os_v4_hek/defs/obje.py index 11beced7..ebfa0161 100644 --- a/reclaimer/os_v4_hek/defs/obje.py +++ b/reclaimer/os_v4_hek/defs/obje.py @@ -9,15 +9,16 @@ from ...os_v3_hek.defs.obje import * -obje_attrs = dict(obje_attrs) -obje_attrs[1] = Bool16('flags', - 'does_not_cast_shadow', - 'transparent_self_occlusion', - 'brighter_than_it_should_be', - 'not_a_pathfinding_obstacle', - 'cast_shadow_by_default', - {NAME: 'xbox_unknown_bit_8', VALUE: 1<<8, VISIBLE: False}, - {NAME: 'xbox_unknown_bit_11', VALUE: 1<<11, VISIBLE: False}, +obje_attrs = desc_variant(obje_attrs, + Bool16('flags', + 'does_not_cast_shadow', + 'transparent_self_occlusion', + 'brighter_than_it_should_be', + 'not_a_pathfinding_obstacle', + 'cast_shadow_by_default', + {NAME: 'xbox_unknown_bit_8', VALUE: 1<<8, VISIBLE: False}, + {NAME: 'xbox_unknown_bit_11', VALUE: 1<<11, VISIBLE: False}, + ), ) obje_body = Struct('tagdata', diff --git a/reclaimer/os_v4_hek/defs/part.py b/reclaimer/os_v4_hek/defs/part.py index 874acc9b..aec9d260 100644 --- a/reclaimer/os_v4_hek/defs/part.py +++ b/reclaimer/os_v4_hek/defs/part.py @@ -9,8 +9,6 @@ from ...hek.defs.part import * -from supyr_struct.util import desc_variant - particle_shader_extensions = reflexive("particle_shader_extensions", Struct("particle_shader_extension", INCLUDE=os_shader_extension), 1 diff --git a/reclaimer/os_v4_hek/defs/pctl.py b/reclaimer/os_v4_hek/defs/pctl.py index ab8f45c5..407dd9dc 100644 --- a/reclaimer/os_v4_hek/defs/pctl.py +++ b/reclaimer/os_v4_hek/defs/pctl.py @@ -9,19 +9,18 @@ from ...hek.defs.pctl import * -particle_state = dict(particle_state) -particle_type = dict(particle_type) -pctl_body = dict(pctl_body) - -particle_state[19] = reflexive( - "shader_extensions", - Struct("shader_extension", INCLUDE=os_shader_extension), - 1) -particle_type[12] = reflexive( - "particle_states", particle_state, 8, DYN_NAME_PATH='.name') -pctl_body[5] = reflexive( - "particle_types", particle_type, 4, DYN_NAME_PATH='.name') - +shader_extensions = reflexive("shader_extensions", + Struct("shader_extension", INCLUDE=os_shader_extension), 1 + ) +particle_state = desc_variant(particle_state, + ("pad_19", shader_extensions), + ) +particle_type = desc_variant(particle_type, + reflexive("particle_states", particle_state, 8, DYN_NAME_PATH='.name'), + ) +pctl_body = desc_variant(pctl_body, + reflexive("particle_types", particle_type, 4, DYN_NAME_PATH='.name'), + ) def get(): return pctl_def diff --git a/reclaimer/os_v4_hek/defs/plac.py b/reclaimer/os_v4_hek/defs/plac.py index 3a1d3673..39033345 100644 --- a/reclaimer/os_v4_hek/defs/plac.py +++ b/reclaimer/os_v4_hek/defs/plac.py @@ -8,17 +8,10 @@ # from ...os_v3_hek.defs.plac import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=10) - -plac_body = dict(plac_body) -plac_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "plac") +plac_body = desc_variant(plac_body, obje_attrs) def get(): return plac_def diff --git a/reclaimer/os_v4_hek/defs/proj.py b/reclaimer/os_v4_hek/defs/proj.py index 5aa6faf0..c77c03f3 100644 --- a/reclaimer/os_v4_hek/defs/proj.py +++ b/reclaimer/os_v4_hek/defs/proj.py @@ -8,17 +8,10 @@ # from ...os_v3_hek.defs.proj import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=5) - -proj_body = dict(proj_body) -proj_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "proj") +proj_body = desc_variant(proj_body, obje_attrs) def get(): return proj_def diff --git a/reclaimer/os_v4_hek/defs/scen.py b/reclaimer/os_v4_hek/defs/scen.py index 0ab5bb32..f1b103b0 100644 --- a/reclaimer/os_v4_hek/defs/scen.py +++ b/reclaimer/os_v4_hek/defs/scen.py @@ -8,17 +8,10 @@ # from ...os_v3_hek.defs.scen import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=6) - -scen_body = dict(scen_body) -scen_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "scen") +scen_body = desc_variant(scen_body, obje_attrs) def get(): return scen_def diff --git a/reclaimer/os_v4_hek/defs/scnr.py b/reclaimer/os_v4_hek/defs/scnr.py index 350c31ac..b9e8c49a 100644 --- a/reclaimer/os_v4_hek/defs/scnr.py +++ b/reclaimer/os_v4_hek/defs/scnr.py @@ -19,7 +19,6 @@ SIZE=124 ) - sky_set_sky = Struct("sky", Pad(2), dyn_senum16("sky_index", @@ -28,14 +27,12 @@ SIZE=20 ) - sky_set = Struct("sky_set", ascii_str32("name"), reflexive("skies", sky_set_sky, 8), SIZE=44 ) - bsp_modifier = Struct("bsp_modifier", Pad(2), dyn_senum16("bsp_index", @@ -45,9 +42,9 @@ SIZE=64 ) - -scnr_body = dict(scnr_body) -scnr_body[65] = reflexive("bsp_modifiers", bsp_modifier, 32) +scnr_body = desc_variant(scnr_body, + ("pad_65", reflexive("bsp_modifiers", bsp_modifier, 32)), + ) def get(): return scnr_def @@ -56,5 +53,5 @@ def get(): blam_header('scnr', 2), scnr_body, - ext=".scenario", endian=">", tag_cls=HekTag + ext=".scenario", endian=">", tag_cls=ScnrTag ) diff --git a/reclaimer/os_v4_hek/defs/senv.py b/reclaimer/os_v4_hek/defs/senv.py index 32e638b2..0f49da24 100644 --- a/reclaimer/os_v4_hek/defs/senv.py +++ b/reclaimer/os_v4_hek/defs/senv.py @@ -51,8 +51,9 @@ ) # replace the padding with an open sauce shader environment extension reflexive -senv_attrs = dict(senv_attrs) -senv_attrs[3] = reflexive("os_shader_environment_ext", os_senv_ext, 1) +senv_attrs = desc_variant(senv_attrs, + ("pad_3", reflexive("os_shader_environment_ext", os_senv_ext, 1)), + ) senv_body = Struct("tagdata", shdr_attrs, diff --git a/reclaimer/os_v4_hek/defs/ssce.py b/reclaimer/os_v4_hek/defs/ssce.py index bb7da50a..32129e80 100644 --- a/reclaimer/os_v4_hek/defs/ssce.py +++ b/reclaimer/os_v4_hek/defs/ssce.py @@ -8,17 +8,10 @@ # from ...os_v3_hek.defs.ssce import * - -#import and use the open saucified obje attrs from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=11) - -ssce_body = dict(ssce_body) -ssce_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "ssce") +ssce_body = desc_variant(ssce_body, obje_attrs) def get(): return ssce_def diff --git a/reclaimer/os_v4_hek/defs/unit.py b/reclaimer/os_v4_hek/defs/unit.py index a3f56c6f..f5b2110c 100644 --- a/reclaimer/os_v4_hek/defs/unit.py +++ b/reclaimer/os_v4_hek/defs/unit.py @@ -236,13 +236,13 @@ ) seat = desc_variant(seat, - ("flags", seat_flags), + seat_flags, ("pad_20", reflexive("seat_extensions", seat_extension, 1)), ) unit_attrs = desc_variant(unit_attrs, ("pad_45", reflexive("unit_extensions", unit_extension, 1)), - ("seats", reflexive("seats", seat, 16, DYN_NAME_PATH='.label')), + reflexive("seats", seat, 16, DYN_NAME_PATH='.label'), ) unit_body = Struct('tagdata', unit_attrs) diff --git a/reclaimer/os_v4_hek/defs/vehi.py b/reclaimer/os_v4_hek/defs/vehi.py index 265ab4e3..9ac5cc60 100644 --- a/reclaimer/os_v4_hek/defs/vehi.py +++ b/reclaimer/os_v4_hek/defs/vehi.py @@ -8,19 +8,11 @@ # from ...os_v3_hek.defs.vehi import * - -#import and use the open saucified obje and unit attrs from .obje import * from .unit import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=1) - -vehi_body = dict(vehi_body) -vehi_body[0] = obje_attrs -vehi_body[1] = unit_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "vehi") +vehi_body = desc_variant(vehi_body, obje_attrs, unit_attrs) def get(): return vehi_def diff --git a/reclaimer/os_v4_hek/defs/weap.py b/reclaimer/os_v4_hek/defs/weap.py index 95b114a5..050d99d3 100644 --- a/reclaimer/os_v4_hek/defs/weap.py +++ b/reclaimer/os_v4_hek/defs/weap.py @@ -11,13 +11,8 @@ from .obje import * from .item import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=2) - -weap_body = dict(weap_body) -weap_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "weap") +weap_body = desc_variant(weap_body, obje_attrs) def get(): return weap_def diff --git a/reclaimer/shadowrun_prototype/common_descs.py b/reclaimer/shadowrun_prototype/common_descs.py index c7b359b5..f8a97ea8 100644 --- a/reclaimer/shadowrun_prototype/common_descs.py +++ b/reclaimer/shadowrun_prototype/common_descs.py @@ -10,6 +10,19 @@ from reclaimer.common_descs import * from reclaimer.shadowrun_prototype.constants import * +# TODO: move shared enumerators into separate enums.py module +# ########################################################################### +# The order of element in all the enumerators is important(DONT SHUFFLE THEM) +# ########################################################################### + +#Shared Enumerator options + +# TODO: update these if any of the new shadowrun tag types are found +# to be used in scripts, or if there are new builtin functions +# NOTE: we're re-defining these here simply as a placeholder +script_types = tuple(script_types) +script_object_types = tuple(script_object_types) + def sr_tag_class(*args, **kwargs): ''' @@ -19,7 +32,6 @@ def sr_tag_class(*args, **kwargs): kwargs["class_mapping"] = sr_tag_class_fcc_to_ext return tag_class(*args, **kwargs) - def dependency(name='tag_ref', valid_ids=None, **kwargs): '''This function serves to macro the creation of a tag dependency''' if isinstance(valid_ids, tuple): @@ -27,16 +39,20 @@ def dependency(name='tag_ref', valid_ids=None, **kwargs): elif isinstance(valid_ids, str): valid_ids = sr_tag_class(valid_ids) elif valid_ids is None: - valid_ids = valid_tags + valid_ids = sr_valid_tags - return TagRef(name, + return desc_variant(tag_ref_struct, valid_ids, - INCLUDE=tag_ref_struct, STEPTREE=StrTagRef( - "filepath", SIZE=tag_ref_str_size, GUI_NAME="", MAX=234), - **kwargs + "filepath", SIZE=tag_ref_str_size, GUI_NAME="", MAX=254 + ), + NAME=name, **kwargs ) +# should really rename "dependency" above to this. +# until then, make an alias so it's clear what we're referencing +dependency_sr = dependency + def blam_header(tagid, version=1): '''This function serves to macro the creation of a tag header''' diff --git a/reclaimer/shadowrun_prototype/defs/bipd.py b/reclaimer/shadowrun_prototype/defs/bipd.py index c8c876e9..7ca9ff98 100644 --- a/reclaimer/shadowrun_prototype/defs/bipd.py +++ b/reclaimer/shadowrun_prototype/defs/bipd.py @@ -11,12 +11,8 @@ from .obje import * from .unit import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=0) - -bipd_body = Struct("tagdata", +obje_attrs = obje_attrs_variant(obje_attrs, "bipd") +bipd_body = Struct("tagdata", obje_attrs, unit_attrs, bipd_attrs, diff --git a/reclaimer/shadowrun_prototype/defs/ctrl.py b/reclaimer/shadowrun_prototype/defs/ctrl.py index 3ff1a7de..dbbd4cca 100644 --- a/reclaimer/shadowrun_prototype/defs/ctrl.py +++ b/reclaimer/shadowrun_prototype/defs/ctrl.py @@ -10,13 +10,8 @@ from ...hek.defs.ctrl import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=8) - -ctrl_body = dict(ctrl_body) -ctrl_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "ctrl") +ctrl_body = desc_variant(ctrl_body, obje_attrs) def get(): return ctrl_def diff --git a/reclaimer/shadowrun_prototype/defs/eqip.py b/reclaimer/shadowrun_prototype/defs/eqip.py index 1513134d..719b7068 100644 --- a/reclaimer/shadowrun_prototype/defs/eqip.py +++ b/reclaimer/shadowrun_prototype/defs/eqip.py @@ -10,13 +10,8 @@ from ...hek.defs.eqip import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=3) - -eqip_body = dict(eqip_body) -eqip_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "eqip") +eqip_body = desc_variant(eqip_body, obje_attrs) def get(): return eqip_def diff --git a/reclaimer/shadowrun_prototype/defs/garb.py b/reclaimer/shadowrun_prototype/defs/garb.py index 18fb1f59..0297e5f3 100644 --- a/reclaimer/shadowrun_prototype/defs/garb.py +++ b/reclaimer/shadowrun_prototype/defs/garb.py @@ -10,14 +10,8 @@ from ...hek.defs.garb import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=4) - -garb_body = dict(garb_body) -garb_body[0] = obje_attrs - +obje_attrs = obje_attrs_variant(obje_attrs, "garb") +garb_body = desc_variant(garb_body, obje_attrs) def get(): return garb_def diff --git a/reclaimer/shadowrun_prototype/defs/lifi.py b/reclaimer/shadowrun_prototype/defs/lifi.py index 6f110a61..bcc0afdb 100644 --- a/reclaimer/shadowrun_prototype/defs/lifi.py +++ b/reclaimer/shadowrun_prototype/defs/lifi.py @@ -10,13 +10,8 @@ from ...hek.defs.lifi import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=9) - -lifi_body = dict(lifi_body) -lifi_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "lifi") +lifi_body = desc_variant(lifi_body, obje_attrs) def get(): return lifi_def diff --git a/reclaimer/shadowrun_prototype/defs/mach.py b/reclaimer/shadowrun_prototype/defs/mach.py index c2381757..73059163 100644 --- a/reclaimer/shadowrun_prototype/defs/mach.py +++ b/reclaimer/shadowrun_prototype/defs/mach.py @@ -10,13 +10,8 @@ from ...hek.defs.mach import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=7) - -mach_body = dict(mach_body) -mach_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "mach") +mach_body = desc_variant(mach_body, obje_attrs) def get(): return mach_def diff --git a/reclaimer/shadowrun_prototype/defs/objs/scnr.py b/reclaimer/shadowrun_prototype/defs/objs/scnr.py new file mode 100644 index 00000000..22ad42cf --- /dev/null +++ b/reclaimer/shadowrun_prototype/defs/objs/scnr.py @@ -0,0 +1,13 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.scnr import ScnrTag + +class SrProtoScnrTag(ScnrTag): + engine = "shadowrun_proto" diff --git a/reclaimer/shadowrun_prototype/defs/plac.py b/reclaimer/shadowrun_prototype/defs/plac.py index e71ceeef..cffcfd66 100644 --- a/reclaimer/shadowrun_prototype/defs/plac.py +++ b/reclaimer/shadowrun_prototype/defs/plac.py @@ -10,13 +10,8 @@ from ...hek.defs.plac import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=10) - -plac_body = dict(plac_body) -plac_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "plac") +plac_body = desc_variant(plac_body, obje_attrs) def get(): return plac_def diff --git a/reclaimer/shadowrun_prototype/defs/proj.py b/reclaimer/shadowrun_prototype/defs/proj.py index ad6f38db..062b16f6 100644 --- a/reclaimer/shadowrun_prototype/defs/proj.py +++ b/reclaimer/shadowrun_prototype/defs/proj.py @@ -10,13 +10,8 @@ from ...hek.defs.proj import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=5) - -proj_body = dict(proj_body) -proj_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "proj") +proj_body = desc_variant(proj_body, obje_attrs) def get(): return proj_def diff --git a/reclaimer/shadowrun_prototype/defs/scen.py b/reclaimer/shadowrun_prototype/defs/scen.py index 020bf262..2109dbf2 100644 --- a/reclaimer/shadowrun_prototype/defs/scen.py +++ b/reclaimer/shadowrun_prototype/defs/scen.py @@ -10,13 +10,8 @@ from ...hek.defs.scen import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=6) - -scen_body = dict(scen_body) -scen_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "scen") +scen_body = desc_variant(scen_body, obje_attrs) def get(): return scen_def diff --git a/reclaimer/shadowrun_prototype/defs/scnr.py b/reclaimer/shadowrun_prototype/defs/scnr.py index 5ccac64f..e24147f1 100644 --- a/reclaimer/shadowrun_prototype/defs/scnr.py +++ b/reclaimer/shadowrun_prototype/defs/scnr.py @@ -8,3 +8,22 @@ # from ...hek.defs.scnr import * +from ..common_descs import * +from .objs.scnr import SrProtoScnrTag + +reference = desc_variant(reference, dependency_sr("reference")) + +scnr_body = desc_variant(scnr_body, + reflexive("references", reference, 256, DYN_NAME_PATH='.reference.filepath'), + ) + +def get(): + return scnr_def + +# TODO: update dependencies +scnr_def = TagDef("scnr", + blam_header('scnr', 2), + scnr_body, + + ext=".scenario", endian=">", tag_cls=SrProtoScnrTag + ) diff --git a/reclaimer/shadowrun_prototype/defs/ssce.py b/reclaimer/shadowrun_prototype/defs/ssce.py index 001a3e4b..d5926367 100644 --- a/reclaimer/shadowrun_prototype/defs/ssce.py +++ b/reclaimer/shadowrun_prototype/defs/ssce.py @@ -10,13 +10,8 @@ from ...hek.defs.ssce import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=11) - -ssce_body = dict(ssce_body) -ssce_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "ssce") +ssce_body = desc_variant(ssce_body, obje_attrs) def get(): return ssce_def diff --git a/reclaimer/shadowrun_prototype/defs/tagc.py b/reclaimer/shadowrun_prototype/defs/tagc.py index 6e3f05af..2fa3dac5 100644 --- a/reclaimer/shadowrun_prototype/defs/tagc.py +++ b/reclaimer/shadowrun_prototype/defs/tagc.py @@ -8,3 +8,23 @@ # from ...hek.defs.tagc import * +from ..common_descs import dependency_sr + +tag_reference = desc_variant(tag_reference, dependency_sr("tag")) + +tagc_body = desc_variant(tagc_body, + reflexive("tag_references", tag_reference, 200, + DYN_NAME_PATH='.tag.filepath' + ) + ) + + +def get(): + return tagc_def + +tagc_def = TagDef("tagc", + blam_header('tagc'), + tagc_body, + + ext=".tag_collection", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/shadowrun_prototype/defs/weap.py b/reclaimer/shadowrun_prototype/defs/weap.py index a98e2e94..a11a3a15 100644 --- a/reclaimer/shadowrun_prototype/defs/weap.py +++ b/reclaimer/shadowrun_prototype/defs/weap.py @@ -10,13 +10,8 @@ from ...hek.defs.weap import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=2) - -weap_body = dict(weap_body) -weap_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "weap") +weap_body = desc_variant(weap_body, obje_attrs) def get(): return weap_def diff --git a/reclaimer/stubbs/common_descs.py b/reclaimer/stubbs/common_descs.py index 958dd631..0cb5343a 100644 --- a/reclaimer/stubbs/common_descs.py +++ b/reclaimer/stubbs/common_descs.py @@ -9,6 +9,7 @@ from reclaimer.common_descs import * +# TODO: move shared enumerators into separate enums.py module # ########################################################################### # The order of element in all the enumerators is important(DONT SHUFFLE THEM) # ########################################################################### @@ -121,11 +122,22 @@ "unknown5", ) +# TODO: update these WHEN the new stubbs tag types are found to +# be used in scripts, or if there are new builtin functions. +# there are definitely new object types inserted, as the player +# script types are set to return weapons now instead of units. +# NOTE: we're re-defining these here simply as a placeholder +script_types = tuple(script_types) +script_object_types = tuple(script_object_types) + + damage_modifiers = QStruct("damage_modifiers", - *(float_zero_to_inf(material_name) for material_name in materials_list) + *(float_zero_to_inf(material_name) for material_name in materials_list), + # NOTE: there's enough allocated for 40 materials. We're assuming + # the rest of the space is all for these damage modifiers + SIZE=4*40 ) - def tag_class_stubbs(*args, **kwargs): ''' A macro for creating a tag_class enum desc with the @@ -144,12 +156,12 @@ def dependency_stubbs(name='tag_ref', valid_ids=None, **kwargs): elif valid_ids is None: valid_ids = stubbs_valid_tags - return TagRef(name, + return desc_variant(tag_ref_struct, valid_ids, - INCLUDE=tag_ref_struct, STEPTREE=StrTagRef( - "filepath", SIZE=tag_ref_str_size, GUI_NAME="", MAX=234), - **kwargs + "filepath", SIZE=tag_ref_str_size, GUI_NAME="", MAX=254 + ), + NAME=name, **kwargs ) diff --git a/reclaimer/stubbs/defs/actr.py b/reclaimer/stubbs/defs/actr.py index 4fac7fac..54ab8a23 100644 --- a/reclaimer/stubbs/defs/actr.py +++ b/reclaimer/stubbs/defs/actr.py @@ -9,17 +9,9 @@ from ...hek.defs.actr import * from ..common_descs import * -from supyr_struct.util import desc_variant - -panic = desc_variant(panic, - ("leader_type", SEnum16("leader_type", *actor_types)) - ) - -actr_body = desc_variant(actr_body, - ("type", SEnum16("type", *actor_types)), - ("panic", panic) - ) +panic = desc_variant(panic, SEnum16("leader_type", *actor_types)) +actr_body = desc_variant(actr_body, SEnum16("type", *actor_types), panic) def get(): return actr_def diff --git a/reclaimer/stubbs/defs/antr.py b/reclaimer/stubbs/defs/antr.py index deb369cd..14760a7e 100644 --- a/reclaimer/stubbs/defs/antr.py +++ b/reclaimer/stubbs/defs/antr.py @@ -10,7 +10,7 @@ from ...hek.defs.antr import * from ..common_descs import * -# As meta, it seems MOST arrays of anim_enum_desc have an extra extra on the end +# As meta, it seems MOST arrays of anim_enum_desc have an extra enum on the end animations_extended_desc = Struct("weapon_types", Pad(16), @@ -18,6 +18,8 @@ SIZE=28, ) +# NOTE: this requires further investigation. It doesn't seem like +# they actually moved the padding to before the yaw_per_frame. unit_weapon_desc = Struct("weapon", ascii_str32("name"), ascii_str32("grip_marker"), @@ -56,27 +58,10 @@ SIZE=64 ) -unit_desc = Struct("unit", - ascii_str32("label"), - #pitch and yaw are saved in radians. - - #Looking screen bounds - float_rad("right_yaw_per_frame"), - float_rad("left_yaw_per_frame"), - SInt16("right_frame_count"), - SInt16("left_frame_count"), - - float_rad("down_pitch_per_frame"), - float_rad("up_pitch_per_frame"), - SInt16("down_frame_count"), - SInt16("up_frame_count"), - - Pad(8), - reflexive("animations", anim_enum_desc), - reflexive("ik_points", ik_point_desc, 4, DYN_NAME_PATH=".marker"), - reflexive("weapons", unit_weapon_desc, DYN_NAME_PATH=".name"), - reflexive("unknown", unknown_unit_desc), +unit_desc = desc_variant(unit_desc, + ("pad_13", reflexive("unknown", unknown_unit_desc)), SIZE=128, + verify=False ) seat_desc = Struct("seat", @@ -86,27 +71,8 @@ SIZE=60 ) -vehicle_desc = Struct("vehicle", - #pitch and yaw are saved in radians. - - #Steering screen bounds - float_rad("right_yaw_per_frame"), - float_rad("left_yaw_per_frame"), - SInt16("right_frame_count"), - SInt16("left_frame_count"), - - float_rad("down_pitch_per_frame"), - float_rad("up_pitch_per_frame"), - SInt16("down_frame_count"), - SInt16("up_frame_count"), - - Pad(56), - reflexive("seats", seat_desc), - reflexive("animations", anim_enum_desc, 8, - 'steering','roll','throttle','velocity', - 'braking','ground-speed','occupied','unoccupied'), - reflexive("suspension_animations", suspension_desc, 8), - SIZE=116, +vehicle_desc = desc_variant(vehicle_desc, + ("pad_9", reflexive("seats", seat_desc)), ) effect_reference_desc = Struct("effect_reference", @@ -114,79 +80,24 @@ SIZE=20, ) -animation_desc = Struct("animation", - ascii_str32("name"), - SEnum16("type", *anim_types), - SInt16("frame_count"), - SInt16("frame_size"), - SEnum16("frame_info_type", *anim_frame_info_types), - SInt32("node_list_checksum"), - SInt16("node_count"), - SInt16("loop_frame_index"), - - Float("weight"), - SInt16("key_frame_index"), - SInt16("second_key_frame_index"), - Pad(8), - - dyn_senum16("next_animation", DYN_NAME_PATH="..[DYN_I].name"), - Bool16("flags", - "compressed_data", - "world_relative", - {NAME:"pal", GUI_NAME:"25Hz(PAL)"}, - ), - dyn_senum16("sound", +animation_desc = desc_variant(animation_desc, + ("pad_11", Pad(8)), + ("sound", dyn_senum16("effect", DYN_NAME_PATH="tagdata.effect_references." + - "effect_references_array[DYN_I].effect.filepath"), - SInt16("sound_frame_index"), - SInt8("left_foot_frame_index"), - SInt8("right_foot_frame_index"), - FlSInt16("first_permutation_index", VISIBLE=False, - TOOLTIP="The index of the first animation in the permutation chain."), - FlFloat("chance_to_play", VISIBLE=False, - MIN=0.0, MAX=1.0, SIDETIP="[0,1]", - TOOLTIP=("Seems to be the chance range to select this permutation.\n" - "Random number in the range [0,1] is rolled. The permutation\n" - "chain is looped until the number is higher than or equal\n" - "to that permutations chance to play. This chance to play\n" - "is likely influenced by the animations 'weight' field.\n" - "All permutation chains should have the last one end with\n" - "a chance to play of 1.0")), - - rawdata_ref("frame_info", max_size=32768), - UInt32("trans_flags0", EDITABLE=False), - UInt32("trans_flags1", EDITABLE=False), - Pad(8), - UInt32("rot_flags0", EDITABLE=False), - UInt32("rot_flags1", EDITABLE=False), - Pad(8), - UInt32("scale_flags0", EDITABLE=False), - UInt32("scale_flags1", EDITABLE=False), - Pad(4), - SInt32("offset_to_compressed_data", EDITABLE=False), - rawdata_ref("default_data", max_size=16384), - rawdata_ref("frame_data", max_size=1048576), + "effect_references_array[DYN_I].effect.filepath" + )), SIZE=188, + verify=False ) -antr_body = Struct("tagdata", - reflexive("objects", object_desc), +antr_body = desc_variant(antr_body, reflexive("units", unit_desc, DYN_NAME_PATH=".label"), reflexive("weapons", weapon_desc), reflexive("vehicles", vehicle_desc), - reflexive("devices", device_desc), reflexive("unit_damages", anim_enum_desc), - reflexive("fp_animations", fp_animation_desc), - - reflexive("effect_references", effect_reference_desc, - DYN_NAME_PATH=".effect.filepath"), - Float("limp_body_node_radius"), - Bool16("flags", - "compress_all_animations", - "force_idle_compression", + ("sound_references", reflexive("effect_references", + effect_reference_desc, DYN_NAME_PATH=".effect.filepath") ), - Pad(2), - reflexive("nodes", nodes_desc, DYN_NAME_PATH=".name"), reflexive("animations", animation_desc, DYN_NAME_PATH=".name"), SIZE=128, ) diff --git a/reclaimer/stubbs/defs/bipd.py b/reclaimer/stubbs/defs/bipd.py index 79b69b35..9a342c0d 100644 --- a/reclaimer/stubbs/defs/bipd.py +++ b/reclaimer/stubbs/defs/bipd.py @@ -14,20 +14,16 @@ from .obje import * from .unit import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=0) - -bipd_body = Struct("tagdata", +obje_attrs = obje_attrs_variant(obje_attrs, "bipd") +bipd_body = Struct("tagdata", obje_attrs, unit_attrs, bipd_attrs, SIZE=1268, ) -#def get(): -# return bipd_def +def get(): + return bipd_def del get bipd_def = TagDef("bipd", diff --git a/reclaimer/stubbs/defs/cdmg.py b/reclaimer/stubbs/defs/cdmg.py index 25687be2..4ed84e9a 100644 --- a/reclaimer/stubbs/defs/cdmg.py +++ b/reclaimer/stubbs/defs/cdmg.py @@ -9,16 +9,9 @@ from ...hek.defs.cdmg import * from ..common_descs import * -from supyr_struct.util import desc_variant -damage = desc_variant(damage, - ("category", SEnum16("category", *damage_category)) - ) - -cdmg_body = desc_variant(cdmg_body, - ("damage", damage), - ("damage_modifiers", damage_modifiers) - ) +damage = desc_variant(damage, SEnum16("category", *damage_category)) +cdmg_body = desc_variant(cdmg_body, damage, damage_modifiers) def get(): return cdmg_def diff --git a/reclaimer/stubbs/defs/coll.py b/reclaimer/stubbs/defs/coll.py index bd47f7b4..4e666bfa 100644 --- a/reclaimer/stubbs/defs/coll.py +++ b/reclaimer/stubbs/defs/coll.py @@ -9,11 +9,8 @@ from ...hek.defs.coll import * from ..common_descs import * -from supyr_struct.util import desc_variant -shield = desc_variant(shield, - ("shield_material_type", SEnum16("shield_material_type", *materials_list)), - ) +shield = desc_variant(shield, SEnum16("shield_material_type", *materials_list)) permutation = Struct("permutation", ascii_str32("name"), @@ -63,12 +60,12 @@ ) coll_body = desc_variant(coll_body, - ("shield", shield), - ("materials", reflexive("materials", material, 32, DYN_NAME_PATH='.name')), - ("regions", reflexive("regions", region, 8, DYN_NAME_PATH='.name')), + shield, + reflexive("materials", material, 32, DYN_NAME_PATH='.name'), + reflexive("regions", region, 8, DYN_NAME_PATH='.name'), ) fast_coll_body = desc_variant(coll_body, - ("nodes", reflexive("nodes", fast_node, 64, DYN_NAME_PATH='.name')), + reflexive("nodes", fast_node, 64, DYN_NAME_PATH='.name'), ) diff --git a/reclaimer/stubbs/defs/ctrl.py b/reclaimer/stubbs/defs/ctrl.py index 3c607e20..09288c39 100644 --- a/reclaimer/stubbs/defs/ctrl.py +++ b/reclaimer/stubbs/defs/ctrl.py @@ -11,10 +11,7 @@ from .obje import * from .devi import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=8) +obje_attrs = obje_attrs_variant(obje_attrs, "ctrl") ctrl_body = Struct("tagdata", obje_attrs, diff --git a/reclaimer/stubbs/defs/eqip.py b/reclaimer/stubbs/defs/eqip.py index abd48d85..941121fe 100644 --- a/reclaimer/stubbs/defs/eqip.py +++ b/reclaimer/stubbs/defs/eqip.py @@ -10,23 +10,14 @@ from ...hek.defs.eqip import * from .item import * from .obje import * -from ..common_descs import * -from supyr_struct.util import desc_variant -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=3) - -eqip_attrs = desc_variant(eqip_attrs, - ("grenade_type", SEnum16('grenade_type', *grenade_types)) - ) +obje_attrs = obje_attrs_variant(obje_attrs, "eqip") +eqip_attrs = desc_variant(eqip_attrs, SEnum16('grenade_type', *grenade_types)) eqip_body = Struct("tagdata", obje_attrs, item_attrs, eqip_attrs, - SIZE=944, ) diff --git a/reclaimer/stubbs/defs/foot.py b/reclaimer/stubbs/defs/foot.py index b7857977..7f07b01b 100644 --- a/reclaimer/stubbs/defs/foot.py +++ b/reclaimer/stubbs/defs/foot.py @@ -31,7 +31,6 @@ ) - def get(): return foot_def diff --git a/reclaimer/stubbs/defs/garb.py b/reclaimer/stubbs/defs/garb.py index bbf969e5..28a994b7 100644 --- a/reclaimer/stubbs/defs/garb.py +++ b/reclaimer/stubbs/defs/garb.py @@ -10,13 +10,8 @@ from ...hek.defs.garb import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=4) - -garb_body = dict(garb_body) -garb_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "garb") +garb_body = desc_variant(garb_body, obje_attrs) def get(): return garb_def diff --git a/reclaimer/stubbs/defs/jpt_.py b/reclaimer/stubbs/defs/jpt_.py index 103ec039..425e78af 100644 --- a/reclaimer/stubbs/defs/jpt_.py +++ b/reclaimer/stubbs/defs/jpt_.py @@ -9,16 +9,9 @@ from ...hek.defs.jpt_ import * from ..common_descs import * -from supyr_struct.util import desc_variant -damage = desc_variant(damage, - ("category", SEnum16("category", *damage_category)) - ) - -jpt__body = desc_variant(jpt__body, - ("damage", damage), - ("damage_modifiers", damage_modifiers) - ) +damage = desc_variant(damage, SEnum16("category", *damage_category)) +jpt__body = desc_variant(jpt__body, damage, damage_modifiers) def get(): diff --git a/reclaimer/stubbs/defs/lifi.py b/reclaimer/stubbs/defs/lifi.py index d54afdaa..516b1618 100644 --- a/reclaimer/stubbs/defs/lifi.py +++ b/reclaimer/stubbs/defs/lifi.py @@ -11,17 +11,8 @@ from .obje import * from .devi import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=9) - -lifi_body = Struct("tagdata", - obje_attrs, - devi_attrs, - - SIZE=720, - ) +obje_attrs = obje_attrs_variant(obje_attrs, "lifi") +lifi_body = desc_variant(lifi_body, obje_attrs) def get(): return lifi_def diff --git a/reclaimer/stubbs/defs/mach.py b/reclaimer/stubbs/defs/mach.py index 205834dc..1c33d92c 100644 --- a/reclaimer/stubbs/defs/mach.py +++ b/reclaimer/stubbs/defs/mach.py @@ -11,18 +11,8 @@ from .obje import * from .devi import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=7) - -mach_body = Struct("tagdata", - obje_attrs, - devi_attrs, - mach_attrs, - - SIZE=804, - ) +obje_attrs = obje_attrs_variant(obje_attrs, "mach") +mach_body = desc_variant(mach_body, obje_attrs, devi_attrs) def get(): return mach_def diff --git a/reclaimer/stubbs/defs/matg.py b/reclaimer/stubbs/defs/matg.py index 74d920c6..be5daed8 100644 --- a/reclaimer/stubbs/defs/matg.py +++ b/reclaimer/stubbs/defs/matg.py @@ -14,29 +14,9 @@ def get(): return matg_def -matg_body = Struct('tagdata', - Pad(248), - reflexive("sounds", sound, 2, - "enter water", "exit water"), - reflexive("cameras", camera, 1), - reflexive("player_controls", player_control, 1), - reflexive("difficulties", difficulty, 1), +matg_body = desc_variant(matg_body, reflexive("grenades", grenade, len(grenade_types), *grenade_types), - reflexive("rasterizer_datas", rasterizer_data, 1), - reflexive("interface_bitmaps", interface_bitmaps, 1), - reflexive("cheat_weapons", cheat_weapon, 20, - DYN_NAME_PATH='.weapon.filepath'), - reflexive("cheat_powerups", cheat_powerup, 20, - DYN_NAME_PATH='.powerup.filepath'), - reflexive("multiplayer_informations", multiplayer_information, 1), - reflexive("player_informations", player_information, 1), - reflexive("first_person_interfaces", first_person_interface, 1), - reflexive("falling_damages", falling_damage, 1), reflexive("materials", material, len(materials_list), *materials_list), - reflexive("playlist_members", playlist_member, 20, - DYN_NAME_PATH='.map_name'), - - SIZE=428 ) matg_def = TagDef("matg", diff --git a/reclaimer/stubbs/defs/mode.py b/reclaimer/stubbs/defs/mode.py index b1af81fd..6bb14167 100644 --- a/reclaimer/stubbs/defs/mode.py +++ b/reclaimer/stubbs/defs/mode.py @@ -27,65 +27,49 @@ def get(): # SIZE=64 # ) -pc_part = desc_variant(part, - ("model_meta_info", model_meta_info), - ) +pc_part = desc_variant(part, model_meta_info) fast_part = desc_variant(part, - ("uncompressed_vertices", raw_reflexive("uncompressed_vertices", fast_uncompressed_vertex)), - ("compressed_vertices", raw_reflexive("compressed_vertices", fast_compressed_vertex)), - ("triangles", raw_reflexive("triangles", triangle)), - ) -fast_pc_part = desc_variant(fast_part, - ("model_meta_info", model_meta_info), + raw_reflexive("uncompressed_vertices", fast_uncompressed_vertex), + raw_reflexive("compressed_vertices", fast_compressed_vertex), + raw_reflexive("triangles", triangle), ) +fast_pc_part = desc_variant(fast_part, model_meta_info) -pc_geometry = desc_variant(geometry, - ("parts", reflexive("parts", pc_part, 32)), - ) -fast_geometry = desc_variant(geometry, - ("parts", reflexive("parts", fast_part, 32)), - ) -fast_pc_geometry = desc_variant(geometry, - ("parts", reflexive("parts", fast_pc_part, 32)), - ) +pc_geometry = desc_variant(geometry, reflexive("parts", pc_part, 32)) +fast_geometry = desc_variant(geometry, reflexive("parts", fast_part, 32)) +fast_pc_geometry = desc_variant(geometry, reflexive("parts", fast_pc_part, 32)) mode_body = desc_variant(mode_body, ("pad_16", reflexive("unknown", unknown_struct, DYN_NAME_PATH=".name")), ) -pc_mode_body = desc_variant(mode_body, - ("geometries", reflexive("geometries", pc_geometry, 256)) - ) -fast_mode_body = desc_variant(mode_body, - ("geometries", reflexive("geometries", fast_geometry, 256)) - ) -fast_pc_mode_body = desc_variant(mode_body, - ("geometries", reflexive("geometries", fast_pc_geometry, 256)) - ) +pc_mode_body = desc_variant(mode_body, reflexive("geometries", pc_geometry, 256)) +fast_mode_body = desc_variant(mode_body, reflexive("geometries", fast_geometry, 256)) +fast_pc_mode_body = desc_variant(mode_body, reflexive("geometries", fast_pc_geometry, 256)) -mode_def = TagDef("mode", - blam_header_stubbs('mode', 6), # increment to differentiate it from mode and mod2 - mode_body, +# increment version to differentiate from halo models +stubbs_mode_header = blam_header_stubbs('mode', 6) +stubbs_mode_kwargs = dict(ext=".model", endian=">", tag_cls=ModeTag) - ext=".model", endian=">", tag_cls=ModeTag +mode_def = TagDef("mode", + stubbs_mode_header, + mode_body, + **stubbs_mode_kwargs ) fast_mode_def = TagDef("mode", - blam_header_stubbs('mode', 6), # increment to differentiate it from mode and mod2 - fast_mode_body, - - ext=".model", endian=">", tag_cls=ModeTag + stubbs_mode_header, + fast_mode_body, + **stubbs_mode_kwargs ) pc_mode_def = TagDef("mode", - blam_header_stubbs('mode', 6), # increment to differentiate it from mode and mod2 + stubbs_mode_header, pc_mode_body, - - ext=".model", endian=">", tag_cls=ModeTag + **stubbs_mode_kwargs ) fast_pc_mode_def = TagDef("mode", - blam_header_stubbs('mode', 6), # increment to differentiate it from mode and mod2 + stubbs_mode_header, fast_pc_mode_body, - - ext=".model", endian=">", tag_cls=ModeTag + **stubbs_mode_kwargs ) diff --git a/reclaimer/stubbs/defs/obje.py b/reclaimer/stubbs/defs/obje.py index 42276461..b46386d0 100644 --- a/reclaimer/stubbs/defs/obje.py +++ b/reclaimer/stubbs/defs/obje.py @@ -9,14 +9,11 @@ from ...hek.defs.obje import * from ..common_descs import * -from supyr_struct.util import desc_variant def get(): return obje_def -obje_attrs = desc_variant(obje_attrs, - ("model", dependency_stubbs('model', 'mode')), - ) +obje_attrs = desc_variant(obje_attrs, dependency_stubbs('model', 'mode')) obje_body = Struct('tagdata', obje_attrs, diff --git a/reclaimer/stubbs/defs/objs/scnr.py b/reclaimer/stubbs/defs/objs/scnr.py new file mode 100644 index 00000000..87db1491 --- /dev/null +++ b/reclaimer/stubbs/defs/objs/scnr.py @@ -0,0 +1,13 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from reclaimer.hek.defs.objs.scnr import ScnrTag + +class StubbsScnrTag(ScnrTag): + engine = "stubbs" diff --git a/reclaimer/stubbs/defs/plac.py b/reclaimer/stubbs/defs/plac.py index e781221b..79214cb9 100644 --- a/reclaimer/stubbs/defs/plac.py +++ b/reclaimer/stubbs/defs/plac.py @@ -10,13 +10,8 @@ from ...hek.defs.plac import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=10) - -plac_body = dict(plac_body) -plac_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "plac") +plac_body = desc_variant(plac_body, obje_attrs) def get(): return plac_def diff --git a/reclaimer/stubbs/defs/proj.py b/reclaimer/stubbs/defs/proj.py index 9f45f995..98051fd8 100644 --- a/reclaimer/stubbs/defs/proj.py +++ b/reclaimer/stubbs/defs/proj.py @@ -10,20 +10,13 @@ from ...hek.defs.proj import * from ..common_descs import * from .obje import * -from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=5) +obje_attrs = obje_attrs_variant(obje_attrs, "proj") material_responses = reflexive("material_responses", material_response, len(materials_list), *materials_list ) -proj_attrs = desc_variant(proj_attrs, - ("material_responses", material_responses) - ) +proj_attrs = desc_variant(proj_attrs, material_responses) proj_body = Struct("tagdata", obje_attrs, diff --git a/reclaimer/stubbs/defs/sbsp.py b/reclaimer/stubbs/defs/sbsp.py index 3816dac0..6a657ffb 100644 --- a/reclaimer/stubbs/defs/sbsp.py +++ b/reclaimer/stubbs/defs/sbsp.py @@ -9,52 +9,24 @@ from ...hek.defs.sbsp import * from ..common_descs import * -from supyr_struct.util import desc_variant - -cluster = Struct("cluster", - SInt16('sky'), - SInt16('fog'), - dyn_senum16('background_sound', - DYN_NAME_PATH="tagdata.background_sounds_palette.STEPTREE[DYN_I].name"), - dyn_senum16('sound_environment', - DYN_NAME_PATH="tagdata.sound_environments_palette." + - "STEPTREE[DYN_I].name"), - dyn_senum16('weather', - DYN_NAME_PATH="tagdata.weather_palettes.STEPTREE[DYN_I].name"), - - UInt16("transition_structure_bsp", VISIBLE=False), - UInt16("first_decal_index", VISIBLE=False), - UInt16("decal_count", VISIBLE=False), - QStruct("unknown0", - Float('float_0'), - Float('float_1'), - Float('float_2'), - Float('float_3'), - Float('float_4'), - Float('float_5'), - SIZE=24 - ), - - reflexive("predicted_resources", predicted_resource, 1024), - reflexive("subclusters", subcluster, 4096), - SInt16("first_lens_flare_marker_index"), - SInt16("lens_flare_marker_count"), - - # stubbs seems to have different data here than surface indices - Pad(12), - reflexive("mirrors", mirror, 16, DYN_NAME_PATH=".shader.filepath"), - reflexive("portals", portal, 128), - SIZE=104 +cluster_unknown = QStruct("unknown", + Float('float_0'), + Float('float_1'), + Float('float_2'), + Float('float_3'), + Float('float_4'), + Float('float_5'), + SIZE=24 ) +surface_indices = desc_variant(reflexive_struct, NAME="surface_indices") + +cluster = desc_variant(cluster, ("pad_8", cluster_unknown), surface_indices) +clusters = reflexive("clusters", cluster, 8192) -sbsp_body = desc_variant(sbsp_body, - ("clusters", reflexive("clusters", cluster, 8192)), - ) -fast_sbsp_body = desc_variant(fast_sbsp_body, - ("clusters", reflexive("clusters", cluster, 8192)), - ) +sbsp_body = desc_variant(sbsp_body, clusters) +fast_sbsp_body = desc_variant(fast_sbsp_body, clusters) def get(): diff --git a/reclaimer/stubbs/defs/scen.py b/reclaimer/stubbs/defs/scen.py index bf619557..4c508784 100644 --- a/reclaimer/stubbs/defs/scen.py +++ b/reclaimer/stubbs/defs/scen.py @@ -10,13 +10,8 @@ from ...hek.defs.scen import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=6) - -scen_body = dict(scen_body) -scen_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "scen") +scen_body = desc_variant(scen_body, obje_attrs) def get(): return scen_def diff --git a/reclaimer/stubbs/defs/scnr.py b/reclaimer/stubbs/defs/scnr.py index 5ccac64f..e2d2d19d 100644 --- a/reclaimer/stubbs/defs/scnr.py +++ b/reclaimer/stubbs/defs/scnr.py @@ -8,3 +8,22 @@ # from ...hek.defs.scnr import * +from ..common_descs import * +from .objs.scnr import StubbsScnrTag + +reference = desc_variant(reference, dependency_stubbs("reference")) + +scnr_body = desc_variant(scnr_body, + reflexive("references", reference, 256, DYN_NAME_PATH='.reference.filepath'), + ) + +def get(): + return scnr_def + +# TODO: update dependencies +scnr_def = TagDef("scnr", + blam_header('scnr', 2), + scnr_body, + + ext=".scenario", endian=">", tag_cls=StubbsScnrTag + ) diff --git a/reclaimer/stubbs/defs/shdr.py b/reclaimer/stubbs/defs/shdr.py index f67a0bcd..9350d1c4 100644 --- a/reclaimer/stubbs/defs/shdr.py +++ b/reclaimer/stubbs/defs/shdr.py @@ -9,12 +9,8 @@ from ...hek.defs.shdr import * from ..common_descs import * -from supyr_struct.util import desc_variant - -shdr_attrs = desc_variant(shdr_attrs, - ("material_type", SEnum16("material_type", *materials_list)), - ) +shdr_attrs = desc_variant(shdr_attrs, SEnum16("material_type", *materials_list)) shader_body = Struct("tagdata", shdr_attrs, SIZE=40 diff --git a/reclaimer/stubbs/defs/soso.py b/reclaimer/stubbs/defs/soso.py index d547151e..29927f03 100644 --- a/reclaimer/stubbs/defs/soso.py +++ b/reclaimer/stubbs/defs/soso.py @@ -10,11 +10,10 @@ from ...hek.defs.soso import * from .shdr import * from supyr_struct.defs.tag_def import TagDef -from supyr_struct.util import desc_variant soso_attrs = desc_variant(soso_attrs, - ("reflection_bump_scale", Float("bump_scale")), - ("reflection_bump_map", dependency_stubbs("bump_map", "bitm")), + ("pad_11", Float("bump_scale")), + ("pad_12", dependency_stubbs("bump_map", "bitm")), ) soso_body = Struct("tagdata", diff --git a/reclaimer/stubbs/defs/sotr.py b/reclaimer/stubbs/defs/sotr.py index 6b004099..4276c520 100644 --- a/reclaimer/stubbs/defs/sotr.py +++ b/reclaimer/stubbs/defs/sotr.py @@ -14,7 +14,6 @@ sotr_body = Struct("tagdata", shdr_attrs, sotr_attrs, - SIZE=108, ) diff --git a/reclaimer/stubbs/defs/spla.py b/reclaimer/stubbs/defs/spla.py index 11bce9ac..00a7c9db 100644 --- a/reclaimer/stubbs/defs/spla.py +++ b/reclaimer/stubbs/defs/spla.py @@ -17,7 +17,6 @@ SIZE=332, ) - def get(): return spla_def diff --git a/reclaimer/stubbs/defs/ssce.py b/reclaimer/stubbs/defs/ssce.py index 3fb9ac38..5426a514 100644 --- a/reclaimer/stubbs/defs/ssce.py +++ b/reclaimer/stubbs/defs/ssce.py @@ -10,13 +10,8 @@ from ...hek.defs.ssce import * from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=11) - -ssce_body = dict(ssce_body) -ssce_body[0] = obje_attrs +obje_attrs = obje_attrs_variant(obje_attrs, "ssce") +ssce_body = desc_variant(ssce_body, obje_attrs) def get(): return ssce_def diff --git a/reclaimer/stubbs/defs/tagc.py b/reclaimer/stubbs/defs/tagc.py index 6e3f05af..90522e11 100644 --- a/reclaimer/stubbs/defs/tagc.py +++ b/reclaimer/stubbs/defs/tagc.py @@ -8,3 +8,23 @@ # from ...hek.defs.tagc import * +from ..common_descs import dependency_stubbs + +tag_reference = desc_variant(tag_reference, dependency_stubbs("tag")) + +tagc_body = desc_variant(tagc_body, + reflexive("tag_references", tag_reference, 200, + DYN_NAME_PATH='.tag.filepath' + ) + ) + + +def get(): + return tagc_def + +tagc_def = TagDef("tagc", + blam_header('tagc'), + tagc_body, + + ext=".tag_collection", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/stubbs/defs/unhi.py b/reclaimer/stubbs/defs/unhi.py index e9d79e9b..bcf694cd 100644 --- a/reclaimer/stubbs/defs/unhi.py +++ b/reclaimer/stubbs/defs/unhi.py @@ -8,3 +8,36 @@ # from ...hek.defs.unhi import * +from ..common_descs import dependency_stubbs + + +health_panel_meter = desc_variant(health_panel_meter, + SIZE=148, verify=False + ) + +auxilary_meter = desc_variant(auxilary_meter, + SEnum16("type", + "integrated_light", + "unknown", + VISIBLE=False + ), + ) + +unhi_body = desc_variant(unhi_body, + health_panel_meter, + # yeah, they're swapped around + ("warning_sounds", reflexive("auxilary_meters", auxilary_meter, 16)), + ("auxilary_meters", dependency_stubbs("screen_effect", "imef")), + verify=False + ) + + +def get(): + return unhi_def + +unhi_def = TagDef("unhi", + blam_header("unhi"), + unhi_body, + + ext=".unit_hud_interface", endian=">", tag_cls=HekTag, + ) \ No newline at end of file diff --git a/reclaimer/stubbs/defs/vehi.py b/reclaimer/stubbs/defs/vehi.py index d45b4ce0..b23d4602 100644 --- a/reclaimer/stubbs/defs/vehi.py +++ b/reclaimer/stubbs/defs/vehi.py @@ -13,17 +13,9 @@ from ...hek.defs.vehi import * from .obje import * from .unit import * -from ..common_descs import * -from supyr_struct.util import desc_variant -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=1) - -vehi_attrs = desc_variant(vehi_attrs, - ("type", SEnum16('type', *vehicle_types)) - ) +obje_attrs = obje_attrs_variant(obje_attrs, "vehi") +vehi_attrs = desc_variant(vehi_attrs, SEnum16('type', *vehicle_types)) vehi_body = Struct("tagdata", obje_attrs, diff --git a/reclaimer/stubbs/defs/weap.py b/reclaimer/stubbs/defs/weap.py index e04bdfeb..a240b3fb 100644 --- a/reclaimer/stubbs/defs/weap.py +++ b/reclaimer/stubbs/defs/weap.py @@ -8,21 +8,11 @@ # from ...hek.defs.weap import * -from .obje import * from .item import * -from ..common_descs import * -from supyr_struct.util import desc_variant - -# replace the object_type enum one that uses -# the correct default value for this object -obje_attrs = dict(obje_attrs) -obje_attrs[0] = dict(obje_attrs[0], DEFAULT=2) +from .obje import * -# replace the object_type enum one that uses -# the correct default value for this object -weap_attrs = desc_variant(weap_attrs, - ("weapon_type", SEnum16('weapon_type', *weapon_types)) - ) +obje_attrs = obje_attrs_variant(obje_attrs, "weap") +weap_attrs = desc_variant(weap_attrs, SEnum16('weapon_type', *weapon_types)) weap_body = Struct("tagdata", obje_attrs, diff --git a/reclaimer/stubbs/defs/wphi.py b/reclaimer/stubbs/defs/wphi.py index 6e303145..ae1a3206 100644 --- a/reclaimer/stubbs/defs/wphi.py +++ b/reclaimer/stubbs/defs/wphi.py @@ -8,3 +8,22 @@ # from ...hek.defs.wphi import * +from ..common_descs import dependency_stubbs + + +wphi_body = desc_variant(wphi_body, + ("pad_11", Pad(16)), + ("screen_effect", dependency_stubbs("screen_effect", "imef")), + verify=False + ) + + +def get(): + return wphi_def + +wphi_def = TagDef("wphi", + blam_header("wphi", 2), + wphi_body, + + ext=".weapon_hud_interface", endian=">", tag_cls=WphiTag, + ) \ No newline at end of file From 7f5bcfa5c71d8638ba42b850e8bca5a9df9d1fca Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sat, 10 Feb 2024 14:01:59 -0600 Subject: [PATCH 25/51] bump version --- reclaimer/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reclaimer/__init__.py b/reclaimer/__init__.py index cfcd3212..197f17de 100644 --- a/reclaimer/__init__.py +++ b/reclaimer/__init__.py @@ -12,8 +12,8 @@ # ############## __author__ = "Sigmmma" # YYYY.MM.DD -__date__ = "2024.01.26" -__version__ = (2, 14, 0) +__date__ = "2024.02.10" +__version__ = (2, 15, 0) __website__ = "https://github.com/Sigmmma/reclaimer" __all__ = ( "animation", "bitmaps", "h2", "h3", "halo_script", "hek", "meta", "misc", From b76edc9df6d9ea15e39eca05fed77742442a1c06 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sat, 10 Feb 2024 14:06:08 -0600 Subject: [PATCH 26/51] Include shader permutation in model dumps --- reclaimer/model/jms/file.py | 5 ++++- reclaimer/model/jms/material.py | 8 +++++++- reclaimer/model/jms/merged_model.py | 6 ++++-- reclaimer/model/model_compilation.py | 20 ++++++-------------- reclaimer/model/model_decompilation.py | 8 +++++--- 5 files changed, 26 insertions(+), 21 deletions(-) diff --git a/reclaimer/model/jms/file.py b/reclaimer/model/jms/file.py index 328ba349..c90aba6a 100644 --- a/reclaimer/model/jms/file.py +++ b/reclaimer/model/jms/file.py @@ -531,7 +531,10 @@ def write_jms(filepath, jms_model, use_blitzkrieg_rounding=False): f.write("%s\n" % len(materials)) for mat in materials: - f.write("%s\n%s\n" % (mat.name + mat.properties, mat.tiff_path)) + f.write("%s%s%d\n%s\n" % ( + mat.name, mat.properties, mat.permutation_name, + mat.tiff_path + )) f.write("%s\n" % len(jms_model.markers)) for marker in jms_model.markers: diff --git a/reclaimer/model/jms/material.py b/reclaimer/model/jms/material.py index b1df6d08..659f188f 100644 --- a/reclaimer/model/jms/material.py +++ b/reclaimer/model/jms/material.py @@ -14,10 +14,15 @@ class JmsMaterial: __slots__ = ( "name", "tiff_path", "shader_path", "shader_type", - "properties" + "properties", "permutation_index" ) def __init__(self, name="__unnamed", tiff_path="", shader_path="", shader_type="", properties=""): + permutation_index = "0" + while name[-1:] in "0123456789": + permutation_index += name[-1] + name = name[:-1] + for c in "!@#$%^&*-.": if c in name and c not in properties: properties += c @@ -28,6 +33,7 @@ def __init__(self, name="__unnamed", tiff_path="", self.shader_path = shader_path if shader_path else name self.shader_type = shader_type self.properties = properties + self.permutation_index = int(permutation_index) @property def ai_defeaning(self): return "&" in self.properties diff --git a/reclaimer/model/jms/merged_model.py b/reclaimer/model/jms/merged_model.py index 76fa0c57..970a90c1 100644 --- a/reclaimer/model/jms/merged_model.py +++ b/reclaimer/model/jms/merged_model.py @@ -75,8 +75,10 @@ def merge_jms_model(self, other_model): for mat in other_model.materials: self.materials.append( JmsMaterial( - mat.name, mat.tiff_path, mat.shader_path, - mat.shader_type, mat.properties) + mat.name + str(mat.permutation_index), + mat.tiff_path, mat.shader_path, + mat.shader_type, mat.properties + ) ) self.regions = {} diff --git a/reclaimer/model/model_compilation.py b/reclaimer/model/model_compilation.py index 8ee4447e..e2ee50d4 100644 --- a/reclaimer/model/model_compilation.py +++ b/reclaimer/model/model_compilation.py @@ -66,30 +66,22 @@ def compile_gbxmodel(mod2_tag, merged_jms, ignore_errors=False): node.pos_x**2 + node.pos_y**2 + node.pos_z**2) / SCALE_INTERNAL_TO_JMS - # record shader ordering and permutation indices + # make shader references mod2_shaders = tagdata.shaders.STEPTREE - shdr_perm_indices_by_name = {} - for mod2_shader in mod2_shaders: - shdr_name = mod2_shader.shader.filepath.split("\\")[-1] - shdr_perm_indices = shdr_perm_indices_by_name.setdefault(shdr_name, []) - shdr_perm_indices.append(mod2_shader.permutation_index) - del mod2_shaders[:] - # make shader references for mat in merged_jms.materials: mod2_shaders.append() mod2_shader = mod2_shaders[-1] - mod2_shader.shader.filepath = mat.shader_path + + mod2_shader.shader.filepath = mat.shader_path + mod2_shader.permutation_index = mat.permutation_index + if mat.shader_type: mod2_shader.shader.tag_class.set_to(mat.shader_type) else: mod2_shader.shader.tag_class.set_to("shader") shdr_name = mod2_shader.shader.filepath.split("\\")[-1].lower() - shdr_perm_indices = shdr_perm_indices_by_name.get(shdr_name) - if shdr_perm_indices: - mod2_shader.permutation_index = shdr_perm_indices.pop(0) - # make regions mod2_regions = tagdata.regions.STEPTREE @@ -295,4 +287,4 @@ def compile_gbxmodel(mod2_tag, merged_jms, ignore_errors=False): i += 2 # calculate final bits of data before saving - mod2_tag.calc_internal_data() \ No newline at end of file + mod2_tag.calc_internal_data() diff --git a/reclaimer/model/model_decompilation.py b/reclaimer/model/model_decompilation.py index c1b2ce2e..a5d3804f 100644 --- a/reclaimer/model/model_decompilation.py +++ b/reclaimer/model/model_decompilation.py @@ -82,9 +82,11 @@ def extract_model(tagdata, tag_path="", **kw): )) for b in tagdata.shaders.STEPTREE: - materials.append(JmsMaterial( - b.shader.filepath.split("/")[-1].split("\\")[-1]) - ) + shader_name = b.shader.filepath.replace("/", "\\").split("\\")[-1] + if b.permutation_index != 0: + shader_name += str(b.permutation_index) + + materials.append(JmsMaterial(shader_name)) markers_by_perm = {} geoms_by_perm_lod_region = {} From 69272e4643c2c4c009945a2cbac45daab6660a6e Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sat, 10 Feb 2024 14:06:35 -0600 Subject: [PATCH 27/51] bump version --- reclaimer/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reclaimer/__init__.py b/reclaimer/__init__.py index 197f17de..94c0b8bb 100644 --- a/reclaimer/__init__.py +++ b/reclaimer/__init__.py @@ -13,7 +13,7 @@ __author__ = "Sigmmma" # YYYY.MM.DD __date__ = "2024.02.10" -__version__ = (2, 15, 0) +__version__ = (2, 15, 1) __website__ = "https://github.com/Sigmmma/reclaimer" __all__ = ( "animation", "bitmaps", "h2", "h3", "halo_script", "hek", "meta", "misc", From d34bd9f540690e152e6a521df6c8e0c198f079ab Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sat, 10 Feb 2024 21:26:26 -0600 Subject: [PATCH 28/51] Fix bitmap extraction --- reclaimer/meta/wrappers/halo1_rsrc_map.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/reclaimer/meta/wrappers/halo1_rsrc_map.py b/reclaimer/meta/wrappers/halo1_rsrc_map.py index cc3a4362..c921cfc4 100644 --- a/reclaimer/meta/wrappers/halo1_rsrc_map.py +++ b/reclaimer/meta/wrappers/halo1_rsrc_map.py @@ -347,6 +347,9 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): bitm_tag.sanitize_bitmaps() bitm_tag.set_swizzled(False) bitm_tag.add_bitmap_padding(False) + + # serialize the pixel_data and replace the parsed block with it + meta.processed_pixel_data.data = meta.processed_pixel_data.data.serialize() except Exception: print(format_exc()) print("Failed to convert xbox bitmap data to pc.") @@ -358,9 +361,6 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): bitmap.pixels_meta_size = bitmap.bitmap_data_pointer = 0 bitmap.bitmap_id_unknown1 = bitmap.bitmap_id_unknown2 = 0 - # serialize the pixel_data and replace the parsed block with it - meta.processed_pixel_data.data = meta.processed_pixel_data.data.serialize() - elif tag_cls == "snd!": meta.maximum_bend_per_second = meta.maximum_bend_per_second ** 30 meta.unknown1 = 0xFFFFFFFF From cceb9cd6f464fb5e80727cd4fe2ce5c33ff533aa Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sat, 10 Feb 2024 21:26:50 -0600 Subject: [PATCH 29/51] bump version --- reclaimer/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reclaimer/__init__.py b/reclaimer/__init__.py index 94c0b8bb..aae55d1c 100644 --- a/reclaimer/__init__.py +++ b/reclaimer/__init__.py @@ -13,7 +13,7 @@ __author__ = "Sigmmma" # YYYY.MM.DD __date__ = "2024.02.10" -__version__ = (2, 15, 1) +__version__ = (2, 15, 2) __website__ = "https://github.com/Sigmmma/reclaimer" __all__ = ( "animation", "bitmaps", "h2", "h3", "halo_script", "hek", "meta", "misc", From c5b7d7f080c4c87aa05f70c14e07b70d8c9dac9b Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sun, 11 Feb 2024 17:37:55 -0600 Subject: [PATCH 30/51] Change how we define max element count --- reclaimer/bitmaps/bitmap_compilation.py | 20 +++++++---- reclaimer/meta/wrappers/halo1_map.py | 46 ++++++++++++------------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/reclaimer/bitmaps/bitmap_compilation.py b/reclaimer/bitmaps/bitmap_compilation.py index d5f8a998..06969060 100644 --- a/reclaimer/bitmaps/bitmap_compilation.py +++ b/reclaimer/bitmaps/bitmap_compilation.py @@ -13,6 +13,7 @@ from arbytmap.bitmap_io import get_channel_order_by_masks,\ get_channel_swap_mapping, swap_array_items from supyr_struct.defs.bitmaps.dds import dds_def +from reclaimer.util import get_block_max __all__ = ("compile_bitmap_from_dds_files", "add_bitmap_to_bitmap_tag", "parse_dds_file", ) @@ -45,16 +46,20 @@ def add_bitmap_to_bitmap_tag(bitm_tag, width, height, depth, typ, fmt, mip_count, new_pixels, seq_name=""): bitm_data = bitm_tag.data.tagdata sequences = bitm_data.sequences.STEPTREE - bitmaps = bitm_data.bitmaps.STEPTREE - seq_name = seq_name[: 31] + bitmaps = bitm_data.bitmaps.STEPTREE + seq_name = seq_name[: 31] - if len(bitmaps) >= 2048: - raise ValueError("Cannot add more bitmaps(max of 2048 per tag).") + max_bitmaps = get_block_max(bitm_data.bitmaps) + max_sequences = get_block_max(bitm_data.sequences) + max_pixel_bytes = get_block_max(bitm_data.processed_pixel_data) + + if len(bitmaps) >= max_bitmaps: + raise ValueError("Cannot add more bitmaps(max of %s per tag)." % max_bitmaps) bitmaps.append() if not sequences or sequences[-1].sequence_name != seq_name: - if len(sequences) >= 256: - print("Cannot add more sequences(max of 256 per tag).") + if len(sequences) >= max_sequences: + print("Cannot add more sequences(max of %s per tag)." % max_sequences) else: sequences.append() sequences[-1].sequence_name = seq_name @@ -102,6 +107,9 @@ def add_bitmap_to_bitmap_tag(bitm_tag, width, height, depth, typ, fmt, bitm_block.pixels_offset = len(bitm_data.processed_pixel_data.data) + if len(bitm_data.processed_pixel_data.data) + len(new_pixels) >= max_pixel_bytes: + raise ValueError("Cannot add more pixel data(max of %s bytes per tag)." % max_pixel_bytes) + # place the pixels from the dds tag into the bitmap tag bitm_data.processed_pixel_data.data += new_pixels diff --git a/reclaimer/meta/wrappers/halo1_map.py b/reclaimer/meta/wrappers/halo1_map.py index 72b509d6..0320cb33 100644 --- a/reclaimer/meta/wrappers/halo1_map.py +++ b/reclaimer/meta/wrappers/halo1_map.py @@ -8,7 +8,9 @@ # import os +import sys +from array import array as PyArray from copy import deepcopy from math import pi, log from pathlib import Path @@ -44,7 +46,7 @@ from reclaimer.constants import tag_class_fcc_to_ext from reclaimer.util.compression import compress_normal32, decompress_normal32 from reclaimer.util import is_overlapping_ranges, is_valid_ascii_name_str,\ - int_to_fourcc + int_to_fourcc, get_block_max from supyr_struct.util import is_path_empty @@ -536,10 +538,9 @@ def clean_tag_meta(self, meta, tag_id, tag_cls): next_anim = animations[next_anim].next_animation anims_to_remove = [] - + max_anim_count = get_block_max(meta.animations) for i in range(len(animations)): - if self.engine != "halo1yelo" and i >= 256: - # cap it to the non-OS limit of 256 animations + if i >= max_anim_count: break anim = animations[i] @@ -640,23 +641,24 @@ def clean_tag_meta(self, meta, tag_id, tag_cls): bsps.extend(node.bsps.STEPTREE) for bsp in bsps: - highest_used_vert = -1 - edge_data = bsp.edges.STEPTREE vert_data = bsp.vertices.STEPTREE - for i in range(0, len(edge_data), 24): - v0_i = (edge_data[i] + - (edge_data[i + 1] << 8) + - (edge_data[i + 2] << 16) + - (edge_data[i + 3] << 24)) - v1_i = (edge_data[i + 4] + - (edge_data[i + 5] << 8) + - (edge_data[i + 6] << 16) + - (edge_data[i + 7] << 24)) - highest_used_vert = max(highest_used_vert, v0_i, v1_i) - - if highest_used_vert * 16 < len(vert_data): - del vert_data[(highest_used_vert + 1) * 16: ] - bsp.vertices.size = highest_used_vert + 1 + # first 2 ints in each edge are the vert indices, and theres + # 6 int32s per edge. find the highest vert index being used + if bsp.edges.STEPTREE: + byteorder = 'big' if engine == "halo1anni" else 'little' + + edges = PyArray("i", bsp.edges.STEPTREE) + if byteorder != sys.byteorder: + edges.byteswap() + + max_start_vert = max(edges[0: len(edges): 6]) + max_end_vert = max(edges[1: len(edges): 6]) + else: + max_start_vert = max_end_vert = -1 + + if max_start_vert * 16 < len(vert_data): + del vert_data[(max_start_vert + 1) * 16: ] + bsp.vertices.size = max_start_vert + 1 elif tag_cls in ("mode", "mod2"): used_shaders = set() @@ -1101,9 +1103,7 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): else: generate_verts = kwargs.get("generate_comp_verts", False) - endian = "<" - if engine == "halo1anni": - endian = ">" + endian = ">" if engine == "halo1anni" else "<" comp_norm = compress_normal32 decomp_norm = decompress_normal32 From 134c7e1879af0ea6781753cf21d95636351799cf Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sun, 11 Feb 2024 17:38:40 -0600 Subject: [PATCH 31/51] bump version --- reclaimer/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reclaimer/__init__.py b/reclaimer/__init__.py index aae55d1c..e330b5dc 100644 --- a/reclaimer/__init__.py +++ b/reclaimer/__init__.py @@ -12,8 +12,8 @@ # ############## __author__ = "Sigmmma" # YYYY.MM.DD -__date__ = "2024.02.10" -__version__ = (2, 15, 2) +__date__ = "2024.02.11" +__version__ = (2, 15, 3) __website__ = "https://github.com/Sigmmma/reclaimer" __all__ = ( "animation", "bitmaps", "h2", "h3", "halo_script", "hek", "meta", "misc", From e3d071d497c9230716e2e7cbbe8e809c171e05f9 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sun, 11 Feb 2024 18:12:44 -0600 Subject: [PATCH 32/51] Specify size for firing block --- reclaimer/hek/defs/item.py | 2 -- reclaimer/hek/defs/weap.py | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/reclaimer/hek/defs/item.py b/reclaimer/hek/defs/item.py index 9e9ba123..9b4c8d6e 100644 --- a/reclaimer/hek/defs/item.py +++ b/reclaimer/hek/defs/item.py @@ -10,8 +10,6 @@ from ...common_descs import * from .objs.tag import HekTag from supyr_struct.defs.tag_def import TagDef -# import here so it can be reused in all variants of item -from supyr_struct.util import desc_variant message_index_comment = """MESSAGE INDEX This sets which string from tags\\ui\\hud\\hud_item_messages.unicode_string_list to display.""" diff --git a/reclaimer/hek/defs/weap.py b/reclaimer/hek/defs/weap.py index 4bb2814b..6ad1d756 100644 --- a/reclaimer/hek/defs/weap.py +++ b/reclaimer/hek/defs/weap.py @@ -89,6 +89,7 @@ from_to_zero_to_one("error"), float_sec("error_acceleration_time"), float_sec("error_deceleration_time"), + SIZE=60 ) trigger = Struct("trigger", From d6a193f4386f792bc865411ef694bb4a9e32ca6e Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sun, 11 Feb 2024 19:03:33 -0600 Subject: [PATCH 33/51] Add support for older animation import files --- reclaimer/__init__.py | 4 +- reclaimer/animation/animation_compilation.py | 16 +-- reclaimer/animation/jma.py | 115 ++++++++++++++--- reclaimer/animation/util.py | 25 ++-- reclaimer/hek/defs/antr.py | 1 + reclaimer/meta/wrappers/halo1_anni_map.py | 2 + reclaimer/model/constants.py | 17 ++- reclaimer/model/jms/file.py | 124 ++++++++++++++----- reclaimer/model/jms/material.py | 6 +- reclaimer/model/jms/model.py | 15 ++- reclaimer/model/jms/node.py | 16 +-- reclaimer/model/jms/vertex.py | 16 ++- reclaimer/os_hek/defs/magy.py | 28 +---- reclaimer/util/__init__.py | 8 ++ 14 files changed, 281 insertions(+), 112 deletions(-) diff --git a/reclaimer/__init__.py b/reclaimer/__init__.py index e330b5dc..23639820 100644 --- a/reclaimer/__init__.py +++ b/reclaimer/__init__.py @@ -12,8 +12,8 @@ # ############## __author__ = "Sigmmma" # YYYY.MM.DD -__date__ = "2024.02.11" -__version__ = (2, 15, 3) +__date__ = "2021.03.23" +__version__ = (2, 11, 2) __website__ = "https://github.com/Sigmmma/reclaimer" __all__ = ( "animation", "bitmaps", "h2", "h3", "halo_script", "hek", "meta", "misc", diff --git a/reclaimer/animation/animation_compilation.py b/reclaimer/animation/animation_compilation.py index 35ca39f3..3d463b5e 100644 --- a/reclaimer/animation/animation_compilation.py +++ b/reclaimer/animation/animation_compilation.py @@ -39,9 +39,9 @@ def compile_animation(anim, jma_anim, ignore_size_limits=False, endian=">"): default_data_size = jma_anim.default_data_size frame_data_size = jma_anim.frame_data_frame_size * stored_frame_count - max_frame_info_size = anim.frame_info.get_desc('MAX', 'size') - max_default_data_size = anim.default_data.get_desc('MAX', 'size') - max_frame_data_size = anim.frame_data.get_desc('MAX', 'size') + max_frame_info_size = util.get_block_max(anim.frame_info) + max_default_data_size = util.get_block_max(anim.default_data) + max_frame_data_size = util.get_block_max(anim.frame_data) if not ignore_size_limits: if frame_info_size > max_frame_info_size: @@ -103,10 +103,10 @@ def compile_animation(anim, jma_anim, ignore_size_limits=False, endian=">"): return errors -def compile_model_animations(antr_tag, jma_anim_set, ignore_size_limits=False, - animation_count_limit=256, delta_tolerance=None, - update_mode=ANIMATION_COMPILE_MODE_PRESERVE, - mod2_nodes=None): +def compile_model_animations( + antr_tag, jma_anim_set, ignore_size_limits=False, delta_tolerance=None, + update_mode=ANIMATION_COMPILE_MODE_PRESERVE, mod2_nodes=None + ): errors = [] tagdata = antr_tag.data.tagdata @@ -220,7 +220,7 @@ def compile_model_animations(antr_tag, jma_anim_set, ignore_size_limits=False, anim_index = antr_indices_by_type_strings.get( name_pieces, len(antr_anims)) - if anim_index >= animation_count_limit: + if anim_index >= util.get_block_max(tagdata.animations): errors.append( "Too many animations. Cannot add '%s'" % jma_anim_name) continue diff --git a/reclaimer/animation/jma.py b/reclaimer/animation/jma.py index 311d005d..ae2cc274 100644 --- a/reclaimer/animation/jma.py +++ b/reclaimer/animation/jma.py @@ -27,6 +27,18 @@ ".jma", ".jmm", ".jmo", ".jmr", ".jmt", ".jmw", ".jmz", ) +JMA_VER_HALO_1_OLDEST_KNOWN = 16390 +JMA_VER_HALO_1_NODE_NAMES = 16391 +JMA_VER_HALO_1_NODE_HIERARCHY = 16392 +JMA_VER_HALO_1_RETAIL = JMA_VER_HALO_1_NODE_HIERARCHY + +JMA_VER_ALL = frozenset(( + JMA_VER_HALO_1_OLDEST_KNOWN, + JMA_VER_HALO_1_NODE_NAMES, + JMA_VER_HALO_1_NODE_HIERARCHY, + JMA_VER_HALO_1_RETAIL, + )) + def get_anim_ext(anim_type, frame_info_type, world_relative=False): anim_type = anim_type.lower() @@ -150,6 +162,7 @@ class JmaAnimation: node_list_checksum = 0 nodes = () frames = () + version = 0 rot_keyframes = () trans_keyframes = () @@ -173,7 +186,9 @@ class JmaAnimation: def __init__(self, name="", node_list_checksum=0, anim_type="", frame_info_type="", world_relative=False, - nodes=None, frames=None, actors=None, frame_rate=30): + nodes=None, frames=None, actors=None, frame_rate=30, + version=JMA_VER_HALO_1_RETAIL, + ): self.name = name.strip(" ") self.node_list_checksum = node_list_checksum @@ -184,6 +199,7 @@ def __init__(self, name="", node_list_checksum=0, self.frame_info_type = frame_info_type self.frame_rate = frame_rate self.actors = actors if actors else ["unnamedActor"] + self.version = version self.root_node_info = [] self.setup_keyframes() @@ -203,6 +219,11 @@ def has_keyframe_data(self): return False return True + @property + def has_node_names(self): return self.version >= JMA_VER_HALO_1_NODE_NAMES + @property + def has_node_hierarchy(self): return self.version >= JMA_VER_HALO_1_NODE_HIERARCHY + @property def ext(self): return get_anim_ext(self.anim_type, self.frame_info_type, @@ -438,8 +459,27 @@ def calculate_root_node_info(self): z += dz yaw += dyaw - verify_nodes_valid = JmsModel.verify_nodes_valid - get_node_depths = JmsModel.get_node_depths + def get_node_depths(self): + if self.has_node_hierarchy: + return JmsModel.get_node_depths(self) + return [] + + def verify_nodes_valid(self): + errors = [] + if self.has_node_hierarchy or len(self.nodes) not in range(1, 64): + return JmsModel.verify_nodes_valid(self) + elif self.has_node_names: + seen_names = set() + for i in range(len(self.nodes)): + n = self.nodes[i] + if len(n.name) >= 32: + errors.append("Node name node '%s' is too long." % n.name) + elif n.name.lower() in seen_names: + errors.append("Multiple nodes named '%s'." % n.name) + + seen_names.add(n.name.lower()) + + return errors def calculate_animation_flags(self, tolerance=None): if tolerance is None: @@ -486,9 +526,15 @@ def verify_animations_match(self, other_jma): errors.append("Node counts do not match.") return errors - for i in range(len(self.nodes)): - if not self.nodes[i].is_node_hierarchy_equal(other_jma.nodes[i]): - errors.append("Nodes '%s' do not match." % i) + if self.has_node_names and other_jma.has_node_names: + for i in range(len(self.nodes)): + if not self.nodes[i].is_node_hierarchy_equal( + other_jma.nodes[i], not ( + self.has_node_hierarchy and + other_jma.has_node_hierarchy + ) + ): + errors.append("Nodes '%s' do not match." % i) return errors @@ -516,6 +562,7 @@ class JmaAnimationSet: node_list_checksum = 0 nodes = () animations = () + version = JMA_VER_HALO_1_OLDEST_KNOWN def __init__(self, *jma_animations): self.nodes = [] @@ -526,14 +573,27 @@ def __init__(self, *jma_animations): verify_animations_match = JmaAnimation.verify_animations_match + @property + def has_node_names(self): return self.version >= JMA_VER_HALO_1_NODE_NAMES + @property + def has_node_hierarchy(self): return self.version >= JMA_VER_HALO_1_NODE_HIERARCHY + def merge_jma_animation(self, other_jma): assert isinstance(other_jma, JmaAnimation) if not other_jma: return + if other_jma.version > self.version and self.nodes: + # if the other jma's version is newer, grab its nodes if they + # match well enough, since they'll contain more detailed data + if (self.node_list_checksum == other_jma.node_list_checksum and + not self.verify_animations_match(other_jma)): + self.nodes = [] + if not self.nodes: self.node_list_checksum = other_jma.node_list_checksum + self.version = other_jma.version self.nodes = [] for node in other_jma.nodes: self.nodes.append( @@ -559,15 +619,18 @@ def read_jma(jma_string, stop_at="", anim_name=""): anim_name, ext = os.path.splitext(anim_name) anim_type, frame_info_type, world_relative = get_anim_types(ext) - jma_anim = JmaAnimation(anim_name, 0, anim_type, - frame_info_type, world_relative) jma_string = jma_string.replace("\n", "\t") data = tuple(d for d in jma_string.split("\t") if d) dat_i = 0 + version = parse_jm_int(data[dat_i]) + + jma_anim = JmaAnimation(anim_name, 0, anim_type, + frame_info_type, world_relative, + version=version) - if parse_jm_int(data[dat_i]) != 16392: - print("JMA identifier '16392' not found.") + if version not in JMA_VER_ALL: + print("Unknown JMA version '%s' found." % version) return jma_anim dat_i += 1 @@ -651,10 +714,20 @@ def read_jma(jma_string, stop_at="", anim_name=""): i = 0 # make sure i is defined in case of exception nodes[:] = [None] * node_count for i in range(node_count): - nodes[i] = JmsNode( - data[dat_i], parse_jm_int(data[dat_i+1]), parse_jm_int(data[dat_i+2]) - ) - dat_i += 3 + if jma_anim.has_node_hierarchy: + node_data = ( + data[dat_i], + parse_jm_int(data[dat_i+1]), + parse_jm_int(data[dat_i+2]) + ) + dat_i += 3 + elif jma_anim.has_node_names: + node_data = (data[dat_i], ) + dat_i += 1 + else: + node_data = ("fake_node_%d" % i, ) + + nodes[i] = JmsNode(*node_data) JmsNode.setup_node_hierarchy(nodes) except Exception: print(traceback.format_exc()) @@ -709,7 +782,7 @@ def write_jma(filepath, jma_anim, use_blitzkrieg_rounding=False): jma_anim.apply_root_node_info_to_states() with filepath.open("w", encoding='latin1', newline="\r\n") as f: - f.write("16392\n") # version number + f.write("%d\n" % jma_anim.version) f.write("%s\n" % jma_anim.frame_count) f.write("%s\n" % jma_anim.frame_rate) @@ -724,10 +797,14 @@ def write_jma(filepath, jma_anim, use_blitzkrieg_rounding=False): f.write("%s\n" % len(nodes)) f.write("%s\n" % int(jma_anim.node_list_checksum)) - for node in nodes: - f.write("%s\n%s\n%s\n" % - (node.name[: 31], node.first_child, node.sibling_index) - ) + if jma_anim.has_node_hierarchy: + for node in nodes: + f.write("%s\n%s\n%s\n" % + (node.name[: 31], node.first_child, node.sibling_index) + ) + elif jma_anim.has_node_names: + for node in nodes: + f.write("%s\n" % node.name[: 31]) for frame in frames: for nf in frame: diff --git a/reclaimer/animation/util.py b/reclaimer/animation/util.py index f4a5b11a..ac284f3f 100644 --- a/reclaimer/animation/util.py +++ b/reclaimer/animation/util.py @@ -11,9 +11,10 @@ from math import pi +from reclaimer.util import get_block_max from reclaimer.enums import unit_animation_names, unit_weapon_animation_names,\ unit_weapon_type_animation_names, vehicle_animation_names,\ - weapon_animation_names, device_animation_names, fp_animation_names,\ + weapon_animation_names, device_animation_names, fp_animation_names_mcc,\ unit_damage_animation_names from reclaimer.hek.defs.mod2 import TagDef, Pad, marker as mod2_marker_desc,\ node as mod2_node_desc, reflexive, Struct @@ -89,7 +90,7 @@ def split_anim_name_into_type_strings(anim_name): if part3: remainder = " ".join((part3, remainder)) part2, part2_sani, perm_num = split_permutation_number(part2, remainder) - if ((part1_sani == "first-person" and part2_sani in fp_animation_names) or + if ((part1_sani == "first-person" and part2_sani in fp_animation_names_mcc) or (part1_sani == "vehicle" and part2_sani in vehicle_animation_names) or (part1_sani == "device" and part2_sani in device_animation_names)): type_strings = part1_sani, part2_sani, perm_num @@ -215,9 +216,10 @@ def set_animation_index(antr_tag, anim_name, anim_index, if part1 == "suspension": anim_name = anim_name.split(" ", 1)[1] - anim_enums = antr_vehicles[0].suspension_animations.STEPTREE - if len(anim_enums) >= 8: - # max of 8 suspension animations + susp_anims = antr_vehicles[0].suspension_animations + anim_enums = susp_anims.STEPTREE + if len(anim_enums) >= get_block_max(susp_anims): + # at the limit of suspension animations return False anim_enums.append() @@ -226,7 +228,8 @@ def set_animation_index(antr_tag, anim_name, anim_index, elif part1 in ("first-person", "device", "vehicle"): if part1 == "first-person": - options = fp_animation_names + # NOTE: using mcc because they're the same, with an extension + options = fp_animation_names_mcc block = antr_fp_animations elif part1 == "device": options = device_animation_names @@ -238,6 +241,10 @@ def set_animation_index(antr_tag, anim_name, anim_index, if not block: block.append() + # trim the options to how many are actually allowed + # NOTE: this is really just for fp_animation_names_mcc + max_anims = get_block_max(block[0].animations) + options = options[:max_anims] try: enum_index = options.index(part2) except ValueError: @@ -338,7 +345,7 @@ def set_animation_index(antr_tag, anim_name, anim_index, break if unit is None: - if len(antr_units) >= antr_units.MAX: + if len(antr_units) >= get_block_max(antr_units.parent): return False antr_units.append() @@ -369,7 +376,7 @@ def set_animation_index(antr_tag, anim_name, anim_index, break if unit_weap is None: - if len(unit_weaps) >= unit_weaps.MAX: + if len(unit_weaps) >= get_block_max(unit_weaps.parent): return False unit_weaps.append() @@ -397,7 +404,7 @@ def set_animation_index(antr_tag, anim_name, anim_index, break if unit_weap_type is None: - if len(unit_weap_types) >= unit_weap_types.MAX: + if len(unit_weap_types) >= get_block_max(unit_weap_types.parent): return False unit_weap_types.append() diff --git a/reclaimer/hek/defs/antr.py b/reclaimer/hek/defs/antr.py index 84d62ee2..8abc7535 100644 --- a/reclaimer/hek/defs/antr.py +++ b/reclaimer/hek/defs/antr.py @@ -292,6 +292,7 @@ Pad(2), reflexive("nodes", nodes_desc, 64, DYN_NAME_PATH=".name"), reflexive("animations", animation_desc, 256, DYN_NAME_PATH=".name"), + Pad(0), # replaced with stock_animation in magy tag class in os_hek SIZE=128, ) diff --git a/reclaimer/meta/wrappers/halo1_anni_map.py b/reclaimer/meta/wrappers/halo1_anni_map.py index b7be7fd6..fef512ff 100644 --- a/reclaimer/meta/wrappers/halo1_anni_map.py +++ b/reclaimer/meta/wrappers/halo1_anni_map.py @@ -201,6 +201,8 @@ def get_meta(self, tag_id, reextract=False, ignore_rsrc_sounds=False, **kw): return meta def byteswap_anniversary_fields(self, meta, tag_cls): + # TODO: use byteswapping.py to quickly handle a lot of this + # due to it possibly having accelerators available. if tag_cls == "antr": unpack_header = PyStruct("<11i").unpack for b in meta.animations.STEPTREE: diff --git a/reclaimer/model/constants.py b/reclaimer/model/constants.py index ee338788..73b70fad 100644 --- a/reclaimer/model/constants.py +++ b/reclaimer/model/constants.py @@ -31,5 +31,18 @@ SCALE_INTERNAL_TO_JMS = 100.0 -JMS_VERSION_HALO_1 = "8200" -JMS_VERSION_HALO_2_8210 = "8210" +JMS_VER_HALO_1_OLDEST_KNOWN = 8197 +JMS_VER_HALO_1_TRI_REGIONS = 8198 +JMS_VER_HALO_1_3D_UVWS = 8199 +JMS_VER_HALO_1_MARKER_RADIUS = 8200 +JMS_VER_HALO_1_RETAIL = JMS_VER_HALO_1_MARKER_RADIUS +JMS_VER_HALO_2_RETAIL = 8210 + +JMS_VER_ALL = frozenset(( + JMS_VER_HALO_1_OLDEST_KNOWN, + JMS_VER_HALO_1_TRI_REGIONS, + JMS_VER_HALO_1_3D_UVWS, + JMS_VER_HALO_1_MARKER_RADIUS, + JMS_VER_HALO_1_RETAIL, + JMS_VER_HALO_2_RETAIL, + )) \ No newline at end of file diff --git a/reclaimer/model/jms/file.py b/reclaimer/model/jms/file.py index c90aba6a..655309e7 100644 --- a/reclaimer/model/jms/file.py +++ b/reclaimer/model/jms/file.py @@ -17,8 +17,9 @@ from pathlib import Path from reclaimer.model.constants import ( - JMS_VERSION_HALO_1, - JMS_VERSION_HALO_2_8210, + JMS_VER_HALO_1_OLDEST_KNOWN, + JMS_VER_HALO_1_RETAIL, + JMS_VER_HALO_2_RETAIL, JMS_PERM_CANNOT_BE_RANDOMLY_CHOSEN_TOKEN, ) from reclaimer.util import ( @@ -37,14 +38,24 @@ # https://regex101.com/r/ySgI1Z/1 JMS_V2_SPLIT_REGEX = re.compile(r'\n+(;.*)*\s*|\t+') + def read_jms(jms_string, stop_at="", perm_name=None): ''' Converts a jms string into a JmsModel instance. ''' - jms_data = tuple(JMS_V1_SPLIT_REGEX.split(jms_string)) + jms_string = jms_string.lstrip("\ufeff") + jms_data = tuple(JMS_V1_SPLIT_REGEX.split(jms_string)) + try: + version = int(jms_data[0].strip()) + except ValueError: + version = 0 - version = jms_data[0].strip() - if version == JMS_VERSION_HALO_1: + if (version < JMS_VER_HALO_1_OLDEST_KNOWN or + version > JMS_VER_HALO_2_RETAIL): + print("Unknown JMS version '%s'" % version) + return None + + if version <= JMS_VER_HALO_1_RETAIL: # Halo 1 return _read_jms_8200(jms_data, stop_at, perm_name) @@ -52,16 +63,12 @@ def read_jms(jms_string, stop_at="", perm_name=None): # is a semicolon, and the line must start with it. # filter out any lines that start with a semicolon. - jms_string = jms_string.lstrip("\ufeff") jms_data = tuple(JMS_V2_SPLIT_REGEX.split(jms_string)) version = jms_data[0].strip() - if version == JMS_VERSION_HALO_2_8210: + if version <= JMS_VER_HALO_2_RETAIL: # Halo 2 return _read_jms_8210(jms_data, stop_at) - else: - print("Unknown JMS version '%s'" % version) - return None def _read_jms_8200(jms_data, stop_at="", perm_name=None): @@ -74,7 +81,7 @@ def _read_jms_8200(jms_data, stop_at="", perm_name=None): dat_i = 0 try: - jms_model.version = str(parse_jm_int(jms_data[dat_i])) + jms_model.version = int(parse_jm_int(jms_data[dat_i])) dat_i += 1 except Exception: print(traceback.format_exc()) @@ -139,15 +146,25 @@ def _read_jms_8200(jms_data, stop_at="", perm_name=None): jms_model.markers[:] = (None, ) * parse_jm_int(jms_data[dat_i]) dat_i += 1 for i in range(len(jms_model.markers)): + marker_name = jms_data[dat_i] + region = 0 + radius = 1 + if jms_model.has_marker_regions: + region = parse_jm_int(jms_data[dat_i+1]) + dat_i += 1 + + if jms_model.has_marker_radius: + radius = parse_jm_float(jms_data[dat_i+9]) + jms_model.markers[i] = JmsMarker( - jms_data[dat_i], jms_model.name, - parse_jm_int(jms_data[dat_i+1]), parse_jm_int(jms_data[dat_i+2]), - parse_jm_float(jms_data[dat_i+3]), parse_jm_float(jms_data[dat_i+4]), - parse_jm_float(jms_data[dat_i+5]), parse_jm_float(jms_data[dat_i+6]), - parse_jm_float(jms_data[dat_i+7]), parse_jm_float(jms_data[dat_i+8]), parse_jm_float(jms_data[dat_i+9]), - parse_jm_float(jms_data[dat_i+10]) + marker_name, jms_model.name, + region, parse_jm_int(jms_data[dat_i+1]), + parse_jm_float(jms_data[dat_i+2]), parse_jm_float(jms_data[dat_i+3]), + parse_jm_float(jms_data[dat_i+4]), parse_jm_float(jms_data[dat_i+5]), + parse_jm_float(jms_data[dat_i+6]), parse_jm_float(jms_data[dat_i+7]), parse_jm_float(jms_data[dat_i+8]), + radius ) - dat_i += 11 + dat_i += 9 + jms_model.has_marker_radius except Exception: print(traceback.format_exc()) print("Failed to read markers.") @@ -178,6 +195,15 @@ def _read_jms_8200(jms_data, stop_at="", perm_name=None): jms_model.verts[:] = (None, ) * parse_jm_int(jms_data[dat_i]) dat_i += 1 for i in range(len(jms_model.verts)): + region = 0 + w_coord = 0 + if jms_model.has_vert_regions: + region = parse_jm_int(jms_data[dat_i]) + dat_i += 1 + + if jms_model.has_3d_uvws: + w_coord = parse_jm_int(jms_data[dat_i+11]) + jms_model.verts[i] = JmsVertex( parse_jm_int(jms_data[dat_i]), parse_jm_float(jms_data[dat_i+1]), parse_jm_float(jms_data[dat_i+2]), parse_jm_float(jms_data[dat_i+3]), @@ -186,9 +212,10 @@ def _read_jms_8200(jms_data, stop_at="", perm_name=None): min(1.0, max(-1.0, parse_jm_float(jms_data[dat_i+5]))), min(1.0, max(-1.0, parse_jm_float(jms_data[dat_i+6]))), parse_jm_int(jms_data[dat_i+7]), parse_jm_float(jms_data[dat_i+8]), - parse_jm_float(jms_data[dat_i+9]), parse_jm_float(jms_data[dat_i+10]), parse_jm_float(jms_data[dat_i+11]) + parse_jm_float(jms_data[dat_i+9]), parse_jm_float(jms_data[dat_i+10]), w_coord, + region=region ) - dat_i += 12 + dat_i += 11 + jms_model.has_3d_uvws except Exception: print(traceback.format_exc()) print("Failed to read vertices.") @@ -203,17 +230,38 @@ def _read_jms_8200(jms_data, stop_at="", perm_name=None): jms_model.tris[:] = (None, ) * parse_jm_int(jms_data[dat_i]) dat_i += 1 for i in range(len(jms_model.tris)): + region = 0 + if jms_model.has_face_regions: + region = parse_jm_int(jms_data[dat_i]) + dat_i += 1 + jms_model.tris[i] = JmsTriangle( - parse_jm_int(jms_data[dat_i]), parse_jm_int(jms_data[dat_i+1]), - parse_jm_int(jms_data[dat_i+2]), parse_jm_int(jms_data[dat_i+3]), parse_jm_int(jms_data[dat_i+4]), + region, parse_jm_int(jms_data[dat_i]), + parse_jm_int(jms_data[dat_i+1]), parse_jm_int(jms_data[dat_i+2]), parse_jm_int(jms_data[dat_i+3]), ) - dat_i += 5 + dat_i += 4 except Exception: print(traceback.format_exc()) print("Failed to read triangles.") del jms_model.tris[i: ] stop = True + # copy the region from the verts into triangles, or + # from the triangles into the verts + try: + verts = jms_model.verts + if jms_model.has_vert_regions: + for tri in jms_model.tris: + tri.region = verts[tri.v0].region + else: + for tri in jms_model.tris: + verts[tri.v0].region = tri.region + verts[tri.v1].region = tri.region + verts[tri.v2].region = tri.region + except Exception: + print(traceback.format_exc()) + print("Failed to copy regions between vertices and triangles.") + # return (jms_models[name], ) return jms_model @@ -532,20 +580,25 @@ def write_jms(filepath, jms_model, use_blitzkrieg_rounding=False): f.write("%s\n" % len(materials)) for mat in materials: f.write("%s%s%d\n%s\n" % ( - mat.name, mat.properties, mat.permutation_name, + mat.name, mat.properties, mat.permutation_index, mat.tiff_path )) f.write("%s\n" % len(jms_model.markers)) for marker in jms_model.markers: - f.write("%s\n%s\n%s\n%s\t%s\t%s\t%s\n%s\t%s\t%s\n%s\n" % ( - marker.name[: 31], marker.region, marker.parent, + f.write("%s\n" % marker.name[: 31]) + if jms_model.has_marker_regions: + f.write("%s\n" % marker.region) + + f.write("%s\n%s\t%s\t%s\t%s\n%s\t%s\t%s\n" % ( + marker.parent, to_str(marker.rot_i), to_str(marker.rot_j), to_str(marker.rot_k), to_str(marker.rot_w), - to_str(marker.pos_x), to_str(marker.pos_y), to_str(marker.pos_z), - to_str(marker.radius) - ) - ) + to_str(marker.pos_x), to_str(marker.pos_y), to_str(marker.pos_z) + )) + + if jms_model.has_marker_radius: + f.write("%s\n" % to_str(marker.radius)) f.write("%s\n" % len(regions)) for region in regions: @@ -553,6 +606,9 @@ def write_jms(filepath, jms_model, use_blitzkrieg_rounding=False): f.write("%s\n" % len(jms_model.verts)) for vert in jms_model.verts: + if jms_model.has_vert_regions: + f.write("%s\n" % vert.region) + f.write("%s\n%s\t%s\t%s\n%s\t%s\t%s\n%s\n%s\n%s\n%s\n%s\n" % ( vert.node_0, to_str(vert.pos_x), to_str(vert.pos_y), to_str(vert.pos_z), @@ -565,8 +621,10 @@ def write_jms(filepath, jms_model, use_blitzkrieg_rounding=False): f.write("%s\n" % len(jms_model.tris)) for tri in jms_model.tris: - f.write("%s\n%s\n%s\t%s\t%s\n" % ( - tri.region, tri.shader, - tri.v0, tri.v1, tri.v2 + if jms_model.has_marker_regions: + f.write("%s\n" % tri.region) + + f.write("%s\n%s\t%s\t%s\n" % ( + tri.shader, tri.v0, tri.v1, tri.v2 ) ) diff --git a/reclaimer/model/jms/material.py b/reclaimer/model/jms/material.py index 659f188f..f06b07f0 100644 --- a/reclaimer/model/jms/material.py +++ b/reclaimer/model/jms/material.py @@ -98,6 +98,6 @@ def render_only(self, new_val): self.properties = self.properties.replace("!", "") + ("!" if new_val else "") def __repr__(self): - return """JmsMaterial(name=%s, - tiff_path=%s -)""" % (self.name, self.tiff_path) + return """JmsMaterial(name=%s%s, + tiff_path=%s, properties=%s +)""" % (self.name, self.permutation_index, self.tiff_path, self.properties) diff --git a/reclaimer/model/jms/model.py b/reclaimer/model/jms/model.py index 37db7182..aef84cf8 100644 --- a/reclaimer/model/jms/model.py +++ b/reclaimer/model/jms/model.py @@ -12,7 +12,9 @@ import math -from ..constants import JMS_PERM_CANNOT_BE_RANDOMLY_CHOSEN_TOKEN +from ..constants import JMS_PERM_CANNOT_BE_RANDOMLY_CHOSEN_TOKEN,\ + JMS_VER_HALO_1_TRI_REGIONS, JMS_VER_HALO_1_3D_UVWS,\ + JMS_VER_HALO_1_MARKER_RADIUS class JmsModel: @@ -61,6 +63,17 @@ def __init__(self, name="", node_list_checksum=0, nodes=None, self.markers = markers if markers else [] self.verts = verts if verts else [] self.tris = tris if tris else [] + + @property + def has_face_regions(self): return self.version >= JMS_VER_HALO_1_TRI_REGIONS + @property + def has_marker_radius(self): return self.version >= JMS_VER_HALO_1_MARKER_RADIUS + @property + def has_marker_regions(self): return self.has_face_regions + @property + def has_vert_regions(self): return not self.has_marker_regions + @property + def has_3d_uvws(self): return self.version >= JMS_VER_HALO_1_3D_UVWS def calculate_vertex_normals(self): verts = self.verts diff --git a/reclaimer/model/jms/node.py b/reclaimer/model/jms/node.py index 98f8be42..3137246c 100644 --- a/reclaimer/model/jms/node.py +++ b/reclaimer/model/jms/node.py @@ -9,7 +9,7 @@ __all__ = ( 'JmsNode', ) -from ..constants import ( JMS_VERSION_HALO_1, JMS_VERSION_HALO_2_8210, ) +from ..constants import ( JMS_VER_HALO_1_RETAIL, JMS_VER_HALO_2_RETAIL, ) class JmsNode: __slots__ = ( @@ -45,7 +45,7 @@ def __repr__(self): def __eq__(self, other): if not isinstance(other, JmsNode): return False - elif self.name != other.name: + elif self.name.lower() != other.name.lower(): return False elif self.first_child != other.first_child: return False @@ -62,11 +62,13 @@ def __eq__(self, other): return False return True - def is_node_hierarchy_equal(self, other): + def is_node_hierarchy_equal(self, other, name_only=False): if not isinstance(other, JmsNode): return False - elif self.name != other.name: + elif self.name.lower() != other.name.lower(): return False + elif name_only: + pass elif self.first_child != other.first_child: return False elif self.sibling_index != other.sibling_index: @@ -74,8 +76,8 @@ def is_node_hierarchy_equal(self, other): return True @classmethod - def setup_node_hierarchy(cls, nodes, jms_version=JMS_VERSION_HALO_1): - if jms_version == JMS_VERSION_HALO_1: + def setup_node_hierarchy(cls, nodes, jms_version=JMS_VER_HALO_1_RETAIL): + if jms_version == JMS_VER_HALO_1_RETAIL: # Halo 1 parented_nodes = set() # setup the parent node hierarchy @@ -93,6 +95,6 @@ def setup_node_hierarchy(cls, nodes, jms_version=JMS_VERSION_HALO_1): sib_node = nodes[sib_idx] sib_node.parent_index = parent_idx sib_idx = sib_node.sibling_index - elif jms_version == JMS_VERSION_HALO_2_8210: + elif jms_version == JMS_VER_HALO_2_RETAIL: # Halo 2 pass diff --git a/reclaimer/model/jms/vertex.py b/reclaimer/model/jms/vertex.py index 8764f1ad..efe40887 100644 --- a/reclaimer/model/jms/vertex.py +++ b/reclaimer/model/jms/vertex.py @@ -19,7 +19,8 @@ class JmsVertex: "tangent_i", "tangent_j", "tangent_k", "node_1", "node_1_weight", "tex_u", "tex_v", "tex_w", - "other_nodes", "other_weights", "other_uvws" + "other_nodes", "other_weights", "other_uvws", + "region" ) def __init__(self, node_0=0, pos_x=0.0, pos_y=0.0, pos_z=0.0, @@ -28,7 +29,9 @@ def __init__(self, node_0=0, tex_u=0, tex_v=0, tex_w=0, binorm_i=0.0, binorm_j=1.0, binorm_k=0.0, tangent_i=1.0, tangent_j=0.0, tangent_k=0.0, - other_nodes=(), other_weights=(), other_uvws=()): + other_nodes=(), other_weights=(), other_uvws=(), + region=0, + ): if node_1_weight <= 0: node_1 = -1 node_1_weight = 0 @@ -54,18 +57,21 @@ def __init__(self, node_0=0, self.other_nodes = other_nodes self.other_weights = other_weights self.other_uvws = other_uvws + self.region = region def __repr__(self): return """JmsVertex(node_0=%s, x=%s, y=%s, z=%s, i=%s, j=%s, k=%s, node_1=%s, node_1_weight=%s, - u=%s, v=%s, w=%s + u=%s, v=%s, w=%s, + region=%s )""" % (self.node_0, self.pos_x, self.pos_y, self.pos_z, self.norm_i, self.norm_j, self.norm_k, self.node_1, self.node_1_weight, - self.tex_u, self.tex_v, self.tex_w) + self.tex_u, self.tex_v, self.tex_w, + self.region) def __eq__(self, other): if not isinstance(other, JmsVertex): @@ -81,6 +87,8 @@ def __eq__(self, other): return False elif abs(self.node_1_weight - other.node_1_weight) > 0.0001: return False + elif self.region != other.region: + return False elif self.node_0 != other.node_0 or self.node_1 != other.node_1: return False diff --git a/reclaimer/os_hek/defs/magy.py b/reclaimer/os_hek/defs/magy.py index 9e94c898..1db831d3 100644 --- a/reclaimer/os_hek/defs/magy.py +++ b/reclaimer/os_hek/defs/magy.py @@ -8,31 +8,11 @@ # from .objs.magy import MagyTag -from ...hek.defs.antr import * +from .antr import * -magy_body = Struct("tagdata", - reflexive("objects", object_desc, 4), - reflexive("units", unit_desc, 32, DYN_NAME_PATH=".label"), - reflexive("weapons", weapon_desc, 1), - reflexive("vehicles", vehicle_desc, 1), - reflexive("devices", device_desc, 1), - reflexive("unit_damages", anim_enum_desc, 176, - *unit_damage_animation_names - ), - reflexive("fp_animations", fp_animation_desc, 1), - #i have no idea why they decided to cap it at 257 instead of 256.... - reflexive("sound_references", sound_reference_desc, 257, - DYN_NAME_PATH=".sound.filepath"), - Float("limp_body_node_radius"), - Bool16("flags", - "compress_all_animations", - "force_idle_compression", - ), - Pad(2), - reflexive("nodes", nodes_desc, 64, DYN_NAME_PATH=".name"), - reflexive("animations", animation_desc, 2048, DYN_NAME_PATH=".name"), - dependency_os("stock_animation", valid_model_animations_yelo), - SIZE=300, +magy_body = desc_variant(antr_body, + ("pad_13", dependency_os("stock_animation", valid_model_animations_yelo)), + SIZE=300, verify=False ) diff --git a/reclaimer/util/__init__.py b/reclaimer/util/__init__.py index e92c7e96..115604db 100644 --- a/reclaimer/util/__init__.py +++ b/reclaimer/util/__init__.py @@ -4,6 +4,7 @@ from math import log from supyr_struct.util import * +from supyr_struct.exceptions import DescKeyError from reclaimer.util import compression from reclaimer.util import geometry from reclaimer.util import matrices @@ -48,6 +49,13 @@ def get_is_xbox_map(engine): return "xbox" in engine or engine in ("stubbs", "shadowrun_proto") +def get_block_max(block, default=0xFFffFFff): + try: + return block.get_desc('MAX', 'size') + except DescKeyError: + return default + + def float_to_str(f, max_sig_figs=7): if f == POS_INF: return "1000000000000000000000000000000000000000" From b0a71ba8f4e4f66cc7e0321021b87b70730975d0 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sun, 11 Feb 2024 19:05:10 -0600 Subject: [PATCH 34/51] bump version --- reclaimer/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reclaimer/__init__.py b/reclaimer/__init__.py index 23639820..c4b417df 100644 --- a/reclaimer/__init__.py +++ b/reclaimer/__init__.py @@ -12,8 +12,8 @@ # ############## __author__ = "Sigmmma" # YYYY.MM.DD -__date__ = "2021.03.23" -__version__ = (2, 11, 2) +__date__ = "2024.02.11" +__version__ = (2, 16, 0) __website__ = "https://github.com/Sigmmma/reclaimer" __all__ = ( "animation", "bitmaps", "h2", "h3", "halo_script", "hek", "meta", "misc", From 64a1064e48affa14c09139d1edd330e3e6caacbd Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Tue, 20 Feb 2024 07:19:30 -0600 Subject: [PATCH 35/51] Def cleanup and improved OS support --- reclaimer/common_descs.py | 35 ++++- reclaimer/constants.py | 69 ++++++-- reclaimer/enums.py | 4 + reclaimer/field_type_methods.py | 16 +- reclaimer/h3/defs/bitm.py | 5 +- reclaimer/h3/defs/zone.py | 24 +++ reclaimer/halo_script/defs/hsc.py | 5 + reclaimer/hek/defs/antr.py | 6 +- reclaimer/hek/defs/bitm.py | 4 +- reclaimer/hek/defs/coll.py | 12 +- reclaimer/hek/defs/eqip.py | 2 + reclaimer/hek/defs/mod2.py | 52 ++++--- reclaimer/hek/defs/mode.py | 38 +++-- reclaimer/hek/defs/sbsp.py | 30 ++-- reclaimer/hek/defs/scnr.py | 62 ++++---- reclaimer/hek/defs/snd_.py | 2 +- reclaimer/hek/hardcoded_ce_tag_paths.py | 12 ++ reclaimer/mcc_hek/defs/scnr.py | 44 +++--- reclaimer/meta/class_repair.py | 102 ++++++++---- reclaimer/meta/halo1_map.py | 28 +--- reclaimer/meta/halo1_map_fast_functions.py | 5 + reclaimer/meta/halo2_map.py | 35 +---- reclaimer/meta/halo3_map.py | 19 +-- reclaimer/meta/wrappers/halo1_anni_map.py | 11 +- reclaimer/meta/wrappers/halo1_map.py | 147 +++++++++++------- reclaimer/meta/wrappers/halo1_mcc_map.py | 52 ++----- reclaimer/meta/wrappers/halo1_rsrc_map.py | 30 ++-- reclaimer/meta/wrappers/halo1_xbox_map.py | 16 +- reclaimer/meta/wrappers/halo1_yelo.py | 103 +++++++++--- reclaimer/meta/wrappers/halo3_beta_map.py | 5 +- reclaimer/meta/wrappers/halo3_map.py | 6 +- reclaimer/meta/wrappers/halo3_odst_map.py | 23 ++- reclaimer/meta/wrappers/halo4_beta_map.py | 5 +- reclaimer/meta/wrappers/halo4_map.py | 5 +- reclaimer/meta/wrappers/halo5_map.py | 3 + reclaimer/meta/wrappers/halo_map.py | 71 +++++---- .../meta/wrappers/halo_reach_beta_map.py | 5 +- reclaimer/meta/wrappers/halo_reach_map.py | 5 +- reclaimer/meta/wrappers/shadowrun_map.py | 6 +- reclaimer/meta/wrappers/stubbs_map.py | 7 +- reclaimer/meta/wrappers/stubbs_map_64bit.py | 4 +- reclaimer/model/util.py | 6 +- reclaimer/os_hek/defs/antr.py | 4 +- reclaimer/os_hek/defs/yelo.py | 15 +- reclaimer/os_v3_hek/defs/antr.py | 20 +++ reclaimer/os_v4_hek/defs/antr.py | 25 ++- reclaimer/os_v4_hek/defs/gelo.py | 2 +- reclaimer/os_v4_hek/defs/obje.py | 2 - reclaimer/os_v4_hek/defs/part.py | 2 +- reclaimer/os_v4_hek/defs/pctl.py | 2 +- reclaimer/os_v4_hek/defs/senv.py | 2 +- reclaimer/os_v4_hek/defs/unit.py | 30 ++++ reclaimer/os_v4_hek/defs/weap.py | 15 +- reclaimer/shadowrun_prototype/defs/matg.py | 15 ++ reclaimer/stubbs/defs/antr.py | 2 +- reclaimer/stubbs/defs/mode.py | 18 +-- reclaimer/util/__init__.py | 2 +- reclaimer/util/geometry.py | 3 +- 58 files changed, 820 insertions(+), 460 deletions(-) diff --git a/reclaimer/common_descs.py b/reclaimer/common_descs.py index 1496d38a..4ad98073 100644 --- a/reclaimer/common_descs.py +++ b/reclaimer/common_descs.py @@ -57,10 +57,12 @@ def tag_class(*args, **kwargs): ) -def reflexive(name, substruct, max_count=MAX_REFLEXIVE_COUNT, *names, **kwargs): +def reflexive(name, substruct, max_count=MAX_REFLEXIVE_COUNT, *names, + EXT_MAX=SANE_MAX_REFLEXIVE_COUNT, **kwargs): '''This function serves to macro the creation of a reflexive''' + EXT_MAX = max(EXT_MAX, max_count) reflexive_fields = ( - SInt32("size", VISIBLE=VISIBILITY_METADATA, EDITABLE=False, MAX=max_count), + SInt32("size", VISIBLE=VISIBILITY_METADATA, EDITABLE=False, MAX=max_count, EXT_MAX=EXT_MAX), reflexive_struct[1], reflexive_struct[2], ) @@ -69,7 +71,7 @@ def reflexive(name, substruct, max_count=MAX_REFLEXIVE_COUNT, *names, **kwargs): SIZE=".size", SUB_STRUCT=substruct, WIDGET=ReflexiveFrame, # NOTE: also adding max here since various things rely on it # (i.e. compilation/mozz tag block size limit/etc) - MAX=max_count + MAX=max_count, EXT_MAX=EXT_MAX ), SIZE=12 ) @@ -597,6 +599,32 @@ def anim_src_func_per_pha_sca_rot_macro(name, **desc): valid_widgets = tag_class('ant!', 'flag', 'glw!', 'mgs2', 'elec') +# Map related descriptors +map_version = UEnum32("version", + ("halo1xbox", 5), + ("halo1pcdemo", 6), + ("halo1pc", 7), + ("halo2", 8), + ("halo3beta", 9), + ("halo3", 11), + ("haloreach", 12), + ("halo1mcc", 13), + ("halo1ce", 609), + ("halo1vap", 134), + ) +gen1_map_type = UEnum16("map_type", + "sp", + "mp", + "ui", + ) +gen2_map_type = UEnum16("map_type", + "sp", + "mp", + "ui", + "shared", + "sharedsp", + ) + # Descriptors tag_header = Struct("blam_header", @@ -699,6 +727,7 @@ def anim_src_func_per_pha_sca_rot_macro(name, **desc): UInt32("unused", VISIBLE=VISIBILITY_METADATA), ) + extra_layers_block = dependency("extra_layer", valid_shaders) damage_modifiers = QStruct("damage_modifiers", diff --git a/reclaimer/constants.py b/reclaimer/constants.py index 5b211d49..4177688f 100644 --- a/reclaimer/constants.py +++ b/reclaimer/constants.py @@ -98,21 +98,48 @@ def inject_halo_constants(): #"halo5": ????, } -GEN_1_HALO_ENGINES = ("halo1xboxdemo", "halo1xbox", - "halo1ce", "halo1vap", "halo1yelo", - "halo1pcdemo", "halo1pc", "halo1anni", - "halo1mcc", ) - -GEN_1_ENGINES = GEN_1_HALO_ENGINES + ( - "stubbs", "stubbspc", "stubbspc64bit", "shadowrun_proto", +# NOTE: do NOT change these. they are used in various places +# to determine how to read tags/data from different maps +GEN_1_HALO_XBOX_ENGINES = frozenset(( + "halo1xboxdemo", "halo1xbox", + )) +GEN_1_STUBBS_ENGINES = frozenset(( + "stubbs", "stubbspc", "stubbspc64bit", + )) +GEN_1_SHADOWRUN_ENGINES = frozenset(( + "shadowrun_proto", + )) +GEN_1_HALO_CUSTOM_ENGINES = frozenset(( + "halo1ce", "halo1vap", "halo1yelo", "halo1mcc", + )) +GEN_1_HALO_PC_ENGINES = frozenset(( + "halo1pcdemo", "halo1pc", + )) + +GEN_1_HALO_GBX_ENGINES = GEN_1_HALO_PC_ENGINES.union( + GEN_1_HALO_CUSTOM_ENGINES ) -GEN_2_ENGINES = ("halo2alpha", "halo2beta", "halo2epsilon", - "halo2xbox", "halo2vista", ) +GEN_1_HALO_ENGINES = frozenset(( + "halo1anni", + )).union(GEN_1_HALO_GBX_ENGINES)\ + .union(GEN_1_HALO_XBOX_ENGINES) + +GEN_1_ENGINES = GEN_1_HALO_ENGINES\ + .union(GEN_1_STUBBS_ENGINES)\ + .union(GEN_1_SHADOWRUN_ENGINES) -GEN_3_ENGINES = ("halo3", "halo3odst", "halo3beta", - "haloreachbeta", "haloreach", - "halo4", "halo4nettest", "halo5", ) +GEN_2_ENGINES = frozenset(( + "halo2alpha", "halo2beta", "halo2epsilon", + "halo2xbox", "halo2vista", + )) + +# NOTE: these aren't all gen3, but this basically means "anything halo 3 and newer". +GEN_3_ENGINES = frozenset(( + "halo3", "halo3odst", "halo3beta", + "haloreachbeta", "haloreach", + "halo4", "halo4nettest", "halo5", + )) # magic is actually the virtual address the map is loaded at. Halo 3 and # beyond instead partition the map into sections with a virtual address for @@ -191,7 +218,7 @@ def inject_halo_constants(): "UNUSED4", "UNUSED5", "DXT1", "DXT3", "DXT5", "P8-BUMP", "P8", "A32R32G32B32F", "R32G32B32F", "R16G16B16F", - "V8U8", "G8B8", "UNUSED6", "UNUSED7", "UNUSED8", + "V8U8", "G8B8", "UNUSED6", "A16R16G16B16F", "UNUSED8", "UNUSED9", "UNUSED10", "UNUSED11", "UNUSED12", "UNUSED13", "UNUSED14", "DXN", "CTX1", "DXT3A", "DXT3Y", "DXT5A", "DXT5Y", "DXT5AY") MCC_FORMAT_NAME_MAP = FORMAT_NAME_MAP[:FORMAT_NAME_MAP.index("P8")] + ("BC7", ) @@ -214,9 +241,21 @@ def inject_halo_constants(): # max value a reflexive count is theoretically allowed to be MAX_REFLEXIVE_COUNT = 2**31-1 -# this number was taken by seeing what the highest indexable reflexive number -# is. +EXT_MAX = "EXT_MAX" # absolute max number of elements a reflexive can +# contain. enforced even when not using safe mode +# this of this as an "extreme" max value. + +# this number was taken by seeing what the highest indexable +# reflexive number is. SANE_MAX_REFLEXIVE_COUNT = 0xFFFE +SINT24_MAX = 0x7FffFF +UINT16_MAX = 0xFFff +SINT16_MAX = 0x7Fff +UINT8_MAX = 0xFF +SINT24_INDEX_MAX = SINT24_MAX + 1 +UINT16_INDEX_MAX = UINT16_MAX + 1 +SINT16_INDEX_MAX = SINT16_MAX + 1 +UINT8_INDEX_MAX = UINT8_MAX + 1 MAX_TAG_PATH_LEN = 254 diff --git a/reclaimer/enums.py b/reclaimer/enums.py index 28177c4c..ca7a4be4 100644 --- a/reclaimer/enums.py +++ b/reclaimer/enums.py @@ -1283,6 +1283,10 @@ def TEST_PRINT_HSC_BUILT_IN_FUNCTIONS(): 'fleeing' ) +unit_animation_names_os = unit_animation_names + ( + "boarding", "mounted" + ) + # MCC Shared Enumerator options grenade_types_mcc = grenade_types_os # they're the same hud_anchors_mcc = ( diff --git a/reclaimer/field_type_methods.py b/reclaimer/field_type_methods.py index cf37cc21..30a672fb 100644 --- a/reclaimer/field_type_methods.py +++ b/reclaimer/field_type_methods.py @@ -229,19 +229,21 @@ def reflexive_parser(self, desc, node=None, parent=None, attr_index=None, s_desc = desc.get(STEPTREE) if s_desc: pointer_converter = kwargs.get('map_pointer_converter') - safe_mode = kwargs.get("safe_mode", True) and not desc.get(IGNORE_SAFE_MODE) - arr_len_max = desc[0].get(MAX, 0) # get the max value from the size field + safe_mode = kwargs.get("safe_mode", True) and not desc.get(IGNORE_SAFE_MODE) + # get the max value from the size field + arr_len_max = desc[0].get(MAX, 0) + arr_ext_len_max = desc[0].get(EXT_MAX, SANE_MAX_REFLEXIVE_COUNT) + arr_abs_len_max = arr_len_max if (safe_mode and arr_len_max) else arr_ext_len_max if pointer_converter is not None: file_ptr = pointer_converter.v_ptr_to_f_ptr(node[1]) if safe_mode: # make sure the reflexive sizes are within sane bounds. - max_size = max(SANE_MAX_REFLEXIVE_COUNT, arr_len_max) - if node[0] > max_size: + if node[0] > arr_abs_len_max: print("Warning: Clipped %s reflexive size from %s to %s" % ( - desc[NAME], node[0], max_size + desc[NAME], node[0], arr_abs_len_max )) - node[0] = max(SANE_MAX_REFLEXIVE_COUNT, arr_len_max) + node[0] = arr_abs_len_max if (file_ptr < 0 or file_ptr + node[0]*s_desc[SUB_STRUCT].get(SIZE, 0) > len(rawdata)): @@ -249,7 +251,7 @@ def reflexive_parser(self, desc, node=None, parent=None, attr_index=None, # (ex: bad hek+ extraction) node[0] = node[1] = 0 - elif node[0] > max(SANE_MAX_REFLEXIVE_COUNT, arr_len_max): + elif node[0] > arr_abs_len_max: raise ValueError("Reflexive size is above highest allowed value.") if not node[0]: diff --git a/reclaimer/h3/defs/bitm.py b/reclaimer/h3/defs/bitm.py index 7fe27cfd..b1a70066 100644 --- a/reclaimer/h3/defs/bitm.py +++ b/reclaimer/h3/defs/bitm.py @@ -63,10 +63,9 @@ ("rgbfp32", 20), ("rgbfp16", 21), ("v8u8", 22), - "unused23", + "g8b8", "unused24", - "unused25", # ui\halox\main_menu.bkd.bitmap is set to this - # and is palettized with dimensions 231 x 1 x 1 + "a16r16g16b16f", "unused26", "unused27", "unused28", diff --git a/reclaimer/h3/defs/zone.py b/reclaimer/h3/defs/zone.py index ae4d4e48..95e9a18f 100644 --- a/reclaimer/h3/defs/zone.py +++ b/reclaimer/h3/defs/zone.py @@ -234,6 +234,22 @@ ENDIAN=">", SIZE=532 ) +# this defines JUST enough of the tag for bitmaps to be readable +zone_body_odst_partial = Struct("tagdata", + SEnum16("map_type", *zone_map_type), + Pad(2), + h3_reflexive("resource_types", zone_resource_type, + DYN_NAME_PATH='.name.string'), + h3_reflexive("resource_structure_types", zone_resource_structure_type, + DYN_NAME_PATH='.name.string'), + Pad(60), + h3_reflexive("tag_resources", zone_tag_resource), + Pad(12*15), + Pad(36), + h3_rawdata_ref("fixup_info"), + ENDIAN=">" + ) + def get(): return zone_def @@ -244,3 +260,11 @@ def get(): ext=".%s" % h3_tag_class_fcc_to_ext["zone"], endian=">", tag_cls=H3Tag ) + + +zone_def_odst_partial = TagDef("zone", + h3_blam_header('zone'), + zone_body_odst_partial, + + ext=".%s" % h3_tag_class_fcc_to_ext["zone"], endian=">", tag_cls=H3Tag + ) \ No newline at end of file diff --git a/reclaimer/halo_script/defs/hsc.py b/reclaimer/halo_script/defs/hsc.py index 0673ea26..995b45dd 100644 --- a/reclaimer/halo_script/defs/hsc.py +++ b/reclaimer/halo_script/defs/hsc.py @@ -36,6 +36,11 @@ HSC_IS_GARBAGE_COLLECTABLE = 1 << 3 HSC_IS_PARAMETER = 1 << 4 HSC_IS_STRIPPED = 1 << 5 +# yelo +# TODO: do something with supporting these if necessary +HSC_YELO_PARAM_IDX = 1 << 4 +HSC_YELO_LOCAL_IDX = 1 << 5 +HSC_YELO_CONST_IDX = 1 << 6 HSC_IS_SCRIPT_OR_GLOBAL = HSC_IS_SCRIPT_CALL | HSC_IS_GLOBAL script_node_ref = BitStruct("node", diff --git a/reclaimer/hek/defs/antr.py b/reclaimer/hek/defs/antr.py index 8abc7535..bb6a17df 100644 --- a/reclaimer/hek/defs/antr.py +++ b/reclaimer/hek/defs/antr.py @@ -103,7 +103,7 @@ *unit_weapon_animation_names ), reflexive("ik_points", ik_point_desc, 4, DYN_NAME_PATH=".marker"), - reflexive("weapon_types", weapon_types_desc, 10, DYN_NAME_PATH=".label"), + reflexive("weapon_types", weapon_types_desc, 16, DYN_NAME_PATH=".label"), SIZE=188, ) @@ -123,7 +123,7 @@ SInt16("up_frame_count"), Pad(8), - reflexive("animations", anim_enum_desc, 30, + reflexive("animations", anim_enum_desc, len(unit_animation_names), *unit_animation_names ), reflexive("ik_points", ik_point_desc, 4, DYN_NAME_PATH=".marker"), @@ -291,7 +291,7 @@ ), Pad(2), reflexive("nodes", nodes_desc, 64, DYN_NAME_PATH=".name"), - reflexive("animations", animation_desc, 256, DYN_NAME_PATH=".name"), + reflexive("animations", animation_desc, 256, DYN_NAME_PATH=".name", EXT_MAX=2048), Pad(0), # replaced with stock_animation in magy tag class in os_hek SIZE=128, ) diff --git a/reclaimer/hek/defs/bitm.py b/reclaimer/hek/defs/bitm.py index 00ac0300..cde55087 100644 --- a/reclaimer/hek/defs/bitm.py +++ b/reclaimer/hek/defs/bitm.py @@ -144,7 +144,7 @@ def pixel_block_size(node, *a, **kwa): SInt16("first_bitmap_index"), SInt16("bitmap_count"), Pad(16), - reflexive("sprites", sprite, 64), + reflexive("sprites", sprite, 64, EXT_MAX=SINT16_MAX), SIZE=64, ) @@ -275,7 +275,7 @@ def pixel_block_size(node, *a, **kwa): UInt16("sprite_spacing", SIDETIP="pixels"), Pad(2), reflexive("sequences", sequence, 256, - DYN_NAME_PATH='.sequence_name', IGNORE_SAFE_MODE=True), + DYN_NAME_PATH='.sequence_name', IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), reflexive("bitmaps", bitmap, 2048, IGNORE_SAFE_MODE=True), SIZE=108, WIDGET=HaloBitmapTagFrame ) diff --git a/reclaimer/hek/defs/coll.py b/reclaimer/hek/defs/coll.py index ad1fb153..2c996a48 100644 --- a/reclaimer/hek/defs/coll.py +++ b/reclaimer/hek/defs/coll.py @@ -141,10 +141,10 @@ permutation_bsp = Struct("permutation_bsp", reflexive("bsp3d_nodes", bsp3d_node, 131072), - reflexive("planes", plane, 65535), - reflexive("leaves", leaf, 65535), + reflexive("planes", plane, UINT16_INDEX_MAX), + reflexive("leaves", leaf, UINT16_INDEX_MAX), reflexive("bsp2d_references", bsp2d_reference, 131072), - reflexive("bsp2d_nodes", bsp2d_node, 65535), + reflexive("bsp2d_nodes", bsp2d_node, UINT16_INDEX_MAX), reflexive("surfaces", surface, 131072), reflexive("edges", edge, 262144), reflexive("vertices", vertex, 131072), @@ -274,10 +274,10 @@ fast_permutation_bsp = Struct("permutation_bsp", raw_reflexive("bsp3d_nodes", bsp3d_node, 131072), - raw_reflexive("planes", plane, 65535), - raw_reflexive("leaves", leaf, 65535), + raw_reflexive("planes", plane, UINT16_INDEX_MAX), + raw_reflexive("leaves", leaf, UINT16_INDEX_MAX), raw_reflexive("bsp2d_references", bsp2d_reference, 131072), - raw_reflexive("bsp2d_nodes", bsp2d_node, 65535), + raw_reflexive("bsp2d_nodes", bsp2d_node, UINT16_INDEX_MAX), raw_reflexive("surfaces", surface, 131072), raw_reflexive("edges", edge, 262144), raw_reflexive("vertices", vertex, 131072), diff --git a/reclaimer/hek/defs/eqip.py b/reclaimer/hek/defs/eqip.py index 6e9b504b..e737fa06 100644 --- a/reclaimer/hek/defs/eqip.py +++ b/reclaimer/hek/defs/eqip.py @@ -25,6 +25,8 @@ SEnum16('grenade_type', *grenade_types), float_sec('powerup_time'), dependency('pickup_sound', "snd!"), + Pad(144), # looks like open sauce HAD plans for this at one point. + # keeping the padding defined here cause, well, who knows? SIZE=168 ) diff --git a/reclaimer/hek/defs/mod2.py b/reclaimer/hek/defs/mod2.py index faf458ce..6c311bed 100644 --- a/reclaimer/hek/defs/mod2.py +++ b/reclaimer/hek/defs/mod2.py @@ -45,12 +45,12 @@ def get(): UInt32('binormal'), UInt32('tangent'), - SInt16('u', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), - SInt16('v', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), + SInt16('u', UNIT_SCALE=1/SINT16_MAX, MIN=-SINT16_MAX, WIDGET_WIDTH=10), + SInt16('v', UNIT_SCALE=1/SINT16_MAX, MIN=-SINT16_MAX, WIDGET_WIDTH=10), SInt8('node_0_index', UNIT_SCALE=1/3, MIN=0, WIDGET_WIDTH=10), SInt8('node_1_index', UNIT_SCALE=1/3, MIN=0, WIDGET_WIDTH=10), - SInt16('node_0_weight', UNIT_SCALE=1/32767, MIN=0, WIDGET_WIDTH=10), + SInt16('node_0_weight', UNIT_SCALE=1/SINT16_MAX, MIN=0, WIDGET_WIDTH=10), SIZE=32 ) @@ -79,12 +79,12 @@ def get(): BBitStruct('binormal', INCLUDE=compressed_normal_32, SIZE=4), BBitStruct('tangent', INCLUDE=compressed_normal_32, SIZE=4), - SInt16('u', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), - SInt16('v', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), + SInt16('u', UNIT_SCALE=1/SINT16_MAX, MIN=-SINT16_MAX, WIDGET_WIDTH=10), + SInt16('v', UNIT_SCALE=1/SINT16_MAX, MIN=-SINT16_MAX, WIDGET_WIDTH=10), SInt8('node_0_index', UNIT_SCALE=1/3, MIN=0, WIDGET_WIDTH=10), SInt8('node_1_index', UNIT_SCALE=1/3, MIN=0, WIDGET_WIDTH=10), - SInt16('node_0_weight', UNIT_SCALE=1/32767, MIN=0, WIDGET_WIDTH=10), + SInt16('node_0_weight', UNIT_SCALE=1/SINT16_MAX, MIN=0, WIDGET_WIDTH=10), SIZE=32 ) @@ -147,7 +147,7 @@ def get(): DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), Pad(2), - reflexive("local_markers", local_marker, 32, DYN_NAME_PATH=".name"), + reflexive("local_markers", local_marker, 32, DYN_NAME_PATH=".name", EXT_MAX=SINT16_MAX), SIZE=88 ) @@ -191,12 +191,12 @@ def get(): QStruct('centroid_translation', INCLUDE=xyz_float), - #reflexive("uncompressed_vertices", uncompressed_vertex_union, 32767), - #reflexive("compressed_vertices", compressed_vertex_union, 32767), - #reflexive("triangles", triangle_union, 32767), - reflexive("uncompressed_vertices", fast_uncompressed_vertex, 32767), - reflexive("compressed_vertices", fast_compressed_vertex, 32767), - reflexive("triangles", triangle, 32767), + #reflexive("uncompressed_vertices", uncompressed_vertex_union, SINT16_MAX), + #reflexive("compressed_vertices", compressed_vertex_union, SINT16_MAX), + #reflexive("triangles", triangle_union, SINT16_MAX), + reflexive("uncompressed_vertices", fast_uncompressed_vertex, SINT16_MAX), + reflexive("compressed_vertices", fast_compressed_vertex, SINT16_MAX), + reflexive("triangles", triangle, SINT16_MAX), #Pad(36), model_meta_info, @@ -212,9 +212,9 @@ def get(): ) fast_part = desc_variant(part, - ("uncompressed_vertices", raw_reflexive("uncompressed_vertices", fast_uncompressed_vertex, 65535)), - ("compressed_vertices", raw_reflexive("compressed_vertices", fast_compressed_vertex, 65535)), - ("triangles", raw_reflexive("triangles", triangle, 65535)), + raw_reflexive("uncompressed_vertices", fast_uncompressed_vertex, SINT16_MAX), + raw_reflexive("compressed_vertices", fast_compressed_vertex, SINT16_MAX), + raw_reflexive("triangles", triangle, SINT16_MAX), ) marker = Struct('marker', @@ -222,7 +222,7 @@ def get(): UInt16('magic_identifier'), Pad(18), - reflexive("marker_instances", marker_instance, 32), + reflexive("marker_instances", marker_instance, 32, EXT_MAX=SINT16_MAX), SIZE=64 ) @@ -254,19 +254,19 @@ def get(): region = Struct('region', ascii_str32("name"), Pad(32), - reflexive("permutations", permutation, 32, DYN_NAME_PATH=".name"), + reflexive("permutations", permutation, 32, DYN_NAME_PATH=".name", EXT_MAX=UINT8_MAX), SIZE=76 ) geometry = Struct('geometry', Pad(36), - reflexive("parts", part, 32), + reflexive("parts", part, 32, EXT_MAX=SINT16_MAX), SIZE=48 ) fast_geometry = Struct('geometry', Pad(36), - reflexive("parts", fast_part, 32), + reflexive("parts", fast_part, 32, EXT_MAX=SINT16_MAX), SIZE=48 ) @@ -305,16 +305,22 @@ def get(): Pad(116), reflexive("markers", marker, 256, DYN_NAME_PATH=".name", VISIBLE=False), + # NOTE: the extended max for nodes and regions can't be set higher due + # to data structure limits. there is an object struct used at + # runtime that only has enough room to reference 8 regions. for + # nodes, there is only enough room in each animation to flag 64 + # nodes as animated. theoretically you could add another 32 if + # if used neighboring padding, but this is untested, and a strech. reflexive("nodes", node, 64, DYN_NAME_PATH=".name"), reflexive("regions", region, 32, DYN_NAME_PATH=".name"), - reflexive("geometries", geometry, 256), - reflexive("shaders", shader, 256, DYN_NAME_PATH=".shader.filepath"), + reflexive("geometries", geometry, 256, EXT_MAX=SINT16_MAX), + reflexive("shaders", shader, 256, DYN_NAME_PATH=".shader.filepath", EXT_MAX=SINT16_MAX), SIZE=232 ) fast_mod2_body = desc_variant(mod2_body, - ("geometries", reflexive("geometries", fast_geometry, 256)), + reflexive("geometries", fast_geometry, 256), ) mod2_def = TagDef("mod2", diff --git a/reclaimer/hek/defs/mode.py b/reclaimer/hek/defs/mode.py index 966c7d0d..9a1b5dce 100644 --- a/reclaimer/hek/defs/mode.py +++ b/reclaimer/hek/defs/mode.py @@ -42,7 +42,7 @@ def get(): DYN_NAME_PATH="tagdata.geometries.geometries_array[DYN_I].NAME"), Pad(2), - reflexive("local_markers", local_marker, 32, DYN_NAME_PATH=".name"), + reflexive("local_markers", local_marker, 32, DYN_NAME_PATH=".name", EXT_MAX=SINT16_MAX), SIZE=88 ) @@ -84,12 +84,12 @@ def get(): QStruct('centroid_translation', INCLUDE=xyz_float), - #reflexive("uncompressed_vertices", uncompressed_vertex_union, 65535), - #reflexive("compressed_vertices", compressed_vertex_union, 65535), - #reflexive("triangles", triangle_union, 65535), - reflexive("uncompressed_vertices", fast_uncompressed_vertex, 65535), - reflexive("compressed_vertices", fast_compressed_vertex, 65535), - reflexive("triangles", triangle, 65535), + #reflexive("uncompressed_vertices", uncompressed_vertex_union, SINT16_MAX), + #reflexive("compressed_vertices", compressed_vertex_union, SINT16_MAX), + #reflexive("triangles", triangle_union, SINT16_MAX), + reflexive("uncompressed_vertices", fast_uncompressed_vertex, SINT16_MAX), + reflexive("compressed_vertices", fast_compressed_vertex, SINT16_MAX), + reflexive("triangles", triangle, SINT16_MAX), #Pad(36), dip_model_meta_info, @@ -98,27 +98,27 @@ def get(): ) fast_part = desc_variant(part, - ("uncompressed_vertices", raw_reflexive("uncompressed_vertices", fast_uncompressed_vertex, 65535)), - ("compressed_vertices", raw_reflexive("compressed_vertices", fast_compressed_vertex, 65535)), - ("triangles", raw_reflexive("triangles", triangle, 65535)), + raw_reflexive("uncompressed_vertices", fast_uncompressed_vertex, SINT16_MAX), + raw_reflexive("compressed_vertices", fast_compressed_vertex, SINT16_MAX), + raw_reflexive("triangles", triangle, SINT16_MAX), ) region = Struct('region', ascii_str32("name"), Pad(32), - reflexive("permutations", permutation, 32, DYN_NAME_PATH=".name"), + reflexive("permutations", permutation, 32, DYN_NAME_PATH=".name", EXT_MAX=UINT8_MAX), SIZE=76 ) geometry = Struct('geometry', Pad(36), - reflexive("parts", part, 32), + reflexive("parts", part, 32, EXT_MAX=SINT16_MAX), SIZE=48 ) fast_geometry = Struct('geometry', Pad(36), - reflexive("parts", fast_part, 32), + reflexive("parts", fast_part, 32, EXT_MAX=SINT16_MAX), SIZE=48 ) @@ -148,17 +148,23 @@ def get(): Pad(104), Pad(12), # replaced with unknown reflexive in stubbs + # NOTE: the extended max for nodes and regions can't be set higher due + # to data structure limits. there is an object struct used at + # runtime that only has enough room to reference 8 regions. for + # nodes, there is only enough room in each animation to flag 64 + # nodes as animated. theoretically you could add another 32 if + # if used neighboring padding, but this is untested, and a strech. reflexive("markers", marker, 256, DYN_NAME_PATH=".name", VISIBLE=False), reflexive("nodes", node, 64, DYN_NAME_PATH=".name"), reflexive("regions", region, 32, DYN_NAME_PATH=".name"), - reflexive("geometries", geometry, 256), - reflexive("shaders", shader, 256, DYN_NAME_PATH=".shader.filepath"), + reflexive("geometries", geometry, 256, EXT_MAX=SINT16_MAX), + reflexive("shaders", shader, 256, DYN_NAME_PATH=".shader.filepath", EXT_MAX=SINT16_MAX), SIZE=232 ) fast_mode_body = desc_variant(mode_body, - ("geometries", reflexive("geometries", fast_geometry, 256)), + reflexive("geometries", fast_geometry, 256, EXT_MAX=SINT16_MAX), ) mode_def = TagDef("mode", diff --git a/reclaimer/hek/defs/sbsp.py b/reclaimer/hek/defs/sbsp.py index b797e71e..21116f5c 100644 --- a/reclaimer/hek/defs/sbsp.py +++ b/reclaimer/hek/defs/sbsp.py @@ -59,8 +59,8 @@ # this normal is the direction the light is hitting from, and # is used for calculating dynamic shadows on dynamic objects UInt32('normal'), - SInt16('u', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), - SInt16('v', UNIT_SCALE=1/32767, MIN=-32767, WIDGET_WIDTH=10), + SInt16('u', UNIT_SCALE=1/SINT16_MAX, MIN=-SINT16_MAX, WIDGET_WIDTH=10), + SInt16('v', UNIT_SCALE=1/SINT16_MAX, MIN=-SINT16_MAX, WIDGET_WIDTH=10), SIZE=8 ) @@ -265,7 +265,7 @@ reflexive("subclusters", subcluster, 4096), SInt16("first_lens_flare_marker_index"), SInt16("lens_flare_marker_count"), - reflexive("surface_indices", surface_index, 32768), + reflexive("surface_indices", surface_index, SINT16_INDEX_MAX), reflexive("mirrors", mirror, 16, DYN_NAME_PATH=".shader.filepath"), reflexive("portals", portal, 128), SIZE=104 @@ -403,7 +403,7 @@ detail_object = Struct("detail_object", reflexive("cells", detail_object_cell, 262144), reflexive("instances", detail_object_instance, 2097152), - reflexive("counts", detail_object_count, 8388608), + reflexive("counts", detail_object_count, SINT24_INDEX_MAX), reflexive("z_reference_vectors", detail_object_z_reference_vector, 262144), Bool8("flags", "enabled", # required to be set on map compile. @@ -426,8 +426,8 @@ ) leaf_map_leaf = Struct("leaf_map_leaf", - reflexive("faces", face, 256), - reflexive("portal_indices", portal_index, 256), + reflexive("faces", face, UINT8_INDEX_MAX), + reflexive("portal_indices", portal_index, UINT8_INDEX_MAX), SIZE=24 ) @@ -473,23 +473,23 @@ QStruct("world_bounds_x", INCLUDE=from_to), QStruct("world_bounds_y", INCLUDE=from_to), QStruct("world_bounds_z", INCLUDE=from_to), - reflexive("leaves", leaf, 65535), + reflexive("leaves", leaf, UINT16_INDEX_MAX), reflexive("leaf_surfaces", leaf_surface, 262144), reflexive("surfaces", surface, 131072), reflexive("lightmaps", lightmap, 128), Pad(12), - reflexive("lens_flares", lens_flare, 256, + reflexive("lens_flares", lens_flare, UINT8_INDEX_MAX, DYN_NAME_PATH='.shader.filepath'), - reflexive("lens_flare_markers", lens_flare_marker, 65535), + reflexive("lens_flare_markers", lens_flare_marker, UINT16_INDEX_MAX), reflexive("clusters", cluster, 8192), # this is an array of 8 byte structs for each cluster - rawdata_ref("cluster_data", max_size=65536), + rawdata_ref("cluster_data", max_size=UINT16_INDEX_MAX), reflexive("cluster_portals", cluster_portal, 512), Pad(12), - reflexive("breakable_surfaces", breakable_surface, 256), + reflexive("breakable_surfaces", breakable_surface, UINT8_INDEX_MAX), reflexive("fog_planes", fog_plane, 32), reflexive("fog_regions", fog_region, 32), reflexive("fog_palettes", fog_palette, 32, @@ -519,7 +519,7 @@ reflexive("runtime_decals", runtime_decal, 6144, VISIBLE=False), Pad(12), - reflexive("leaf_map_leaves", leaf_map_leaf, 65536, VISIBLE=False), + reflexive("leaf_map_leaves", leaf_map_leaf, UINT16_INDEX_MAX, VISIBLE=False), reflexive("leaf_map_portals", leaf_map_portal, 524288, VISIBLE=False), SIZE=648, ) @@ -527,11 +527,11 @@ fast_sbsp_body = desc_variant(sbsp_body, ("collision_bsp", reflexive("collision_bsp", fast_collision_bsp, 1)), ("nodes", raw_reflexive("nodes", node, 131072)), - ("leaves", raw_reflexive("leaves", leaf, 65535)), + ("leaves", raw_reflexive("leaves", leaf, UINT16_INDEX_MAX)), ("leaf_surfaces", raw_reflexive("leaf_surfaces", leaf_surface, 262144)), ("surfaces", raw_reflexive("surface", surface, 131072)), - ("lens_flare_markers", raw_reflexive("lens_flare_markers", lens_flare_marker, 65535)), - ("breakable_surfaces", raw_reflexive("breakable_surfaces", breakable_surface, 256)), + ("lens_flare_markers", raw_reflexive("lens_flare_markers", lens_flare_marker, UINT16_INDEX_MAX)), + ("breakable_surfaces", raw_reflexive("breakable_surfaces", breakable_surface, UINT8_INDEX_MAX)), ("pathfinding_surfaces", raw_reflexive("pathfinding_surfaces", pathfinding_surface, 131072)), ("pathfinding_edges", raw_reflexive("pathfinding_edges", pathfinding_edge, 262144)), ("markers", raw_reflexive("markers", marker, 1024, DYN_NAME_PATH='.name')), diff --git a/reclaimer/hek/defs/scnr.py b/reclaimer/hek/defs/scnr.py index ec736ee2..a54bdda4 100644 --- a/reclaimer/hek/defs/scnr.py +++ b/reclaimer/hek/defs/scnr.py @@ -733,8 +733,8 @@ def object_swatch(name, def_id, size=48): from_to_sec("respawn_delay"), Pad(48), - reflexive("move_positions", move_position, 31), - reflexive("starting_locations", actor_starting_location, 31), + reflexive("move_positions", move_position, 32), + reflexive("starting_locations", actor_starting_location, 32), SIZE=232 ) @@ -975,61 +975,61 @@ def object_swatch(name, def_id, size=48): reflexive("predicted_resources", predicted_resource, 1024, VISIBLE=False), reflexive("functions", function, 32, DYN_NAME_PATH='.name'), - rawdata_ref("scenario_editor_data", max_size=65536), + rawdata_ref("scenario_editor_data", max_size=UINT16_MAX), reflexive("comments", comment, 1024), Pad(12), # replaced with scavenger_hunt_objects in mcc_hek Pad(212), reflexive("object_names", object_name, 512, - DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), - reflexive("sceneries", scenery, 2000, IGNORE_SAFE_MODE=True), + DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), + reflexive("sceneries", scenery, 2000, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), reflexive("sceneries_palette", scenery_swatch, 100, - DYN_NAME_PATH='.name.filepath'), - reflexive("bipeds", biped, 128, IGNORE_SAFE_MODE=True), + DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), + reflexive("bipeds", biped, 128, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), reflexive("bipeds_palette", biped_swatch, 100, - DYN_NAME_PATH='.name.filepath'), - reflexive("vehicles", vehicle, 80, IGNORE_SAFE_MODE=True), + DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), + reflexive("vehicles", vehicle, 80, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), reflexive("vehicles_palette", vehicle_swatch, 100, - DYN_NAME_PATH='.name.filepath'), - reflexive("equipments", equipment, 256, IGNORE_SAFE_MODE=True), + DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), + reflexive("equipments", equipment, 256, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), reflexive("equipments_palette", equipment_swatch, 100, - DYN_NAME_PATH='.name.filepath'), - reflexive("weapons", weapon, 128, IGNORE_SAFE_MODE=True), + DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), + reflexive("weapons", weapon, 128, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), reflexive("weapons_palette", weapon_swatch, 100, - DYN_NAME_PATH='.name.filepath'), + DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), reflexive("device_groups", device_group, 128, DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), - reflexive("machines", machine, 400, IGNORE_SAFE_MODE=True), + reflexive("machines", machine, 400, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), reflexive("machines_palette", machine_swatch, 100, - DYN_NAME_PATH='.name.filepath'), - reflexive("controls", control, 100, IGNORE_SAFE_MODE=True), + DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), + reflexive("controls", control, 100, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), reflexive("controls_palette", control_swatch, 100, - DYN_NAME_PATH='.name.filepath'), - reflexive("light_fixtures", light_fixture, 500, IGNORE_SAFE_MODE=True), + DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), + reflexive("light_fixtures", light_fixture, 500, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), reflexive("light_fixtures_palette", light_fixture_swatch, 100, - DYN_NAME_PATH='.name.filepath'), - reflexive("sound_sceneries", sound_scenery, 256, IGNORE_SAFE_MODE=True), + DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), + reflexive("sound_sceneries", sound_scenery, 256, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), reflexive("sound_sceneries_palette", sound_scenery_swatch, 100, - DYN_NAME_PATH='.name.filepath'), + DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), Pad(84), reflexive("player_starting_profiles", player_starting_profile, 256, - DYN_NAME_PATH='.name'), - reflexive("player_starting_locations", player_starting_location, 256), + DYN_NAME_PATH='.name', EXT_MAX=SINT16_MAX), + reflexive("player_starting_locations", player_starting_location, 256, EXT_MAX=SINT16_MAX), reflexive("trigger_volumes", trigger_volume, 256, - DYN_NAME_PATH='.name'), + DYN_NAME_PATH='.name', EXT_MAX=SINT16_MAX), reflexive("recorded_animations", recorded_animation, 1024, - DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), + DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), reflexive("netgame_flags", netgame_flag, 200, - DYN_NAME_PATH='.type.enum_name'), + DYN_NAME_PATH='.type.enum_name', EXT_MAX=SINT16_MAX), reflexive("netgame_equipments", netgame_equipment, 200, - DYN_NAME_PATH='.item_collection.filepath'), - reflexive("starting_equipments", starting_equipment, 200), + DYN_NAME_PATH='.item_collection.filepath', EXT_MAX=SINT16_MAX), + reflexive("starting_equipments", starting_equipment, 200, EXT_MAX=SINT16_MAX), reflexive("bsp_switch_trigger_volumes", bsp_switch_trigger_volume, 256), - reflexive("decals", decal, 65535), + reflexive("decals", decal, 65535, EXT_MAX=UINT16_MAX), reflexive("decals_palette", decal_swatch, 128, - DYN_NAME_PATH='.name.filepath'), + DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), reflexive("detail_object_collection_palette", detail_object_collection_swatch, 32, DYN_NAME_PATH='.name.filepath'), diff --git a/reclaimer/hek/defs/snd_.py b/reclaimer/hek/defs/snd_.py index a5769eb6..812fab9a 100644 --- a/reclaimer/hek/defs/snd_.py +++ b/reclaimer/hek/defs/snd_.py @@ -99,7 +99,7 @@ SInt32("unknown2", VISIBLE=False, DEFAULT=-1), reflexive("permutations", permutation, 256, - DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), + DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), SIZE=72, ) diff --git a/reclaimer/hek/hardcoded_ce_tag_paths.py b/reclaimer/hek/hardcoded_ce_tag_paths.py index a3907a9f..08c286dd 100644 --- a/reclaimer/hek/hardcoded_ce_tag_paths.py +++ b/reclaimer/hek/hardcoded_ce_tag_paths.py @@ -183,3 +183,15 @@ HARDCODED_matg_TAG_PATHS + HARDCODED_scnr_TAG_PATHS + HARDCODED_lsnd_TAG_PATHS + HARDCODED_snd__TAG_PATHS ) + +HARDCODED_TAG_PATHS_BY_TYPE = { + "ustr": HARDCODED_ustr_TAG_PATHS, + "DeLa": HARDCODED_DeLa_TAG_PATHS, + "font": HARDCODED_font_TAG_PATHS, + "vcky": HARDCODED_vcky_TAG_PATHS, + "bitm": HARDCODED_bitm_TAG_PATHS, + "matg": HARDCODED_matg_TAG_PATHS, + "scnr": HARDCODED_scnr_TAG_PATHS, + "lsnd": HARDCODED_lsnd_TAG_PATHS, + "snd!": HARDCODED_snd__TAG_PATHS, + } \ No newline at end of file diff --git a/reclaimer/mcc_hek/defs/scnr.py b/reclaimer/mcc_hek/defs/scnr.py index 7c246c72..579d8272 100644 --- a/reclaimer/mcc_hek/defs/scnr.py +++ b/reclaimer/mcc_hek/defs/scnr.py @@ -88,28 +88,28 @@ scnr_body = desc_variant(scnr_body, scnr_flags, ("pad_13", reflexive("scavenger_hunt_objects", scavenger_hunt_objects, 16)), - reflexive("object_names", object_name, 640, DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True), - reflexive("sceneries", scenery, 2000, IGNORE_SAFE_MODE=True), - reflexive("bipeds", biped, 128, IGNORE_SAFE_MODE=True), - reflexive("vehicles", vehicle, 256, IGNORE_SAFE_MODE=True), - reflexive("equipments", equipment, 256, IGNORE_SAFE_MODE=True), - reflexive("weapons", weapon, 128, IGNORE_SAFE_MODE=True), - reflexive("machines", machine, 400, IGNORE_SAFE_MODE=True), - reflexive("controls", control, 100, IGNORE_SAFE_MODE=True), - reflexive("light_fixtures", light_fixture, 500, IGNORE_SAFE_MODE=True), - reflexive("sound_sceneries", sound_scenery, 256, IGNORE_SAFE_MODE=True), - reflexive("sceneries_palette", scenery_swatch, 256, DYN_NAME_PATH='.name.filepath'), - reflexive("bipeds_palette", biped_swatch, 256, DYN_NAME_PATH='.name.filepath'), - reflexive("vehicles_palette", vehicle_swatch, 256, DYN_NAME_PATH='.name.filepath'), - reflexive("equipments_palette", equipment_swatch, 256, DYN_NAME_PATH='.name.filepath'), - reflexive("weapons_palette", weapon_swatch, 256, DYN_NAME_PATH='.name.filepath'), - reflexive("machines_palette", machine_swatch, 256, DYN_NAME_PATH='.name.filepath'), - reflexive("controls_palette", control_swatch, 256, DYN_NAME_PATH='.name.filepath'), - reflexive("light_fixtures_palette", light_fixture_swatch, 256, DYN_NAME_PATH='.name.filepath'), - reflexive("sound_sceneries_palette", sound_scenery_swatch, 256, DYN_NAME_PATH='.name.filepath'), - reflexive("player_starting_profiles", player_starting_profile, 256, DYN_NAME_PATH='.name'), - reflexive("netgame_equipments", netgame_equipment, 200, DYN_NAME_PATH='.item_collection.filepath'), - reflexive("starting_equipments", starting_equipment, 200), + reflexive("object_names", object_name, 640, DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), + reflexive("sceneries", scenery, 2000, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), + reflexive("bipeds", biped, 128, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), + reflexive("vehicles", vehicle, 256, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), + reflexive("equipments", equipment, 256, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), + reflexive("weapons", weapon, 128, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), + reflexive("machines", machine, 400, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), + reflexive("controls", control, 100, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), + reflexive("light_fixtures", light_fixture, 500, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), + reflexive("sound_sceneries", sound_scenery, 256, IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), + reflexive("sceneries_palette", scenery_swatch, 256, DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), + reflexive("bipeds_palette", biped_swatch, 256, DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), + reflexive("vehicles_palette", vehicle_swatch, 256, DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), + reflexive("equipments_palette", equipment_swatch, 256, DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), + reflexive("weapons_palette", weapon_swatch, 256, DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), + reflexive("machines_palette", machine_swatch, 256, DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), + reflexive("controls_palette", control_swatch, 256, DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), + reflexive("light_fixtures_palette", light_fixture_swatch, 256, DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), + reflexive("sound_sceneries_palette", sound_scenery_swatch, 256, DYN_NAME_PATH='.name.filepath', EXT_MAX=SINT16_MAX), + reflexive("player_starting_profiles", player_starting_profile, 256, DYN_NAME_PATH='.name', EXT_MAX=SINT16_MAX), + reflexive("netgame_equipments", netgame_equipment, 200, DYN_NAME_PATH='.item_collection.filepath', EXT_MAX=SINT16_MAX), + reflexive("starting_equipments", starting_equipment, 200, EXT_MAX=SINT16_MAX), rawdata_ref("script_syntax_data", max_size=655396, IGNORE_SAFE_MODE=True), rawdata_ref("script_string_data", max_size=819200, IGNORE_SAFE_MODE=True), reflexive("scripts", halo_script, 1024, DYN_NAME_PATH='.name'), diff --git a/reclaimer/meta/class_repair.py b/reclaimer/meta/class_repair.py index ebb63043..08ef85e1 100644 --- a/reclaimer/meta/class_repair.py +++ b/reclaimer/meta/class_repair.py @@ -12,6 +12,7 @@ repair_dependency, repair_dependency_array from reclaimer.halo_script.hsc import get_hsc_data_block,\ HSC_IS_SCRIPT_OR_GLOBAL +from reclaimer.constants import * #from supyr_struct.util import * MAX_MATERIAL_COUNT = 33 @@ -84,6 +85,8 @@ def repair_item_attrs(offset, index_array, map_data, magic, repair, engine): def repair_unit_attrs(offset, index_array, map_data, magic, repair, engine): + is_yelo = engine == "halo1yelo" + # struct size is 372 args = (index_array, map_data, magic, repair, engine) repair_dependency(*(args + (b'effe', offset + 12))) @@ -119,7 +122,7 @@ def repair_unit_attrs(offset, index_array, map_data, magic, repair, engine): repair_dependency(*(args + (b'vtca', moff + 248))) - if "yelo" in engine: + if is_yelo: # seat extension for moff2 in iter_reflexive_offs(map_data, moff + 264 - magic, 100, 1, magic): # seat boarding @@ -134,7 +137,7 @@ def repair_unit_attrs(offset, index_array, map_data, magic, repair, engine): repair_dependency(*(args + (b'!tpj', moff3 + 4))) repair_dependency(*(args + (b'!tpj', moff3 + 96))) - if "yelo" in engine: + if is_yelo: # unit extension for moff in iter_reflexive_offs(map_data, offset + 288 - magic, 60, 1, magic): # mounted states @@ -167,12 +170,16 @@ def repair_ant_(tag_id, index_array, map_data, magic, repair, engine, safe_mode= def repair_antr(tag_id, index_array, map_data, magic, repair, engine, safe_mode=True): + is_mcc = engine == "halo1mcc" + + # sound references ct, moff, _ = read_reflexive( - map_data, index_array[tag_id].meta_offset + 0x54 - magic, 257, 20, magic) + map_data, index_array[tag_id].meta_offset + 0x54 - magic, + 257*(2 if is_mcc else 1), 20, magic + ) repair_dependency_array(index_array, map_data, magic, repair, engine, b'!dns', moff, ct, 20) - def repair_coll(tag_id, index_array, map_data, magic, repair, engine, safe_mode=True): tag_offset = index_array[tag_id].meta_offset args = (index_array, map_data, magic, repair, engine, b'effe') @@ -193,8 +200,15 @@ def repair_coll(tag_id, index_array, map_data, magic, repair, engine, safe_mode= def repair_cont(tag_id, index_array, map_data, magic, repair, engine, safe_mode=True): tag_offset = index_array[tag_id].meta_offset args = (index_array, map_data, magic, repair, engine) + is_yelo = engine == "halo1yelo" repair_dependency(*(args + (b'mtib', tag_offset + 0x30))) + # OS v4 shader extension + ct, _, __ = read_reflexive(map_data, tag_offset + 0xB4 - magic) + if is_yelo and ct > 1: + map_data.seek(tag_offset + 0xB4 - magic) + map_data.write(b'\x01\x00\x00\x00') + repair_dependency(*(args + (b'mtib', tag_offset + 0xD0))) # point states @@ -220,13 +234,12 @@ def repair_DeLa(tag_id, index_array, map_data, magic, repair, engine, safe_mode= repair_dependency(*(args + (b'!dns', moff + 24))) # search and replace functions - # Not needed anymore when tags are parsed in safe-mode - #ct, _, __ = read_reflexive(map_data, tag_offset + 96 - magic) - #if ct > 32: - # # some people apparently think its cute to set this reflexive - # # count so high so that tool just fails to compile the tag - # map_data.seek(tag_offset + 96 - magic) - # map_data.write(b'\x20\x00\x00\x00') + ct, _, __ = read_reflexive(map_data, tag_offset + 96 - magic) + if ct > 32: + # some people apparently think its cute to set this reflexive + # count so high so that tool just fails to compile the tag + map_data.seek(tag_offset + 96 - magic) + map_data.write(b'\x20\x00\x00\x00') repair_dependency(*(args + (b'rtsu', tag_offset + 236))) repair_dependency(*(args + (b'tnof', tag_offset + 252))) @@ -395,6 +408,8 @@ def repair_lsnd(tag_id, index_array, map_data, magic, repair, engine, safe_mode= def repair_matg(tag_id, index_array, map_data, magic, repair, engine, safe_mode=True): tag_offset = index_array[tag_id].meta_offset args = (index_array, map_data, magic, repair, engine) + is_yelo = engine == "halo1yelo" + is_mcc = engine == "halo1mcc" # sounds ct, moff, _ = read_reflexive(map_data, tag_offset + 0xF8 - magic, 2, 16, magic) @@ -405,7 +420,10 @@ def repair_matg(tag_id, index_array, map_data, magic, repair, engine, safe_mode= repair_dependency_array(*(args + (b'kart', moff, ct))) # grenades - for moff in iter_reflexive_offs(map_data, tag_offset + 0x128 - magic, 68, 4, magic): + for moff in iter_reflexive_offs( + map_data, tag_offset + 0x128 - magic, 68, + 4 if (is_yelo or is_mcc) else 2, magic + ): repair_dependency(*(args + (b'effe', moff + 4))) repair_dependency(*(args + (b'ihrg', moff + 20))) repair_dependency(*(args + (b'piqe', moff + 36))) @@ -554,10 +572,11 @@ def repair_object(tag_id, index_array, map_data, magic, repair, engine, safe_mod # not an object return + is_yelo = engine == "halo1yelo" # obje_attrs struct size is 380 args = (index_array, map_data, magic, repair, engine) repair_dependency(*(args + (b'2dom', tag_offset + 40))) - repair_dependency(*(args + (b'rtna', tag_offset + 56))) + repair_dependency(*(args + (None if is_yelo else b'rtna', tag_offset + 56))) repair_dependency(*(args + (b'lloc', tag_offset + 112))) repair_dependency(*(args + (b'syhp', tag_offset + 128))) @@ -607,7 +626,7 @@ def repair_object(tag_id, index_array, map_data, magic, repair, engine, safe_mod repair_dependency(*(args + (None, tag_offset + 280))) repair_dependency(*(args + (None, tag_offset + 296))) repair_dependency(*(args + (b'2dom', tag_offset + 340))) - repair_dependency(*(args + (b'rtna', tag_offset + 356))) + repair_dependency(*(args + (None if is_yelo else b'rtna', tag_offset + 356))) repair_dependency(*(args + (b'ihpw', tag_offset + 376))) repair_dependency(*(args + (b'!dns', tag_offset + 392))) repair_dependency(*(args + (b'!dns', tag_offset + 408))) @@ -623,7 +642,9 @@ def repair_object(tag_id, index_array, map_data, magic, repair, engine, safe_mod repair_dependency(*(args + (None, moff + 72))) # magazine items - ct, moff2, _ = read_reflexive(map_data, moff + 100 - magic, 2, 28, magic) + ct, moff2, _ = read_reflexive( + map_data, moff + 100 - magic, 8 if is_yelo else 2, 28, magic + ) repair_dependency_array(*(args + (b'piqe', moff2 + 12, ct, 28))) # triggers @@ -689,18 +710,27 @@ def repair_object(tag_id, index_array, map_data, magic, repair, engine, safe_mod def repair_part(tag_id, index_array, map_data, magic, repair, engine, safe_mode=True): tag_offset = index_array[tag_id].meta_offset args = (index_array, map_data, magic, repair, engine) + is_yelo = engine == "halo1yelo" repair_dependency(*(args + (b'mtib', tag_offset + 0x4))) repair_dependency(*(args + (b'yhpp', tag_offset + 0x14))) repair_dependency(*(args + (b'toof', tag_offset + 0x24))) repair_dependency(*(args + (None, tag_offset + 0x48))) repair_dependency(*(args + (None, tag_offset + 0x58))) + # OS v4 shader extension + ct, _, __ = read_reflexive(map_data, tag_offset + 0xE0 - magic) + if is_yelo and ct > 1: + map_data.seek(tag_offset + 0xE0 - magic) + map_data.write(b'\x01\x00\x00\x00') + repair_dependency(*(args + (b'mtib', tag_offset + 0xFC))) def repair_pctl(tag_id, index_array, map_data, magic, repair, engine, safe_mode=True): tag_offset = index_array[tag_id].meta_offset args = (index_array, map_data, magic, repair, engine) + is_yelo = engine == "halo1yelo" + repair_dependency(*(args + (b'yhpp', tag_offset + 56))) # particle types @@ -709,6 +739,12 @@ def repair_pctl(tag_id, index_array, map_data, magic, repair, engine, safe_mode= for moff2 in iter_reflexive_offs(map_data, moff + 116 - magic, 376, 8, magic): repair_dependency(*(args + (b'mtib', moff2 + 48))) repair_dependency(*(args + (b'yhpp', moff2 + 132))) + # OS v4 shader extension + ct, _, __ = read_reflexive(map_data, moff2 + 0xE8 - magic) + if is_yelo and ct > 1: + map_data.seek(moff2 + 0xE8 - magic) + map_data.write(b'\x01\x00\x00\x00') + repair_dependency(*(args + (b'mtib', moff2 + 260))) @@ -764,12 +800,13 @@ def repair_sbsp(tag_offset, index_array, map_data, magic, repair, engine, def repair_scnr(tag_id, index_array, map_data, magic, repair, engine, safe_mode=True): - ### Need to finish this up. not all the limits specified here - # should be as low as they are because open sauce is a thing tag_offset = index_array[tag_id].meta_offset args = (index_array, map_data, magic, repair, engine) - if "yelo" in engine: + is_yelo = engine == "halo1yelo" + is_mcc = engine == "halo1mcc" + + if is_yelo: repair_dependency(*(args + (b'oley', tag_offset))) # bsp modifiers @@ -818,7 +855,7 @@ def repair_scnr(tag_id, index_array, map_data, magic, repair, engine, safe_mode= # ai animation reference ct, moff, _ = read_reflexive(map_data, tag_offset + 1092 - magic, 128, 60, magic) - repair_dependency_array(*(args + (b'rtna', moff + 32, ct, 60))) + repair_dependency_array(*(args + (None if is_yelo else b'rtna', moff + 32, ct, 60))) # ai conversations for moff in iter_reflexive_offs(map_data, tag_offset + 1128 - magic, 116, 128, magic): @@ -832,7 +869,7 @@ def repair_scnr(tag_id, index_array, map_data, magic, repair, engine, safe_mode= repair_dependency(*(args + (b'!dns', moff2 + 108))) # tag references - ct, moff, _ = read_reflexive(map_data, tag_offset + 1204 - magic, 256, 40, magic) + ct, moff, _ = read_reflexive(map_data, tag_offset + 1204 - magic, 512 if is_mcc else 256, 40, magic) repair_dependency_array(*(args + (None, moff + 24, ct, 40))) # structure bsps @@ -842,11 +879,14 @@ def repair_scnr(tag_id, index_array, map_data, magic, repair, engine, safe_mode= # palettes # NOTE: Can't trust that these palettes are valid. # Need to check what the highest one used by all instances + max_pal_ct = 256 if is_mcc else 100 for off, inst_size in ( (540, 72), (564, 120), (588, 120), # scen bipd vehi (612, 40), (636, 92), (672, 64), # eqip weap mach (696, 64), (720, 88), (744, 40)): # ctrl lifi ssce - pal_ct, pal_moff, _ = read_reflexive(map_data, tag_offset + off - magic) + pal_ct, pal_moff, _ = read_reflexive( + map_data, tag_offset + off - magic, max_pal_ct + ) if safe_mode: used_pal_indices = set() @@ -867,7 +907,7 @@ def repair_scnr(tag_id, index_array, map_data, magic, repair, engine, safe_mode= # script syntax data references size, _, __, moff, ___ = read_rawdata_ref(map_data, tag_offset + 1140 - magic, magic) map_data.seek(moff - magic) - script_syntax_data_nodes = get_hsc_data_block(map_data.read(size)).nodes + script_syntax_data_nodes = get_hsc_data_block(map_data.read(size), engine).nodes for node in script_syntax_data_nodes: tag_cls = { 24: 'snd!', 25: 'effe', 26: 'jpt!', 27: 'lsnd', @@ -911,6 +951,7 @@ def repair_shader(tag_id, index_array, map_data, magic, repair, engine, safe_mod tag_offset = index_array[tag_id].meta_offset map_data.seek(tag_offset + 36 - magic) shader_type = int.from_bytes(map_data.read(2), 'little') + is_yelo = engine == "halo1yelo" if shader_type != -1 and shader_type not in range(len(shader_class_bytes) - 1): # not a shader @@ -931,7 +972,7 @@ def repair_shader(tag_id, index_array, map_data, magic, repair, engine, safe_mod repair_dependency(*(args + (b'mtib', tag_offset + 0x22C))) repair_dependency(*(args + (b'mtib', tag_offset + 0x2FC))) # shader environment os extension - if "yelo" in engine: + if is_yelo: ct, moff, _ = read_reflexive(map_data, tag_offset + 0xC8 - magic, 1, 100, magic) repair_dependency_array(*(args + (b'mtib', moff + 8, ct, 100))) @@ -941,7 +982,7 @@ def repair_shader(tag_id, index_array, map_data, magic, repair, engine, safe_mod repair_dependency(*(args + (b'mtib', tag_offset + 0xB4))) repair_dependency(*(args + (b'mtib', tag_offset + 0x13C))) # shader model os extension - if "yelo" in engine: + if is_yelo: for moff in iter_reflexive_offs( map_data, tag_offset + 0xC8 - magic, 192, 1, magic): repair_dependency(*(args + (b'mtib', moff))) @@ -993,9 +1034,10 @@ def repair_shader(tag_id, index_array, map_data, magic, repair, engine, safe_mod def repair_sky(tag_id, index_array, map_data, magic, repair, engine, safe_mode=True): tag_offset = index_array[tag_id].meta_offset args = (index_array, map_data, magic, repair, engine) + is_yelo = engine == "halo1yelo" repair_dependency(*(args + (b'2dom', tag_offset))) - repair_dependency(*(args + (b'rtna', tag_offset + 0x10))) + repair_dependency(*(args + (None if is_yelo else b'rtna', tag_offset + 0x10))) repair_dependency(*(args + (b' gof', tag_offset + 0x98))) # lights ct, moff, _ = read_reflexive(map_data, tag_offset + 0xC4 - magic, 8, 116, magic) @@ -1163,16 +1205,16 @@ def repair_avto(tag_id, index_array, map_data, magic, repair, engine, safe_mode= args = (index_array, map_data, magic, repair, engine) # instigators - for moff in iter_reflexive_offs(map_data, tag_offset + 44, 32, 16, magic): + for moff in iter_reflexive_offs(map_data, tag_offset + 52, 32, 16, magic): repair_dependency(*(args + (b'tinu', moff))) # keyframe actions - for moff in iter_reflexive_offs(map_data, tag_offset + 88, 72, 9, magic): + for moff in iter_reflexive_offs(map_data, tag_offset + 96, 72, 9, magic): repair_dependency(*(args + (b'!tpj', moff + 8))) repair_dependency(*(args + (b'effe', moff + 24))) # attachments - for moff in iter_reflexive_offs(map_data, tag_offset + 104, 120, 16, magic): + for moff in iter_reflexive_offs(map_data, tag_offset + 112, 120, 16, magic): repair_dependency(*(args + (b'ejbo', moff))) @@ -1189,7 +1231,7 @@ def repair_efpg(tag_id, index_array, map_data, magic, repair, engine, safe_mode= ct, moff, _ = read_reflexive( map_data, index_array[tag_id].meta_offset + 60 - magic, 12, 16, magic) repair_dependency_array( - index_array, map_data, magic, repair, engine, b'gphs', moff, ct) + index_array, map_data, magic, repair, engine, b'gphs', moff, ct, 16) def repair_gelc(tag_id, index_array, map_data, magic, repair, engine, safe_mode=True): @@ -1232,7 +1274,7 @@ def repair_gelo(tag_id, index_array, map_data, magic, repair, engine, safe_mode= def repair_magy(tag_id, index_array, map_data, magic, repair, engine, safe_mode=True): repair_antr(tag_id, index_array, map_data, magic, repair, engine) repair_dependency(index_array, map_data, magic, repair, engine, - b'rtna', index_array[tag_id].meta_offset + 0x80) + None, index_array[tag_id].meta_offset + 0x80) def repair_shpg(tag_id, index_array, map_data, magic, repair, engine, safe_mode=True): diff --git a/reclaimer/meta/halo1_map.py b/reclaimer/meta/halo1_map.py index 86b2b4a6..d40e6335 100644 --- a/reclaimer/meta/halo1_map.py +++ b/reclaimer/meta/halo1_map.py @@ -180,33 +180,17 @@ def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): SIZE=480 ) -map_version = UEnum32("version", - ("halo1xbox", 5), - ("halo1pcdemo", 6), - ("halo1pc", 7), - ("halo2", 8), - ("halo3beta", 9), - ("halo3", 11), - ("halo1mcc", 13), - ("halo1ce", 609), - ("halo1vap", 134), - ) - # Halo Demo maps have a different header # structure with garbage filling the padding map_header_demo = Struct("map header", Pad(2), - UEnum16("map type", - "sp", - "mp", - "ui", - ), + gen1_map_type, # NOTE: in common_descs.py Pad(700), UEnum32('head', ('head', 'Ehed'), EDITABLE=False, DEFAULT='Ehed'), UInt32("tag data size"), ascii_str32("build date", EDITABLE=False), Pad(672), - map_version, + map_version, # NOTE: in common_descs.py ascii_str32("map name"), UInt32("unknown"), UInt32("crc32"), @@ -220,7 +204,7 @@ def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): map_header = Struct("map header", UEnum32('head', ('head', 'head'), DEFAULT='head'), - map_version, + map_version, # NOTE: in common_descs.py UInt32("decomp len"), UInt32("unknown"), UInt32("tag index header offset"), @@ -228,11 +212,7 @@ def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): Pad(8), ascii_str32("map name"), ascii_str32("build date", EDITABLE=False), - UEnum16("map type", - "sp", - "mp", - "ui", - ), + gen1_map_type, # NOTE: in common_descs.py Pad(2), UInt32("crc32"), Pad(1), diff --git a/reclaimer/meta/halo1_map_fast_functions.py b/reclaimer/meta/halo1_map_fast_functions.py index b0c01808..05c5b364 100644 --- a/reclaimer/meta/halo1_map_fast_functions.py +++ b/reclaimer/meta/halo1_map_fast_functions.py @@ -86,6 +86,9 @@ def read_reflexive(map_data, refl_offset, max_count=0xFFffFFff, map_data.seek(0, 2) max_count = min(max_count, (map_data.tell() - (start - tag_magic)) // struct_size) + if count > max_count: + print("Warning: Clipped %s reflexive size from %s to %s" % (count, max_count)) + return min(count, max_count), start, id @@ -173,6 +176,8 @@ def repair_dependency(index_array, map_data, tag_magic, repair, engine, cls, return cls = shader_class_bytes[shader_type] + elif cls == b'lcpw': + cls = b'cmti' elif cls in (b'2dom', b'edom'): if "xbox" in engine or "halo" not in engine: cls = b'edom' diff --git a/reclaimer/meta/halo2_map.py b/reclaimer/meta/halo2_map.py index 3e0f7ae4..b48f3c1c 100644 --- a/reclaimer/meta/halo2_map.py +++ b/reclaimer/meta/halo2_map.py @@ -55,15 +55,7 @@ def string_id_table_name_pointer(parent=None, new_value=None, **kwargs): h2x_map_header = Struct("map header", UEnum32('head', ('head', 'head'), EDITABLE=False, DEFAULT='head'), - UEnum32("version", - ("halo1xbox", 5), - ("halo1pcdemo", 6), - ("halo1pc", 7), - ("halo2", 8), - ("halo3beta", 9), - ("halo3", 11), - ("halo1ce", 609), - ), + map_version, # NOTE: in common_descs.py UInt32("decomp len"), UInt32("unknown0"), UInt32("tag index header offset"), @@ -73,13 +65,7 @@ def string_id_table_name_pointer(parent=None, new_value=None, **kwargs): Pad(256), ascii_str32("build date"), - UEnum16("map type", - "sp", - "mp", - "ui", - "shared", - "sharedsp", - ), + gen2_map_type, # NOTE: in common_descs.py Pad(2), UInt32("crc32"), Pad(16), @@ -120,14 +106,7 @@ def string_id_table_name_pointer(parent=None, new_value=None, **kwargs): h2v_map_header = Struct("map header", UEnum32('head', ('head', 'head'), EDITABLE=False, DEFAULT='head'), - UEnum32("version", - ("halo1xbox", 5), - ("halo1pcdemo", 6), - ("halo1pc", 7), - ("halo2", 8), - ("halo3", 11), - ("halo1ce", 609), - ), + map_version, # NOTE: in common_descs.py UInt32("decomp len"), UInt32("unknown0"), UInt32("tag index header offset"), @@ -140,13 +119,7 @@ def string_id_table_name_pointer(parent=None, new_value=None, **kwargs): Pad(256), ascii_str32("build date"), - UEnum16("map type", - "sp", - "mp", - "ui", - "shared", - "sharedsp", - ), + gen2_map_type, # NOTE: in common_descs.py Pad(2), UInt32("crc32"), Pad(16), diff --git a/reclaimer/meta/halo3_map.py b/reclaimer/meta/halo3_map.py index eb7d150d..704ad10a 100644 --- a/reclaimer/meta/halo3_map.py +++ b/reclaimer/meta/halo3_map.py @@ -174,16 +174,7 @@ def root_tags_array_pointer(parent=None, new_value=None, **kwargs): UEnum32('head', ('head', 'head'), EDITABLE=False, VISIBLE=False, DEFAULT='head' ), - UEnum32("version", - ("halo1xbox", 5), - ("halo1pcdemo", 6), - ("halo1pc", 7), - ("halo2", 8), - ("halo3beta", 9), - ("halo3", 11), - ("haloreach", 12), - ("halo1ce", 609), - ), + map_version, # NOTE: in common_descs.py UInt32("decomp len"), UInt32("unknown0", VISIBLE=False), UInt32("tag index header offset"), @@ -192,13 +183,7 @@ def root_tags_array_pointer(parent=None, new_value=None, **kwargs): Pad(256), ascii_str32("build date"), - UEnum16("map type", - "sp", - "mp", - "ui", - "shared", - "sharedsp", - ), + gen2_map_type, # NOTE: in common_descs.py UInt16("unknown2", VISIBLE=False), UInt8("unknown3", VISIBLE=False), UInt8("unknown4", VISIBLE=False), diff --git a/reclaimer/meta/wrappers/halo1_anni_map.py b/reclaimer/meta/wrappers/halo1_anni_map.py index fef512ff..b09aa0f4 100644 --- a/reclaimer/meta/wrappers/halo1_anni_map.py +++ b/reclaimer/meta/wrappers/halo1_anni_map.py @@ -68,17 +68,18 @@ def end_swap_uint16(v): class Halo1AnniMap(Halo1MccMap): tag_headers = None + # NOTE: setting defs to None so setup_defs doesn't think the + # defs are setup cause of class property inheritance. defs = None handler_class = HaloHandler sbsp_meta_header_def = sbsp_meta_header_def - - @property - def uses_fmod_sound_bank(self): return True - def get_resource_map_paths(self, maps_dir=""): - return {} + @property + def uses_bitmaps_map(self): return False + @property + def uses_sounds_map(self): return False def get_dependencies(self, meta, tag_id, tag_cls): if self.is_indexed(tag_id): diff --git a/reclaimer/meta/wrappers/halo1_map.py b/reclaimer/meta/wrappers/halo1_map.py index 0320cb33..2e5034db 100644 --- a/reclaimer/meta/wrappers/halo1_map.py +++ b/reclaimer/meta/wrappers/halo1_map.py @@ -31,7 +31,6 @@ from reclaimer.hek.defs.snd_ import snd__meta_stub_blockdef from reclaimer.hek.defs.sbsp import sbsp_meta_header_def from reclaimer.hek.handler import HaloHandler -from reclaimer.os_hek.defs.gelc import gelc_def from reclaimer.os_v4_hek.defs.coll import fast_coll_def from reclaimer.os_v4_hek.defs.sbsp import fast_sbsp_def from reclaimer.meta.wrappers.byteswapping import raw_block_def, byteswap_animation,\ @@ -43,7 +42,7 @@ from reclaimer.meta.wrappers.map_pointer_converter import MapPointerConverter from reclaimer.meta.wrappers.tag_index_manager import TagIndexManager from reclaimer import data_extraction -from reclaimer.constants import tag_class_fcc_to_ext +from reclaimer.constants import tag_class_fcc_to_ext, GEN_1_HALO_CUSTOM_ENGINES from reclaimer.util.compression import compress_normal32, decompress_normal32 from reclaimer.util import is_overlapping_ranges, is_valid_ascii_name_str,\ int_to_fourcc, get_block_max @@ -102,6 +101,10 @@ class Halo1Map(HaloMap): sbsp_meta_header_def = sbsp_meta_header_def data_extractors = data_extraction.h1_data_extractors + + indexable_tag_classes = set(( + "bitm", "snd!", "font", "hmt ", "ustr" + )) def __init__(self, maps=None): HaloMap.__init__(self, maps) @@ -118,14 +121,53 @@ def __init__(self, maps=None): self.setup_tag_headers() + @property + def globals_tag_id(self): + if not self.tag_index: + return None + + for b in self.tag_index.tag_index: + if int_to_fourcc(b.class_1.data) == "matg": + return b.id & 0xFFff + + @property + def resource_map_prefix(self): + return "" + @property + def resource_maps_folder(self): + return self.filepath.parent + @property + def resources_maps_mismatched(self): + maps_dir = self.resource_maps_folder + if not maps_dir: + return False + + for map_name, filepath in self.get_resource_map_paths().items(): + if filepath and filepath.parent != maps_dir: + return True + return False + @property + def uses_bitmaps_map(self): + return not self.is_resource + @property + def uses_loc_map(self): + return not self.is_resource and "pc" not in self.engine + @property + def uses_sounds_map(self): + return not self.is_resource + @property def decomp_file_ext(self): - if self.engine == "halo1yelo": - return ".yelo" - elif self.engine == "halo1vap": - return ".vap" - else: - return ".map" + return ( + ".vap" if self.engine == "halo1vap" else + self._decomp_file_ext + ) + + def is_indexed(self, tag_id): + tag_header = self.tag_index.tag_index[tag_id] + if not tag_header.indexed: + return False + return int_to_fourcc(tag_header.class_1.data) in self.indexable_tag_classes def setup_defs(self): this_class = type(self) @@ -138,7 +180,6 @@ def setup_defs(self): this_class.defs = dict(this_class.handler.defs) this_class.defs["coll"] = fast_coll_def - this_class.defs["gelc"] = gelc_def this_class.defs["sbsp"] = fast_sbsp_def this_class.defs = FrozenDict(this_class.defs) @@ -156,7 +197,7 @@ def ensure_sound_maps_valid(self): return self.sound_rsrc_id = id(sounds) - if self.engine in ("halo1ce", "halo1yelo", "halo1vap", "halo1mcc"): + if self.engine in GEN_1_HALO_CUSTOM_ENGINES: # ce resource sounds are recognized by tag_path # so we must cache their offsets by their paths rsrc_snd_map = self.ce_rsrc_sound_indexes_by_path = {} @@ -353,13 +394,7 @@ def load_map(self, map_path, **kwargs): # get the globals meta try: - matg_id = None - for b in tag_index_array: - if int_to_fourcc(b.class_1.data) == "matg": - matg_id = b.id & 0xFFff - break - - self.matg_meta = self.get_meta(matg_id) + self.matg_meta = self.get_meta(self.globals_tag_id) if self.matg_meta is None: print("Could not read globals tag") except Exception: @@ -373,6 +408,15 @@ def load_map(self, map_path, **kwargs): self.clear_map_cache() + if self.resources_maps_mismatched and kwargs.get("unlink_mismatched_resources", True): + # this map reference different resource maps depending on what + # folder its located in. we need to ignore any resource maps + # passed in unless they're in the same folder as this map. + print("Unlinking potentially incompatible resource maps from %s" % + self.map_name + ) + self.maps = {} + def get_meta(self, tag_id, reextract=False, ignore_rsrc_sounds=False, **kw): ''' Takes a tag reference id as the sole argument. @@ -381,20 +425,19 @@ def get_meta(self, tag_id, reextract=False, ignore_rsrc_sounds=False, **kw): if tag_id is None: return - magic = self.map_magic - engine = self.engine - map_data = self.map_data - tag_index = self.tag_index - tag_index_array = tag_index.tag_index - - # if we are given a 32bit tag id, mask it off - tag_id &= 0xFFFF tag_index_ref = self.tag_index_manager.get_tag_index_ref(tag_id) if tag_index_ref is None: return + # if we are given a 32bit tag id, mask it off + tag_id &= 0xFFFF + magic = self.map_magic + engine = self.engine + map_data = self.map_data + tag_cls = None - if tag_id == (tag_index.scenario_tag_id & 0xFFFF): + is_scenario = (tag_id == (self.tag_index.scenario_tag_id & 0xFFFF)) + if is_scenario: tag_cls = "scnr" elif tag_index_ref.class_1.enum_name not in ("", "NONE"): tag_cls = int_to_fourcc(tag_index_ref.class_1.data) @@ -462,7 +505,7 @@ def get_meta(self, tag_id, reextract=False, ignore_rsrc_sounds=False, **kw): return meta elif not reextract: - if tag_id == tag_index.scenario_tag_id & 0xFFff and self.scnr_meta: + if is_scenario and self.scnr_meta: return self.scnr_meta elif tag_cls == "matg" and self.matg_meta: return self.matg_meta @@ -481,7 +524,7 @@ def get_meta(self, tag_id, reextract=False, ignore_rsrc_sounds=False, **kw): map_pointer_converter=pointer_converter, offset=pointer_converter.v_ptr_to_f_ptr(offset), tag_index_manager=self.tag_index_manager, - safe_mode=not kw.get("disable_safe_mode"), + safe_mode=(self.safe_mode and not kw.get("disable_safe_mode")), parsing_resource=force_parsing_rsrc) except Exception: print(format_exc()) @@ -490,6 +533,7 @@ def get_meta(self, tag_id, reextract=False, ignore_rsrc_sounds=False, **kw): meta = block[0] try: + # TODO: remove this dirty-ass hack if tag_cls == "bitm" and get_is_xbox_map(engine): for bitmap in meta.bitmaps.STEPTREE: # make sure to set this for all xbox bitmaps @@ -645,7 +689,7 @@ def clean_tag_meta(self, meta, tag_id, tag_cls): # first 2 ints in each edge are the vert indices, and theres # 6 int32s per edge. find the highest vert index being used if bsp.edges.STEPTREE: - byteorder = 'big' if engine == "halo1anni" else 'little' + byteorder = 'big' if self.engine == "halo1anni" else 'little' edges = PyArray("i", bsp.edges.STEPTREE) if byteorder != sys.byteorder: @@ -1359,36 +1403,31 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): return meta def get_resource_map_paths(self, maps_dir=""): - if self.is_resource or self.engine not in ("halo1pc", "halo1pcdemo", - "halo1ce", "halo1yelo", "halo1vap"): + if self.is_resource or not self.resource_maps_folder: return {} - map_paths = {"bitmaps": None, "sounds": None, "loc": None} - if self.engine not in ("halo1ce", "halo1yelo", "halo1vap"): - map_paths.pop('loc') - - data_files = False - if hasattr(self.map_header, "yelo_header"): - data_files = self.map_header.yelo_header.flags.uses_mod_data_files - - if not is_path_empty(maps_dir): - maps_dir = Path(maps_dir) - elif data_files: - maps_dir = self.filepath.parent.joinpath("data_files") - else: - maps_dir = self.filepath.parent + map_paths = { + name: None for name in ( + *(["bitmaps"] if self.uses_bitmaps_map else []), + *(["sounds"] if self.uses_sounds_map else []), + *(["loc"] if self.uses_loc_map else []), + ) + } - map_name_str = "%s.map" - if data_files: - map_name_str = "~" + map_name_str + name_str = self.resource_map_prefix + "%s.map" + maps_dir = ( + Path(maps_dir) if not is_path_empty(maps_dir) else + self.resource_maps_folder + ) # detect the map paths for the resource maps - for map_name in sorted(map_paths.keys()): - map_path = maps_dir.joinpath(map_name_str % map_name) - if self.maps.get(map_name) is not None: - map_paths[map_name] = self.maps[map_name].filepath - elif map_path.is_file(): - map_paths[map_name] = map_path + if maps_dir: + for map_name in sorted(map_paths.keys()): + map_path = maps_dir.joinpath(name_str % map_name) + if self.maps.get(map_name) is not None: + map_paths[map_name] = self.maps[map_name].filepath + elif map_path.is_file(): + map_paths[map_name] = map_path return map_paths diff --git a/reclaimer/meta/wrappers/halo1_mcc_map.py b/reclaimer/meta/wrappers/halo1_mcc_map.py index c999dc51..0b6e733d 100644 --- a/reclaimer/meta/wrappers/halo1_mcc_map.py +++ b/reclaimer/meta/wrappers/halo1_mcc_map.py @@ -21,60 +21,26 @@ class Halo1MccMap(Halo1Map): tag_defs_module = "reclaimer.mcc_hek.defs" # Handler that controls how to load tags, eg tag definitions handler_class = MCCHaloHandler + # NOTE: setting defs to None so setup_defs doesn't think the + # defs are setup cause of class property inheritance. + defs = None sbsp_meta_header_def = sbsp_meta_header_def - def load_map(self, map_path, **kwargs): - super().load_map(map_path, **kwargs) + indexable_tag_classes = frozenset(("bitm", "snd!")) - # NOTE: mcc halo 1 resource maps MUST be handled differently than - # pc and ce because of the multiple types they may use. - # mcc maps may reference different bitmaps.map and sounds.map - # depending on what folder they're located in, so we're - # going to ignore any resource maps passed in unless - # they're coming from the same folder as this map. - if not self.are_resources_in_same_directory: - print("Unlinking potentially incompatible resource maps from %s" % - self.map_name - ) - self.maps = {} - @property - def uses_fmod_sound_bank(self): - return not self.uses_sounds_map - + def uses_loc_map(self): + return False @property def uses_sounds_map(self): try: return self.map_header.mcc_flags.use_sounds_map except AttributeError: return False - - def get_resource_map_paths(self, maps_dir=""): - if self.is_resource: - return {} - - map_paths = dict(bitmaps=None, sounds=None) - - # NOTE: for now we're just checking if ANY of these flags are set. - # if they ARE, we assume we're using classic resources, which - # means reading from sounds.map. if they're NOT set, we still - # expect bitmap resource data to be in bitmaps.map, but the - # external sound data is in fmod sound banks. - if self.uses_fmod_sound_bank: - map_paths.pop("sounds") - - maps_dir = self.filepath.parent if is_path_empty(maps_dir) else Path(maps_dir) - - # detect the map paths for the resource maps - for map_name in sorted(map_paths.keys()): - map_path = maps_dir.joinpath(map_name + ".map") - if self.maps.get(map_name) is not None: - map_paths[map_name] = self.maps[map_name].filepath - elif map_path.is_file(): - map_paths[map_name] = map_path - - return map_paths + @property + def uses_fmod_sound_bank(self): + return not self.uses_sounds_map def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): # for sounds, ensure we can extract ALL their sample data from either diff --git a/reclaimer/meta/wrappers/halo1_rsrc_map.py b/reclaimer/meta/wrappers/halo1_rsrc_map.py index c921cfc4..69aea25f 100644 --- a/reclaimer/meta/wrappers/halo1_rsrc_map.py +++ b/reclaimer/meta/wrappers/halo1_rsrc_map.py @@ -12,6 +12,8 @@ from struct import unpack from traceback import format_exc +from reclaimer.constants import GEN_1_HALO_CUSTOM_ENGINES,\ + GEN_1_HALO_PC_ENGINES, GEN_1_HALO_GBX_ENGINES from reclaimer import data_extraction from reclaimer.mcc_hek.defs.bitm import bitm_def as pixel_root_subdef from reclaimer.mcc_hek.defs.objs.bitm import MccBitmTag, HALO_P8_PALETTE @@ -151,19 +153,15 @@ def load_map(self, map_path, **kwargs): pth = self.orig_tag_index[0].tag.path if self.orig_tag_index else "" self.filepath = map_path - ce_engine = "" - for halo_map in self.maps.values(): - ce_engine = getattr(halo_map, "engine") - if ce_engine: - break - rsrc_tag_count = len(rsrc_map.data.tags) if resource_type == 3 or (pth.endswith('__pixels') or pth.endswith('__permutations')): - if ce_engine: - self.engine = ce_engine - else: - self.engine = "halo1ce" + engine = "" + for halo_map in self.maps.values(): + engine = engine or getattr(halo_map, "engine") + if engine: break + + self.engine = engine or "halo1ce" elif ((resource_type == 1 and rsrc_tag_count == 1107) or (resource_type == 2 and rsrc_tag_count == 7192)): self.engine = "halo1pcdemo" @@ -270,8 +268,7 @@ def get_meta(self, tag_id, reextract=False, **kw): kwargs = dict(parsing_resource=True) desc = self.get_meta_descriptor(tag_cls) - if desc is None or self.engine not in ("halo1ce", "halo1yelo", - "halo1vap", "halo1mcc"): + if desc is None or self.engine not in GEN_1_HALO_CUSTOM_ENGINES: return elif self.engine == "halo1mcc" and tag_cls == "bitm": return @@ -293,7 +290,7 @@ def get_meta(self, tag_id, reextract=False, **kw): desc, parent=block, attr_index=0, rawdata=self.map_data, tag_index_manager=self.snd_rsrc_tag_index_manager, tag_cls=tag_cls, root_offset=tag_index_ref.meta_offset, - safe_mode=not kw.get("disable_safe_mode"), + safe_mode=(self.safe_mode and not kw.get("disable_safe_mode")), indexed=True, **kwargs) FieldType.force_normal() @@ -412,8 +409,7 @@ def inject_rawdata(self, meta, tag_cls, tag_index_ref): loc_data = getattr(loc, "map_data", None) is_not_indexed = not self.is_indexed(tag_index_ref.id & 0xFFff) - might_be_in_rsrc = engine in ("halo1pc", "halo1pcdemo", "halo1mcc", - "halo1ce", "halo1yelo", "halo1vap") + might_be_in_rsrc = engine in GEN_1_HALO_GBX_ENGINES might_be_in_rsrc &= not self.is_resource # get some rawdata that would be pretty annoying to do in the parser @@ -468,9 +464,9 @@ def inject_rawdata(self, meta, tag_cls, tag_index_ref): elif tag_cls == "snd!": # might need to get samples and permutations from the resource map - is_pc = engine in ("halo1pc", "halo1pcdemo") - is_ce = engine in ("halo1ce", "halo1yelo", "halo1vap") is_mcc = engine == "halo1mcc" + is_pc = engine in GEN_1_HALO_PC_ENGINES + is_ce = engine in GEN_1_HALO_CUSTOM_ENGINES and not is_mcc # ce tagpaths are in the format: path__permutations # ex: sound\sfx\impulse\coolant\enter_water__permutations diff --git a/reclaimer/meta/wrappers/halo1_xbox_map.py b/reclaimer/meta/wrappers/halo1_xbox_map.py index c20871f7..c6875098 100644 --- a/reclaimer/meta/wrappers/halo1_xbox_map.py +++ b/reclaimer/meta/wrappers/halo1_xbox_map.py @@ -16,6 +16,18 @@ class Halo1XboxMap(Halo1Map): tag_defs_module = "reclaimer.hek.defs" # Handler that controls how to load tags, eg tag definitions handler_class = HaloHandler + # NOTE: setting defs to None so setup_defs doesn't think the + # defs are setup cause of class property inheritance. + defs = None - def __init__(self, maps=None): - super().__init__(maps) + def is_indexed(self, tag_id): + return False + + @property + def resource_maps_folder(self): return None + @property + def uses_bitmaps_map(self): return False + @property + def uses_loc_map(self): return False + @property + def uses_sounds_map(self): return False \ No newline at end of file diff --git a/reclaimer/meta/wrappers/halo1_yelo.py b/reclaimer/meta/wrappers/halo1_yelo.py index 526b0caf..0e92bbc6 100644 --- a/reclaimer/meta/wrappers/halo1_yelo.py +++ b/reclaimer/meta/wrappers/halo1_yelo.py @@ -6,31 +6,96 @@ # Reclaimer is free software under the GNU General Public License v3.0. # See LICENSE for more information. # -from reclaimer.meta.wrappers.halo1_map import Halo1Map -from reclaimer.os_v4_hek.handler import OsV4HaloHandler +from reclaimer.meta.wrappers.halo1_map import Halo1Map, int_to_fourcc +from reclaimer.os_hek.defs.gelc import gelc_def +from reclaimer.os_v4_hek.defs.coll import fast_coll_def +from reclaimer.os_v4_hek.defs.sbsp import fast_sbsp_def +from reclaimer.os_v4_hek.handler import OsV4HaloHandler +from supyr_struct.defs.frozen_dict import FrozenDict + class Halo1YeloMap(Halo1Map): '''Generation 1 Yelo map''' + resource_map_prefix = "~" # Module path printed when loading the tag defs tag_defs_module = "reclaimer.os_v4_hek.defs" # Handler that controls how to load tags, eg tag definitions handler_class = OsV4HaloHandler + # NOTE: setting defs to None so setup_defs doesn't think the + # defs are setup cause of class property inheritance. + defs = None + + @property + def is_fully_yelo(self): + # since it's possible to compile open sauce maps without the hard + # requirement that they be yelo maps, this wrapper is able to be + # used with the engine still set to halo1ce. to determine if the + # map is truely a .yelo map, we need to check the engine. + return self.engine == "halo1yelo" + + @property + def uses_mod_data_files(self): + if not self.is_fully_yelo: + return False + + try: + return self.map_header.yelo_header.flags.uses_mod_data_files + except AttributeError: + return False + @property + def resource_map_prefix(self): + return "~" if self.uses_mod_data_files else "" + @property + def resource_maps_folder(self): + return self.filepath.parent.joinpath( + "data_files" if self.uses_mod_data_files else "" + ) + @property + def decomp_file_ext(self): + return ".yelo" if self.is_fully_yelo else self._decomp_file_ext + + @property + def project_yellow_tag_id(self): + if not(self.is_fully_yelo and self.tag_index): + return None + + for b in self.tag_index.tag_index: + if int_to_fourcc(b.class_1.data) == "yelo": + return b.id & 0xFFff + + @property + def globals_tag_id(self): + matg_tag_id = None + if self.is_fully_yelo: + yelo_meta = self.get_meta(self.project_yellow_tag_id) + if yelo_meta: + matg_tag_id = yelo_meta.globals_override.id & 0xFFff + + for b in self.tag_index.tag_index: + if matg_tag_id in range(len(self.tag_index.tag_index)): + break + + if int_to_fourcc(b.class_1.data) == "matg": + matg_tag_id = b.id & 0xFFff + + if matg_tag_id in range(len(self.tag_index.tag_index)): + return self.tag_index.tag_index[matg_tag_id].id & 0xFFff + + def setup_defs(self): + this_class = type(self) + if this_class.defs is None: + this_class.defs = defs = {} + print(" Loading definitions in '%s'" % self.tag_defs_module) + this_class.handler = self.handler_class( + build_reflexive_cache=False, build_raw_data_cache=False, + debug=2) + + this_class.defs = dict(this_class.handler.defs) + this_class.defs["coll"] = fast_coll_def + this_class.defs["sbsp"] = fast_sbsp_def + this_class.defs["gelc"] = gelc_def + this_class.defs = FrozenDict(this_class.defs) - def __init__(self, maps=None): - Halo1Map.__init__(self, maps) - - def load_map(self, map_path, **kwargs): - super().load_map(map_path, **kwargs) - - # NOTE: yelo halo 1 resource maps MUST be handled differently than - # pc and ce because of the multiple types they may use. - # yelo maps may reference different resource maps - # depending on what folder they're located in, so we're - # going to ignore any resource maps passed in unless - # they're coming from the same folder as this map. - if not self.are_resources_in_same_directory: - print("Unlinking potentially incompatible resource maps from %s" % - self.map_name - ) - self.maps = {} \ No newline at end of file + # make a shallow copy for this instance to manipulate + self.defs = dict(self.defs) \ No newline at end of file diff --git a/reclaimer/meta/wrappers/halo3_beta_map.py b/reclaimer/meta/wrappers/halo3_beta_map.py index 8667686a..71a837fc 100644 --- a/reclaimer/meta/wrappers/halo3_beta_map.py +++ b/reclaimer/meta/wrappers/halo3_beta_map.py @@ -11,4 +11,7 @@ class Halo3BetaMap(Halo3Map): tag_defs_module = "" - tag_classes_to_load = tuple() + tag_classes_to_load = () + # NOTE: setting defs to None so setup_defs doesn't think the + # defs are setup cause of class property inheritance. + defs = None \ No newline at end of file diff --git a/reclaimer/meta/wrappers/halo3_map.py b/reclaimer/meta/wrappers/halo3_map.py index 420849da..e2b37a1d 100644 --- a/reclaimer/meta/wrappers/halo3_map.py +++ b/reclaimer/meta/wrappers/halo3_map.py @@ -66,8 +66,6 @@ def get_bitmap_pixel_data(halo_map, bitm_meta, bitmap_index): def inject_bitmap_data(halo_map, bitm_meta): processed_pixel_data = bitm_meta.processed_pixel_data bitmaps = bitm_meta.bitmaps.STEPTREE - n_assets = bitm_meta.zone_assets_normal.STEPTREE - i_assets = bitm_meta.zone_assets_interleaved.STEPTREE processed_pixel_data.data = bytearray() for i in range(len(bitmaps)): @@ -184,7 +182,7 @@ def get_root_tag(self, tag_id_or_cls): if isinstance(tag_id_or_cls, int): tag_id_or_cls &= 0xFFff if tag_id_or_cls not in self.root_tags: - self.load_root_tags(tag_id_or_cls) + self.load_root_tags([tag_id_or_cls]) return self.root_tags.get(tag_id_or_cls) @@ -198,7 +196,7 @@ def load_root_tags(self, tag_ids_to_load=()): ("scenario", "globals")) for tag_id in tag_ids_to_load: - tag_cls = tag_classes_to_load_by_ids[tag_id] + tag_cls = tag_classes_to_load_by_ids.get(tag_id) meta = self.get_meta(tag_id) if meta: self.root_tags[tag_id & 0xFFff] = meta diff --git a/reclaimer/meta/wrappers/halo3_odst_map.py b/reclaimer/meta/wrappers/halo3_odst_map.py index 7f23f18f..baa43e9c 100644 --- a/reclaimer/meta/wrappers/halo3_odst_map.py +++ b/reclaimer/meta/wrappers/halo3_odst_map.py @@ -8,7 +8,28 @@ # from .halo3_map import Halo3Map +from reclaimer.h3.defs.bitm import bitm_def +from reclaimer.h3.defs.play import play_def +from reclaimer.h3.defs.zone import zone_def_odst_partial +from supyr_struct.defs.frozen_dict import FrozenDict class Halo3OdstMap(Halo3Map): tag_defs_module = "" - tag_classes_to_load = tuple() + tag_classes_to_load = ( + "play", "bitm", "zone" + ) + # NOTE: setting defs to None so setup_defs doesn't think the + # defs are setup cause of class property inheritance. + defs = None + + def setup_defs(self): + this_class = type(self) + if this_class.defs is None: + this_class.defs = FrozenDict({ + "zone": zone_def_odst_partial, + "bitm": bitm_def, + "play": play_def, + }) + + # make a shallow copy for this instance to manipulate + self.defs = dict(self.defs) \ No newline at end of file diff --git a/reclaimer/meta/wrappers/halo4_beta_map.py b/reclaimer/meta/wrappers/halo4_beta_map.py index 50fa37c5..200d2aa9 100644 --- a/reclaimer/meta/wrappers/halo4_beta_map.py +++ b/reclaimer/meta/wrappers/halo4_beta_map.py @@ -11,4 +11,7 @@ class Halo4BetaMap(Halo4Map): tag_defs_module = "" - tag_classes_to_load = tuple() + tag_classes_to_load = () + # NOTE: setting defs to None so setup_defs doesn't think the + # defs are setup cause of class property inheritance. + defs = None \ No newline at end of file diff --git a/reclaimer/meta/wrappers/halo4_map.py b/reclaimer/meta/wrappers/halo4_map.py index dadf043c..de723470 100644 --- a/reclaimer/meta/wrappers/halo4_map.py +++ b/reclaimer/meta/wrappers/halo4_map.py @@ -11,4 +11,7 @@ class Halo4Map(Halo3Map): tag_defs_module = "" - tag_classes_to_load = tuple() + tag_classes_to_load = () + # NOTE: setting defs to None so setup_defs doesn't think the + # defs are setup cause of class property inheritance. + defs = None \ No newline at end of file diff --git a/reclaimer/meta/wrappers/halo5_map.py b/reclaimer/meta/wrappers/halo5_map.py index 06f1c548..9d1415cc 100644 --- a/reclaimer/meta/wrappers/halo5_map.py +++ b/reclaimer/meta/wrappers/halo5_map.py @@ -12,3 +12,6 @@ class Halo5Map(Halo3Map): tag_defs_module = "" tag_classes_to_load = tuple() + # NOTE: setting defs to None so setup_defs doesn't think the + # defs are setup cause of class property inheritance. + defs = None \ No newline at end of file diff --git a/reclaimer/meta/wrappers/halo_map.py b/reclaimer/meta/wrappers/halo_map.py index c050f369..154a9641 100644 --- a/reclaimer/meta/wrappers/halo_map.py +++ b/reclaimer/meta/wrappers/halo_map.py @@ -16,6 +16,7 @@ from traceback import format_exc from string import ascii_letters +from reclaimer.constants import GEN_1_HALO_CUSTOM_ENGINES from reclaimer.meta.halo_map import get_map_version, get_map_header,\ get_tag_index, get_index_magic, get_map_magic, get_is_compressed_map,\ decompress_map, get_engine_name @@ -29,7 +30,12 @@ VALID_MODULE_NAME_CHARS = ascii_letters + '_' + '0123456789' -backslash_fix = re.compile(r"\\{2,}") +multi_backslash_sub = re.compile(r"\\{2,}|/+") +lead_trail_slash_space_sub = re.compile(r"^[\s\\]+|[\s\\]+$") +def sanitize_tag_path(tag_path): + return lead_trail_slash_space_sub.sub( + '', multi_backslash_sub.sub(r'\\', tag_path) + ).lower() class HaloMap: @@ -66,6 +72,7 @@ class HaloMap: engine = "" is_resource = False is_compressed = False + safe_mode = True handler = None @@ -147,13 +154,6 @@ def get_writable_map_data(self): # and replace self.map_data with it return self.map_data - @property - def are_resources_in_same_directory(self): - for map_name, filepath in self.get_resource_map_paths().items(): - if filepath and filepath.parent != self.filepath.parent: - return False - return True - # wrappers around the tag index handler def get_total_dir_count(self, dir=""): return self.tag_index_manager.get_total_dir_count(dir) def get_total_file_count(self, dir=""): return self.tag_index_manager.get_total_file_count(dir) @@ -219,7 +219,7 @@ def get_dependencies(self, meta, tag_id, tag_cls): return () def is_indexed(self, tag_id): - return bool(self.tag_index.tag_index[tag_id].indexed) + return False def cache_original_tag_paths(self): tags = () if self.tag_index is None else self.tag_index.tag_index @@ -231,23 +231,39 @@ def basic_deprotection(self): if self.tag_index is None: return - i = 0 - found_counts = {} - for b in self.tag_index.tag_index: - tag_path = backslash_fix.sub( - r'\\', b.path.replace("/", "\\")).strip().strip("\\").lower() - - name_id = (tag_path, b.class_1.enum_name) - if is_protected_tag(tag_path): + name_id_counts = {} + # count how many times each tag name and class combination is used + for i, b in enumerate(self.tag_index.tag_index): + name_id = (sanitize_tag_path(b.path), b.class_1.enum_name) + name_id_counts[name_id] = name_id_counts.get(name_id, 0) + 1 + + # figure out if any reused tag names stand out as protected + # because of being reused in with the same tag class + protected_names = set(( + name_id[0] + for name_id, count in name_id_counts.items() + if is_protected_tag(name_id[0]) or count > 1 + )) + + # rename tags deemed to be protected + for i, b in enumerate(self.tag_index.tag_index): + tag_path = sanitize_tag_path(b.path) + if not self.is_indexed(i) and tag_path in protected_names: tag_path = "protected_%s" % i - elif name_id in found_counts: - tag_path = "%s_%s" % (tag_path, found_counts[name_id]) - found_counts[name_id] += 1 - else: - found_counts[name_id] = 1 b.path = tag_path - i += 1 + + name_id_counts = {} + # this bit is cause apparently spv3/open sauce v4/neil fucked up + # resource maps, and made it so that can contain duplicate tags. + for i, b in enumerate(self.tag_index.tag_index): + tag_path = sanitize_tag_path(b.path) + name_id = (tag_path, b.class_1.enum_name) + count = name_id_counts.setdefault(name_id, 1) + if self.is_indexed(i) and count > 1: + b.path = "%s_%s" % (tag_path, count) + + name_id_counts[name_id] = count + 1 def get_meta_descriptor(self, tag_cls): tagdef = self.defs.get(tag_cls) @@ -326,11 +342,12 @@ def load_resource_maps(self, maps_dir="", map_paths=(), **kw): print("Loading %s..." % map_name) new_map.load_map(map_path, **kw) - if self.engine == "halo1mcc" and new_map.engine in ("halo1pc", "halo1ce"): - # cant tell the difference between mcc, ce, and pc resource maps + if (self.engine in GEN_1_HALO_CUSTOM_ENGINES and + new_map.engine in ("halo1pc", "halo1ce")): + # cant tell mcc resource maps apart from ce and pc resource maps. + # the same can be said of telling apart yelo and ce resource maps. new_map.engine = self.engine - - if new_map.engine != self.engine: + elif new_map.engine != self.engine: if do_printout: print("Incorrect engine for this map.") self.maps.pop(new_map.map_name, None) diff --git a/reclaimer/meta/wrappers/halo_reach_beta_map.py b/reclaimer/meta/wrappers/halo_reach_beta_map.py index a99c0ca3..d68deefb 100644 --- a/reclaimer/meta/wrappers/halo_reach_beta_map.py +++ b/reclaimer/meta/wrappers/halo_reach_beta_map.py @@ -11,4 +11,7 @@ class HaloReachBetaMap(HaloReachMap): tag_defs_module = "" - tag_classes_to_load = tuple() + tag_classes_to_load = () + # NOTE: setting defs to None so setup_defs doesn't think the + # defs are setup cause of class property inheritance. + defs = None \ No newline at end of file diff --git a/reclaimer/meta/wrappers/halo_reach_map.py b/reclaimer/meta/wrappers/halo_reach_map.py index 5159e618..cae81c23 100644 --- a/reclaimer/meta/wrappers/halo_reach_map.py +++ b/reclaimer/meta/wrappers/halo_reach_map.py @@ -11,4 +11,7 @@ class HaloReachMap(Halo3Map): tag_defs_module = "" - tag_classes_to_load = tuple() + tag_classes_to_load = () + # NOTE: setting defs to None so setup_defs doesn't think the + # defs are setup cause of class property inheritance. + defs = None \ No newline at end of file diff --git a/reclaimer/meta/wrappers/shadowrun_map.py b/reclaimer/meta/wrappers/shadowrun_map.py index 43f27f8d..9dc31608 100644 --- a/reclaimer/meta/wrappers/shadowrun_map.py +++ b/reclaimer/meta/wrappers/shadowrun_map.py @@ -7,13 +7,15 @@ # See LICENSE for more information. # -from reclaimer.meta.wrappers.halo1_map import Halo1Map +from reclaimer.meta.wrappers.halo1_xbox_map import Halo1XboxMap from reclaimer.shadowrun_prototype.handler import ShadowrunPrototypeHandler from reclaimer.shadowrun_prototype.constants import sr_tag_class_fcc_to_ext from supyr_struct.defs.frozen_dict import FrozenDict -class ShadowrunMap(Halo1Map): +class ShadowrunMap(Halo1XboxMap): + # NOTE: setting defs to None so setup_defs doesn't think the + # defs are setup cause of class property inheritance. defs = None handler_class = ShadowrunPrototypeHandler diff --git a/reclaimer/meta/wrappers/stubbs_map.py b/reclaimer/meta/wrappers/stubbs_map.py index 746744cf..9f03dca1 100644 --- a/reclaimer/meta/wrappers/stubbs_map.py +++ b/reclaimer/meta/wrappers/stubbs_map.py @@ -7,13 +7,13 @@ # See LICENSE for more information. # -from reclaimer.meta.wrappers.halo1_map import Halo1Map +from reclaimer.meta.wrappers.halo1_xbox_map import Halo1XboxMap from reclaimer.stubbs.constants import stubbs_tag_class_fcc_to_ext from reclaimer.stubbs.handler import StubbsHandler from supyr_struct.defs.frozen_dict import FrozenDict -class StubbsMap(Halo1Map): +class StubbsMap(Halo1XboxMap): xbox_defs = None pc_defs = None @@ -34,6 +34,9 @@ def defs(self, val): else: this_class.xbox_defs = val + def is_indexed(self, tag_id): + return False + def setup_defs(self): this_class = type(self) if not(this_class.xbox_defs and this_class.pc_defs): diff --git a/reclaimer/meta/wrappers/stubbs_map_64bit.py b/reclaimer/meta/wrappers/stubbs_map_64bit.py index 441b30df..e9201a85 100644 --- a/reclaimer/meta/wrappers/stubbs_map_64bit.py +++ b/reclaimer/meta/wrappers/stubbs_map_64bit.py @@ -19,7 +19,9 @@ class StubbsMap64Bit(StubbsMap): tag_defs_module = StubbsHandler.default_defs_path tag_classes_to_load = () - defs = () + # NOTE: setting defs to None so setup_defs doesn't think the + # defs are setup cause of class property inheritance. + defs = None def setup_defs(self): self.defs = {} diff --git a/reclaimer/model/util.py b/reclaimer/model/util.py index ddf329e2..0fe8d3e8 100644 --- a/reclaimer/model/util.py +++ b/reclaimer/model/util.py @@ -9,7 +9,7 @@ import os -from reclaimer.constants import LOD_NAMES +from reclaimer.constants import LOD_NAMES, SINT16_MAX from reclaimer.model.jms import JmsVertex from reclaimer.hek.defs.scex import scex_def from reclaimer.hek.defs.schi import schi_def @@ -32,12 +32,12 @@ mod2_verts_def = BlockDef( - raw_reflexive("vertices", mod2_vert_struct, 65535), + raw_reflexive("vertices", mod2_vert_struct, SINT16_MAX), endian='>' ) mod2_tri_strip_def = BlockDef( - raw_reflexive("triangle", mod2_tri_struct, 65535), + raw_reflexive("triangle", mod2_tri_struct, SINT16_MAX), endian='>' ) diff --git a/reclaimer/os_hek/defs/antr.py b/reclaimer/os_hek/defs/antr.py index e483d137..322c33aa 100644 --- a/reclaimer/os_hek/defs/antr.py +++ b/reclaimer/os_hek/defs/antr.py @@ -10,7 +10,9 @@ from ...hek.defs.antr import * antr_body = desc_variant(antr_body, - reflexive("animations", animation_desc, 2048, DYN_NAME_PATH=".name") + # original maximum according to comment? + # https://github.com/HaloMods/OpenSauce/blob/master/OpenSauce/Halo1/Halo1_CheApe/Halo1_CheApe_Readme.txt#L40 + reflexive("animations", animation_desc, 500, DYN_NAME_PATH=".name") ) def get(): diff --git a/reclaimer/os_hek/defs/yelo.py b/reclaimer/os_hek/defs/yelo.py index 1aef998f..52bb800c 100644 --- a/reclaimer/os_hek/defs/yelo.py +++ b/reclaimer/os_hek/defs/yelo.py @@ -22,6 +22,9 @@ "release", ), SInt32("revision"), + Timestamp64("timestamp", VISIBLE=False), + StrHex("uuid", SIZE=16, VISIBLE=False), + SIZE=48 ) @@ -73,22 +76,26 @@ reflexive("build_info", build_info, 1), Pad(40), + reflexive("scripted_ui_widgets", scripted_ui_widget, 128, DYN_NAME_PATH='.name'), - Pad(16), + # Physics Float("gravity_scale", MIN=0.0, MAX=2.0, SIDETIP="[0,2]"), Float("player_speed_scale", MIN=0.0, MAX=6.0, SIDETIP="[0,6]"), + Pad(20), + + Bool32("networking_flags", VISIBLE=False), # unused + Pad(20), - Pad(44), Bool32("gameplay_model", "prohibit_multi_team_vehicles", ), - Pad(20), + reflexive("yelo_scripting", yelo_scripting, 1), - Pad(12),#reflexive("unknown", void_desc), + Pad(92), SIZE=312 ) diff --git a/reclaimer/os_v3_hek/defs/antr.py b/reclaimer/os_v3_hek/defs/antr.py index 811f9e16..0d01d23c 100644 --- a/reclaimer/os_v3_hek/defs/antr.py +++ b/reclaimer/os_v3_hek/defs/antr.py @@ -8,3 +8,23 @@ # from ...os_hek.defs.antr import * + +antr_body = desc_variant(antr_body, + # open sauce increases all these + # https://github.com/HaloMods/OpenSauce/blob/master/OpenSauce/shared/Include/blamlib/Halo1/models/model_animation_definitions.hpp + reflexive("units", unit_desc, 64, DYN_NAME_PATH=".label"), + reflexive("sound_references", sound_reference_desc, 257*2, + DYN_NAME_PATH=".sound.filepath" + ), + reflexive("animations", animation_desc, 2048, DYN_NAME_PATH=".name") + ) + +def get(): + return antr_def + +antr_def = TagDef("antr", + blam_header('antr', 4), + antr_body, + + ext=".model_animations", endian=">", tag_cls=AntrTag + ) \ No newline at end of file diff --git a/reclaimer/os_v4_hek/defs/antr.py b/reclaimer/os_v4_hek/defs/antr.py index 811f9e16..444296ab 100644 --- a/reclaimer/os_v4_hek/defs/antr.py +++ b/reclaimer/os_v4_hek/defs/antr.py @@ -7,4 +7,27 @@ # See LICENSE for more information. # -from ...os_hek.defs.antr import * +from ...os_v3_hek.defs.antr import * + +unit_desc = desc_variant(unit_desc, + reflexive("animations", anim_enum_desc, + len(unit_animation_names_os), + *unit_animation_names_os + ) + ) + +antr_body = desc_variant(antr_body, + # this was further increased in os v4 + # https://github.com/HaloMods/OpenSauce/blob/master/OpenSauce/shared/Include/blamlib/Halo1/models/model_animation_definitions.hpp#L21 + reflexive("units", unit_desc, 512, DYN_NAME_PATH=".label"), + ) + +def get(): + return antr_def + +antr_def = TagDef("antr", + blam_header('antr', 4), + antr_body, + + ext=".model_animations", endian=">", tag_cls=AntrTag + ) \ No newline at end of file diff --git a/reclaimer/os_v4_hek/defs/gelo.py b/reclaimer/os_v4_hek/defs/gelo.py index 80c9bae8..0066aa1f 100644 --- a/reclaimer/os_v4_hek/defs/gelo.py +++ b/reclaimer/os_v4_hek/defs/gelo.py @@ -7,7 +7,7 @@ # See LICENSE for more information. # -from ...os_hek.defs.gelo import * +from ...os_v3_hek.defs.gelo import * gelo_body = desc_variant(gelo_body, # was removed diff --git a/reclaimer/os_v4_hek/defs/obje.py b/reclaimer/os_v4_hek/defs/obje.py index ebfa0161..7813138d 100644 --- a/reclaimer/os_v4_hek/defs/obje.py +++ b/reclaimer/os_v4_hek/defs/obje.py @@ -16,8 +16,6 @@ 'brighter_than_it_should_be', 'not_a_pathfinding_obstacle', 'cast_shadow_by_default', - {NAME: 'xbox_unknown_bit_8', VALUE: 1<<8, VISIBLE: False}, - {NAME: 'xbox_unknown_bit_11', VALUE: 1<<11, VISIBLE: False}, ), ) diff --git a/reclaimer/os_v4_hek/defs/part.py b/reclaimer/os_v4_hek/defs/part.py index aec9d260..130df894 100644 --- a/reclaimer/os_v4_hek/defs/part.py +++ b/reclaimer/os_v4_hek/defs/part.py @@ -7,7 +7,7 @@ # See LICENSE for more information. # -from ...hek.defs.part import * +from ...os_v3_hek.defs.part import * particle_shader_extensions = reflexive("particle_shader_extensions", Struct("particle_shader_extension", INCLUDE=os_shader_extension), diff --git a/reclaimer/os_v4_hek/defs/pctl.py b/reclaimer/os_v4_hek/defs/pctl.py index 407dd9dc..571260f4 100644 --- a/reclaimer/os_v4_hek/defs/pctl.py +++ b/reclaimer/os_v4_hek/defs/pctl.py @@ -7,7 +7,7 @@ # See LICENSE for more information. # -from ...hek.defs.pctl import * +from ...os_v3_hek.defs.pctl import * shader_extensions = reflexive("shader_extensions", Struct("shader_extension", INCLUDE=os_shader_extension), 1 diff --git a/reclaimer/os_v4_hek/defs/senv.py b/reclaimer/os_v4_hek/defs/senv.py index 0f49da24..0b00e968 100644 --- a/reclaimer/os_v4_hek/defs/senv.py +++ b/reclaimer/os_v4_hek/defs/senv.py @@ -7,7 +7,7 @@ # See LICENSE for more information. # -from ...os_hek.defs.senv import * +from ...os_v3_hek.defs.senv import * dlm_comment = """DIRECTIONAL LIGHTMAP PROPERTIES Special shader settings for when your map has directional lightmaps rendered for it.""" diff --git a/reclaimer/os_v4_hek/defs/unit.py b/reclaimer/os_v4_hek/defs/unit.py index f5b2110c..e38c7479 100644 --- a/reclaimer/os_v4_hek/defs/unit.py +++ b/reclaimer/os_v4_hek/defs/unit.py @@ -240,7 +240,37 @@ ("pad_20", reflexive("seat_extensions", seat_extension, 1)), ) +unit_flags = Bool32("flags", + "circular_aiming", + "destroyed_after_dying", + "half_speed_interpolation", + "fires_from_camera", + "entrance_inside_bounding_sphere", + "unused", + "causes_passenger_dialogue", + "resists_pings", + "melee_attack_is_fatal", + "dont_reface_during_pings", + "has_no_aiming", + "simple_creature", + "impact_melee_attaches_to_unit", + "impact_melee_dies_on_shields", + "cannot_open_doors_automatically", + "melee_attackers_cannot_attach", + "not_instantly_killed_by_melee", + "shield_sapping", + "runs_around_flaming", + "inconsequential", + "special_cinematic_unit", + "ignored_by_autoaiming", + "shields_fry_infection_forms", + "integrated_light_controls_weapon", + "integrated_light_lasts_forever", + ("has_boarding_seats", 1<<31) + ) + unit_attrs = desc_variant(unit_attrs, + unit_flags, ("pad_45", reflexive("unit_extensions", unit_extension, 1)), reflexive("seats", seat, 16, DYN_NAME_PATH='.label'), ) diff --git a/reclaimer/os_v4_hek/defs/weap.py b/reclaimer/os_v4_hek/defs/weap.py index 050d99d3..ee92f91d 100644 --- a/reclaimer/os_v4_hek/defs/weap.py +++ b/reclaimer/os_v4_hek/defs/weap.py @@ -11,8 +11,21 @@ from .obje import * from .item import * +magazine = desc_variant(magazine, + reflexive("magazine_items", magazine_item, 8, + "primary", + "secondary_primary_2", + # so uh, spv3 does a thing with extra ammo pickups that + # act as alternate ammo counts for the primary magazine + *("primary_%s" % (3 + i) for i in range(6)) + ), + ) +weap_attrs = desc_variant(weap_attrs, + reflexive("magazines", magazine, 2, "primary", "secondary") + ) + obje_attrs = obje_attrs_variant(obje_attrs, "weap") -weap_body = desc_variant(weap_body, obje_attrs) +weap_body = desc_variant(weap_body, obje_attrs, weap_attrs) def get(): return weap_def diff --git a/reclaimer/shadowrun_prototype/defs/matg.py b/reclaimer/shadowrun_prototype/defs/matg.py index 780084e6..2c383d66 100644 --- a/reclaimer/shadowrun_prototype/defs/matg.py +++ b/reclaimer/shadowrun_prototype/defs/matg.py @@ -8,3 +8,18 @@ # from ...hek.defs.matg import * + +def get(): + return matg_def + +# shadowrun has more grenade types +matg_body = desc_variant(matg_body, + reflexive("grenades", grenade, 5) + ) + +matg_def = TagDef("matg", + blam_header_os('matg', 3), + matg_body, + + ext=".globals", endian=">", tag_cls=HekTag + ) diff --git a/reclaimer/stubbs/defs/antr.py b/reclaimer/stubbs/defs/antr.py index 14760a7e..01e24765 100644 --- a/reclaimer/stubbs/defs/antr.py +++ b/reclaimer/stubbs/defs/antr.py @@ -98,7 +98,7 @@ ("sound_references", reflexive("effect_references", effect_reference_desc, DYN_NAME_PATH=".effect.filepath") ), - reflexive("animations", animation_desc, DYN_NAME_PATH=".name"), + reflexive("animations", animation_desc, DYN_NAME_PATH=".name", EXT_MAX=2048), SIZE=128, ) diff --git a/reclaimer/stubbs/defs/mode.py b/reclaimer/stubbs/defs/mode.py index 6bb14167..7104e66d 100644 --- a/reclaimer/stubbs/defs/mode.py +++ b/reclaimer/stubbs/defs/mode.py @@ -29,22 +29,22 @@ def get(): pc_part = desc_variant(part, model_meta_info) fast_part = desc_variant(part, - raw_reflexive("uncompressed_vertices", fast_uncompressed_vertex), - raw_reflexive("compressed_vertices", fast_compressed_vertex), - raw_reflexive("triangles", triangle), + raw_reflexive("uncompressed_vertices", fast_uncompressed_vertex, SINT16_MAX), + raw_reflexive("compressed_vertices", fast_compressed_vertex, SINT16_MAX), + raw_reflexive("triangles", triangle, SINT16_MAX), ) fast_pc_part = desc_variant(fast_part, model_meta_info) -pc_geometry = desc_variant(geometry, reflexive("parts", pc_part, 32)) -fast_geometry = desc_variant(geometry, reflexive("parts", fast_part, 32)) -fast_pc_geometry = desc_variant(geometry, reflexive("parts", fast_pc_part, 32)) +pc_geometry = desc_variant(geometry, reflexive("parts", pc_part, 32, EXT_MAX=SINT16_MAX)) +fast_geometry = desc_variant(geometry, reflexive("parts", fast_part, 32, EXT_MAX=SINT16_MAX)) +fast_pc_geometry = desc_variant(geometry, reflexive("parts", fast_pc_part, 32, EXT_MAX=SINT16_MAX)) mode_body = desc_variant(mode_body, ("pad_16", reflexive("unknown", unknown_struct, DYN_NAME_PATH=".name")), ) -pc_mode_body = desc_variant(mode_body, reflexive("geometries", pc_geometry, 256)) -fast_mode_body = desc_variant(mode_body, reflexive("geometries", fast_geometry, 256)) -fast_pc_mode_body = desc_variant(mode_body, reflexive("geometries", fast_pc_geometry, 256)) +pc_mode_body = desc_variant(mode_body, reflexive("geometries", pc_geometry, 256, EXT_MAX=SINT16_MAX)) +fast_mode_body = desc_variant(mode_body, reflexive("geometries", fast_geometry, 256, EXT_MAX=SINT16_MAX)) +fast_pc_mode_body = desc_variant(mode_body, reflexive("geometries", fast_pc_geometry, 256, EXT_MAX=SINT16_MAX)) # increment version to differentiate from halo models stubbs_mode_header = blam_header_stubbs('mode', 6) diff --git a/reclaimer/util/__init__.py b/reclaimer/util/__init__.py index 115604db..7257a8df 100644 --- a/reclaimer/util/__init__.py +++ b/reclaimer/util/__init__.py @@ -20,7 +20,7 @@ VALID_NUMERIC_CHARS = frozenset("0123456789") for name in ('CON', 'PRN', 'AUX', 'NUL'): RESERVED_WINDOWS_FILENAME_MAP[name] = '_' + name -for i in range(1, 9): +for i in VALID_NUMERIC_CHARS: RESERVED_WINDOWS_FILENAME_MAP['COM%s' % i] = '_COM%s' % i RESERVED_WINDOWS_FILENAME_MAP['LPT%s' % i] = '_LPT%s' % i INVALID_PATH_CHARS.update('<>:"|?*') diff --git a/reclaimer/util/geometry.py b/reclaimer/util/geometry.py index 30cf386f..f495e940 100644 --- a/reclaimer/util/geometry.py +++ b/reclaimer/util/geometry.py @@ -345,9 +345,8 @@ def depth(self): return depth -def planes_to_verts_and_edge_loops(planes, center, plane_dir=True, max_plane_ct=32, +def planes_to_verts_and_edge_loops(planes, plane_dir=True, max_plane_ct=32, use_double_rounding=False, round_adjust=0): - assert len(center) == 3 # make a set out of the planes to remove duplicates planes = list(set(tuple(Plane(p).normalized) for p in planes)) indices_by_planes = {p: set() for p in planes} From 16b22c84c8a23869859eba1b69bc97edf38ff6b4 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Tue, 20 Feb 2024 07:21:21 -0600 Subject: [PATCH 36/51] Bump version --- reclaimer/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reclaimer/__init__.py b/reclaimer/__init__.py index c4b417df..1208b4df 100644 --- a/reclaimer/__init__.py +++ b/reclaimer/__init__.py @@ -12,8 +12,8 @@ # ############## __author__ = "Sigmmma" # YYYY.MM.DD -__date__ = "2024.02.11" -__version__ = (2, 16, 0) +__date__ = "2024.02.20" +__version__ = (2, 17, 0) __website__ = "https://github.com/Sigmmma/reclaimer" __all__ = ( "animation", "bitmaps", "h2", "h3", "halo_script", "hek", "meta", "misc", From 76ebd11cc70b2bdfbb2df839c2004a9cdf94c404 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Mon, 26 Feb 2024 01:38:15 -0600 Subject: [PATCH 37/51] Update audio conversion to work with python 3.12 --- reclaimer/constants.py | 14 + reclaimer/hek/defs/mod2.py | 9 +- reclaimer/hek/defs/mode.py | 9 +- reclaimer/hek/defs/sbsp.py | 13 +- reclaimer/hek/defs/snd_.py | 2 +- reclaimer/hek/handler.py | 14 +- reclaimer/meta/wrappers/byteswapping.py | 137 +++++++++- reclaimer/meta/wrappers/halo1_anni_map.py | 310 +++++---------------- reclaimer/meta/wrappers/halo1_map.py | 285 ++++++++++--------- reclaimer/meta/wrappers/halo1_mcc_map.py | 2 +- reclaimer/meta/wrappers/halo1_rsrc_map.py | 8 +- reclaimer/sounds/adpcm.py | 11 +- reclaimer/sounds/audioop.py | 318 ++++++++++++++++++++++ reclaimer/sounds/profile_data.txt | 51 ++++ reclaimer/sounds/util.py | 26 +- reclaimer/util/compression.py | 11 +- 16 files changed, 778 insertions(+), 442 deletions(-) create mode 100644 reclaimer/sounds/audioop.py create mode 100644 reclaimer/sounds/profile_data.txt diff --git a/reclaimer/constants.py b/reclaimer/constants.py index 4177688f..43d1fd51 100644 --- a/reclaimer/constants.py +++ b/reclaimer/constants.py @@ -259,6 +259,20 @@ def inject_halo_constants(): MAX_TAG_PATH_LEN = 254 +# NOTE: do not change these names. they are used with Block.set_to +H1_TRIANGLE_BUFFER_TYPES = ( + "triangle_list", + "triangle_strip" + ) +H1_VERTEX_BUFFER_TYPES = ( + "sbsp_uncomp_material_verts", + "sbsp_comp_material_verts", + "sbsp_uncomp_lightmap_verts", + "sbsp_comp_lightmap_verts", + "model_uncomp_verts", + "model_comp_verts", + ) + # maps tag class four character codes(fccs) in # their string encoding to their int encoding. tag_class_fcc_to_be_int = {} diff --git a/reclaimer/hek/defs/mod2.py b/reclaimer/hek/defs/mod2.py index 6c311bed..30df8d0c 100644 --- a/reclaimer/hek/defs/mod2.py +++ b/reclaimer/hek/defs/mod2.py @@ -152,19 +152,14 @@ def get(): ) model_meta_info = Struct("model_meta_info", - UEnum16("index_type", # name is a guess. always 1? - ("uncompressed", 1), - ), + UEnum16("index_type", *H1_TRIANGLE_BUFFER_TYPES), Pad(2), UInt32("index_count"), # THESE TWO VALUES ARE DIFFERENT THAN ON XBOX IT SEEMS UInt32("indices_magic_offset"), UInt32("indices_offset"), - UEnum16("vertex_type", # name is a guess - ("uncompressed", 4), - ("compressed", 5), - ), + UEnum16("vertex_type", *H1_VERTEX_BUFFER_TYPES), Pad(2), UInt32("vertex_count"), Pad(4), # always 0? diff --git a/reclaimer/hek/defs/mode.py b/reclaimer/hek/defs/mode.py index 9a1b5dce..d7b841ea 100644 --- a/reclaimer/hek/defs/mode.py +++ b/reclaimer/hek/defs/mode.py @@ -48,18 +48,13 @@ def get(): # dip == double-indirect pointer dip_model_meta_info = Struct("model_meta_info", - UEnum16("index_type", # name is a guess. always 1? - ("uncompressed", 1), - ), + UEnum16("index_type", *H1_TRIANGLE_BUFFER_TYPES), Pad(2), UInt32("index_count"), UInt32("indices_offset"), UInt32("indices_reflexive_offset"), - UEnum16("vertex_type", # name is a guess - ("uncompressed", 4), - ("compressed", 5), - ), + UEnum16("vertex_type", *H1_VERTEX_BUFFER_TYPES), Pad(2), UInt32("vertex_count"), Pad(4), # always 0? diff --git a/reclaimer/hek/defs/sbsp.py b/reclaimer/hek/defs/sbsp.py index 21116f5c..9d610f5e 100644 --- a/reclaimer/hek/defs/sbsp.py +++ b/reclaimer/hek/defs/sbsp.py @@ -151,7 +151,9 @@ QStruct("shadow_color", INCLUDE=rgb_float), QStruct("plane", INCLUDE=plane), SInt16("breakable_surface", EDITABLE=False), - Pad(6), + Pad(2), + UEnum8("vertex_type", *H1_VERTEX_BUFFER_TYPES, VISIBLE=False), + Pad(3), SInt32("vertices_count", EDITABLE=False), SInt32("vertices_offset", EDITABLE=False, VISIBLE=False), @@ -163,13 +165,8 @@ "bspmagic relative pointer to the vertices."), VISIBLE=False ), - FlUEnum16("vertex_type", # name is a guess - ("unknown", 0), - ("uncompressed", 2), - ("compressed", 3), - VISIBLE=False, - ), - Pad(2), + UEnum8("lightmap_vertex_type", *H1_VERTEX_BUFFER_TYPES, VISIBLE=False), + Pad(3), SInt32("lightmap_vertices_count", EDITABLE=False), SInt32("lightmap_vertices_offset", EDITABLE=False, VISIBLE=False), diff --git a/reclaimer/hek/defs/snd_.py b/reclaimer/hek/defs/snd_.py index 812fab9a..c5494782 100644 --- a/reclaimer/hek/defs/snd_.py +++ b/reclaimer/hek/defs/snd_.py @@ -171,6 +171,6 @@ def get(): ) snd__meta_stub = desc_variant( - snd__body, ("pitch_ranges", Pad(12)) + snd__body, ("pitch_ranges", reflexive_struct) ) snd__meta_stub_blockdef = BlockDef(snd__meta_stub) diff --git a/reclaimer/hek/handler.py b/reclaimer/hek/handler.py index 05a3ef2b..ce963b08 100644 --- a/reclaimer/hek/handler.py +++ b/reclaimer/hek/handler.py @@ -83,8 +83,6 @@ def __init__(self, *args, **kwargs): self.datadir = Path( kwargs.get("datadir", self.tagsdir.parent.joinpath("data"))) - # These break on Python 3.9 - if self.tag_ref_cache is None: self.tag_ref_cache = self.build_loc_caches(TagRef) @@ -112,16 +110,10 @@ def _build_loc_cache(self, cond, desc={}): if f_type is None: return NO_LOC_REFS - # python 3.9 band-aid - - try: - nodepath_ref = NodepathRef(cond(desc)) - except Exception: - print("Ignore me if you're not a developer") - print(format_exc()) - return NO_LOC_REFS - + nodepath_ref = NodepathRef(cond(desc)) for key in desc: + if not isinstance(desc[key], dict): + continue sub_nodepath_ref = self._build_loc_cache(cond, desc[key]) if sub_nodepath_ref.is_ref or sub_nodepath_ref: nodepath_ref[key] = sub_nodepath_ref diff --git a/reclaimer/meta/wrappers/byteswapping.py b/reclaimer/meta/wrappers/byteswapping.py index 89b30c72..6a6a6467 100644 --- a/reclaimer/meta/wrappers/byteswapping.py +++ b/reclaimer/meta/wrappers/byteswapping.py @@ -14,7 +14,8 @@ ''' import array -from reclaimer.sounds.util import byteswap_pcm16_sample_data +from struct import Struct as PyStruct +from reclaimer.sounds import audioop from supyr_struct.field_types import BytearrayRaw from supyr_struct.defs.block_def import BlockDef @@ -24,12 +25,49 @@ except: fast_byteswapping = False +# These end_swap_XXXX functions are for byteswapping the +# endianness of values parsed from tags as the wrong order. +def end_swap_float(v, packer=PyStruct(">f").pack, + unpacker=PyStruct("= -0x80000000 and v < 0x80000000 + if v < 0: + v += 0x100000000 + v = ((((v << 24) + (v >> 24)) & 0xFF0000FF) + + ((v << 8) & 0xFF0000) + + ((v >> 8) & 0xFF00)) + if v & 0x80000000: + return v - 0x100000000 + return v + +def end_swap_int16(v): + assert v >= -0x8000 and v < 0x8000 + if v < 0: + v += 0x10000 + v = ((v << 8) + (v >> 8)) & 0xFFFF + if v & 0x8000: + return v - 0x10000 + return v + +def end_swap_uint32(v): + assert v >= 0 and v <= 0xFFFFFFFF + return ((((v << 24) + (v >> 24)) & 0xFF0000FF) + + ((v << 8) & 0xFF0000) + + ((v >> 8) & 0xFF00)) + +def end_swap_uint16(v): + assert v >= 0 and v <= 0xFFFF + return ((v << 8) + (v >> 8)) & 0xFFFF + raw_block_def = BlockDef("raw_block", BytearrayRaw('data', SIZE=lambda node, *a, **kw: 0 if node is None else len(node)) ) + def make_mutable_struct_array_copy(data, struct_size): valid_length = struct_size*(len(data)//struct_size) if valid_length == len(data): @@ -118,7 +156,8 @@ def byteswap_coll_bsp(bsp): def byteswap_pcm16_samples(pcm_block): # replace the verts with the byteswapped ones pcm_block.STEPTREE = bytearray( - byteswap_pcm16_sample_data(pcm_block.STEPTREE)) + audioop.byteswap(pcm_block.STEPTREE, 2) + ) def byteswap_sbsp_meta(meta): @@ -132,6 +171,100 @@ def byteswap_sbsp_meta(meta): byteswap_raw_reflexive(b) +def byteswap_anniversary_antr(meta): + # NOTE: don't need to byteswap the uncompresed animation data, as that's + # already handled by the non-anniversary antr byteswapping code. + + for b in meta.animations.STEPTREE: + b.first_permutation_index = end_swap_int16(b.first_permutation_index) + b.chance_to_play = end_swap_float(b.chance_to_play) + if not b.flags.compressed_data: + continue + + # slice out the compressed data and byteswap the + # 11 UInt32 that make up the 44 byte header that + # points to the + comp_data = bytearray(b.frame_data.data[b.offset_to_compressed_data: ]) + unswapped = bytes(comp_data) + byteswap_struct_array( + unswapped, comp_data, count=11, size=4, four_byte_offs=[0], + ) + header = PyStruct("<11i").unpack(comp_data[: 44]) + + # figure out where each array starts, ends, and the item size + starts = (0, *header) + ends = (*header, len(comp_data)) + widths = (4, 2, 2, 2, 4, 2, 4, 4, 4, 2, 4, 4) + + # byteswap each array + for start, end, width in zip(starts, ends, widths): + byteswap_struct_array( + unswapped, comp_data, size=width, + count = (end - start)//width, + four_byte_offs=([0] if width == 4 else []), + two_byte_offs=( [0] if width == 2 else []), + ) + + # replace the frame_data with the compressed data and some + # blank uncompressed default/frame data so tool doesnt cry + frame_data_size = b.frame_count * b.frame_size + default_data_size = b.node_count * (12 + 8 + 4) - b.frame_size + + b.offset_to_compressed_data = frame_data_size + + b.frame_data.data = bytearray(frame_data_size) + comp_data + b.default_data.data += bytearray( + max(0, len(b.default_data.data) - default_data_size) + ) + + +def byteswap_anniversary_sbsp(meta): + # make a copy of the nodes to byteswap to + orig = meta.nodes.STEPTREE + swapped = meta.nodes.STEPTREE = make_mutable_struct_array_copy(orig, 2) + byteswap_struct_array( + orig, swapped, size=2, + two_byte_offs=[0], + ) + + for lm in meta.lightmaps.STEPTREE: + for b in lm.materials.STEPTREE: + # logic is the same, so put comp/uncomp byteswap in a loop + for v_size, lm_v_size, rawdata_ref, sint16_offs in ( + [56, 20, b.uncompressed_vertices, ()], + [32, 8, b.compressed_vertices, (4, 6)] + ): + verts_size = v_size * b.vertices_count + lm_verts_size = lm_v_size * b.lightmap_vertices_count + + # make a copy of the verts to byteswap to + orig = rawdata_ref.STEPTREE[: verts_size+lm_verts_size] + swapped = rawdata_ref.STEPTREE = make_mutable_struct_array_copy(orig, 2) + + # render verts first + byteswap_struct_array( + orig, swapped, size=v_size, + start=0, count=b.vertices_count, + four_byte_offs=range(0, v_size, 4), + ) + + # followed by lightmap verts + byteswap_struct_array( + orig, swapped, size=lm_v_size, + start=verts_size, count=b.lightmap_vertices_count, + four_byte_offs=range(0, lm_v_size, 4), + two_byte_offs=sint16_offs, + ) + + +def byteswap_anniversary_rawdata_ref(rawdata_ref, **kwargs): + if rawdata_ref.size: + orig = rawdata_ref.serialize(attr_index="data") + swapped = bytearray(orig) + byteswap_struct_array(orig, swapped, **kwargs) + rawdata_ref.parse(rawdata=swapped, attr_index="data") + + def byteswap_scnr_script_syntax_data(meta): original = meta.script_syntax_data.data swapped = original[: ((len(original)-56)//20) * 20 + 56] diff --git a/reclaimer/meta/wrappers/halo1_anni_map.py b/reclaimer/meta/wrappers/halo1_anni_map.py index b09aa0f4..7fce6131 100644 --- a/reclaimer/meta/wrappers/halo1_anni_map.py +++ b/reclaimer/meta/wrappers/halo1_anni_map.py @@ -8,9 +8,13 @@ # from math import pi, sqrt, log -from struct import Struct as PyStruct from traceback import format_exc +from reclaimer.meta.wrappers.byteswapping import byteswap_anniversary_sbsp,\ + byteswap_anniversary_antr, byteswap_anniversary_rawdata_ref,\ + byteswap_scnr_script_syntax_data, end_swap_float, end_swap_int32,\ + end_swap_int16, end_swap_uint32, end_swap_uint16 + from reclaimer.meta.wrappers.halo_map import HaloMap from reclaimer.meta.wrappers.halo1_rsrc_map import Halo1RsrcMap from reclaimer.meta.wrappers.halo1_mcc_map import Halo1MccMap @@ -18,7 +22,6 @@ from reclaimer.halo_script.hsc import h1_script_syntax_data_def from reclaimer.hek.defs.coll import fast_coll_def from reclaimer.hek.defs.sbsp import fast_sbsp_def -from reclaimer.mcc_hek.defs.sbsp import sbsp_meta_header_def from reclaimer.hek.handler import HaloHandler from reclaimer.util import int_to_fourcc @@ -27,45 +30,6 @@ __all__ = ("Halo1AnniMap",) -def end_swap_float(v, packer=PyStruct(">f").pack, - unpacker=PyStruct("= -0x80000000 and v < 0x80000000 - if v < 0: - v += 0x100000000 - v = ((((v << 24) + (v >> 24)) & 0xFF0000FF) + - ((v << 8) & 0xFF0000) + - ((v >> 8) & 0xFF00)) - if v & 0x80000000: - return v - 0x100000000 - return v - - -def end_swap_int16(v): - assert v >= -0x8000 and v < 0x8000 - if v < 0: - v += 0x10000 - v = ((v << 8) + (v >> 8)) & 0xFFFF - if v & 0x8000: - return v - 0x10000 - return v - - -def end_swap_uint32(v): - assert v >= 0 and v <= 0xFFFFFFFF - return ((((v << 24) + (v >> 24)) & 0xFF0000FF) + - ((v << 8) & 0xFF0000) + - ((v >> 8) & 0xFF00)) - - -def end_swap_uint16(v): - assert v >= 0 and v <= 0xFFFF - return ((v << 8) + (v >> 8)) & 0xFFFF - - class Halo1AnniMap(Halo1MccMap): tag_headers = None # NOTE: setting defs to None so setup_defs doesn't think the @@ -73,47 +37,14 @@ class Halo1AnniMap(Halo1MccMap): defs = None handler_class = HaloHandler - - sbsp_meta_header_def = sbsp_meta_header_def @property def uses_bitmaps_map(self): return False @property def uses_sounds_map(self): return False - def get_dependencies(self, meta, tag_id, tag_cls): - if self.is_indexed(tag_id): - if tag_cls != "snd!": - return () - - rsrc_id = meta.promotion_sound.id & 0xFFff - if rsrc_id == 0xFFFF: return () - - sounds = self.maps.get("sounds") - rsrc_id = rsrc_id // 2 - if sounds is None: return () - elif rsrc_id >= len(sounds.tag_index.tag_index): return () - - tag_path = sounds.tag_index.tag_index[rsrc_id].path - inv_snd_map = getattr(self, 'ce_tag_indexs_by_paths', {}) - tag_id = inv_snd_map.get(tag_path, 0xFFFF) - if tag_id >= len(self.tag_index.tag_index): return () - - return [self.tag_index.tag_index[tag_id]] - - if self.handler is None: return () - - dependency_cache = self.handler.tag_ref_cache.get(tag_cls) - if not dependency_cache: return () - - nodes = self.handler.get_nodes_by_paths(dependency_cache, (None, meta)) - dependencies = [] - - for node in nodes: - if node.id & 0xFFff == 0xFFFF: - continue - dependencies.append(node) - return dependencies + def is_indexed(self, tag_id): + return False def setup_sbsp_headers(self): with FieldType.force_big: @@ -202,48 +133,17 @@ def get_meta(self, tag_id, reextract=False, ignore_rsrc_sounds=False, **kw): return meta def byteswap_anniversary_fields(self, meta, tag_cls): - # TODO: use byteswapping.py to quickly handle a lot of this - # due to it possibly having accelerators available. + # fix all the forced-little-endian fields that are big-endian, but were + # read as little-endian(that's what FlUInt/FlSInt/FlFloat fields are) + + # TODO: use handler build_loc_caches to locate all forced-little-endian + # fields and force them to big-endian without having to write these if tag_cls == "antr": - unpack_header = PyStruct("<11i").unpack for b in meta.animations.STEPTREE: b.first_permutation_index = end_swap_int16(b.first_permutation_index) - b.chance_to_play = end_swap_float(b.chance_to_play) - if not b.flags.compressed_data: - continue - - comp_data = b.frame_data.data[b.offset_to_compressed_data: ] - # byteswap compressed frame data header - for i in range(0, 44, 4): - data = comp_data[i: i + 4] - for j in range(4): - comp_data[i + 3 - j] = data[j] - - header = list(unpack_header(comp_data[: 44])) - header.insert(0, 44) - header.append(len(comp_data)) - - item_sizes = (4, 2, 2, 2, 4, 2, 4, 4, 4, 2, 4, 4) - comp_data_off_len_size = [] - for i in range(len(header) - 1): - comp_data_off_len_size.append([ - header[i], header[i + 1] - header[i], item_sizes[i]]) - - for off, length, size in comp_data_off_len_size: - for i in range(off, off + length, size): - data = comp_data[i: i + size] - for j in range(size): - comp_data[i + size - 1 - j] = data[j] - - # replace the frame_data with the compressed data and some - # blank default / frame data so tool doesnt shit the bed. - default_data_size = b.node_count * (12 + 8 + 4) - b.frame_size - b.default_data.data += bytearray( - max(0, len(b.default_data.data) - default_data_size)) - - b.offset_to_compressed_data = b.frame_count * b.frame_size - b.frame_data.data = bytearray( - b.frame_count * b.frame_size) + comp_data + b.chance_to_play = end_swap_float(b.chance_to_play) + + byteswap_anniversary_antr(meta) elif tag_cls == "bitm": for b in meta.bitmaps.STEPTREE: @@ -261,13 +161,7 @@ def byteswap_anniversary_fields(self, meta, tag_cls): b.unknown1 = end_swap_int16(b.unknown1) elif tag_cls == "hmt ": - block_bytes = bytearray(meta.string.serialize()) - for i in range(20, len(block_bytes), 2): - byte = block_bytes[i + 1] - block_bytes[i + 1] = block_bytes[i] - block_bytes[i] = byte - - meta.string.parse(rawdata=block_bytes) + byteswap_anniversary_rawdata_ref(meta.string, size=2, two_byte_offs=[0]) elif tag_cls == "lens": meta.cosine_falloff_angle = end_swap_float(meta.cosine_falloff_angle) @@ -293,8 +187,8 @@ def byteswap_anniversary_fields(self, meta, tag_cls): node.scale = end_swap_float(node.scale) for b in (node.rot_jj_kk, node.rot_kk_ii, node.rot_ii_jj, node.translation_to_root): - for i in range(len(b)): - b[i] = end_swap_float(b[i]) + for i, val in enumerate(b): + b[i] = end_swap_float(val) elif tag_cls == "part": meta.rendering.unknown0 = end_swap_int32(meta.rendering.unknown0) @@ -302,86 +196,33 @@ def byteswap_anniversary_fields(self, meta, tag_cls): meta.rendering.unknown2 = end_swap_uint32(meta.rendering.unknown2) elif tag_cls == "pphy": - meta.scaled_density = end_swap_float(meta.scaled_density) + meta.scaled_density = end_swap_float(meta.scaled_density) meta.water_gravity_scale = end_swap_float(meta.water_gravity_scale) - meta.air_gravity_scale = end_swap_float(meta.air_gravity_scale) + meta.air_gravity_scale = end_swap_float(meta.air_gravity_scale) elif tag_cls == "sbsp": - # TODO: Might need to byteswap cluster data and sound_pas data + for b in meta.collision_materials.STEPTREE: + b.material_type.data = end_swap_int16(b.material_type.data) + + for b in meta.fog_planes.STEPTREE: + b.material_type.data = end_swap_int16(b.material_type.data) + + for lm in meta.lightmaps.STEPTREE: + for b in lm.materials.STEPTREE: + b.unknown_meta_offset0 = end_swap_uint32(b.unknown_meta_offset0) + b.unknown_meta_offset1 = end_swap_uint32(b.unknown_meta_offset1) + b.vertices_meta_offset = end_swap_uint32(b.vertices_meta_offset) + b.lightmap_vertices_meta_offset = end_swap_uint32(b.lightmap_vertices_meta_offset) - for coll_mat in meta.collision_materials.STEPTREE: - coll_mat.material_type.data = end_swap_int16(coll_mat.material_type.data) - - node_data = meta.nodes.STEPTREE - for i in range(0, len(node_data), 2): - b0 = node_data[i] - node_data[i] = node_data[i + 1] - node_data[i + 1] = b0 - - leaf_data = meta.leaves.STEPTREE - for i in range(0, len(leaf_data), 16): - b0 = leaf_data[i] - leaf_data[i] = leaf_data[i + 1] - leaf_data[i + 1] = b0 - - b0 = leaf_data[i + 2] - leaf_data[i + 2] = leaf_data[i + 3] - leaf_data[i + 3] = b0 - - b0 = leaf_data[i + 4] - leaf_data[i + 4] = leaf_data[i + 5] - leaf_data[i + 5] = b0 - - b0 = leaf_data[i + 6] - leaf_data[i + 6] = leaf_data[i + 7] - leaf_data[i + 7] = b0 - - for lightmap in meta.lightmaps.STEPTREE: - for b in lightmap.materials.STEPTREE: - vt_ct = b.vertices_count - l_vt_ct = b.lightmap_vertices_count - - u_verts = b.uncompressed_vertices.STEPTREE - c_verts = b.compressed_vertices.STEPTREE - - b.unknown_meta_offset0 = end_swap_uint32( - b.unknown_meta_offset0) - b.vertices_meta_offset = end_swap_uint32( - b.vertices_meta_offset) - - b.vertex_type.data = end_swap_uint16(b.vertex_type.data) - - b.unknown_meta_offset1 = end_swap_uint32( - b.unknown_meta_offset1) - b.lightmap_vertices_meta_offset = end_swap_uint32( - b.lightmap_vertices_meta_offset) - - # byteswap (un)compressed verts and lightmap verts - for data in (u_verts, c_verts): - for i in range(0, len(data), 4): - b0 = data[i] - b1 = data[i+1] - data[i] = data[i+3] - data[i+1] = data[i+2] - data[i+2] = b1 - data[i+3] = b0 - - # since the compressed lightmap u and v coordinates are - # 2 byte fields rather than 4, the above byteswapping - # will have swapped u and v. we need to swap them back. - # multiply vt_ct by 32 to skip non-lightmap verts, and - # add 4 to skip the 4 byte compressed lightmap normal. - for i in range(vt_ct * 32 + 4, len(c_verts), 8): - c_verts[i: i + 1] = c_verts[i+1], c_verts[i] - - for fog_plane in meta.fog_planes.STEPTREE: - fog_plane.material_type.data = end_swap_int16( - fog_plane.material_type.data) + # byteswap the rawdata + byteswap_anniversary_sbsp(meta) + + # TODO: Might need to byteswap cluster data and sound_pas data elif tag_cls == "scnr": for b in meta.object_names.STEPTREE: b.object_type.data = end_swap_int16(b.object_type.data) - b.reflexive_index = end_swap_int16(b.reflexive_index) + b.reflexive_index = end_swap_int16(b.reflexive_index) for b in meta.trigger_volumes.STEPTREE: b.unknown0 = end_swap_uint16(b.unknown0) @@ -389,34 +230,13 @@ def byteswap_anniversary_fields(self, meta, tag_cls): for b in meta.encounters.STEPTREE: b.unknown = end_swap_uint16(b.unknown) - # PROLLY GONNA HAVE TO BYTESWAP RECORDED ANIMS AND MORE SHIT - syntax_data = meta.script_syntax_data.data - with FieldType.force_big: - syntax_header = h1_script_syntax_data_def.build(rawdata=syntax_data) - - i = 56 - for node_i in range(syntax_header.last_node): - n_typ = syntax_data[i + 5] + (syntax_data[i + 4] << 8) - flags = syntax_data[i + 7] + (syntax_data[i + 6] << 8) - if flags & 7 == 1: - # node is a primitive - if n_typ == 5: - # node is a boolean - syntax_data[i + 19] = syntax_data[i + 16] - syntax_data[i + 16: i + 19] = (0, 0, 0) # null these 3 - elif n_typ == 7: - # node is a sint16 - syntax_data[i + 18] = syntax_data[i + 16] - syntax_data[i + 19] = syntax_data[i + 17] - syntax_data[i + 16: i + 18] = (0, 0) # null these 2 - - i += 20 + # NOTE: this is gonna get swapped back when converting to tagdata + byteswap_scnr_script_syntax_data(meta) elif tag_cls == "senv": - meta.senv_attrs.bump_properties.map_scale_x = end_swap_float( - meta.senv_attrs.bump_properties.map_scale_x) - meta.senv_attrs.bump_properties.map_scale_y = end_swap_float( - meta.senv_attrs.bump_properties.map_scale_y) + bump_props = meta.senv_attrs.bump_properties + bump_props.map_scale_x = end_swap_float(bump_props.map_scale_x) + bump_props.map_scale_y = end_swap_float(bump_props.map_scale_y) elif tag_cls == "snd!": for pr in meta.pitch_ranges.STEPTREE: @@ -424,34 +244,30 @@ def byteswap_anniversary_fields(self, meta, tag_cls): b.buffer_size = end_swap_uint32(b.buffer_size) elif tag_cls == "spla": - meta.spla_attrs.primary_noise_map.unknown0 = end_swap_uint16( - meta.spla_attrs.primary_noise_map.unknown0) - meta.spla_attrs.primary_noise_map.unknown1 = end_swap_uint16( - meta.spla_attrs.primary_noise_map.unknown1) - - meta.spla_attrs.secondary_noise_map.unknown0 = end_swap_uint16( - meta.spla_attrs.secondary_noise_map.unknown0) - meta.spla_attrs.secondary_noise_map.unknown1 = end_swap_uint16( - meta.spla_attrs.secondary_noise_map.unknown1) + for noise_map in ( + meta.spla_attrs.primary_noise_map, + meta.spla_attrs.secondary_noise_map + ): + noise_map.unknown0 = end_swap_uint16(noise_map.unknown0) + noise_map.unknown1 = end_swap_uint16(noise_map.unknown1) elif tag_cls == "ustr": + # need to serialize the unicode strings reflexive back to the + # endianness it was read as, and then byteswap the code-points + # of each character(NOTE: 12 is the end of the refelxive header) for b in meta.strings.STEPTREE: - block_bytes = bytearray(b.serialize()) - for i in range(12, len(block_bytes), 2): - byte = block_bytes[i + 1] - block_bytes[i + 1] = block_bytes[i] - block_bytes[i] = byte - - b.parse(rawdata=block_bytes) + byteswap_anniversary_rawdata_ref(b, size=2, two_byte_offs=[0]) if tag_cls in ("bipd", "vehi", "weap", "eqip", "garb", "proj", "scen", "mach", "ctrl", "lifi", "plac", "ssce", "obje"): meta.obje_attrs.object_type.data = end_swap_int16( - meta.obje_attrs.object_type.data) + meta.obje_attrs.object_type.data + ) elif tag_cls in ("senv", "soso", "sotr", "schi", "scex", "swat", "sgla", "smet", "spla", "shdr"): meta.shdr_attrs.shader_type.data = end_swap_int16( - meta.shdr_attrs.shader_type.data) + meta.shdr_attrs.shader_type.data + ) def inject_rawdata(self, meta, tag_cls, tag_index_ref): # TODO: Update this with extracting from sabre paks if @@ -472,11 +288,15 @@ def inject_rawdata(self, meta, tag_cls, tag_index_ref): return meta def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): - if tag_cls in ("bitm", "snd!", "scnr", ): - # no bitmap pixels or sound samples in map. cant extract. - # also, we don't know how to properly byteswap recorded - # animations, so scenarios can't be extracted properly. - return + # no bitmap pixels or sound samples in map. cant extract. + # also, we don't know how to properly byteswap recorded + # animations, so scenarios can't be extracted properly. + if tag_cls == "bitm": + raise ValueError("Bitmap pixel data missing.") + elif tag_cls == "snd!": + raise ValueError("Sound sample data missing.") + elif tag_cls == "scnr": + raise ValueError("Cannot byteswap recorded animations.") kwargs["byteswap"] = False super().meta_to_tag_data(meta, tag_cls, tag_index_ref, **kwargs) diff --git a/reclaimer/meta/wrappers/halo1_map.py b/reclaimer/meta/wrappers/halo1_map.py index 2e5034db..2893bafa 100644 --- a/reclaimer/meta/wrappers/halo1_map.py +++ b/reclaimer/meta/wrappers/halo1_map.py @@ -14,7 +14,7 @@ from copy import deepcopy from math import pi, log from pathlib import Path -from struct import unpack, pack_into +from struct import unpack, unpack_from, pack_into from traceback import format_exc from types import MethodType @@ -253,9 +253,15 @@ def get_dependencies(self, meta, tag_id, tag_cls): dependencies = [] for node in nodes: - if node.id & 0xFFff == 0xFFFF: + # need to filter to dependencies that are actually valid + tag_id = node.id & 0xFFff + if tag_id not in range(len(tag_index_array)): continue - dependencies.append(node) + + tag_index_ref = tag_index_array[tag_id] + if (node.tag_class.enum_name == tag_index_ref.class_1.enum_name and + node.id == tag_index_ref.id): + dependencies.append(node) if tag_cls == "scnr": # collect the tag references from the scenarios syntax data @@ -268,6 +274,7 @@ def get_dependencies(self, meta, tag_id, tag_cls): tag_index_id not in seen_tag_ids): seen_tag_ids.add(tag_index_id) tag_index_ref = tag_index_array[tag_index_id] + dependencies.append(make_dependency_os_block( tag_index_ref.class_1.enum_name, tag_index_ref.id, tag_index_ref.path, tag_index_ref.path_offset)) @@ -460,18 +467,20 @@ def get_meta(self, tag_id, reextract=False, ignore_rsrc_sounds=False, **kw): tag_id = offset rsrc_map = None - if tag_cls == "snd!" and "sounds" in self.maps: - rsrc_map = self.maps["sounds"] - sound_mapping = self.ce_rsrc_sound_indexes_by_path - tag_path = tag_index_ref.path - if sound_mapping is None or tag_path not in sound_mapping: - return + if tag_cls == "snd!": + if "sounds" in self.maps: + rsrc_map = self.maps["sounds"] + sound_mapping = self.ce_rsrc_sound_indexes_by_path + tag_path = tag_index_ref.path + if sound_mapping is None or tag_path not in sound_mapping: + return - tag_id = sound_mapping[tag_path]//2 + tag_id = sound_mapping[tag_path]//2 - elif tag_cls == "bitm" and "bitmaps" in self.maps: - rsrc_map = self.maps["bitmaps"] - tag_id = tag_id//2 + elif tag_cls == "bitm": + if "bitmaps" in self.maps: + rsrc_map = self.maps["bitmaps"] + tag_id = tag_id//2 elif "loc" in self.maps: rsrc_map = self.maps["loc"] @@ -486,12 +495,12 @@ def get_meta(self, tag_id, reextract=False, ignore_rsrc_sounds=False, **kw): return meta = rsrc_map.get_meta(tag_id, **kw) + snd_stub = None if tag_cls == "snd!": - # since we're reading the resource tag from the perspective of - # the map referencing it, we have more accurate information - # about which other sound it could be referencing. This is only - # a concern when dealing with open sauce resource maps, as they - # could have additional promotion sounds we cant statically map + # while the sound samples and complete tag are in the + # resource map, the metadata for the body of the sound + # tag is in the main map. Need to copy its values into + # the resource map sound tag we extracted. try: # read the meta data from the map with FieldType.force_little: @@ -499,10 +508,25 @@ def get_meta(self, tag_id, reextract=False, ignore_rsrc_sounds=False, **kw): rawdata=map_data, offset=pointer_converter.v_ptr_to_f_ptr(offset), tag_index_manager=self.tag_index_manager) - meta.promotion_sound = snd_stub.promotion_sound except Exception: print(format_exc()) + if snd_stub: + # copy values over + for name in ( + "flags", "sound_class", "sample_rate", + "minimum_distance", "maximum_distance", + "skip_fraction", "random_pitch_bounds", + "inner_cone_angle", "outer_cone_angle", + "outer_cone_gain", "gain_modifier", + "maximum_bend_per_second", + "modifiers_when_scale_is_zero", + "modifiers_when_scale_is_one", + "encoding", "compression", "promotion_sound", + "promotion_count", "max_play_length", + ): + setattr(meta, name, getattr(snd_stub, name)) + return meta elif not reextract: if is_scenario and self.scnr_meta: @@ -898,7 +922,6 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): for perm in change_color.permutations.STEPTREE: perm.weight, cutoff = perm.weight - cutoff, perm.weight - if tag_cls == "actv": # multiply grenade velocity by 30 meta.grenades.grenade_velocity *= 30 @@ -995,6 +1018,10 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): model_magic = None else: model_magic = magic + + # need to unset this flag, as it forces map-compile-time processing + # to occur on the model's vertices, which shouldn't be done twice. + meta.flags.blend_shared_normals = False # lod cutoffs are swapped between tag and cache form cutoffs = (meta.superlow_lod_cutoff, meta.low_lod_cutoff, @@ -1043,14 +1070,21 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): tris_block = part.triangles info = part.model_meta_info - if info.vertex_type.enum_name == "compressed": + if info.vertex_type.enum_name == "model_comp_verts": verts_block = part.compressed_vertices byteswap_verts = byteswap_comp_verts vert_size = 32 - else: + elif info.vertex_type.enum_name == "model_uncomp_verts": verts_block = part.uncompressed_vertices byteswap_verts = byteswap_uncomp_verts vert_size = 68 + else: + print("Error: Unknown vertex type in model: %s" % info.vertex_type.data) + continue + + if info.index_type.enum_name != "triangle_strip": + print("Error: Unknown index type in model: %s" % info.index_type.data) + continue # null out certain things in the part part.centroid_primary_node = 0 @@ -1140,124 +1174,123 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): for coll_mat in meta.collision_materials.STEPTREE: coll_mat.material_type.data = 0 # supposed to be 0 in tag form - compressed = "xbox" in engine or engine in ("stubbs", "shadowrun_proto") - - if compressed: - generate_verts = kwargs.get("generate_uncomp_verts", False) - else: - generate_verts = kwargs.get("generate_comp_verts", False) - - endian = ">" if engine == "halo1anni" else "<" - comp_norm = compress_normal32 decomp_norm = decompress_normal32 - comp_vert_nbt_unpacker = MethodType(unpack, endian + "3I") - uncomp_vert_nbt_packer = MethodType(pack_into, endian + "12s9f8s") - - comp_vert_nuv_unpacker = MethodType(unpack, endian + "I2h") - uncomp_vert_nuv_packer = MethodType(pack_into, endian + "5f") - - uncomp_vert_nbt_unpacker = MethodType(unpack, endian + "9f") - comp_vert_nbt_packer = MethodType(pack_into, endian + "12s3I8s") + comp_vert_unpacker = MethodType(unpack_from, "<12s3I8s") + comp_vert_packer = MethodType(pack_into, "<12s3I8s") + uncomp_vert_unpacker = MethodType(unpack_from, "<12s9f8s") + uncomp_vert_packer = MethodType(pack_into, "<12s9f8s") - uncomp_vert_nuv_unpacker = MethodType(unpack, endian + "5f") - comp_vert_nuv_packer = MethodType(pack_into, endian + "I2h") + comp_lm_vert_unpacker = MethodType(unpack_from, " 1 else u)*32767), + int((-1 if v < -1 else 1 if v > 1 else v)*32767), + ) + elif (kwargs.get("generate_uncomp_verts") and + lm_vert_type == "sbsp_comp_lightmap_verts" + ): + # generate uncompressed lightmap verts from compressed + u_buffer += bytearray(u_lm_verts_size) + for u_off, c_off in lm_vert_offs: + n, u, v = comp_lm_vert_unpacker(c_buffer, c_off) + uncomp_lm_vert_packer( + u_buffer, u_off, + *decomp_norm(n), u/32767, v/32767 + ) + + # need to null these or original CE sapien could crash + mat.unknown_meta_offset0 = mat.vertices_meta_offset = 0 + mat.unknown_meta_offset1 = mat.lightmap_vertices_meta_offset = 0 + + # set these to the correct vertex types based on what we have + vert_type_str = ( + "sbsp_comp_%s_verts" + if c_verts_size and c_lm_verts_size else + "sbsp_uncomp_%s_verts" + ) + mat.vertex_type.set_to(vert_type_str % "material") + mat.lightmap_vertex_type.set_to(vert_type_str % "lightmap") - # replace the buffers - u_verts.STEPTREE = uncomp_buffer - c_verts.STEPTREE = comp_buffer + mat.uncompressed_vertices.STEPTREE = u_buffer + mat.compressed_vertices.STEPTREE = c_buffer elif tag_cls == "scnr": # need to remove the references to the child scenarios diff --git a/reclaimer/meta/wrappers/halo1_mcc_map.py b/reclaimer/meta/wrappers/halo1_mcc_map.py index 0b6e733d..66e6ed35 100644 --- a/reclaimer/meta/wrappers/halo1_mcc_map.py +++ b/reclaimer/meta/wrappers/halo1_mcc_map.py @@ -53,7 +53,7 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): if uses_external_sounds(meta): # no sounds.map to read sounds from, and sound # data is specified as external. can't extract - return None + raise ValueError("Sound sample data missing.") meta = super().meta_to_tag_data(meta, tag_cls, tag_index_ref, **kwargs) if tag_cls == "sbsp": diff --git a/reclaimer/meta/wrappers/halo1_rsrc_map.py b/reclaimer/meta/wrappers/halo1_rsrc_map.py index 69aea25f..08539d8f 100644 --- a/reclaimer/meta/wrappers/halo1_rsrc_map.py +++ b/reclaimer/meta/wrappers/halo1_rsrc_map.py @@ -268,9 +268,11 @@ def get_meta(self, tag_id, reextract=False, **kw): kwargs = dict(parsing_resource=True) desc = self.get_meta_descriptor(tag_cls) - if desc is None or self.engine not in GEN_1_HALO_CUSTOM_ENGINES: - return - elif self.engine == "halo1mcc" and tag_cls == "bitm": + if (desc is None or self.engine == "halo1mcc" or + self.engine not in GEN_1_HALO_CUSTOM_ENGINES): + # NOTE: mcc resource maps DON'T contain metadata, they only + # contain bitmap pixel data and sound sample data. + # as such, they're EXACTLY like halo1pc resource maps return elif tag_cls != 'snd!': # the pitch ranges pointer in resource sound tags is invalid, so diff --git a/reclaimer/sounds/adpcm.py b/reclaimer/sounds/adpcm.py index ec11e808..ae7afa69 100644 --- a/reclaimer/sounds/adpcm.py +++ b/reclaimer/sounds/adpcm.py @@ -15,13 +15,13 @@ # IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import array -import audioop import sys + from struct import unpack_from from types import MethodType -from . import constants +from . import constants, util, audioop try: from .ext import adpcm_ext @@ -48,7 +48,6 @@ def _slow_decode_xbadpcm_samples(in_data, out_data, channel_ct): pcm_blocksize = channel_ct * constants.XBOX_ADPCM_DECOMPRESSED_BLOCKSIZE // 2 adpcm_blocksize = channel_ct * constants.XBOX_ADPCM_COMPRESSED_BLOCKSIZE - adpcm2lin = audioop.adpcm2lin all_codes = memoryview(in_data) state_unpacker = MethodType(unpack_from, "<" + "hBx" * channel_ct) @@ -73,7 +72,7 @@ def _slow_decode_xbadpcm_samples(in_data, out_data, channel_ct): for j in range(c * 4, len(swapped_codes), code_block_size) ) decoded_samples = memoryview( - adpcm2lin(swapped_codes, 2, all_states[c])[0]).cast("h") + audioop.adpcm2lin(swapped_codes, 2, all_states[c])[0]).cast("h") # interleave the samples for each channel out_data[k + c] = all_states[c][0] @@ -115,7 +114,7 @@ def encode_adpcm_samples(in_data, channel_ct, input_big_endian=False, "Accelerator module not detected. Cannot compress to ADPCM.") if (sys.byteorder == "big") != input_big_endian: - out_data = audioop.byteswap(out_data, 2) + in_data = audioop.byteswap(in_data, 2) adpcm_blocksize = constants.XBOX_ADPCM_COMPRESSED_BLOCKSIZE * channel_ct pcm_blocksize = constants.XBOX_ADPCM_DECOMPRESSED_BLOCKSIZE * channel_ct @@ -134,4 +133,4 @@ def encode_adpcm_samples(in_data, channel_ct, input_big_endian=False, adpcm_ext.encode_xbadpcm_samples( in_data, out_data, channel_ct, noise_shaping, lookahead) - return bytes(out_data) + return bytes(out_data) \ No newline at end of file diff --git a/reclaimer/sounds/audioop.py b/reclaimer/sounds/audioop.py new file mode 100644 index 00000000..060a94ca --- /dev/null +++ b/reclaimer/sounds/audioop.py @@ -0,0 +1,318 @@ +''' +This module provides a pure-python replacement for MOST of the audioop functions +that Reclaimer relies on for audio conversion. This module exists because audioop +is being deprecated in python 3.11, and will be removed in 3.13. Some of this +functionality isn't necessary for audio compiling, and those methods that arent +have not been reimplemented. + +A better solution would be to use a pypi library replacement, however the +pure-python versions out there are extremely slow. Example: + https://github.com/jiaaro/pydub/blob/master/pydub/pyaudioop.py + +To learn about the deprecation, see here: + https://peps.python.org/pep-0594/#audioop +''' +from types import MethodType +import array +import itertools + +try: + import audioop +except ModuleNotFoundError: + # THEY TOOK MY FUCKING BATTERIES!!!!! + audioop = None + +SAMPLE_WIDTH_BOUNDS = ( + ( -0x80, 0x7F), + ( -0x8000, 0x7Fff), + ( -0x800000, 0x7FffFF), + (-0x80000000, 0x7FffFFff), + ) + +SAMPLE_TYPECODES = ( + "b", + "h", + "t", # NOTE: not a real typecode + "i" + ) + +def adpcm2lin(fragment, width, state): + # TODO: replace with python fallback + raise NotImplementedError( + "No accelerators found, and audioop is removed in this " + "version of Python. Cannot decode ADPCM audio samples." + ) + + +def bias(fragment, width, bias): + if width not in (1, 2, 4): + raise NotImplementedError("Cannot bias sample data of width %s." % width) + + # ensure fragment is the correct data type + if width > 1 and not(isinstance(fragment, array.array) and + fragment.itemsize == 1): + fragment = array.array("I" if width == 4 else "H", fragment) + + modulous = 1 << (8*width) + mapper = map( + modulous.__rmod__, + map(int(bias).__add__, fragment) + ) + + if width == 1: + fragment = bytes(mapper) + elif width in (2, 4): + fragment = array.array("I" if width == 4 else "H", mapper).tobytes() + + return fragment + + +def byteswap(fragment, width): + if width not in (1, 2, 3, 4): + raise NotImplementedError( + "Cannot byteswap sample data of width %s." % width + ) + + orig_fragment = ( + fragment if isinstance(fragment, (bytes, bytearray)) else + fragment.tobytes() if isinstance(fragment, array.array) else + bytes(fragment) + ) + if width == 3: + # for 24bit we gotta do some clever shit to do this reasonable fast. + # sample data must be bytes or bytearray if we want to use slices + fragment = bytearray(orig_fragment) + # use slices to move byte 0 of each original word into byte 2 + # of the byteswapped word, and vice versa for the other bytes + fragment[::3] = orig_fragment[2::3] + fragment[2::3] = orig_fragment[::3] + fragment = bytes(fragment) + elif width == 2 or width == 4: + # we can use array.array to byteswap 16/32 bit pcm + fragment = array.array("i" if width == 4 else "h", fragment) + fragment.byteswap() + fragment = fragment.tobytes() + else: + fragment = orig_fragment + + return fragment + + +def lin2lin(fragment, width, newwidth): + if width not in (1, 2, 4): + raise NotImplementedError("Cannot convert from sample width %s." % width) + elif newwidth not in (1, 2, 4): + raise NotImplementedError("Cannot convert to sample width %s." % newwidth) + + typecode = SAMPLE_TYPECODES[width-1] + new_typecode = SAMPLE_TYPECODES[newwidth-1] + if not(isinstance(fragment, array.array) and + fragment.typecode == typecode): + fragment = array.array(typecode, fragment) + + if width == newwidth: + # same width? why? whatever.... + new_fragment = fragment + else: + shift_diff = abs(width - newwidth) * 8 + shift_func = ( + shift_diff.__rrshift__ # shift samples right(divide) + if width > newwidth else + shift_diff.__rlshift__ # shift samples left(multiply) + ) + + min_val_clip = MethodType(max, SAMPLE_WIDTH_BOUNDS[newwidth-1][0]) + max_val_clip = MethodType(min, SAMPLE_WIDTH_BOUNDS[newwidth-1][1]) + new_fragment = array.array(new_typecode, + map(max_val_clip, + map(min_val_clip, + map(round, + map(shift_func, fragment + ))))) + + return new_fragment.tobytes() + + +def ratecv(fragment, width, nchannels, inrate, outrate, state, weightA=1, weightB=0): + # TODO: replace with python fallback + raise NotImplementedError( + "audioop is removed in this version of Python. " + "Cannot convert sample rate to target." + ) + + +def tomono(fragment, width, lfactor, rfactor): + if width not in (1, 2, 4): + raise NotImplementedError( + "Cannot convert %s width samples to mono." % width + ) + + typecode = SAMPLE_TYPECODES[width-1] + if not(isinstance(fragment, array.array) and + fragment.typecode == typecode): + fragment = array.array(typecode, fragment) + + min_val_clip = MethodType(max, SAMPLE_WIDTH_BOUNDS[width-1][0]) + max_val_clip = MethodType(min, SAMPLE_WIDTH_BOUNDS[width-1][1]) + + # these are some pretty simple map chains that just grab odd/even + # audio channel values and multiply them by their channel factor + left_channel_data = map(lfactor.__mul__, fragment[0::2]) + right_channel_data = map(rfactor.__mul__, fragment[1::2]) + + # WARNING: MAP FROM HELL + # now that we have our audio channels separated into maps, + # we can zip them together, pass that zip into a mapped sum + # to add the left/right channels, pass the result into a + # min/max clip map, and finally fast everything to integers. + new_fragment = array.array(typecode, + map(max_val_clip, + map(min_val_clip, + map(round, + map(sum, + zip(left_channel_data, right_channel_data) + ))))) + + return new_fragment.tobytes() + + +def tostereo(fragment, width, lfactor, rfactor): + if width not in (1, 2, 4): + raise NotImplementedError( + "Cannot convert %s width samples to stereo." % width + ) + + typecode = SAMPLE_TYPECODES[width-1] + if not(isinstance(fragment, array.array) and + fragment.typecode == typecode): + fragment = array.array(typecode, fragment) + + min_val_clip = MethodType(max, SAMPLE_WIDTH_BOUNDS[width-1][0]) + max_val_clip = MethodType(min, SAMPLE_WIDTH_BOUNDS[width-1][1]) + + # WARNING: MAPS FROM HELL + # essentially we're doing the opposite of the tomono maps above. + # we're multiplying the input samples by the left/right factors, + # rounding to integers, and then clipping to out min/max values. + interleaved_fragment = array.array( + typecode, b'\x00'*(len(fragment)*width*2) + ) + interleaved_fragment[0::2] = fragment + interleaved_fragment[1::2] = fragment + + new_fragment = array.array(typecode, + map(max_val_clip, + map(min_val_clip, + map(round, + itertools.starmap(float.__mul__, + zip( + itertools.cycle((lfactor, rfactor)), + interleaved_fragment, + )))))) + return new_fragment.tobytes() + + +# TESTS +# these tests show that behavior is unchanged between the audioop +# implementation and the pure python version we've developed +def _run_tests(): + + for test_vals in [ + # NOTE: we use steps here to ensure we generate enough data + # for the test, but not TOO MUCH data(i.e. multiple MB) + ( -0x80, 0x7F, 0x01, 1, 2, "b", 0x80), + ( -0x80, 0x7F, 0x01, 1, 4, "b", 0x80), + ( 0x00, 0xFF, 0x01, 1, 2, "B", 0x80), + ( 0x00, 0xFF, 0x01, 1, 4, "B", 0x80), + ( -0x8000, 0x7Fff, 0x01, 2, 1, "h", 0x8000), + ( -0x8000, 0x7Fff, 0x01, 2, 4, "h", 0x8000), + ( 0x00, 0xFFff, 0x01, 2, 1, "H", 0x8000), + ( 0x00, 0xFFff, 0x01, 2, 4, "H", 0x8000), + ( -0x800000, 0x7FffFF, 0x80, 3, 1, "i", 0x800000), + ( -0x800000, 0x7FffFF, 0x80, 3, 4, "i", 0x800000), + ( 0x00, 0xFFffFF, 0x80, 3, 1, "I", 0x800000), + ( 0x00, 0xFFffFF, 0x80, 3, 4, "I", 0x800000), + (-0x80000000, 0x7FffFFff, 0x8000, 4, 1, "i", 0x7FffFFff), + (-0x80000000, 0x7FffFFff, 0x8000, 4, 2, "i", 0x7FffFFff), + ( 0x00, 0xFFffFFff, 0x8000, 4, 1, "I", 0x7FffFFff), + ( 0x00, 0xFFffFFff, 0x8000, 4, 2, "I", 0x7FffFFff), + #(-0x80000000, 0x7FffFFff, 0x100, 4, 1, "i", 0x7FffFFff), + #(-0x80000000, 0x7FffFFff, 0x100, 4, 2, "i", 0x7FffFFff), + #( 0x00, 0xFFffFFff, 0x100, 4, 1, "I", 0x7FffFFff), + #( 0x00, 0xFFffFFff, 0x100, 4, 2, "I", 0x7FffFFff), + ]: + get_delta = lambda: list( + itertools.starmap(int.__sub__, + zip( + array.array(typecode, audioop_data), + array.array(typecode, reclaimer_data) + )) + ) + + min_val, max_val, step_val, width, new_width, typecode, bias_val = test_vals + test_vals_str = ", ".join(str(v) for v in test_vals) + + test_data = array.array(typecode, range(min_val, max_val+1, step_val)) + # ensure test data is multiple of sample width + # also ensure audio data is a multiple of 2 for stereo/mono tests + test_data = test_data.tobytes()[:width*2*(len(test_data)//(width*2))] + + # test 1: audioop.byteswap vs byteswap + audioop_data = audioop.byteswap(test_data, width) + reclaimer_data = byteswap(test_data, width) + + if width == 3: + # NOTE: can't do much ATM with 24bit samples cause of how awkward they are + continue + + delta = get_delta() + if max(map(abs, delta)) > 1: + print(delta) + print("Test failure: Inconsistency in byteswap(%s)" % test_vals_str) + + # test 2: audioop.bias vs bias + audioop_data = audioop.bias(test_data, width, bias_val) + reclaimer_data = bias(test_data, width, bias_val) + delta = get_delta() + if max(map(abs, delta)) > 1: + print(delta) + print("Test failure: Inconsistency in bias(%s)" % test_vals_str) + + + # test 3: audioop.tomono vs tomono + audioop_data = audioop.tomono(test_data, width, 0.5, 0.5) + reclaimer_data = tomono(test_data, width, 0.5, 0.5) + delta = get_delta() + if max(map(abs, delta)) > 1: + print(delta) + print("Test failure: Inconsistency in tomono(%s)" % test_vals_str) + + + # test 4: audioop.tostereo vs tostereo + audioop_data = audioop.tostereo(test_data, width, 1.0, 1.0) + reclaimer_data = tostereo(test_data, width, 1.0, 1.0) + delta = get_delta() + if max(map(abs, delta)) > 1: + print(delta) + print("Test failure: Inconsistency in tostereo(%s)" % test_vals_str) + input() + + # test 5: audioop.lin2lin vs lin2lin + audioop_data = audioop.lin2lin(test_data, width, new_width) + reclaimer_data = lin2lin(test_data, width, new_width) + delta = get_delta() + if max(map(abs, delta)) > 1: + print(delta) + print("Test failure: Inconsistency in lin2lin(%s)" % test_vals_str) + input() + + +if __name__ == "__main__" and audioop is not None: + _run_tests() + #import cProfile + #cProfile.runctx("_run_tests()", locals(), globals()) + + +if audioop is not None: + # if audioop is loaded, use its methods + from audioop import * diff --git a/reclaimer/sounds/profile_data.txt b/reclaimer/sounds/profile_data.txt new file mode 100644 index 00000000..5f7dabc2 --- /dev/null +++ b/reclaimer/sounds/profile_data.txt @@ -0,0 +1,51 @@ +ncalls: for the number of calls. +tottime: for the total time spent in the given function (and excluding time made in calls to sub-functions) +percall: is the quotient of tottime divided by ncalls +cumtime: is the cumulative time spent in this and all subfunctions (from invocation till exit). This figure is accurate even for recursive functions. +percall: is the quotient of cumtime divided by primitive calls + + +329195580 function calls in 105.508 seconds + + ncalls tottime percall cumtime percall filename:lineno(function) + + +PURE PYTHON audioop functions + ncalls tottime percall cumtime percall filename:lineno(function) + 12 2.833 0.236 2.847 0.237 audioop.py:49(bias) + 16 0.017 0.001 0.045 0.003 audioop.py:72(byteswap) + 12 7.460 0.622 7.466 0.622 audioop.py:103(lin2lin) + 12 5.873 0.489 5.880 0.490 audioop.py:146(tomono) + 12 17.638 1.470 17.666 1.472 audioop.py:181(tostereo) + +NATIVE audioop functions + ncalls tottime percall cumtime percall filename:lineno(function) + 12 0.026 0.002 0.026 0.002 {built-in method audioop.bias} + 16 0.057 0.004 0.057 0.004 {built-in method audioop.byteswap} + 12 0.022 0.002 0.022 0.002 {built-in method audioop.lin2lin} + 12 0.074 0.006 0.074 0.006 {built-in method audioop.tomono} + 12 0.192 0.016 0.192 0.016 {built-in method audioop.tostereo} + + +generators used in testing delta of pure-python vs native + ncalls tottime percall cumtime percall filename:lineno(function) + 128 0.000 0.000 0.000 0.000 audioop.py:246() + 67896328 6.276 0.000 6.276 0.000 audioop.py:256() + 67372044 5.998 0.000 5.998 0.000 audioop.py:273() + 33686028 3.076 0.000 3.076 0.000 audioop.py:285() +134744076 12.662 0.000 12.662 0.000 audioop.py:297() + 25496588 2.336 0.000 2.336 0.000 audioop.py:309() + + +MISC CALLS FROM ABOVE + ncalls tottime percall cumtime percall filename:lineno(function) + 1 35.529 35.529 105.490 105.490 audioop.py:220(_run_tests) + 1 0.000 0.000 105.508 105.508 {built-in method builtins.exec} + 12 0.000 0.000 0.000 0.000 {built-in method builtins.abs} + 60 0.000 0.000 0.000 0.000 {built-in method builtins.isinstance} + 28 0.000 0.000 0.000 0.000 {built-in method builtins.len} + 64 5.282 0.083 5.282 0.083 {built-in method builtins.sum} + 8 0.015 0.002 0.015 0.002 {method 'byteswap' of 'array.array' objects} + 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} + 16 0.000 0.000 0.000 0.000 {method 'join' of 'str' objects} + 68 0.124 0.002 0.124 0.002 {method 'tobytes' of 'array.array' objects} \ No newline at end of file diff --git a/reclaimer/sounds/util.py b/reclaimer/sounds/util.py index 378729cd..3b818c10 100644 --- a/reclaimer/sounds/util.py +++ b/reclaimer/sounds/util.py @@ -8,12 +8,11 @@ # import array -import audioop import re import struct import sys -from reclaimer.sounds import constants +from reclaimer.sounds import audioop, constants BAD_PATH_CHAR_REMOVAL = re.compile(r'[<>:"|?*]{1, }') @@ -72,13 +71,6 @@ def get_sample_count(sample_data, compression, encoding): return chunk_count -def byteswap_pcm16_sample_data(samples): - return convert_pcm_to_pcm( - samples, - constants.COMPRESSION_PCM_16_LE, - constants.COMPRESSION_PCM_16_BE) - - def is_big_endian_pcm(compression): ''' Returns True if the endianness of the compression modes are different. @@ -144,11 +136,11 @@ def convert_pcm_to_pcm(samples, compression, target_compression, samples = samples[: len(samples) - (len(samples) % current_width)] if compression == constants.COMPRESSION_PCM_8_UNSIGNED: - # bias by 128 to shift unsigned into signed + # convert unsigned into signed samples = audioop.bias(samples, 1, 128) elif current_width > 1 and compression not in constants.NATIVE_ENDIANNESS_FORMATS: # byteswap samples to system endianness before processing - samples = audioop.byteswap(samples, current_width) + samples = audioop.byteswap(samples, target_width) compression = change_pcm_endianness(compression) if current_width != target_width: @@ -177,7 +169,7 @@ def convert_pcm_to_pcm(samples, compression, target_compression, sample_rate = target_sample_rate if target_compression == constants.COMPRESSION_PCM_8_UNSIGNED: - # bias by 128 to shift signed back into unsigned + # convert signed back into unsigned samples = audioop.bias(samples, 1, 128) elif target_width > 1 and (is_big_endian_pcm(compression) != is_big_endian_pcm(target_compression)): @@ -207,18 +199,14 @@ def generate_mouth_data(sample_data, compression, sample_rate, encoding): sample_width = constants.sample_widths[compression] channel_count = constants.channel_counts[encoding] - + sample_typecode = constants.sample_typecodes[encoding] if compression == constants.COMPRESSION_PCM_8_UNSIGNED: - # bias by 128 to shift unsigned into signed sample_data = audioop.bias(sample_data, 1, 128) elif sample_width > 1 and compression not in constants.NATIVE_ENDIANNESS_FORMATS: - # byteswap samples to system endianness before processing sample_data = audioop.byteswap(sample_data, sample_width) - if sample_width == 2: - sample_data = memoryview(sample_data).cast("h") - elif sample_width == 4: - sample_data = memoryview(sample_data).cast("i") + if sample_width != 1: + sample_data = memoryview(sample_data).cast(sample_typecode) # mouth data is sampled at 30Hz, so we divide the audio # sample_rate by that to determine how many samples we must diff --git a/reclaimer/util/compression.py b/reclaimer/util/compression.py index 026445a1..0b1316ff 100644 --- a/reclaimer/util/compression.py +++ b/reclaimer/util/compression.py @@ -58,17 +58,16 @@ def decompress_normal32(n): def compress_normal32(i, j, k): - i = min(max(i, -1.0), 1.0) - j = min(max(j, -1.0), 1.0) - k = min(max(k, -1.0), 1.0) + # logical comparisons like this are faster than chaining 2(more) function calls + ci = 1024 if i <= -1 else 1023 if i >= 1 else int(round(i*1023)) % 2047 + cj = 1024 if j <= -1 else 1023 if j >= 1 else int(round(j*1023)) % 2047 + ck = 512 if k <= -1 else 511 if k >= 1 else int(round(k*511)) % 1023 # original algorithm before shelly's optimization, kept for clarity #if i < 0: i += 2047 #if j < 0: j += 2047 #if k < 0: k += 1023 #return i | (j << 11) | (k << 22) - return ((int(round(i*1023)) % 2047) | - ((int(round(j*1023)) % 2047) << 11) | - ((int(round(k*511)) % 1023) << 22)) + return ci | (cj << 11) | (ck << 22) #uncomp_norm = [.333, -.75, 1] From 46d2b8da23db1b58068712a1ad5007793e913fb2 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Mon, 26 Feb 2024 01:38:44 -0600 Subject: [PATCH 38/51] bump version --- reclaimer/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reclaimer/__init__.py b/reclaimer/__init__.py index 1208b4df..07d5b298 100644 --- a/reclaimer/__init__.py +++ b/reclaimer/__init__.py @@ -12,8 +12,8 @@ # ############## __author__ = "Sigmmma" # YYYY.MM.DD -__date__ = "2024.02.20" -__version__ = (2, 17, 0) +__date__ = "2024.02.26" +__version__ = (2, 18, 0) __website__ = "https://github.com/Sigmmma/reclaimer" __all__ = ( "animation", "bitmaps", "h2", "h3", "halo_script", "hek", "meta", "misc", From 1f973d14adaf58f9a7ca76ccb124150ea177e7f8 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sat, 9 Mar 2024 23:17:18 -0600 Subject: [PATCH 39/51] Sound playback and HEK+ workarounds --- reclaimer/bitmaps/bitmap_decompilation.py | 70 ++-- reclaimer/common_descs.py | 38 +- reclaimer/constants.py | 37 +- reclaimer/h2/common_descs.py | 21 +- reclaimer/h3/common_descs.py | 21 +- reclaimer/h3/util.py | 16 +- reclaimer/hek/defs/objs/bitm.py | 6 +- reclaimer/hek/defs/objs/mod2.py | 39 -- reclaimer/hek/defs/objs/mode.py | 39 ++ reclaimer/hek/defs/scnr.py | 78 ++-- reclaimer/hek/defs/snd_.py | 5 +- reclaimer/hek/handler.py | 2 + reclaimer/meta/wrappers/halo1_anni_map.py | 18 +- reclaimer/meta/wrappers/halo1_map.py | 87 ++--- reclaimer/meta/wrappers/halo1_rsrc_map.py | 14 +- reclaimer/meta/wrappers/halo1_yelo.py | 75 +++- reclaimer/misc/defs/composer_playlist.py | 2 +- reclaimer/misc/defs/recorded_animations.py | 325 ++++++++++++++++ reclaimer/misc/handler.py | 2 + reclaimer/sounds/adpcm.py | 18 +- reclaimer/sounds/blam_sound_bank.py | 50 ++- reclaimer/sounds/blam_sound_permutation.py | 153 +++++--- reclaimer/sounds/blam_sound_samples.py | 48 +-- reclaimer/sounds/constants.py | 78 +++- reclaimer/sounds/ogg.py | 123 +++--- reclaimer/sounds/playback.py | 412 +++++++++++++++++++++ reclaimer/sounds/sound_compilation.py | 51 ++- reclaimer/sounds/sound_decompilation.py | 157 ++++---- reclaimer/sounds/util.py | 7 +- 29 files changed, 1499 insertions(+), 493 deletions(-) create mode 100644 reclaimer/misc/defs/recorded_animations.py create mode 100644 reclaimer/sounds/playback.py diff --git a/reclaimer/bitmaps/bitmap_decompilation.py b/reclaimer/bitmaps/bitmap_decompilation.py index 12577e41..33e898d2 100644 --- a/reclaimer/bitmaps/bitmap_decompilation.py +++ b/reclaimer/bitmaps/bitmap_decompilation.py @@ -8,33 +8,25 @@ # try: - import arbytmap - if not hasattr(arbytmap, "FORMAT_P8"): - arbytmap.FORMAT_P8 = "P8" + import arbytmap as ab + if not hasattr(ab, "FORMAT_P8"): + ab.FORMAT_P8 = "P8" """ADD THE P8 FORMAT TO THE BITMAP CONVERTER""" - arbytmap.register_format(format_id=arbytmap.FORMAT_P8, - depths=(8,8,8,8)) + ab.register_format( + format_id=ab.FORMAT_P8, depths=(8,8,8,8) + ) - if not hasattr(arbytmap, "FORMAT_P8_BUMP"): - arbytmap.FORMAT_P8_BUMP = "P8-BUMP" + if not hasattr(ab, "FORMAT_P8_BUMP"): + ab.FORMAT_P8_BUMP = "P8-BUMP" """ADD THE P8 FORMAT TO THE BITMAP CONVERTER""" - arbytmap.register_format(format_id=arbytmap.FORMAT_P8_BUMP, - depths=(8,8,8,8)) - - from arbytmap import Arbytmap, bitmap_io, TYPE_2D, TYPE_3D, TYPE_CUBEMAP,\ - FORMAT_A8, FORMAT_L8, FORMAT_AL8, FORMAT_A8L8,\ - FORMAT_R5G6B5, FORMAT_A1R5G5B5, FORMAT_A4R4G4B4,\ - FORMAT_X8R8G8B8, FORMAT_A8R8G8B8,\ - FORMAT_DXT1, FORMAT_DXT3, FORMAT_DXT5, FORMAT_CTX1, FORMAT_DXN,\ - FORMAT_DXT3Y, FORMAT_DXT3A, FORMAT_DXT3AY,\ - FORMAT_DXT5Y, FORMAT_DXT5A, FORMAT_DXT5AY,\ - FORMAT_P8_BUMP, FORMAT_P8, FORMAT_V8U8, FORMAT_R8G8,\ - FORMAT_R16G16B16F, FORMAT_A16R16G16B16F,\ - FORMAT_R32G32B32F, FORMAT_A32R32G32B32F + ab.register_format( + format_id=ab.FORMAT_P8_BUMP, depths=(8,8,8,8) + ) + except ImportError: - arbytmap = Arbytmap = None + ab = None import zlib @@ -127,11 +119,11 @@ def extract_bitmaps(tagdata, tag_path, **kw): ext = "dds" is_gen3 = hasattr(tagdata, "zone_assets_normal") - if Arbytmap is None: + if ab is None: # cant extract xbox bitmaps yet return " Arbytmap not loaded. Cannot extract bitmaps." - arby = Arbytmap() + arby = ab.Arbytmap() bitm_i = 0 multi_bitmap = len(tagdata.bitmaps.STEPTREE) > 1 size_calc = get_h3_pixel_bytes_size if is_gen3 else get_pixel_bytes_size @@ -161,8 +153,8 @@ def extract_bitmaps(tagdata, tag_path, **kw): filepath=str(filepath_base.joinpath(filename + "." + ext)) ) tex_info["texture_type"] = { - "texture_2d": TYPE_2D, "texture_3d": TYPE_3D, - "cubemap": TYPE_CUBEMAP}.get(typ, TYPE_2D) + "texture_2d": ab.TYPE_2D, "texture_3d": ab.TYPE_3D, + "cubemap": ab.TYPE_CUBEMAP}.get(typ, ab.TYPE_2D) tex_info["sub_bitmap_count"] = { "texture_2d": 1, "texture_3d": 1, "cubemap": 6, "multipage_2d": d}.get(typ, 1) @@ -174,21 +166,21 @@ def extract_bitmaps(tagdata, tag_path, **kw): if fmt == "p8_bump": tex_info.update( palette=[p8_palette.p8_palette_32bit_packed]*(bitmap.mipmaps + 1), - palette_packed=True, indexing_size=8, format=FORMAT_P8_BUMP) + palette_packed=True, indexing_size=8, format=ab.FORMAT_P8_BUMP) else: tex_info["format"] = { - "a8": FORMAT_A8, "y8": FORMAT_L8, "ay8": FORMAT_AL8, - "a8y8": FORMAT_A8L8, "p8": FORMAT_A8, - "v8u8": FORMAT_V8U8, "g8b8": FORMAT_R8G8, - "x8r8g8b8": FORMAT_A8R8G8B8, "a8r8g8b8": FORMAT_A8R8G8B8, - "r5g6b5": FORMAT_R5G6B5, "a1r5g5b5": FORMAT_A1R5G5B5, - "a4r4g4b4": FORMAT_A4R4G4B4, - "dxt1": FORMAT_DXT1, "dxt3": FORMAT_DXT3, "dxt5": FORMAT_DXT5, - "ctx1": FORMAT_CTX1, "dxn": FORMAT_DXN, "dxt5ay": FORMAT_DXT5AY, - "dxt3a": FORMAT_DXT3A, "dxt3y": FORMAT_DXT3Y, - "dxt5a": FORMAT_DXT5A, "dxt5y": FORMAT_DXT5Y, - "rgbfp16": FORMAT_R16G16B16F, "argbfp32": FORMAT_A32R32G32B32F, - "rgbfp32": FORMAT_R32G32B32F}.get(fmt, None) + "a8": ab.FORMAT_A8, "y8": ab.FORMAT_L8, "ay8": ab.FORMAT_AL8, + "a8y8": ab.FORMAT_A8L8, "p8": ab.FORMAT_A8, + "v8u8": ab.FORMAT_V8U8, "g8b8": ab.FORMAT_R8G8, + "x8r8g8b8": ab.FORMAT_A8R8G8B8, "a8r8g8b8": ab.FORMAT_A8R8G8B8, + "r5g6b5": ab.FORMAT_R5G6B5, "a1r5g5b5": ab.FORMAT_A1R5G5B5, + "a4r4g4b4": ab.FORMAT_A4R4G4B4, + "dxt1": ab.FORMAT_DXT1, "dxt3": ab.FORMAT_DXT3, "dxt5": ab.FORMAT_DXT5, + "ctx1": ab.FORMAT_CTX1, "dxn": ab.FORMAT_DXN, "dxt5ay": ab.FORMAT_DXT5AY, + "dxt3a": ab.FORMAT_DXT3A, "dxt3y": ab.FORMAT_DXT3Y, + "dxt5a": ab.FORMAT_DXT5A, "dxt5y": ab.FORMAT_DXT5Y, + "rgbfp16": ab.FORMAT_R16G16B16F, "argbfp32": ab.FORMAT_A32R32G32B32F, + "rgbfp32": ab.FORMAT_R32G32B32F}.get(fmt, None) arby_fmt = tex_info["format"] @@ -204,7 +196,7 @@ def extract_bitmaps(tagdata, tag_path, **kw): array('B', pix_data[off: off + (mip_size // 4)])) off += len(tex_block[-1]) else: - off = bitmap_io.bitmap_bytes_to_array( + off = ab.bitmap_io.bitmap_bytes_to_array( pix_data, off, tex_block, arby_fmt, 1, 1, 1, mip_size) diff --git a/reclaimer/common_descs.py b/reclaimer/common_descs.py index 4ad98073..a7e6b311 100644 --- a/reclaimer/common_descs.py +++ b/reclaimer/common_descs.py @@ -9,21 +9,28 @@ from copy import copy, deepcopy from math import pi -try: - from mozzarilla.widgets.field_widgets import ReflexiveFrame,\ - HaloRawdataFrame, HaloUInt32ColorPickerFrame, TextFrame,\ - ColorPickerFrame, EntryFrame, HaloScriptSourceFrame,\ - SoundSampleFrame, DynamicArrayFrame, DynamicEnumFrame,\ - HaloScriptTextFrame, HaloBitmapTagFrame, FontCharacterFrame,\ - MeterImageFrame, HaloHudMessageTextFrame -except Exception: - ReflexiveFrame = HaloRawdataFrame = HaloUInt32ColorPickerFrame =\ - TextFrame = ColorPickerFrame = EntryFrame =\ - HaloScriptSourceFrame = SoundSampleFrame =\ - DynamicArrayFrame = DynamicEnumFrame =\ - HaloScriptTextFrame = HaloBitmapTagFrame =\ - FontCharacterFrame = MeterImageFrame =\ - HaloHudMessageTextFrame = None +import os + +from reclaimer.constants import RECLAIMER_NO_GUI + +HaloRawdataFrame = ReflexiveFrame = SoundPlayerFrame = \ + HaloUInt32ColorPickerFrame = ColorPickerFrame = ContainerFrame =\ + EntryFrame = TextFrame = HaloScriptSourceFrame = SoundSampleFrame =\ + HaloScriptTextFrame = HaloBitmapTagFrame = MeterImageFrame =\ + FontCharacterFrame = HaloHudMessageTextFrame =\ + DynamicArrayFrame = DynamicEnumFrame = None + +if not os.environ.get(RECLAIMER_NO_GUI): + try: + from mozzarilla.widgets.field_widgets import \ + HaloRawdataFrame, ReflexiveFrame, SoundPlayerFrame,\ + HaloUInt32ColorPickerFrame, ColorPickerFrame, ContainerFrame,\ + EntryFrame, TextFrame, HaloScriptSourceFrame, SoundSampleFrame,\ + HaloScriptTextFrame, HaloBitmapTagFrame, MeterImageFrame,\ + FontCharacterFrame, HaloHudMessageTextFrame,\ + DynamicArrayFrame, DynamicEnumFrame + except Exception: + print("Unable to import mozzarilla widgets. UI features may not work.") from supyr_struct.defs.common_descs import * from supyr_struct.defs.block_def import BlockDef @@ -35,6 +42,7 @@ from reclaimer.constants import * from reclaimer.enums import * + # before we do anything, we need to inject these constants so any definitions # that are built that use them will have them in their descriptor entries. inject_halo_constants() diff --git a/reclaimer/constants.py b/reclaimer/constants.py index 43d1fd51..c47cc7fd 100644 --- a/reclaimer/constants.py +++ b/reclaimer/constants.py @@ -7,11 +7,46 @@ # See LICENSE for more information. # +import os from struct import unpack from supyr_struct.defs.constants import * from supyr_struct.util import fourcc_to_int -from binilla.constants import * + +# environment variable controls +RECLAIMER_NO_GUI = "RECLAIMER_NO_GUI" # if non-empty, tells reclaimer to +# not load binilla or mozzarilla +# modules wherever possible. +RECLAIMER_NO_ARBY = "RECLAIMER_NO_ARBY" # if non-empty, tells reclaimer to not +# load arbytmap wherever possible. + +# copied from binilla.constants for in case they're not available +EDITABLE = "EDITABLE" +VISIBLE = "VISIBLE" +GUI_NAME = "GUI_NAME" +HIDE_TITLE = "HIDE_TITLE" +ORIENT = "ORIENT" +WIDGET_WIDTH = "WIDGET_WIDTH" +TOOLTIP = "TOOLTIP" +COMMENT = "COMMENT" +SIDETIP = "SIDETIP" +ALLOW_MAX = "ALLOW_MAX" +ALLOW_MIN = "ALLOW_MIN" +UNIT_SCALE = "UNIT_SCALE" +EXT = "EXT" +PORTABLE = "PORTABLE" +WIDGET = "WIDGET" +DYN_NAME_PATH = "DYN_NAME_PATH" +DYN_I = "[DYN_I]" +VISIBILITY_SHOWN = 1 +VISIBILITY_HIDDEN = 0 +VISIBILITY_METADATA = -1 + +if not os.environ.get(RECLAIMER_NO_GUI): + try: + from binilla.constants import * + except ImportError: + pass # some reflexives are so massive that it's significantly faster to treat them # as raw data and just byteswap them using precalculated offsets and sizes diff --git a/reclaimer/h2/common_descs.py b/reclaimer/h2/common_descs.py index b574b6ad..ec5ebcbd 100644 --- a/reclaimer/h2/common_descs.py +++ b/reclaimer/h2/common_descs.py @@ -8,19 +8,18 @@ # from copy import copy, deepcopy +import os + +from reclaimer.h2.constants import RECLAIMER_NO_GUI, h2_tag_class_fcc_to_ext + +Halo2BitmapTagFrame = None +if not os.environ.get(RECLAIMER_NO_GUI): + try: + from mozzarilla.widgets.field_widgets import Halo2BitmapTagFrame + except ImportError: + print("Unable to import mozzarilla widgets. UI features may not work.") -try: - from mozzarilla.widgets.field_widgets import ReflexiveFrame, HaloRawdataFrame,\ - TextFrame, ColorPickerFrame, EntryFrame, SoundSampleFrame,\ - DynamicArrayFrame, Halo2BitmapTagFrame -except Exception: - ReflexiveFrame = HaloRawdataFrame = TextFrame = ColorPickerFrame =\ - EntryFrame = SoundSampleFrame = DynamicArrayFrame =\ - Halo2BitmapTagFrame = None from reclaimer.common_descs import * -from reclaimer.h2.constants import STEPTREE, DYN_NAME_PATH, NAME_MAP,\ - COMMENT, TOOLTIP, WIDGET, MAX, MAX_REFLEXIVE_COUNT, VISIBLE, ORIENT,\ - MAX_TAG_PATH_LEN, DEFAULT, h2_tag_class_fcc_to_ext from reclaimer.h2.field_types import * from reclaimer.h2.enums import * diff --git a/reclaimer/h3/common_descs.py b/reclaimer/h3/common_descs.py index 8864ba77..88a77c8c 100644 --- a/reclaimer/h3/common_descs.py +++ b/reclaimer/h3/common_descs.py @@ -8,20 +8,19 @@ # from copy import copy, deepcopy +import os + +from reclaimer.h3.constants import RECLAIMER_NO_GUI, h3_tag_class_fcc_to_ext + +Halo3BitmapTagFrame = None +if not os.environ.get(RECLAIMER_NO_GUI): + try: + from mozzarilla.widgets.field_widgets import Halo3BitmapTagFrame + except ImportError: + print("Unable to import mozzarilla widgets. UI features may not work.") -try: - from mozzarilla.widgets.field_widgets import ReflexiveFrame,\ - HaloRawdataFrame, TextFrame, ColorPickerFrame, EntryFrame,\ - SoundSampleFrame, DynamicArrayFrame, Halo3BitmapTagFrame -except Exception: - ReflexiveFrame = HaloRawdataFrame = TextFrame = ColorPickerFrame =\ - EntryFrame = SoundSampleFrame = DynamicArrayFrame = \ - Halo3BitmapTagFrame = None from reclaimer.common_descs import * from reclaimer.h3.field_types import H3TagRef, H3Reflexive, H3RawdataRef -from reclaimer.h3.constants import DYN_NAME_PATH, STEPTREE, NAME_MAP, WIDGET,\ - TOOLTIP, COMMENT, MAX, DEFAULT, VISIBLE, SIZE, MAX_REFLEXIVE_COUNT,\ - h3_tag_class_fcc_to_ext from reclaimer.h3.enums import * from supyr_struct.defs.block_def import BlockDef diff --git a/reclaimer/h3/util.py b/reclaimer/h3/util.py index dfbe01aa..ba5d6612 100644 --- a/reclaimer/h3/util.py +++ b/reclaimer/h3/util.py @@ -9,11 +9,23 @@ from math import ceil, log -from arbytmap.bitmap_io import get_pixel_bytes_size -from reclaimer.h3.constants import HALO3_SHARED_MAP_TYPES +from reclaimer.h3.constants import HALO3_SHARED_MAP_TYPES, RECLAIMER_NO_ARBY from reclaimer.util import * +def get_pixel_bytes_size(*args, **kwargs): + raise NameError( + "Arbytmap is not installed. " + "Cannot determine pixel bytes size" + ) + +if not os.environ.get(RECLAIMER_NO_ARBY): + try: + from arbytmap.bitmap_io import get_pixel_bytes_size + except ImportError: + pass + + def get_virtual_dimension(bitm_fmt, dim, mip_level=0, tiled=False): dim = max(1, dim >> mip_level) if bitm_fmt in ("A8R8G8B8", "X8R8G8B8"): diff --git a/reclaimer/hek/defs/objs/bitm.py b/reclaimer/hek/defs/objs/bitm.py index a9f0beeb..03beb196 100644 --- a/reclaimer/hek/defs/objs/bitm.py +++ b/reclaimer/hek/defs/objs/bitm.py @@ -15,12 +15,14 @@ try: import arbytmap as ab - if not hasattr(ab, "FORMAT_P8_BUMP"): ab.FORMAT_P8_BUMP = "P8-BUMP" """ADD THE P8 FORMAT TO THE BITMAP CONVERTER""" - ab.register_format(format_id=ab.FORMAT_P8_BUMP, depths=(8,8,8,8)) + ab.register_format( + format_id=ab.FORMAT_P8_BUMP, depths=(8,8,8,8) + ) + except (ImportError, AttributeError): ab = None diff --git a/reclaimer/hek/defs/objs/mod2.py b/reclaimer/hek/defs/objs/mod2.py index c264ecdb..ea0a6341 100644 --- a/reclaimer/hek/defs/objs/mod2.py +++ b/reclaimer/hek/defs/objs/mod2.py @@ -11,45 +11,6 @@ class Mod2Tag(ModeTag): - def globalize_local_markers(self): - tagdata = self.data.tagdata - all_global_markers = tagdata.markers.STEPTREE - all_global_markers_by_name = {b.name: b.marker_instances.STEPTREE - for b in all_global_markers} - - for i in range(len(tagdata.regions.STEPTREE)): - region = tagdata.regions.STEPTREE[i] - for j in range(len(region.permutations.STEPTREE)): - perm = region.permutations.STEPTREE[j] - - for k in range(len(perm.local_markers.STEPTREE)): - local_marker = perm.local_markers.STEPTREE[k] - global_markers = all_global_markers_by_name.get( - local_marker.name, None) - - if global_markers is None or len(global_markers) >= 32: - all_global_markers.append() - all_global_markers[-1].name = local_marker.name - global_markers = all_global_markers[-1].marker_instances.STEPTREE - all_global_markers_by_name[local_marker.name] = global_markers - - global_markers.append() - global_marker = global_markers[-1] - - global_marker.region_index = i - global_marker.permutation_index = j - global_marker.node_index = local_marker.node_index - global_marker.rotation[:] = local_marker.rotation[:] - global_marker.translation[:] = local_marker.translation[:] - - del perm.local_markers.STEPTREE[:] - - # sort the markers how Halo's picky ass wants them - name_map = {all_global_markers[i].name: i - for i in range(len(all_global_markers))} - all_global_markers[:] = list(all_global_markers[name_map[name]] - for name in sorted(name_map)) - def delocalize_part_nodes(self, geometry_index, part_index): part = self.data.tagdata.geometries.STEPTREE\ [geometry_index].parts.STEPTREE[part_index] diff --git a/reclaimer/hek/defs/objs/mode.py b/reclaimer/hek/defs/objs/mode.py index bf81229f..83fa2091 100644 --- a/reclaimer/hek/defs/objs/mode.py +++ b/reclaimer/hek/defs/objs/mode.py @@ -137,6 +137,45 @@ def calc_internal_data(self): for lod, highest_node in max_lod_nodes.items(): self.data.tagdata["%s_lod_nodes" % lod] = max(0, highest_node) + def globalize_local_markers(self): + tagdata = self.data.tagdata + all_global_markers = tagdata.markers.STEPTREE + all_global_markers_by_name = {b.name: b.marker_instances.STEPTREE + for b in all_global_markers} + + for i in range(len(tagdata.regions.STEPTREE)): + region = tagdata.regions.STEPTREE[i] + for j in range(len(region.permutations.STEPTREE)): + perm = region.permutations.STEPTREE[j] + + for k in range(len(perm.local_markers.STEPTREE)): + local_marker = perm.local_markers.STEPTREE[k] + global_markers = all_global_markers_by_name.get( + local_marker.name, None) + + if global_markers is None or len(global_markers) >= 32: + all_global_markers.append() + all_global_markers[-1].name = local_marker.name + global_markers = all_global_markers[-1].marker_instances.STEPTREE + all_global_markers_by_name[local_marker.name] = global_markers + + global_markers.append() + global_marker = global_markers[-1] + + global_marker.region_index = i + global_marker.permutation_index = j + global_marker.node_index = local_marker.node_index + global_marker.rotation[:] = local_marker.rotation[:] + global_marker.translation[:] = local_marker.translation[:] + + del perm.local_markers.STEPTREE[:] + + # sort the markers how Halo's picky ass wants them + name_map = {all_global_markers[i].name: i + for i in range(len(all_global_markers))} + all_global_markers[:] = list(all_global_markers[name_map[name]] + for name in sorted(name_map)) + def compress_part_verts(self, geometry_index, part_index): part = self.data.tagdata.geometries.STEPTREE\ [geometry_index].parts.STEPTREE[part_index] diff --git a/reclaimer/hek/defs/scnr.py b/reclaimer/hek/defs/scnr.py index a54bdda4..9e5b4f10 100644 --- a/reclaimer/hek/defs/scnr.py +++ b/reclaimer/hek/defs/scnr.py @@ -11,6 +11,18 @@ from .objs.scnr import ScnrTag from supyr_struct.defs.tag_def import TagDef from supyr_struct.util import desc_variant +from reclaimer.misc.defs.recorded_animations import build_r_a_stream_block + + +def compute_decompiled_ra_stream(parent=None, **kwargs): + try: + if parent is not None: + parent.decompiled_stream = build_r_a_stream_block( + parent.parent.unit_control_data_version, + parent.parent.recorded_animation_event_stream.STEPTREE + ) + except Exception: + pass def object_reference(name, *args, **kwargs): @@ -42,47 +54,6 @@ def object_swatch(name, def_id, size=48): SIZE=size ) -fl_float_xyz = QStruct("", - FlFloat("x"), - FlFloat("y"), - FlFloat("z"), - ORIENT="h" - ) - -stance_flags = FlBool16("stance", - "walk", - "look_only", - "primary_fire", - "secondary_fire", - "jump", - "crouch", - "melee", - "flashlight", - "action1", - "action2", - "action_hold", - ) - -unit_control_packet = Struct("unit_control_packet", - - ) - -r_a_stream_header = Struct("r_a_stream_header", - UInt8("move_index", DEFAULT=3, MAX=6), - UInt8("bool_index"), - stance_flags, - FlSInt16("weapon", DEFAULT=-1), - QStruct("speed", FlFloat("x"), FlFloat("y"), ORIENT="h"), - QStruct("feet", INCLUDE=fl_float_xyz), - QStruct("body", INCLUDE=fl_float_xyz), - QStruct("head", INCLUDE=fl_float_xyz), - QStruct("change", INCLUDE=fl_float_xyz), - FlUInt16("unknown1"), - FlUInt16("unknown2"), - FlUInt16("unknown3", DEFAULT=0xFFFF), - FlUInt16("unknown4", DEFAULT=0xFFFF), - SIZE=60 - ) device_flags = ( "initially_open", # value of 1.0 @@ -456,7 +427,19 @@ def object_swatch(name, def_id, size=48): SInt16("length_of_animation", SIDETIP="ticks"), # ticks Pad(6), rawdata_ref("recorded_animation_event_stream", max_size=2097152), - SIZE=64 + Pad(0), + SIZE=64, + ) + +recorded_animation_with_ra_stream = desc_variant(recorded_animation, + ("pad_8", QStruct("decompiled_stream", + # NOTE: making this a steptree to ensure the data in the + # recorded_animation_event_stream is parsed and available + STEPTREE=Computed("decompiled_stream", + COMPUTE_READ=compute_decompiled_ra_stream, WIDGET=ContainerFrame + ), + SIZE=0, + )) ) netgame_flag = Struct("netgame_flag", @@ -1070,6 +1053,11 @@ def object_swatch(name, def_id, size=48): SIZE=1456, ) +scnr_body_with_decomp_ra_stream = desc_variant(scnr_body, + reflexive("recorded_animations", recorded_animation_with_ra_stream, 1024, + DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX) + ) + def get(): return scnr_def @@ -1079,3 +1067,9 @@ def get(): ext=".scenario", endian=">", tag_cls=ScnrTag ) + +scnr_with_decomp_ra_stream_def = TagDef("scnr", + blam_header('scnr', 2), + scnr_body_with_decomp_ra_stream, + ext=".scenario", endian=">", tag_cls=ScnrTag + ) \ No newline at end of file diff --git a/reclaimer/hek/defs/snd_.py b/reclaimer/hek/defs/snd_.py index c5494782..b6d072e0 100644 --- a/reclaimer/hek/defs/snd_.py +++ b/reclaimer/hek/defs/snd_.py @@ -99,7 +99,8 @@ SInt32("unknown2", VISIBLE=False, DEFAULT=-1), reflexive("permutations", permutation, 256, - DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX), + DYN_NAME_PATH='.name', IGNORE_SAFE_MODE=True, EXT_MAX=SINT16_MAX + ), SIZE=72, ) @@ -156,7 +157,7 @@ reflexive("pitch_ranges", pitch_range, 8, DYN_NAME_PATH='.name'), - SIZE=164, + SIZE=164, WIDGET=SoundPlayerFrame ) diff --git a/reclaimer/hek/handler.py b/reclaimer/hek/handler.py index ce963b08..a46769e7 100644 --- a/reclaimer/hek/handler.py +++ b/reclaimer/hek/handler.py @@ -14,6 +14,8 @@ from traceback import format_exc from pathlib import Path, PureWindowsPath +# NOTE: this is a pretty tough dependency to move to make +# reclaimer able to operate without binilla installed. from binilla.handler import Handler from reclaimer.data_extraction import h1_data_extractors diff --git a/reclaimer/meta/wrappers/halo1_anni_map.py b/reclaimer/meta/wrappers/halo1_anni_map.py index 7fce6131..3c3da47e 100644 --- a/reclaimer/meta/wrappers/halo1_anni_map.py +++ b/reclaimer/meta/wrappers/halo1_anni_map.py @@ -15,6 +15,7 @@ byteswap_scnr_script_syntax_data, end_swap_float, end_swap_int32,\ end_swap_int16, end_swap_uint32, end_swap_uint16 +from reclaimer.misc.defs.recorded_animations import build_r_a_stream_block from reclaimer.meta.wrappers.halo_map import HaloMap from reclaimer.meta.wrappers.halo1_rsrc_map import Halo1RsrcMap from reclaimer.meta.wrappers.halo1_mcc_map import Halo1MccMap @@ -230,6 +231,21 @@ def byteswap_anniversary_fields(self, meta, tag_cls): for b in meta.encounters.STEPTREE: b.unknown = end_swap_uint16(b.unknown) + for ra in meta.recorded_animations.STEPTREE: + # parse the recorded animations as big-endian + # and serialize back as little-endian + try: + with FieldType.force_big: + ra_block = build_r_a_stream_block( + ra.unit_control_data_version, + ra.recorded_animation_event_stream.STEPTREE, + simple=True + ) + ra.recorded_animation_event_stream.STEPTREE = ra_block.serialize() + except Exception: + print(format_exc()) + print("Could not byteswap recorded animation '%s'" % ra.name) + # NOTE: this is gonna get swapped back when converting to tagdata byteswap_scnr_script_syntax_data(meta) @@ -295,8 +311,6 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): raise ValueError("Bitmap pixel data missing.") elif tag_cls == "snd!": raise ValueError("Sound sample data missing.") - elif tag_cls == "scnr": - raise ValueError("Cannot byteswap recorded animations.") kwargs["byteswap"] = False super().meta_to_tag_data(meta, tag_cls, tag_index_ref, **kwargs) diff --git a/reclaimer/meta/wrappers/halo1_map.py b/reclaimer/meta/wrappers/halo1_map.py index 2893bafa..0f8af4ab 100644 --- a/reclaimer/meta/wrappers/halo1_map.py +++ b/reclaimer/meta/wrappers/halo1_map.py @@ -927,6 +927,18 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): meta.grenades.grenade_velocity *= 30 elif tag_cls in ("antr", "magy"): + # try to fix HEK+ extraction bug + for obj in meta.objects.STEPTREE: + for enum in (obj.function, obj.function_controls): + uint16_data = enum.data & 0xFFff + if (uint16_data & 0xFF00 and not uint16_data & 0xFF): + # higher bits are set than lower. this is likely + # a HEK plus extraction bug and should be fixed + uint16_data = ((uint16_data>>8) | (uint16_data<<8)) & 0xFFff + enum.data = uint16_data - ( + 0 if uint16_data < 0x8000 else 0x10000 + ) + # byteswap animation data for anim in meta.animations.STEPTREE: if not byteswap: break @@ -1427,6 +1439,16 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): meta.soso_attrs.model_shader.flags.data |= 1<<6 elif tag_cls == "weap": + # try to fix HEK+ extraction bug + uint16_data = (meta.weap_attrs.aiming.zoom_levels & 0xFFff) + if (uint16_data & 0xFF00 and not uint16_data & 0xFF): + # higher bits are set than lower. this is likely + # a HEK plus extraction bug and should be fixed + uint16_data = ((uint16_data>>8) | (uint16_data<<8)) & 0xFFff + meta.weap_attrs.aiming.zoom_levels = uint16_data - ( + 0 if uint16_data < 0x8000 else 0x10000 + ) + predicted_resources.append(meta.weap_attrs.predicted_resources) # remove any predicted resources @@ -1553,74 +1575,11 @@ def generate_map_info_string(self): header.compressed_lightmap_materials_pointer + offset - magic, ) - if self.engine == "halo1yelo": - string += self.generate_yelo_info_string() - elif self.engine == "halo1vap": + if self.engine == "halo1vap": string += self.generate_vap_info_string() return string - def generate_yelo_info_string(self): - yelo = self.map_header.yelo_header - flags = yelo.flags - info = yelo.build_info - version = yelo.tag_versioning - cheape = yelo.cheape_definitions - rsrc = yelo.resources - min_os = info.minimum_os_build - - return """ -Yelo information: - Mod name == %s - Memory upgrade amount == %sx - - Flags: - uses memory upgrades == %s - uses mod data files == %s - is protected == %s - uses game state upgrades == %s - has compression parameters == %s - - Build info: - build string == %s - timestamp == %s - stage == %s - revision == %s - - Cheape: - build string == %s - version == %s.%s.%s - size == %s - offset == %s - decompressed size == %s - - Versioning: - minimum open sauce == %s.%s.%s - project yellow == %s - project yellow globals == %s - - Resources: - compression parameters header offset == %s - tag symbol storage header offset == %s - string id storage header offset == %s - tag string to id storage header offset == %s\n""" % ( - yelo.mod_name, yelo.memory_upgrade_multiplier, - bool(flags.uses_memory_upgrades), - bool(flags.uses_mod_data_files), - bool(flags.is_protected), - bool(flags.uses_game_state_upgrades), - bool(flags.has_compression_params), - info.build_string, info.timestamp, info.stage.enum_name, - info.revision, cheape.build_string, - info.cheape.maj, info.cheape.min, info.cheape.build, - cheape.size, cheape.offset, cheape.decompressed_size, - min_os.maj, min_os.min, min_os.build, - version.project_yellow, version.project_yellow_globals, - rsrc.compression_params_header_offset, - rsrc.tag_symbol_storage_header_offset, - rsrc.string_id_storage_header_offset, - rsrc.tag_string_to_id_storage_header_offset) - def generate_vap_info_string(self): vap = self.map_header.vap_header diff --git a/reclaimer/meta/wrappers/halo1_rsrc_map.py b/reclaimer/meta/wrappers/halo1_rsrc_map.py index 08539d8f..e875ac6a 100644 --- a/reclaimer/meta/wrappers/halo1_rsrc_map.py +++ b/reclaimer/meta/wrappers/halo1_rsrc_map.py @@ -25,6 +25,7 @@ from reclaimer.meta.wrappers.map_pointer_converter import MapPointerConverter from reclaimer.meta.wrappers.tag_index_manager import TagIndexManager from reclaimer.meta.wrappers.halo_map import HaloMap +from reclaimer.sounds import ogg as sounds_ogg, constants as sound_const from supyr_struct.buffer import BytearrayBuffer, get_rawdata from supyr_struct.field_types import FieldType @@ -364,6 +365,10 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): meta.maximum_bend_per_second = meta.maximum_bend_per_second ** 30 meta.unknown1 = 0xFFFFFFFF meta.unknown2 = 0xFFFFFFFF + channels = sound_const.channel_counts.get( + meta.encoding.data, 1 + ) + bytes_per_sample = 2 * channels for pitch_range in meta.pitch_ranges.STEPTREE: # null some meta-only fields pitch_range.playback_rate = 0.0 @@ -377,8 +382,13 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): # byteswap pcm audio byteswap_pcm16_samples(perm.samples) elif perm.compression.enum_name == "ogg": - # TODO: find a way to calculate the buffer size here - buffer_size = perm.buffer_size + # oggvorbis NEEDS this set for proper playback ingame + buffer_size = ( + sounds_ogg.get_pcm_sample_count(perm.samples.data) + if sound_const.OGGVORBIS_AVAILABLE else + # oh well. default to whatever it's set to + (perm.buffer_size // bytes_per_sample) + ) * bytes_per_sample else: buffer_size = 0 diff --git a/reclaimer/meta/wrappers/halo1_yelo.py b/reclaimer/meta/wrappers/halo1_yelo.py index 0e92bbc6..8ba796f2 100644 --- a/reclaimer/meta/wrappers/halo1_yelo.py +++ b/reclaimer/meta/wrappers/halo1_yelo.py @@ -32,7 +32,11 @@ def is_fully_yelo(self): # requirement that they be yelo maps, this wrapper is able to be # used with the engine still set to halo1ce. to determine if the # map is truely a .yelo map, we need to check the engine. - return self.engine == "halo1yelo" + yelo_header = self.map_header.yelo_header + return "" not in ( + yelo_header.yelo.enum_name, + yelo_header.version_type.enum_name + ) @property def uses_mod_data_files(self): @@ -98,4 +102,71 @@ def setup_defs(self): this_class.defs = FrozenDict(this_class.defs) # make a shallow copy for this instance to manipulate - self.defs = dict(self.defs) \ No newline at end of file + self.defs = dict(self.defs) + + def generate_map_info_string(self): + string = super().generate_map_info_string() + if self.is_fully_yelo: + string += self.generate_yelo_info_string() + return string + + def generate_yelo_info_string(self): + yelo = self.map_header.yelo_header + flags = yelo.flags + info = yelo.build_info + version = yelo.tag_versioning + cheape = yelo.cheape_definitions + rsrc = yelo.resources + min_os = info.minimum_os_build + + return """ +Yelo information: + Mod name == %s + Memory upgrade amount == %sx + + Flags: + uses memory upgrades == %s + uses mod data files == %s + is protected == %s + uses game state upgrades == %s + has compression parameters == %s + + Build info: + build string == %s + timestamp == %s + stage == %s + revision == %s + + Cheape: + build string == %s + version == %s.%s.%s + size == %s + offset == %s + decompressed size == %s + + Versioning: + minimum open sauce == %s.%s.%s + project yellow == %s + project yellow globals == %s + + Resources: + compression parameters header offset == %s + tag symbol storage header offset == %s + string id storage header offset == %s + tag string to id storage header offset == %s\n""" % ( + yelo.mod_name, yelo.memory_upgrade_multiplier, + bool(flags.uses_memory_upgrades), + bool(flags.uses_mod_data_files), + bool(flags.is_protected), + bool(flags.uses_game_state_upgrades), + bool(flags.has_compression_params), + info.build_string, info.timestamp, info.stage.enum_name, + info.revision, cheape.build_string, + info.cheape.maj, info.cheape.min, info.cheape.build, + cheape.size, cheape.offset, cheape.decompressed_size, + min_os.maj, min_os.min, min_os.build, + version.project_yellow, version.project_yellow_globals, + rsrc.compression_params_header_offset, + rsrc.tag_symbol_storage_header_offset, + rsrc.string_id_storage_header_offset, + rsrc.tag_string_to_id_storage_header_offset) \ No newline at end of file diff --git a/reclaimer/misc/defs/composer_playlist.py b/reclaimer/misc/defs/composer_playlist.py index 49dfe7c8..c6ffdb82 100644 --- a/reclaimer/misc/defs/composer_playlist.py +++ b/reclaimer/misc/defs/composer_playlist.py @@ -11,7 +11,7 @@ from supyr_struct.field_types import * from supyr_struct.defs.tag_def import TagDef -from binilla.constants import VISIBILITY_METADATA +from reclaimer.constants import VISIBILITY_METADATA def get(): return composer_playlist_def diff --git a/reclaimer/misc/defs/recorded_animations.py b/reclaimer/misc/defs/recorded_animations.py new file mode 100644 index 00000000..748ad855 --- /dev/null +++ b/reclaimer/misc/defs/recorded_animations.py @@ -0,0 +1,325 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +from math import pi +from supyr_struct.field_types import * +from supyr_struct.defs.tag_def import TagDef +from reclaimer.common_descs import * + +import traceback + +# Faster than if/elif chain +FUNCTION_MAP = { + "end_anim" : None, + "set_anim_state" : "anim_state", + "set_aim_speed" : "aim_speed", + "set_control_flags" : "control_flags", + "set_weapon_index" : "weapon_index", + "set_throttle" : "throttle", + "set_face_char" : "angle_delta_byte", + "set_aim_char" : "angle_delta_byte", + "set_face_aim_char" : "angle_delta_byte", + "set_look_char" : "angle_delta_byte", + "set_face_look_char" : "angle_delta_byte", + "set_aim_look_char" : "angle_delta_byte", + "set_face_aim_look_char" : "angle_delta_byte", + "set_face_short" : "angle_delta_short", + "set_aim_short" : "angle_delta_short", + "set_face_aim_short" : "angle_delta_short", + "set_look_short" : "angle_delta_short", + "set_face_look_short" : "angle_delta_short", + "set_aim_look_short" : "angle_delta_short", + "set_face_aim_look_short" : "angle_delta_short", + } +ANGLE_DELTA_SCALE = pi/1000 + + +def build_r_a_stream_block(version, rawdata, simple=False): + v0_def = r_a_stream_v0_simple_def if simple else r_a_stream_v0_def + v3_def = r_a_stream_v3_simple_def if simple else r_a_stream_v3_def + v4_def = r_a_stream_v4_simple_def if simple else r_a_stream_v4_def + return ( + v0_def.build(rawdata=rawdata) if version == 0 else + v3_def.build(rawdata=rawdata) if version == 3 else + v4_def.build(rawdata=rawdata) if version == 4 else + None + ) + + +def has_next_event(parent=None, rawdata=None, **kwargs): + try: + # Proper case + if len(parent) and parent[-1].event_function.function.enum_name == "end_anim": + return False + # End of stream reached before end packet read from stream + elif len(rawdata.peek(1)) < 1: + print("Warning: End of data reached before an end packet was read. Tag may be corrupt!") + return False + return True + except AttributeError: + return False + + +def time_delta_case(parent=None, **kwargs): + return parent.event_function.size.enum_name if parent else None + + +def parameter_case(parent=None, **kwargs): + func_name = parent.event_function.function.enum_name if parent else None + return FUNCTION_MAP.get(func_name, None) + + +def get_event_summary_string(parent=None, **kw): + if parent is None: + return None + + try: + param_strs = [] + + time_type = parent.event_function.time_delay_type.enum_name + func_type = parent.event_function.function.enum_name + param_strs.append( + "[%.3fs] " % (( + 1 if time_type == "tick" else + 0 if time_type == "instant" else + parent.time.delay + ) / 30) + ) + + if func_type != "end_anim": + param_strs.append("%s {" % func_type.\ + replace("char", "").replace("short", "").strip("_") + ) + else: + param_strs.append("end_anim") + + params_desc = "" + params = parent.parameters + if func_type == "set_anim_state": + params_desc = "%s}" % params.animation_state.enum_name + elif func_type == "set_aim_speed": + params_desc = "%s}" % params.aim_speed.enum_name + elif func_type == "set_control_flags": + flags = params.control_flags + params_desc = "%s}" % "|".join( + name.split("_")[0] for name in flags.NAME_MAP + if flags[name] + ) or "None" + elif func_type == "set_weapon_index": + params_desc = "%s}" % params.weapon_index + elif func_type != "end_anim": + scale = 1 if func_type == "set_throttle" else ANGLE_DELTA_SCALE + + params_desc = ", ".join( + "%.3f" % ( + params[name] * scale + ) + for name in params.NAME_MAP + ) + "}" + + if params_desc: + param_strs.append(params_desc) + + parent.summary = "".join(param_strs) + except Exception: + print(traceback.format_exc()) + + +animation_state = UEnum8("animation_state", + "sleep", + "alert1", + "alert2", + "stand1", + "stand2", + "flee", + "flaming", + ) + +aim_speed = UEnum8("aim_speed", + "alert", + "relaxed", + ) + +control_flags = Bool16("control_flags", + "crouch", + "jump", + "user1", + "user2", + "flashlight", + "lock_facing", + "action", + "melee", + "unlock_facing", + "walk", + "reload", + "primary_trigger", + "secondary_trigger", + "grenade", + "exchange", + ) + +angle_delta_byte = QStruct("", + SInt8("x"), SInt8("y"), ORIENT="h" + ) + +angle_delta_short = QStruct("", + SInt16("x"), SInt16("y"), ORIENT="h" + ) + +event_function = BitStruct("event_function", + UBitEnum("time_delay_type", + "instant", + "tick", + "byte", # Suggested edit: The code asserts 1 < time_delta < UNSIGNED_CHAR_MAX + "short", # Suggested edit: The code asserts UNSIGNED_CHAR_MAX < time_delta + SIZE=2 + ), + UBitEnum("function", # Suggested edit: The code asserts this to being n < NUMBEROF(apply_funcs) [0x5b] + ("end_anim", 1), # 1 + "set_anim_state", # 2 + "set_aim_speed", # 3 + "set_control_flags", # 4 + "set_weapon_index", # 5 + "set_throttle", # 6 + # start of char difference # 7 For future mgmt: This isn't skipped, we just precalculated the flags below + ("set_face_char", 8), # 8 + "set_aim_char", # 9 + "set_face_aim_char", # 10 + "set_look_char", # 11 + "set_face_look_char", # 12 + "set_aim_look_char", # 13 + "set_face_aim_look_char", # 14 + # start of short difference # 15 For future mgmt: This isn't skipped, we just precalculated the flags below + ("set_face_short", 16), # 16 + "set_aim_short", # 17 + "set_face_aim_short", # 18 + "set_look_short", # 19 + "set_face_look_short", # 20 + "set_aim_look_short", # 21 + "set_face_aim_look_short", # 22 + SIZE=6 + ), + SIZE=1, HIDE_TITLE=True + ) + +event_fields = ( + event_function, + Switch("time", + CASES={ + "byte" : QStruct("time", UInt8("delay")), # Renamed to "delay" as I feel it better represents what's going on here + "short" : QStruct("time", UInt16("delay")), + }, + CASE=".event_function.time_delay_type.enum_name" + ), + Switch("parameters", + CASES={ + "aim_speed" : Struct("aim_speed", aim_speed), + "control_flags" : Struct("control_flags", control_flags), + "anim_state" : Struct("anim_state", animation_state), + "weapon_index" : QStruct("weapon_index", SInt16("weapon_index")), + "throttle" : QStruct("throttle", INCLUDE=xy_float), + "angle_delta_byte" : QStruct("angle_delta", INCLUDE=angle_delta_byte), + "angle_delta_short" : QStruct("angle_delta", INCLUDE=angle_delta_short), + }, + CASE=parameter_case, + ) + ) + +event = Container("event", + *event_fields, + # NOTE: summary is after event so the event data is already parsed + Computed("summary", + COMPUTE_READ=get_event_summary_string, WIDGET_WIDTH=60 + ) + ) + +event_simple = Container("event", + *event_fields, + ) + +events_simple = WhileArray("events", + SUB_STRUCT=event_simple, CASE=has_next_event + ) + +events = WhileArray("events", + SUB_STRUCT=event, CASE=has_next_event, + DYN_NAME_PATH=".summary", WIDGET=DynamicArrayFrame + ) + +# Suggested edit: the "stream_headers" versions are actually "control" versions internally +r_a_stream_header_fields_0 = ( + animation_state, + aim_speed, + control_flags, + # This is weird and I hate it. Still trying to figure out what is going on here + SInt8("unknown_01", DEFAULT=-1), + SInt8("weapon_index", DEFAULT=-1, MIN=-1, MAX=3), # clamped[-1,4) + + UInt8("unknown_02"), + UInt8("unknown_03"), + + QStruct("velocity", INCLUDE=xy_float), + QStruct("facing_vector", INCLUDE=xyz_float), #, EDITABLE=False), # For now I'm gonna keep editable + QStruct("aiming_vector", INCLUDE=xyz_float), #, EDITABLE=False), + QStruct("looking_vector", INCLUDE=xyz_float), #, EDITABLE=False), + ) + +r_a_stream_header_fields_1 = ( + QStruct("facing", INCLUDE=angle_delta_short), + QStruct("aiming", INCLUDE=angle_delta_short), + QStruct("looking", INCLUDE=angle_delta_short), + ) + +r_a_stream_header_v0 = Struct("r_a_stream_header_v0", + *r_a_stream_header_fields_0, + *r_a_stream_header_fields_1, + SIZE=64 + ) + +r_a_stream_header_v3 = Struct("r_a_stream_header_v3", + *r_a_stream_header_fields_0, + + SInt16("unknown_04"), + SInt16("unknown_05"), + SInt16("grenade_index", MIN=0, MAX=3), + + *r_a_stream_header_fields_1, + SIZE=70 + ) + +r_a_stream_header_v4 = Struct("r_a_stream_header_v4", + *r_a_stream_header_fields_0, + + SInt16("unknown_04"), + SInt16("unknown_05"), + SInt16("grenade_index", MIN=0, MAX=3), # Must be -1 < n < 4 + SInt16("zoom_level", MIN=0), # Must be positive + + *r_a_stream_header_fields_1, + SIZE=72 + ) + +r_a_stream_v0 = Container("r_a_stream", r_a_stream_header_v0, events) +r_a_stream_v3 = Container("r_a_stream", r_a_stream_header_v3, events) +r_a_stream_v4 = Container("r_a_stream", r_a_stream_header_v4, events) + +r_a_stream_v0_def = BlockDef(r_a_stream_v0) +r_a_stream_v3_def = BlockDef(r_a_stream_v3) +r_a_stream_v4_def = BlockDef(r_a_stream_v4) + +r_a_stream_v0_simple_def = BlockDef("r_a_stream", r_a_stream_header_v0, events_simple) +r_a_stream_v3_simple_def = BlockDef("r_a_stream", r_a_stream_header_v3, events_simple) +r_a_stream_v4_simple_def = BlockDef("r_a_stream", r_a_stream_header_v4, events_simple) + +r_a_stream_v0_tagdef = TagDef(r_a_stream_v0, endian="<") +r_a_stream_v3_tagdef = TagDef(r_a_stream_v3, endian="<") +r_a_stream_v4_tagdef = TagDef(r_a_stream_v4, endian="<") + +def get(): + return r_a_stream_v0_tagdef, r_a_stream_v3_tagdef, r_a_stream_v4_tagdef diff --git a/reclaimer/misc/handler.py b/reclaimer/misc/handler.py index 8671376a..4226b891 100644 --- a/reclaimer/misc/handler.py +++ b/reclaimer/misc/handler.py @@ -10,6 +10,8 @@ from pathlib import Path import os +# NOTE: this is a pretty tough dependency to move to make +# reclaimer able to operate without binilla installed. from binilla.handler import Handler from reclaimer.misc.defs import __all__ as all_def_names diff --git a/reclaimer/sounds/adpcm.py b/reclaimer/sounds/adpcm.py index ae7afa69..0303367e 100644 --- a/reclaimer/sounds/adpcm.py +++ b/reclaimer/sounds/adpcm.py @@ -103,7 +103,8 @@ def decode_adpcm_samples(in_data, channel_ct, output_big_endian=False): def encode_adpcm_samples(in_data, channel_ct, input_big_endian=False, - noise_shaping=NOISE_SHAPING_OFF, lookahead=3): + noise_shaping=NOISE_SHAPING_OFF, lookahead=3, + fit_to_blocksize=True): assert noise_shaping in (NOISE_SHAPING_OFF, NOISE_SHAPING_STATIC, NOISE_SHAPING_DYNAMIC) assert lookahead in range(6) @@ -117,13 +118,16 @@ def encode_adpcm_samples(in_data, channel_ct, input_big_endian=False, in_data = audioop.byteswap(in_data, 2) adpcm_blocksize = constants.XBOX_ADPCM_COMPRESSED_BLOCKSIZE * channel_ct - pcm_blocksize = constants.XBOX_ADPCM_DECOMPRESSED_BLOCKSIZE * channel_ct - - pad_size = len(in_data) % pcm_blocksize - if pad_size: - pad_size = pcm_blocksize - pad_size + pcm_blocksize = constants.XBOX_ADPCM_DECOMPRESSED_BLOCKSIZE * channel_ct + pcm_block_count = len(in_data) // pcm_blocksize + + pad_size = (pcm_blocksize - (len(in_data) % pcm_blocksize)) % pcm_blocksize + if fit_to_blocksize and pad_size: + # truncate to fit to blocksize + in_data = in_data[:pcm_block_count * pcm_blocksize] + elif pad_size: # repeat the last sample to the end to pad to a multiple of blocksize - pad_piece_size = (channel_ct * 2) + pad_piece_size = channel_ct * 2 in_data += in_data[-pad_piece_size: ] * (pad_size // pad_piece_size) out_data = bytearray( diff --git a/reclaimer/sounds/blam_sound_bank.py b/reclaimer/sounds/blam_sound_bank.py index 49034382..475943ad 100644 --- a/reclaimer/sounds/blam_sound_bank.py +++ b/reclaimer/sounds/blam_sound_bank.py @@ -13,7 +13,7 @@ from traceback import format_exc from reclaimer.sounds.blam_sound_permutation import BlamSoundPermutation -from reclaimer.sounds import constants, ogg, adpcm +from reclaimer.sounds import constants, adpcm class BlamSoundPitchRange: @@ -55,11 +55,13 @@ def create_from_directory(directory): return new_pitch_range def export_to_directory(self, directory, overwrite=False, - export_source=True, decompress=True): + export_source=True, decompress=True, + format=constants.CONTAINER_EXT_WAV): for name, perm in self.permutations.items(): perm.export_to_file( Path(directory, name), overwrite, - export_source, decompress) + export_source, decompress, format + ) def import_from_directory(self, directory, clear_existing=True, replace_existing=True): @@ -70,8 +72,7 @@ def import_from_directory(self, directory, clear_existing=True, # import each sound file to a new sound permutation for filename in files: filepath = Path(root, filename) - if filepath.suffix.lower() != ".wav": - # only import wav files + if filepath.suffix.lower() not in constants.SUPPORTED_CONTAINER_EXTS: continue name_key = filepath.stem.lower().strip() @@ -97,16 +98,32 @@ class BlamSoundBank: # this value is fine to bump under most circumstances. chunk_size = constants.DEF_SAMPLE_CHUNK_SIZE - vorbis_bitrate_info = None - + # if True, truncates samples to fit to a multiple of 130. + # otherwise pads the block up with the last sample in it. + adpcm_fit_to_blocksize = True adpcm_noise_shaping = adpcm.NOISE_SHAPING_OFF adpcm_lookahead = 3 + # Combinations of nominal/lower/upper carry these implications: + # all three set to the same value: + # implies a fixed rate bitstream + # only nominal set: + # implies a VBR stream that has this nominal bitrate. + # No hard upper/lower limit + # upper and or lower set: + # implies a VBR bitstream that obeys the bitrate limits. + # nominal may also be set to give a nominal rate. + # none set: + # the coder does not care to speculate. + ogg_bitrate_lower = -1 + ogg_bitrate_upper = -1 + ogg_bitrate_nominal = -1 + ogg_bitrate_window = -1 + _pitch_ranges = () def __init__(self): self._pitch_ranges = {} - self.vorbis_bitrate_info = ogg.VorbisBitrateInfo() @property def pitch_ranges(self): @@ -122,11 +139,15 @@ def compress_samples(self): chunk_size = self.chunk_size adpcm_kwargs = dict( - noise_shaping=self.adpcm_noise_shaping, - lookahead=self.adpcm_lookahead + noise_shaping = self.adpcm_noise_shaping, + lookahead = self.adpcm_lookahead, + fit_to_blocksize = self.adpcm_fit_to_blocksize, ) ogg_kwargs = dict( - bitrate_info=self.vorbis_bitrate_info + ogg_bitrate_lower = self.ogg_bitrate_lower, + ogg_bitrate_upper = self.ogg_bitrate_upper, + ogg_bitrate_nominal = self.ogg_bitrate_nominal, + ogg_bitrate_window = self.ogg_bitrate_window, ) for pitch_range in self.pitch_ranges.values(): @@ -151,7 +172,8 @@ def create_from_directory(directory): return new_sound_bank def export_to_directory(self, directory, overwrite=False, - export_source=True, decompress=True): + export_source=True, decompress=True, + format=constants.CONTAINER_EXT_WAV): for name, pitch_range in self.pitch_ranges.items(): if len(self.pitch_ranges) > 1: pitch_directory = Path(directory, name) @@ -159,7 +181,9 @@ def export_to_directory(self, directory, overwrite=False, pitch_directory = Path(directory) pitch_range.export_to_directory( - pitch_directory, overwrite, export_source, decompress) + pitch_directory, overwrite, export_source, + decompress, format + ) def import_from_directory(self, directory, clear_existing=True, merge_same_names=False): diff --git a/reclaimer/sounds/blam_sound_permutation.py b/reclaimer/sounds/blam_sound_permutation.py index c64694c1..ff4750ea 100644 --- a/reclaimer/sounds/blam_sound_permutation.py +++ b/reclaimer/sounds/blam_sound_permutation.py @@ -89,10 +89,7 @@ def partition_samples(self, target_compression=None, if target_encoding is None: target_encoding = self.source_encoding - if self.source_compression == constants.COMPRESSION_OGG: - raise NotImplementedError( - "Cannot partition Ogg Vorbis samples.") - elif (target_compression not in constants.PCM_FORMATS and + if (target_compression not in constants.PCM_FORMATS and target_compression != constants.COMPRESSION_IMA_ADPCM and target_compression != constants.COMPRESSION_XBOX_ADPCM and target_compression != constants.COMPRESSION_OGG): @@ -214,21 +211,27 @@ def get_concatenated_sample_data(self, target_compression=None, def get_concatenated_mouth_data(self): return b''.join(p.mouth_data for p in self.processed_samples) - def regenerate_source(self): + def regenerate_source( + self, compression=None, sample_rate=None, encoding=None + ): ''' Regenerates an uncompressed, concatenated audio stream from the compressed samples. Use when loading a sound tag for re-compression, re-sampling, or re-encoding. ''' - # always regenerate to constants.DEFAULT_UNCOMPRESSED_FORMAT + # default to regenerating to constants.DEFAULT_UNCOMPRESSED_FORMAT # because, technically speaking, that is highest sample depth # we can ever possibly see in Halo CE. + if compression is None: compression = constants.DEFAULT_UNCOMPRESSED_FORMAT + if sample_rate is None: sample_rate = self.sample_rate + if encoding is None: encoding = self.encoding + self._source_sample_data = self.get_concatenated_sample_data( - constants.DEFAULT_UNCOMPRESSED_FORMAT, - self.sample_rate, self.encoding) - self._source_compression = constants.DEFAULT_UNCOMPRESSED_FORMAT - self._source_sample_rate = self.sample_rate - self._source_encoding = self.encoding + compression, sample_rate, encoding + ) + self._source_compression = compression + self._source_sample_rate = sample_rate + self._source_encoding = encoding @staticmethod def create_from_file(filepath): @@ -242,74 +245,89 @@ def create_from_file(filepath): return new_perm def export_to_file(self, filepath_base, overwrite=False, - export_source=True, decompress=True): + export_source=True, decompress=True, + format=constants.CONTAINER_EXT_WAV): perm_chunks = [] - encoding = self.encoding - sample_rate = self.sample_rate + + filepath = Path(util.BAD_PATH_CHAR_REMOVAL.sub("_", str(filepath_base))) + filepath = Path("unnamed" if is_path_empty(filepath) else filepath) + filepath = filepath.with_suffix(format) + if export_source and self.source_sample_data: # export the source data perm_chunks.append( - (self.compression, self.source_encoding, self.source_sample_data) + (self.compression, self.source_sample_rate, + self.source_encoding, self.source_sample_data, + filepath) ) - sample_rate = self.source_sample_rate elif self.processed_samples: # concatenate processed samples if source samples don't exist. # also, if compression is ogg, we have to decompress compression = self.compression - if decompress or compression == constants.COMPRESSION_OGG: + if decompress or compression in constants.PYOGG_CONTAINER_FORMATS: compression = constants.COMPRESSION_PCM_16_LE try: sample_data = self.get_concatenated_sample_data( - compression, sample_rate, encoding) + compression, self.sample_rate, self.encoding + ) if sample_data: - perm_chunks.append((compression, self.encoding, sample_data)) + perm_chunks.append(( + compression, self.sample_rate, self.encoding, + sample_data, filepath + )) except Exception: - print("Could not decompress permutation pieces. Concatenating.") - perm_chunks.extend( - (piece.compression, piece.encoding, piece.sample_data) - for piece in self.processed_samples - ) - - i = -1 - wav_file = wav_def.build() - for compression, encoding, sample_data in perm_chunks: - i += 1 - filepath = Path(util.BAD_PATH_CHAR_REMOVAL.sub("_", str(filepath_base))) - - if is_path_empty(filepath): - filepath = Path("unnamed") - - if len(perm_chunks) > 1: - filepath = filepath.parent.joinpath(filepath.stem + "__%s" % i) - - # figure out if the sample data is already encapsulated in a - # container, or if it'll need to be encapsulated in a wav file. - is_container_format = True - if compression == constants.COMPRESSION_OGG: - ext = ".ogg" - elif compression == constants.COMPRESSION_WMA: - ext = ".wma" - elif compression == constants.COMPRESSION_UNKNOWN: - ext = ".bin" - else: - is_container_format = False - ext = ".wav" + print("Could not decompress permutation pieces. Exporting in pieces.") + # gotta switch format to the container it's compressed as + for i, piece in enumerate(self.processed_samples): + format = { + constants.COMPRESSION_OGG: constants.CONTAINER_EXT_OGG, + constants.COMPRESSION_OPUS: constants.CONTAINER_EXT_OPUS, + constants.COMPRESSION_FLAC: constants.CONTAINER_EXT_FLAC, + }.get(piece.compression, format) + + name_base = "%s__%%s%s" % (filepath.stem, format) + perm_chunks.append( + (piece.compression, piece.sample_rate, piece.encoding, + piece.sample_data, filepath.with_name(name_base % i)) + ) + + if format == constants.CONTAINER_EXT_WAV: + self._export_to_wav(perm_chunks, overwrite) + elif format in constants.SUPPORTED_CONTAINER_EXTS: + self._export_to_container(perm_chunks, overwrite) + else: + raise ValueError("Unknown compression method.") - if filepath.suffix.lower() != ext: - filepath = filepath.with_suffix(ext) + def _export_to_container(self, perm_chunks, overwrite): + for perm_chunk in perm_chunks: + compression, sample_rate, encoding, sample_data, filepath = perm_chunk - if not sample_data or (not overwrite and filepath.is_file()): + format = filepath.suffix.lower() + if format == constants.CONTAINER_EXT_WAV: + # if somehow a wav got passed in, pass off to that method + self._export_to_wav([perm_chunk], overwrite) continue + elif not sample_data or (not overwrite and filepath.is_file()): + continue + + # TODO: write this + # make sure to handle samples already being in ogg and not + # needing to be decompressed and reencoded if not chunked. + + try: + filepath.parent.mkdir(exist_ok=True, parents=True) + with filepath.open("wb") as f: + f.write(sample_data) + except Exception: + print(format_exc()) - if is_container_format: - try: - filepath.parent.mkdir(exist_ok=True, parents=True) - with filepath.open("wb") as f: - f.write(sample_data) - except Exception: - print(format_exc()) + def _export_to_wav(self, perm_chunks, overwrite): + wav_file = wav_def.build() + for perm_chunk in perm_chunks: + compression, sample_rate, encoding, sample_data, filepath = perm_chunk + if not sample_data or (not overwrite and filepath.is_file()): continue wav_file.filepath = filepath @@ -364,10 +382,25 @@ def export_to_file(self, filepath_base, overwrite=False, def import_from_file(self, filepath): filepath = Path(filepath) + format = filepath.suffix.lower() + if not filepath.is_file(): raise OSError('File "%s" does not exist. Cannot import.' % filepath) - wav_file = wav_def.build(filepath=filepath) + if format == constants.CONTAINER_EXT_WAV: + self._import_from_wav(filepath) + elif (format in constants.SUPPORTED_CONTAINER_EXTS and + format in constants.PYOGG_CONTAINER_EXTS): + self._import_from_container(filepath) + else: + raise ValueError("Cannot import this type of file.") + + def _import_from_container(self, filepath): + # TODO: update this to handle ogg vorbis, flac, and opus + raise NotImplementedError() + + def _import_from_wav(self, filepath): + wav_file = wav_def.build(filepath=filepath) wav_header = wav_file.data.wav_header wav_format = wav_file.data.wav_format wav_chunks = wav_file.data.wav_chunks diff --git a/reclaimer/sounds/blam_sound_samples.py b/reclaimer/sounds/blam_sound_samples.py index 0132d4b8..8b3bee22 100644 --- a/reclaimer/sounds/blam_sound_samples.py +++ b/reclaimer/sounds/blam_sound_samples.py @@ -9,7 +9,7 @@ from traceback import format_exc -from reclaimer.sounds import constants, ogg, util, adpcm +from reclaimer.sounds import adpcm, constants, ogg, util class BlamSoundSamples: @@ -65,9 +65,10 @@ def compress(self, target_compression, target_sample_rate=None, return if (target_compression == constants.COMPRESSION_OGG and - not constants.OGG_VORBIS_AVAILABLE): + not constants.OGGVORBIS_AVAILABLE): raise NotImplementedError( - "Ogg encoder not available. Cannot compress.") + "Ogg encoder not available. Cannot compress." + ) elif (target_compression not in constants.PCM_FORMATS and target_compression != constants.COMPRESSION_XBOX_ADPCM and target_compression != constants.COMPRESSION_IMA_ADPCM and @@ -112,21 +113,23 @@ def compress(self, target_compression, target_sample_rate=None, constants.ADPCM_DECOMPRESSED_FORMAT) compression = constants.ADPCM_DECOMPRESSED_FORMAT - adpcm_kwargs = compressor_kwargs.get("adpcm_kwargs", {}) - sample_data = adpcm.encode_adpcm_samples( sample_data, constants.channel_counts[target_encoding], - util.is_big_endian_pcm(compression), **adpcm_kwargs) + util.is_big_endian_pcm(compression), + **compressor_kwargs.get("adpcm_kwargs", {}) + ) elif target_compression == constants.COMPRESSION_OGG: # compress to ogg vorbis - # TODO: Finish this - ogg_kwargs = compressor_kwargs.get("ogg_kwargs", {}) - - raise NotImplementedError("Whoops, ogg is not implemented.") + sample_data = encode_oggvorbis( + sample_data, constants.channel_counts[target_encoding], + target_sample_rate, util.is_big_endian_pcm(compression), + **compressor_kwargs.get("ogg_kwargs", {}) + ) elif target_compression != self.compression: # convert to a different pcm format sample_data = util.convert_pcm_to_pcm( - sample_data, self.compression, target_compression) + sample_data, self.compression, target_compression + ) self._sample_data = sample_data self._compression = target_compression @@ -146,21 +149,22 @@ def get_decompressed(self, target_compression, target_sample_rate=None, assert target_sample_rate > 0 curr_compression = self.compression + curr_encoding = self.encoding + curr_sample_rate = self.sample_rate if curr_compression in (constants.COMPRESSION_XBOX_ADPCM, constants.COMPRESSION_IMA_ADPCM): # decompress adpcm to 16bit pcm sample_data = adpcm.decode_adpcm_samples( - self.sample_data, constants.channel_counts[self.encoding], + self.sample_data, constants.channel_counts[curr_encoding], util.is_big_endian_pcm(target_compression)) curr_compression = constants.ADPCM_DECOMPRESSED_FORMAT elif not self.is_compressed: # samples are decompressed. use as-is sample_data = self.sample_data elif curr_compression == constants.COMPRESSION_OGG: - if not constants.OGG_VORBIS_AVAILABLE: - raise NotImplementedError( - "Ogg decoder not available. Cannot decompress.") - # TODO: Finish this + sample_data, curr_compression, curr_encoding, curr_sample_rate =\ + ogg.decode_oggvorbis(self.sample_data) + elif curr_compression == constants.COMPRESSION_WMA: if not constants.WMA_AVAILABLE: raise NotImplementedError( @@ -170,12 +174,14 @@ def get_decompressed(self, target_compression, target_sample_rate=None, raise ValueError("Unknown compression format.") if (curr_compression != target_compression or - self.encoding != target_encoding or - self.sample_rate != target_sample_rate): + curr_encoding != target_encoding or + curr_sample_rate != target_sample_rate): sample_data = util.convert_pcm_to_pcm( - sample_data, curr_compression, target_compression, - self.encoding, target_encoding, - self.sample_rate, target_sample_rate) + sample_data, + curr_compression, target_compression, + curr_encoding, target_encoding, + curr_sample_rate, target_sample_rate + ) return sample_data diff --git a/reclaimer/sounds/constants.py b/reclaimer/sounds/constants.py index 349ad1f9..fe7443a5 100644 --- a/reclaimer/sounds/constants.py +++ b/reclaimer/sounds/constants.py @@ -9,24 +9,50 @@ import sys +PLAYBACK_AVAILABLE = False +OGGVORBIS_AVAILABLE = False +FLAC_AVAILABLE = False +OPUS_AVAILABLE = False +WMA_AVAILABLE = False + +OGGVORBIS_ENCODING_AVAILABLE = False + try: - from reclaimer.sounds.ext import ogg_ext - OGG_VORBIS_AVAILABLE = True + import pyogg + OGGVORBIS_AVAILABLE = ( + pyogg.PYOGG_OGG_AVAIL and + pyogg.PYOGG_VORBIS_AVAIL and + pyogg.PYOGG_VORBIS_FILE_AVAIL + ) + FLAC_AVAILABLE = pyogg.PYOGG_FLAC_AVAIL + OPUS_AVAILABLE = ( + pyogg.PYOGG_OPUS_AVAIL and + pyogg.PYOGG_OPUS_FILE_AVAIL + ) + # encoding isn't available in all releases + OGGVORBIS_ENCODING_AVAILABLE = OGGVORBIS_AVAILABLE and getattr( + pyogg, "PYOGG_VORBIS_ENC_AVAIL", False + ) + + # NOTE: for right now these won't be available. + # still need to implement them. + OGGVORBIS_ENCODING_AVAILABLE = False + OPUS_AVAILABLE = FLAC_AVAILABLE = False except ImportError: - OGG_VORBIS_AVAILABLE = False + pass try: - from reclaimer.sounds.ext import wma_ext - WMA_AVAILABLE = True + import simpleaudio + del simpleaudio + PLAYBACK_AVAILABLE = True except ImportError: - WMA_AVAILABLE = False + pass SOUND_COMPILE_MODE_NEW = 0 SOUND_COMPILE_MODE_PRESERVE = 1 SOUND_COMPILE_MODE_ADDITIVE = 2 - COMPRESSION_UNKNOWN = -1 # NOTE: the ordering of these constants is such that their endianess # can be swapped by flipping the first bit. This is used in util.py @@ -45,6 +71,10 @@ COMPRESSION_OGG = 18 # halo pc only COMPRESSION_WMA = 19 # halo 2 only +# picking much higher enum values that will never actually be used +COMPRESSION_OPUS = 1024 +COMPRESSION_FLAC = 1025 + # these encoding constants mirror halo 1/2 enum values. ENCODING_UNKNOWN = -1 ENCODING_MONO = 0 @@ -85,6 +115,31 @@ WAV_FORMAT_IMA_ADPCM, WAV_FORMAT_XBOX_ADPCM )) +PYOGG_CONTAINER_FORMATS = set(( + COMPRESSION_OGG, + COMPRESSION_OPUS, + COMPRESSION_FLAC, + )) + +CONTAINER_EXT_WAV = ".wav" +CONTAINER_EXT_OGG = ".ogg" +CONTAINER_EXT_OPUS = ".opus" +CONTAINER_EXT_FLAC = ".flac" +PYOGG_CONTAINER_EXTS = frozenset(( + CONTAINER_EXT_OGG, + CONTAINER_EXT_OPUS, + CONTAINER_EXT_FLAC + )) +SUPPORTED_CONTAINER_EXTS = frozenset(( + CONTAINER_EXT_WAV, + *([CONTAINER_EXT_OGG] if OGGVORBIS_AVAILABLE else []), + *([CONTAINER_EXT_OPUS] if OPUS_AVAILABLE else []), + *([CONTAINER_EXT_FLAC] if FLAC_AVAILABLE else []), + )) + +# for all our purposes we only care about +# decoding ogg to little-endian 16bit pcm +OGG_DECOMPRESSED_FORMAT = COMPRESSION_PCM_16_LE # Endianness interop constants if sys.byteorder == "little": @@ -153,6 +208,10 @@ 0: SAMPLE_RATE_22K, 1: SAMPLE_RATE_44K, } +halo_1_encodings = { + 0: ENCODING_MONO, + 1: ENCODING_STEREO, + } # these mappings key halo 2 compression enums # to the compression/sample rate constants @@ -168,6 +227,11 @@ 1: SAMPLE_RATE_44K, 2: SAMPLE_RATE_32K, } +halo_2_encodings = { + 0: ENCODING_MONO, + 1: ENCODING_STEREO, + 2: ENCODING_CODEC, + } # unneeded for export del sys diff --git a/reclaimer/sounds/ogg.py b/reclaimer/sounds/ogg.py index f5c2dc26..424002ce 100644 --- a/reclaimer/sounds/ogg.py +++ b/reclaimer/sounds/ogg.py @@ -1,69 +1,70 @@ -# -# This file is part of Reclaimer. -# -# For authors and copyright check AUTHORS.TXT -# -# Reclaimer is free software under the GNU General Public License v3.0. -# See LICENSE for more information. -# +import ctypes +import pathlib +import tempfile +import threading +import uuid -class VorbisBitrateInfo: - ''' - Intermediary class for storing bitrate info to pass to vorbis compression - functions. +from reclaimer.sounds import constants, util - Combinations of nominal, lower, upper values carry the following - implications: - all three set to the same value: - implies a fixed rate bitstream - only nominal set: - implies a VBR stream that nominals the nominal bitrate. No hard - upper/lower limit - upper and or lower set: - implies a VBR bitstream that obeys the bitrate limits. nominal - may also be set to give a nominal rate. - none set: - the coder does not care to speculate. - ''' - lower = -1 - upper = -1 - nominal = -1 +try: + import pyogg + from pyogg import vorbis as pyogg_vorbis +except ImportError: + pyogg = pyogg_vorbis = None - # compression base quality [-0.1, 1.0] - quality = None +TEMP_ROOT = pathlib.Path(tempfile.gettempdir(), "reclaimer_tmp") +NAME_FORMAT = "ogg_tmpfile_%s.ogg" - use_quality = False - def __init__(self, nominal=-1, lower=-1, upper=-1, quality=0.5): - ''' - See class documentation for variable descriptions. - ''' - if quality is not None: - self.set_bitrate_quality(quality) - else: - self.set_bitrate_variable(nominal, upper, lower) +def vorbis_file_from_data_stream(data_stream, streaming=False): + if not (pyogg and constants.OGGVORBIS_AVAILABLE): + raise NotImplementedError( + "OggVorbis decoder not available. Cannot decompress." + ) + + # look, it's WAY easier to just dump the file to a temp folder and + # have VorbisFile decode the entire thing than to try and hook into + # calling the ogg parsing and vorbis decoder functions on a stream. + filepath = TEMP_ROOT.joinpath(NAME_FORMAT % threading.get_native_id()) + filepath.parent.mkdir(parents=True, exist_ok=True) + with open(filepath, "wb") as f: + f.write(data_stream) - def set_bitrate_fixed(self, bitrate): - ''' - Sets a fixed bitrate to the requested number. - ''' - self.upper = self.nominal = self.lower = bitrate - self.use_quality = False + return ( + pyogg.VorbisFileStream if streaming else + pyogg.VorbisFile + )(str(filepath)) - def set_bitrate_variable(self, nominal, upper=-1, lower=-1): - ''' - Sets a variable bitrate based on the nominal and - optionally upper and lower numbers. - Check class docstring for further detail. - ''' - self.nominal = nominal - self.upper = upper - self.lower = lower - self.use_quality = False - def set_bitrate_quality(self, quality): - ''' - Sets a bitrate using the quality float. - ''' - self.quality = min(1.0, max(-0.1, float(quality))) - self.use_quality = True +def decode_oggvorbis(data_stream): + vorbis_file = vorbis_file_from_data_stream(data_stream) + sample_data = vorbis_file.buffer + sample_rate = vorbis_file.frequency + compression = constants.OGG_DECOMPRESSED_FORMAT + encoding = ( + constants.ENCODING_STEREO if vorbis_file.channels == 2 else + constants.ENCODING_MONO if vorbis_file.channels == 1 else + constants.ENCODING_UNKNOWN + ) + + return sample_data, compression, encoding, sample_rate + + +def encode_oggvorbis( + sample_data, channel_count, sample_rate, + is_big_endian=False, **kwargs + ): + if not (pyogg and constants.OGGVORBIS_ENCODING_AVAILABLE): + raise NotImplementedError( + "OggVorbis encoder not available. Cannot compress." + ) + data_stream = b'' + + return data_stream + + +def get_pcm_sample_count(data_stream): + vorbis_file = vorbis_file_from_data_stream(data_stream, streaming=True) + return pyogg_vorbis.libvorbisfile.ov_pcm_total( + ctypes.byref(vorbis_file.vf), 0 + ) \ No newline at end of file diff --git a/reclaimer/sounds/playback.py b/reclaimer/sounds/playback.py new file mode 100644 index 00000000..3114fc0d --- /dev/null +++ b/reclaimer/sounds/playback.py @@ -0,0 +1,412 @@ +# +# This file is part of Reclaimer. +# +# For authors and copyright check AUTHORS.TXT +# +# Reclaimer is free software under the GNU General Public License v3.0. +# See LICENSE for more information. +# + +import threading +import time +from traceback import format_exc + +try: + import simpleaudio +except ImportError: + simpleaudio = None + +from reclaimer.sounds import audioop, constants, util,\ + blam_sound_permutation, sound_decompilation as sound_decomp + + +def build_wave_object(sample_data, encoding, compression, sample_rate): + #print("CREATING WAVE OBJECT") + if not simpleaudio: + raise NotImplementedError( + "Could not detect simpleaudio. Cannot build WaveObject." + ) + + channel_count = constants.channel_counts.get(encoding, 'unknown') + sample_width = constants.sample_widths.get(compression, 'unknown') + if channel_count not in (1, 2): + raise ValueError( + "Cannot build WaveObject from audio with %s channels" % channel_count + ) + elif sample_width not in (1, 2, 3, 4): + raise ValueError( + "Cannot build WaveObject from samples of %s byte width" % sample_width + ) + + if sample_width > 1 and util.is_big_endian_pcm(compression): + sample_data = audioop.byteswap(sample_data, sample_width) + + return simpleaudio.WaveObject( + sample_data, channel_count, sample_width, sample_rate + ) + + +def wave_object_from_blam_sound_perm(blam_perm): + if not blam_perm: + return None + elif not blam_perm.source_sample_data: + # if there is no source sample data, we'll try to use + # the concatenated processed sample data instead. + # need to decode to 16bit pcm + blam_perm.regenerate_source(constants.COMPRESSION_PCM_16_LE) + + return build_wave_object( + blam_perm.source_sample_data, + blam_perm.source_encoding, + blam_perm.source_compression, + blam_perm.source_sample_rate + ) if blam_perm.source_sample_data else None + + +class SoundPlayerBase: + _wave_objects = () + + _play_objects_by_wave_ids = () + _player_locks_by_wave_ids = () + _force_stops_by_wave_ids = () + _merged_play_objects = () + _merged_player_lock = None + _merged_force_stop = None + + pitch_range_index = -1 + permutation_index = -1 + separate_wave_queues = True + concatenate_perm_chain = True + + class ForceStop: + _stop = False + def __bool__(self): return self._stop + def set(self, val): self._stop = bool(val) + + def __init__(self): + self._wave_objects = {} + self._play_objects_by_wave_ids = {} + self._player_locks_by_wave_ids = {} + self._force_stops_by_wave_ids = {} + self._merged_play_objects = {} + self._merged_player_lock = threading.Lock() + self._merged_force_stop = SoundPlayerBase.ForceStop() + + def get_compression(self, wave_id=None): + raise NotImplementedError("Must override this method") + def get_sample_rate(self, wave_id=None): + raise NotImplementedError("Must override this method") + def get_encoding(self, wave_id=None): + raise NotImplementedError("Must override this method") + + def get_play_objects(self, wave_id=None): + wave_id = wave_id or self.get_wave_id() + return ( + self._play_objects_by_wave_ids.setdefault(wave_id, {}) + if self.separate_wave_queues else + self._merged_play_objects + ) + + def get_player_lock(self, wave_id=None): + wave_id = wave_id or self.get_wave_id() + return ( + self._player_locks_by_wave_ids.setdefault(wave_id, threading.Lock()) + if self.separate_wave_queues else + self._merged_player_lock + ) + + def get_force_stop(self, wave_id=None): + wave_id = wave_id or self.get_wave_id() + return ( + self._force_stops_by_wave_ids.setdefault(wave_id, SoundPlayerBase.ForceStop()) + if self.separate_wave_queues else + self._merged_force_stop + ) + + @property + def pitch_ranges(self): + raise NotImplementedError("Must override this method") + @property + def permutations(self): + raise NotImplementedError("Must override this method") + + def get_wave_id(self, pr_index=None, perm_index=None): + if pr_index is None: + pr_index = self.pitch_range_index + if perm_index is None: + perm_index = self.permutation_index + + wave_id = ( + None if pr_index not in self.pitch_ranges else pr_index, + None if perm_index not in self.permutations else perm_index, + ) + return None if None in wave_id else wave_id + + def get_pitch_range(self, pr_index=None): + if pr_index is None: + pr_index = self.pitch_range_index + + return self.pitch_ranges.get(pr_index) + + def get_permutation(self, pr_index=None, perm_index=None): + if perm_index is None: + perm_index = self.permutation_index + + pr = self.get_pitch_range(pr_index) + return None if pr is None else pr.permutations.get(pr_index) + + def play_sound(self, wave_id=None, threaded=True): + try: + comp = self.get_compression(wave_id) + if not ( + (comp == constants.COMPRESSION_OGG and constants.OGGVORBIS_AVAILABLE) or + (comp == constants.COMPRESSION_OPUS and constants.OPUS_AVAILABLE) or + (comp == constants.COMPRESSION_FLAC and constants.FLAC_AVAILABLE) or + (comp == constants.COMPRESSION_XBOX_ADPCM) or + (comp in constants.PCM_FORMATS) + ): + print("Cannot play audio (unknown/unsupported compression: %s)." % comp) + return + + wave_id = wave_id or self.get_wave_id() + pr_index, perm_index = wave_id + if None in wave_id: + return + + wave_object = self._wave_objects.get(wave_id) + + # create the wave object if it doesnt exist yet + if wave_object is None: + blam_perm = self.get_permutation(pr_index, perm_index) + if blam_perm is None: + return + + if not isinstance(blam_perm, blam_sound_permutation.BlamSoundPermutation): + perms = self.permutations + if self.concatenate_perm_chain: + permlist, _ = sound_decomp.get_tag_perm_chain(perms, perm_index) + elif perm_index in range(len(perms)): + permlist = [perms[perm_index]] + else: + permlist = [] + + blam_perm = sound_decomp.tag_perm_chain_to_blam_perm( + permlist, self.get_sample_rate(wave_id), self.get_encoding(wave_id), + (self.get_compression(wave_id) == constants.COMPRESSION_PCM_16_BE) + ) + wave_object = wave_object_from_blam_sound_perm(blam_perm) + + # initiate a queued play of the sound + if wave_object is None: + return + + self._wave_objects[wave_id] = wave_object + # ensure the play_objects queue, lock, and force_stop all exist + self.get_play_objects(wave_id) + self.get_player_lock(wave_id) + self.get_force_stop(wave_id).set(False) + + if threaded: + self.play_wave_object_threaded(wave_id) + else: + self.play_wave_object(wave_id) + + except Exception: + print(format_exc()) + + def stop_sound(self, wave_id=None): + self._stop_play_objects( + self.get_play_objects(wave_id), + self.get_player_lock(wave_id), + self.get_force_stop(wave_id), + ) + + def stop_all_sounds(self): + self._stop_play_objects( + self._merged_play_objects, + self._merged_player_lock, + self._merged_force_stop, + ) + for wave_id in self._play_objects_by_wave_ids.keys(): + self._stop_play_objects( + self._play_objects_by_wave_ids.get(wave_id), + self._player_locks_by_wave_ids.get(wave_id), + self._force_stops_by_wave_ids.get(wave_id), + ) + + def play_wave_object_threaded(self, wave_id=None): + play_thread = threading.Thread( + target=self.play_wave_object, daemon=True, + args=(wave_id or self.get_wave_id(), ) + ) + play_thread.start() + return play_thread + + def play_wave_object(self, wave_id=None): + wave_id = wave_id or self.get_wave_id() + wave_object = self._wave_objects[wave_id] + if wave_object is None: + return + + play_objects = self.get_play_objects(wave_id) + player_lock = self.get_player_lock(wave_id) + force_stop = self.get_force_stop(wave_id) + + # create a spot at the end of the queue and wait our turn + queue_id = 1 + with player_lock: + queue_id = max(tuple(play_objects) + (0, )) + 1 + play_objects[queue_id] = None + #print("%s ENTERED QUEUE" % queue_id) + + # wait till its our turn to play + while play_objects: + curr_po_id, curr_po = queue_id, None + with player_lock: + curr_po_id = min(tuple(play_objects) + (queue_id, )) + curr_po = play_objects.get(curr_po_id) + + if curr_po_id != queue_id and curr_po is None: + # sound that's supposed to play hasn't yet. + # we'll wait a short time, but if it doesn't play + # then we'll need to remove it to remove deadlock + time.sleep(1) + with player_lock: + curr_po_id = min(tuple(play_objects) + (queue_id, )) + curr_po = play_objects.get(curr_po_id) + if curr_po is None: + #print("%s KICKING %s FROM QUEUE" % (queue_id, curr_po_id)) + play_objects.pop(curr_po_id, None) + + #print("%s SEES CURR PLAYING IS %s" % (queue_id, curr_po_id)) + if curr_po_id == queue_id: + break + elif curr_po_id > queue_id: + # our place in line got removed by another thread + # that decided we deadlocked. oh well, gotta return + #print("%s KICKED OUT OF QUEUE. RETURNING" % queue_id) + return + elif curr_po and curr_po.is_playing(): + #print("%s WAITING ON %s" % (queue_id, curr_po_id)) + curr_po.wait_done() + + if force_stop: + #print("%s FORCE STOPPED. RETURNING" % queue_id) + return + + # only play if we're still queued(sounds weren't stopped) + po = None + with player_lock: + #print("%s PLAYING" % queue_id) + # play the sound and add to the queue for tracking + po = play_objects[queue_id] = wave_object.play() + + # wait till sound is done, and then cleanup + try: + po.wait_done() + finally: + #print("%s DONE PLAYING" % queue_id) + play_objects.pop(queue_id, None) + + def _stop_play_objects(self, play_objects, player_lock, force_stop): + force_stop.set(True) + play_objects_copy = dict(play_objects) + play_objects.clear() + for queue_id in play_objects_copy: + try: + play_objects_copy[queue_id].stop() + except Exception: + pass + + +class SoundTagPlayer(SoundPlayerBase): + sound_data = None + big_endian_pcm = False + + @property + def pitch_ranges(self): + try: + return { + i: pr for i, pr in enumerate( + self.sound_data.pitch_ranges.STEPTREE + )} + except AttributeError: + return {} + @property + def permutations(self): + try: + return { + i: pr for i, pr in enumerate( + self.get_pitch_range().permutations.STEPTREE + )} + except AttributeError: + return {} + + def get_permutation(self, pr_index=None, perm_index=None): + if perm_index is None: + perm_index = self.permutation_index + + pr = self.get_pitch_range(pr_index) + perms = None if pr is None else pr.permutations.STEPTREE + return ( + perms[perm_index] + if pr and perm_index in range(len(perms)) + else None + ) + + def get_compression(self, wave_id=None): + block = getattr(self.get_permutation(), "compression", None) + comp = None if block is None else constants.halo_1_compressions[block.data] + return ( + comp if not comp in constants.PCM_FORMATS else + constants.COMPRESSION_PCM_16_BE if self.big_endian_pcm else + constants.COMPRESSION_PCM_16_LE + ) + + def get_sample_rate(self, wave_id=None): + block = getattr(self.sound_data, "sample_rate", None) + return None if block is None else constants.halo_1_sample_rates[block.data] + + def get_encoding(self, wave_id=None): + block = getattr(self.sound_data, "encoding", None) + return None if block is None else constants.halo_1_encodings[block.data] + + +class SoundSourcePlayer(SoundPlayerBase): + sound_bank = None + + @property + def pitch_ranges(self): + try: + prs = self.sound_bank.pitch_ranges + return {i: prs[name] for i, name in enumerate(sorted(prs))} + except AttributeError: + return {} + @property + def permutations(self): + try: + perms = self.get_pitch_range().permutations + return {i: perms[name] for i, name in enumerate(sorted(perms))} + except AttributeError: + return {} + + def get_permutation(self, pr_index=None, perm_index=None): + if perm_index is None: + perm_index = self.permutation_index + + perms = getattr(self.get_pitch_range(pr_index), "permutations", {}) + try: + perm_name = list(sorted(perms))[perm_index] + except IndexError: + perm_name = None + + return perms.get(perm_name) + + def get_compression(self, wave_id=None): + return getattr(self.get_permutation(), "compression", None) + + def get_sample_rate(self, wave_id=None): + return getattr(self.get_permutation(), "sample_rate", None) + + def get_encoding(self, wave_id=None): + return getattr(self.get_permutation(), "encoding", None) \ No newline at end of file diff --git a/reclaimer/sounds/sound_compilation.py b/reclaimer/sounds/sound_compilation.py index b6b5f0fb..8ee0be9f 100644 --- a/reclaimer/sounds/sound_compilation.py +++ b/reclaimer/sounds/sound_compilation.py @@ -10,7 +10,7 @@ import math import traceback -from reclaimer.sounds import util, constants +from reclaimer.sounds import audioop, constants, ogg, util __all__ = ("compile_sound", "compile_pitch_range",) @@ -37,7 +37,7 @@ def compile_pitch_range(pitch_range, blam_pitch_range, if channel_count != blam_channel_count: errors.append('Cannot add %s channel sounds to %s channel tag.' % - (blam_channel_count, sample_rate)) + (blam_channel_count, channel_count)) if blam_sound_perm.compression not in (constants.COMPRESSION_PCM_16_LE, constants.COMPRESSION_PCM_16_BE, @@ -57,8 +57,8 @@ def compile_pitch_range(pitch_range, blam_pitch_range, if errors: return errors - snd__perms = pitch_range.permutations.STEPTREE - snd__perm_names = set() + snd__perms = pitch_range.permutations.STEPTREE + snd__perm_names = set() # loop over the permutations blam_samples and string # them together into lists of permutation blocks for blam_perm_name in sorted(blam_pitch_range.permutations): @@ -73,22 +73,32 @@ def compile_pitch_range(pitch_range, blam_pitch_range, snd__perms.append() # create the new perm block snd__perm, _ = snd__perms.pop(-1) snd__perm.name = name - snd__perm.buffer_size = ( - channel_count * 2 * blam_samples.sample_count) snd__perm.samples.data = blam_samples.sample_data snd__perm.mouth_data.data = blam_samples.mouth_data + comp = blam_samples.compression - if blam_samples.compression == constants.COMPRESSION_XBOX_ADPCM: - snd__perm.compression.set_to("xbox_adpcm") - snd__perm.buffer_size = 0 # adpcm has this as 0 always - elif blam_samples.compression == constants.COMPRESSION_IMA_ADPCM: - snd__perm.compression.set_to("ima_adpcm") - snd__perm.buffer_size = 0 # adpcm has this as 0 always - elif blam_samples.compression == constants.COMPRESSION_OGG: - snd__perm.compression.set_to("ogg") - else: - snd__perm.compression.set_to("none") + if comp == constants.COMPRESSION_PCM_16_LE: + # need to byteswap to big-endian + snd__perm.samples.data = audioop.byteswap(snd__perm.samples.data, 2) + comp_name = ( + "xbox_adpcm" if comp == constants.COMPRESSION_XBOX_ADPCM else + "ima_adpcm" if comp == constants.COMPRESSION_IMA_ADPCM else + "ogg" if comp == constants.COMPRESSION_OGG else + "none" + ) + + # only ogg and 16bit pcm need the buffer size calculated + sample_count = ( + ogg.get_pcm_sample_count(blam_samples.sample_data) + if comp_name == "ogg" and sound_const.OGGVORBIS_AVAILABLE else + blam_samples.sample_count + if comp_name in ("ogg", "none") else + 0 + ) + + snd__perm.compression.set_to(comp_name) + snd__perm.buffer_size = channel_count * 2 * sample_count snd__perm_chain.append(snd__perm) errors.extend( @@ -104,7 +114,7 @@ def compile_pitch_range(pitch_range, blam_pitch_range, def compile_sound(snd__tag, blam_sound_bank, ignore_size_limits=False, update_mode=constants.SOUND_COMPILE_MODE_PRESERVE, - force_sample_rate=False): + force_sample_rate=False, sapien_pcm_hack=False): ''' Compiles the given blam_sound_bank into the given (Halo 1 snd!) snd__tag. ''' @@ -135,13 +145,14 @@ def compile_sound(snd__tag, blam_sound_bank, ignore_size_limits=False, tagdata.flags.fit_to_adpcm_blocksize = False tagdata.flags.split_long_sound_into_permutations = bool( blam_sound_bank.split_into_smaller_chunks) + tagdata.flags.fit_to_adpcm_blocksize = bool( + blam_sound_bank.adpcm_fit_to_blocksize) if blam_sound_bank.compression in (constants.COMPRESSION_PCM_16_LE, constants.COMPRESSION_PCM_16_BE): - # intentionally set this to something other than "none" + # set this to something other than "none" # as otherwise the sound won't play in sapien - tagdata.compression.set_to("ogg") - # tagdata.compression.set_to("none") + tagdata.compression.set_to("ogg" if sapien_pcm_hack else "none") elif blam_sound_bank.compression == constants.COMPRESSION_XBOX_ADPCM: tagdata.flags.fit_to_adpcm_blocksize = True tagdata.compression.set_to("xbox_adpcm") diff --git a/reclaimer/sounds/sound_decompilation.py b/reclaimer/sounds/sound_decompilation.py index 95784136..b3d4562d 100644 --- a/reclaimer/sounds/sound_decompilation.py +++ b/reclaimer/sounds/sound_decompilation.py @@ -17,7 +17,78 @@ from reclaimer.sounds.blam_sound_samples import BlamSoundSamples -__all__ = ("extract_h1_sounds", "extract_h2_sounds", ) +__all__ = ( + "extract_h1_sounds", "extract_h2_sounds", + "tag_perm_chain_to_blam_perm", "get_tag_perm_chain" + ) + + +def tag_perm_chain_to_blam_perm( + sound_perm_chain, sample_rate, encoding, pcm_is_big_endian=False + ): + compression = constants.COMPRESSION_PCM_16_LE + for perm in sound_perm_chain: + compression = constants.halo_1_compressions.get( + perm.compression.data, str(perm.compression.data) + ) + break + + if compression is None: + print("Unknown audio compression type: %s" % compression) + return + + blam_permutation = BlamSoundPermutation( + sample_rate=sample_rate, encoding=encoding, + compression=compression + ) + channels = constants.channel_counts.get(encoding, 1) + + for perm in sound_perm_chain: + sample_data = perm.samples.data + compression = constants.halo_1_compressions.get( + perm.compression.data, str(perm.compression.data) + ) + if compression == constants.COMPRESSION_OGG: + sample_count = perm.buffer_size // 2 + elif compression == constants.COMPRESSION_PCM_16_LE: + compression = (constants.COMPRESSION_PCM_16_BE + if pcm_is_big_endian else + constants.COMPRESSION_PCM_16_LE) + sample_count = len(sample_data) // 2 + elif compression == constants.COMPRESSION_XBOX_ADPCM: + sample_count = ( + (constants.XBOX_ADPCM_DECOMPRESSED_BLOCKSIZE // 2) * + (len(sample_data) // constants.XBOX_ADPCM_COMPRESSED_BLOCKSIZE)) + elif compression == constants.COMPRESSION_IMA_ADPCM: + sample_count = ( + (constants.IMA_ADPCM_DECOMPRESSED_BLOCKSIZE // 2) * + (len(sample_data) // constants.IMA_ADPCM_COMPRESSED_BLOCKSIZE)) + else: + print("Unknown audio compression type: %s" % compression) + continue + + sample_count = sample_count // channels + # NOTE: do NOT modify sample rate by natural pitch. + # it doesn't seem this is how it's used. try + # with warthog_7_engine to hear the issue + blam_permutation.processed_samples.append( + BlamSoundSamples( + sample_data, sample_count, compression, + sample_rate, encoding, perm.mouth_data.data) + ) + + return blam_permutation + + +def get_tag_perm_chain(permutations, perm_index): + seen, permlist = set(), [] + while perm_index not in seen and perm_index in range(len(permutations)): + seen.add(perm_index) + perm = permutations[perm_index] + permlist.append(perm) + perm_index = perm.next_permutation_index + + return permlist, seen def extract_h1_sounds(tagdata, tag_path, **kw): @@ -27,8 +98,6 @@ def extract_h1_sounds(tagdata, tag_path, **kw): pcm_is_big_endian = kw.get('byteswap_pcm_samples', False) tagpath_base = Path(kw['out_dir']).joinpath(Path(tag_path).with_suffix("")) - encoding = tagdata.encoding.data - channels = constants.channel_counts.get(encoding, 1) sample_rate = constants.halo_1_sample_rates.get( tagdata.sample_rate.data, 0) compression = constants.halo_1_compressions.get( @@ -38,9 +107,9 @@ def extract_h1_sounds(tagdata, tag_path, **kw): sound_bank = BlamSoundBank() sound_bank.split_into_smaller_chunks = tagdata.flags.split_long_sound_into_permutations sound_bank.split_to_adpcm_blocksize = tagdata.flags.fit_to_adpcm_blocksize - sound_bank.sample_rate = sample_rate - sound_bank.compression = compression - sound_bank.encoding = encoding + sound_bank.sample_rate = sample_rate + sound_bank.compression = compression + sound_bank.encoding = tagdata.encoding.data same_pr_names = {} for i, pr in enumerate(tagdata.pitch_ranges.STEPTREE): @@ -59,75 +128,33 @@ def extract_h1_sounds(tagdata, tag_path, **kw): same_names_permlists = {} unchecked_perms = set(range(len(pr.permutations.STEPTREE))) - perm_indices = list( - range(min(pr.actual_permutation_count, len(unchecked_perms)))) + perm_indices = list(range( + min(pr.actual_permutation_count, len(unchecked_perms)) + )) if not perm_indices: perm_indices = set(unchecked_perms) - playback_speed = 1.0 - if pr.natural_pitch > 0: - playback_speed = 1 / pr.natural_pitch - while perm_indices: # loop over all of the actual permutation indices and combine # the permutations they point to into a list with a shared name. # we do this so we can combine the permutations together into one. for j in perm_indices: - perm = pr.permutations.STEPTREE[j] - compression = perm.compression.enum_name - name = perm.name if perm.name else str(j) - permlist = same_names_permlists.get(name, []) - same_names_permlists[name] = permlist - - while j in unchecked_perms: - perm = pr.permutations.STEPTREE[j] - if compression != perm.compression.enum_name: - # cant combine when compression is different - break - - unchecked_perms.remove(j) - permlist.append(perm) - - j = perm.next_permutation_index + name = pr.permutations.STEPTREE[j].name.strip() or str(j) + + perm_list, seen = get_tag_perm_chain(pr.permutations.STEPTREE, j) + same_names_permlists.setdefault(name, []).extend(perm_list) + + unchecked_perms.difference_update(seen) + perm_indices = set(unchecked_perms) for name, permlist in same_names_permlists.items(): - blam_permutation = BlamSoundPermutation( - sample_rate=sample_rate, encoding=encoding) - sound_bank.pitch_ranges[pr_name].permutations[name] = blam_permutation - - for perm in permlist: - sample_data = perm.samples.data - if perm.compression.enum_name == "ogg": - # not actually a sample count. fix struct field name - compression = constants.COMPRESSION_OGG - sample_count = perm.buffer_size // 2 - elif perm.compression.enum_name == "none": - compression = (constants.COMPRESSION_PCM_16_BE - if pcm_is_big_endian else - constants.COMPRESSION_PCM_16_LE) - sample_count = len(sample_data) // 2 - elif perm.compression.enum_name == "xbox_adpcm": - compression = constants.COMPRESSION_XBOX_ADPCM - sample_count = ( - (constants.XBOX_ADPCM_DECOMPRESSED_BLOCKSIZE // 2) * - (len(sample_data) // constants.XBOX_ADPCM_COMPRESSED_BLOCKSIZE)) - elif perm.compression.enum_name == "ima_adpcm": - compression = constants.COMPRESSION_IMA_ADPCM - sample_count = ( - (constants.IMA_ADPCM_DECOMPRESSED_BLOCKSIZE // 2) * - (len(sample_data) // constants.IMA_ADPCM_COMPRESSED_BLOCKSIZE)) - else: - print("Unknown audio compression type:", perm.compression.data) - continue - - sample_count = sample_count // channels - blam_permutation.processed_samples.append( - BlamSoundSamples( - sample_data, sample_count, compression, - int(round(sample_rate * playback_speed)), - encoding, perm.mouth_data.data) - ) + blam_permutation = tag_perm_chain_to_blam_perm( + permlist, sample_rate, tagdata.encoding.data, + pcm_is_big_endian + ) + if blam_permutation: + sound_bank.pitch_ranges[pr_name].permutations[name] = blam_permutation if do_write_wav: sound_bank.export_to_directory( @@ -143,8 +170,6 @@ def get_sound_name(import_names, index): def extract_h2_sounds(tagdata, tag_path, **kw): - # TODO: Make this multiply the sample rate by the natural pitch - halo_map = kw.get('halo_map') if not halo_map: print("Cannot run this function on tags.") diff --git a/reclaimer/sounds/util.py b/reclaimer/sounds/util.py index 3b818c10..8b4f83ab 100644 --- a/reclaimer/sounds/util.py +++ b/reclaimer/sounds/util.py @@ -199,14 +199,15 @@ def generate_mouth_data(sample_data, compression, sample_rate, encoding): sample_width = constants.sample_widths[compression] channel_count = constants.channel_counts[encoding] - sample_typecode = constants.sample_typecodes[encoding] if compression == constants.COMPRESSION_PCM_8_UNSIGNED: sample_data = audioop.bias(sample_data, 1, 128) elif sample_width > 1 and compression not in constants.NATIVE_ENDIANNESS_FORMATS: sample_data = audioop.byteswap(sample_data, sample_width) if sample_width != 1: - sample_data = memoryview(sample_data).cast(sample_typecode) + sample_data = memoryview(sample_data).cast( + "h" if sample_width == 2 else "i" + ) # mouth data is sampled at 30Hz, so we divide the audio # sample_rate by that to determine how many samples we must @@ -257,4 +258,4 @@ def generate_mouth_data(sample_data, compression, sample_rate, encoding): else: mouth_data[i] = int(255 * mouth_sample) - return bytes(mouth_data) + return bytes(mouth_data) \ No newline at end of file From 3377ea0809067a27eebe56d97a07081d46874b2b Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sat, 9 Mar 2024 23:17:50 -0600 Subject: [PATCH 40/51] bump version --- reclaimer/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reclaimer/__init__.py b/reclaimer/__init__.py index 07d5b298..58c07daf 100644 --- a/reclaimer/__init__.py +++ b/reclaimer/__init__.py @@ -12,8 +12,8 @@ # ############## __author__ = "Sigmmma" # YYYY.MM.DD -__date__ = "2024.02.26" -__version__ = (2, 18, 0) +__date__ = "2024.03.09" +__version__ = (2, 19, 0) __website__ = "https://github.com/Sigmmma/reclaimer" __all__ = ( "animation", "bitmaps", "h2", "h3", "halo_script", "hek", "meta", "misc", From fbccd1a0419125287580abbfb34f79daf214b019 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sun, 10 Mar 2024 00:24:16 -0600 Subject: [PATCH 41/51] Delete profile_data.txt --- reclaimer/sounds/profile_data.txt | 51 ------------------------------- 1 file changed, 51 deletions(-) delete mode 100644 reclaimer/sounds/profile_data.txt diff --git a/reclaimer/sounds/profile_data.txt b/reclaimer/sounds/profile_data.txt deleted file mode 100644 index 5f7dabc2..00000000 --- a/reclaimer/sounds/profile_data.txt +++ /dev/null @@ -1,51 +0,0 @@ -ncalls: for the number of calls. -tottime: for the total time spent in the given function (and excluding time made in calls to sub-functions) -percall: is the quotient of tottime divided by ncalls -cumtime: is the cumulative time spent in this and all subfunctions (from invocation till exit). This figure is accurate even for recursive functions. -percall: is the quotient of cumtime divided by primitive calls - - -329195580 function calls in 105.508 seconds - - ncalls tottime percall cumtime percall filename:lineno(function) - - -PURE PYTHON audioop functions - ncalls tottime percall cumtime percall filename:lineno(function) - 12 2.833 0.236 2.847 0.237 audioop.py:49(bias) - 16 0.017 0.001 0.045 0.003 audioop.py:72(byteswap) - 12 7.460 0.622 7.466 0.622 audioop.py:103(lin2lin) - 12 5.873 0.489 5.880 0.490 audioop.py:146(tomono) - 12 17.638 1.470 17.666 1.472 audioop.py:181(tostereo) - -NATIVE audioop functions - ncalls tottime percall cumtime percall filename:lineno(function) - 12 0.026 0.002 0.026 0.002 {built-in method audioop.bias} - 16 0.057 0.004 0.057 0.004 {built-in method audioop.byteswap} - 12 0.022 0.002 0.022 0.002 {built-in method audioop.lin2lin} - 12 0.074 0.006 0.074 0.006 {built-in method audioop.tomono} - 12 0.192 0.016 0.192 0.016 {built-in method audioop.tostereo} - - -generators used in testing delta of pure-python vs native - ncalls tottime percall cumtime percall filename:lineno(function) - 128 0.000 0.000 0.000 0.000 audioop.py:246() - 67896328 6.276 0.000 6.276 0.000 audioop.py:256() - 67372044 5.998 0.000 5.998 0.000 audioop.py:273() - 33686028 3.076 0.000 3.076 0.000 audioop.py:285() -134744076 12.662 0.000 12.662 0.000 audioop.py:297() - 25496588 2.336 0.000 2.336 0.000 audioop.py:309() - - -MISC CALLS FROM ABOVE - ncalls tottime percall cumtime percall filename:lineno(function) - 1 35.529 35.529 105.490 105.490 audioop.py:220(_run_tests) - 1 0.000 0.000 105.508 105.508 {built-in method builtins.exec} - 12 0.000 0.000 0.000 0.000 {built-in method builtins.abs} - 60 0.000 0.000 0.000 0.000 {built-in method builtins.isinstance} - 28 0.000 0.000 0.000 0.000 {built-in method builtins.len} - 64 5.282 0.083 5.282 0.083 {built-in method builtins.sum} - 8 0.015 0.002 0.015 0.002 {method 'byteswap' of 'array.array' objects} - 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} - 16 0.000 0.000 0.000 0.000 {method 'join' of 'str' objects} - 68 0.124 0.002 0.124 0.002 {method 'tobytes' of 'array.array' objects} \ No newline at end of file From d2cabb5d62d38ea1af656fa573de56db1ceebfae Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sun, 10 Mar 2024 00:47:39 -0600 Subject: [PATCH 42/51] Include new imports --- requirements.txt | 2 ++ setup.py | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 00ba78f9..39ef0e4c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ supyr_struct binilla +simpleaudio +pyogg \ No newline at end of file diff --git a/setup.py b/setup.py index fcde4aec..4d77dfa6 100644 --- a/setup.py +++ b/setup.py @@ -39,9 +39,13 @@ 'reclaimer.h3.defs', 'reclaimer.h3.defs.objs', 'reclaimer.halo_script', + 'reclaimer.halo_script.defs', 'reclaimer.hek', 'reclaimer.hek.defs', 'reclaimer.hek.defs.objs', + 'reclaimer.mcc_hek', + 'reclaimer.mcc_hek.defs', + 'reclaimer.mcc_hek.defs.objs', 'reclaimer.meta', 'reclaimer.meta.gen3_resources', 'reclaimer.meta.objs', @@ -64,6 +68,7 @@ 'reclaimer.sounds.ext', 'reclaimer.shadowrun_prototype', 'reclaimer.shadowrun_prototype.defs', + 'reclaimer.shadowrun_prototype.defs.objs', 'reclaimer.strings', 'reclaimer.stubbs', 'reclaimer.stubbs.defs', @@ -89,8 +94,8 @@ }, platforms=["POSIX", "Windows"], keywords=["reclaimer", "halo"], - install_requires=['supyr_struct>=1.5.0', 'binilla', 'arbytmap'], - requires=['supyr_struct', 'binilla', 'arbytmap'], + install_requires=['supyr_struct>=1.5.0', 'binilla', 'arbytmap', 'simpleaudio', 'pyogg'], + requires=['supyr_struct', 'binilla', 'arbytmap', 'simpleaudio', 'pyogg'], provides=['reclaimer'], python_requires=">=3.5", classifiers=[ From f5ede5aa299d5369f71301746fde44ef8314e758 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sun, 10 Mar 2024 12:41:24 -0500 Subject: [PATCH 43/51] Fix script extraction and tag crc --- reclaimer/halo_script/hsc_decompilation.py | 30 ++++++++++++---------- reclaimer/util/__init__.py | 9 +++++-- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/reclaimer/halo_script/hsc_decompilation.py b/reclaimer/halo_script/hsc_decompilation.py index 820f54b3..cd06bd98 100644 --- a/reclaimer/halo_script/hsc_decompilation.py +++ b/reclaimer/halo_script/hsc_decompilation.py @@ -131,19 +131,23 @@ def extract_scripts( ) # concatenate script sources until they are too large to be compiled - sources.append(header) - for src in sorted_sources: - if not src: - continue - - # translate \n to \r\n since that's what haloscripts uses. - src = _lf_to_crlf(src + ("\n" if minify else "\n\n")) - - # if we're gonna pass our limit, append a new source file - if len(sources[-1]) + len(src) >= max_script_size: - sources.append(header) - - sources[-1] += src + concat_src = "" + for i, src in enumerate(sorted_sources): + if src: + # translate \n to \r\n since that's what haloscripts uses. + src = _lf_to_crlf(src + ("\n" if minify else "\n\n")) + + # we're gonna pass the limit on script size if we concatenate + # this script source, so we need to append it and start anew. + if len(concat_src) + len(src) >= max_script_size: + sources.append(header + concat_src) + concat_src = src + else: + concat_src += src + + # ensure the last script source is appended if it's not empty + if i+1 == len(sorted_sources): + sources.append(header + concat_src) # TEMPORARY CODE #from reclaimer.enums import TEST_PRINT_HSC_BUILT_IN_FUNCTIONS diff --git a/reclaimer/util/__init__.py b/reclaimer/util/__init__.py index 7257a8df..f7064a62 100644 --- a/reclaimer/util/__init__.py +++ b/reclaimer/util/__init__.py @@ -134,10 +134,15 @@ def is_valid_ascii_name_str(string): return True def calc_halo_crc32(buffer, offset=None, size=None, crc=0xFFffFFff): - if offset is not None: + offset = 0 if offset is None else offset + if isinstance(buffer, (bytes, bytearray)): + size = len(buffer) if size is None else size + tag_bytes = buffer[offset: offset + size] + else: buffer.seek(offset) + tag_bytes = buffer.read(size) - return zlib.crc32(buffer.read(size), crc ^ 0xFFffFFff) ^ 0xFFffFFff + return zlib.crc32(tag_bytes, crc ^ 0xFFffFFff) ^ 0xFFffFFff NEWLINE_MATCHER = re.compile(r'\r\n|\n\r|\r|\n') From 9be7ccceb57910e09d9b8935e78926a9413095a5 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sat, 16 Mar 2024 10:56:08 -0500 Subject: [PATCH 44/51] OGG support --- reclaimer/__init__.py | 4 +- reclaimer/hek/defs/objs/snd_.py | 19 +- reclaimer/meta/wrappers/halo1_rsrc_map.py | 18 +- reclaimer/sounds/audioop.py | 12 +- reclaimer/sounds/blam_sound_bank.py | 54 ++-- reclaimer/sounds/blam_sound_permutation.py | 299 +++++++++++---------- reclaimer/sounds/blam_sound_samples.py | 8 +- reclaimer/sounds/constants.py | 34 ++- reclaimer/sounds/ogg.py | 298 ++++++++++++++++++-- reclaimer/sounds/sound_compilation.py | 28 +- reclaimer/sounds/util.py | 68 ++++- 11 files changed, 602 insertions(+), 240 deletions(-) diff --git a/reclaimer/__init__.py b/reclaimer/__init__.py index 58c07daf..1c57fc77 100644 --- a/reclaimer/__init__.py +++ b/reclaimer/__init__.py @@ -12,8 +12,8 @@ # ############## __author__ = "Sigmmma" # YYYY.MM.DD -__date__ = "2024.03.09" -__version__ = (2, 19, 0) +__date__ = "2024.03.16" +__version__ = (2, 20, 0) __website__ = "https://github.com/Sigmmma/reclaimer" __all__ = ( "animation", "bitmaps", "h2", "h3", "halo_script", "hek", "meta", "misc", diff --git a/reclaimer/hek/defs/objs/snd_.py b/reclaimer/hek/defs/objs/snd_.py index 79352b87..f42b35d2 100644 --- a/reclaimer/hek/defs/objs/snd_.py +++ b/reclaimer/hek/defs/objs/snd_.py @@ -8,6 +8,7 @@ # from reclaimer.hek.defs.objs.tag import HekTag +from reclaimer.sounds import constants as sound_const, ogg class Snd_Tag(HekTag): @@ -15,11 +16,27 @@ def calc_internal_data(self): HekTag.calc_internal_data(self) tagdata = self.data.tagdata + bytes_per_sample = sound_const.channel_counts.get( + tagdata.encoding.data, 1 + ) * 2 for pitch_range in tagdata.pitch_ranges.STEPTREE: pitch_range.playback_rate = 1 if pitch_range.natural_pitch: pitch_range.playback_rate = 1 / pitch_range.natural_pitch + # ensure buffer sizes are correct + for perm in pitch_range.permutations.STEPTREE: + if perm.compression.enum_name == "none": + perm.buffer_size = len(perm.samples) + elif perm.compression.enum_name == "ogg": + # oggvorbis NEEDS this set for proper playback ingame + perm.buffer_size = ( + ogg.get_ogg_pcm_sample_count(perm.samples.data) + if sound_const.OGGVORBIS_AVAILABLE else + # oh well. default to whatever it's set to + (perm.buffer_size // bytes_per_sample) + ) * bytes_per_sample + # default sound min and max distance by class sound_class = tagdata.sound_class.enum_name distance_defaults = None @@ -78,4 +95,4 @@ def calc_internal_data(self): not tagdata.modifiers_when_scale_is_one.gain ): tagdata.modifiers_when_scale_is_zero.gain = zero_gain_modifier_default - tagdata.modifiers_when_scale_is_one.gain = 1.0 \ No newline at end of file + tagdata.modifiers_when_scale_is_one.gain = 1.0 diff --git a/reclaimer/meta/wrappers/halo1_rsrc_map.py b/reclaimer/meta/wrappers/halo1_rsrc_map.py index e875ac6a..0274dc5c 100644 --- a/reclaimer/meta/wrappers/halo1_rsrc_map.py +++ b/reclaimer/meta/wrappers/halo1_rsrc_map.py @@ -9,6 +9,7 @@ import math from collections import namedtuple +from copy import deepcopy from struct import unpack from traceback import format_exc @@ -244,10 +245,16 @@ def get_dependencies(self, meta, tag_id, tag_cls): return () tag_id = meta.promotion_sound.id & 0xFFff - if tag_id not in range(len(self.tag_index.tag_index)): + tag_index_array = self.tag_index.tag_index + if tag_id not in range(len(tag_index_array)): return () - return [self.tag_index.tag_index[tag_id]] + ref = deepcopy(meta.promotion_sound) + tag_index_ref = tag_index_array[tag_id] + ref.tag_class.data = tag_index_ref.class_1.data + ref.id = tag_index_ref.id + ref.filepath = tag_index_ref.path + return [ref] def is_indexed(self, tag_id): return True @@ -365,10 +372,9 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): meta.maximum_bend_per_second = meta.maximum_bend_per_second ** 30 meta.unknown1 = 0xFFFFFFFF meta.unknown2 = 0xFFFFFFFF - channels = sound_const.channel_counts.get( + bytes_per_sample = sound_const.channel_counts.get( meta.encoding.data, 1 - ) - bytes_per_sample = 2 * channels + ) * 2 for pitch_range in meta.pitch_ranges.STEPTREE: # null some meta-only fields pitch_range.playback_rate = 0.0 @@ -384,7 +390,7 @@ def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): elif perm.compression.enum_name == "ogg": # oggvorbis NEEDS this set for proper playback ingame buffer_size = ( - sounds_ogg.get_pcm_sample_count(perm.samples.data) + sounds_ogg.get_ogg_pcm_sample_count(perm.samples.data) if sound_const.OGGVORBIS_AVAILABLE else # oh well. default to whatever it's set to (perm.buffer_size // bytes_per_sample) diff --git a/reclaimer/sounds/audioop.py b/reclaimer/sounds/audioop.py index 060a94ca..9fbb2798 100644 --- a/reclaimer/sounds/audioop.py +++ b/reclaimer/sounds/audioop.py @@ -46,7 +46,7 @@ def adpcm2lin(fragment, width, state): def bias(fragment, width, bias): if width not in (1, 2, 4): - raise NotImplementedError("Cannot bias sample data of width %s." % width) + raise NotImplementedError("Cannot bias %s-byte width samples." % width) # ensure fragment is the correct data type if width > 1 and not(isinstance(fragment, array.array) and @@ -70,7 +70,7 @@ def bias(fragment, width, bias): def byteswap(fragment, width): if width not in (1, 2, 3, 4): raise NotImplementedError( - "Cannot byteswap sample data of width %s." % width + "Cannot byteswap %s-byte width samples." % width ) orig_fragment = ( @@ -100,9 +100,9 @@ def byteswap(fragment, width): def lin2lin(fragment, width, newwidth): if width not in (1, 2, 4): - raise NotImplementedError("Cannot convert from sample width %s." % width) + raise NotImplementedError("Cannot convert from %s-byte width samples." % width) elif newwidth not in (1, 2, 4): - raise NotImplementedError("Cannot convert to sample width %s." % newwidth) + raise NotImplementedError("Cannot convert to %s-byte width samples." % newwidth) typecode = SAMPLE_TYPECODES[width-1] new_typecode = SAMPLE_TYPECODES[newwidth-1] @@ -144,7 +144,7 @@ def ratecv(fragment, width, nchannels, inrate, outrate, state, weightA=1, weight def tomono(fragment, width, lfactor, rfactor): if width not in (1, 2, 4): raise NotImplementedError( - "Cannot convert %s width samples to mono." % width + "Cannot convert %s-byte width samples to mono." % width ) typecode = SAMPLE_TYPECODES[width-1] @@ -179,7 +179,7 @@ def tomono(fragment, width, lfactor, rfactor): def tostereo(fragment, width, lfactor, rfactor): if width not in (1, 2, 4): raise NotImplementedError( - "Cannot convert %s width samples to stereo." % width + "Cannot convert %s-byte width samples to stereo." % width ) typecode = SAMPLE_TYPECODES[width-1] diff --git a/reclaimer/sounds/blam_sound_bank.py b/reclaimer/sounds/blam_sound_bank.py index 475943ad..9f67b7e2 100644 --- a/reclaimer/sounds/blam_sound_bank.py +++ b/reclaimer/sounds/blam_sound_bank.py @@ -56,11 +56,11 @@ def create_from_directory(directory): def export_to_directory(self, directory, overwrite=False, export_source=True, decompress=True, - format=constants.CONTAINER_EXT_WAV): + ext=constants.CONTAINER_EXT_WAV, **kwargs): for name, perm in self.permutations.items(): perm.export_to_file( Path(directory, name), overwrite, - export_source, decompress, format + export_source, decompress, ext, **kwargs ) def import_from_directory(self, directory, clear_existing=True, @@ -72,12 +72,15 @@ def import_from_directory(self, directory, clear_existing=True, # import each sound file to a new sound permutation for filename in files: filepath = Path(root, filename) - if filepath.suffix.lower() not in constants.SUPPORTED_CONTAINER_EXTS: + if filepath.suffix.lower() not in constants.SUPPORTED_IMPORT_EXTS: continue - name_key = filepath.stem.lower().strip() perm = BlamSoundPermutation.create_from_file(filepath) + if perm is None: + continue + perm.name = filepath.stem.strip() + name_key = perm.name.lower() if (perm and perm.source_sample_data) and ( replace_existing or name_key not in self.permutations): @@ -116,9 +119,10 @@ class BlamSoundBank: # none set: # the coder does not care to speculate. ogg_bitrate_lower = -1 - ogg_bitrate_upper = -1 ogg_bitrate_nominal = -1 - ogg_bitrate_window = -1 + ogg_bitrate_upper = -1 + ogg_quality_setting = 0 + ogg_use_quality_value = False _pitch_ranges = () @@ -129,6 +133,22 @@ def __init__(self): def pitch_ranges(self): return self._pitch_ranges + @property + def adpcm_kwargs(self): return dict( + noise_shaping = self.adpcm_noise_shaping, + lookahead = self.adpcm_lookahead, + fit_to_blocksize = self.adpcm_fit_to_blocksize, + ) + + @property + def ogg_kwargs(self): return dict( + bitrate_lower = self.ogg_bitrate_lower, + bitrate_upper = self.ogg_bitrate_upper, + bitrate_nominal = self.ogg_bitrate_nominal, + quality_setting = self.ogg_quality_setting, + use_quality_value = self.ogg_use_quality_value, + ) + def generate_mouth_data(self): for pitch_range in self.pitch_ranges.values(): pitch_range.generate_mouth_data() @@ -138,22 +158,10 @@ def compress_samples(self): if self.split_into_smaller_chunks: chunk_size = self.chunk_size - adpcm_kwargs = dict( - noise_shaping = self.adpcm_noise_shaping, - lookahead = self.adpcm_lookahead, - fit_to_blocksize = self.adpcm_fit_to_blocksize, - ) - ogg_kwargs = dict( - ogg_bitrate_lower = self.ogg_bitrate_lower, - ogg_bitrate_upper = self.ogg_bitrate_upper, - ogg_bitrate_nominal = self.ogg_bitrate_nominal, - ogg_bitrate_window = self.ogg_bitrate_window, - ) - for pitch_range in self.pitch_ranges.values(): pitch_range.compress_samples( - self.compression, self.sample_rate, self.encoding, - chunk_size, adpcm_kwargs=adpcm_kwargs, ogg_kwargs=ogg_kwargs, + self.compression, self.sample_rate, self.encoding, chunk_size, + adpcm_kwargs=self.adpcm_kwargs, ogg_kwargs=self.ogg_kwargs ) def regenerate_source(self): @@ -173,7 +181,7 @@ def create_from_directory(directory): def export_to_directory(self, directory, overwrite=False, export_source=True, decompress=True, - format=constants.CONTAINER_EXT_WAV): + ext=constants.CONTAINER_EXT_WAV): for name, pitch_range in self.pitch_ranges.items(): if len(self.pitch_ranges) > 1: pitch_directory = Path(directory, name) @@ -181,8 +189,8 @@ def export_to_directory(self, directory, overwrite=False, pitch_directory = Path(directory) pitch_range.export_to_directory( - pitch_directory, overwrite, export_source, - decompress, format + pitch_directory, overwrite, export_source, decompress, ext, + adpcm_kwargs=self.adpcm_kwargs, ogg_kwargs=self.ogg_kwargs ) def import_from_directory(self, directory, clear_existing=True, diff --git a/reclaimer/sounds/blam_sound_permutation.py b/reclaimer/sounds/blam_sound_permutation.py index ff4750ea..5186a937 100644 --- a/reclaimer/sounds/blam_sound_permutation.py +++ b/reclaimer/sounds/blam_sound_permutation.py @@ -10,7 +10,7 @@ from pathlib import Path from traceback import format_exc -from reclaimer.sounds import constants, util, blam_sound_samples +from reclaimer.sounds import blam_sound_samples, constants as const, ogg, util from supyr_struct.defs.audio.wav import wav_def from supyr_struct.util import is_path_empty @@ -20,17 +20,17 @@ class BlamSoundPermutation: # permutation properties _source_sample_data = b'' - _source_compression = constants.COMPRESSION_PCM_16_LE - _source_sample_rate = constants.SAMPLE_RATE_22K - _source_encoding = constants.ENCODING_MONO + _source_compression = const.COMPRESSION_PCM_16_LE + _source_sample_rate = const.SAMPLE_RATE_22K + _source_encoding = const.ENCODING_MONO # processed properties _processed_samples = () def __init__(self, sample_data=b'', - compression=constants.COMPRESSION_PCM_16_LE, - sample_rate=constants.SAMPLE_RATE_22K, - encoding=constants.ENCODING_MONO, **kwargs): + compression=const.COMPRESSION_PCM_16_LE, + sample_rate=const.SAMPLE_RATE_22K, + encoding=const.ENCODING_MONO, **kwargs): self.load_source_samples( sample_data, compression, sample_rate, encoding) @@ -89,13 +89,13 @@ def partition_samples(self, target_compression=None, if target_encoding is None: target_encoding = self.source_encoding - if (target_compression not in constants.PCM_FORMATS and - target_compression != constants.COMPRESSION_IMA_ADPCM and - target_compression != constants.COMPRESSION_XBOX_ADPCM and - target_compression != constants.COMPRESSION_OGG): + if (target_compression not in const.PCM_FORMATS and + target_compression != const.COMPRESSION_IMA_ADPCM and + target_compression != const.COMPRESSION_XBOX_ADPCM and + target_compression != const.COMPRESSION_OGG): raise ValueError('Unknown compression type "%s"' % target_compression) - elif target_encoding not in (constants.ENCODING_MONO, - constants.ENCODING_STEREO): + elif target_encoding not in (const.ENCODING_MONO, + const.ENCODING_STEREO): raise ValueError("Compression encoding must be mono or stereo.") elif target_sample_rate <= 0: raise ValueError("Sample rate must be greater than zero.") @@ -110,9 +110,9 @@ def partition_samples(self, target_compression=None, if (source_compression == target_compression and source_sample_rate == target_sample_rate and source_encoding == target_encoding and - (source_compression in constants.PCM_FORMATS or - source_compression == constants.COMPRESSION_IMA_ADPCM or - source_compression == constants.COMPRESSION_XBOX_ADPCM)): + (source_compression in const.PCM_FORMATS or + source_compression == const.COMPRESSION_IMA_ADPCM or + source_compression == const.COMPRESSION_XBOX_ADPCM)): # compressing to same settings and can split at target_chunk_size # because format has fixed compression ratio. recompression not # necessary. Just split source into pieces at target_chunk_size. @@ -125,7 +125,7 @@ def partition_samples(self, target_compression=None, source_sample_data, 0, source_compression, source_sample_rate, source_encoding ) - source_compression = constants.DEFAULT_UNCOMPRESSED_FORMAT + source_compression = const.DEFAULT_UNCOMPRESSED_FORMAT source_sample_rate = target_sample_rate source_encoding = target_encoding source_sample_data = decompressor.get_decompressed( @@ -182,11 +182,11 @@ def get_concatenated_sample_data(self, target_compression=None, if target_encoding is None: target_encoding = self.source_encoding - assert target_encoding in constants.channel_counts + assert target_encoding in const.channel_counts if (target_compression != self.compression or target_sample_rate != self.sample_rate or - target_encoding != self.encoding): + target_encoding != self.encoding): # decompress processed samples to the target compression sample_data = b''.join( p.get_decompressed( @@ -200,7 +200,7 @@ def get_concatenated_sample_data(self, target_compression=None, if piece.compression != compression: raise ValueError( "Cannot combine differently compressed samples without decompressing.") - elif piece.compression == constants.COMPRESSION_OGG: + elif piece.compression == const.COMPRESSION_OGG: raise ValueError( "Cannot combine ogg samples without decompressing.") @@ -219,10 +219,10 @@ def regenerate_source( from the compressed samples. Use when loading a sound tag for re-compression, re-sampling, or re-encoding. ''' - # default to regenerating to constants.DEFAULT_UNCOMPRESSED_FORMAT + # default to regenerating to const.DEFAULT_UNCOMPRESSED_FORMAT # because, technically speaking, that is highest sample depth # we can ever possibly see in Halo CE. - if compression is None: compression = constants.DEFAULT_UNCOMPRESSED_FORMAT + if compression is None: compression = const.DEFAULT_UNCOMPRESSED_FORMAT if sample_rate is None: sample_rate = self.sample_rate if encoding is None: encoding = self.encoding @@ -246,26 +246,25 @@ def create_from_file(filepath): def export_to_file(self, filepath_base, overwrite=False, export_source=True, decompress=True, - format=constants.CONTAINER_EXT_WAV): + ext=const.CONTAINER_EXT_WAV, **kwargs): perm_chunks = [] filepath = Path(util.BAD_PATH_CHAR_REMOVAL.sub("_", str(filepath_base))) filepath = Path("unnamed" if is_path_empty(filepath) else filepath) - filepath = filepath.with_suffix(format) + filepath = filepath.with_suffix(ext) if export_source and self.source_sample_data: # export the source data - perm_chunks.append( - (self.compression, self.source_sample_rate, - self.source_encoding, self.source_sample_data, - filepath) - ) + perm_chunks.append(( + filepath, self.compression, self.source_sample_rate, + self.source_encoding, self.source_sample_data, + )) elif self.processed_samples: # concatenate processed samples if source samples don't exist. - # also, if compression is ogg, we have to decompress + # if compression isn't some form of PCM, need to decompress it compression = self.compression - if decompress or compression in constants.PYOGG_CONTAINER_FORMATS: - compression = constants.COMPRESSION_PCM_16_LE + if decompress or compression not in const.PCM_FORMATS: + compression = const.COMPRESSION_PCM_16_LE try: sample_data = self.get_concatenated_sample_data( @@ -273,131 +272,145 @@ def export_to_file(self, filepath_base, overwrite=False, ) if sample_data: perm_chunks.append(( - compression, self.sample_rate, self.encoding, - sample_data, filepath + filepath, compression, self.sample_rate, + self.encoding, sample_data, )) except Exception: print("Could not decompress permutation pieces. Exporting in pieces.") # gotta switch format to the container it's compressed as for i, piece in enumerate(self.processed_samples): - format = { - constants.COMPRESSION_OGG: constants.CONTAINER_EXT_OGG, - constants.COMPRESSION_OPUS: constants.CONTAINER_EXT_OPUS, - constants.COMPRESSION_FLAC: constants.CONTAINER_EXT_FLAC, - }.get(piece.compression, format) - - name_base = "%s__%%s%s" % (filepath.stem, format) - perm_chunks.append( - (piece.compression, piece.sample_rate, piece.encoding, - piece.sample_data, filepath.with_name(name_base % i)) - ) - - if format == constants.CONTAINER_EXT_WAV: - self._export_to_wav(perm_chunks, overwrite) - elif format in constants.SUPPORTED_CONTAINER_EXTS: - self._export_to_container(perm_chunks, overwrite) - else: - raise ValueError("Unknown compression method.") - - def _export_to_container(self, perm_chunks, overwrite): - for perm_chunk in perm_chunks: - compression, sample_rate, encoding, sample_data, filepath = perm_chunk + ext = { + const.COMPRESSION_OGG: const.CONTAINER_EXT_OGG, + const.COMPRESSION_OPUS: const.CONTAINER_EXT_OPUS, + const.COMPRESSION_FLAC: const.CONTAINER_EXT_FLAC, + }.get(piece.compression, ext) - format = filepath.suffix.lower() - if format == constants.CONTAINER_EXT_WAV: - # if somehow a wav got passed in, pass off to that method - self._export_to_wav([perm_chunk], overwrite) - continue - elif not sample_data or (not overwrite and filepath.is_file()): - continue + name_base = "%s__%%s%s" % (filepath.stem, ext) + perm_chunks.append(( + filepath.with_name(name_base % i), piece.compression, + piece.sample_rate, piece.encoding, piece.sample_data + )) - # TODO: write this - # make sure to handle samples already being in ogg and not - # needing to be decompressed and reencoded if not chunked. + for perm_info in perm_chunks: + filepath, comp, rate, enc, data = perm_info + ext = filepath.suffix.lower() + if not data or (not overwrite and filepath.is_file()): + return + elif ext not in const.SUPPORTED_EXPORT_EXTS: + raise ValueError("Unsupported audio extension '%s'." % ext) + elif ext in const.PYOGG_CONTAINER_EXTS: + exporter = BlamSoundPermutation._export_to_pyogg_file + else: + exporter = BlamSoundPermutation._export_to_wav try: - filepath.parent.mkdir(exist_ok=True, parents=True) - with filepath.open("wb") as f: - f.write(sample_data) + exporter(*perm_info, **kwargs) except Exception: print(format_exc()) - def _export_to_wav(self, perm_chunks, overwrite): - wav_file = wav_def.build() + @staticmethod + def _export_to_pyogg_file( + filepath, compression, sample_rate, + encoding, sample_data, **kwargs + ): + ext = filepath.suffix.lower() + if ext != const.CONTAINER_EXT_OGG: + raise ValueError("Exporting '%s' is currently unsupported" % ext) + elif compression != const.COMPRESSION_OGG: + # need to encode data to oggvorbis first + sample_data = ogg.encode_oggvorbis( + sample_data, sample_rate, + constants.sample_widths[compression], + constants.channel_counts[encoding], + util.is_big_endian_pcm(compression), + **kwargs.get("ogg_kwargs", {}) + ) - for perm_chunk in perm_chunks: - compression, sample_rate, encoding, sample_data, filepath = perm_chunk - if not sample_data or (not overwrite and filepath.is_file()): - continue - - wav_file.filepath = filepath - - wav_fmt = wav_file.data.wav_format - wav_chunks = wav_file.data.wav_chunks - wav_chunks.append(case="data") - data_chunk = wav_chunks[-1] - - wav_fmt.fmt.data = constants.WAV_FORMAT_PCM - wav_fmt.channels = constants.channel_counts.get(encoding, 1) - wav_fmt.sample_rate = sample_rate - - samples_len = len(sample_data) - if compression in constants.PCM_FORMATS: - # one of the uncompressed pcm formats - if util.is_big_endian_pcm(compression): - sample_data = util.convert_pcm_to_pcm( - sample_data, compression, - util.change_pcm_endianness(compression)) - - sample_width = constants.sample_widths[compression] - wav_fmt.bits_per_sample = sample_width * 8 - wav_fmt.block_align = sample_width * wav_fmt.channels - wav_fmt.byte_rate = wav_fmt.sample_rate * wav_fmt.block_align - elif compression == constants.COMPRESSION_IMA_ADPCM: - # 16bit adpcm - wav_fmt.fmt.data = constants.WAV_FORMAT_IMA_ADPCM - wav_fmt.bits_per_sample = 16 - wav_fmt.block_align = constants.IMA_ADPCM_COMPRESSED_BLOCKSIZE * wav_fmt.channels - wav_fmt.byte_rate = int( - (wav_fmt.sample_rate * wav_fmt.block_align / - (constants.IMA_ADPCM_DECOMPRESSED_BLOCKSIZE // 2)) - ) - elif compression == constants.COMPRESSION_XBOX_ADPCM: - # 16bit adpcm - wav_fmt.fmt.data = constants.WAV_FORMAT_XBOX_ADPCM - wav_fmt.bits_per_sample = 16 - wav_fmt.block_align = constants.XBOX_ADPCM_COMPRESSED_BLOCKSIZE * wav_fmt.channels - wav_fmt.byte_rate = int( - (wav_fmt.sample_rate * wav_fmt.block_align / - (constants.XBOX_ADPCM_DECOMPRESSED_BLOCKSIZE // 2)) - ) - else: - print("Unknown compression method:", compression) - continue + filepath.parent.mkdir(exist_ok=True, parents=True) + with filepath.open("wb") as f: + f.write(sample_data) - data_chunk.data = sample_data - wav_file.data.wav_header.filesize = 36 + samples_len + @staticmethod + def _export_to_wav( + filepath, compression, sample_rate, encoding, + sample_data, **kwargs + ): + if not (compression in const.PCM_FORMATS or + compression == const.COMPRESSION_IMA_ADPCM or + compression == const.COMPRESSION_XBOX_ADPCM + ): + print("Unknown compression method:", compression) + return + + block_ratio = 1 + block_size = const.sample_widths[compression] + if compression == const.COMPRESSION_IMA_ADPCM: + # 16bit imaadpcm + block_ratio /= const.IMA_ADPCM_DECOMPRESSED_BLOCKSIZE * 2 + block_size = const.IMA_ADPCM_COMPRESSED_BLOCKSIZE + elif compression == const.COMPRESSION_XBOX_ADPCM: + # 16bit xbox adpcm + block_ratio /= const.XBOX_ADPCM_DECOMPRESSED_BLOCKSIZE * 2 + block_size = const.XBOX_ADPCM_COMPRESSED_BLOCKSIZE + elif util.is_big_endian_pcm(compression): + # one of the big-endian uncompressed pcm formats + sample_data = util.convert_pcm_to_pcm( + sample_data, compression, + util.change_pcm_endianness(compression) + ) - wav_file.serialize(temp=False, backup=False) + wav_file = wav_def.build() + wav_file.filepath = filepath + + wav_fmt = wav_file.data.wav_format + wav_chunks = wav_file.data.wav_chunks + wav_chunks.append(case="data") + data_chunk = wav_chunks[-1] + + wav_fmt.channels = const.channel_counts.get(encoding, 1) + wav_fmt.sample_rate = sample_rate + wav_fmt.bits_per_sample = block_size * 8 + wav_fmt.block_align = block_size * wav_fmt.channels + wav_fmt.byte_rate = int( + sample_rate * wav_fmt.block_align * block_ratio + ) + wav_fmt.fmt.data = { + const.COMPRESSION_IMA_ADPCM: const.WAV_FORMAT_IMA_ADPCM, + const.COMPRESSION_XBOX_ADPCM: const.WAV_FORMAT_XBOX_ADPCM, + }.get(compression, const.WAV_FORMAT_PCM) + + data_chunk.data = sample_data + wav_file.data.wav_header.filesize = 36 + len(sample_data) + + wav_file.serialize(temp=False, backup=False) def import_from_file(self, filepath): filepath = Path(filepath) - format = filepath.suffix.lower() - if not filepath.is_file(): raise OSError('File "%s" does not exist. Cannot import.' % filepath) - if format == constants.CONTAINER_EXT_WAV: + ext = filepath.suffix.lower() + if ext == const.CONTAINER_EXT_WAV: self._import_from_wav(filepath) - elif (format in constants.SUPPORTED_CONTAINER_EXTS and - format in constants.PYOGG_CONTAINER_EXTS): - self._import_from_container(filepath) + elif (ext in const.PYOGG_CONTAINER_EXTS and + ext in const.SUPPORTED_IMPORT_EXTS): + self._import_from_pyogg_file(filepath, ext) else: - raise ValueError("Cannot import this type of file.") + raise ValueError("Unsupported audio extension '%s'." % ext) - def _import_from_container(self, filepath): - # TODO: update this to handle ogg vorbis, flac, and opus - raise NotImplementedError() + def _import_from_pyogg_file(self, filepath, ext): + pyogg_audio_file = ogg.pyogg_audiofile_from_filepath( + filepath, ext, streaming=False + ) + sample_data = pyogg_audio_file.buffer + sample_rate = pyogg_audio_file.frequency + compression = const.OGG_DECOMPRESSED_FORMAT + encoding = ( + const.ENCODING_STEREO if pyogg_audio_file.channels == 2 else + const.ENCODING_MONO if pyogg_audio_file.channels == 1 else + const.ENCODING_UNKNOWN + ) + self.load_source_samples(sample_data, compression, sample_rate, encoding) def _import_from_wav(self, filepath): wav_file = wav_def.build(filepath=filepath) @@ -421,7 +434,7 @@ def _import_from_wav(self, filepath): "Format signature is invalid. Not a valid wav file.") elif data_chunk is None: raise ValueError("Data chunk not present. Not a valid wav file.") - elif wav_format.fmt.data not in constants.ALLOWED_WAV_FORMATS: + elif wav_format.fmt.data not in const.ALLOWED_WAV_FORMATS: raise ValueError( 'Invalid compression format "%s".' % wav_format.fmt.data) elif wav_format.channels not in (1, 2): @@ -431,12 +444,12 @@ def _import_from_wav(self, filepath): elif wav_format.sample_rate == 0: raise ValueError( "Sample rate cannot be zero. Not a valid wav file") - elif (wav_format.fmt.data == constants.WAV_FORMAT_PCM_FLOAT and + elif (wav_format.fmt.data == const.WAV_FORMAT_PCM_FLOAT and wav_format.bits_per_sample != 32): raise ValueError( "Pcm float sample width must be 32, not %s." % wav_format.bits_per_sample) - elif (wav_format.fmt.data == constants.WAV_FORMAT_PCM and + elif (wav_format.fmt.data == const.WAV_FORMAT_PCM and wav_format.bits_per_sample not in (8, 16, 24, 32)): raise ValueError( "Pcm sample width must be 8, 16, 24, or 32, not %s." % @@ -452,20 +465,20 @@ def _import_from_wav(self, filepath): "Sample data may be truncated.") if wav_format.channels == 2: - encoding = constants.ENCODING_STEREO + encoding = const.ENCODING_STEREO else: - encoding = constants.ENCODING_MONO + encoding = const.ENCODING_MONO sample_data = data_chunk.data - if wav_format.fmt.data == constants.WAV_FORMAT_PCM_FLOAT: - sample_data = util.convert_pcm_float32_to_pcm_32(sample_data) - compression = constants.COMPRESSION_PCM_32_LE + if wav_format.fmt.data == const.WAV_FORMAT_PCM_FLOAT: + sample_data = util.convert_pcm_float32_to_pcm_int(sample_data, 4) + compression = const.COMPRESSION_PCM_32_LE else: sample_width = None - if wav_format.fmt.data == constants.WAV_FORMAT_PCM: + if wav_format.fmt.data == const.WAV_FORMAT_PCM: sample_width = wav_format.bits_per_sample // 8 - compression = constants.wav_format_mapping.get( + compression = const.wav_format_mapping.get( (wav_format.fmt.data, sample_width)) self.load_source_samples( diff --git a/reclaimer/sounds/blam_sound_samples.py b/reclaimer/sounds/blam_sound_samples.py index 8b3bee22..1ab1b888 100644 --- a/reclaimer/sounds/blam_sound_samples.py +++ b/reclaimer/sounds/blam_sound_samples.py @@ -120,9 +120,11 @@ def compress(self, target_compression, target_sample_rate=None, ) elif target_compression == constants.COMPRESSION_OGG: # compress to ogg vorbis - sample_data = encode_oggvorbis( - sample_data, constants.channel_counts[target_encoding], - target_sample_rate, util.is_big_endian_pcm(compression), + sample_data = ogg.encode_oggvorbis( + sample_data, target_sample_rate, + constants.sample_widths[compression], + constants.channel_counts[target_encoding], + util.is_big_endian_pcm(compression), **compressor_kwargs.get("ogg_kwargs", {}) ) elif target_compression != self.compression: diff --git a/reclaimer/sounds/constants.py b/reclaimer/sounds/constants.py index fe7443a5..c61766f4 100644 --- a/reclaimer/sounds/constants.py +++ b/reclaimer/sounds/constants.py @@ -7,7 +7,9 @@ # See LICENSE for more information. # +import pathlib import sys +import tempfile PLAYBACK_AVAILABLE = False OGGVORBIS_AVAILABLE = False @@ -17,6 +19,9 @@ OGGVORBIS_ENCODING_AVAILABLE = False +TEMP_ROOT = pathlib.Path(tempfile.gettempdir(), "reclaimer_tmp") +OGGVORBIS_TMPNAME_FORMAT = "pyogg_tmpfile_%s%s" + try: import pyogg OGGVORBIS_AVAILABLE = ( @@ -34,10 +39,9 @@ pyogg, "PYOGG_VORBIS_ENC_AVAIL", False ) - # NOTE: for right now these won't be available. - # still need to implement them. - OGGVORBIS_ENCODING_AVAILABLE = False - OPUS_AVAILABLE = FLAC_AVAILABLE = False + # NOTE: for right now this won't be available. + # still need to implement it. + #OGGVORBIS_ENCODING_AVAILABLE = False except ImportError: pass @@ -77,12 +81,12 @@ # these encoding constants mirror halo 1/2 enum values. ENCODING_UNKNOWN = -1 -ENCODING_MONO = 0 -ENCODING_STEREO = 1 -ENCODING_CODEC = 2 +ENCODING_MONO = 0 +ENCODING_STEREO = 1 +ENCODING_CODEC = 2 SAMPLE_RATE_22K = 22050 -SAMPLE_RATE_32K = 32000 +SAMPLE_RATE_32K = 32000 # halo 2 SAMPLE_RATE_44K = 44100 # Halo constants @@ -100,8 +104,8 @@ XBOX_ADPCM_COMPRESSED_BLOCKSIZE = 36 XBOX_ADPCM_DECOMPRESSED_BLOCKSIZE = 128 -IMA_ADPCM_COMPRESSED_BLOCKSIZE = 36 # not correct -IMA_ADPCM_DECOMPRESSED_BLOCKSIZE = 128 # not correct +IMA_ADPCM_COMPRESSED_BLOCKSIZE = 36 # not correct +IMA_ADPCM_DECOMPRESSED_BLOCKSIZE = 128 # not correct # Wave file format constants WAV_FORMAT_PCM = 0x0001 @@ -130,12 +134,15 @@ CONTAINER_EXT_OPUS, CONTAINER_EXT_FLAC )) -SUPPORTED_CONTAINER_EXTS = frozenset(( +SUPPORTED_IMPORT_EXTS = frozenset(( CONTAINER_EXT_WAV, *([CONTAINER_EXT_OGG] if OGGVORBIS_AVAILABLE else []), *([CONTAINER_EXT_OPUS] if OPUS_AVAILABLE else []), *([CONTAINER_EXT_FLAC] if FLAC_AVAILABLE else []), )) +SUPPORTED_EXPORT_EXTS = frozenset(( + CONTAINER_EXT_WAV, CONTAINER_EXT_OGG + )) # for all our purposes we only care about # decoding ogg to little-endian 16bit pcm @@ -184,6 +191,9 @@ COMPRESSION_PCM_24_BE: 3, COMPRESSION_PCM_32_LE: 4, COMPRESSION_PCM_32_BE: 4, + # this is what width they get decompressed to + COMPRESSION_XBOX_ADPCM: 2, + COMPRESSION_IMA_ADPCM: 2, } # maps wave format enum options and sample widths to our compression constants @@ -234,4 +244,6 @@ } # unneeded for export +del pathlib del sys +del tempfile \ No newline at end of file diff --git a/reclaimer/sounds/ogg.py b/reclaimer/sounds/ogg.py index 424002ce..ca2be66f 100644 --- a/reclaimer/sounds/ogg.py +++ b/reclaimer/sounds/ogg.py @@ -1,43 +1,69 @@ import ctypes +import io +import itertools import pathlib -import tempfile +import random import threading -import uuid +import reclaimer from reclaimer.sounds import constants, util try: import pyogg - from pyogg import vorbis as pyogg_vorbis + from pyogg import vorbis as pyogg_vorbis, ogg as pyogg_ogg except ImportError: - pyogg = pyogg_vorbis = None + pyogg = pyogg_vorbis = pyogg_ogg = None -TEMP_ROOT = pathlib.Path(tempfile.gettempdir(), "reclaimer_tmp") -NAME_FORMAT = "ogg_tmpfile_%s.ogg" +class VorbisEncoderSetupError(Exception): + pass -def vorbis_file_from_data_stream(data_stream, streaming=False): - if not (pyogg and constants.OGGVORBIS_AVAILABLE): - raise NotImplementedError( - "OggVorbis decoder not available. Cannot decompress." - ) - +class VorbisAnalysisError(Exception): + pass + + +# ogg does analysis and compression on 1024 float samples at a time +OGG_ANALYSIS_BUFFER_SIZE = 1024 + + +def pyogg_audiofile_from_filepath(filepath, ext=None, streaming=False): + cls = _get_pyogg_class(filepath, ext, streaming) + return cls(str(filepath)) + + +def pyogg_audiofile_from_data_stream(data_stream, ext, streaming=False): # look, it's WAY easier to just dump the file to a temp folder and # have VorbisFile decode the entire thing than to try and hook into # calling the ogg parsing and vorbis decoder functions on a stream. - filepath = TEMP_ROOT.joinpath(NAME_FORMAT % threading.get_native_id()) + # TODO: linux supports memory-only files, so look into doing that + # so we don't have to dump to a temp directory on linux. + filepath = constants.TEMP_ROOT.joinpath( + constants.OGGVORBIS_TMPNAME_FORMAT % ( + threading.get_native_id(), ext + ) + ) + cls = _get_pyogg_class(filepath, ext, streaming) + filepath.parent.mkdir(parents=True, exist_ok=True) with open(filepath, "wb") as f: f.write(data_stream) - return ( - pyogg.VorbisFileStream if streaming else - pyogg.VorbisFile - )(str(filepath)) + return cls(str(filepath)) + + +def get_ogg_pcm_sample_count(data_stream): + vorbis_file = pyogg_audiofile_from_data_stream( + data_stream, constants.CONTAINER_EXT_OGG, streaming=True + ) + return pyogg_vorbis.libvorbisfile.ov_pcm_total( + ctypes.byref(vorbis_file.vf), 0 + ) def decode_oggvorbis(data_stream): - vorbis_file = vorbis_file_from_data_stream(data_stream) + vorbis_file = pyogg_audiofile_from_data_stream( + data_stream, constants.CONTAINER_EXT_OGG, streaming=False + ) sample_data = vorbis_file.buffer sample_rate = vorbis_file.frequency compression = constants.OGG_DECOMPRESSED_FORMAT @@ -51,20 +77,240 @@ def decode_oggvorbis(data_stream): def encode_oggvorbis( - sample_data, channel_count, sample_rate, - is_big_endian=False, **kwargs + sample_data, sample_rate, sample_width, channels, is_big_endian=False, + bitrate_lower=-1, bitrate_nominal=-1, bitrate_upper=-1, + quality_setting=1.0, use_quality_value=True, bitrate_managed=True ): if not (pyogg and constants.OGGVORBIS_ENCODING_AVAILABLE): raise NotImplementedError( "OggVorbis encoder not available. Cannot compress." ) - data_stream = b'' + elif channels not in (1, 2): + raise NotImplementedError( + "Cannot encode %s channel audio to OggVorbis." % channels + ) + elif sample_width not in (1, 2, 4): + raise NotImplementedError( + "Cannot encode %s-byte width samples to OggVorbis." % width + ) - return data_stream + # one Ogg bitstream page. Vorbis packets inside + opg = pyogg_ogg.ogg_page() + opg_p = ctypes.pointer(opg) + # one raw packet of data for decode + opk_p = ctypes.pointer(pyogg_ogg.ogg_packet()) + # takes pages, weld into a logical packet stream + oss_p = ctypes.pointer(pyogg_ogg.ogg_stream_state()) -def get_pcm_sample_count(data_stream): - vorbis_file = vorbis_file_from_data_stream(data_stream, streaming=True) - return pyogg_vorbis.libvorbisfile.ov_pcm_total( - ctypes.byref(vorbis_file.vf), 0 + # local working space for packet->PCM decode + vbl_p = ctypes.pointer(pyogg_vorbis.vorbis_block()) + # struct that stores all user comments + vco_p = ctypes.pointer(pyogg_vorbis.vorbis_comment()) + # central working state for packet->PCM decoder + vds_p = ctypes.pointer(pyogg_vorbis.vorbis_dsp_state()) + # struct storing all static vorbis bitstream settings + vin_p = ctypes.pointer(pyogg_vorbis.vorbis_info()) + + pyogg_vorbis.vorbis_info_init(vin_p) + + if use_quality_value: + err = pyogg_vorbis.vorbis_encode_init_vbr(vin_p, + ctypes.c_int(channels), + ctypes.c_int(sample_rate), + ctypes.c_float(quality_setting) + ) + else: + err = pyogg_vorbis.vorbis_encode_init(vin_p, + ctypes.c_int(channels), + ctypes.c_int(sample_rate), + ctypes.c_int(bitrate_upper), + ctypes.c_int(bitrate_nominal), + ctypes.c_int(bitrate_lower) + ) + + if err: + raise VorbisEncoderSetupError( + "Vorbis encoder returned error code during setup. " + "Encoder settings are likely incorrect." + ) + + # create an output buffer + ogg_data = io.BytesIO() + + # add comments + pyogg_vorbis.vorbis_comment_init(vco_p) + for string in ( + str.encode("RECLAIMERVER=%s.%s.%s" % reclaimer.__version__), + str.encode("PYOGGVER=%s" % pyogg.__version__), + ): + pyogg_vorbis.vorbis_comment_add(vco_p, + ctypes.create_string_buffer(string) + ) + + # set up the analysis state and auxiliary encoding storage + pyogg_vorbis.vorbis_analysis_init(vds_p, vin_p) + pyogg_vorbis.vorbis_block_init(vds_p, vbl_p) + + # initialise stream state + # pick a random serial number; that way we can more + # likely build chained streams just by concatenation + pyogg_ogg.ogg_stream_init(oss_p, _get_random_serial_no()) + + # Vorbis streams begin with three headers; the initial header + # (with most of the codec setup parameters) which is mandated + # by the Ogg bitstream spec. The second header holds any comment + # fields. The third header holds the bitstream codebook. + # We merely need to make the headers and pass them to libvorbis one + # by one; libvorbis handles the additional Ogg bitstream constraints + ovh_p = ctypes.pointer(pyogg_ogg.ogg_packet()) + ovh_comm_p = ctypes.pointer(pyogg_ogg.ogg_packet()) + ovh_code_p = ctypes.pointer(pyogg_ogg.ogg_packet()) + + pyogg_vorbis.vorbis_analysis_headerout( + vds_p, vco_p, ovh_p, ovh_comm_p, ovh_code_p + ) + pyogg_ogg.ogg_stream_packetin(oss_p, ovh_p) + pyogg_ogg.ogg_stream_packetin(oss_p, ovh_comm_p) + pyogg_ogg.ogg_stream_packetin(oss_p, ovh_code_p) + + # per spec, ensure vorbis audio data starts on a new page, so + # we need to flush the current page and start a new one + while pyogg_ogg.ogg_stream_flush(oss_p, opg_p): + _write_ogg_page(ogg_data, opg) + + # deinterleave the audio if it's stereo, and convert it to float + inp_buffers = [ + util.convert_pcm_int_to_pcm_float32(buffer, sample_width, True) + for buffer in ( + util.deinterleave_stereo(sample_data, sample_width, True) + if channels == 2 else [sample_data] + ) + ] + + i, sample_count = 0, min(len(b) for b in inp_buffers) + samples_per_chunk = OGG_ANALYSIS_BUFFER_SIZE // channels + vorbis_buf_size = ctypes.c_int(OGG_ANALYSIS_BUFFER_SIZE) + + # if using bitrate managed encoding, vorbis coded block is not + # dumped directly to a packet. instead, it must be flushed using + # vorbis_bitrate_flushpacket after calling vorbis_bitrate_addblock. + # when not using bitrate managed encoding, we pass the pointer to + # the packet the vorbis analysis should dump its results directly to. + vorbis_out_opk_p = None if bitrate_managed else opk_p + + # encode the pcm data to vorbis and dump it to the ogg_data buffer + while i <= sample_count: + # figure out how much data to pass to the analysis buffer + read = min(sample_count - i, samples_per_chunk) + + # transfer sample data to analysis buffer(if any) + if read: + vorbis_buffers = pyogg_vorbis.vorbis_analysis_buffer( + vds_p, vorbis_buf_size + ) + for c in range(channels): + tuple( + itertools.starmap( + vorbis_buffers[c].__setitem__, + enumerate(inp_buffers[c][i: i+read]) + )) + + i += read + + # tell the vorbis encoder how many samples to analyze + pyogg_vorbis.vorbis_analysis_wrote(vds_p, ctypes.c_int(read)) + + # get a single block for encoding + while pyogg_vorbis.vorbis_analysis_blockout(vds_p, vbl_p) == 1: + # do analysis + res = pyogg_vorbis.vorbis_analysis(vbl_p, vorbis_out_opk_p) + if res: + raise VorbisAnalysisError("Vorbis analysis returned error: %d" % res) + + # NOTE: we're ALWAYS going to use the bitrate management + # system, as there are no downsides, but i am keeping + # this code here as an example of how to not use it. + # see here for why: + # https://xiph.org/vorbis/doc/libvorbis/overview.html + # + if not bitrate_managed: + pyogg_ogg.ogg_stream_packetin(oss_p, opk_p) + while pyogg_ogg.ogg_stream_pageout(oss_p, opg_p): + _write_ogg_page(ogg_data, opg) + + continue + + pyogg_vorbis.vorbis_bitrate_addblock(vbl_p) + + # while there are packets to flush, weld them into + # the bitstream and write out any resulting pages. + while pyogg_vorbis.vorbis_bitrate_flushpacket(vds_p, opk_p) == 1: + pyogg_ogg.ogg_stream_packetin(oss_p, opk_p) + while pyogg_ogg.ogg_stream_pageout(oss_p, opg_p): + _write_ogg_page(ogg_data, opg) + + if pyogg_ogg.ogg_page_eos(opg_p): + i = sample_count + 1 + break + + # ensure everything is flushed + while pyogg_ogg.ogg_stream_flush(oss_p, opg_p): + _write_ogg_page(ogg_data, opg) + + # finish up by clearing everything + pyogg_ogg.ogg_stream_clear(oss_p) + pyogg_vorbis.vorbis_block_clear(vbl_p) + pyogg_vorbis.vorbis_comment_clear(vco_p) + pyogg_vorbis.vorbis_dsp_clear(vds_p) + pyogg_vorbis.vorbis_info_clear(vin_p) + + ogg_bitstream_bytes = ogg_data.getbuffer().tobytes() + return ogg_bitstream_bytes + + +def _get_random_serial_no(bit_count=(ctypes.sizeof(ctypes.c_int)*8)): + return ctypes.c_int(random.randint( + -(1<<(bit_count - 1)), + (1<<(bit_count - 1))-1 + )) + + +def _c_pointer_to_buffer(pointer, buf_len, buf_typ=ctypes.c_ubyte): + BufferPtr = ctypes.POINTER(buf_typ * buf_len) + return BufferPtr(pointer)[0] + + +def _write_ogg_page(buffer, ogg_page): + buffer.write(_c_pointer_to_buffer( + ogg_page.header.contents, ogg_page.header_len + )) + buffer.write(_c_pointer_to_buffer( + ogg_page.body.contents, ogg_page.body_len + )) + + +def _get_pyogg_class(filepath=None, ext=None, streaming=False): + if filepath and ext is None: + ext = pathlib.Path(filepath).suffix + + if not pyogg: + raise NotImplementedError("PyOgg not available. Cannot open files.") + elif ext not in constants.PYOGG_CONTAINER_EXTS: + raise ValueError("Unknown PyOgg extension '%s'" % ext) + elif ((ext == constants.CONTAINER_EXT_OPUS and not constants.OPUS_AVAILABLE) or + (ext == constants.CONTAINER_EXT_FLAC and not constants.FLAC_AVAILABLE) or + (ext == constants.CONTAINER_EXT_OGG and not constants.OGGVORBIS_AVAILABLE) + ): + raise NotImplementedError( + "PyOgg was improperly initialized. Cannot open '%s' files" % ext + ) + + return ( + (pyogg.OpusFileStream if streaming else pyogg.OpusFile) + if ext == constants.CONTAINER_EXT_OPUS else + (pyogg.FlacFileStream if streaming else pyogg.FlacFile) + if ext == constants.CONTAINER_EXT_FLAC else + (pyogg.VorbisFileStream if streaming else pyogg.VorbisFile) + # NOTE: not checking for ogg ext cause it was done above ) \ No newline at end of file diff --git a/reclaimer/sounds/sound_compilation.py b/reclaimer/sounds/sound_compilation.py index 8ee0be9f..5ee52076 100644 --- a/reclaimer/sounds/sound_compilation.py +++ b/reclaimer/sounds/sound_compilation.py @@ -39,11 +39,14 @@ def compile_pitch_range(pitch_range, blam_pitch_range, errors.append('Cannot add %s channel sounds to %s channel tag.' % (blam_channel_count, channel_count)) - if blam_sound_perm.compression not in (constants.COMPRESSION_PCM_16_LE, - constants.COMPRESSION_PCM_16_BE, - constants.COMPRESSION_XBOX_ADPCM, - constants.COMPRESSION_IMA_ADPCM, - constants.COMPRESSION_OGG): + if blam_sound_perm.compression not in ( + constants.COMPRESSION_PCM_16_LE, + constants.COMPRESSION_PCM_16_BE, + constants.COMPRESSION_XBOX_ADPCM, + # not supported in-engine + #constants.COMPRESSION_IMA_ADPCM, + constants.COMPRESSION_OGG + ): errors.append('Unknown permutation compression "%s"' % blam_sound_perm.compression) @@ -83,15 +86,16 @@ def compile_pitch_range(pitch_range, blam_pitch_range, comp_name = ( "xbox_adpcm" if comp == constants.COMPRESSION_XBOX_ADPCM else - "ima_adpcm" if comp == constants.COMPRESSION_IMA_ADPCM else + # not supported in-engine + #"ima_adpcm" if comp == constants.COMPRESSION_IMA_ADPCM else "ogg" if comp == constants.COMPRESSION_OGG else "none" ) # only ogg and 16bit pcm need the buffer size calculated sample_count = ( - ogg.get_pcm_sample_count(blam_samples.sample_data) - if comp_name == "ogg" and sound_const.OGGVORBIS_AVAILABLE else + ogg.get_ogg_pcm_sample_count(blam_samples.sample_data) + if comp_name == "ogg" and constants.OGGVORBIS_AVAILABLE else blam_samples.sample_count if comp_name in ("ogg", "none") else 0 @@ -138,7 +142,6 @@ def compile_sound(snd__tag, blam_sound_bank, ignore_size_limits=False, tagdata.modifiers_when_scale_is_zero[:] = (1.0, 0.0, 1.0) tagdata.modifiers_when_scale_is_one[:] = (1.0, 1.0, 1.0) - if update_mode != constants.SOUND_COMPILE_MODE_ADDITIVE: # update the flags, compression, encoding, and sample rate # of the tag to that of the samples being stored in it. @@ -156,9 +159,10 @@ def compile_sound(snd__tag, blam_sound_bank, ignore_size_limits=False, elif blam_sound_bank.compression == constants.COMPRESSION_XBOX_ADPCM: tagdata.flags.fit_to_adpcm_blocksize = True tagdata.compression.set_to("xbox_adpcm") - elif blam_sound_bank.compression == constants.COMPRESSION_IMA_ADPCM: - tagdata.flags.fit_to_adpcm_blocksize = True - tagdata.compression.set_to("ima_adpcm") + # not supported in-engine + #elif blam_sound_bank.compression == constants.COMPRESSION_IMA_ADPCM: + # tagdata.flags.fit_to_adpcm_blocksize = True + # tagdata.compression.set_to("ima_adpcm") elif blam_sound_bank.compression == constants.COMPRESSION_OGG: tagdata.compression.set_to("ogg") else: diff --git a/reclaimer/sounds/util.py b/reclaimer/sounds/util.py index 8b4f83ab..d350e66f 100644 --- a/reclaimer/sounds/util.py +++ b/reclaimer/sounds/util.py @@ -11,6 +11,7 @@ import re import struct import sys +from types import MethodType from reclaimer.sounds import audioop, constants @@ -179,17 +180,70 @@ def convert_pcm_to_pcm(samples, compression, target_compression, return samples -def convert_pcm_float32_to_pcm_32(sample_data): - samples = array.array('f', sample_data) +def convert_pcm_float32_to_pcm_int(sample_data, width, wantarray=False): + typecode = audioop.SAMPLE_TYPECODES[width-1] + float_samples = array.array('f', sample_data) + if sys.byteorder == "big" and not isinstance(sample_data, array.array): + # data is expected to be passed in as + # little-endian unless it's an array + float_samples.byteswap() + + maxval = (1 << (8*width-1)) - 1 + minval = -(maxval + 1) + + out_data = array.array(typecode, + map(MethodType(max, minval), + map(MethodType(min, maxval), + map(round, + map(float(maxval+1).__rmul__, float_samples) + )))) + + if sys.byteorder == "big" and width > 1: + # return is expected to be little-endian + out_data.byteswap() + + return out_data if wantarray else out_data.tobytes() + + +def convert_pcm_int_to_pcm_float32(sample_data, width, wantarray=False): + typecode = audioop.SAMPLE_TYPECODES[width-1] + int_samples = array.array(typecode, sample_data) if sys.byteorder == "big": samples.byteswap() - samples = [-0x7fFFffFF if val <= -1.0 else - (0x7fFFffFF if val >= 1.0 else - int(val * 0x7fFFffFF)) - for val in samples] + scale = 1/(1 << (8*width-1)) + + out_data = array.array("f", + map(MethodType(max, -1.0), + map(MethodType(min, 1.0), + map(scale.__rmul__, int_samples) + ))) + + if sys.byteorder == "big" and width > 1: + # return is expected to be little-endian + out_data.byteswap() + + return out_data if wantarray else out_data.tobytes() + + +def deinterleave_stereo(fragment, width, wantarray=False): + if width not in (1, 2, 4): + raise NotImplementedError( + "Cannot deinterleave %s-byte width samples." % width + ) + + typecode = audioop.SAMPLE_TYPECODES[width-1] + if not(isinstance(fragment, array.array) and + fragment.typecode == typecode): + fragment = array.array(typecode, fragment) + + left_channel_data = array.array(typecode, fragment[0::2]) + right_channel_data = array.array(typecode, fragment[1::2]) - return struct.pack("<%di" % len(samples), *samples) + return ( + left_channel_data if wantarray else left_channel_data.tobytes(), + right_channel_data if wantarray else right_channel_data.tobytes() + ) def generate_mouth_data(sample_data, compression, sample_rate, encoding): From 255f0fa666708cb84c0e57b0e1a6785be8768a51 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sat, 16 Mar 2024 21:51:54 -0500 Subject: [PATCH 45/51] Fixed sound playback bug --- reclaimer/__init__.py | 2 +- reclaimer/sounds/blam_sound_permutation.py | 40 ++++++++++++++++------ reclaimer/sounds/playback.py | 31 +++++++++-------- 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/reclaimer/__init__.py b/reclaimer/__init__.py index 1c57fc77..9a9156d8 100644 --- a/reclaimer/__init__.py +++ b/reclaimer/__init__.py @@ -13,7 +13,7 @@ __author__ = "Sigmmma" # YYYY.MM.DD __date__ = "2024.03.16" -__version__ = (2, 20, 0) +__version__ = (2, 21, 0) __website__ = "https://github.com/Sigmmma/reclaimer" __all__ = ( "animation", "bitmaps", "h2", "h3", "halo_script", "hek", "meta", "misc", diff --git a/reclaimer/sounds/blam_sound_permutation.py b/reclaimer/sounds/blam_sound_permutation.py index 5186a937..1f0bb695 100644 --- a/reclaimer/sounds/blam_sound_permutation.py +++ b/reclaimer/sounds/blam_sound_permutation.py @@ -19,6 +19,7 @@ class BlamSoundPermutation: name = "" # permutation properties + _source_filename = "" _source_sample_data = b'' _source_compression = const.COMPRESSION_PCM_16_LE _source_sample_rate = const.SAMPLE_RATE_22K @@ -46,6 +47,9 @@ def source_sample_rate(self): @property def source_encoding(self): return self._source_encoding + @property + def source_filename(self): + return self._source_filename @property def processed_samples(self): @@ -70,12 +74,13 @@ def encoding(self): return self._source_encoding def load_source_samples(self, sample_data, compression, - sample_rate, encoding): + sample_rate, encoding, filename=""): self._source_sample_data = sample_data self._source_compression = compression self._source_sample_rate = sample_rate - self._source_encoding = encoding - self._processed_samples = [] + self._source_encoding = encoding + self._source_filename = filename + self._processed_samples = [] def partition_samples(self, target_compression=None, target_sample_rate=None, target_encoding=None, @@ -102,7 +107,7 @@ def partition_samples(self, target_compression=None, source_compression = self.source_compression source_sample_rate = self.source_sample_rate - source_encoding = self.source_encoding + source_encoding = self.source_encoding source_sample_data = self.source_sample_data target_chunk_size = util.get_sample_chunk_size( @@ -121,15 +126,12 @@ def partition_samples(self, target_compression=None, else: # decompress samples so we can partition to a # different compression/encoding/sample rate - decompressor = blam_sound_samples.BlamSoundSamples( - source_sample_data, 0, source_compression, - source_sample_rate, source_encoding - ) source_compression = const.DEFAULT_UNCOMPRESSED_FORMAT source_sample_rate = target_sample_rate - source_encoding = target_encoding - source_sample_data = decompressor.get_decompressed( - source_compression, source_sample_rate, source_encoding) + source_encoding = target_encoding + source_sample_data = self.decompress_source_samples( + source_compression, source_sample_rate, source_encoding + ) source_bytes_per_sample = util.get_block_size( source_compression, source_encoding) @@ -170,6 +172,20 @@ def compress_samples(self, compression, sample_rate=None, encoding=None, samples.compress(compression, sample_rate, encoding, **compressor_kwargs) + def decompress_source_samples(self, compression, sample_rate, encoding): + assert compression in const.PCM_FORMATS + assert encoding in const.channel_counts + + # decompress samples so we can partition to a + # different compression/encoding/sample rate + decompressor = blam_sound_samples.BlamSoundSamples( + self.source_sample_data, 0, self.source_compression, + self.source_sample_rate, self.source_encoding + ) + return decompressor.get_decompressed( + compression, sample_rate, encoding + ) + def get_concatenated_sample_data(self, target_compression=None, target_sample_rate=None, target_encoding=None): @@ -398,6 +414,8 @@ def import_from_file(self, filepath): else: raise ValueError("Unsupported audio extension '%s'." % ext) + self._source_filename = filepath.name + def _import_from_pyogg_file(self, filepath, ext): pyogg_audio_file = ogg.pyogg_audiofile_from_filepath( filepath, ext, streaming=False diff --git a/reclaimer/sounds/playback.py b/reclaimer/sounds/playback.py index 3114fc0d..5a46b176 100644 --- a/reclaimer/sounds/playback.py +++ b/reclaimer/sounds/playback.py @@ -47,20 +47,23 @@ def build_wave_object(sample_data, encoding, compression, sample_rate): def wave_object_from_blam_sound_perm(blam_perm): - if not blam_perm: - return None - elif not blam_perm.source_sample_data: - # if there is no source sample data, we'll try to use - # the concatenated processed sample data instead. - # need to decode to 16bit pcm - blam_perm.regenerate_source(constants.COMPRESSION_PCM_16_LE) - - return build_wave_object( - blam_perm.source_sample_data, - blam_perm.source_encoding, - blam_perm.source_compression, - blam_perm.source_sample_rate - ) if blam_perm.source_sample_data else None + samples = getattr(blam_perm, "source_sample_data", None) + comp = getattr(blam_perm, "source_compression", None) + enc = getattr(blam_perm, "source_encoding", None) + sr = getattr(blam_perm, "source_sample_rate", None) + if blam_perm and (not samples or comp not in constants.PCM_FORMATS): + comp = constants.COMPRESSION_PCM_16_LE + if samples: + # sample data is compressed in a format we can't + # linearly read, so we need to decompress it. + samples = blam_perm.decompress_source_samples(comp, sr, enc) + else: + # if there is no source sample data, so we'll try + # to use the decompressed processed samples instead. + blam_perm.regenerate_source(comp) + samples = blam_perm.source_sample_data + + return build_wave_object(samples, enc, comp, sr) if samples else None class SoundPlayerBase: From d63d5f1f643051654d289462af79cde737d8df62 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Mon, 18 Mar 2024 12:04:20 -0500 Subject: [PATCH 46/51] Fix some regressions --- reclaimer/__init__.py | 4 ++-- reclaimer/hek/defs/objs/obje.py | 6 +++--- reclaimer/hek/handler.py | 4 +++- reclaimer/mcc_hek/defs/antr.py | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/reclaimer/__init__.py b/reclaimer/__init__.py index 9a9156d8..837e5abb 100644 --- a/reclaimer/__init__.py +++ b/reclaimer/__init__.py @@ -12,8 +12,8 @@ # ############## __author__ = "Sigmmma" # YYYY.MM.DD -__date__ = "2024.03.16" -__version__ = (2, 21, 0) +__date__ = "2024.03.18" +__version__ = (2, 22, 0) __website__ = "https://github.com/Sigmmma/reclaimer" __all__ = ( "animation", "bitmaps", "h2", "h3", "halo_script", "hek", "meta", "misc", diff --git a/reclaimer/hek/defs/objs/obje.py b/reclaimer/hek/defs/objs/obje.py index e8b5ce10..db9db624 100644 --- a/reclaimer/hek/defs/objs/obje.py +++ b/reclaimer/hek/defs/objs/obje.py @@ -21,8 +21,8 @@ def calc_internal_data(self): self.ext = '.' + full_class_name self.filepath = os.path.splitext(str(self.filepath))[0] + self.ext - tag_data = self.data.tagdata - object_type = tagdata.obje_attrs.object_type + obje_attrs = self.data.tagdata.obje_attrs + object_type = obje_attrs.object_type if full_class_name == "object": object_type.data = -1 elif full_class_name == "biped": @@ -53,7 +53,7 @@ def calc_internal_data(self): raise ValueError("Unknown object type '%s'" % full_class_name) # normalize color change weights - for cc in tagdata.change_colors.STEPTREE: + for cc in obje_attrs.change_colors.STEPTREE: perms = cc.permutations.STEPTREE total_weight = sum(max(0, perm.weight) for perm in perms) total_weight = total_weight or len(perms) diff --git a/reclaimer/hek/handler.py b/reclaimer/hek/handler.py index a46769e7..80a0a4ee 100644 --- a/reclaimer/hek/handler.py +++ b/reclaimer/hek/handler.py @@ -187,8 +187,10 @@ def get_def_id(self, filepath): engine_id = f.read(4).decode(encoding='latin-1') if def_id in self.defs and engine_id == self.tag_header_engine_id: return def_id + except FileNotFoundError: + pass except Exception: - print(format_exc()); + print(format_exc()) return self.ext_id_map.get(filepath.suffix.lower()) diff --git a/reclaimer/mcc_hek/defs/antr.py b/reclaimer/mcc_hek/defs/antr.py index 045cedaf..d3fbd080 100644 --- a/reclaimer/mcc_hek/defs/antr.py +++ b/reclaimer/mcc_hek/defs/antr.py @@ -11,7 +11,7 @@ unit_weapon_desc = desc_variant(unit_weapon_desc, reflexive("ik_points", ik_point_desc, 8, DYN_NAME_PATH=".marker"), - reflexive("weapon_types", weapon_types_desc, 8, DYN_NAME_PATH=".label"), + reflexive("weapon_types", weapon_types_desc, 64, DYN_NAME_PATH=".label"), ) unit_desc = desc_variant(unit_desc, reflexive("ik_points", ik_point_desc, 8, DYN_NAME_PATH=".marker"), From fd1071061de896fca42d1a32f86140a01d290763 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Wed, 27 Mar 2024 21:30:47 -0500 Subject: [PATCH 47/51] Fix H1X mip map issue --- reclaimer/meta/halo1_map.py | 1893 +++++++++++++++++++++++++----- reclaimer/meta/halo1_rsrc_map.py | 607 +++++++++- 2 files changed, 2148 insertions(+), 352 deletions(-) diff --git a/reclaimer/meta/halo1_map.py b/reclaimer/meta/halo1_map.py index d40e6335..4dccce6f 100644 --- a/reclaimer/meta/halo1_map.py +++ b/reclaimer/meta/halo1_map.py @@ -7,292 +7,1607 @@ # See LICENSE for more information. # -''' -The formula for calculating the magic for ANY map is as follows: - magic = idx_magic - idx_off - -idx_off is the offset of the tag_index_header and idx_magic -varies depending on which engine version the map is for. -To get the idx_magic, do: - map_magics[map_header.version.enum_name] - -where map_magics is in reclaimer.constants - -To convert a magic relative pointer to an absolute pointer, -simply do: abs_pointer = magic_pointer - magic -''' - -from reclaimer.common_descs import * - -from supyr_struct.defs.block_def import BlockDef -from supyr_struct.util import desc_variant - -def tag_path_pointer(parent=None, new_value=None, magic=0, **kwargs): - ''' - Calculates the pointer value for a tag_path based on the file offset. - ''' - if parent is None: - raise KeyError() - if new_value is None: - return parent.path_offset - magic - parent.path_offset = new_value + magic - - -def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): - ''' - Calculates the pointer value for a tag_index array based on file offset. - ''' - if new_value is None: - return parent.tag_index_offset - magic - parent.tag_index_offset = new_value + magic - - -yelo_header = Struct("yelo header", - UEnum32("yelo", ('yelo', 'yelo'), EDITABLE=False, DEFAULT='yelo'), - UEnum16("version type", - ("version", 1), - ("version minimum build", 2) - ), - Bool16("flags", - "uses memory upgrades", - "uses mod data files", - "is protected", - "uses game state upgrades", - "has compression params", - ), - QStruct("tag versioning", - UInt8("project yellow"), - UInt8("project yellow globals"), - Pad(2), - SIZE=4 - ), - - Float("memory upgrade multiplier"), - - Struct("cheape definitions", - UInt32("size"), - UInt32("decompressed size"), - UInt32("offset"), - Pad(4), - - ascii_str32("build string"), - SIZE=48 - ), - - ascii_str32("mod name"), - - Struct("build info", - Pad(2), - UEnum16("stage", - "ship", - "alpha", - "beta", - "delta", - "epsilon", - "release", - ), - UInt32("revision"), - Timestamp64("timestamp"), - - ascii_str32("build string"), - - QStruct("cheape", - UInt8("maj"), - UInt8("min"), - UInt16("build"), - ), - BytesRaw("uuid buffer", SIZE=16), - - QStruct("minimum os build", - UInt8("maj"), - UInt8("min"), - UInt16("build"), - ), - Pad(4*3), - SIZE=84 - ), - - QStruct("resources", - UInt32("compression params header offset"), - UInt32("tag symbol storage header offset"), - UInt32("string id storage header offset"), - UInt32("tag string to id storage header offset"), - SIZE=16 - ), - - SIZE=196 - ) - -vap_block = Struct("vap_block", - # File offset of the compressed block - UInt32("file_offset"), - - # File size of the compressed block - UInt32("file_size"), - - # Decompressed size of the compressed block - UInt32("decompressed_size"), - - # Reserved for potential future usage - UInt32("reserved"), - ) - -# validated archive pattern(uh huh, sure) -vap_header = Struct("vap_header", - # File size of the map when decompressed, include header size - UInt32("decompressed_size"), - UEnum16("compression_type", - "uncompressed", - "lzma", - ), - UEnum16("vap_version", - "chimera_1", - "next", - ), - - # Number of blocks. If 0, data is assumed to be one contiguous stream - UInt32("block_count"), - # File size of the map when compressed, include header size - UInt32("compressed_size"), - - UEnum32("feature_level", - "chimera_1", - "next", - ), - - # Player limit (campaign). Set to 0 for multiplayer and user interface maps - UInt16("max_players"), - # Reserved for future use. Leave it at 0 - Pad(10), - - # Build date of the map in ISO 8601 (yyyy-mm-ddThh:mm:ss.fffffffff) format - ascii_str32("build_date"), - - # Human-readable of the map - ascii_str32("name"), - - # Human-readable description of the map - StrLatin1("description", SIZE=128), - Pad(256), - STEPTREE=Array("blocks", - SIZE=".block_count", SUB_STRUCT=vap_block - ), - SIZE=480 - ) - -# Halo Demo maps have a different header -# structure with garbage filling the padding -map_header_demo = Struct("map header", - Pad(2), - gen1_map_type, # NOTE: in common_descs.py - Pad(700), - UEnum32('head', ('head', 'Ehed'), EDITABLE=False, DEFAULT='Ehed'), - UInt32("tag data size"), - ascii_str32("build date", EDITABLE=False), - Pad(672), - map_version, # NOTE: in common_descs.py - ascii_str32("map name"), - UInt32("unknown"), - UInt32("crc32"), - Pad(52), - UInt32("decomp len"), - UInt32("tag index header offset"), - UEnum32('foot', ('foot', 'Gfot'), EDITABLE=False, DEFAULT='Gfot'), - Pad(524), - SIZE=2048 - ) - -map_header = Struct("map header", - UEnum32('head', ('head', 'head'), DEFAULT='head'), - map_version, # NOTE: in common_descs.py - UInt32("decomp len"), - UInt32("unknown"), - UInt32("tag index header offset"), - UInt32("tag data size"), - Pad(8), - ascii_str32("map name"), - ascii_str32("build date", EDITABLE=False), - gen1_map_type, # NOTE: in common_descs.py - Pad(2), - UInt32("crc32"), - Pad(1), - Pad(3), - Pad(4), - yelo_header, - UEnum32('foot', ('foot', 'foot'), DEFAULT='foot', OFFSET=2044), - SIZE=2048 - ) - -mcc_flags = Bool8("mcc_flags", - "use_bitmaps_map", - "use_sounds_map", - "disable_remastered_sync", - ) - -map_header_mcc = desc_variant( - map_header, - ("pad_12", mcc_flags), - ) - -map_header_vap = desc_variant( - map_header, - ("yelo_header", Struct("vap_header", INCLUDE=vap_header, OFFSET=128)), - verify=False, - ) - -tag_header = Struct("tag_header", - UEnum32("class_1", GUI_NAME="primary tag class", INCLUDE=valid_tags_os), - UEnum32("class_2", GUI_NAME="secondary tag class", INCLUDE=valid_tags_os), - UEnum32("class_3", GUI_NAME="tertiary tag class", INCLUDE=valid_tags_os), - UInt32("id"), - UInt32("path_offset"), - UInt32("meta_offset"), - UInt8("indexed"), - Pad(3), - # if indexed is non-zero, the meta_offset is the literal index in - # the bitmaps, sounds, or loc cache that the meta data is located in. - # NOTE: indexed is NOT a bitfield, if it is non-zero it is True - UInt32("pad"), - STEPTREE=CStrTagRef("path", POINTER=tag_path_pointer, MAX=768), - SIZE=32 - ) - -tag_index_array = TagIndex("tag_index", - SIZE=".tag_count", SUB_STRUCT=tag_header, POINTER=tag_index_array_pointer - ) - -tag_index_pc = Struct("tag_index", - UInt32("tag_index_offset"), - UInt32("scenario_tag_id"), - UInt32("map_id"), # normally unused, but can be used - # for spoofing the maps checksum. - UInt32("tag_count"), - - UInt32("vertex_parts_count"), - UInt32("model_data_offset"), - - UInt32("index_parts_count"), - UInt32("index_parts_offset"), - UInt32("model_data_size"), - UInt32("tag_sig", EDITABLE=False, DEFAULT='tags'), - - SIZE=40, - STEPTREE=tag_index_array - ) - -tag_index_xbox = desc_variant(tag_index_pc, - ("model_data_size", Pad(0)), - SIZE=36, verify=False - ) - -map_header_def = BlockDef(map_header) -map_header_anni_def = BlockDef(map_header, endian=">") -map_header_demo_def = BlockDef(map_header_demo) -map_header_vap_def = BlockDef(map_header_vap) - -tag_index_xbox_def = BlockDef(tag_index_xbox) -tag_index_pc_def = BlockDef(tag_index_pc) -tag_index_anni_def = BlockDef(tag_index_pc, endian=">") - -map_header_vap_def = BlockDef(map_header_vap) -map_header_mcc_def = BlockDef(map_header_mcc) +import os +import sys + +from array import array as PyArray +from copy import deepcopy +from math import pi, log +from pathlib import Path +from struct import unpack, unpack_from, pack_into +from traceback import format_exc +from types import MethodType + +from supyr_struct.buffer import BytearrayBuffer +from supyr_struct.field_types import FieldType +from supyr_struct.defs.frozen_dict import FrozenDict + +from reclaimer.halo_script.hsc_decompilation import extract_scripts +from reclaimer.halo_script.hsc import get_hsc_data_block,\ + get_script_syntax_node_tag_refs, clean_script_syntax_nodes,\ + get_script_types, HSC_IS_SCRIPT_OR_GLOBAL,\ + SCRIPT_OBJECT_TYPES_TO_SCENARIO_REFLEXIVES +from reclaimer.common_descs import make_dependency_os_block +from reclaimer.hek.defs.snd_ import snd__meta_stub_blockdef +from reclaimer.hek.defs.sbsp import sbsp_meta_header_def +from reclaimer.hek.handler import HaloHandler +from reclaimer.os_v4_hek.defs.coll import fast_coll_def +from reclaimer.os_v4_hek.defs.sbsp import fast_sbsp_def +from reclaimer.meta.wrappers.byteswapping import raw_block_def, byteswap_animation,\ + byteswap_uncomp_verts, byteswap_comp_verts, byteswap_tris,\ + byteswap_coll_bsp, byteswap_sbsp_meta, byteswap_scnr_script_syntax_data,\ + byteswap_pcm16_samples +from reclaimer.meta.wrappers.halo_map import HaloMap +from reclaimer.meta.wrappers.halo1_rsrc_map import Halo1RsrcMap, inject_sound_data, get_is_xbox_map +from reclaimer.meta.wrappers.map_pointer_converter import MapPointerConverter +from reclaimer.meta.wrappers.tag_index_manager import TagIndexManager +from reclaimer import data_extraction +from reclaimer.constants import tag_class_fcc_to_ext, GEN_1_HALO_CUSTOM_ENGINES +from reclaimer.util.compression import compress_normal32, decompress_normal32 +from reclaimer.util import is_overlapping_ranges, is_valid_ascii_name_str,\ + int_to_fourcc, get_block_max + +from supyr_struct.util import is_path_empty + + +__all__ = ("Halo1Map", "Halo1RsrcMap") + + +def reparse_reflexive(block, new_size, pointer_converter, + map_data, tag_index_manager): + steptree = block.STEPTREE + file_ptr = pointer_converter.v_ptr_to_f_ptr(block.pointer) + + if (file_ptr + new_size * steptree.get_desc("SIZE", 0) > len(map_data) or + file_ptr <= 0): + return + + # read the palette array from the map + with FieldType.force_little: + del steptree[:] + steptree.extend(new_size) + steptree.TYPE.parser( + steptree.desc, node=steptree, + map_pointer_converter=pointer_converter, + rawdata=map_data, offset=file_ptr, + safe_mode=False, parsing_resource=False, + tag_index_manager=tag_index_manager) + + +class Halo1Map(HaloMap): + '''Generation 1 map''' + ce_rsrc_sound_indexes_by_path = None + ce_tag_indexs_by_paths = None + sound_rsrc_id = None + defs = None + + # Module path printed when loading the tag defs + tag_defs_module = "reclaimer.hek.defs" + tag_classes_to_load = tuple(sorted(tag_class_fcc_to_ext.keys())) + + # Handler that controls how to load tags, eg tag definitions + handler_class = HaloHandler + + force_checksum = False + + inject_rawdata = Halo1RsrcMap.inject_rawdata + + bsp_magics = () + bsp_sizes = () + bsp_headers = () + bsp_header_offsets = () + bsp_pointer_converters = () + + sbsp_meta_header_def = sbsp_meta_header_def + + data_extractors = data_extraction.h1_data_extractors + + indexable_tag_classes = set(( + "bitm", "snd!", "font", "hmt ", "ustr" + )) + + def __init__(self, maps=None): + HaloMap.__init__(self, maps) + + self.resource_map_class = Halo1RsrcMap + self.ce_rsrc_sound_indexes_by_path = {} + self.ce_tag_indexs_by_paths = {} + + self.bsp_magics = {} + self.bsp_sizes = {} + self.bsp_header_offsets = {} + self.bsp_headers = {} + self.bsp_pointer_converters = {} + + self.setup_tag_headers() + + @property + def globals_tag_id(self): + if not self.tag_index: + return None + + for b in self.tag_index.tag_index: + if int_to_fourcc(b.class_1.data) == "matg": + return b.id & 0xFFff + + @property + def resource_map_prefix(self): + return "" + @property + def resource_maps_folder(self): + return self.filepath.parent + @property + def resources_maps_mismatched(self): + maps_dir = self.resource_maps_folder + if not maps_dir: + return False + + for map_name, filepath in self.get_resource_map_paths().items(): + if filepath and filepath.parent != maps_dir: + return True + return False + @property + def uses_bitmaps_map(self): + return not self.is_resource + @property + def uses_loc_map(self): + return not self.is_resource and "pc" not in self.engine + @property + def uses_sounds_map(self): + return not self.is_resource + + @property + def decomp_file_ext(self): + return ( + ".vap" if self.engine == "halo1vap" else + self._decomp_file_ext + ) + + def is_indexed(self, tag_id): + tag_header = self.tag_index.tag_index[tag_id] + if not tag_header.indexed: + return False + return int_to_fourcc(tag_header.class_1.data) in self.indexable_tag_classes + + def setup_defs(self): + this_class = type(self) + if this_class.defs is None: + this_class.defs = defs = {} + print(" Loading definitions in '%s'" % self.tag_defs_module) + this_class.handler = self.handler_class( + build_reflexive_cache=False, build_raw_data_cache=False, + debug=2) + + this_class.defs = dict(this_class.handler.defs) + this_class.defs["coll"] = fast_coll_def + this_class.defs["sbsp"] = fast_sbsp_def + this_class.defs = FrozenDict(this_class.defs) + + # make a shallow copy for this instance to manipulate + self.defs = dict(self.defs) + + def ensure_sound_maps_valid(self): + sounds = self.maps.get("sounds") + if not sounds or self.is_resource: + return + + if id(sounds) == self.sound_rsrc_id and ( + self.ce_rsrc_sound_indexes_by_path and + self.ce_tag_indexs_by_paths): + return + + self.sound_rsrc_id = id(sounds) + if self.engine in GEN_1_HALO_CUSTOM_ENGINES: + # ce resource sounds are recognized by tag_path + # so we must cache their offsets by their paths + rsrc_snd_map = self.ce_rsrc_sound_indexes_by_path = {} + inv_snd_map = self.ce_tag_indexs_by_paths = {} + + if sounds is not None: + i = 0 + for tag_header in sounds.rsrc_map.data.tags: + rsrc_snd_map[tag_header.tag.path] = i + i += 1 + + i = 0 + for tag_header in self.tag_index.tag_index: + inv_snd_map[tag_header.path] = i + i += 1 + + def get_dependencies(self, meta, tag_id, tag_cls): + tag_index_array = self.tag_index.tag_index + if not self.is_indexed(tag_id): + # not indexed. get dependencies like normal + pass + elif tag_cls != "snd!": + # among the indexable tags, only sounds can have valid dependencies + return () + elif not hasattr(meta, "pitch_ranges"): + # tag is indexed AND we were provided with the indexed version + rsrc_id = meta.promotion_sound.id & 0xFFff + if rsrc_id == 0xFFFF: return () + + sounds = self.maps.get("sounds") + if sounds is None: return () + elif rsrc_id >= len(sounds.tag_index.tag_index): return () + + tag_path = sounds.tag_index.tag_index[rsrc_id].path + inv_snd_map = getattr(self, 'ce_tag_indexs_by_paths', {}) + tag_id = inv_snd_map.get(tag_path, 0xFFFF) + if tag_id >= len(tag_index_array): return () + + ref = deepcopy(meta.promotion_sound) + tag_index_ref = tag_index_array[tag_id] + ref.tag_class.data = tag_index_ref.class_1.data + ref.id = tag_index_ref.id + ref.filepath = tag_index_ref.path + + return [ref] + + if self.handler is None: return () + + dependency_cache = self.handler.tag_ref_cache.get(tag_cls) + if not dependency_cache: return () + + nodes = self.handler.get_nodes_by_paths(dependency_cache, (None, meta)) + dependencies = [] + + for node in nodes: + # need to filter to dependencies that are actually valid + tag_id = node.id & 0xFFff + if tag_id not in range(len(tag_index_array)): + continue + + tag_index_ref = tag_index_array[tag_id] + if (node.tag_class.enum_name == tag_index_ref.class_1.enum_name and + node.id == tag_index_ref.id): + dependencies.append(node) + + if tag_cls == "scnr": + # collect the tag references from the scenarios syntax data + try: + seen_tag_ids = set() + syntax_data = get_hsc_data_block(meta.script_syntax_data.data) + for node in get_script_syntax_node_tag_refs(syntax_data): + tag_index_id = node.data & 0xFFff + if (tag_index_id in range(len(tag_index_array)) and + tag_index_id not in seen_tag_ids): + seen_tag_ids.add(tag_index_id) + tag_index_ref = tag_index_array[tag_index_id] + + dependencies.append(make_dependency_os_block( + tag_index_ref.class_1.enum_name, tag_index_ref.id, + tag_index_ref.path, tag_index_ref.path_offset)) + except Exception: + pass + + return dependencies + + def setup_sbsp_pointer_converters(self): + # get the scenario meta + if self.scnr_meta is None: + print("Cannot setup sbsp pointer converters without scenario tag.") + return + + try: + metadata_range = range(self.map_header.tag_index_header_offset, + self.map_header.tag_index_header_offset + + self.map_header.tag_data_size) + invalid_bsp_tag_ids = [self.tag_index.scenario_tag_id & 0xFFff] + i = 0 + for b in self.scnr_meta.structure_bsps.STEPTREE: + bsp_id = b.structure_bsp.id & 0xFFff + + # these checks are necessary because apparently these structs + # can be super fucked up, and might not affect anything + if (bsp_id not in range(len(self.tag_index.tag_index)) or + bsp_id in invalid_bsp_tag_ids): + print("Scenario structure_bsp %s contains invalid tag_id." % i) + elif is_overlapping_ranges( + metadata_range, range(b.bsp_pointer, b.bsp_size)): + print("Scenario structure_bsp %s contains invalid pointer." % i) + elif self.tag_index.tag_index[bsp_id].id != b.structure_bsp.id: + print("Scenario structure_bsp %s contains invalid tag_id." % i) + else: + self.bsp_header_offsets[bsp_id] = b.bsp_pointer + self.bsp_magics[bsp_id] = b.bsp_magic + self.bsp_sizes[bsp_id] = b.bsp_size + + self.bsp_pointer_converters[bsp_id] = MapPointerConverter( + (b.bsp_magic, b.bsp_pointer, b.bsp_size) + ) + + i += 1 + + self.setup_sbsp_headers() + + except Exception: + print(format_exc()) + + def setup_sbsp_headers(self): + # read the sbsp headers + for tag_id, offset in self.bsp_header_offsets.items(): + header = self.sbsp_meta_header_def.build( + rawdata=self.map_data, offset=offset) + + if header.sig != header.get_desc("DEFAULT", "sig"): + print("Sbsp header is invalid for '%s'" % + self.tag_index.tag_index[tag_id].path) + self.bsp_headers[tag_id] = header + self.tag_index.tag_index[tag_id].meta_offset = header.meta_pointer + + def setup_rawdata_pages(self): + tag_index = self.tag_index + + last_bsp_end = 0 + # calculate the start of the rawdata section + for tag_id in self.bsp_headers: + bsp_end = self.bsp_header_offsets[tag_id] + self.bsp_sizes[tag_id] + if last_bsp_end < bsp_end: + last_bsp_end = bsp_end + + # add the rawdata section + self.map_pointer_converter.add_page_info( + last_bsp_end, last_bsp_end, + tag_index.model_data_offset - last_bsp_end, + ) + + # add the model data section + if hasattr(tag_index, "model_data_size"): + # PC tag index + self.map_pointer_converter.add_page_info( + 0, tag_index.model_data_offset, + tag_index.model_data_size, + ) + else: + # XBOX tag index + self.map_pointer_converter.add_page_info( + 0, tag_index.model_data_offset, + (self.map_header.tag_index_header_offset - + tag_index.model_data_offset), + ) + + def load_map(self, map_path, **kwargs): + HaloMap.load_map(self, map_path, **kwargs) + + tag_index = self.tag_index + tag_index_array = tag_index.tag_index + + # cache the original paths BEFORE running basic deprotection + self.cache_original_tag_paths() + + # make all contents of the map parseable + self.basic_deprotection() + + self.tag_index_manager = TagIndexManager(tag_index_array) + + # add the tag data section + self.map_pointer_converter.add_page_info( + self.index_magic, self.map_header.tag_index_header_offset, + self.map_header.tag_data_size + ) + + # cache the scenario meta + try: + self.scnr_meta = self.get_meta(self.tag_index.scenario_tag_id) + if self.scnr_meta is None: + print("Could not read scenario tag") + except Exception: + print(format_exc()) + print("Could not read scenario tag") + + self.setup_sbsp_pointer_converters() + self.setup_rawdata_pages() + + # get the globals meta + try: + self.matg_meta = self.get_meta(self.globals_tag_id) + if self.matg_meta is None: + print("Could not read globals tag") + except Exception: + print(format_exc()) + print("Could not read globals tag") + + if self.map_name == "sounds": + for halo_map in self.maps.values(): + if hasattr(halo_map, "ensure_sound_maps_valid"): + halo_map.ensure_sound_maps_valid() + + self.clear_map_cache() + + if self.resources_maps_mismatched and kwargs.get("unlink_mismatched_resources", True): + # this map reference different resource maps depending on what + # folder its located in. we need to ignore any resource maps + # passed in unless they're in the same folder as this map. + print("Unlinking potentially incompatible resource maps from %s" % + self.map_name + ) + self.maps = {} + + def get_meta(self, tag_id, reextract=False, ignore_rsrc_sounds=False, **kw): + ''' + Takes a tag reference id as the sole argument. + Returns that tags meta data as a parsed block. + ''' + if tag_id is None: + return + + tag_index_ref = self.tag_index_manager.get_tag_index_ref(tag_id) + if tag_index_ref is None: + return + + # if we are given a 32bit tag id, mask it off + tag_id &= 0xFFFF + magic = self.map_magic + engine = self.engine + map_data = self.map_data + + tag_cls = None + is_scenario = (tag_id == (self.tag_index.scenario_tag_id & 0xFFFF)) + if is_scenario: + tag_cls = "scnr" + elif tag_index_ref.class_1.enum_name not in ("", "NONE"): + tag_cls = int_to_fourcc(tag_index_ref.class_1.data) + + # if we dont have a defintion for this tag_cls, then return nothing + if self.get_meta_descriptor(tag_cls) is None: + return + + self.ensure_sound_maps_valid() + pointer_converter = self.bsp_pointer_converters.get( + tag_id, self.map_pointer_converter) + offset = tag_index_ref.meta_offset + + if tag_cls is None: + # couldn't determine the tag class + return + elif self.is_indexed(tag_id) and ( + tag_cls != "snd!" or not ignore_rsrc_sounds): + # tag exists in a resource cache + tag_id = offset + + rsrc_map = None + if tag_cls == "snd!": + if "sounds" in self.maps: + rsrc_map = self.maps["sounds"] + sound_mapping = self.ce_rsrc_sound_indexes_by_path + tag_path = tag_index_ref.path + if sound_mapping is None or tag_path not in sound_mapping: + return + + tag_id = sound_mapping[tag_path]//2 + + elif tag_cls == "bitm": + if "bitmaps" in self.maps: + rsrc_map = self.maps["bitmaps"] + tag_id = tag_id//2 + + elif "loc" in self.maps: + rsrc_map = self.maps["loc"] + # this resource tag COULD be in a yelo loc.map, which + # means we will need to set its tag class to what this + # map specifies it as or else the resource map wont + # know what type of tag to extract it as. + rsrc_map.tag_index.tag_index[tag_id].class_1.set_to( + tag_index_ref.class_1.enum_name) + + if rsrc_map is None: + return + + meta = rsrc_map.get_meta(tag_id, **kw) + snd_stub = None + if tag_cls == "snd!": + # while the sound samples and complete tag are in the + # resource map, the metadata for the body of the sound + # tag is in the main map. Need to copy its values into + # the resource map sound tag we extracted. + try: + # read the meta data from the map + with FieldType.force_little: + snd_stub = snd__meta_stub_blockdef.build( + rawdata=map_data, + offset=pointer_converter.v_ptr_to_f_ptr(offset), + tag_index_manager=self.tag_index_manager) + except Exception: + print(format_exc()) + + if snd_stub: + # copy values over + for name in ( + "flags", "sound_class", "sample_rate", + "minimum_distance", "maximum_distance", + "skip_fraction", "random_pitch_bounds", + "inner_cone_angle", "outer_cone_angle", + "outer_cone_gain", "gain_modifier", + "maximum_bend_per_second", + "modifiers_when_scale_is_zero", + "modifiers_when_scale_is_one", + "encoding", "compression", "promotion_sound", + "promotion_count", "max_play_length", + ): + setattr(meta, name, getattr(snd_stub, name)) + + return meta + elif not reextract: + if is_scenario and self.scnr_meta: + return self.scnr_meta + elif tag_cls == "matg" and self.matg_meta: + return self.matg_meta + + force_parsing_rsrc = False + if tag_cls in ("antr", "magy") and not kw.get("disable_tag_cleaning"): + force_parsing_rsrc = True + + desc = self.get_meta_descriptor(tag_cls) + block = [None] + try: + # read the meta data from the map + with FieldType.force_little: + desc['TYPE'].parser( + desc, parent=block, attr_index=0, rawdata=map_data, + map_pointer_converter=pointer_converter, + offset=pointer_converter.v_ptr_to_f_ptr(offset), + tag_index_manager=self.tag_index_manager, + safe_mode=(self.safe_mode and not kw.get("disable_safe_mode")), + parsing_resource=force_parsing_rsrc) + except Exception: + print(format_exc()) + if not kw.get("allow_corrupt"): + return + + meta = block[0] + try: + # TODO: remove this dirty-ass hack + if tag_cls == "bitm" and get_is_xbox_map(engine): + for bitmap in meta.bitmaps.STEPTREE: + # make sure to set this for all xbox bitmaps + # so they can be interpreted properly + bitmap.base_address = 1073751810 + + self.record_map_cache_read(tag_id, 0) + if self.map_cache_over_limit(): + self.clear_map_cache() + + if not kw.get("ignore_rawdata", False): + self.inject_rawdata(meta, tag_cls, tag_index_ref) + except Exception: + print(format_exc()) + if not kw.get("allow_corrupt"): + meta = None + + if not kw.get("disable_tag_cleaning"): + try: + self.clean_tag_meta(meta, tag_id, tag_cls) + except Exception: + print(format_exc()) + + return meta + + def clean_tag_meta(self, meta, tag_id, tag_cls): + tag_index_array = self.tag_index.tag_index + + if tag_cls in ("antr", "magy"): + highest_valid = -1 + found_valid = False + animations = meta.animations.STEPTREE + main_node_count = animations[0].node_count if animations else 0 + main_node_list_checksum = animations[0].node_list_checksum if animations else 0 + + permutation_chains = {} + for i in range(len(animations)): + if i in permutation_chains: + continue + + permutation_chains[i] = i + next_anim = animations[i].next_animation + while (next_anim in range(len(animations)) and + next_anim not in permutation_chains): + permutation_chains[next_anim] = i + next_anim = animations[next_anim].next_animation + + anims_to_remove = [] + max_anim_count = get_block_max(meta.animations) + for i in range(len(animations)): + if i >= max_anim_count: + break + + anim = animations[i] + valid = is_valid_ascii_name_str(anim.name) + + trans_int = anim.trans_flags0 + (anim.trans_flags1 << 32) + rot_int = anim.rot_flags0 + (anim.rot_flags1 << 32) + scale_int = anim.scale_flags0 + (anim.scale_flags1 << 32) + + trans_flags = (bool(trans_int & (1 << i)) + for i in range(anim.node_count)) + rot_flags = (bool(rot_int & (1 << i)) + for i in range(anim.node_count)) + scale_flags = (bool(scale_int & (1 << i)) + for i in range(anim.node_count)) + + expected_frame_size = (12 * sum(trans_flags) + + 8 * sum(rot_flags) + + 4 * sum(scale_flags)) + expected_frame_info_size = {1: 8, 2: 12, 3: 16}.get( + anim.frame_info_type.data, 0) * anim.frame_count + expected_frame_data_size = expected_frame_size * anim.frame_count + expected_default_data_size = ( + anim.node_count * (12 + 8 + 4) - anim.frame_size) + + if anim.frame_count == 0: + expected_default_data_size = 0 + + if (anim.type.enum_name == "" or + anim.frame_info_type.enum_name == ""): + valid = False + elif (anim.node_count != main_node_count or + anim.node_count not in range(1, 65)): + valid = False + elif anim.first_permutation_index != permutation_chains[i]: + valid = False + elif not anim.flags.compressed_data: + if anim.default_data.size < expected_default_data_size: + valid = False + elif anim.frame_info.size < expected_frame_info_size: + valid = False + elif anim.frame_data.size < expected_frame_data_size: + valid = False + elif anim.frame_size != expected_frame_size: + valid = False + elif anim.offset_to_compressed_data >= anim.frame_data.size: + valid = False + + if valid: + highest_valid = i + if not found_valid: + main_node_count = anim.node_count + main_node_list_checksum = anim.node_list_checksum + found_valid = True + else: + # delete the animation info + anims_to_remove.append(i) + + # make sure all animations have the same node count, checksum, and a name + for i in anims_to_remove: + animations.pop(i) + animations.insert(i) + animations[i].node_count = main_node_count + animations[i].node_list_checksum = main_node_list_checksum + animations[i].name = "REMOVED_%s" % i + + # remove the highest invalid animations + if highest_valid + 1 < len(animations): + del animations[highest_valid + 1: ] + + # inject the animation data for all remaining animations since + # it's not safe to try and read it all at parse time + for anim in animations: + for block in (anim.default_data, anim.frame_info, anim.frame_data): + if not block.size: + continue + + file_ptr = self.map_pointer_converter.v_ptr_to_f_ptr( + block.pointer) + if not block.pointer or file_ptr < 0: + file_ptr = block.raw_pointer + + if file_ptr + block.size > len(self.map_data) or file_ptr <= 0: + continue + + try: + self.map_data.seek(file_ptr) + block.data = bytearray(self.map_data.read(block.size)) + except Exception: + print("Couldn't read animation data.") + + elif tag_cls == "bitm": + bitmaps = [b for b in meta.bitmaps.STEPTREE + if "dxt" in b.format.enum_name] + # correct mipmap count on xbox dxt bitmaps. texels for any + # mipmaps whose dimensions are 2x2 or smaller are pruned + for bitmap in bitmaps: + # figure out largest dimension(clip to 1 to avoid log(0, 2)) + max_dim = max(1, bitmap.width, bitmap.height) + + # subtract 2 to account for width/height of 1 or 2 not having mips + maxmips = int(max(0, math.log(max_dim, 2) - 2)) + + # clip mipmap count to max and min number that can exist + bitmap.mipmaps = max(0, min(maxmips, bitmap.mipmaps)) + + elif tag_cls in ("sbsp", "coll"): + if tag_cls == "sbsp" : + bsps = meta.collision_bsp.STEPTREE + else: + bsps = [] + for node in meta.nodes.STEPTREE: + bsps.extend(node.bsps.STEPTREE) + + for bsp in bsps: + vert_data = bsp.vertices.STEPTREE + # first 2 ints in each edge are the vert indices, and theres + # 6 int32s per edge. find the highest vert index being used + if bsp.edges.STEPTREE: + byteorder = 'big' if self.engine == "halo1anni" else 'little' + + edges = PyArray("i", bsp.edges.STEPTREE) + if byteorder != sys.byteorder: + edges.byteswap() + + max_start_vert = max(edges[0: len(edges): 6]) + max_end_vert = max(edges[1: len(edges): 6]) + else: + max_start_vert = max_end_vert = -1 + + if max_start_vert * 16 < len(vert_data): + del vert_data[(max_start_vert + 1) * 16: ] + bsp.vertices.size = max_start_vert + 1 + + elif tag_cls in ("mode", "mod2"): + used_shaders = set() + shaders = meta.shaders.STEPTREE + + for geom in meta.geometries.STEPTREE: + for part in geom.parts.STEPTREE: + if part.shader_index >= 0: + used_shaders.add(part.shader_index) + + # determine the max number of shader indices actually used + # by all the geometry parts, and reparse them with that many. + if used_shaders: + try: + reparse_reflexive( + meta.shaders, max(used_shaders) + 1, + self.map_pointer_converter, + self.map_data, self.tag_index_manager) + except Exception: + print(format_exc()) + print("Couldn't re-parse %s data." % meta.shaders) + + new_i = 0 + rebase_map = {} + new_shaders = [None] * len(used_shaders) + for i in sorted(used_shaders): + new_shaders[new_i] = shaders[i] + rebase_map[i] = new_i + new_i += 1 + + # rebase the shader indices + for geom in meta.geometries.STEPTREE: + for part in geom.parts.STEPTREE: + if part.shader_index in range(len(shaders)): + part.shader_index = rebase_map[part.shader_index] + else: + part.shader_index = -1 + + shaders[:] = new_shaders + + elif tag_cls == "scnr": + skies = meta.skies.STEPTREE + comments = meta.comments.STEPTREE + + highest_valid_sky = -1 + for i in range(len(skies)): + sky = skies[i].sky + sky_tag_index_id = sky.id & 0xFFff + if (sky_tag_index_id not in range(len(tag_index_array)) or + tag_index_array[sky_tag_index_id].id != sky.id): + # invalid sky + sky.id = 0xFFffFFff + sky.filepath = "" + sky.tag_class.set_to("sky") + else: + highest_valid_sky = i + + # clear the highest invalid skies + del skies[highest_valid_sky + 1: ] + + # clear the child scenarios since they aren't used + del meta.child_scenarios.STEPTREE[:] + + # determine if there are any fucked up comments + comments_to_keep = set() + for i in range(len(comments)): + comment = comments[i] + if max(max(comment.position), abs(min(comment.position))) > 5000: + # check if the position is outside halos max world bounds + continue + + if not (comment.comment_data.data and + is_valid_ascii_name_str(comment.comment_data.data)): + comments_to_keep.add(i) + + if len(comments_to_keep) != len(comments): + # clean up any fucked up comments + comments[:] = [comments[i] for i in sorted(comments_to_keep)] + + syntax_data = get_hsc_data_block(meta.script_syntax_data.data) + script_nodes_modified = False + + # lets not use magic numbers here + _, script_object_types = get_script_types(self.engine) + biped_node_enum = script_object_types.index("actor_type") + + # clean up any fucked up palettes + for pal_block, inst_block in ( + (meta.sceneries_palette, meta.sceneries), + (meta.bipeds_palette, meta.bipeds), + (meta.vehicles_palette, meta.vehicles), + (meta.equipments_palette, meta.equipments), + (meta.weapons_palette, meta.weapons), + (meta.machines_palette, meta.machines), + (meta.controls_palette, meta.controls), + (meta.light_fixtures_palette, meta.light_fixtures), + (meta.sound_sceneries_palette, meta.sound_sceneries), + ): + palette, instances = pal_block.STEPTREE, inst_block.STEPTREE + + used_pal_indices = set(inst.type for inst in instances + if inst.type >= 0) + script_nodes_to_modify = set() + + if inst_block.NAME == "bipeds": + # determine which palette indices are used by script data + for i in range(len(syntax_data.nodes)): + node = syntax_data.nodes[i] + if node.type == biped_node_enum and not(node.flags & HSC_IS_SCRIPT_OR_GLOBAL): + script_nodes_to_modify.add(i) + used_pal_indices.add(node.data & 0xFFff) + + # determine the max number of palette indices actually used by all + # the object instances, and reparse the palette with that many. + if used_pal_indices: + try: + reparse_reflexive( + pal_block, max(used_pal_indices) + 1, + self.map_pointer_converter, + self.map_data, self.tag_index_manager) + except Exception: + print(format_exc()) + print("Couldn't re-parse %s data." % pal_block.NAME) + + # figure out what to rebase the palette swatch indices to + new_i = 0 + rebase_map = {} + new_palette = [None] * len(used_pal_indices) + for i in sorted(used_pal_indices): + new_palette[new_i] = palette[i] + rebase_map[i] = new_i + new_i += 1 + + # rebase the palette indices of the instances and + # move the palette swatches into their new indices + for inst in instances: + if inst.type in range(len(instances)): + inst.type = rebase_map[inst.type] + else: + inst.type = -1 + + palette[:] = new_palette + + # modify the script syntax nodes that need to be + for i in script_nodes_to_modify: + node = syntax_data.nodes[i] + salt = node.data & 0xFFff0000 + cur_index = node.data & 0xFFff + new_index = rebase_map[cur_index] + if cur_index != new_index: + script_nodes_modified = True + node.data = salt + new_index + + # replace the script syntax data + if script_nodes_modified: + with FieldType.force_little: + new_script_data = syntax_data.serialize() + meta.script_syntax_data.data[: len(new_script_data)] = new_script_data + meta.script_syntax_data.size = len(meta.script_syntax_data.data) + + elif tag_cls in ("tagc", "Soul"): + tag_collection = meta[0].STEPTREE + highest_valid = -1 + for i in range(len(tag_collection)): + tag_ref = tag_collection[i][0] + if tag_cls == "Soul" and tag_ref.tag_class.enum_name != "ui_widget_definition": + continue + elif (tag_ref.id & 0xFFff) not in range(len(tag_index_array)): + continue + elif tag_index_array[tag_ref.id & 0xFFff].id != tag_ref.id: + continue + + highest_valid = i + + # clear the highest invalid entries + del tag_collection[highest_valid + 1: ] + + def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): + magic = self.map_magic + engine = self.engine + map_data = self.map_data + tag_index = self.tag_index + byteswap = kwargs.get("byteswap", True) + + predicted_resources = [] + + if hasattr(meta, "obje_attrs"): + predicted_resources.append(meta.obje_attrs.predicted_resources) + + # fix the change colors permutations + for change_color in meta.obje_attrs.change_colors.STEPTREE: + cutoff = 0 + for perm in change_color.permutations.STEPTREE: + perm.weight, cutoff = perm.weight - cutoff, perm.weight + + if tag_cls == "actv": + # multiply grenade velocity by 30 + meta.grenades.grenade_velocity *= 30 + + elif tag_cls in ("antr", "magy"): + # try to fix HEK+ extraction bug + for obj in meta.objects.STEPTREE: + for enum in (obj.function, obj.function_controls): + uint16_data = enum.data & 0xFFff + if (uint16_data & 0xFF00 and not uint16_data & 0xFF): + # higher bits are set than lower. this is likely + # a HEK plus extraction bug and should be fixed + uint16_data = ((uint16_data>>8) | (uint16_data<<8)) & 0xFFff + enum.data = uint16_data - ( + 0 if uint16_data < 0x8000 else 0x10000 + ) + + # byteswap animation data + for anim in meta.animations.STEPTREE: + if not byteswap: break + byteswap_animation(anim) + + elif tag_cls in ("bitm", "snd!"): + meta = Halo1RsrcMap.meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs) + + elif tag_cls == "cdmg": + # divide camera shaking wobble period by 30 + meta.camera_shaking.wobble_function_period /= 30 + + elif tag_cls == "coll": + # byteswap the raw bsp collision data + for node in meta.nodes.STEPTREE: + for perm_bsp in node.bsps.STEPTREE: + if not byteswap: break + byteswap_coll_bsp(perm_bsp) + + elif tag_cls == "effe": + # mask away the meta-only flags + # NOTE: xbox has a cache flag in the 2nd + # bit, so it should be masked out too. + meta.flags.data &= (1 if "xbox" in engine else 3) + + for event in meta.events.STEPTREE: + # tool exceptions if any parts reference a damage effect + # tag type, but have an empty filepath for the reference + parts = event.parts.STEPTREE + for i in range(len(parts) - 1, -1, -1): + part = parts[i] + if (part.type.tag_class.enum_name == "damage_effect" and + not part.type.filepath): + parts.pop(i) + + elif tag_cls == "jpt!": + # camera shaking wobble period by 30 + meta.camera_shaking.wobble_function_period /= 30 + + elif tag_cls == "glw!": + # increment enumerators properly + for b in (meta.particle_rotational_velocity, + meta.effect_rotational_velocity, + meta.effect_translational_velocity, + meta.particle_distance_to_object, + meta.particle_size, + meta.particle_color): + b.attachment.data += 1 + + elif tag_cls == "lens": + # DON'T multiply corona rotation by pi/180 + # reminder that this is not supposed to be changed + + if meta.corona_rotation.function_scale == 360.0: + # fix a really old bug(i think its the + # reason the above comment was created) + meta.corona_rotation.function_scale = 0.0 + + elif tag_cls == "ligh": + # divide light time by 30 + meta.effect_parameters.duration /= 30 + + elif tag_cls == "matg": + # tool will fail to compile any maps if the + # multiplayer_info or falling_damage is blank + + # make sure there is multiplayer info. + multiplayer_info = meta.multiplayer_informations.STEPTREE + if not len(multiplayer_info): + multiplayer_info.append() + + # make sure there is falling damage info. + falling_damages = meta.falling_damages.STEPTREE + if not len(falling_damages): + falling_damages.append() + + elif tag_cls == "metr": + # The meter bitmaps can literally point to not + # only the wrong tag, but the wrong TYPE of tag. + # Since dependencies in meter tags are useless, we null them out. + meta.stencil_bitmap.filepath = meta.source_bitmap.filepath = '' + + elif tag_cls in ("mode", "mod2"): + if engine in ("halo1yelo", "halo1ce", "halo1pc", "halo1vap", "halo1mcc", + "halo1anni", "halo1pcdemo", "stubbspc", "stubbspc64bit"): + # model_magic seems to be the same for all pc maps + verts_start = tag_index.model_data_offset + tris_start = verts_start + tag_index.index_parts_offset + model_magic = None + else: + model_magic = magic + + # need to unset this flag, as it forces map-compile-time processing + # to occur on the model's vertices, which shouldn't be done twice. + meta.flags.blend_shared_normals = False + + # lod cutoffs are swapped between tag and cache form + cutoffs = (meta.superlow_lod_cutoff, meta.low_lod_cutoff, + meta.high_lod_cutoff, meta.superhigh_lod_cutoff) + meta.superlow_lod_cutoff = cutoffs[3] + meta.low_lod_cutoff = cutoffs[2] + meta.high_lod_cutoff = cutoffs[1] + meta.superhigh_lod_cutoff = cutoffs[0] + + # localize the global markers + # ensure all local marker arrays are empty + for region in meta.regions.STEPTREE: + for perm in region.permutations.STEPTREE: + del perm.local_markers.STEPTREE[:] + + for g_marker in meta.markers.STEPTREE: + for g_marker_inst in g_marker.marker_instances.STEPTREE: + try: + region = meta.regions.STEPTREE[g_marker_inst.region_index] + except IndexError: + print("Model marker instance for", g_marker.name, "has invalid region index", g_marker_inst.region_index, "and is skipped.") + continue + + try: + perm = region.permutations.STEPTREE[g_marker_inst.permutation_index] + except IndexError: + print("Model marker instance for", g_marker.name, "has invalid permutation index", g_marker_inst.permutation_index, "and is skipped.") + continue + + # make a new local marker + perm.local_markers.STEPTREE.append() + l_marker = perm.local_markers.STEPTREE[-1] + + # copy the global marker into the local + l_marker.name = g_marker.name + l_marker.node_index = g_marker_inst.node_index + l_marker.translation[:] = g_marker_inst.translation[:] + l_marker.rotation[:] = g_marker_inst.rotation[:] + + # clear the global markers + del meta.markers.STEPTREE[:] + + # grab vertices and indices from the map + for geom in meta.geometries.STEPTREE: + for part in geom.parts.STEPTREE: + tris_block = part.triangles + info = part.model_meta_info + + if info.vertex_type.enum_name == "model_comp_verts": + verts_block = part.compressed_vertices + byteswap_verts = byteswap_comp_verts + vert_size = 32 + elif info.vertex_type.enum_name == "model_uncomp_verts": + verts_block = part.uncompressed_vertices + byteswap_verts = byteswap_uncomp_verts + vert_size = 68 + else: + print("Error: Unknown vertex type in model: %s" % info.vertex_type.data) + continue + + if info.index_type.enum_name != "triangle_strip": + print("Error: Unknown index type in model: %s" % info.index_type.data) + continue + + # null out certain things in the part + part.centroid_primary_node = 0 + part.centroid_secondary_node = 0 + part.centroid_primary_weight = 0.0 + part.centroid_secondary_weight = 0.0 + + # make the new blocks to hold the raw data + verts_block.STEPTREE = raw_block_def.build() + tris_block.STEPTREE = raw_block_def.build() + + # read the offsets of the vertices and indices from the map + if model_magic is None: + verts_off = verts_start + info.vertices_offset + tris_off = tris_start + info.indices_offset + else: + map_data.seek( + info.vertices_reflexive_offset + 4 - model_magic) + verts_off = unpack( + " 1 else u)*32767), + int((-1 if v < -1 else 1 if v > 1 else v)*32767), + ) + elif (kwargs.get("generate_uncomp_verts") and + lm_vert_type == "sbsp_comp_lightmap_verts" + ): + # generate uncompressed lightmap verts from compressed + u_buffer += bytearray(u_lm_verts_size) + for u_off, c_off in lm_vert_offs: + n, u, v = comp_lm_vert_unpacker(c_buffer, c_off) + uncomp_lm_vert_packer( + u_buffer, u_off, + *decomp_norm(n), u/32767, v/32767 + ) + + # need to null these or original CE sapien could crash + mat.unknown_meta_offset0 = mat.vertices_meta_offset = 0 + mat.unknown_meta_offset1 = mat.lightmap_vertices_meta_offset = 0 + + # set these to the correct vertex types based on what we have + vert_type_str = ( + "sbsp_comp_%s_verts" + if c_verts_size and c_lm_verts_size else + "sbsp_uncomp_%s_verts" + ) + mat.vertex_type.set_to(vert_type_str % "material") + mat.lightmap_vertex_type.set_to(vert_type_str % "lightmap") + + mat.uncompressed_vertices.STEPTREE = u_buffer + mat.compressed_vertices.STEPTREE = c_buffer + + elif tag_cls == "scnr": + # need to remove the references to the child scenarios + del meta.child_scenarios.STEPTREE[:] + + # set the bsp pointers and stuff to 0 + for b in meta.structure_bsps.STEPTREE: + b.bsp_pointer = b.bsp_size = b.bsp_magic = 0 + + predicted_resources.append(meta.predicted_resources) + + # byteswap the script syntax data + if byteswap: + byteswap_scnr_script_syntax_data(meta) + + # rename duplicate stuff that causes errors when compiling scripts + if kwargs.get("rename_scnr_dups", False): + string_data = meta.script_string_data.data.decode("latin-1") + syntax_data = get_hsc_data_block(raw_syntax_data=meta.script_syntax_data.data) + + # lets not use magic numbers here + _, script_object_types = get_script_types(engine) + trigger_volume_enum = script_object_types.index("trigger_volume") + + # NOTE: For a list of all the script object types + # with their corrosponding enum value, check + # reclaimer.halo_script.hsc.get_script_types + keep_these = {script_object_types.index(typ): set() for typ in + SCRIPT_OBJECT_TYPES_TO_SCENARIO_REFLEXIVES} + + # don't de-duplicate trigger volumes + for b in meta.bsp_switch_trigger_volumes.STEPTREE: + keep_these[trigger_volume_enum].add(b.trigger_volume) + + # for everything we're keeping, clear the upper 16bits of the data + for i in range(min(syntax_data.last_node, len(syntax_data.nodes))): + node = syntax_data.nodes[i] + if node.type in keep_these: + keep_these[node.type].add(node.data & 0xFFff) + + # for everything else, rename duplicates + for script_object_type, reflexive_name in \ + SCRIPT_OBJECT_TYPES_TO_SCENARIO_REFLEXIVES.items(): + + script_object_type_enum = script_object_types.index(script_object_type) + keep = keep_these[script_object_type_enum] + reflexive = meta[reflexive_name].STEPTREE + counts = {b.name.lower(): 0 for b in reflexive} + for b in reflexive: + counts[b.name.lower()] += 1 + + for i in range(len(reflexive)): + name = reflexive[i].name.lower() + if counts[name] > 1 and i not in keep: + reflexive[i].name = ("DUP%s~%s" % (i, name))[: 31] + + # null tag refs after we're done with them + clean_script_syntax_nodes(syntax_data, engine) + + # decompile scripts and put them in the source_files array so + # sapien can recompile them when it opens an extracted scenario + source_files = meta.source_files.STEPTREE + del source_files[:] + script_sources, global_sources = extract_scripts( + engine=engine, tagdata=meta, add_comments=False, minify=True + ) + i = 0 + for source in (*script_sources, *global_sources): + source_files.append() + source_files[-1].source_name = "decompiled_%s.hsc" % i + source_files[-1].source.data = source.encode('latin-1') + i += 1 + + # divide the cutscene times by 30(they're in ticks) and + # subtract the fade-in time from the up_time(normally added + # together as a total up-time in maps, but not in tag form) + for b in meta.cutscene_titles.STEPTREE: + b.up_time = max(b.up_time - b.fade_in_time, 0.0) + + b.fade_in_time /= 30 + b.fade_out_time /= 30 + b.up_time /= 30 + + elif tag_cls == "shpp": + predicted_resources.append(meta.predicted_resources) + + elif tag_cls == "shpg": + shpg_attrs = meta.shpg_attrs + + # copy all merged values into their respective reflexives + for b in shpg_attrs.merged_values.STEPTREE: + typ = b.value_type.enum_name + cnt = b.value_count + if typ == "boolean": array = shpg_attrs.booleans.STEPTREE + elif typ == "integer": array = shpg_attrs.integers.STEPTREE + elif typ == "color": array = shpg_attrs.colors.STEPTREE + elif typ == "bitmap": array = shpg_attrs.bitmaps.STEPTREE + elif typ != "float": continue # unknown type + elif cnt == 1: array = shpg_attrs.floats_1d.STEPTREE + elif cnt == 2: array = shpg_attrs.floats_2d.STEPTREE + elif cnt == 3: array = shpg_attrs.floats_3d.STEPTREE + elif cnt == 4: array = shpg_attrs.floats_4d.STEPTREE + else: continue # unknown float type + + array.append() + new_b = array[-1] + new_b.value_name = b.value_name + values = b.values.u_node + + if typ == "bitmap": + new_b.bitmap = b.bitmap + new_b.bitmap_index = values.bitmap_index + continue + + new_b.runtime_value = b.runtime_value + new_b.animation_function = b.animation_function + new_b.animation_flags = b.animation_flags + new_b.animation_duration = b.animation_duration + new_b.animation_rate = b.animation_rate + + if typ == "boolean": + new_b.flags = b.flags + new_b.value = values.value + else: + new_b.value_lower_bound = values.value_lower_bound + new_b.value_upper_bound = values.value_upper_bound + + # clear the merged values reflexive + del shpg_attrs.merged_values.STEPTREE[:] + + elif tag_cls == "soso": + # set the mcc multipurpose_map_uses_og_xbox_channel_order flag + if "xbox" in engine or "stubbs" in engine or engine == "shadowrun_proto": + meta.soso_attrs.model_shader.flags.data |= 1<<6 + + elif tag_cls == "weap": + # try to fix HEK+ extraction bug + uint16_data = (meta.weap_attrs.aiming.zoom_levels & 0xFFff) + if (uint16_data & 0xFF00 and not uint16_data & 0xFF): + # higher bits are set than lower. this is likely + # a HEK plus extraction bug and should be fixed + uint16_data = ((uint16_data>>8) | (uint16_data<<8)) & 0xFFff + meta.weap_attrs.aiming.zoom_levels = uint16_data - ( + 0 if uint16_data < 0x8000 else 0x10000 + ) + + predicted_resources.append(meta.weap_attrs.predicted_resources) + + # remove any predicted resources + for pr in predicted_resources: + del pr.STEPTREE[:] + + return meta + + def get_resource_map_paths(self, maps_dir=""): + if self.is_resource or not self.resource_maps_folder: + return {} + + map_paths = { + name: None for name in ( + *(["bitmaps"] if self.uses_bitmaps_map else []), + *(["sounds"] if self.uses_sounds_map else []), + *(["loc"] if self.uses_loc_map else []), + ) + } + + name_str = self.resource_map_prefix + "%s.map" + maps_dir = ( + Path(maps_dir) if not is_path_empty(maps_dir) else + self.resource_maps_folder + ) + + # detect the map paths for the resource maps + if maps_dir: + for map_name in sorted(map_paths.keys()): + map_path = maps_dir.joinpath(name_str % map_name) + if self.maps.get(map_name) is not None: + map_paths[map_name] = self.maps[map_name].filepath + elif map_path.is_file(): + map_paths[map_name] = map_path + + return map_paths + + def generate_map_info_string(self): + string = HaloMap.generate_map_info_string(self) + index, header = self.tag_index, self.map_header + + if self.engine == "halo1mcc": + string += """\n Calculated information: + use bitmaps map == %s + use sounds map == %s + no remastered sync == %s""" % ( + bool(header.mcc_flags.use_bitmaps_map), + bool(header.mcc_flags.use_sounds_map), + bool(header.mcc_flags.disable_remastered_sync), + ) + + string += """ + +Calculated information: + index magic == %s + map magic == %s + +Tag index: + tag count == %s + scenario tag id == %s + index array pointer == %s non-magic == %s + meta data length == %s + vertex parts count == %s + index parts count == %s""" % ( + self.index_magic, self.map_magic, + index.tag_count, index.scenario_tag_id & 0xFFff, + index.tag_index_offset, index.tag_index_offset - self.map_magic, + header.tag_data_size, + index.vertex_parts_count, index.index_parts_count) + + if hasattr(index, "model_data_size"): + string += """ + vertex data pointer == %s + index data pointer == %s + index data size == %s + model data size == %s""" % ( + index.model_data_offset, + index.index_parts_offset, + index.model_data_size - index.index_parts_offset, + index.model_data_size + ) + else: + string += """ + vertex refs pointer == %s non-magic == %s + index refs pointer == %s non-magic == %s""" % ( + index.model_data_offset, index.model_data_offset - self.map_magic, + index.index_parts_offset, index.index_parts_offset - self.map_magic, + ) + + string += "\n\nSbsp magic and headers:\n" + for tag_id in self.bsp_magics: + header = self.bsp_headers.get(tag_id) + if header is None: continue + + magic = self.bsp_magics[tag_id] + offset = self.bsp_header_offsets[tag_id] + string += """ %s.structure_scenario_bsp + bsp base pointer == %s + bsp magic == %s + bsp size == %s + bsp metadata pointer == %s non-magic == %s\n""" % ( + index.tag_index[tag_id].path, offset, + magic, self.bsp_sizes[tag_id], header.meta_pointer, + header.meta_pointer + offset - magic + ) + if self.engine in ("halo1mcc", "halo1anni"): + string += """\ + render verts size == %s + render verts pointer == %s\n""" % ( + header.uncompressed_render_vertices_size, + header.uncompressed_render_vertices_pointer, + ) + else: + string += """\ + uncomp mats count == %s + uncomp mats pointer == %s non-magic == %s + comp mats count == %s + comp mats pointer == %s non-magic == %s\n""" % ( + header.uncompressed_lightmap_materials_count, + header.uncompressed_lightmap_materials_pointer, + header.uncompressed_lightmap_materials_pointer + offset - magic, + header.compressed_lightmap_materials_count, + header.compressed_lightmap_materials_pointer, + header.compressed_lightmap_materials_pointer + offset - magic, + ) + + if self.engine == "halo1vap": + string += self.generate_vap_info_string() + + return string + + def generate_vap_info_string(self): + vap = self.map_header.vap_header + + return """ +VAP information: + name == %s + build date == %s + description == %s + + vap version == %s + feature level == %s + max players == %s\n""" % ( + vap.name, vap.build_date, vap.description, + vap.vap_version.enum_name, vap.feature_level.enum_name, + vap.max_players, + ) diff --git a/reclaimer/meta/halo1_rsrc_map.py b/reclaimer/meta/halo1_rsrc_map.py index f9213b06..22965a09 100644 --- a/reclaimer/meta/halo1_rsrc_map.py +++ b/reclaimer/meta/halo1_rsrc_map.py @@ -6,67 +6,548 @@ # Reclaimer is free software under the GNU General Public License v3.0. # See LICENSE for more information. # +from collections import namedtuple +from copy import deepcopy +from struct import unpack +from traceback import format_exc -from reclaimer.common_descs import * -from reclaimer.meta.objs.halo1_rsrc_map import Halo1RsrcMapTag -from supyr_struct.defs.tag_def import TagDef - - -def get(): - return halo1_rsrc_map_def - - -def tag_path_pointer(parent=None, new_value=None, **kwargs): - if parent is None: - raise KeyError() - t_head = parent.parent - if new_value is None: - return t_head.parent.parent.tag_paths_pointer + t_head.path_offset - t_head.path_offset = new_value - t_head.parent.parent.tag_paths_pointer - - -rsrc_tag = Container("tag", - BytesRaw("data", SIZE="..size", POINTER="..offset"), - CStrTagRef("path", POINTER=tag_path_pointer, MAX=768, WIDGET=EntryFrame), - ) - -tag_header = Struct("tag header", - LUInt32("path offset"), - LUInt32("size"), - LUInt32("offset"), - STEPTREE=rsrc_tag - ) - -halo1_rsrc_map_def = TagDef("halo1_rsrc_map", - LUEnum32("resource type", - 'NONE', - 'bitmaps', - 'sounds', - 'strings' - ), - LPointer32("tag paths pointer"), - LPointer32("tag headers pointer"), - LUInt32("tag count"), - Array("tags", - SIZE='.tag_count', SUB_STRUCT=tag_header, - POINTER='.tag_headers_pointer', - ), - endian="<", ext=".map", tag_cls=Halo1RsrcMapTag - ) - -lite_rsrc_tag = Container("tag", - Void("data"), - CStrLatin1("path", POINTER=tag_path_pointer), - ) -lite_tag_header = Struct("tag header", - INCLUDE=tag_header, STEPTREE=lite_rsrc_tag - ) -lite_halo1_rsrc_map_desc = dict(halo1_rsrc_map_def.descriptor) -lite_halo1_rsrc_map_desc[4] = Array("tags", - SIZE='.tag_count', SUB_STRUCT=lite_tag_header, - POINTER='.tag_headers_pointer', - ) -lite_halo1_rsrc_map_def = TagDef("lite_halo1_rsrc_map", - descriptor=lite_halo1_rsrc_map_desc, - endian="<", ext=".map", tag_cls=Halo1RsrcMapTag - ) +from reclaimer.constants import GEN_1_HALO_CUSTOM_ENGINES,\ + GEN_1_HALO_PC_ENGINES, GEN_1_HALO_GBX_ENGINES +from reclaimer import data_extraction +from reclaimer.mcc_hek.defs.bitm import bitm_def as pixel_root_subdef +from reclaimer.mcc_hek.defs.objs.bitm import MccBitmTag, HALO_P8_PALETTE +from reclaimer.stubbs.defs.objs.bitm import StubbsBitmTag, STUBBS_P8_PALETTE +from reclaimer.util import get_is_xbox_map +from reclaimer.meta.halo_map import map_header_def, tag_index_pc_def +from reclaimer.meta.halo1_rsrc_map import lite_halo1_rsrc_map_def as halo1_rsrc_map_def +from reclaimer.meta.wrappers.byteswapping import raw_block_def, byteswap_pcm16_samples +from reclaimer.meta.wrappers.map_pointer_converter import MapPointerConverter +from reclaimer.meta.wrappers.tag_index_manager import TagIndexManager +from reclaimer.meta.wrappers.halo_map import HaloMap +from reclaimer.sounds import ogg as sounds_ogg, constants as sound_const + +from supyr_struct.buffer import BytearrayBuffer, get_rawdata +from supyr_struct.field_types import FieldType + +# reassign since we only want a reference to the sub-definition +pixel_root_subdef = pixel_root_subdef.subdefs['pixel_root'] + +# this is ultra hacky, but it seems to be the only +# way to fix the tagid for the sounds resource map +sound_rsrc_id_map = { + 92: 7, # sound\sfx\impulse\impacts\smallrock + 93: 8, # sound\sfx\impulse\impacts\medrocks + 94: 9, # sound\sfx\impulse\impacts\lrgrocks + + 125: 12, # sound\sfx\impulse\impacts\metal_chips + 126: 13, # sound\sfx\impulse\impacts\metal_chip_med + + 372: 61, # sound\sfx\impulse\shellcasings\double_shell_dirt + 373: 62, # sound\sfx\impulse\shellcasings\multi_shell_dirt + 374: 63, # sound\sfx\impulse\shellcasings\single_shell_metal + 375: 64, # sound\sfx\impulse\shellcasings\double_shell_metal + 376: 65, # sound\sfx\impulse\shellcasings\multi_shell_metal + + 1545: 264, # sound\sfx\impulse\glass\glass_medium + 1546: 265, # sound\sfx\impulse\glass\glass_large + } + +DEFAULT_LOC_TAG_COUNT = 176 +DEFAULT_SOUNDS_TAG_COUNT = 376 +DEFAULT_BITMAPS_TAG_COUNT = 853 + + +# Tag classes aren't stored in the cache maps, so we need to +# have a cache of them somewhere. Might as well do it manually +loc_exts = {0:'font', 1:'font', 4:'hud_message_text', 56:'font', 58:'font'} + +bitmap_exts = ('bitmap', ) * DEFAULT_BITMAPS_TAG_COUNT +sound_exts = ('sound', ) * DEFAULT_SOUNDS_TAG_COUNT +loc_exts = tuple(loc_exts.get(i, 'unicode_string_list') + for i in range(DEFAULT_LOC_TAG_COUNT)) + + +def inject_sound_data(map_data, rsrc_data, rawdata_ref, map_magic): + if rawdata_ref.flags.data_in_resource_map: + data, ptr = rsrc_data, rawdata_ref.raw_pointer + elif rawdata_ref.pointer == 0: + data, ptr = map_data, rawdata_ref.raw_pointer + else: + data, ptr = map_data, rawdata_ref.pointer + map_magic + + if data and rawdata_ref.size: + data.seek(ptr) + rawdata_ref.data = data.read(rawdata_ref.size) + else: + # hack to ensure the size is preserved when + # we replace the rawdata with empty bytes + size = rawdata_ref.size + rawdata_ref.data = b'' + rawdata_ref.size = size + + +def uses_external_sounds(sound_meta): + for pitches in sound_meta.pitch_ranges.STEPTREE: + for perm in pitches.permutations.STEPTREE: + for b in (perm.samples, perm.mouth_data, perm.subtitle_data): + if b.flags.data_in_resource_map: + return True + return False + + +class MetaBitmTag(): + ''' + This class exists to facilitate processing bitmap tags extracted + from maps without fully converting them to tag objects first. + ''' + _fake_data_block = namedtuple('FakeDataBlock', + ("blam_header", "tagdata") + ) + def __init__(self, tagdata=None): + self.data = self._fake_data_block(None, tagdata) + + # stubed since there's nothing to calculate here + def calc_internal_data(self): pass + + @property + def pixel_root_definition(self): return pixel_root_subdef + + +class MetaHaloBitmTag(MetaBitmTag, MccBitmTag): + @property + def p8_palette(self): return HALO_P8_PALETTE + +class MetaStubbsBitmTag(MetaBitmTag, StubbsBitmTag): + @property + def p8_palette(self): return STUBBS_P8_PALETTE + + +class Halo1RsrcMap(HaloMap): + '''Generation 1 resource map''' + + # the original resource map header + rsrc_map = None + + tag_classes = None + tag_headers = None + + tag_defs_module = "reclaimer.hek.defs" + tag_classes_to_load = ("bitm", "snd!", "font", "hmt ", "ustr") + + data_extractors = data_extraction.h1_data_extractors + + def __init__(self, maps=None): + HaloMap.__init__(self, maps) + self.setup_tag_headers() + + def load_map(self, map_path, **kwargs): + map_data = get_rawdata(filepath=map_path, writable=False) + + resource_type = unpack(" Date: Wed, 27 Mar 2024 21:51:35 -0500 Subject: [PATCH 48/51] Fix previous commit --- reclaimer/meta/halo1_map.py | 1893 ++++----------------- reclaimer/meta/halo1_rsrc_map.py | 607 +------ reclaimer/meta/wrappers/halo1_map.py | 15 + reclaimer/meta/wrappers/halo1_rsrc_map.py | 16 +- 4 files changed, 368 insertions(+), 2163 deletions(-) diff --git a/reclaimer/meta/halo1_map.py b/reclaimer/meta/halo1_map.py index 4dccce6f..d40e6335 100644 --- a/reclaimer/meta/halo1_map.py +++ b/reclaimer/meta/halo1_map.py @@ -7,1607 +7,292 @@ # See LICENSE for more information. # -import os -import sys - -from array import array as PyArray -from copy import deepcopy -from math import pi, log -from pathlib import Path -from struct import unpack, unpack_from, pack_into -from traceback import format_exc -from types import MethodType - -from supyr_struct.buffer import BytearrayBuffer -from supyr_struct.field_types import FieldType -from supyr_struct.defs.frozen_dict import FrozenDict - -from reclaimer.halo_script.hsc_decompilation import extract_scripts -from reclaimer.halo_script.hsc import get_hsc_data_block,\ - get_script_syntax_node_tag_refs, clean_script_syntax_nodes,\ - get_script_types, HSC_IS_SCRIPT_OR_GLOBAL,\ - SCRIPT_OBJECT_TYPES_TO_SCENARIO_REFLEXIVES -from reclaimer.common_descs import make_dependency_os_block -from reclaimer.hek.defs.snd_ import snd__meta_stub_blockdef -from reclaimer.hek.defs.sbsp import sbsp_meta_header_def -from reclaimer.hek.handler import HaloHandler -from reclaimer.os_v4_hek.defs.coll import fast_coll_def -from reclaimer.os_v4_hek.defs.sbsp import fast_sbsp_def -from reclaimer.meta.wrappers.byteswapping import raw_block_def, byteswap_animation,\ - byteswap_uncomp_verts, byteswap_comp_verts, byteswap_tris,\ - byteswap_coll_bsp, byteswap_sbsp_meta, byteswap_scnr_script_syntax_data,\ - byteswap_pcm16_samples -from reclaimer.meta.wrappers.halo_map import HaloMap -from reclaimer.meta.wrappers.halo1_rsrc_map import Halo1RsrcMap, inject_sound_data, get_is_xbox_map -from reclaimer.meta.wrappers.map_pointer_converter import MapPointerConverter -from reclaimer.meta.wrappers.tag_index_manager import TagIndexManager -from reclaimer import data_extraction -from reclaimer.constants import tag_class_fcc_to_ext, GEN_1_HALO_CUSTOM_ENGINES -from reclaimer.util.compression import compress_normal32, decompress_normal32 -from reclaimer.util import is_overlapping_ranges, is_valid_ascii_name_str,\ - int_to_fourcc, get_block_max - -from supyr_struct.util import is_path_empty - - -__all__ = ("Halo1Map", "Halo1RsrcMap") - - -def reparse_reflexive(block, new_size, pointer_converter, - map_data, tag_index_manager): - steptree = block.STEPTREE - file_ptr = pointer_converter.v_ptr_to_f_ptr(block.pointer) - - if (file_ptr + new_size * steptree.get_desc("SIZE", 0) > len(map_data) or - file_ptr <= 0): - return - - # read the palette array from the map - with FieldType.force_little: - del steptree[:] - steptree.extend(new_size) - steptree.TYPE.parser( - steptree.desc, node=steptree, - map_pointer_converter=pointer_converter, - rawdata=map_data, offset=file_ptr, - safe_mode=False, parsing_resource=False, - tag_index_manager=tag_index_manager) - - -class Halo1Map(HaloMap): - '''Generation 1 map''' - ce_rsrc_sound_indexes_by_path = None - ce_tag_indexs_by_paths = None - sound_rsrc_id = None - defs = None - - # Module path printed when loading the tag defs - tag_defs_module = "reclaimer.hek.defs" - tag_classes_to_load = tuple(sorted(tag_class_fcc_to_ext.keys())) - - # Handler that controls how to load tags, eg tag definitions - handler_class = HaloHandler - - force_checksum = False - - inject_rawdata = Halo1RsrcMap.inject_rawdata - - bsp_magics = () - bsp_sizes = () - bsp_headers = () - bsp_header_offsets = () - bsp_pointer_converters = () - - sbsp_meta_header_def = sbsp_meta_header_def - - data_extractors = data_extraction.h1_data_extractors - - indexable_tag_classes = set(( - "bitm", "snd!", "font", "hmt ", "ustr" - )) - - def __init__(self, maps=None): - HaloMap.__init__(self, maps) - - self.resource_map_class = Halo1RsrcMap - self.ce_rsrc_sound_indexes_by_path = {} - self.ce_tag_indexs_by_paths = {} - - self.bsp_magics = {} - self.bsp_sizes = {} - self.bsp_header_offsets = {} - self.bsp_headers = {} - self.bsp_pointer_converters = {} - - self.setup_tag_headers() - - @property - def globals_tag_id(self): - if not self.tag_index: - return None - - for b in self.tag_index.tag_index: - if int_to_fourcc(b.class_1.data) == "matg": - return b.id & 0xFFff - - @property - def resource_map_prefix(self): - return "" - @property - def resource_maps_folder(self): - return self.filepath.parent - @property - def resources_maps_mismatched(self): - maps_dir = self.resource_maps_folder - if not maps_dir: - return False - - for map_name, filepath in self.get_resource_map_paths().items(): - if filepath and filepath.parent != maps_dir: - return True - return False - @property - def uses_bitmaps_map(self): - return not self.is_resource - @property - def uses_loc_map(self): - return not self.is_resource and "pc" not in self.engine - @property - def uses_sounds_map(self): - return not self.is_resource - - @property - def decomp_file_ext(self): - return ( - ".vap" if self.engine == "halo1vap" else - self._decomp_file_ext - ) - - def is_indexed(self, tag_id): - tag_header = self.tag_index.tag_index[tag_id] - if not tag_header.indexed: - return False - return int_to_fourcc(tag_header.class_1.data) in self.indexable_tag_classes - - def setup_defs(self): - this_class = type(self) - if this_class.defs is None: - this_class.defs = defs = {} - print(" Loading definitions in '%s'" % self.tag_defs_module) - this_class.handler = self.handler_class( - build_reflexive_cache=False, build_raw_data_cache=False, - debug=2) - - this_class.defs = dict(this_class.handler.defs) - this_class.defs["coll"] = fast_coll_def - this_class.defs["sbsp"] = fast_sbsp_def - this_class.defs = FrozenDict(this_class.defs) - - # make a shallow copy for this instance to manipulate - self.defs = dict(self.defs) - - def ensure_sound_maps_valid(self): - sounds = self.maps.get("sounds") - if not sounds or self.is_resource: - return - - if id(sounds) == self.sound_rsrc_id and ( - self.ce_rsrc_sound_indexes_by_path and - self.ce_tag_indexs_by_paths): - return - - self.sound_rsrc_id = id(sounds) - if self.engine in GEN_1_HALO_CUSTOM_ENGINES: - # ce resource sounds are recognized by tag_path - # so we must cache their offsets by their paths - rsrc_snd_map = self.ce_rsrc_sound_indexes_by_path = {} - inv_snd_map = self.ce_tag_indexs_by_paths = {} - - if sounds is not None: - i = 0 - for tag_header in sounds.rsrc_map.data.tags: - rsrc_snd_map[tag_header.tag.path] = i - i += 1 - - i = 0 - for tag_header in self.tag_index.tag_index: - inv_snd_map[tag_header.path] = i - i += 1 - - def get_dependencies(self, meta, tag_id, tag_cls): - tag_index_array = self.tag_index.tag_index - if not self.is_indexed(tag_id): - # not indexed. get dependencies like normal - pass - elif tag_cls != "snd!": - # among the indexable tags, only sounds can have valid dependencies - return () - elif not hasattr(meta, "pitch_ranges"): - # tag is indexed AND we were provided with the indexed version - rsrc_id = meta.promotion_sound.id & 0xFFff - if rsrc_id == 0xFFFF: return () - - sounds = self.maps.get("sounds") - if sounds is None: return () - elif rsrc_id >= len(sounds.tag_index.tag_index): return () - - tag_path = sounds.tag_index.tag_index[rsrc_id].path - inv_snd_map = getattr(self, 'ce_tag_indexs_by_paths', {}) - tag_id = inv_snd_map.get(tag_path, 0xFFFF) - if tag_id >= len(tag_index_array): return () - - ref = deepcopy(meta.promotion_sound) - tag_index_ref = tag_index_array[tag_id] - ref.tag_class.data = tag_index_ref.class_1.data - ref.id = tag_index_ref.id - ref.filepath = tag_index_ref.path - - return [ref] - - if self.handler is None: return () - - dependency_cache = self.handler.tag_ref_cache.get(tag_cls) - if not dependency_cache: return () - - nodes = self.handler.get_nodes_by_paths(dependency_cache, (None, meta)) - dependencies = [] - - for node in nodes: - # need to filter to dependencies that are actually valid - tag_id = node.id & 0xFFff - if tag_id not in range(len(tag_index_array)): - continue - - tag_index_ref = tag_index_array[tag_id] - if (node.tag_class.enum_name == tag_index_ref.class_1.enum_name and - node.id == tag_index_ref.id): - dependencies.append(node) - - if tag_cls == "scnr": - # collect the tag references from the scenarios syntax data - try: - seen_tag_ids = set() - syntax_data = get_hsc_data_block(meta.script_syntax_data.data) - for node in get_script_syntax_node_tag_refs(syntax_data): - tag_index_id = node.data & 0xFFff - if (tag_index_id in range(len(tag_index_array)) and - tag_index_id not in seen_tag_ids): - seen_tag_ids.add(tag_index_id) - tag_index_ref = tag_index_array[tag_index_id] - - dependencies.append(make_dependency_os_block( - tag_index_ref.class_1.enum_name, tag_index_ref.id, - tag_index_ref.path, tag_index_ref.path_offset)) - except Exception: - pass - - return dependencies - - def setup_sbsp_pointer_converters(self): - # get the scenario meta - if self.scnr_meta is None: - print("Cannot setup sbsp pointer converters without scenario tag.") - return - - try: - metadata_range = range(self.map_header.tag_index_header_offset, - self.map_header.tag_index_header_offset + - self.map_header.tag_data_size) - invalid_bsp_tag_ids = [self.tag_index.scenario_tag_id & 0xFFff] - i = 0 - for b in self.scnr_meta.structure_bsps.STEPTREE: - bsp_id = b.structure_bsp.id & 0xFFff - - # these checks are necessary because apparently these structs - # can be super fucked up, and might not affect anything - if (bsp_id not in range(len(self.tag_index.tag_index)) or - bsp_id in invalid_bsp_tag_ids): - print("Scenario structure_bsp %s contains invalid tag_id." % i) - elif is_overlapping_ranges( - metadata_range, range(b.bsp_pointer, b.bsp_size)): - print("Scenario structure_bsp %s contains invalid pointer." % i) - elif self.tag_index.tag_index[bsp_id].id != b.structure_bsp.id: - print("Scenario structure_bsp %s contains invalid tag_id." % i) - else: - self.bsp_header_offsets[bsp_id] = b.bsp_pointer - self.bsp_magics[bsp_id] = b.bsp_magic - self.bsp_sizes[bsp_id] = b.bsp_size - - self.bsp_pointer_converters[bsp_id] = MapPointerConverter( - (b.bsp_magic, b.bsp_pointer, b.bsp_size) - ) - - i += 1 - - self.setup_sbsp_headers() - - except Exception: - print(format_exc()) - - def setup_sbsp_headers(self): - # read the sbsp headers - for tag_id, offset in self.bsp_header_offsets.items(): - header = self.sbsp_meta_header_def.build( - rawdata=self.map_data, offset=offset) - - if header.sig != header.get_desc("DEFAULT", "sig"): - print("Sbsp header is invalid for '%s'" % - self.tag_index.tag_index[tag_id].path) - self.bsp_headers[tag_id] = header - self.tag_index.tag_index[tag_id].meta_offset = header.meta_pointer - - def setup_rawdata_pages(self): - tag_index = self.tag_index - - last_bsp_end = 0 - # calculate the start of the rawdata section - for tag_id in self.bsp_headers: - bsp_end = self.bsp_header_offsets[tag_id] + self.bsp_sizes[tag_id] - if last_bsp_end < bsp_end: - last_bsp_end = bsp_end - - # add the rawdata section - self.map_pointer_converter.add_page_info( - last_bsp_end, last_bsp_end, - tag_index.model_data_offset - last_bsp_end, - ) - - # add the model data section - if hasattr(tag_index, "model_data_size"): - # PC tag index - self.map_pointer_converter.add_page_info( - 0, tag_index.model_data_offset, - tag_index.model_data_size, - ) - else: - # XBOX tag index - self.map_pointer_converter.add_page_info( - 0, tag_index.model_data_offset, - (self.map_header.tag_index_header_offset - - tag_index.model_data_offset), - ) - - def load_map(self, map_path, **kwargs): - HaloMap.load_map(self, map_path, **kwargs) - - tag_index = self.tag_index - tag_index_array = tag_index.tag_index - - # cache the original paths BEFORE running basic deprotection - self.cache_original_tag_paths() - - # make all contents of the map parseable - self.basic_deprotection() - - self.tag_index_manager = TagIndexManager(tag_index_array) - - # add the tag data section - self.map_pointer_converter.add_page_info( - self.index_magic, self.map_header.tag_index_header_offset, - self.map_header.tag_data_size - ) - - # cache the scenario meta - try: - self.scnr_meta = self.get_meta(self.tag_index.scenario_tag_id) - if self.scnr_meta is None: - print("Could not read scenario tag") - except Exception: - print(format_exc()) - print("Could not read scenario tag") - - self.setup_sbsp_pointer_converters() - self.setup_rawdata_pages() - - # get the globals meta - try: - self.matg_meta = self.get_meta(self.globals_tag_id) - if self.matg_meta is None: - print("Could not read globals tag") - except Exception: - print(format_exc()) - print("Could not read globals tag") - - if self.map_name == "sounds": - for halo_map in self.maps.values(): - if hasattr(halo_map, "ensure_sound_maps_valid"): - halo_map.ensure_sound_maps_valid() - - self.clear_map_cache() - - if self.resources_maps_mismatched and kwargs.get("unlink_mismatched_resources", True): - # this map reference different resource maps depending on what - # folder its located in. we need to ignore any resource maps - # passed in unless they're in the same folder as this map. - print("Unlinking potentially incompatible resource maps from %s" % - self.map_name - ) - self.maps = {} - - def get_meta(self, tag_id, reextract=False, ignore_rsrc_sounds=False, **kw): - ''' - Takes a tag reference id as the sole argument. - Returns that tags meta data as a parsed block. - ''' - if tag_id is None: - return - - tag_index_ref = self.tag_index_manager.get_tag_index_ref(tag_id) - if tag_index_ref is None: - return - - # if we are given a 32bit tag id, mask it off - tag_id &= 0xFFFF - magic = self.map_magic - engine = self.engine - map_data = self.map_data - - tag_cls = None - is_scenario = (tag_id == (self.tag_index.scenario_tag_id & 0xFFFF)) - if is_scenario: - tag_cls = "scnr" - elif tag_index_ref.class_1.enum_name not in ("", "NONE"): - tag_cls = int_to_fourcc(tag_index_ref.class_1.data) - - # if we dont have a defintion for this tag_cls, then return nothing - if self.get_meta_descriptor(tag_cls) is None: - return - - self.ensure_sound_maps_valid() - pointer_converter = self.bsp_pointer_converters.get( - tag_id, self.map_pointer_converter) - offset = tag_index_ref.meta_offset - - if tag_cls is None: - # couldn't determine the tag class - return - elif self.is_indexed(tag_id) and ( - tag_cls != "snd!" or not ignore_rsrc_sounds): - # tag exists in a resource cache - tag_id = offset - - rsrc_map = None - if tag_cls == "snd!": - if "sounds" in self.maps: - rsrc_map = self.maps["sounds"] - sound_mapping = self.ce_rsrc_sound_indexes_by_path - tag_path = tag_index_ref.path - if sound_mapping is None or tag_path not in sound_mapping: - return - - tag_id = sound_mapping[tag_path]//2 - - elif tag_cls == "bitm": - if "bitmaps" in self.maps: - rsrc_map = self.maps["bitmaps"] - tag_id = tag_id//2 - - elif "loc" in self.maps: - rsrc_map = self.maps["loc"] - # this resource tag COULD be in a yelo loc.map, which - # means we will need to set its tag class to what this - # map specifies it as or else the resource map wont - # know what type of tag to extract it as. - rsrc_map.tag_index.tag_index[tag_id].class_1.set_to( - tag_index_ref.class_1.enum_name) - - if rsrc_map is None: - return - - meta = rsrc_map.get_meta(tag_id, **kw) - snd_stub = None - if tag_cls == "snd!": - # while the sound samples and complete tag are in the - # resource map, the metadata for the body of the sound - # tag is in the main map. Need to copy its values into - # the resource map sound tag we extracted. - try: - # read the meta data from the map - with FieldType.force_little: - snd_stub = snd__meta_stub_blockdef.build( - rawdata=map_data, - offset=pointer_converter.v_ptr_to_f_ptr(offset), - tag_index_manager=self.tag_index_manager) - except Exception: - print(format_exc()) - - if snd_stub: - # copy values over - for name in ( - "flags", "sound_class", "sample_rate", - "minimum_distance", "maximum_distance", - "skip_fraction", "random_pitch_bounds", - "inner_cone_angle", "outer_cone_angle", - "outer_cone_gain", "gain_modifier", - "maximum_bend_per_second", - "modifiers_when_scale_is_zero", - "modifiers_when_scale_is_one", - "encoding", "compression", "promotion_sound", - "promotion_count", "max_play_length", - ): - setattr(meta, name, getattr(snd_stub, name)) - - return meta - elif not reextract: - if is_scenario and self.scnr_meta: - return self.scnr_meta - elif tag_cls == "matg" and self.matg_meta: - return self.matg_meta - - force_parsing_rsrc = False - if tag_cls in ("antr", "magy") and not kw.get("disable_tag_cleaning"): - force_parsing_rsrc = True - - desc = self.get_meta_descriptor(tag_cls) - block = [None] - try: - # read the meta data from the map - with FieldType.force_little: - desc['TYPE'].parser( - desc, parent=block, attr_index=0, rawdata=map_data, - map_pointer_converter=pointer_converter, - offset=pointer_converter.v_ptr_to_f_ptr(offset), - tag_index_manager=self.tag_index_manager, - safe_mode=(self.safe_mode and not kw.get("disable_safe_mode")), - parsing_resource=force_parsing_rsrc) - except Exception: - print(format_exc()) - if not kw.get("allow_corrupt"): - return - - meta = block[0] - try: - # TODO: remove this dirty-ass hack - if tag_cls == "bitm" and get_is_xbox_map(engine): - for bitmap in meta.bitmaps.STEPTREE: - # make sure to set this for all xbox bitmaps - # so they can be interpreted properly - bitmap.base_address = 1073751810 - - self.record_map_cache_read(tag_id, 0) - if self.map_cache_over_limit(): - self.clear_map_cache() - - if not kw.get("ignore_rawdata", False): - self.inject_rawdata(meta, tag_cls, tag_index_ref) - except Exception: - print(format_exc()) - if not kw.get("allow_corrupt"): - meta = None - - if not kw.get("disable_tag_cleaning"): - try: - self.clean_tag_meta(meta, tag_id, tag_cls) - except Exception: - print(format_exc()) - - return meta - - def clean_tag_meta(self, meta, tag_id, tag_cls): - tag_index_array = self.tag_index.tag_index - - if tag_cls in ("antr", "magy"): - highest_valid = -1 - found_valid = False - animations = meta.animations.STEPTREE - main_node_count = animations[0].node_count if animations else 0 - main_node_list_checksum = animations[0].node_list_checksum if animations else 0 - - permutation_chains = {} - for i in range(len(animations)): - if i in permutation_chains: - continue - - permutation_chains[i] = i - next_anim = animations[i].next_animation - while (next_anim in range(len(animations)) and - next_anim not in permutation_chains): - permutation_chains[next_anim] = i - next_anim = animations[next_anim].next_animation - - anims_to_remove = [] - max_anim_count = get_block_max(meta.animations) - for i in range(len(animations)): - if i >= max_anim_count: - break - - anim = animations[i] - valid = is_valid_ascii_name_str(anim.name) - - trans_int = anim.trans_flags0 + (anim.trans_flags1 << 32) - rot_int = anim.rot_flags0 + (anim.rot_flags1 << 32) - scale_int = anim.scale_flags0 + (anim.scale_flags1 << 32) - - trans_flags = (bool(trans_int & (1 << i)) - for i in range(anim.node_count)) - rot_flags = (bool(rot_int & (1 << i)) - for i in range(anim.node_count)) - scale_flags = (bool(scale_int & (1 << i)) - for i in range(anim.node_count)) - - expected_frame_size = (12 * sum(trans_flags) + - 8 * sum(rot_flags) + - 4 * sum(scale_flags)) - expected_frame_info_size = {1: 8, 2: 12, 3: 16}.get( - anim.frame_info_type.data, 0) * anim.frame_count - expected_frame_data_size = expected_frame_size * anim.frame_count - expected_default_data_size = ( - anim.node_count * (12 + 8 + 4) - anim.frame_size) - - if anim.frame_count == 0: - expected_default_data_size = 0 - - if (anim.type.enum_name == "" or - anim.frame_info_type.enum_name == ""): - valid = False - elif (anim.node_count != main_node_count or - anim.node_count not in range(1, 65)): - valid = False - elif anim.first_permutation_index != permutation_chains[i]: - valid = False - elif not anim.flags.compressed_data: - if anim.default_data.size < expected_default_data_size: - valid = False - elif anim.frame_info.size < expected_frame_info_size: - valid = False - elif anim.frame_data.size < expected_frame_data_size: - valid = False - elif anim.frame_size != expected_frame_size: - valid = False - elif anim.offset_to_compressed_data >= anim.frame_data.size: - valid = False - - if valid: - highest_valid = i - if not found_valid: - main_node_count = anim.node_count - main_node_list_checksum = anim.node_list_checksum - found_valid = True - else: - # delete the animation info - anims_to_remove.append(i) - - # make sure all animations have the same node count, checksum, and a name - for i in anims_to_remove: - animations.pop(i) - animations.insert(i) - animations[i].node_count = main_node_count - animations[i].node_list_checksum = main_node_list_checksum - animations[i].name = "REMOVED_%s" % i - - # remove the highest invalid animations - if highest_valid + 1 < len(animations): - del animations[highest_valid + 1: ] - - # inject the animation data for all remaining animations since - # it's not safe to try and read it all at parse time - for anim in animations: - for block in (anim.default_data, anim.frame_info, anim.frame_data): - if not block.size: - continue - - file_ptr = self.map_pointer_converter.v_ptr_to_f_ptr( - block.pointer) - if not block.pointer or file_ptr < 0: - file_ptr = block.raw_pointer - - if file_ptr + block.size > len(self.map_data) or file_ptr <= 0: - continue - - try: - self.map_data.seek(file_ptr) - block.data = bytearray(self.map_data.read(block.size)) - except Exception: - print("Couldn't read animation data.") - - elif tag_cls == "bitm": - bitmaps = [b for b in meta.bitmaps.STEPTREE - if "dxt" in b.format.enum_name] - # correct mipmap count on xbox dxt bitmaps. texels for any - # mipmaps whose dimensions are 2x2 or smaller are pruned - for bitmap in bitmaps: - # figure out largest dimension(clip to 1 to avoid log(0, 2)) - max_dim = max(1, bitmap.width, bitmap.height) - - # subtract 2 to account for width/height of 1 or 2 not having mips - maxmips = int(max(0, math.log(max_dim, 2) - 2)) - - # clip mipmap count to max and min number that can exist - bitmap.mipmaps = max(0, min(maxmips, bitmap.mipmaps)) - - elif tag_cls in ("sbsp", "coll"): - if tag_cls == "sbsp" : - bsps = meta.collision_bsp.STEPTREE - else: - bsps = [] - for node in meta.nodes.STEPTREE: - bsps.extend(node.bsps.STEPTREE) - - for bsp in bsps: - vert_data = bsp.vertices.STEPTREE - # first 2 ints in each edge are the vert indices, and theres - # 6 int32s per edge. find the highest vert index being used - if bsp.edges.STEPTREE: - byteorder = 'big' if self.engine == "halo1anni" else 'little' - - edges = PyArray("i", bsp.edges.STEPTREE) - if byteorder != sys.byteorder: - edges.byteswap() - - max_start_vert = max(edges[0: len(edges): 6]) - max_end_vert = max(edges[1: len(edges): 6]) - else: - max_start_vert = max_end_vert = -1 - - if max_start_vert * 16 < len(vert_data): - del vert_data[(max_start_vert + 1) * 16: ] - bsp.vertices.size = max_start_vert + 1 - - elif tag_cls in ("mode", "mod2"): - used_shaders = set() - shaders = meta.shaders.STEPTREE - - for geom in meta.geometries.STEPTREE: - for part in geom.parts.STEPTREE: - if part.shader_index >= 0: - used_shaders.add(part.shader_index) - - # determine the max number of shader indices actually used - # by all the geometry parts, and reparse them with that many. - if used_shaders: - try: - reparse_reflexive( - meta.shaders, max(used_shaders) + 1, - self.map_pointer_converter, - self.map_data, self.tag_index_manager) - except Exception: - print(format_exc()) - print("Couldn't re-parse %s data." % meta.shaders) - - new_i = 0 - rebase_map = {} - new_shaders = [None] * len(used_shaders) - for i in sorted(used_shaders): - new_shaders[new_i] = shaders[i] - rebase_map[i] = new_i - new_i += 1 - - # rebase the shader indices - for geom in meta.geometries.STEPTREE: - for part in geom.parts.STEPTREE: - if part.shader_index in range(len(shaders)): - part.shader_index = rebase_map[part.shader_index] - else: - part.shader_index = -1 - - shaders[:] = new_shaders - - elif tag_cls == "scnr": - skies = meta.skies.STEPTREE - comments = meta.comments.STEPTREE - - highest_valid_sky = -1 - for i in range(len(skies)): - sky = skies[i].sky - sky_tag_index_id = sky.id & 0xFFff - if (sky_tag_index_id not in range(len(tag_index_array)) or - tag_index_array[sky_tag_index_id].id != sky.id): - # invalid sky - sky.id = 0xFFffFFff - sky.filepath = "" - sky.tag_class.set_to("sky") - else: - highest_valid_sky = i - - # clear the highest invalid skies - del skies[highest_valid_sky + 1: ] - - # clear the child scenarios since they aren't used - del meta.child_scenarios.STEPTREE[:] - - # determine if there are any fucked up comments - comments_to_keep = set() - for i in range(len(comments)): - comment = comments[i] - if max(max(comment.position), abs(min(comment.position))) > 5000: - # check if the position is outside halos max world bounds - continue - - if not (comment.comment_data.data and - is_valid_ascii_name_str(comment.comment_data.data)): - comments_to_keep.add(i) - - if len(comments_to_keep) != len(comments): - # clean up any fucked up comments - comments[:] = [comments[i] for i in sorted(comments_to_keep)] - - syntax_data = get_hsc_data_block(meta.script_syntax_data.data) - script_nodes_modified = False - - # lets not use magic numbers here - _, script_object_types = get_script_types(self.engine) - biped_node_enum = script_object_types.index("actor_type") - - # clean up any fucked up palettes - for pal_block, inst_block in ( - (meta.sceneries_palette, meta.sceneries), - (meta.bipeds_palette, meta.bipeds), - (meta.vehicles_palette, meta.vehicles), - (meta.equipments_palette, meta.equipments), - (meta.weapons_palette, meta.weapons), - (meta.machines_palette, meta.machines), - (meta.controls_palette, meta.controls), - (meta.light_fixtures_palette, meta.light_fixtures), - (meta.sound_sceneries_palette, meta.sound_sceneries), - ): - palette, instances = pal_block.STEPTREE, inst_block.STEPTREE - - used_pal_indices = set(inst.type for inst in instances - if inst.type >= 0) - script_nodes_to_modify = set() - - if inst_block.NAME == "bipeds": - # determine which palette indices are used by script data - for i in range(len(syntax_data.nodes)): - node = syntax_data.nodes[i] - if node.type == biped_node_enum and not(node.flags & HSC_IS_SCRIPT_OR_GLOBAL): - script_nodes_to_modify.add(i) - used_pal_indices.add(node.data & 0xFFff) - - # determine the max number of palette indices actually used by all - # the object instances, and reparse the palette with that many. - if used_pal_indices: - try: - reparse_reflexive( - pal_block, max(used_pal_indices) + 1, - self.map_pointer_converter, - self.map_data, self.tag_index_manager) - except Exception: - print(format_exc()) - print("Couldn't re-parse %s data." % pal_block.NAME) - - # figure out what to rebase the palette swatch indices to - new_i = 0 - rebase_map = {} - new_palette = [None] * len(used_pal_indices) - for i in sorted(used_pal_indices): - new_palette[new_i] = palette[i] - rebase_map[i] = new_i - new_i += 1 - - # rebase the palette indices of the instances and - # move the palette swatches into their new indices - for inst in instances: - if inst.type in range(len(instances)): - inst.type = rebase_map[inst.type] - else: - inst.type = -1 - - palette[:] = new_palette - - # modify the script syntax nodes that need to be - for i in script_nodes_to_modify: - node = syntax_data.nodes[i] - salt = node.data & 0xFFff0000 - cur_index = node.data & 0xFFff - new_index = rebase_map[cur_index] - if cur_index != new_index: - script_nodes_modified = True - node.data = salt + new_index - - # replace the script syntax data - if script_nodes_modified: - with FieldType.force_little: - new_script_data = syntax_data.serialize() - meta.script_syntax_data.data[: len(new_script_data)] = new_script_data - meta.script_syntax_data.size = len(meta.script_syntax_data.data) - - elif tag_cls in ("tagc", "Soul"): - tag_collection = meta[0].STEPTREE - highest_valid = -1 - for i in range(len(tag_collection)): - tag_ref = tag_collection[i][0] - if tag_cls == "Soul" and tag_ref.tag_class.enum_name != "ui_widget_definition": - continue - elif (tag_ref.id & 0xFFff) not in range(len(tag_index_array)): - continue - elif tag_index_array[tag_ref.id & 0xFFff].id != tag_ref.id: - continue - - highest_valid = i - - # clear the highest invalid entries - del tag_collection[highest_valid + 1: ] - - def meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs): - magic = self.map_magic - engine = self.engine - map_data = self.map_data - tag_index = self.tag_index - byteswap = kwargs.get("byteswap", True) - - predicted_resources = [] - - if hasattr(meta, "obje_attrs"): - predicted_resources.append(meta.obje_attrs.predicted_resources) - - # fix the change colors permutations - for change_color in meta.obje_attrs.change_colors.STEPTREE: - cutoff = 0 - for perm in change_color.permutations.STEPTREE: - perm.weight, cutoff = perm.weight - cutoff, perm.weight - - if tag_cls == "actv": - # multiply grenade velocity by 30 - meta.grenades.grenade_velocity *= 30 - - elif tag_cls in ("antr", "magy"): - # try to fix HEK+ extraction bug - for obj in meta.objects.STEPTREE: - for enum in (obj.function, obj.function_controls): - uint16_data = enum.data & 0xFFff - if (uint16_data & 0xFF00 and not uint16_data & 0xFF): - # higher bits are set than lower. this is likely - # a HEK plus extraction bug and should be fixed - uint16_data = ((uint16_data>>8) | (uint16_data<<8)) & 0xFFff - enum.data = uint16_data - ( - 0 if uint16_data < 0x8000 else 0x10000 - ) - - # byteswap animation data - for anim in meta.animations.STEPTREE: - if not byteswap: break - byteswap_animation(anim) - - elif tag_cls in ("bitm", "snd!"): - meta = Halo1RsrcMap.meta_to_tag_data(self, meta, tag_cls, tag_index_ref, **kwargs) - - elif tag_cls == "cdmg": - # divide camera shaking wobble period by 30 - meta.camera_shaking.wobble_function_period /= 30 - - elif tag_cls == "coll": - # byteswap the raw bsp collision data - for node in meta.nodes.STEPTREE: - for perm_bsp in node.bsps.STEPTREE: - if not byteswap: break - byteswap_coll_bsp(perm_bsp) - - elif tag_cls == "effe": - # mask away the meta-only flags - # NOTE: xbox has a cache flag in the 2nd - # bit, so it should be masked out too. - meta.flags.data &= (1 if "xbox" in engine else 3) - - for event in meta.events.STEPTREE: - # tool exceptions if any parts reference a damage effect - # tag type, but have an empty filepath for the reference - parts = event.parts.STEPTREE - for i in range(len(parts) - 1, -1, -1): - part = parts[i] - if (part.type.tag_class.enum_name == "damage_effect" and - not part.type.filepath): - parts.pop(i) - - elif tag_cls == "jpt!": - # camera shaking wobble period by 30 - meta.camera_shaking.wobble_function_period /= 30 - - elif tag_cls == "glw!": - # increment enumerators properly - for b in (meta.particle_rotational_velocity, - meta.effect_rotational_velocity, - meta.effect_translational_velocity, - meta.particle_distance_to_object, - meta.particle_size, - meta.particle_color): - b.attachment.data += 1 - - elif tag_cls == "lens": - # DON'T multiply corona rotation by pi/180 - # reminder that this is not supposed to be changed - - if meta.corona_rotation.function_scale == 360.0: - # fix a really old bug(i think its the - # reason the above comment was created) - meta.corona_rotation.function_scale = 0.0 - - elif tag_cls == "ligh": - # divide light time by 30 - meta.effect_parameters.duration /= 30 - - elif tag_cls == "matg": - # tool will fail to compile any maps if the - # multiplayer_info or falling_damage is blank - - # make sure there is multiplayer info. - multiplayer_info = meta.multiplayer_informations.STEPTREE - if not len(multiplayer_info): - multiplayer_info.append() - - # make sure there is falling damage info. - falling_damages = meta.falling_damages.STEPTREE - if not len(falling_damages): - falling_damages.append() - - elif tag_cls == "metr": - # The meter bitmaps can literally point to not - # only the wrong tag, but the wrong TYPE of tag. - # Since dependencies in meter tags are useless, we null them out. - meta.stencil_bitmap.filepath = meta.source_bitmap.filepath = '' - - elif tag_cls in ("mode", "mod2"): - if engine in ("halo1yelo", "halo1ce", "halo1pc", "halo1vap", "halo1mcc", - "halo1anni", "halo1pcdemo", "stubbspc", "stubbspc64bit"): - # model_magic seems to be the same for all pc maps - verts_start = tag_index.model_data_offset - tris_start = verts_start + tag_index.index_parts_offset - model_magic = None - else: - model_magic = magic - - # need to unset this flag, as it forces map-compile-time processing - # to occur on the model's vertices, which shouldn't be done twice. - meta.flags.blend_shared_normals = False - - # lod cutoffs are swapped between tag and cache form - cutoffs = (meta.superlow_lod_cutoff, meta.low_lod_cutoff, - meta.high_lod_cutoff, meta.superhigh_lod_cutoff) - meta.superlow_lod_cutoff = cutoffs[3] - meta.low_lod_cutoff = cutoffs[2] - meta.high_lod_cutoff = cutoffs[1] - meta.superhigh_lod_cutoff = cutoffs[0] - - # localize the global markers - # ensure all local marker arrays are empty - for region in meta.regions.STEPTREE: - for perm in region.permutations.STEPTREE: - del perm.local_markers.STEPTREE[:] - - for g_marker in meta.markers.STEPTREE: - for g_marker_inst in g_marker.marker_instances.STEPTREE: - try: - region = meta.regions.STEPTREE[g_marker_inst.region_index] - except IndexError: - print("Model marker instance for", g_marker.name, "has invalid region index", g_marker_inst.region_index, "and is skipped.") - continue - - try: - perm = region.permutations.STEPTREE[g_marker_inst.permutation_index] - except IndexError: - print("Model marker instance for", g_marker.name, "has invalid permutation index", g_marker_inst.permutation_index, "and is skipped.") - continue - - # make a new local marker - perm.local_markers.STEPTREE.append() - l_marker = perm.local_markers.STEPTREE[-1] - - # copy the global marker into the local - l_marker.name = g_marker.name - l_marker.node_index = g_marker_inst.node_index - l_marker.translation[:] = g_marker_inst.translation[:] - l_marker.rotation[:] = g_marker_inst.rotation[:] - - # clear the global markers - del meta.markers.STEPTREE[:] - - # grab vertices and indices from the map - for geom in meta.geometries.STEPTREE: - for part in geom.parts.STEPTREE: - tris_block = part.triangles - info = part.model_meta_info - - if info.vertex_type.enum_name == "model_comp_verts": - verts_block = part.compressed_vertices - byteswap_verts = byteswap_comp_verts - vert_size = 32 - elif info.vertex_type.enum_name == "model_uncomp_verts": - verts_block = part.uncompressed_vertices - byteswap_verts = byteswap_uncomp_verts - vert_size = 68 - else: - print("Error: Unknown vertex type in model: %s" % info.vertex_type.data) - continue - - if info.index_type.enum_name != "triangle_strip": - print("Error: Unknown index type in model: %s" % info.index_type.data) - continue - - # null out certain things in the part - part.centroid_primary_node = 0 - part.centroid_secondary_node = 0 - part.centroid_primary_weight = 0.0 - part.centroid_secondary_weight = 0.0 - - # make the new blocks to hold the raw data - verts_block.STEPTREE = raw_block_def.build() - tris_block.STEPTREE = raw_block_def.build() - - # read the offsets of the vertices and indices from the map - if model_magic is None: - verts_off = verts_start + info.vertices_offset - tris_off = tris_start + info.indices_offset - else: - map_data.seek( - info.vertices_reflexive_offset + 4 - model_magic) - verts_off = unpack( - " 1 else u)*32767), - int((-1 if v < -1 else 1 if v > 1 else v)*32767), - ) - elif (kwargs.get("generate_uncomp_verts") and - lm_vert_type == "sbsp_comp_lightmap_verts" - ): - # generate uncompressed lightmap verts from compressed - u_buffer += bytearray(u_lm_verts_size) - for u_off, c_off in lm_vert_offs: - n, u, v = comp_lm_vert_unpacker(c_buffer, c_off) - uncomp_lm_vert_packer( - u_buffer, u_off, - *decomp_norm(n), u/32767, v/32767 - ) - - # need to null these or original CE sapien could crash - mat.unknown_meta_offset0 = mat.vertices_meta_offset = 0 - mat.unknown_meta_offset1 = mat.lightmap_vertices_meta_offset = 0 - - # set these to the correct vertex types based on what we have - vert_type_str = ( - "sbsp_comp_%s_verts" - if c_verts_size and c_lm_verts_size else - "sbsp_uncomp_%s_verts" - ) - mat.vertex_type.set_to(vert_type_str % "material") - mat.lightmap_vertex_type.set_to(vert_type_str % "lightmap") - - mat.uncompressed_vertices.STEPTREE = u_buffer - mat.compressed_vertices.STEPTREE = c_buffer - - elif tag_cls == "scnr": - # need to remove the references to the child scenarios - del meta.child_scenarios.STEPTREE[:] - - # set the bsp pointers and stuff to 0 - for b in meta.structure_bsps.STEPTREE: - b.bsp_pointer = b.bsp_size = b.bsp_magic = 0 - - predicted_resources.append(meta.predicted_resources) - - # byteswap the script syntax data - if byteswap: - byteswap_scnr_script_syntax_data(meta) - - # rename duplicate stuff that causes errors when compiling scripts - if kwargs.get("rename_scnr_dups", False): - string_data = meta.script_string_data.data.decode("latin-1") - syntax_data = get_hsc_data_block(raw_syntax_data=meta.script_syntax_data.data) - - # lets not use magic numbers here - _, script_object_types = get_script_types(engine) - trigger_volume_enum = script_object_types.index("trigger_volume") - - # NOTE: For a list of all the script object types - # with their corrosponding enum value, check - # reclaimer.halo_script.hsc.get_script_types - keep_these = {script_object_types.index(typ): set() for typ in - SCRIPT_OBJECT_TYPES_TO_SCENARIO_REFLEXIVES} - - # don't de-duplicate trigger volumes - for b in meta.bsp_switch_trigger_volumes.STEPTREE: - keep_these[trigger_volume_enum].add(b.trigger_volume) - - # for everything we're keeping, clear the upper 16bits of the data - for i in range(min(syntax_data.last_node, len(syntax_data.nodes))): - node = syntax_data.nodes[i] - if node.type in keep_these: - keep_these[node.type].add(node.data & 0xFFff) - - # for everything else, rename duplicates - for script_object_type, reflexive_name in \ - SCRIPT_OBJECT_TYPES_TO_SCENARIO_REFLEXIVES.items(): - - script_object_type_enum = script_object_types.index(script_object_type) - keep = keep_these[script_object_type_enum] - reflexive = meta[reflexive_name].STEPTREE - counts = {b.name.lower(): 0 for b in reflexive} - for b in reflexive: - counts[b.name.lower()] += 1 - - for i in range(len(reflexive)): - name = reflexive[i].name.lower() - if counts[name] > 1 and i not in keep: - reflexive[i].name = ("DUP%s~%s" % (i, name))[: 31] - - # null tag refs after we're done with them - clean_script_syntax_nodes(syntax_data, engine) - - # decompile scripts and put them in the source_files array so - # sapien can recompile them when it opens an extracted scenario - source_files = meta.source_files.STEPTREE - del source_files[:] - script_sources, global_sources = extract_scripts( - engine=engine, tagdata=meta, add_comments=False, minify=True - ) - i = 0 - for source in (*script_sources, *global_sources): - source_files.append() - source_files[-1].source_name = "decompiled_%s.hsc" % i - source_files[-1].source.data = source.encode('latin-1') - i += 1 - - # divide the cutscene times by 30(they're in ticks) and - # subtract the fade-in time from the up_time(normally added - # together as a total up-time in maps, but not in tag form) - for b in meta.cutscene_titles.STEPTREE: - b.up_time = max(b.up_time - b.fade_in_time, 0.0) - - b.fade_in_time /= 30 - b.fade_out_time /= 30 - b.up_time /= 30 - - elif tag_cls == "shpp": - predicted_resources.append(meta.predicted_resources) - - elif tag_cls == "shpg": - shpg_attrs = meta.shpg_attrs - - # copy all merged values into their respective reflexives - for b in shpg_attrs.merged_values.STEPTREE: - typ = b.value_type.enum_name - cnt = b.value_count - if typ == "boolean": array = shpg_attrs.booleans.STEPTREE - elif typ == "integer": array = shpg_attrs.integers.STEPTREE - elif typ == "color": array = shpg_attrs.colors.STEPTREE - elif typ == "bitmap": array = shpg_attrs.bitmaps.STEPTREE - elif typ != "float": continue # unknown type - elif cnt == 1: array = shpg_attrs.floats_1d.STEPTREE - elif cnt == 2: array = shpg_attrs.floats_2d.STEPTREE - elif cnt == 3: array = shpg_attrs.floats_3d.STEPTREE - elif cnt == 4: array = shpg_attrs.floats_4d.STEPTREE - else: continue # unknown float type - - array.append() - new_b = array[-1] - new_b.value_name = b.value_name - values = b.values.u_node - - if typ == "bitmap": - new_b.bitmap = b.bitmap - new_b.bitmap_index = values.bitmap_index - continue - - new_b.runtime_value = b.runtime_value - new_b.animation_function = b.animation_function - new_b.animation_flags = b.animation_flags - new_b.animation_duration = b.animation_duration - new_b.animation_rate = b.animation_rate - - if typ == "boolean": - new_b.flags = b.flags - new_b.value = values.value - else: - new_b.value_lower_bound = values.value_lower_bound - new_b.value_upper_bound = values.value_upper_bound - - # clear the merged values reflexive - del shpg_attrs.merged_values.STEPTREE[:] - - elif tag_cls == "soso": - # set the mcc multipurpose_map_uses_og_xbox_channel_order flag - if "xbox" in engine or "stubbs" in engine or engine == "shadowrun_proto": - meta.soso_attrs.model_shader.flags.data |= 1<<6 - - elif tag_cls == "weap": - # try to fix HEK+ extraction bug - uint16_data = (meta.weap_attrs.aiming.zoom_levels & 0xFFff) - if (uint16_data & 0xFF00 and not uint16_data & 0xFF): - # higher bits are set than lower. this is likely - # a HEK plus extraction bug and should be fixed - uint16_data = ((uint16_data>>8) | (uint16_data<<8)) & 0xFFff - meta.weap_attrs.aiming.zoom_levels = uint16_data - ( - 0 if uint16_data < 0x8000 else 0x10000 - ) - - predicted_resources.append(meta.weap_attrs.predicted_resources) - - # remove any predicted resources - for pr in predicted_resources: - del pr.STEPTREE[:] - - return meta - - def get_resource_map_paths(self, maps_dir=""): - if self.is_resource or not self.resource_maps_folder: - return {} - - map_paths = { - name: None for name in ( - *(["bitmaps"] if self.uses_bitmaps_map else []), - *(["sounds"] if self.uses_sounds_map else []), - *(["loc"] if self.uses_loc_map else []), - ) - } - - name_str = self.resource_map_prefix + "%s.map" - maps_dir = ( - Path(maps_dir) if not is_path_empty(maps_dir) else - self.resource_maps_folder - ) - - # detect the map paths for the resource maps - if maps_dir: - for map_name in sorted(map_paths.keys()): - map_path = maps_dir.joinpath(name_str % map_name) - if self.maps.get(map_name) is not None: - map_paths[map_name] = self.maps[map_name].filepath - elif map_path.is_file(): - map_paths[map_name] = map_path - - return map_paths - - def generate_map_info_string(self): - string = HaloMap.generate_map_info_string(self) - index, header = self.tag_index, self.map_header - - if self.engine == "halo1mcc": - string += """\n Calculated information: - use bitmaps map == %s - use sounds map == %s - no remastered sync == %s""" % ( - bool(header.mcc_flags.use_bitmaps_map), - bool(header.mcc_flags.use_sounds_map), - bool(header.mcc_flags.disable_remastered_sync), - ) - - string += """ - -Calculated information: - index magic == %s - map magic == %s - -Tag index: - tag count == %s - scenario tag id == %s - index array pointer == %s non-magic == %s - meta data length == %s - vertex parts count == %s - index parts count == %s""" % ( - self.index_magic, self.map_magic, - index.tag_count, index.scenario_tag_id & 0xFFff, - index.tag_index_offset, index.tag_index_offset - self.map_magic, - header.tag_data_size, - index.vertex_parts_count, index.index_parts_count) - - if hasattr(index, "model_data_size"): - string += """ - vertex data pointer == %s - index data pointer == %s - index data size == %s - model data size == %s""" % ( - index.model_data_offset, - index.index_parts_offset, - index.model_data_size - index.index_parts_offset, - index.model_data_size - ) - else: - string += """ - vertex refs pointer == %s non-magic == %s - index refs pointer == %s non-magic == %s""" % ( - index.model_data_offset, index.model_data_offset - self.map_magic, - index.index_parts_offset, index.index_parts_offset - self.map_magic, - ) - - string += "\n\nSbsp magic and headers:\n" - for tag_id in self.bsp_magics: - header = self.bsp_headers.get(tag_id) - if header is None: continue - - magic = self.bsp_magics[tag_id] - offset = self.bsp_header_offsets[tag_id] - string += """ %s.structure_scenario_bsp - bsp base pointer == %s - bsp magic == %s - bsp size == %s - bsp metadata pointer == %s non-magic == %s\n""" % ( - index.tag_index[tag_id].path, offset, - magic, self.bsp_sizes[tag_id], header.meta_pointer, - header.meta_pointer + offset - magic - ) - if self.engine in ("halo1mcc", "halo1anni"): - string += """\ - render verts size == %s - render verts pointer == %s\n""" % ( - header.uncompressed_render_vertices_size, - header.uncompressed_render_vertices_pointer, - ) - else: - string += """\ - uncomp mats count == %s - uncomp mats pointer == %s non-magic == %s - comp mats count == %s - comp mats pointer == %s non-magic == %s\n""" % ( - header.uncompressed_lightmap_materials_count, - header.uncompressed_lightmap_materials_pointer, - header.uncompressed_lightmap_materials_pointer + offset - magic, - header.compressed_lightmap_materials_count, - header.compressed_lightmap_materials_pointer, - header.compressed_lightmap_materials_pointer + offset - magic, - ) - - if self.engine == "halo1vap": - string += self.generate_vap_info_string() - - return string - - def generate_vap_info_string(self): - vap = self.map_header.vap_header - - return """ -VAP information: - name == %s - build date == %s - description == %s - - vap version == %s - feature level == %s - max players == %s\n""" % ( - vap.name, vap.build_date, vap.description, - vap.vap_version.enum_name, vap.feature_level.enum_name, - vap.max_players, - ) +''' +The formula for calculating the magic for ANY map is as follows: + magic = idx_magic - idx_off + +idx_off is the offset of the tag_index_header and idx_magic +varies depending on which engine version the map is for. +To get the idx_magic, do: + map_magics[map_header.version.enum_name] + +where map_magics is in reclaimer.constants + +To convert a magic relative pointer to an absolute pointer, +simply do: abs_pointer = magic_pointer - magic +''' + +from reclaimer.common_descs import * + +from supyr_struct.defs.block_def import BlockDef +from supyr_struct.util import desc_variant + +def tag_path_pointer(parent=None, new_value=None, magic=0, **kwargs): + ''' + Calculates the pointer value for a tag_path based on the file offset. + ''' + if parent is None: + raise KeyError() + if new_value is None: + return parent.path_offset - magic + parent.path_offset = new_value + magic + + +def tag_index_array_pointer(parent=None, new_value=None, magic=0, **kwargs): + ''' + Calculates the pointer value for a tag_index array based on file offset. + ''' + if new_value is None: + return parent.tag_index_offset - magic + parent.tag_index_offset = new_value + magic + + +yelo_header = Struct("yelo header", + UEnum32("yelo", ('yelo', 'yelo'), EDITABLE=False, DEFAULT='yelo'), + UEnum16("version type", + ("version", 1), + ("version minimum build", 2) + ), + Bool16("flags", + "uses memory upgrades", + "uses mod data files", + "is protected", + "uses game state upgrades", + "has compression params", + ), + QStruct("tag versioning", + UInt8("project yellow"), + UInt8("project yellow globals"), + Pad(2), + SIZE=4 + ), + + Float("memory upgrade multiplier"), + + Struct("cheape definitions", + UInt32("size"), + UInt32("decompressed size"), + UInt32("offset"), + Pad(4), + + ascii_str32("build string"), + SIZE=48 + ), + + ascii_str32("mod name"), + + Struct("build info", + Pad(2), + UEnum16("stage", + "ship", + "alpha", + "beta", + "delta", + "epsilon", + "release", + ), + UInt32("revision"), + Timestamp64("timestamp"), + + ascii_str32("build string"), + + QStruct("cheape", + UInt8("maj"), + UInt8("min"), + UInt16("build"), + ), + BytesRaw("uuid buffer", SIZE=16), + + QStruct("minimum os build", + UInt8("maj"), + UInt8("min"), + UInt16("build"), + ), + Pad(4*3), + SIZE=84 + ), + + QStruct("resources", + UInt32("compression params header offset"), + UInt32("tag symbol storage header offset"), + UInt32("string id storage header offset"), + UInt32("tag string to id storage header offset"), + SIZE=16 + ), + + SIZE=196 + ) + +vap_block = Struct("vap_block", + # File offset of the compressed block + UInt32("file_offset"), + + # File size of the compressed block + UInt32("file_size"), + + # Decompressed size of the compressed block + UInt32("decompressed_size"), + + # Reserved for potential future usage + UInt32("reserved"), + ) + +# validated archive pattern(uh huh, sure) +vap_header = Struct("vap_header", + # File size of the map when decompressed, include header size + UInt32("decompressed_size"), + UEnum16("compression_type", + "uncompressed", + "lzma", + ), + UEnum16("vap_version", + "chimera_1", + "next", + ), + + # Number of blocks. If 0, data is assumed to be one contiguous stream + UInt32("block_count"), + # File size of the map when compressed, include header size + UInt32("compressed_size"), + + UEnum32("feature_level", + "chimera_1", + "next", + ), + + # Player limit (campaign). Set to 0 for multiplayer and user interface maps + UInt16("max_players"), + # Reserved for future use. Leave it at 0 + Pad(10), + + # Build date of the map in ISO 8601 (yyyy-mm-ddThh:mm:ss.fffffffff) format + ascii_str32("build_date"), + + # Human-readable of the map + ascii_str32("name"), + + # Human-readable description of the map + StrLatin1("description", SIZE=128), + Pad(256), + STEPTREE=Array("blocks", + SIZE=".block_count", SUB_STRUCT=vap_block + ), + SIZE=480 + ) + +# Halo Demo maps have a different header +# structure with garbage filling the padding +map_header_demo = Struct("map header", + Pad(2), + gen1_map_type, # NOTE: in common_descs.py + Pad(700), + UEnum32('head', ('head', 'Ehed'), EDITABLE=False, DEFAULT='Ehed'), + UInt32("tag data size"), + ascii_str32("build date", EDITABLE=False), + Pad(672), + map_version, # NOTE: in common_descs.py + ascii_str32("map name"), + UInt32("unknown"), + UInt32("crc32"), + Pad(52), + UInt32("decomp len"), + UInt32("tag index header offset"), + UEnum32('foot', ('foot', 'Gfot'), EDITABLE=False, DEFAULT='Gfot'), + Pad(524), + SIZE=2048 + ) + +map_header = Struct("map header", + UEnum32('head', ('head', 'head'), DEFAULT='head'), + map_version, # NOTE: in common_descs.py + UInt32("decomp len"), + UInt32("unknown"), + UInt32("tag index header offset"), + UInt32("tag data size"), + Pad(8), + ascii_str32("map name"), + ascii_str32("build date", EDITABLE=False), + gen1_map_type, # NOTE: in common_descs.py + Pad(2), + UInt32("crc32"), + Pad(1), + Pad(3), + Pad(4), + yelo_header, + UEnum32('foot', ('foot', 'foot'), DEFAULT='foot', OFFSET=2044), + SIZE=2048 + ) + +mcc_flags = Bool8("mcc_flags", + "use_bitmaps_map", + "use_sounds_map", + "disable_remastered_sync", + ) + +map_header_mcc = desc_variant( + map_header, + ("pad_12", mcc_flags), + ) + +map_header_vap = desc_variant( + map_header, + ("yelo_header", Struct("vap_header", INCLUDE=vap_header, OFFSET=128)), + verify=False, + ) + +tag_header = Struct("tag_header", + UEnum32("class_1", GUI_NAME="primary tag class", INCLUDE=valid_tags_os), + UEnum32("class_2", GUI_NAME="secondary tag class", INCLUDE=valid_tags_os), + UEnum32("class_3", GUI_NAME="tertiary tag class", INCLUDE=valid_tags_os), + UInt32("id"), + UInt32("path_offset"), + UInt32("meta_offset"), + UInt8("indexed"), + Pad(3), + # if indexed is non-zero, the meta_offset is the literal index in + # the bitmaps, sounds, or loc cache that the meta data is located in. + # NOTE: indexed is NOT a bitfield, if it is non-zero it is True + UInt32("pad"), + STEPTREE=CStrTagRef("path", POINTER=tag_path_pointer, MAX=768), + SIZE=32 + ) + +tag_index_array = TagIndex("tag_index", + SIZE=".tag_count", SUB_STRUCT=tag_header, POINTER=tag_index_array_pointer + ) + +tag_index_pc = Struct("tag_index", + UInt32("tag_index_offset"), + UInt32("scenario_tag_id"), + UInt32("map_id"), # normally unused, but can be used + # for spoofing the maps checksum. + UInt32("tag_count"), + + UInt32("vertex_parts_count"), + UInt32("model_data_offset"), + + UInt32("index_parts_count"), + UInt32("index_parts_offset"), + UInt32("model_data_size"), + UInt32("tag_sig", EDITABLE=False, DEFAULT='tags'), + + SIZE=40, + STEPTREE=tag_index_array + ) + +tag_index_xbox = desc_variant(tag_index_pc, + ("model_data_size", Pad(0)), + SIZE=36, verify=False + ) + +map_header_def = BlockDef(map_header) +map_header_anni_def = BlockDef(map_header, endian=">") +map_header_demo_def = BlockDef(map_header_demo) +map_header_vap_def = BlockDef(map_header_vap) + +tag_index_xbox_def = BlockDef(tag_index_xbox) +tag_index_pc_def = BlockDef(tag_index_pc) +tag_index_anni_def = BlockDef(tag_index_pc, endian=">") + +map_header_vap_def = BlockDef(map_header_vap) +map_header_mcc_def = BlockDef(map_header_mcc) diff --git a/reclaimer/meta/halo1_rsrc_map.py b/reclaimer/meta/halo1_rsrc_map.py index 22965a09..f9213b06 100644 --- a/reclaimer/meta/halo1_rsrc_map.py +++ b/reclaimer/meta/halo1_rsrc_map.py @@ -6,548 +6,67 @@ # Reclaimer is free software under the GNU General Public License v3.0. # See LICENSE for more information. # -from collections import namedtuple -from copy import deepcopy -from struct import unpack -from traceback import format_exc -from reclaimer.constants import GEN_1_HALO_CUSTOM_ENGINES,\ - GEN_1_HALO_PC_ENGINES, GEN_1_HALO_GBX_ENGINES -from reclaimer import data_extraction -from reclaimer.mcc_hek.defs.bitm import bitm_def as pixel_root_subdef -from reclaimer.mcc_hek.defs.objs.bitm import MccBitmTag, HALO_P8_PALETTE -from reclaimer.stubbs.defs.objs.bitm import StubbsBitmTag, STUBBS_P8_PALETTE -from reclaimer.util import get_is_xbox_map -from reclaimer.meta.halo_map import map_header_def, tag_index_pc_def -from reclaimer.meta.halo1_rsrc_map import lite_halo1_rsrc_map_def as halo1_rsrc_map_def -from reclaimer.meta.wrappers.byteswapping import raw_block_def, byteswap_pcm16_samples -from reclaimer.meta.wrappers.map_pointer_converter import MapPointerConverter -from reclaimer.meta.wrappers.tag_index_manager import TagIndexManager -from reclaimer.meta.wrappers.halo_map import HaloMap -from reclaimer.sounds import ogg as sounds_ogg, constants as sound_const - -from supyr_struct.buffer import BytearrayBuffer, get_rawdata -from supyr_struct.field_types import FieldType - -# reassign since we only want a reference to the sub-definition -pixel_root_subdef = pixel_root_subdef.subdefs['pixel_root'] - -# this is ultra hacky, but it seems to be the only -# way to fix the tagid for the sounds resource map -sound_rsrc_id_map = { - 92: 7, # sound\sfx\impulse\impacts\smallrock - 93: 8, # sound\sfx\impulse\impacts\medrocks - 94: 9, # sound\sfx\impulse\impacts\lrgrocks - - 125: 12, # sound\sfx\impulse\impacts\metal_chips - 126: 13, # sound\sfx\impulse\impacts\metal_chip_med - - 372: 61, # sound\sfx\impulse\shellcasings\double_shell_dirt - 373: 62, # sound\sfx\impulse\shellcasings\multi_shell_dirt - 374: 63, # sound\sfx\impulse\shellcasings\single_shell_metal - 375: 64, # sound\sfx\impulse\shellcasings\double_shell_metal - 376: 65, # sound\sfx\impulse\shellcasings\multi_shell_metal - - 1545: 264, # sound\sfx\impulse\glass\glass_medium - 1546: 265, # sound\sfx\impulse\glass\glass_large - } - -DEFAULT_LOC_TAG_COUNT = 176 -DEFAULT_SOUNDS_TAG_COUNT = 376 -DEFAULT_BITMAPS_TAG_COUNT = 853 - - -# Tag classes aren't stored in the cache maps, so we need to -# have a cache of them somewhere. Might as well do it manually -loc_exts = {0:'font', 1:'font', 4:'hud_message_text', 56:'font', 58:'font'} - -bitmap_exts = ('bitmap', ) * DEFAULT_BITMAPS_TAG_COUNT -sound_exts = ('sound', ) * DEFAULT_SOUNDS_TAG_COUNT -loc_exts = tuple(loc_exts.get(i, 'unicode_string_list') - for i in range(DEFAULT_LOC_TAG_COUNT)) - - -def inject_sound_data(map_data, rsrc_data, rawdata_ref, map_magic): - if rawdata_ref.flags.data_in_resource_map: - data, ptr = rsrc_data, rawdata_ref.raw_pointer - elif rawdata_ref.pointer == 0: - data, ptr = map_data, rawdata_ref.raw_pointer - else: - data, ptr = map_data, rawdata_ref.pointer + map_magic - - if data and rawdata_ref.size: - data.seek(ptr) - rawdata_ref.data = data.read(rawdata_ref.size) - else: - # hack to ensure the size is preserved when - # we replace the rawdata with empty bytes - size = rawdata_ref.size - rawdata_ref.data = b'' - rawdata_ref.size = size - - -def uses_external_sounds(sound_meta): - for pitches in sound_meta.pitch_ranges.STEPTREE: - for perm in pitches.permutations.STEPTREE: - for b in (perm.samples, perm.mouth_data, perm.subtitle_data): - if b.flags.data_in_resource_map: - return True - return False - - -class MetaBitmTag(): - ''' - This class exists to facilitate processing bitmap tags extracted - from maps without fully converting them to tag objects first. - ''' - _fake_data_block = namedtuple('FakeDataBlock', - ("blam_header", "tagdata") - ) - def __init__(self, tagdata=None): - self.data = self._fake_data_block(None, tagdata) - - # stubed since there's nothing to calculate here - def calc_internal_data(self): pass - - @property - def pixel_root_definition(self): return pixel_root_subdef - - -class MetaHaloBitmTag(MetaBitmTag, MccBitmTag): - @property - def p8_palette(self): return HALO_P8_PALETTE - -class MetaStubbsBitmTag(MetaBitmTag, StubbsBitmTag): - @property - def p8_palette(self): return STUBBS_P8_PALETTE - - -class Halo1RsrcMap(HaloMap): - '''Generation 1 resource map''' - - # the original resource map header - rsrc_map = None - - tag_classes = None - tag_headers = None - - tag_defs_module = "reclaimer.hek.defs" - tag_classes_to_load = ("bitm", "snd!", "font", "hmt ", "ustr") - - data_extractors = data_extraction.h1_data_extractors - - def __init__(self, maps=None): - HaloMap.__init__(self, maps) - self.setup_tag_headers() - - def load_map(self, map_path, **kwargs): - map_data = get_rawdata(filepath=map_path, writable=False) - - resource_type = unpack(" Date: Sun, 31 Mar 2024 12:46:20 -0500 Subject: [PATCH 49/51] Set JMS versions --- reclaimer/model/jms/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reclaimer/model/jms/model.py b/reclaimer/model/jms/model.py index aef84cf8..2c3f2169 100644 --- a/reclaimer/model/jms/model.py +++ b/reclaimer/model/jms/model.py @@ -14,7 +14,7 @@ from ..constants import JMS_PERM_CANNOT_BE_RANDOMLY_CHOSEN_TOKEN,\ JMS_VER_HALO_1_TRI_REGIONS, JMS_VER_HALO_1_3D_UVWS,\ - JMS_VER_HALO_1_MARKER_RADIUS + JMS_VER_HALO_1_MARKER_RADIUS, JMS_VER_HALO_1_RETAIL class JmsModel: @@ -36,7 +36,7 @@ class JmsModel: def __init__(self, name="", node_list_checksum=0, nodes=None, materials=None, markers=None, regions=None, - verts=None, tris=None, version="8200"): + verts=None, tris=None, version=JMS_VER_HALO_1_RETAIL): name = name.strip(" ") perm_name = name From 59d8e8e76b8d7b917f7096d1f7eeaee173e90ed5 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Sun, 31 Mar 2024 12:47:28 -0500 Subject: [PATCH 50/51] Remove math import name --- reclaimer/meta/wrappers/halo1_map.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reclaimer/meta/wrappers/halo1_map.py b/reclaimer/meta/wrappers/halo1_map.py index 4dccce6f..37663674 100644 --- a/reclaimer/meta/wrappers/halo1_map.py +++ b/reclaimer/meta/wrappers/halo1_map.py @@ -710,7 +710,7 @@ def clean_tag_meta(self, meta, tag_id, tag_cls): max_dim = max(1, bitmap.width, bitmap.height) # subtract 2 to account for width/height of 1 or 2 not having mips - maxmips = int(max(0, math.log(max_dim, 2) - 2)) + maxmips = int(max(0, log(max_dim, 2) - 2)) # clip mipmap count to max and min number that can exist bitmap.mipmaps = max(0, min(maxmips, bitmap.mipmaps)) From 21e7faedea776710a046f089a547c3e4f4440945 Mon Sep 17 00:00:00 2001 From: Steven Garcia Date: Thu, 9 May 2024 02:13:34 -0500 Subject: [PATCH 51/51] Fix effect saving --- reclaimer/hek/defs/objs/effe.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/reclaimer/hek/defs/objs/effe.py b/reclaimer/hek/defs/objs/effe.py index 84cee398..c2aa7aa5 100644 --- a/reclaimer/hek/defs/objs/effe.py +++ b/reclaimer/hek/defs/objs/effe.py @@ -9,28 +9,31 @@ from reclaimer.hek.defs.objs.tag import HekTag from reclaimer.util.matrices import euler_2d_to_vector_3d -#from reclaimer.common_descs import valid_objects +from reclaimer.enums import object_types +from reclaimer.util import fourcc_to_int + +object_tag_class_ids = tuple( + fourcc_to_int(fourcc, byteorder='big') for fourcc in object_types + ) class EffeTag(HekTag): def calc_internal_data(self): HekTag.calc_internal_data(self) - never_cull = False + dont_cull = False for event in self.data.tagdata.events.STEPTREE: for part in event.parts.STEPTREE: - if part.type.tag_class.enum_name == 'light': - never_cull = True - - part.effect_class = part.type.tag_class - - #TODO: There is no good way to do this right now - #object_types = valid_objects('b').desc[0]['NAME_MAP'].keys() - #if part.effect_class.enum_name in object_types: - # part.effect_class.enum_name = 'object' + tag_cls = part.type.tag_class + if tag_cls.data in object_tag_class_ids: + dont_cull = True + part.effect_class.set_to('object') + elif tag_cls.enum_name in ("damage_effect", "light"): + dont_cull = True + part.effect_class.set_to(tag_cls.enum_name) for particle in event.particles.STEPTREE: particle.relative_direction_vector[:] = euler_2d_to_vector_3d( *particle.relative_direction ) - self.data.tagdata.flags.never_cull = never_cull + self.data.tagdata.flags.must_be_deterministic = dont_cull