586 lines
12 KiB
Lua
586 lines
12 KiB
Lua
-- Mobs Api (16th January 2016)
|
|
|
|
function mob_goTo(self, pos)
|
|
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- register mob function
|
|
function mobs:register_simple_mob(name, def)
|
|
print("bad bad")
|
|
--[[
|
|
local btdata = {
|
|
waypoints= {},
|
|
counters={},
|
|
inv=minetest.create_detached_inventory("main", {}),
|
|
|
|
history={},
|
|
history_queue={},
|
|
history_depth=20,
|
|
}
|
|
|
|
btdata.inv:set_size("main", 9)]]
|
|
|
|
minetest.register_entity(name, {
|
|
|
|
stepheight = def.stepheight or 0.6,
|
|
name = name,
|
|
type = def.type,
|
|
attack_type = def.attack_type,
|
|
fly = def.fly,
|
|
fly_in = def.fly_in or "air",
|
|
owner = def.owner or "",
|
|
order = def.order or "",
|
|
on_die = def.on_die,
|
|
do_custom = def.do_custom,
|
|
jump_height = def.jump_height or 6,
|
|
jump_chance = def.jump_chance or 0,
|
|
drawtype = def.drawtype, -- DEPRECATED, use rotate instead
|
|
rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2
|
|
--lifetimer = def.lifetimer or 180, -- 3 minutes
|
|
hp_min = def.hp_min or 5,
|
|
hp_max = def.hp_max or 10,
|
|
physical = true,
|
|
collisionbox = def.collisionbox,
|
|
visual = def.visual,
|
|
visual_size = def.visual_size or {x = 1, y = 1},
|
|
mesh = def.mesh,
|
|
makes_footstep_sound = def.makes_footstep_sound or false,
|
|
view_range = def.view_range or 5,
|
|
pace_velocity = {
|
|
walk = def.walk_velocity or 1,
|
|
run = def.run_velocity or 2,
|
|
},
|
|
damage = def.damage or 0,
|
|
light_damage = def.light_damage or 0,
|
|
water_damage = def.water_damage or 0,
|
|
lava_damage = def.lava_damage or 0,
|
|
fall_damage = def.fall_damage or 1,
|
|
fall_speed = def.fall_speed or -10, -- must be lower than -2 (default: -10)
|
|
drops = def.drops or {},
|
|
armor = def.armor,
|
|
on_rightclick = def.on_rightclick,
|
|
arrow = def.arrow,
|
|
shoot_interval = def.shoot_interval,
|
|
sounds = def.sounds or {},
|
|
animation = def.animation,
|
|
follow = def.follow,
|
|
jump = def.jump or true,
|
|
walk_chance = def.walk_chance or 50,
|
|
attacks_monsters = def.attacks_monsters or false,
|
|
group_attack = def.group_attack or false,
|
|
--fov = def.fov or 120,
|
|
passive = def.passive or false,
|
|
recovery_time = def.recovery_time or 0.5,
|
|
knock_back = def.knock_back or 3,
|
|
blood_amount = def.blood_amount or 5,
|
|
blood_texture = def.blood_texture or "mobs_blood.png",
|
|
shoot_offset = def.shoot_offset or 0,
|
|
floats = def.floats or 1, -- floats in water by default
|
|
replace_rate = def.replace_rate,
|
|
replace_what = def.replace_what,
|
|
replace_with = def.replace_with,
|
|
replace_offset = def.replace_offset or 0,
|
|
timer = 0,
|
|
env_damage_timer = 0, -- only used when state = "attack"
|
|
tamed = false,
|
|
pause_timer = 0,
|
|
horny = false,
|
|
hornytimer = 0,
|
|
child = false,
|
|
gotten = false,
|
|
health = 0,
|
|
reach = def.reach or 3,
|
|
htimer = 0,
|
|
child_texture = def.child_texture,
|
|
docile_by_day = def.docile_by_day or false,
|
|
time_of_day = 0.5,
|
|
fear_height = def.fear_height or 0,
|
|
runaway = def.runaway,
|
|
runaway_timer = 0,
|
|
destination = nil,
|
|
pace = "walk",
|
|
|
|
-- needed for mobf meshes (wolves, deer)
|
|
automatic_face_movement_dir = true,
|
|
|
|
bt_timer = 0,
|
|
old_y = 0, -- some sort of weird bug
|
|
|
|
goTo = mob_goTo,
|
|
|
|
bt = nil,
|
|
btData = nil,
|
|
|
|
on_step = function(self, dtime)
|
|
local btdata = self.btData
|
|
|
|
local pos = self.object:getpos()
|
|
local yaw = self.object:getyaw() or 0
|
|
|
|
btdata.pos = pos
|
|
btdata.yaw = yaw
|
|
btdata.mob = self
|
|
--btdata.inv = self.object:get_inventory()
|
|
--btdata.inv:set_size("main", 9)
|
|
--btdata.inv:add_item("main", "default:tree 50")
|
|
|
|
self.bt_timer = self.bt_timer + dtime
|
|
--print("bt_timer "..self.bt_timer)
|
|
|
|
if self.bt_timer > 2 then
|
|
|
|
-- debug -- print("\n<<< start >>>")
|
|
|
|
-- inventories cannot be serialized and cause the game to crash if
|
|
-- placed in the entity's table
|
|
local inv = minetest.get_inventory({type="detached", name=self.inv_id})
|
|
btdata.inv = inv
|
|
|
|
bt.tick(self.bt, btdata)
|
|
-- debug -- print("<<< end >>>\n")
|
|
|
|
-- so clear it out after running the behavior trees
|
|
btdata.inv = nil
|
|
-- the inventory exists on its own
|
|
|
|
self.bt_timer = 0
|
|
end
|
|
|
|
--self.object:set_inventory(btdata.inv)
|
|
btdata.lastpos = pos
|
|
|
|
|
|
if not self.fly then
|
|
|
|
-- floating in water (or falling)
|
|
local v = self.object:getvelocity()
|
|
|
|
-- going up then apply gravity
|
|
if v.y > 0.1 then
|
|
|
|
self.object:setacceleration({
|
|
x = 0,
|
|
y = self.fall_speed,
|
|
z = 0
|
|
})
|
|
end
|
|
|
|
-- in water then float up
|
|
if minetest.registered_nodes[node_ok(pos).name].groups.water then
|
|
|
|
if self.floats == 1 then
|
|
|
|
self.object:setacceleration({
|
|
x = 0,
|
|
y = -self.fall_speed / (math.max(1, v.y) ^ 2),
|
|
z = 0
|
|
})
|
|
end
|
|
else
|
|
-- fall downwards
|
|
self.object:setacceleration({
|
|
x = 0,
|
|
y = self.fall_speed,
|
|
z = 0
|
|
})
|
|
|
|
-- fall damage
|
|
if self.fall_damage == 1
|
|
and self.object:getvelocity().y == 0 then
|
|
|
|
local d = self.old_y - self.object:getpos().y
|
|
|
|
if d > 5 then
|
|
|
|
self.object:set_hp(self.object:get_hp() - math.floor(d - 5))
|
|
|
|
effect(pos, 5, "tnt_smoke.png")
|
|
print("death by falling")
|
|
if check_for_death(self, pos) then
|
|
return
|
|
end
|
|
end
|
|
|
|
self.old_y = self.object:getpos().y
|
|
end
|
|
end
|
|
end
|
|
|
|
-- -- knockback timer
|
|
-- if self.pause_timer > 0 then
|
|
--
|
|
-- self.pause_timer = self.pause_timer - dtime
|
|
--
|
|
-- if self.pause_timer < 1 then
|
|
-- self.pause_timer = 0
|
|
-- end
|
|
--
|
|
-- return
|
|
-- end
|
|
|
|
-- attack timer
|
|
-- self.timer = self.timer + dtime
|
|
|
|
-- if self.state ~= "attack" then
|
|
--
|
|
-- if self.timer < 1 then
|
|
-- return
|
|
-- end
|
|
--
|
|
-- self.timer = 0
|
|
-- end
|
|
|
|
-- never go over 100
|
|
if self.timer > 100 then
|
|
self.timer = 1
|
|
end
|
|
|
|
-- node replace check (cow eats grass etc.)
|
|
-- handled by btree -- replace(self, pos)
|
|
|
|
-- mob plays random sound at times
|
|
if self.sounds.random
|
|
and math.random(1, 100) == 1 then
|
|
|
|
minetest.sound_play(self.sounds.random, {
|
|
object = self.object,
|
|
max_hear_distance = self.sounds.distance
|
|
})
|
|
end
|
|
|
|
-- environmental damage timer (every 1 second)
|
|
self.env_damage_timer = self.env_damage_timer + dtime
|
|
|
|
if (self.state == "attack" and self.env_damage_timer > 1)
|
|
or self.state ~= "attack" then
|
|
|
|
self.env_damage_timer = 0
|
|
|
|
do_env_damage(self)
|
|
|
|
-- custom function (defined in mob lua file)
|
|
if self.do_custom then
|
|
self.do_custom(self)
|
|
end
|
|
end
|
|
|
|
|
|
if self.destination ~= nil then
|
|
|
|
--print("destination ")
|
|
|
|
local dist = distance(pos, self.destination)
|
|
-- print("walk dist ".. dist)
|
|
local s = self.destination
|
|
local vec = {
|
|
x = pos.x - s.x,
|
|
y = pos.y - s.y,
|
|
z = pos.z - s.z
|
|
}
|
|
|
|
-- tprint(vec)
|
|
|
|
if vec.x ~= 0
|
|
or vec.z ~= 0 then
|
|
|
|
yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate
|
|
|
|
if s.x > pos.x then
|
|
yaw = yaw + math.pi
|
|
end
|
|
|
|
self.object:setyaw(yaw)
|
|
end
|
|
|
|
-- anyone but standing npc's can move along
|
|
if dist > (self.approachDistance or .1) then
|
|
|
|
if (self.jump
|
|
and get_velocity(self) <= 0.5
|
|
and self.object:getvelocity().y == 0)
|
|
or (self.object:getvelocity().y == 0
|
|
and self.jump_chance > 0) then
|
|
do_jump(self)
|
|
end
|
|
|
|
|
|
|
|
set_velocity(self, self.pace_velocity[self.pace] or self.pace_velocity.walk)
|
|
set_animation(self, "walk")
|
|
else
|
|
-- we have arrived
|
|
self.destination = nil
|
|
|
|
set_velocity(self, 0)
|
|
set_animation(self, "stand")
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
end,
|
|
|
|
on_punch = function(self, hitter, tflp, tool_capabilities, dir)
|
|
print("got punched !!!!")
|
|
-- weapon wear
|
|
local weapon = hitter:get_wielded_item()
|
|
local punch_interval = 1.4
|
|
|
|
if tool_capabilities then
|
|
punch_interval = tool_capabilities.full_punch_interval or 1.4
|
|
end
|
|
|
|
if weapon:get_definition()
|
|
and weapon:get_definition().tool_capabilities then
|
|
|
|
weapon:add_wear(math.floor((punch_interval / 75) * 9000))
|
|
hitter:set_wielded_item(weapon)
|
|
end
|
|
|
|
-- weapon sounds
|
|
if weapon:get_definition().sounds ~= nil then
|
|
|
|
local s = math.random(0, #weapon:get_definition().sounds)
|
|
|
|
minetest.sound_play(weapon:get_definition().sounds[s], {
|
|
object = hitter,
|
|
max_hear_distance = 8
|
|
})
|
|
else
|
|
minetest.sound_play("default_punch", {
|
|
object = hitter,
|
|
max_hear_distance = 5
|
|
})
|
|
end
|
|
local hp = self.object:get_hp()
|
|
|
|
local pos = self.object:getpos()
|
|
print("punched (hp: "..hp..")at "..pos.x..", "..pos.y..", "..pos.z)
|
|
|
|
|
|
-- exit here if dead
|
|
if check_for_death(self, pos) then
|
|
print("died from punch")
|
|
return
|
|
end
|
|
print("didn't die from punch")
|
|
-- blood_particles
|
|
if self.blood_amount > 0
|
|
and not disable_blood then
|
|
|
|
local pos = self.object:getpos()
|
|
|
|
pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) / 2
|
|
|
|
effect(pos, self.blood_amount, self.blood_texture)
|
|
end
|
|
|
|
-- knock back effect
|
|
if self.knock_back > 0 then
|
|
|
|
local v = self.object:getvelocity()
|
|
local r = 1.4 - math.min(punch_interval, 1.4)
|
|
local kb = r * 5
|
|
|
|
self.object:setvelocity({
|
|
x = (dir.x or 0) * kb,
|
|
y = 2,
|
|
z = (dir.z or 0) * kb
|
|
})
|
|
|
|
self.pause_timer = r
|
|
end
|
|
|
|
|
|
end,
|
|
|
|
on_activate = function(self, staticdata, dtime_s)
|
|
self.btData = {
|
|
groupID = "default",
|
|
|
|
waypoints= {},
|
|
paths= {},
|
|
counters={},
|
|
|
|
history={},
|
|
history_queue={},
|
|
history_depth=20,
|
|
|
|
posStack={},
|
|
}
|
|
|
|
local btdata = self.btData
|
|
|
|
self.inv_id= name..":"..math.random(1, 2000000000)
|
|
--print(btdata.id)
|
|
|
|
btdata.lastpos = self.object:getpos()
|
|
|
|
if type(def.pre_activate) == "function" then
|
|
def.pre_activate(self, static_data, dtime_s)
|
|
end
|
|
|
|
-- load entity variables
|
|
if staticdata then
|
|
|
|
local tmp = minetest.deserialize(staticdata)
|
|
|
|
if tmp then
|
|
|
|
for _,stat in pairs(tmp) do
|
|
self[_] = stat
|
|
end
|
|
end
|
|
else
|
|
self.object:remove()
|
|
|
|
return
|
|
end
|
|
|
|
local inventory = minetest.create_detached_inventory(self.inv_id, {})
|
|
inventory:set_size("main", 9)
|
|
|
|
|
|
-- select random texture, set model and size
|
|
if not self.base_texture then
|
|
|
|
self.base_texture = def.textures[math.random(1, #def.textures)]
|
|
self.base_mesh = def.mesh
|
|
self.base_size = self.visual_size
|
|
self.base_colbox = self.collisionbox
|
|
end
|
|
|
|
-- set texture, model and size
|
|
local textures = self.base_texture
|
|
local mesh = self.base_mesh
|
|
local vis_size = self.base_size
|
|
local colbox = self.base_colbox
|
|
|
|
-- specific texture if gotten
|
|
if self.gotten == true
|
|
and def.gotten_texture then
|
|
textures = def.gotten_texture
|
|
end
|
|
|
|
-- specific mesh if gotten
|
|
if self.gotten == true
|
|
and def.gotten_mesh then
|
|
mesh = def.gotten_mesh
|
|
end
|
|
|
|
-- set child objects to half size
|
|
if self.child == true then
|
|
|
|
vis_size = {
|
|
x = self.base_size.x / 2,
|
|
y = self.base_size.y / 2
|
|
}
|
|
|
|
if def.child_texture then
|
|
textures = def.child_texture[1]
|
|
end
|
|
|
|
colbox = {
|
|
self.base_colbox[1] / 2,
|
|
self.base_colbox[2] / 2,
|
|
self.base_colbox[3] / 2,
|
|
self.base_colbox[4] / 2,
|
|
self.base_colbox[5] / 2,
|
|
self.base_colbox[6] / 2
|
|
}
|
|
end
|
|
|
|
if self.health == 0 then
|
|
self.health = math.random (self.hp_min, self.hp_max)
|
|
end
|
|
|
|
self.object:set_hp(self.health)
|
|
self.object:set_armor_groups({fleshy = self.armor})
|
|
self.old_y = self.object:getpos().y
|
|
self.object:setyaw(math.random(1, 360) / 180 * math.pi)
|
|
self.sounds.distance = (self.sounds.distance or 10)
|
|
self.textures = textures
|
|
self.mesh = mesh
|
|
self.collisionbox = colbox
|
|
self.visual_size = vis_size
|
|
|
|
-- set anything changed above
|
|
self.object:set_properties(self)
|
|
update_tag(self)
|
|
|
|
if type(def.post_activate) == "function" then
|
|
def.post_activate(self, static_data, dtime_s)
|
|
end
|
|
end,
|
|
|
|
get_staticdata = function(self)
|
|
|
|
-- remove mob when out of range unless tamed
|
|
if mobs.remove
|
|
and self.remove_ok
|
|
and not self.tamed then
|
|
|
|
--print ("REMOVED", self.remove_ok, self.name)
|
|
|
|
self.object:remove()
|
|
|
|
return nil
|
|
end
|
|
|
|
self.remove_ok = true
|
|
self.attack = nil
|
|
self.following = nil
|
|
self.state = "stand"
|
|
|
|
if self.btData ~= nil then
|
|
self.btData.inv = nil -- just in case
|
|
self.btData.mob = nil -- just in case
|
|
end
|
|
|
|
-- used to rotate older mobs
|
|
if self.drawtype
|
|
and self.drawtype == "side" then
|
|
self.rotate = math.rad(90)
|
|
end
|
|
|
|
local tmp = {}
|
|
|
|
for _,stat in pairs(self) do
|
|
|
|
local t = type(stat)
|
|
|
|
if t ~= 'function'
|
|
and t ~= 'nil'
|
|
and t ~= 'userdata' then
|
|
tmp[_] = self[_]
|
|
end
|
|
end
|
|
|
|
-- print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
|
|
return minetest.serialize(tmp)
|
|
end,
|
|
|
|
})
|
|
|
|
end -- END mobs:register_mob function
|
|
|
|
|
|
|
|
|
|
-- set content id's
|
|
local c_air = minetest.get_content_id("air")
|
|
local c_ignore = minetest.get_content_id("ignore")
|
|
local c_obsidian = minetest.get_content_id("default:obsidian")
|
|
local c_brick = minetest.get_content_id("default:obsidianbrick")
|
|
local c_chest = minetest.get_content_id("default:chest_locked")
|
|
|
|
|