Add Zombies (finally)

master
BlockMen 2014-04-27 14:28:26 +02:00
parent 873da37115
commit 59a054543b
19 changed files with 6207 additions and 0 deletions

44
mods/creatures/README.txt Normal file
View File

@ -0,0 +1,44 @@
Minetest mod "Creatures"
=======================
by BlockMen (c) 2014
Version: wasteland modified
About
~~~~~
This mod adds zombies to Minetest.
Zombies can spawn to every day-time in the world as long there is not to much light.
So you will find some in caves, dark forests and ofc a lot at night. If they notice you they will attack.
Zombies have 20 HP (like players) and drop rotten flesh randomly.
They can't harm you in your house (in case there is no door open). If it becomes day both mobs will take damage
by the sunlight, so they will die after a while.
Notice: Weapons and tools get damaged when hitting a zombie. The wearout is calculated on the damage amout
of the tools/weapons. The more damage they can do that longer they can be used.
Example:
- Diamond Sword: 1500 uses
- Wooden Sword: 30 uses
License of source code, textures and mesh model: WTFPL
------------------------------------------------------
(c) Copyright BlockMen (2014)
Licenses of sounds
------------------
following sounds are created by Under7dude (freesound.org)
- creatures_zombie.1.ogg, CC0
- creatures_zombie.2.ogg, CC0
- creatures_zombie.3.ogg, CC0
- creatures_zombie_death.ogg, CC0
This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.

View File

@ -0,0 +1 @@
default

138
mods/creatures/init.lua Normal file
View File

@ -0,0 +1,138 @@
creatures = {}
creatures.ANIM_STAND = 1
creatures.ANIM_SIT = 2
creatures.ANIM_LAY = 3
creatures.ANIM_WALK = 4
creatures.ANIM_EAT = 5
creatures.ANIM_RUN = 6
local tool_uses = {0, 30, 110, 150, 280, 300, 500, 1000}
-- helping functions
function creatures.spawn(pos, number, mob, limit, range)
if not pos or not number or not mob then return end
if number < 1 then return end
if limit == nil then limit = 1 end
if range == nil then range = 10 end
local m_name = string.sub(mob,11)
local res,mobs,player_near = creatures.find_mates(pos, m_name, range)
for i=1,number do
local x = 1/math.random(1,3)
local z = 1/math.random(1,3)
local p = {x=pos.x+x,y=pos.y,z=pos.z+z}
if mobs+i <= limit then
minetest.after(i/5,function()
minetest.env:add_entity(p, mob)
minetest.log("action", "Spawned "..mob.." at ("..pos.x..","..pos.y..","..pos.z..")")
end)
end
end
end
function creatures.add_wear(player, def)
if not minetest.setting_getbool("creative_mode") then
local item = player:get_wielded_item()
if def and def.damage_groups and def.damage_groups.fleshy then
local uses = tool_uses[def.damage_groups.fleshy] or 0
if uses > 0 then
local wear = 65535/uses
item:add_wear(wear)
player:set_wielded_item(item)
end
end
end
end
function creatures.drop(pos, items, dir)
if dir == nil then
dir = {x=1,y=1,z=1}
end
for _,item in ipairs(items) do
for i=1,item.count do
local x = 1/math.random(1,5)*dir.x--math.random(0, 6)/3 - 0.5*dir.x
local z = 1/math.random(1,5)*dir.z--math.random(0, 6)/3 - 0.5*dir.z
local p = {x=pos.x+x,y=pos.y,z=pos.z+z}
local node = minetest.get_node_or_nil(p)
if node == nil or not node.name or node.name ~= "air" then
p = pos
end
local obj = minetest.env:add_item(p, {name=item.name})
end
end
end
function creatures.find_mates(pos, name, radius)
local player_near = false
local mobs = 0
for _,obj in ipairs(minetest.env:get_objects_inside_radius(pos, radius)) do
if obj:is_player() then
player_near = true
else
local entity = obj:get_luaentity()
if entity and entity.mob_name and entity.mob_name == name then
mobs = mobs + 1
end
end
end
if mobs > 1 then
return true,mobs,player_near
end
return false,mobs,player_near
end
function creatures.compare_pos(pos1,pos2)
if pos1.x == pos2.x and pos1.y == pos2.y and pos1.z == pos2.z then
return true
end
return false
end
function creatures.attack(self, pos1, pos2, dist, range)
if not self then return end
if not pos1 or not pos2 then return end
if minetest.line_of_sight(pos1,pos2) ~= true then
return
end
if dist < range and self.attacking_timer > 0.6 then
self.attacker:punch(self.object, 1.0, {
full_punch_interval=1.0,
damage_groups = {fleshy=1}
})
self.attacking_timer = 0
end
end
function creatures.jump(self, pos, jump_y, timer)
if not self or not pos then return end
if self.direction ~= nil then
if self.jump_timer > timer then
self.jump_timer = 0
local p = {x=pos.x + self.direction.x,y=pos.y,z=pos.z + self.direction.z}-- pos
local n = minetest.get_node_or_nil(p)
p.y = p.y+1
local n2 = minetest.get_node_or_nil(p)
local def = nil
local def2 = nil
if n and n.name then
def = minetest.registered_items[n.name]
end
if n2 and n2.name then
def2 = minetest.registered_items[n2.name]
end
if def and def.walkable and def2 and not def2.walkable and not def.groups.fences and n.name ~= "default:fence_wood" then--
self.object:setvelocity({x=self.object:getvelocity().x,y=jump_y,z=self.object:getvelocity().z})
end
end
end
end
-- hostile mobs
dofile(minetest.get_modpath("creatures").."/zombie.lua")
-- spawning
dofile(minetest.get_modpath("creatures").."/spawn.lua")
dofile(minetest.get_modpath("creatures").."/spawners.lua")
-- other stuff
dofile(minetest.get_modpath("creatures").."/items.lua")

30
mods/creatures/items.lua Normal file
View File

@ -0,0 +1,30 @@
-- drop items
minetest.register_craftitem("creatures:flesh", {
description = "Flesh",
inventory_image = "creatures_flesh.png",
on_use = minetest.item_eat(4),
})
minetest.register_craftitem("creatures:rotten_flesh", {
description = "Rotten Flesh",
inventory_image = "creatures_rotten_flesh.png",
on_use = minetest.item_eat(1),
})
-- spawn-eggs
minetest.register_craftitem("creatures:zombie_spawn_egg", {
description = "Zombie spawn-egg",
inventory_image = "creatures_egg_zombie.png",
liquids_pointable = false,
stack_max = 99,
on_place = function(itemstack, placer, pointed_thing)
if pointed_thing.type == "node" then
local p = pointed_thing.above
p.y = p.y+1
creatures.spawn(p, 1, "creatures:zombie", 1, 1)
if not minetest.setting_getbool("creative_mode") then itemstack:take_item() end
return itemstack
end
end,
})

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

32
mods/creatures/spawn.lua Normal file
View File

@ -0,0 +1,32 @@
-- hostile mobs
if not minetest.setting_getbool("only_peaceful_mobs") then
-- zombie
minetest.register_abm({
nodenames = creatures.z_spawn_nodes,
interval = 40.0,
chance = 7600,
action = function(pos, node, active_object_count, active_object_count_wider)
local n = minetest.get_node_or_nil(pos)
--if n and n.name and n.name ~= "default:stone" and math.random(1,4)>3 then return end
pos.y = pos.y+1
local ll = minetest.env:get_node_light(pos)
local wtime = minetest.env:get_timeofday()
if not ll then
return
end
if ll >= creatures.z_ll then
return
end
if ll < -1 then
return
end
if minetest.env:get_node(pos).name ~= "air" then
return
end
pos.y = pos.y+1
if minetest.env:get_node(pos).name ~= "air" then
return
end
creatures.spawn(pos, 1, "creatures:zombie", 2, 20)
end})
end

View File

@ -0,0 +1,93 @@
function creatures.register_spawner(mob,size,offset,mesh,texture,range,max,max_ll,min_ll,day_only)
local DUMMY = {
hp_max = 1,
physical = true,
collisionbox = {0,0,0,0,0,0},
visual = "mesh",
visual_size = size,
mesh = mesh,
textures = texture,
makes_footstep_sound = false,
timer = 0,
automatic_rotate = math.pi * -2.9,
m_name = "dummy"
}
DUMMY.on_activate = function(self)
self.object:setvelocity({x=0, y=0, z=0})
self.object:setacceleration({x=0, y=0, z=0})
self.object:set_armor_groups({immortal=1})
end
DUMMY.on_step = function(self, dtime)
self.timer = self.timer + 0.01
local n = minetest.get_node_or_nil(self.object:getpos())
if self.timer > 1 then
if n and n.name and n.name ~= "creatures:"..mob.."_spawner" then
self.object:remove()
end
end
end
DUMMY.on_punch = function(self, hitter)
end
minetest.register_entity("creatures:dummy_"..mob, DUMMY)
-- node
minetest.register_node("creatures:"..mob.."_spawner", {
description = mob.." spawner",
paramtype = "light",
tiles = {"creatures_spawner.png"},
is_ground_content = true,
drawtype = "allfaces",
groups = {cracky=1,level=1},
drop = "",
on_construct = function(pos)
pos.y = pos.y + offset
minetest.env:add_entity(pos,"creatures:dummy_"..mob)
end,
on_destruct = function(pos)
for _,obj in ipairs(minetest.env:get_objects_inside_radius(pos, 1)) do
if not obj:is_player() then
if obj ~= nil and obj:get_luaentity().m_name == "dummy" then
obj:remove()
end
end
end
end
})
--abm
minetest.register_abm({
nodenames = {"creatures:"..mob.."_spawner"},
interval = 2.0,
chance = 20,
action = function(pos, node, active_object_count, active_object_count_wider)
local res,player_near = false
local mobs = 0
res,mobs,player_near = creatures.find_mates(pos, mob, range)
if player_near then
if mobs < max then
pos.x = pos.x+1
local p = minetest.find_node_near(pos, 5, {"air"})
local ll = minetest.env:get_node_light(p)
local wtime = minetest.env:get_timeofday()
if not ll then return end
if ll > max_ll then return end
if ll < min_ll then return end
if minetest.env:get_node(p).name ~= "air" then return end
p.y = p.y+1
if minetest.env:get_node(p).name ~= "air" then return end
if not day_only then
if (wtime > 0.2 and wtime < 0.805) and pos.y > 0 then return end
end
p.y = p.y-1
creatures.spawn(p, 1, "creatures:"..mob,range,max)
end
end
end })
end
-- spawner
creatures.register_spawner("zombie",{x=0.42,y=0.42},0.08,"creatures_mob.x",{"creatures_zombie.png"},17,6,7,-1,false)

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

345
mods/creatures/zombie.lua Normal file
View File

@ -0,0 +1,345 @@
local z_chillaxin_speed = 1.5
local z_animation_speed = 15
local z_mesh = "creatures_mob.x"
local z_texture = {"creatures_zombie.png"}
local z_hp = 20
local z_drop = "creatures:rotten_flesh"
local z_life_max = 80 --~5min
local z_player_radius = 14
z_hit_radius = 1.4
creatures.z_ll = 7
local z_sound_normal = "creatures_zombie"
local z_sound_hit = "creatures_zombie_hit"
local z_sound_dead = "creatures_zombie_death"
creatures.z_spawn_nodes = {"default:dirt_with_grass","default:stone","default:dirt","default:desert_sand"}
creatures.z_spawner_range = 17
creatures.z_spawner_max_mobs = 6
local function z_get_animations()
return {
stand_START = 0,
stand_END = 79,
lay_START = 162,
lay_END = 166,
walk_START = 168,
walk_END = 188,
-- not used
sit_START = 81,
sit_END = 160
}
end
function z_hit(self)
local sound = z_sound_hit
if self.object:get_hp() < 1 then sound = z_sound_dead end
minetest.sound_play(sound, {pos = self.object:getpos(), max_hear_distance = 10, loop = false, gain = 0.4})
prop = {
mesh = z_mesh,
textures = {"creatures_zombie.png^creatures_zombie_hit.png"},
}
self.object:set_properties(prop)
self.can_punch = false
minetest.after(0.4, function()
z_update_visuals_def(self)
end)
end
function z_update_visuals_def(self)
self.can_punch = true
prop = {
mesh = z_mesh,
textures = z_texture,
}
self.object:set_properties(prop)
end
ZOMBIE_DEF = {
physical = true,
collisionbox = {-0.25, -1, -0.3, 0.25, 0.75, 0.3},
visual = "mesh",
visual_size = {x=1, y=1},
mesh = z_mesh,
textures = z_texture,
makes_footstep_sound = true,
npc_anim = 0,
lifetime = 0,
timer = 0,
turn_timer = 0,
vec = 0,
yaw = 0,
yawwer = 0,
state = 1,
can_punch = true,
dead = false,
jump_timer = 0,
last_pos = {x=0,y=0,z=0},
punch_timer = 0,
sound_timer = 0,
attacker = "",
attacking_timer = 0,
mob_name = "zombie"
}
ZOMBIE_DEF.get_staticdata = function(self)
return minetest.serialize({
itemstring = self.itemstring,
timer = self.timer,
lifetime = self.lifetime,
})
end
ZOMBIE_DEF.on_activate = function(self, staticdata, dtime_s)
z_update_visuals_def(self)
self.anim = z_get_animations()
self.object:set_animation({x=self.anim.stand_START,y=self.anim.stand_END}, z_animation_speed, 0)
self.npc_anim = ANIM_STAND
self.object:setacceleration({x=0,y=-20,z=0})
self.state = 1
self.object:set_hp(z_hp)
self.object:set_armor_groups({fleshy=130})
self.last_pos = {x=0,y=0,z=0}
self.can_punch = true
self.dead = false
self.lifetime = 0
if staticdata then
local tmp = minetest.deserialize(staticdata)
if tmp and tmp.timer then
self.timer = tmp.timer
end
if tmp and tmp.lifetime ~= nil then
self.lifetime = tmp.lifetime
end
end
end
ZOMBIE_DEF.on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
if not self.can_punch then return end
local my_pos = self.object:getpos()
if puncher ~= nil then
self.attacker = puncher
if time_from_last_punch >= 0.45 then
z_hit(self)
local v = self.object:getvelocity()
self.direction = {x=v.x, y=v.y, z=v.z}
self.punch_timer = 0
self.object:setvelocity({x=dir.x*z_chillaxin_speed,y=5,z=dir.z*z_chillaxin_speed})
if self.state == 1 then
self.state = 8
elseif self.state >= 2 then
self.state = 9
end
-- add wear to sword/tool
creatures.add_wear(puncher, tool_capabilities)
end
end
if self.object:get_hp() < 1 then
creatures.drop(my_pos, {{name=z_drop, count=math.random(0,2)}}, dir)
end
end
ZOMBIE_DEF.on_step = function(self, dtime)
if self.dead then return end
self.timer = self.timer + 0.01
self.lifetime = self.lifetime + 0.01
self.turn_timer = self.turn_timer + 0.01
self.jump_timer = self.jump_timer + 0.01
self.punch_timer = self.punch_timer + 0.01
self.attacking_timer = self.attacking_timer + 0.01
self.sound_timer = self.sound_timer + 0.01
local current_pos = self.object:getpos()
local current_node = minetest.env:get_node_or_nil(current_pos)
if self.time_passed == nil then
self.time_passed = 0
end
-- death
if self.object:get_hp() < 1 then
self.object:setvelocity({x=0,y=-20,z=0})
self.object:set_hp(0)
self.attacker = ""
self.state = 0
self.dead = true
minetest.sound_play(z_sound_dead, {pos = current_pos, max_hear_distance = 10, gain = 0.9})
self.object:set_animation({x=self.anim.lay_START,y=self.anim.lay_END}, z_animation_speed, 0)
minetest.after(1, function()
self.object:remove()
if self.object:get_hp() < 1 then
creatures.drop(current_pos, {{name=z_drop, count=math.random(0,2)}})
end
end)
end
-- die if old
if self.lifetime > z_life_max then
self.object:set_hp(0)
self.state = 0
self.dead = true
self.object:remove()
return
end
-- die when in water, lava or sunlight
local wtime = minetest.env:get_timeofday()
local ll = minetest.env:get_node_light({x=current_pos.x,y=current_pos.y+1,z=current_pos.z}) or 0
local nn = nil
if current_node ~= nil then nn = current_node.name end
if nn ~= nil and nn == "default:water_source" or
nn == "default:water_flowing" or
nn == "default:lava_source" or
nn == "default:lava_flowing" or
(wtime > 0.2 and wtime < 0.805 and current_pos.y > 0 and ll > 11) then
self.sound_timer = self.sound_timer + dtime
if self.sound_timer >= 0.8 then
local damage = 5
if ll > 11 then damage = 2 end
self.sound_timer = 0
self.object:set_hp(self.object:get_hp()-damage)
z_hit(self)
end
else
self.time_passed = 0
end
-- update moving state every 0.5 or 1 second
if self.state < 3 then
if self.timer > 0.2 then
if self.attacker == "" then
self.state = math.random(1,2)
else self.state = 5 end
self.timer = 0
end
end
-- play random sound
if self.sound_timer > math.random(5,35) then
minetest.sound_play(z_sound_normal, {pos = current_pos, max_hear_distance = 10, gain = 0.7})
self.sound_timer = 0
end
-- after knocked back
if self.state >= 8 then
if self.punch_timer > 0.15 then
if self.state == 9 then
self.object:setvelocity({x=self.direction.x*z_chillaxin_speed,y=-20,z=self.direction.z*z_chillaxin_speed})
self.state = 2
elseif self.state == 8 then
self.object:setvelocity({x=0,y=-20,z=0})
self.state = 1
end
end
end
--STANDING
if self.state == 1 then
self.yawwer = true
self.attacker = ""
-- seach for players
for _,object in ipairs(minetest.env:get_objects_inside_radius(current_pos, z_player_radius)) do
if object:is_player() then
self.yawwer = false
NPC = current_pos
PLAYER = object:getpos()
self.vec = {x=PLAYER.x-NPC.x, y=PLAYER.y-NPC.y, z=PLAYER.z-NPC.z}
self.yaw = math.atan(self.vec.z/self.vec.x)+math.pi^2
if PLAYER.x > NPC.x then
self.yaw = self.yaw + math.pi
end
self.yaw = self.yaw - 2
self.object:setyaw(self.yaw)
self.attacker = object
end
end
if self.attacker == "" and self.turn_timer > math.random(1,4) then
self.yaw = 360 * math.random()
self.object:setyaw(self.yaw)
self.turn_timer = 0
self.direction = {x = math.sin(self.yaw)*-1, y = -20, z = math.cos(self.yaw)}
end
self.object:setvelocity({x=0,y=self.object:getvelocity().y,z=0})
if self.npc_anim ~= creatures.ANIM_STAND then
self.anim = z_get_animations()
self.object:set_animation({x=self.anim.stand_START,y=self.anim.stand_END}, z_animation_speed, 0)
self.npc_anim = creatures.ANIM_STAND
end
if self.attacker ~= "" then
self.direction = {x = math.sin(self.yaw)*-1, y = -20, z = math.cos(self.yaw)}
self.state = 2
end
end
--UPDATE DIR
if self.state == 5 then
self.yawwer = true
self.attacker = ""
-- seach for players
for _,object in ipairs(minetest.env:get_objects_inside_radius(current_pos, z_player_radius)) do
if object:is_player() then
self.yawwer = false
NPC = current_pos
PLAYER = object:getpos()
self.vec = {x=PLAYER.x-NPC.x, y=PLAYER.y-NPC.y, z=PLAYER.z-NPC.z}
self.yaw = math.atan(self.vec.z/self.vec.x)+math.pi^2
if PLAYER.x > NPC.x then
self.yaw = self.yaw + math.pi
end
self.yaw = self.yaw - 2
self.object:setyaw(self.yaw)
self.attacker = object
end
end
if self.attacker ~= "" then
self.direction = {x = math.sin(self.yaw)*-1, y = -20, z = math.cos(self.yaw)}
self.state = 2
else
self.state =1
end
end
-- WALKING
if self.state == 2 then
if self.attacker ~= "" then
self.direction = {x=math.sin(self.yaw)*-1, y=-20, z=math.cos(self.yaw)}
end
if self.direction ~= nil then
self.object:setvelocity({x=self.direction.x*z_chillaxin_speed,y=self.object:getvelocity().y,z=self.direction.z*z_chillaxin_speed})
end
if self.turn_timer > math.random(1,4) and not self.attacker then
self.yaw = 360 * math.random()
self.object:setyaw(self.yaw)
self.turn_timer = 0
self.direction = {x=math.sin(self.yaw)*-1, y=-20, z=math.cos(self.yaw)}
end
if self.npc_anim ~= creatures.ANIM_WALK then
self.npc_anim = creatures.ANIM_WALK
self.object:set_animation({x=self.anim.walk_START,y=self.anim.walk_END}, z_animation_speed, 0)
end
--jump
local p = current_pos
p.y = p.y-0.5
creatures.jump(self, p, 7, 0.25)
if self.attacker ~= "" and minetest.setting_getbool("enable_damage") then
local s = current_pos
local attacker_pos = self.attacker:getpos() or nil
if attacker_pos == nil then return end
local p = attacker_pos
if (s ~= nil and p ~= nil) then
local dist = ((p.x-s.x)^2 + (p.y-s.y)^2 + (p.z-s.z)^2)^0.5
creatures.attack(self, current_pos, attacker_pos, dist, z_hit_radius)
end
end
end
end
minetest.register_entity("creatures:zombie", ZOMBIE_DEF)