parent
a849767796
commit
0ae41552a8
17
README.txt
17
README.txt
|
@ -27,11 +27,11 @@ Here are some of the other highlights of the Mobs Lite engine:
|
||||||
* Animals will attempt to flee to safety when a player that previously punched them
|
* Animals will attempt to flee to safety when a player that previously punched them
|
||||||
returns to the field of view.
|
returns to the field of view.
|
||||||
|
|
||||||
* Creatures avoid running into most obstacles by analyzing the surroundings and steadily
|
* Animals can be programmed to follow players that are wielding food and to eat directly
|
||||||
adjusting their course.
|
from the player's hand.
|
||||||
|
|
||||||
* Mobs can be randomly spawned in the vicinity of players, thus relieving the overhead of
|
* Seamless integration with Axon allows Mobs to react to various environmental stimulii
|
||||||
costly ABM-based spawners.
|
like smells, sounds, etc.
|
||||||
|
|
||||||
* Timekeeper helper class ensures efficient dispatching of mob-related callbacks at the
|
* Timekeeper helper class ensures efficient dispatching of mob-related callbacks at the
|
||||||
appropriate server step.
|
appropriate server step.
|
||||||
|
@ -39,6 +39,12 @@ Here are some of the other highlights of the Mobs Lite engine:
|
||||||
* Builtin lookup table allows for efficiently iterating over multiple classes of objects
|
* Builtin lookup table allows for efficiently iterating over multiple classes of objects
|
||||||
within a specific radius.
|
within a specific radius.
|
||||||
|
|
||||||
|
* Mobs can be randomly spawned in the vicinity of players, thus relieving the overhead of
|
||||||
|
costly ABM-based spawners.
|
||||||
|
|
||||||
|
* Mobs will avoid running into most obstacles by analyzing the surroundings and steadily
|
||||||
|
adjusting their course.
|
||||||
|
|
||||||
* And of course, much much more!
|
* And of course, much much more!
|
||||||
|
|
||||||
Since Mobs Lite is still in early beta, there is the likelihood of lingering bugs. The API
|
Since Mobs Lite is still in early beta, there is the likelihood of lingering bugs. The API
|
||||||
|
@ -70,6 +76,9 @@ TNT Mod (required)
|
||||||
Default Mod (required)
|
Default Mod (required)
|
||||||
https://github.com/minetest-game-mods/default
|
https://github.com/minetest-game-mods/default
|
||||||
|
|
||||||
|
Axon Mod (optional)
|
||||||
|
https://bitbucket.org/sorcerykid/axon
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
52
animals.lua
52
animals.lua
|
@ -34,21 +34,32 @@ mobs.register_mob( "mobs:kitten", {
|
||||||
hunger_params = { offset = -0.5, spread = 2.5 },
|
hunger_params = { offset = -0.5, spread = 2.5 },
|
||||||
alertness_states = {
|
alertness_states = {
|
||||||
ignore = { view_offset = 2, view_radius = 4, view_height = 4, view_acuity = 3 },
|
ignore = { view_offset = 2, view_radius = 4, view_height = 4, view_acuity = 3 },
|
||||||
|
search = { view_offset = 2, view_radius = 10, view_height = 4, view_acuity = 3, view_filter = function ( self, obj, clarity )
|
||||||
|
return clarity == 0.0 and "search" or "follow"
|
||||||
|
end },
|
||||||
follow = { view_offset = 2, view_radius = 10, view_height = 4, view_acuity = 3 },
|
follow = { view_offset = 2, view_radius = 10, view_height = 4, view_acuity = 3 },
|
||||||
escape = { view_offset = 2, view_radius = 10, view_height = 4, view_acuity = 3 },
|
escape = { view_offset = 2, view_radius = 10, view_height = 4, view_acuity = 3 },
|
||||||
},
|
},
|
||||||
|
awareness_stages = {
|
||||||
|
search = { decay = 12.0, abort_state = "ignore" },
|
||||||
|
follow = { decay = 0.0, abort_state = "search" },
|
||||||
|
escape = { decay = 12.0, abort_state = "ignore" },
|
||||||
|
},
|
||||||
|
|
||||||
certainty = 1.0,
|
certainty = 1.0,
|
||||||
sensitivity = 0.6,
|
sensitivity = 0.6,
|
||||||
|
|
||||||
fear_factor = 0,
|
fear_factor = 6,
|
||||||
flee_factor = 10,
|
flee_factor = 10,
|
||||||
|
sneak_velocity = 0.8,
|
||||||
walk_velocity = 0.8,
|
walk_velocity = 0.8,
|
||||||
stray_velocity = 1.0,
|
stray_velocity = 1.0,
|
||||||
recoil_velocity = 1.0,
|
recoil_velocity = 1.0,
|
||||||
run_velocity = 1.2,
|
run_velocity = 1.2,
|
||||||
escape_range = 3.0,
|
search_range = 3.0,
|
||||||
follow_range = 2.0,
|
follow_range = 2.0,
|
||||||
pickup_range = 2.0,
|
pickup_range = 2.0,
|
||||||
|
escape_range = 3.0,
|
||||||
can_jump = false,
|
can_jump = false,
|
||||||
can_walk = true,
|
can_walk = true,
|
||||||
enable_fall_damage = true,
|
enable_fall_damage = true,
|
||||||
|
@ -129,21 +140,32 @@ mobs.register_mob( "mobs:rat", {
|
||||||
hunger_params = { offset = 0.0, spread = 2.5 },
|
hunger_params = { offset = 0.0, spread = 2.5 },
|
||||||
alertness_states = {
|
alertness_states = {
|
||||||
ignore = { view_offset = 0, view_radius = 3, view_height = 3, view_acuity = 4 },
|
ignore = { view_offset = 0, view_radius = 3, view_height = 3, view_acuity = 4 },
|
||||||
|
search = { view_offset = 0, view_radius = 6, view_height = 3, view_acuity = 6, view_filter = function ( self, obj, clarity )
|
||||||
|
return clarity == 0.0 and "search" or "follow"
|
||||||
|
end },
|
||||||
follow = { view_offset = 0, view_radius = 6, view_height = 3, view_acuity = 6 },
|
follow = { view_offset = 0, view_radius = 6, view_height = 3, view_acuity = 6 },
|
||||||
escape = { view_offset = 0, view_radius = 6, view_height = 3, view_acuity = 6 },
|
escape = { view_offset = 0, view_radius = 6, view_height = 3, view_acuity = 6 },
|
||||||
},
|
},
|
||||||
|
awareness_stages = {
|
||||||
|
search = { decay = 12.0, abort_state = "ignore" },
|
||||||
|
follow = { decay = 0.0, abort_state = "search" },
|
||||||
|
escape = { decay = 12.0, abort_state = "ignore" },
|
||||||
|
},
|
||||||
|
|
||||||
certainty = 1.0,
|
certainty = 1.0,
|
||||||
sensitivity = 0.0,
|
sensitivity = 0.0,
|
||||||
|
|
||||||
fear_factor = 8,
|
fear_factor = 8,
|
||||||
flee_factor = 10,
|
flee_factor = 10,
|
||||||
|
sneak_velocity = 0.5,
|
||||||
walk_velocity = 0.5,
|
walk_velocity = 0.5,
|
||||||
stray_velocity = 0.5,
|
stray_velocity = 0.5,
|
||||||
recoil_velocity = 0.5,
|
recoil_velocity = 0.5,
|
||||||
run_velocity = 1.2,
|
run_velocity = 1.2,
|
||||||
escape_range = 0.0,
|
search_range = 2.0,
|
||||||
follow_range = 2.0,
|
follow_range = 2.0,
|
||||||
pickup_range = 2.0,
|
pickup_range = 2.0,
|
||||||
|
escape_range = 0.0,
|
||||||
can_jump = false,
|
can_jump = false,
|
||||||
can_walk = true,
|
can_walk = true,
|
||||||
enable_fall_damage = true,
|
enable_fall_damage = true,
|
||||||
|
@ -212,21 +234,32 @@ mobs.register_mob( "mobs:hare", {
|
||||||
hunger_params = { offset = 0.0, spread = 2.0 },
|
hunger_params = { offset = 0.0, spread = 2.0 },
|
||||||
alertness_states = {
|
alertness_states = {
|
||||||
ignore = { view_offset = 0, view_radius = 2, view_height = 6, view_acuity = 0 },
|
ignore = { view_offset = 0, view_radius = 2, view_height = 6, view_acuity = 0 },
|
||||||
|
search = { view_offset = 0, view_radius = 12, view_height = 6, view_acuity = 3, view_filter = function ( self, obj, clarity )
|
||||||
|
return clarity == 0.0 and "search" or "follow"
|
||||||
|
end },
|
||||||
follow = { view_offset = 0, view_radius = 12, view_height = 6, view_acuity = 3 },
|
follow = { view_offset = 0, view_radius = 12, view_height = 6, view_acuity = 3 },
|
||||||
escape = { view_offset = 0, view_radius = 12, view_height = 6, view_acuity = 3 },
|
escape = { view_offset = 0, view_radius = 12, view_height = 6, view_acuity = 3 },
|
||||||
},
|
},
|
||||||
|
awareness_stages = {
|
||||||
|
search = { decay = 12.0, abort_state = "ignore" },
|
||||||
|
follow = { decay = 0.0, abort_state = "search" },
|
||||||
|
escape = { decay = 12.0, abort_state = "ignore" },
|
||||||
|
},
|
||||||
|
|
||||||
certainty = 1.0,
|
certainty = 1.0,
|
||||||
sensitivity = 0.5,
|
sensitivity = 0.5,
|
||||||
|
|
||||||
fear_factor = 6,
|
fear_factor = 6,
|
||||||
flee_factor = 10,
|
flee_factor = 10,
|
||||||
|
sneak_velocity = 2.0,
|
||||||
walk_velocity = 3.0,
|
walk_velocity = 3.0,
|
||||||
stray_velocity = 1.0,
|
stray_velocity = 1.0,
|
||||||
recoil_velocity = 2.0,
|
recoil_velocity = 2.0,
|
||||||
run_velocity = 3.5,
|
run_velocity = 3.5,
|
||||||
escape_range = 3.0,
|
search_range = 3.0,
|
||||||
follow_range = 3.0,
|
follow_range = 3.0,
|
||||||
pickup_range = 2.0,
|
pickup_range = 2.0,
|
||||||
|
escape_range = 3.0,
|
||||||
can_jump = true,
|
can_jump = true,
|
||||||
can_walk = true,
|
can_walk = true,
|
||||||
enable_fall_damage = true,
|
enable_fall_damage = true,
|
||||||
|
@ -306,18 +339,29 @@ mobs.register_mob( "mobs:chicken", {
|
||||||
hunger_params = { offset = 0.0, spread = 2.0 },
|
hunger_params = { offset = 0.0, spread = 2.0 },
|
||||||
alertness_states = {
|
alertness_states = {
|
||||||
ignore = { view_offset = 2, view_radius = 4, view_height = 4, view_acuity = 0 },
|
ignore = { view_offset = 2, view_radius = 4, view_height = 4, view_acuity = 0 },
|
||||||
|
search = { view_offset = 2, view_radius = 6, view_height = 4, view_acuity = 2, view_filter = function ( self, obj, clarity )
|
||||||
|
return clarity == 0.0 and "search" or "follow"
|
||||||
|
end },
|
||||||
follow = { view_offset = 2, view_radius = 6, view_height = 4, view_acuity = 2 },
|
follow = { view_offset = 2, view_radius = 6, view_height = 4, view_acuity = 2 },
|
||||||
escape = { view_offset = 2, view_radius = 6, view_height = 4, view_acuity = 2 },
|
escape = { view_offset = 2, view_radius = 6, view_height = 4, view_acuity = 2 },
|
||||||
},
|
},
|
||||||
|
awareness_stages = {
|
||||||
|
search = { decay = 12.0, abort_state = "ignore" },
|
||||||
|
follow = { decay = 0.0, abort_state = "search" },
|
||||||
|
escape = { decay = 12.0, abort_state = "ignore" },
|
||||||
|
},
|
||||||
|
|
||||||
certainty = 1.0,
|
certainty = 1.0,
|
||||||
sensitivity = 0.3,
|
sensitivity = 0.3,
|
||||||
|
|
||||||
fear_factor = 2,
|
fear_factor = 2,
|
||||||
flee_factor = 10,
|
flee_factor = 10,
|
||||||
|
sneak_velocity = 1.5,
|
||||||
walk_velocity = 1.5,
|
walk_velocity = 1.5,
|
||||||
stray_velocity = 1.0,
|
stray_velocity = 1.0,
|
||||||
recoil_velocity = 2.0,
|
recoil_velocity = 2.0,
|
||||||
run_velocity = 2.0,
|
run_velocity = 2.0,
|
||||||
|
search_range = 3.0,
|
||||||
follow_range = 3.0,
|
follow_range = 3.0,
|
||||||
pickup_range = 2.0,
|
pickup_range = 2.0,
|
||||||
escape_range = 3.0,
|
escape_range = 3.0,
|
||||||
|
|
399
init.lua
399
init.lua
|
@ -20,6 +20,8 @@ local world_gravity = -10
|
||||||
local liquid_density = 0.5
|
local liquid_density = 0.5
|
||||||
local liquid_viscosity = 0.6
|
local liquid_viscosity = 0.6
|
||||||
|
|
||||||
|
local next_noise_id = 1
|
||||||
|
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
local random = math.random
|
local random = math.random
|
||||||
|
@ -66,7 +68,7 @@ function Timekeeper( this )
|
||||||
end
|
end
|
||||||
|
|
||||||
self.start_now = function ( period, name, func )
|
self.start_now = function ( period, name, func )
|
||||||
if func( this, 0, period, 0.0, 0.0 ) then
|
if not func( this, 0, period, 0.0, 0.0 ) then
|
||||||
timer_defs[ name ] = { cycles = 0, period = period, expiry = clock + period, started = clock, func = func }
|
timer_defs[ name ] = { cycles = 0, period = period, expiry = clock + period, started = clock, func = func }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -256,8 +258,9 @@ minetest.register_on_leaveplayer( function( player, is_timeout )
|
||||||
|
|
||||||
-- delete all target references (if applicable)
|
-- delete all target references (if applicable)
|
||||||
for id, obj in pairs( registry.avatars ) do
|
for id, obj in pairs( registry.avatars ) do
|
||||||
if obj:get_luaentity( ).target == player then
|
local this = obj:get_luaentity( )
|
||||||
this:reset_target( )
|
if this.target and this.target.obj == player then
|
||||||
|
this:reset_alertness( "ignore" )
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
registry.players[ name ] = nil
|
registry.players[ name ] = nil
|
||||||
|
@ -287,8 +290,9 @@ end
|
||||||
|
|
||||||
builtin_item.on_deactivate = function ( self, id )
|
builtin_item.on_deactivate = function ( self, id )
|
||||||
for id, obj in pairs( registry.avatars ) do
|
for id, obj in pairs( registry.avatars ) do
|
||||||
if obj:get_luaentity( ).target == self.object then
|
local this = obj:get_luaentity( )
|
||||||
this:reset_target( )
|
if this.target and this.target.obj == self.object then
|
||||||
|
this:reset_alertness( "ignore" )
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
registry.spawnitems[ id ] = nil
|
registry.spawnitems[ id ] = nil
|
||||||
|
@ -324,11 +328,13 @@ mobs.register_mob = function ( name, def )
|
||||||
receptrons = def.receptrons,
|
receptrons = def.receptrons,
|
||||||
death_message = def.death_message,
|
death_message = def.death_message,
|
||||||
alertness_states = def.alertness_states,
|
alertness_states = def.alertness_states,
|
||||||
|
awareness_stages = def.awareness_stages,
|
||||||
sensitivity = def.sensitivity or 0.0,
|
sensitivity = def.sensitivity or 0.0,
|
||||||
certainty = def.certainty or 1.0,
|
certainty = def.certainty or 1.0,
|
||||||
attack_range = def.attack_range,
|
attack_range = def.attack_range,
|
||||||
escape_range = def.escape_range,
|
escape_range = def.escape_range,
|
||||||
follow_range = def.follow_range,
|
follow_range = def.follow_range,
|
||||||
|
search_range = def.search_range,
|
||||||
pickup_range = def.pickup_range,
|
pickup_range = def.pickup_range,
|
||||||
sneak_velocity = def.sneak_velocity,
|
sneak_velocity = def.sneak_velocity,
|
||||||
walk_velocity = def.walk_velocity,
|
walk_velocity = def.walk_velocity,
|
||||||
|
@ -351,9 +357,9 @@ mobs.register_mob = function ( name, def )
|
||||||
shoot_period = def.shoot_period,
|
shoot_period = def.shoot_period,
|
||||||
shoot_chance = def.shoot_chance,
|
shoot_chance = def.shoot_chance,
|
||||||
weapon_params = def.weapon_params,
|
weapon_params = def.weapon_params,
|
||||||
watch_wielditems = def.watch_wielditems,
|
watch_wielditems = def.watch_wielditems or { },
|
||||||
watch_spawnitems = def.watch_spawnitems,
|
watch_spawnitems = def.watch_spawnitems or { },
|
||||||
watch_players = def.watch_players,
|
watch_players = def.watch_players or { },
|
||||||
sounds = def.sounds,
|
sounds = def.sounds,
|
||||||
animation = def.animation,
|
animation = def.animation,
|
||||||
fear_factor = def.fear_factor,
|
fear_factor = def.fear_factor,
|
||||||
|
@ -365,11 +371,13 @@ mobs.register_mob = function ( name, def )
|
||||||
enable_swimming = true,
|
enable_swimming = true,
|
||||||
shoot_count = 0,
|
shoot_count = 0,
|
||||||
timeout = def.timeout,
|
timeout = def.timeout,
|
||||||
|
neutral_state = "ignore",
|
||||||
|
defense_state = def.type == "monster" and "attack" or "ignore",
|
||||||
is_tamed = false,
|
is_tamed = false,
|
||||||
description = def.description,
|
description = def.description,
|
||||||
after_activate = def.after_activate,
|
after_activate = def.after_activate,
|
||||||
before_deactivate = def.before_deactivate,
|
before_deactivate = def.before_deactivate,
|
||||||
after_state_change = def.after_state_change,
|
before_state_change = def.before_state_change,
|
||||||
before_punch = def.before_punch,
|
before_punch = def.before_punch,
|
||||||
|
|
||||||
-- prepare noise generator with seed, octaves, persistence, spread
|
-- prepare noise generator with seed, octaves, persistence, spread
|
||||||
|
@ -426,14 +434,14 @@ mobs.register_mob = function ( name, def )
|
||||||
get_direct_yaw_delta = function ( self, pos )
|
get_direct_yaw_delta = function ( self, pos )
|
||||||
local yaw = self:get_direct_yaw( pos )
|
local yaw = self:get_direct_yaw( pos )
|
||||||
return abs( normalize_angle( yaw - self.yaw ) )
|
return abs( normalize_angle( yaw - self.yaw ) )
|
||||||
end
|
|
||||||
|
|
||||||
get_target_yaw = function ( self, pos, r_limit, r_ratio )
|
|
||||||
return self:get_direct_yaw( pos ) + upper_random( r_limit, r_ratio or 1 )
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
get_target_yaw_delta = function ( self, pos )
|
get_target_yaw = function ( self, r_limit, r_ratio )
|
||||||
local yaw = self:get_direct_yaw( pos )
|
return self:get_direct_yaw( self.target.pos ) + upper_random( r_limit, r_ratio or 1 )
|
||||||
|
end,
|
||||||
|
|
||||||
|
get_target_yaw_delta = function ( self )
|
||||||
|
local yaw = self:get_direct_yaw( self.target.pos )
|
||||||
return abs( normalize_angle( yaw - self.yaw ) )
|
return abs( normalize_angle( yaw - self.yaw ) )
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
@ -482,7 +490,7 @@ mobs.register_mob = function ( name, def )
|
||||||
end,
|
end,
|
||||||
|
|
||||||
play_sound = function ( self, name )
|
play_sound = function ( self, name )
|
||||||
minetest.sound_play( name, { object = self.object } )
|
minetest.sound_play( name, { object = self.object, loop = false } )
|
||||||
end,
|
end,
|
||||||
|
|
||||||
play_sound_repeat = function ( self, name )
|
play_sound_repeat = function ( self, name )
|
||||||
|
@ -504,31 +512,34 @@ mobs.register_mob = function ( name, def )
|
||||||
|
|
||||||
-- sensory processing --
|
-- sensory processing --
|
||||||
|
|
||||||
check_suspect = function ( self, target, elapsed )
|
check_suspect = function ( self, target_obj, clarity, elapsed )
|
||||||
local entity = target:get_luaentity( )
|
-- default to defense or neutral state if not in any watch list
|
||||||
local suspect
|
local suspect = random( 10 ) <= self.fear_factor and self.defense_state or self.neutral_state
|
||||||
|
local entity = target_obj:get_luaentity( )
|
||||||
|
|
||||||
if not entity then
|
if not entity then
|
||||||
local player_name = target:get_player_name( )
|
local player_name = target_obj:get_player_name( )
|
||||||
local item_name = target:get_wielded_item( ):get_name( )
|
local item_name = target_obj:get_wielded_item( ):get_name( )
|
||||||
suspect = self.watch_players[ player_name ] or self.watch_wielditems[ item_name ]
|
suspect = self.watch_players[ player_name ] or self.watch_wielditems[ item_name ] or suspect
|
||||||
elseif entity.name == "__builtin:item" then
|
elseif entity.name == "__builtin:item" then
|
||||||
suspect = self.watch_spawnitems[ entity.item_name ]
|
suspect = self.watch_spawnitems[ entity.item_name ] or suspect
|
||||||
end
|
end
|
||||||
|
|
||||||
if type( suspect ) == "function" then
|
if type( suspect ) == "function" then
|
||||||
return suspect( self, target, elapsed or 0.0 )
|
return suspect( self, target_obj, clarity, elapsed or 0.0 ) -- must always return a valid state
|
||||||
else
|
else
|
||||||
return suspect
|
return suspect
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
is_paranoid = function ( self, target_pos )
|
get_visibility = function ( self, target_pos )
|
||||||
local length = get_vector_length( self.pos, target_pos )
|
local length = get_vector_length( self.pos, target_pos )
|
||||||
|
local height = get_vector_height( self.pos, target_pos )
|
||||||
local this = self.alertness
|
local this = self.alertness
|
||||||
|
|
||||||
if not this or length > 48 then
|
if not this or length > 48 or height > 48 then -- immediately eliminate far objects
|
||||||
return false
|
return 0.0, false, false
|
||||||
|
|
||||||
else
|
else
|
||||||
local view_range = this.view_radius + this.view_offset
|
local view_range = this.view_radius + this.view_offset
|
||||||
local view_pos = this.view_offset == 0.0 and self.pos or vector.new(
|
local view_pos = this.view_offset == 0.0 and self.pos or vector.new(
|
||||||
|
@ -538,93 +549,123 @@ mobs.register_mob = function ( name, def )
|
||||||
)
|
)
|
||||||
|
|
||||||
local radius = get_vector_length( view_pos, target_pos )
|
local radius = get_vector_length( view_pos, target_pos )
|
||||||
local height = get_vector_height( view_pos, target_pos )
|
|
||||||
local clarity = get_power_decrease( 1.0, this.view_acuity, length / view_range )
|
local clarity = get_power_decrease( 1.0, this.view_acuity, length / view_range )
|
||||||
|
|
||||||
-- certainty factor ranges from 0 (target never evident) to 1 (target always evident)
|
-- certainty factor ranges from 0 (target never evident) to 1 (target always evident)
|
||||||
-- sensitivity threshold ranges from 0 (full perception) to 1 (no perception)
|
-- sensitivity threshold ranges from 0 (full perception) to 1 (no perception)
|
||||||
|
|
||||||
--node_locator( vector.offset_y( view_pos ), 4.5, 1.0, "yellow" )
|
--node_locator( vector.offset_y( view_pos ), 4.5, 1.0, "yellow" )
|
||||||
--printf( "is_paranoid(%s):\n[%s] radius <= view_radius\n[%s] height <= view_height\n%0.2f = clarity (%0.2f = sensitivity, %0.2f = certainty)", self.name, radius <= this.view_radius and "x" or " ", height <= this.view_height and "x" or " ", clarity, self.sensitivity, self.certainty )
|
--printf( "is_paranoid(%s):\n[%s] radius <= view_radius\n[%s] height <= view_height\n%0.2f = clarity (%0.2f = sensitivity, %$
|
||||||
|
|
||||||
return radius <= this.view_radius and height <= this.view_height and
|
local is_visible = radius <= this.view_radius and height <= this.view_height
|
||||||
clarity > self.sensitivity and clarity * self.certainty > random( )
|
local is_evident = clarity > self.sensitivity and clarity * self.certainty > random( )
|
||||||
|
|
||||||
|
return clarity, is_visible, is_evident
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
-- state-change handlers --
|
-- target functions --
|
||||||
|
|
||||||
reset_target = function ( self )
|
create_target = function ( self, obj )
|
||||||
if self.target then
|
local pos = obj:get_pos( )
|
||||||
self:set_ignore_state( )
|
local clarity, is_visible, is_evident = self:get_visibility( pos )
|
||||||
|
local alertness = self.alertness_states[ self.state ]
|
||||||
|
|
||||||
|
-- when creating target it must be visible and evident!
|
||||||
|
if is_visible and is_evident then
|
||||||
|
if alertness and alertness.view_filter then
|
||||||
|
return alertness.view_filter( self, obj, clarity ), { obj = obj, pos = pos }
|
||||||
|
elseif clarity > 0.0 then
|
||||||
|
return self:check_suspect( obj, clarity ), { obj = obj, pos = pos }
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return self.state, self.target
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
set_ignore_state = function ( self )
|
verify_target = function ( self, elapsed )
|
||||||
if self.state == "ignore" then return end
|
if self.target.obj then
|
||||||
|
local target_pos = self.target.obj:get_pos( ) -- check target's new position
|
||||||
|
local clarity, is_visible, is_evident = self:get_visibility( target_pos )
|
||||||
|
local alertness = self.alertness_states[ self.state ]
|
||||||
|
|
||||||
self.state = "ignore"
|
-- update last-known target position only if visible and evident
|
||||||
self.target = nil
|
if is_visible and is_evident then
|
||||||
self.alertness = self.alertness_states.ignore
|
self.target.pos = target_pos
|
||||||
self.is_tamed = false
|
else
|
||||||
|
clarity = 0.0
|
||||||
|
end
|
||||||
|
|
||||||
if self.custom.after_state_change then
|
if alertness and alertness.view_filter then
|
||||||
self.custom.after_state_change( self )
|
return alertness.view_filter( self, self.target.obj, clarity )
|
||||||
|
elseif clarity > 0.0 then
|
||||||
|
return self:check_suspect( self.target.obj, clarity, elapsed )
|
||||||
|
else
|
||||||
|
return self.abort_state or self.neutral_state
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self:start_ignore_action( )
|
return self.state
|
||||||
end,
|
end,
|
||||||
|
|
||||||
set_escape_state = function ( self, target )
|
locate_target = function ( self )
|
||||||
if self.state == "escape" then return end
|
if self.sounds and self.sounds.random and random( 35 ) == 1 then
|
||||||
self.state = "escape"
|
minetest.sound_play( self.sounds.random, { object = self.object } )
|
||||||
|
end
|
||||||
|
|
||||||
|
-- when not upset, seek out food or prey at random intervals
|
||||||
|
for obj in mobs.iterate_registry( self.pos, 30, 30, { players = true, spawnitems = true } ) do
|
||||||
|
|
||||||
|
if random( 10 ) <= self.fear_factor and obj:is_player( ) and not obj:get_attach( ) then
|
||||||
|
local state, target = self:create_target( obj )
|
||||||
|
if state ~= self.state then
|
||||||
|
self:reset_alertness( state, target )
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- alertness and awareness functions --
|
||||||
|
|
||||||
|
start_awareness = function ( self, target )
|
||||||
|
local awareness = self.awareness_stages[ self.state ]
|
||||||
|
|
||||||
|
if awareness then
|
||||||
|
self.abort_state = awareness.abort_state
|
||||||
|
if awareness.decay > 0 then
|
||||||
|
self.timekeeper.start( awareness.decay, "awareness", function ( )
|
||||||
|
self:reset_alertness( self.abort_state, self.target )
|
||||||
|
end )
|
||||||
|
else
|
||||||
|
self.timekeeper.clear( "awareness" )
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.abort_state = nil
|
||||||
|
self.timekeeper.clear( "awareness" )
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
reset_alertness = function ( self, state, target )
|
||||||
|
if state == self.state then return end
|
||||||
|
|
||||||
|
if self.before_state_change then
|
||||||
|
self:before_state_change( self.state, state )
|
||||||
|
end
|
||||||
|
|
||||||
|
self.state = state
|
||||||
self.target = target
|
self.target = target
|
||||||
self.alertness = self.alertness_states.escape
|
self.alertness = self.alertness_states[ state ]
|
||||||
self.is_tamed = false
|
|
||||||
|
|
||||||
if random( 2 ) == 1 then
|
self:start_awareness( )
|
||||||
-- this is a bad player, so keep watch
|
self.action_funcs[ state ]( self )
|
||||||
self.watch_players[ target:get_player_name( ) ] = "escape"
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.custom.after_state_change then
|
|
||||||
self.custom.after_state_change( self )
|
|
||||||
end
|
|
||||||
|
|
||||||
self:start_escape_action( )
|
|
||||||
end,
|
|
||||||
|
|
||||||
set_follow_state = function ( self, target )
|
|
||||||
if self.state == "follow" then return end
|
|
||||||
self.state = "follow"
|
|
||||||
self.target = target
|
|
||||||
self.alertness = self.alertness_states.follow
|
|
||||||
self.is_tamed = true
|
|
||||||
|
|
||||||
if self.custom.after_state_change then
|
|
||||||
self.custom.after_state_change( self )
|
|
||||||
end
|
|
||||||
|
|
||||||
self:start_follow_action( )
|
|
||||||
end,
|
|
||||||
|
|
||||||
set_attack_state = function ( self, target )
|
|
||||||
if self.state == "attack" then return end
|
|
||||||
self.state = "attack"
|
|
||||||
self.target = target
|
|
||||||
self.alertness = self.alertness_states.attack
|
|
||||||
self.is_tamed = false
|
|
||||||
|
|
||||||
if self.custom.after_state_change then
|
|
||||||
self.custom.after_state_change( self )
|
|
||||||
end
|
|
||||||
|
|
||||||
self:start_attack_action( )
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
-- action hooks --
|
-- action hooks --
|
||||||
|
|
||||||
start_ignore_action = function ( self )
|
start_ignore_action = function ( self )
|
||||||
|
self.target = nil -- forget any target
|
||||||
|
|
||||||
if not self.move_result.is_standing then
|
if not self.move_result.is_standing then
|
||||||
-- always stand when in mid-air
|
-- always stand when in mid-air
|
||||||
self:set_speed( 0 )
|
self:set_speed( 0 )
|
||||||
|
@ -637,7 +678,7 @@ mobs.register_mob = function ( name, def )
|
||||||
self:set_animation( "stand" )
|
self:set_animation( "stand" )
|
||||||
end
|
end
|
||||||
|
|
||||||
self.timekeeper.start( 1.0, "hunger", self.handle_hunger )
|
self.timekeeper.start( 1.0, "hunger", self.locate_target )
|
||||||
self.timekeeper.start( 1.0, "action", self.on_ignore_action )
|
self.timekeeper.start( 1.0, "action", self.on_ignore_action )
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
@ -727,6 +768,8 @@ mobs.register_mob = function ( name, def )
|
||||||
end,
|
end,
|
||||||
|
|
||||||
start_follow_action = function ( self )
|
start_follow_action = function ( self )
|
||||||
|
assert( self.target.pos ) -- sanity check
|
||||||
|
|
||||||
self:turn_to( self:get_target_yaw( rad_20 ), 10 )
|
self:turn_to( self:get_target_yaw( rad_20 ), 10 )
|
||||||
self:set_speed( self.recoil_velocity )
|
self:set_speed( self.recoil_velocity )
|
||||||
self:set_animation( "walk" )
|
self:set_animation( "walk" )
|
||||||
|
@ -736,13 +779,10 @@ mobs.register_mob = function ( name, def )
|
||||||
end,
|
end,
|
||||||
|
|
||||||
on_follow_action = function ( self, cycles, period, elapsed )
|
on_follow_action = function ( self, cycles, period, elapsed )
|
||||||
local target_pos = self:get_target_pos( 0.5 )
|
if cycles % 2 == 0 then -- validate target every 1.0 seconds
|
||||||
local dist = vector.distance( self.pos, target_pos )
|
local goal_state = self:verify_target( )
|
||||||
|
if goal_state ~= self.state then
|
||||||
if cycles % 2 == 0 then
|
self:reset_alertness( goal_state, self.target )
|
||||||
local next_state = validate_target( )
|
|
||||||
if next_state ~= self.state then
|
|
||||||
self:reset_state( next_state, self.target )
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -753,6 +793,9 @@ mobs.register_mob = function ( name, def )
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local target_pos = self.target.pos
|
||||||
|
local dist = vector.distance( self.pos, target_pos )
|
||||||
|
|
||||||
if dist <= self.follow_range then
|
if dist <= self.follow_range then
|
||||||
if self.speed > 0 then
|
if self.speed > 0 then
|
||||||
self:set_speed( 0 )
|
self:set_speed( 0 )
|
||||||
|
@ -786,10 +829,71 @@ mobs.register_mob = function ( name, def )
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
start_escape_action = function ( self )
|
start_search_action = function ( self )
|
||||||
local target_pos = vector.offset_y( self.target:get_pos( ) )
|
assert( self.target.pos ) -- sanity check
|
||||||
local dist = vector.distance( self.pos, self.target.pos )
|
|
||||||
|
|
||||||
|
self:turn_to( self:get_target_yaw( rad_5 ), 10 )
|
||||||
|
self:set_speed( self.walk_velocity )
|
||||||
|
self:set_animation( "walk" )
|
||||||
|
|
||||||
|
self.timekeeper.clear( "hunger" )
|
||||||
|
self.timekeeper.start( 0.5, "action", self.on_search_action )
|
||||||
|
end,
|
||||||
|
|
||||||
|
on_search_action = function ( self, cycles, period, elapsed )
|
||||||
|
if cycles % 2 == 0 then -- validate target every 1.0 seconds
|
||||||
|
local goal_state = self:verify_target( )
|
||||||
|
if goal_state ~= self.state then
|
||||||
|
self:reset_alertness( goal_state, self.target )
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local target_pos = self.target.pos
|
||||||
|
local dist = vector.distance( self.pos, target_pos )
|
||||||
|
|
||||||
|
-- go to last known position of target and look around
|
||||||
|
if dist <= self.search_range then
|
||||||
|
if self.speed > 0 then
|
||||||
|
self:set_speed( 0 )
|
||||||
|
self:set_animation( "stand" )
|
||||||
|
end
|
||||||
|
|
||||||
|
if random( 4 ) == 1 then
|
||||||
|
self:turn_to( self:get_random_yaw( rad_180 ), 20 )
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if self.speed == 0 then
|
||||||
|
self:set_speed( self.walk_velocity )
|
||||||
|
self:set_animation( "walk" )
|
||||||
|
|
||||||
|
else
|
||||||
|
if self.can_fly then
|
||||||
|
-- descend or ascend to slightly above player altitude, but prevent incessant bobbing
|
||||||
|
local v = 0
|
||||||
|
if target_pos.y + 2.5 > self.pos.y then
|
||||||
|
v = self.walk_velocity
|
||||||
|
elseif target_pos.y + 0.5 < self.pos.y then
|
||||||
|
v = -self.walk_velocity
|
||||||
|
end
|
||||||
|
self:set_velocity_vert( v )
|
||||||
|
|
||||||
|
elseif self.can_jump and self.move_result.is_standing then
|
||||||
|
if self.yield_level < 3 and self.move_result.collides_xz then
|
||||||
|
self.yield_level = self.yield_level + 1
|
||||||
|
self.object:set_velocity_vert( 5 )
|
||||||
|
elseif random( 2 ) == 1 then
|
||||||
|
self.object:set_velocity_vert( 5 )
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
start_escape_action = function ( self )
|
||||||
|
assert( self.target.pos ) -- sanity check
|
||||||
|
|
||||||
|
local dist = vector.distance( self.pos, self.target.pos )
|
||||||
if self:get_target_yaw_delta( ) < rad_60 and dist <= self.escape_range then
|
if self:get_target_yaw_delta( ) < rad_60 and dist <= self.escape_range then
|
||||||
-- recoil if facing intruder
|
-- recoil if facing intruder
|
||||||
self:set_speed( -self.recoil_velocity )
|
self:set_speed( -self.recoil_velocity )
|
||||||
|
@ -804,6 +908,11 @@ mobs.register_mob = function ( name, def )
|
||||||
self:set_animation( "walk" )
|
self:set_animation( "walk" )
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if self.target.obj and self.target.obj:is_player( ) then
|
||||||
|
-- this is a bad player, so keep watch
|
||||||
|
self.watch_players[ self.target.obj:get_player_name( ) ] = "escape"
|
||||||
|
end
|
||||||
|
|
||||||
if self.sounds and self.sounds.escape and random( 2 ) == 1 then
|
if self.sounds and self.sounds.escape and random( 2 ) == 1 then
|
||||||
minetest.sound_play( self.sounds.escape, { object = self.object } )
|
minetest.sound_play( self.sounds.escape, { object = self.object } )
|
||||||
end
|
end
|
||||||
|
@ -813,16 +922,17 @@ mobs.register_mob = function ( name, def )
|
||||||
end,
|
end,
|
||||||
|
|
||||||
on_escape_action = function ( self, cycles, period, elapsed )
|
on_escape_action = function ( self, cycles, period, elapsed )
|
||||||
local target_pos = self:get_target_pos( 0.5 )
|
|
||||||
local dist = vector.distance( self.pos, target_pos )
|
|
||||||
|
|
||||||
if cycles % 2 == 0 then
|
if cycles % 2 == 0 then
|
||||||
if not self:is_paranoid( elapsed ) or random( 10 ) > self.flee_factor then
|
local goal_state = self:verify_target( )
|
||||||
self:lower_awareness( )
|
if goal_state ~= self.state or random( 10 ) > self.flee_factor then
|
||||||
|
self:reset_alertness( goal_state, self.target )
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local target_pos = self.target.pos
|
||||||
|
local dist = vector.distance( self.pos, target_pos )
|
||||||
|
|
||||||
if dist <= self.escape_range then
|
if dist <= self.escape_range then
|
||||||
-- if close, keep backtracking
|
-- if close, keep backtracking
|
||||||
if cycles % 2 == 0 then
|
if cycles % 2 == 0 then
|
||||||
|
@ -867,6 +977,8 @@ mobs.register_mob = function ( name, def )
|
||||||
end,
|
end,
|
||||||
|
|
||||||
start_attack_action = function ( self )
|
start_attack_action = function ( self )
|
||||||
|
assert( self.target.pos and self.target.obj ) -- sanity check
|
||||||
|
|
||||||
self:set_speed( self.run_velocity )
|
self:set_speed( self.run_velocity )
|
||||||
self:set_animation( "run" )
|
self:set_animation( "run" )
|
||||||
self:turn_to( self:get_target_yaw( rad_90 ), 10 )
|
self:turn_to( self:get_target_yaw( rad_90 ), 10 )
|
||||||
|
@ -880,23 +992,24 @@ mobs.register_mob = function ( name, def )
|
||||||
end,
|
end,
|
||||||
|
|
||||||
on_attack_action = function ( self, cycles, period, elapsed )
|
on_attack_action = function ( self, cycles, period, elapsed )
|
||||||
if self.target:get_hp( ) == 0 or self.target:get_attach( ) then
|
if self.target.obj:get_hp( ) == 0 or self.target.obj:get_attach( ) then
|
||||||
self:set_ignore_state( )
|
self:reset_alertness( "ignore" )
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if cycles % 5 == 0 then -- validate target once per second
|
if cycles % 5 == 0 then -- validate target once per second
|
||||||
if not self:is_paranoid( elapsed ) then
|
local goal_state = self:verify_target( )
|
||||||
self:lower_awareness( )
|
if goal_state ~= self.state then
|
||||||
|
self:reset_alertness( goal_state, self.target )
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self:turn_to( self:get_target_yaw( rad_0 ), 5 )
|
local target_pos = self.target.pos
|
||||||
|
|
||||||
local target_pos = vector.offset_y( self.target.pos, 0.5 )
|
|
||||||
local dist = vector.distance( self.pos, target_pos )
|
local dist = vector.distance( self.pos, target_pos )
|
||||||
|
|
||||||
|
self:turn_to( self:get_target_yaw( rad_0 ), 5 )
|
||||||
|
|
||||||
if dist <= self.attack_range then
|
if dist <= self.attack_range then
|
||||||
if self.attack_type == "shoot" and self.fire_weapon then
|
if self.attack_type == "shoot" and self.fire_weapon then
|
||||||
if cycles % ( self.shoot_period * 5 ) == 0 and random( self.shoot_chance ) == 1 then
|
if cycles % ( self.shoot_period * 5 ) == 0 and random( self.shoot_chance ) == 1 then
|
||||||
|
@ -915,7 +1028,7 @@ mobs.register_mob = function ( name, def )
|
||||||
minetest.sound_play( self.sounds.attack, { object = self.object } )
|
minetest.sound_play( self.sounds.attack, { object = self.object } )
|
||||||
end
|
end
|
||||||
-- if minetest.line_of_sight( pos, target_pos, 0.5 ) then -- do not hit player through walls!
|
-- if minetest.line_of_sight( pos, target_pos, 0.5 ) then -- do not hit player through walls!
|
||||||
self.target:punch( self.object, 1.0, {
|
self.target.obj:punch( self.object, 1.0, {
|
||||||
full_punch_interval = 1.0,
|
full_punch_interval = 1.0,
|
||||||
damage_groups = { fleshy=self.damage }
|
damage_groups = { fleshy=self.damage }
|
||||||
}, vector.direction( self.pos, target_pos ) )
|
}, vector.direction( self.pos, target_pos ) )
|
||||||
|
@ -1027,7 +1140,7 @@ mobs.register_mob = function ( name, def )
|
||||||
self.cur_animation = type
|
self.cur_animation = type
|
||||||
end,
|
end,
|
||||||
|
|
||||||
-- damage and hunger routines --
|
-- damage handler --
|
||||||
|
|
||||||
handle_damage = function ( self )
|
handle_damage = function ( self )
|
||||||
-- handle environmental damage (light, water, and lava)
|
-- handle environmental damage (light, water, and lava)
|
||||||
|
@ -1066,24 +1179,6 @@ mobs.register_mob = function ( name, def )
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
locate_target = function ( self )
|
|
||||||
if self.sounds and self.sounds.random and random( 35 ) == 1 then
|
|
||||||
minetest.sound_play( self.sounds.random, { object = self.object } )
|
|
||||||
end
|
|
||||||
|
|
||||||
-- when not upset, seek out food or prey at random intervals
|
|
||||||
for obj in mobs.iterate_registry( self.pos, 30, 30, { players = true, spawnitems = true } ) do
|
|
||||||
|
|
||||||
if random( 10 ) <= self.fear_factor and obj:is_player( ) and not obj:get_attach( ) then
|
|
||||||
local state, target = self:create_target( obj )
|
|
||||||
if state ~= self.state then
|
|
||||||
self:reset_state( state, target )
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
-- generic callbacks --
|
-- generic callbacks --
|
||||||
|
|
||||||
on_step = function( self, dtime, pos, rot, new_vel, old_vel, move_result )
|
on_step = function( self, dtime, pos, rot, new_vel, old_vel, move_result )
|
||||||
|
@ -1153,19 +1248,13 @@ mobs.register_mob = function ( name, def )
|
||||||
self.move_result = { collides_xz = false, collides_y = true, is_standing = true }
|
self.move_result = { collides_xz = false, collides_y = true, is_standing = true }
|
||||||
self.pos = self.object:get_pos( )
|
self.pos = self.object:get_pos( )
|
||||||
|
|
||||||
if self.type == "monster" then
|
self.action_funcs = {
|
||||||
self.on_create_target = function ( obj, clarity )
|
ignore = self.start_ignore_action,
|
||||||
return clarity < 0.5 and "search" or "attack"
|
remark = self.start_remark_action,
|
||||||
end
|
search = self.start_search_action,
|
||||||
else
|
follow = self.start_follow_action,
|
||||||
self:set_escape_state( obj )
|
escape = self.start_escape_action,
|
||||||
return
|
attack = self.start_attack_action,
|
||||||
end
|
|
||||||
|
|
||||||
self.reset_funcs = {
|
|
||||||
ignore = self.set_ignore_state,
|
|
||||||
remark = self.set_remark_state,
|
|
||||||
search = self.set_search_state,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if staticdata then
|
if staticdata then
|
||||||
|
@ -1191,7 +1280,7 @@ mobs.register_mob = function ( name, def )
|
||||||
self.after_activate( self, id )
|
self.after_activate( self, id )
|
||||||
end
|
end
|
||||||
|
|
||||||
self:set_ignore_state( )
|
self:reset_alertness( self.neutral_state )
|
||||||
end,
|
end,
|
||||||
|
|
||||||
on_deactivate = function ( self, id )
|
on_deactivate = function ( self, id )
|
||||||
|
@ -1238,9 +1327,9 @@ mobs.register_mob = function ( name, def )
|
||||||
end
|
end
|
||||||
|
|
||||||
if hp - damage <= self.hp_low then
|
if hp - damage <= self.hp_low then
|
||||||
self:set_escape_state( hitter )
|
self:reset_alertness( "escape", { obj = hitter, pos = hitter:get_pos( ) } )
|
||||||
elseif self.type == "monster" then
|
elseif self.type == "monster" then
|
||||||
self:set_attack_state( hitter )
|
self:reset_alertness( "attack", { obj = hitter, pos = hitter:get_pos( ) } )
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
@ -1373,18 +1462,18 @@ end
|
||||||
|
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
mobs.play_sound = function ( obj, name )
|
mobs.play_sound = function ( pos, name )
|
||||||
minetest.sound_play( name, { loop = false }, true )
|
minetest.sound_play( name, { loop = false }, true )
|
||||||
end
|
end
|
||||||
|
|
||||||
mobs.make_noise = function ( pos, radius, group, intensity )
|
mobs.make_noise = function ( pos, radius, group, intensity )
|
||||||
axon.generate_radial_stimulus( pos, radius, 0.0, 0.0, 1, { [group] = intensity }, { avatars = true } )
|
axon.generate_radial_stimulus( pos, radius, 0.0, 0.0, { [group] = intensity }, { avatars = true } )
|
||||||
end
|
end
|
||||||
|
|
||||||
mobs.make_noise_repeat = function ( pos, radius, interval, duration, group, intensity )
|
mobs.make_noise_repeat = function ( pos, radius, interval, duration, group, intensity )
|
||||||
globaltimer.start_now( interval, "noise" .. next_noise_id, function ( this, cycles, period, elapsed )
|
globaltimer.start_now( interval, "noise" .. next_noise_id, function ( this, cycles, period, elapsed )
|
||||||
if elapsed >= duration then return true end -- we're finished, so cancel timer
|
if elapsed >= duration then return true end -- we're finished, so cancel timer
|
||||||
axon.generate_radial_stimulus( pos, radius, 0.0, 0.0, 1, { [group] = intensity }, { avatars = true } )
|
axon.generate_radial_stimulus( pos, radius, 0.0, 0.0, { [group] = intensity }, { avatars = true } )
|
||||||
end )
|
end )
|
||||||
next_noise_id = next_noise_id + 1
|
next_noise_id = next_noise_id + 1
|
||||||
end
|
end
|
||||||
|
@ -1412,11 +1501,11 @@ mobs.presets = {
|
||||||
local dist = vector.distance( self.pos, target_pos )
|
local dist = vector.distance( self.pos, target_pos )
|
||||||
|
|
||||||
if self:get_direct_yaw_delta( target_pos ) < rad_30 and dist <= self.pickup_range then
|
if self:get_direct_yaw_delta( target_pos ) < rad_30 and dist <= self.pickup_range then
|
||||||
if target:is_player( ) then
|
if target_obj:is_player( ) then
|
||||||
target:get_wielded_item( ):take_item( )
|
target_obj:get_wielded_item( ):take_item( )
|
||||||
target:set_wielded_item( "" )
|
target_obj:set_wielded_item( "" )
|
||||||
elseif target:get_entity_name( ) == "__builtin:item" then
|
elseif target_obj:get_entity_name( ) == "__builtin:item" then
|
||||||
target:remove( )
|
target_obj:remove( )
|
||||||
end
|
end
|
||||||
if can_eat then
|
if can_eat then
|
||||||
-- minetest.sound_play( "hbhunger_eat_generic", {
|
-- minetest.sound_play( "hbhunger_eat_generic", {
|
||||||
|
|
66
monsters.lua
66
monsters.lua
|
@ -35,9 +35,19 @@ mobs.register_mob( "mobs:ghost", {
|
||||||
hunger_params = { offset = -0.1, spread = 3.0 },
|
hunger_params = { offset = -0.1, spread = 3.0 },
|
||||||
alertness_states = {
|
alertness_states = {
|
||||||
ignore = { view_offset = 2, view_radius = 8, view_height = 8, view_acuity = 0 },
|
ignore = { view_offset = 2, view_radius = 8, view_height = 8, view_acuity = 0 },
|
||||||
attack = { view_offset = 2, view_radius = 14, view_height = 8, view_acuity = 3 },
|
search = { view_offset = 2, view_radius = 14, view_height = 8, view_acuity = 3, view_filter = function ( self, obj, clarity )
|
||||||
|
return clarity == 0.0 and "search" or "attack"
|
||||||
|
end },
|
||||||
|
attack = { view_offset = 2, view_radius = 14, view_height = 8, view_acuity = 3 , view_filter = function ( self, obj, clarity )
|
||||||
|
return clarity == 0.0 and "search" or "attack"
|
||||||
|
end },
|
||||||
escape = { view_offset = 2, view_radius = 14, view_height = 8, view_acuity = 3 },
|
escape = { view_offset = 2, view_radius = 14, view_height = 8, view_acuity = 3 },
|
||||||
},
|
},
|
||||||
|
awareness_stages = {
|
||||||
|
attack = { decay = 15.0, abort_state = "ignore" },
|
||||||
|
escape = { decay = 10.0, abort_state = "ignore" },
|
||||||
|
},
|
||||||
|
|
||||||
certainty = 1.0,
|
certainty = 1.0,
|
||||||
sensitivity = 0.2,
|
sensitivity = 0.2,
|
||||||
|
|
||||||
|
@ -46,8 +56,9 @@ mobs.register_mob( "mobs:ghost", {
|
||||||
attack_type = "melee",
|
attack_type = "melee",
|
||||||
standoff = 4.0,
|
standoff = 4.0,
|
||||||
attack_range = 6.0,
|
attack_range = 6.0,
|
||||||
|
search_range = 2.5,
|
||||||
escape_range = 2.5,
|
escape_range = 2.5,
|
||||||
sneak_velocity = 0.2,
|
sneak_velocity = 0.5,
|
||||||
walk_velocity = 0.5,
|
walk_velocity = 0.5,
|
||||||
recoil_velocity = 1.5,
|
recoil_velocity = 1.5,
|
||||||
run_velocity = 1.5,
|
run_velocity = 1.5,
|
||||||
|
@ -126,9 +137,20 @@ mobs.register_mob( "mobs:spider", {
|
||||||
hunger_params = { offset = 0.3, spread = 4.0 },
|
hunger_params = { offset = 0.3, spread = 4.0 },
|
||||||
alertness_states = {
|
alertness_states = {
|
||||||
ignore = { view_offset = 6, view_radius = 6, view_height = 6, view_acuity = 3 },
|
ignore = { view_offset = 6, view_radius = 6, view_height = 6, view_acuity = 3 },
|
||||||
attack = { view_offset = 6, view_radius = 12, view_height = 6, view_acuity = 5 },
|
search = { view_offset = 6, view_radius = 12, view_height = 6, view_acuity = 5, view_filter = function ( self, obj, clarity )
|
||||||
|
return clarity == 0.0 and "search" or "attack"
|
||||||
|
end },
|
||||||
|
attack = { view_offset = 6, view_radius = 12, view_height = 6, view_acuity = 5, view_filter = function ( self, obj, clarity )
|
||||||
|
return clarity == 0.0 and "search" or "attack"
|
||||||
|
end },
|
||||||
escape = { view_offset = 6, view_radius = 12, view_height = 6, view_acuity = 5 },
|
escape = { view_offset = 6, view_radius = 12, view_height = 6, view_acuity = 5 },
|
||||||
},
|
},
|
||||||
|
awareness_stages = {
|
||||||
|
search = { decay = 18.0, abort_state = "ignore" },
|
||||||
|
attack = { decay = 0.0, abort_state = "search" },
|
||||||
|
escape = { decay = 18.0, abort_state = "ignore" },
|
||||||
|
},
|
||||||
|
|
||||||
certainty = 1.0,
|
certainty = 1.0,
|
||||||
sensitivity = 0.2,
|
sensitivity = 0.2,
|
||||||
|
|
||||||
|
@ -136,7 +158,9 @@ mobs.register_mob( "mobs:spider", {
|
||||||
flee_factor = 8,
|
flee_factor = 8,
|
||||||
attack_type = "melee",
|
attack_type = "melee",
|
||||||
attack_range = 3.0,
|
attack_range = 3.0,
|
||||||
|
search_range = 3.0,
|
||||||
escape_range = 3.0,
|
escape_range = 3.0,
|
||||||
|
sneak_velocity = 0.5,
|
||||||
walk_velocity = 1.0,
|
walk_velocity = 1.0,
|
||||||
stray_velocity = 1.0,
|
stray_velocity = 1.0,
|
||||||
recoil_velocity = 0.5,
|
recoil_velocity = 0.5,
|
||||||
|
@ -174,10 +198,7 @@ mobs.register_mob( "mobs:spider", {
|
||||||
damage_hand = "mobs_damage_hand",
|
damage_hand = "mobs_damage_hand",
|
||||||
},
|
},
|
||||||
drops = {
|
drops = {
|
||||||
{ name = "farming:blueberries", chance = 6, min = 1, max = 2 },
|
|
||||||
{ name = "farming:raspberries", chance = 6, min = 1, max = 2 },
|
|
||||||
{ name = "default:grass_1", chance = 8, min = 1, max = 2 },
|
{ name = "default:grass_1", chance = 8, min = 1, max = 2 },
|
||||||
{ name = "default:shrub", chance = 8, min = 1, max = 2 },
|
|
||||||
},
|
},
|
||||||
on_rightclick = nil,
|
on_rightclick = nil,
|
||||||
} )
|
} )
|
||||||
|
@ -220,9 +241,20 @@ mobs.register_mob( "mobs:bat", {
|
||||||
hunger_params = { offset = 1.0, spread = 1.0 },
|
hunger_params = { offset = 1.0, spread = 1.0 },
|
||||||
alertness_states = {
|
alertness_states = {
|
||||||
ignore = { view_offset = 10, view_radius = 15, view_height = 15, view_acuity = 3 },
|
ignore = { view_offset = 10, view_radius = 15, view_height = 15, view_acuity = 3 },
|
||||||
attack = { view_offset = 10, view_radius = 20, view_height = 15, view_acuity = 5 },
|
search = { view_offset = 10, view_radius = 20, view_height = 15, view_acuity = 5, view_filter = function ( self, obj, clarity )
|
||||||
|
return clarity == 0.0 and "search" or "attack"
|
||||||
|
end },
|
||||||
|
attack = { view_offset = 10, view_radius = 20, view_height = 15, view_acuity = 5, view_filter = function ( self, obj, clarity )
|
||||||
|
return clarity == 0.0 and "search" or "attack"
|
||||||
|
end },
|
||||||
escape = { view_offset = 10, view_radius = 20, view_height = 15, view_acuity = 3 },
|
escape = { view_offset = 10, view_radius = 20, view_height = 15, view_acuity = 3 },
|
||||||
},
|
},
|
||||||
|
awareness_stages = {
|
||||||
|
search = { decay = 14.0, abort_state = "ignore" },
|
||||||
|
attack = { decay = 0.0, abort_state = "search" },
|
||||||
|
escape = { decay = 14.0, abort_state = "ignore" },
|
||||||
|
},
|
||||||
|
|
||||||
certainty = 1.0,
|
certainty = 1.0,
|
||||||
sensitivity = 0.3,
|
sensitivity = 0.3,
|
||||||
|
|
||||||
|
@ -230,6 +262,7 @@ mobs.register_mob( "mobs:bat", {
|
||||||
flee_factor = 6,
|
flee_factor = 6,
|
||||||
attack_type = "melee",
|
attack_type = "melee",
|
||||||
attack_range = 3.0,
|
attack_range = 3.0,
|
||||||
|
search_range = 5.0,
|
||||||
escape_range = 5.0,
|
escape_range = 5.0,
|
||||||
sneak_velocity = 1.0,
|
sneak_velocity = 1.0,
|
||||||
walk_velocity = 1.0,
|
walk_velocity = 1.0,
|
||||||
|
@ -271,7 +304,6 @@ mobs.register_mob( "mobs:bat", {
|
||||||
},
|
},
|
||||||
drops = {
|
drops = {
|
||||||
{ name = "default:apple", chance = 4, min = 1, max = 2 },
|
{ name = "default:apple", chance = 4, min = 1, max = 2 },
|
||||||
{ name = "default:orange", chance = 4, min = 1, max = 2 },
|
|
||||||
},
|
},
|
||||||
} )
|
} )
|
||||||
|
|
||||||
|
@ -313,9 +345,20 @@ mobs.register_mob( "mobs:griefer_ghost", {
|
||||||
hunger_params = { offset = 0.3, spread = 6.0 },
|
hunger_params = { offset = 0.3, spread = 6.0 },
|
||||||
alertness_states = {
|
alertness_states = {
|
||||||
ignore = { view_offset = 5, view_radius = 10, view_height = 8, view_acuity = 2 },
|
ignore = { view_offset = 5, view_radius = 10, view_height = 8, view_acuity = 2 },
|
||||||
attack = { view_offset = 5, view_radius = 20, view_height = 8, view_acuity = 2 },
|
search = { view_offset = 5, view_radius = 20, view_height = 8, view_acuity = 2, view_filter = function ( self, obj, clarity )
|
||||||
|
return clarity == 0.0 and "search" or "attack"
|
||||||
|
end },
|
||||||
|
attack = { view_offset = 5, view_radius = 20, view_height = 8, view_acuity = 2, view_filter = function ( self, obj, clarity )
|
||||||
|
return clarity == 0.0 and "search" or "attack"
|
||||||
|
end },
|
||||||
escape = { view_offset = 5, view_radius = 20, view_height = 8, view_acuity = 2 },
|
escape = { view_offset = 5, view_radius = 20, view_height = 8, view_acuity = 2 },
|
||||||
},
|
},
|
||||||
|
awareness_stages = {
|
||||||
|
search = { decay = 8.0, abort_state = "ignore" },
|
||||||
|
attack = { decay = 25.0, abort_state = "search" },
|
||||||
|
escape = { decay = 8.0, abort_state = "ignore" },
|
||||||
|
},
|
||||||
|
|
||||||
certainty = 1.0,
|
certainty = 1.0,
|
||||||
sensitivity = 0.6,
|
sensitivity = 0.6,
|
||||||
|
|
||||||
|
@ -323,7 +366,9 @@ mobs.register_mob( "mobs:griefer_ghost", {
|
||||||
flee_factor = 10,
|
flee_factor = 10,
|
||||||
attack_type = "melee",
|
attack_type = "melee",
|
||||||
attack_range = 3.0,
|
attack_range = 3.0,
|
||||||
|
search_range = 4.0,
|
||||||
escape_range = 4.0,
|
escape_range = 4.0,
|
||||||
|
sneak_velocity = 0.5,
|
||||||
walk_velocity = 1.0,
|
walk_velocity = 1.0,
|
||||||
stray_velocity = 1.0,
|
stray_velocity = 1.0,
|
||||||
recoil_velocity = 0.5,
|
recoil_velocity = 0.5,
|
||||||
|
@ -360,9 +405,6 @@ mobs.register_mob( "mobs:griefer_ghost", {
|
||||||
},
|
},
|
||||||
drops = {
|
drops = {
|
||||||
{ name = "default:papyrus", chance = 6, min = 1, max = 2 },
|
{ name = "default:papyrus", chance = 6, min = 1, max = 2 },
|
||||||
{ name = "default:cactus", chance = 6, min = 1, max = 2 },
|
|
||||||
{ name = "farming:pumpkin_slice", chance = 8, min = 1, max = 2 },
|
|
||||||
{ name = "farming:melon_slice", chance = 8, min = 1, max = 2 },
|
|
||||||
},
|
},
|
||||||
} )
|
} )
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue