diff --git a/README.md b/README.md index b5dc8552..f2817f69 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,6 @@ The following mods are also included: * [chatlog][] ([CC0](mods/chat/chatlog/Readme.txt)) * crafting/ * [craft_guide][] ([BSD 3-Clause](mods/crafting/craft_guide/LICENSE)) -* engines/ - * [creatures (Creatures MOB-Engine)][cme] ([zlib/CC-BY-SA](doc/modpacks/cme/README.txt)) * farming/ * [farming_plus][] ([WTFPL](mods/farming/farming_plus/README.txt)) * friendlies/ @@ -44,6 +42,9 @@ The following mods are also included: * [quartz][] ([MIT](mods/materials/quartz/LICENSE.txt)) * [unifieddyes][] ([GPL](mods/materials/unifieddyes/LICENSE)) * [mesecons (modpack)][mesecons] ([LGPL/CC-BY-SA](mods/mesecons/COPYING.txt)) +* mob_engines/ + * [creatures (Creatures MOB-Engine)][cme] ([zlib/CC-BY-SA](doc/modpacks/cme/README.txt)) + * [mobf (MOB Framework core)][mobf] ([CC-BY-SA](mods/mob_engines/mobf/License.txt)) * [pipeworks][] ([WTFPL](mods/pipeworks/LICENSE)) * plantlife/ * player/ @@ -86,6 +87,7 @@ The following mods are also included: [homedecor]: https://forum.minetest.net/viewtopic.php?t=2041 [lightning]: https://forum.minetest.net/viewtopic.php?t=13886 [mesecons]: https://forum.minetest.net/viewtopic.php?t=628 +[mobf]: https://github.com/sapier/mobf_core [moreblocks]: https://forum.minetest.net/viewtopic.php?t=509 [moreores]: https://forum.minetest.net/viewtopic.php?t=549 [moretrees]: https://forum.minetest.net/viewtopic.php?t=4394 diff --git a/mods/mob_engines/mobf/License.txt b/mods/mob_engines/mobf/License.txt new file mode 100644 index 00000000..15877d80 --- /dev/null +++ b/mods/mob_engines/mobf/License.txt @@ -0,0 +1,5 @@ +Licenses + +Everything not mentioned: + CC-BY-SA 3.0, Author sapier + URL: http://creativecommons.org/licenses/by-sa/3.0/de/legalcode \ No newline at end of file diff --git a/mods/mob_engines/mobf/README b/mods/mob_engines/mobf/README new file mode 100644 index 00000000..b36dc8fa --- /dev/null +++ b/mods/mob_engines/mobf/README @@ -0,0 +1,63 @@ +------------------------------------------------------------------------------- +Mob Framework Mod (former animals mod) provides a framework for creating mobs + +(c) sapier (code,some graphics) + +Contact sapier a t gmx net +------------------------------------------------------------------------------- +Note: this is now mobf core readme only, +for modpack readme see animals_modpack/README +------------------------------------------------------------------------------- + +Documentation: +https://github.com/sapier/animals_modpack/wiki/User-documentation + +FAQ: +https://github.com/sapier/animals_modpack/wiki/Frequently-asked-questions + +Changelog: +------------------------------------------------------------------------------- +Changes 2.5.1 +-Add compatibility fix for old minetest versions + +Changes 2.5.0 +-Pass clicker to rightlick button name handlers +-Implement usage of different animations in mgen_follow for walking and waiting +-Fixed some bugs detected on writing mobs_redo compat layer +-Add multitexture mob support +-Add support for manually specifying item image +-Add support for manual animation speed +-Add support for configurable footstep sounds + +Changes 2.4.94 +-Add support for enabling spawner regeneration + +Changes 2.4.93 +-Fix custom oncatch handler triggered to early +-Fix facedir offset fix not applied to initial state after activation + +Changes 2.4.92 +-add owner() member function to mobf entities +-add get_persistent_data() member fucntion to mobf entities +-add set_state(statename) member function to mobf entities +-add support for entity specific on_rightclick callbacks +-add support for entity specific function can_be_cought in catching definition +-fix crash if barn beeing punched by something different to player +-fix a lot of warnings about use of undefined globals +-add workaround for minetests broken pathfinding +-add spanish translation (wip) + +Changes 2.4.91 +-fix crash on loading mobf_settings +-fix crash on fighting +-fix owner of breed animals +-add more debug info + +Changes 2.4.90 +-make mobs stop within sane distance to player on doing melee fight +-add support for specifiying a model orientation offset for models "heading" + towards "side" + +Changes 2.4.9x +-multi random sound specification has changed in incompatible way and needs to be +updated. diff --git a/mods/mob_engines/mobf/api.lua b/mods/mob_engines/mobf/api.lua new file mode 100644 index 00000000..1383abfe --- /dev/null +++ b/mods/mob_engines/mobf/api.lua @@ -0,0 +1,296 @@ +------------------------------------------------------------------------------- +-- 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 api.lua +--! @brief api functions to be used by other mods +--! @copyright Sapier +--! @author Sapier +--! @date 2012-12-27 +-- +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +-- @function mobf_register_on_death_callback(callback) +-- +--! @brief get version of mob framework +--! @ingroup framework_mob +-- +--! @param callback callback to register +--! @return true/false +------------------------------------------------------------------------------- +function mobf_register_on_death_callback(callback) + return fighting.register_on_death_callback(callback) +end + +------------------------------------------------------------------------------- +-- @function mobf_get_mob_definition(mobname) +-- +--! @brief get COPY of mob definition +--! @ingroup framework_mob +-- +--! @return mobf version +------------------------------------------------------------------------------- +function mobf_get_mob_definition(mobname) + + if mobf_rtd.registred_mob_data[mobname] ~= nil then + local copy = minetest.serialize(mobf_rtd.registred_mob_data[mobname]) + return minetest.deserialize(copy) + end + return nil +end + +------------------------------------------------------------------------------- +-- @function mobf_get_version() +-- +--! @brief get version of mob framework +--! @ingroup framework_mob +-- +--! @return mobf version +------------------------------------------------------------------------------- +function mobf_get_version() + return mobf_version +end + +------------------------------------------------------------------------------ +-- @function mobf_add_mob(mob) +-- +--! @brief register a mob within mob framework +--! @ingroup framework_mob +-- +--! @param mob a mob declaration +------------------------------------------------------------------------------- +function mobf_add_mob(mob) + + if not mobf.check_definition(mob) then + minetest.log(LOGLEVEL_ERROR,"MOBF: mob definition is invalid!") + return + end + + --check if mob is blacklisted + --mobs from the blacklist are pre-registered at startup + if mobf_contains(mobf_rtd.registred_mob,mob.modname.. ":"..mob.name) then + mobf.blacklisthandling(mob) + return false + end + + --if a random drop is specified for this mob register it + if mob.random_drop ~= nil then + random_drop.register(mob.random_drop) + end + + --create default entity + minetest.log(LOGLEVEL_INFO,"MOBF: adding: " .. mob.name) + mob_state.prepare_states(mob) + + mobf.register_entity(":" .. mob.modname .. ":"..mob.name, + graphics.graphics_by_statename(mob,"default"), mob) + + --add compatibility entity to replace old __default entities by new ones + minetest.log(LOGLEVEL_INFO,"MOBF: registering compatibility entity: >" .. + ":" .. mob.modname .. ":"..mob.name .. "__default" .. "<") + minetest.register_entity(":" .. mob.modname .. ":"..mob.name .. "__default", + { + replacement_name = mob.modname .. ":"..mob.name, + on_activate = function(self,staticdata) + + local pos = self.object:getpos() + + if pos ~= nil then + local newobject = minetest.add_entity(pos,self.replacement_name) + local spawned_entity = mobf_find_entity(newobject) + + if spawned_entity ~= nil then + spawned_entity.dynamic_data.initialized = false + if (staticdata ~= "") then + spawned_entity.dynamic_data.last_static_data = staticdata + end + end + end + self.object:remove() + end, + }) + + mobf.register_mob_item(mob.name,mob.modname,mob.generic.description, mob.generic.itemimage) + + --check if a movement pattern was specified + if mobf_rtd.movement_patterns[mob.movement.pattern] == nil then + minetest.log(LOGLEVEL_WARNING,"MOBF: no movement pattern specified!") + end + + if mob.spawning ~= nil then + minetest.log(LOGLEVEL_WARNING,"MOBF: \"" .. mob.name .. + "\" is still using mob internal spawning," .. + " this is DEPRECATED and going to be removed soon!") + spawning.register_mob(mob) + end + + --register factions required by mob + mobf_factions.setupmob(mob.factions) + + if mob.generic.stepheight == nil then + mob.generic.stepheight = 0 + end + + --register mob name to internal data structures + table.insert(mobf_rtd.registred_mob,mob.modname.. ":"..mob.name) + mobf_rtd.registred_mob_data[mob.modname.. ":"..mob.name] = mob; + + return true +end + +------------------------------------------------------------------------------ +-- @function mobf_is_known_mob(name) +-- +--! @brief check if mob of name is known +--! @ingroup framework_mob +-- +--! @param name name to check if it's a mob +--! @return true/false +------------------------------------------------------------------------------- +function mobf_is_known_mob(name) + for i,val in ipairs(mobf_rtd.registred_mob) do + if name == val then + return true + end + end + + return false +end + +------------------------------------------------------------------------------ +-- @function mobf_register_environment(name,environment) +-- +--! @brief register an environment to mob framework +--! @ingroup framework_mob +-- +--! @param name of environment +--! @param environment specification +--! @return true/false +------------------------------------------------------------------------------- +function mobf_register_environment(name,environment) + return environment.register(name,environment) +end + +------------------------------------------------------------------------------ +-- @function mobf_environment_by_name(name) +-- +--! @brief get environment by name +--! @ingroup framework_mob +-- +--! @param name of environment +--! @return environment definition +------------------------------------------------------------------------------- +function mobf_environment_by_name(name) + if environment_list[name] ~= nil then + return minetest.deserialize(minetest.serialize(environment_list[name])) + else + return nil + end +end + +------------------------------------------------------------------------------ +-- @function mobf_probab_movgen_register_pattern(pattern) +-- +--! @brief register an movement pattern for probabilistic movement gen +--! @ingroup framework_mob +-- +--! @param pattern to register (see pattern specification) +--! @return true/false +------------------------------------------------------------------------------- +function mobf_probab_movgen_register_pattern(pattern) + return movement_gen.register_pattern(pattern) +end + +------------------------------------------------------------------------------ +-- @function mobf_spawner_register(name,spawndef) +-- +--! @brief register a spawndef to adv_spawning +--! @ingroup framework_mob +-- +--! @param name of spawner +--! @param mobname name of mob to register spawner for +--! @param spawndef defintion of spawner +--! @return true/false +------------------------------------------------------------------------------- +function mobf_spawner_register(name,mobname,spawndef) + + --check if spawning is enabled + if minetest.world_setting_get("mobf_disable_animal_spawning") then + return false + end + + --check if mob is blacklisted + if mobf_contains(mobf_rtd.registred_mob,mobname) then + minetest.log(LOGLEVEL_NOTICE,"MOBF: " .. mobname .. " is blacklisted, not adding spawner") + return false + end + + local customcheck = spawndef.custom_check + + + spawndef.custom_check = function(pos,spawndef) + local entities_around = spawndef.entities_around + + if entities_around ~= nil then + for i=1,#entities_around,1 do + + --only do this check if relevant area is larger then activity range + if entities_around[i].distance > adv_spawning.active_range then + local count = spawning.count_deactivated_mobs( + mobname, + pos, + entities_around[i].distance) + + local entity_active = + minetest.get_objects_inside_radius(pos, + entities_around[i].distance) + + for j=1,#entity_active,1 do + local entity = entity_active[j]:get_luaentity() + + if entity ~= nil then + if entity.name == entities_around[i].entityname then + count = count +1 + end + + if count + count > entities_around[i].threshold then + break + end + end + end + + if entities_around[i].type == "MIN" and + count < entities_around[i].threshold then + dbg_mobf.mobf_core_lvl3( + "MOBF: MIN around not met: already: " .. count .. + " relevant entities around") + return false, "not enough entities around, only: " .. count .. " < " .. entities_around[i].threshold + end + + if entities_around[i].type == "MAX" and + count > entities_around[i].threshold then + dbg_mobf.mobf_core_lvl3( + "MOBF: MAX around not met: already: " .. count .. + " relevant entities around") + + return false, "too many entities around, " .. count .. " > " .. entities_around[i].threshold + end + end + end + end + + if type(customcheck) == "function" and not customcheck(pos,spawndef) then + return false, "customcheck failed" + end + + return true, "entities around and customcheck ok" + end + + --register + adv_spawning.register(name,spawndef) +end diff --git a/mods/mob_engines/mobf/attention.lua b/mods/mob_engines/mobf/attention.lua new file mode 100644 index 00000000..67565103 --- /dev/null +++ b/mods/mob_engines/mobf/attention.lua @@ -0,0 +1,421 @@ +------------------------------------------------------------------------------- +-- 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(not core.global_exists("attention")) +--! @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 = graphics.getyaw(entity) + + 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 + + local table_id = tostring(objectlist[i]) + + --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 object: " ..table_id) + end + end + + --calculate new value + + local sum_values = 0; + + 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 \ No newline at end of file diff --git a/mods/mob_engines/mobf/compatibility.lua b/mods/mob_engines/mobf/compatibility.lua new file mode 100644 index 00000000..6fb64273 --- /dev/null +++ b/mods/mob_engines/mobf/compatibility.lua @@ -0,0 +1,111 @@ +------------------------------------------------------------------------------- +-- 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 compatibility.lua +--! @brief contains compatibility/transition code thats to be removed +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +minetest.register_abm({ + nodenames = { "animalmaterials:wool_white" }, + interval = 1, + chance = 1, + + action = function(pos, node, active_object_count, active_object_count_wider) + minetest.remove_node(pos) + minetest.add_node(pos,{name="wool:white"}) + end + + }) + +minetest.register_abm({ + nodenames = { "animalmaterials:wool_grey" }, + interval = 1, + chance = 1, + + action = function(pos, node, active_object_count, active_object_count_wider) + minetest.remove_node(pos) + minetest.add_node(pos,{name="wool:grey"}) + end + + }) + +minetest.register_abm({ + nodenames = { "animalmaterials:wool_brown" }, + interval = 1, + chance = 1, + + action = function(pos, node, active_object_count, active_object_count_wider) + minetest.remove_node(pos) + minetest.add_node(pos,{name="wool:brown"}) + end + + }) + +minetest.register_abm({ + nodenames = { "animalmaterials:wool_black" }, + interval = 1, + chance = 1, + + action = function(pos, node, active_object_count, active_object_count_wider) + minetest.remove_node(pos) + minetest.add_node(pos,{name="wool:black"}) + end + + }) + + +minetest.register_entity("mobf:compat_spawner", + { + collisionbox = {0,0,0,0,0,0}, + physical = false, + groups = { "immortal" }, + on_activate = + function(self,staticdata,dtime_s) + local pos = self.object:getpos() + local delta,y_offset = adv_spawning.get_spawner_density() + + local spawnerpos = { + x = math.floor(pos.x/delta) * delta, + y = math.floor((pos.y-y_offset)/delta) * delta + y_offset, + z = math.floor(pos.x/delta) * delta + } + + local objects_at = minetest.get_objects_inside_radius(spawnerpos, 0.5) + + local found = false + + for i=1,#objects_at,1 do + local luaentity = objects_at[i]:get_luaentity() + + if luaentity ~= nil then + if luaentity.name == "adv_spawning:spawn_seed" then + found = true + end + end + end + + if not found then + minetest.add_entity(spawnerpos,"adv_spawning:spawn_seed") + end + + self.object:remove() + end, + }) + + +------------------------------------------------------------------------------- +-- compatibility functions to make transition to new name easier +------------------------------------------------------------------------------- + +function animals_add_animal(animal) + mobf_add_mob(animal) +end \ No newline at end of file diff --git a/mods/mob_engines/mobf/debug.lua b/mods/mob_engines/mobf/debug.lua new file mode 100644 index 00000000..6355806e --- /dev/null +++ b/mods/mob_engines/mobf/debug.lua @@ -0,0 +1,500 @@ +------------------------------------------------------------------------------- +-- 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 debug.lua +--! @brief contains debug functions for mob framework +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +--! +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +mobf_assert_backtrace(not core.global_exists("mobf_debug")) +--! @defgroup debug_in_game In game debugging functions +--! @brief debugging functions to be called from in game +--! @ingroup framework_int +--! @{ +mobf_debug = {} + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_debug] print_usage(player,command,toadd) +-- +--! @brief send errormessage to player +-- +--! @param player name of player to print usage +--! @param command display usage for this command +--! @param toadd additional information to transfer to player +------------------------------------------------------------------------------- +function mobf_debug.print_usage(player, command, toadd) + + if toadd == nil then + toadd = "" + end + + if command == "spawnmob" then + print("CMD: ".. player .."> ".. "Usage: /spawnmob " .. toadd) + minetest.chat_send_player(player, "Usage: /spawnmob " .. toadd) + end + + if command == "ukn_mob" then + print("CMD: ".. player .."> ".. "Unknown mob name "..toadd) + minetest.chat_send_player(player, "Unknown mob name "..toadd) + end + + if command == "inv_pos" then + print("CMD: ".. player .."> ".. "Invalid position "..toadd) + minetest.chat_send_player(player, "Invalid position "..toadd) + end + + if command == "mob_spawned" then + print("CMD: ".. player .."> ".. "Mob successfully spawned "..toadd) + minetest.chat_send_player(player, "Mob successfully spawned "..toadd) + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_debug] spawn_mob(name,param) +-- +--! @brief handle a spawn mob command +-- +--! @param name name of player +--! @param param parameters received +------------------------------------------------------------------------------ +function mobf_debug.spawn_mob(name,param) + local parameters = param:split(" ") + + if #parameters ~= 1 and + #parameters ~= 2 then + mobf_debug.print_usage(name,"spawnmob") + return + end + + if mobf_is_known_mob(parameters[1]) ~= true then + mobf_debug.print_usage(name,"ukn_mob", ">"..parameters[1].."<") + return true + end + + if #parameters == 2 then + local pos_strings = parameters[2]:split(",") + + if #pos_strings ~= 3 then + mobf_debug.print_usage(name,"spawmob") + return + end + + + + local spawnpoint = { + x=tonumber(pos_strings[1]), + y=tonumber(pos_strings[2]), + z=tonumber(pos_strings[3]) + } + + if spawnpoint.x == nil or + spawnpoint.y == nil or + spawnpoint.z == nil then + mobf_debug.print_usage(name,"spawnmob") + return + end + + spawning.spawn_and_check(parameters[1],spawnpoint,"mobf_debug_spawner") + else + --todo find random pos + + local player = minetest.get_player_by_name(name) + + if player == nil then + + return + end + + local pos = player:getpos() + + if pos == nil then + return + end + + local found = false + local maxtries = 10 + + while (found == false) and (maxtries > 0) do + local toadd = {} + toadd.x = pos.x + (math.random(20) -10) + toadd.z = pos.z + (math.random(20) -10) + + local y = mobf_get_surface(toadd.x,toadd.z,pos.y-10,pos.y+10) + + if y ~= nil then + toadd.y = y +2 + if spawning.spawn_and_check(parameters[1],toadd,"mobf_debug_spawner") then + found = true + end + end + + maxtries = maxtries -1 + end + end + + +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_debug] list_active_mobs(name,param) +-- +--! @brief print list of all current active mobs +-- +--! @param name name of player +--! @param param parameters received +------------------------------------------------------------------------------ +function mobf_debug.list_active_mobs(name,param) + + local count = 1 + for index,value in pairs(minetest.luaentities) do + if value.data ~= nil and value.data.name ~= nil then + local tosend = count .. ": " .. value.data.name .. " at " + .. printpos(value.object:getpos()) + print(tosend) + minetest.chat_send_player(name,tosend) + count = count +1 + end + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_debug] list_spawners(name,param) +-- +--! @brief print list of all spawners around player +-- +--! @param name name of player +--! @param param parameters received +------------------------------------------------------------------------------ +function mobf_debug.list_spawners(name,param) + + for index,value in pairs(minetest.luaentities) do + if value ~= nil and value.spawner_mob_name ~= nil then + local resultline = "SPW: " + .. mobf_fixed_size_string(value.spawner_mob_name,24) .. " " + .. mobf_fixed_size_string(printpos(value.object:getpos()),16) + .. " STATE: " + .. mobf_fixed_size_string(dump(value.spawner_last_result),32) + .. " TIME: " .. value.spawner_time_passed + + print(resultline) + end + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_debug] mob_count(name,param) +-- +--! @brief count active mobs +-- +--! @param name name of player +--! @param param parameters received +------------------------------------------------------------------------------ +function mobf_debug.mob_count(name,param) + + local count = 1 + for index,value in pairs(minetest.luaentities) do + if value.data ~= nil and value.data.name ~= nil then + count = count +1 + end + end + + minetest.chat_send_player(name,"Active mobs: " .. count) +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_debug] add_tools(name,param) +-- +--! @brief add toolset for testing +-- +--! @param name name of player +--! @param param parameters received +------------------------------------------------------------------------------ +function mobf_debug.add_tools(name,param) + local player = minetest.get_player_by_name(name) + + if player ~= nil then + player:get_inventory():add_item("main", "animalmaterials:lasso 20") + player:get_inventory():add_item("main", "animalmaterials:net 20") + player:get_inventory():add_item("main", "animalmaterials:scissors 1") + player:get_inventory():add_item("main", "vessels:drinking_glass 10") + end + +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_debug] list_defined_mobs(name,param) +-- +--! @brief list all registred mobs +-- +--! @param name name of player +--! @param param parameters received +------------------------------------------------------------------------------ +function mobf_debug.list_defined_mobs(name,param) + + local text = "" + for i,val in ipairs(mobf_rtd.registred_mob) do + text = text .. val .. " " + end + minetest.chat_send_player(name, "MOBF: "..text) +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_debug] init() +-- +--! @brief initialize debug commands chat handler +-- +------------------------------------------------------------------------------ +function mobf_debug.init() + + minetest.register_chatcommand("spawnmob", + { + params = " ", + description = "spawn a mob at position(optional)" , + privs = {mobfw_admin=true}, + func = mobf_debug.spawn_mob + }) + + minetest.register_chatcommand("listactivemobs", + { + params = "", + description = "list all currently active mobs" , + privs = {mobfw_admin=true}, + func = mobf_debug.list_active_mobs + }) + + minetest.register_chatcommand("listdefinedmobs", + { + params = "", + description = "list all currently defined mobs" , + privs = {mobfw_admin=true}, + func = mobf_debug.list_defined_mobs + }) + + minetest.register_chatcommand("mob_add_tools", + { + params = "", + description = "add some mob specific tools to player" , + privs = {mobfw_admin=true}, + func = mobf_debug.add_tools + }) + + minetest.register_chatcommand("mobf_version", + { + params = "", + description = "show mobf version number" , + privs = {}, + func = function(name,param) + minetest.chat_send_player(name,"MOBF version: " .. mobf_version) + end + }) + + minetest.register_chatcommand("listspawners", + { + params = "", + description = "debug info about spawner entities" , + privs = {mobfw_admin=true}, + func = mobf_debug.list_spawners + }) + + minetest.register_chatcommand("mobf_count", + { + params = "", + description = "number of active mobs" , + privs = {}, + func = mobf_debug.mob_count + }) + + minetest.register_chatcommand("mobf_mobs_offline", + { + params = "", + description = "print offline mobs" , + privs = {}, + func = mobf_debug.print_offline_mobs + }) + + + if mobf_rtd.luatrace_enabled then + minetest.register_chatcommand("traceon", + { + params = "", + description = "start luatrace tracing" , + privs = {mobfw_admin=true}, + func = function() + luatrace.tron(nil) + end + }) + + minetest.register_chatcommand("traceoff", + { + params = "", + description = "stop luatrace tracing" , + privs = {mobfw_admin=true}, + func = function() + luatrace.troff() + end + }) + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_debug] print_offline_mobs(name,message) +-- +--! @brief spawn small house +-- +--! @param name name of player +--! @param param parameters +------------------------------------------------------------------------------ +function mobf_debug.print_offline_mobs(name,param) + + local count = 0 + + for key,value in pairs(spawning.mob_spawn_data) do + for hash,enabled in pairs (value) do + count = count +1 + local mobpos = mobf_hash_to_pos(hash) + + print(string.format("%5d: ",count) .. key .. " " .. printpos(mobpos)) + end + end + + print("Total of " .. count .. " mobs stored as offline") +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_debug] rightclick_callback(entity,player) +-- +--! @brief show rightclick info +-- +--! @param entity entity rightclicked +--! @param player player doing rightclick +------------------------------------------------------------------------------ +function mobf_debug.rightclick_callback(entity,player) + local basepos = entity.getbasepos(entity) + local lifetime = mobf_get_current_time() - entity.dynamic_data.spawning.original_spawntime + print("MOBF: " .. entity.data.name .. " " .. tostring(entity) .. " is alive for " .. lifetime .. " seconds") + print("MOBF: \tAbsolute spawntime: " .. entity.dynamic_data.spawning.original_spawntime) + print("MOBF: \tCurrent state: " .. entity.dynamic_data.state.current.name ) + print("MOBF: \tCurrent movgen: " .. entity.dynamic_data.current_movement_gen.name ) + print("MOBF: \tHP: " .. entity.object:get_hp()) + if entity.dynamic_data.current_movement_gen.name == "follow_mov_gen" or + entity.dynamic_data.current_movement_gen.name == "mgen_path" then + + local targetpos = entity.dynamic_data.spawning.spawnpoint + if entity.dynamic_data.movement.target ~= nil then + if not mobf_is_pos(entity.dynamic_data.movement.target) then + targetpos = entity.dynamic_data.movement.target:getpos() + else + targetpos = entity.dynamic_data.movement.target + end + end + if targetpos ~= nil then + print("MOBF: \t\tmovement state: " .. mgen_follow.identify_movement_state(basepos,targetpos) ) + else + print("MOBF: \t\tmovement state: invalid") + end + print("MOBF: \t\tguard spawnpoint: " .. dump(entity.dynamic_data.movement.guardspawnpoint)) + print("MOBF: \t\ttarget: " .. dump(entity.dynamic_data.movement.target)) + end + if entity.dynamic_data.current_movement_gen.name == "mgen_path" then + print("MOBF: \t\tpath index: " .. entity.dynamic_data.p_movement.next_path_index) + print("MOBF: \t\tpath: " .. dump(entity.dynamic_data.p_movement.path)) + if entity.dynamic_data.p_movement.path ~= nil then + for i,v in ipairs(entity.dynamic_data.p_movement.path) do + local objects = minetest.get_objects_inside_radius(v,0.5) + local found = false; + for i=1,#objects,1 do + local luaentity = objects[i]:get_luaentity() + if luaentity ~= nil and + luaentity.name == "mobf:path_marker_entity" then + found = true + break + end + end + + local node_at = minetest.get_node(v) + + if not found and + node_at.name ~= nil and + node_at.name ~= "ignore" then + spawning.spawn_and_check("mobf:path_marker_entity",v,"mark_path") + end + end + print("MOBF: \t\tdistance to next point: " .. p_mov_gen.distance_to_next_point(entity,entity.object:getpos())) + end + end + + local predicted_pos = movement_generic.predict_next_block( + basepos, + entity.object:getvelocity(), + entity.object:getacceleration()) + if not ( entity.data.movement.canfly == true) then + predicted_pos.y = basepos.y + end + local pos_state = environment.pos_is_ok(predicted_pos,entity) + local pos_quality = environment.pos_quality(basepos,entity) + local predicted_quality = environment.pos_quality(predicted_pos,entity) + + print("MOBF: \tTime to state change: " .. entity.dynamic_data.state.time_to_next_change .. " seconds") + print("MOBF: \tCurrent environmental state: " .. environment.pos_is_ok(entity.getbasepos(entity),entity)) + if mobf_rtd.detailed_state then + print("MOBF: \tCurrent detailed state: " .. pos_quality:shortstring()) + end + print("MOBF: \tCan fly: " .. dump(entity.data.movement.canfly)) + print("MOBF: \tCurrent accel: " .. printpos(entity.object:getacceleration())) + print("MOBF: \tDefault gravity: " .. dump(environment.get_default_gravity(basepos, entity.environment.media, entity.data.movement.canfly))) + print("MOBF: \tCurrent speed: " .. printpos(entity.object:getvelocity())) + print("MOBF: \tSpawnpoint: " .. printpos(entity.dynamic_data.spawning.spawnpoint)) + print("MOBF: \tSpawner: " .. dump(entity.dynamic_data.spawning.spawner)) + print("MOBF: \tCurrent pos: " .. printpos(basepos)) + print("MOBF: \tPredicted pos: " .. printpos(predicted_pos)) + print("MOBF: \tPredicted state: " .. pos_state) + print("MOBF: \tMovement facedir offset: " .. dump(entity.automatic_face_movement_dir)) + if entity.dynamic_data.sound ~= nil and entity.dynamic_data.sound.random_next ~= nil then + print("MOBF: \tNext random sound: " .. + (entity.dynamic_data.sound.random_next - mobf_get_current_time())) + end + if mobf_rtd.detailed_state then + print("MOBF: \tPredicted detail: " .. predicted_quality:shortstring()) + end + if entity.dynamic_data.combat ~= nil then + print("MOBF: \tCurrent combat target: " .. fighting.get_target_name(entity.dynamic_data.combat.target)) + end + if entity.dynamic_data.attention ~= nil then + print("MOBF: \t Current attention table:") + for k,v in pairs(entity.dynamic_data.attention.watched_objects) do + print("MOBF: \t\t " .. k .. ": " .. v.value) + end + + if entity.dynamic_data.attention.most_relevant_target ~= nil then + local attention_name = tostring(entity.dynamic_data.attention.most_relevant_target) + + if (entity.dynamic_data.attention.most_relevant_target:is_player()) then + attention_name = entity.dynamic_data.attention.most_relevant_target:get_player_name() + end + print("MOBF: \tTop attention object: " .. attention_name) + end + end + + if entity.dynamic_data.graphics.last_fps ~= nil then + print("MOBF: Animating with: " .. entity.dynamic_data.graphics.last_fps .. " fps") + end + + if entity.data.states ~= nil then + print("MOBF: \tStatecount: " .. #entity.data.states) + for i,v in ipairs(entity.data.states) do + print("MOBF: \t\t" .. i .. ": " .. v.name .. " chance=" .. v.chance) + end + end + return false +end + + +--!@} diff --git a/mods/mob_engines/mobf/debug_trace.lua b/mods/mob_engines/mobf/debug_trace.lua new file mode 100644 index 00000000..27587bdb --- /dev/null +++ b/mods/mob_engines/mobf/debug_trace.lua @@ -0,0 +1,116 @@ +------------------------------------------------------------------------------- +-- 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 debug_trace.lua +--! @brief contains switchable debug trace functions +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +--! +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @defgroup debug_trace Debug trace functions +--! @brief central configuration of trace functions +--! @ingroup framework_int + +--lvl1 excessive output +--lvl2 medium output +--lvl3 less output + +--! @brief configuration of trace level to use for various components +--! @ingroup debug_trace +dbg_mobf = { + + generic_lvl1 = function () end, + generic_lvl2 = function () end, + generic_lvl3 = function () end, + + graphics_lvl1 = function () end, + graphics_lvl2 = function () end, + graphics_lvl3 = function () end, + + spawning_lvl1 = function () end, + spawning_lvl2 = function () end, + spawning_lvl3 = function () end, + + permanent_store_lvl1 = function () end, + permanent_store_lvl2 = function () end, + permanent_store_lvl3 = function () end, + + movement_lvl1 = function () end, + movement_lvl2 = function () end, + movement_lvl3 = function () end, + + pmovement_lvl1 = function () end, + pmovement_lvl2 = function () end, + pmovement_lvl3 = function () end, + + fmovement_lvl1 = function () end, + fmovement_lvl2 = function () end, + fmovement_lvl3 = function () end, + + flmovement_lvl1 = function () end, + flmovement_lvl2 = function () end, + flmovement_lvl3 = function () end, + + path_mov_lvl1 = function () end, + path_mov_lvl2 = function () end, + path_mov_lvl3 = function () end, + + fighting_lvl1 = function () end, + fighting_lvl2 = function () end, + fighting_lvl3 = function () end, + + environment_lvl1 = function () end, + environment_lvl2 = function () end, + environment_lvl3 = function () end, + + harvesting_lvl1 = function () end, + harvesting_lvl2 = function () end, + harvesting_lvl3 = function () end, + + sound_lvl1 = function () end, + sound_lvl2 = function () end, + sound_lvl3 = function () end, + + random_drop_lvl1 = function () end, + random_drop_lvl2 = function () end, + random_drop_lvl3 = function () end, + + mob_state_lvl1 = function () end, + mob_state_lvl2 = function () end, + mob_state_lvl3 = function () end, + + mobf_core_lvl1 = function () end, + mobf_core_lvl2 = function () end, + mobf_core_lvl3 = function () end, + + mobf_core_helper_lvl1 = function () end, + mobf_core_helper_lvl2 = function () end, + mobf_core_helper_lvl3 = function () end, + + trader_inv_lvl1 = function () end, + trader_inv_lvl2 = function () end, + trader_inv_lvl3 = function () end, + + ride_lvl1 = function () end, + ride_lvl2 = function () end, + ride_lvl3 = function () end, + + path_lvl1 = function () end, + path_lvl2 = function () end, + path_lvl3 = function () end, + + lifebar_lvl1 = function () end, + lifebar_lvl2 = function () end, + lifebar_lvl3 = function () end, + + attention_lvl1 = function () end, + attention_lvl2 = function () end, + attention_lvl3 = function () end, +} \ No newline at end of file diff --git a/mods/mob_engines/mobf/depends.txt b/mods/mob_engines/mobf/depends.txt new file mode 100644 index 00000000..4eacd9b4 --- /dev/null +++ b/mods/mob_engines/mobf/depends.txt @@ -0,0 +1,4 @@ +default +adv_spawning? +intllib? +factions? diff --git a/mods/mob_engines/mobf/doc/Doxyfile b/mods/mob_engines/mobf/doc/Doxyfile new file mode 100644 index 00000000..38289509 --- /dev/null +++ b/mods/mob_engines/mobf/doc/Doxyfile @@ -0,0 +1,1774 @@ +# Doxyfile 1.7.6.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = "Mob Framework Minetest Mod" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = . + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = "The $name class" "The $name widget" "The $name file" is provides specifies contains represents a an the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST = YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. The create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = NO + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ../ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +#FILE_PATTERNS = *.c \ + +FILE_PATTERNS = *.lua + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = *.lua=/usr/local/bin/lua2dox + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# style sheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = YES + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 1 + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = YES + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the +# mathjax.org site, so you can quickly see the result without installing +# MathJax, but it is strongly recommended to install a local copy of MathJax +# before deployment. + +MATHJAX_RELPATH = http://www.mathjax.org/mathjax + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = NO + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = YES + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/mods/mob_engines/mobf/doc/Testcases.txt b/mods/mob_engines/mobf/doc/Testcases.txt new file mode 100644 index 00000000..37274ead --- /dev/null +++ b/mods/mob_engines/mobf/doc/Testcases.txt @@ -0,0 +1,28 @@ +Date 2012 01 28 + +testcases to check before release: + + +Features: + +-mobs moving +-catching mobs with lasso +-catching mobs with net +-killing mobs +-harvesting mobs +-mobs doing meele attack +-mobs transforming on harvest +-mobs auto transform +-mobs taking damage in sun +-mobs self destruct +-mobs honoring jump limitiations +-mobs random dropping +-mobs honoring movement medium +-mobs doing random jumps +-mobs level changing + + +General: +-version number correct? +-readme correct? +-debug output disabled? diff --git a/mods/mob_engines/mobf/doc/mainpage_description.lua b/mods/mob_engines/mobf/doc/mainpage_description.lua new file mode 100644 index 00000000..9837e327 --- /dev/null +++ b/mods/mob_engines/mobf/doc/mainpage_description.lua @@ -0,0 +1,47 @@ +------------------------------------------------------------------------------- +-- 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 mainpage_description.lua +--! @brief just a doc page +--! @copyright Sapier +--! @author Sapier +--! @date 2013-04-28 + +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @mainpage Mob Framework Mod 2.5.x +--! +--! This documentation uses doxygen created from lua code. As some of you +--! probably know doxygen doesn't support lua on it's own so some of the +--! shown descriptions arent perfectly correct.\n +--! +--! On this page some of the caveats are explained:\n +--! Lua doesn't support classes and structs but tables which ain't supported by +--! doxygen.\n +--! +--! Mapping of classes and structs: +--! \li \b classes are used to group (sub)components containing primary functions\n +--! \li \b structs are result of parsing tables and containing configuration or +--! temporary data +--! +--! Datatypes shown in doxygen and it's meaning: +--! \li \b function this is a return value of function and can be nil too +--! \li \b var a variable +--! \li \b struct some table element +--! \li \b anonymous_value a table element specified without a name +--! \li \b parameter is a parameter declared in a function definition +--! +--! +--! Keywords used in config parameter description +--! \li \b MANDATORY some parameter required +--! \li \b OPTIONAL parameter may be nill without causinh an error +--! \li \b MOV_GEN_DEPENDENT parameter is required dependent of selected movement gen +--! \li \b 2D MANDATORY parameter is required in case of 2D mob +--! \li \b 3D MANDATORY parameter is required in case of 3D mob +--! \li \b ALGORITHM \b DEPENDENT is required dependent on selected algorithm +--! \li \b UPPER_VALUE_DEPENDENT if you specify upper value you need to specify this too diff --git a/mods/mob_engines/mobf/doc/mob_template.lua b/mods/mob_engines/mobf/doc/mob_template.lua new file mode 100644 index 00000000..b9bdf489 --- /dev/null +++ b/mods/mob_engines/mobf/doc/mob_template.lua @@ -0,0 +1,455 @@ +------------------------------------------------------------------------------- +-- 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 mob_template.lua +--! @brief template for mob +--! @copyright Sapier +--! @author Sapier +--! @date 2013-01-27 +-- +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +--+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +-- WARNING this code might be not correct lua in order to get doxygen +-- compatibility! +--+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +--+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +--! @brief Template for creating mobs +--! +--! This template trys to describe all available configuration options +--! in mob framework. +--! @ingroup framework_mob +local mob_template = { + + --! @brief [MANDATORY] name of mob @b (alphanumeric and "_" only!!) + name = "some name", + --! @brief [MANDATORY] name of mod defining the mob + modname = "name of mod", + + --! @brief [MANDATORY] generic parameters for mob + generic = { + --! @brief [MANDATORY] description to show on mouse over in inventory + description="Some mob", + + --! @brief [MANDATORY] maximum health + base_health=0, + + --! @brief [MANDATORY] environment of mob to be + envid="some environment id", + + --! @brief [OPTIONAL] item description OR function all returning a item description of whats the result of a kill + kill_result = nil, + + --! @brief [OPTIONAL] armor groups of mob + armor_groups = nil, + + --! @brief [OPTIONAL] custom on_hit(entity,player) callback return true to skip normal fight callback + on_hit_callback = nil, + + --! @brief [OPTIONAL] custom on_kill(entity,player) callback return true to skip normal on kill handling + on_kill_callback = nil, + + --! @brief [OPTIONAL] custom on_place(entity, placer, pointed_thing) callback called after normal on place handling is done + custom_on_place_handler = nil, + + --! @brief [OPTIONAL] custom on_activate(entity) callback called after normal on_activate handling is done + custom_on_activate_handler = function(entity) end, + + --! @brief [OPTIONAL] custom on_step(entity) callback called after normal on_step handling is done + custom_on_step_handler = nil, + + --! @brief [OPTIONAL] list of callbacks + on_rightclick_callbacks = { + { + handler = function(entity, player) end, + name = "internal name shown in debug info", + visiblename = function(entity) end or "some label of rightclick button" + } + }, + }, + + --! @brief [MANDATORY] configuration of movement generator + movement = { + --! @brief [MANDATORY] is this a flying mob + canfly=false, + + --! @brief [MANDATORY] minumum acceleration of mob + min_accel=0, + + --! @brief [MANDATORY] maximum acceleration of mob + max_accel=0, + + --! @brief [MANDATORY] maximum absolute speed of mob + max_speed=0, + + --! @brief [OPTIONAL] minimum speed a mob shall move (if moving at all) + min_speed=0, + + --! @brief [MOV_GEN_DEPENDENT | MANDATORY] pattern based movement gen -> pattern to use for movement + pattern="some pattern id", + + --! @brief [MOV_GEN_DEPENDENT | OPTIONAL] follow movement gen -> does this mob guard it's spawnpoint + guardspawnpoint = false, + + --! @brief [MOV_GEN_DEPENDENT | OPTIONAL] follow movement gen -> time until this mob teleports to its target + teleportdelay = 60, + + }, + + --! @brief [OPTIONAL] if mob is harvestable configure it here + harvest = { + --! @brief [OPTIONAL] tool required for harvesting + tool=nil, + + --! @brief [OPTIONAL] is tool consuled by harvesting + tool_consumed=false, + + --! @brief [MANDATORY] result of harvest + result="", + + --! @brief [OPTIONAL] mob transforms to this mob on harvest + transforms_to="", + + --! @brief [MANDATORY] minimum time between two harvests (in case of transform set this to -1) + min_delay=-1, + }, + + --! @brief [OPTIONAL] configuration how to catch the mob + catching = { + --! @brief [MANDATORY] tool required to wear to catch the mob + tool = "some item", + --! @brief [MANDATORY] is tool consumed by catching + consumed = true, + --! @brief [OPTIONAL] function to be called to check if a mob can be cought in current state + can_be_cought = function(entity) end, + }, + + --! @brief [OPTIONAL] does this mob do random drops + random_drop = { + + --! @brief [MANDATORY] item to be dropped + result="some_material", + + --! @brief [MANDATORY] minimum delay between two drops + min_delay=60, + + --! @brief [MANDATORY] chance per second to drop after min_delay has passed + chance=0.2 + }, + + --! @brief [OPTIONAL] if this mob s intended to transform by its own configure it here + auto_transform = { + + --! @brief [MANDATORY] mob to transform to + result="some_mob", + + --! @brief [MANDATORY] time to transformation + delay=1800 + }, + + --! @brief [OPTIONAL] combat settings for mob + combat = { + --! @brief [MANDATORY] does mob start an attack on its own? + starts_attack=true, + + --! @brief [MANDATORY] chance mob will attack (if starting attack on its own or beeing attacked) + angryness=0.95, + + --! @brief [OPTIONAL] is mob sensitive to sun? + sun_sensitive=true, + + --! @brief [OPTIONAL] attacks hostile mobs + attack_hostile_mobs=false, + + --! @brief [UNUSED] attacks hostile mobs + --attack_friendly_mobs=false, + + --! @brief [OPTIONAL] configuration of meele attack + melee = { + --! @brief [MANDATORY] maximum damage mob does per hit + maxdamage=4, + --! @brief [MANDATORY] range mob will hit + range=2, + --! @brief [MANDATORY] minimum time between two hits + speed=2, + --! @brief [OPTIONAL] list of groups damage is done to, if not specified "fleshy" is used + weapon_damage_groups= { "fleshy" }, + }, + --! @brief [OPTIONAL] configuration of distance attack + distance = { + --! @brief [MANDATORY] distance projectile to issue + attack="some_entity", + --! @brief [MANDATORY] distance to issue an attack + range=10, + --! @brief [OPTIONAL] minimum range of distance attack (should be > range of melee attack) + min_range = 3, + --! @brief [MANDATORY] minimum time between two attacks + speed=2, + }, + + --! @brief [OPTIONAL] configuration for self destructing mob + self_destruct = { + --! [MANDATORY] maximum damage to be done on self destruct + damage=15, + --! [MANDATORY] maximum range to do damage + range=5, + --! [MANDATORY] range to destroy nodes on self destruction + node_damage_range = 1.5, + --! [MANDATORY] delay between self destruct triggering and explosion + delay=5, + }, + }, + --! @brief [MANDATORY] spawning configuration for mob + spawning = { + --! @brief [MANDATORY] rate this mob is spawned + rate=0.01, + --! @brief [MANDATORY] typical distance between two mobs of this type when spawend + density=1000, + --! @brief [MANDATORY] identifyer of spawn algorithm + algorithm="some algorithm id", + + --! @brief [ALGORITHM DEPENDENT] shadows minimum number of air blocks above pos + height = 1, + }, + + --! @brief [OPTIONAL] sounds to be played by mob + sound = { + --! @brief [OPTIONAL] random sound to be played + random = { + --! @brief [MANDATORY] default interval between random sounds + interval = 60, + + --! @brief [MANDATORY] maximum deviation from default interval + max_interval_deviation = 10, + + --! @brief [MANDATORY] list of random sounds to play + list = { + { + --! @brief [MANDATORY] basename of file + name="random_1", + --! @brief [MANDATORY] amplify the sound by this value + gain = 1, + --! @brief [MANDATORY] maximum distance sound is heared + max_hear_distance = 75, + }, + { + --! @brief [MANDATORY] basename of file + name="random_2", + --! @brief [MANDATORY] amplify the sound by this value + gain = 1, + --! @brief [MANDATORY] maximum distance sound is heared + max_hear_distance = 75, + }, + { + --! @brief [MANDATORY] basename of file + name="random_x", + --! @brief [MANDATORY] amplify the sound by this value + gain = 1, + --! @brief [MANDATORY] maximum distance sound is heared + max_hear_distance = 75, + }, + }, + }, + --! @brief [OPTIONAL] sound played on self destruction + self_destruct = { + --! @brief [MANDATORY] basename of file + name="bomb_explosion", + --! @brief [MANDATORY] amplify the sound by this value + gain = 2, + --! @brief [MANDATORY] maximum distance sound is heared + max_hear_distance = 1000, + }, + --! @brief [OPTIONAL] sound played on harvest + harvest = { + --! @brief [MANDATORY] basename of file + name="harvest", + --! @brief [MANDATORY] amplify the sound by this value + gain = 0.8, + --! @brief [MANDATORY] maximum distance sound is heared + max_hear_distance = 5, + }, + --! @brief [OPTIONAL] sound played on distance attack + distance = { + --! @brief [MANDATORY] basename of file + name="fireball", + --! @brief [MANDATORY] amplify the sound by this value + gain = 1, + --! @brief [MANDATORY] maximum distance sound is heared + max_hear_distance = 100, + }, + --! @brief [OPTIONAL] sound played if mob dies + die = { + --! @brief [MANDATORY] basename of file + name="die", + --! @brief [MANDATORY] amplify the sound by this value + gain = 1, + --! @brief [MANDATORY] maximum distance sound is heared + max_hear_distance = 100, + }, + --! @brief [OPTIONAL] sound played if mob does meele attack + melee = { + --! @brief [MANDATORY] basename of file + name="hit", + --! @brief [MANDATORY] amplify the sound by this value + gain = 1, + --! @brief [MANDATORY] maximum distance sound is heared + max_hear_distance = 100, + }, + }, + + --! @brief [OPTIONAL] parameters for rideable mobs + ride = { + --! @brief [OPTIONAL] speed when walking + walkspeed = 7.8, + --! @brief [OPTIONAL] speed when sneaking + sneakspeed = 0.8, + --! @brief [OPTIONAL] inital jumpspeed + jumpspeed = 58, + --! @brief [OPTIONAL] offset to center player is put to + attacheoffset = { x=0,y=2,z=0}, + --! @brief [OPTIONAL] texture modifier when player is attached + texturemod = "^mob_ostrich_ostrich_saddle_mesh.png", + --! @brief [OPTIONAL] animation to show when riding + walk_anim = "walk", + }, + + --! @brief [OPTIONAL] description of animations + animation = { + --! @brief [OPTIONAL] one or many animation descriptions + animationname = { + --! @brief [MANDATORY] start frame of animation + start_frame = 1, + --! @brief [MANDATORY] end frame of animation + end_frame = 2, + }, + }, + + --! @brief [OPTIONAL] configuration for a trader mob + trader_inventory = { + --! @brief [MANDATORY] goodlist to be sold + goods = { + --! @brief [MANDOTORY] first element in list + { "default:mese 1", "default:dirt 99", "default:cobble 50"}, + --! @brief [OPTIONAL] any further element + { "default:steel_ingot 1", "default:dirt 50", "default:cobble 20"}, + }, + --! @brief [MANDATORY] list of names randomly choosen for trader + random_names = { "Name1","Name2","Name3"}, + }, + + --! @brief [OPTIONAL] configuration for attention handling + attention = { + --! @brief [OPTIONAL] hear distance value + hear_distance = 3, + --! @brief [UPPER_VALUE_DEPENDENT | MANDATORY] value to add if within hear distance + hear_distance_value = 0.5, + + --! @brief [OPTIONAL] view angle to consider + view_angle = nil, + --! @brief [UPPER_VALUE_DEPENDENT | MANDATORY] value to add if someone is within view angke + own_view_value = 0, + + --! @brief [OPTIONAL] is remove view angle relevant? + remote_view = false, + --! @brief [UPPER_VALUE_DEPENDENT | MANDATORY] value to add if remote target looks at mob + remote_view_value = 0, + + --! @brief [MANDATORY] value to add if within attention distance + attention_distance_value = 0.2, + + --! @brief [MANDATORY] threshold to issue watch callback + watch_threshold = 2, + + --! @brief [OPTIONAL] threshold to issue attack callback + attack_threshold = nil, + + --! @brief [MANDATORY] maximum distance to consider objects for attantion + attention_distance = 7.5, + + --! @brief [MANDATORY] maximum amount of attention any object can draw + attention_max = 10, + + --! @brief [OPTIONAL] watch_callback(entity,target) a callback to be called once attention threshold is reached + watch_callback = nil + }, + + --! @brief [OPTIONAL] factions configuration + factions = { + --! @brief [OPTIONAL] define factions to be member in + member = { + "faction_1", + "faction_2", + "faction_3", + "faction_4" + }, + }, + --! @brief [OPTIONAL] used to specify different movement/model states, + --! you may specify as many states as you like + states = { + { + --! @brief [MANDATORY] (default state is MUST have!) name of state + name = "default", + --! @brief [MANDATORY] typical duration of this state + typical_state_time = 180, + --! @brief [MANDATORY] chance of state to be selected (SUM may not be > 1) + chance = 0.5, + --! @brief [MANDATORY] a special movement handler for this state + movgen = "none", + --! @brief [3D MANDATORY] a special model to be used for this state + graphics_3d = { + --! @brief [MANDATORY] this is the drawtype to use + visual = "mesh", + --! @brief [MANDATORY] this is the drawtype to use + mesh = "mesh.x", + --! @brief [MANDATORY] the model of the mob + textures = {"some node declatation"}, + --! @brief [MANDATORY] collisionbox to use + collisionbox = { "" }, + --! @brief [MANDATORY] xyz scale factors for the model + visual_size = {x=1,y=1,z=1}, + --! @brief [OPTIONAL] additional yaw offset used to fix models with broken orientation + model_orientation_fix = -math.pi/2, + }, + --! @brief [2D MANDATORY] a special sprite to be used for this state + graphics_2d = { + --! @brief [MANDATORY] scale of sprite + sprite_scale={x=0,y=0}, + --! @brief [MANDATORY] description of multi dimensional sprites (e.g. containing burning/ differend view directions) + sprite_div = {x=0,y=0}, + --! @brief [MANDATORY] height of sprite + visible_height = 3.2, + + }, + --! @brief [MANDATORY] a animation to be played while this state is active + animation = "walk", + }, + { + --! @brief [MANDATORY] name of state + name = "another_state_example", + --! @brief [MANDATORY] typical duration of this state + typical_state_time = 180, + --! @brief [MANDATORY] chance of state to be selected (SUM may not be > 1) + chance = 0.5, + --! @brief [OPTIONAL] a function to check before switching to this state + HANDLER_precondition = nil, + --! @brief [OPTIONAL] a special movement handler for this state + movgen = "none", + --! @brief [OPTIONAL] a special model to be used for this state + graphics_3d = "", + --! @brief [OPTIONAL] a special sprite to be used for this state + graphics_2d = "", + --! @brief [OPTIONAL] a animation to be played while this state is active + animation = "name", + --! @brief [OPTIONAL] mode to differentiate different states e.g. "auto" for auto switched states "user_def" for user defined or "combat" for combat states + state_mode = "auto", + }, + }, + } \ No newline at end of file diff --git a/mods/mob_engines/mobf/env_constants.lua b/mods/mob_engines/mobf/env_constants.lua new file mode 100644 index 00000000..9cf04d79 --- /dev/null +++ b/mods/mob_engines/mobf/env_constants.lua @@ -0,0 +1,140 @@ +------------------------------------------------------------------------------- +-- 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 env_constants.lua +--! @brief constants used for environmental functions +--! @copyright Sapier +--! @author Sapier +--! @date 2013-08-17 +-- +--! @defgroup environment Environment subcomponent +--! @brief Environment check functions used by different subcomponents +--! @ingroup framework_int +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +MQ_IN_MEDIA = 100 +MQ_IN_WATER = 20 +MQ_IN_AIR = 10 +MQ_SOLID = 5 + +GQ_FULL = 100 +GQ_PARTIAL = 60 +GQ_NONE = 30 + +SQ_OK = 100 +SQ_POSSIBLE = 60 +SQ_WRONG = 30 +SQ_WATER = 10 + +LQ_OK = 100 +LQ_ABOVE = 60 +LQ_BELOW = 30 + +Q_UNKNOWN = 0 + +--define some common limits for convenience + +--! @brief a position where mob is in media, has full surface contact and is at least on a possible surface +LT_GOOD_POS = { + old_state=nil, + min_media=MQ_IN_MEDIA, + min_geom=GQ_FULL, + min_geom_center=nil, + min_min_surface=SQ_POSSIBLE, + min_max_surface=SQ_OK, + min_center_surface=nil +} + +LT_GOOD_FLY_POS = { + old_state=nil, + min_media=MQ_IN_MEDIA, + min_geom=nil, + min_geom_center=nil, + min_min_surface=Q_UNKNOWN, + min_max_surface=Q_UNKNOWN, + min_center_surface=nil +} + +--! @brief a position where mob is in media, has surface contact and is at least on a possible surface +LT_SAFE_POS = { + old_state=nil, + min_media=MQ_IN_MEDIA, + min_geom=GQ_PARTIAL, + min_geom_center=nil, + min_min_surface=nil, + min_max_surface=SQ_POSSIBLE, + min_center_surface=nil +} + +--! @brief a position where mob is in media, it's center has surface contact and is at least on a possible surface +LT_SAFE_EDGE_POS = { + old_state=nil, + min_media=MQ_IN_MEDIA, + min_geom=nil, + min_geom_center=GQ_FULL, + min_min_surface=SQ_POSSIBLE, + min_max_surface=SQ_OK, + min_center_surface=nil +} + +--! @brief a position where mob is in media, it's center has surface contact and is on a possible surface +LT_SAFE_POSSIBLE_EDGE_POS = { + old_state=nil, + min_media=MQ_IN_MEDIA, + min_geom=nil, + min_geom_center=GQ_FULL, + min_min_surface=SQ_POSSIBLE, + min_max_surface=SQ_POSSIBLE, + min_center_surface=nil +} + +--! @brief a position where mob is in media, it's center has surface contact and it's center is at least on a possible surface +LT_EDGE_POS_POSSIBLE_CENTER = { + old_state=nil, + min_media=MQ_IN_MEDIA, + min_geom=nil, + min_geom_center=GQ_FULL, + min_min_surface=nil, + min_max_surface=nil, + min_center_surface=SQ_POSSIBLE +} + +--! @brief a position where mob is in media, it's center has surface contact and it's center is at least on a good surface +LT_EDGE_POS_GOOD_CENTER = { + old_state=nil, + min_media=MQ_IN_MEDIA, + min_geom=nil, + min_geom_center=GQ_FULL, + min_min_surface=nil, + min_max_surface=nil, + min_center_surface=SQ_OK +} + +--! @brief a position where mob is in media, has at least partial contact and is at least on possible surface +LT_EDGE_POS = { + old_state=nil, + min_media=MQ_IN_MEDIA, + min_geom=GQ_PARTIAL, + min_geom_center=nil, + min_min_surface=SQ_POSSIBLE, + min_max_surface=SQ_OK, + min_center_surface=nil +} + +--! @brief a position where mob is in media, has at least partial contact but no contact at center +LT_DROP_PENDING = { + old_state=nil, + min_media=MQ_IN_MEDIA, + min_geom=GQ_PARTIAL, + min_geom_center=GQ_NONE, + min_min_surface=nil, + min_max_surface=nil, + min_center_surface=nil +} \ No newline at end of file diff --git a/mods/mob_engines/mobf/environment.lua b/mods/mob_engines/mobf/environment.lua new file mode 100644 index 00000000..0aa1f490 --- /dev/null +++ b/mods/mob_engines/mobf/environment.lua @@ -0,0 +1,1130 @@ +------------------------------------------------------------------------------- +-- 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 environment.lua +--! @brief component for environment related functions +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +--! @defgroup environment Environment subcomponent +--! @brief Environment check functions used by different subcomponents +--! @ingroup framework_int +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- +mobf_assert_backtrace(not core.global_exists("environment")) +environment = {} + +mobf_assert_backtrace(not core.global_exists("environment_list")) +--! @brief list of known environments +--! @memberof environment +environment_list = {} + +------------------------------------------------------------------------------- +-- @function [parent=#environment] get_pos_same_level(pos_raw,maxsearcharea,entity,checkfunc) +-- +--! @brief find a position suitable around a specific position +--! @ingroup environment +-- +--! @param pos_raw position to look at +--! @param maxsearcharea max range to look for suitable position +--! @param entity mob to look for position +--! @param checkfunc function doing check if a pos is ok +--! @return {x,y,z} position found or nil +------------------------------------------------------------------------------- +function environment.get_pos_same_level(pos_raw,maxsearcharea,entity,checkfunc) + dbg_mobf.environment_lvl2("MOBF: --> get_pos_same_level " + .. printpos(pos_raw)) + local pos = mobf_round_pos(pos_raw) + + dbg_mobf.environment_lvl3("MOBF: Starting pos is "..printpos(pos) + .." max search area is "..maxsearcharea) + local e1 = "|" + local e2 = "|" + local e3 = "|" + local e4 = "|" + + local possible_targets = {} + + --search next position on solid ground + for search=1, maxsearcharea,1 do + + --find along edge 1 + for current=-search,search,1 do + local pos_tocheck = { x= pos.x + current,y=pos.y,z=pos.z -search} + local pos_quality = environment.pos_quality(pos_tocheck,entity) + + local pos_state = checkfunc(pos_quality) + dbg_mobf.environment_lvl3("MOBF: state of "..printpos(pos_tocheck).." is " + .. dump(pos_state)) + + if pos_state then + dbg_mobf.environment_lvl3(" -->found good pos") + table.insert(possible_targets, pos_tocheck) + else + e1 = e1.."-|" + end + end + + --find along edge 2 + for current=-(search-1),(search-1),1 do + local pos_tocheck = { x= pos.x + search,y=pos.y,z=pos.z + current} + local pos_quality = environment.pos_quality(pos_tocheck,entity) + + local pos_state = checkfunc(pos_quality) + dbg_mobf.environment_lvl3("MOBF: state of "..printpos(pos_tocheck).." is " + .. dump(pos_state)) + + if pos_state then + dbg_mobf.environment_lvl3(" -->found good pos") + table.insert(possible_targets, pos_tocheck) + else + e2 = e2.. "-|" + end + end + + --find along edge 3 + + for current=search,-search,-1 do + local pos_tocheck = { x= pos.x + current,y=pos.y,z=pos.z + search} + local pos_quality = environment.pos_quality(pos_tocheck,entity) + + local pos_state = checkfunc(pos_quality) + dbg_mobf.environment_lvl3("MOBF: state of "..printpos(pos_tocheck).." is " + .. dump(pos_state)) + + if pos_state then + dbg_mobf.environment_lvl3(" -->found good pos") + table.insert(possible_targets, pos_tocheck) + else + e3 = e3.."-|" + end + end + + --find along edge 4 + for current=(search-1),-(search-1),-1 do + local pos_tocheck = { x= pos.x -search,y=pos.y,z=pos.z + current} + local pos_quality = environment.pos_quality(pos_tocheck,entity) + + local pos_state = checkfunc(pos_quality) + dbg_mobf.environment_lvl3("MOBF: state of "..printpos(pos_tocheck).." is " + .. dump(pos_state)) + + if pos_state then + dbg_mobf.environment_lvl3(" -->found good pos") + table.insert(possible_targets, pos_tocheck) + else + e4 = e4.."-|" + end + end + end + +-- print("MOBF: Bug !!! didn't find a suitable position to place mob") +-- print("Surrounding of " .. printpos(pos_raw) .. "was:") +-- print(e1) +-- print(" " .. e2) +-- print(e4) +-- print(e3) + + if #possible_targets > 0 then + local i = math.random(1, #possible_targets) + dbg_mobf.environment_lvl1("Found " .. #possible_targets + .. " possible positions, selected: " + .. i .. ": " .. printpos(possible_targets[i])) + return possible_targets[i] + end + + return nil +end + +------------------------------------------------------------------------------- +-- @function [parent=#environment] get_suitable_pos_same_level(pos_raw,maxsearcharea,entity) +-- +--! @brief find a position suitable around a specific position +--! @ingroup environment +-- +--! @param pos_raw position to look at +--! @param maxsearcharea max range to look for suitable position +--! @param entity mob to look for position +--! @param accept_possible return position thats possible only too +--! @return {x,y,z} position found or nil +------------------------------------------------------------------------------- +function environment.get_suitable_pos_same_level(pos_raw,maxsearcharea,entity,accept_possible) + + if accept_possible then + return environment.get_pos_same_level(pos_raw,maxsearcharea,entity, + function(quality) + if entity.data.movement.canfly then + return environment.evaluate_state(quality,LT_GOOD_FLY_POS) + else + return environment.evaluate_state(quality,LT_SAFE_POSSIBLE_EDGE_POS) + end + end) + else + return environment.get_pos_same_level(pos_raw,maxsearcharea,entity, + function(quality) + if entity.data.movement.canfly then + return environment.evaluate_state(quality,LT_GOOD_FLY_POS) + else + return environment.evaluate_state(quality,LT_SAFE_EDGE_POS) + end + end) + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#environment] is_media_element(nodename,environment) +-- +--! @brief check if nodename is in environment +--! @ingroup environment +-- +--! @param nodename name to check +--! @param media environment of mob +--! @return true/false +------------------------------------------------------------------------------ +function environment.is_media_element( nodename, media ) + + --security check + if media == false then + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: BUG!!!! no environment specified!") + return false + end + + for i,v in ipairs(media) do + if v == nodename then + return true + end + end + + dbg_mobf.environment_lvl2("MOBF: " .. nodename .. " is not within environment list:") + + for i,v in ipairs(media) do + dbg_mobf.environment_lvl3("MOBF: \t" .. v) + end + + return false +end + +------------------------------------------------------------------------------- +-- @function [parent=#environment] get_absolute_min_max_pos(env, pos) +-- +--! @brief check if nodename is in environment +--! @ingroup environment +-- +--! @param env environment mob should be +--! @param pos position it is currently +--! @return { minpos,maxpos } +------------------------------------------------------------------------------ +function environment.get_absolute_min_max_pos(env,pos) + + local node = minetest.get_node(pos) + + --if is not within environment it should be return current position + --as min max + if environment.is_media_element(node.name,env.media) == false then + return pos.y,pos.y + end + + local min_y = env.min_height_above_ground + local max_y = env.max_height_above_ground + + + --a fully generic check isn't possible here so we need to use media + --specific ways ... it's ugly but works + if node.name == "air" then + min_y = min_y + ( pos.y - mobf_surface_distance(pos)) + max_y = max_y + ( pos.y - mobf_surface_distance(pos)) + end + + if node.name == "default:water" or + node.name == "defailt:water_flowing" then + -- water mobs do use min/max directly + end + + if node.name == "default:lava" or + node.name == "default:lava_flowing" then + --TODO e.g. lava fish + --not implemented by now + end + + return min_y,max_y +end + + +------------------------------------------------------------------------------- +-- @function [parent=#environment] is_jumpable_surface(name) +-- +--! @brief check if name is a surface an mob may jump onto +--! @ingroup environment +-- +--! @param name name to check +--! @return true/false +------------------------------------------------------------------------------- +function environment.is_jumpable_surface(name) + + + if name == "default:dirt" or + name == "default:dirt_with_grass" or + name == "default:stone" or + name == "default:sand" or + name == "default:clay" + then + return true + end + + dbg_mobf.environment_lvl1("MOBF: is "..name.." a jumpable surface?") + return false +end + +------------------------------------------------------------------------------- +-- @function [parent=#environment] checksurface(pos,surfaces) +-- +--! @brief check if a position is suitable for an mob +--! @ingroup environment +-- +--! @param pos position to check +--! @param surface surfaces valid +--! @return true on valid surface false if not +------------------------------------------------------------------------------- +function environment.checksurface(pos,surface) + + local pos_below = {x=pos.x,y=pos.y-1,z=pos.z} + local node_below = minetest.get_node(pos_below) + + --if we couldn't even get the node count as wrong + if node_below == nil then + return "wrong_surface" + end + + --if no surfaces are specified any surface is treated as ok + if surface == nil then + if minetest.registered_nodes[node_below.name] ~= nil then + if minetest.registered_nodes[node_below.name].walkable then + return "ok",node_below.name + else + return "wrong_surface",node_below.name + end + else + dbg_mobf.environment_lvl1("ENV surface check: " .. node_below.name.. + " isn't even a registred node??") + --not a registred nodename??? count as wrong surface + return "wrong_surface",node_below.name + end + end + + for i,v in ipairs(surface.good) do + if node_below.name == v then + return "ok",node_below.name + end + end + + if surface.possible ~= nil then + for i,v in ipairs(surface.possible) do + if node_below.name == v then + return "possible_surface",node_below.name + end + end + end + + return "wrong_surface",node_below.name + +end + +------------------------------------------------------------------------------- +-- @function [parent=#environment] get_min_max_ground_dist(entity) +-- +--! @brief calculate absolute minimum and maximum height for a mob +--! @ingroup environment +-- +--! @param entity mob to check +--! @return min y val,max y val +------------------------------------------------------------------------------- +function environment.get_min_max_ground_dist(entity) + local min_ground_distance = 0 + local max_ground_distance = 0 + + if entity.environment.max_height_above_ground ~= nil then + max_ground_distance = entity.environment.max_height_above_ground + end + + if entity.environment.min_height_above_ground ~= nil then + min_ground_distance = entity.environment.min_height_above_ground + end + + if entity.data.movement.canfly == nil or + entity.data.movement.canfly == false then + max_ground_distance = 1 + end + + return min_ground_distance,max_ground_distance +end + +------------------------------------------------------------------------------- +-- @function [parent=#environment] evaluate_pos_media(pos,media) +-- +--! @brief check position media quality +--! @ingroup environment +-- +--! @param pos position to check +--! @param media media description to check +--! +--! @return 100 = in media 10 collision 0 invalid, nodename +------------------------------------------------------------------------------- +function environment.evaluate_pos_media(pos,media) + local node_to_check = minetest.get_node(pos) + + if node_to_check == nil then + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: BUG!!!! checking position with invalid node") + return 0,nil + end + + if not environment.is_media_element(node_to_check.name,media) then + return 10,node_to_check + end + + return 100,node_to_check +end + +------------------------------------------------------------------------------- +-- @function [parent=#environment] same_state(state1,state2) +-- +--! @brief compare two states +--! @ingroup environment +-- +--! @param state1 +--! @param state2 +--! +--! @return true/false +------------------------------------------------------------------------------- +function environment.same_state(state1,state2) + environment.is_state(state1) + environment.is_state(state2) + + if state1.valid == false or + state2.valid == false then + return false + end + + if state1.media_quality ~= state2.media_quality then + return false + end + + if state1.geometry_quality ~= state2.geometry_quality then + return false + end + + if state1.surface_quality_min ~= state2.surface_quality_min then + return false + end + + if state1.surface_quality_max ~= state2.surface_quality_max then + return false + end + + if state1.level_quality ~= state2.level_quality then + return false + end + + return true +end + +------------------------------------------------------------------------------- +-- @function [parent=#environment] compare_state(state1,state2) +-- +--! @brief compare state1 and state2 +--! @ingroup environment +-- +--! @param state1 +--! @param state2 +--! +--! @return -1, 0, 1 +------------------------------------------------------------------------------- +function environment.compare_state(state1,state2) + environment.is_state(state1) + environment.is_state(state2) + + local right_better = false + local left_better = false + + if state1.valid == false and state2.valid == true then + return 1 + end + + if state1.valid == true and state2.valid == false then + return -1 + end + + if state1.media_quality < state2.media_quality or + state1.geometry_quality < state2.geometry_quality or + state1.surface_quality_min < state2.surface_quality_min or + state1.surface_quality_max < state2.surface_quality_max or + state1.level_quality < state2.level_quality + then + right_better = true + end + + if state1.media_quality > state2.media_quality or + state1.geometry_quality > state2.geometry_quality or + state1.surface_quality_min > state2.surface_quality_min or + state1.surface_quality_max > state2.surface_quality_max or + state1.level_quality > state2.level_quality + then + left_better = true + end + if right_better and not left_better then + return 1 + end + + if left_better and not right_better then + return -1 + end + + return 0 +end + +------------------------------------------------------------------------------- +-- @function [parent=#environment] is_state(state) +-- +-- +--! @brief assert if state is something else than a state +--! @ingroup environment +------------------------------------------------------------------------------- +function environment.is_state(state) + mobf_assert_backtrace(state ~= nil) + mobf_assert_backtrace(type(state) == "table") + mobf_assert_backtrace(state.valid ~= nil) + mobf_assert_backtrace(state.media_quality ~= nil) + mobf_assert_backtrace(state.geometry_quality ~= nil) + mobf_assert_backtrace(state.center_geometry_quality ~= nil) + mobf_assert_backtrace(state.surface_quality_min ~= nil) + mobf_assert_backtrace(state.surface_quality_max ~= nil) + mobf_assert_backtrace(state.level_quality ~= nil) + mobf_assert_backtrace(state.surface_quality_center ~= nil) +end +------------------------------------------------------------------------------- +-- @function [parent=#environment] evaluate_state(state,old_state, min_media, +-- min_geom,min_geom_center,min_min_surface,min_max_surface) +-- +--! @brief evaluate a position +--! @ingroup environment +-- +-- @param state verify this state +-- @param limits = { +-- old_state if set new state needs to be better or equal to this state only +-- min_media minimum media quality required +-- min_geom minimum geometry quality required +-- min_geom_center minimum geometry quality at center +-- min_min_surface minimum value for minimal surface quality +-- min_max_surface minimum value for maximal surface quality +-- min_center_surface minimum value for surface quality at center +-- } +-- +-- @return true/false +------------------------------------------------------------------------------- +function environment.evaluate_state(state,limits) + + mobf_assert_backtrace(type(limits) == "table") + environment.is_state(state) + + if state.valid == false then return false end + + if limits.min_media ~= nil and state.media_quality < limits.min_media + then return false end + + if limits.min_geom_center ~= nil and state.center_geometry_quality < limits.min_geom_center + then return false end + + if limits.old_state ~= nil then + if environment.compare_state(state,limits.old_state) == -1 then + return true + end + end + + local retval = true + + if limits.min_geom ~= nil and state.geometry_quality < limits.min_geom + then retval = false end + if limits.min_min_surface ~= nil and state.surface_quality_min < limits.min_min_surface + then retval = false end + if limits.min_max_surface ~= nil and state.surface_quality_max < limits.min_max_surface + then retval = false end + if limits.min_center_surface ~= nil and state.surface_quality_center < limits.min_center_surface + then retval = false end + + return retval +end + +------------------------------------------------------------------------------- +-- @function [parent=#environment] pos_quality(pos,entity) +-- +--! @brief check position quality +--! @ingroup environment +-- +--! @param pos position to check +--! @param entity mob to check +--! @return position quality +--! { +--! media_quality 100 = in media +--! 30 = collision (not passed outside) +--! 20 = special in_water +--! 10 = special in_air +--! 5 = solid +--! 0 = not evaluated +--! geometry_quality = 100 = full contact +--! 60 = partial contact +--! 30 = no contact +--! 0 = not evaluated +--! center_geometry_quality = 100 = contact +--! 30 = no contact +--! 0 = not evaluated +--! surface_quality_min = 100 = all ok +--! 60 = worst is possible +--! 30 = worst is wrong +--! 10 = special "above_water" +--! 0 = not evaluated +--! surface_quality_max = 100 = all ok +--! 60 = best is possible +--! 30 = best is wrong +--! 10 = special "above_water" +--! 0 = not evaluated +--! surface_quality_center = 100 = ok +--! 60 = best is possible +--! 30 = best is wrong +--! 10 = special "above_water" +--! 0 = not evaluated +--! level_quality = 100 = ok +--! 60 = above_limit +--! 30 = below_limit +--! 0 = not evaluated +--! valid = true = data is valid +--! false = no valid data +------------------------------------------------------------------------------- +function environment.pos_quality(pos,entity) + mobf_assert_backtrace(pos ~= nil) + mobf_assert_backtrace(entity ~= nil) + mobf_assert_backtrace(entity.collisionbox ~= nil) + mobf_assert_backtrace(entity.environment ~= nil) + + local retval = { + media_quality = 100, + geometry_quality = 0, + center_geometry_quality = 0, + surface_quality_min = 100, + surface_quality_max = 0, + surface_quality_center = 0, + level_quality = 100, + valid = true, + + tostring = function(state) + environment.is_state(state) + + local retval = "\nState: ".. dump(state.valid) .. "\n" + retval = retval .."\tmedia_quality: (" .. state.media_quality ..") " + if state.media_quality == 100 then retval = retval .. "in media" end + if state.media_quality == 30 then retval = retval .. "collision" end + if state.media_quality == 20 then retval = retval .. "in water" end + if state.media_quality == 10 then retval = retval .. "in air" end + if state.media_quality == 5 then retval = retval .. "solid" end + if state.media_quality == 0 then retval = retval .. "not evaluated" end + + retval = retval .."\n\tgeometry_quality: (" .. state.geometry_quality .. ") " + if state.geometry_quality == 100 then retval = retval .. "full contact" end + if state.geometry_quality == 60 then retval = retval .. "partial contact" end + if state.geometry_quality == 30 then retval = retval .. "no contact" end + if state.geometry_quality == 0 then retval = retval .. "not evaluated" end + + retval = retval .."\n\tcenter_geometry_quality: (" .. state.center_geometry_quality .. ") " + if state.center_geometry_quality == 100 then retval = retval .. "contact" end + if state.center_geometry_quality == 30 then retval = retval .. "no contact" end + if state.center_geometry_quality == 0 then retval = retval .. "not evaluated" end + + retval = retval .."\n\tsurface_quality_min: (" .. state.surface_quality_min ..") " + if state.surface_quality_min == 100 then retval = retval .. "ok" end + if state.surface_quality_min == 60 then retval = retval .. "possible" end + if state.surface_quality_min == 30 then retval = retval .. "wrong" end + if state.surface_quality_min == 10 then retval = retval .. "above water" end + if state.surface_quality_min == 0 then retval = retval .. "not evaluated" end + + retval = retval .."\n\tsurface_quality_max: (" .. state.surface_quality_max .. ") " + if state.surface_quality_max == 100 then retval = retval .. "ok" end + if state.surface_quality_max == 60 then retval = retval .. "possible" end + if state.surface_quality_max == 30 then retval = retval .. "wrong" end + if state.surface_quality_max == 10 then retval = retval .. "above water" end + if state.surface_quality_max == 0 then retval = retval .. "not evaluated" end + + retval = retval .."\n\tsurface_quality_center: (" .. state.surface_quality_center .. ") " + if state.surface_quality_center == 100 then retval = retval .. "ok" end + if state.surface_quality_center == 60 then retval = retval .. "possible" end + if state.surface_quality_center == 30 then retval = retval .. "wrong" end + if state.surface_quality_center == 10 then retval = retval .. "above water" end + if state.surface_quality_center == 0 then retval = retval .. "not evaluated" end + + retval = retval .."\n\tlevel_quality: (" .. state.level_quality .. ") " + if state.level_quality == 100 then retval = retval .. "ok" end + if state.level_quality == 60 then retval = retval .. "above limit" end + if state.level_quality == 30 then retval = retval .. "below limit" end + if state.level_quality == 0 then retval = retval .. "not evaluated" end + retval = retval .. "\n" + + return retval + end, + shortstring = function(state) + return dump(state.valid) .. ":" .. + state.media_quality .. ";" .. + state.geometry_quality .. "," .. state.center_geometry_quality .. ";" .. + state.surface_quality_min .. "," .. state.surface_quality_center .. "," ..state.surface_quality_max .. ";" .. + state.level_quality + end + } + + + + local cornerpositions = {} + local lastpos = nil -- performance improvement to skip checking same pos multiple times + + table.insert(cornerpositions,{x=pos.x + entity.collisionbox[4] -0.01,y=pos.y,z=pos.z + entity.collisionbox[6] -0.01}) + table.insert(cornerpositions,{x=pos.x + entity.collisionbox[4] -0.01,y=pos.y,z=pos.z + entity.collisionbox[3] +0.01}) + table.insert(cornerpositions,{x=pos.x + entity.collisionbox[1] +0.01,y=pos.y,z=pos.z + entity.collisionbox[6] -0.01}) + table.insert(cornerpositions,{x=pos.x + entity.collisionbox[1] +0.01,y=pos.y,z=pos.z + entity.collisionbox[3] +0.01}) + + local min_ground_distance,max_ground_distance = environment.get_min_max_ground_dist(entity) + + --check if mob at pos will be in correct environment + for i=1,#cornerpositions,1 do + if not mobf_pos_is_same(lastpos,cornerpositions[i]) then + local med_quality,node_to_check = + environment.evaluate_pos_media(cornerpositions[i], + entity.environment.media) + --if current result is worse than old one + if med_quality < retval.media_quality then + if med_quality == 0 then + retval.valid = false + end + + if node_to_check.name == "default:water_source" or + node_to_check.name == "default:water_flowing" then + retval.media_quality = 20 + end + + if node_to_check.name == "air" then + retval.media_quality = 10 + break + end + + if med_quality < retval.media_quality then + retval.media_quality = 5 + end + end + end + lastpos = cornerpositions[i] + end + + --check height level for flying mobs + if entity.data.movement.canfly == true then + lastpos = nil + for i=1,#cornerpositions,1 do + if not mobf_pos_is_same(lastpos,cornerpositions[i]) then + local miny,maxy = environment.get_absolute_min_max_pos(entity.environment,cornerpositions[i]) + + dbg_mobf.environment_lvl2("MOBF: \tflying mob detected, min: " + .. miny .. " max: " .. maxy .. " current: " .. pos.y) + + + if cornerpositions[i].y < miny then + retval.level_quality = 30 + end + + if cornerpositions[i].y > maxy then + retval.level_quality = 60 + end + + if retval.level_quality < 100 then + break + end + end + + lastpos = cornerpositions[i] + end + else + + --check geometry and surface quality + lastpos = nil + local have_contact = false + local have_no_contact = false + + table.insert(cornerpositions,pos) + + for i=1,#cornerpositions,1 do + if not mobf_pos_is_same(lastpos,cornerpositions[i]) then + + local ground_distance = mobf_ground_distance(cornerpositions[i], entity.environment.media) + + + --first check if on surface or not + if ground_distance <= max_ground_distance then + local is_center = false + if mobf_pos_is_same(pos,cornerpositions[i]) then + retval.center_geometry_quality = 100 + is_center = true + end + have_contact = true + + local current_surface,belowname = + environment.checksurface(cornerpositions[i],entity.environment.surfaces) + + if current_surface == "ok" then + if retval.surface_quality_max < 100 then + retval.surface_quality_max = 100 + end + + if is_center then + retval.surface_quality_center = 100 + end + end + + if current_surface == "possible_surface" then + if retval.surface_quality_max < 60 then + retval.surface_quality_max = 60 + end + + if retval.surface_quality_min > 60 then + retval.surface_quality_min = 60 + end + + if is_center then + retval.surface_quality_center = 60 + end + end + + if current_surface == "wrong_surface" then + if belowname == "default:water_source" or + belowname == "default:water_flowing" then + if retval.surface_quality_max < 10 then + retval.surface_quality_max = 10 + end + + if retval.surface_quality_min > 10 then + retval.surface_quality_min = 10 + end + + if is_center then + retval.surface_quality_center = 10 + end + else + if retval.surface_quality_max < 30 then + retval.surface_quality_max = 30 + end + + if retval.surface_quality_min > 30 then + retval.surface_quality_min = 30 + end + + if is_center then + retval.surface_quality_center = 30 + end + end + end + else + if mobf_pos_is_same(pos,cornerpositions[i]) then + retval.center_geometry_quality = 30 + end + have_no_contact = true + end + end + + lastpos = cornerpositions[i] + end + + if have_contact and not have_no_contact then + retval.geometry_quality = 100 + end + + if have_contact and have_no_contact then + retval.geometry_quality = 60 + end + + if not have_contact and have_no_contact then + retval.geometry_quality = 30 + end + end + + return retval +end + + +------------------------------------------------------------------------------- +-- @function [parent=#environment] pos_is_ok(pos,entity) DEPRECATED +-- +--! @brief check if a position is suitable for an mob +--! @ingroup environment +-- +--! @param pos position to check +--! @param entity mob to check +--! @param dont_do_jumpcheck +--! @return suitability of position for mob values: +--! -ok -@>position is ok +--! -collision -@>position is within a node +--! -collision_jumpable -@>position is within a node that can be jumped onto +--! -drop -@>position is a drop +--! -drop_above_water -@>position is to far above water +--! -above_water -@>position is right over water +--! -in_water -@>position is within a water node(source or flow) +--! -in_air -@>position is in air +--! -above_limit -@>position is above level limit +--! -below_limit -@>position is below level limit +--! -wrong_surface -@>position is above surface mob shouldn't be +--! -invalid -@>unable to check position +------------------------------------------------------------------------------- +function environment.pos_is_ok(pos,entity,dont_do_jumpcheck) + + local min_ground_distance,max_ground_distance = environment.get_min_max_ground_dist(entity) + + local cornerpositions = {} + + table.insert(cornerpositions,pos) + --read positions at corners + table.insert(cornerpositions,{x=pos.x + entity.collisionbox[4] -0.01,y=pos.y,z=pos.z + entity.collisionbox[6] -0.01}) + table.insert(cornerpositions,{x=pos.x + entity.collisionbox[4] -0.01,y=pos.y,z=pos.z + entity.collisionbox[3] +0.01}) + table.insert(cornerpositions,{x=pos.x + entity.collisionbox[1] +0.01,y=pos.y,z=pos.z + entity.collisionbox[6] -0.01}) + table.insert(cornerpositions,{x=pos.x + entity.collisionbox[1] +0.01,y=pos.y,z=pos.z + entity.collisionbox[3] +0.01}) + + local lastpos = nil + + local retval = "temp_ok" + + local min_ground_distance = 33000 -- above max height + + --check if mob at pos will be in correct environment + for i=1,#cornerpositions,1 do + if not mobf_pos_is_same(lastpos,cornerpositions[i]) then + local node_to_check = minetest.get_node(cornerpositions[i]) + + if node_to_check == nil then + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: BUG!!!! checking position with invalid node") + retval = "invalid" + break + end + + if not environment.is_media_element(node_to_check.name,entity.environment.media) == true then + dbg_mobf.environment_lvl3("MOBF: " .. i .. ": " .. + printpos(cornerpositions[i]) .. " -- " .. printpos(pos) .. + " not within environment") + + if mobf_pos_is_same(pos,cornerpositions[i]) then + if node_to_check.name == "default:water_source" or + node_to_check.name == "default:water_flowing" then + retval = "in_water" + break + end + + if node_to_check.name == "air" then + retval = "in_air" + break + end + + --TODO maybe replace by "invalid medium" + else + retval = "collision" + end + end + + min_ground_distance = + MIN(min_ground_distance, + mobf_ground_distance(cornerpositions[i],entity.environment.media)) + end + lastpos = cornerpositions[i] + end + + -- + if retval == "temp_ok" then + dbg_mobf.environment_lvl3("MOBF: \tin environment") + local ground_distance = mobf_ground_distance(pos,entity.environment.media) + + --following return codes are only usefull for non flying + if entity.data.movement.canfly == nil or + entity.data.movement.canfly == false then + + if mobf_above_water(pos) then + + if min_ground_distance > max_ground_distance then + dbg_mobf.environment_lvl2("MOBF: \tdropping above water") + retval = "drop_above_water" + end + dbg_mobf.environment_lvl2("MOBF: \tabove water") + retval = "above_water" + end + + if min_ground_distance > max_ground_distance then + dbg_mobf.environment_lvl2("MOBF: \tdropping " + .. ground_distance .. " / " .. max_ground_distance) + retval = "drop" + else + dbg_mobf.environment_lvl2("MOBF: \tsurface dependent") + retval = environment.checksurface(pos,entity.environment.surfaces) + end + else + local miny,maxy = environment.get_absolute_min_max_pos(entity.environment,pos) + dbg_mobf.environment_lvl2("MOBF: \tflying mob detected, min: " + .. miny .. " max: " .. maxy .. " current: " .. pos.y) + if pos.y < miny then + retval = "below_limit" + else if pos.y > maxy then + retval = "above_limit" + else + retval = environment.checksurface(pos,entity.environment.surfaces) + end end + end + end + + if retval == "collision" and not dont_do_jumpcheck then + dbg_mobf.environment_lvl2("MOBF: check if pos is jumpable") + local upper_pos_state = environment.pos_is_ok({x=pos.x, + y=pos.y+1, + z=pos.z}, + entity,true) + if upper_pos_state == "ok" then + retval = "collision_jumpable" + else + dbg_mobf.environment_lvl2("MOBF: upper pos state was: " .. upper_pos_state) + end + end + + return retval +end + +------------------------------------------------------------------------------- +-- @function [parent=#environment] get_default_gravity(pos,environment,canfly) +-- +--! @brief get default acceleration depending on mobs medium and pos +--! @ingroup environment +-- +--! @param pos position where to check gravity +--! @param media mobs movement medium +--! @param canfly is mob capable of flying? +--! @return y-acceleration +------------------------------------------------------------------------------ +function environment.get_default_gravity(pos,media,canfly) + + if pos == nil then + return nil + end + + local node = minetest.get_node(pos) + + --if an mob can't fly or isn't within it's medium default acceleration + -- for it's current medium is applied + if canfly == nil or + canfly == false or + environment.is_media_element(node.name,media) == false + then + if (node.name == "air") then + return -9.81 + end + + if node.name == "default:water_source" or + node.name == "default:water_flowing" then + return -2.5 + end + + if node.name == "default:lava" then + return 0.1 + end + + --mob is at invalid position thus returning default air acceleration + return -9.81 + end + + return 0 +end + + +------------------------------------------------------------------------------- +-- @function [parent=#environment] fix_base_pos(entity, middle_to_bottom) +-- +--! @brief fix the mobs y position according to model or sprite height +--! @ingroup environment +-- +--! @param entity mob to fix base position +--! @param center_to_bottom distance from center of mob to its bottom (absolute value) +--! @return new position set by function +------------------------------------------------------------------------------ +function environment.fix_base_pos(entity, center_to_bottom) + + if center_to_bottom > 0.5 then + + local pos = entity.object:getpos() + + local node_pos = minetest.get_node(pos) + + local pos_to_check = {x=pos.x,y=pos.y-center_to_bottom+0.1,z=pos.z} + local node_pos_check = minetest.get_node(pos_to_check) + + if node_pos ~= nil and + node_pos_check ~= nil then + dbg_mobf.environment_lvl3("MOBF: fixing y position / base position required? " + .. node_pos.name .. " " .. node_pos_check.name) + if node_pos.name ~= node_pos_check.name then + local distance_to_ground = mobf_surface_distance(pos) + + pos.y = pos.y + (center_to_bottom - distance_to_ground +0.5) + dbg_mobf.environment_lvl2("MOBF: fixing y position of " .. entity.data.name + .. " got distance " .. center_to_bottom .. " moving to " ..printpos(pos)) + entity.object:moveto(pos) + entity.dynamic_data.spawning.spawnpoint = pos + end + end + end + + return entity.getbasepos(entity) +end + +------------------------------------------------------------------------------- +-- @function [parent=#environment] register(name, environment) +-- +--! @brief register an environment to mob framework +--! @ingroup environment +-- +--! @param name id of environment +--! @param environment description of environment +--! @return true/false succesfully registred environment +------------------------------------------------------------------------------- +function environment.register(name, environment) + + if environment_list[name] ~= nil then + return false + end + + environment_list[name] = environment + return true +end + +------------------------------------------------------------------------------- +-- @function [parent=#environment] pos_state_is_impossible(entity,pos) +-- +--! @brief checks if a entity can be there (not if it would move there by its own) +--! @ingroup environment +-- +--! @param entity entity to check +--! @param pos position to check +--! @return true entity may be there, entity can never be there +------------------------------------------------------------------------------- +function environment.possible_pos(entity,pos) + local state = environment.pos_is_ok(pos,entity) + + if state == "collision" or + state == "collision_jumpable" or + state == "invalid" then + return false + end + + return true +end + +--!@} + diff --git a/mods/mob_engines/mobf/factions.lua b/mods/mob_engines/mobf/factions.lua new file mode 100644 index 00000000..b94c9dd1 --- /dev/null +++ b/mods/mob_engines/mobf/factions.lua @@ -0,0 +1,325 @@ +------------------------------------------------------------------------------- +-- 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 factions.lua +--! @brief contains factions adaption of mobf +--! @copyright Sapier +--! @author Sapier +--! @date 2013-08-21 +--! +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +mobf_assert_backtrace(not core.global_exists("mobf_factions")) +--! @class mobf_factions +--! @brief faction mod support for mobf +mobf_factions = {} + + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_factions] init() +-- +--! @brief initialize mobf factions support +--! @memberof mobf_factions +-- +------------------------------------------------------------------------------- +function mobf_factions.init() + if minetest.get_modpath("factions")then + mobf_rtd.factions_available = true + end + + --need to catch my form events + minetest.register_on_player_receive_fields(mobf_factions.button_handler) +end + + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_factions] setupmob(factionsdata) +-- +--! @brief creat factions a mob may be member of +--! @memberof mobf_factions +-- +--! @param factionsdata data to set for mob +-- +------------------------------------------------------------------------------- +function mobf_factions.setupmob(factionsdata) + if mobf_rtd.factions_available then + if factionsdata ~= nil and + factionsdata.member ~= nil then + + for i=1,#factionsdata.member,1 do + if not factions.exists(factionsdata.member[i]) then + factions.add_faction(factionsdata.member[i]) + end + end + end + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_factions] setupentity(entity,preserved_data) +-- +--! @brief add a mob to it's factons +--! @memberof mobf_factions +-- +--! @param entity to be added to it's factions +--! @param preserved_data mob reputation data +-- +------------------------------------------------------------------------------- +function mobf_factions.setupentity(entity,preserved_data) + if mobf_rtd.factions_available then + if entity.data.factions ~= nil and + entity.data.factions.member ~= nil then + + for i=1,#entity.data.factions.member,1 do + factions.member_add(entity.data.factions.member[i],entity) + end + end + + + if preserved_data ~= nil then + for i=1,#preserved_data,1 do + factions.member_add(preserved_data[i],entity) + end + end + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_factions] cleanup_entity(entity) +-- +--! @brief called on deactivation of a mob +--! @memberof mobf_factions +-- +--! @param entity to be cleant up +-- +------------------------------------------------------------------------------- +function mobf_factions.cleanupentity(entity) + if mobf_rtd.factions_available then + return factions.get_factions(entity.object) + else + return "" + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_factions] mob_rightclick_callback(entity,player) +-- +--! @brief show factions rightclick menu +--! @memberof mobf_factions +-- +--! @param entity to modify +--! @param player issuing rightclick +-- +------------------------------------------------------------------------------- +function mobf_factions.mob_rightclick_callback(entity,player) + local menu_data = {} + + local new_id = mobf_global_data_store(menu_data) + + menu_data.entity = entity + menu_data.player = player + + mobf_factions.show_mob_factions_menu(new_id,menu_data) +end + + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_factions] config_check(entity,player) +-- +--! @brief show factions rightclick menu +--! @memberof mobf_factions +-- +--! @param entity clicked +-- +------------------------------------------------------------------------------- +function mobf_factions.config_check(entity) + if not mobf_rtd.factions_available then + return false + end + return false +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_factions] button_handler(entity,player) +-- +--! @brief show factions rightclick menu +--! @memberof mobf_factions +-- +--! @param player issuing rightclick +--! @param formname name of form being transmitted +--! @param fields date for fields in current form +-- +------------------------------------------------------------------------------- +function mobf_factions.button_handler(player, formname, fields) + local playername = player:get_player_name() + + mobf_assert_backtrace(playername ~= nil) + + if formname == "mobf:factions:main_menu" then + dbg_mobf.path_lvl2("MOBF: factions menu opened") + for k,v in pairs(fields) do + local parts = string.split(k,":") + + if parts[1] == "mobf_factions" then + + local menu_data = mobf_factions.handle_rightclick(parts,fields,formname,player,k) + + if menu_data ~= nil then + --push data back to store + local new_id = mobf_global_data_store(menu_data) + mobf_factions.show_mob_factions_menu(new_id,menu_data) + break + end + + end + end + return true + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_factions] handle_rightclick(current_fields,fields,formname,player,fieldname) +-- +--! @brief show factions rightclick menu +--! @memberof mobf_factions +-- +--! @param current_fields +--! @param fields +--! @param formname +--! @param player +--! @param fieldname name of field causingt the callback +-- +--! @return true/false show menu again +------------------------------------------------------------------------------- +function mobf_factions.handle_rightclick(current_fields,fields,formname,player,fieldname) + print("current_fields: " .. dump(current_fields)) + --try to read data from global store + local menu_data = mobf_global_data_get(current_fields[3]) + if menu_data ~= nil then + if current_fields[2] == "btn_add_mob_faction" then + if menu_data.tl_owner_selected ~= nil then + local factionlist_owner = factions.get_factions(menu_data.player) + + if factionlist_owner ~= nil and + #factionlist_owner >= menu_data.tl_owner_selected then + + local toadd = factionlist_owner[menu_data.tl_owner_selected] + factions.member_add(toadd,menu_data.entity.object) + end + end + elseif current_fields[2] == "btn_drop_mob_faction" then + if menu_data.tl_mob_selected ~= nil then + local factionlist_mob = factions.get_factions(menu_data.entity.object) + + local todel = factionlist_mob[menu_data.tl_mob_selected] + + if factions.is_member(todel,player) then + factions.member_remove(todel,menu_data.entity.object) + else + --TODO maybe send chat message + print("MOBF Factions you're not member of " .. todel .. " so you can't remove a mob from it") + end + else + print("MOBF Factions: trying to delete from faction but no faction selected") + end + elseif current_fields[2] == "tl_mob_factions" then + local event = core.explode_textlist_event(fields[fieldname]) + + if event.typ ~= "INV" then + menu_data.tl_mob_selected = event.index + end + elseif current_fields[2] == "tl_owner_factions" then + local event = core.explode_textlist_event(fields[fieldname]) + + if event.typ ~= "INV" then + menu_data.tl_owner_selected = event.index + end + end + else + dbg_mobf.path_lvl1("MOBF: factions menu failed to find menu_data") + end + + return menu_data +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_factions] show_mob_factions_menu(new_id,menu_data) +-- +--! @brief show factions rightclick menu +--! @memberof mobf_factions +-- +--! @param new_id +--! @param menu_data +-- +--! @return true/false show menu again +------------------------------------------------------------------------------- +function mobf_factions.show_mob_factions_menu(new_id,menu_data) + + mobf_assert_backtrace(menu_data.entity ~= nil) + mobf_assert_backtrace(menu_data.player ~= nil) + + local playername = menu_data.player:get_player_name() + + local formspec = "" + + --check if menu creator is owner of the specific mob + if menu_data.entity.dynamic_data.spawning.spawner == playername then + + if menu_data.tl_mob_selected == nil then + menu_data.tl_mob_selected = 0 + end + + if menu_data.tl_owner_selected == nil then + menu_data.tl_owner_selected = 0 + end + + formspec = formspec .. "size[8,8]" + .."label[0,-0.25;Member of factions:]" + .."textlist[0,0.3;3.7,7;mobf_factions:tl_mob_factions:" .. new_id .. ";" + + local factionlist_mob = factions.get_factions(menu_data.entity.object) + + if #factionlist_mob ~= 0 then + for i=1,#factionlist_mob,1 do + formspec = formspec .. factionlist_mob[i] .. "," + end + else + formspec = formspec .. "not member of any faction" + end + + formspec = formspec .. ";" .. menu_data.tl_mob_selected .. "]" + + local factionlist_player = factions.get_factions(menu_data.player) + + formspec = formspec + .."label[4,-0.25;Owner factions:]" + .."textlist[4,0.3;3.7,7;mobf_factions:tl_owner_factions: " .. new_id .. ";" + + if #factionlist_player ~= 0 then + for i=1,#factionlist_player,1 do + formspec = formspec .. factionlist_player[i] .. "," + end + else + formspec = formspec .. "not member of any faction" + end + + formspec = formspec .. ";" .. menu_data.tl_owner_selected .. "]" + + formspec = formspec + .. "button[0,7.5;3.95,0.5;mobf_factions:btn_drop_mob_faction:" .. new_id .. ";Remove from faction]" + .. "button[4,7.5;3.95,0.5;mobf_factions:btn_add_mob_faction:" .. new_id .. ";<-- Mob join faction]" + + + else + formspec = "size[4,1]label[0,0;This is not your mob keep away!]" .. + "button_exit[1,0.75;2,0.5;btn_exit;Okay Okay!]" + end + + --show formspec + minetest.show_formspec(playername,"mobf:factions:main_menu",formspec) +end \ No newline at end of file diff --git a/mods/mob_engines/mobf/fighting.lua b/mods/mob_engines/mobf/fighting.lua new file mode 100644 index 00000000..28576b8e --- /dev/null +++ b/mods/mob_engines/mobf/fighting.lua @@ -0,0 +1,1541 @@ +------------------------------------------------------------------------------- +-- 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 fighting.lua +--! @brief component for fighting related features +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +--! @defgroup fighting Combat subcomponent +--! @brief Component handling all fighting +--! @ingroup framework_int +--! @{ +-- Contact: sapier a t gmx net +------------------------------------------------------------------------------- + +--! @class fighting + +--! @brief factor added to mob melee combat range to get its maximum agression radius +MOBF_AGRESSION_FACTOR = 5 + +--!@} + +mobf_assert_backtrace(not core.global_exists("fighting")) +--! @brief fighting class reference +fighting = {} +fighting.healdb = minetest.world_setting_get("fighting.healdb") + +--! @brief user defined on death callback +--! @memberof fighting +fighting.on_death_callbacks = {} + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] register_on_death_callback(callback) +-- +--! @brief register an additional callback to be called on death of a mob +--! @memberof fighting +-- +--! @param callback function to call +--! @return true/false +------------------------------------------------------------------------------- +function fighting.register_on_death_callback(callback) + + if type(callback) == "function" then + + table.insert(fighting.on_death_callbacks,callback) + return true + end + return false +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] do_on_death_callback(entity,hitter) +-- +--! @brief call all registred on_death callbacks +--! @memberof fighting +-- +--! @param entity to do callback for +--! @param hitter object doing last punch +------------------------------------------------------------------------------- +function fighting.do_on_death_callback(entity,hitter) + + for i,v in ipairs(fighting.on_death_callbacks) do + v(entity.data.name,entity.getbasepos(),hitter) + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] push_back(entity,player) +-- +--! @brief move a mob backward if it's punched +--! @memberof fighting +--! @private +-- +--! @param entity mobbeing punched +--! @param dir direction to push back +------------------------------------------------------------------------------- +function fighting.push_back(entity,dir) + --get some base information + local mob_pos = entity.object:getpos() + local mob_basepos = entity.getbasepos(entity) + local dir_rad = mobf_calc_yaw(dir.x,dir.z) + local posdelta = mobf_calc_vector_components(dir_rad,0.5) + + --push back mob + local new_pos = { + x=mob_basepos.x + posdelta.x, + y=mob_basepos.y, + z=mob_basepos.z + posdelta.z + } + + local pos_valid = environment.possible_pos(entity,new_pos) + new_pos.y = mob_pos.y + local line_of_sight = mobf_line_of_sight(mob_pos,new_pos) + + dbg_mobf.fighting_lvl2("MOBF: trying to punch mob from " .. printpos(mob_pos) + .. " to ".. printpos(new_pos)) + if pos_valid and line_of_sight then + dbg_mobf.fighting_lvl2("MOBF: punching back ") + entity.object:moveto(new_pos) + else + dbg_mobf.fighting_lvl2("MOBF: not punching mob: " .. dump(pos_valid) .. " " ..dump(line_of_sight)) + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] dodamage(entity,attacker) +-- +--! @brief cause damage to be done to entity +--! @memberof fighting +-- +--! @param entity mob being hit +--! @param attacker player/object hitting the mob +--! @param kill_reason reason to log for killing +------------------------------------------------------------------------------- +function fighting.dodamage(entity,attacker, kill_reason) + local mob_pos = entity.object:getpos() + + --update lifebar + mobf_lifebar.set(entity.lifebar,entity.object:get_hp()/entity.hp_max) + + -- make it die + if entity.object:get_hp() < 0.5 then + + mobf_lifebar.del(entity.lifebar) + + local result = entity.data.generic.kill_result + if type(entity.data.generic.kill_result) == "function" then + result = entity.data.generic.kill_result(entity, attacker) + end + + + --call on kill callback and superseed normal on kill handling + if entity.data.generic.on_kill_callback == nil or + entity.data.generic.on_kill_callback(entity,attacker) == false + then + + if entity.data.sound ~= nil then + sound.play(mob_pos,entity.data.sound.die); + end + + if attacker:is_player() then + if type(result) == "table" then + for i=1,#result, 1 do + if attacker:get_inventory():room_for_item("main", result[i]) then + attacker:get_inventory():add_item("main", result[i]) + end + end + else + if attacker:get_inventory():room_for_item("main", result) then + attacker:get_inventory():add_item("main", result) + end + end + else + --todo check if spawning a stack is possible + minetest.add_item(mob_pos,result) + end + spawning.remove(entity, kill_reason) + else + dbg_mobf.fighting_lvl2("MOBF: ".. entity.data.name + .. " custom on kill handler superseeds generic handling") + end + return + end +end + + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] hit(entity,attacker) +-- +--! @brief handler for mob beeing hit +--! @memberof fighting +-- +--! @param entity mob being hit +--! @param attacker player/object hitting the mob +------------------------------------------------------------------------------- +function fighting.hit(entity,attacker) + mobf_assert_backtrace(entity ~= nil) + mobf_assert_backtrace(attacker ~= nil) + + --execute user defined on_hit_callback + if entity.data.generic.on_hit_callback ~= nil and + entity.data.generic.on_hit_callback(entity,attacker) == true + then + dbg_mobf.fighting_lvl2("MOBF: ".. entity.data.name .. " custom on hit handler superseeds generic handling") + return + end + + --get some base information + local mob_pos = entity.object:getpos() + local mob_basepos = entity.getbasepos(entity) + local targetpos = attacker:getpos() + local dir = mobf_get_direction(targetpos,mob_basepos) + + + --don't attack spawner + if entity.dynamic_data.spawning.spawner ~= nil and + attacker:is_player() then + local playername = attacker:get_player_name() + if entity.dynamic_data.spawning.spawner == playername then + if entity.dynamic_data.state.current ~= "combat" then + local tool = attacker:get_wielded_item() + -- rotation is only done if player punches using hand + if tool:get_name() == "" then + local current_yaw = graphics.getyaw(entity) + graphics.setyaw(entity, current_yaw + math.pi/4) + return + end + end + end + end + + --play hit sound + if entity.data.sound ~= nil then + sound.play(mob_pos,entity.data.sound.hit); + end + + if entity.data.combat ~= nil and + entity.data.combat.on_hit_overlay ~= nil then + mobf_assert_backtrace( entity.data.combat.on_hit_overlay.texture ~= nil) + mobf_assert_backtrace( entity.data.combat.on_hit_overlay.timer ~= nil) + + if entity.dynamic_data.combat.old_textures == nil then + entity.dynamic_data.combat.old_textures = entity.textures + end + + local new_props = { + textures = { entity.dynamic_data.combat.old_textures[1] .. "^" .. + entity.data.combat.on_hit_overlay.texture } + } + + + + core.after(entity.data.combat.on_hit_overlay.timer,function() + local restore_probs = { + textures = entity.dynamic_data.combat.old_textures + } + entity.dynamic_data.combat.old_textures = nil + entity.object:set_properties(restore_probs) + end) + + entity.object:set_properties(new_props) + end + + --push mob back + fighting.push_back(entity,dir) + + --cause damage to be evaluated + fighting.dodamage(entity, attacker, "killed") + + --dbg_mobf.fighting_lvl2("MOBF: attack chance is ".. entity.data.combat.angryness) + -- fight back + if entity.data.combat ~= nil and + ( entity.data.combat.can_fight or + (entity.data.combat.angryness ~= nil and entity.data.combat.angryness > 0) + + ) and + entity.object:get_hp() > (entity.data.generic.base_health/3) then + + --face attacker + if entity.mode ~= "3d" then + graphics.setyaw(entity, mobf_calc_yaw(dir.x,dir.z)-math.pi) + end + + dbg_mobf.fighting_lvl2("MOBF: mob with chance of fighting back attacked") + --either the mob hasn't been attacked by now or a new player joined fight + + if math.random() < entity.data.combat.angryness then + fighting.set_target(entity,attacker) + end + else + --make non agressive animals run away + fighting.run_away(entity,dir,attacker) + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] run_away(entity,dir_to_enemy,enemy) +-- +--! @brief make a mob run away +--! @memberof fighting +--! @private +-- +--! @param entity mob to run away +--! @param dir_to_enemy direction towards enemy +--! @param enemy the enemy to avoid +------------------------------------------------------------------------------- +function fighting.run_away(entity,dir_to_enemy,enemy) + local flee_state = mob_state.get_state_by_name(entity,"flee") + + if flee_state == nil then + local new_state = mob_state.get_state_by_name(entity,"walking") + local dir_rad = mobf_calc_yaw(dir_to_enemy.x,dir_to_enemy.z) + local fleevelocity = mobf_calc_vector_components(dir_rad, + entity.data.movement.max_accel*2) + + local current_accel = entity.object:getacceleration() + local current_velocity = entity.object:getvelocity() + + mob_state.change_state(entity,new_state) + + entity.object:setvelocity({x=0,y=current_velocity.y,z=0}) + entity.object:setacceleration({ + x=fleevelocity.x, + y=current_accel.y, + z=fleevelocity.z} + ) + else + mob_state.change_state(entity,flee_state) + entity.dynamic_data.current_movement_gen.set_target(entity,enemy) + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] identify_combat_state(entity,target,distance) +-- +--! @brief identify combat state to use +--! @memberof fighting +--! @private +-- +--! @param entity mob to find state for +--! @param distance distance to target +-- +--! @return state to use +------------------------------------------------------------------------------- +function fighting.identify_combat_state(entity,distance) + + local target = entity.dynamic_data.combat.target + mobf_assert_backtrace(entity ~= nil) + mobf_assert_backtrace(target ~= nil) + + local combat_melee = mob_state.get_state_by_name(entity,"combat_melee") + local combat_distance = mob_state.get_state_by_name(entity,"combat_distance") + local combat_generic = mob_state.get_state_by_name(entity,"combat") + + if distance == nil then + local mob_pos = entity.object:getpos() + local targetpos = target:getpos() + distance = mobf_calc_distance(mob_pos,targetpos) + end + + dbg_mobf.fighting_lvl2("MOBF: Identify combat state, mob: " .. entity.data.name .. " distance: " .. distance) + + if combat_melee ~= nil and + distance < entity.data.combat.melee.range then + return combat_melee + end + + if combat_distance and + entity.data.combat.distance ~= nil and + distance < entity.data.combat.distance.range and + (entity.data.combat.distance.min_range == nil or + distance > entity.data.combat.distance.min_range) then + + -- distance within mele range + return combat_distance + end + + return combat_generic +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] switch_to_combat_state(entity,now,target) +-- +--! @brief switch to combat state +--! @memberof fighting +--! @private +-- +--! @param entity mob to switch state +--! @param now current time in seconds +--! @param target the target to attack +------------------------------------------------------------------------------- +function fighting.switch_to_combat_state(entity,now,target) + + mobf_assert_backtrace(entity ~= nil) + + --precheck + if target == nil then + dbg_mobf.fighting_lvl2("MOBF: no target for combat state change specified") + return + end + + --set attack target + entity.dynamic_data.combat.target = target + + local current_state = entity.dynamic_data.state.current + + mobf_assert_backtrace(current_state.state_mode ~= "combat") + + local combat_state = fighting.identify_combat_state(entity) + if combat_state == nil then + dbg_mobf.fighting_lvl2("MOBF: no special combat state") + return + end + + dbg_mobf.fighting_lvl2("MOBF: switching to combat state") + + --make sure state is locked + mob_state.lock(entity,true) + + --backup dynamic movement data + local backup = {} + backup.movement = entity.dynamic_data.movement + backup.p_movement = entity.dynamic_data.p_movement + + --create new movement data + entity.dynamic_data.movement = {} + entity.dynamic_data.p_movement = {} + + backup.current_state = entity.dynamic_data.state.current + dbg_mobf.fighting_lvl2("MOBF: backing up state: " .. backup.current_state.name) + + --switch state + mob_state.change_state(entity,combat_state) + + --save old movement data to use on switching back + entity.dynamic_data.combat.movement_backup = backup + + --make sure a fighting mob ain't teleporting to target + entity.dynamic_data.movement.teleportsupport = false + + --make sure we do follow our target + entity.dynamic_data.movement.guardspawnpoint = false + + --set target + entity.dynamic_data.current_movement_gen.set_target(entity,target) + + -- play start_attack sound + if entity.data.sound ~= nil and + entity.data.sound.start_attack ~= nil then + sound.play(entity.object:getpos(),entity.data.sound.start_attack); + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] restore_previous_state(entity,now) +-- +--! @brief restore default movement generator of mob +--! @memberof fighting +--! @private +-- +--! @param entity mob to restore movement generator +--! @param now current time in seconds +------------------------------------------------------------------------------- +function fighting.restore_previous_state(entity,now) + + --check if ther is anything we can restore + if entity.dynamic_data.combat.movement_backup ~= nil then + local backup = entity.dynamic_data.combat.movement_backup + + mobf_assert_backtrace(backup.current_state ~= "combat") + + if backup.current_state ~= nil then + dbg_mobf.fighting_lvl2("MOBF: restore state: " .. backup.current_state.name) + mob_state.change_state(entity,backup.current_state) + else + minetest.log(LOGLEVEL_WARNING,"MOBF: unable to restore previous state switching to default") + mob_state.change_state(entity,mob_state.get_state_by_name(entity,"default")) + end + + backup.current_state = nil + + --restore old movement data + entity.dynamic_data.movement = backup.movement + entity.dynamic_data.p_movement = backup.p_movement + + --don't restore old movement target if not valid anymore + if entity.dynamic_data.movement.target == nil or + (not mobf_is_pos(entity.dynamic_data.movement.target) and + entity.dynamic_data.movement.target:getpos() == nil) then + entity.dynamic_data.movement.target = nil + end + + --make sure all remaining data is deleted + entity.dynamic_data.combat.movement_backup = nil + end + + --make sure state is unlocked + mob_state.lock(entity,false) +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] in_range(entity,now) +-- +--! @brief check if mob is within range of target +--! @memberof fighting +--! @private +-- +--! @param entity mob +--! @param distance to target +------------------------------------------------------------------------------- +function fighting.in_range(entity,distance) + if (entity.data.combat.melee == nil or + distance > entity.data.combat.melee.range) and + (entity.data.combat.distance == nil or + distance > entity.data.combat.distance.range) then + + if entity.data.combat.melee ~= nil or + entity.data.combat.distance ~= nil then + dbg_mobf.fighting_lvl2("MOBF: distance="..distance) + + if entity.data.combat.melee ~= nil then + dbg_mobf.fighting_lvl2("MOBF: melee="..entity.data.combat.melee.range) + end + if entity.data.combat.distance ~= nil then + dbg_mobf.fighting_lvl2("MOBF: distance="..entity.data.combat.distance.range) + end + end + return false + end + + return true +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] combat(entity,now) +-- +--! @brief periodic callback called to do mobs own combat related actions +--! @memberof fighting +-- +--! @param entity mob to do action +--! @param now current time +--! @param dtime time fraction since last call +-- +--! @return continue callback execution or not +------------------------------------------------------------------------------- +function fighting.combat(entity,now,dtime) + + --handle self destruct mobs + if fighting.self_destruct_handler(entity,now) then + return false + end + + --fight against generic enemy "sun" + if fighting.sun_damage_handler(entity,now) then + return false + end + + if entity.dynamic_data.combat ~= nil and + entity.dynamic_data.combat.target ~= nil then + + --check if target is still valid + if not entity.dynamic_data.combat.target:is_player() then + local target_entity = entity.dynamic_data.combat.target:get_luaentity() + local target_pos = entity.dynamic_data.combat.target:getpos() + + --print("MOBF: target is not player checking if stil valid: " + -- .. dump(target_entity) .. " " .. dump(target_pos)) + + if target_entity == nil or + target_entity.data == nil or + target_pos == nil then + + -- switch back to default movement gen + fighting.restore_previous_state(entity,now) + + --there is no player by that name, stop attack + entity.dynamic_data.combat.target = nil + + dbg_mobf.fighting_lvl1("MOBF: not a valid target: " + .. dump(entity.dynamic_data.combat.target)) + return true + end + end + + --make mob run away if only 1/3 of health is left + if entity.object:get_hp() < (entity.data.generic.base_health/3) then + local old_target = fighting.get_target(entity) + + --restore state before attack + fighting.restore_previous_state(entity,now) + + --stop attack + entity.dynamic_data.combat.target = nil + + --make mob run away + if old_target ~= nil then + local dir = mobf_get_direction(old_target:getpos(),entity.object:getpos()) + fighting.run_away(entity,dir,old_target) + end + return true + end + + local targetname = + fighting.get_target_name(entity.dynamic_data.combat.target) + + + dbg_mobf.fighting_lvl1("MOBF: attacking player: " + ..targetname) + + + --calculate some basic data + local mob_pos = entity.object:getpos() + local targetpos = entity.dynamic_data.combat.target:getpos() + local distance = mobf_calc_distance(mob_pos,targetpos) + local dir = mobf_get_direction(targetpos,mob_pos) + local target = entity.dynamic_data.combat.target + + --look towards target + if entity.mode == "3d" then + graphics.setyaw(entity, mobf_calc_yaw(dir.x,dir.z)+math.pi) + else + graphics.setyaw(entity, mobf_calc_yaw(dir.x,dir.z)-math.pi) + end + + --initiate self destruct + fighting.self_destruct_trigger(entity,distance,now) + + local range = entity.data.combat.melee.range * MOBF_AGRESSION_FACTOR + + if entity.data.combat.distance ~= nil and + entity.data.combat.distance.range > range then + range = entity.data.combat.distance.range + end + + --find out if attacker is next to mob + if distance > range then + dbg_mobf.fighting_lvl2("MOBF: " .. entity.data.name .. " player >" + .. targetname .. "< to far away " .. distance .. " > " + .. range + .. " stopping attack") + + --switch back to default movement gen + fighting.restore_previous_state(entity,now) + + --there is no player by that name, stop attack + entity.dynamic_data.combat.target = nil + return true + end + + --check if state needs to be switched + local required_state = fighting.identify_combat_state(entity,distance) + + if required_state ~= nil and + required_state.name ~= entity.dynamic_data.state.current then + mob_state.change_state(entity,required_state) + + --reset current attack target as movement target after state switch + entity.dynamic_data.current_movement_gen.set_target(entity,target) + end + + --is mob near enough for any attack attack? + if not fighting.in_range(entity,distance) then + if entity.dynamic_data.combat.reset_path_counter > 1.5 then + entity.dynamic_data.current_movement_gen.set_target(entity,target) + entity.dynamic_data.combat.reset_path_counter = 0 + end + entity.dynamic_data.combat.reset_path_counter = + entity.dynamic_data.combat.reset_path_counter + dtime + return true + end + + --check if melee attack can be done + if fighting.melee_attack_handler(entity,now,distance) == false then + --check if distance attack can be done + if fighting.distance_attack_handler(entity, + targetpos,mob_pos,now,distance) then + + -- mob did an attack so give chance to stop attack + local rand_value = math.random() + + if entity.data.combat.angryness ~= nil and + rand_value > entity.data.combat.angryness then + dbg_mobf.fighting_lvl2("MOBF: rand=".. rand_value + .. " angryness=" .. entity.data.combat.angryness) + dbg_mobf.fighting_lvl2("MOBF: " .. entity.data.name .. " " + .. now .. " random aborting attack at " + ..targetname) + -- switch back to default movement gen + fighting.restore_previous_state(entity,now) + entity.dynamic_data.combat.target = nil + end + end + end + end + + return true +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] get_target(entity) +-- +--! @brief find and possible target next to mob +--! @memberof fighting +--! @private +-- +--! @param entity mob to look around +--! @return target +------------------------------------------------------------------------------- +function fighting.get_target(entity) + + local possible_targets = {} + + if entity.data.combat.melee.range > 0 then + + local range = entity.data.combat.melee.range*MOBF_AGRESSION_FACTOR + + if entity.data.combat.distance ~= nil and + entity.data.combat.distance.range > range then + range = entity.data.combat.distance.range + end + + local objectlist = minetest.get_objects_inside_radius( + entity.object:getpos(),range) + + local count = 0 + + for i,v in ipairs(objectlist) do + + if v:is_player() then + local playername = v:get_player_name() + + --don't attack spawner + if entity.dynamic_data.spawning.spawner == nil or + entity.dynamic_data.spawning.spawner ~= playername then + count = count + 1 + table.insert(possible_targets,v) + dbg_mobf.fighting_lvl2("MOBF: " .. playername .. + " is next to a mob of type ".. entity.data.name) + else + dbg_mobf.fighting_lvl2("MOBF: " .. entity.data.name .. + " not attacking: " .. playername .. " is spawner") + end + else + if entity.data.combat.attack_hostile_mobs then + dbg_mobf.fighting_lvl2("MOBF: " .. entity.data.name .. + " trying to attack hostile mobs too") + + local target_entity = v:get_luaentity() + if target_entity ~= nil then + local same_origin_protection = false + + if mobf_rtd.factions_available then + same_origin_protection = not attention.is_enemy(entity,v) + elseif target_entity.dynamic_data ~= nil and + target_entity.dynamic_data.spawning ~= nil then + + same_origin_protection = + target_entity.dynamic_data.spawning.spawner == + entity.dynamic_data.spawning.spawner + end + + if target_entity ~= entity and + target_entity.data ~= nil and + target_entity.data.combat ~= nil and + target_entity.data.combat.starts_attack and + not same_origin_protection then + + table.insert(possible_targets,v) + dbg_mobf.fighting_lvl3(target_entity.data.name + .. " is next to a mob of type " + .. entity.data.name) + end + end + end + end + end + dbg_mobf.fighting_lvl2("MOBF: found ".. count .. " objects within" .. + " attack range of " .. entity.data.name) + end + + + local targets_within_sight = {} + + for i,v in ipairs(possible_targets) do + + local entity_pos = entity.object:getpos() + local target_pos = v:getpos() + + --is there a line of sight between mob and possible target + --line of sight is calculated 1block above ground + if mobf_line_of_sight({x=entity_pos.x,y=entity_pos.y+1,z=entity_pos.z}, + {x=target_pos.x,y=target_pos.y+1,z=target_pos.z}) then + + table.insert(targets_within_sight,v) + end + + end + + local nearest_target = nil + local min_distance = -1 + + for i,v in ipairs(targets_within_sight) do + + local distance = mobf_calc_distance(entity.object:getpos(),v:getpos()) + + if min_distance < 0 or + distance < min_distance then + + nearest_target = v + min_distance = distance + end + + end + + return nearest_target + +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] self_destruct_trigger(entity,distance) +-- +--! @brief handle self destruct features +--! @memberof fighting +--! @private +-- +--! @param entity mob to do action +--! @param distance current distance to target +--! @param now current time +--! @return true/false if handled or not +------------------------------------------------------------------------------- +function fighting.self_destruct_trigger(entity,distance,now) + if entity.data.combat ~= nil and + entity.data.combat.self_destruct ~= nil then + + dbg_mobf.fighting_lvl1("MOBF: checking for self destruct trigger " .. + distance .. " " .. + entity.dynamic_data.combat.ts_self_destruct_triggered .. + " " .. now) + + --trigger self destruct + if distance <= entity.data.combat.self_destruct.range and + entity.dynamic_data.combat.ts_self_destruct_triggered == -1 then + dbg_mobf.fighting_lvl2("MOBF: self destruct triggered") + entity.dynamic_data.combat.ts_self_destruct_triggered = now + end + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] do_area_damage(pos,immune,damage_groups,range) +-- +--! @brief damage all objects within a certain range +--! @memberof fighting +--! @private +-- +--! @param pos cennter of damage area +--! @param immune object immune to damage +--! @param damage_groups list of damage groups to do damage to +--! @param range range around pos +------------------------------------------------------------------------------- +function fighting.do_area_damage(pos,immune,damage_groups,range) + --damage objects within inner blast radius + mobf_assert_backtrace(type(range) ~= "table") + + local objs = minetest.get_objects_inside_radius(pos, range) + for k, obj in pairs(objs) do + + --don't do damage to issuer + if obj ~= immune and obj ~= nil then + + --TODO as long as minetest still crashes without puncher use this workaround + + local worst_damage = 0 + if type(damage_groups) == "table" then + for k,v in pairs(damage_groups) do + if v > worst_damage then + worst_damage = v + end + end + elseif type(damage_groups) == "number" then + worst_damage = damage_groups + else + mobf_assert_backtrace("invalid damage_groups" == "selected") + end + + + local current_hp = obj:get_hp() + obj:set_hp(current_hp - worst_damage) + + --punch + --obj:punch(nil, 1.0, { + -- full_punch_interval=1.0, + -- damage_groups = damage_groups, + --}, nil) + end + end +end + + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] do_node_damage(pos,immune_list,range,chance) +-- +--! @brief damage all objects within a certain range +--! @memberof fighting +--! @private +-- +--! @brief damage all nodes within a certain range +-- +--! @param pos center of area +--! @param immune_list list of nodes immune to damage +--! @param range range to do damage +--! @param chance chance damage is done to a node +------------------------------------------------------------------------------- +function fighting.do_node_damage(pos,immune_list,range,chance) + --do node damage + for i=pos.x-range, pos.x+range, 1 do + for j=pos.y-range, pos.y+range, 1 do + for k=pos.z-range,pos.z+range,1 do + --TODO create a little bit more sophisticated blast resistance + if math.random() < chance then + local toremove = minetest.get_node({x=i,y=j,z=k}) + + if toremove ~= nil then + local immune = false + + if immune_list ~= nil then + for i,v in ipairs(immune_list) do + if (toremove.name == v) then + immune = true + end + end + end + + + if immune ~= true then + minetest.remove_node({x=i,y=j,z=k}) + end + end + end + end + end + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] self_destruct_handler(entity) +-- +--! @brief handle self destruct features +--! @memberof fighting +--! @private +-- +--! @param entity mob to do action +--! @param now current time +--! @return true/false if handled or not +------------------------------------------------------------------------------- +function fighting.self_destruct_handler(entity,now) + --self destructing mob? + if entity.data.combat ~= nil and + entity.data.combat.self_destruct ~= nil then + + local pos = entity.object:getpos() + + dbg_mobf.fighting_lvl1("MOBF: checking for self destruct imminent") + --do self destruct + if entity.dynamic_data.combat.ts_self_destruct_triggered > 0 and + entity.dynamic_data.combat.ts_self_destruct_triggered + + entity.data.combat.self_destruct.delay + <= now then + + dbg_mobf.fighting_lvl2("MOBF: executing self destruct") + + if entity.data.sound ~= nil then + sound.play(pos,entity.data.sound.self_destruct); + end + + fighting.do_area_damage(pos,nil, + entity.data.combat.self_destruct.damage, + entity.data.combat.self_destruct.range) + + --TODO determine block removal by damage and remove blocks + fighting.do_node_damage(pos,{}, + entity.data.combat.self_destruct.node_damage_range, + 1 - 1/entity.data.combat.self_destruct.node_damage_range) + + if mobf_rtd.fire_enabled then + --Add fire + for i=pos.x-entity.data.combat.self_destruct.range/2, + pos.x+entity.data.combat.self_destruct.range/2, 1 do + for j=pos.y-entity.data.combat.self_destruct.range/2, + pos.y+entity.data.combat.self_destruct.range/2, 1 do + for k=pos.z-entity.data.combat.self_destruct.range/2, + pos.z+entity.data.combat.self_destruct.range/2, 1 do + + local current = minetest.get_node({x=i,y=j,z=k}) + + if (current.name == "air") then + minetest.set_node({x=i,y=j,z=k}, + {name="fire:basic_flame"}) + end + + end + end + end + else + minetest.log(LOGLEVEL_NOTICE, + "MOBF: self destruct without fire isn't really impressive!") + end + mobf_lifebar.del(entity.lifebar) + spawning.remove(entity, "self destruct") + return true + end + end + return false +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] melee_attack_handler(entity,now) +-- +--! @brief handle melee attack +--! @memberof fighting +--! @private +-- +--! @param entity mob to do action +--! @param now current time +--! @param distance distance to player +--! @return true/false if handled or not +------------------------------------------------------------------------------- +function fighting.melee_attack_handler(entity,now,distance) + + if entity.data.combat.melee == nil then + dbg_mobf.fighting_lvl2("MOBF: no meele attack specified") + return false + end + + local time_of_next_attack_chance = entity.dynamic_data.combat.ts_last_attack + + entity.data.combat.melee.speed + --check if mob is ready to attack + if now < time_of_next_attack_chance then + dbg_mobf.fighting_lvl1("MOBF: to early for meele attack " .. + now .. " >= " .. time_of_next_attack_chance) + return false + end + + mobf_assert_backtrace( entity.dynamic_data.combat.target ~= nil) + local ownpos = entity.object:getpos() + local target_obj = entity.dynamic_data.combat.target.object + + if target_obj == nil then + target_obj = entity.dynamic_data.combat.target + end + + if distance <= entity.data.combat.melee.range + and mobf_line_of_sight(ownpos,target_obj:getpos()) then + + --save time of attack + entity.dynamic_data.combat.ts_last_attack = now + + if entity.data.sound ~= nil then + sound.play(entity.object:getpos(),entity.data.sound.melee); + end + + --calculate damage to be done + local damage_done = + math.floor(math.random(0,entity.data.combat.melee.maxdamage)) + 1 + + --TODO call punch instead of manually setting health for player too + if target_obj:is_player() then + local target_health = target_obj:get_hp() + + --do damage + target_obj:set_hp(target_health -damage_done) + else + local damage_groups = nil + + if entity.data.combat.melee.weapon_groupcaps ~= nil then + damage_groups = entity.data.combat.melee.weapon_damage_groups + end + + if damage_groups == nil and + entity.data.combat.melee.weapongroups ~= nil then + damage_groups = {} + + for i=1, #entity.data.combat.melee.weapongroups, 1 do + damage_groups[entity.data.combat.melee.weapon_damage_groups[i]] = damage_done + end + end + + if damage_groups == nil then + damage_groups= { fleshy=damage_done } + + end + + target_obj:punch(entity.object, 1.0, { + full_punch_interval=1.0, + damage_groups = damage_groups, + }, nil) + end + + dbg_mobf.fighting_lvl2("MOBF: ".. entity.data.name .. + " doing melee attack damage=" .. damage_done) + + return true + end + dbg_mobf.fighting_lvl1("MOBF: not within meele range " .. + distance .. " > " .. entity.data.combat.melee.range) + return false +end + + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] distance_attack_handler(entity,now) +-- +--! @brief handle distance attack +--! @memberof fighting +--! @private +-- +--! @param entity mob to do action +--! @param targetpos position of target +--! @param mob_pos position of mob +--! @param now current time +--! @param distance distance between target and player +--! @return true/false if handled or not +------------------------------------------------------------------------------- +function fighting.distance_attack_handler(entity,targetpos,mob_pos,now,distance) + if entity.data.combat.distance == nil then + dbg_mobf.fighting_lvl2("MOBF: no distance attack specified") + return false + end + + local time_of_next_attack_chance = entity.dynamic_data.combat.ts_last_attack + + entity.data.combat.distance.speed + + --check if mob is ready to attack + if now < time_of_next_attack_chance then + dbg_mobf.fighting_lvl1("MOBF: to early for distance attack " .. + now .. " >= " .. time_of_next_attack_chance) + return false + end + + if distance <= entity.data.combat.distance.range and + (entity.data.combat.distance.min_range == nil or + distance > entity.data.combat.distance.min_range) + then + + dbg_mobf.fighting_lvl2("MOBF: ".. entity.data.name .. + " doing distance attack") + + --save time of attack + entity.dynamic_data.combat.ts_last_attack = now + + local dir = mobf_get_direction({ x=mob_pos.x, + y=mob_pos.y+1, + z=mob_pos.z + }, + targetpos) + + if entity.data.sound ~= nil then + sound.play(mob_pos,entity.data.sound.distance); + end + + local newobject=minetest.add_entity({ x=mob_pos.x+dir.x, + y=mob_pos.y+dir.y+1, + z=mob_pos.z+dir.z + }, + entity.data.combat.distance.attack + ) + + local thrown_entity = mobf_find_entity(newobject) + + if thrown_entity ~= nil then + local vel_thrown = { + x=dir.x*thrown_entity.velocity + math.random(0,0.05), + y=dir.y*thrown_entity.velocity, + z=dir.z*thrown_entity.velocity + math.random(0,0.05), + } + + if entity.data.combat.distance.balistic == true then + --this isn't an exact calculation but just something to make + --it not too perfect + local height_diff = targetpos.y - mob_pos.y + local current_scalar_speed = + mobf_calc_scalar_speed(vel_thrown.x,vel_thrown.z) + local time_to_target = (distance/current_scalar_speed) + + local y_vel = mobf_balistic_start_speed( + height_diff -1, + time_to_target, + -thrown_entity.gravity) + + vel_thrown.y = y_vel + math.random(0,0.25) + end + + dbg_mobf.fighting_lvl2("MOBF: throwing with velocity: " .. + printpos(vel_thrown)) + + newobject:setvelocity(vel_thrown) + + newobject:setacceleration({x=0, y=-thrown_entity.gravity, z=0}) + thrown_entity.owner = entity.object + + if entity.data.sound ~= nil then + sound.play(mob_pos,entity.data.sound.shoot_distance); + end + + dbg_mobf.fighting_lvl2("MOBF: distance attack issued") + else + minetest.log(LOGLEVEL_ERROR, + "MOBF: unable to find entity for distance attack") + end + return true + end + dbg_mobf.fighting_lvl1("MOBF: not within distance range " .. + distance .. " > " .. + entity.data.combat.distance.range) + return false +end + + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] sun_damage_handler(entity,now) +-- +--! @brief handle damage done by sun +--! @memberof fighting +--! @private +-- +--! @param entity mob to do action +--! @param now current time +-- +--! @return true/false if killed or not +------------------------------------------------------------------------------- +function fighting.sun_damage_handler(entity,now) + if entity.data.combat ~= nil and + entity.data.combat.sun_sensitive then + mobf_assert_backtrace(entity.dynamic_data.combat ~= nil) + + local pos = entity.object:getpos() + local current_state = entity.dynamic_data.state.current + local current_light = minetest.get_node_light(pos) + + if current_light == nil then + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: Bug!!! didn't get a light value for " + .. printpos(pos)) + return false + end + --check if mob is in sunlight + if ( current_light > LIGHT_MAX) then + dbg_mobf.fighting_lvl1("MOBF: " .. entity.data.name .. + " health at start:" .. entity.object:get_hp()) + + if current_state.animation ~= nil and + entity.data.animation ~= nil and + entity.data.animation[current_state.animation .. "__burning"] ~= nil then + graphics.set_animation(entity,current_state.animation .. "burning") + else + graphics.set_animation(entity,"burning") + end + + + if entity.dynamic_data.combat.ts_last_sun_damage +1 < now then + local damage = (1 + math.floor(entity.data.generic.base_health/15)) + dbg_mobf.fighting_lvl1("Mob ".. entity.data.name .. " takes " + ..damage .." damage because of sun") + + entity.object:set_hp(entity.object:get_hp() - damage) + mobf_lifebar.set(entity.lifebar,entity.object:get_hp()/entity.hp_max) + + if entity.data.sound ~= nil then + sound.play(pos,entity.data.sound.sun_damage); + end + + if entity.object:get_hp() <= 0 then + --if entity.dynamic_data.generic.health <= 0 then + dbg_mobf.fighting_lvl2("Mob ".. entity.data.name .. " died of sun") + mobf_lifebar.del(entity.lifebar) + spawning.remove(entity,"died by sun") + return true + end + entity.dynamic_data.combat.ts_last_sun_damage = now + end + else + --use last sun damage to avoid setting animation over and over + --even if nothing changed + if entity.dynamic_data.combat.ts_last_sun_damage ~= -1 and + current_state.animation ~= nil then + graphics.set_animation(entity,current_state.animation) + entity.dynamic_data.combat.ts_last_sun_damage = -1 + end + end + end + + return false +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] get_target_name(target) +-- +--! @brief get name of target +--! @memberof fighting +--! @private +-- +--! @param target to get name for +-- +--! @return name of target +------------------------------------------------------------------------------- +function fighting.get_target_name(target) + if target == nil then + return "invalid" + end + + if target:is_player() then + return target:get_player_name() + else + local target_entity = target:get_luaentity() + if target_entity ~= nil and + target_entity.data ~= nil and + target_entity.data.name ~= nil then + return "MOB: " .. target_entity.data.name + end + end + + return "unknown" +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] set_target(entity,target) +-- +--! @brief decide if only switching target or state +--! @memberof fighting +--! @public +-- +--! @param entity entity to update +--! @param target to set +------------------------------------------------------------------------------- +function fighting.set_target(entity,target) + + mobf_assert_backtrace(entity.dynamic_data ~= nil) + + if not fighting.is_valid_target(target) then + return + end + + if entity.dynamic_data.combat.target ~= nil then + dbg_mobf.fighting_lvl2("MOBF: switching attack target") + + local target_distance = nil + if entity.data.combat.melee ~= nil and + entity.data.combat.melee.range ~= nil then + target_distance = 0.75 * entity.data.combat.melee.range + end + + --set movement target + entity.dynamic_data.current_movement_gen.set_target(entity, + target, true, target_distance) + + --set attack target + entity.dynamic_data.combat.target = target + else + if entity.dynamic_data.combat.target ~= target then + + local attackername = fighting.get_target_name(target) + dbg_mobf.fighting_lvl2("MOBF: initial attack at: ".. attackername) + + if entity.dynamic_data.combat.target == nil then + fighting.switch_to_combat_state(entity,mobf_get_current_time(),target) + end + + end + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] is_valid_target(target) +-- +--! @brief check if a target is a valid target +--! @memberof fighting +--! @public +-- +--! @param target to set +------------------------------------------------------------------------------- +function fighting.is_valid_target(target) + + --remove target case + if target == nil then + return true + end + + --valid if it's a player + if target:is_player() then + return true + end + + --valid if it's a lua entity + if target:get_luaentity() ~= nil then + return true + end + + --invalid any other case + return false +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] init_dynamic_data() +-- +--! @brief initialize all dynamic data on activate +--! @memberof fighting +-- +--! @param entity mob to do action +--! @param now current time +------------------------------------------------------------------------------- +function fighting.init_dynamic_data(entity,now) + local data = { + ts_last_sun_damage = now, + ts_last_attack = now, + ts_last_aggression_chance = now, + ts_self_destruct_triggered = -1, + + target = nil, + reset_path_counter = 0, + } + + entity.dynamic_data.combat = data +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] heal() +-- +--! @brief heal a mob +--! @memberof fighting +-- +--! @param entity mob to do action +--! @param player the one doing the rightclick +------------------------------------------------------------------------------- +function fighting.heal(entity,player) + + local health = entity.object:get_hp() + + if entity.data.generic.base_health == entity.object:get_hp() then + return + end + + if not player:is_player() then + return + end + + local tool = player:get_wielded_item() + + if tool == nil then + return + end + + tool = tool:get_name() + + if not fighting.healdb or not fighting.healdb[tool] then + dbg_mobf.fighting_lvl1("MOBF: unknown heal item: " .. tool) + return + end + + local new_health = 0 + + print("healdb value: " .. dump(fighting.healdb[tool].value)) + + if fighting.healdb[tool].value >= 0 then + new_health = MIN(entity.object:get_hp() + fighting.healdb[tool].value, + entity.data.generic.base_health) + else + new_health = MAX(entity.object:get_hp() + fighting.healdb[tool].value, 0) + end + + entity.object:set_hp(new_health) + + if new_health <= 0.5 then + fighting.dodamage(entity, player, "poisoned") + else + mobf_lifebar.set(entity.lifebar,new_health/entity.hp_max) + end + + player:get_inventory():remove_item("main",tool.." 1") + + if fighting.healdb[tool].replacement ~= nil then + player:get_inventory():add_item("main", + fighting.healdb[tool].replacement.." 1") + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] heal_caption() +-- +--! @brief get caption for heal button +--! @memberof fighting +-- +--! @param entity mob to do action +------------------------------------------------------------------------------- +function fighting.heal_caption(entity) + if entity.data.generic.base_health ~= entity.object:get_hp() then + return "heal" + else + return "nothing to heal" + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#fighting] update_healdb() +-- +--! @brief update mobfs heal db +--! @memberof fighting +-- +--! @param hp_change +--! @param replace_with_item +--! @param itemstack -- unused +--! @param player player issuing the update +--! @param pointed_thing -- unused +------------------------------------------------------------------------------- +function fighting.update_healdb(hp_change, replace_with_item, itemstack, player, + pointed_thing) + + if not player:is_player() then + return + end + + local tool = player:get_wielded_item() + + if tool == nil then + return + end + + tool = tool:get_name() + + local replacement = nil + if replace_with_item ~= nil then + if type(replace_with_item) ~= "string" then + replacement = replace_with_item:get_name() + end + end + + if fighting.healdb == nil then + fighting.healdb = {} + end + + if fighting.healdb[tool] ~= nil and + fighting.healdb[tool].value == hp_change and + fighting.healdb[tool].replacement == replacement or + hp_change == nil then + return false + end + + + fighting.healdb[tool] = { + value = hp_change, + replacement = replacement + } + + minetest.world_setting_set("fighting.healdb",fighting.healdb) + return false +end + +minetest.register_on_item_eat(fighting.update_healdb) diff --git a/mods/mob_engines/mobf/graphics.lua b/mods/mob_engines/mobf/graphics.lua new file mode 100644 index 00000000..bae234f6 --- /dev/null +++ b/mods/mob_engines/mobf/graphics.lua @@ -0,0 +1,370 @@ +------------------------------------------------------------------------------- +-- 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 graphics.lua +--! @brief graphics related parts of mob +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +mobf_assert_backtrace(not core.global_exists("graphics")) +--! @class graphics +--! @brief graphic features +graphics = {} + +------------------------------------------------------------------------------- +-- @function [parent=#graphics] init_dynamic_data(entity,velocity) +-- +--! @brief initialize values required by graphics +--! @memberof graphics +-- +--! @param entity mob initialize +--! @param now current time +------------------------------------------------------------------------------- +function graphics.init_dynamic_data(entity,now) + local data = { + last_scalar_speed = nil, + } + + entity.dynamic_data.graphics = data +end + +------------------------------------------------------------------------------- +-- @function [parent=#graphics] update(entity,now,dtime) +-- +--! @brief callback for updating graphics of mob +--! @memberof graphics +-- +--! @param entity mob to calculate direction +--! @param now current time +--! @param dtime current dtime +------------------------------------------------------------------------------- +function graphics.update(entity,now,dtime) + + + --update animation speed + --replaced by core function (if ever merged) + --graphics.update_animation(entity,now,dtime) + + --update attention + if entity.dynamic_data ~= nil and + entity.dynamic_data.attention ~= nil and + entity.data.attention ~= nil and + entity.dynamic_data.attention.most_relevant_target ~= nil and + entity.data.attention.watch_threshold ~= nil and + (entity.dynamic_data.attention.current_value > + entity.data.attention.watch_threshold) then + dbg_mobf.graphics_lvl3("MOBF: attention mode orientation update") + local direction = mobf_get_direction(entity.object:getpos(), + entity.dynamic_data.attention.most_relevant_target:getpos()) + if entity.mode == "3d" then + graphics.setyaw(entity, + mobf_calc_yaw(direction.x,direction.z)) + else + graphics.setyaw(entity, + mobf_calc_yaw(direction.x,direction.z)+math.pi/2) + end + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#graphics] update_animation(entity,now,dtime) +-- +--! @brief callback for updating graphics of mob +--! @memberof graphics +-- +--! @param entity mob to calculate direction +--! @param now current time +--! @param dtime current dtime +------------------------------------------------------------------------------- +function graphics.update_animation(entity,now,dtime) + if entity.dynamic_data.animation ~= nil then + + local animdata = entity.data.animation[entity.dynamic_data.animation] + if animdata ~= nil and + animdata.basevelocity ~= nil then + + local current_velocity = entity.object:getvelocity() + local scalar_velocity = mobf_calc_scalar_speed(current_velocity.x,current_velocity.z) + + if entity.dynamic_data.graphics.last_scalar_speed ~= nil then + local speeddiff = + DELTA(scalar_velocity, + entity.dynamic_data.graphics.last_scalar_speed) + + if speeddiff > 0.05 then + local current_fps = scalar_velocity/animdata.basevelocity * 15 + + entity.object:set_animation_speed(current_fps) + + entity.dynamic_data.graphics.last_scalar_speed = scalar_velocity + entity.dynamic_data.graphics.last_fps = current_fps + end + else + entity.dynamic_data.graphics.last_scalar_speed = scalar_velocity + end + end + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#graphics] set_animation(entity,name) +-- +--! @brief set the drawmode for an mob entity +--! @memberof graphics +-- +--! @param entity mob to set drawmode for +--! @param name name of animation +------------------------------------------------------------------------------- +function graphics.set_animation(entity,name) + + if name == nil then + dbg_mobf.graphics_lvl2("MOBF: calling updating animation without name for " .. entity.data.name) + return + end + + if entity.mode == "2d" then + + if id == "stand" then + entity.object:setsprite({x=0,y=0}, 1, 0, true) + end + + if name == "burning" then + entity.object:setsprite({x=0,y=1}, 1, 0, true) + return + end + + --fallback + entity.object:setsprite({x=0,y=0}, 1, 0, true) + + return + end + + if entity.mode == "3d" then + --TODO change frame rate due to movement speed + dbg_mobf.graphics_lvl2("MOBF: " .. entity.data.name .. " updating animation: " .. name) + if entity.data.animation ~= nil and + name ~= nil and + entity.data.animation[name] ~= nil and + entity.dynamic_data.animation ~= name then + + dbg_mobf.graphics_lvl2("MOBF:\tSetting animation to " .. name + .. " start: " .. entity.data.animation[name].start_frame + .. " end: " .. entity.data.animation[name].end_frame) + entity.object:set_animation({ + x=entity.data.animation[name].start_frame, + y=entity.data.animation[name].end_frame + }, + entity.data.animation[name].anim_speed, + nil, + entity.data.animation[name].basevelocity) + entity.dynamic_data.animation = name + end + + return + end + + mobf_bug_warning(LOGLEVEL_WARNING,"MOBF BUG!!: invalid graphics mode specified " + .. dump(entity.mode)) + +end + +------------------------------------------------------------------------------ +-- @function [parent=#graphics] graphics_by_statename(graphics2d,graphics3d) +-- +--! @brief get graphics information +--! @memberof graphics +-- +--! @param mob static data +--! @param statename name of state +-- +--! @return grahphic information +------------------------------------------------------------------------------- +function graphics.graphics_by_statename(mob,statename) + + local dummyentity = { data = mob } + + local default_state = mob_state.get_state_by_name(dummyentity,"default") + local selected_state = nil + + if statename == "default" then + selected_state = default_state + else + selected_state = mob_state.get_state_by_name(dummyentity,statename) + end + + if selected_state == nil then + selected_state = default_state + end + + if selected_state.graphics_3d == nil then + selected_state.graphics_3d = default_state.graphics_3d + end + + if selected_state.graphics == nil then + selected_state.graphics = default_state.graphics + end + + + local setgraphics = {} + + if (selected_state.graphics_3d == nil) or + minetest.world_setting_get("mobf_disable_3d_mode") then + + if (selected_state.graphics == nil) then + --no idea if there is any legitimate reason for this + mobf_print("state: " .. dump(selected_state)) + mobf_print("there ain't even 2d graphics available") + return nil + end + + local basename = modname .. name + + if statename ~= nil and + statename ~= "default" then + basename = basename .. "__" .. statename + end + + setgraphics.collisionbox = {-0.5, + -0.5 * selected_state.graphics.visible_height, + -0.5, + 0.5, + 0.5 * selected_state.graphics.visible_height, + 0.5} + if selected_state.graphics.visual ~= nil then + selected_state.graphics.visual = selected_state.graphics.visual + else + selected_state.graphics.visual = "sprite" + end + setgraphics.textures = { basename..".png^[makealpha:128,0,0^[makealpha:128,128,0" } + setgraphics.visual_size = selected_state.graphics.sprite_scale + setgraphics.spritediv = selected_state.graphics.sprite_div + setgraphics.mode = "2d" + else + if selected_state.graphics_3d.visual == "mesh" then + setgraphics.mesh = selected_state.graphics_3d.mesh + end + + setgraphics.collisionbox = selected_state.graphics_3d.collisionbox + setgraphics.visual = selected_state.graphics_3d.visual + setgraphics.visual_size = selected_state.graphics_3d.visual_size + setgraphics.textures = selected_state.graphics_3d.textures + setgraphics.texturelist = selected_state.graphics_3d.texturelist + setgraphics.mode = "3d" + setgraphics.model_orientation_fix = selected_state.graphics_3d.model_orientation_fix + end + + return setgraphics +end + +------------------------------------------------------------------------------ +-- @function [parent=#graphics] prepare_graphic_info(graphics2d,graphics3d) +-- +--! @brief get graphics information +--! @memberof graphics +-- +--! @param graphics2d +--! @param graphics3d +--! @param modname +--! @param name +--! @param statename +--! @return grahpic information +------------------------------------------------------------------------------- +function graphics.prepare_info(graphics2d,graphics3d,modname,name,statename) + + local setgraphics = {} + + if (graphics3d == nil) or + minetest.world_setting_get("mobf_disable_3d_mode") then + if (graphics2d == nil) then + --this maybe correct if there's a state model requested! + return nil + end + + local basename = modname .. name + + if statename ~= nil and + statename ~= "default" then + basename = basename .. "__" .. statename + end + + setgraphics.collisionbox = {-0.5, + -0.5 * graphics2d.visible_height, + -0.5, + 0.5, + 0.5 * graphics2d.visible_height, + 0.5} + if graphics2d.visual ~= nil then + setgraphics.visual = graphics2d.visual + else + setgraphics.visual = "sprite" + end + setgraphics.textures = { basename..".png^[makealpha:128,0,0^[makealpha:128,128,0" } + setgraphics.visual_size = graphics2d.sprite_scale + setgraphics.spritediv = graphics2d.sprite_div + setgraphics.mode = "2d" + else + if graphics3d.visual == "mesh" then + setgraphics.mesh = graphics3d.mesh + end + + setgraphics.collisionbox = graphics3d.collisionbox --todo is this required for mesh? + setgraphics.visual = graphics3d.visual + setgraphics.visual_size = graphics3d.visual_size + setgraphics.textures = graphics3d.textures + setgraphics.mode = "3d" + end + + return setgraphics +end + +------------------------------------------------------------------------------ +-- @function [parent=#graphics] setyaw(entity,value) +-- +--! @brief update yaw for a specific entity (overlay to workaround model bugs) +--! @memberof graphics +-- +--! @param entity entity to set yaw for +--! @param value yaw value to set +------------------------------------------------------------------------------- +function graphics.setyaw(entity, value) + + local current_graphics = graphics.graphics_by_statename(entity.data, + entity.dynamic_data.state.current.name) + + if current_graphics.model_orientation_fix ~= nil then + value = value + current_graphics.model_orientation_fix + end + entity.object:setyaw(value) +end + +------------------------------------------------------------------------------ +-- @function [parent=#graphics] getyaw(entity,value) +-- +--! @brief read yaw for this object, fix model offset +--! @memberof graphics +-- +--! @param entity entity to set yaw for +--! @param value yaw value to set +------------------------------------------------------------------------------- +function graphics.getyaw(entity) + + local retval = 0 + local current_graphics = graphics.graphics_by_statename(entity.data, + entity.dynamic_data.state.current.name) + + if current_graphics.model_orientation_fix ~= nil then + retval = retval - current_graphics.model_orientation_fix + end + + retval = retval + entity.object:getyaw() + + return retval +end \ No newline at end of file diff --git a/mods/mob_engines/mobf/harvesting.lua b/mods/mob_engines/mobf/harvesting.lua new file mode 100644 index 00000000..a786a98a --- /dev/null +++ b/mods/mob_engines/mobf/harvesting.lua @@ -0,0 +1,258 @@ +------------------------------------------------------------------------------- +-- 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 harvesting.lua +--! @brief component for all harvesting related mob features +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +--! @defgroup harvesting Harvesting subcomponent +--! @brief Component handling harvesting +--! @ingroup framework_int +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +mobf_assert_backtrace(not core.global_exists("harvesting")) +--! @class harvesting +--! @brief harvesting features +harvesting = {} + +--!@} + +------------------------------------------------------------------------------- +-- @function [parent=#harvesting] init_dynamic_data(entity,now) +-- +--! @brief initialize dynamic data required by harvesting +--! @memberof harvesting +-- +--! @param entity mob to initialize harvest dynamic data +--! @param now current time +------------------------------------------------------------------------------- +function harvesting.init_dynamic_data(entity,now) + dbg_mobf.harvesting_lvl1("MOBF: " .. entity.data.name + .. " initializing harvesting dynamic data") + local data = { + ts_last = now, + } + entity.dynamic_data.harvesting = data +end + +------------------------------------------------------------------------------- +-- @function [parent=#harvesting] callback(entity,player,now) +-- +--! @brief callback handler for harvest by player +--! @memberof harvesting +-- +--! @param entity mob being harvested +--! @param player player harvesting +--! @param now the current time +--! @return true/false if handled by harvesting or not +------------------------------------------------------------------------------- +function harvesting.callback(entity,player,now) + + dbg_mobf.harvesting_lvl1("MOBF: harvest function called") + + local now = mobf_get_current_time() + + --handle catching of mob + if entity.data.catching ~= nil and + entity.data.catching.tool ~= "" then + + if (entity.dynamic_data.spawning.player_spawned and + entity.dynamic_data.spawning.spawner == nil) then + dbg_mobf.harvesting_lvl1("MOBF: mob flagged as player spanwned but no spawner set!") + entity.dynamic_data.spawning.player_spawned = false + end + + --grief protection + if minetest.world_setting_get("mobf_grief_protection") and + entity.dynamic_data.spawning.player_spawned and + entity.dynamic_data.spawning.spawner ~= player:get_player_name() then + dbg_mobf.harvesting_lvl1("MOBF: anti gief triggered catching aborted") + return true + end + + -- what's wielded by player + local tool = player:get_wielded_item() + + if tool:get_name() == entity.data.catching.tool then + dbg_mobf.harvesting_lvl1("MOBF: player wearing ".. entity.data.catching.tool) + + if type(entity.data.catching.can_be_cought) == "function" then + if (not entity.data.catching.can_be_cought(entity)) then + dbg_mobf.harvesting_lvl1("MOBF: entity denied catching") + return true + end + end + + --check if player has enough room + local inventory_add_result = nil + + if entity.data.generic.addoncatch ~= nil then + inventory_add_result = player:get_inventory():add_item("main", + entity.data.generic.addoncatch.." 1") + dbg_mobf.harvesting_lvl2( + "MOBF: adding specified oncatch item: " .. + entity.data.generic.addoncatch) + else + inventory_add_result = player:get_inventory():add_item("main", + entity.data.modname ..":"..entity.data.name.." 1") + dbg_mobf.harvesting_lvl2( + "MOBF: adding automatic oncatch item: " .. + entity.data.modname ..":"..entity.data.name) + end + + if not inventory_add_result:is_empty() then + minetest.chat_send_player(player:get_player_name(), + "You don't have any room left in inventory!") + return true + end + + --play catch sound + if entity.data.sound ~= nil then + sound.play(entity.object:getpos(),entity.data.sound.catch); + end + + if entity.data.catching.consumed == true then + if player:get_inventory():contains_item("main",entity.data.catching.tool.." 1") then + dbg_mobf.harvesting_lvl2("MOBF: removing: " + .. entity.data.catching.tool.." 1") + player:get_inventory():remove_item("main", + entity.data.catching.tool.." 1") + else + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: BUG!!! player is" + .. " wearing a item he doesn't have in inventory!!!") + end + end + spawning.remove(entity, "cought") + return true + end + end + + + --handle harvestable mobs, check if player is wearing correct tool + if entity.data.harvest ~= nil then + + dbg_mobf.harvesting_lvl1("MOBF: trying to harvest harvestable mob") + if (entity.data.harvest.tool ~= "") then + local tool = player:get_wielded_item() + if tool ~= nil then + dbg_mobf.harvesting_lvl1("MOBF: Player is wearing >" + .. tool:get_name() .. "< required is >".. entity.data.harvest.tool + .. "< wear: " .. tool:get_wear()) + + if (tool:get_name() ~= entity.data.harvest.tool) then + --player is wearing wrong tool do an attack + return false + else + --tool is completely consumed + if entity.data.harvest.tool_consumed == true then + if player:get_inventory():contains_item("main",entity.data.harvest.tool.." 1") == false then + dbg_mobf.harvesting_lvl1("MOBF: Player doesn't have" + .. " at least 1 of ".. entity.data.harvest.tool) + --handled but not ok so don't attack + return true + end + else + --damage tool + local tool_wear = tool:get_wear() + + dbg_mobf.harvesting_lvl1("MOBF: tool " .. tool:get_name() + .. " wear: " .. tool_wear) + -- damage used tool + if tool_wear ~= nil and + entity.data.harvest.max_tool_usage ~= nil then + + local todamage = (65535/entity.data.harvest.max_tool_usage) + dbg_mobf.harvesting_lvl1("MOBF: tool damage calculated: " + .. todamage); + if tool:add_wear(todamage) ~= true then + dbg_mobf.harvesting_lvl3("MOBF: Tried to damage non tool item " + .. tool:get_name() .. "!"); + end + player:set_wielded_item(tool) + end + end + end + else + --player isn't wearing a tool so this has to be an attack + return false + end + else + --no havest tool defined so this has to be an attack + return false + end + + + --transformation and harvest delay is exclusive + + --harvest delay mode + if entity.data.harvest.min_delay < 0 or + entity.dynamic_data.harvesting.ts_last + entity.data.harvest.min_delay < now then + + --TODO check if player has enough room + player:get_inventory():add_item("main", entity.data.harvest.result.." 1") + + --check if tool is consumed by action + if entity.data.harvest.tool_consumed then + dbg_mobf.harvesting_lvl2("MOBF: removing " + ..entity.data.harvest.tool.." 1") + player:get_inventory():remove_item("main",entity.data.harvest.tool.." 1") + end + else + dbg_mobf.harvesting_lvl1("MOBF: " .. entity.data.name + .. " not ready to be harvested") + end + + -- check if mob is transformed by harvest + if entity.data.harvest.transforms_to ~= nil and + entity.data.harvest.transforms_to ~= "" then + local transformed = spawning.replace_entity(entity, + entity.data.harvest.transforms_to) + else + entity.dynamic_data.harvesting.ts_last = mobf_get_current_time() + end + + + --play harvest sound + if entity.data.sound ~= nil then + sound.play(entity.object:getpos(),entity.data.sound.harvest); + end + + --harvest done + return true + end + + return false +end + + +------------------------------------------------------------------------------- +-- @function transform(entity) +-- +--! @brief self transform callback for mob +--! @ingroup harvesting +-- +--! @param entity mob calling +--! @param now current time +------------------------------------------------------------------------------- +function transform(entity,now) + + --check if it's a transformable mob + if entity.data.auto_transform ~= nil then + + if now - entity.dynamic_data.spawning.original_spawntime + > entity.data.auto_transform.delay then + spawning.replace_entity(entity,entity.data.auto_transform.result) + return false + end + + end + +end diff --git a/mods/mob_engines/mobf/init.lua b/mods/mob_engines/mobf/init.lua new file mode 100644 index 00000000..63803e8d --- /dev/null +++ b/mods/mob_engines/mobf/init.lua @@ -0,0 +1,408 @@ +------------------------------------------------------------------------------- +-- 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 allowed to pretend you have written it. +-- +--! @file init.lua +--! @brief main module file responsible for including all parts of mob framework mod +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +--! @defgroup framework_int Internal framework subcomponent API +--! @brief this functions are used to provide additional features to mob framework +--! e.g. add additional spawn algorithms, movement generators, environments ... +-- +-- +--! @defgroup framework_mob Mob Framework API +--! @brief this functions are used to add a mob to mob framework +-- +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +core.log("action","MOD: mobf loading ...") + +--! @brief runtime data required to be setup once on start +mobf_rtd = { + --!is mob running with fire support + fire_enabled = false, + --!do we have luatrace + luatrace_enabled = false, + --!do we have inventory plus support + have_adv_spawning = false, + --!do we have factions support + factions_available = false, + --!registry for movement patterns + movement_patterns = {}, + --!registry of mobs + registred_mob = {}, + --!registred mobs_data + registred_mob_data = {}, + --!timesource + timesource = "os.clock() (10ms ONLY!)", + --!total spawned mobs + total_spawned = 0, + --!detailed debug state + detailed_state = false, +} + +--!path of mod +mobf_modpath = minetest.get_modpath("mobf") + +LOGLEVEL_INFO = "verbose" +LOGLEVEL_NOTICE = "info" +LOGLEVEL_WARNING = "action" +LOGLEVEL_ERROR = "error" +LOGLEVEL_CRITICAL = "error" + +-- initialize luatrace if necessary +if mobf_rtd.luatrace_enabled then + luatrace = require("luatrace") +end + +-- minetest workarounds + +if not type(core.global_exists) ~= "function" then + core.global_exists = function(name) + return core[name] ~= nil + end +end + + +--include debug trace functions +dofile (mobf_modpath .. "/utils/text.lua") +dofile (mobf_modpath .. "/debug_trace.lua") + +--include engine +dofile (mobf_modpath .. "/utils/error_handling.lua") +dofile (mobf_modpath .. "/utils/settings.lua") +dofile (mobf_modpath .. "/utils/generic_functions.lua") +dofile (mobf_modpath .. "/utils/data_storage.lua") +dofile (mobf_modpath .. "/utils/tracing.lua") +dofile (mobf_modpath .. "/utils/geometry.lua") +dofile (mobf_modpath .. "/utils/permanent_data.lua") +dofile (mobf_modpath .. "/lifebar.lua") +dofile (mobf_modpath .. "/env_constants.lua") +dofile (mobf_modpath .. "/environment.lua") +dofile (mobf_modpath .. "/attention.lua") +dofile (mobf_modpath .. "/movement_generic.lua") +dofile (mobf_modpath .. "/graphics.lua") +dofile (mobf_modpath .. "/movement_gen_registry.lua") +dofile (mobf_modpath .. "/harvesting.lua") +dofile (mobf_modpath .. "/fighting.lua") +dofile (mobf_modpath .. "/random_drop.lua") +dofile (mobf_modpath .. "/sound.lua") +dofile (mobf_modpath .. "/ride.lua") +dofile (mobf_modpath .. "/mobf.lua") +dofile (mobf_modpath .. "/api.lua") +dofile (mobf_modpath .. "/debug.lua") +dofile (mobf_modpath .. "/mob_state.lua") +dofile (mobf_modpath .. "/inventory.lua") +dofile (mobf_modpath .. "/path.lua") +dofile (mobf_modpath .. "/factions.lua") +dofile (mobf_modpath .. "/step_quota.lua") + +--include spawning support +dofile (mobf_modpath .. "/spawning.lua") + +--include movement generators +dofile (mobf_modpath .. "/mgen_probab/main_probab.lua") +dofile (mobf_modpath .. "/mgen_follow/main_follow.lua") +dofile (mobf_modpath .. "/mgen_rasterized/mgen_raster.lua") +dofile (mobf_modpath .. "/mgen_jordan4ibanez/mgen_jordan4ibanez.lua") +dofile (mobf_modpath .. "/mgen_pathbased/main.lua") +dofile (mobf_modpath .. "/mgen_flee/main_flee.lua") +dofile (mobf_modpath .. "/mov_gen_none.lua") + +mobf_version = "2.5.1" + + +--! @brief main initialization function +function mobf_init_framework() + + minetest.log(LOGLEVEL_NOTICE,"MOBF: Initializing mob framework") + + minetest.log(LOGLEVEL_NOTICE,"MOBF: Reading mob blacklist") + local mobf_mob_blacklist_string = minetest.world_setting_get("mobf_blacklist") + + if mobf_mob_blacklist_string ~= nil then + mobf_rtd.registred_mob = minetest.deserialize(mobf_mob_blacklist_string) + + if mobf_rtd.registred_mob == nil then + minetest.log(LOGLEVEL_ERROR,"MOBF: Error on serializing blacklist!") + mobf_rtd.registred_mob = {} + end + end + + minetest.log(LOGLEVEL_NOTICE,"MOBF: Initialize timesource...") + mobf_init_timesource() + + minetest.log(LOGLEVEL_NOTICE,"MOBF: Initialize statistics...") + mobf_init_statistics() + + minetest.log(LOGLEVEL_NOTICE,"MOBF: Initialize factions support...") + mobf_factions.init() + + minetest.log(LOGLEVEL_NOTICE,"MOBF: Initialize external mod dependencys...") + mobf_init_mod_deps() + + minetest.log(LOGLEVEL_NOTICE,"MOBF: Initializing probabilistic movement generator") + movement_gen.initialize() + + minetest.log(LOGLEVEL_NOTICE,"MOBF: Initializing debug hooks..") + mobf_debug.init() + + minetest.log(LOGLEVEL_NOTICE,"MOBF: Initializing mob preservation..") + local preserved_mobs_raw = mobf_get_world_setting("mobf_preserve_mobs") + + if preserved_mobs_raw ~= nil then + mobf.current_preserve_list = + minetest.deserialize(preserved_mobs_raw) + end + + if mobf.current_preserve_list == nil then + mobf.current_preserve_list = {} + end + + minetest.log(LOGLEVEL_NOTICE,"MOBF: Initialize path handling subsystem..") + mobf_path.init() + + minetest.log(LOGLEVEL_NOTICE,"MOBF: Initialize lifebar subsystem..") + mobf_lifebar.init() + + minetest.log(LOGLEVEL_NOTICE,"MOBF: Initialize spawning subsystem..") + spawning.init() + + minetest.log(LOGLEVEL_NOTICE,"MOBF: Initialize mobf supplied modules..") + mobf_init_modules() + + minetest.log(LOGLEVEL_NOTICE,"MOBF: Register rightclick button handler..") + minetest.register_on_player_receive_fields(mobf.rightclick_button_handler) + + minetest.log(LOGLEVEL_NOTICE,"MOBF: Initializing step time quota .. ") + mobf_step_quota.initialize() + + -- register privilege to change mobf settings + minetest.register_privilege("mobfw_admin", + { + description = "Player may change mobf settings", + give_to_singleplayer = true + }) + + core.log("action","MOD: mob framework mod "..mobf_version.." loaded") +end + +--! @brief initialize mod dependencys +function mobf_init_mod_deps() + local modlist = minetest.get_modnames() + + for i=1,#modlist,1 do + if modlist[i] == "fire" then + mobf_rtd.fire_enabled = true + end + + if modlist[i] == "adv_spawning" then + mobf_rtd.have_adv_spawning = true + end + end +end + +--! @brief initialize mobf submodules +function mobf_init_modules() + + --state change callback + mobf.register_on_step_callback({ + name = "state_change", + handler = mob_state.callback, + init = mob_state.initialize, + configcheck = function(entity) + if entity.data.states ~= nil then + return true + end + return false + end + }) + + --auto transform hook + mobf.register_on_step_callback({ + name = "transform", + handler = transform, + init = nil, + configcheck = function(entity) + if entity.data.auto_transform ~= nil then + return true + end + return false + end + }) + + --combat hook + mobf.register_on_step_callback({ + name = "combat", + handler = fighting.combat, + init = fighting.init_dynamic_data, + configcheck = function(entity) + if entity.data.combat ~= nil then + return true + end + return false + end + }) + + --attention hook + mobf.register_on_step_callback({ + name = "attention", + handler = attention.callback, + init = attention.init_dynamic_data, + configcheck = function(entity) + if entity.data.attention ~= nil or + entity.data.combat ~= nil then + return true + end + return false + end + }) + + --workaround for shortcomings in spawn algorithm + mobf.register_on_step_callback({ + name = "check_pop_dense", + handler = spawning.population_density_check, + init = spawning.init_dynamic_data, + configcheck = function(entity) + if entity.data.generic.population_density ~= nil or + entity.data.spawning ~= nil then + return true + else + return false + end + end + }) + + --random drop hook + mobf.register_on_step_callback({ + name = "random_drop", + handler = random_drop.callback, + init = random_drop.init_dynamic_data, + configcheck = function(entity) + if entity.data.random_drop ~= nil then + return true + end + return false + end + }) + + --random sound hook + mobf.register_on_step_callback({ + name = "sound", + handler = sound.play_random, + init = sound.init_dynamic_data, + configcheck = function(entity) + if entity.data.sound ~= nil and + entity.data.sound.random ~= nil then + return true + end + return false + end + }) + + --visual change hook + mobf.register_on_step_callback({ + name = "update_graphics", + handler = graphics.update, + init = graphics.init_dynamic_data, + configcheck = function(entity) + return true + end + }) + + --custom hook + mobf.register_on_step_callback({ + name = "custom_hooks", + handler = function(entity,now,dtime) + if type(entity.data.generic.custom_on_step_handler) == "function" then + entity.data.generic.custom_on_step_handler(entity,now,dtime) + end + end, + configcheck = function(entity) + return true + end + }) + + + --on punch callbacks + mobf.register_on_punch_callback({ + name = "harvesting", + handler = harvesting.callback, + init = harvesting.init_dynamic_data, + configcheck = function(entity) + if (entity.data.catching ~= nil and + entity.data.catching.tool ~= "" ) or + entity.data.harvest ~= nil then + return true + end + return false + end + }) + + mobf.register_on_punch_callback({ + name = "riding", + handler = mobf_ride.on_punch_callback, + configcheck = mobf_ride.is_enabled + }) + + mobf.register_on_punch_callback({ + name = "punching", + handler = fighting.hit, + configcheck = function(entity) + return true + end + }) + + + --on rightclick callbacks + mobf.register_on_rightclick_callback({ + name = "tradercallback", + visiblename = "Trade", + handler = mob_inventory.trader_callback, + configcheck = mob_inventory.config_check + }) + + mobf.register_on_rightclick_callback({ + name = "debugcallback", + visiblename = "Show debuginfo", + handler = mobf_debug.rightclick_callback, + configcheck = function(entity) + return true + end, + privs = {mobfw_admin=true} + }) + + mobf.register_on_rightclick_callback({ + name = "pathcallback", + visiblename = mobf_path.buttontext, + handler = mobf_path.mob_rightclick_callback, + configcheck = mobf_path.config_check + }) + + mobf.register_on_rightclick_callback({ + name = "factions", + visiblename = "Factions", + handler = mobf_factions.mob_rightclick_callback, + configcheck = mobf_factions.config_check + }) + + mobf.register_on_rightclick_callback({ + name = "heal", + visiblename = fighting.heal_caption, + handler = fighting.heal, + configcheck = function(entity) + return true + end, + }) +end + +mobf_init_framework() + +dofile (mobf_modpath .. "/compatibility.lua") diff --git a/mods/mob_engines/mobf/inventory.lua b/mods/mob_engines/mobf/inventory.lua new file mode 100644 index 00000000..7238f60d --- /dev/null +++ b/mods/mob_engines/mobf/inventory.lua @@ -0,0 +1,643 @@ +------------------------------------------------------------------------------- +-- 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 inventory.lua +--! @brief component containing mob inventory related functions +--! @copyright Sapier +--! @author Sapier +--! @date 2013-01-02 +-- +--! @defgroup Inventory Inventory subcomponent +--! @brief Component handling mob inventory +--! @ingroup framework_int +--! @{ +-- +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- +mobf_assert_backtrace(not core.global_exists("mob_inventory")) +--! @class mob_inventory +--! @brief inventory handling for trader like mobs +--! @} +mob_inventory = {} + +-- Boilerplate to support localized strings if intllib mod is installed. +local S +if core.global_exists("intllib") then + S = intllib.Getter() +else + S = function(s) return s end +end +------------------------------------------------------------------------------- +mob_inventory.trader_inventories = {} +mob_inventory.formspecs = {} + +------------------------------------------------------------------------------- +-- @function [parent=#mob_inventory] allow_move(inv, from_list, from_index, to_list, to_index, count, player) +-- +--! @brief check if there is enough at payroll +--! @memberof mob_inventory +--! @private +-- +--! @param inv inventory reference +--! @param from_list name of list elements taken +--! @param from_index index at list elements taken +--! @param to_list list name of list elements being put +--! @param to_index index at list elements being put +--! @param count number of elements moved +--! @param player doing changes +-- +--! @return number of elements allowed to move +------------------------------------------------------------------------------- +function mob_inventory.allow_move(inv, from_list, from_index, to_list, to_index, count, player) + + dbg_mobf.trader_inv_lvl1("MOBF: move inv: " .. tostring(inv) .. " from:" + .. dump(from_list) .. " to: " .. dump(to_list)) + if to_list ~= "selection" or + from_list == "price_1" or + from_list == "price_2" or + from_list == "pay" or + from_list == "takeaway" or + from_list == "identifier" then + return 0 + end + + -- forbid moving of parts of stacks + local old_stack = inv.get_stack(inv, from_list, from_index) + if count ~= old_stack.get_count(old_stack) then + return 0; + end + + return count +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_inventory] allow_put(inv, listname, index, stack, player) +-- +--! @brief check if there is enough at payroll +--! @memberof mob_inventory +--! @private +-- +--! @param inv inventory reference +--! @param listname name of list changed +--! @param index index in list changed +--! @param stack moved +--! @param player doing changes +-- +--! @return number of elements allowed to put +------------------------------------------------------------------------------- +function mob_inventory.allow_put(inv, listname, index, stack, player) + dbg_mobf.trader_inv_lvl1("MOBF: put inv: " .. tostring(inv) .. " to:" + .. dump(listname)) + + if listname == "pay" then + return 99 + end + + return 0 +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_inventory] allow_take(inv, listname, index, stack, player) +-- +--! @brief check if there is enough at payroll +--! @memberof mob_inventory +--! @private +-- +--! @param inv inventory reference +--! @param listname name of list changed +--! @param index index in list changed +--! @param stack moved +--! @param player doing changes +-- +--! @return number of elements allowed to take +------------------------------------------------------------------------------- +function mob_inventory.allow_take(inv, listname, index, stack, player) + dbg_mobf.trader_inv_lvl1("MOBF: take inv: " .. tostring(inv) .. " to:" + .. dump(listname)) + + if listname == "takeaway" or + listname == "pay" then + return 99 + end + return 0 +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_inventory] on_move(inv, from_list, from_index, to_list, to_index, count, player) +-- +--! @brief check if there is enough at payroll +--! @memberof mob_inventory +--! @private +-- +--! @param inv inventory reference +--! @param from_list name of list elements taken +--! @param from_index index at list elements taken +--! @param to_list list name of list elements being put +--! @param to_index index at list elements being put +--! @param count number of elements moved +--! @param player doing changes +------------------------------------------------------------------------------- +function mob_inventory.on_move(inv, from_list, from_index, to_list, to_index, count, player) + dbg_mobf.trader_inv_lvl1("MOBF: inv\"" .. tostring(inv) .. "\" moving " + .. count .. " items from: " .. from_list .. ":" .. from_index .. " to: " + .. to_list .. ":" .. to_index) + + if from_list == "goods" and + to_list == "selection" then + + local moved = inv.get_stack(inv,to_list, to_index) + + local goodname = moved.get_name(moved) + local elements = moved.get_count(moved) + + + -- handle situations where diffrent amounts of the same good are offered- + dbg_mobf.trader_inv_lvl2("MOBF: moving " .. dump(elements) .."/" + .. dump(count) .. " from " + .. dump(from_list).. " to ".. dump(from_list ).. + " at index "..dump(from_index )) + + -- if it was the same type of good (and now contains a summed up number + -- of offerings) put the old stack back into the traders inv + if( elements > count ) then + dbg_mobf.trader_inv_lvl2( + "MOBF: ok, whe have to give back a stack of " + ..tostring( elements - count ).." "..tostring( goodname ) + ..". target stack: "..tostring( from_index )) + -- remove the surplus parts + inv.set_stack(inv,"selection", 1,goodname.." "..tostring( count )) + -- the slot we took from is now free + inv.set_stack(inv,"goods",from_index, + goodname.." "..tostring( elements - count )) + -- update the real amount of items in the slot now + elements = count + end + + local entity = mob_inventory.get_entity(inv) + + if entity == nil then + dbg_mobf.trader_inv_lvl1("MOBF: move unable to find linked entity") + return + end + + dbg_mobf.trader_inv_lvl1("MOBF: good selected: " .. goodname) + + --get element put to selection + mob_inventory.fill_prices(entity,inv,goodname,count) + mob_inventory.update_takeaway(inv) + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_inventory] on_put(inv, listname, index, stack, player) +-- +--! @brief check if there is enough at payroll +--! @memberof mob_inventory +--! @private +-- +--! @param inv inventory reference +--! @param listname name of list changed +--! @param index index in list changed +--! @param stack moved +--! @param player doing changes +------------------------------------------------------------------------------- +function mob_inventory.on_put(inv, listname, index, stack, player) + if listname == "pay" then + local now_at_pay = inv.get_stack(inv,"pay",1) + local playername = player.get_player_name(player) + local count = now_at_pay.get_count(now_at_pay) + local name = now_at_pay.get_name(now_at_pay) + dbg_mobf.trader_inv_lvl1("MOBF: putpay player: " .. playername + .. " pays now count=" .. count .. " of type=" ..name) + + mob_inventory.update_takeaway(inv) + end +end + + + +------------------------------------------------------------------------------- +-- @function [parent=#mob_inventory] on_take(inv, listname, index, stack, player) +-- +--! @brief check if there is enough at payroll +--! @memberof mob_inventory +--! @private +-- +--! @param inv inventory reference +--! @param listname name of list changed +--! @param index index in list changed +--! @param stack moved +--! @param player doing changes +------------------------------------------------------------------------------- +function mob_inventory.on_take(inv, listname, index, stack, player) + if listname == "takeaway" then + local now_at_pay = inv.get_stack(inv,"pay",index) + local playername = player.get_player_name(player) + local count = now_at_pay.get_count(now_at_pay) + local name = now_at_pay.get_name(now_at_pay) + dbg_mobf.trader_inv_lvl2("MOBF: takeaway player: " .. playername + .. " pays now count=" .. count .. " of type=" ..name) + + if not mob_inventory.check_pay(inv,true) then + dbg_mobf.trader_inv_lvl1("MOBF: error player hasn't payed enough!") + end + + mob_inventory.update_takeaway(inv) + end + + if listname == "pay" then + if mob_inventory.check_pay(inv,false) then + local selection = inv.get_stack(inv,"selection", 1) + + if selection ~= nil then + inv.set_stack(inv,"takeaway",1,selection) + else + dbg_mobf.trader_inv_lvl1("MOBF: nothing selected to buy") + end + else + inv.set_stack(inv,"takeaway",1,nil) + end + end +end + + +------------------------------------------------------------------------------- +-- @function [parent=#mob_inventory] update_takeaway(inv) +-- +--! @brief update content of takeaway +--! @memberof mob_inventory +--! @private +-- +--! @param inv to update +------------------------------------------------------------------------------- +function mob_inventory.update_takeaway(inv) + if mob_inventory.check_pay(inv,false) then + local selection = inv.get_stack(inv,"selection", 1) + + if selection ~= nil then + inv.set_stack(inv,"takeaway",1,selection) + else + dbg_mobf.trader_inv_lvl1("MOBF: nothing selected to buy") + end + else + inv.set_stack(inv,"takeaway",1,nil) + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_inventory] check_pay(inv) +-- +--! @brief check if there is enough at payroll +--! @memberof mob_inventory +--! @private +-- +--! @param inv inventory to do check +--! @param paynow true/false if it's called to pay or not +-- +--! @return true/false +------------------------------------------------------------------------------- +function mob_inventory.check_pay(inv,paynow) + local now_at_pay = inv.get_stack(inv,"pay",1) + local count = now_at_pay.get_count(now_at_pay) + local name = now_at_pay.get_name(now_at_pay) + + local price1 = inv.get_stack(inv,"price_1", 1) + local price2 = inv.get_stack(inv,"price_2", 1) + + dbg_mobf.trader_inv_lvl1( + "MOBF: p1 " .. dump(price1) .. " " .. dump(price1:get_name())) + if price1:get_name() == name then + local price = price1:get_count() + if price > 0 and + price <= count then + if paynow then + now_at_pay.take_item(now_at_pay,price) + inv.set_stack(inv,"pay",1,now_at_pay) + return true + else + return true + end + else + if paynow then + inv.set_stack(inv,"pay",1,nil) + end + end + end + + dbg_mobf.trader_inv_lvl1( + "MOBF: p2 " .. dump(price1) .. " " .. dump(price2:get_name())) + if price2:get_name() == name then + local price = price2:get_count() + if price > 0 and + price <= count then + if paynow then + now_at_pay.take_item(now_at_pay,price) + inv.set_stack(inv,"pay",1,now_at_pay) + return true + else + return true + end + else + if paynow then + inv.set_stack(inv,"pay",1,nil) + end + end + end + return false +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_inventory] init_detached_inventories(entity,now) +-- +--! @brief initialize dynamic data required by harvesting +--! @memberof mob_inventory +--! @public +-- +--! @param entity mob to initialize harvest dynamic data +------------------------------------------------------------------------------- +function mob_inventory.init_trader_inventory(entity) + --TODO find out why calling "tostring" is necessary?! + local tradername = tostring( + entity.data.trader_inventory.random_names[ + math.random(1,#entity.data.trader_inventory.random_names)]) + dbg_mobf.trader_inv_lvl3( + "MOBF: randomly selected \"" .. tradername .. "\" as name") + local unique_entity_id = string.gsub(tostring(entity),"table: ","") + --local unique_entity_id = "testinv" + local trader_inventory = + minetest.create_detached_inventory(unique_entity_id, + { + allow_move = mob_inventory.allow_move, + allow_put = mob_inventory.allow_put, + allow_take = mob_inventory.allow_take, + + on_move = mob_inventory.on_move, + on_put = mob_inventory.on_put, + on_take = mob_inventory.on_take, + }) + + trader_inventory.set_size(trader_inventory,"goods",16) + trader_inventory.set_size(trader_inventory,"takeaway",1) + trader_inventory.set_size(trader_inventory,"selection",1) + trader_inventory.set_size(trader_inventory,"price_1",1) + trader_inventory.set_size(trader_inventory,"price_2",1) + trader_inventory.set_size(trader_inventory,"pay",1) + + mob_inventory.add_goods(entity,trader_inventory) + + --register to trader inventories + table.insert(mob_inventory.trader_inventories, { + identifier = unique_entity_id, + inv_ref = trader_inventory, + ent_ref = entity, + }) + dbg_mobf.trader_inv_lvl3( + "MOBF: registering identifier: " .. unique_entity_id + .. " invref \"" .. tostring(trader_inventory) .. "\" for entity \"" + .. tostring(entity) .. "\"" ) + + local trader_formspec = "size[8,10;]" .. + "label[2,0;"..S("Trader %s Inventory"):format(tradername).."]" .. + "label[0,1;"..S("Selling:").."]" .. + "list[detached:" .. unique_entity_id .. ";goods;0,1.5;8,2;]" .. + "label[0,4.0;"..S("Selection").."]" .. + "list[detached:" .. unique_entity_id .. ";selection;0,4.5;1,1;]" .. + "label[1.25,4.75;-->]" .. + "label[2,4.0;"..S("Price").."]" .. + "list[detached:" .. unique_entity_id .. ";price_1;2,4.5;1,1;]" .. + "label[3,4.0;"..S("or").."]" .. + "list[detached:" .. unique_entity_id .. ";price_2;3,4.5;1,1;]" .. + "label[4.25,4.75;-->]" .. + "label[5,4.0;"..S("Pay").."]" .. + "list[detached:" .. unique_entity_id .. ";pay;5,4.5;1,1;]" .. + "label[6.25,4.75;-->]" .. + "label[6.75,4.0;"..S("Takeaway").."]" .. + "list[detached:" .. unique_entity_id .. ";takeaway;7,4.5;1,1;]" .. + "list[current_player;main;0,6;8,4;]" + + if mob_inventory.register_formspec("formspec_" + .. unique_entity_id,trader_formspec) == false then + dbg_mobf.trader_inv_lvl1("MOBF: unable to create trader formspec") + end +end + + +------------------------------------------------------------------------------- +-- @function [parent=#mob_inventory] config_check(entity) +-- +--! @brief check if mob is configured as trader +--! @memberof mob_inventory +--! @public +-- +--! @param entity mob being checked +--! @return true/false if trader or not +------------------------------------------------------------------------------- +function mob_inventory.config_check(entity) + if entity.data.trader_inventory ~= nil then + return true + end + + return false +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_inventory] register_formspec(name,formspec) +-- +--! @brief check if mob is configured as trader +--! @memberof mob_inventory +--! @public +-- +--! @param name name of formspec to register +--! @param formspec formspec definition +-- +--! @return true/false if succesfull or not +------------------------------------------------------------------------------- +function mob_inventory.register_formspec(name,formspec) + + if mob_inventory.formspecs[name] == nil then + mob_inventory.formspecs[name] = formspec + return true + end + + return false +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_inventory] trader_callback(entity,player) +-- +--! @brief callback handler for inventory by rightclick +--! @memberof mob_inventory +--! @public +-- +--! @param entity mob being harvested +--! @param player player harvesting +-- +--! @return true/false if handled by harvesting or not +------------------------------------------------------------------------------- +function mob_inventory.trader_callback(entity, player) + local unique_entity_id = string.gsub(tostring(entity),"table: ","") + --local unique_entity_id = "testinv" + local playername = player.get_player_name(player) + + if entity.data.sound ~= nil and + entity.data.sound.inventory_open ~= nil then + sound.play(playername, entity.data.sound.inventory_open) + end + + if mob_inventory.formspecs["formspec_" .. unique_entity_id] ~= nil then + + local pos = entity.object:getpos() + + if pos == nil then + dbg_mobf.trader_inv_lvl1("MOBF: unable to get trader pos") + minetest.show_formspec(playername,"") + return + end + + --rotate mob to face player + local direction = mobf_get_direction(pos, player:getpos()) + + if entity.mode == "3d" then + graphics.setyaw(entity, + mobf_calc_yaw(direction.x,direction.z)) + else + graphics.setyaw(entity, + mobf_calc_yaw(direction.x,direction.z)+math.pi/2) + end + + attention.increase_attention_level(entity,player,10) + + if minetest.show_formspec(playername, + "formspec_" .. unique_entity_id, + mob_inventory.formspecs["formspec_" .. unique_entity_id]) + == false then + dbg_mobf.trader_inv_lvl1("MOBF: unable to show trader formspec") + end + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_inventory] get_entity(inv) +-- +--! @brief find entity linked to inventory +--! @memberof mob_inventory +--! @private +-- +--! @param inv name of inventory +------------------------------------------------------------------------------- +function mob_inventory.get_entity(inv) + dbg_mobf.trader_inv_lvl3("MOBF: checking " + .. #mob_inventory.trader_inventories + .. " registred inventorys") + + local location = inv.get_location(inv) + + if location.type == "detached" then + for i=1,#mob_inventory.trader_inventories,1 do + dbg_mobf.trader_inv_lvl3("MOBF: comparing \"" + .. location.name .. "\" to \"" + .. mob_inventory.trader_inventories[i].identifier .. "\"") + if mob_inventory.trader_inventories[i].identifier + == location.name then + return mob_inventory.trader_inventories[i].ent_ref + end + end + end + + return nil +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_inventory] fill_prices(entity,inventory,goodname) +-- +--! @brief fill price fields +--! @memberof mob_inventory +--! @private +-- +--! @param entity to look for prices +--! @param inventory to set prices +--! @param goodname name of good to set prices for +--! @param count number of elements +------------------------------------------------------------------------------- +function mob_inventory.fill_prices(entity,inventory,goodname,count) + + --get price info from entity + local good = nil + + for i=1,#entity.data.trader_inventory.goods,1 do + local stackstring = goodname .." " .. count + dbg_mobf.trader_inv_lvl3("MOBF: comparing \"" .. stackstring .. "\"" .. + " to \"" .. entity.data.trader_inventory.goods[i][1] .. "\"") + if entity.data.trader_inventory.goods[i][1] == stackstring then + good = entity.data.trader_inventory.goods[i] + end + end + + if good ~= nil then + inventory.set_stack(inventory,"price_1", 1, good[2]) + inventory.set_stack(inventory,"price_2", 1, good[3]) + else + inventory.set_stack(inventory,"price_1", 1, nil) + inventory.set_stack(inventory,"price_2", 1, nil) + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_inventory] add_goods(entity,trader_inventory) +-- +--! @brief fill inventory with mobs goods +--! @memberof mob_inventory +--! @private +-- +--! @param entity to look for prices +--! @param trader_inventory to put goods +------------------------------------------------------------------------------- +function mob_inventory.add_goods(entity,trader_inventory) + + local goods_to_add = nil + + if entity.data.trader_inventory.goodlist ~= nil then + local total_sum_chances = 0 + + for i=1,#entity.data.trader_inventory.goodlist,1 do + total_sum_chances = total_sum_chances + + entity.data.trader_inventory.goodlist[i].chance + end + + local rand_value = math.random(0,total_sum_chances) + + local selected_index=1 + local runvalue = 0 + + for j=1,#entity.data.trader_inventory.goodlist,1 do + + runvalue = runvalue + + entity.data.trader_inventory.goodlist[j].chance + + if runvalue < rand_value then + selected_index=j + break; + end + end + + goods_to_add = + entity.data.trader_inventory.goodlist[selected_index].goods + else + goods_to_add = entity.data.trader_inventory.goods + end + + dbg_mobf.trader_inv_lvl3("MOBF: adding " + .. #goods_to_add + .. " goods for trader") + for i=1,#goods_to_add,1 do + dbg_mobf.trader_inv_lvl3("MOBF:\tadding " .. + goods_to_add[i][1]) + trader_inventory.set_stack(trader_inventory,"goods", i, + goods_to_add[i][1]) + end + +end diff --git a/mods/mob_engines/mobf/lifebar.lua b/mods/mob_engines/mobf/lifebar.lua new file mode 100644 index 00000000..f8aa48f3 --- /dev/null +++ b/mods/mob_engines/mobf/lifebar.lua @@ -0,0 +1,158 @@ +------------------------------------------------------------------------------- +-- 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 lifebar.lua +--! @brief mobf_lifebar implementation +--! @copyright Sapier +--! @author Sapier +--! @date 2013-02-14 +-- +--! @defgroup mobf_lifebar +--! @brief lifebar implements a visible lifebar showing health of a mob abov +--! its head +--! @ingroup framework_int +--! @{ +-- Contact: sapier a t gmx net +------------------------------------------------------------------------------- +mobf_assert_backtrace(not core.global_exists("mobf_lifebar")) +--! @class mobf_lifebar +--! @brief a simple lifebar implementation +--!@} +mobf_lifebar = {} + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_lifebar] init() +-- +--! @brief register lifebar entity +--! @memberof mobf_lifebar +------------------------------------------------------------------------------- +function mobf_lifebar.init() + minetest.register_entity(":mobf:lifebar", + { + physical = false, + collisionbox = { 0,0,0,0,0,0 }, + visual = "sprite", + textures = { "mobf_lb_64.png" }, + visual_size = {x=1,y=0.2}, + groups = { immortal=1, }, + is_visible = true, + initial_sprite_basepos = {x=0, y=0}, + + lifetime = 0, + initialized = false, + + on_step = function (self,dtime) + self.lifetime = self.lifetime + dtime + if not self.initialized then + if self.lifetime > 1 then + dbg_mobf.lifebar_lvl3("MOBF: lifebar not attached deleting") + self.object:remove() + end + end + + --parent will reset lifetime while it's active + if self.lifetime > 5 then + self.object:remove() + end + end + + }) +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_lifebar] add(entity) +-- +--! @brief add a lifebat to an entity +--! @memberof mobf_lifebar +-- +--! @param entity entity to add lifebar +-- +--! @return reference to lifebar added +------------------------------------------------------------------------------- +function mobf_lifebar.add(entity) + local pos = entity.object:getpos() + local BS = 10 + + local lifebar_offset = (MAX(entity.collisionbox[4]-entity.collisionbox[1], + entity.collisionbox[6]-entity.collisionbox[3]) / 0.5) * 0.4 + lifebar_offset = lifebar_offset * lifebar_offset + + pos.y = pos.y + entity.collisionbox[5] + lifebar_offset + + local lifebar = minetest.add_entity(pos,"mobf:lifebar") + + if lifebar ~= nil then + + lifebar:set_attach(entity.object,"",{x=0,y=(entity.collisionbox[5] + 0.1) * BS,z=0},{x=0,y=-90,z=0}) + + local luaentity = lifebar:get_luaentity() + if luaentity ~= nil then + dbg_mobf.lifebar_lvl3("MOBF: marking lifebar as initialized") + luaentity.initialized = true + else + dbg_mobf.lifebar_lvl3("MOBF: unable to create lifebar entity") + end + end + + return lifebar +end + + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_lifebar] del(lifebar) +-- +--! @brief delete a lifebar +--! @memberof mobf_lifebar +-- +--! @param lifebar lifebar do telete +------------------------------------------------------------------------------- +function mobf_lifebar.del(lifebar) + if lifebar ~= nil then + lifebar:set_detach() + lifebar:remove() + end +end + +------------------------------------------------------------------------------- +-- name: set(lifebar,value) +-- +--! @brief set value of a lifebar +--! @memberof mobf_lifebar +--! @private +-- +--! @param lifebar lifebar do update +--! @param value (0-1) value of lifebar +------------------------------------------------------------------------------- +function mobf_lifebar.set(lifebar,value) + if lifebar ~= nil then + local modifiername = mobf_lifebar.get_imagename(value) + dbg_mobf.lifebar_lvl2("MOBF: got modifier " .. modifiername .. " for value " .. value) + lifebar:settexturemod(modifiername) + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_lifebar] get_imagename(value) +-- +--! @brief calculate imagename from value +--! @memberof mobf_lifebar +--! @private +-- +--! @param value to get image for +------------------------------------------------------------------------------- +function mobf_lifebar.get_imagename(value) + + local number = math.floor((value*32) +0.5) + + dbg_mobf.lifebar_lvl2("MOBF: calculated number: " .. number ) + + if number < 5 then + return "^mobf_lb_0" .. number * 2 .. ".png" + else + return "^mobf_lb_" .. number * 2 .. ".png" + end +end \ No newline at end of file diff --git a/mods/mob_engines/mobf/locale/de.txt b/mods/mob_engines/mobf/locale/de.txt new file mode 100644 index 00000000..2599c69a --- /dev/null +++ b/mods/mob_engines/mobf/locale/de.txt @@ -0,0 +1,12 @@ +# Translation by Xanthin + +Path marker tool = Pfadmarkierer + +###Inventory.lua### +Trader %s Inventory = Haendler %s Waren +Selling: = Zu verkaufen: +Selection = Auswahl +Price = Preis +or = oder +Pay = Bezahlen +Takeaway = Wegnehmen diff --git a/mods/mob_engines/mobf/locale/es.txt b/mods/mob_engines/mobf/locale/es.txt new file mode 100644 index 00000000..7a9c2f72 --- /dev/null +++ b/mods/mob_engines/mobf/locale/es.txt @@ -0,0 +1,14 @@ +# Spanish translation for Animals Modpack. +# Traducción al español de Animals Modpack. +# Author/Autor: Diego Martínez + +Path marker tool = Herramienta de marcado de camino + +###Inventory.lua### +Trader %s Inventory = Inventario de Mercader %s +Selling: = Vende: +Selection = Seleccion +Price = Precio +or = o +Pay = Paga +Takeaway = Toma diff --git a/mods/mob_engines/mobf/locale/template.txt b/mods/mob_engines/mobf/locale/template.txt new file mode 100644 index 00000000..6835aaff --- /dev/null +++ b/mods/mob_engines/mobf/locale/template.txt @@ -0,0 +1,12 @@ +# Template + +Path marker tool = + +###Inventory.lua### +Trader %s Inventory = +Selling: = +Selection = +Price = +or = +Pay = +Takeaway = diff --git a/mods/mob_engines/mobf/mgen_flee/main_flee.lua b/mods/mob_engines/mobf/mgen_flee/main_flee.lua new file mode 100644 index 00000000..b3a27206 --- /dev/null +++ b/mods/mob_engines/mobf/mgen_flee/main_flee.lua @@ -0,0 +1,451 @@ +------------------------------------------------------------------------------- +-- 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 main_flee.lua +--! @brief component containing a movement generator trying to avoid a target +--! @copyright Sapier +--! @author Sapier +--! @date 2013-09-10 +-- +--! @defgroup mgen_flee MGEN: flee movement generator +--! @brief A movement generator creating movement that trys to avoid a moving +--! target or get away as far as possible from a given point on map +--! @ingroup framework_int +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @class mgen_flee +--! @brief a movement generator trying to get a way from a target + +--!@} + +mgen_flee = {} + +--! @brief movement generator identifier +--! @memberof mgen_flee +mgen_flee.name = "flee_mov_gen" + +------------------------------------------------------------------------------- +-- name: callback(entity,now) +-- +--! @brief main callback to make a mob flee from a target +--! @memberof mgen_flee +-- +--! @param entity mob to generate movement for +--! @param now current time +------------------------------------------------------------------------------- +function mgen_flee.callback(entity,now) + + dbg_mobf.flmovement_lvl3("MOBF: Follow mgen callback called") + + if entity == nil then + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!!: called movement gen without entity!") + return + end + + if entity.dynamic_data == nil or + entity.dynamic_data.movement == nil then + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!!: >" ..entity.data.name .. + "< removed=" .. dump(entity.removed) .. " entity=" .. + tostring(entity) .. " probab movement callback") + return + end + + local flee_speedup = {x=10,y=2,z=10 } + + if entity.data.movement.flee_speedup ~= nil then + if type(entity.data.movement.flee_speedup) == "table" then + flee_speedup = entity.data.movement.flee_speedup + else + flee_speedup.x= entity.data.movement.flee_speedup + flee_speedup.z= entity.data.movement.flee_speedup + end + end + + --check max speed limit + mgen_flee.checkspeed(entity) + + + --check environment + local basepos = entity.getbasepos(entity) + local pos_quality = environment.pos_quality(basepos,entity) + + if environment.evaluate_state(pos_quality, LT_GOOD_POS) or + (entity.data.movement.canfly and + environment.evaluate_state(pos_quality,LT_GOOD_FLY_POS)) then + local toset = { + x= basepos.x, + y= basepos.y - 0.5 - entity.collisionbox[2], + z= basepos.z } + --save known good position + entity.dynamic_data.movement.last_pos_in_env = toset + end + + if pos_quality.media_quality == MQ_IN_AIR or -- wrong media + pos_quality.media_quality == MQ_IN_WATER or -- wrong media + pos_quality.geometry_quality == GQ_NONE or -- no ground contact (TODO this was drop above water before) + pos_quality.surface_quality_min == SQ_WATER then -- above water + + + if entity.dynamic_data.movement.invalid_env_count == nil then + entity.dynamic_data.movement.invalid_env_count = 0 + end + + entity.dynamic_data.movement.invalid_env_count = + entity.dynamic_data.movement.invalid_env_count + 1 + + + --don't change at first invalid pos but give some steps to cleanup by + --other less invasive mechanisms + if entity.dynamic_data.movement.invalid_env_count > 10 then + dbg_mobf.flmovement_lvl1("MOBF: fled to wrong place " .. pos_quality.tostring(pos_quality)) + if entity.dynamic_data.movement.last_pos_in_env ~= nil then + entity.object:moveto(entity.dynamic_data.movement.last_pos_in_env) + basepos = entity.getbasepos(entity) + else + local newpos = environment.get_suitable_pos_same_level(basepos,1,entity,true) + + if newpos == nil then + newpos = environment.get_suitable_pos_same_level( { + x=basepos.x, + y=basepos.y-1, + z=basepos.z } + ,1,entity,true) + end + + if newpos == nil then + newpos = environment.get_suitable_pos_same_level( { + x=basepos.x, + y=basepos.y+1, + z=basepos.z } + ,1,entity,true) + end + + if newpos == nil then + dbg_mobf.flmovement_lvl1("MOBF: no way to fix it removing mob") + spawning.remove(entity,"mgen_flee poscheck") + else + newpos.y = newpos.y - (entity.collisionbox[2] + 0.49) + entity.object:moveto(newpos) + basepos = entity.getbasepos(entity) + end + end + end + else + entity.dynamic_data.movement.invalid_env_count = 0 + end + + if pos_quality.level_quality ~= LQ_OK and + entity.data.movement.canfly then + local current_accel = entity.object:getacceleration() + + if pos_quality.level_quality == LQ_ABOVE then + if current_accel.y >= 0 then + current_accel.y = - entity.data.movement.max_accel + end + end + + if pos_quality.level_quality == LQ_BELOW then + local current_accel = entity.object:getacceleration() + if current_accel.y <= 0 then + current_accel.y = entity.data.movement.max_accel + end + end + + entity.object:setacceleration(current_accel) + return + end + + --fixup height + local current_accel = entity.object:getacceleration() + if entity.data.movement.canfly then + if current_accel.y ~= 0 then + current_accel.y = 0 + entity.object:setacceleration(current_accel) + end + end + + if entity.dynamic_data.movement.target ~= nil then + dbg_mobf.flmovement_lvl3("MOBF: Target available") + --calculate distance to target + local targetpos = nil + + if entity.dynamic_data.movement.target ~= nil then + dbg_mobf.flmovement_lvl3("MOBF: have moving target") + + if not mobf_is_pos(entity.dynamic_data.movement.target) then + targetpos = entity.dynamic_data.movement.target:getpos() + else + targetpos = entity.dynamic_data.movement.target + end + end + + if targetpos == nil then + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: flee " .. entity.data.name + .. " don't have targetpos " + .. " TGT: " .. dump(entity.dynamic_data.movement.target)) + return + end + + --TODO need to do something if not line of sight? + if mobf_line_of_sight({x=basepos.x,y=basepos.y+1,z=basepos.z}, + {x=targetpos.x,y=targetpos.y+1,z=targetpos.z}) == false then + dbg_mobf.flmovement_lvl3("MOBF: no line of sight") + --TODO teleport support? + --TODO other ways to handle this? + --return + end + + local current_velocity = entity.object:getvelocity() + local predicted_pos = + movement_generic.predict_next_block(basepos,current_velocity,current_accel) + local current_distance = mobf_calc_distance(targetpos,basepos) + local predicted_distance = mobf_calc_distance(targetpos,predicted_pos) + + if not (predicted_distance > current_distance) then + + local current_rotation_addon = 0 + local accel_to_set = nil + local jump_required = false + + while accel_to_set == nil and current_rotation_addon < math.pi*(5/16) do + + accel_to_set,jump_required = mgen_flee.calc_accel(entity, + targetpos, + basepos, + current_rotation_addon, + current_velocity) + + if accel_to_set == nil then + accel_to_set,jump_required = mgen_flee.calc_accel(entity, + targetpos, + basepos, + -current_rotation_addon, + current_velocity) + end + + if accel_to_set == nil then + current_rotation_addon = current_rotation_addon + math.pi/8 + end + end + + if accel_to_set ~= nil then + mgen_flee.set_acceleration(entity,accel_to_set,flee_speedup,basepos) + if jump_required then + local jumpvel = current_velocity + jumpvel.y = 5 + entity.object:setvelocity(jumpvel) + end + else + dbg_mobf.flmovement_lvl2("MOBF: didn't find a way to flee, stopping") + end + end + else + --TODO evaluate if this is an error case + end +end + +function mgen_flee.calc_accel(entity,targetpos,basepos,rot_addon,cur_vel) + local dirxz,dirxy = mobf_calc_direction(targetpos,basepos) + + local dirxz = dirxz + rot_addon + + local accel_to_set = mobf_calc_vector_components(dirxz,entity.data.movement.max_speed) + + local yaccel = environment.get_default_gravity(basepos, + entity.environment.media, + entity.data.movement.canfly) + + accel_to_set.y = yaccel + + local predicted_pos = + movement_generic.predict_next_block(basepos,cur_vel,accel_to_set) + local pos_quality = environment.pos_quality(predicted_pos,entity) + + + if pos_quality.media_quality == MQ_IN_MEDIA and + pos_quality.surface_quality_min > SQ_WATER then + return accel_to_set,false + end + + predicted_pos.y = predicted_pos.y+1 + pos_quality = environment.pos_quality(predicted_pos,entity) + if pos_quality.media_quality == MQ_IN_MEDIA and + pos_quality.surface_quality_min > SQ_WATER then + return accel_to_set,true + end + + return nil,false +end + +------------------------------------------------------------------------------- +-- name: next_block_ok() +-- +--! @brief check quality of next block +--! @memberof mgen_flee +--! @public +------------------------------------------------------------------------------- +function mgen_flee.next_block_ok(entity,pos,acceleration,velocity) + local current_velocity = velocity + + if current_velocity == nil then + current_velocity = entity.object:getvelocity() + end + + local predicted_pos = movement_generic.predict_next_block(pos,current_velocity,acceleration) + + local quality = environment.pos_quality(predicted_pos,entity) + + return ( + (quality.media_quality == MQ_IN_MEDIA) and + (quality.level_quality == LQ_OK) and + ( + (quality.surface_quality_min == Q_UNKNOWN) or + (quality.surface_quality_min >= SQ_WRONG) + ) + ) +end + +------------------------------------------------------------------------------- +-- name: initialize() +-- +--! @brief initialize movement generator +--! @memberof mgen_flee +--! @public +------------------------------------------------------------------------------- +function mgen_flee.initialize(entity,now) + --intentionally empty +end + +------------------------------------------------------------------------------- +-- name: init_dynamic_data(entity,now) +-- +--! @brief initialize dynamic data required by movement generator +--! @memberof mgen_flee +--! @public +-- +--! @param entity mob to initialize dynamic data +--! @param now current time +------------------------------------------------------------------------------- +function mgen_flee.init_dynamic_data(entity,now) + + local pos = entity.object:getpos() + + + local data = { + target = nil, + invalid_env_count = 0, + } + + entity.dynamic_data.movement = data +end + +------------------------------------------------------------------------------- +-- name: checkspeed(entity) +-- +--! @brief check if mobs speed is within it's limits and correct if necessary +--! @memberof mgen_flee +--! @private +-- +--! @param entity mob to initialize dynamic data +------------------------------------------------------------------------------- +function mgen_flee.checkspeed(entity) + + local current_velocity = entity.object:getvelocity() + + local xzspeed = + mobf_calc_scalar_speed(current_velocity.x,current_velocity.z) + + if (xzspeed > (entity.data.movement.max_speed * 10)) then + + --preserver orientation when correcting speed + local dir = mobf_calc_yaw(current_velocity.x,current_velocity.z) + local velocity_to_set = mobf_calc_vector_components(dir,entity.data.movement.max_speed * 0.25) + + velocity_to_set.y=current_velocity.y + + entity.object:setvelocity(velocity_to_set) + + return true + end + + return false +end + +------------------------------------------------------------------------------- +-- name: set_acceleration(entity,accel,speedup) +-- +--! @brief apply acceleration to entity +--! @memberof mgen_flee +--! @private +-- +--! @param entity mob to apply to +--! @param accel acceleration to set +--! @param speedup speedup factor +--! @param pos current position +------------------------------------------------------------------------------- +function mgen_flee.set_acceleration(entity,accel,speedup,pos) + mobf_assert_backtrace(entity ~= nil) + mobf_assert_backtrace(accel ~= nil) + mobf_assert_backtrace(speedup ~= nil) + + accel.x = accel.x*speedup.x + accel.z = accel.z*speedup.z + + if entity.data.movement.canfly then + accel.y = accel.y*speedup.y + end + + if mgen_flee.next_block_ok(entity,pos,accel) then + dbg_mobf.flmovement_lvl3("MOBF: flee setting acceleration to: " .. printpos(accel)); + entity.object:setacceleration(accel) + return true + elseif mgen_flee.next_block_ok(entity,pos,{x=0,y=0,z=0}) then + accel = {x=0,y=0,z=0} + dbg_mobf.flmovement_lvl3("MOBF: flee setting acceleration to: " .. printpos(accel)); + entity.object:setacceleration(accel) + return true + else + local current_velocity = entity.object:getvelocity() + current_velocity.y = 0 + + if mgen_flee.next_block_ok(entity,pos,{x=0,y=0,z=0},current_velocity) then + accel = {x=0,y=0,z=0} + entity.object:setvelocity(current_velocity) + entity.object:setacceleration(accel) + return true + end + end + + dbg_mobf.flmovement_lvl1( + "MOBF: \t acceleration " .. printpos(accel) .. + " would result in invalid position not applying!") + + return false +end + +------------------------------------------------------------------------------- +-- name: set_target(entity, target, follow_speedup, max_distance) +-- +--! @brief set target for movgen +--! @memberof mgen_flee +-- +--! @param entity mob to apply to +--! @param target to set +--! @param follow_speedup --unused here +--! @param max_distance --unused here +------------------------------------------------------------------------------- +function mgen_flee.set_target(entity, target, follow_speedup, max_distance) + mobf_assert_backtrace(entity ~= nil) + mobf_assert_backtrace(target ~= nil) + entity.dynamic_data.movement.target = target + return true +end + +--register this movement generator +registerMovementGen(mgen_flee.name,mgen_flee) \ No newline at end of file diff --git a/mods/mob_engines/mobf/mgen_follow/main_follow.lua b/mods/mob_engines/mobf/mgen_follow/main_follow.lua new file mode 100644 index 00000000..520fc29e --- /dev/null +++ b/mods/mob_engines/mobf/mgen_follow/main_follow.lua @@ -0,0 +1,676 @@ +------------------------------------------------------------------------------- +-- 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 main_follow.lua +--! @brief component containing a targeted movement generator +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +--! @defgroup mgen_follow MGEN: follow movement generator +--! @brief A movement generator creating movement that trys to follow a moving +--! target or reach a given point on map +--! @ingroup framework_int +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @class mgen_follow +--! @brief a movement generator trying to follow or reach a target + +--!@} + +mgen_follow = {} + +--! @brief movement generator identifier +--! @memberof mgen_follow +mgen_follow.name = "follow_mov_gen" + +------------------------------------------------------------------------------- +-- name: identify_movement_state(ownpos,targetpos) +-- +--! @brief check what situation we are +--! @memberof mgen_follow +--! @private +-- +--! @param ownpos position of entity +--! @param targetpos position of target +--! +--! @return "below_los" +--! "below_no_los" +--! "same_height_los" +--! "same_height_no_los" +--! "above_los" +--! "above_no_los" +--! "unknown" +------------------------------------------------------------------------------- +function mgen_follow.identify_movement_state(ownpos,targetpos) + mobf_assert_backtrace(ownpos ~= nil) + mobf_assert_backtrace(targetpos ~= nil) + + local same_height_delta = 0.1 + + local los = mobf_line_of_sight(ownpos,targetpos) + + if ownpos.y > targetpos.y - same_height_delta and + ownpos.y < targetpos.y + same_height_delta then + + if los then + return "same_height_los" + else + return "same_height_no_los" + end + end + + if ownpos.y < targetpos.y then + if los then + return "below_los" + else + return "below_no_los" + end + end + + if ownpos.y > targetpos.y then + if los then + return "above_los" + else + return "above_no_los" + end + end + + return "unknown" +end + +------------------------------------------------------------------------------- +-- name: handleteleport(entity,now) +-- +--! @brief handle teleportsupport +--! @memberof mgen_follow +--! @private +-- +--! @param entity mob to check for teleport +--! @param now current time +--! @param targetpos position of target +--! +--! @return true/false finish processing +------------------------------------------------------------------------------- +function mgen_follow.handleteleport(entity,now,targetpos) + + if (entity.dynamic_data.movement.last_next_to_target ~= nil ) then + local time_since_next_to_target = + now - entity.dynamic_data.movement.last_next_to_target + + dbg_mobf.fmovement_lvl3("MOBF: time since next to target: " .. time_since_next_to_target .. + " delay: " .. dump(entity.data.movement.teleportdelay) .. + " teleportsupport: " .. dump(entity.dynamic_data.movement.teleportsupport)) + + if (entity.dynamic_data.movement.teleportsupport) and + time_since_next_to_target > entity.data.movement.teleportdelay then + + --check targetpos try to playe above if not valid + local maxoffset = 5 + local current_offset = 0 + while (not environment.possible_pos(entity,{ + x=targetpos.x, + y=targetpos.y + current_offset, + z=targetpos.z + })) and + current_offset < maxoffset do + dbg_mobf.fmovement_lvl2( + "MOBF: teleport target within block trying above: " .. current_offset) + current_offset = current_offset +1 + end + + targetpos.y = targetpos.y + current_offset + + --adjust to collisionbox of mob + if entity.collisionbox[2] < -0.5 then + targetpos.y = targetpos.y - (entity.collisionbox[2] + 0.49) + end + + entity.object:setvelocity({x=0,y=0,z=0}) + entity.object:setacceleration({x=0,y=0,z=0}) + entity.object:moveto(targetpos) + entity.dynamic_data.movement.last_next_to_target = now + return true + end + end + return false +end + +------------------------------------------------------------------------------- +-- name: callback(entity,now) +-- +--! @brief main callback to make a mob follow its target +--! @memberof mgen_follow +-- +--! @param entity mob to generate movement for +--! @param now current time +------------------------------------------------------------------------------- +function mgen_follow.callback(entity,now) + + dbg_mobf.fmovement_lvl3("MOBF: Follow mgen callback called") + + if entity == nil then + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!!: called movement gen without entity!") + return + end + + if entity.dynamic_data == nil or + entity.dynamic_data.movement == nil then + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!!: >" ..entity.data.name .. "< removed=" .. dump(entity.removed) .. " entity=" .. tostring(entity) .. " probab movement callback") + return + end + + local follow_speedup = {x=10,y=2,z=10 } + + if entity.data.movement.follow_speedup ~= nil then + if type(entity.data.movement.follow_speedup) == "table" then + follow_speedup = entity.data.movement.follow_speedup + else + follow_speedup.x= entity.data.movement.follow_speedup + follow_speedup.z= entity.data.movement.follow_speedup + end + end + + --if speedup is disabled reset + if not entity.dynamic_data.movement.follow_speedup then + follow_speedup = { x=1, y=1, z=1} + end + + --check max speed limit + mgen_follow.checkspeed(entity) + + + --check environment + local basepos = entity.getbasepos(entity) + local pos_quality = environment.pos_quality(basepos,entity) + + if environment.evaluate_state(pos_quality, LT_GOOD_POS) or + (entity.data.movement.canfly and + environment.evaluate_state(pos_quality,LT_GOOD_FLY_POS)) then + local toset = { + x= basepos.x, + y= basepos.y - 0.5 - entity.collisionbox[2], + z= basepos.z } + --save known good position + entity.dynamic_data.movement.last_pos_in_env = toset + end + + if pos_quality.media_quality == MQ_IN_AIR or -- wrong media + pos_quality.media_quality == MQ_IN_WATER or -- wrong media + pos_quality.geometry_quality == GQ_NONE or -- no ground contact (TODO this was drop above water before) + pos_quality.surface_quality_min == SQ_WATER then -- above water + + + if entity.dynamic_data.movement.invalid_env_count == nil then + entity.dynamic_data.movement.invalid_env_count = 0 + end + + entity.dynamic_data.movement.invalid_env_count = + entity.dynamic_data.movement.invalid_env_count + 1 + + + --don't change at first invalid pos but give some steps to cleanup by + --other less invasive mechanisms + if entity.dynamic_data.movement.invalid_env_count > 10 then + dbg_mobf.fmovement_lvl1("MOBF: followed to wrong place " .. pos_quality.tostring(pos_quality)) + if entity.dynamic_data.movement.last_pos_in_env ~= nil then + entity.object:moveto(entity.dynamic_data.movement.last_pos_in_env) + basepos = entity.getbasepos(entity) + else + local newpos = environment.get_suitable_pos_same_level(basepos,1,entity,true) + + if newpos == nil then + newpos = environment.get_suitable_pos_same_level( { + x=basepos.x, + y=basepos.y-1, + z=basepos.z } + ,1,entity,true) + end + + if newpos == nil then + newpos = environment.get_suitable_pos_same_level( { + x=basepos.x, + y=basepos.y+1, + z=basepos.z } + ,1,entity,true) + end + + if newpos == nil then + dbg_mobf.fmovement_lvl1("MOBF: no way to fix it removing mob") + spawning.remove(entity,"mgen_follow poscheck") + else + newpos.y = newpos.y - (entity.collisionbox[2] + 0.49) + entity.object:moveto(newpos) + basepos = entity.getbasepos(entity) + end + end + end + else + entity.dynamic_data.movement.invalid_env_count = 0 + end + + local current_accel = entity.object:getacceleration() + + if pos_quality.level_quality ~= LQ_OK and + entity.data.movement.canfly then + + + if pos_quality.level_quality == LQ_ABOVE then + if current_accel.y >= 0 then + current_accel.y = - entity.data.movement.max_accel + end + end + + if pos_quality.level_quality == LQ_BELOW then + local current_accel = entity.object:getacceleration() + if current_accel.y <= 0 then + current_accel.y = entity.data.movement.max_accel + end + end + + entity.object:setacceleration(current_accel) + return + end + + --fixup height fixup + if entity.data.movement.canfly then + if current_accel.y ~= 0 then + current_accel.y = 0 + entity.object:setacceleration(current_accel) + end + end + + if entity.dynamic_data.movement.target ~= nil or + entity.dynamic_data.movement.guardspawnpoint then + + dbg_mobf.fmovement_lvl3("MOBF: Target available") + --calculate distance to target + local targetpos = nil + + if entity.dynamic_data.movement.target ~= nil then + dbg_mobf.fmovement_lvl3("MOBF: have moving target") + + if not mobf_is_pos(entity.dynamic_data.movement.target) then + targetpos = entity.dynamic_data.movement.target:getpos() + else + targetpos = entity.dynamic_data.movement.target + end + end + + if targetpos == nil and + entity.dynamic_data.movement.guardspawnpoint == true then + dbg_mobf.fmovement_lvl3("MOBF: non target selected") + targetpos = entity.dynamic_data.spawning.spawnpoint + end + + if targetpos == nil then + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: " .. entity.data.name + .. " don't have targetpos " + .. "SP: " .. dump(entity.dynamic_data.spawning.spawnpoint) + .. " TGT: " .. dump(entity.dynamic_data.movement.target)) + return + end + + local distance = nil + local height_distance = nil + + if entity.data.movement.canfly then + --real pos is relevant not basepos for flying mobs + --target for flying mobs is always slightly above it's target + distance = mobf_calc_distance(entity.object:getpos(), + {x=targetpos.x, y=(targetpos.y+1), z=targetpos.z }) + height_distance = entity.object:getpos().y - (targetpos.y+1) + else + distance = mobf_calc_distance_2d(basepos,targetpos) + end + + if mobf_line_of_sight({x=basepos.x,y=basepos.y+1,z=basepos.z}, + {x=targetpos.x,y=targetpos.y+1,z=targetpos.z}) == false then + dbg_mobf.fmovement_lvl3("MOBF: no line of sight") + --TODO teleport support? + --TODO other ways to handle this? + --return + end + --dbg_mobf.fmovement_lvl3("MOBF: line of sight") + + local max_distance = entity.dynamic_data.movement.max_distance + + if max_distance == nil then + max_distance = 1 + end + + --check if mob needs to move towards target + dbg_mobf.fmovement_lvl3("MOBF: max distance is set to : " + .. max_distance .. " dist: " .. distance) + if distance > max_distance then + entity.dynamic_data.movement.was_moving_last_step = true + + if mgen_follow.handleteleport(entity,now,targetpos) then + return + end + + dbg_mobf.fmovement_lvl3("MOBF: distance:" .. distance) + + local current_state = + mgen_follow.identify_movement_state(basepos,targetpos) + local handled = false + + if handled == false and + (current_state == "same_height_los" or + current_state == "above_los" or + current_state == "above_no_los" ) then + dbg_mobf.fmovement_lvl3("MOBF: \t Case 1: " .. current_state) + local accel_to_set = + movement_generic.get_accel_to(targetpos,entity,true) + + handled = + mgen_follow.set_acceleration(entity, + accel_to_set, + follow_speedup, + basepos) + end + + if handled == false and + (current_state == "below_los" or + current_state == "below_no_los" or + current_state == "same_height_no_los" ) then + dbg_mobf.fmovement_lvl3("MOBF: \t Case 2: " .. current_state) + local accel_to_set = + movement_generic.get_accel_to(targetpos,entity) + + --seems to be a flying mob + if (accel_to_set.y >0) then + handled = + mgen_follow.set_acceleration(entity, + accel_to_set, + follow_speedup, + basepos) + else + local current_velocity = entity.object:getvelocity() + local predicted_pos = + movement_generic.predict_next_block(basepos, + current_velocity, + accel_to_set) + + --TODO replace by quality based mechanism!!!!!!!------------ + local pos_state = + environment.pos_is_ok(predicted_pos,entity) + if pos_state == "collision_jumpable" then + local pos_to_set = entity.object:getpos() + pos_to_set.y = pos_to_set.y + 1.1 + entity.object:moveto(pos_to_set) + basepos.y=basepos.y+1.1 + end + ------------------------------------------------------------ + + dbg_mobf.fmovement_lvl3("MOBF: setting acceleration to: " + .. printpos(accel_to_set) .. " predicted_state: " + .. pos_state); + handled = + mgen_follow.set_acceleration(entity, + accel_to_set, + follow_speedup, + basepos) + end + end + + if handled == false then + dbg_mobf.fmovement_lvl1( + "MOBF: \t Unexpected or unhandled movement state: " + .. current_state) + local yaccel = environment.get_default_gravity(basepos, + entity.environment.media, + entity.data.movement.canfly) + --entity.object:setvelocity({x=0,y=0,z=0}) + entity.object:setacceleration({x=0,y=yaccel,z=0}) + end + mgen_follow.update_animation(entity, "following") + --nothing to do + elseif height_distance ~= nil and math.abs(height_distance) > 0.1 then + mgen_follow.set_acceleration(entity, + { x=0,y=(height_distance*-0.2),z=0}, + follow_speedup, + basepos) + mgen_follow.update_animation(entity, "following") + --we're next to target stop movement + else + local yaccel = environment.get_default_gravity(basepos, + entity.environment.media, + entity.data.movement.canfly) + + if entity.dynamic_data.movement.was_moving_last_step == true or + current_accel.Y ~= yaccel then + + dbg_mobf.fmovement_lvl3("MOBF: next to target") + entity.object:setvelocity({x=0,y=0,z=0}) + entity.object:setacceleration({x=0,y=yaccel,z=0}) + entity.dynamic_data.movement.last_next_to_target = now + mgen_follow.update_animation(entity, "ntt") + end + end + + else + --TODO evaluate if this is an error case + end +end + +------------------------------------------------------------------------------- +-- name: update_animation() +-- +--! @brief update animation according to the follow movegen substate +--! @memberof mgen_follow +--! @public +------------------------------------------------------------------------------- +function mgen_follow.update_animation(entity, anim_state) + + -- no need to change + if anim_state == entity.dynamic_data.movement.anim_selected then + return + end + + -- check if there's a animation specified for stand in this state + local statename, state = entity:get_state() + + if anim_state == "following" then + entity.dynamic_data.movement.anim_selected = "following" + + if state.animation_walk ~= nil then + graphics.set_animation(entity, state.animation_walk) + elseif state.animation ~= nil then + graphics.set_animation(entity, state.animation) + end + elseif anim_state == "ntt" then + entity.dynamic_data.movement.anim_selected = "ntt" + + if state.animation_next_to_target ~= nil then + graphics.set_animation(entity, state.animation_next_to_target) + end + end +end + +------------------------------------------------------------------------------- +-- name: next_block_ok() +-- +--! @brief check quality of next block +--! @memberof mgen_follow +--! @public +------------------------------------------------------------------------------- +function mgen_follow.next_block_ok(entity,pos,acceleration,velocity) + local current_velocity = velocity + + if current_velocity == nil then + current_velocity = entity.object:getvelocity() + end + + local predicted_pos = movement_generic.predict_next_block(pos,current_velocity,acceleration) + + local quality = environment.pos_quality(predicted_pos,entity) + + return ( + (quality.media_quality == MQ_IN_MEDIA) and + (quality.level_quality == LQ_OK) and + ( + (quality.surface_quality_min == Q_UNKNOWN) or + (quality.surface_quality_min >= SQ_WRONG) + ) + ) +end + +------------------------------------------------------------------------------- +-- name: initialize() +-- +--! @brief initialize movement generator +--! @memberof mgen_follow +--! @public +------------------------------------------------------------------------------- +function mgen_follow.initialize(entity,now) + --intentionally empty +end + +------------------------------------------------------------------------------- +-- name: init_dynamic_data(entity,now) +-- +--! @brief initialize dynamic data required by movement generator +--! @memberof mgen_follow +--! @public +-- +--! @param entity mob to initialize dynamic data +--! @param now current time +------------------------------------------------------------------------------- +function mgen_follow.init_dynamic_data(entity,now) + + local pos = entity.object:getpos() + + + local data = { + target = nil, + guardspawnpoint = false, + max_distance = entity.data.movement.max_distance, + invalid_env_count = 0, + follow_speedup = true, + } + + if entity.data.movement.guardspawnpoint ~= nil and + entity.data.movement.guardspawnpoint then + dbg_mobf.fmovement_lvl3("MOBF: setting guard point to: " .. printpos(entity.dynamic_data.spawning.spawnpoint)) + data.guardspawnpoint = true + end + + if entity.data.movement.teleportdelay~= nil then + data.last_next_to_target = now + data.teleportsupport = true + end + + entity.dynamic_data.movement = data +end + +------------------------------------------------------------------------------- +-- name: checkspeed(entity) +-- +--! @brief check if mobs speed is within it's limits and correct if necessary +--! @memberof mgen_follow +--! @private +-- +--! @param entity mob to initialize dynamic data +------------------------------------------------------------------------------- +function mgen_follow.checkspeed(entity) + + local current_velocity = entity.object:getvelocity() + + local xzspeed = + mobf_calc_scalar_speed(current_velocity.x,current_velocity.z) + + if (xzspeed > entity.data.movement.max_speed) then + + local direction = mobf_calc_yaw(current_velocity.x, + current_velocity.z) + + --reduce speed to 90% of current speed + local new_speed = mobf_calc_vector_components(direction,xzspeed*0.9) + + local current_accel = entity.object:getacceleration() + + new_speed.y = current_velocity.y + entity.object:setvelocity(new_speed) + entity.object:setacceleration({x=0,y=current_accel.y,z=0}) + + return true + end + + return false +end + +------------------------------------------------------------------------------- +-- name: set_acceleration(entity,accel,speedup) +-- +--! @brief apply acceleration to entity +--! @memberof mgen_follow +--! @private +-- +--! @param entity mob to apply to +--! @param accel acceleration to set +--! @param speedup speedup factor +--! @param pos current position +------------------------------------------------------------------------------- +function mgen_follow.set_acceleration(entity,accel,speedup,pos) + + accel.x = accel.x*speedup.x + accel.z = accel.z*speedup.z + + if entity.data.movement.canfly then + accel.y = accel.y*speedup.y + end + + if mgen_follow.next_block_ok(entity,pos,accel) then + dbg_mobf.fmovement_lvl3("MOBF: setting acceleration to: " .. printpos(accel)); + entity.object:setacceleration(accel) + return true + elseif mgen_follow.next_block_ok(entity,pos,{x=0,y=0,z=0}) then + accel = {x=0,y=0,z=0} + dbg_mobf.fmovement_lvl3("MOBF: setting acceleration to: " .. printpos(accel)); + entity.object:setacceleration(accel) + return true + else + local current_velocity = entity.object:getvelocity() + current_velocity.y = 0 + + if mgen_follow.next_block_ok(entity,pos,{x=0,y=0,z=0},current_velocity) then + accel = {x=0,y=0,z=0} + entity.object:setvelocity(current_velocity) + entity.object:setacceleration(accel) + return true + end + end + + dbg_mobf.fmovement_lvl1( + "MOBF: \t acceleration " .. printpos(accel) .. + " would result in invalid position not applying!") + + return false +end + +------------------------------------------------------------------------------- +-- name: set_target(entity, target, follow_speedup, max_distance) +-- +--! @brief set target for movgen +--! @memberof mgen_follow +-- +--! @param entity mob to apply to +--! @param target to set +--! @param follow_speedup --unused here +--! @param max_distance maximum distance to target to be tried to reach +------------------------------------------------------------------------------- +function mgen_follow.set_target(entity,target, follow_speedup, max_distance) + entity.dynamic_data.movement.target = target + entity.dynamic_data.movement.max_distance = max_distance + return true +end + +--register this movement generator +registerMovementGen(mgen_follow.name,mgen_follow) \ No newline at end of file diff --git a/mods/mob_engines/mobf/mgen_jordan4ibanez/mgen_jordan4ibanez.lua b/mods/mob_engines/mobf/mgen_jordan4ibanez/mgen_jordan4ibanez.lua new file mode 100644 index 00000000..cda9b975 --- /dev/null +++ b/mods/mob_engines/mobf/mgen_jordan4ibanez/mgen_jordan4ibanez.lua @@ -0,0 +1,158 @@ +------------------------------------------------------------------------------- +-- 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 main_follow.lua +--! @brief component containing a movement generator based uppon jordan4ibanez code +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +--! @defgroup mgen_jordan4ibanez MGEN: a velocity based movement generator +--! @brief A movement generator creating simple random movement +--! @ingroup framework_int +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @class mgen_jordan4ibanez +--! @brief a movement generator trying to follow or reach a target +--!@} + +mgen_jordan4ibanez = {} + +--! @brief chillaxin_speed +--! @memberof mgen_jordan4ibanez +mgen_jordan4ibanez.chillaxin_speed = 0.1 + + +--! @brief movement generator identifier +--! @memberof mgen_jordan4ibanez +mgen_jordan4ibanez.name = "jordan4ibanez_mov_gen" + +------------------------------------------------------------------------------- +-- name: callback(entity,now) +-- +--! @brief main callback to make a mob follow its target +--! @memberof mgen_jordan4ibanez +-- +--! @param entity mob to generate movement for +--! @param now current time +------------------------------------------------------------------------------- +function mgen_jordan4ibanez.callback(entity,now) + + --update timers + entity.dynamic_data.movement.timer = entity.dynamic_data.movement.timer + 0.01 + entity.dynamic_data.movement.turn_timer = entity.dynamic_data.movement.turn_timer + 0.01 + entity.dynamic_data.movement.jump_timer = entity.dynamic_data.movement.jump_timer + 0.01 + entity.dynamic_data.movement.door_timer = entity.dynamic_data.movement.door_timer + 0.01 + + + if entity.dynamic_data.movement.direction ~= nil then + entity.object:setvelocity({x=entity.dynamic_data.movement.direction.x*mgen_jordan4ibanez.chillaxin_speed, + y=entity.object:getvelocity().y, + z=entity.dynamic_data.movement.direction.z*mgen_jordan4ibanez.chillaxin_speed}) + end + + if entity.dynamic_data.movement.turn_timer > math.random(1,4) then + entity.dynamic_data.movement.yaw = 360 * math.random() + graphics.setyaw(entity, entity.dynamic_data.movement.yaw) + entity.dynamic_data.movement.turn_timer = 0 + entity.dynamic_data.movement.direction = {x = math.sin(entity.dynamic_data.movement.yaw)*-1, + y = -10, + z = math.cos(entity.dynamic_data.movement.yaw)} + --entity.object:setvelocity({x=entity.dynamic_data.movement.direction.x,y=entity.object:getvelocity().y,z=entity.dynamic_data.movement.direction.z}) + --entity.object:setacceleration(entity.dynamic_data.movement.direction) + end + + --TODO update animation + + --open a door [alpha] + if entity.dynamic_data.movement.direction ~= nil then + if entity.dynamic_data.movement.door_timer > 2 then + local is_a_door = minetest.get_node({x=entity.object:getpos().x + entity.dynamic_data.movement.direction.x, + y=entity.object:getpos().y,z=entity.object:getpos(). + z + entity.dynamic_data.movement.direction.z}).name + if is_a_door == "doors:door_wood_t_1" then + minetest.punch_node({x=entity.object:getpos().x + entity.dynamic_data.movement.direction.x, + y=entity.object:getpos().y-1, + z=entity.object:getpos().z + entity.dynamic_data.movement.direction.z}) + entity.dynamic_data.movement.door_timer = 0 + end + local is_in_door = minetest.get_node(entity.object:getpos()).name + if is_in_door == "doors:door_wood_t_1" then + minetest.punch_node(entity.object:getpos()) + end + end + end + + --jump + if entity.dynamic_data.movement.direction ~= nil then + if entity.dynamic_data.movement.jump_timer > 0.3 then + if minetest.registered_nodes[minetest.get_node({x=entity.object:getpos().x + entity.dynamic_data.movement.direction.x, + y=entity.object:getpos().y-1, + z=entity.object:getpos().z + entity.dynamic_data.movement.direction.z}).name].walkable then + entity.object:setvelocity({x=entity.object:getvelocity().x,y=5,z=entity.object:getvelocity().z}) + entity.dynamic_data.movement.jump_timer = 0 + end + end + end + +end + +------------------------------------------------------------------------------- +-- name: initialize() +-- +--! @brief initialize movement generator +--! @memberof mgen_jordan4ibanez +--! @public +------------------------------------------------------------------------------- +function mgen_jordan4ibanez.initialize(entity,now) +--intentionaly doing nothing this function is for symmetry reasons only +end + +------------------------------------------------------------------------------- +-- name: init_dynamic_data(entity,now) +-- +--! @brief initialize dynamic data required by movement generator +--! @memberof mgen_jordan4ibanez +--! @public +-- +--! @param entity mob to initialize dynamic data +--! @param now current time +------------------------------------------------------------------------------- +function mgen_jordan4ibanez.init_dynamic_data(entity,now) + + local data = { + timer = 0, + turn_timer = 0, + jump_timer = 0, + door_timer = 0, + direction = nil, + yaw = nil, + } + + entity.dynamic_data.movement = data +end + +------------------------------------------------------------------------------- +-- name: set_target(entity, target, follow_speedup, max_distance) +-- +--! @brief set target for movgen +--! @memberof mgen_jordan4ibanez +-- +--! @param entity mob to apply to --unused here +--! @param target to set --unused here +--! @param follow_speedup --unused here +--! @param max_distance --unused here +------------------------------------------------------------------------------- +function mgen_jordan4ibanez.set_target(entity,target) + return false +end + + +--register this movement generator +registerMovementGen(mgen_jordan4ibanez.name,mgen_jordan4ibanez) \ No newline at end of file diff --git a/mods/mob_engines/mobf/mgen_pathbased/main.lua b/mods/mob_engines/mobf/mgen_pathbased/main.lua new file mode 100644 index 00000000..a018ae4e --- /dev/null +++ b/mods/mob_engines/mobf/mgen_pathbased/main.lua @@ -0,0 +1,359 @@ +------------------------------------------------------------------------------- +-- 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 path_based_movement_gen.lua +--! @brief component containing a path based movement generator +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +--! @defgroup mgen_path_based MGEN: Path based movement generator +--! @ingroup framework_int +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @class p_mov_gen +--! @brief a movement generator evaluating a path to a target and following it +--!@} +p_mov_gen = {} +p_mov_gen.max_waypoint_distance = 0.5 + + + +--! @brief movement generator identifier +--! @memberof p_mov_gen +p_mov_gen.name = "mgen_path" + +------------------------------------------------------------------------------- +-- name: callback(entity,now) +-- +--! @brief path based movement generator callback +--! @memberof p_mov_gen +-- +-- param1: mob to do movement +-- param2: current time +-- retval: - +------------------------------------------------------------------------------- +function p_mov_gen.callback(entity,now,dstep) + + mobf_assert_backtrace(entity ~= nil) + mobf_assert_backtrace(entity.dynamic_data ~= nil) + mobf_assert_backtrace(entity.dynamic_data.p_movement ~= nil) + + if entity.dynamic_data.p_movement.eta ~= nil then + if now < entity.dynamic_data.p_movement.eta then + return + end + end + + local current_pos = entity.object:getpos() + local handled = false + + if entity.dynamic_data.p_movement.path == nil then + dbg_mobf.path_mov_lvl1( + "MOBF: path movement but mo path set!!") + return + end + + + local max_distance = p_mov_gen.max_waypoint_distance + + mobf_assert_backtrace(entity.dynamic_data.p_movement.next_path_index ~= nil) + mobf_assert_backtrace(max_distance ~= nil) + + --check if target is reached + if p_mov_gen.distance_to_next_point(entity,current_pos) + < max_distance then + dbg_mobf.path_mov_lvl1("MOBF: pathmov next to next point switching target") + local update_target = true + + if entity.dynamic_data.p_movement.waypoint_stop then + entity.object:setvelocity({x=0,y=0,z=0}) + end + + --return to begining of path + if entity.dynamic_data.p_movement.next_path_index + == #entity.dynamic_data.p_movement.path then + + if entity.dynamic_data.p_movement.cycle_path or + (entity.dynamic_data.p_movement.cycle_path == nil and + entity.data.patrol ~= nil and + entity.data.patrol.cycle_path) then + --0 is correct as it's incremented by one later + entity.dynamic_data.p_movement.next_path_index = 0 + else + if entity.dynamic_data.p_movement.HANDLER_end_of_path ~= nil + and type(entity.dynamic_data.p_movement.HANDLER_end_of_path) == "function" then + entity.dynamic_data.p_movement.HANDLER_end_of_path(entity) + end + dbg_mobf.path_mov_lvl1("MOBF: cycle not set not updating point") + update_target = false + handled = true + end + end + + if update_target then + mobf_assert_backtrace(entity.dynamic_data.p_movement.path ~= nil) + entity.dynamic_data.p_movement.next_path_index = + entity.dynamic_data.p_movement.next_path_index + 1 + + entity.dynamic_data.movement.target = + entity.dynamic_data.p_movement.path + [entity.dynamic_data.p_movement.next_path_index] + + dbg_mobf.path_mov_lvl1("MOBF: (1) setting new target to index: " .. + entity.dynamic_data.p_movement.next_path_index .. " pos: " .. + printpos(entity.dynamic_data.movement.target)) + handled = true + end + end + + if not handled and + entity.dynamic_data.movement.target == nil then + mobf_assert_backtrace(entity.dynamic_data.p_movement.path ~= nil) + + entity.dynamic_data.movement.target = + entity.dynamic_data.p_movement.path + [entity.dynamic_data.p_movement.next_path_index] + + dbg_mobf.path_mov_lvl1("MOBF: (2) setting new target to index: " .. + entity.dynamic_data.p_movement.next_path_index .. " pos: " .. + printpos(entity.dynamic_data.movement.target)) + end + + mgen_follow.callback(entity,now) +end + + +------------------------------------------------------------------------------- +-- name: distance_to_next_point(entity) +-- +--! @brief get distance to next target point (2d only) +--! @memberof p_mov_gen +--! @private +-- +--! @param entity mob to check +--! @param current_pos position mob is atm +-- +--! @retval distance +------------------------------------------------------------------------------- +function p_mov_gen.distance_to_next_point(entity,current_pos) + local index = entity.dynamic_data.p_movement.next_path_index + mobf_assert_backtrace(entity.dynamic_data.p_movement.path ~= nil) + mobf_assert_backtrace(index <= #entity.dynamic_data.p_movement.path) + return mobf_calc_distance_2d(current_pos, + entity.dynamic_data.p_movement.path[index]) +end + +------------------------------------------------------------------------------- +-- name: init_dynamic_data(entity,now) +-- +--! @brief initialize dynamic data required by movement generator +--! @memberof p_mov_gen +-- +--! @param entity to initialize +--! @param now current time +--! @param restored_data data restored on activate +------------------------------------------------------------------------------- +function p_mov_gen.init_dynamic_data(entity,now,restored_data) + + local pos = entity.object:getpos() + + local data = { + path = nil, + eta = nil, + last_move_stop = now, + next_path_index = 1, + force_target = nil, + pathowner = nil, + pathname = nil, + waypoint_stop = true, + } + + if restored_data ~= nil and + type(restored_data) == "table" then + dbg_mobf.path_mov_lvl3( + "MOBF: path movement reading stored data: " .. dump(restored_data)) + if restored_data.pathowner ~= nil and + restored_data.pathname ~= nil then + data.pathowner = restored_data.pathowner + data.pathname = restored_data.pathname + + data.path = mobf_path.getpoints(data.pathowner,data.pathname) + dbg_mobf.path_mov_lvl3( + "MOBF: path movement restored points: " .. dump(data.path)) + end + + if restored_data.pathindex ~= nil and + type(restored_data.pathindex) == "number" and + restored_data.pathindex > 0 and + data.path ~= nil and + restored_data.pathindex < #data.path then + data.next_path_index = restored_data.pathindex + end + end + + entity.dynamic_data.p_movement = data + + mgen_follow.init_dynamic_data(entity,now) + + entity.dynamic_data.movement.follow_speedup = false +end + +------------------------------------------------------------------------------- +-- name: set_path(entity,path) +-- +--! @brief set target for movgen +--! @memberof p_mov_gen +--! @public +-- +--! @param entity mob to apply to +--! @param path to set +--! @param enable_speedup shall follow speedup be applied to path movement? +------------------------------------------------------------------------------- +function p_mov_gen.set_path(entity,path, enable_speedup) + mobf_assert_backtrace(entity.dynamic_data.p_movement ~= nil) + if path ~= nil then + entity.dynamic_data.p_movement.next_path_index = 1 + entity.dynamic_data.movement.max_distance = + p_mov_gen.max_waypoint_distance + entity.dynamic_data.p_movement.path = path + + --a valid path has at least 2 positions + mobf_assert_backtrace(#entity.dynamic_data.p_movement.path > 1) + entity.dynamic_data.movement.target = + entity.dynamic_data.p_movement.path[2] + entity.dynamic_data.movement.follow_speedup = enable_speedup + return true + else + entity.dynamic_data.p_movement.next_path_index = nil + entity.dynamic_data.movement.max_distance = nil + entity.dynamic_data.p_movement.path = nil + entity.dynamic_data.movement.target = nil + entity.dynamic_data.movement.follow_speedup = nil + return false + end +end + +------------------------------------------------------------------------------- +-- name: set_cycle_path(entity,value) +-- +--! @brief set state of path cycle mechanism +--! @memberof p_mov_gen +--! @public +-- +--! @param entity mob to apply to +--! @param value to set true/false/nil(mob global default) +------------------------------------------------------------------------------- +function p_mov_gen.set_cycle_path(entity,value) + mobf_assert_backtrace(entity.dynamic_data.p_movement ~= nil) + entity.dynamic_data.p_movement.cycle_path = value +end + +------------------------------------------------------------------------------- +-- name: set_end_of_path_handler(entity,handler) +-- +--! @brief set handler to call for non cyclic paths if final target is reached +--! @memberof p_mov_gen +--! @public +-- +--! @param entity mob to apply to +--! @param handler to call at final target +------------------------------------------------------------------------------- +function p_mov_gen.set_end_of_path_handler(entity,handler) + entity.dynamic_data.p_movement.HANDLER_end_of_path = handler +end + +------------------------------------------------------------------------------- +-- name: set_target(entity, target, follow_speedup, max_distance) +-- +--! @brief set target for movgen +--! @memberof p_mov_gen +--! @public +-- +--! @param entity mob to apply to +--! @param target to set +--! @param follow_speedup use follow speedup to reach target +--! @param max_distance --unused here +------------------------------------------------------------------------------- +function p_mov_gen.set_target(entity, target, follow_speedup, max_distance) + mobf_assert_backtrace(target ~= nil) + + local current_pos = entity.getbasepos(entity) + local targetpos = nil + + if not mobf_is_pos(target) then + if target:is_player() then + targetpos = target:getpos() + targetpos.y = targetpos.y +0.5 + else + if type(target.getbasepos) == "function" then + targetpos = target.getbasepos(target) + else + targetpos = target:getpos() + end + end + else + targetpos = target + end + + if targetpos == nil then + return false + end + + if entity.dynamic_data.p_movement.lasttargetpos ~= nil then + if mobf_pos_is_same(entity.dynamic_data.p_movement.lasttargetpos, + targetpos) then + return true + end + end + + entity.dynamic_data.p_movement.lasttargetpos = targetpos + + entity.dynamic_data.p_movement.path = nil + entity.dynamic_data.p_movement.next_path_index = 1 + + --on target mode max distance is always 0.5 + entity.dynamic_data.movement.max_distance = p_mov_gen.max_waypoint_distance + + --try to find path on our own + if not mobf_get_world_setting("mobf_disable_pathfinding") then + entity.dynamic_data.p_movement.path = + mobf_path.find_path(current_pos,targetpos,5,1,1,nil) + else + entity.dynamic_data.p_movement.path = nil + end + + if entity.dynamic_data.p_movement.path ~= nil then + --a valid path has at least 2 positions + mobf_assert_backtrace(#entity.dynamic_data.p_movement.path > 1) + entity.dynamic_data.movement.target = + entity.dynamic_data.p_movement.path[2] + entity.dynamic_data.movement.follow_speedup = follow_speedup + return true + end + + + if entity.dynamic_data.p_movement.path == nil then + minetest.log(LOGLEVEL_INFO, + "MOBF: no pathfinding support/ no path found directly setting targetpos as path") + + entity.dynamic_data.p_movement.path = {} + + table.insert(entity.dynamic_data.p_movement.path,targetpos) + entity.dynamic_data.movement.target = + entity.dynamic_data.p_movement.path[1] + entity.dynamic_data.movement.follow_speedup = follow_speedup + return true + end + + return false +end + +--register this movement generator +registerMovementGen(p_mov_gen.name,p_mov_gen) diff --git a/mods/mob_engines/mobf/mgen_probab/direction_control.lua b/mods/mob_engines/mobf/mgen_probab/direction_control.lua new file mode 100644 index 00000000..e4b36948 --- /dev/null +++ b/mods/mob_engines/mobf/mgen_probab/direction_control.lua @@ -0,0 +1,608 @@ +------------------------------------------------------------------------------- +-- 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 direction_control.lua +--! @brief functions for direction control in probabilistic movement gen +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +--! @ingroup mgen_probab +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @class direction_control +--! @brief functions for direction control in probabilistic movement gen +direction_control = {} +--!@} + +------------------------------------------------------------------------------- +-- name: changeaccel(pos,entity,velocity) +-- +--! @brief find a suitable new acceleration for mob +--! @memberof direction_control +--! @private +-- +--! @param pos current position +--! @param entity mob to get acceleration for +--! @param current_velocity current velocity +--! @return {{ x/y/z accel} + jump flag really? +------------------------------------------------------------------------------- +function direction_control.changeaccel(pos,entity,current_velocity) + + local maxtries = 5 + local old_quality = environment.pos_quality(pos,entity) + + local new_accel = + direction_control.get_random_acceleration( + entity.data.movement.min_accel, + entity.data.movement.max_accel,graphics.getyaw(entity),0) + local pos_predicted = + movement_generic.predict_next_block(pos,current_velocity,new_accel) + + local new_quality = environment.pos_quality(pos_predicted,entity) + local prefered_state = + environment.evaluate_state( new_quality, + old_quality, + MQ_IN_MEDIA, + nil, + GQ_FULL, + SQ_POSSIBLE, + SQ_OK) + + while not prefered_state do + dbg_mobf.pmovement_lvl1("MOBF: predicted pos " .. printpos(pos_predicted) + .. " isn't perfect " .. maxtries .. " tries left, state: " + .. new_quality.tostring(new_quality)) + + --don't loop forever get to save mode and try next time + if maxtries <= 0 then + dbg_mobf.pmovement_lvl1( + "MOBF: Aborting acceleration finding for this cycle due to max retries") + if state == "collision_jumpable" then + dbg_mobf.movement_lvl1("Returning " + ..printpos(new_accel).." as new accel as mob may jump") + return new_accel + end + + dbg_mobf.pmovement_lvl1( + "MOBF: Didn't find a suitable acceleration stopping movement: " + .. entity.data.name .. printpos(pos)) + entity.object:setvelocity({x=0,y=0,z=0}) + entity.dynamic_data.movement.started = false + --don't slow down mob + return { x=0, + y=0, + z=0 } + end + + local probab = math.random() + + --accept possible surface in rare cases + if probab < 0.3 then + local acceptable_state = + environment.evaluate_state(new_quality, + nil, + MQ_IN_MEDIA, + GQ_PARTIAL, + nil, + SQ_WRONG, + SQ_POSSIBLE) + + if acceptable_state then + return new_accel + end + end + + --accept possible surface in rare cases + if probab < 0.2 then + local acceptable_state = + environment.evaluate_state(new_quality, + nil, + MQ_IN_MEDIA, + nil, + GQ_FULL, + SQ_WRONG, + SQ_POSSIBLE) + + if acceptable_state then + return new_accel + end + end + + --try another acceleration + new_accel = + direction_control.get_random_acceleration( + entity.data.movement.min_accel, + entity.data.movement.max_accel, + graphics.getyaw(entity),1.57) + pos_predicted = + movement_generic.predict_next_block(pos,current_velocity,new_accel) + + + local prefered_state = + environment.evaluate_state( new_quality, + old_quality, + MQ_IN_MEDIA, + nil, + GQ_FULL, + SQ_POSSIBLE, + SQ_OK) + maxtries = maxtries -1 + end + + return new_accel + +end + +------------------------------------------------------------------------------- +-- name: get_random_acceleration(minaccel,maxaccel,current_yaw, minrotation) +-- +--! @brief get a random x/z acceleration within a specified acceleration range +--! @memberof direction_control +--! @private +-- +--! @param minaccel minimum acceleration to use +--! @param maxaccel maximum acceleration +--! @param current_yaw current orientation of mob +--! @param minrotation minimum rotation to perform +--! @return x/y/z acceleration +------------------------------------------------------------------------------- +function direction_control.get_random_acceleration( + minaccel,maxaccel,current_yaw, minrotation) + + local direction = 1 + if math.random() < 0.5 then + direction = -1 + end + + --calc random absolute value + local rand_accel = (math.random() * (maxaccel - minaccel)) + minaccel + + local orientation_delta = mobf_gauss(math.pi/6,1/2) + + --calculate new acceleration + local new_direction = + current_yaw + ((minrotation + orientation_delta) * direction) + + local new_accel = mobf_calc_vector_components(new_direction,rand_accel) + + dbg_mobf.pmovement_lvl3(" new direction: " .. new_direction .. + " old direction: " .. current_yaw .. + " new accel: " .. printpos(new_accel) .. + " orientation_delta: " .. orientation_delta) + + return new_accel +end + + + +------------------------------------------------------------------------------- +-- name: precheck_movement(entity,movement_state,pos_predicted,pos_predicted_quality) +-- +--! @brief check if x/z movement results in invalid position and change +-- movement if required +--! @memberof direction_control +-- +--! @param entity mob to generate movement +--! @param movement_state current state of movement +--! @param pos_predicted position mob will be next +--! @param pos_predicted_quality quality of predicted position +--! @return movement_state is changed! +------------------------------------------------------------------------------- +function direction_control.precheck_movement( + entity,movement_state,pos_predicted,pos_predicted_quality) + + if movement_state.changed then + --someone already changed something + return + end + + local prefered_quality = + environment.evaluate_state(pos_predicted_quality, LT_GOOD_POS) + + -- ok predicted pos isn't as good as we'd wanted it to be let's find out why + if not prefered_quality then + + local mob_is_safe = + environment.evaluate_state(pos_predicted_quality, LT_SAFE_POS) + + if movement_state.current_quality == nil then + movement_state.current_quality = environment.pos_quality( + movement_state.basepos, + entity + ) + end + + if environment.compare_state( + movement_state.current_quality, + pos_predicted_quality) > 0 + and + pos_predicted_quality.media_quality == MQ_IN_MEDIA then + --movement state is better than old one so we're fine + return + end + + local walking_at_edge = + environment.evaluate_state(pos_predicted_quality, LT_SAFE_EDGE_POS) + + if walking_at_edge then + --mob center still on ground but at worst walking at edge, do nothing + return + end + + if (pos_predicted_quality.geometry_quality == GQ_NONE) then + dbg_mobf.pmovement_lvl2("MOBF: mob " .. entity.data.name .. " is dropping") + return + end + + local drop_pending = + (pos_predicted_quality.geometry_quality <= GQ_PARTIAL and + pos_predicted_quality.center_geometry_quality <= GQ_NONE) or + pos_predicted_quality.surface_quality_min <= SQ_WATER + + if drop_pending then + dbg_mobf.pmovement_lvl2( + "MOBF: mob " .. entity.data.name + .. " is going to walk on water or drop") + + local new_pos = + environment.get_pos_same_level(movement_state.basepos,1,entity, + function(quality) + return environment.evaluate_state(quality,LT_SAFE_EDGE_POS) + end + ) + + if new_pos == nil then + dbg_mobf.pmovement_lvl2( + "MOBF: mob " .. entity.data.name .. " trying edge pos") + new_pos = environment.get_pos_same_level(movement_state.basepos,1,entity, + function(quality) + return environment.evaluate_state(quality,LT_EDGE_POS) + end + ) + end + + if new_pos == nil then + dbg_mobf.pmovement_lvl2( + "MOBF: mob " .. entity.data.name .. " trying relaxed surface") + new_pos = environment.get_pos_same_level(movement_state.basepos,1,entity, + function(quality) + return environment.evaluate_state(quality, + LT_EDGE_POS_GOOD_CENTER) + end + ) + end + + if new_pos == nil then + dbg_mobf.pmovement_lvl2( + "MOBF: mob " .. entity.data.name + .. " trying even more relaxed surface") + new_pos = environment.get_pos_same_level(movement_state.basepos,1,entity, + function(quality) + return environment.evaluate_state(quality, + LT_EDGE_POS_POSSIBLE_CENTER) + end + ) + end + + if new_pos ~= nil then + dbg_mobf.pmovement_lvl2( + "MOBF: trying to redirect to safe position .. " .. printpos(new_pos)) + local speedfactor = 0.1 + local speed_found = false + repeat + movement_state.accel_to_set = + movement_generic.get_accel_to(new_pos, entity, nil, + entity.data.movement.max_accel*speedfactor) + + local next_pos = + movement_generic.predict_next_block( + movement_state.basepos, + movement_state.current_velocity, + movement_state.accel_to_set) + + local next_quality = environment.pos_quality( + next_pos, + entity + ) + + if environment.evaluate_state(next_quality, + LT_EDGE_POS_POSSIBLE_CENTER) then + speed_found = true + end + + speedfactor = speedfactor +0.1 + until ( speedfactor > 1 or speed_found) + + -- try if our state would at least keep same if we walk towards + -- the good pos + if not speed_found then + + dbg_mobf.pmovement_lvl2("MOBF: trying min speed towards good pos") + movement_state.accel_to_set = + movement_generic.get_accel_to(new_pos, entity, nil, + entity.data.movement.min_accel) + local next_pos = + movement_generic.predict_next_block( + movement_state.basepos, + movement_state.current_velocity, + movement_state.accel_to_set) + + local next_quality = environment.pos_quality( + next_pos, + entity + ) + + if ((mobf_calc_distance(next_pos,new_pos) < + (mobf_calc_distance(movement_state.basepos,new_pos))) and + environment.evaluate_state(next_quality, + LT_DROP_PENDING)) then + speed_found = true + end + end + + if speed_found then + dbg_mobf.pmovement_lvl2("MOBF: redirecting to safe position .. " + .. printpos(new_pos)) + movement_state.changed = true + return + end + end + + if new_pos == nil then + dbg_mobf.pmovement_lvl2("MOBF: no suitable pos found") + else + dbg_mobf.pmovement_lvl2("MOBF: didn't find a way to suitable pos") + end + + --no suitable pos found, if mob is safe atm just stop it + if mob_is_safe then + if movement_state.current_quality == GQ_FULL then + local targetpos = {x= movement_state.basepos.x, + y=movement_state.basepos.y, + z=movement_state.basepos.z} + + targetpos.x = targetpos.x - movement_state.current_velocity.x + targetpos.z = targetpos.z - movement_state.current_velocity.z + + movement_state.accel_to_set = + movement_generic.get_accel_to(targetpos, entity, nil, + entity.data.movement.min_accel) + dbg_mobf.pmovement_lvl2( + "MOBF: good pos, slowing down") + movement_state.changed = true + return + else --stop immediatlely + entity.object:setvelocity({x=0,y=0,z=0}) + movement_state.accel_to_set = {x=0,y=nil,z=0} + dbg_mobf.pmovement_lvl2( + "MOBF: stopping at safe pos") + movement_state.changed = true + return + end + end + + dbg_mobf.pmovement_lvl2("MOBF: mob " .. entity.data.name .. + " didn't find a way to fix drop trying random") + --make mgen change direction randomly + movement_state.force_change = true + return + end + + --check if mob is going to be somewhere where it can't be + if pos_predicted_quality.media_quality ~= MQ_IN_MEDIA then + dbg_mobf.pmovement_lvl2("MOBF: collision pending " + .. printpos(movement_state.basepos) .. "-->" + .. printpos(pos_predicted)) + + --try to find a better position at same level + local new_pos = + environment.get_suitable_pos_same_level(movement_state.basepos,1,entity) + + if new_pos == nil then + new_pos = + environment.get_suitable_pos_same_level( + movement_state.basepos,1,entity,true) + end + + --there is at least one direction to go + if new_pos ~= nil then + dbg_mobf.pmovement_lvl2("MOBF: mob " ..entity.data.name + .. " redirecting to:" .. printpos(new_pos)) + local new_predicted_state = nil + local new_predicted_pos = nil + for i=1,5,1 do + movement_state.accel_to_set = + movement_generic.get_accel_to(new_pos,entity) + --TODO check if acceleration is enough + new_predicted_pos = + movement_generic.predict_enter_next_block( entity, + movement_state.basepos, + movement_state.current_velocity, + movement_state.accel_to_set) + new_predicted_state = environment.pos_quality( + new_predicted_pos, + entity + ) + if new_predicted_state.media_quality == MQ_IN_MEDIA then + break + end + end + if new_predicted_state.media_quality ~= MQ_IN_MEDIA then + movement_state.accel_to_set = movement_state.current_acceleration + + dbg_mobf.pmovement_lvl2("MOBF: mob " ..entity.data.name + .. " acceleration not enough to avoid collision try to jump") + if math.random() < + ( entity.dynamic_data.movement.mpattern.jump_up * + PER_SECOND_CORRECTION_FACTOR) then + local upper_pos = { + x= pos_predicted.x, + y= pos_predicted.y +1, + z= pos_predicted.z + } + + local upper_quality = environment.pos_quality( + upper_pos, + entity + ) + + if environment.evaluate_state( upper_quality,LT_EDGE_POS) then + + entity.object:setvelocity( + {x=movement_state.current_velocity.x, + y=5, + z=movement_state.current_velocity.z}) + end + end + end + movement_state.changed = true + return + end + + --try to find a better position above + new_pos = environment.get_suitable_pos_same_level({ x=movement_state.basepos.x, + y=movement_state.basepos.y+1, + z=movement_state.basepos.z}, + 1,entity) + + if new_pos == nil then + new_pos = environment.get_suitable_pos_same_level({ x=movement_state.basepos.x, + y=movement_state.basepos.y+1, + z=movement_state.basepos.z}, + 1,entity) + end + + if new_pos ~= nil then + dbg_mobf.pmovement_lvl2("MOBF: mob " ..entity.data.name + .. " seems to be locked in, jumping to:" .. printpos(new_pos)) + + entity.object:setvelocity({x=0, + y=5.5, + z=0}) + movement_state.accel_to_set = movement_generic.get_accel_to(new_pos,entity) + movement_state.changed = true + return + end + + dbg_mobf.pmovement_lvl2("MOBF: mob " ..entity.data.name + .. " unable to fix collision try random") + --a collision is going to happen force change of direction + movement_state.force_change = true + return + end + + local suboptimal_surface = + environment.evaluate_state( pos_predicted_quality, + { old_state=nil, + min_media=MQ_IN_MEDIA, + min_geom=GQ_PARTIAL, + min_geom_center=nil, + min_min_surface=SQ_WRONG, + min_max_surface=SQ_POSSIBLE, + min_center_surface=nil }) + + if suboptimal_surface then + dbg_mobf.pmovement_lvl2( + "MOBF: suboptimal positiond detected trying to find better pos") + --try to find a better position at same level + local new_pos = + environment.get_suitable_pos_same_level( + movement_state.basepos,1,entity) + + if new_pos ~= nil then + dbg_mobf.pmovement_lvl2( + "MOBF: redirecting to better position .. " .. printpos(new_pos)) + movement_state.accel_to_set = movement_generic.get_accel_to(new_pos,entity) + movement_state.changed = true + return + else + -- pos isn't critical don't do anything + return + end + end + + local geom_ok = + environment.evaluate_state( pos_predicted_quality, + { old_state=nil, + min_media=MQ_IN_MEDIA, + min_geom=GQ_PARTIAL, + min_geom_center=nil, + min_min_surface=nil, + min_max_surface=nil, + min_center_surface=nil }) + if geom_ok and + pos_predicted_quality.surface_quality_max == SQ_WRONG then + dbg_mobf.pmovement_lvl2( + "MOBF: wrong surface detected trying to find better pos") + local new_pos = + environment.get_suitable_pos_same_level( + movement_state.basepos,1,entity) + + if new_pos == nil then + new_pos = + environment.get_suitable_pos_same_level( + movement_state.basepos,2,entity) + end + + if new_pos ~= nil then + dbg_mobf.pmovement_lvl2( + "MOBF: redirecting to better position .. " .. printpos(new_pos)) + movement_state.accel_to_set = + movement_generic.get_accel_to(new_pos,entity) + movement_state.changed = true + return + else + --try generic + movement_state.force_change = true + return + end + end + + dbg_mobf.pmovement_lvl2("MOBF: Unhandled suboptimal state:" + .. pos_predicted_quality.tostring(pos_predicted_quality)) + movement_state.force_change = true + end +end + +------------------------------------------------------------------------------- +-- name: random_movement_handler(entity,movement_state) +-- +--! @brief generate a random y-movement +--! @memberof direction_control +-- +--! @param entity mob to apply random jump +--! @param movement_state current movement state +--! @return movement_state is modified! +------------------------------------------------------------------------------- +function direction_control.random_movement_handler(entity,movement_state) + dbg_mobf.pmovement_lvl3("MOBF: random movement handler called") + local rand_value = math.random() + local max_value = + entity.dynamic_data.movement.mpattern.random_acceleration_change + * PER_SECOND_CORRECTION_FACTOR + if movement_state.changed == false and + (rand_value < max_value or + movement_state.force_change) then + + movement_state.accel_to_set = direction_control.changeaccel(movement_state.basepos, + entity,movement_state.current_velocity) + if movement_state.accel_to_set ~= nil then + --retain current y acceleration + movement_state.accel_to_set.y = movement_state.current_acceleration.y + movement_state.changed = true + end + dbg_mobf.pmovement_lvl1("MOBF: randomly changing speed from ".. + printpos(movement_state.current_acceleration).." to ".. + printpos(movement_state.accel_to_set)) + else + dbg_mobf.pmovement_lvl3("MOBF:" .. entity.data.name .. + " not changing speed random: " .. rand_value .." >= " .. max_value) + end +end \ No newline at end of file diff --git a/mods/mob_engines/mobf/mgen_probab/height_level_control.lua b/mods/mob_engines/mobf/mgen_probab/height_level_control.lua new file mode 100644 index 00000000..600a00d2 --- /dev/null +++ b/mods/mob_engines/mobf/mgen_probab/height_level_control.lua @@ -0,0 +1,292 @@ +------------------------------------------------------------------------------- +-- 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 height_level_control.lua +--! @brief component containing random drop features +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +--! @ingroup mgen_probab +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @class height_level_control +--! @brief Subcomponent of probabilistic movement generator containing height +--! level control and change functionality +height_level_control = {} + + +--!@} + +------------------------------------------------------------------------------- +-- name: calc_level_change_time(entity) +-- +--! @brief calculate time required to change one height level +--! @memberof height_level_control +--! @private +-- +--! @param entity mob to calculate change time +--! @param default_accel default accel for mob +--! @return time in seconds +------------------------------------------------------------------------------- +function height_level_control.calc_level_change_time(entity,default_accel) + local retval = 1 --default value + + --calculate a reasonable value to stop level change + if entity.data.movement.canfly == nil or + entity.data.movement.canfly == false then --case mob can't fly + return 0 + else + -- if it's a flying mob and left it's normal medium + if default_accel ~= 0 then + retval = 0 + else + retval = math.sqrt(2/entity.data.movement.min_accel) + end + end + + return retval +end + +------------------------------------------------------------------------------- +-- name: precheck_movement(entity,movement_state,pos_predicted,pos_predicted_quality) +-- +--! @brief check if there is a level change in progress that may +-- need to be stopped +--! @memberof height_level_control +-- +--! @param entity mob to check for level change +--! @param movement_state current state of movement +--! @param pos_predicted position the mob will be next +--! @param pos_predicted_quality quality of the next position +------------------------------------------------------------------------------- +function height_level_control.precheck_movement(entity,movement_state,pos_predicted,pos_predicted_quality) + + if entity.data.movement.canfly ~= nil and + entity.data.movement.canfly == true and + entity.dynamic_data.movement.changing_levels == true then + + local level_change_time = height_level_control.calc_level_change_time(entity,movement_state.default_y_accel) + + local time_completed = entity.dynamic_data.movement.ts_random_jump + level_change_time + + dbg_mobf.pmovement_lvl1("MOBF: ".. movement_state.now .. " " .. entity.data.name .. + " check complete level change " .. time_completed) + + if entity.dynamic_data.movement.changing_levels and + time_completed < movement_state.now then + + dbg_mobf.pmovement_lvl2("MOBF: ".. movement_state.now .. " " .. entity.data.name .. + " level change complete reestablishing default y acceleration " .. movement_state.default_y_accel) + entity.dynamic_data.movement.changing_levels = false + + movement_state.current_velocity.y = 0 + entity.object:setvelocity(movement_state.current_velocity) + + movement_state.accel_to_set = movement_state.current_acceleration + movement_state.accel_to_set.y = movement_state.default_y_accel + movement_state.changed = true + end + end + + --mob would fly/swim into height it shouldn't be + if movement_state.changed == false then + local invalid_pos_handled = false + + --mob would fly/swim into height it shouldn't be + if invalid_pos_handled == false and + pos_predicted_quality.level_quality == LQ_ABOVE then + + local min_y,max_y = environment.get_absolute_min_max_pos(entity.environment,movement_state.basepos) + + if (pos_predicted.y - max_y) > 10 then + movement_state.override_height_change_chance = 1 + else + movement_state.override_height_change_chance = (pos_predicted.y - max_y)/10 + end + + invalid_pos_handled = true + end + + --mob would fly/swim into height it shouldn't be + if invalid_pos_handled == false and + pos_predicted_quality.level_quality == LQ_BELOW then + + local min_y,max_y = environment.get_absolute_min_max_pos(entity.environment,movement_state.basepos) + + if (min_y - pos_predicted.y) > 10 then + movement_state.override_height_change_chance = 1 + else + movement_state.override_height_change_chance = (min_y - pos_predicted.y)/10 + end + end + end +end + +------------------------------------------------------------------------------- +-- name: random_jump_fly(entity,movement_state) +-- +--! @brief apply random jump for flying mobs +--! @memberof height_level_control +--! @private +-- +--! @param entity mob to apply random jump +--! @param movement_state current movement state +--! @return movement_state is modified! +------------------------------------------------------------------------------- +function height_level_control.random_jump_fly(entity,movement_state) + + --get some information + local accel_to_set = movement_state.current_acceleration + local current_state = environment.pos_is_ok(movement_state.basepos,entity) + + --find direction + local ydirection = 1 + + if current_state == "ok" then + if math.random() <= 0.5 then + ydirection = -1 + end + end + + if current_state == "above_limit" then + ydirection = -1 + end + + --state "below_limit" is already handled by initialization value + + + --prepare basic information about what may be afterwards + local targetpos = {x=movement_state.basepos.x,y=movement_state.basepos.y+ydirection,z=movement_state.basepos.z} + local target_state = environment.pos_is_ok(targetpos,entity); + + dbg_mobf.pmovement_lvl2("MOBF: " .. entity.data.name .." flying change y accel dir="..ydirection.." ypos="..movement_state.basepos.y.. + " min="..entity.environment.min_height_above_ground.. + " max="..entity.environment.max_height_above_ground.. + " below="..target_state) + + --really do level change + if (target_state == "ok") or + target_state == current_state then + + local min_y, max_y = environment.get_absolute_min_max_pos(entity.environment,movement_state.basepos) + + dbg_mobf.pmovement_lvl2("MOBF: check level borders current=".. movement_state.basepos.y .. + " min=" .. min_y .. + " max=" .. max_y) + + movement_state.accel_to_set.y = ydirection * entity.data.movement.min_accel + entity.dynamic_data.movement.ts_random_jump = movement_state.now + entity.dynamic_data.movement.changing_levels = true + dbg_mobf.pmovement_lvl2("MOBF: " .. entity.data.name .. " " .. movement_state.now .. " " .. + " changing level within borders: " .. movement_state.accel_to_set.y) + movement_state.changed = true + end + +end + +------------------------------------------------------------------------------- +-- name: random_jump_ground(entity,movement_state) +-- +--! @brief apply random jump for ground mobs +--! @memberof height_level_control +--! @private +-- +--! @param entity mob to apply random jump +--! @param movement_state current movement state +--! @return movement_state is a modification! +------------------------------------------------------------------------------- +function height_level_control.random_jump_ground(entity,movement_state) + + if movement_state.default_y_accel == 0 then + minetest.log(LOGLEVEL_ERROR,"MOBF BUG!!! ground mob can't have zero default acceleration!!!") + end + + local random_jump_time = 2 * (entity.dynamic_data.movement.mpattern.random_jump_initial_speed / + math.abs(movement_state.default_y_accel)) + + local next_jump = entity.dynamic_data.movement.ts_random_jump + + entity.dynamic_data.movement.mpattern.random_jump_delay + + random_jump_time + + dbg_mobf.movement_lvl2("MOBF: now=" .. movement_state.now .. " time of next jump=" .. next_jump .. + " | " .. random_jump_time .. " " .. entity.dynamic_data.movement.mpattern.random_jump_delay .. " " .. + entity.dynamic_data.movement.ts_random_jump .. " " .. movement_state.default_y_accel .. " " .. + entity.dynamic_data.movement.mpattern.random_jump_initial_speed) + + if movement_state.now > next_jump then + + local ground_distance = mobf_ground_distance(movement_state.basepos) + + + --we shouldn't be moving in y direction at that time so probably we are just dropping + --we can only jump while on solid ground! + if ground_distance <= 1 then + + local current_speed = entity.object:getvelocity(); + + dbg_mobf.pmovement_lvl2("MOBF: " .. entity.data.name .." doing random jump ".. + "speed: " .. entity.dynamic_data.movement.mpattern.random_jump_initial_speed .. + ": ",entity) + + current_speed.y = entity.dynamic_data.movement.mpattern.random_jump_initial_speed + + entity.object:setvelocity(current_speed) + + entity.dynamic_data.movement.ts_random_jump = movement_state.now + else + dbg_mobf.pmovement_lvl2("MOBF: " .. entity.data.name .. " Random jump but ground distance was:" .. ground_distance .. + " " ..movement_state.current_acceleration.y .. + " " ..movement_state.default_y_accel) + + --this is a workaround for a bug + --setting y acceleration to 0 for ground born mobs shouldn't be possible + if movement_state.current_acceleration.y == 0 then + movement_state.accel_to_set.x = movement_state.current_acceleration.x + movement_state.accel_to_set.y = movement_state.default_y_accel + movement_state.accel_to_set.z = movement_state.current_acceleration.z + movement_state.changed = true + end + end + end +end + +------------------------------------------------------------------------------- +-- name: random_movement_handler(entity,movement_state) +-- +--! @brief generate a random y-movement +--! @memberof height_level_control +-- +--! @param entity mob to apply random jump +--! @param movement_state current movement state +--! @return movement_state is a modified! +------------------------------------------------------------------------------- +function height_level_control.random_movement_handler(entity,movement_state) + --generate random y movement changes + if movement_state.changed == false and + entity.dynamic_data.movement.changing_levels == false and + (math.random() < entity.dynamic_data.movement.mpattern.random_jump_chance or + math.random() < movement_state.override_height_change_chance) then + + dbg_mobf.pmovement_lvl1("MOBF: Random jump chance for mob " .. entity.data.name .. " ".. + entity.dynamic_data.movement.ts_random_jump .. " ".. + entity.dynamic_data.movement.mpattern.random_jump_delay .. " " .. movement_state.now + ) + + local to_set + --flying/swiming mobs do level change on random jump chance + if entity.data.movement.canfly then + + height_level_control.random_jump_fly(entity,movement_state) + --ground mobs + else + height_level_control.random_jump_ground(entity,movement_state) + end + end +end diff --git a/mods/mob_engines/mobf/mgen_probab/main_probab.lua b/mods/mob_engines/mobf/mgen_probab/main_probab.lua new file mode 100644 index 00000000..c2ec0646 --- /dev/null +++ b/mods/mob_engines/mobf/mgen_probab/main_probab.lua @@ -0,0 +1,656 @@ +------------------------------------------------------------------------------- +-- 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 main_probab.lua +--! @brief component containing a probabilistic movement generator +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +-- +--! @defgroup mgen_probab MGEN: probabilistic movement generator +--! @brief A movement generator trying to create a random movement +--! @ingroup framework_int +--! @{ +-- +--! @defgroup mpatterns Movement patterns +--! @brief movement patterns used for probabilistic movement gen +-- +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + + +--factor used to adjust chances defined as x/per second to on_step frequency +PER_SECOND_CORRECTION_FACTOR = 0.25 + +--! @class movement_gen +--! @brief a probabilistic movement generator +movement_gen = {} + +--! @brief movement patterns known to probabilistic movement gen +mov_patterns_defined = {} + +--!@} + +dofile (mobf_modpath .. "/mgen_probab/movement_patterns/dont_move.lua") +dofile (mobf_modpath .. "/mgen_probab/movement_patterns/run_and_jump_low.lua") +dofile (mobf_modpath .. "/mgen_probab/movement_patterns/stop_and_go.lua") +dofile (mobf_modpath .. "/mgen_probab/movement_patterns/swim_pattern1.lua") +dofile (mobf_modpath .. "/mgen_probab/movement_patterns/swim_pattern2.lua") +dofile (mobf_modpath .. "/mgen_probab/movement_patterns/flight_pattern1.lua") + +dofile (mobf_modpath .. "/mgen_probab/height_level_control.lua") +dofile (mobf_modpath .. "/mgen_probab/direction_control.lua") + +--! @brief movement generator identifier +--! @memberof movement_gen +movement_gen.name = "probab_mov_gen" + +------------------------------------------------------------------------------- +-- name: register_pattern(pattern) +-- +--! @brief initialize movement generator +--! @memberof movement_gen +-- +--! @param pattern pattern to register +--! @return true/false +------------------------------------------------------------------------------- +function movement_gen.register_pattern(pattern) + core.log("action","\tregistering pattern "..pattern.name) + if mobf_rtd.movement_patterns[pattern.name] == nil then + mobf_rtd.movement_patterns[pattern.name] = pattern + return true + else + return false + end +end + + +------------------------------------------------------------------------------- +-- name: initialize(entity) +-- +--! @brief initialize movement generator +--! @memberof movement_gen +-- +------------------------------------------------------------------------------- +function movement_gen.initialize() + for index,value in ipairs(mov_patterns_defined) do + movement_gen.register_pattern(value) + end +end + +------------------------------------------------------------------------------- +-- name: callback(entity) +-- +--! @brief main movement generation function +--! @memberof movement_gen +--! @private +-- +--! @param entity mob to generate movement for +------------------------------------------------------------------------------- +function movement_gen.callback(entity) + mobf_assert_backtrace(entity ~= nil) + + if entity.dynamic_data == nil or + entity.dynamic_data.movement == nil then + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!!: >" ..entity.data.name .. + "< removed=" .. dump(entity.removed) .. " entity=" .. + tostring(entity) .. " probab movement callback") + return + end + + --initialize status datastructure + local movement_state = { + basepos = entity.getbasepos(entity), + default_y_accel = nil, + centerpos = entity.object:getpos(), + current_acceleration = nil, + current_velocity = entity.object:getvelocity(), + now = nil, + + override_height_change_chance = 0, + accel_to_set = nil, + force_change = false, + changed = false, + } + + + --------------------------------------------------------------------------- + --------------------------------------------------------------------------- + -- -- + -- CHECK MOB POSITION -- + -- -- + --------------------------------------------------------------------------- + --------------------------------------------------------------------------- + dbg_mobf.pmovement_lvl3("MOBF: position check for mob ".. entity.data.name .. + " "..printpos(movement_state.basepos)) + movement_state.default_y_accel = + environment.get_default_gravity(movement_state.basepos, + entity.environment.media, + entity.data.movement.canfly) + mobf_assert_backtrace(movement_state.default_y_accel ~= nil) + movement_state.now = mobf_get_current_time() + + + --check current position + --movement state is modified by this function + local retval = movement_gen.fix_current_pos(entity, movement_state) + + --mob has been removed due to unfixable problems + if retval.abort_processing then + return + end + + --read additional information required for further processing + movement_state.current_acceleration = entity.object:getacceleration() + + if movement_state.changed then + minetest.log(LOGLEVEL_WARNING, + "MOBF:position got fixed ".. entity.data.name) + end + + --------------------------------------------------------------------------- + --------------------------------------------------------------------------- + -- -- + -- CHECK MOB MOVEMENT ITSELF -- + -- -- + --------------------------------------------------------------------------- + --------------------------------------------------------------------------- + dbg_mobf.pmovement_lvl3("MOBF: movement hard limits check for mob ".. + entity.data.name .. " "..printpos(movement_state.basepos)) + + movement_gen.fix_runaway(entity,movement_state) + + --don't do slowness check each callback + if entity.dynamic_data.movement.ts_last_slowcheck == nil or + entity.dynamic_data.movement.ts_last_slowcheck +2 < movement_state.now then + movement_gen.fix_to_slow(entity,movement_state) + entity.dynamic_data.movement.ts_last_slowcheck = movement_state.now + end + + --------------------------------------------------------------------------- + --------------------------------------------------------------------------- + -- -- + -- DO / PREPARE CHANGES THAT NEED TO BE DONE BECAUSE OF MOB IS -- + -- PREDICTED TO BE SOMEWHERE WHERE IT SHOULDN'T BE -- + -- -- + --------------------------------------------------------------------------- + --------------------------------------------------------------------------- + dbg_mobf.pmovement_lvl3("MOBF: movement check for mob ".. entity.data.name + .. " "..printpos(movement_state.basepos)) + + --skip if movement already got changed + if movement_state.changed == false then + --predict next block mob will be + local pos_predicted = + movement_generic.predict_next_block( movement_state.basepos, + movement_state.current_velocity, + movement_state.current_acceleration) + + --if this isn't a flying mob ignore y prediction as it's not honoring + --collisions + if not entity.data.movement.canfly then + pos_predicted.y = movement_state.basepos.y + end + --local pos_predicted = + -- movement_generic.predict_enter_next_block(entity, movement_state.basepos, + -- movement_state.current_velocity, + -- movement_state.current_acceleration) + + local pos_predicted_quality = environment.pos_quality(pos_predicted,entity) + + dbg_mobf.pmovement_lvl3("MOBF: Pos pred quality: ".. entity.data.name + .. ": " .. pos_predicted_quality:shortstring()) + + -- Y-Movement + if movement_state.changed == false then + height_level_control.precheck_movement(entity,movement_state, + pos_predicted,pos_predicted_quality) + end + + -- X/Z-Movement + if movement_state.changed == false then + direction_control.precheck_movement(entity,movement_state, + pos_predicted,pos_predicted_quality) + end + + end + + + + --------------------------------------------------------------------------- + --------------------------------------------------------------------------- + -- -- + -- DO RANDOM CHANGES TO MOB MOVEMENT -- + -- -- + --------------------------------------------------------------------------- + --------------------------------------------------------------------------- + dbg_mobf.pmovement_lvl3("MOBF: randomized movement for mob ".. + entity.data.name .. " "..printpos(movement_state.basepos)) + + --do randomized changes if not fighting + height_level_control.random_movement_handler(entity,movement_state) + direction_control.random_movement_handler(entity,movement_state) + + + + --------------------------------------------------------------------------- + --------------------------------------------------------------------------- + -- -- + -- APPLY CHANGES CALCULATED BEFORE -- + -- -- + --------------------------------------------------------------------------- + --------------------------------------------------------------------------- + movement_gen.apply_movement_changes(entity,movement_state) +end + + +------------------------------------------------------------------------------- +-- name: apply_movement_changes(entity,movement_state) +-- +--! @brief check and apply the changes from movement_state +--! @memberof movement_gen +--! @private +-- +--! @param entity mob to apply changes +--! @param movement_state changes to apply +------------------------------------------------------------------------------- +function movement_gen.apply_movement_changes(entity,movement_state) + if movement_state.changed then + + --check if there is a acceleration to set + if movement_state.accel_to_set == nil then + movement_state.accel_to_set = {x=0, + y=movement_state.default_y_accel, + z=0} + entity.object:setvelocity({x=0,y=0,z=0}) + minetest.log(LOGLEVEL_ERROR, + "MOBF BUG!!!! set accel requested but no accel given!") + end + + --check if gravity is set + if movement_state.accel_to_set.y == nil then + + --print("fixing y movement: ".. printpos(movement_state.accel_to_set)) + movement_state.accel_to_set.y = movement_state.current_acceleration.y + end + + -- make sure non flying mobs have default acceleration + if entity.data.movement.canfly == false then + movement_state.accel_to_set.y = movement_state.default_y_accel + + if movement_state.default_y_accel == 0 then + minetest.log(LOGLEVEL_ERROR,"MOBF BUG!!! " .. entity.data.name + .. " mob that can't fly has acceleration 0!") + end + end + + dbg_mobf.pmovement_lvl2("MOBF: setting acceleration to " + ..printpos(movement_state.accel_to_set)) + + -- todo check for harsh direction changes + entity.dynamic_data.movement.acceleration = movement_state.accel_to_set + entity.object:setacceleration(movement_state.accel_to_set) + end +end + +------------------------------------------------------------------------------- +-- name: init_dynamic_data(entity,now) +-- +--! @brief initialize dynamic data required by movement generator +--! @memberof movement_gen +-- +--! @param entity mob to initialize +--! @param now current time +------------------------------------------------------------------------------- +function movement_gen.init_dynamic_data(entity,now) + + local accel_to_set = {x=0,y=9.81,z=0} + local pos = entity.object:getpos() + + --initialize acceleration values + accel_to_set.y = environment.get_default_gravity(pos, + entity.environment.media, + entity.data.movement.canfly) + + mobf_assert_backtrace(accel_to_set.y ~= nil) + + local data = { + started = false, + acceleration = accel_to_set, + changing_levels = false, + ts_random_jump = now, + ts_orientation_upd = now, + mpattern = mobf_rtd.movement_patterns[entity.data.movement.pattern], + ts_last_slowcheck = now, + } + + entity.dynamic_data.movement = data +end + +------------------------------------------------------------------------------- +-- name: fix_runaway(entity,movement_state) +-- +--! @brief fix runaway speed mobs +--! @memberof movement_gen +--! @private +-- +--! @param entity mob to check speed limits +--! @param movement_state current state of movement +------------------------------------------------------------------------------- +function movement_gen.fix_runaway(entity,movement_state) + + + --avoid mobs racing away + local xzspeed = mobf_calc_scalar_speed(movement_state.current_velocity.x, + movement_state.current_velocity.z) + + dbg_mobf.pmovement_lvl3("MOBF: checkrunaway x=" .. + movement_state.current_velocity.x .. + " z=" ..movement_state.current_velocity.z .. + " xz=" .. xzspeed .. + " maximum=" .. entity.data.movement.max_speed) + + if xzspeed > entity.data.movement.max_speed then + dbg_mobf.pmovement_lvl3("MOBF: too fast! vxz=" .. xzspeed) + dbg_mobf.pmovement_lvl3("MOBF: current acceleration:" .. + printpos(movement_state.current_acceleration)) + + local direction = mobf_calc_yaw(movement_state.current_velocity.x, + movement_state.current_velocity.z) + + --reduce speed to 90% of current speed + local new_speed = mobf_calc_vector_components(direction,xzspeed*0.9) + new_speed.y = movement_state.current_velocity.y + entity.object:setvelocity(new_speed) + + movement_state.current_velocity = new_speed + + --don't accelerate any longer + movement_state.accel_to_set = {x=0,z=0} + movement_state.changed = true + + dbg_mobf.pmovement_lvl2("MOBF: fix runaway new acceleration:" .. + printpos(movement_state.accel_to_set)) + end +end + +------------------------------------------------------------------------------- +-- name: fix_to_slow(entity,movement_state) +-- +--! @brief check if mobs motion is below its minimal speed (e.g. flying birds) +--! @memberof movement_gen +--! @private +-- +--! @param entity mob to check speed limits +--! @param movement_state current state of movement +------------------------------------------------------------------------------- +function movement_gen.fix_to_slow(entity,movement_state) + local xzspeed = mobf_calc_scalar_speed(movement_state.current_velocity.x, + movement_state.current_velocity.z) + + dbg_mobf.pmovement_lvl3("MOBF: checktoslow x=" .. + movement_state.current_velocity.x .. + " z=" ..movement_state.current_velocity.z .. + " xz=" .. xzspeed .. + " minimum=" .. dump(entity.data.movement.min_speed)) + + --this ain't perfect to avoid flying mobs standing in air + --but it's a quick solution to fix most of the problems + if (entity.data.movement.min_speed ~= nil and + xzspeed < entity.data.movement.min_speed) or + xzspeed == nil or + xzspeed == 0 then + + dbg_mobf.pmovement_lvl2("MOBF: too slow! vxz=" .. xzspeed) + --use normal speed change handling + movement_state.force_change = true + end +end + +------------------------------------------------------------------------------- +-- name: fix_current_pos(entity,movement_state) +-- +--! @brief check current position for validity and try to fix +--! @memberof movement_gen +--! @private +-- +--! @param entity to fix position +--! @param movement_state movement state of mob +--! @return { speed_to_set = {x,y,z}, +--! speed_changed = true/false, +--! force_speed_change = true/false } +------------------------------------------------------------------------------- +function movement_gen.fix_current_pos(entity,movement_state) + + --check if current pos is ok + local current_state = environment.pos_is_ok(movement_state.basepos,entity) + local handled = false + + dbg_mobf.pmovement_lvl3("MOBF: current state ".. + entity.data.name .. ": " .. current_state) + + movement_state.accel_to_set = { x=0, + y=movement_state.default_y_accel, + z=0 } + + local abort_processing = false + + if current_state == "ok" or + current_state == "possible_surface" then + entity.dynamic_data.movement.last_pos_in_env = movement_state.centerpos + handled = true + end + + --states ok drop and wrong_surface don't require an imediate action + if current_state ~= "ok" and + current_state ~= "drop" and + current_state ~= "wrong_surface" and + current_state ~= "possible_surface" and + current_state ~= "below_limit" and + current_state ~= "above_limit" then + dbg_mobf.movement_lvl1("MOBF: BUG !!! somehow your mob managed to get" + .." where it shouldn't be (" + .. current_state .. "), trying to fix") + + --stop mob from moving at all + entity.object:setacceleration({x=0,y=movement_state.default_y_accel,z=0}) + movement_state.force_change = true + + --mob is currently in water, + --try to find a suitable position 1 level above current level + if current_state == "in_water" or + current_state == "above_water" then + + local targetpos = nil + + --if we don't have an old pos or old pos is to far away + --try to finde another good pos around + if entity.dynamic_data.movement.last_pos_in_env == nil or + entity.dynamic_data.movement.last_pos_in_env.y > + movement_state.centerpos.y + 2 then + targetpos = + environment.get_suitable_pos_same_level({ + x=movement_state.basepos.x, + y=movement_state.basepos.y+1, + z=movement_state.basepos.z}, + 1, + entity) + if targetpos ~= nil then + targetpos.y = targetpos.y - entity.collisionbox[2] + end + else + targetpos = entity.dynamic_data.movement.last_pos_in_env + end + + if targetpos ~= nil then + mobf_bug_warning(LOGLEVEL_INFO,"MOBF: BUG !!! didn't find a way" + .. " out of water, for mob at: " .. + printpos(movement_state.basepos) .. + " using last known good position") + + if targetpos == nil then + targetpos = entity.dynamic_data.movement.last_pos_in_env + else + targetpos.y = targetpos.y - entity.collisionbox[2] + end + + minetest.log(LOGLEVEL_INFO,"MOBF: Your mob " .. + entity.data.name .. " " .. + tostring(entity) .. " dropt into water moving to ".. + printpos(targetpos).." state: ".. + environment.pos_is_ok(targetpos,entity)) + entity.object:moveto(targetpos) + movement_state.current_velocity.x = 0 --movement_state.current_velocity.x/10 + movement_state.current_velocity.z = 0 --movement_state.current_velocity.z/10 + entity.object:setvelocity(movement_state.current_velocity) + movement_state.centerpos = targetpos + movement_state.basepos = entity.getbasepos(entity) + + movement_state.accel_to_set.y = + environment.get_default_gravity(targetpos, + entity.environment.media, + entity.data.movement.canfly) + + mobf_assert_backtrace(movement_state.accel_to_set.y ~= nil) + else + mobf_bug_warning(LOGLEVEL_WARNING,"MOBF: BUG !!! didn't find a way" + .." out of water, for mob at: " .. + printpos(movement_state.basepos) .. " drowning, last pos in env:" .. + dump(entity.dynamic_data.movement.last_pos_in_env)) + abort_processing = true + spawning.remove(entity, "mgen probab watercheck") + end + + handled = true + end + + if current_state == "in_air" then + --TODO die? + handled = true + end + end + + local damagetime = 60 + + if entity.data.generic.wrong_surface_damage_time ~= nil then + damagetime = entity.data.generic.wrong_surface_damage_time + end + + if current_state == "wrong_surface" and + entity.dynamic_data.good_surface ~= nil then + if movement_state.now - entity.dynamic_data.good_surface > damagetime then + + entity.object:set_hp( entity.object:get_hp() - + (entity.data.generic.base_health/25)) + dbg_mobf.movement_lvl1("MOBF: mob is on wrong surface it will slowly take damage") + + --reset timer for next damage interval + entity.dynamic_data.good_surface = movement_state.now + if entity.object:get_hp() <= 0 then + abort_processing = true + spawning.remove(entity, "mgen probab surfacecheck") + end + + movement_state.force_change = true + end + + handled = true + else + entity.dynamic_data.good_surface = movement_state.now + end + + if current_state == "collision" then + + local current_pos = mobf_round_pos(movement_state.basepos); + local current_node = minetest.get_node( current_pos ); + + local walkable = false + if minetest.registered_nodes[current_node.name] then + walkable = minetest.registered_nodes[current_node.name].walkable + end + + dbg_mobf.movement_lvl2( "MOBF: Mob collided with ".. + tostring( current_pos.x )..":".. + tostring( current_pos.y )..":".. + tostring( current_pos.z ).." Nodename:".. + tostring( current_node.name ).." walkable:".. + tostring( walkable )) + + if not mobf_is_walkable(current_node) then + local targetpos = entity.dynamic_data.movement.last_pos_in_env + + if targetpos == nil then + targetpos = + environment.get_suitable_pos_same_level({x=current_pos.x, + y=current_pos.y, + z=current_pos.z}, + 1, + entity) + end + + if targetpos == nil then + targetpos = + environment.get_suitable_pos_same_level({x=current_pos.x, + y=current_pos.y+1, + z=current_pos.z}, + 1, + entity) + end + + if targetpos ~= nil then + minetest.log(LOGLEVEL_INFO, + "MOBF: Your mob " .. + entity.data.name .. + " is within solid block moving to".. + printpos(targetpos).." state: ".. + environment.pos_is_ok(targetpos,entity)) + + entity.object:moveto(targetpos) + movement_state.accel_to_set.y = + environment.get_default_gravity(targetpos, + entity.environment.media, + entity.data.movement.canfly) + mobf_assert_backtrace(movement_state.default_y_accel ~= nil) + else + minetest.log(LOGLEVEL_WARNING,"MOBF: mob " .. entity.data.name + .. " was within solid block, removed") + abort_processing = true + spawning.remove(entity, "mgen probab solidblockcheck") + end + else + dbg_mobf.movement_lvl2( "MOBF: mob is on walkable surface " .. + "so no forced repositioning" ) + end + + handled = true + end + + if not handled then + dbg_mobf.movement_lvl1("MOBF: ".. entity.data.name .. " state: ".. + current_state .. " not handled!") + end + + return { abort_processing=abort_processing, } +end + +------------------------------------------------------------------------------- +-- name: set_target(entity,target) +-- +--! @brief set target for movgen +--! @memberof movement_gen +--! @private +-- +--! @param entity mob to apply to +--! @param target to set +------------------------------------------------------------------------------- +function movement_gen.set_target(entity,target) + return false +end + +--register this movement generator +registerMovementGen(movement_gen.name,movement_gen) \ No newline at end of file diff --git a/mods/mob_engines/mobf/mgen_probab/movement_patterns/dont_move.lua b/mods/mob_engines/mobf/mgen_probab/movement_patterns/dont_move.lua new file mode 100644 index 00000000..8a0c2e55 --- /dev/null +++ b/mods/mob_engines/mobf/mgen_probab/movement_patterns/dont_move.lua @@ -0,0 +1,39 @@ +------------------------------------------------------------------------------- +-- 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 dont_move.lua +--! @brief movementpattern for immobile mob +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-10 +-- +--! @addtogroup mpatterns +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + + +--! @struct dont_move_prototype +--! @brief a movement pattern resulting in a mob not moving at all +local dont_move_prototype = { + name ="dont_move", + jump_up =0, + + random_jump_chance =0, + random_jump_initial_speed =0, + random_jump_delay =0, + random_acceleration_change =0, +-- +-- --run towards player or run away? 1 <-> -1 +-- player_attraction =0, +-- --maximum distance a player has an effect +-- player_attraction_range =-1, + } + +--!@} + +table.insert(mov_patterns_defined,dont_move_prototype) \ No newline at end of file diff --git a/mods/mob_engines/mobf/mgen_probab/movement_patterns/flight_pattern1.lua b/mods/mob_engines/mobf/mgen_probab/movement_patterns/flight_pattern1.lua new file mode 100644 index 00000000..8bd1f781 --- /dev/null +++ b/mods/mob_engines/mobf/mgen_probab/movement_patterns/flight_pattern1.lua @@ -0,0 +1,34 @@ +------------------------------------------------------------------------------- +-- 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 flight_pattern1.lua +--! @brief movementpattern flight 1 +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-10 +-- +--! @addtogroup mpatterns +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + + +--! @struct flight_pattern1_prototype +--! @brief a movement pattern used for flying mobs +local flight_pattern1_prototype = { + name ="flight_pattern1", + jump_up =0, + + random_jump_chance =0.4, + random_jump_initial_speed =0, + random_jump_delay =10, + random_acceleration_change =0.3, + } + +--!@} + +table.insert(mov_patterns_defined,flight_pattern1_prototype) \ No newline at end of file diff --git a/mods/mob_engines/mobf/mgen_probab/movement_patterns/run_and_jump_low.lua b/mods/mob_engines/mobf/mgen_probab/movement_patterns/run_and_jump_low.lua new file mode 100644 index 00000000..7c3fec68 --- /dev/null +++ b/mods/mob_engines/mobf/mgen_probab/movement_patterns/run_and_jump_low.lua @@ -0,0 +1,35 @@ +------------------------------------------------------------------------------- +-- 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 run_and_jump_low.lua +--! @brief movementpattern running arround ad doing random low jumps +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-10 +-- +--! @addtogroup mpatterns +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + + +--! @struct run_and_jump_low_prototype +--! @brief a movement pattern used for quick moving and direction changing mobs +--! that jump every now and then +local run_and_jump_low_prototype = { + name ="run_and_jump_low", + jump_up =0.2, + + random_jump_chance =0.3, + random_jump_initial_speed =3.5, + random_jump_delay =2, + random_acceleration_change =0.6, + } + +--!~@} + +table.insert(mov_patterns_defined,run_and_jump_low_prototype) \ No newline at end of file diff --git a/mods/mob_engines/mobf/mgen_probab/movement_patterns/stop_and_go.lua b/mods/mob_engines/mobf/mgen_probab/movement_patterns/stop_and_go.lua new file mode 100644 index 00000000..b934d502 --- /dev/null +++ b/mods/mob_engines/mobf/mgen_probab/movement_patterns/stop_and_go.lua @@ -0,0 +1,33 @@ +------------------------------------------------------------------------------- +-- 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 stop_and_go.lua +--! @brief movementpattern creating a random stop and go movement e.g. sheep/cow +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-10 +-- +--! @addtogroup mpatterns +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @struct stop_and_go_prototype +--! @brief movement pattern for mobs wandering around randomly +local stop_and_go_prototype = { + name ="stop_and_go", + jump_up =0.4, + + random_jump_chance =0, + random_jump_initial_speed =0, + random_jump_delay =0, + random_acceleration_change =0.05, + } + +--!@} + +table.insert(mov_patterns_defined,stop_and_go_prototype) \ No newline at end of file diff --git a/mods/mob_engines/mobf/mgen_probab/movement_patterns/swim_pattern1.lua b/mods/mob_engines/mobf/mgen_probab/movement_patterns/swim_pattern1.lua new file mode 100644 index 00000000..672444b8 --- /dev/null +++ b/mods/mob_engines/mobf/mgen_probab/movement_patterns/swim_pattern1.lua @@ -0,0 +1,33 @@ +------------------------------------------------------------------------------- +-- 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 swim_pattern1.lua +--! @brief movementpattern for slow swimming mobs +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-10 +-- +--! @addtogroup mpatterns +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @struct swim_pattern1_prototype +--! @brief movement pattern for mobs swimming slow +local swim_pattern1_prototype = { + name ="swim_pattern1", + jump_up =0, + + random_jump_chance =0.2, + random_jump_initial_speed =0, + random_jump_delay =10, + random_acceleration_change =0.5, + } + +--!@} + +table.insert(mov_patterns_defined,swim_pattern1_prototype) \ No newline at end of file diff --git a/mods/mob_engines/mobf/mgen_probab/movement_patterns/swim_pattern2.lua b/mods/mob_engines/mobf/mgen_probab/movement_patterns/swim_pattern2.lua new file mode 100644 index 00000000..08013c91 --- /dev/null +++ b/mods/mob_engines/mobf/mgen_probab/movement_patterns/swim_pattern2.lua @@ -0,0 +1,33 @@ +------------------------------------------------------------------------------- +-- 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 swim_pattern2.lua +--! @brief movementpattern for medium swimming mobs +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-10 +-- +--! @addtogroup mpatterns +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @struct swim_pattern1_prototype +--! @brief movement pattern for mobs swimming medium speeds +local swim_pattern2_prototype = { + name ="swim_pattern2", + jump_up =0, + + random_jump_chance =0.4, + random_jump_initial_speed =0, + random_jump_delay =15, + random_acceleration_change =0.7, + } + +--!@} + +table.insert(mov_patterns_defined,swim_pattern2_prototype) \ No newline at end of file diff --git a/mods/mob_engines/mobf/mgen_probab/movement_patterns/swim_pattern3.lua b/mods/mob_engines/mobf/mgen_probab/movement_patterns/swim_pattern3.lua new file mode 100644 index 00000000..2d7ef76d --- /dev/null +++ b/mods/mob_engines/mobf/mgen_probab/movement_patterns/swim_pattern3.lua @@ -0,0 +1,35 @@ +------------------------------------------------------------------------------- +-- 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 swim_pattern1.lua +--! @brief movementpattern for slow swimming mobs +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-10 +-- +--! @addtogroup mpatterns +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @struct swim_pattern1_prototype +--! @brief movement pattern for mobs swimming slow +local swim_pattern3_prototype = { + name ="swim_pattern3", + jump_up =0, + + random_jump_chance =0.2, + random_jump_initial_speed =0, + random_jump_delay =10, + random_acceleration_change =0.5, + + min_height_above_ground = 3 + } + +--!@} + +table.insert(mov_patterns_defined,swim_pattern3_prototype) \ No newline at end of file diff --git a/mods/mob_engines/mobf/mgen_probab/movement_patterns/template.lua b/mods/mob_engines/mobf/mgen_probab/movement_patterns/template.lua new file mode 100644 index 00000000..51410484 --- /dev/null +++ b/mods/mob_engines/mobf/mgen_probab/movement_patterns/template.lua @@ -0,0 +1,37 @@ +------------------------------------------------------------------------------- +-- 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 template.lua +--! @brief template file for movement patterns +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-11 +-- +--! @addtogroup mpatterns +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- +--movement pattern for movement generator +-- { +-- --patternname +-- name ="example" +-- +-- --chance to jump to higher level instead of turning +-- jump_up =0, +-- +-- --chance an mob does random jumps +-- random_jump_chance =0, +-- --mobs jump speed (controling height of jump) +-- random_jump_initial_speed =0, +-- --minimum time between random jumps +-- random_jump_delay =0, +-- +-- --chance an mob randomly changes its speed/direction +-- random_acceleration_change =0, +-- } + +--!@} \ No newline at end of file diff --git a/mods/mob_engines/mobf/mgen_rasterized/mgen_raster.lua b/mods/mob_engines/mobf/mgen_rasterized/mgen_raster.lua new file mode 100644 index 00000000..b260dc83 --- /dev/null +++ b/mods/mob_engines/mobf/mgen_rasterized/mgen_raster.lua @@ -0,0 +1,208 @@ +------------------------------------------------------------------------------- +-- 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 mgen_raster.lua +--! @brief component containing a probabilistic movement generator (uses mgen follow) +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +--! @defgroup mgen_raster MGEN: raster based movement generator +--! @brief A movement generator creating movement that creates a rasterized +--! random movement +--! @ingroup framework_int +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @class mgen_raster +--! @brief a movement generator creating a rasterized probabilistic movement +--!@} +mgen_raster = {} + +--! @brief movement generator identifier +--! @memberof mgen_follow +mgen_raster.name = "probab_raster_mov_gen" + + +------------------------------------------------------------------------------- +-- name: stop(entity) +-- +--! @brief stop this entity +--! @memberof mgen_raster +-- +--! @param entity mob to stop +------------------------------------------------------------------------------- +function mgen_raster.stop(entity) + local defgrav = environment.get_default_gravity(entity.getbasepos(entity), + entity.environment, + entity.data.movement.canfly) + + entity.dynamic_data.movement.target = nil + entity.object:setvelocity({x=0,y=0,z=0}) + entity.object:setacceleration({x=0,y=defgrav,z=0}) +end + +------------------------------------------------------------------------------- +-- name: callback(entity,now) +-- +--! @brief main callback to make a mob follow its target +--! @memberof mgen_raster +-- +--! @param entity mob to generate movement for +--! @param now current time +------------------------------------------------------------------------------- +function mgen_raster.callback(entity,now) + + local basepos = entity.getbasepos(entity) + local dtime = entity.current_dtime + + entity.dynamic_data.movement.time_travelled = entity.dynamic_data.movement.time_travelled + dtime + + --check environment + local pos_state = environment.pos_is_ok(basepos,entity) + if pos_state ~= "ok" and + pos_state ~= "possible_surface" then + mgen_raster.stop(entity) + + --try to find a good position around to move mob to + local new_pos = environment.get_suitable_pos_same_level(basepos,1,entity,false) + + if (new_pos == nil ) then + new_pos = environment.get_suitable_pos_same_level({x=basepos.x, + y=basepos.y+1, + z=basepos.z} + ,1,entity,false) + end + + if (new_pos == nil ) then + new_pos = environment.get_suitable_pos_same_level({x=basepos.x, + y=basepos.y+1, + z=basepos.z} + ,1,entity,false) + end + + if (new_pos ~= nil ) then + --TODO fix position according to model information! + enity.object:moveto(new_pos) + basepos = new_pos + else + --TODO error handling + end + end + + --check on ground + if not mgen_raster.onground(entity,now,basepos) then + mgen_raster.stop(entity) + end + + --check how long we've been traveling + if ( entity.dynamic_data.movement.time_travelled > entity.dynamic_data.movement.eta ) then + mgen_raster.stop(entity) + end + + --check distance to target (target reached or missed) + local distance_to_target = mobf_calc_distance(basepos,entity.dynamic_data.movement.target) + if distance_to_target > 2.0 or + distance_to_target < 0.1 then + mgen_raster.stop(entity) + end + + --find next point + if entity.dynamic_data.nextpoint == nil then + entity.dynamic_data.nextpoint = environment.get_suitable_pos_same_level(basepos,1,entity,false) + --TODO calc maximum eta + 10% + end + + --call move to target mgen + helper_mgen.callback(entity,now) +end + +------------------------------------------------------------------------------- +-- name: initialize() +-- +--! @brief initialize movement generator +--! @memberof mgen_raster +--! @public +------------------------------------------------------------------------------- +function mgen_raster.initialize(entity,now) +--intentionaly doing nothing this function is for symmetry reasons only +end + +------------------------------------------------------------------------------- +-- name: init_dynamic_data(entity,now) +-- +--! @brief initialize dynamic data required by movement generator +--! @memberof mgen_raster +--! @public +-- +--! @param entity mob to initialize dynamic data +--! @param now current time +------------------------------------------------------------------------------- +function mgen_raster.init_dynamic_data(entity,now) + + local pos = entity.object:getpos() + + local data = { + target = nil, + guardspawnpoint = true, + time_travelled = 0, + helper_mgen = getMovementGen(follow_mov_gen), + max_distance = 0.1, + } + + entity.dynamic_data.movement = data + +end + + +------------------------------------------------------------------------------- +-- name: onground(entity,now) +-- +--! @brief check if mob is touching ground +--! @memberof mgen_raster +--! @public +-- +--! @param entity mob to initialize dynamic data +--! @param now current time +--! @param basepos position of feet +------------------------------------------------------------------------------- +function mgen_raster.onground(entity,now,basepos) + + local posbelow = { x=basepos.x, y=basepos.y-1,z=basepos.z} + + for dx=-1,1 do + for dz=-1,1 do + local fp0 = posbelow + local fp = { x= posbelow.x + (0.5*dx), + y= posbelow.y, + z= posbelow.z + (0.5*dz) } + local n = minetest.get_node(fp) + if mobf_is_walkable(n) then + return true + end + end + end + +end + +------------------------------------------------------------------------------- +-- name: set_target(entity,target) +-- +--! @brief set target for movgen +--! @memberof mgen_raster +--! @private +-- +--! @param entity mob to apply to +--! @param target to set +------------------------------------------------------------------------------- +function mgen_raster.set_target(entity,target) + return false +end + +--register this movement generator +registerMovementGen(mgen_raster.name,mgen_raster) \ No newline at end of file diff --git a/mods/mob_engines/mobf/mob_state.lua b/mods/mob_engines/mobf/mob_state.lua new file mode 100644 index 00000000..7907812b --- /dev/null +++ b/mods/mob_engines/mobf/mob_state.lua @@ -0,0 +1,721 @@ +------------------------------------------------------------------------------- +-- 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 mob_state.lua +--! @brief component mob state transition handling +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +--! @defgroup mob_state State handling functions +--! @brief a component to do basic changes to mob on state change +--! @ingroup framework_int +--! @{ +-- +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- +mobf_assert_backtrace(not core.global_exists("mob_state")) +--! @class mob_state +--! @brief state handling class +--! @} +mob_state = {} +mob_state.default_state_time = 30 + +------------------------------------------------------------------------------- +-- @function [parent=#mob_state] initialize(entity,now) +-- +--! @brief initialize state dynamic data +--! @memberof mob_state +--! @public +-- +--! @param entity elemet to initialize state data +--! @param now current time +------------------------------------------------------------------------------- +function mob_state.initialize(entity,now) + + dbg_mobf.mob_state_lvl3("MOBF: " .. entity.data.name + .. " initializing state dynamic data") + + local state = { + current = "default", + time_to_next_change = 30, + locked = false, + enabled = false, + } + + local sum_chances = 0 + local state_count = 0 + + if entity.data.states ~= nil then + for s = 1, #entity.data.states , 1 do + sum_chances = sum_chances + entity.data.states[s].chance + + if entity.data.states[s].name ~= "combat" and + entity.data.states[s].name ~= "default" then + state_count = state_count +1 + end + end + end + + --sanity check for state chances + if sum_chances > 1 then + minetest.log(LOGLEVEL_WARNING,"MOBF: Warning sum of state chances for mob " + .. entity.data.name .. " > 1") + end + + --only enable state changeing if there is at least one state + if state_count > 0 then + state.enabled = true + end + + entity.dynamic_data.state = state +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_state] get_state_by_name(entity,name) +-- +--! @brief get a state by its name +--! @memberof mob_state +--! @public +-- +--! @param entity elemet to look for state data +--! @param name of state +--! +--! @return state data or nil +------------------------------------------------------------------------------- +function mob_state.get_state_by_name(entity,name) + mobf_assert_backtrace(entity ~= nil and entity.data ~= nil) + + for i=1, #entity.data.states, 1 do + if entity.data.states[i].name == name then + return entity.data.states[i] + end + end + + return nil +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_state] lock(entity,value) +-- +--! @brief disable random state changes for a mob +--! @memberof mob_state +--! @public +-- +--! @param entity elemet to lock +--! @param value to set +------------------------------------------------------------------------------- +function mob_state.lock(entity,value) + if value ~= false and value ~= true then + return + end + if entity.dynamic_data.state == nil then + dbg_mobf.mob_state_lvl1("MOBF: unable to lock state for: " + .. entity.data.name .. " no state dynamic data present") + return + end + + entity.dynamic_data.state.locked = value +end + + +------------------------------------------------------------------------------- +-- @function [parent=#mob_state] callback(entity,now,dstep) +-- +--! @brief callback handling state changes +--! @memberof mob_state +--! @public +-- +--! @param entity elemet to look for state data +--! @param now current time +--! @param dstep time passed since last call +-- +--! @return +------------------------------------------------------------------------------- +function mob_state.callback(entity,now,dstep) + +--TODO find out if this needs to be replaced +-- if entity.dynamic_data.state == nil then +-- minetest.log(LOGLEVEL_ERRROR,"MOBF BUG: " .. entity.data.name +-- .. " mob state callback without mob dynamic data!") +-- mob_state.initialize(entity,now) +-- local default_state = mob_state.get_state_by_name(self,"default") +-- entity.dynamic_data.current_movement_gen = getMovementGen(default_state.movgen) +-- entity.dynamic_data.current_movement_gen.init_dynamic_data(entity,mobf_get_current_time()) +-- entity = spawning.replace_entity(entity,entity.data.modname .. ":"..entity.data.name,true) +-- return true +-- end + + --abort state change if current state is locked + if entity.dynamic_data.state.locked or + entity.dynamic_data.state.enabled == false then + dbg_mobf.mob_state_lvl3("MOBF: " .. entity.data.name + .. " state locked or no custom states definded ") + return true + end + + entity.dynamic_data.state.time_to_next_change = entity.dynamic_data.state.time_to_next_change -dstep + + --do only change if last state timed out + if entity.dynamic_data.state.time_to_next_change < 0 then + + dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name + .. " time to change state: " .. entity.dynamic_data.state.time_to_next_change + .. " , " .. dstep .. " entity=" .. tostring(entity)) + + local rand = math.random() + + local maxvalue = 0 + + local state_table = {} + + --fill table with available states + for i=1, #entity.data.states, 1 do + if (entity.data.states[i].HANDLER_precondition == nil or + entity.data.states[i].HANDLER_precondition(entity,entity.data.states[i])) and + --ignore states that are not supposed to be switched to + --by automatic state change handling e.g. fighting states or + --manual set states + ( entity.data.states[i].state_mode == nil or + entity.data.states[i].state_mode == "auto") then + table.insert(state_table,entity.data.states[i]) + end + end + + dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name .. " " + .. dump(#state_table) .. " states do pass pecondition check ") + + --try to get a random state to change to + for i=1, #state_table, 1 do + + local rand_state = math.random(#state_table) + local current_chance = 0 + + if type (state_table[rand_state].chance) == "function" then + current_chance = state_table[rand_state].chance(entity,now,dstep) + else + if state_table[rand_state].chance ~= nil then + current_chance = state_table[rand_state].chance + end + end + + local random_value = math.random() + + if random_value < current_chance then + dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name + .. " switching to state " .. state_table[rand_state].name) + mob_state.change_state(entity,state_table[rand_state]) + return true + else + dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name + .. " not switching to state " .. state_table[rand_state].name + .. " rand was: " .. random_value) + table.remove(state_table,rand_state) + end + end + dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name + .. " no specific state selected switching to default state ") + --switch to default state (only reached if no change has been done + mob_state.change_state(entity,mob_state.get_state_by_name(entity,"default")) + else + dbg_mobf.mob_state_lvl3("MOBF: " .. entity.data.name + .. " is not ready for state change ") + return true + end + + return true +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_state] switch_switch_movgenentity(entity,state) +-- +--! @brief helper function to swich a movement based on new state +--! @memberof mob_state +--! @private +-- +--! @param entity to change movement gen +--! @param state to take new entity +------------------------------------------------------------------------------- +function mob_state.switch_movgen(entity,state) + mobf_assert_backtrace(entity ~= nil) + mobf_assert_backtrace(state ~= nil) + local mov_to_set = nil + + --determine new movement gen + if state.movgen ~= nil then + mov_to_set = getMovementGen(state.movgen) + else + local default_state = mob_state.get_state_by_name(entity,"default") + mov_to_set = getMovementGen(default_state.movgen) + end + + --check if new mov gen differs from old one + if mov_to_set ~= nil and + mov_to_set ~= entity.dynamic_data.current_movement_gen then + entity.dynamic_data.current_movement_gen = mov_to_set + + --TODO initialize new movement gen + entity.dynamic_data.current_movement_gen.init_dynamic_data(entity,mobf_get_current_time()) + end +end + + +------------------------------------------------------------------------------- +-- @function [parent=#mob_state] change_state(entity,state) +-- +--! @brief change state for an entity +--! @memberof mob_state +--! @public +-- +--! @param entity to change state +--! @param state to change to +------------------------------------------------------------------------------- +function mob_state.change_state(entity,state) + + dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name + .. " state change called entity=" .. tostring(entity) .. " state:" + .. dump(state)) + + --check if custom precondition handler tells us to stop state change + if state ~= nil and + type(state.HANDLER_precondition) == "function" then + if not state.HANDLER_precondition(entity,state) then + dbg_mobf.mob_state_lvl1("MOBF: " .. entity.data.name + .. " custom precondition handler didn't meet ") + --don't assert but switch to default in this case + state = mob_state.get_state_by_name(entity,"default") + end + end + + --switch to default state if no state given + if state == nil then + dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name + .. " invalid state switch, switching to default instead of: " + .. dump(state)) + state = mob_state.get_state_by_name(entity,"default") + end + + local entityname = entity.data.name + local statename = state.name + + dbg_mobf.mob_state_lvl2("MOBF: " .. entityname .. " switching state to " + .. statename) + + if entity.dynamic_data.state == nil then + mobf_bug_warning(LOGLEVEL_WARNING,"MOBF BUG!!! mob_state no state dynamic data") + end + + if entity.dynamic_data.state.current.name ~= state.name then + --call leave state handler for old state + if entity.dynamic_data.state.current.HANDLER_leave_state ~= nil and + type(entity.dynamic_data.state.current.HANDLER_leave_state) == "function" then + entity.dynamic_data.state.current.HANDLER_leave_state(entity,state) + end + + dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name + .. " different states now really changeing to " .. state.name) + + mob_state.switch_movgen(entity,state) + + entity.dynamic_data.state.time_to_next_change = + mob_state.getTimeToNextState(state.typical_state_time) + entity.dynamic_data.state.current = state + + graphics.set_animation(entity,state.animation) + + if state.HANDLER_enter_state ~= nil and + type(state.HANDLER_enter_state) == "function" then + state.HANDLER_enter_state(entity) + end + else + dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name + .. " switching to same state as before") + entity.dynamic_data.state.time_to_next_change = mob_state.getTimeToNextState(state.typical_state_time) + + if state.HANDLER_enter_state ~= nil and + type(state.HANDLER_enter_state) == "function" then + state.HANDLER_enter_state(entity) + end + end + + --update model on each state change + mob_state.switch_model(entity,state) + + dbg_mobf.mob_state_lvl2("MOBF: time to next change = " + .. entity.dynamic_data.state.time_to_next_change) +end + + +------------------------------------------------------------------------------- +-- @function [parent=#mob_state] getTimeToNextState(typical_state_time) +-- +--! @brief helper function to calculate a gauss distributed random value +--! @memberof mob_state +--! @private +-- +--! @param typical_state_time center of gauss +--! +--! @return a random value around typical_state_time +------------------------------------------------------------------------------- +function mob_state.getTimeToNextState(typical_state_time) + + if typical_state_time == nil then + mobf_bug_warning(LOGLEVEL_WARNING,"MOBF MOB BUG!!! missing typical state time!") + typical_state_time = mob_state.default_state_time + end + + local u1 = 2 * math.random() -1 + local u2 = 2 * math.random() -1 + + local q = u1*u1 + u2*u2 + + local maxtries = 0 + + while (q == 0 or q >= 1) and maxtries < 10 do + u1 = math.random() + u2 = math.random() * -1 + q = u1*u1 + u2*u2 + + maxtries = maxtries +1 + end + + --abort random generation + if maxtries >= 10 then + return typical_state_time + end + + local p = math.sqrt( (-2*math.log(q))/q ) + + local retval = 2 + --calculate normalized state time with maximum error or half typical time up and down + if math.random() < 0.5 then + retval = typical_state_time + ( u1*p * (typical_state_time/2)) + else + retval = typical_state_time + ( u2*p * (typical_state_time/2)) + end + + -- ensure minimum state time of 2 seconds + if retval > 2 then + return retval + else + return 2 + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_state] prepare_states(mob) +-- +--! @brief prepare mobs states upon registration +--! @memberof mob_state +--! @public +-- +--! @param mob a mob declaration +------------------------------------------------------------------------------- +function mob_state.prepare_states(mob) + local builtin_state_overrides = {} + + --scan for states overriding mobf internal state definitions + if mob.states ~= nil then + for s = 1, #mob.states , 1 do + if mob.states[s].name == "combat" then + builtin_state_overrides["combat"] = true + end + + --TODO patrol state + + --hunger state + if mob.states[s].name == "RSVD_hunger" then + builtin_state_overrides["RSVD_hunger"] = true + end + + end + else + mob.states = {} + end + + --add a default combat state if no custom state is defined + if mob.combat ~= nil and builtin_state_overrides["combat"] ~= true then + table.insert(mob.states, + { + name = "combat", + HANDLER_precondition = nil, + movgen = "follow_mov_gen", + typical_state_time = -1, + chance = 0, + }) + end + + if mob.hunger ~= nil and builtin_state_overrides["RSVD_hunger"] ~= true then + table.insert(mob.states, + { + name = "RSVD_hunger", + HANDLER_precondition = mob_state.BuiltinHungerPrecondition(mob), + HANDLER_leave_state = mob_state.BuiltinHungerLeave(mob), + HANDLER_enter_state = mob_state.BuiltinHungerEnter(mob), + movgen = "mgen_path", + typical_state_time = mob.hunger.typical_walk_time or 45, + chance = mob.hunger.chance or 0.1, + animation = mob.hunger.animation or "walk" + }) + end + +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_state] switch_model(entity,state) +-- +--! @brief switch model used for a entity +--! @memberof mob_state +--! @public +-- +--! @param entity to switch model +--! @param state to change to +------------------------------------------------------------------------------- +function mob_state.switch_model(entity, state) + + + local new_graphics = graphics.graphics_by_statename(entity.data, state.name) + + if type(new_graphics.texturelist) == "table" then + if entity.dynamic_data.textureidx ~= nil and + entity.dynamic_data.textureidx < #new_graphics.texturelist then + new_graphics.textures = new_graphics.texturelist[entity.dynamic_data.textureidx] + else + new_graphics.textures = new_graphics.texturelist[1] + end + end + + local new_props = { + automatic_face_movement_dir = true, + textures = new_graphics.textures + } + + if new_graphics.model_orientation_fix ~= nil then + new_props.automatic_face_movement_dir = + (new_graphics.model_orientation_fix / math.pi) * 360 + 90 + end + + --TODO apply new model too + + entity.object:set_properties(new_props) +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_state] BuiltinHungerPrecondition(mob) +-- +--! @brief prepare builtin hunger precondition handler +--! @memberof mob_state +--! @public +-- +--! @param mob definition of mob +--! @return function to be called as precondition handler +------------------------------------------------------------------------------- +function mob_state.BuiltinHungerPrecondition(mob) + + mobf_assert_backtrace( + (mob.hunger.target_nodes ~= nil) or + (mob.hunger.target_entities ~= nil)) + mobf_assert_backtrace(mob.hunger.range ~= nil) + + return function(entity,state) + mobf_assert_backtrace(state ~= nil) + mobf_assert_backtrace(state.name == "RSVD_hunger") + + local pos = entity.object:getpos() + --mobf_print("MOBF: trying to find " .. + -- dump(mob.hunger.target_nodes) .. " or " .. + -- dump(mob.hunger.target_entities) .. + -- " around: " .. printpos(pos)) + + local lower_pos = {x=pos.x-mob.hunger.range, + y=pos.y-mob.hunger.range, + z=pos.z-mob.hunger.range} + local upper_pos = {x=pos.x+mob.hunger.range, + y=pos.y+mob.hunger.range, + z=pos.z+mob.hunger.range} + + local target_nodes = nil + local target_entities = nil + + if mob.hunger.target_nodes ~= nil then + target_nodes = minetest.find_nodes_in_area(lower_pos, + upper_pos, + mob.hunger.target_nodes) + end + + if mob.hunger.target_entities ~= nil then + local objectlist = minetest.get_objects_inside_radius(pos,mob.hunger.range) + --mobf_print("MOBF: found: " .. #objectlist .. " objects around") + if objectlist ~= nil and #objectlist > 0 then + target_entities = {} + for i=1,#objectlist,1 do + local luaentity = objectlist[i]:get_luaentity() + if luaentity ~= nil and + mobf_contains(mob.hunger.target_entities,luaentity.name) then + table.insert(target_entities,objectlist[i]) + end + end + end + end + + local targets = {} + + if target_nodes ~= nil then + for i=1,#target_nodes,1 do + table.insert(targets,target_nodes[i]) + end + end + + if target_entities ~= nil then + for i=1,#target_entities,1 do + table.insert(targets,target_entities[i]) + end + end + + if targets ~= nil then + dbg_mobf.mob_state_lvl3("MOBF: Hunger found " .. #targets .. " targets") + for i=1,5,1 do + if #targets == 0 then + break + end + + local index = math.floor(math.random(1,#targets) + 0.5) + + local target = targets[index] + table.remove(targets,index) + + --target is a entity + if type(target) == "userdata" then + entity.dynamic_data.hunger = {} + entity.dynamic_data.hunger.target = target + --mobf_print("MOBF: saving hungerdata: " .. dump(entity.dynamic_data.hunger)) + return true + else + local targetpos = target + targetpos.y = targetpos.y +1 + + --if mob is not in air try 1 above for pathfinding + local current_node = minetest.get_node(pos) + if current_node ~= nil and + current_node.name ~= "air" then + pos.y = pos.y+1 + end + + local path = mobf_path.find_path(pos,targetpos,5,1,1,"A*_noprefetch") + + if path ~= nil then + entity.dynamic_data.hunger = {} + entity.dynamic_data.hunger.target = { x=targetpos.x,y=targetpos.y-1,z=targetpos.z} + entity.dynamic_data.hunger.path = path + --mobf_print("MOBF: Found new target: " .. printpos(targetpos) .. " Path: " .. dump(path)) + --mobf_print("MOBF: saving hungerdata: " .. dump(entity.dynamic_data.hunger)) + return true + else + dbg_mobf.mob_state_lvl2("MOBF: hunger no path to: " .. printpos(targetpos)) + end + end + end + end + return false + end --end of handler +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_state] BuiltinHungerLeave(mob) +-- +--! @brief prepare builtin hunger leave handler +--! @memberof mob_state +--! @public +-- +--! @param mob definition of mob +--! @return function to be called as leave handler +------------------------------------------------------------------------------- +function mob_state.BuiltinHungerLeave(mob) + + return function(entity,state) + --restore old stepheight + entity.object:set_properties({stepheight=entity.dynamic_data.hunger.old_stepheight}) + entity.dynamic_data.hunger = nil + p_mov_gen.set_cycle_path(entity,nil) + p_mov_gen.set_path(entity,nil) + p_mov_gen.set_end_of_path_handler(entity,nil) + + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_state] BuiltinHungerEnter(mob) +-- +--! @brief prepare builtin hunger enter handler +--! @memberof mob_state +--! @public +-- +--! @param mob definition of mob +--! @return function to be called as enter handler +------------------------------------------------------------------------------- +function mob_state.BuiltinHungerEnter(mob) + + return function(entity) + mobf_assert_backtrace(entity.dynamic_data.state.current.name == "RSVD_hunger") + mobf_assert_backtrace(entity.dynamic_data.hunger ~= nil) + + --use stepheight 1 as we did look for a path by using this + entity.dynamic_data.hunger.old_stepheight = entity.stepheight + + if (type(entity.dynamic_data.hunger.target) == "userdata") then + if not p_mov_gen.set_target(entity,entity.dynamic_data.hunger.target) then + dbg_mobf.mob_state_lvl1("MOBF: EnterHungerState, failed to set target") + end + else + entity.object:set_properties({stepheight=1}) + p_mov_gen.set_path(entity,entity.dynamic_data.hunger.path) + end + --p_mov_gen.set_cycle_path(entity,handler) + p_mov_gen.set_cycle_path(entity,false) + p_mov_gen.set_end_of_path_handler(entity,mob_state.BuiltinHungerTargetReached) + + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mob_state] BuiltinHungerTargetReached(entity) +-- +--! @brief handle target reached +--! @memberof mob_state +--! @public +-- +--! @param entity that reached the target +------------------------------------------------------------------------------- +function mob_state.BuiltinHungerTargetReached(entity) + --consume original target + if (entity.dynamic_data.hunger.target ~= nil) and + (entity.data.hunger.keep_food == nil or + entity.data.hunger.keep_food == false) then + if type(entity.dynamic_data.hunger.target) ~= "userdata" then + dbg_mobf.mob_state_lvl2("MOBF: consuming targetnode: " .. + printpos(entity.dynamic_data.hunger.target)) + minetest.remove_node(entity.dynamic_data.hunger.target) + else + local targetentity = entity.dynamic_data.hunger.target:get_luaentity() + + if targetentity ~= nil and + type(targetentity.mobf_hunger_interface) == "function" then + targetentity.mobf_hunger_interface(entity,"HUNGER_CONSUME") + dbg_mobf.mob_state_lvl2("MOBF: consuming targetentity") + end + end + end + + local eating_state = mob_state.get_state_by_name(entity,"eating") + + if eating_state ~= nil then + mob_state.change_state(entity,eating_state) + else + local default_state = mob_state.get_state_by_name(entity,"default") + mob_state.change_state(entity,default_state) + end +end \ No newline at end of file diff --git a/mods/mob_engines/mobf/mobf.lua b/mods/mob_engines/mobf/mobf.lua new file mode 100644 index 00000000..b665cefa --- /dev/null +++ b/mods/mob_engines/mobf/mobf.lua @@ -0,0 +1,1228 @@ +------------------------------------------------------------------------------- +-- 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 mobf.lua +--! @brief class containing mob initialization functions +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +-- +--! @defgroup mobf Basic mob entity functions +--! @brief a component containing basic functions for mob handling and initialization +--! @ingroup framework_int +--! @{ +-- +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- +mobf_assert_backtrace(not core.global_exists("mobf")) +--! @class mobf +--! @brief basic management component of mob functions +--!@} +mobf = {} + +mobf.on_step_callbacks = {} +mobf.on_punch_callbacks = {} +mobf.on_rightclick_callbacks = {} + + +------------------------------------------------------------------------------ +-- @function [parent=#mobf] register_on_step_callback(callback) +-- +--! @brief make a new on_step callback known to mobf +--! @memberof mobf +--! @public +-- +--! @param callback to make known to mobf +------------------------------------------------------------------------------- +function mobf.register_on_step_callback(callback) + + if callback.configcheck == nil or + type(callback.configcheck) ~= "function" then + return false + end + + if callback.handler == nil or + type(callback.configcheck) ~= "function" then + return false + end + + table.insert(mobf.on_step_callbacks,callback) +end + + +------------------------------------------------------------------------------ +-- @function [parent=#mobf] init_on_step_callbacks(entity,now) +-- +--! @brief initalize callbacks to be used on step +--! @memberof mobf +--! @private +-- +--! @param entity entity to initialize on_step handler +--! @param now current time +------------------------------------------------------------------------------- +function mobf.init_on_step_callbacks(entity,now) + entity.on_step_hooks = {} + + if #mobf.on_step_callbacks > 32 then + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!!: " + .. #mobf.on_step_callbacks + .. " incredible high number of onstep callbacks registred!") + end + + dbg_mobf.mobf_core_lvl2("MOBF: initializing " .. #mobf.on_step_callbacks + .. " on_step callbacks for " .. entity.data.name + .. " entity=" .. tostring(entity)) + for i = 1, #mobf.on_step_callbacks , 1 do + if mobf.on_step_callbacks[i].configcheck(entity) then + dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ") enabling callback " + .. mobf.on_step_callbacks[i].name) + table.insert(entity.on_step_hooks,mobf.on_step_callbacks[i].handler) + if type(mobf.on_step_callbacks[i].init) == "function" then + dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ")" + .." executing init function for " + .. mobf.on_step_callbacks[i].name) + mobf.on_step_callbacks[i].init(entity,now) + else + dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ")" + .." no init function defined") + end + else + dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ") callback " + .. mobf.on_step_callbacks[i].name + .. " disabled due to config check") + end + end + +end + +------------------------------------------------------------------------------ +-- @function [parent=#mobf] register_on_rightclick_callback(callback) +-- +--! @brief make a new on_rightclick callback known to mobf +--! @memberof mobf +--! @public +-- +--! @param callback to make known to mobf +------------------------------------------------------------------------------- +function mobf.register_on_rightclick_callback(callback) + + if callback.configcheck == nil or + type(callback.configcheck) ~= "function" then + return false + end + + if callback.handler == nil or + type(callback.configcheck) ~= "function" then + return false + end + + table.insert(mobf.on_rightclick_callbacks,callback) +end + + +------------------------------------------------------------------------------ +-- @function [parent=#mobf] register_on_punch_callback(callback) +-- +--! @brief make a new on_punch callback known to mobf +--! @memberof mobf +--! @public +-- +--! @param callback the callback to register in mobf +------------------------------------------------------------------------------- +function mobf.register_on_punch_callback(callback) + if callback.configcheck == nil or + type(callback.configcheck) ~= "function" then + return false + end + + if callback.handler == nil or + type(callback.configcheck) ~= "function" then + return false + end + + table.insert(mobf.on_punch_callbacks,callback) +end + + +------------------------------------------------------------------------------ +-- @function [parent=#mobf] init_on_punch_callbacks(entity,now) +-- +--! @brief initalize callbacks to be used on punch +--! @memberof mobf +--! @private +-- +--! @param entity entity to initialize on_punch handler +--! @param now current time +------------------------------------------------------------------------------- +function mobf.init_on_punch_callbacks(entity,now) + entity.on_punch_hooks = {} + + dbg_mobf.mobf_core_lvl2("MOBF: initializing " .. #mobf.on_punch_callbacks + .. " on_punch callbacks for " .. entity.data.name + .. " entity=" .. tostring(entity)) + for i = 1, #mobf.on_punch_callbacks , 1 do + if mobf.on_punch_callbacks[i].configcheck(entity) and + type(mobf.on_punch_callbacks[i].handler) == "function" then + dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ") enabling callback " + .. mobf.on_punch_callbacks[i].name) + table.insert(entity.on_punch_hooks,mobf.on_punch_callbacks[i].handler) + + if type(mobf.on_punch_callbacks[i].init) == "function" then + dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ")" + .." executing init function for " + .. mobf.on_punch_callbacks[i].name) + mobf.on_punch_callbacks[i].init(entity,now) + else + dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ")" + .." no init function defined") + end + else + dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ") callback " + .. mobf.on_punch_callbacks[i].name + .. " disabled due to config check") + end + end +end + +------------------------------------------------------------------------------ +-- @function [parent=#mobf] init_on_rightclick_callbacks(entity,now) +-- +--! @brief initalize callbacks to be used on punch +--! @memberof mobf +--! @private +-- +--! @param entity entity to initialize on_punch handler +--! @param now current time +------------------------------------------------------------------------------- +function mobf.init_on_rightclick_callbacks(entity,now) + entity.on_rightclick_hooks = {} + + dbg_mobf.mobf_core_lvl2("MOBF: initializing " .. #mobf.on_rightclick_callbacks + .. " on_rightclick callbacks for " .. entity.data.name + .. " entity=" .. tostring(entity)) + for i = 1, #mobf.on_rightclick_callbacks , 1 do + if mobf.on_rightclick_callbacks[i].configcheck(entity) and + type(mobf.on_rightclick_callbacks[i].handler) == "function" then + dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ") enabling callback " + .. mobf.on_rightclick_callbacks[i].name) + table.insert(entity.on_rightclick_hooks,mobf.on_rightclick_callbacks[i]) + + if type(mobf.on_rightclick_callbacks[i].init) == "function" then + dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ")" + .. " executing init function for " + .. mobf.on_rightclick_callbacks[i].name) + mobf.on_rightclick_callbacks[i].init(entity,now) + else + dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ")" + .. "no init function defined") + end + else + dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ") callback " + .. mobf.on_rightclick_callbacks[i].name + .. " disabled due to config check") + end + end + + if entity.data.generic.on_rightclick_callbacks ~= nil then + + for i = 1, #entity.data.generic.on_rightclick_callbacks, 1 do + if type(entity.data.generic.on_rightclick_callbacks[i].handler) == "function" and + type(entity.data.generic.on_rightclick_callbacks[i].name) == "string" and + (type(entity.data.generic.on_rightclick_callbacks[i].visiblename) == "string" or + type(entity.data.generic.on_rightclick_callbacks[i].visiblename) == "function") then + + table.insert(entity.on_rightclick_hooks, entity.data.generic.on_rightclick_callbacks[i]) + + if type(entity.data.generic.on_rightclick_callbacks[i].init) == "function" then + entity.data.generic.on_rightclick_callbacks[i].init(entity) + end + end + end + + end +end + +------------------------------------------------------------------------------ +-- @function [parent=#mobf] get_basepos(entity) +-- +--! @brief get basepos for an entity +--! @memberof mobf +--! @public +-- +--! @param entity entity to fetch basepos +--! @return basepos of mob +------------------------------------------------------------------------------- +function mobf.get_basepos(entity) + local pos = entity.object:getpos() + local nodeatpos = minetest.get_node(pos) + + dbg_mobf.mobf_core_helper_lvl3("MOBF: " .. entity.data.name + .. " Center Position: " .. printpos(pos) .. " is: " .. nodeatpos.name) + + -- if visual height is more than one block the center of base block is + -- below the entities center + if (entity.collisionbox[2] < -0.5) then + pos.y = pos.y + (entity.collisionbox[2] + 0.5) + dbg_mobf.mobf_core_helper_lvl3("MOBF: collision box lower end: " + .. entity.collisionbox[2]) + end + + nodeatpos = minetest.get_node(pos) + dbg_mobf.mobf_core_helper_lvl3("MOBF: Base Position: " .. printpos(pos) + .. " is: " .. nodeatpos.name) + + return pos +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf] get_persistent_data(entity) +-- +--! @brief get persistent data of a particular entity +--! @memberof mobf +--! @private +-- +--! @return persistent entity data +------------------------------------------------------------------------------- +function mobf.get_persistent_data(entity) + if (entity.dynamic_data.custom_persistent == nil) then + entity.dynamic_data.custom_persistent = {} + end + + return entity.dynamic_data.custom_persistent +end + +------------------------------------------------------------------------------ +-- @function [parent=#mobf] mobf_activate_handler(self,staticdata) +-- +--! @brief hanlder called for basic mob initialization +--! @memberof mobf +--! @private +-- +--! @param self entity to initialize onstep handler +--! @param staticdata data to use for initialization +------------------------------------------------------------------------------- +function mobf.activate_handler(self,staticdata) + local starttime = mobf_get_time_ms() + + if mobf_step_quota.is_exceeded() then + --mobf_print("MOBF: step quota exceeded for mob: ".. + -- self.data.name .." (" .. tostring(self) .. ")") + return + end + --do some initial checks + local pos = self.object:getpos() + + if pos == nil then + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: mob at nil pos!") + end + + local current_node = minetest.get_node(pos) + + if current_node == nil then + mobf_bug_warning(LOGLEVEL_ERROR, + "MOBF: trying to activate mob in nil node! removing") + + spawning.remove_uninitialized(self,staticdata) + mobf_step_quota.consume(starttime) + return + end + + --restore saved data + local preserved_data = mobf_deserialize_permanent_entity_data(staticdata) + self.dynamic_data.last_static_data = nil + + --check if position would collide with other entities + if not spawning.check_activation_overlap(self,pos,preserved_data) then + spawning.remove_uninitialized(self,staticdata) + mobf_step_quota.consume(starttime) + return + end + + --check if position environment os ok + if environment.is_media_element(current_node.name,self.environment.media) == false then + minetest.log(LOGLEVEL_WARNING,"MOBF: trying to activate mob " + .. self.data.name .. " at invalid position") + minetest.log(LOGLEVEL_WARNING," Activation at: " .. printpos(pos) .. " " + .. current_node.name .. " --> removing") + ----------------------------- + --TODO try to move 1 block up + ----------------------------- + spawning.remove_uninitialized(self,staticdata) + mobf_step_quota.consume(starttime) + return + end + + --reset replaced marker + self.replaced = nil + + ---------------------------------------------------------------------------- + -- initialize environment <-> mob <-> player interaction + ---------------------------------------------------------------------------- + local now = mobf_get_current_time() + + spawning.init_dynamic_data(self,now) + + mobf.init_on_step_callbacks(self,now) + mobf.init_on_punch_callbacks(self,now) + + --initialize ride support + mobf_ride.init(self) + + --check if this was a replacement mob + if self.dyndata_delayed ~= nil then + minetest.log(LOGLEVEL_ERROR,"MOBF: delayed activation for replacement mob." + .."This shouldn't happen at all but if we can fix it") + self.dynamic_data = dyndata_delayed.data + self.object:set_hp(dyndata_delayed.health) + graphics.setyaw(self, dyndata_delayed.entity_orientation) + self.dyndata_delayed = nil + self.dynamic_data.initialized = true + mobf_step_quota.consume(starttime) + return + end + + ---------------------------------------------------------------------------- + -- initialize preserved data + ---------------------------------------------------------------------------- + if self.dynamic_data.spawning ~= nil then + if mobf_pos_is_zero(preserved_data.spawnpoint) ~= true then + self.dynamic_data.spawning.spawnpoint = preserved_data.spawnpoint + else + self.dynamic_data.spawning.spawnpoint = mobf_round_pos(pos) + end + self.dynamic_data.spawning.player_spawned = preserved_data.playerspawned + + if preserved_data.original_spawntime ~= -1 then + self.dynamic_data.spawning.original_spawntime = preserved_data.original_spawntime + end + + if preserved_data.spawner ~= nil then + minetest.log(LOGLEVEL_INFO,"MOBF: setting spawner to: " + .. preserved_data.spawner) + self.dynamic_data.spawning.spawner = preserved_data.spawner + end + + --only relevant if mob has different states + if preserved_data.state ~= nil and + self.dynamic_data.state ~= nil then + minetest.log(LOGLEVEL_INFO,"MOBF: setting current state to: " + .. preserved_data.state) + self.dynamic_data.state.current = + mob_state.get_state_by_name(self,preserved_data.state) + end + + self.dynamic_data.textureidx = preserved_data.textureidx + end + + self.dynamic_data.custom_persistent = preserved_data.custom_persistent + + ---------------------------------------------------------------------------- + -- initialize mob state + -- ------------------------------------------------------------------------- + + local default_state = mob_state.get_state_by_name(self,"default") + + if self.dynamic_data.state.current == nil or + (self.dynamic_data.state.current.state_mode ~= "auto" and + self.dynamic_data.state.current.state_mode ~= "user_def" and + self.dynamic_data.state.current.state_mode ~= nil) or + (self.dynamic_data.state.current.HANDLER_precondition ~= nil and + (not self.dynamic_data.state.current.HANDLER_precondition(self,self.dynamic_data.state.current))) then + self.dynamic_data.state.current = default_state + end + + --user defined states are locked per definition + if self.dynamic_data.state.current.state_mode == "user_def" then + mob_state.lock(self,true) + end + + dbg_mobf.mobf_core_lvl2("MOBF: " .. self.data.name .. " restoring state: " + .. self.dynamic_data.state.current.name) + + + -- if there is a texturelist specified for this mob select one and stay there + if default_state.graphics_3d.texturelist ~= nil then + if self.dynamic_data.textureidx == nil then + self.dynamic_data.textureidx = math.random(1, #default_state.graphics_3d.texturelist) + end + mob_state.switch_model(self, self.dynamic_data.state.current) + end + + ---------------------------------------------------------------------------- + -- initializing movement engine + ---------------------------------------------------------------------------- + if self.dynamic_data.state.current.movgen ~= nil then + dbg_mobf.mobf_core_lvl1( + "MOBF: setting movegen to: " .. self.dynamic_data.state.current.movgen) + self.dynamic_data.current_movement_gen = + getMovementGen(self.dynamic_data.state.current.movgen) + else + dbg_mobf.mobf_core_lvl1( + "MOBF: setting movegen to: " .. default_state.movgen) + self.dynamic_data.current_movement_gen = + getMovementGen(default_state.movgen) + end + + if self.dynamic_data.state.current.animation ~= nil then + dbg_mobf.mobf_core_lvl1( + "MOBF: setting animation to: " .. self.dynamic_data.state.current.animation) + graphics.set_animation(self,self.dynamic_data.state.current.animation) + else + dbg_mobf.mobf_core_lvl1( + "MOBF: setting animation to: " .. dump(default_state.animation)) + graphics.set_animation(self,default_state.animation) + end + + mobf_assert_backtrace(self.dynamic_data.current_movement_gen ~= nil) + + --initialize movegen entity,current time, permanent data + self.dynamic_data.current_movement_gen.init_dynamic_data(self,now,preserved_data) + + --call enter state fct + if self.dynamic_data.state.current.HANDLER_enter_state ~= nil then + self.dynamic_data.state.current.HANDLER_enter_state(self) + end + + --initialize armor groups + if self.data.generic.armor_groups ~= nil then + self.object:set_armor_groups(self.data.generic.armor_groups) + end + + --initialize factions + mobf_factions.setupentity(self,preserved_data.factions) + + --initialize height level + environment.fix_base_pos(self, self.collisionbox[2] * -1) + + --custom on activate handler + if (self.data.generic.custom_on_activate_handler ~= nil) then + self.data.generic.custom_on_activate_handler(self) + end + + --check may need data present after initialization has completed + mobf.init_on_rightclick_callbacks(self,now) + + --add lifebar + if minetest.world_setting_get("mobf_lifebar") then + self.lifebar = mobf_lifebar.add(self) + mobf_lifebar.set(self.lifebar,self.object:get_hp()/self.hp_max) + end + + + ---------------------------------------------------------------------------- + -- activation may have been delayed due to quota + -- ------------------------------------------------------------------------- + if self.dynamic_data.delayed_placement ~= nil then + self.dynamic_data.spawning.player_spawned = + self.dynamic_data.delayed_placement.player_spawned + + self.dynamic_data.spawning.spawner = + self.dynamic_data.delayed_placement.spawner + + if self.dynamic_data.delayed_placement.callback ~= nil then + self.dynamic_data.delayed_placement.callback(self, + self.dynamic_data.delayed_placement.placer, + self.dynamic_data.delayed_placement.pointed_thing) + end + + self.dynamic_data.delayed_placement = nil + end + + --mark as initialized now + self.dynamic_data.initialized = true + mobf_step_quota.consume(starttime) +end + + +------------------------------------------------------------------------------ +-- @function [parent=#mobf] init_factions(entityn) +-- +--! @brief register mob to factions nod +--! @memberof mobf +--! @private +-- +--! @param entity entity to initialize +------------------------------------------------------------------------------- +function mobf.init_factions(entity) + + if not mobf_rtd.factions_available then + return + end + +end + +------------------------------------------------------------------------------ +-- @function [parent=#mobf] register_entity(entityname,graphics) +-- +--! @brief register an entity +--! @memberof mobf +--! @private +-- +--! @param name of entity to add +--! @param graphics graphics to use for entity +--! @param mob data to use +------------------------------------------------------------------------------- +function mobf.register_entity(name, cur_graphics, mob) + dbg_mobf.mobf_core_lvl1("MOBF: registering new entity: " .. name) + minetest.log(LOGLEVEL_NOTICE,"MOBF: registering new entity: \"" .. name .. "\"") + + mobf_assert_backtrace(environment_list[mob.generic.envid] ~= nil) + + local face_movement_dir = true + + if cur_graphics.model_orientation_fix ~= nil then + face_movement_dir = (cur_graphics.model_orientation_fix / math.pi) * 360 + 90 + end + + local footstep_sounds = true + + if mob.generic.makes_footstep_sound ~= nil then + footstep_sounds = mob.generic.makes_footstep_sound + end + + minetest.register_entity(name, + { + physical = true, + collisionbox = cur_graphics.collisionbox, + visual = cur_graphics.visual, + textures = cur_graphics.textures, + visual_size = cur_graphics.visual_size, + spritediv = cur_graphics.spritediv, + mesh = cur_graphics.mesh, + mode = cur_graphics.mode, + initial_sprite_basepos = {x=0, y=0}, + makes_footstep_sound = footstep_sounds, + automatic_rotate = true, + groups = mob.generic.groups, + hp_max = mob.generic.base_health, + stepheight = mob.generic.stepheight, + automatic_face_movement_dir = face_movement_dir, + automatic_face_movement_max_rotation_per_sec = 180, + + + + -- actions to be done by mob on its own + on_step = function(self, dtime) + local starttime = mobf_get_time_ms() + + if self.removed == true then + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: on_step: " + .. self.data.name .. " on_step for removed entity???? (" + .. tostring(self) .. ")") + mobf_warn_long_fct(starttime,"on_step_total_removed","on_step_total") + return + end + + if self.dynamic_data == nil then + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: on_step: " + .. "no dynamic data available!") + mobf_warn_long_fct(starttime,"on_step_total_no_dyn","on_step_total") + return + end + + if (self.dynamic_data.initialized ~= true) then + if entity_at_loaded_pos(self.object:getpos(),self.data.name) then + mobf.activate_handler(self,self.dynamic_data.last_static_data) + + --if quota is exceeded activation is delayed don't continue + --until initialization is done + if self.dynamic_data.initialized ~= true then + return + end + self.dynamic_data.last_static_data = nil + else + mobf_warn_long_fct(starttime,"on_step_total_no_init","on_step_total") + return + end + end + + + --do special ride callback + if mobf_ride.on_step_callback(self) then + mobf_warn_long_fct(starttime,"on_step_total_ride_cb","on_step_total") + return + end + + self.current_dtime = self.current_dtime + dtime + + if self.lifebar ~= nil then + local luaentity = self.lifebar:get_luaentity() + if luaentity ~= nil then + luaentity.lifetime = 0 + else + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: on_step: " + .. "trying to update lifebar but no luaentity present!") + self.lifebar = nil + end + end + + if mobf_step_quota.is_exceeded() then + return + end + + local quotatime = mobf_get_time_ms() + + local now = mobf_get_current_time() + + if self.current_dtime < 0.25 then + mobf_warn_long_fct(starttime,"on_step_total_pre_update","on_step_total") + mobf_step_quota.consume(quotatime) + return + end + + --check lifetime + if spawning.lifecycle_callback(self,now) == false then + mobf_warn_long_fct(starttime,"on_step_total_lifecycle","on_step_total") + mobf_step_quota.consume(quotatime) + return + end + + local movestart = mobf_get_time_ms() + --movement generator + self.dynamic_data.current_movement_gen.callback(self,now) + + mobf_warn_long_fct(movestart,"on_step_movement","movement") + + if self.on_step_hooks ~= nil then + if #self.on_step_hooks > 32 then + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!!: " + .. tostring(self) + .. " incredible high number of onstep hooks! " + .. #self.on_step_hooks .. " ohlist: " + .. tostring(self.on_step_hooks)) + end + + --dynamic modules + for i = 1, #self.on_step_hooks, 1 do + local cb_starttime = mobf_get_time_ms() + --check return value if on_step hook tells us to stop any other processing + if self.on_step_hooks[i](self,now,self.current_dtime) == false then + dbg_mobf.mobf_core_lvl1("MOBF: on_step: " .. self.data.name + .. " aborting callback processing entity=" .. tostring(self)) + break + end + mobf_warn_long_fct(cb_starttime,"callback_os_" .. self.data.name .. "_" .. i,"callback nr " .. i) + end + end + + mobf_warn_long_fct(starttime,"on_step_" .. self.data.name .. "_total","on_step_total") + self.current_dtime = 0 + mobf_step_quota.consume(quotatime) + end, + + --player <-> mob interaction + on_punch = function(self, hitter, time_from_last_punch, tool_capabilities, dir) + local starttime = mobf_get_time_ms() + local now = mobf_get_current_time() + + if self.dynamic_data.initialized ~= true then + return + end + + for i = 1, #self.on_punch_hooks, 1 do + if self.on_punch_hooks[i](self,hitter,now, + time_from_last_punch, tool_capabilities, dir) then + mobf_warn_long_fct(starttime,"onpunch_total","onpunch_total") + return + end + mobf_warn_long_fct(starttime,"callback nr " .. i, + "callback_op_".. self.data.name .. "_" .. i) + end + mobf_warn_long_fct(starttime,"onpunch_total","onpunch_total") + end, + + --rightclick handler + on_rightclick = mobf.rightclick_handler, + + --do basic mob initialization on activation + on_activate = function(self,staticdata) + local starttime = mobf_get_time_ms() + self.dynamic_data = {} + self.dynamic_data.initialized = false + + --check for replace in progress marker and transfer to entity + if spawning.replacing_NOW then + self.replaced = true + end + + --make sure entity is in loaded area at initialization + local pos = self.object:getpos() + + --remove from mob offline storage + spawning.activate_mob(self.data.modname .. ":" .. self.data.name,pos) + + if pos ~= nil and + entity_at_loaded_pos(pos,self.data.name) then + mobf.activate_handler(self,staticdata) + end + + if self.dynamic_data.initialized ~= true then + minetest.log(LOGLEVEL_INFO, + "MOBF: delaying activation") + if self.dynamic_data.last_static_data == nil and + staticdata ~= "" then + self.dynamic_data.last_static_data = staticdata + end + end + mobf_warn_long_fct(starttime,"onactivate_total_" .. self.data.name,"onactivate_total") + end, + + getbasepos = mobf.get_basepos, + get_persistent_data = mobf.get_persistent_data, + owner = function(entity) + if (entity.dynamic_data.spawning.spawner) then + return entity.dynamic_data.spawning.spawner + else + return nil + end + end, + + set_state = function(entity, statename) + local state = mob_state.get_state_by_name(entity,statename) + + if (state == nil) then + return false + end + + mob_state.change_state(entity,state) + return true + end, + get_state = function(entity) + local statename = entity.dynamic_data.state.current.name + local state = mob_state.get_state_by_name(entity,statename) + return statename, state + end, + is_on_ground = function(entity) + + local basepos = entity.getbasepos(entity) + local posbelow = { x=basepos.x, y=basepos.y-1,z=basepos.z} + + for dx=-1,1 do + for dz=-1,1 do + local fp0 = posbelow + local fp = { x= posbelow.x + (0.5*dx), + y= posbelow.y, + z= posbelow.z + (0.5*dz) } + local n = minetest.get_node(fp) + if not mobf_is_walkable(n) then + return true + end + end + end + + return false + end, + + --prepare permanent data + --NOTE this isn't called if a object is deleted + get_staticdata = function(self) + --add to mob offline storage + spawning.deactivate_mob(self.data.modname .. ":" .. self.data.name,self.object:getpos()) + return mobf_serialize_permanent_entity_data(self) + end, + + --custom variables for each mob + data = mob, + environment = environment_list[mob.generic.envid], + current_dtime = 0, + } + ) +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf] rightclick_handler(entity,clicker) +-- +--! @brief handle rightclick of mob +--! @memberof mobf +--! @private +-- +--! @param entity +--! @param clicker +-- +--! @return true/false if handled or not +------------------------------------------------------------------------------- +function mobf.rightclick_handler(entity,clicker) + local starttime = mobf_get_time_ms() + + if entity.dynamic_data.initialized ~= true then + return + end + + if #entity.on_rightclick_hooks >= 1 then + + --get rightclick storage id + local storage_id = mobf_global_data_store(entity) + local y_pos = 0.25 + local buttons = "" + + local playername = clicker:get_player_name() + + for i = 1, #entity.on_rightclick_hooks, 1 do + if entity.on_rightclick_hooks[i].privs == nil or + minetest.check_player_privs(playername, + entity.on_rightclick_hooks[i].privs) or + minetest.is_singleplayer() then + + local callback_storage_id = + mobf_global_data_store(entity.on_rightclick_hooks[i].handler) + buttons = buttons .. "button_exit[0," .. y_pos .. ";2.5,0.5;" .. + "mobfrightclick_" .. storage_id .. "_" .. + callback_storage_id .. ";" + + if type(entity.on_rightclick_hooks[i].visiblename) == "function" then + buttons = buttons .. + entity.on_rightclick_hooks[i].visiblename(entity, clicker) .. "]" + else + buttons = buttons .. + entity.on_rightclick_hooks[i].visiblename .. "]" + end + + y_pos = y_pos + 0.75 + end + end + + local y_size = y_pos + local formspec = "size[2.5," .. y_size .. "]" .. + buttons + + if playername ~= nil then + --TODO start form close timer + minetest.show_formspec(playername,"mobf_rightclick:main",formspec) + end + return true + end + + if #entity.on_rightclick_hooks == 1 then + entity.on_rightclick_hooks[1].handler(entity,clicker) + return true + end + + return false +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf] rightclick_button_handler(player,formname,fields) +-- +--! @brief handle button clicks as result of rightclick of mob +--! @memberof mobf +--! @private +-- +--! @param player issuer of rightclick +--! @param formname name of form that has been clicked +--! @param fields fields passed to form +-- +--! @return true/false if handled or not +------------------------------------------------------------------------------- +function mobf.rightclick_button_handler(player, formname, fields) + + if formname == "mobf_rightclick:main" then + for k,v in pairs(fields) do + local parts = string.split(k,"_") + + if parts[1] == "mobfrightclick" then + local entity_store_id = parts[2] + local callback_store_id = parts[3] + + dbg_mobf.mobf_core_lvl1("MOBF: rightclick button handler got: " + .. dump(fields) .. " parted to: " .. dump(parts)) + + local entity = mobf_global_data_get(entity_store_id) + local callback = mobf_global_data_get(callback_store_id) + + if entity ~= nil and + callback ~= nil then + callback(entity, player) + else + dbg_mobf.mobf_core_lvl1("MOBF: unable to do callback: " + .. dump(entity) .. " " .. dump(callback)) + end + end + end + return true + end + return false +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf] register_mob_item(mob) +-- +--! @brief add mob item for catchable mobs +--! @memberof mobf +--! @private +-- +--! @param name name of mob +--! @param modname name of mod mob is defined in +--! @param description description to use for mob +--! @param imagename name of itemimage to use +------------------------------------------------------------------------------- +function mobf.register_mob_item(name,modname,description, imagename) + minetest.register_craftitem(modname..":"..name, { + description = description, + image = imagename or modname.."_"..name.."_item.png", + on_place = function(item, placer, pointed_thing) + if pointed_thing.type == "node" then + local pos = pointed_thing.above + + local entity = + spawning.spawn_and_check(modname..":"..name,pos,"item_spawner") + + if entity ~= nil then + if entity.dynamic_data.spawning ~= nil then + entity.dynamic_data.spawning.player_spawned = true + + if placer:is_player(placer) then + minetest.log(LOGLEVEL_INFO,"MOBF: mob placed by " + .. placer:get_player_name(placer)) + entity.dynamic_data.spawning.spawner = + placer:get_player_name(placer) + end + + if entity.data.generic.custom_on_place_handler ~= nil then + entity.data.generic.custom_on_place_handler(entity, + placer, pointed_thing) + end + else + -------------------------------------------------------- + -- quota may have been exceeded make sure no data is lost + -------------------------------------------------------- + entity.dynamic_data.delayed_placement = { + player_spawned = true, + spawner = placer:get_player_name(placer), + placer = placer, + pointed_thing = pointed_thing, + callback = entity.data.generic.custom_on_place_handler + } + end + + item:take_item() + end + return item + end + end + }) +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf] blacklisthandling(mob) +-- +--! @brief add mob item for catchable mobs +--! @memberof mobf +--! @public +-- +--! @param mob +------------------------------------------------------------------------------- +function mobf.blacklisthandling(mob) + dbg_mobf.mobf_core_lvl2("MOBF: blacklisthandling for " .. + mob.modname .. ":" .. mob.name) + + local blacklisted = minetest.registered_entities[mob.modname.. ":"..mob.name] + + + local on_activate_fct = nil + + --remove unknown animal objects + if minetest.world_setting_get("mobf_delete_disabled_mobs") then + on_activate_fct = function(self,staticdata) + self.object:remove() + end + + --cleanup spawners too + if minetest.registered_entities[mob.modname.. ":"..mob.name] == nil and + environment_list[mob.generic.envid] ~= nil then + + if type(mob.spawning.primary_algorithms) == "table" then + for i=1 , #mob.spawning.primary_algorithms , 1 do + local sp = mob.spawning.primary_algorithms[i] + local cleanup = mobf_spawn_algorithms[sp.algorithm].initialize_cleanup + dbg_mobf.mobf_core_lvl2("MOBF: blacklist cleanup for primary spawner " .. sp.algorithm) + + if type(cleanup) == "function" then + cleanup(mob.modname.. ":" .. mob.name .. "_spawner_" .. sp.algorithm) + else + dbg_mobf.mobf_core_lvl2("MOBF: blacklist cleanup impossible - no cleanup function defined") + end + end + end + if type(mob.spawning.secondary_algorithms) == "table" then + for i=1 , #mob.spawning.secondary_algorithms , 1 do + + local sp = mob.spawning.secondary_algorithms[i] + local cleanup = mobf_spawn_algorithms[sp.algorithm].initialize_cleanup + dbg_mobf.mobf_core_lvl2("MOBF: blacklist cleanup for secondary spawner " .. sp.algorithm) + + if type(cleanup) == "function" then + cleanup(mob.modname.. ":" .. mob.name .. "_spawner_" .. sp.algorithm) + else + dbg_mobf.mobf_core_lvl2("MOBF: blacklist cleanup impossible - no cleanup function defined") + end + end + end + end + else + on_activate_fct = function(self,staticdata) + self.object:setacceleration({x=0,y=0,z=0}) + self.object:setvelocity({x=0,y=0,z=0}) + end + end + + if minetest.registered_entities[mob.modname.. ":"..mob.name] == nil then + + --cleanup mob entities + minetest.register_entity(mob.modname.. ":"..mob.name, + { + on_activate = on_activate_fct + }) + end + + + if blacklisted == nil then + minetest.log(LOGLEVEL_INFO,"MOBF: " .. mob.modname.. + ":"..mob.name .. " was blacklisted") + else + minetest.log(LOGLEVEL_ERROR,"MOBF: " .. mob.modname.. + ":"..mob.name .. " already known not registering mob with same name!") + end +end + +------------------------------------------------------------------------------- +--- @function [parent=#mobf] preserve_removed(entity,reason) +--- +---! @brief check if a mob needs to be preserved +---! @memberof mobf +---! @public +--- +---! @param entity entity to check +---! @param reason reason for removal +-------------------------------------------------------------------------------- +function mobf.preserve_removed(entity,reason) + + if reason ~= "cought" and + reason ~= "killed" and + reason ~= "died by sun" and + reason ~= "replaced" then + + if entity.dynamic_data.spawning.player_spawned then + local toset = {} + + toset.modname = entity.data.modname + toset.name = entity.data.name + toset.owner = entity.dynamic_data.spawning.spawner + toset.reason = reason + + if toset.owner ~= nil then + dbg_mobf.mobf_core_lvl2("MOBF: preserving " .. toset.modname .. + ":" .. toset.name .. " for player " .. toset.owner ) + table.insert(mobf.current_preserve_list,toset) + + mobf_set_world_setting("mobf_preserve_mobs", + minetest.serialize(mobf.current_preserve_list)) + else + dbg_mobf.mobf_core_lvl1("MOBF: unable to preserve mob") + end + else + dbg_mobf.mobf_core_lvl2("MOBF: not preserving " .. entity.data.name + .. " it's not playerspawned: " .. dump(entity.dynamic_data.spawning) ) + end + else + dbg_mobf.mobf_core_lvl2("MOBF: not preserving " .. entity.data.name + .. " removed by valid reason" ) + end +end + +------------------------------------------------------------------------------- +--- @function [parent=#mobf] check_definition(definition) +-- +--! @brief check if a mob definition contains obvious errors +--! @memberof mobf +--! @public +-- +--! @param entity entity to check +--! @return true/false +-------------------------------------------------------------------------------- +function mobf.check_definition(definition) + + if definition.name == nil or + definition.modname == nil then + minetest.log(LOGLEVEL_ERROR,"MOBF: name and modname are mandatory for ALL mobs!") + return false + end + + local default_state = nil + for i,v in ipairs(definition.states) do + if v.name == "default" then + default_state = v + break + end + end + + if default_state == nil then + minetest.log(LOGLEVEL_ERROR,"MOBF: default state is mandatory for ALL mobs!") + return false + end + + if default_state.movgen == nil then + minetest.log(LOGLEVEL_ERROR,"MOBF: movgen has to be specified for default state!") + return false + end + + if definition.attention then + + if not definition.attention.attention_distance then + minetest.log(LOGLEVEL_ERROR,"MOBF: \"attention\" requires " .. + "\"attention.attention_distance\" to be present.") + return false + end + + if not definition.attention.attention_max then + minetest.log(LOGLEVEL_ERROR,"MOBF: \"attention\" requires " .. + "\"attention.attention_max\" to be present.") + return false + end + + if not definition.attention.attention_distance_value then + minetest.log(LOGLEVEL_ERROR,"MOBF: \"attention\" requires " .. + "\"attention.attention_distance_value\" to be present.") + return false + end + + if definition.attention.hear_distance and + not definition.attention.hear_distance_value then + minetest.log(LOGLEVEL_ERROR,"MOBF: \"attention.hear_distance\" requires " .. + "\"attention.hear_distance_value\" to be present.") + return false + end + end + + if definition.combat then + + if definition.combat.melee then + if not definition.combat.melee.range then + minetest.log(LOGLEVEL_ERROR,"MOBF: \"combat.melee\" requires " .. + "\"combat.melee.range\" to be present.") + return false + end + + if not definition.combat.melee.maxdamage then + minetest.log(LOGLEVEL_ERROR,"MOBF: \"combat.melee\" requires " .. + "\"combat.melee.maxdamage\" to be present.") + return false + end + + if not definition.combat.melee.speed then + minetest.log(LOGLEVEL_ERROR,"MOBF: \"combat.melee\" requires " .. + "\"combat.melee.speed\" to be present.") + return false + end + end + + end + + -- TODO check all mandatory definition elements! + + return true +end diff --git a/mods/mob_engines/mobf/models/mobf_path_marker.b3d b/mods/mob_engines/mobf/models/mobf_path_marker.b3d new file mode 100644 index 00000000..df7a057b Binary files /dev/null and b/mods/mob_engines/mobf/models/mobf_path_marker.b3d differ diff --git a/mods/mob_engines/mobf/models/path_marker.blend b/mods/mob_engines/mobf/models/path_marker.blend new file mode 100644 index 00000000..a00909de Binary files /dev/null and b/mods/mob_engines/mobf/models/path_marker.blend differ diff --git a/mods/mob_engines/mobf/mov_gen_none.lua b/mods/mob_engines/mobf/mov_gen_none.lua new file mode 100644 index 00000000..cfc1a774 --- /dev/null +++ b/mods/mob_engines/mobf/mov_gen_none.lua @@ -0,0 +1,110 @@ +------------------------------------------------------------------------------- +-- 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 mov_gen_none.lua +--! @brief a dummy movement gen +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +mobf_assert_backtrace(not core.global_exists("mgen_none")) +--! @class mgen_none +--! @brief a movement generator doing nothing +mgen_none = {} + +--!@} + +--! @brief movement generator identifier +--! @memberof mgen_none +mgen_none.name = "none" + +------------------------------------------------------------------------------- +-- name: callback(entity,now) +-- +--! @brief main callback to do nothing +--! @memberof mgen_none +-- +--! @param entity mob to generate movement for +--! @param now current time +------------------------------------------------------------------------------- +function mgen_none.callback(entity,now) + mobf_assert_backtrace(entity ~= nil) + + local pos = entity.getbasepos(entity) + + local current_state = environment.pos_is_ok(pos,entity) + + if current_state == "in_water" or + current_state == "in_air" or + current_state == "above_water" then + spawning.remove(entity, "mgen none envcheck") + return + end + + local speed = entity.object:getvelocity() + local default_y_acceleration = environment.get_default_gravity(pos, + entity.environment.media, + entity.data.movement.canfly) + + entity.object:setacceleration({x=0,y=default_y_acceleration,z=0}) + + if default_y_acceleration ~= 0 then + entity.object:setvelocity({x=0,y=speed.y,z=0}) + else + entity.object:setvelocity({x=0,y=0,z=0}) + end + +end + +------------------------------------------------------------------------------- +-- name: initialize() +-- +--! @brief initialize movement generator +--! @memberof mgen_none +--! @public +------------------------------------------------------------------------------- +function mgen_none.initialize(entity,now) +end + +------------------------------------------------------------------------------- +-- name: init_dynamic_data(entity,now) +-- +--! @brief initialize dynamic data required by movement generator +--! @memberof mgen_none +--! @public +-- +--! @param entity mob to initialize dynamic data +--! @param now current time +------------------------------------------------------------------------------- +function mgen_none.init_dynamic_data(entity,now) + + local data = { + moving = false, + } + + entity.dynamic_data.movement = data +end + +------------------------------------------------------------------------------- +-- name: set_target(entity,target) +-- +--! @brief set target for movgen +--! @memberof mgen_none +--! @public +-- +--! @param entity mob to apply to +--! @param target to set +------------------------------------------------------------------------------- +function mgen_none.set_target(entity,target) + return false +end + +--register this movement generator +registerMovementGen(mgen_none.name,mgen_none) \ No newline at end of file diff --git a/mods/mob_engines/mobf/movement_gen_registry.lua b/mods/mob_engines/mobf/movement_gen_registry.lua new file mode 100644 index 00000000..47076cd1 --- /dev/null +++ b/mods/mob_engines/mobf/movement_gen_registry.lua @@ -0,0 +1,74 @@ +------------------------------------------------------------------------------- +-- 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 movement_gen_registry.lua +--! @brief registry for movement generators +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- +mobf_assert_backtrace(not core.global_exists("movement_generators")) +movement_generators = {} + + +------------------------------------------------------------------------------- +-- name: getMovementGen(id) +-- +--! @brief get movement generator specified by id +--! @ingroup framework_int +-- +--! @param id id of movementgenerator +--! @return module pointer for movementgenerator +------------------------------------------------------------------------------- +function getMovementGen(id) + if movement_generators[id] == nil then + local name = "" + if id ~= nil then + name = id + end + minetest.log(LOGLEVEL_ERROR,"MOBF: movement generator " .. dump(id) .. " not found!") + return nil + end + return movement_generators[id] +end + +------------------------------------------------------------------------------- +-- name: registerMovementGen(name,generator) +-- +--! @brief register a movement generator to mob framework +--! @ingroup framework_mob +-- +--! @param name id to use for movement generator +--! @param generator pointer to movement generator +--! @return true/false successfully added generator +------------------------------------------------------------------------------- +function registerMovementGen(name,generator) + + --some movement gen checks + + if generator.init_dynamic_data == nil then + return false + end + + if generator.callback == nil then + return false + end + if movement_generators[name] == nil then + movement_generators[name] = generator + + minetest.log(LOGLEVEL_NOTICE,"\tRegistering movement generator ".. name) + if generator.initilize ~= nil then + generator.initialize() + end + return true + else + return false + end + +end \ No newline at end of file diff --git a/mods/mob_engines/mobf/movement_generic.lua b/mods/mob_engines/mobf/movement_generic.lua new file mode 100644 index 00000000..6f92d6d9 --- /dev/null +++ b/mods/mob_engines/mobf/movement_generic.lua @@ -0,0 +1,226 @@ +------------------------------------------------------------------------------- +-- 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 movement_generic.lua +--! @brief generic movement related functions +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +--! @defgroup generic_movement Generic movement functions +--! @brief Movement related functions used by different movement generators +--! @ingroup framework_int +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- +mobf_assert_backtrace(not core.global_exists("movement_generic")) +movement_generic = {} + +--!@} + +------------------------------------------------------------------------------- +-- @function [parent=#movement_generic] get_accel_to(new_pos,entity,ymovement,accel) +-- +--! @brief calculate a random speed directed to new_pos +-- +--! @param new_pos position to go to +--! @param entity mob to move +--! @param ymovement current movement in y direction +--! @return { x,y,z } random speed directed to new_pos +------------------------------------------------------------------------------- +-- +function movement_generic.get_accel_to(new_pos, entity, ymovement, accel) + + if new_pos == nil or entity == nil then + minetest.log(LOGLEVEL_CRITICAL, + "MOBF: movement_generic.get_accel_to : Invalid parameters") + end + + local old_pos = entity.object:getpos() + local node = minetest.get_node(old_pos) + local maxaccel = entity.data.movement.max_accel + local minaccel = entity.data.movement.min_accel + + local yaccel = environment.get_default_gravity(old_pos, + entity.environment.media, + entity.data.movement.canfly) + mobf_assert_backtrace(yaccel ~= nil) + + --calculate plane direction to target + local xz_direction = + mobf_calc_yaw(new_pos.x-old_pos.x,new_pos.z-old_pos.z) + + local absolute_accel = nil + --find a new speed + if not accel then + absolute_accel = minaccel + (maxaccel - minaccel) * math.random() + else + absolute_accel = accel + end + + local new_accel_vector = nil + + --flying mob calculate accel towards target + if entity.data.movement.canfly and + yaccel == 0 then + local xz_direction,xy_direction = mobf_calc_direction(old_pos,new_pos) + + new_accel_vector = + mobf_calc_vector_components_3d(xz_direction, + xy_direction, + absolute_accel) + + if (new_pos.y > old_pos.y) then + mobf_assert_backtrace(new_accel_vector.y >= 0) + end + + if (new_pos.y < old_pos.y) then + mobf_assert_backtrace(new_accel_vector.y <= 0) + end + + else + new_accel_vector = + mobf_calc_vector_components(xz_direction,absolute_accel) + new_accel_vector.y = yaccel + end + + return new_accel_vector +end + + + +------------------------------------------------------------------------------- +-- @function [parent=#movement_generic] calc_new_pos(pos,acceleration,prediction_time) +-- +--! @brief calc the position a mob would be after a specified time +-- this doesn't handle velocity changes due to colisions +-- +--! @param pos position +--! @param acceleration acceleration to predict pos +--! @param prediction_time time to predict pos +--! @param current_velocity current velocity of mob +--! @return { x,y,z } position after specified time +------------------------------------------------------------------------------- +function movement_generic.calc_new_pos(pos,acceleration,prediction_time,current_velocity) + + local predicted_pos = {x=pos.x,y=pos.y,z=pos.z} + + predicted_pos.x = predicted_pos.x + current_velocity.x * prediction_time + (acceleration.x/2)*math.pow(prediction_time,2) + predicted_pos.z = predicted_pos.z + current_velocity.z * prediction_time + (acceleration.z/2)*math.pow(prediction_time,2) + + + return predicted_pos +end + +------------------------------------------------------------------------------- +-- @function [parent=#movement_generic] predict_next_block(pos,velocity,acceleration) +-- +--! @brief predict next block based on pos velocity and acceleration +-- +--! @param pos current position +--! @param velocity current velocity +--! @param acceleration current acceleration +--! @return { x,y,z } position of next block +------------------------------------------------------------------------------- +function movement_generic.predict_next_block(pos,velocity,acceleration) + + local prediction_time = 2 + + local pos_predicted = movement_generic.calc_new_pos(pos, + acceleration, + prediction_time, + velocity + ) + local count = 1 + + --check if after prediction time we would have traveled more than one block and adjust to not predict to far + while mobf_calc_distance(pos,pos_predicted) > 1 do + + pos_predicted = movement_generic.calc_new_pos(pos, + acceleration, + prediction_time - (count*0.1), + velocity + ) + + if (prediction_time - (count*0.1)) < 0 then + minetest.log(LOGLEVEL_WARNING,"MOBF: Bug!!!! didn't find a suitable prediction time. Mob will move more than one block within prediction period") + break + end + + count = count +1 + end + + return pos_predicted +end + +------------------------------------------------------------------------------- +-- @function [parent=#movement_generic] predict_enter_next_block(entity,pos,velocity,acceleration) +-- +--! @brief predict next block based on pos velocity and acceleration +-- +--! @param entity entitiy to do prediction for +--! @param pos current position +--! @param velocity current velocity +--! @param acceleration current acceleration +--! @return { x,y,z } position of next block +------------------------------------------------------------------------------- +function movement_generic.predict_enter_next_block(entity,pos,velocity,acceleration) + + + local cornerpositions = {} + + table.insert(cornerpositions,{x=pos.x + entity.collisionbox[4] -0.01,y=pos.y,z=pos.z + entity.collisionbox[6] -0.01}) + table.insert(cornerpositions,{x=pos.x + entity.collisionbox[4] -0.01,y=pos.y,z=pos.z + entity.collisionbox[3] +0.01}) + table.insert(cornerpositions,{x=pos.x + entity.collisionbox[1] +0.01,y=pos.y,z=pos.z + entity.collisionbox[6] -0.01}) + table.insert(cornerpositions,{x=pos.x + entity.collisionbox[1] +0.01,y=pos.y,z=pos.z + entity.collisionbox[3] +0.01}) + + local sameblock = function(a,b) + for i=1,#a,1 do + if not mobf_pos_is_same( + mobf_round_pos(a[i]), + mobf_round_pos(b[i])) then + return false + end + end + return true + end + + local prediction_time = 0.1 + local predicted_corners = {} + + for i=1,#cornerpositions,1 do + predicted_corners[i] = movement_generic.calc_new_pos(cornerpositions[i], + acceleration, + prediction_time, + velocity + ) + end + + --check if any of the corners is in different block after prediction time + while sameblock(cornerpositions,predicted_corners) and + prediction_time < 2 do + + prediction_time = prediction_time + 0.1 + + for i=1,#cornerpositions,1 do + predicted_corners[i] = movement_generic.calc_new_pos(cornerpositions[i], + acceleration, + prediction_time, + velocity + ) + end + + end + + local pos_predicted = movement_generic.calc_new_pos(pos, + acceleration, + prediction_time, + velocity + ) + + return pos_predicted +end \ No newline at end of file diff --git a/mods/mob_engines/mobf/path.lua b/mods/mob_engines/mobf/path.lua new file mode 100644 index 00000000..569ac631 --- /dev/null +++ b/mods/mob_engines/mobf/path.lua @@ -0,0 +1,667 @@ +------------------------------------------------------------------------------- +-- Mob Framework Settings 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 path.lua +--! @brief path support for mobf +--! @copyright Sapier +--! @author Sapier +--! @date 2013-02-09 +-- +-- Contact sapier a t gmx net + +-- Boilerplate to support localized strings if intllib mod is installed. +local S +if (minetest.get_modpath("intllib")) then + dofile(minetest.get_modpath("intllib").."/intllib.lua") + S = intllib.Getter(minetest.get_current_modname()) +else + S = function ( s ) return s end +end + +------------------------------------------------------------------------------- +mobf_assert_backtrace(not core.global_exists("mobf_path")) +mobf_path = {} + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] find_path() +-- +--! @brief workaround for broken mintest find_path function +--! @ingroup mobf_path +------------------------------------------------------------------------------- +function mobf_path.find_path(pos1,pos2,searchdistance,max_jump,max_drop,algorithm) + + local interim_path = minetest.find_path(pos1, pos2, searchdistance, + max_jump, max_drop, algorithm) + if interim_path == nil then + return nil + end + + local pos_n_minor_1 = nil + local pos_n_minor_2 = nil + + local path_optimized = {} + + table.insert(path_optimized, interim_path[1]) + + for i,v in ipairs(interim_path) do + if ( pos_n_minor_1 ~= nil ) and ( pos_n_minor_2 ~= nil) then + + local x_pitch = pos_n_minor_2.x - v.x + local y_pitch = pos_n_minor_2.y - v.y + local z_pitch = pos_n_minor_2.z - v.z + + local x_delta = (pos_n_minor_1.x - pos_n_minor_2.x) / x_pitch; + local y_delta = (pos_n_minor_1.y - pos_n_minor_2.y) / y_pitch; + local z_delta = (pos_n_minor_1.z - pos_n_minor_2.z) / z_pitch; + + if (x_pitch ~= 0) and (y_pitch ~= 0) and (x_delta ~= y_delta ) then + table.insert(path_optimized, pos_n_minor_1) + elseif (y_pitch ~= 0) and (z_pitch ~= 0) and (y_delta ~= z_delta) then + table.insert(path_optimized, pos_n_minor_1) + elseif (x_pitch ~= 0) and (z_pitch ~= 0) and (y_delta ~= z_delta) then + table.insert(path_optimized, pos_n_minor_1) + end + end + + + pos_n_minor_2 = pos_n_minor_1 + pos_n_minor_1 = v + end + + table.insert(path_optimized, interim_path[#interim_path]) + + return path_optimized +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] init() +-- +--! @brief initialize path subsystem +--! @ingroup mobf_path +------------------------------------------------------------------------------- +function mobf_path.init() + mobf_path.load() + + if mobf_rtd.path_data == nil then + mobf_rtd.path_data = {} + end + + if mobf_rtd.path_data.users == nil then + mobf_rtd.path_data.users = {} + end + + --register path marker entity + minetest.register_entity("mobf:path_marker_entity", + { + physical = false, + collisionbox = {-0.5,-0.5,-0.5,0.5,1.5,0.5 }, + visual = "mesh", + textures = { "mobf_path_marker.png" }, + mesh = "mobf_path_marker.b3d", + initial_sprite_basepos = {x=0, y=0}, + automatic_rotate = 2, + + on_step = function(self,dtime) + + if self.creationtime == nil then + self.creationtime = 0 + end + + self.creationtime = self.creationtime + dtime + + if self.creationtime > 30 then + self.object:remove() + end + end + }) + + minetest.register_craftitem(":mobf:path_marker", { + description = S("Path marker tool"), + image = "mobf_path_marker_item.png", + on_place = function(item, placer, pointed_thing) + if pointed_thing.type == "node" then + local pos = pointed_thing.above + + local entity = + spawning.spawn_and_check("mobf:path_marker_entity", + pos,"path_marker_click") + + if entity ~= nil then + mobf_path.handle_path_marker_place(placer,pos) + end + return item + end + end + }) + + minetest.register_on_player_receive_fields(mobf_path.button_handler) +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] save() +-- +--! @brief save all path data +--! @ingroup mobf_path +------------------------------------------------------------------------------- +function mobf_path.save() + mobf_set_world_setting("mobf_path_data",minetest.serialize(mobf_rtd.path_data)) +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] load() +-- +--! @brief save all path data +--! @ingroup mobf_path +------------------------------------------------------------------------------- +function mobf_path.load() + local paths_raw = mobf_get_world_setting("mobf_path_data") + + if paths_raw ~= nil then + mobf_rtd.path_data = minetest.deserialize(mobf_get_world_setting("mobf_path_data")) + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] handle_path_marker_place(placer,pos) +-- +--! @brief initialize path subsystem +--! @ingroup mobf_path +-- +--! @param placer player object placing the path marker +--! @param pos position placed at +------------------------------------------------------------------------------- +function mobf_path.handle_path_marker_place(placer,pos) + + mobf_assert_backtrace(placer ~= nil) + mobf_assert_backtrace(pos ~= nil) + + if placer:is_player() then + local playername = placer:get_player_name() + local player_paths = mobf_path.get_editable_path_names(playername) + mobf_path.show_add_point_menu(playername,player_paths,pos) + end +end + + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] get_editable_path_names(playername) +-- +--! @brief get list of pathnames for a player +--! @ingroup mobf_path +-- +--! @param playername name of player to get paths for +-- +--! @return list of names +------------------------------------------------------------------------------- +function mobf_path.get_editable_path_names(playername) + + if mobf_rtd.path_data.users[playername] == nil then + return nil + end + + if mobf_rtd.path_data.users[playername].paths == nil then + return nil + end + + local retval = {} + + for k,v in pairs(mobf_rtd.path_data.users[playername].paths) do + if not v.locked then + table.insert(retval,k) + end + end + + if #retval > 0 then + return retval + end + + return nil +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] add_point(playername,pathname,point) +-- +--! @brief add a point to a path +--! @ingroup mobf_path +-- +--! @param playername name of player to store point +--! @param pathname name of path to add point to +--! @param point point to add +------------------------------------------------------------------------------- +function mobf_path.add_point(playername,pathname,point) + + if mobf_rtd.path_data.users[playername] == nil then + mobf_rtd.path_data.users[playername] = {} + end + + if mobf_rtd.path_data.users[playername].paths == nil then + mobf_rtd.path_data.users[playername].paths = {} + end + + if mobf_rtd.path_data.users[playername].paths[pathname] == nil then + mobf_rtd.path_data.users[playername].paths[pathname] = {} + mobf_rtd.path_data.users[playername].paths[pathname].locked = false + mobf_rtd.path_data.users[playername].paths[pathname].points = {} + end + + table.insert(mobf_rtd.path_data.users[playername].paths[pathname].points,point) +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] show_add_point_menu(pathnames) +-- +--! @brief show a menu containing all paths a point may be added to +--! @ingroup mobf_path +-- +--! @param playername player to show menu +--! @param pathnames names of paths +--! @param point point to add +------------------------------------------------------------------------------- +function mobf_path.show_add_point_menu(playername,pathnames,point) + local buttons = "" + local y_pos = 0.25 + local storage_id = mobf_global_data_store(point) + if pathnames ~= nil then + + + + for i = 1, #pathnames, 1 do + buttons = buttons .. "button_exit[0," .. y_pos .. ";4.5,0.5;" .. + "mobfpath:existing:" .. storage_id .. + ":" .. pathnames[i] .. ";" .. pathnames[i] .. "]" + y_pos = y_pos + 0.75 + end + end + + local y_size = y_pos + 3 * 0.75 - 0.25 + --add new path element + local formspec = "size[4.5," .. y_size .. "]" .. + buttons .. + "label[0," .. y_pos .. ";-----------------------------]" .. + "field[0.25," .. (y_pos + 1) .. ";4.5,0.5;new_path_name;;]" .. + "button_exit[1.5," .. (y_pos + 1.5) .. ";1.5,0.5;mobfpath:addnew:" .. + storage_id .. ";new path]" + + --show formspec + minetest.show_formspec(playername,"mobf:path:path_name_menu",formspec) +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] button_handler(player, formname, fields) +-- +--! @brief handle button click in mobf_path menu +--! @ingroup mobf_path +-- +--! @param player player issuing click +--! @param formname name of form beeing clicked +--! @param fields data submitted to form +-- +--! @return true/false event has been handled by this handler +------------------------------------------------------------------------------- +function mobf_path.button_handler(player, formname, fields) + local playername = player:get_player_name() + + mobf_assert_backtrace(playername ~= nil) + + if formname == "mobf:path:path_name_menu" then + dbg_mobf.path_lvl2("MOBF: Path marker rightclick path selected") + for k,v in pairs(fields) do + local parts = string.split(k,":") + + if parts[1] == "mobfpath" then + local point = mobf_global_data_get(parts[3]) + local pathname = parts[4] + if parts[2] == "addnew" then + pathname = fields.new_path_name + end + + if point ~= nil and + pathname ~= nil and + pathname ~= "" then + mobf_path.add_point(playername,pathname,point) + mobf_path.save() + end + end + end + return true + end + + if formname == "mobf:path:add_path_to_entity" then + dbg_mobf.path_lvl2("MOBF: Adding path to an entity") + for k,v in pairs(fields) do + local parts = string.split(k,":") + if parts[1] == "mobfpath" then + local entity = mobf_global_data_get(parts[2]) + local pathname = parts[3] + + if entity ~= nil and + pathname ~= nil and + mobf_rtd.path_data.users[playername].paths[pathname] ~= nil and + entity.data.patrol ~= nil then + + --switch to guard state + mobf_path.switch_patrol(entity,playername,pathname) + + end + end + end + return true + end + + --not handled by this callback + return false +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] delete_path(ownername,pathname) +-- +--! @brief show path markers +--! @ingroup mobf_path +-- +--! @param ownername name of path owner +--! @param pathname name of path +-- +------------------------------------------------------------------------------- +function mobf_path.delete_path(ownername, pathname) + dbg_mobf.path_lvl1("MOBF: delete path issued: " + .. pathname .. " owner: " .. ownername) + mobf_rtd.path_data.users[ownername].paths[pathname] = nil + mobf_path.save() +end +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] show_pathmarkers(ownername,pathname) +-- +--! @brief show path markers +--! @ingroup mobf_path +-- +--! @param ownername name of path owner +--! @param pathname name of path +-- +------------------------------------------------------------------------------- +function mobf_path.show_pathmarkers(ownername,pathname) + for i,v in ipairs(mobf_rtd.path_data.users[ownername].paths[pathname].points) do + local objects = minetest.get_objects_inside_radius(v,0.5) + + dbg_mobf.path_lvl3("MOBF: got " .. #objects .. + " around pos checking for marker") + local found = false; + for i=1,#objects,1 do + local luaentity = objects[i]:get_luaentity() + dbg_mobf.path_lvl3("MOBF: checking: " .. dump(luaentity)) + if luaentity.name == "mobf:path_marker_entity" then + found = true + break + end + end + + local node_at = minetest.get_node(v) + + if not found and + node_at.name ~= nil and + node_at.name ~= "ignore" then + spawning.spawn_and_check("mobf:path_marker_entity",v,"mark_path") + end + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] get_pathlist(playername,isadmin) +-- +--! @brief get a list of paths for a specific player +--! @ingroup mobf_path +-- +--! @param playername name of player to get paths +--! @param isadmin does this player have admin rights? +-- +--! @return list of paths +------------------------------------------------------------------------------- +function mobf_path.get_pathlist(playername,isadmin) + local retval = {} + if isadmin then + for local_playername,userdata in pairs(mobf_rtd.path_data.users) do + for pathname,path in pairs(userdata.paths) do + dbg_mobf.path_lvl3("MOBF: Adding path: " .. pathname .. + " data:" .. dump(path)) + local toadd = { + ownername = local_playername, + pathname = pathname + } + dbg_mobf.path_lvl3("MOBF: Adding path entry: " .. dump(toadd)) + table.insert(retval,toadd) + end + end + else + if playername ~= nil and + mobf_rtd.path_data.users[playername] ~= nil then + for pathname,path in pairs(mobf_rtd.path_data.users[playername].paths) do + local toadd = { + ownername = playername, + pathname = pathname + } + table.insert(retval,toadd) + end + end + end + + return retval +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] make_button_name(buttonid,data) +-- +--! @brief create a button name +--! @ingroup mobf_path +-- +--! @param buttonid id to use for this button +--! @param data information to add to this button +-- +--! @return string containing data +------------------------------------------------------------------------------- +function mobf_path.make_button_name(buttonid,data) + local retval = buttonid .. ":" + + if data.pathname ~= nil then + retval = retval .. data.pathname .. ":" + else + retval = retval .. ":" + end + + if data.ownername ~= nil then + retval = retval .. data.ownername .. ":" + else + retval = retval .. ":" + end + + return retval +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] parse_button_name(datastring) +-- +--! @brief get data from button name +--! @ingroup mobf_path +-- +--! @param datastring name to parse +-- +--! @return parsed data +------------------------------------------------------------------------------- +function mobf_path.parse_button_name(datastring) + mobf_assert_backtrace(datastring ~= nil) + + local data = {} + local parts = string.split(datastring,":") + + data.buttonid = parts[1] + data.pathname = parts[2] + data.ownername = parts[3] + if data.pathname == "" then + data.pathname = nil + data.ownername = nil + end + + return data +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] mob_rightclick_callback(entity,player) +-- +--! @brief do rightclick action +--! @ingroup mobf_path +-- +--! @param entity mobf rightclicked +--! @param player issuing rightclick +------------------------------------------------------------------------------- + +function mobf_path.mob_rightclick_callback(entity,player) + + local playername = player:get_player_name() + + if entity.dynamic_data.spawning.spawner ~= playername then + core.show_formspec(playername,"mobf:path:add_path_to_entity", + "size[4,1]label[0,0;This is not your mob keep away!]" .. + "button_exit[1,0.75;2,0.5;btn_exit;Okay Okay!]") + return + end + + if entity.dynamic_data.patrol_state_before ~= nil then + mobf_path.switch_patrol(entity,nil,nil) + else + local buttons = "" + local y_pos = 0.25 + local storage_id = mobf_global_data_store(entity) + local playername = player:get_player_name() + + local pathlist = mobf_path.get_pathlist(playername,false) + + dbg_mobf.path_lvl2("MOBF: Pathlist contains: " .. dump(pathlist)) + + for i = 1, #pathlist, 1 do + buttons = buttons .. "button_exit[0," .. y_pos .. ";4.5,0.5;" .. + "mobfpath:" .. storage_id .. + ":" .. pathlist[i].pathname .. ";" .. pathlist[i].pathname .. "]" + y_pos = y_pos + 0.75 + end + + local y_size = y_pos - 0.25 + local formspec = "size[4.5," .. y_size .. "]" .. + buttons + + --show formspec + core.show_formspec(playername,"mobf:path:add_path_to_entity",formspec) + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] config_check(entity) +-- +--! @brief check if mob is configured as trader +--! @ingroup mobf_path +-- +--! @param entity mob being checked +--! @return true/false if trader or not +------------------------------------------------------------------------------- +function mobf_path.config_check(entity) + if entity.data.patrol ~= nil then + return true + end + + return false +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] buttontext(entity) +-- +--! @brief return text for rightclick button +--! @ingroup mobf_path +-- +--! @param entity to get text for +--! @return buttonname +------------------------------------------------------------------------------- +function mobf_path.buttontext(entity) + if entity.dynamic_data.patrol_state_before == nil then + return "Select path" + end + + return "Disable path" +end + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] switch_patrol(entity,points) +-- +--! @brief check if mob is configured as trader +--! @ingroup mobf_path +-- +--! @param entity mob being switched +--! @param pathowner owner of path to switch to +--! @param pathname path to switch to +------------------------------------------------------------------------------- +function mobf_path.switch_patrol(entity,pathowner,pathname) + if pathowner ~= nil and + pathname ~= nil and + entity.data.patrol.state ~= nil then + + if entity.dynamic_data.patrol_state_before == nil then + + if entity.dynamic_data.state.current ~= entity.data.patrol.state then + entity.dynamic_data.patrol_state_before = entity.dynamic_data.state.current + else + entity.dynamic_data.patrol_state_before = "default" + end + mob_state.lock(entity,true) + end + local new_state = mob_state.get_state_by_name(entity,entity.data.patrol.state) + + mobf_assert_backtrace(new_state ~= nil) + mob_state.change_state(entity,new_state) + entity.dynamic_data.p_movement.pathowner = pathowner + entity.dynamic_data.p_movement.pathname = pathname + entity.dynamic_data.p_movement.path = + mobf_rtd.path_data.users[pathowner].paths[pathname].points + entity.dynamic_data.p_movement.next_path_index = 1 + else + if entity.dynamic_data.patrol_state_before ~= nil then + local new_state = mob_state.get_state_by_name(entity,entity.dynamic_data.patrol_state_before) + + if new_state == nil then + new_state = mob_state.get_state_by_name(entity,"default") + end + + mobf_assert_backtrace(new_state ~= nil) + mob_state.change_state(entity,new_state) + entity.dynamic_data.patrol_state_before = nil + mob_state.lock(entity,false) + end + end +end + + +------------------------------------------------------------------------------- +-- @function [parent=#mobf_path] getpoints(owner,name) +-- +--! @brief get a path by owner and name +--! @ingroup mobf_path +-- +--! @param pathowner player owning the path +--! @param pathname name of path +--! @return list of points +------------------------------------------------------------------------------- +function mobf_path.getpoints(pathowner,pathname) + if mobf_rtd.path_data.users[pathowner] == nil then + dbg_mobf.path_lvl2("MOBF: no paths for " .. dump(pathowner) .. " found") + return nil + end + + if mobf_rtd.path_data.users[pathowner].paths[pathname] == nil then + dbg_mobf.path_lvl2( + "MOBF: no path " .. dump(pathname) .. + " found for owner " .. pathowner .. + " have paths: " .. dump(mobf_rtd.path_data.users[pathowner].paths)) + return nil + end + + return mobf_rtd.path_data.users[pathowner].paths[pathname].points +end diff --git a/mods/mob_engines/mobf/random_drop.lua b/mods/mob_engines/mobf/random_drop.lua new file mode 100644 index 00000000..10bc7710 --- /dev/null +++ b/mods/mob_engines/mobf/random_drop.lua @@ -0,0 +1,199 @@ +------------------------------------------------------------------------------- +-- 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 random_drop.lua +--! @brief component containing random drop features +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +--! @defgroup randdrop Random Drop subcomponent +--! @brief Component handling all random drops +--! @ingroup framework_int +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- +mobf_assert_backtrace(not core.global_exists("random_drop")) +--! @class random_drop +--! @brief random drop features e.g lay eggs +--!@} +random_drop = {} + + + +------------------------------------------------------------------------------- +-- @function [parent=#random_drop] callback(entity) +-- +--! @brief random drop periodic callback +--! @memberof random_drop +-- +--! @param entity mob calling it +--! @param now current time +------------------------------------------------------------------------------- +function random_drop.callback(entity,now) + if entity.data.random_drop ~= nil and + entity.dynamic_data.random_drop ~= nil and + entity.data.random_drop.result ~= "" then + + dbg_mobf.random_drop_lvl3("MOBF: random drop for ".. entity.data.name .." is set") + + if entity.dynamic_data.random_drop.ts_last_drop + entity.data.random_drop.min_delay < now then + + dbg_mobf.random_drop_lvl3("MOBF: enough time passed give drop a chance") + if math.random() < entity.data.random_drop.chance then + + entity.dynamic_data.random_drop.ts_last_drop = now + + local entitybasepos = entity.getbasepos(entity) + + --find pos around + local toput = environment.get_suitable_pos_same_level(entitybasepos,1,entity) + + if toput ~= nil then + minetest.add_entity(toput,entity.data.random_drop.result.."_ent") + dbg_mobf.random_drop_lvl3("MOBF: adding random drop for "..entity.data.name .. ": "..entity.data.random_drop.result.."_ent" .. " at " .. printpos(toput)) + if entity.data.sound ~= nil then + sound.play(entitybasepos,entity.data.sound.random_drop) + end + else + dbg_mobf.random_drop_lvl2("MOBF: didn't find a place to put random drop for ".. entity.data.name) + end + end + + end + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#random_drop] register_random_drop(random_drop) +-- +--! @brief register random drop item and entity +--! @memberof random_drop +-- +--! @param random_drop configuration for random drop feature +------------------------------------------------------------------------------- +function random_drop.register(random_drop) + + --get basename from random drop item name + local start_pos = 1 + local end_pos = string.find(random_drop.result,":") + + if end_pos == nil then + return + end + + local drop_basename = string.sub(random_drop.result,start_pos,end_pos-1) + local drop_itemname = string.sub(random_drop.result,end_pos+1) + + + if drop_itemname == nil or + drop_basename == nil then + return + end + + minetest.log(LOGLEVEL_INFO,"MOBF:\tregistering random drop entity: "..":"..random_drop.result.."_ent".. + " item="..drop_itemname .. " basename=" .. drop_basename) + + local ent_graphics = {} + local id = drop_basename .. "_" .. drop_itemname + + if minetest.world_setting_get("mobf_disable_3d_mode") or + animalmaterialsdata[id] == nil or + animalmaterialsdata[id].graphics_3d == nil then + ent_graphics.visual = "sprite" + ent_graphics.textures = {drop_basename .. "_"..drop_itemname..".png"} + ent_graphics.collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5} + else + ent_graphics.visual = animalmaterialsdata[id].graphics_3d.visual + ent_graphics.mesh = animalmaterialsdata[id].graphics_3d.mesh + ent_graphics.textures = animalmaterialsdata[id].graphics_3d.textures + ent_graphics.collisionbox = animalmaterialsdata[id].graphics_3d.collisionbox + ent_graphics.visual_size = animalmaterialsdata[id].graphics_3d.visual_size + end + + + --Entity + minetest.register_entity(":"..random_drop.result.."_ent", + { + physical = true, + collisionbox = ent_graphics.collisionbox, + visual = ent_graphics.visual, + textures = ent_graphics.textures, + mesh = ent_graphics.mesh, + visual_size = ent_graphics.visual_size, + on_drop_callback= random_drop.on_drop_callback, + on_timeout_callback= random_drop.on_timeout_callback, + + on_activate = function(self,staticdata) + + self.object:setacceleration({x=0,y=-9.81,z=0}) + local now = mobf_get_current_time() + + if staticdata == "" then + self.dropped = now + if type(self.on_drop_callback) == "function" then + self:on_drop_callback() + end + else + + self.dropped = tonumber(staticdata) + end + + if self.dropped + self.random_drop_max_life < now then + dbg_mobf.random_drop_lvl2("MOBF: random drop entity timed out") + self.object:remove() + end + end, + + on_punch = function(self, hitter) + hitter:get_inventory():add_item("main", random_drop.result.." 1") + self.object:remove() + end, + + on_step = function(self,dtime) + if self.dropped + self.random_drop_max_life < mobf_get_current_time() then + dbg_mobf.random_drop_lvl2("MOBF: random drop entity timed out") + if type(self.on_timeout_callback) == "function" then + self:on_timeout_callback() + end + self.object:remove() + end + + end, + + + get_staticdata = function(self) + return self.dropped + end, + + random_drop_max_life = random_drop.min_delay/2, + dropped = 0, + + }) +end + +------------------------------------------------------------------------------- +-- @function [parent=#random_drop] init_dynamic_data(entity,now) +-- +--! @brief initialize dynamic data required by random drop +--! @memberof random_drop +-- +--! @param entity mob to add data +--! @param now current time +------------------------------------------------------------------------------- +function random_drop.init_dynamic_data(entity,now) + if entity.data.random_drop ~= nil and + entity.data.random_drop.min_delay > 5 then + entity.dynamic_data.random_drop = { + ts_last_drop = now + math.random(5,entity.data.random_drop.min_delay) + } + else + entity.dynamic_data.random_drop = { + ts_last_drop = now + } + end +end diff --git a/mods/mob_engines/mobf/ride.lua b/mods/mob_engines/mobf/ride.lua new file mode 100644 index 00000000..9b6ec119 --- /dev/null +++ b/mods/mob_engines/mobf/ride.lua @@ -0,0 +1,321 @@ +------------------------------------------------------------------------------- +-- 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 ride.lua +--! @brief class containing mobf functions for riding +--! @copyright Sapier +--! @author Sapier +--! @date 2013-01-06 +-- +-- +--! @defgroup mobf_ride Rideable mobs subcomponent +--! @brief a component containing all functions required to ride a mob +--! @ingroup framework_int +--! @{ +-- +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- +mobf_assert_backtrace(not core.global_exists("mobf_ride")) +--! @class mobf_ride +--! @brief contains all riding specific functions +--! @} + +mobf_ride = {} + +------------------------------------------------------------------------------ +-- @function [parent=#mobf_ride] attach_player(entity,player) +-- +--! @brief make a player ride this mob +--! @class mobf_ride +--! @private +-- +--! @param entity entity to be ridden +--! @param player player riding +------------------------------------------------------------------------------- +function mobf_ride.attach_player(entity,player) + + entity.dynamic_data.ride.is_attached = true + entity.dynamic_data.ride.player = player + entity.object:setacceleration({x=0,y=-9.81,z=0}) + entity.object:setvelocity({x=0,y=-9.81,z=0}) + + local attacheoffset = {x=0,y=0.5,z=0} + + if entity.data.ride ~= nil and + entity.data.ride.attacheoffset ~= nil then + attacheoffset = entity.data.ride.attacheoffset + end + + player:set_attach(entity.object,"",attacheoffset, {x=0,y=90,z=0}) + +-- default always overrides animations even for attached players +-- if type(default.player_set_animation) == "function" then +-- default.player_set_animation(player, "sit") +-- end + if entity.data.ride.texturemod ~= nil then + entity.object:settexturemod(entity.data.ride.texturemod); + end +end + +------------------------------------------------------------------------------ +-- @function [parent=#mobf_ride] dettach_player(entity,player) +-- +--! @brief make a player ride this mob +--! @class mobf_ride +--! @private +-- +--! @param entity entity to be ridden +------------------------------------------------------------------------------- +function mobf_ride.dettach_player(entity) + + entity.dynamic_data.ride.is_attached = false + entity.dynamic_data.ride.player:set_detach() + entity.dynamic_data.ride.player = nil + entity.object:settexturemod(""); +end + + +------------------------------------------------------------------------------ +-- @function [parent=#mobf_ride] on_step_callback(entity) +-- +--! @brief make a player ride this mob +--! @class mobf_ride +--! @public +-- +--! @param entity entity to be ridden +------------------------------------------------------------------------------- +function mobf_ride.on_step_callback(entity) + + if entity.dynamic_data.ride.is_attached then + dbg_mobf.ride_lvl3("MOBF: have attached player") + local walkspeed = 3 + local sneakspeed = 0.5 + local jumpspeed = 30 + local runspeed = walkspeed + + if entity.data.ride ~= nil then + if entity.data.ride.walkspeed ~= nil then + walkspeed = entity.data.ride.walkspeed + end + + if entity.data.ride.runspeed ~= nil then + runspeed = entity.data.ride.runspeed + end + + if entity.data.ride.sneakspeed ~= nil then + sneakspeed = entity.data.ride.sneakspeed + end + + if entity.data.ride.jumpspeed ~= nil then + jumpspeed = entity.data.ride.jumpspeed + end + end + + local dir = entity.dynamic_data.ride.player:get_look_yaw() + local current_speed = entity.object:getacceleration() + + local speed_to_set = {x=0,y=current_speed.y,z=0} + if dir ~= nil then + local playerctrl = entity.dynamic_data.ride.player:get_player_control() + + if playerctrl ~= nil then + + local setspeed = false + + if playerctrl.jump and + entity.is_on_ground(entity) then + speed_to_set.y = jumpspeed + setspeed = true + end + + --just set speed to playerview direction + if playerctrl.up then + setspeed = true + end + + --invert playerview direction + if playerctrl.down then + dir = dir + math.pi + setspeed = true + end + + if playerctrl.left then + if playerctrl.up then + dir = dir + math.pi/4 + elseif playerctrl.down then + dir = dir - math.pi/4 + else + dir = dir + math.pi/2 + end + setspeed = true + end + + if playerctrl.right then + if playerctrl.up then + dir = dir - math.pi/4 + elseif playerctrl.down then + dir = dir + math.pi/4 + else + dir = dir - math.pi/2 + end + setspeed = true + end + + local selected_speed = walkspeed + + if playerctrl.sneak then + selected_speed = sneakspeed + end + + + + if setspeed then + local speed_to_set_xz = mobf_calc_vector_components(dir,selected_speed) + + speed_to_set.x = speed_to_set_xz.x + speed_to_set.z = speed_to_set_xz.z + + if entity.data.ride.walk_anim ~= nil then + graphics.set_animation(entity,entity.data.ride.walk_anim) + end + else + if entity.data.ride.walk_anim ~= nil then + if entity.data.ride.stand_anim ~= nil then + graphics.set_animation(entity,entity.data.ride.stand_anim) + mob_state.change_state(entity,mob_state.get_state_by_name(entity,entity.data.ride.state_stand)) + else + graphics.set_animation(entity,"stand") + end + end + end + + entity.object:setvelocity(speed_to_set) + + --fix switched model orientation + graphics.setyaw(entity,dir) + end + + + end + return true + else + return false + end +end + +------------------------------------------------------------------------------ +-- @function [parent=#mobf_ride] on_punch_callback(entity,player) +-- +--! @brief make a player ride this mob +--! @class mobf_ride +--! @public +-- +--! @param entity entity to be ridden +--! @param player player riding +------------------------------------------------------------------------------- +function mobf_ride.on_punch_callback(entity,player) + dbg_mobf.ride_lvl2("MOBF: ride on punch callback") + print("MOBF: ride on punch callback") + + local saddle = "animalmaterials:saddle" + + if entity.data.ride.saddle ~= nil then + saddle = entity.data.ride.saddle + end + --detache + if entity.dynamic_data.ride.is_attached ~= false then + dbg_mobf.ride_lvl2("MOBF: punched ridden mob") + if entity.dynamic_data.ride.player == player then + dbg_mobf.ride_lvl2("MOBF: detaching player") + mobf_ride.dettach_player(entity) + player:get_inventory():add_item("main",saddle .. " 1") + return true + end + else + --check if player has saddle + dbg_mobf.ride_lvl2("MOBF: punched free mob") + if player:get_wielded_item():get_name() == saddle then + dbg_mobf.ride_lvl2("MOBF: punching with saddle") + + if player:get_inventory():contains_item("main",saddle .. " 1") then + dbg_mobf.ride_lvl2("MOBF: have saddle") + mobf_ride.attach_player(entity,player) + player:get_inventory():remove_item("main",saddle .. " 1") + return true + end + else + dbg_mobf.ride_lvl2("MOBF: not punching with saddle but: " .. player:get_wielded_item():get_name()) + end + end + + return false +end + +------------------------------------------------------------------------------ +-- @function [parent=#mobf_ride] is_enabled(entity) +-- +--! @brief check if riding is enabled for a mob +--! @class mobf_ride +--! @public +-- +--! @param entity entity to be ridden +------------------------------------------------------------------------------- +function mobf_ride.is_enabled(entity) + if entity.data.ride ~= nil then + return true + end + dbg_mobf.ride_lvl2("riding of " .. entity.data.name .. " is disabled") + return false +end + +------------------------------------------------------------------------------ +-- @function [parent=#mobf_ride] init(entity) +-- +--! @brief initialize ride dynamic data +--! @class mobf_ride +--! @public +-- +--! @param entity entity to be ridden +------------------------------------------------------------------------------- +function mobf_ride.init(entity) + local data = { + is_attached = false, + player = nil, + } + + entity.dynamic_data.ride = data +end + +-- special handler on leave +minetest.register_on_leaveplayer( function(player) + if player ~= nil and + player.object ~= nil then + local pos = player.object:getpos() + + --print("MOBF: got player position: " ..printpos(pos) ) + + if pos ~= nil then + local objects = get_objects_inside_radius(pos, 5) + + print("MOBF: found " .. dump(#objects) .. " objects around player") + + if objects ~= nil then + for i=1,#objects,1 do + local entity = objects[i]:get_luaentity() + if entity ~= nil and + entity.dynamic_data ~= nil and + entity.dynamic_data.ride ~= nil and + entity.dynamic_data.ride.player == player then + print("MOBF: found player to be attached") + ride.dettach_player(entity) + break + end + end + end + end + end +end) \ No newline at end of file diff --git a/mods/mob_engines/mobf/sound.lua b/mods/mob_engines/mobf/sound.lua new file mode 100644 index 00000000..d33ad5f5 --- /dev/null +++ b/mods/mob_engines/mobf/sound.lua @@ -0,0 +1,205 @@ +------------------------------------------------------------------------------- +-- 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 sound.lua +--! @brief component containing sound related functions +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- +--! @defgroup grp_sound Sound subcomponent +--! @brief Component handling all sound related actions +--! @ingroup framework_int +--! @{ +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- +mobf_assert_backtrace(not core.global_exists("sound")) + +--! @class sound +--! @brief sound selection and play functions +--!@} +sound = {} + +------------------------------------------------------------------------------- +-- @function [parent=#sound] play(entity) +-- +--! @brief play a sound at a specified position +--! @memberof sound +-- +--! @param param1 position to play sound at or playername to play sound for +--! @param soundspec sound to play +------------------------------------------------------------------------------- +function sound.play(param1, soundspec) + + local pos = nil + local playername = nil + + if type(param1) == "string" then + playername = param1 + else + pos = param1 + end + + if (soundspec ~= nil) then + + local toplay = { + gain = soundspec.gain, + pos = pos, + to_player = playername, + max_hear_distance = soundspec.max_hear_distance, + loop = false, + } + + minetest.sound_play(soundspec.name,toplay) + else + dbg_mobf.sound_lvl2("MOBF: no soundspec") + --todo add log entry + end +end + + +------------------------------------------------------------------------------- +-- @function [parent=#sound] play_random(entity,now) +-- +--! @brief play a random sound for mob +--! @memberof sound +-- +--! @param entity mob to do action +--! @param now current time +------------------------------------------------------------------------------- +function sound.play_random(entity,now) + + if entity.dynamic_data == nil or + entity.dynamic_data.sound == nil then + mobf_bug_warning(LOGLEVEL_ERROR, + "MOBF BUG!!!: >" ..entity.data.name .. "< removed=" .. + dump(entity.removed) .. " entity=" .. tostring(entity) .. + " sound callback without dynamic data") + return + end + + if entity.data.sound ~= nil and + entity.data.sound.random ~= nil then + + + -- check for old style random sound definition using chance + min_delta + if entity.data.sound.random.min_delta ~= nil and + entity.dynamic_data.sound.random_last + + entity.data.sound.random.min_delta > now then + return + end + + -- check for old style random sound definition using chance + if entity.data.sound.random.chance ~= nil and + math.random() > entity.data.sound.random.chance then + return + end + + -- check for new style sounds done by gauss distribution + if entity.dynamic_data.sound.random_next ~= nil and + entity.dynamic_data.sound.random_next > now then + return + end + + -- init variable to be passed to play + local toplay = nil + + -- sound list mode + if entity.data.sound.random.list ~= nil then + + -- select random sound from list + local current_random_sound = + math.floor(math.random(1,#entity.data.sound.random.list) + 0.5) + toplay = entity.data.sound.random.list[current_random_sound] + dbg_mobf.sound_lvl3("MOBF: selected random sound: " .. + current_random_sound .. "/" .. #entity.data.sound.random.list) + -- single sound mode + elseif entity.data.sound.random.name ~= nil then + toplay = entity.data.sound.random + else + dbg_mobf.sound_lvl1("MOBF: invalid random sound configuration") + end + + if toplay ~= nil then + sound.play(entity.object:getpos(),toplay) + entity.dynamic_data.sound.random_last = now + dbg_mobf.sound_lvl3("MOBF: playing sound: " .. toplay.name) + end + + if entity.dynamic_data.sound.random_next ~= nil then + if entity.data.sound.random.interval == nil or + entity.data.sound.random.max_interval_deviation == nil then + dbg_mobf.sound_lvl1("MOBF: invalid random sound configuration," .. + " missing \"interval\" or \"max_interval_deviation\"") + return + end + + local delta_next = sound.calc_random_sound_delay(entity) + + dbg_mobf.sound_lvl1("MOBF: next random sound in: " .. delta_next .. "s") + entity.dynamic_data.sound.random_next = now + delta_next + else + entity.dynamic_data.sound.random_last = now + end + end +end + +------------------------------------------------------------------------------- +-- @function [parent=#sound] sound.calc_random_sound_delay(entity) +-- +--! @brief calculate delay for random sound +--! @memberof sound +-- +--! @param entity mob to calc for +------------------------------------------------------------------------------- +function sound.calc_random_sound_delay(entity) + + local base_value = mobf_gauss(entity.data.sound.random.interval, + entity.data.sound.random.max_interval_deviation/20) + + local delta_next = math.max(entity.data.sound.random.interval - + entity.data.sound.random.max_interval_deviation, + base_value) + delta_next = math.min(delta_next, + entity.data.sound.random.interval + + entity.data.sound.random.max_interval_deviation) + + return delta_next +end + +------------------------------------------------------------------------------- +-- @function [parent=#sound] sound.init_dynamic_data(entity, now) +-- +--! @brief initialize all dynamic data for sound on activate +--! @memberof sound +-- +--! @param entity mob to initialize +--! @param now current time +------------------------------------------------------------------------------- +function sound.init_dynamic_data(entity, now) + local data = { + random_last = now, + } + + if entity.data.sound.random ~= nil and + entity.data.sound.random.interval ~= nil and + entity.data.sound.random.max_interval_deviation then + + local delta_next = sound.calc_random_sound_delay(entity) + + data.random_next = delta_next + now + dbg_mobf.sound_lvl2("MOBF: initalizing random_next to: " .. delta_next + .. " Interval: " .. entity.data.sound.random.interval .. " Deviation: " + .. entity.data.sound.random.max_interval_deviation) + elseif entity.data.sound.random ~= nil and + entity.data.sound.random.chance == nil then + dbg_mobf.sound_lvl1("MOBF: invalid random sound definition for \"" .. + entity.data.name .. "\" ... neither invterval nor probability mode configured") + end + + entity.dynamic_data.sound = data +end \ No newline at end of file diff --git a/mods/mob_engines/mobf/spawning.lua b/mods/mob_engines/mobf/spawning.lua new file mode 100644 index 00000000..ea4dc155 --- /dev/null +++ b/mods/mob_engines/mobf/spawning.lua @@ -0,0 +1,661 @@ +------------------------------------------------------------------------------- +-- 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 spawning.lua +--! @brief component containing spawning features +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +-- + +--! @defgroup spawning Spawn mechanisms +--! @brief all functions and variables required for automatic mob spawning +--! @ingroup framework_int +--! @{ +-- +--! @defgroup spawn_algorithms (DEPRECATED) Spawn algorithms +--! @brief spawn algorithms provided by previous mob framework versions. New +--! mobs are strongly encouraged to use advanced spawning mob as spawning will +--! be removed from mobf as of version 2.5 +-- +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- +mobf_assert_backtrace(not core.global_exists("spawning")) +--! @class spawning +--! @brief spawning features +spawning = {} + +--!@} + +mobf_assert_backtrace(not core.global_exists("mobf_spawn_algorithms")) +--! @brief registry for spawn algorithms +--! @memberof spawning +--! @private +mobf_spawn_algorithms = {} + +------------------------------------------------------------------------------- +-- name: init() +-- @function [parent=#spawning] init +-- +--! @brief initialize spawning data +--! @memberof spawning +-- +------------------------------------------------------------------------------- +function spawning.init() + --read from file + local world_path = minetest.get_worldpath() + + local file,error = io.open(world_path .. "/mobf_spawning_data","r") + + if file ~= nil then + local data_raw = file:read("*a") + file:close() + + if data_raw ~= nil then + spawning.mob_spawn_data = minetest.deserialize(data_raw) + end + end + + if spawning.mob_spawn_data == nil then + spawning.mob_spawn_data = {} + end + + --register spawndata persistent storer to globalstep + minetest.after(300,spawning.preserve_spawn_data,true) + + --register cleanup handler + minetest.register_on_shutdown(function(dstep) spawning.preserve_spawn_data(false) end) +end + +------------------------------------------------------------------------------- +-- name: preserve_spawn_data() +-- @function [parent=#spawning] preserve_spawn_data +-- +--! @brief save data on regular base +--! @memberof spawning +-- +--! @param cyclic if this is true spawn data is saved in cyclic intervals +------------------------------------------------------------------------------- +function spawning.preserve_spawn_data(cyclic) + + local world_path = minetest.get_worldpath() + local file,error = io.open(world_path .. "/mobf_spawning_data","w") + + if error ~= nil then + minetest.log(LOGLEVEL_ERROR,"MOBF: failed to spawning preserve file") + end + mobf_assert_backtrace(file ~= nil) + + local serialized_data = minetest.serialize(spawning.mob_spawn_data) + + file:write(serialized_data) + + if cyclic then + minetest.after(300,spawning.preserve_spawn_data,cyclic) + end +end + +------------------------------------------------------------------------------- +-- name: total_offline_mobs() +-- @function [parent=#spawning] total_offline_mobs +-- +--! @brief count total number of offline mobs +--! @memberof spawning +-- +--! @return number of mobs +------------------------------------------------------------------------------- +function spawning.total_offline_mobs() + local count = 0 + for key,value in pairs(spawning.mob_spawn_data) do + for hash,v in pairs(value) do + count = count +1 + end + end + + return count +end + +------------------------------------------------------------------------------- +-- name: count_deactivated_mobs(name,pos,range) +-- @function [parent=#spawning] count_deactivated_mobs +-- +--! @brief count number of mobs of specific type within a certain range +--! @memberof spawning +-- +--! @param name name of mob to count +--! @param pos to check distance to +--! @param range to check +-- +--! @return number of mobs +------------------------------------------------------------------------------- +function spawning.count_deactivated_mobs(name,pos,range) + local count = 0 + if spawning.mob_spawn_data[name] ~= nil then + for hash,v in pairs(spawning.mob_spawn_data[name]) do + local mobpos = mobf_hash_to_pos(hash) + local distance = vector.distance(pos,mobpos) + if distance < range then + local node = core.get_node(mobpos) + local notfound = true + -- if we are within active object range and + -- that position is loaded check if there's really a mob at that location + if node.name ~= "ignore" and distance < 32 then + local found = false + local objects_around = core.get_objects_inside_radius(mobpos, 1) + if objects_around and #objects_around > 0 then + for i,v in ipairs(objects_around) do + local luaentity = v:get_luaentity() + if luaentity ~= nil then + if luaentity.data ~= nil and + luaentity.data.name == name then + found = true + break + end + end + end + end + if not found then + dbg_mobf.spawning_lvl2( + "MOBF: clearing stall deactivated entry at: " .. + core.pos_to_string(mobpos)) + notfound = false + spawning.mob_spawn_data[name][hash] = nil + end + end + + if notfound then + count = count +1 + end + end + end + end + return count +end + +------------------------------------------------------------------------------- +-- name: deactivate_mob(entity) +-- @function [parent=#spawning] deactivate_mob +-- +--! @brief add mob to deactivated list +--! @memberof spawning +-- +--! @param name name of mob to be deactivated +--! @param pos position to deactivate mob at +------------------------------------------------------------------------------- +function spawning.deactivate_mob(name,pos) + if spawning.mob_spawn_data[name] == nil then + spawning.mob_spawn_data[name] = {} + end + + local rounded_pos = vector.round(pos) + local hash = minetest.hash_node_position(rounded_pos) + --assert (mobf_pos_is_same(mobf_hash_to_pos(hash),rounded_pos)) + spawning.mob_spawn_data[name][hash] = true +end + +------------------------------------------------------------------------------- +-- name: activate_mob(name,pos) +-- @function [parent=#spawning] preserve_spawn_data +-- +--! @brief save data on regular base +--! @memberof spawning +-- +--! @param name name of mob to be activated +--! @param pos position to activate mob at +------------------------------------------------------------------------------- +function spawning.activate_mob(name,pos) + if spawning.mob_spawn_data[name] ~= nil then + local rounded_pos = vector.round(pos) + local hash = minetest.hash_node_position(rounded_pos) + --assert(mobf_pos_is_same(mobf_hash_to_pos(hash),rounded_pos)) + spawning.mob_spawn_data[name][hash] = nil + end +end + + +------------------------------------------------------------------------------- +-- name: remove_uninitialized(entity,staticdata) +-- @function [parent=#spawning] remove_uninitialized +-- +--! @brief remove a spawn point based uppon staticdata supplied +--! @memberof spawning +-- +--! @param entity to remove +--! @param staticdata of mob +------------------------------------------------------------------------------- +function spawning.remove_uninitialized(entity, staticdata) + --entity may be known in spawnlist + if staticdata ~= nil then + local permanent_data = mobf_deserialize_permanent_entity_data(staticdata) + if (permanent_data.spawnpoint ~= nil) then + + --prepare information required to remove entity + entity.dynamic_data = {} + entity.dynamic_data.spawning = {} + entity.dynamic_data.spawning.spawnpoint = permanent_data.spawnpoint + entity.dynamic_data.spawning.player_spawned = permanent_data.playerspawned + entity.dynamic_data.spawning.spawner = permanent_data.spawner + + spawning.remove(entity,"remove uninitialized") + end + else + dbg_mobf.spawning_lvl1("MOBF: remove uninitialized entity=" .. tostring(entity)) + --directly remove it can't be known to spawnlist + entity.object:remove() + end +end + +------------------------------------------------------------------------------- +-- name: remove(entity) +-- @function [parent=#spawning] remove +-- +--! @brief remove a mob +--! @memberof spawning +-- +--! @param entity mob to remove +--! @param reason text to log as reason for removal +------------------------------------------------------------------------------- +function spawning.remove(entity,reason) + local pos = entity.object:getpos() + dbg_mobf.spawning_lvl3("MOBF: --> remove " .. printpos(pos)) + if entity ~= nil then + entity.removed = true + dbg_mobf.spawning_lvl1("MOBF: remove entity=" .. tostring(entity)) + if minetest.world_setting_get("mobf_log_removed_entities") then + if reason == nil then + reason = "unknown" + end + minetest.log(LOGLEVEL_NOTICE,"MOBF: removing " .. entity.data.name .. + " at " .. printpos(pos) .. " due to: " .. reason) + end + mobf.preserve_removed(entity,reason) + if entity.lifebar ~= nil then + mobf_lifebar.del(entity.lifebar) + end + entity.object:remove() + else + minetest.log(LOGLEVEL_ERROR,"Trying to delete an an non existant mob") + end + + dbg_mobf.spawning_lvl3("MOBF: <-- remove") +end + +------------------------------------------------------------------------------- +-- name: init_dynamic_data(entity) +-- @function [parent=#spawning] init_dynamic_data +-- +--! @brief initialize dynamic data required for spawning +--! @memberof spawning +-- +--! @param entity mob to initialize dynamic data +--! @param now current time +------------------------------------------------------------------------------- +function spawning.init_dynamic_data(entity,now) + + local player_spawned = false + + if entity.dynamic_data.spawning ~= nil and + entity.dynamic_data.spawning.player_spawned then + player_spawned = true + end + + local data = { + player_spawned = player_spawned, + ts_dense_check = now, + spawnpoint = entity.object:getpos(), + original_spawntime = now, + spawner = nil, + density = spawning.population_density_get_min(entity), + } + + entity.removed = false + entity.dynamic_data.spawning = data +end + +------------------------------------------------------------------------------- +-- name: population_density_check(mob) +-- @function [parent=#spawning] population_density_check +-- +--! @brief check and fix if there are too many mobs within a specific range +--! @memberof spawning +-- +--! @param entity mob to check +--! @param now current time +------------------------------------------------------------------------------- +function spawning.population_density_check(entity,now) + + if entity == nil or + entity.dynamic_data == nil or + entity.dynamic_data.spawning == nil then + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!! " .. entity.data.name .. + " pop dense check called for entity with missing spawn data entity=" .. + tostring(entity)) + return false + end + + + --only check every 5 seconds + if entity.dynamic_data.spawning.ts_dense_check + 5 > now then + return true + end + + -- don't check if mob is player spawned + if entity.dynamic_data.spawning.player_spawned == true then + dbg_mobf.spawning_lvl1("MOBF: mob is player spawned skipping pop dense check") + return true + end + + --don't do population check while fighting + if entity.dynamic_data.combat ~= nil and + entity.dynamic_data.combat.target ~= nil and + entity.dynamic_data.combat.target ~= "" then + dbg_mobf.spawning_lvl1( + "MOBF: fighting right now skipping pop dense check: " .. + dump(entity.dynamic_data.combat.target)) + return true + end + + entity.dynamic_data.spawning.ts_dense_check = now + + local entitypos = mobf_round_pos(entity.object:getpos()) + + --mob either not initialized completely or a bug + if mobf_pos_is_zero(entitypos) then + dbg_mobf.spawning_lvl1("MOBF: can't do a sane check") + return true + end + + local secondary_name = "" + if entity.data.harvest ~= nil then + secondary_name = entity.data.harvest.transform_to + end + + local check_density = entity.dynamic_data.spawning.density + + if entity.data.generic.population_density ~= nil then + check_density = entity.data.generic.population_density + end + + mobf_assert_backtrace(check_density ~= nil) + + local mob_count = mobf_mob_around(entity.data.modname..":"..entity.data.name, + secondary_name, + entitypos, + check_density, + true) + if mob_count > 5 then + dbg_mobf.spawning_lvl1("MOBF: " .. entity.data.name .. + mob_count .. " mobs of same type around") + entity.removed = true + minetest.log(LOGLEVEL_INFO,"MOBF: Too many ".. mob_count .. " ".. + entity.data.name.." at one place dying: " .. + tostring(entity.dynamic_data.spawning.player_spawned)) + spawning.remove(entity, "population density check") + return false + else + dbg_mobf.spawning_lvl2("MOBF: " .. entity.data.name .. + " density ok only "..mob_count.." mobs around") + return true + end +end + +------------------------------------------------------------------------------- +-- name: replace_entity(pos,name,spawnpos,health) +-- @function [parent=#spawning] replace_entity +-- +--! @brief replace mob at a specific position by a new one +--! @memberof spawning +-- +--! @param entity mob to replace +--! @param name of the mob to add +--! @param preserve preserve original spawntime +--! @return entity added or nil on error +------------------------------------------------------------------------------- +function spawning.replace_entity(entity,name,preserve) + dbg_mobf.spawning_lvl3("MOBF: --> replace_entity(" + .. entity.data.name .. "|" .. name .. ")") + + if minetest.registered_entities[name] == nil then + minetest.log(LOGLEVEL_ERROR,"MOBF: replace_entity: Bug no " + ..name.." is registred") + return nil + end + + -- avoid switching to same entity + if entity.name == name then + minetest.log(LOGLEVEL_INFO,"MOBF: not replacing " .. name .. + " by entity of same type!") + return nil + end + + + -- get data to be transfered to new entity + local pos = mobf.get_basepos(entity) + local health = entity.object:get_hp() + local temporary_dynamic_data = entity.dynamic_data + local entity_orientation = graphics.getyaw(entity) + + if preserve == nil or preserve == false then + temporary_dynamic_data.spawning.original_spawntime = mobf_get_current_time() + end + + --calculate new y pos + if minetest.registered_entities[name].collisionbox ~= nil then + pos.y = pos.y - minetest.registered_entities[name].collisionbox[2] + end + + + --delete current mob + dbg_mobf.spawning_lvl2("MOBF: replace_entity: removing " .. entity.data.name) + + --unlink dynamic data (this should work but doesn't due to other bugs) + entity.dynamic_data = nil + + --removing is done after exiting lua! + spawning.remove(entity,"replaced") + + --set marker to true to make sure activate handler knows it's replacing right now + spawning.replacing_NOW = true + local newobject = minetest.add_entity(pos,name) + spawning.replacing_NOW = false + local newentity = mobf_find_entity(newobject) + + if newentity ~= nil then + if newentity.dynamic_data ~= nil then + dbg_mobf.spawning_lvl2("MOBF: replace_entity: " .. name) + newentity.dynamic_data = temporary_dynamic_data + newentity.object:set_hp(health) + graphics.setyaw(newentity, entity_orientation) + else + minetest.log(LOGLEVEL_ERROR, + "MOBF: replace_entity: dynamic data not set for "..name.. + " maybe delayed activation?") + newentity.dyndata_delayed = { + data = temporary_dynamic_data, + health = health, + orientation = entity_orientation + } + end + else + minetest.log(LOGLEVEL_ERROR, + "MOBF: replace_entity 4 : Bug no "..name.." has been created") + end + dbg_mobf.spawning_lvl3("MOBF: <-- replace_entity") + return newentity +end + +------------------------------------------------------------------------------ +-- name: lifecycle_callback() +-- @function [parent=#spawning] lifecycle_callback +-- +--! @brief check mob lifecycle_callback +--! @memberof spawning +-- +--! @return true/false still alive dead +------------------------------------------------------------------------------- +function spawning.lifecycle_callback(entity,now) + + if entity.dynamic_data.spawning.original_spawntime ~= nil then + if entity.data.spawning ~= nil and + entity.data.spawning.lifetime ~= nil then + + local lifetime = entity.data.spawning.lifetime + + local current_age = now - entity.dynamic_data.spawning.original_spawntime + + if current_age > 0 and + current_age > lifetime then + dbg_mobf.spawning_lvl1("MOBF: removing animal due to limited lifetime") + spawning.remove(entity," limited mob lifetime") + return false + end + end + else + entity.dynamic_data.spawning.original_spawntime = now + end + + return true +end + +------------------------------------------------------------------------------ +-- name: spawn_and_check(name,pos,text) +-- @function [parent=#spawning] spawn_and_check +-- +--! @brief spawn an entity and check for presence +--! @memberof spawning +--! @param name name of entity +--! @param pos position to spawn mob at +--! @param text message used for log messages +-- +--! @return spawned mob entity +------------------------------------------------------------------------------- +function spawning.spawn_and_check(name,pos,text) + mobf_assert_validpos(pos) + mobf_assert_backtrace(name ~= nil) + + local newobject = minetest.add_entity(pos,name) + + if newobject then + local newentity = mobf_find_entity(newobject) + + if newentity == nil then + dbg_mobf.spawning_lvl3("MOBF BUG!!! no " .. name .. + " entity has been created by " .. text .. "!") + mobf_bug_warning(LOGLEVEL_ERROR,"BUG!!! no " .. name .. + " entity has been created by " .. text .. "!") + else + dbg_mobf.spawning_lvl2("MOBF: spawning "..name .. + " entity by " .. text .. " at position ".. printpos(pos)) + minetest.log(LOGLEVEL_INFO,"MOBF: spawning "..name .. + " entity by " .. text .. " at position ".. printpos(pos)) + return newentity + end + else + dbg_mobf.spawning_lvl3("MOBF BUG!!! no "..name.. + " object has been created by " .. text .. "!") + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!! no "..name.. + " object has been created by " .. text .. "!") + end + + return nil +end + +------------------------------------------------------------------------------ +-- name: population_density_get_min(entity) +-- @function [parent=#spawning] population_density_get_min +-- +--! @brief get minimum density for this mob +--! @memberof spawning +-- +--! @param entity the mob itself +-- +--! @return minimum density over all spawners defined for this mob +------------------------------------------------------------------------------- +function spawning.population_density_get_min(entity) + if entity.data.spawning == nil then + return entity.data.generic.population_density + end + -- legacy code + if type(entity.data.spawning.primary_algorithms) == "table" then + local density = nil + for i=1 , #entity.data.spawning.primary_algorithms , 1 do + if density == nil or + entity.data.spawning.primary_algorithms[i].density < density then + + density = entity.data.spawning.primary_algorithms[i].density + end + end + return density + else + return entity.data.spawning.density + end +end + +------------------------------------------------------------------------------- +-- name: check_activation_overlap(entity,pos,preserved_data) +-- +--! @brief check if a activating entity is spawned within some other entity +-- +--! @param entity entity to check +--! @param pos position spawned at +--! @param preserved_data data loaded for entity +--! @return true +------------------------------------------------------------------------------- +function spawning.check_activation_overlap(entity,pos,preserved_data) + + local cleaned_objectcount = mobf_objects_around(pos,0.25,{ "mobf:lifebar" }) + + --honor replaced marker + if (entity.replaced ~= true and cleaned_objectcount > 1) or + cleaned_objectcount > 2 then + + ------------------------------ + -- debug output only + -- --------------------------- + local spawner = "unknown" + if preserved_data ~= nil and + preserved_data.spawner ~= nil then + spawner = preserved_data.spawner + + mobf_bug_warning(LOGLEVEL_WARNING, + "MOBF: trying to activate mob \"" ..entity.data.name .. + " at " .. printpos(pos) .. " (" .. tostring(entity) + .. ")".. + "\" within something else!" .. + " originaly spawned by: " .. spawner .. + " --> removing") + + objectlist = minetest.get_objects_inside_radius(pos,0.25) + + for i=1,#objectlist,1 do + local luaentity = objectlist[i]:get_luaentity() + if luaentity ~= nil then + if luaentity.data ~= nil and + luaentity.data.name ~= nil then + dbg_mobf.mobf_core_helper_lvl3( + i .. " LE: " .. luaentity.name .. " (" .. tostring(luaentity) .. ") " .. + luaentity.data.name .. " " .. + printpos(objectlist[i]:getpos())) + else + dbg_mobf.mobf_core_helper_lvl3( + i .. " LE: " .. luaentity.name .. " (" .. tostring(luaentity) .. ") " .. + dump(luaentity)) + end + else + dbg_mobf.mobf_core_helper_lvl3( + i .. " " .. tostring(objectlist[i]) .. + printpos(objectlist[i]:getpos())) + end + end + ------------------------------ + -- end debug output + -- --------------------------- + return false + end + end + return true +end diff --git a/mods/mob_engines/mobf/step_quota.lua b/mods/mob_engines/mobf/step_quota.lua new file mode 100644 index 00000000..e043a0be --- /dev/null +++ b/mods/mob_engines/mobf/step_quota.lua @@ -0,0 +1,98 @@ +------------------------------------------------------------------------------- +-- 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 step_quota.lua +--! @brief class containing mobf step quota handling +--! @copyright Sapier +--! @author Sapier +--! @date 2013-11-30 +-- +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +mobf_assert_backtrace(not core.global_exists("mobf_step_quota")) + +--! @class mobf_step_quota +--! @brief step_quota handling +mobf_step_quota = {} + + +------------------------------------------------------------------------------ +-- @function [parent=#mobf_step_quota] is_exceeded +-- +--! @brief check if quota is exceeded +--! @memberof mobf_step_quota +--! @public +-- +--! @return true == exceeded false == not exceeded +------------------------------------------------------------------------------- +function mobf_step_quota.is_exceeded() + return mobf_step_quota.remaining_quota <= 0 +end + +------------------------------------------------------------------------------ +-- @function [parent=#mobf_step_quota] remaining() +-- +--! @brief get remaining time this quota +--! @memberof mobf_step_quota +--! @public +-- +--! @return time left +------------------------------------------------------------------------------- +function mobf_step_quota.remaining() + if mobf_step_quota.remaining_quota >= 0 then + return mobf_step_quota.remaining_quota + else + return 0 + end +end + +------------------------------------------------------------------------------ +-- @function [parent=#mobf_step_quota] initialize() +-- +--! @brief initialize quota handling +--! @memberof mobf_step_quota +--! @public +-- +------------------------------------------------------------------------------- +function mobf_step_quota.initialize() + --todo add setting + mobf_step_quota.reload_value = 50 + minetest.register_globalstep(mobf_step_quota.reload) +end + +------------------------------------------------------------------------------ +-- @function [parent=#mobf_step_quota] reload() +-- +--! @brief reload current quota +--! @memberof mobf_step_quota +--! @public +-- +------------------------------------------------------------------------------- +function mobf_step_quota.reload() + mobf_step_quota.remaining_quota = mobf_step_quota.reload_value +end + +------------------------------------------------------------------------------ +-- @function [parent=#mobf_step_quota] cosume(starttime) +-- +--! @brief reduce remaining quota by time passed +--! @memberof mobf_step_quota +--! @public +-- +--! @param starttime time this operation started +------------------------------------------------------------------------------- +function mobf_step_quota.consume(starttime) + local now = mobf_get_time_ms() + local passed = now - starttime + + if passed >= 0 then + mobf_step_quota.remaining_quota = mobf_step_quota.remaining_quota - passed + else + --mobf_print("MOBF: error calculation consumed time: " .. starttime .. " --> " .. now) + end +end \ No newline at end of file diff --git a/mods/mob_engines/mobf/textures/animals_spawn_marker.png b/mods/mob_engines/mobf/textures/animals_spawn_marker.png new file mode 100644 index 00000000..d1614b6b Binary files /dev/null and b/mods/mob_engines/mobf/textures/animals_spawn_marker.png differ diff --git a/mods/mob_engines/mobf/textures/invisible.png b/mods/mob_engines/mobf/textures/invisible.png new file mode 100644 index 00000000..8cb82be9 Binary files /dev/null and b/mods/mob_engines/mobf/textures/invisible.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_alignment_grid.png b/mods/mob_engines/mobf/textures/mobf_alignment_grid.png new file mode 100644 index 00000000..7f1e209a Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_alignment_grid.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_00.png b/mods/mob_engines/mobf/textures/mobf_lb_00.png new file mode 100644 index 00000000..ac10a068 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_00.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_02.png b/mods/mob_engines/mobf/textures/mobf_lb_02.png new file mode 100644 index 00000000..08293f26 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_02.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_04.png b/mods/mob_engines/mobf/textures/mobf_lb_04.png new file mode 100644 index 00000000..92e8078f Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_04.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_06.png b/mods/mob_engines/mobf/textures/mobf_lb_06.png new file mode 100644 index 00000000..abad192f Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_06.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_08.png b/mods/mob_engines/mobf/textures/mobf_lb_08.png new file mode 100644 index 00000000..63ba5f8d Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_08.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_10.png b/mods/mob_engines/mobf/textures/mobf_lb_10.png new file mode 100644 index 00000000..f59645ac Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_10.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_12.png b/mods/mob_engines/mobf/textures/mobf_lb_12.png new file mode 100644 index 00000000..eedde32d Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_12.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_14.png b/mods/mob_engines/mobf/textures/mobf_lb_14.png new file mode 100644 index 00000000..93b207dd Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_14.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_16.png b/mods/mob_engines/mobf/textures/mobf_lb_16.png new file mode 100644 index 00000000..765a1df6 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_16.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_18.png b/mods/mob_engines/mobf/textures/mobf_lb_18.png new file mode 100644 index 00000000..b515b235 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_18.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_20.png b/mods/mob_engines/mobf/textures/mobf_lb_20.png new file mode 100644 index 00000000..142222dd Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_20.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_22.png b/mods/mob_engines/mobf/textures/mobf_lb_22.png new file mode 100644 index 00000000..869f93f8 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_22.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_24.png b/mods/mob_engines/mobf/textures/mobf_lb_24.png new file mode 100644 index 00000000..1ee8ae4e Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_24.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_26.png b/mods/mob_engines/mobf/textures/mobf_lb_26.png new file mode 100644 index 00000000..9ebb4389 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_26.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_28.png b/mods/mob_engines/mobf/textures/mobf_lb_28.png new file mode 100644 index 00000000..7e4a0a77 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_28.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_30.png b/mods/mob_engines/mobf/textures/mobf_lb_30.png new file mode 100644 index 00000000..781f2ca7 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_30.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_32.png b/mods/mob_engines/mobf/textures/mobf_lb_32.png new file mode 100644 index 00000000..70b6ff7a Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_32.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_34.png b/mods/mob_engines/mobf/textures/mobf_lb_34.png new file mode 100644 index 00000000..5d14c9a2 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_34.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_36.png b/mods/mob_engines/mobf/textures/mobf_lb_36.png new file mode 100644 index 00000000..d8741310 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_36.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_38.png b/mods/mob_engines/mobf/textures/mobf_lb_38.png new file mode 100644 index 00000000..1dda97d6 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_38.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_40.png b/mods/mob_engines/mobf/textures/mobf_lb_40.png new file mode 100644 index 00000000..45cf9aa9 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_40.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_42.png b/mods/mob_engines/mobf/textures/mobf_lb_42.png new file mode 100644 index 00000000..90739ec8 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_42.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_44.png b/mods/mob_engines/mobf/textures/mobf_lb_44.png new file mode 100644 index 00000000..2d263276 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_44.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_46.png b/mods/mob_engines/mobf/textures/mobf_lb_46.png new file mode 100644 index 00000000..2505ff4b Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_46.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_48.png b/mods/mob_engines/mobf/textures/mobf_lb_48.png new file mode 100644 index 00000000..81ec7b97 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_48.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_50.png b/mods/mob_engines/mobf/textures/mobf_lb_50.png new file mode 100644 index 00000000..fee3f458 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_50.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_52.png b/mods/mob_engines/mobf/textures/mobf_lb_52.png new file mode 100644 index 00000000..f974f5a1 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_52.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_54.png b/mods/mob_engines/mobf/textures/mobf_lb_54.png new file mode 100644 index 00000000..855b7958 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_54.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_56.png b/mods/mob_engines/mobf/textures/mobf_lb_56.png new file mode 100644 index 00000000..0f7e251d Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_56.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_58.png b/mods/mob_engines/mobf/textures/mobf_lb_58.png new file mode 100644 index 00000000..e07e99d5 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_58.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_60.png b/mods/mob_engines/mobf/textures/mobf_lb_60.png new file mode 100644 index 00000000..bb1f6751 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_60.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_62.png b/mods/mob_engines/mobf/textures/mobf_lb_62.png new file mode 100644 index 00000000..0e349a11 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_62.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_lb_64.png b/mods/mob_engines/mobf/textures/mobf_lb_64.png new file mode 100644 index 00000000..5eb25047 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_lb_64.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_path_marker.png b/mods/mob_engines/mobf/textures/mobf_path_marker.png new file mode 100644 index 00000000..e96ed039 Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_path_marker.png differ diff --git a/mods/mob_engines/mobf/textures/mobf_path_marker_item.png b/mods/mob_engines/mobf/textures/mobf_path_marker_item.png new file mode 100644 index 00000000..7621c1bf Binary files /dev/null and b/mods/mob_engines/mobf/textures/mobf_path_marker_item.png differ diff --git a/mods/mob_engines/mobf/textures/old/templates/animals_template_128_4.png b/mods/mob_engines/mobf/textures/old/templates/animals_template_128_4.png new file mode 100644 index 00000000..8b399c3f Binary files /dev/null and b/mods/mob_engines/mobf/textures/old/templates/animals_template_128_4.png differ diff --git a/mods/mob_engines/mobf/textures/old/templates/animals_template_128_4_2levels.png b/mods/mob_engines/mobf/textures/old/templates/animals_template_128_4_2levels.png new file mode 100644 index 00000000..48b00c4a Binary files /dev/null and b/mods/mob_engines/mobf/textures/old/templates/animals_template_128_4_2levels.png differ diff --git a/mods/mob_engines/mobf/textures/old/templates/animals_template_128_6.png b/mods/mob_engines/mobf/textures/old/templates/animals_template_128_6.png new file mode 100644 index 00000000..ac7e420d Binary files /dev/null and b/mods/mob_engines/mobf/textures/old/templates/animals_template_128_6.png differ diff --git a/mods/mob_engines/mobf/textures/old/templates/animals_template_32.png b/mods/mob_engines/mobf/textures/old/templates/animals_template_32.png new file mode 100644 index 00000000..05ec25a5 Binary files /dev/null and b/mods/mob_engines/mobf/textures/old/templates/animals_template_32.png differ diff --git a/mods/mob_engines/mobf/utils/data_storage.lua b/mods/mob_engines/mobf/utils/data_storage.lua new file mode 100644 index 00000000..49c66b94 --- /dev/null +++ b/mods/mob_engines/mobf/utils/data_storage.lua @@ -0,0 +1,103 @@ +------------------------------------------------------------------------------- +-- 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 data_storage.lua +--! @brief generic functions used in many different places +--! @copyright Sapier +--! @author Sapier +--! @date 2013-02-04 +--! +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @defgroup gen_func Generic functions +--! @brief functions for various tasks +--! @ingroup framework_int +--! @{ +------------------------------------------------------------------------------- +-- name: mobf_global_data_store(value) +-- +--! @brief save data and return unique identifier +-- +--! @param value to save +-- +--! @return unique identifier +------------------------------------------------------------------------------- +local mobf_global_data_identifier = 0 +local mobf_global_data = {} +mobf_global_data.cleanup_index = 0 +mobf_global_data.last_cleanup = mobf_get_current_time() +function mobf_global_data_store(value) + + local current_id = mobf_global_data_identifier + + mobf_global_data_identifier = mobf_global_data_identifier + 1 + + mobf_global_data[current_id] = { + value = value, + added = mobf_get_current_time(), + } + return current_id +end + + +------------------------------------------------------------------------------- +-- name: mobf_global_data_store(value) +-- +--! @brief pop data from global store +-- +--! @param id to pop +-- +--! @return stored value +------------------------------------------------------------------------------- +function mobf_global_data_get(id) + + local dataid = tonumber(id) + + if dataid == nil or + mobf_global_data[dataid] == nil then + dbg_mobf.generic_lvl1("MOBF: data not found, id: " .. dump(dataid)) + return nil + end + + local retval = mobf_global_data[dataid].value + mobf_global_data[dataid] = nil + return retval +end + +------------------------------------------------------------------------------- +-- name: mobf_global_data_cleanup() +-- +--! @brief periodic cleanup handler +-- +------------------------------------------------------------------------------- +function mobf_global_data_cleanup() + + if mobf_global_data.last_cleanup + 500 < + mobf_get_current_time() then + + for i=1,50,1 do + if mobf_global_data[mobf_global_data.cleanup_index] ~= nil then + if mobf_global_data[mobf_global_data.cleanup_index].added < + mobf_get_current_time() - 300 then + + mobf_global_data[mobf_global_data.cleanup_index] = nil + end + mobf_global_data.cleanup_index = mobf_global_data.cleanup_index +1 + + if mobf_global_data.cleanup_index > #mobf_global_data then + mobf_global_data.cleanup_index = 0 + break + end + end + end + + mobf_global_data.last_cleanup = mobf_get_current_time() + end +end + +--!@} \ No newline at end of file diff --git a/mods/mob_engines/mobf/utils/error_handling.lua b/mods/mob_engines/mobf/utils/error_handling.lua new file mode 100644 index 00000000..b4e15c66 --- /dev/null +++ b/mods/mob_engines/mobf/utils/error_handling.lua @@ -0,0 +1,47 @@ +------------------------------------------------------------------------------- +-- 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 allowed to pretend you have written it. +-- +--! @file error_handling.lua +--! @brief code required to do error handling +--! @copyright Sapier +--! @author Sapier +--! @date 2013-05-010 +--! +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- +-- +------------------------------------------------------------------------------- +-- name: mobf_assert_backtrace(value) +-- +--! @brief assert in case value is false +-- +--! @param value to evaluate +------------------------------------------------------------------------------- +function mobf_assert_backtrace(value) + if value == false then + print(debug.traceback("Current Callstack:\n")) + assert(value) + end +end + +------------------------------------------------------------------------------- +-- name: mobf_assert_validpos(pos) +-- +--! @brief check if a pos is valid +-- +--! @param pos to evaluate +------------------------------------------------------------------------------- +function mobf_assert_validpos(pos) + mobf_assert_backtrace(pos ~= nil) + mobf_assert_backtrace(type(pos) == "table") + mobf_assert_backtrace(pos.x ~= nil) + mobf_assert_backtrace(pos.y ~= nil) + mobf_assert_backtrace(pos.z ~= nil) + mobf_assert_backtrace(type(pos.x) == "number") + mobf_assert_backtrace(type(pos.y) == "number") + mobf_assert_backtrace(type(pos.z) == "number") +end \ No newline at end of file diff --git a/mods/mob_engines/mobf/utils/generic_functions.lua b/mods/mob_engines/mobf/utils/generic_functions.lua new file mode 100644 index 00000000..f1768eed --- /dev/null +++ b/mods/mob_engines/mobf/utils/generic_functions.lua @@ -0,0 +1,800 @@ +------------------------------------------------------------------------------- +-- 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 allowed to pretend you have written it. +-- +--! @file generic_functions.lua +--! @brief generic functions used in many different places +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +--! +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @defgroup gen_func Generic functions +--! @brief functions for various tasks +--! @ingroup framework_int +--! @{ + +------------------------------------------------------------------------------- +-- name: mobf_get_time_ms() +-- +--! @brief get current time in ms +-- +--! @return current time in ms +------------------------------------------------------------------------------- +function mobf_get_time_ms() + --this fct is overwritten on init with best timesource available atm + return os.clock() * 1000 +end + +------------------------------------------------------------------------------- +-- name: mobf_contains(cur_table,element) +-- +--! @brief check if element is in table +-- +--! @param cur_table table to look in +--! @param element element to look for +--! @return true/false +------------------------------------------------------------------------------- +function mobf_contains(cur_table,element) + + if cur_table == nil then + return false + end + + for i,v in ipairs(cur_table) do + if v == element then + return true + end + end + + return false +end + +------------------------------------------------------------------------------- +-- name: MIN(a,b) +-- +--! @brief minimum of two numbers +-- +--! @param a number 1 +--! @param b number 2 +--! @return minimum +------------------------------------------------------------------------------- +function MIN(a,b) + mobf_assert_backtrace(type(a) == "number") + mobf_assert_backtrace(type(b) == "number") + if a > b then + return b + else + return a + end +end + +------------------------------------------------------------------------------- +-- name: MAX(a,b) +-- +--! @brief maximum of two numbers +-- +--! @param a number 1 +--! @param b number 2 +--! @return maximum +------------------------------------------------------------------------------- +function MAX(a,b) + mobf_assert_backtrace(type(a) == "number") + mobf_assert_backtrace(type(b) == "number") + if a > b then + return a + else + return b + end +end + +------------------------------------------------------------------------------- +-- name: DELTA(a,b) +-- +--! @brief delta of two numbers +-- +--! @param a number 1 +--! @param b number 2 +--! @return delta +------------------------------------------------------------------------------- +function DELTA(a,b) + return math.abs(a-b) +end +------------------------------------------------------------------------------- +-- name: mobf_is_walkable(node) +-- +--! @brief check if walkable flag is set for a node +-- +--! @param node to check +--! @return true/false +------------------------------------------------------------------------------- +function mobf_is_walkable(node) + return (node and node.name and minetest.registered_nodes[node.name] and + minetest.registered_nodes[node.name].walkable == false) +end + +------------------------------------------------------------------------------- +-- name: mobf_get_current_time() +-- +--! @brief alias to get current time +-- +--! @return current time in seconds +------------------------------------------------------------------------------- +function mobf_get_current_time() + if type(minetest.get_time) == "function" then + return minetest.get_time() + else + return os.time(os.date('*t')) + end +end + +------------------------------------------------------------------------------- +-- name: mobf_round_pos(pos) +-- +--! @brief calculate integer position +-- +--! @param pos position to be rounded +--! @return rounded position +------------------------------------------------------------------------------- +function mobf_round_pos(pos) + if pos == nil then + return pos + end + + return { x=math.floor(pos.x + 0.5), + y=math.floor(pos.y + 0.5), + z=math.floor(pos.z + 0.5) + } +end + +------------------------------------------------------------------------------- +-- name: mobf_find_entity(newobject) DEPRECATED +-- +--! @brief find entity by object reference +-- +--! @param newobject r object reference +--! @return entity object reference points at or nil on error +------------------------------------------------------------------------------- +function mobf_find_entity(newobject) + return newobject:get_luaentity() +end + +------------------------------------------------------------------------------- +-- name: mobf_max_light_around(pos,range,daytime) +-- +--! @brief get maximum light level around specified position +-- +--! @param pos center of area to search +--! @param distance radius of area +--! @param daytime time of day to check +--! @return highest detected light level +------------------------------------------------------------------------------- +function mobf_max_light_around(pos,distance,daytime) + mobf_assert_validpos(pos) + local max_light = 0 + + for y_run=pos.y-distance,pos.y+distance,1 do + for z_run=pos.z-distance,pos.z+distance,1 do + for x_run=pos.x-distance,pos.x+distance,1 do + local current_pos = {x=x_run,y=y_run,z=z_run } + local node = minetest.get_node(current_pos) + + if node.name == "air" then + local current_light = minetest.get_node_light(current_pos,daytime) + + if current_light > max_light then + max_light = current_light + end + end + end + end + end + + return max_light +end + +------------------------------------------------------------------------------- +-- name: mobf_min_light_around(pos,range,daytime) +-- +--! @brief get minimum light level around specified position +-- +--! @param pos center of area to search +--! @param distance radius of area +--! @param daytime time of day to check +--! @return highest detected light level +------------------------------------------------------------------------------- +function mobf_min_light_around(pos,distance,daytime) + mobf_assert_validpos(pos) + local min_light = LIGHT_MAX+1 + + for y_run=pos.y-distance,pos.y+distance,1 do + for z_run=pos.z-distance,pos.z+distance,1 do + for x_run=pos.x-distance,pos.x+distance,1 do + local current_pos = {x=x_run,y=y_run,z=z_run } + local node = minetest.get_node(current_pos) + + if node.name == "air" then + local current_light = minetest.get_node_light(current_pos,daytime) + + if current_light < min_light then + min_light = current_light + end + end + end + end + end + + return min_light +end + +------------------------------------------------------------------------------- +-- name: mobf_objects_around(pos,range,ignorelist) +-- +--! @brief get number of objects within a certain range +-- +--! @param pos position to look around +--! @param range range to check +--! @param ignorelist list of entitynames to ignore +--! @return count of objects +------------------------------------------------------------------------------- +function mobf_objects_around(pos,range,ignorelist) + + local objectlist = minetest.get_objects_inside_radius(pos,range) + + local cleaned_objectcount = 0 + + for i=1,#objectlist,1 do + local luaentity = objectlist[i]:get_luaentity() + if luaentity ~= nil then + if not luaentity.mobf_spawner and + not mobf_contains(ignorelist,luaentity.name) then + cleaned_objectcount = cleaned_objectcount + 1 + end + else + cleaned_objectcount = cleaned_objectcount + 1 + end + end + + return cleaned_objectcount +end + +------------------------------------------------------------------------------- +-- name: mobf_mob_around(mob_name,mob_transform_name,pos,range,) +-- +--! @brief get number of mobs of specified type within range of pos +-- +--! @param mob_name basic name of mob +--! @param mob_transform secondary name of mob +--! @param pos position to check +--! @param range range to check +--! @param ignore_playerspawned ignore mob spawned by players for check +--! @return number of mob found +------------------------------------------------------------------------------- +function mobf_mob_around(mob_name,mob_transform,pos,range,ignore_playerspawned) + local count = 0 + local objectcount = 0 + + mobf_assert_backtrace(range ~= nil) + mobf_assert_backtrace(pos ~= nil) + + local objectlist = minetest.get_objects_inside_radius(pos,range) + + if mob_transform == nil then + mob_transform = "" + end + + local objcount = 0 + if objectlist ~= nil then + objcount = #objectlist + end + + dbg_mobf.generic_lvl1("MOBF: entity at "..printpos(pos).. + " looking for: "..mob_name .. + " or " .. mob_transform .. + " within " .. objcount .. " objects" ) + + for index,value in pairs(objectlist) do + + local entity = mobf_find_entity(value) + + --any mob is required to have a name so we may use this to decide + --if an entity is an mob or not + if entity ~= nil and + entity.data ~= nil and + entity.dynamic_data ~= nil then + + if entity.removed == false then + + if entity.data.modname..":"..entity.data.name == mob_name or + entity.data.modname..":"..entity.data.name == mob_transform then + + -- oops we don't yet know if this is playerspawned, + -- for performance reasons assume it isn't + if entity.dynamic_data.spawning == nil then + count = count + 1 + else + if (ignore_playerspawned and entity.dynamic_data.spawning.player_spawned) or + ignore_playerspawned ~= false then + dbg_mobf.generic_lvl1("MOBF: Found "..mob_name.. " or " + ..mob_transform .. " within specified range of "..range) + count = count + 1 + end + end + end + end + end + objectcount = objectcount +1 + end + + dbg_mobf.generic_lvl2("MOBF: found " .. objectcount .. " within range " + .. count .. " of them are relevant mobs ") + + return count +end + +------------------------------------------------------------------------------- +-- name: mobf_spawner_around(mob_name,pos,range) +-- +--! @brief get number of mobs of specified type within range of pos +-- +--! @param mob_name basic name of mob +--! @param pos position to check +--! @param range range to check +--! @return number of mob found +------------------------------------------------------------------------------- +function mobf_spawner_around(mob_name,pos,range) + mobf_assert_validpos(pos) + dbg_mobf.generic_lvl2("MOBF: mobf_spawner_around param: ".. dump(mob_name) + .. " "..dump(pos).. " " .. dump(range)) + + local count = 0 + local objectcount = 0 + + local objectlist = minetest.get_objects_inside_radius(pos,range) + + for index,value in pairs(objectlist) do + + local entity = value:get_luaentity() + + dbg_mobf.generic_lvl3("MOBF: entity at: "..dump(value:getpos()).. + " looking for: "..mob_name .. " " .. + dump(value) .. " " .. + dump(entity)) + + --any mob is required to have a name so we may use this to decide + --if an entity is an mob or not + if entity ~= nil and + entity.spawner_mob_name ~= nil then + + if entity.spawner_mob_name == mob_name then + dbg_mobf.generic_lvl2("MOBF: Found "..mob_name + .. " within specified range of "..range) + count = count + 1 + end + end + + objectcount = objectcount +1 + end + + dbg_mobf.generic_lvl2("MOBF: found " .. objectcount .. " within range " + .. count .. " of them are relevant spawners ") + + return count +end + +------------------------------------------------------------------------------- +-- name: mobf_line_of_sight(pos1,pos2) +-- +--! @brief is there a line of sight between two specified positions +-- +--! @param pos1 start position of los check +--! @param pos2 end position of los check +--! @return: true/false +------------------------------------------------------------------------------- +function mobf_line_of_sight(pos1,pos2) + + --print("Checking line of sight between "..printpos(pos1).." and "..printpos(pos2)) + local distance = mobf_calc_distance(pos1,pos2) + + local normalized_vector = { x=(pos2.x-pos1.x)/distance, + y=(pos2.y-pos1.y)/distance, + z=(pos2.z-pos1.z)/distance} + + + local line_of_sight = true + + for i=1,distance, 1 do + local tocheck = { x=pos1.x + (normalized_vector.x * i), + y=pos1.y + (normalized_vector.y *i), + z=pos1.z + (normalized_vector.z *i)} + + local node = minetest.env:get_node(tocheck) + + + if minetest.registered_nodes[node.name] == nil or + minetest.registered_nodes[node.name].sunlight_propagates ~= true then + line_of_sight = false + break + end + end + + return line_of_sight +end + +function mobf_line_of_sightX(pos1,pos2) + return minetest.line_of_sight(pos1,pos2) +end + +------------------------------------------------------------------------------- +-- name: mobf_pos_is_zero(pos) +-- +--! @brief check if position is (0,0,0) +-- +--! @param pos position to check +--! @return true/false +------------------------------------------------------------------------------- + +function mobf_pos_is_zero(pos) + + if pos.x ~= 0 then return false end + if pos.y ~= 0 then return false end + if pos.z ~= 0 then return false end + + return true +end + +------------------------------------------------------------------------------- +-- name: mobf_air_above(pos,height) +-- +--! @brief check if theres at least height air abov pos +-- +--! @param pos position to check +--! @param height min number of air to check +--! @return true/false +------------------------------------------------------------------------------- +function mobf_air_above(pos,height) + mobf_assert_validpos(pos) + for i=0, height, 1 do + local pos_above = { + x = pos.x, + y = pos.y + 1, + z = pos.z + } + local node_above = minetest.get_node(pos_above) + + if node_above.name ~= "air" then + return false + end + end + + return true +end + + +------------------------------------------------------------------------------- +-- name: mobf_ground_distance(pos,media) +-- +--! @brief get number of blocks above solid ground +-- +--! @param pos_raw position to check +--! @param media table of blocks not considered to be ground +--! @param max_check_height abort looking for ground after this number of nodes +-- +--! @return number of blocks to ground +------------------------------------------------------------------------------- +function mobf_ground_distance(pos_raw,media,max_check_height) + + local pos = { + x=pos_raw.x, + y=math.floor(pos_raw.y + 0.5), + z=pos_raw.z + } + + local node_to_check = minetest.get_node(pos) + + local count = 0 + + if max_check_height == nil then + max_check_height = 32 + end + + while node_to_check ~= nil and mobf_contains(media,node_to_check.name) and + count < max_check_height do + count = count +1 + pos = {x=pos.x,y=pos.y-1,z=pos.z}; + node_to_check = minetest.get_node(pos) + end + + return count +end + +------------------------------------------------------------------------------- +-- name: mobf_surface_distance(pos) +-- +--! @brief get number of blocks above surface (solid or fluid!) +-- +--! @param pos position to check +--! @return number of blocks to ground +------------------------------------------------------------------------------- +function mobf_surface_distance(pos) + + local node_to_check = minetest.get_node(pos) + + local count = 0 + + while node_to_check ~= nil and + node_to_check.name == "air" and + count < 32 do + + count = count +1 + + pos = {x=pos.x,y=pos.y-1,z=pos.z}; + node_to_check = minetest.get_node(pos) + end + + return count +end + +------------------------------------------------------------------------------- +-- name: mobf_air_distance(pos) +-- +--! @brief get number of blocks below waterline +-- +--! @param pos position to check +--! @return number of blocks to air +------------------------------------------------------------------------------- +function mobf_air_distance(pos) + mobf_assert_validpos(pos) + + local node_to_check = minetest.get_node(pos) + + local count = 0 + + while node_to_check ~= nil and ( + node_to_check.name == "default:water_source" or + node_to_check.name == "default:water_flowing") do + + count = count +1 + pos = {x=pos.x,y=pos.y+1,z=pos.z}; + node_to_check = minetest.get_node(pos) + end + + if node_to_check.name == "air" then + return count + else + return -1 + end +end + +------------------------------------------------------------------------------- +-- name: mobf_above_water(pos) +-- +--! @brief check if next non-air block below mob is a water block +-- +--! @param pos position to check +--! @return true/false +------------------------------------------------------------------------------- +function mobf_above_water(pos) + + local node_to_check = minetest.get_node(pos) + + while node_to_check ~= nil and + node_to_check.name == "air" do + + pos = {x=pos.x,y=pos.y-1,z=pos.z}; + node_to_check = minetest.get_node(pos) + end + + if node_to_check.name == "default:water_source" or + node_to_check.name == "default:water_flowing" then + return true + end + + return false +end + +------------------------------------------------------------------------------- +-- name: get_sunlight_surface(x,z, min_y, max_y) +-- +--! @brief get surface for x/z coordinates +-- +--! @param x x-coordinate +--! @param z z-coordinate +--! @param min_y minimum y-coordinate to consider +--! @param max_y maximum y-coordinate to consider +--! @return y value of surface or nil +------------------------------------------------------------------------------- +function mobf_get_sunlight_surface(x,z, min_y, max_y) + for runy = min_y, max_y,1 do + local pos = { x=x,y=runy, z=z } + local node_to_check = minetest.get_node(pos) + + if node_to_check.name == "default:dirt_with_grass" then + return pos.y + end + end + + return nil +end + +------------------------------------------------------------------------------- +-- name: get_surface(x,z, min_y, max_y) +-- +--! @brief get surface for x/z coordinates +-- +--! @param x x-coordinate +--! @param z z-coordinate +--! @param min_y minimum y-coordinate to consider +--! @param max_y maximum y-coordinate to consider +--! @return y value of surface (first air node) or nil +------------------------------------------------------------------------------- +function mobf_get_surface(x,z, min_y, max_y) + mobf_assert_backtrace(min_y ~= nil) + mobf_assert_backtrace(max_y ~= nil) + mobf_assert_backtrace(x ~= nil) + mobf_assert_backtrace(z ~= nil) + + if type(minetest.get_surface) == "function" then + local basepos = {x=x,y=min_y,z=z} + local offset = max_y-min_y + local retval = minetest.get_surface(basepos,offset) + return retval + end + + local last_node = minetest.get_node({ x=x,y=min_y, z=z }) + for runy = min_y+1, max_y,1 do + local pos = { x=x,y=runy, z=z } + local node_to_check = minetest.get_node(pos) + if node_to_check.name == "air" and + last_node.name ~= "air" and + last_node.mame ~= "ignore" then + return pos.y + end + last_node = node_to_check + end + return nil +end + +------------------------------------------------------------------------------- +-- name: entity_at_loaded_pos(entity.mobname) +-- +--! @brief check if entity is activated at already loaded pos +-- +--! @param pos to check +--! @param mobname name of mob +--! @return true/false +------------------------------------------------------------------------------- +function entity_at_loaded_pos(pos,mobname) + + local current_node = minetest.get_node(pos) + if mobname == nil then + mobname = "" + end + + if current_node ~= nil then + if current_node.name == "ignore" then + minetest.log(LOGLEVEL_WARNING,"MOBF: " ..mobname .. " spawned at unloaded pos! : " + .. dump(pos) .. " node: " .. dump(current_node)) + return false + else + return true + end + end + minetest.log(LOGLEVEL_WARNING,"MOBF: spawned at invalid pos!") + return false +end + +------------------------------------------------------------------------------- +-- name: mobf_random_direction() +-- +--! @brief get a random (blocked) 3d direction +-- +--! @return 3d dir value +------------------------------------------------------------------------------- +function mobf_random_direction() + + local retval = {} + + retval.x=math.random(-1,1) + retval.y=math.random(-1,1) + retval.z=math.random(-1,1) + + return retval +end + +------------------------------------------------------------------------------- +-- name: mobf_pos_is_same(pos1,pos2) +-- +--! @brief check if two positions are equal +-- +--! @param pos1 +--! @param pos2 +-- +--! @return true/false +------------------------------------------------------------------------------- +function mobf_pos_is_same(pos1,pos2) + if pos1 == nil or + pos2 == nil then + return false + end + + if pos1.x ~= pos2.x or + pos1.y ~= pos2.y or + pos1.z ~= pos2.z or + pos1.x == nil or + pos1.y == nil or + pos1.z == nil or + pos2.x == nil or + pos2.y == nil or + pos2.z == nil then + return false + end + + return true +end + +------------------------------------------------------------------------------- +-- name: mobf_is_pos(value) +-- +--! @brief check if a given value is a position +-- +--! @param value to check +-- +--! @return true/false +------------------------------------------------------------------------------- +function mobf_is_pos(value) + + if value == nil or + type(value) ~= "table" then + return false + end + + if value.x == nil or + tonumber(value.x) == nil then + return false + end + + if value.y == nil or + tonumber(value.y) == nil then + return false + end + + if value.z == nil or + tonumber(value.z) == nil then + return false + end + + + return true +end + +------------------------------------------------------------------------------- +-- name: mobf_hash_to_pos(hash) +-- +--! @brief restore a position from a pos hash value +-- +--! @param hash to restore pos from +-- +--! @return posistion reconstructed from hash +------------------------------------------------------------------------------- +function mobf_hash_to_pos(hash) + local retval = {} + + local raw_x = (hash % 65536) + local raw_y = ((hash - raw_x) % (65536*65536)) / 65536 + local raw_z = ((hash - raw_x - raw_y) / 65536) / 65536 + + local mobpos = {} + retval.x = raw_x - 32768 + retval.y = raw_y - 32768 + retval.z = math.floor(raw_z - 32768) + + return retval +end + +--!@} diff --git a/mods/mob_engines/mobf/utils/geometry.lua b/mods/mob_engines/mobf/utils/geometry.lua new file mode 100644 index 00000000..f7b6fad0 --- /dev/null +++ b/mods/mob_engines/mobf/utils/geometry.lua @@ -0,0 +1,358 @@ +------------------------------------------------------------------------------- +-- 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 geometry.lua +--! @brief generic functions used in many different places +--! @copyright Sapier +--! @author Sapier +--! @date 2013-02-04 +--! +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @defgroup gen_func Generic functions +--! @brief functions for various tasks +--! @ingroup framework_int +--! @{ + +------------------------------------------------------------------------------- +-- name: mobf_calc_distance(pos1,pos2) +-- +--! @brief calculate 3d distance between to points +-- +--! @param pos1 first position +--! @param pos2 second position +--! @retval scalar value, distance +------------------------------------------------------------------------------- +function mobf_calc_distance(pos1,pos2) + mobf_assert_backtrace(pos1 ~= nil) + mobf_assert_backtrace(pos2 ~= nil) + + return math.sqrt( math.pow(pos1.x-pos2.x,2) + + math.pow(pos1.y-pos2.y,2) + + math.pow(pos1.z-pos2.z,2)) +end + +------------------------------------------------------------------------------- +-- name: mobf_calc_distance_2d(pos1,pos2) +-- +--! @brief calculate 2d distance between to points +-- +--! @param pos1 target position +--! @param pos2 start position +--! @return scalar value, distance +------------------------------------------------------------------------------- +function mobf_calc_distance_2d(pos1,pos2) + return math.sqrt( math.pow(pos1.x-pos2.x,2) + + math.pow(pos1.z-pos2.z,2)) +end + +------------------------------------------------------------------------------- +-- name: mobf_calc_direction(pos1,pos2) +-- +--! @brief calculate direction angles from pos2 to pos1 +-- +--! @param start starting position +--! @param destination end position +--! @return anglexz,anglexy +------------------------------------------------------------------------------- +function mobf_calc_direction(start,destination) + mobf_assert_backtrace(start ~= nil) + mobf_assert_backtrace(destination ~= nil) + + local xdelta = destination.x - start.x + local ydelta = destination.y - start.y + local zdelta = destination.z - start.z + + local distance = math.sqrt( + math.pow(xdelta,2) + + math.pow(ydelta,2) + + math.pow(zdelta,2) + ) + + local anglexz = math.atan2(zdelta,xdelta) + local anglexy = math.acos(ydelta/distance) + return anglexz,anglexy +end + +------------------------------------------------------------------------------- +-- name: mobf_calc_scalar_speed(speedx,speedz) +-- +--! @brief calculate scalar speed +-- +--! @param speedx speed in direction x +--! @param speedz speed in direction z +-- +--! @return scalar speed value +------------------------------------------------------------------------------- +function mobf_calc_scalar_speed(speedx,speedz) + return math.sqrt(math.pow(speedx,2)+ + math.pow(speedz,2)) +end + +------------------------------------------------------------------------------- +-- name: mobf_calc_vector_components(dir_radians,absolute_speed) +-- +--! @brief calculate calculate x and z components of a directed speed +-- +--! @param dir_radians direction of movement radians +--! @param absolute_speed speed in direction +-- +--! @return {x,z} +------------------------------------------------------------------------------- +function mobf_calc_vector_components(dir_radians,absolute_speed) + + local retval = {x=0,z=0} + + retval.x = absolute_speed * math.cos(dir_radians) + retval.z = absolute_speed * math.sin(dir_radians) + + return retval +end + +------------------------------------------------------------------------------- +-- name: mobf_calc_vector_components_3d(xzplane_radians,xy_plane_radians,absolute_speed) +-- +--! @brief calculate calculate x and z components of a directed speed +-- +--! @param xz_plane_radians direction of movement within x-z plane radians +--! @param xy_plane_radians direction of movement within x-y plane radians +--! @param absolute_speed speed in direction +-- +--! @return {x,z} +------------------------------------------------------------------------------- +function mobf_calc_vector_components_3d(xz_plane_radians,xy_plane_radians,absolute_speed) + + local retval = {x=0,z=0,y=0} + + retval.x= absolute_speed * math.sin(xy_plane_radians) * math.cos(xz_plane_radians) + retval.z= absolute_speed * math.sin(xy_plane_radians) * math.sin(xz_plane_radians) + retval.y= absolute_speed * math.cos(xy_plane_radians) + + return retval +end + +------------------------------------------------------------------------------- +-- name: mobf_get_direction(pos1,pos2) +-- +--! @brief get normalized direction from pos1 to pos2 +-- +--! @param pos1 source point +--! @param pos2 destination point +--! @return xyz direction +------------------------------------------------------------------------------- +function mobf_get_direction(pos1,pos2) + + local x_raw = pos2.x -pos1.x + local y_raw = pos2.y -pos1.y + local z_raw = pos2.z -pos1.z + + + local x_abs = math.abs(x_raw) + local y_abs = math.abs(y_raw) + local z_abs = math.abs(z_raw) + + if x_abs >= y_abs and + x_abs >= z_abs then + + y_raw = y_raw * (1/x_abs) + z_raw = z_raw * (1/x_abs) + + x_raw = x_raw/x_abs + + end + + if y_abs >= x_abs and + y_abs >= z_abs then + + + x_raw = x_raw * (1/y_abs) + z_raw = z_raw * (1/y_abs) + + y_raw = y_raw/y_abs + + end + + if z_abs >= y_abs and + z_abs >= x_abs then + + x_raw = x_raw * (1/z_abs) + y_raw = y_raw * (1/z_abs) + + z_raw = z_raw/z_abs + + end + + return {x=x_raw,y=y_raw,z=z_raw} + +end + +------------------------------------------------------------------------------- +-- name: mobf_calc_yaw(x,z) +-- +--! @brief calculate radians value of a 2 dimendional vector +-- +--! @param x vector component 1 +--! @param z vector component 2 +-- +--! @return radians value +------------------------------------------------------------------------------- +function mobf_calc_yaw(x,z) + local direction = math.atan2(z,x) + + while direction < 0 do + direction = direction + (2* math.pi) + end + + while direction > (2*math.pi) do + direction = direction - (2* math.pi) + end + + return direction +end + +------------------------------------------------------------------------------- +-- name: mobf_assert_backtrace(value) +-- +--! @brief assert in case value is false +-- +--! @param heightdiff height difference between shooter and target +--! @param time time to reach target +--! @param acceleration acceleration set for object +-- +--! @return y-velocity at start +------------------------------------------------------------------------------- +function mobf_balistic_start_speed(heightdiff,time,acceleration) + mobf_assert_backtrace(heightdiff ~= nil) + mobf_assert_backtrace(time ~= nil) + mobf_assert_backtrace(acceleration ~= nil) + + return (heightdiff - (acceleration/2) * (time*time)) / time +end + +------------------------------------------------------------------------------- +-- name: mobf_calc_travel_time(distance,velocity,acceleration) +-- +--! @brief assert in case value is false +-- +--! @param distance distance to target +--! @param velocity to start with +--! @param acceleration acceleratio to use +-- +--! @return time to target +------------------------------------------------------------------------------- +function mobf_calc_travel_time(distance,velocity,acceleration) + mobf_assert_backtrace(distance ~= nil) + mobf_assert_backtrace(velocity ~= nil) + mobf_assert_backtrace(acceleration ~= nil) + + if acceleration == 0 then + --print("no accel shortcut time calculation") + return distance/velocity + end + + local a = acceleration/2 + local b = velocity + local c = -distance + + --print("a=" .. a .. " b=" .. b .. " c=" .. c) + + local det = b*b - 4*a*c + + --print("det=" .. det) + + if det < 0 then + return nil + end + + local ret1 = (-b + math.sqrt(det))/(2*a) + local ret2 = (-b - math.sqrt(det))/(2*a) + + --print("x1=" .. ret1 .. " x2=" .. ret2) + + if ret1 > 0 then + return ret1 + end + + return ret2 +end + +------------------------------------------------------------------------------- +-- name: mobf_same_quadrant(x1,z1,x2,z2) +-- +--! @brief check if two points are in same quadrant +-- +--! @param x1 x of point1 +--! @param z1 z of point1 +--! @param x2 x of point2 +--! @param z2 z of point2 +-- +--! @return true/false +------------------------------------------------------------------------------- +function mobf_same_quadrant(x1,z1,x2,z2) + + if x1 > 0 and x2 < 0 or + x1 < 0 and x2 >0 then + return false + end + + if z1 > 0 and z2 < 0 or + z1 < 0 and z2 >0 then + return false + end + + return true + +end + + +------------------------------------------------------------------------------- +-- name: mobf_gauss(center) +-- +--! @brief calc random value based uppon gauss distribution around a value +-- +--! @param center center value for distribution +--! @param max_deviation maximum deviation from center +-- +--! @return gauss randomized value +------------------------------------------------------------------------------- +function mobf_gauss(center,max_deviation) + + local u1 = 2 * math.random() -1 + local u2 = 2 * math.random() -1 + + local q = u1*u1 + u2*u2 + + local maxtries = 0 + + while (q == 0 or q >= 1) and maxtries < 10 do + u1 = math.random() + u2 = math.random() * -1 + q = u1*u1 + u2*u2 + + maxtries = maxtries +1 + end + + --abort random generation + if maxtries >= 10 then + return center + end + + local p = math.sqrt( (-2*math.log(q))/q ) + + local retval = nil + + --calculate normalized value of max deviation + if math.random() < 0.5 then + retval = center + ( u1*p * (center*max_deviation)) + else + retval = center + ( u2*p * (center*max_deviation)) + end + + return retval +end +--!@} \ No newline at end of file diff --git a/mods/mob_engines/mobf/utils/permanent_data.lua b/mods/mob_engines/mobf/utils/permanent_data.lua new file mode 100644 index 00000000..1ecb698d --- /dev/null +++ b/mods/mob_engines/mobf/utils/permanent_data.lua @@ -0,0 +1,248 @@ +------------------------------------------------------------------------------- +-- 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 permanent_data.lua +--! @brief functions for storing required data permanently +--! @copyright Sapier +--! @author Sapier +--! @date 2012-08-09 +--! +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @defgroup perm_data Permanent data handling +--! @brief functions to deal with permanent data +--! @ingroup framework_int +--! @{ + +------------------------------------------------------------------------------- +-- name: mobf_deserialize_permanent_entity_data(datastring) +-- +--! @brief parse datastring and return table of data +-- +--! @param staticdata string to deserialize +--! @return table containing data +------------------------------------------------------------------------------- + +function mobf_deserialize_permanent_entity_data(staticdata) + + --old style serialized static data + local retval = {spawnpoint={x=0,y=0,z=0},playerspawned=false,original_spawntime=-1,state="default"} + + if staticdata == nil then + return retval + end + + local deserialized = minetest.deserialize(staticdata) + + if deserialized ~= nil and + deserialized.version ~= nil then + return deserialized + end + + if staticdata ~= nil and + staticdata ~= "" then + local start_pos = 1 + local end_pos = string.find(staticdata,";") + + if end_pos ~= nil then + dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as first element") + if string.sub(staticdata,start_pos,end_pos-1) == "true" then + retval.playerspawned = true + end + else + return retval + end + + start_pos = end_pos +1 + end_pos = string.find(staticdata,";",start_pos) + + if end_pos ~= nil then + dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as second element") + retval.spawnpoint.x = tonumber(string.sub(staticdata,start_pos,end_pos-1)) + else + return retval + end + + start_pos = end_pos +1 + end_pos = string.find(staticdata,";",start_pos) + + if end_pos ~= nil then + dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as third element") + retval.spawnpoint.y = tonumber(string.sub(staticdata,start_pos,end_pos-1)) + else + return retval + end + + start_pos = end_pos +1 + end_pos = string.find(staticdata,";",start_pos) + + if end_pos ~= nil then + dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as fourth element") + retval.spawnpoint.z = tonumber(string.sub(staticdata,start_pos,end_pos-1)) + else + return retval + end + + start_pos = end_pos +1 + end_pos = string.find(staticdata,";",start_pos) + + if end_pos ~= nil then + dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as fivth element") + retval.original_spawntime = tonumber(string.sub(staticdata,start_pos,end_pos-1)) + else + return retval + end + + start_pos = end_pos +1 + end_pos = string.find(staticdata,";",start_pos) + + if end_pos ~= nil then + dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as sixth element") + retval.spawner = string.sub(staticdata,start_pos,end_pos-1) + if retval.spawner == "" then + retval.spawner = nil + end + else + return retval + end + + start_pos = end_pos +1 + end_pos = string.find(staticdata,";",start_pos) + + if end_pos ~= nil then + dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as seventh element") + retval.state = string.sub(staticdata,start_pos,end_pos-1) + if retval.state == "" then + retval.state = nil + end + else + return retval + end + + start_pos = end_pos +1 + end_pos = string.find(staticdata,";",start_pos) + + if end_pos ~= nil then + dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as eigth element") + retval.pathindex_raw = string.sub(staticdata,start_pos,end_pos-1) + retval.pathindex = tonumber(retval.pathindex_raw) + else + return retval + end + + start_pos = end_pos +1 + end_pos = string.find(staticdata,";",start_pos) + + if end_pos ~= nil then + dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as nineth element") + retval.pathowner = string.sub(staticdata,start_pos,end_pos-1) + if retval.pathowner == "" then + retval.pathowner = nil + end + else + return retval + end + + start_pos = end_pos +1 + end_pos = string.find(staticdata,";",start_pos) + + if end_pos ~= nil then + dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as tenth element") + retval.pathname = string.sub(staticdata,start_pos,end_pos-1) + if retval.pathname == "" then + retval.pathname = nil + end + else + return retval + end + end + + return retval + +end + +------------------------------------------------------------------------------- +-- name: mobf_serialize_permanent_entity_data(entity) +-- +--! @brief return string containing all entity data to be preserved +-- +--! @param entity to get data from +--! @return string containing entitys permanent data +------------------------------------------------------------------------------- +function mobf_serialize_permanent_entity_data(entity) + if entity.dynamic_data ~= nil and + entity.dynamic_data.last_static_data ~= nil and + entity.dynamic_data.last_static_data ~= "" then + --mobf_print("MOBF: mob " .. entity.data.name .. "(" .. tostring(entity) + -- .. ") wasn't even completely activated by now: " .. + -- dump(entity.dynamic_data.last_static_data)) + return entity.dynamic_data.last_static_data + end + + if entity.dynamic_data ~= nil and + entity.dynamic_data.spawning ~= nil then + + local state = "default" + if entity.dynamic_data.state ~= nil and + entity.dynamic_data.state.current ~= nil then + state = entity.dynamic_data.state.current.name + end + + if entity.dynamic_data.spawning.original_spawntime == nil then + entity.dynamic_data.spawning.original_spawntime = mobf_get_current_time() + minetest.log(LOGLEVEL_WARNING, + "MOBF: saving entity without spawntime setting current time") + end + + local pathowner = "" + local pathname = "" + local pathindex = "" + + if entity.dynamic_data.p_movement ~= nil then + if entity.dynamic_data.p_movement.pathowner ~= nil then + pathowner = entity.dynamic_data.p_movement.pathowner + end + + if entity.dynamic_data.p_movement.pathname ~= nil then + pathname = entity.dynamic_data.p_movement.pathname + end + + if entity.dynamic_data.p_movement.next_path_index ~= nil then + pathindex = "" .. entity.dynamic_data.p_movement.next_path_index + end + end + + local factions = mobf_factions.cleanupentity(entity) + + local toserialize = { + spawnpoint = entity.dynamic_data.spawning.spawnpoint, + playerspawned = entity.dynamic_data.spawning.player_spawned, + original_spawntime = entity.dynamic_data.spawning.original_spawntime, + spawner = entity.dynamic_data.spawning.spawner, + version = 3, + state = state, + pathindex = pathindex, + pathowner = pathowner, + pathname = pathname, + custom_persistent = entity.dynamic_data.custom_persistent, + factions = factions, + textureidx = entity.dynamic_data.textureidx + } + local serialized = minetest.serialize(toserialize) + --mobf_print("DEBUG: serialized -> " .. serialized) + return serialized + else + mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: >" .. dump(entity.data.name) .. + "< removed=" ..dump(entity.removed) .. " entity=" .. tostring(entity) .. + " No spawning information available on saving mob") + end + + return "" +end + +--!@} diff --git a/mods/mob_engines/mobf/utils/settings.lua b/mods/mob_engines/mobf/utils/settings.lua new file mode 100644 index 00000000..626a7b2b --- /dev/null +++ b/mods/mob_engines/mobf/utils/settings.lua @@ -0,0 +1,210 @@ +------------------------------------------------------------------------------- +-- 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 settings.lua +--! @brief generic functions used in many different places +--! @copyright Sapier +--! @author Sapier +--! @date 2013-02-04 +--! +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @defgroup gen_func Generic functions +--! @brief functions for various tasks +--! @ingroup framework_int +--! @{ + +------------------------------------------------------------------------------- +-- name: mobf_init_world_specific_settings(name,value) +-- +--! @brief check if world specific settings are supported by core if not do in lua +-- +------------------------------------------------------------------------------- +function mobf_init_world_specific_settings() + local mobf_world_settings_data = nil + + if minetest.world_setting_set == nil or + type(minetest.world_setting_set) ~= "function" then + + local mobf_world_path = minetest.get_worldpath() + + --initialy read settings file + mobf_world_settings_data = nil + local file,error = io.open(mobf_world_path .. "/mobf_settings.conf","r") + + if error ~= nil then + minetest.log(LOGLEVEL_WARNING,"MOBF: failed to open world specific config file") + mobf_world_settings_data = {} + else + local data_raw = file:read("*a") + file:close() + + data_raw=data_raw:gsub("\n","") + + if data_raw ~= nil then + mobf_world_settings_data = minetest.deserialize(data_raw) + end + end + + --set world settings function + minetest.world_setting_set = function(name,value) + mobf_world_settings_data[name] = value + + local file,error = io.open(mobf_world_path .. "/mobf_settings.conf.new","w") + + if error ~= nil then + minetest.log(LOGLEVEL_ERROR,"MOBF: failed to open world specific config file") + end + mobf_assert_backtrace(file ~= nil) + + local serialized_data = minetest.serialize(mobf_world_settings_data) + + local prefix="" + local lastwasnewline = false + local commahandled = false + local got_non_space_char = false + + for i = 1, #serialized_data do + local c = serialized_data:sub(i,i) + + if (c == "{") then + if not lastwasnewline then + file:write("\n") + end + file:write(prefix .. c .. "\n") + prefix = prefix .. " " + lastwasnewline = true + got_non_space_char = false + elseif (c == "}") then + if not lastwasnewline then + file:write("\n") + end + prefix = prefix:sub(1,#prefix-4) + local nextchar = serialized_data:sub(i+1,i+1) + if nextchar == "," then + file:write(prefix .. c ..nextchar .. "\n") + commahandled = true + else + file:write(prefix .. c .. "\n") + end + got_non_space_char = false + lastwasnewline = true + elseif (c == ",") then + if not commahandled then + file:write(c .. "\n") + end + commahandled = false + lastwasnewline = true + got_non_space_char = false + elseif (c == " ") then + if got_non_space_char then + file:write(c) + end + else + if lastwasnewline then + file:write(prefix) + end + file:write(c) + lastwasnewline = false + got_non_space_char = true + end + end + file:write("\n") + file:close() + + if not os.rename(mobf_world_path .. "/mobf_settings.conf.new", + mobf_world_path .. "/mobf_settings.conf") then + minetest.log(LOGLEVEL_ERROR,"MOBF: failed to swap old conf file to new one") + end + end + + minetest.world_setting_get = function(name) + mobf_assert_backtrace(mobf_world_settings_data ~= nil) + + return mobf_world_settings_data[name] + end + end + + + --initialize defaults + if minetest.world_setting_get("vombie_3d_burn_animation_enabled") == nil then + minetest.world_setting_set("vombie_3d_burn_animation_enabled",false) + end + + if minetest.world_setting_get("mobf_log_removed_entities") == nil then + minetest.world_setting_set("mobf_log_removed_entities",false) + end + + if minetest.world_setting_get("mobf_disable_animal_spawning") == nil then + minetest.world_setting_set("mobf_disable_animal_spawning",false) + end + + if minetest.world_setting_get("mobf_disable_3d_mode") == nil then + minetest.world_setting_set("mobf_disable_3d_mode",false) + end + + if minetest.world_setting_get("mobf_disable_pathfinding") == nil then + minetest.world_setting_set("mobf_disable_pathfinding",true) + end + + if minetest.world_setting_get("mobf_delayed_spawning") == nil then + minetest.world_setting_set("mobf_delayed_spawning",true) + end + + if minetest.world_setting_get("mobf_log_bug_warnings") == nil then + minetest.world_setting_set("mobf_log_bug_warnings",false) + end + + if minetest.world_setting_get("mobf_show_spawners") == nil then + minetest.world_setting_set("mobf_show_spawners",false) + end + + if minetest.world_setting_get("mobf_lifebar") == nil then + minetest.world_setting_set("mobf_lifebar",true) + end + + if minetest.world_setting_get("mobf_enable_statistics") == nil then + minetest.world_setting_set("mobf_enable_statistics",false) + end + + if minetest.world_setting_get("mobf_animal_spawning_secondary") == nil then + minetest.world_setting_set("mobf_animal_spawning_secondary",false) + end + + if minetest.world_setting_get("mobf_grief_protection") == nil then + minetest.world_setting_set("mobf_grief_protection",false) + end + +end + +------------------------------------------------------------------------------- +-- name: mobf_set_world_setting(name,value) +-- +--! @brief save a setting dedicated to a single world only +-- +--! @param name key to use for storage +--! @param value to save +------------------------------------------------------------------------------- +function mobf_set_world_setting(name,value) + minetest.world_setting_set(name,value) +end + +------------------------------------------------------------------------------- +-- name: mobf_get_world_setting(name,value) +-- +--! @brief read a setting dedicated to a single world only +-- +--! @param name key to use for storage +------------------------------------------------------------------------------- +function mobf_get_world_setting(name) + return minetest.world_setting_get(name) +end + +mobf_init_world_specific_settings() + +--!@} \ No newline at end of file diff --git a/mods/mob_engines/mobf/utils/text.lua b/mods/mob_engines/mobf/utils/text.lua new file mode 100644 index 00000000..4c710a83 --- /dev/null +++ b/mods/mob_engines/mobf/utils/text.lua @@ -0,0 +1,96 @@ +------------------------------------------------------------------------------- +-- 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 text.lua +--! @brief generic functions used in many different places +--! @copyright Sapier +--! @author Sapier +--! @date 2013-02-04 +--! +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @defgroup gen_func Generic functions +--! @brief functions for various tasks +--! @ingroup framework_int +--! @{ + +------------------------------------------------------------------------------- +-- name: printpos(pos) +-- +--! @brief convert pos to string of type "(X,Y,Z)" +-- +--! @param pos position to convert +--! @return string with coordinates of pos +------------------------------------------------------------------------------- +function printpos(pos) + if pos ~= nil then + if pos.y ~= nil then + mobf_assert_backtrace(type(pos.x) == "number") + mobf_assert_backtrace(type(pos.z) == "number") + mobf_assert_backtrace(type(pos.y) == "number") + return "("..string.format("%3f",pos.x).."," + ..string.format("%3f",pos.y).."," + ..string.format("%3f",pos.z)..")" + else + mobf_assert_backtrace(type(pos.x) == "number") + mobf_assert_backtrace(type(pos.z) == "number") + return "("..string.format("%3f",pos.x)..", ? ," + ..string.format("%3f",pos.z)..")" + end + end + return "" +end + +------------------------------------------------------------------------------- +-- name: mobf_print(text) +-- +--! @brief print adding timestamp in front of text +-- +--! @param text to show +------------------------------------------------------------------------------- +function mobf_print(text) + print("[" .. string.format("%10f",os.clock()) .. "]" .. text) +end + +------------------------------------------------------------------------------- +-- name: mobf_fixed_size_string(text,length) +-- +--! @brief make a text fixed length +-- +--! @param text text to enforce lenght +--! @param length lenght to enforce +--! +--! @return text with exactly lenght characters +------------------------------------------------------------------------------- +function mobf_fixed_size_string(text,length) + mobf_assert_backtrace(length ~= nil) + + if text == nil then + text="nil" + end + + local current_length = string.len(text) + + if current_length == nil then + current_length = 0 + text = "" + end + + if current_length < length then + + while current_length < length do + text = text .. " " + current_length = current_length +1 + end + + return text + else + return string.sub(text,1,length) + end +end +--!@} \ No newline at end of file diff --git a/mods/mob_engines/mobf/utils/tracing.lua b/mods/mob_engines/mobf/utils/tracing.lua new file mode 100644 index 00000000..3d8a9e28 --- /dev/null +++ b/mods/mob_engines/mobf/utils/tracing.lua @@ -0,0 +1,382 @@ +------------------------------------------------------------------------------- +-- 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 tracing.lua +--! @brief generic functions used in many different places +--! @copyright Sapier +--! @author Sapier +--! @date 2013-02-04 +--! +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +--! @defgroup gen_func Generic functions +--! @brief functions for various tasks +--! @ingroup framework_int +--! @{ + +local callback_statistics = {} + +local statistics = {} +statistics.total = 0 +statistics.abms = 0 +statistics.onstep = 0 +statistics.mapgen = 0 +statistics.lastcalc = 0 +statistics.activate = 0 +statistics.queue_load = 0 +statistics.spawn_onstep = 0 +statistics.user_1 = 0 +statistics.user_2 = 0 +statistics.user_3 = 0 +statistics.data = {} +statistics.data.total = { current=0,maxabs=0,max=0 } +statistics.data.abm = { current=0,maxabs=0,max=0 } +statistics.data.onstep = { current=0,maxabs=0,max=0 } +statistics.data.mapgen = { current=0,maxabs=0,max=0 } +statistics.data.activate = { current=0,maxabs=0,max=0 } +statistics.data.queue_load = { current=0,maxabs=0,max=0 } +statistics.data.mobs = { current=0,maxabs=" ",max=0 } +statistics.data.queue = { current=0,maxabs=" ",max=0 } +statistics.data.spawn_onstep = { current=0,maxabs=0,max=0 } + +statistics.data.user_1 = { current=0,maxabs=0,max=0 } +statistics.data.user_2 = { current=0,maxabs=0,max=0 } +statistics.data.user_3 = { current=0,maxabs=0,max=0 } + +------------------------------------------------------------------------------- +-- name: mobf_statistic_calc() +-- +--! @brief periodic update statistics +-- +------------------------------------------------------------------------------- +function mobf_statistic_calc(dtime) + local refresh_interval = 50 -- in tenth of seconds + local now = mobf_get_time_ms() + if statistics.lastcalc == nil or now > statistics.lastcalc + + (refresh_interval*100) then + local delta = now - statistics.lastcalc + local current_total = (statistics.total/delta)*100 + local current_abm = (statistics.abms/delta)*100 + local current_onstep = (statistics.onstep/delta)*100 + + local current_mapgen = (statistics.mapgen/delta)*100 + local current_activate = (statistics.activate/delta)*100 + local current_queue_load = (statistics.queue_load/delta)*100 + local current_spawn_onstep = (statistics.spawn_onstep/delta)*100 + local current_user_1 = (statistics.user_1/delta)*100 + local current_user_2 = (statistics.user_2/delta)*100 + local current_user_3 = (statistics.user_3/delta)*100 + + local active_mobs = 1 + for index,value in pairs(minetest.luaentities) do + if value.data ~= nil and value.data.name ~= nil then + active_mobs = active_mobs +1 + end + end + + statistics.total = 0 + statistics.abms = 0 + statistics.onstep = 0 + statistics.mapgen = 0 + statistics.activate = 0 + statistics.queue_load = 0 + statistics.spawn_onstep = 0 + statistics.user_1 = 0 + statistics.user_2 = 0 + statistics.user_3 = 0 + + statistics.data.total.current = current_total + statistics.data.total.maxabs = MAX(statistics.data.total.maxabs, math.floor(current_total*refresh_interval)) + statistics.data.total.max = MAX(statistics.data.total.max,current_total) + + statistics.data.abm.current = current_abm + statistics.data.abm.maxabs = MAX(statistics.data.abm.maxabs, math.floor(current_abm*refresh_interval)) + statistics.data.abm.max = MAX(statistics.data.abm.max,current_abm) + + statistics.data.onstep.current = current_onstep + statistics.data.onstep.maxabs = MAX(statistics.data.onstep.maxabs, math.floor(current_onstep*refresh_interval)) + statistics.data.onstep.max = MAX(statistics.data.onstep.max,current_onstep) + + statistics.data.mapgen.current = current_mapgen + statistics.data.mapgen.maxabs = MAX(statistics.data.mapgen.maxabs, math.floor(current_mapgen*refresh_interval)) + statistics.data.mapgen.max = MAX(statistics.data.mapgen.max,current_mapgen) + + statistics.data.activate.current = current_activate + statistics.data.activate.maxabs = MAX(statistics.data.activate.maxabs, math.floor(current_activate*refresh_interval)) + statistics.data.activate.max = MAX(statistics.data.activate.max,current_activate) + + statistics.data.queue_load.current = current_queue_load + statistics.data.queue_load.maxabs = MAX(statistics.data.queue_load.maxabs, math.floor(current_queue_load*refresh_interval)) + statistics.data.queue_load.max = MAX(statistics.data.queue_load.max,current_queue_load) + + statistics.data.spawn_onstep.current = current_spawn_onstep + statistics.data.spawn_onstep.maxabs = MAX(statistics.data.spawn_onstep.maxabs, math.floor(current_spawn_onstep*refresh_interval)) + statistics.data.spawn_onstep.max = MAX(statistics.data.spawn_onstep.max,current_spawn_onstep) + + statistics.data.mobs.current = active_mobs + statistics.data.mobs.max = MAX(statistics.data.mobs.max,active_mobs) + + statistics.data.user_1.current = current_user_1 + statistics.data.user_1.maxabs = MAX(statistics.data.user_1.maxabs, math.floor(current_user_1*refresh_interval)) + statistics.data.user_1.max = MAX(statistics.data.user_1.max,current_user_1) + + statistics.data.user_2.current = current_user_2 + statistics.data.user_2.maxabs = MAX(statistics.data.user_2.maxabs, math.floor(current_user_2*refresh_interval)) + statistics.data.user_2.max = MAX(statistics.data.user_2.max,current_user_2) + + statistics.data.user_3.current = current_user_3 + statistics.data.user_3.maxabs = MAX(statistics.data.user_3.maxabs, math.floor(current_user_3*refresh_interval)) + statistics.data.user_3.max = MAX(statistics.data.user_3.max,current_user_3) + + statistics.lastcalc = now + end +end + +------------------------------------------------------------------------------- +-- name: mobf_get_statistics() +-- +--! @brief get mobf statistic information +-- +--! @return mobf statistics +------------------------------------------------------------------------------- +function mobf_get_statistics() + return statistics +end + +------------------------------------------------------------------------------- +-- name: mobf_warn_long_fct(starttime,fctname,facility) +-- +--! @brief alias to get current time +-- +--! @param starttime time fct started +--! @param fctname name of function +--! @param facility name of facility to add time to +-- +--! @return current time in seconds +------------------------------------------------------------------------------- +function mobf_warn_long_fct(starttime,fctname,facility) + local currenttime = mobf_get_time_ms() + mobf_assert_backtrace(starttime ~= nil) + local delta = 0 + + if (starttime < currenttime) then + delta = currenttime - starttime + end + + if delta > 0 and minetest.world_setting_get("mobf_enable_statistics") then + if facility == "abm" then + statistics.abms = statistics.abms + delta + statistics.total = statistics.total + delta + end + + if facility == "on_step_total" then + statistics.onstep = statistics.onstep + delta + statistics.total = statistics.total + delta + end + + if facility == "mapgen" then + statistics.mapgen = statistics.mapgen + delta + statistics.total = statistics.total + delta + end + + if facility == "spawn_onstep" then + statistics.spawn_onstep = statistics.spawn_onstep + delta + statistics.total = statistics.total + delta + end + + if facility == "onpunch_total" then + statistics.onstep = statistics.onstep + delta + statistics.total = statistics.total + delta + end + + if facility == "delayed_processing" then + statistics.queue_load = statistics.queue_load + delta + statistics.total = statistics.total + delta + end + + if facility == "onactivate_total" then + statistics.activate = statistics.activate + delta + statistics.total = statistics.total + delta + end + + if facility == "user_1" then + statistics.user_1 = statistics.user_1 + delta + end + + if facility == "user_2" then + statistics.user_2 = statistics.user_2 + delta + end + + if facility == "user_3" then + statistics.user_3 = statistics.user_3 + delta + end + end + + if minetest.world_setting_get("mobf_enable_callback_statistics") then + if fctname == nil then + fctname = "unknown" + end + + if callback_statistics[fctname] == nil then + callback_statistics[fctname] = { + upto_005ms = 0, + upto_010ms = 0, + upto_020ms = 0, + upto_050ms = 0, + upto_100ms = 0, + upto_200ms = 0, + more = 0, + valcount = 0, + sum = 0, + last_time = 0, + } + end + + callback_statistics[fctname].valcount = callback_statistics[fctname].valcount +1 + callback_statistics[fctname].sum = callback_statistics[fctname].sum + delta + + if callback_statistics[fctname].valcount == 1000 then + callback_statistics[fctname].valcount = 0 + local deltatime = currenttime - callback_statistics[fctname].last_time + callback_statistics[fctname].last_time = currenttime + + minetest.log(LOGLEVEL_ERROR,"Statistics for: " .. fctname .. ": " .. + callback_statistics[fctname].upto_005ms .. "," .. + callback_statistics[fctname].upto_010ms .. "," .. + callback_statistics[fctname].upto_020ms .. "," .. + callback_statistics[fctname].upto_050ms .. "," .. + callback_statistics[fctname].upto_100ms .. "," .. + callback_statistics[fctname].upto_200ms .. "," .. + callback_statistics[fctname].more .. + " (".. callback_statistics[fctname].sum .. " / " .. deltatime .. ") " .. + tostring(math.floor((callback_statistics[fctname].sum/deltatime) * 100)) .. "%") + + callback_statistics[fctname].sum = 0 + end + + if delta < 5 then + callback_statistics[fctname].upto_005ms = callback_statistics[fctname].upto_005ms +1 + return + end + if delta < 10 then + callback_statistics[fctname].upto_010ms = callback_statistics[fctname].upto_010ms +1 + return + end + if delta < 20 then + callback_statistics[fctname].upto_020ms = callback_statistics[fctname].upto_020ms +1 + return + end + if delta < 50 then + callback_statistics[fctname].upto_050ms = callback_statistics[fctname].upto_050ms +1 + return + end + if delta < 100 then + callback_statistics[fctname].upto_100ms = callback_statistics[fctname].upto_100ms +1 + return + end + + if delta < 200 then + callback_statistics[fctname].upto_200ms = callback_statistics[fctname].upto_200ms +1 + return + end + + callback_statistics[fctname].more = callback_statistics[fctname].more +1 + end + + if delta >200 then + minetest.log(LOGLEVEL_ERROR,"MOBF: function " .. fctname .. " took too long: " .. delta .. " ms") + end +end + +------------------------------------------------------------------------------- +-- name: mobf_bug_warning() +-- +--! @brief make bug warnings configurable +-- +--! @param level bug severity level to use for minetest.log +--! @param text data to print to log +------------------------------------------------------------------------------- +function mobf_bug_warning(level,text) + if minetest.world_setting_get("mobf_log_bug_warnings") then + minetest.log(level,text) + end +end + +------------------------------------------------------------------------------- +-- name: mobf_init_timesource() +-- +--! @brief select timesource to be used by mobf +-- +------------------------------------------------------------------------------- +function mobf_init_timesource() + --try to get timesource with best accuracy + if type(minetest.get_us_time) == "function" then + mobf_get_time_ms = function() + return minetest.get_us_time() / 1000 + end + mobf_rtd.timesource = "minetest.get_us_time()" + else + if socket == nil then + local status, module = pcall(require, 'socket') + + if status and type(module.gettime) == "function" then + mobf_get_time_ms = function() + return socket.gettime()*1000 + end + mobf_rtd.timesource = "socket.gettime()" + end + else + if type(socket.gettime) == "function" then + mobf_get_time_ms = function() + return socket.gettime()*1000 + end + mobf_rtd.timesource = "socket.gettime()" + end + end + end +end + +------------------------------------------------------------------------------- +-- name: mobf_init_statistics() +-- +--! @brief initialize profiling/statistics support +-- +------------------------------------------------------------------------------- +function mobf_init_statistics() + + --initialize statistics callback + if minetest.world_setting_get("mobf_enable_statistics") then + minetest.register_globalstep(mobf_statistic_calc) + + local abm_register_call = minetest.register_abm + + minetest.register_abm = function(spec) + local modname = minetest.get_current_modname() + + local mob_start,mob_end = string.find(modname,"mob") + local animal_start,animal_end = string.find(modname,"animal") + local mobf_start,mobf_end = string.find(modname,"mobf") + + if mob_start == 1 or animal_start == 1 or mobf_start == 1 then + local action = spec.action + + spec.action = function(pos, node, active_object_count, active_object_count_wider) + local starttime = mobf_get_time_ms() + local retval = action(pos, node, active_object_count, active_object_count_wider) + mobf_warn_long_fct(starttime,"auto_abm","abm") + return retval + end + + minetest.log(LOGLEVEL_NOTICE,"MOBF: tracing enabled instrumenting abm for mod " .. modname) + end + abm_register_call(spec) + end + end + +end +--!@} \ No newline at end of file