481 lines
16 KiB
Lua
481 lines
16 KiB
Lua
-------------------------------------------------------------------------------
|
|
-- Mob Framework Mod by Sapier
|
|
--
|
|
-- You may copy, use, modify or do nearly anything except removing this
|
|
-- copyright notice.
|
|
-- And of course you are NOT allow to pretend you have written it.
|
|
--
|
|
--! @file 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,entity.object:getyaw(),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,entity.object:getyaw(),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 = 0
|
|
|
|
--randomize direction
|
|
for i=0, 100 do
|
|
|
|
if math.random() < 0.2 then
|
|
break
|
|
end
|
|
orientation_delta = orientation_delta +0.1
|
|
end
|
|
|
|
--calculate new acceleration
|
|
|
|
|
|
local new_direction = current_yaw + ((minrotation + orientation_delta) * direction)
|
|
|
|
local new_accel = {
|
|
x = math.sin(new_direction) *rand_accel,
|
|
y = nil,
|
|
z = math.cos(new_direction) *rand_accel
|
|
}
|
|
|
|
dbg_mobf.pmovement_lvl3(" new direction: " .. new_direction .. " old direction: " .. current_yaw .. " new accel: " .. printpos(new_accel))
|
|
|
|
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
|
|
|
|
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: redirecting to safe position .. " .. printpos(new_pos))
|
|
movement_state.accel_to_set = movement_generic.get_accel_to(new_pos,entity)
|
|
movement_state.changed = true
|
|
return
|
|
else
|
|
--no suitable pos found, if mob is safe atm just stop it
|
|
if mob_is_safe then
|
|
entity.object:setvelocity({x=0,y=0,z=0})
|
|
movement_state.accel_to_set = {x=0,y=nil,z=0}
|
|
movement_state.changed = true
|
|
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
|
|
|
|
mobf_print("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_lvl2("MOBF: random movement handler called")
|
|
if movement_state.changed == false and
|
|
(math.random() < (entity.dynamic_data.movement.mpattern.random_acceleration_change * PER_SECOND_CORRECTION_FACTOR) 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))
|
|
end
|
|
end |