420 lines
13 KiB
Lua
420 lines
13 KiB
Lua
-------------------------------------------------------------------------------
|
|
-- 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 |