diff --git a/MHR_Overlay/Monsters/large_monster.lua b/MHR_Overlay/Monsters/large_monster.lua index 4073d94..2782086 100644 --- a/MHR_Overlay/Monsters/large_monster.lua +++ b/MHR_Overlay/Monsters/large_monster.lua @@ -41,6 +41,8 @@ function large_monster.new(enemy) monster.rage_seconds_left = 0; monster.rage_timer_percentage = 0; + monster.game_object = nil + monster.transform = nil monster.position = Vector3f.new(0, 0, 0); monster.name = "Large Monster"; @@ -177,52 +179,125 @@ function large_monster.init_dynamic_UI(monster) end end +local enemy_character_base_type = sdk.find_type_definition("snow.enemy.EnemyCharacterBase") +local physical_param_field = enemy_character_base_type:get_field("k__BackingField"); +local status_param_field = enemy_character_base_type:get_field("k__BackingField") +local stamina_param_field = enemy_character_base_type:get_field("k__BackingField") +local anger_param_field = enemy_character_base_type:get_field("k__BackingField") + +local physical_param_type = physical_param_field:get_type() +local get_vital_method = physical_param_type:get_method("getVital") +local get_capture_hp_vital_method = physical_param_type:get_method("get_CaptureHpVital") +local vital_list_field = physical_param_type:get_field("_VitalList") + +local vital_param_type = get_vital_method:get_return_type() +local get_current_method = vital_param_type:get_method("get_Current") +local get_max_method = vital_param_type:get_method("get_Max") + +local stamina_param_type = stamina_param_field:get_type() +local get_stamina_method = stamina_param_type:get_method("getStamina") +local get_max_stamina_method = stamina_param_type:get_method("getMaxStamina") + +local anger_param_type = anger_param_field:get_type() +local is_anger_method = anger_param_type:get_method("isAnger") +local get_anger_point_method = anger_param_type:get_method("get_AngerPoint") +local get_limit_anger_method = anger_param_type:get_method("get_LimitAnger") +local anger_param_get_timer_method = anger_param_type:get_method("get_Timer") +local get_timer_anger_method = anger_param_type:get_method("get_TimerAnger") +local get_count_anger_method = anger_param_type:get_method("get_CountAnger") + +local get_gameobject_method = sdk.find_type_definition("via.Component"):get_method("get_GameObject") +local get_transform_method = sdk.find_type_definition("via.GameObject"):get_method("get_Transform") +local get_position_method = sdk.find_type_definition("via.Transform"):get_method("get_Position") + +function large_monster.update_position(enemy) + if not config.current_config.large_monster_UI.enabled then + return; + end + + local monster = large_monster.get_monster(enemy); + if not monster then return end + + -- cache off the game object and transform + -- as these are pretty much guaranteed to stay constant + -- as long as the enemy is alive + if monster.game_object == nil then + monster.game_object = get_gameobject_method:call(enemy) + if monster.game_object == nil then + customization_menu.status = "No enemy game object"; + return; + end + end + + if monster.transform == nil then + monster.transform = get_transform_method:call(monster.game_object) + if monster.transform == nil then + customization_menu.status = "No enemy transform"; + return; + end + end + + local position = get_position_method:call(monster.transform) + if not position then + customization_menu.status = "No enemy position"; + return; + end + + if position ~= nil then + monster.position = position; + end +end + function large_monster.update(enemy) + if not config.current_config.large_monster_UI.enabled then + return; + end + if enemy == nil then return; end local monster = large_monster.get_monster(enemy); - local physical_param = enemy:get_field("k__BackingField"); + local physical_param = physical_param_field:get_data(enemy) if physical_param == nil then customization_menu.status = "No physical param"; return; end - local vital_param = physical_param:call("getVital", 0, 0); + local vital_param = get_vital_method:call(physical_param, 0, 0); if vital_param == nil then customization_menu.status = "No vital param"; return; end - local health = vital_param:call("get_Current"); - local max_health = vital_param:call("get_Max"); - local capture_health = physical_param:call("get_CaptureHpVital"); + local health = get_current_method:call(vital_param) + local max_health = get_max_method:call(vital_param) + local capture_health = get_capture_hp_vital_method:call(physical_param) - local stamina_param = enemy:get_field("k__BackingField"); + local stamina_param = stamina_param_field:get_data(enemy) if stamina_param == nil then customization_menu.status = "No stamina param"; return; end - local stamina = stamina_param:call("getStamina"); - local max_stamina = stamina_param:call("getMaxStamina"); + local stamina = get_stamina_method:call(stamina_param) + local max_stamina = get_max_stamina_method:call(stamina_param) - local anger_param = enemy:get_field("k__BackingField"); + local anger_param = anger_param_field:get_data(enemy) if anger_param == nil then customization_menu.status = "No anger param"; return; end - local is_in_rage = anger_param:call("isAnger"); - local rage_point = anger_param:call("get_AngerPoint"); - local rage_limit = anger_param:call("get_LimitAnger"); - local rage_timer = anger_param:call("get_Timer"); - local rage_duration = anger_param:call("get_TimerAnger"); - local rage_count = anger_param:call("get_CountAnger"); + local is_in_rage = is_anger_method:call(anger_param) + local rage_point = get_anger_point_method:call(anger_param) + local rage_limit = get_limit_anger_method:call(anger_param) + local rage_timer = anger_param_get_timer_method:call(anger_param) + local rage_duration = get_timer_anger_method:call(anger_param) + local rage_count = get_count_anger_method:call(anger_param) - local vital_list = physical_param:get_field("_VitalList"); + local vital_list = vital_list_field:get_data(physical_param) if vital_list == nil then customization_menu.status = "No vital list"; return; @@ -290,23 +365,7 @@ function large_monster.update(enemy) ::continue:: end - local enemy_game_object = enemy:call("get_GameObject"); - if enemy_game_object == nil then - customization_menu.status = "No enemy game object"; - return; - end - - local enemy_transform = enemy_game_object:call("get_Transform"); - if enemy_transform == nil then - customization_menu.status = "No enemy transform"; - return; - end - - local position = enemy_transform:call("get_Position"); - if not position then - customization_menu.status = "No enemy position"; - return; - end + large_monster.update_position(enemy); if health ~= nil then monster.health = health; @@ -327,10 +386,6 @@ function large_monster.update(enemy) end end - if position ~= nil then - monster.position = position; - end - if stamina ~= nil then monster.stamina = stamina; end diff --git a/MHR_Overlay/Monsters/monster_hook.lua b/MHR_Overlay/Monsters/monster_hook.lua index 0d46acb..37aca49 100644 --- a/MHR_Overlay/Monsters/monster_hook.lua +++ b/MHR_Overlay/Monsters/monster_hook.lua @@ -11,17 +11,100 @@ end, function(retval) return retval; end); +local is_boss_enemy_method = sdk.find_type_definition("snow.enemy.EnemyCharacterBase"):get_method("get_isBossEnemy"); +local tick_count = 0 +local last_update_tick = 0 +local recorded_monsters = {} +local updated_monsters = {} +local known_big_monsters = {} +local num_known_monsters = 0 +local num_updated_monsters = 0 + +local updates_this_tick = 0 +local MAX_UPDATES_PER_TICK = 2 + +-- run every tick to keep track of msonsters +-- whenever we've updated enough monsters to surpass how many we've seen, +-- we reset and start over +-- this allows us to only update N monsters per tick to save on performance +-- the reason for this is that the hooks on all the monsters' update functions +-- causes a HUGE performance hit (adds ~3+ ms to UpdateBehavior and frame time) +re.on_pre_application_entry("UpdateBehavior", function() + tick_count = tick_count + 1 + updates_this_tick = 0 + + if num_known_monsters ~= 0 and + num_updated_monsters >= num_known_monsters or + tick_count >= num_known_monsters * 2 + then + recorded_monsters = {} + updated_monsters = {} + known_big_monsters = {} + last_update_tick = 0 + tick_count = 0 + num_known_monsters = 0 + num_updated_monsters = 0 + updates_this_tick = 0 + end +end) + function monster.update_monster(enemy) - if enemy == nil then return; end - local is_large = enemy:call("get_isBossEnemy"); + if not recorded_monsters[enemy] then + num_known_monsters = num_known_monsters + 1 + recorded_monsters[enemy] = true + end + + -- saves on a method call. + if not known_big_monsters[enemy] then + known_big_monsters[enemy] = is_boss_enemy_method:call(enemy); + end + + local is_large = known_big_monsters[enemy]; + if is_large == nil then return; end + + if updated_monsters[enemy] then + -- this is the VERY LEAST thing we should do all the time + -- so the position doesn't lag all over the place + -- due to how infrequently we update the monster(s). + if is_large then + large_monster.update_position(enemy); + else + small_monster.update_position(enemy); + end + + return + end + + -- only updates N monsters per tick to increase performance + if tick_count == last_update_tick then + if updates_this_tick >= MAX_UPDATES_PER_TICK then + -- this is the VERY LEAST thing we should do all the time + -- so the position doesn't lag all over the place + -- due to how infrequently we update the monster(s). + if is_large then + large_monster.update_position(enemy); + else + small_monster.update_position(enemy); + end + + return + end + end + + updates_this_tick = updates_this_tick + 1; + last_update_tick = tick_count; + num_updated_monsters = num_updated_monsters + 1; + updated_monsters[enemy] = true; + -- actually update the enemy now. we don't do this very often + -- due to how much CPU time it takes to update each monster. if is_large then large_monster.update(enemy); else diff --git a/MHR_Overlay/Monsters/small_monster.lua b/MHR_Overlay/Monsters/small_monster.lua index 476cc28..0d768a6 100644 --- a/MHR_Overlay/Monsters/small_monster.lua +++ b/MHR_Overlay/Monsters/small_monster.lua @@ -25,7 +25,10 @@ function small_monster.new(enemy) monster.stamina_percentage = 0; monster.missing_stamina = 0; + monster.game_object = nil + monster.transform = nil monster.position = Vector3f.new(0, 0, 0); + monster.name = "Small Monster"; small_monster.init(monster, enemy); @@ -77,60 +80,106 @@ function small_monster.init_UI(monster) ); end +local enemy_character_base_type = sdk.find_type_definition("snow.enemy.EnemyCharacterBase") +local physical_param_field = enemy_character_base_type:get_field("k__BackingField"); +local status_param_field = enemy_character_base_type:get_field("k__BackingField") +local stamina_param_field = enemy_character_base_type:get_field("k__BackingField") + +local physical_param_type = physical_param_field:get_type() +local get_vital_method = physical_param_type:get_method("getVital") +local get_capture_hp_vital_method = physical_param_type:get_method("get_CaptureHpVital") + +local vital_param_type = get_vital_method:get_return_type() +local get_current_method = vital_param_type:get_method("get_Current") +local get_max_method = vital_param_type:get_method("get_Max") + +local stamina_param_type = stamina_param_field:get_type() +local get_stamina_method = stamina_param_type:get_method("getStamina") +local get_max_stamina_method = stamina_param_type:get_method("getMaxStamina") + +local get_gameobject_method = sdk.find_type_definition("via.Component"):get_method("get_GameObject") +local get_transform_method = sdk.find_type_definition("via.GameObject"):get_method("get_Transform") +local get_position_method = sdk.find_type_definition("via.Transform"):get_method("get_Position") + +function small_monster.update_position(enemy) + if not config.current_config.small_monster_UI.enabled then + return; + end + + local monster = small_monster.get_monster(enemy); + if not monster then return end + + -- cache off the game object and transform + -- as these are pretty much guaranteed to stay constant + -- as long as the enemy is alive + if monster.game_object == nil then + monster.game_object = get_gameobject_method:call(enemy) + if monster.game_object == nil then + customization_menu.status = "No enemy game object"; + return; + end + end + + if monster.transform == nil then + monster.transform = get_transform_method:call(monster.game_object) + if monster.transform == nil then + customization_menu.status = "No enemy transform"; + return; + end + end + + local position = get_position_method:call(monster.transform) + if not position then + customization_menu.status = "No enemy position"; + return; + end + + if position ~= nil then + monster.position = position; + end +end + function small_monster.update(enemy) if enemy == nil then return; end - local physical_param = enemy:get_field("k__BackingField"); + if not config.current_config.small_monster_UI.enabled then + return; + end + + local physical_param = physical_param_field:get_data(enemy) if physical_param == nil then customization_menu.status = "No physical param"; return; end - local status_param = enemy:get_field("k__BackingField"); + local status_param = status_param_field:get_data(enemy) if status_param == nil then customization_menu.status = "No status param"; return; end - local stamina_param = enemy:get_field("k__BackingField"); + local stamina_param = stamina_param_field:get_data(enemy) if stamina_param == nil then customization_menu.status = "No stamina param"; return; end - local vital_param = physical_param:call("getVital", 0, 0); + local vital_param = get_vital_method:call(physical_param, 0, 0); if vital_param == nil then customization_menu.status = "No vital param"; return; end - local health = vital_param:call("get_Current"); - local max_health = vital_param:call("get_Max"); - local capture_health = physical_param:call("get_CaptureHpVital"); + local health = get_current_method:call(vital_param) + local max_health = get_max_method:call(vital_param) + local capture_health = get_capture_hp_vital_method:call(physical_param) - local stamina = stamina_param:call("getStamina"); - local max_stamina = stamina_param:call("getMaxStamina"); + local stamina = get_stamina_method:call(stamina_param) + local max_stamina = get_max_stamina_method:call(stamina_param) - - local enemy_game_object = enemy:call("get_GameObject"); - if enemy_game_object == nil then - customization_menu.status = "No enemy game object"; - return; - end - - local enemy_transform = enemy_game_object:call("get_Transform"); - if enemy_transform == nil then - customization_menu.status = "No enemy transform"; - return; - end - - local position = enemy_transform:call("get_Position"); - if not position then - customization_menu.status = "No enemy position"; - return; - end + small_monster.update_position(enemy) local monster = small_monster.get_monster(enemy); @@ -153,10 +202,6 @@ function small_monster.update(enemy) end end - if position ~= nil then - monster.position = position; - end - if stamina ~= nil then monster.stamina = stamina; end