1
-------------------------------------------------------------------------------
2
-- Mob Framework Mod by Sapier
4
-- You may copy, use, modify or do nearly anything except removing this
6
-- And of course you are NOT allow to pretend you have written it.
8
--! @file main_follow.lua
9
--! @brief component containing a targeted movement generator
14
--! @defgroup mgen_follow MGEN: follow movement generator
15
--! @brief A movement generator creating movement that trys to follow a moving
16
--! target or reach a given point on map
17
--! @ingroup framework_int
19
-- Contact sapier a t gmx net
20
-------------------------------------------------------------------------------
22
--! @class mgen_follow
23
--! @brief a movement generator trying to follow or reach a target
29
--! @brief movement generator identifier
30
--! @memberof mgen_follow
31
mgen_follow.name = "follow_mov_gen"
33
-------------------------------------------------------------------------------
34
-- name: identify_movement_state(ownpos,targetpos)
36
--! @brief check what situation we are
37
--! @memberof mgen_follow
40
--! @param ownpos position of entity
41
--! @param targetpos position of target
43
--! @return "below_los"
46
--! "same_height_no_los"
50
-------------------------------------------------------------------------------
51
function mgen_follow.identify_movement_state(ownpos,targetpos)
52
mobf_assert_backtrace(ownpos ~= nil)
53
mobf_assert_backtrace(targetpos ~= nil)
55
local same_height_delta = 0.1
57
local los = mobf_line_of_sight(ownpos,targetpos)
59
if ownpos.y > targetpos.y - same_height_delta and
60
ownpos.y < targetpos.y + same_height_delta then
63
return "same_height_los"
65
return "same_height_no_los"
69
if ownpos.y < targetpos.y then
77
if ownpos.y > targetpos.y then
88
-------------------------------------------------------------------------------
89
-- name: handleteleport(entity,now)
91
--! @brief handle teleportsupport
92
--! @memberof mgen_follow
95
--! @param entity mob to check for teleport
96
--! @param now current time
97
--! @param targetpos position of target
99
--! @return true/false finish processing
100
-------------------------------------------------------------------------------
101
function mgen_follow.handleteleport(entity,now,targetpos)
103
if (entity.dynamic_data.movement.last_next_to_target ~= nil ) then
104
local time_since_next_to_target =
105
now - entity.dynamic_data.movement.last_next_to_target
107
dbg_mobf.fmovement_lvl3("MOBF: time since next to target: " .. time_since_next_to_target ..
108
" delay: " .. dump(entity.data.movement.teleportdelay) ..
109
" teleportsupport: " .. dump(entity.dynamic_data.movement.teleportsupport))
111
if (entity.dynamic_data.movement.teleportsupport) and
112
time_since_next_to_target > entity.data.movement.teleportdelay then
114
--check targetpos try to playe above if not valid
116
local current_offset = 0
117
while (not environment.possible_pos(entity,{
119
y=targetpos.y + current_offset,
122
current_offset < maxoffset do
123
dbg_mobf.fmovement_lvl2(
124
"MOBF: teleport target within block trying above: " .. current_offset)
125
current_offset = current_offset +1
128
targetpos.y = targetpos.y + current_offset
130
--adjust to collisionbox of mob
131
if entity.collisionbox[2] < -0.5 then
132
targetpos.y = targetpos.y - (entity.collisionbox[2] + 0.49)
135
entity.object:setvelocity({x=0,y=0,z=0})
136
entity.object:setacceleration({x=0,y=0,z=0})
137
entity.object:moveto(targetpos)
138
entity.dynamic_data.movement.last_next_to_target = now
145
-------------------------------------------------------------------------------
146
-- name: callback(entity,now)
148
--! @brief main callback to make a mob follow its target
149
--! @memberof mgen_follow
151
--! @param entity mob to generate movement for
152
--! @param now current time
153
-------------------------------------------------------------------------------
154
function mgen_follow.callback(entity,now)
156
dbg_mobf.fmovement_lvl3("MOBF: Follow mgen callback called")
158
if entity == nil then
159
mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!!: called movement gen without entity!")
163
if entity.dynamic_data == nil or
164
entity.dynamic_data.movement == nil then
165
mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!!: >" ..entity.data.name .. "< removed=" .. dump(entity.removed) .. " entity=" .. tostring(entity) .. " probab movement callback")
169
local follow_speedup = {x=10,y=2,z=10 }
171
if entity.data.movement.follow_speedup ~= nil then
172
if type(entity.data.movement.follow_speedup) == "table" then
173
follow_speedup = entity.data.movement.follow_speedup
175
follow_speedup.x= entity.data.movement.follow_speedup
176
follow_speedup.z= entity.data.movement.follow_speedup
180
--if speedup is disabled reset
181
if not entity.dynamic_data.movement.follow_speedup then
182
follow_speedup = { x=1, y=1, z=1}
185
--check max speed limit
186
mgen_follow.checkspeed(entity)
190
local basepos = entity.getbasepos(entity)
191
local pos_quality = environment.pos_quality(basepos,entity)
193
if environment.evaluate_state(pos_quality, LT_GOOD_POS) or
194
(entity.data.movement.canfly and
195
environment.evaluate_state(pos_quality,LT_GOOD_FLY_POS)) then
198
y= basepos.y - 0.5 - entity.collisionbox[2],
200
--save known good position
201
entity.dynamic_data.movement.last_pos_in_env = toset
204
if pos_quality.media_quality == MQ_IN_AIR or -- wrong media
205
pos_quality.media_quality == MQ_IN_WATER or -- wrong media
206
pos_quality.geometry_quality == GQ_NONE or -- no ground contact (TODO this was drop above water before)
207
pos_quality.surface_quality_min == SQ_WATER then -- above water
210
if entity.dynamic_data.movement.invalid_env_count == nil then
211
entity.dynamic_data.movement.invalid_env_count = 0
214
entity.dynamic_data.movement.invalid_env_count =
215
entity.dynamic_data.movement.invalid_env_count + 1
218
--don't change at first invalid pos but give some steps to cleanup by
219
--other less invasive mechanisms
220
if entity.dynamic_data.movement.invalid_env_count > 10 then
221
dbg_mobf.fmovement_lvl1("MOBF: followed to wrong place " .. pos_quality.tostring(pos_quality))
222
if entity.dynamic_data.movement.last_pos_in_env ~= nil then
223
entity.object:moveto(entity.dynamic_data.movement.last_pos_in_env)
224
basepos = entity.getbasepos(entity)
226
local newpos = environment.get_suitable_pos_same_level(basepos,1,entity,true)
228
if newpos == nil then
229
newpos = environment.get_suitable_pos_same_level( {
236
if newpos == nil then
237
newpos = environment.get_suitable_pos_same_level( {
244
if newpos == nil then
245
dbg_mobf.fmovement_lvl1("MOBF: no way to fix it removing mob")
246
spawning.remove(entity,"mgen_follow poscheck")
248
newpos.y = newpos.y - (entity.collisionbox[2] + 0.49)
249
entity.object:moveto(newpos)
250
basepos = entity.getbasepos(entity)
255
entity.dynamic_data.movement.invalid_env_count = 0
258
local current_accel = entity.object:getacceleration()
260
if pos_quality.level_quality ~= LQ_OK and
261
entity.data.movement.canfly then
264
if pos_quality.level_quality == LQ_ABOVE then
265
if current_accel.y >= 0 then
266
current_accel.y = - entity.data.movement.max_accel
270
if pos_quality.level_quality == LQ_BELOW then
271
local current_accel = entity.object:getacceleration()
272
if current_accel.y <= 0 then
273
current_accel.y = entity.data.movement.max_accel
277
entity.object:setacceleration(current_accel)
282
if entity.data.movement.canfly then
283
if current_accel.y ~= 0 then
285
entity.object:setacceleration(current_accel)
289
if entity.dynamic_data.movement.target ~= nil or
290
entity.dynamic_data.movement.guardspawnpoint then
292
dbg_mobf.fmovement_lvl3("MOBF: Target available")
293
--calculate distance to target
294
local targetpos = nil
296
if entity.dynamic_data.movement.target ~= nil then
297
dbg_mobf.fmovement_lvl3("MOBF: have moving target")
299
if not mobf_is_pos(entity.dynamic_data.movement.target) then
300
targetpos = entity.dynamic_data.movement.target:getpos()
302
targetpos = entity.dynamic_data.movement.target
306
if targetpos == nil and
307
entity.dynamic_data.movement.guardspawnpoint == true then
308
dbg_mobf.fmovement_lvl3("MOBF: non target selected")
309
targetpos = entity.dynamic_data.spawning.spawnpoint
312
if targetpos == nil then
313
mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: " .. entity.data.name
314
.. " don't have targetpos "
315
.. "SP: " .. dump(entity.dynamic_data.spawning.spawnpoint)
316
.. " TGT: " .. dump(entity.dynamic_data.movement.target))
321
local height_distance = nil
323
if entity.data.movement.canfly then
324
--real pos is relevant not basepos for flying mobs
325
--target for flying mobs is always slightly above it's target
326
distance = mobf_calc_distance(entity.object:getpos(),
327
{x=targetpos.x, y=(targetpos.y+1), z=targetpos.z })
328
height_distance = entity.object:getpos().y - (targetpos.y+1)
330
distance = mobf_calc_distance_2d(basepos,targetpos)
333
if mobf_line_of_sight({x=basepos.x,y=basepos.y+1,z=basepos.z},
334
{x=targetpos.x,y=targetpos.y+1,z=targetpos.z}) == false then
335
dbg_mobf.fmovement_lvl3("MOBF: no line of sight")
336
--TODO teleport support?
337
--TODO other ways to handle this?
340
--dbg_mobf.fmovement_lvl3("MOBF: line of sight")
342
local max_distance = entity.dynamic_data.movement.max_distance
344
if max_distance == nil then
348
--check if mob needs to move towards target
349
dbg_mobf.fmovement_lvl3("MOBF: max distance is set to : "
350
.. max_distance .. " dist: " .. distance)
351
if distance > max_distance then
352
entity.dynamic_data.movement.was_moving_last_step = true
354
if mgen_follow.handleteleport(entity,now,targetpos) then
358
dbg_mobf.fmovement_lvl3("MOBF: distance:" .. distance)
360
local current_state =
361
mgen_follow.identify_movement_state(basepos,targetpos)
362
local handled = false
364
if handled == false and
365
(current_state == "same_height_los" or
366
current_state == "above_los" or
367
current_state == "above_no_los" ) then
368
dbg_mobf.fmovement_lvl3("MOBF: \t Case 1: " .. current_state)
370
movement_generic.get_accel_to(targetpos,entity,true)
373
mgen_follow.set_acceleration(entity,
379
if handled == false and
380
(current_state == "below_los" or
381
current_state == "below_no_los" or
382
current_state == "same_height_no_los" ) then
383
dbg_mobf.fmovement_lvl3("MOBF: \t Case 2: " .. current_state)
385
movement_generic.get_accel_to(targetpos,entity)
387
--seems to be a flying mob
388
if (accel_to_set.y >0) then
390
mgen_follow.set_acceleration(entity,
395
local current_velocity = entity.object:getvelocity()
396
local predicted_pos =
397
movement_generic.predict_next_block(basepos,
401
--TODO replace by quality based mechanism!!!!!!!------------
403
environment.pos_is_ok(predicted_pos,entity)
404
if pos_state == "collision_jumpable" then
405
local pos_to_set = entity.object:getpos()
406
pos_to_set.y = pos_to_set.y + 1.1
407
entity.object:moveto(pos_to_set)
408
basepos.y=basepos.y+1.1
410
------------------------------------------------------------
412
dbg_mobf.fmovement_lvl3("MOBF: setting acceleration to: "
413
.. printpos(accel_to_set) .. " predicted_state: "
416
mgen_follow.set_acceleration(entity,
423
if handled == false then
424
dbg_mobf.fmovement_lvl1(
425
"MOBF: \t Unexpected or unhandled movement state: "
427
local yaccel = environment.get_default_gravity(basepos,
428
entity.environment.media,
429
entity.data.movement.canfly)
430
--entity.object:setvelocity({x=0,y=0,z=0})
431
entity.object:setacceleration({x=0,y=yaccel,z=0})
433
mgen_follow.update_animation(entity, "following")
435
elseif height_distance ~= nil and math.abs(height_distance) > 0.1 then
436
mgen_follow.set_acceleration(entity,
437
{ x=0,y=(height_distance*-0.2),z=0},
440
mgen_follow.update_animation(entity, "following")
441
--we're next to target stop movement
443
local yaccel = environment.get_default_gravity(basepos,
444
entity.environment.media,
445
entity.data.movement.canfly)
447
if entity.dynamic_data.movement.was_moving_last_step == true or
448
current_accel.Y ~= yaccel then
450
dbg_mobf.fmovement_lvl3("MOBF: next to target")
451
entity.object:setvelocity({x=0,y=0,z=0})
452
entity.object:setacceleration({x=0,y=yaccel,z=0})
453
entity.dynamic_data.movement.last_next_to_target = now
454
mgen_follow.update_animation(entity, "ntt")
459
--TODO evaluate if this is an error case
463
-------------------------------------------------------------------------------
464
-- name: update_animation()
466
--! @brief update animation according to the follow movegen substate
467
--! @memberof mgen_follow
469
-------------------------------------------------------------------------------
470
function mgen_follow.update_animation(entity, anim_state)
473
if anim_state == entity.dynamic_data.movement.anim_selected then
477
-- check if there's a animation specified for stand in this state
478
local statename, state = entity:get_state()
480
if anim_state == "following" then
481
entity.dynamic_data.movement.anim_selected = "following"
483
if state.animation_walk ~= nil then
484
graphics.set_animation(entity, state.animation_walk)
485
elseif state.animation ~= nil then
486
graphics.set_animation(entity, state.animation)
488
elseif anim_state == "ntt" then
489
entity.dynamic_data.movement.anim_selected = "ntt"
491
if state.animation_next_to_target ~= nil then
492
graphics.set_animation(entity, state.animation_next_to_target)
497
-------------------------------------------------------------------------------
498
-- name: next_block_ok()
500
--! @brief check quality of next block
501
--! @memberof mgen_follow
503
-------------------------------------------------------------------------------
504
function mgen_follow.next_block_ok(entity,pos,acceleration,velocity)
505
local current_velocity = velocity
507
if current_velocity == nil then
508
current_velocity = entity.object:getvelocity()
511
local predicted_pos = movement_generic.predict_next_block(pos,current_velocity,acceleration)
513
local quality = environment.pos_quality(predicted_pos,entity)
516
(quality.media_quality == MQ_IN_MEDIA) and
517
(quality.level_quality == LQ_OK) and
519
(quality.surface_quality_min == Q_UNKNOWN) or
520
(quality.surface_quality_min >= SQ_WRONG)
525
-------------------------------------------------------------------------------
526
-- name: initialize()
528
--! @brief initialize movement generator
529
--! @memberof mgen_follow
531
-------------------------------------------------------------------------------
532
function mgen_follow.initialize(entity,now)
533
--intentionally empty
536
-------------------------------------------------------------------------------
537
-- name: init_dynamic_data(entity,now)
539
--! @brief initialize dynamic data required by movement generator
540
--! @memberof mgen_follow
543
--! @param entity mob to initialize dynamic data
544
--! @param now current time
545
-------------------------------------------------------------------------------
546
function mgen_follow.init_dynamic_data(entity,now)
548
local pos = entity.object:getpos()
553
guardspawnpoint = false,
554
max_distance = entity.data.movement.max_distance,
555
invalid_env_count = 0,
556
follow_speedup = true,
559
if entity.data.movement.guardspawnpoint ~= nil and
560
entity.data.movement.guardspawnpoint then
561
dbg_mobf.fmovement_lvl3("MOBF: setting guard point to: " .. printpos(entity.dynamic_data.spawning.spawnpoint))
562
data.guardspawnpoint = true
565
if entity.data.movement.teleportdelay~= nil then
566
data.last_next_to_target = now
567
data.teleportsupport = true
570
entity.dynamic_data.movement = data
573
-------------------------------------------------------------------------------
574
-- name: checkspeed(entity)
576
--! @brief check if mobs speed is within it's limits and correct if necessary
577
--! @memberof mgen_follow
580
--! @param entity mob to initialize dynamic data
581
-------------------------------------------------------------------------------
582
function mgen_follow.checkspeed(entity)
584
local current_velocity = entity.object:getvelocity()
587
mobf_calc_scalar_speed(current_velocity.x,current_velocity.z)
589
if (xzspeed > entity.data.movement.max_speed) then
591
local direction = mobf_calc_yaw(current_velocity.x,
594
--reduce speed to 90% of current speed
595
local new_speed = mobf_calc_vector_components(direction,xzspeed*0.9)
597
local current_accel = entity.object:getacceleration()
599
new_speed.y = current_velocity.y
600
entity.object:setvelocity(new_speed)
601
entity.object:setacceleration({x=0,y=current_accel.y,z=0})
609
-------------------------------------------------------------------------------
610
-- name: set_acceleration(entity,accel,speedup)
612
--! @brief apply acceleration to entity
613
--! @memberof mgen_follow
616
--! @param entity mob to apply to
617
--! @param accel acceleration to set
618
--! @param speedup speedup factor
619
--! @param pos current position
620
-------------------------------------------------------------------------------
621
function mgen_follow.set_acceleration(entity,accel,speedup,pos)
623
accel.x = accel.x*speedup.x
624
accel.z = accel.z*speedup.z
626
if entity.data.movement.canfly then
627
accel.y = accel.y*speedup.y
630
if mgen_follow.next_block_ok(entity,pos,accel) then
631
dbg_mobf.fmovement_lvl3("MOBF: setting acceleration to: " .. printpos(accel));
632
entity.object:setacceleration(accel)
634
elseif mgen_follow.next_block_ok(entity,pos,{x=0,y=0,z=0}) then
635
accel = {x=0,y=0,z=0}
636
dbg_mobf.fmovement_lvl3("MOBF: setting acceleration to: " .. printpos(accel));
637
entity.object:setacceleration(accel)
640
local current_velocity = entity.object:getvelocity()
641
current_velocity.y = 0
643
if mgen_follow.next_block_ok(entity,pos,{x=0,y=0,z=0},current_velocity) then
644
accel = {x=0,y=0,z=0}
645
entity.object:setvelocity(current_velocity)
646
entity.object:setacceleration(accel)
651
dbg_mobf.fmovement_lvl1(
652
"MOBF: \t acceleration " .. printpos(accel) ..
653
" would result in invalid position not applying!")
658
-------------------------------------------------------------------------------
659
-- name: set_target(entity, target, follow_speedup, max_distance)
661
--! @brief set target for movgen
662
--! @memberof mgen_follow
664
--! @param entity mob to apply to
665
--! @param target to set
666
--! @param follow_speedup --unused here
667
--! @param max_distance maximum distance to target to be tried to reach
668
-------------------------------------------------------------------------------
669
function mgen_follow.set_target(entity,target, follow_speedup, max_distance)
670
entity.dynamic_data.movement.target = target
671
entity.dynamic_data.movement.max_distance = max_distance
675
--register this movement generator
676
registerMovementGen(mgen_follow.name,mgen_follow)
b'\\ No newline at end of file'