Complete rewrite and rename to Creatures MOB-Engine
188
API.txt
Normal file
@ -0,0 +1,188 @@
|
||||
Creatures MOB-Engine API
|
||||
------------------------
|
||||
|
||||
creatures.register_mob(#Creature definition)
|
||||
-registers a mob at MOB-Engine; returns true when sucessfull
|
||||
|
||||
creatures.rnd(chance_table)
|
||||
-returns a weighted random table element; chance_sum of table must be 1
|
||||
^ example: creatures.rnd({elem1 = {chance = 0.7}, {elem2 = {chance = 0.3}})
|
||||
|
||||
creatures.compare_pos(pos1, pos2)
|
||||
-returns true if pos1 == pos2
|
||||
|
||||
creatures.findTarget(search_obj, pos, radius, search_type, mob_name, xray, no_count)
|
||||
-returns table of found objects (as ObjectRef) and boolean player_near
|
||||
^ search_obj is searching object; can be nil
|
||||
^ pos is starting position for search radius
|
||||
^ radius for searching in blocks/node
|
||||
^ search_type that specifies returned object requirements
|
||||
^ "all" -- returns every object except dropped Items
|
||||
^ "hostile" -- returns every object(creature) that has hostile setting or is player
|
||||
^ ignores "mob_type" if specified
|
||||
^ "nonhostile" -- returns every object that is not hostile or player
|
||||
^ "player" -- returns all players
|
||||
^ "mates" -- returns all objects(creatures) that are of same kind
|
||||
^ requires "mob_type" specifies
|
||||
^ mob_type specifies creature that is ignored or searched, depending on search_type
|
||||
^ xray allows searching through blocks/nodes (default == false)
|
||||
^ no_count skips collecting loop and returns just the boolean player_near
|
||||
^ table is empty
|
||||
|
||||
creatures.dropItems(pos, drops)
|
||||
-drops items at position pos
|
||||
^ pos where to drop Items
|
||||
^ drops table in #ItemDrops format
|
||||
|
||||
|
||||
#ItemDrops
|
||||
----------
|
||||
{
|
||||
{
|
||||
<Itemname>, -- e.g. "default:wood"
|
||||
<amount>, -- either a <number> or table in format {min = <number>, max = <number>}; optional
|
||||
<rarity> -- "chance = <value>": <value> between 0.0 and 1.0
|
||||
},
|
||||
}
|
||||
|
||||
Example:
|
||||
Will drop with a chance of 30% 1 to 3 items of type "default:wood"
|
||||
and with a chance of 100% 2 items of type "default:stone"
|
||||
{
|
||||
{"default:wood", {min = 1, max = 3}, chance = 0.3},
|
||||
{"default:stone", 2}
|
||||
}
|
||||
|
||||
|
||||
#Creature definition
|
||||
--------------------
|
||||
{
|
||||
name = "", -- e.g. "creatures:sheep"
|
||||
stats = {
|
||||
hp = 1, -- 1 HP = "1/2 player heart"
|
||||
hostile = false, -- is mob hostile (required for mode "attack") <optional>
|
||||
lifetime = 300, -- after which time mob despawns, in seconds <optional>
|
||||
dies_when_tamed = false, -- stop despawn when tamed <optional>
|
||||
can_jump = 1, -- height in nodes <optional>
|
||||
can_swim = false, -- can mob swim or will it drown <optional>
|
||||
can_fly = false, -- allows to fly (requires mode "fly") and disable step sounds <optional>
|
||||
can_burn = false, -- takes damage of lava <optional>
|
||||
can_panic = false, -- runs fast around when hit (requires mode "walk") <optional>
|
||||
has_falldamage = false, -- deals damage if falling more than 3 blocks <optional>
|
||||
has_kockback = false, -- get knocked back when hit <optional>
|
||||
sneaky = false, -- disables step sounds <optional>
|
||||
light = {min, max}, -- which light level will burn creature (requires can_burn = true) <optional>
|
||||
},
|
||||
|
||||
modes = {
|
||||
idle = {chance = <part of 1.0>, duration = <time>, moving_speed = <number>, update_yaw = <yawtime>},
|
||||
^ chance -- number between 0.0 and 1.0 (!!NOTE: sum of all modes MUST be 1.0!!)
|
||||
^ if chance is 0 then mode is not chosen automatically
|
||||
^ duration -- time in seconds until the next mode is chosen (depending on chance)
|
||||
^ moving_speed -- moving speed(flying/walking) <optional>
|
||||
^ update_yaw -- timer in seconds until the looking dir is changed <optional>
|
||||
^ if moving_speed > 0 then the moving direction is also changed
|
||||
|
||||
-- special modes
|
||||
attack = {<same as above>}
|
||||
follow = {<same as above>, radius = <number>, timer = <time>, items = <table>},
|
||||
^ same as above -- all possible values like specified above
|
||||
^ radius -- search distance in blocks/nodes for player
|
||||
^ timer -- time in seconds between each check for player
|
||||
^ items -- table of items to make mob follow in format {<Itemname>, <Itemname>}; e.g. {"farming:wheat"}
|
||||
eat = {<same as above>, nodes = <table>},
|
||||
^ same as above -- all possible values like specified above
|
||||
^ items -- eatable nodes in format {<Itemname>, <Itemname>}; e.g. {"default:dirt_with_grass"}
|
||||
},
|
||||
|
||||
model = {
|
||||
mesh = "creatures_sheep.x", -- mesh name; see Minetest Documentation for supported filetypes
|
||||
textures = {"creatures_sheep.png"}, -- table of textures; see Minetest Documentation
|
||||
collisionbox = <NodeBox>, -- defines mesh collision box; see Minetest Documentation
|
||||
rotation = 0.0, -- sets rotation offset when moving
|
||||
animations = { -- animation used if defined <optional>
|
||||
idle = {#AnimationDef}, -- see #AnimationDef
|
||||
... -- depends on modes (must correspond to be used); supported "special modes": eat, follow, attack
|
||||
},
|
||||
},
|
||||
|
||||
sounds = {
|
||||
on_damage = {#SoundDef}, -- see #SoundDef <optional>
|
||||
on_death = {#SoundDef}, -- see #SoundDef <optional>
|
||||
swim = {#SoundDef}, -- see #SoundDef <optional>
|
||||
random = { -- depends on mode <optional>
|
||||
idle = {#SoundDef}, -- <optional>
|
||||
... -- depends on modes (must correspond to be used); supports "special modes": eat, follow, attack
|
||||
},
|
||||
},
|
||||
|
||||
drops = {#ItemDrops}, -- see #ItemDrops definition <optional>
|
||||
^ can also be a function; receives "self" reference
|
||||
|
||||
spawning = { -- defines spawning in world <optional>
|
||||
abm_nodes = {
|
||||
spawn_on = {<table>}, -- on what nodes mob can spawn <optional>
|
||||
^ table -- nodes and groups in table format; e.g. {"group:stone", "default:stone"}
|
||||
neighbors = {}, -- what node should be neighbors to spawnnode <optional>
|
||||
^ can be nil or table as above; "air" is forced always as neighbor
|
||||
},
|
||||
abm_interval = <interval>, -- time in seconds until Minetest tries to find a node with set specs
|
||||
abm_chance = <chance>, -- chance is 1/<chance>
|
||||
max_number = <number>, -- maximum mobs of this kind per mapblock(16x16x16)
|
||||
number = <amount>, -- how many mobs are spawned if found suitable spawn position
|
||||
^ amount -- number or table {min = <value>, max = <value>}
|
||||
time_range = <range>, -- time range in time of day format (0-24000) <optional>
|
||||
^ range -- table {min = <value>, max = <value>}
|
||||
light = <range>, -- min and max lightvalue at spawn position <optional>
|
||||
^ range -- table {min = <value>, max = <value>}
|
||||
height_limit = <range>, -- min and max height (world Y coordinate) <optional>
|
||||
^ range -- table {min = <value>, max = <value>}
|
||||
|
||||
spawn_egg = { -- is set a spawn_egg is added to creative inventory <optional>
|
||||
description = <desc>, -- Item description as string
|
||||
texture = <name>, -- texture name as string
|
||||
},
|
||||
|
||||
spawner = { -- is set a spawner_node is added to creative inventory <optional>
|
||||
range = <number>, -- defines an area (in blocks/nodes) within mobs are spawned
|
||||
number = <number>, -- maxmimum number of mobs spawned in area defined via range
|
||||
light = <range>, -- min and max lightvalue at spawn position <optional>
|
||||
^ range -- table {min = <value>, max = <value>}
|
||||
}
|
||||
},
|
||||
|
||||
on_rightclick = func(self, clicker) -- called when mob is rightclicked
|
||||
^ prevents default action when returns boolean true
|
||||
|
||||
on_punch = func(self, puncher) -- called when mob is punched (puncher can be nil)
|
||||
^ prevents default action when returns boolean true
|
||||
|
||||
on_step = func(self, dtime) -- called each server step
|
||||
^ prevents default action when returns boolean true
|
||||
|
||||
on_activate = func(self, staticdata) -- called when mob (re-)actived
|
||||
^ Note: staticdata is deserialized by MOB-Engine (including costum values)
|
||||
|
||||
get_staticdata = func(self) -- called when mob is punched (puncher can be nil)
|
||||
^ must return a table to save mob data (serialization is done by MOB-Engine)
|
||||
^ e.g:
|
||||
return {
|
||||
costum_mob_data = self.my_value,
|
||||
}
|
||||
}
|
||||
|
||||
#AnimationDef {
|
||||
start = 0, -- animation start frame
|
||||
stop = 80, -- animation end frame
|
||||
speed = 15, -- animation speed
|
||||
loop = true, -- if false, animation if just played once <optional>
|
||||
duration = 1 -- only supported in "death"-Animation, sets time the animation needs until mob is removed <optional>
|
||||
}
|
||||
|
||||
#SoundDef {
|
||||
name = <name>, -- sound name as string; see Minetest documentation
|
||||
gain = 1.0, -- sound gain; see Minetest documentation
|
||||
distance = <number>, -- hear distance in blocks/nodes <optional>
|
||||
time_min = <time> -- minimum time in seconds between sounds (random only) <optional>
|
||||
time_max = <time> -- maximum time in seconds between sounds (random only) <optional>
|
||||
}
|
129
README.txt
@ -1,121 +1,36 @@
|
||||
Minetest mod "Creatures"
|
||||
=======================
|
||||
by BlockMen (c) 2014 - 2015
|
||||
Creatures MOB-Engine
|
||||
====================
|
||||
Copyright (c) 2015 BlockMen <blockmen2015@gmail.com>
|
||||
|
||||
Version: 1.1.5 Beta
|
||||
|
||||
About
|
||||
~~~~~
|
||||
This mod adds 2 hostile and 1 friendly mob to Minetest, so far zombies, ghosts and sheeps.
|
||||
|
||||
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.
|
||||
|
||||
Ghosts only spawn at night-time. Also they don't spawn underground and are a bit more rare than zombies.
|
||||
They are flying in the world and attack you aswell if they notice you.
|
||||
Ghosts have 12 HP and don't drop any items atm (might be changed if i have an idea what they could drop).
|
||||
|
||||
Sheeps spawn only at day-time and are friendly mobs. They remain around 5 minutes in the world unless there
|
||||
are other sheeps around, then there is no fixed limit. If there is grass (dirt with grass) they eat the grass
|
||||
and get new wool that way. They will follow you if you have Wheat in your hand.
|
||||
Sheeps have 8 HP and drop 1-2 wool when punched or 2 wool when using shears. They need to eat grass until they can produce new wool.
|
||||
|
||||
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.
|
||||
Version: 2.0 Beta
|
||||
|
||||
|
||||
Notice: Weapons and tools get damaged when hitting a zombie or ghost. 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
|
||||
|
||||
Crafting Shears:
|
||||
----------- steel ingot
|
||||
steel ingot stick
|
||||
A Mod(pack) for Minetest that provides a MOB-Engine and adds several creatures to the game.
|
||||
Currently included: Ghosts, Zombies, Sheep.
|
||||
|
||||
|
||||
|
||||
License of source code:
|
||||
-----------------------
|
||||
(c) Copyright BlockMen (2014-2015), modified zlib-License
|
||||
|
||||
License:
|
||||
~~~~~~~~
|
||||
Code(if not stated differently):
|
||||
(c) Copyright 2015 BlockMen; modified zlib-License
|
||||
see "LICENSE.txt" for details.
|
||||
|
||||
Media(if not stated differently):
|
||||
(c) Copyright (2014-2015) BlockMen; CC-BY-SA 3.0
|
||||
|
||||
see each MOB-Module for detailed informations.
|
||||
|
||||
|
||||
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
|
||||
|
||||
following sounds are created by confusion_music (freesound.org)
|
||||
- creatures_sheep.1.ogg, CC-BY 3.0
|
||||
- creatures_sheep.2.ogg, CC-BY 3.0
|
||||
|
||||
following sound is created by Yuval (freesound.org)
|
||||
- creatures_sheep.3.ogg, CC-BY 3.0
|
||||
|
||||
following sound is created by SmartWentCody (freesound.org)
|
||||
- creatures_shears.ogg, CC-BY 3.0
|
||||
Github:
|
||||
~~~~~~~
|
||||
https://github.com/BlockMen/creatures_mob_engine
|
||||
|
||||
|
||||
License of all other Media (textures, meshes/models, sounds):
|
||||
-------------------------------------------------------------
|
||||
(c) Copyright BlockMen (2014-2015), CC BY-SA 3.0
|
||||
|
||||
Forum:
|
||||
~~~~~~
|
||||
https://forum.minetest.net/viewtopic.php?id=8638
|
||||
|
||||
|
||||
Changelog:
|
||||
----------
|
||||
# 1.0.1
|
||||
- fixed incompatibility with pyramids mod
|
||||
|
||||
# 1.1
|
||||
- new mob: sheep
|
||||
- fixed crash caused by unknown node
|
||||
- fixed spawning, added spawn limit
|
||||
- fixed weapon & tool damage
|
||||
- tweaked and restructured code
|
||||
- ghosts only spawn on grass and desert-sand blocks
|
||||
- ghosts have now 12 HP (instead 15 HP)
|
||||
- zombies don't jump over fences anymore
|
||||
|
||||
# 1.1.1
|
||||
- Zombies have also a maximum lifetime now
|
||||
- Use line_of_sight() befor attacking
|
||||
- Fix different crashes
|
||||
- Experimental spawning control
|
||||
|
||||
# 1.1.2
|
||||
- Sheeps follow you when holding wheat
|
||||
- Added Shears (rightclick)
|
||||
- Items are only droped (without player action) with builtin_mod
|
||||
|
||||
# 1.1.3
|
||||
- Fixed crash caused by spawning control
|
||||
- Prevent the mobs flood once and for all
|
||||
- Drop items aswell when settings define max lifetime
|
||||
|
||||
# 1.1.4
|
||||
- Fixed mobs flood (yes, really!)
|
||||
- Sheep drop raw flesh on death
|
||||
- Sheep are not stuck that often in valleys; spawning less
|
||||
- Improved jumping of zombies and sheep
|
||||
|
||||
# 1.1.5
|
||||
- Fixed global variable (by vitaminx)
|
||||
- Added description.txt and screenshot (by ExcaliburZero)
|
||||
- Fixed sounds (zombie and sheep)
|
||||
- Using [colorize modifier instead of texture overlays
|
||||
|
||||
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.
|
||||
~~~~~~~~~~
|
||||
see Changelog.txt
|
||||
|
20
creatures/LICENSE.txt
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright (c) 2015 BlockMen <blockmen2015@gmail.com>
|
||||
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty. In no
|
||||
event will the authors be held liable for any damages arising from the use of
|
||||
this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose, including
|
||||
commercial applications, and to alter it and redistribute it freely, subject to the
|
||||
following restrictions:
|
||||
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software in a
|
||||
product, an acknowledgment in the product documentation is required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not
|
||||
be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
121
creatures/README.txt
Normal file
@ -0,0 +1,121 @@
|
||||
Minetest mod "Creatures"
|
||||
=======================
|
||||
by BlockMen (c) 2014 - 2015
|
||||
|
||||
Version: 1.1.5 Beta
|
||||
|
||||
About
|
||||
~~~~~
|
||||
This mod adds 2 hostile and 1 friendly mob to Minetest, so far zombies, ghosts and sheeps.
|
||||
|
||||
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.
|
||||
|
||||
Ghosts only spawn at night-time. Also they don't spawn underground and are a bit more rare than zombies.
|
||||
They are flying in the world and attack you aswell if they notice you.
|
||||
Ghosts have 12 HP and don't drop any items atm (might be changed if i have an idea what they could drop).
|
||||
|
||||
Sheeps spawn only at day-time and are friendly mobs. They remain around 5 minutes in the world unless there
|
||||
are other sheeps around, then there is no fixed limit. If there is grass (dirt with grass) they eat the grass
|
||||
and get new wool that way. They will follow you if you have Wheat in your hand.
|
||||
Sheeps have 8 HP and drop 1-2 wool when punched or 2 wool when using shears. They need to eat grass until they can produce new wool.
|
||||
|
||||
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 or ghost. 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
|
||||
|
||||
Crafting Shears:
|
||||
----------- steel ingot
|
||||
steel ingot stick
|
||||
|
||||
|
||||
|
||||
License of source code:
|
||||
-----------------------
|
||||
(c) Copyright BlockMen (2014-2015), modified zlib-License
|
||||
|
||||
see "LICENSE.txt" for details.
|
||||
|
||||
|
||||
|
||||
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
|
||||
|
||||
following sounds are created by confusion_music (freesound.org)
|
||||
- creatures_sheep.1.ogg, CC-BY 3.0
|
||||
- creatures_sheep.2.ogg, CC-BY 3.0
|
||||
|
||||
following sound is created by Yuval (freesound.org)
|
||||
- creatures_sheep.3.ogg, CC-BY 3.0
|
||||
|
||||
following sound is created by SmartWentCody (freesound.org)
|
||||
- creatures_shears.ogg, CC-BY 3.0
|
||||
|
||||
|
||||
License of all other Media (textures, meshes/models, sounds):
|
||||
-------------------------------------------------------------
|
||||
(c) Copyright BlockMen (2014-2015), CC BY-SA 3.0
|
||||
|
||||
|
||||
|
||||
Changelog:
|
||||
----------
|
||||
# 1.0.1
|
||||
- fixed incompatibility with pyramids mod
|
||||
|
||||
# 1.1
|
||||
- new mob: sheep
|
||||
- fixed crash caused by unknown node
|
||||
- fixed spawning, added spawn limit
|
||||
- fixed weapon & tool damage
|
||||
- tweaked and restructured code
|
||||
- ghosts only spawn on grass and desert-sand blocks
|
||||
- ghosts have now 12 HP (instead 15 HP)
|
||||
- zombies don't jump over fences anymore
|
||||
|
||||
# 1.1.1
|
||||
- Zombies have also a maximum lifetime now
|
||||
- Use line_of_sight() befor attacking
|
||||
- Fix different crashes
|
||||
- Experimental spawning control
|
||||
|
||||
# 1.1.2
|
||||
- Sheeps follow you when holding wheat
|
||||
- Added Shears (rightclick)
|
||||
- Items are only droped (without player action) with builtin_mod
|
||||
|
||||
# 1.1.3
|
||||
- Fixed crash caused by spawning control
|
||||
- Prevent the mobs flood once and for all
|
||||
- Drop items aswell when settings define max lifetime
|
||||
|
||||
# 1.1.4
|
||||
- Fixed mobs flood (yes, really!)
|
||||
- Sheep drop raw flesh on death
|
||||
- Sheep are not stuck that often in valleys; spawning less
|
||||
- Improved jumping of zombies and sheep
|
||||
|
||||
# 1.1.5
|
||||
- Fixed global variable (by vitaminx)
|
||||
- Added description.txt and screenshot (by ExcaliburZero)
|
||||
- Fixed sounds (zombie and sheep)
|
||||
- Using [colorize modifier instead of texture overlays
|
||||
|
||||
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.
|
148
creatures/common.lua
Normal file
@ -0,0 +1,148 @@
|
||||
--= Creatures MOB-Engine (cme) =--
|
||||
-- Copyright (c) 2015 BlockMen <blockmen2015@gmail.com>
|
||||
--
|
||||
-- common.lua
|
||||
--
|
||||
-- This software is provided 'as-is', without any express or implied warranty. In no
|
||||
-- event will the authors be held liable for any damages arising from the use of
|
||||
-- this software.
|
||||
--
|
||||
-- Permission is granted to anyone to use this software for any purpose, including
|
||||
-- commercial applications, and to alter it and redistribute it freely, subject to the
|
||||
-- following restrictions:
|
||||
--
|
||||
-- 1. The origin of this software must not be misrepresented; you must not
|
||||
-- claim that you wrote the original software. If you use this software in a
|
||||
-- product, an acknowledgment in the product documentation is required.
|
||||
-- 2. Altered source versions must be plainly marked as such, and must not
|
||||
-- be misrepresented as being the original software.
|
||||
-- 3. This notice may not be removed or altered from any source distribution.
|
||||
--
|
||||
|
||||
|
||||
-- constants
|
||||
nullVec = {x = 0, y = 0, z = 0}
|
||||
DEGTORAD = math.pi / 180.0
|
||||
|
||||
-- common functions
|
||||
function creatures.rnd(table, errval)
|
||||
if not errval then
|
||||
errval = false
|
||||
end
|
||||
|
||||
local res = 1000000000
|
||||
local rn = math.random(0, res - 1)
|
||||
local retval = nil
|
||||
|
||||
local psum = 0
|
||||
for s,w in pairs(table) do
|
||||
psum = psum + ((tonumber(w) or w.chance or 0) * res)
|
||||
if psum > rn then
|
||||
retval = s
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return retval
|
||||
end
|
||||
|
||||
function throw_error(msg)
|
||||
core.log("error", "#Creatures: ERROR: " .. msg)
|
||||
end
|
||||
|
||||
function creatures.compare_pos(pos1, pos2)
|
||||
if not pos1 or not pos2 then
|
||||
return
|
||||
end
|
||||
if pos1.x == pos2.x and pos1.y == pos2.y and pos1.z == pos2.z then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function creatures.findTarget(search_obj, pos, radius, search_type, ignore_mob, xray, no_count)
|
||||
local player_near = false
|
||||
local mobs = {}
|
||||
for _,obj in ipairs(core.get_objects_inside_radius(pos, radius)) do
|
||||
if obj ~= search_obj then
|
||||
if xray or core.line_of_sight(pos, obj:getpos()) == true then
|
||||
local is_player = obj:is_player()
|
||||
if is_player then
|
||||
player_near = true
|
||||
if no_count == true then
|
||||
return {}, true
|
||||
end
|
||||
end
|
||||
local entity = obj:get_luaentity()
|
||||
local isItem = (entity and entity.name == "__builtin:item") or false
|
||||
local ignore = (entity and entity.mob_name == ignore_mob and search_type ~= "mates") or false
|
||||
|
||||
if search_type == "all" then
|
||||
if not isItem and not ignore then
|
||||
table.insert(mobs, obj)
|
||||
end
|
||||
elseif search_type == "hostile" then
|
||||
if not ignore and (entity and entity.hostile == true) or is_player then
|
||||
table.insert(mobs, obj)
|
||||
end
|
||||
elseif search_type == "nonhostile" then
|
||||
if entity and not entity.hostile and not isItem and not ignore then
|
||||
table.insert(mobs, obj)
|
||||
end
|
||||
elseif search_type == "player" then
|
||||
if is_player then
|
||||
table.insert(mobs, obj)
|
||||
end
|
||||
elseif search_type == "mate" then
|
||||
if not isItem and (entity and entity.mob_name == ignore_mob) then
|
||||
table.insert(mobs, obj)
|
||||
end
|
||||
end
|
||||
end
|
||||
end --for
|
||||
end
|
||||
|
||||
return mobs,player_near
|
||||
end
|
||||
|
||||
function creatures.dropItems(pos, drops)
|
||||
if not pos or not drops then
|
||||
return
|
||||
end
|
||||
|
||||
-- convert drops table
|
||||
local tab = {}
|
||||
for _,elem in pairs(drops) do
|
||||
local name = tostring(elem[1])
|
||||
local v = elem[2]
|
||||
local chance = elem.chance
|
||||
local amount = ""
|
||||
-- check if drops depending on defined chance
|
||||
if name and chance then
|
||||
local ct = {}
|
||||
ct[name] = chance
|
||||
ct["_fake"] = 1 - chance
|
||||
local res = creatures.rnd(ct)
|
||||
if res == "_fake" then
|
||||
name = nil
|
||||
end
|
||||
end
|
||||
-- get amount
|
||||
if name and v then
|
||||
if type(v) == "table" then
|
||||
amount = math.random(v.min or 1, v.max or 1) or 1
|
||||
elseif type(v) == "number" then
|
||||
amount = v
|
||||
end
|
||||
if amount > 0 then
|
||||
amount = " " .. amount
|
||||
end
|
||||
end
|
||||
if name then
|
||||
local obj = core.add_item(pos, name .. amount)
|
||||
if not obj then
|
||||
throw_error("Could not drop item '" .. name .. amount .. "'")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
1
creatures/description.txt
Normal file
@ -0,0 +1 @@
|
||||
A Mod(pack) for Minetest that provides a MOB-Engine and adds several creatures to the game.
|
644
creatures/functions.lua
Normal file
@ -0,0 +1,644 @@
|
||||
--= Creatures MOB-Engine (cme) =--
|
||||
-- Copyright (c) 2015 BlockMen <blockmen2015@gmail.com>
|
||||
--
|
||||
-- functions.lua
|
||||
--
|
||||
-- This software is provided 'as-is', without any express or implied warranty. In no
|
||||
-- event will the authors be held liable for any damages arising from the use of
|
||||
-- this software.
|
||||
--
|
||||
-- Permission is granted to anyone to use this software for any purpose, including
|
||||
-- commercial applications, and to alter it and redistribute it freely, subject to the
|
||||
-- following restrictions:
|
||||
--
|
||||
-- 1. The origin of this software must not be misrepresented; you must not
|
||||
-- claim that you wrote the original software. If you use this software in a
|
||||
-- product, an acknowledgment in the product documentation is required.
|
||||
-- 2. Altered source versions must be plainly marked as such, and must not
|
||||
-- be misrepresented as being the original software.
|
||||
-- 3. This notice may not be removed or altered from any source distribution.
|
||||
--
|
||||
|
||||
|
||||
local function knockback(selfOrObject, dir, old_dir, strengh)
|
||||
local object = selfOrObject
|
||||
if selfOrObject.mob_name then
|
||||
object = selfOrObject.object
|
||||
end
|
||||
object:set_properties({automatic_face_movement_dir = false})
|
||||
object:setvelocity(vector.add(old_dir, {x = dir.x * strengh, y = 3.5, z = dir.z * strengh}))
|
||||
old_dir.y = 0---10
|
||||
core.after(0.4, function()
|
||||
object:set_properties({automatic_face_movement_dir = -90.0})
|
||||
object:setvelocity(old_dir)
|
||||
selfOrObject.falltimer = nil
|
||||
if selfOrObject.stunned == true then
|
||||
selfOrObject.stunned = false
|
||||
if selfOrObject.can_panic == true then
|
||||
selfOrObject.mode = "_run"
|
||||
selfOrObject.modetimer = 0
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function on_hit(me)
|
||||
core.after(0.1, function()
|
||||
me:settexturemod("^[colorize:#c4000099")
|
||||
end)
|
||||
core.after(0.5, function()
|
||||
me:settexturemod("")
|
||||
end)
|
||||
end
|
||||
|
||||
local hasMoved = creatures.compare_pos
|
||||
|
||||
local function getDir(pos1, pos2)
|
||||
local retval
|
||||
if pos1 and pos2 then
|
||||
retval = {x = pos2.x - pos1.x, y = pos2.y - pos1.y, z = pos2.z - pos1.z}
|
||||
end
|
||||
return retval
|
||||
end
|
||||
|
||||
local function getDistance(vec, fly_offset)
|
||||
if not vec then
|
||||
return -1
|
||||
end
|
||||
if fly_offset then
|
||||
vec.y = vec.y + fly_offset
|
||||
end
|
||||
return math.sqrt((vec.x)^2 + (vec.y)^2 + (vec.z)^2)
|
||||
end
|
||||
|
||||
local findTarget = creatures.findTarget
|
||||
|
||||
local function update_animation(obj_ref, mode, anim_defs)
|
||||
local anim = nil
|
||||
for mn,range in pairs(anim_defs) do
|
||||
if tostring(mn) == mode then
|
||||
anim = range
|
||||
break
|
||||
end
|
||||
end
|
||||
if anim and obj_ref then
|
||||
obj_ref:set_animation({x = anim.start, y = anim.stop}, anim.speed, 0, anim.loop)
|
||||
end
|
||||
end
|
||||
|
||||
local function update_velocity(obj_ref, dir, speed, add)
|
||||
local velo = obj_ref:getvelocity()
|
||||
if not dir.y then
|
||||
dir.y = velo.y/speed
|
||||
end
|
||||
local new_velo = {x = dir.x * speed, y = dir.y * speed or velo.y , z = dir.z * speed}
|
||||
if add then
|
||||
new_velo = vector.add(velo, new_velo)
|
||||
end
|
||||
obj_ref:setvelocity(new_velo)
|
||||
end
|
||||
|
||||
local function getYaw(dirOrYaw)
|
||||
local yaw = 360 * math.random()
|
||||
if dirOrYaw and type(dirOrYaw) == "table" then
|
||||
yaw = math.atan(dirOrYaw.z / dirOrYaw.x) + math.pi^2 - 2
|
||||
if dirOrYaw.x > 0 then
|
||||
yaw = yaw + math.pi
|
||||
end
|
||||
elseif dirOrYaw and type(dirOrYaw) == "number" then
|
||||
-- here could be a value based on given yaw
|
||||
end
|
||||
|
||||
return yaw
|
||||
end
|
||||
|
||||
local dropItems = creatures.dropItems
|
||||
|
||||
local function killMob(me, def)
|
||||
if not def then
|
||||
if me then
|
||||
me:remove()
|
||||
end
|
||||
end
|
||||
local pos = me:getpos()
|
||||
me:setvelocity(nullVec)
|
||||
me:set_hp(0)
|
||||
|
||||
if def.sounds and def.sounds.on_death then
|
||||
local death_snd = def.sounds.on_death
|
||||
core.sound_play(death_snd.name, {pos = pos, max_hear_distance = death_snd.distance or 5, gain = death_snd.gain or 1})
|
||||
end
|
||||
|
||||
if def.model.animations.death then
|
||||
local dur = def.model.animations.death.duration or 0.5
|
||||
update_animation(me, "death", def.model.animations)
|
||||
core.after(dur, function()
|
||||
me:remove()
|
||||
end)
|
||||
else
|
||||
me:remove()
|
||||
end
|
||||
if def.drops then
|
||||
if type(def.drops) == "function" then
|
||||
def.drops(me:get_luaentity())
|
||||
else
|
||||
dropItems(pos, def.drops)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function onDamage(self, hp)
|
||||
local me = self.object
|
||||
local def = core.registered_entities[self.mob_name]
|
||||
hp = hp or me:get_hp()
|
||||
|
||||
if hp <= 0 then
|
||||
self.stunned = true
|
||||
killMob(me, def)
|
||||
else
|
||||
on_hit(me) -- red flashing
|
||||
if def.sounds and def.sounds.on_damage then
|
||||
local dmg_snd = def.sounds.on_damage
|
||||
core.sound_play(dmg_snd.name, {pos = me:getpos(), max_hear_distance = dmg_snd.distance or 5, gain = dmg_snd.gain or 1})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function changeHP(self, value)
|
||||
local me = self.object
|
||||
local hp = me:get_hp()
|
||||
hp = hp + math.floor(value)
|
||||
if value < 0 then
|
||||
me:set_hp(hp)
|
||||
onDamage(self, hp)
|
||||
end
|
||||
end
|
||||
|
||||
local function checkWielded(wielded, itemList)
|
||||
for s,w in pairs(itemList) do
|
||||
if w == wielded then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local tool_uses = {0, 30, 110, 150, 280, 300, 500, 1000}
|
||||
local function addWearout(player, tool_def)
|
||||
if not core.setting_getbool("creative_mode") then
|
||||
local item = player:get_wielded_item()
|
||||
if tool_def and tool_def.damage_groups and tool_def.damage_groups.fleshy then
|
||||
local uses = tool_uses[tool_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
|
||||
|
||||
local function spawnParticles(...)
|
||||
end
|
||||
if core.setting_getbool("creatures_enable_particles") == true then
|
||||
spawnParticles = function(pos, velocity, texture_str)
|
||||
local vel = vector.multiply(velocity, 0.5)
|
||||
vel.y = 0
|
||||
core.add_particlespawner({
|
||||
amount = 8,
|
||||
time = 1,
|
||||
minpos = vector.add(pos, -0.7),
|
||||
maxpos = vector.add(pos, 0.7),
|
||||
minvel = vector.add(vel, {x = -0.1, y = -0.01, z = -0.1}),
|
||||
maxvel = vector.add(vel, {x = 0.1, y = 0, z = 0.1}),
|
||||
minacc = vector.new(),
|
||||
maxacc = vector.new(),
|
||||
minexptime = 0.8,
|
||||
maxexptime = 1,
|
||||
minsize = 1,
|
||||
maxsize = 2.5,
|
||||
texture = texture_str,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- --
|
||||
-- Default entity functions
|
||||
-- --
|
||||
|
||||
creatures.on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
|
||||
if self.stunned == true then
|
||||
return
|
||||
end
|
||||
|
||||
local me = self.object
|
||||
local mypos = me:getpos()
|
||||
if puncher then
|
||||
if self.hostile then
|
||||
self.target = puncher
|
||||
end
|
||||
if time_from_last_punch >= 0.45 and self.stunned == false then
|
||||
if self.has_kockback == true then
|
||||
local v = me:getvelocity()
|
||||
v.y = 0
|
||||
if not self.can_fly then
|
||||
me:setacceleration({x = 0, y = -15, z = 0})
|
||||
end
|
||||
knockback(self, dir, v, 5)
|
||||
self.stunned = true
|
||||
end
|
||||
|
||||
-- add wearout to weapons/tools
|
||||
addWearout(puncher, tool_capabilities)
|
||||
onDamage(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
creatures.on_rightclick = function(self, clicker)
|
||||
-- do sth
|
||||
core.chat_send_all("Hey, i got clicked!")
|
||||
end
|
||||
|
||||
creatures.on_step = function(self, dtime)
|
||||
-- first get the relevant specs; exit if we don't know anything (1-3ms)
|
||||
local def = core.registered_entities[self.mob_name]
|
||||
if not def then
|
||||
throw_error("Can't load creature-definition")
|
||||
return
|
||||
end
|
||||
|
||||
-- timer updates
|
||||
self.lifetimer = self.lifetimer + dtime
|
||||
self.modetimer = self.modetimer + dtime
|
||||
self.soundtimer = self.soundtimer + dtime
|
||||
self.yawtimer = self.yawtimer + dtime
|
||||
self.nodetimer = self.nodetimer + dtime
|
||||
self.followtimer = self.followtimer + dtime
|
||||
if self.envtimer then
|
||||
self.envtimer = self.envtimer + dtime
|
||||
end
|
||||
if self.falltimer then
|
||||
self.falltimer = self.falltimer + dtime
|
||||
end
|
||||
if self.searchtimer then
|
||||
self.searchtimer = self.searchtimer + dtime
|
||||
end
|
||||
if self.attacktimer then
|
||||
self.attacktimer = self.attacktimer + dtime
|
||||
end
|
||||
if self.swimtimer then
|
||||
self.swimtimer = self.swimtimer + dtime
|
||||
end
|
||||
|
||||
-- main
|
||||
if self.stunned == true then
|
||||
return
|
||||
end
|
||||
|
||||
if self.lifetimer > def.stats.lifetime and not (self.mode == "attack" and self.target) then
|
||||
self.lifetimer = 0
|
||||
if not self.tamed or (self.tamed and def.stats.dies_when_tamed) then
|
||||
killMob(self.object, def)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- localize some things
|
||||
local me = self.object
|
||||
local current_pos = me:getpos()
|
||||
current_pos.y = current_pos.y + 0.5
|
||||
local moved = hasMoved(current_pos, self.last_pos) or false
|
||||
local fallen = false
|
||||
|
||||
-- Update pos and current node if necessary
|
||||
if moved == true or not self.last_pos then
|
||||
-- for falldamage
|
||||
if self.has_falldamage and self.last_pos and not self.in_water then
|
||||
local dist = math.abs(current_pos.y - self.last_pos.y)
|
||||
if dist > 0 then
|
||||
self.fall_dist = self.fall_dist - dist
|
||||
if not self.falltimer then
|
||||
self.falltimer = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.last_pos = current_pos
|
||||
if self.nodetimer > 0.2 then
|
||||
self.nodetimer = 0
|
||||
local current_node = core.get_node_or_nil(current_pos)
|
||||
self.last_node = current_node
|
||||
if def.stats.light then
|
||||
local wtime = core.get_timeofday()
|
||||
local llvl = core.get_node_light({x = current_pos.x, y = current_pos.y + 0.5, z = current_pos.z}) or 0
|
||||
self.last_llvl = llvl
|
||||
end
|
||||
end
|
||||
else
|
||||
if (def.modes[self.mode].moving_speed or 0) > 0 then
|
||||
update_velocity(me, nullVec, 0)
|
||||
if def.modes["idle"] and not (self.mode == "attack" or self.mode == "follow") then
|
||||
self.mode = "idle"
|
||||
self.modetimer = 0
|
||||
end
|
||||
end
|
||||
if self.fall_dist < 0 then
|
||||
fallen = true
|
||||
end
|
||||
end
|
||||
|
||||
if fallen then
|
||||
local falltime = tonumber(self.falltimer) or 0
|
||||
local dist = math.abs(self.fall_dist) or 0
|
||||
self.falltimer = 0
|
||||
self.fall_dist = 0
|
||||
fallen = false
|
||||
|
||||
local damage = 0
|
||||
if dist > 3 and not self.in_water and falltime/dist < 0.2 then
|
||||
damage = dist - 3
|
||||
end
|
||||
|
||||
-- damage by calced value
|
||||
if damage > 0 then
|
||||
changeHP(self, damage * -1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- special mode handling
|
||||
-- check distance to target
|
||||
if self.target and self.followtimer > 0.6 then
|
||||
self.followtimer = 0
|
||||
local p2 = self.target:getpos()
|
||||
local dir = getDir(current_pos, p2)
|
||||
local offset
|
||||
if self.can_fly then
|
||||
offset = def.modes["fly"].target_offset
|
||||
end
|
||||
local dist = getDistance(dir, offset)
|
||||
local radius
|
||||
if self.hostile and def.combat then
|
||||
radius = def.combat.search_radius
|
||||
elseif def.modes["follow"] then
|
||||
radius = def.modes["follow"].radius
|
||||
end
|
||||
if dist == -1 or dist > (radius or 5) then
|
||||
self.target = nil
|
||||
self.mode = ""
|
||||
elseif dist > -1 and self.hostile and dist < def.combat.attack_radius then
|
||||
-- attack
|
||||
if self.attacktimer > def.combat.attack_speed then
|
||||
self.attacktimer = 0
|
||||
if core.line_of_sight(current_pos, p2) == true then
|
||||
self.target:punch(me, 1.0, {
|
||||
full_punch_interval = def.combat.attack_speed,
|
||||
damage_groups = {fleshy = def.combat.attack_damage}
|
||||
})
|
||||
end
|
||||
update_velocity(me, self.dir, 0)
|
||||
end
|
||||
else
|
||||
if self.mode == "attack" or self.mode == "follow" then
|
||||
self.dir = vector.normalize(dir)
|
||||
me:setyaw(getYaw(dir))
|
||||
if self.in_water then
|
||||
self.dir.y = me:getvelocity().y
|
||||
end
|
||||
update_velocity(me, self.dir, def.modes[self.mode].moving_speed or 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- search a target (1-2ms)
|
||||
if not self.target and ((self.hostile and def.combat.search_enemy) or def.modes["follow"]) then
|
||||
local timer
|
||||
if self.hostile then
|
||||
timer = def.combat.search_timer or 2
|
||||
elseif def.modes["follow"] then
|
||||
timer = def.modes["follow"].timer
|
||||
end
|
||||
if self.searchtimer > (timer or 4) then
|
||||
self.searchtimer = 0
|
||||
local targets = {}
|
||||
if self.hostile then
|
||||
targets = findTarget(me, current_pos, def.combat.search_radius, def.combat.search_type, def.combat.search_xray)
|
||||
else
|
||||
targets = findTarget(me, current_pos, def.modes["follow"].radius or 5, "player")
|
||||
end
|
||||
if #targets > 1 then
|
||||
self.target = targets[math.random(1, #targets)]
|
||||
elseif #targets == 1 then
|
||||
self.target = targets[1]
|
||||
end
|
||||
if self.target then
|
||||
if self.hostile and def.modes["attack"] then
|
||||
self.mode = "attack"
|
||||
else
|
||||
local name = self.target:get_wielded_item():get_name()
|
||||
if name and checkWielded(name, def.modes["follow"].items) == true then
|
||||
self.mode = "follow"
|
||||
self.modetimer = 0
|
||||
else
|
||||
self.target = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self.mode == "eat" and not self.eat_node then
|
||||
local nodes = def.modes[self.mode].nodes
|
||||
local p = {x = current_pos.x, y = current_pos.y - 1, z = current_pos.z}
|
||||
local sn = core.get_node_or_nil(p)
|
||||
local eat_node
|
||||
for _,name in pairs(nodes) do
|
||||
if name == self.last_node.name then
|
||||
eat_node = current_pos
|
||||
break
|
||||
elseif name == sn.name then
|
||||
eat_node = p
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not eat_node then
|
||||
self.mode = "idle"
|
||||
else
|
||||
self.eat_node = eat_node
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- further mode handling
|
||||
-- update mode
|
||||
if self.mode ~= "attack" and
|
||||
(self.mode == "" or self.modetimer > (def.modes[self.mode].duration or 4)) then
|
||||
self.modetimer = 0
|
||||
|
||||
local new_mode = creatures.rnd(def.modes) or "idle"
|
||||
if new_mode == "eat" and self.in_water == true then
|
||||
new_mode = "idle"
|
||||
end
|
||||
if self.mode == "follow" and math.random(1, 10) < 3 then
|
||||
new_mode = self.mode
|
||||
elseif self.mode == "follow" then
|
||||
-- "lock" searching a little bit
|
||||
self.searchtimer = math.random(5, 8) * -1
|
||||
self.target = nil
|
||||
end
|
||||
self.mode = new_mode
|
||||
|
||||
-- change eaten node when mode changes
|
||||
if self.eat_node then
|
||||
local n = core.get_node_or_nil(self.eat_node)
|
||||
local nnn = n.name
|
||||
local def = core.registered_nodes[n.name]
|
||||
local sounds
|
||||
if def then
|
||||
if def.drop and type(def.drop) == "string" then
|
||||
nnn = def.drop
|
||||
elseif not def.walkable then
|
||||
nnn = "air"
|
||||
end
|
||||
end
|
||||
if nnn and nnn ~= n.name and core.registered_nodes[nnn] then
|
||||
core.set_node(self.eat_node, {name = nnn})
|
||||
if not sounds then
|
||||
sounds = def.sounds
|
||||
end
|
||||
if sounds and sounds.dug then
|
||||
core.sound_play(sounds.dug, {pos = self.eat_node, max_hear_distance = 5, gain = 1})
|
||||
end
|
||||
end
|
||||
self.eat_node = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- mode has changes, do things
|
||||
if self.mode ~= self.last_mode then
|
||||
self.last_mode = self.mode
|
||||
|
||||
local moving_speed = def.modes[self.mode].moving_speed or 0
|
||||
if moving_speed > 0 then
|
||||
local yaw = (getYaw(me:getyaw()) + 90.0) * DEGTORAD
|
||||
me:setyaw(yaw + 4.73)
|
||||
self.dir = {x = math.cos(yaw), y = 0, z = math.sin(yaw)}
|
||||
if self.can_fly then
|
||||
if current_pos.y >= (def.modes["fly"].max_height or 50) and not self.target then
|
||||
self.dir.y = -0.5
|
||||
else
|
||||
self.dir.y = (math.random() - 0.5) --*0.1
|
||||
end
|
||||
end
|
||||
|
||||
-- reduce speed in water
|
||||
if self.in_water == true then
|
||||
moving_speed = moving_speed * 0.7
|
||||
end
|
||||
else
|
||||
self.dir = nullVec
|
||||
end
|
||||
|
||||
update_velocity(me, self.dir, moving_speed)
|
||||
update_animation(me, self.mode, def.model.animations)
|
||||
end
|
||||
|
||||
-- update yaw
|
||||
if self.mode ~= "attack" and self.mode ~= "follow" and
|
||||
(def.modes[self.mode].update_yaw or 0) > 0 and
|
||||
self.yawtimer > (def.modes[self.mode].update_yaw or 4) then
|
||||
self.yawtimer = 0
|
||||
local mod = nil
|
||||
if self.mode == "_run" then
|
||||
mod = me:getyaw()
|
||||
end
|
||||
local yaw = (getYaw(mod) + 90.0) * DEGTORAD
|
||||
me:setyaw(yaw + 4.73)
|
||||
local moving_speed = def.modes[self.mode].moving_speed or 0
|
||||
if moving_speed > 0 then
|
||||
self.dir = {x = math.cos(yaw), y = nil, z = math.sin(yaw)}
|
||||
update_velocity(me, self.dir, moving_speed)
|
||||
end
|
||||
end
|
||||
|
||||
--swim
|
||||
if self.can_swim and self.swimtimer > 0.8 and self.last_node then
|
||||
self.swimtimer = 0
|
||||
local name = self.last_node.name
|
||||
if name then
|
||||
if name == "default:water_source" then
|
||||
self.air_cnt = 0
|
||||
local vel = me:getvelocity()
|
||||
update_velocity(me, {x = vel.x, y = 0.9, z = vel.z}, 1)
|
||||
me:setacceleration({x = 0, y = -1.2, z = 0})
|
||||
self.in_water = true
|
||||
-- play swimming sounds
|
||||
if def.sounds and def.sounds.swim then
|
||||
local swim_snd = def.sounds.swim
|
||||
core.sound_play(swim_snd.name, {pos = current_pos, gain = swim_snd.gain or 1, max_hear_distance = swim_snd.distance or 10})
|
||||
end
|
||||
spawnParticles(current_pos, vel, "bubble.png")
|
||||
else
|
||||
self.air_cnt = self.air_cnt + 1
|
||||
if self.in_water == true and self.air_cnt > 5 then
|
||||
self.in_water = false
|
||||
if not self.can_fly then
|
||||
me:setacceleration({x = 0, y = -15, z = 0})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Add damage when drowning or in lava
|
||||
if self.env_damage and self.envtimer > 1 and self.last_node then
|
||||
self.envtimer = 0
|
||||
local name = self.last_node.name
|
||||
if not self.can_swim and name == "default:water_source" then
|
||||
changeHP(self, -1)
|
||||
elseif self.can_burn then
|
||||
if name == "fire:basic_flame" or name == "default:lava_source" then
|
||||
changeHP(self, -4)
|
||||
end
|
||||
end
|
||||
|
||||
-- add damage when light is too bright or too dark
|
||||
local tod = core.get_timeofday() * 24000
|
||||
if self.last_llvl and self.can_burn and self.last_llvl > (def.stats.light.max or 15) and tod < 18000 then
|
||||
changeHP(self, -1)
|
||||
elseif self.last_llvl and self.last_llvl < (def.stats.light.min or 0) then
|
||||
changeHP(self, -2)
|
||||
end
|
||||
end
|
||||
|
||||
-- Random sounds
|
||||
if def.sounds and def.sounds.random[self.mode] then
|
||||
local rnd_sound = def.sounds.random[self.mode]
|
||||
if not self.snd_rnd_time then
|
||||
self.snd_rnd_time = math.random((rnd_sound.time_min or 5), (rnd_sound.time_max or 35))
|
||||
end
|
||||
if rnd_sound and self.soundtimer > self.snd_rnd_time + math.random() then
|
||||
self.soundtimer = 0
|
||||
self.snd_rnd_time = nil
|
||||
core.sound_play(rnd_sound.name, {pos = current_pos, gain = rnd_sound.gain or 1, max_hear_distance = rnd_sound.distance or 30})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
creatures.get_staticdata = function(self)
|
||||
return {
|
||||
hp = self.object:get_hp(),
|
||||
mode = self.mode,
|
||||
tamed = self.tamed,
|
||||
modetimer = self.modetimer,
|
||||
lifetimer = self.lifetimer,
|
||||
soundtimer = self.soundtimer,
|
||||
fall_dist = self.fall_dist,
|
||||
in_water = self.in_water,
|
||||
}
|
||||
end
|
33
creatures/init.lua
Normal file
@ -0,0 +1,33 @@
|
||||
--= Creatures MOB-Engine (cme) =--
|
||||
-- Copyright (c) 2015 BlockMen <blockmen2015@gmail.com>
|
||||
--
|
||||
-- init.lua
|
||||
--
|
||||
-- This software is provided 'as-is', without any express or implied warranty. In no
|
||||
-- event will the authors be held liable for any damages arising from the use of
|
||||
-- this software.
|
||||
--
|
||||
-- Permission is granted to anyone to use this software for any purpose, including
|
||||
-- commercial applications, and to alter it and redistribute it freely, subject to the
|
||||
-- following restrictions:
|
||||
--
|
||||
-- 1. The origin of this software must not be misrepresented; you must not
|
||||
-- claim that you wrote the original software. If you use this software in a
|
||||
-- product, an acknowledgment in the product documentation is required.
|
||||
-- 2. Altered source versions must be plainly marked as such, and must not
|
||||
-- be misrepresented as being the original software.
|
||||
-- 3. This notice may not be removed or altered from any source distribution.
|
||||
--
|
||||
|
||||
|
||||
creatures = {}
|
||||
|
||||
local modpath = core.get_modpath("creatures")
|
||||
|
||||
-- API and common functions
|
||||
dofile(modpath .."/common.lua")
|
||||
dofile(modpath .."/functions.lua")
|
||||
dofile(modpath .."/register.lua")
|
||||
|
||||
-- Common items
|
||||
dofile(modpath .."/items.lua")
|
39
creatures/items.lua
Normal file
@ -0,0 +1,39 @@
|
||||
--= Creatures MOB-Engine (cme) =--
|
||||
-- Copyright (c) 2015 BlockMen <blockmen2015@gmail.com>
|
||||
--
|
||||
-- items.lua
|
||||
--
|
||||
-- This software is provided 'as-is', without any express or implied warranty. In no
|
||||
-- event will the authors be held liable for any damages arising from the use of
|
||||
-- this software.
|
||||
--
|
||||
-- Permission is granted to anyone to use this software for any purpose, including
|
||||
-- commercial applications, and to alter it and redistribute it freely, subject to the
|
||||
-- following restrictions:
|
||||
--
|
||||
-- 1. The origin of this software must not be misrepresented; you must not
|
||||
-- claim that you wrote the original software. If you use this software in a
|
||||
-- product, an acknowledgment in the product documentation is required.
|
||||
-- 2. Altered source versions must be plainly marked as such, and must not
|
||||
-- be misrepresented as being the original software.
|
||||
-- 3. This notice may not be removed or altered from any source distribution.
|
||||
--
|
||||
|
||||
|
||||
core.register_craftitem("creatures:flesh", {
|
||||
description = "Flesh",
|
||||
inventory_image = "creatures_flesh.png",
|
||||
on_use = core.item_eat(2),
|
||||
})
|
||||
|
||||
core.register_craftitem("creatures:meat", {
|
||||
description = "Cooked Meat",
|
||||
inventory_image = "creatures_meat.png",
|
||||
on_use = core.item_eat(4),
|
||||
})
|
||||
|
||||
core.register_craft({
|
||||
type = "cooking",
|
||||
output = "creatures:meat",
|
||||
recipe = "creatures:flesh",
|
||||
})
|
574
creatures/register.lua
Normal file
@ -0,0 +1,574 @@
|
||||
--= Creatures MOB-Engine (cme) =--
|
||||
-- Copyright (c) 2015 BlockMen <blockmen2015@gmail.com>
|
||||
--
|
||||
-- register.lua
|
||||
--
|
||||
-- This software is provided 'as-is', without any express or implied warranty. In no
|
||||
-- event will the authors be held liable for any damages arising from the use of
|
||||
-- this software.
|
||||
--
|
||||
-- Permission is granted to anyone to use this software for any purpose, including
|
||||
-- commercial applications, and to alter it and redistribute it freely, subject to the
|
||||
-- following restrictions:
|
||||
--
|
||||
-- 1. The origin of this software must not be misrepresented; you must not
|
||||
-- claim that you wrote the original software. If you use this software in a
|
||||
-- product, an acknowledgment in the product documentation is required.
|
||||
-- 2. Altered source versions must be plainly marked as such, and must not
|
||||
-- be misrepresented as being the original software.
|
||||
-- 3. This notice may not be removed or altered from any source distribution.
|
||||
--
|
||||
|
||||
|
||||
local allow_hostile = core.setting_getbool("only_peaceful_mobs") ~= true
|
||||
|
||||
local function translate_def(def)
|
||||
local new_def = {
|
||||
physical = true,
|
||||
visual = "mesh",
|
||||
stepheight = 0.6, -- ensure we get over slabs/stairs
|
||||
automatic_face_movement_dir = def.model.rotation or 0.0,
|
||||
|
||||
mesh = def.model.mesh,
|
||||
textures = def.model.textures,
|
||||
collisionbox = def.model.collisionbox or {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
|
||||
visual_size = def.model.scale or {x = 1, y = 1},
|
||||
|
||||
stats = def.stats,
|
||||
model = def.model,
|
||||
sounds = def.sounds,
|
||||
combat = def.combat,
|
||||
modes = {},
|
||||
drops = def.drops,
|
||||
}
|
||||
|
||||
-- Tanslate modes to better accessable format
|
||||
for mn,def in pairs(def.modes) do
|
||||
local name = tostring(mn)
|
||||
if name ~= "update_time" then
|
||||
new_def.modes[name] = def
|
||||
--if name == "attack" then new_def.modes[name].chance = 0 end
|
||||
end
|
||||
end
|
||||
-- insert special mode "_run" which is used when in panic
|
||||
if def.stats.can_panic then
|
||||
if def.modes.walk then
|
||||
local new = table.copy(new_def.modes["walk"])
|
||||
new.chance = 0
|
||||
new.duration = 3
|
||||
new.moving_speed = new.moving_speed * 2
|
||||
new.update_yaw = 0.7
|
||||
new_def.modes["_run"] = new
|
||||
local new_anim = table.copy(def.model.animations.walk)
|
||||
new_anim.speed = new_anim.speed * 2 --= {start = 81, stop = 100, speed = 15},
|
||||
new_def.model.animations._run = new_anim
|
||||
end
|
||||
end
|
||||
|
||||
if def.stats.can_jump and type(def.stats.can_jump) == "number" then
|
||||
if def.stats.can_jump > 0 then
|
||||
new_def.stepheight = def.stats.can_jump + 0.1
|
||||
end
|
||||
end
|
||||
|
||||
if not def.stats.can_fly or not def.stats.sneaky then
|
||||
new_def.makes_footstep_sound = true
|
||||
end
|
||||
|
||||
|
||||
new_def.get_staticdata = function(self)
|
||||
local main_tab = creatures.get_staticdata(self)
|
||||
-- is own staticdata function defined? If so, merge results
|
||||
if def.get_staticdata then
|
||||
local data = def.get_staticdata(self)
|
||||
if data and type(data) == "table" then
|
||||
for s,w in pairs(data) do
|
||||
main_tab[s] = w
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- return data serialized
|
||||
return core.serialize(main_tab)
|
||||
end
|
||||
|
||||
new_def.on_activate = function(self, staticdata)
|
||||
|
||||
-- Add everything we need as basis for the engine
|
||||
self.mob_name = def.name
|
||||
self.hp = def.stats.hp
|
||||
self.hostile = def.stats.hostile
|
||||
self.mode = ""
|
||||
self.stunned = false -- if knocked back or hit do nothing else
|
||||
|
||||
self.has_kockback = def.stats.has_kockback
|
||||
self.has_falldamage = def.stats.has_falldamage
|
||||
self.can_swim = def.stats.can_swim
|
||||
self.can_fly = def.stats.can_fly
|
||||
self.can_burn = def.stats.can_burn
|
||||
self.can_panic = def.stats.can_panic == true and def.modes.walk ~= nil
|
||||
--self.is_tamed = nil
|
||||
--self.target = nil
|
||||
self.dir = {x = 0, z = 0}
|
||||
|
||||
--self.last_pos = nil (was nullVec)
|
||||
--self.last_node = nil
|
||||
--self.last_llvl = 0
|
||||
self.fall_dist = 0
|
||||
self.air_cnt = 0
|
||||
|
||||
|
||||
-- Timers
|
||||
self.lifetimer = 0
|
||||
self.modetimer = math.random()--0
|
||||
self.soundtimer = math.random()
|
||||
self.nodetimer = 2 -- ensure we get the first step
|
||||
self.yawtimer = math.random() * 2--0
|
||||
self.followtimer = 0
|
||||
if self.can_swim then
|
||||
self.swimtimer = 2 -- ensure we get the first step
|
||||
-- self.in_water = nil
|
||||
end
|
||||
if self.hostile then
|
||||
self.attacktimer = 0
|
||||
end
|
||||
if self.hostile or def.modes.follow then
|
||||
self.searchtimer = 0
|
||||
end
|
||||
if self.can_burn or not def.stats.can_swim or self.has_falldamage then
|
||||
self.env_damage = true
|
||||
self.envtimer = 0
|
||||
end
|
||||
|
||||
-- Other things
|
||||
|
||||
|
||||
if staticdata then
|
||||
local tab = core.deserialize(staticdata)
|
||||
if tab and type(tab) == "table" then
|
||||
for s,w in pairs(tab) do
|
||||
self[tostring(s)] = w
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- check we got a valid mode
|
||||
if not new_def.modes[self.mode] or (new_def.modes[self.mode].chance or 0) <= 0 then
|
||||
self.mode = "idle"
|
||||
end
|
||||
|
||||
if not self.can_fly then
|
||||
if not self.in_water then
|
||||
self.object:setacceleration({x = 0, y = -15, z = 0})
|
||||
end
|
||||
end
|
||||
|
||||
-- check if falling and set velocity only 0 when not falling
|
||||
if self.fall_dist == 0 then
|
||||
self.object:setvelocity(nullVec)
|
||||
end
|
||||
|
||||
self.object:set_hp(self.hp)
|
||||
|
||||
if not core.setting_getbool("enable_damage") then
|
||||
self.hostile = false
|
||||
end
|
||||
|
||||
|
||||
-- call custom on_activate if defined
|
||||
if def.on_activate then
|
||||
def.on_activate(self, staticdata)
|
||||
end
|
||||
end
|
||||
|
||||
new_def.on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
|
||||
if def.on_punch and def.on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir) == true then
|
||||
return
|
||||
end
|
||||
|
||||
creatures.on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir)
|
||||
end
|
||||
|
||||
new_def.on_rightclick = function(self, clicker)
|
||||
if def.on_rightclick and def.on_rightclick(self, clicker) == true then
|
||||
return
|
||||
end
|
||||
|
||||
creatures.on_rightclick(self, clicker)
|
||||
end
|
||||
|
||||
new_def.on_step = function(self, dtime)
|
||||
if def.on_step and def.on_step(self, dtime) == true then
|
||||
return
|
||||
end
|
||||
|
||||
creatures.on_step(self, dtime)
|
||||
end
|
||||
|
||||
return new_def
|
||||
end
|
||||
|
||||
function creatures.register_mob(def) -- returns true if sucessfull
|
||||
if not def or not def.name then
|
||||
throw_error("Can't register mob. No name or Definition given.")
|
||||
return false
|
||||
end
|
||||
|
||||
local mob_def = translate_def(def)
|
||||
|
||||
core.register_entity(":" .. def.name, mob_def)
|
||||
|
||||
-- register spawn
|
||||
if def.spawning and not (def.stats.hostile and not allow_hostile) then
|
||||
local spawn_def = def.spawning
|
||||
spawn_def.mob_name = def.name
|
||||
spawn_def.mob_size = def.model.collisionbox
|
||||
if creatures.register_spawn(spawn_def) ~= true then
|
||||
throw_error("Couldn't register spawning for '" .. def.name .. "'")
|
||||
end
|
||||
|
||||
if spawn_def.spawn_egg then
|
||||
local egg_def = def.spawning.spawn_egg
|
||||
egg_def.mob_name = def.name
|
||||
egg_def.box = def.model.collisionbox
|
||||
creatures.register_egg(egg_def)
|
||||
end
|
||||
|
||||
if spawn_def.spawner then
|
||||
local spawner_def = def.spawning.spawner
|
||||
spawner_def.mob_name = def.name
|
||||
spawner_def.range = spawner_def.range or 4
|
||||
spawner_def.number = spawner_def.number or 6
|
||||
spawner_def.model = def.model
|
||||
creatures.register_spawner(spawner_def)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
local function inRange(min_max, value)
|
||||
if not value or not min_max or not min_max.min or not min_max.max then
|
||||
return false
|
||||
end
|
||||
if (value >= min_max.min and value <= min_max.max) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function checkSpace(pos, height)
|
||||
for i = 0, height do
|
||||
local n = core.get_node_or_nil({x = pos.x, y = pos.y + i, z = pos.z})
|
||||
if not n or n.name ~= "air" then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local time_taker = 0
|
||||
local function step(tick)
|
||||
core.after(tick, step, tick)
|
||||
time_taker = time_taker + tick
|
||||
end
|
||||
step(0.5)
|
||||
|
||||
local function stopABMFlood()
|
||||
if time_taker == 0 then
|
||||
return true
|
||||
end
|
||||
time_taker = 0
|
||||
end
|
||||
|
||||
local function groupSpawn(pos, mob, group, nodes, range, max_loops)
|
||||
local cnt = 0
|
||||
local cnt2 = 0
|
||||
|
||||
local nodes = core.find_nodes_in_area({x = pos.x - range, y = pos.y - range, z = pos.z - range},
|
||||
{x = pos.x + range, y = pos.y, z = pos.z + range}, nodes)
|
||||
local number = #nodes - 1
|
||||
if max_loops and type(max_loops) == "number" then
|
||||
number = max_loops
|
||||
end
|
||||
while cnt < group and cnt2 < number do
|
||||
cnt2 = cnt2 + 1
|
||||
local p = nodes[math.random(1, number)]
|
||||
p.y = p.y + 1
|
||||
if checkSpace(p, mob.size) == true then
|
||||
cnt = cnt + 1
|
||||
core.add_entity(p, mob.name)
|
||||
end
|
||||
end
|
||||
if cnt < group then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function creatures.register_spawn(spawn_def)
|
||||
if not spawn_def or not spawn_def.abm_nodes then
|
||||
throw_error("No valid definition for given.")
|
||||
return false
|
||||
end
|
||||
|
||||
if not spawn_def.abm_nodes.neighbors then
|
||||
spawn_def.abm_nodes.neighbors = {}
|
||||
end
|
||||
table.insert(spawn_def.abm_nodes.neighbors, "air")
|
||||
|
||||
core.register_abm({
|
||||
nodenames = spawn_def.abm_nodes.spawn_on,
|
||||
neighbors = spawn_def.abm_nodes.neighbors,
|
||||
interval = spawn_def.abm_interval or 44,
|
||||
chance = spawn_def.abm_chance or 7000,
|
||||
action = function(pos, node, active_object_count, active_object_count_wider)
|
||||
-- prevent abm-"feature"
|
||||
if stopABMFlood() then
|
||||
return
|
||||
end
|
||||
-- if time_taker == 0 then
|
||||
-- return
|
||||
-- end
|
||||
-- time_taker = 0
|
||||
|
||||
-- time check
|
||||
local tod = core.get_timeofday() * 24000
|
||||
if spawn_def.time_range then
|
||||
local wanted_res = false
|
||||
local range = table.copy(spawn_def.time_range)
|
||||
if range.min > range.max and range.min <= tod then
|
||||
wanted_res = true
|
||||
end
|
||||
if inRange(range, tod) == wanted_res then
|
||||
print(spawn_def.mob_name .. " - tod" .. dump(spawn_def.time_range) .. " " .. dump(tod))
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- position check
|
||||
if spawn_def.height_limit and not inRange(spawn_def.height_limit, pos.y) then
|
||||
print("height")
|
||||
return
|
||||
end
|
||||
|
||||
-- light check
|
||||
pos.y = pos.y + 1
|
||||
local llvl = core.get_node_light(pos)
|
||||
if spawn_def.light and not inRange(spawn_def.light, llvl) then
|
||||
print("light")
|
||||
return
|
||||
end
|
||||
-- creature count check
|
||||
local max
|
||||
if active_object_count_wider > (spawn_def.max_number or 1) then
|
||||
local mates_num = #creatures.findTarget(nil, pos, 16, "mate", spawn_def.mob_name, true)
|
||||
if (mates_num or 0) >= spawn_def.max_number then
|
||||
-- print("too much")
|
||||
return
|
||||
else
|
||||
max = spawn_def.max_number - mates_num
|
||||
-- return
|
||||
end
|
||||
end
|
||||
|
||||
-- ok everything seems fine, spawn creature
|
||||
local height_min = (spawn_def.mob_size[5] or 2) - (spawn_def.mob_size[2] or 0)
|
||||
height_min = math.ceil(height_min)
|
||||
|
||||
local number = 0
|
||||
if type(spawn_def.number) == "table" then
|
||||
number = math.random(spawn_def.number.min, spawn_def.number.max)
|
||||
else
|
||||
number = spawn_def.number or 1
|
||||
end
|
||||
|
||||
if max and number > max then
|
||||
number = max
|
||||
end
|
||||
|
||||
if number > 1 then
|
||||
groupSpawn(pos, {name = spawn_def.mob_name, size = height_min}, number, spawn_def.abm_nodes.spawn_on, 5)
|
||||
else
|
||||
-- space check
|
||||
if not checkSpace(pos, height_min) then
|
||||
return
|
||||
end
|
||||
core.add_entity(pos, spawn_def.mob_name)
|
||||
end
|
||||
print("spawned " .. spawn_def.mob_name .. " " .. number .. " ToD:"..tod)
|
||||
end,
|
||||
})
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function eggSpawn(itemstack, placer, pointed_thing, egg_def)
|
||||
if pointed_thing.type == "node" then
|
||||
local pos = pointed_thing.above
|
||||
pos.y = pos.y + 0.5
|
||||
local height = (egg_def.box[5] or 2) - (egg_def.box[2] or 0)
|
||||
if checkSpace(pos, height) == true then
|
||||
core.add_entity(pos, egg_def.mob_name)
|
||||
if not core.setting_getbool("creative_mode") then
|
||||
itemstack:take_item()
|
||||
end
|
||||
end
|
||||
return itemstack
|
||||
end
|
||||
end
|
||||
|
||||
function creatures.register_egg(egg_def)
|
||||
if not egg_def or not egg_def.mob_name or not egg_def.box then
|
||||
throw_error("Can't register Spawn-Egg. Not enough parameters given.")
|
||||
return false
|
||||
end
|
||||
|
||||
core.register_craftitem(":" .. egg_def.mob_name .. "_spawn_egg", {
|
||||
description = egg_def.description,
|
||||
inventory_image = egg_def.texture,
|
||||
liquids_pointable = false,
|
||||
on_place = function(itemstack, placer, pointed_thing)
|
||||
eggSpawn(itemstack, placer, pointed_thing, egg_def)
|
||||
end,
|
||||
})
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
local function makeSpawnerEntiy(mob_name, model)
|
||||
core.register_entity(":" .. mob_name .. "_spawner_dummy", {
|
||||
hp_max = 1,
|
||||
physical = false,
|
||||
collide_with_objects = false,
|
||||
collisionbox = nullVec,
|
||||
visual = "mesh",
|
||||
visual_size = {x = 0.42, y = 0.42},
|
||||
mesh = model.mesh,
|
||||
textures = model.textures,
|
||||
makes_footstep_sound = false,
|
||||
automatic_rotate = math.pi * -2.9,
|
||||
mob_name = "_" .. mob_name .. "_dummy",
|
||||
|
||||
on_activate = function(self)
|
||||
self.timer = 0
|
||||
self.object:setvelocity(nullVec)
|
||||
self.object:setacceleration(nullVec)
|
||||
self.object:set_armor_groups({immortal = 1})
|
||||
--self.object:set_bone_position("Root", nullVec, {x=45,y=0,z=0})
|
||||
end,
|
||||
|
||||
on_step = function(self, dtime)
|
||||
self.timer = self.timer + dtime
|
||||
if self.timer > 30 then
|
||||
self.timer = 0
|
||||
local n = core.get_node_or_nil(self.object:getpos())
|
||||
if n and n.name and n.name ~= mob_name .. "_spawner" then
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
local function spawnerSpawn(pos, spawner_def)
|
||||
local mates = creatures.findTarget(nil, pos, spawner_def.range, "mate", spawner_def.mob_name, true) or {}
|
||||
if #mates >= spawner_def.number then
|
||||
return false
|
||||
end
|
||||
local number_max = spawner_def.number - #mates
|
||||
|
||||
local rh = math.floor(spawner_def.range/2)
|
||||
local area = {
|
||||
min = {x = pos.x - rh, y=pos.y - rh, z = pos.z - rh},
|
||||
max = {x = pos.x + rh, y=pos.y + rh - spawner_def.height - 1, z = pos.z + rh}
|
||||
}
|
||||
|
||||
local height = area.max.y - area.min.y
|
||||
local cnt = 0
|
||||
for i = 0, height do
|
||||
if cnt >= number_max then
|
||||
break
|
||||
end
|
||||
local p = {x = math.random(area.min.x, area.max.x), y = area.min.y + i, z = math.random(area.min.z, area.max.z)}
|
||||
local n = core.get_node_or_nil(p)
|
||||
if n and n.name then
|
||||
local walkable = core.registered_nodes[n.name].walkable or false
|
||||
p.y = p.y + 1
|
||||
if walkable and checkSpace(p, spawner_def.height) == true then
|
||||
cnt = cnt + 1
|
||||
core.add_entity(p, spawner_def.mob_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local spawner_timers = {}
|
||||
function creatures.register_spawner(spawner_def)
|
||||
if not spawner_def or not spawner_def.mob_name or not spawner_def.model then
|
||||
throw_error("Can't register Spawn-Egg. Not enough parameters given.")
|
||||
return false
|
||||
end
|
||||
|
||||
makeSpawnerEntiy(spawner_def.mob_name, spawner_def.model)
|
||||
|
||||
core.register_node(":" .. spawner_def.mob_name .. "_spawner", {
|
||||
description = spawner_def.mob_name .. " spawner",
|
||||
paramtype = "light",
|
||||
tiles = {"creatures_spawner.png"},
|
||||
is_ground_content = true,
|
||||
drawtype = "glasslike",
|
||||
groups = {cracky = 1, level = 1},
|
||||
drop = "",
|
||||
on_construct = function(pos)
|
||||
pos.y = pos.y - 0.3
|
||||
core.add_entity(pos, spawner_def.mob_name .. "_spawner_dummy")
|
||||
end,
|
||||
on_destruct = function(pos)
|
||||
for _,obj in ipairs(core.get_objects_inside_radius(pos, 1)) do
|
||||
local entity = obj:get_luaentity()
|
||||
if obj and entity and entity.mob_name == "_" .. spawner_def.mob_name .. "_dummy" then
|
||||
obj:remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
local box = spawner_def.model.collisionbox
|
||||
local height = (box[5] or 2) - (box[2] or 0)
|
||||
spawner_def.height = height
|
||||
|
||||
if spawner_def.player_range and type(spawner_def.player_range) == "number" then
|
||||
core.register_abm({
|
||||
nodenames = {spawner_def.mob_name .. "_spawner"},
|
||||
interval = 2,
|
||||
chance = 1,
|
||||
action = function(pos)
|
||||
local id = core.pos_to_string(pos)
|
||||
if not spawner_timers[id] then
|
||||
spawner_timers[id] = os.time()
|
||||
end
|
||||
local time_from_last_call = os.time() - spawner_timers[id]
|
||||
local mobs,player_near = creatures.findTarget(nil, pos, spawner_def.player_range, "player", nil, true, true)
|
||||
if player_near == true and time_from_last_call > 10 and (math.random(1, 5) == 1 or (time_from_last_call ) > 27) then
|
||||
spawner_timers[id] = os.time()
|
||||
|
||||
spawnerSpawn(pos, spawner_def)
|
||||
end
|
||||
end
|
||||
})
|
||||
else
|
||||
core.register_abm({
|
||||
nodenames = {spawner_def.mob_name .. "_spawner"},
|
||||
interval = 10,
|
||||
chance = 3,
|
||||
action = function(pos)
|
||||
|
||||
spawnerSpawn(pos, spawner_def)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
Before Width: | Height: | Size: 246 KiB After Width: | Height: | Size: 246 KiB |
BIN
creatures/sounds/creatures_splash.1.ogg
Normal file
BIN
creatures/sounds/creatures_splash.2.ogg
Normal file
BIN
creatures/textures/creatures_flesh.png
Normal file
After Width: | Height: | Size: 441 B |
BIN
creatures/textures/creatures_meat.png
Normal file
After Width: | Height: | Size: 478 B |
BIN
creatures/textures/creatures_spawn_egg.png
Normal file
After Width: | Height: | Size: 686 B |
Before Width: | Height: | Size: 453 B After Width: | Height: | Size: 453 B |
@ -1 +0,0 @@
|
||||
This mod adds several creatures to Minetest, including zombies, ghosts, and sheep.
|
290
ghost.lua
@ -1,290 +0,0 @@
|
||||
local g_chillaxin_speed = 2
|
||||
local g_animation_speed = 10
|
||||
local g_mesh = "creatures_mob.x"
|
||||
local g_texture = {"creatures_ghost.png"}
|
||||
local g_hp = 12
|
||||
local g_drop = ""
|
||||
local g_player_radius = 14
|
||||
local g_hit_radius = 1
|
||||
creatures.g_ll = 7
|
||||
|
||||
local g_sound_normal = "creatures_ghost"
|
||||
local g_sound_hit = "creatures_ghost_hit"
|
||||
local g_sound_dead = "creatures_ghost_death"
|
||||
|
||||
creatures.g_spawn_nodes = {"default:dirt_with_grass","default:desert_sand"}
|
||||
|
||||
local function g_get_animations()
|
||||
return {
|
||||
walk_START = 168,
|
||||
walk_END = 187
|
||||
}
|
||||
end
|
||||
|
||||
function g_hit(self)
|
||||
local sound = g_sound_hit
|
||||
if self.object:get_hp() < 1 then sound = g_sound_dead end
|
||||
minetest.sound_play(sound, {pos = self.object:getpos(), max_hear_distance = 10, loop = false, gain = 0.4})
|
||||
self.object:settexturemod("^[colorize:#c4000099")
|
||||
self.can_punch = false
|
||||
minetest.after(0.4, function()
|
||||
self.can_punch = true
|
||||
self.object:settexturemod("")
|
||||
end)
|
||||
end
|
||||
|
||||
function g_init_visuals(self)
|
||||
prop = {
|
||||
mesh = g_mesh,
|
||||
textures = g_texture,
|
||||
}
|
||||
self.object:set_properties(prop)
|
||||
end
|
||||
|
||||
GHOST_DEF = {
|
||||
physical = true,
|
||||
collisionbox = {-0.3, -0.5, -0.3, 0.3, 0.75, 0.3},
|
||||
visual = "mesh",
|
||||
visual_size = {x=1, y=1},
|
||||
mesh = g_mesh,
|
||||
textures = g_texture,
|
||||
makes_footstep_sound = false,
|
||||
npc_anim = 0,
|
||||
timer = 0,
|
||||
turn_timer = 0,
|
||||
vec = 0,
|
||||
yaw = 0,
|
||||
yawwer = 0,
|
||||
dist = 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 = "ghost"
|
||||
}
|
||||
|
||||
|
||||
GHOST_DEF.on_activate = function(self)
|
||||
g_init_visuals(self)
|
||||
self.anim = g_get_animations()
|
||||
self.object:set_animation({x=self.anim.stand_START,y=self.anim.stand_END}, g_animation_speed, 0)
|
||||
self.npc_anim = ANIM_STAND
|
||||
self.object:setacceleration({x=0,y=0,z=0})
|
||||
self.state = 1
|
||||
self.object:set_hp(g_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.dist = math.random(0,1)
|
||||
end
|
||||
|
||||
GHOST_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
|
||||
g_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*g_chillaxin_speed,y=0,z=dir.z*g_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
|
||||
local obj = minetest.env:add_item(my_pos, g_drop.." "..math.random(0,3))
|
||||
end
|
||||
end
|
||||
|
||||
GHOST_DEF.on_step = function(self, dtime)
|
||||
if self.dead then return end
|
||||
self.timer = self.timer + 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(current_pos)
|
||||
if self.time_passed == nil then
|
||||
self.time_passed = 0
|
||||
end
|
||||
|
||||
-- death
|
||||
if self.object:get_hp() < 1 then
|
||||
self.object:set_hp(0)
|
||||
self.attacker = ""
|
||||
self.state = 0
|
||||
self.dead = true
|
||||
minetest.sound_play(g_sound_dead, {pos = current_pos, max_hear_distance = 10 , gain = 0.5})
|
||||
self.object:set_animation({x=self.anim.lay_START,y=self.anim.lay_END}, g_animation_speed, 0)
|
||||
minetest.after(1, function()
|
||||
self.object:remove()
|
||||
local obj = minetest.env:add_item(current_pos, g_drop.." "..math.random(0,3))
|
||||
end)
|
||||
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)
|
||||
g_hit(self)
|
||||
end
|
||||
else
|
||||
self.time_passed = 0
|
||||
end
|
||||
|
||||
-- update moving state every 1 or 2 seconds
|
||||
if self.state < 3 then
|
||||
if self.timer > 0.2 then
|
||||
if self.attacker == "" then
|
||||
self.state = math.random(1,2)
|
||||
else self.state = 1 end
|
||||
self.timer = 0
|
||||
end
|
||||
end
|
||||
|
||||
-- play random sound
|
||||
if self.sound_timer > math.random(5,35) then
|
||||
minetest.sound_play(g_sound_normal, {pos = current_pos, max_hear_distance = 10, gain = 0.6})
|
||||
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*g_chillaxin_speed,y=0,z=self.direction.z*g_chillaxin_speed})
|
||||
self.state = 2
|
||||
elseif self.state == 8 then
|
||||
self.object:setvelocity({x=0,y=0,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, g_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 = 0, 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 = g_get_animations()
|
||||
self.object:set_animation({x=self.anim.stand_START,y=self.anim.stand_END}, g_animation_speed, 0)
|
||||
self.npc_anim = creatures.ANIM_STAND
|
||||
end
|
||||
if self.attacker ~= "" then
|
||||
self.direction = {x = math.sin(self.yaw)*-1, y = 0, z = math.cos(self.yaw)}
|
||||
self.state = 2
|
||||
end
|
||||
end
|
||||
|
||||
-- WALKING
|
||||
if self.state == 2 then
|
||||
|
||||
if self.direction ~= nil then
|
||||
self.object:setvelocity({x=self.direction.x*g_chillaxin_speed,y=self.object:getvelocity().y,z=self.direction.z*g_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 = 0, z = math.cos(self.yaw)}
|
||||
end
|
||||
if self.npc_anim ~= creatures.ANIM_WALK then
|
||||
self.anim = g_get_animations()
|
||||
self.object:set_animation({x=self.anim.walk_START,y=self.anim.walk_END}, g_animation_speed, 0)
|
||||
self.npc_anim = creatures.ANIM_WALK
|
||||
end
|
||||
--jump
|
||||
if self.direction ~= nil and self.attacker ~= "" then
|
||||
if self.jump_timer > 0.1 then
|
||||
self.jump_timer = 0
|
||||
local p1 = current_pos
|
||||
if not p1 then return end
|
||||
local me_y = math.floor(p1.y)
|
||||
local p2 = self.attacker:getpos()
|
||||
if not p2 then return end
|
||||
local p_y = math.floor(p2.y+3-self.dist)
|
||||
if me_y < p_y then
|
||||
self.object:setvelocity({x=self.object:getvelocity().x,y=1*g_chillaxin_speed,z=self.object:getvelocity().z})
|
||||
elseif me_y > p_y then
|
||||
self.object:setvelocity({x=self.object:getvelocity().x,y=-1*g_chillaxin_speed,z=self.object:getvelocity().z})
|
||||
else
|
||||
self.object:setvelocity({x=self.object:getvelocity().x,y=0,z=self.object:getvelocity().z})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self.attacker == "" then
|
||||
self.object:setvelocity({x=self.object:getvelocity().x,y=0,z=self.object:getvelocity().z})
|
||||
end
|
||||
|
||||
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
|
||||
p.y = p.y+3-self.dist
|
||||
self.direction = {x = math.sin(self.yaw)*-1, y = 0, z = math.cos(self.yaw)}
|
||||
if (s ~= nil and p ~= nil) then
|
||||
local dist = ((p.x-s.x)^2 + (p.y-s.y)^2 + (p.z-s.z)^0.5)^2
|
||||
creatures.attack(self, current_pos, attacker_pos, dist, g_hit_radius)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_entity("creatures:ghost", GHOST_DEF)
|
209
init.lua
@ -1,209 +0,0 @@
|
||||
creatures = {}
|
||||
|
||||
-- Max number of mobs per mapblock
|
||||
creatures.zombie_max = 3
|
||||
creatures.ghost_max = 1
|
||||
creatures.sheep_max = 4
|
||||
|
||||
creatures.ANIM_STAND = 1
|
||||
creatures.ANIM_SIT = 2
|
||||
creatures.ANIM_LAY = 3
|
||||
creatures.ANIM_WALK = 4
|
||||
creatures.ANIM_EAT = 5
|
||||
creatures.ANIM_RUN = 6
|
||||
|
||||
-- Drop items (when not killed by player) only if items get removed
|
||||
creatures.drop_on_death = false
|
||||
local remove_items = minetest.setting_get("remove_items")
|
||||
local remove_items_2 = minetest.setting_get("item_entity_ttl")
|
||||
if minetest.get_modpath("builtin_item") ~= nil and ((remove_items ~= nil and tonumber(remove_items) > 0) or
|
||||
(remove_items_2 ~= nil and tonumber(remove_items_2) > 0)) then
|
||||
creatures.drop_on_death = true
|
||||
end
|
||||
|
||||
-- spawning controls (experimental)
|
||||
creatures.spawned = {}
|
||||
local spawn_day = 600
|
||||
local tod = 0
|
||||
local absolute_mob_max = 50
|
||||
|
||||
local function timer(tick)
|
||||
tod = tod + 1
|
||||
if tod > spawn_day then
|
||||
tod = 0
|
||||
creatures.spawned = nil
|
||||
creatures.spawned = {}
|
||||
end
|
||||
minetest.after(tick, timer, tick)
|
||||
end
|
||||
|
||||
timer(1)
|
||||
|
||||
|
||||
local tool_uses = {0, 30, 110, 150, 280, 300, 500, 1000}
|
||||
|
||||
-- helping functions
|
||||
|
||||
function creatures.spawn(pos, number, mob, limit, range, abs_max)
|
||||
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
|
||||
if abs_max == nil then abs_max = absolute_mob_max end
|
||||
local m_name = string.sub(mob,11)
|
||||
local spawned = creatures.spawned[m_name]
|
||||
if not spawned then spawned = 0 end
|
||||
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 and spawned+i < abs_max then
|
||||
local obj = minetest.add_entity(p, mob)
|
||||
if obj then
|
||||
creatures.spawned[m_name] = spawned + 1
|
||||
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.add_item(p, {name=item.name})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function creatures.find_mates(pos, name, radius)
|
||||
local player_near = false
|
||||
local mobs = 0
|
||||
local res = false
|
||||
for _,obj in ipairs(minetest.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
|
||||
res = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return res,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*2.2,y=jump_y,z=self.object:getvelocity().z*2.2})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function creatures.follow(self, items, radius)
|
||||
local current_pos = self.object:getpos()
|
||||
-- seach for players
|
||||
for _,object in ipairs(minetest.env:get_objects_inside_radius(current_pos, radius)) do
|
||||
if object:is_player() then
|
||||
local item = object:get_wielded_item()
|
||||
local item_name = item:get_name()
|
||||
if item and item_name and item_name ~= "" then
|
||||
local quit = true
|
||||
for _,food in ipairs(items) do
|
||||
if food.name == item_name then
|
||||
quit = false
|
||||
end
|
||||
end
|
||||
if quit then return end
|
||||
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.feeder = object
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- hostile mobs
|
||||
dofile(minetest.get_modpath("creatures").."/ghost.lua")
|
||||
dofile(minetest.get_modpath("creatures").."/zombie.lua")
|
||||
|
||||
-- friendly mobs
|
||||
dofile(minetest.get_modpath("creatures").."/sheep.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")
|
91
items.lua
@ -1,91 +0,0 @@
|
||||
-- shears
|
||||
minetest.register_tool("creatures:shears", {
|
||||
description = "Shears",
|
||||
inventory_image = "creatures_shears.png",
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = 'creatures:shears',
|
||||
recipe = {
|
||||
{'','default:steel_ingot'},
|
||||
{'default:steel_ingot','default:stick'},
|
||||
}
|
||||
})
|
||||
|
||||
-- drop items
|
||||
minetest.register_craftitem("creatures:flesh", {
|
||||
description = "Flesh",
|
||||
inventory_image = "creatures_flesh.png",
|
||||
on_use = minetest.item_eat(2),
|
||||
})
|
||||
|
||||
minetest.register_craftitem("creatures:rotten_flesh", {
|
||||
description = "Rotten Flesh",
|
||||
inventory_image = "creatures_rotten_flesh.png",
|
||||
on_use = minetest.item_eat(1),
|
||||
})
|
||||
|
||||
-- food
|
||||
minetest.register_craftitem("creatures:meat", {
|
||||
description = "Cooked Meat",
|
||||
inventory_image = "creatures_meat.png",
|
||||
on_use = minetest.item_eat(4),
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
type = "cooking",
|
||||
output = "creatures:meat",
|
||||
recipe = "creatures:flesh",
|
||||
})
|
||||
|
||||
-- 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,
|
||||
|
||||
})
|
||||
|
||||
minetest.register_craftitem("creatures:ghost_spawn_egg", {
|
||||
description = "Ghost spawn-egg",
|
||||
inventory_image = "creatures_egg_ghost.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+0.5
|
||||
creatures.spawn(p, 1, "creatures:ghost", 1, 1)
|
||||
if not minetest.setting_getbool("creative_mode") then itemstack:take_item() end
|
||||
return itemstack
|
||||
end
|
||||
end,
|
||||
|
||||
})
|
||||
|
||||
minetest.register_craftitem("creatures:sheep_spawn_egg", {
|
||||
description = "Sheep spawn-egg",
|
||||
inventory_image = "creatures_egg_sheep.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+0.5
|
||||
creatures.spawn(p, 1, "creatures:sheep", 1, 1)
|
||||
if not minetest.setting_getbool("creative_mode") then itemstack:take_item() end
|
||||
return itemstack
|
||||
end
|
||||
end,
|
||||
|
||||
})
|
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 3.1 KiB |
0
modpack.txt
Normal file
394
sheep.lua
@ -1,394 +0,0 @@
|
||||
local s_chillaxin_speed = 1.5
|
||||
local s_animation_speed = 15
|
||||
local s_mesh = "creatures_sheep.x"
|
||||
local s_texture = {"creatures_sheep.png"}
|
||||
local s_hp = 8
|
||||
local s_life_max = 80 --~5min
|
||||
local s_drop = "wool:white"
|
||||
local s_drop2 = "creatures:flesh"
|
||||
|
||||
local s_player_radius = 14
|
||||
|
||||
local s_sound_normal = "creatures_sheep"
|
||||
local s_sound_hit = "creatures_sheep"
|
||||
local s_sound_dead = "creatures_sheep"
|
||||
local s_sound_shears = "creatures_shears"
|
||||
|
||||
creatures.s_spawn_nodes = {"default:dirt_with_grass"}
|
||||
|
||||
local function s_get_animations()
|
||||
return {
|
||||
stand_START = 0,
|
||||
stand_END = 80,
|
||||
walk_START = 81,
|
||||
walk_END = 100,
|
||||
eat_START = 107,
|
||||
eat_END = 185
|
||||
}
|
||||
end
|
||||
|
||||
local function eat_cn(self, pos)
|
||||
self.object:setvelocity({x=0,y=-20,z=0})
|
||||
local p = {x=pos.x,y=pos.y-1,z=pos.z}
|
||||
local n = minetest.get_node(p) or nil
|
||||
if n and n.name and n.name == "default:dirt_with_grass" then
|
||||
self.object:set_animation({x=self.anim.eat_START,y=self.anim.eat_END}, s_animation_speed, 0)
|
||||
self.npc_anim = creatures.ANIM_EAT
|
||||
self.timer = 0
|
||||
minetest.after(3.5, function()
|
||||
self.has_wool = true
|
||||
self.state = 1
|
||||
s_update_visuals(self)
|
||||
minetest.set_node(p,{name="default:dirt"})
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function s_hit(self)
|
||||
local sound = s_sound_hit
|
||||
if self.object:get_hp() < 1 then
|
||||
sound = s_sound_dead
|
||||
end
|
||||
minetest.sound_play(sound, {pos = self.object:getpos(), max_hear_distance = 10, loop = false, gain = 0.4})
|
||||
s_update_visuals(self, "^[colorize:#c4000099")
|
||||
self.can_punch = false
|
||||
minetest.after(0.4, function()
|
||||
s_update_visuals(self)
|
||||
end)
|
||||
end
|
||||
|
||||
function s_update_visuals(self, hit)
|
||||
self.txture = {"creatures_sheep.png" .. (hit or "")}
|
||||
if not self.has_wool then
|
||||
self.txture = {"creatures_sheep_shaved.png" .. (hit or "")}
|
||||
end
|
||||
local prop = {
|
||||
mesh = s_mesh,
|
||||
textures = self.txture,
|
||||
}
|
||||
self.object:set_properties(prop)
|
||||
end
|
||||
|
||||
SHEEP_DEF = {
|
||||
physical = true,
|
||||
collisionbox = {-0.4, -0.01, -0.6, 0.4, 0.9, 0.4},
|
||||
visual = "mesh",
|
||||
visual_size = {x=1, y=1},
|
||||
mesh = s_mesh,
|
||||
textures = s_texture,
|
||||
txture = s_texture,
|
||||
makes_footstep_sound = true,
|
||||
npc_anim = 0,
|
||||
lifetime = 0,
|
||||
timer = 0,
|
||||
turn_timer = 0,
|
||||
vec = 0,
|
||||
yaw = 0,
|
||||
yawwer = 0,
|
||||
has_wool = true,
|
||||
state = 1,
|
||||
can_punch = true,
|
||||
dead = false,
|
||||
jump_timer = 0,
|
||||
last_pos = {x=0,y=0,z=0},
|
||||
punch_timer = 0,
|
||||
sound_timer = 0,
|
||||
feeder = "",
|
||||
mob_name = "sheep"
|
||||
}
|
||||
|
||||
SHEEP_DEF.get_staticdata = function(self)
|
||||
return minetest.serialize({
|
||||
itemstring = self.itemstring,
|
||||
timer = self.timer,
|
||||
txture = self.txture,
|
||||
has_wool = self.has_wool,
|
||||
lifetime = self.lifetime,
|
||||
})
|
||||
end
|
||||
|
||||
SHEEP_DEF.on_activate = function(self, staticdata, dtime_s)
|
||||
self.txture = s_texture
|
||||
s_update_visuals(self)
|
||||
self.anim = s_get_animations()
|
||||
self.object:set_animation({x=self.anim.stand_START,y=self.anim.stand_END}, s_animation_speed, 0)
|
||||
self.npc_anim = ANIM_STAND
|
||||
self.object:setacceleration({x=0,y=-20,z=0})
|
||||
self.object:setyaw(self.object:getyaw()+((math.random(0,90)-45)/45*math.pi))
|
||||
self.lastpos = self.object:getpos()
|
||||
self.state = 1
|
||||
self.object:set_hp(s_hp)
|
||||
self.object:set_armor_groups({fleshy=130})
|
||||
self.can_punch = true
|
||||
self.dead = false
|
||||
self.has_wool = true
|
||||
self.lifetime = 0
|
||||
self.feeder = ""
|
||||
if staticdata then
|
||||
local tmp = minetest.deserialize(staticdata)
|
||||
if tmp and tmp.timer then
|
||||
self.timer = tmp.timer
|
||||
end
|
||||
if tmp and tmp.has_wool ~= nil then
|
||||
self.has_wool = tmp.has_wool
|
||||
end
|
||||
if tmp and tmp.lifetime ~= nil then
|
||||
self.lifetime = tmp.lifetime
|
||||
end
|
||||
if not self.has_wool then
|
||||
s_update_visuals(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
SHEEP_DEF.on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
|
||||
if not self.can_punch then return end
|
||||
|
||||
self.feeder = ""
|
||||
self.timer = 0
|
||||
|
||||
if puncher ~= nil then
|
||||
if time_from_last_punch >= 0.2 then --0.45
|
||||
s_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*2.5,y=5.2,z=dir.z*2.5})
|
||||
self.state = 9
|
||||
-- add wear to sword/tool
|
||||
creatures.add_wear(puncher, tool_capabilities)
|
||||
end
|
||||
|
||||
local my_pos = self.object:getpos()
|
||||
my_pos.y = my_pos.y + 0.4
|
||||
|
||||
-- drop 1-2 whool when punched
|
||||
if self.has_wool then
|
||||
self.has_wool = false
|
||||
creatures.drop(my_pos, {{name=s_drop, count=math.random(1,2)}}, dir)
|
||||
end
|
||||
if self.object:get_hp() < 1 then
|
||||
creatures.drop(my_pos, {{name=s_drop2, count=1}}, dir)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
SHEEP_DEF.on_rightclick = function(self, clicker)
|
||||
if not clicker or not self.has_wool then
|
||||
return
|
||||
end
|
||||
|
||||
local item = clicker:get_wielded_item()
|
||||
local name = item:get_name()
|
||||
if item and name and name == "creatures:shears" then
|
||||
local my_pos = self.object:getpos()
|
||||
minetest.sound_play(s_sound_shears, {pos = my_pos, max_hear_distance = 10, gain = 1})
|
||||
my_pos.y = my_pos.y + 0.4
|
||||
self.has_wool = false
|
||||
s_update_visuals(self)
|
||||
creatures.drop(my_pos, {{name=s_drop, count=2}})
|
||||
if not minetest.setting_getbool("creative_mode") then
|
||||
item:add_wear(65535/100)
|
||||
clicker:set_wielded_item(item)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
SHEEP_DEF.on_step = function(self, dtime)
|
||||
if self.dead then return end
|
||||
if self.lifetime == nil then self.lifetime = 0 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.sound_timer = self.sound_timer + 0.01
|
||||
|
||||
local current_pos = self.object:getpos()
|
||||
local current_node = minetest.env:get_node(current_pos)
|
||||
|
||||
-- death
|
||||
if self.object:get_hp() < 1 then
|
||||
self.object:setvelocity({x=0,y=-20,z=0})
|
||||
self.object:set_hp(0)
|
||||
self.state = 0
|
||||
self.dead = true
|
||||
minetest.sound_play(s_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}, s_animation_speed, 0)
|
||||
minetest.after(0.5, function()
|
||||
if creatures.drop_on_death then
|
||||
local drop = {{name=s_drop2, count=1}}
|
||||
if self.has_wool then
|
||||
drop[2] = {name=s_drop, count=math.random(1,2)}
|
||||
end
|
||||
creatures.drop(current_pos, drop, dir)
|
||||
end
|
||||
self.object:remove()
|
||||
end)
|
||||
end
|
||||
|
||||
-- die if old and alone
|
||||
if self.lifetime > s_life_max then
|
||||
if creatures.find_mates(current_pos, "sheep", 15) then
|
||||
self.lifetime = 0
|
||||
else
|
||||
self.object:set_hp(0)
|
||||
self.state = 0
|
||||
self.dead = true
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- die when in water, lava
|
||||
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" then
|
||||
self.sound_timer = self.sound_timer + 0.1
|
||||
if self.sound_timer >= 0.8 then
|
||||
local damage = 2
|
||||
self.sound_timer = 0
|
||||
self.object:set_hp(self.object:get_hp()-damage)
|
||||
s_hit(self)
|
||||
end
|
||||
end
|
||||
|
||||
if self.state < 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- update moving state depending on current state
|
||||
if self.state < 4 then
|
||||
if self.timer > 4/self.state then
|
||||
self.timer = 0
|
||||
--local new = math.random(1,3)
|
||||
--if self.state == 3 then new = 1 end
|
||||
--if self.feeder == "" then new = 5 end
|
||||
self.state = 5--new
|
||||
--s_update_visuals(self)
|
||||
end
|
||||
elseif self.state == 4 and self.timer > 1.5 then
|
||||
self.state = 2
|
||||
self.timer = 0
|
||||
elseif self.state == 5 then
|
||||
local new = math.random(1,3)
|
||||
if self.state == 3 then new = 1 end
|
||||
if self.feeder ~= "" then new = 5 end
|
||||
self.state = new
|
||||
self.timer = 0
|
||||
end
|
||||
|
||||
-- play random sound
|
||||
local num = tonumber(self.lifetime/2) or 35
|
||||
if num < 6 then num = 6 end
|
||||
if self.sound_timer > self.timer + math.random(5, num) then
|
||||
minetest.sound_play(s_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*s_chillaxin_speed,y=-20,z=self.direction.z*s_chillaxin_speed})
|
||||
self.state = 4
|
||||
self.punch_timer = 0
|
||||
elseif self.state == 8 then
|
||||
self.object:setvelocity({x=0,y=-20,z=0})
|
||||
self.state = 1
|
||||
end
|
||||
self.can_punch = true
|
||||
end
|
||||
end
|
||||
|
||||
--STANDING
|
||||
if self.state == 1 then
|
||||
self.yawwer = true
|
||||
if self.turn_timer > math.random(1,4) then
|
||||
local last = self.yaw
|
||||
self.yaw = last + math.random(-0.5,1)
|
||||
if self.yaw > 22 or self.yaw < -17 then self.yaw = 0 end
|
||||
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 = s_get_animations()
|
||||
self.object:set_animation({x=self.anim.stand_START,y=self.anim.stand_END}, s_animation_speed, 0)
|
||||
self.npc_anim = creatures.ANIM_STAND
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- stop walking when not moving
|
||||
if self.state == 2 and creatures.compare_pos(self.object:getpos(),self.lastpos) and self.jump_timer <= 0.2 then
|
||||
self.state = 1
|
||||
end
|
||||
|
||||
-- CHECK FEEDER
|
||||
if self.state == 5 then
|
||||
self.feeder = ""
|
||||
creatures.follow(self, {{name="farming:wheat"}}, 8)
|
||||
if self.feeder ~= "" then
|
||||
self.direction = {x = math.sin(self.yaw)*-1, y = -20, z = math.cos(self.yaw)}
|
||||
self.state = 2
|
||||
else
|
||||
local new = math.random(1,3)
|
||||
if self.state == 3 then new = 1 end
|
||||
self.state = new
|
||||
end
|
||||
end
|
||||
|
||||
-- WALKING
|
||||
if self.state == 2 or self.state == 4 then
|
||||
self.lastpos = self.object:getpos()
|
||||
local speed = 1
|
||||
local anim = creatures.ANIM_WALK
|
||||
if self.state == 4 then
|
||||
speed = 2.2
|
||||
anim = creatures.ANIM_RUN
|
||||
end
|
||||
if self.feeder ~= "" then
|
||||
--use this for following weed, etc
|
||||
--self.direction = {x = math.sin(self.yaw)*-1, y = -20, z = math.cos(self.yaw)}
|
||||
self.state = 5
|
||||
end
|
||||
|
||||
if self.direction ~= nil then
|
||||
self.object:setvelocity({x=self.direction.x*s_chillaxin_speed*speed,y=self.object:getvelocity().y,z=self.direction.z*s_chillaxin_speed*speed})
|
||||
end
|
||||
if (self.turn_timer > math.random(0.8,2)) or (self.state == 4 and self.turn_timer > 0.2) then
|
||||
if self.state == 2 then
|
||||
local last = self.yaw
|
||||
self.yaw = last + math.random(-1,0.5)
|
||||
if self.yaw > 22 or self.yaw < -17 then self.yaw = 0 end
|
||||
else
|
||||
self.yaw = 360 * math.random()
|
||||
end
|
||||
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 ~= anim then
|
||||
self.object:set_animation({x=self.anim.walk_START,y=self.anim.walk_END}, s_animation_speed*speed, 0)
|
||||
self.npc_anim = anim
|
||||
end
|
||||
--jump
|
||||
creatures.jump(self, current_pos, 7.7, 0.2)
|
||||
end
|
||||
|
||||
-- EATING
|
||||
if self.state == 3 then
|
||||
self.state = -1 -- deactivate most while eating
|
||||
eat_cn(self, current_pos)
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_entity("creatures:sheep", SHEEP_DEF)
|
101
spawn.lua
@ -1,101 +0,0 @@
|
||||
local max_mobs_sum = creatures.zombie_max + creatures.ghost_max + creatures.sheep_max - 1
|
||||
-- hostile mobs
|
||||
if not minetest.setting_getbool("only_peaceful_mobs") then
|
||||
-- zombie
|
||||
minetest.register_abm({
|
||||
nodenames = creatures.z_spawn_nodes,
|
||||
neighbors = {"air"},
|
||||
interval = 40.0,
|
||||
chance = 7600,
|
||||
action = function(pos, node, active_object_count, active_object_count_wider)
|
||||
-- check per mapblock max (max per creature is done by .spawn())
|
||||
if active_object_count_wider > max_mobs_sum then
|
||||
return
|
||||
end
|
||||
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})
|
||||
-- ghost
|
||||
minetest.register_abm({
|
||||
nodenames = creatures.g_spawn_nodes,
|
||||
neighbors = {"air"},
|
||||
interval = 44.0,
|
||||
chance = 8350,
|
||||
action = function(pos, node, active_object_count, active_object_count_wider)
|
||||
-- check per mapblock max (max per creature is done by .spawn())
|
||||
if active_object_count_wider > max_mobs_sum then
|
||||
return
|
||||
end
|
||||
if pos.y < 0 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.g_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
|
||||
if (wtime > 0.2 and wtime < 0.805) then
|
||||
return
|
||||
end
|
||||
creatures.spawn(pos, 1, "creatures:ghost", 2, 35)
|
||||
end})
|
||||
|
||||
end
|
||||
|
||||
-- peaceful
|
||||
minetest.register_abm({
|
||||
nodenames = creatures.s_spawn_nodes,
|
||||
neighbors = {"air"},
|
||||
interval = 55.0,
|
||||
chance = 7800,
|
||||
action = function(pos, node, active_object_count, active_object_count_wider)
|
||||
-- check per mapblock max (max per creature is done by .spawn())
|
||||
if active_object_count_wider > max_mobs_sum then
|
||||
return
|
||||
end
|
||||
if pos.y < 0 then return end
|
||||
pos.y = pos.y+1
|
||||
local ll = minetest.env:get_node_light(pos) or 0
|
||||
if ll < 14 then return end
|
||||
local wtime = minetest.env:get_timeofday()
|
||||
if minetest.env:get_node(pos).name ~= "air" then
|
||||
return
|
||||
end
|
||||
if (wtime < 0.2 and wtime > 0.805) then
|
||||
return
|
||||
end
|
||||
if math.random(1,10) > 8 then return end
|
||||
creatures.spawn(pos, 1, "creatures:sheep", 4, 50, 30)
|
||||
end})
|
94
spawners.lua
@ -1,94 +0,0 @@
|
||||
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)
|
||||
creatures.register_spawner("sheep",{x=0.42,y=0.42},-0.3,"creatures_sheep.x",{"creatures_sheep.png"},17,6,18,10,true)
|
Before Width: | Height: | Size: 718 B |
Before Width: | Height: | Size: 675 B |
Before Width: | Height: | Size: 714 B |
Before Width: | Height: | Size: 351 B |
Before Width: | Height: | Size: 404 B |
Before Width: | Height: | Size: 433 B |
Before Width: | Height: | Size: 282 B |
340
zombie.lua
@ -1,340 +0,0 @@
|
||||
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
|
||||
local 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})
|
||||
self.object:settexturemod("^[colorize:#c4000099")
|
||||
self.can_punch = false
|
||||
minetest.after(0.4, function()
|
||||
self.can_punch = true
|
||||
self.object:settexturemod("")
|
||||
end)
|
||||
end
|
||||
|
||||
function z_init_visuals(self)
|
||||
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_init_visuals(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 and creatures.drop_on_death 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.4, 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)
|