------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file attention.lua --! @brief component for calculating attention of mobs --! @copyright Sapier --! @author Sapier --! @date 2013-04-02 -- --! @defgroup attention Attention subcomponent --! @brief Component handling attention of a mob. This incudes aggression as --! well as initial attack handling. --! @ingroup framework_int --! @{ -- Contact: sapier a t gmx net ------------------------------------------------------------------------------- --! @class attention --!@} mobf_assert_backtrace(attention == nil) --! @brief attention handling class reference --TODO rename to fix documentation issues attention = {} ------------------------------------------------------------------------------- -- @function [parent=#attention] aggression(entity) -- --! @brief old agression handler to be used for legacy mobs --! @memberof attention -- --! @param entity mob to do action --! @param now current time ------------------------------------------------------------------------------- function attention.aggression(entity,now) --if no combat data is specified don't do anything if entity.data.combat == nil then return end local current_state = entity.dynamic_data.state.current --mob is specified as self attacking if entity.data.combat.starts_attack and entity.dynamic_data.combat.target == nil and current_state.state_mode ~= "combat" then dbg_mobf.fighting_lvl3("MOBF: ".. entity.data.name .. " " .. now .. " aggressive mob, is it time to attack?") if entity.dynamic_data.combat.ts_last_aggression_chance + 1 < now then dbg_mobf.fighting_lvl3("MOBF: ".. entity.data.name .. " " .. now .. " lazzy time over try to find an enemy") entity.dynamic_data.combat.ts_last_aggression_chance = now if entity.data.combat.angryness ~= nil and math.random() < entity.data.combat.angryness then dbg_mobf.fighting_lvl3("MOBF: ".. entity.data.name .. " " .. now .. " really is angry") local target = fighting.get_target(entity) if target ~= nil then if target ~= entity.dynamic_data.combat.target then entity.dynamic_data.combat.target = target fighting.switch_to_combat_state(entity,now,target) local targetname = fighting.get_target_name(target) dbg_mobf.fighting_lvl2("MOBF: ".. entity.data.name .. " " .. now .. " starting attack at player: " ..targetname) minetest.log(LOGLEVEL_INFO, "MOBF: starting attack at player "..targetname) end end end end end end ------------------------------------------------------------------------------- -- @function [parent=#attention] callback(entity) -- --! @brief calculate attenntion level for mob mob --! @memberof attention -- --! @param entity mob to do action --! @param now current time ------------------------------------------------------------------------------- function attention.callback(entity,now) --do legacy code if entity.data.attention == nil then attention.aggression(entity,now) return end local top_attention_object = nil local top_attention_value = 0 local top_attention_enemy = nil local top_attention_enemy_value = 0 local current_attention_value = 0 mobf_assert_backtrace(entity.dynamic_data.attention ~= nil) --set default values local reduction_value = 0.1 local attention_distance = 5 local target_switch_offset = 0.5 if entity.data.attention.attention_distance ~= nil then attention_distance = entity.data.attention.attention_distance end if entity.data.attention.target_switch_offset ~= nil then target_switch_offset = entity.data.attention.target_switch_offset end --reduce attention level for all objects for k,v in pairs(entity.dynamic_data.attention.watched_objects) do if v.value > reduction_value then v.value = v.value - reduction_value dbg_mobf.attention_lvl3("MOBF: preserving " .. k .. " for watchlist new value: " .. v.value) else entity.dynamic_data.attention.watched_objects[k] = nil dbg_mobf.attention_lvl3("MOBF: removing " .. k .. " from watchlist") end end local new_objecttable = entity.dynamic_data.attention.watched_objects entity.dynamic_data.attention.watched_objects = new_objecttable local own_pos = entity.object:getpos() --get list of all objects in attention range local objectlist = minetest.get_objects_inside_radius(own_pos,attention_distance) if #objectlist > 0 then for i = 1 , #objectlist, 1 do local continue = true dbg_mobf.attention_lvl3("MOBF: checking " .. tostring(objectlist[i])) if not objectlist[i]:is_player() then local lua_entity = objectlist[i]:get_luaentity() if lua_entity ~= nil and not lua_entity.draws_attention then continue = false end end if continue then local remote_pos = objectlist[i]:getpos() local hear_addon = false local own_view_addon = false local remote_view_addon = false --is in audible distance if entity.data.attention.hear_distance ~= nil then local distance = mobf_calc_distance(own_pos,remote_pos) if distance < entity.data.attention.hear_distance then hear_addon = true end end --does own view angle matter if entity.data.attention.view_angle ~= nil then local own_view = entity.object:getyaw() local min_yaw = own_view - entity.data.attention.view_angle/2 local max_yaw = own_view + entity.data.attention.view_angle/2 local direction = mobf_get_direction(own_pos,remote_pos) local yaw_to_target = mobf_calc_yaw(direction.x,direction.z) if yaw_to_target > min_yaw and yaw_to_target < max_yaw then own_view_addon = true end end --does remote view angle matter if entity.data.attention.remote_view == true then local remote_view = objectlist[i]:getyaw() if objectlist[i]:is_player() then remote_view = objectlist[i]:get_look_yaw() end if remote_view ~= nil then local direction = mobf_get_direction(own_pos,remote_pos) local yaw_to_target = mobf_calc_yaw(direction.x,direction.z) --TODO check for overflows if remote_view > yaw_to_target - (math.pi/2) and remote_view < yaw_to_target + (math.pi/2) then remote_view_addon = true end else dbg_mobf.attention_lvl2( "MOBF: unable to get yaw for obeject: " ..table_id) end end --calculate new value local sum_values = 0; table_id = tostring(objectlist[i]) if hear_addon then dbg_mobf.attention_lvl3("MOBF: " .. table_id .. " within hear distance") sum_values = sum_values + entity.data.attention.hear_distance_value end if own_view_addon then dbg_mobf.attention_lvl3("MOBF: " .. table_id .. " in view") sum_values = sum_values + entity.data.attention.own_view_value end if remote_view_addon then dbg_mobf.attention_lvl3("MOBF: " .. table_id .. " looks towards mob") sum_values = sum_values + entity.data.attention.remote_view_value end sum_values = sum_values + entity.data.attention.attention_distance_value if new_objecttable[table_id] == nil then dbg_mobf.attention_lvl3("MOBF: " .. table_id .. " unknown adding new entry") new_objecttable[table_id] = { value = 0 } end new_objecttable[table_id].value = new_objecttable[table_id].value + sum_values if entity.data.attention.attention_max ~= nil and new_objecttable[table_id].value > entity.data.attention.attention_max then new_objecttable[table_id].value = entity.data.attention.attention_max end dbg_mobf.attention_lvl3("MOBF: adding " .. sum_values .. " to " .. table_id .. " new value " .. new_objecttable[table_id].value) --update overall atttention values if new_objecttable[table_id].value > top_attention_value then top_attention_value = new_objecttable[table_id].value top_attention_object = objectlist[i] end --update value of old most relevant target only if objectlist[i] == entity.dynamic_data.attention.most_relevant_target then current_attention_value = new_objecttable[table_id].value end --update enemy attention values if new_objecttable[table_id].value > top_attention_enemy_value and attention.is_enemy(entity,objectlist[i]) then top_attention_enemy_value = new_objecttable[table_id].value top_attention_enemy = objectlist[i] end end end end --check if top attention exceeds current + offset if top_attention_value > current_attention_value + target_switch_offset then --update top attention object entity.dynamic_data.attention.most_relevant_target = top_attention_object current_attention_value = top_attention_value end dbg_mobf.attention_lvl3("MOBF: value=" .. current_attention_value .. " attack_threshold=" .. dump(entity.data.attention.attack_threshold) .. " watch_threshold=" .. dump(entity.data.attention.watch_threshold)) local toattack = nil local attack_attention_value = nil if mobf_rtd.factions_available then if top_attention_enemy ~= nil then attack_attention_value = top_attention_enemy_value toattack = top_attention_enemy end --don't attack anyone if factions mod is available and no enemy is found else attack_attention_value = top_attention_value toattack = top_attention_object end if entity.data.attention.attack_threshold ~= nil and attack_attention_value ~= nil and attack_attention_value > entity.data.attention.attack_threshold then local current_state = mob_state.get_state_by_name(entity,entity.dynamic_data.state.current) if entity.data.combat.starts_attack then dbg_mobf.attention_lvl3("MOBF: attack threshold exceeded starting attack of " .. dump(entity.dynamic_data.attention.most_relevant_target)) entity.dynamic_data.attention.most_relevant_target = toattack current_attention_value = attack_attention_value fighting.set_target(entity,toattack) end else if entity.data.attention.watch_threshold ~= nil and current_attention_value > entity.data.attention.watch_threshold then dbg_mobf.attention_lvl2("MOBF: watch threshold exceeded: value=" .. current_attention_value .. " threshold=" .. entity.data.attention.watch_threshold ) if entity.data.attention.watch_callback ~= nil and type(entity.data.attention.watch_callback) == "function" then entity.data.attention.watch_callback(entity, entity.dynamic_data.attention.most_relevant_target) end end end entity.dynamic_data.attention.current_value = current_attention_value end ------------------------------------------------------------------------------- -- @function [parent=#attention] init_dynamic_data(entity) -- --! @brief initialize all dynamic data on activate --! @memberof attention -- --! @param entity mob to do action --! @param now current time ------------------------------------------------------------------------------- function attention.init_dynamic_data(entity,now) local data = { watched_objects = {}, most_relevant_target = nil, current_value = 0, } entity.dynamic_data.attention = data end ------------------------------------------------------------------------------- -- @function [parent=#attention] increase_attention_level(entity,source,value) -- --! @brief initialize all dynamic data on activate --! @memberof attention -- --! @param entity mob to do action --! @param source object causing this change --! @param value amount of change ------------------------------------------------------------------------------- function attention.increase_attention_level(entity,source,value) table_id = tostring(source) if entity.dynamic_data.attention ~= nil then if entity.dynamic_data.attention.watched_objects[table_id] == nil then entity.dynamic_data.attention.watched_objects[table_id] = { value = 0 } end entity.dynamic_data.attention.watched_objects[table_id].value = entity.dynamic_data.attention.watched_objects[table_id].value + value end end ------------------------------------------------------------------------------- -- @function [parent=#attention] is_enemy(entity,object) -- --! @brief initialize all dynamic data on activate --! @memberof attention -- --! @param entity mob to do action --! @param object to check if it's an enemy --! @return true/false ------------------------------------------------------------------------------- function attention.is_enemy(entity,object) mobf_assert_backtrace(entity ~= nil) mobf_assert_backtrace(object ~= nil) if mobf_rtd.factions_available then if entity.object == object then return false end local remote_factions = factions.get_factions(object) if remote_factions == nil or #remote_factions < 1 then dbg_mobf.attention_lvl3("MOBF: " .. entity.data.name .. " no remote factions for: " .. tostring(object)) return false end for j=1, #remote_factions, 1 do local rep = factions.get_reputation(remote_factions[j],entity) if rep < 0 then dbg_mobf.attention_lvl3("MOBF: ".. remote_factions[j] .. " " .. tostring(object) .. " is enemy: " .. rep) return true end end end return false end