initial commit

master
Izzy 2016-01-19 19:21:22 -08:00
parent 87ad5d3e55
commit 35a60019a1
16 changed files with 3541 additions and 0 deletions

View File

@ -1,2 +1,7 @@
# minetest_giants
A giant-themed mobs mod based on behavior trees
Based on Mobs (Redo) by PilzAdam, KrupnovPavel, Zeg9 and TenPlus1
https://forum.minetest.net/viewtopic.php?f=9&t=9917

2408
api.lua Normal file

File diff suppressed because it is too large Load Diff

372
behavior.lua Normal file
View File

@ -0,0 +1,372 @@
bt = {
reset={},
tick={},
}
function tprint (tbl, indent)
local formatting = ""
if not indent then indent = 0 end
if tbl == nil then
print(formatting .. "nil")
return
end
for k, v in pairs(tbl) do
formatting = string.rep(" ", indent) .. k .. ": "
if type(v) == "table" then
print(formatting)
tprint(v, indent+1)
elseif type(v) == 'boolean' then
print(formatting .. tostring(v))
elseif type(v) == 'function' then
print(formatting .. "[function]")
elseif type(v) == 'userdata' then
print(formatting .. "[userdata]")
else
print(formatting .. v)
end
end
end
function mkSelector(name, list)
return {
name=name,
kind="selector",
current_kid=-1,
kids=list,
}
end
function mkSequence(name, list)
return {
name=name,
kind="sequence",
current_kid=-1,
kids=list,
}
end
function mkRepeat(name, what)
return {
name=name,
kind="repeat",
kids = what,
}
end
bt.reset.selector = function(node, data)
node.current_kid = -1
end
bt.reset.sequence = function(node, data)
node.current_kid = -1
end
bt.reset["repeat"] = function(node, data)
end
bt.tick["repeat"] = function(node, data)
--tprint(node)
local ret = bt.tick[node.kids[1].kind](node.kids[1], data)
if ret ~= "running" then
print("repeat resetting")
bt.reset[node.kids[1].kind](node.kids[1], data)
end
print("repeat ending\n&&&&&")
return "success"
end
-- nodes never call :reset() on themselves
bt.tick.selector = function(node, data)
local ret
if node.current_kid == -1 then
node.current_kid = 1
ret = "failed" -- trick reset into being run
end
while node.current_kid <= table.getn(node.kids) do
local cn = node.kids[node.current_kid]
-- reset fresh nodes
if ret == "failed" then
bt.reset[cn.kind](cn, data)
end
-- tick the current node
ret = bt.tick[cn.kind](cn, data)
print(" selector got status ["..ret.."] from kid "..node.current_kid)
if ret == "running" or ret == "success" then
return ret
end
node.current_kid = node.current_kid + 1
end
return "failed"
end
bt.tick.sequence = function(node, data)
local ret
if node.current_kid == -1 then
node.current_kid = 1
ret = "failed" -- trick reset into being run
end
while node.current_kid <= table.getn(node.kids) do
local cn = node.kids[node.current_kid]
-- reset fresh nodes
if ret == "failed" then
bt.reset[cn.kind](cn, data)
end
-- tick the current node
ret = bt.tick[cn.kind](cn, data)
print(" selector got status ["..ret.."] from kid "..node.current_kid)
if ret == "running" or ret == "failed" then
return ret
end
node.current_kid = node.current_kid + 1
end
return "success"
end
-- distance on x-z plane
function distance(a,b)
local x = a.x - b.x
local z = a.z - b.z
return math.abs(math.sqrt(x*x + z*z))
end
bt.reset.find_node_near = function(node, data)
local targetpos = minetest.find_node_near(data.pos, node.dist, node.sel)
data.targetPos = targetpos
end
bt.tick.find_node_near = function(node, data)
if data.targetPos == nil then
print("could not find node near")
return "failed"
end
return "success"
end
function mkFindNodeNear(sel, dist)
return {
name="find node near",
kind="find_node_near",
dist = dist,
sel = sel,
}
end
bt.reset.approach = function(node, data)
if data.targetPos ~= nil then
print("Approaching target ("..data.targetPos.x..","..data.targetPos.y..","..data.targetPos.z..")")
data.mob.destination = data.targetPos
else
print("Approach: targetPos is nil")
end
end
bt.tick.approach = function(node, data)
if data.targetPos == nil then
return "failed"
end
local d = distance(data.pos, data.targetPos)
print("dist: "..d)
if d <= node.dist then
print("arrived at target")
return "success"
end
return "running"
end
function mkApproach(dist)
return {
name="go to",
kind="approach",
dist=dist,
}
end
function mkTryApproach(dist)
return {
name="try to go to",
kind="try_approach",
dist=dist,
}
end
bt.reset.try_approach = function(node, data)
node.last_d = nil
if data.targetPos ~= nil then
print("Approaching target ("..data.targetPos.x..","..data.targetPos.y..","..data.targetPos.z..")")
data.mob.destination = data.targetPos
else
print("Approach: targetPos is nil")
end
end
bt.tick.try_approach = function(node, data)
if data.targetPos == nil then
return "failed"
end
local d = distance(data.pos, data.targetPos)
if d <= node.dist then
print("arrived at target")
node.last_d = nil
return "success"
end
if node.last_d == nil then
node.last_d = d
else
local dd = math.abs(node.last_d - d)
print("dist: ".. dd)
if dd < .02 then
-- we're stuck
node.last_d = nil
return "failed"
end
end
print("dist: ".. math.abs(node.last_d - d))
return "running"
end
bt.reset.destroy = function(node, data)
end
bt.tick.destroy = function(node, data)
print("Destroying target")
if data.targetPos == nil then
return "failed"
end
minetest.set_node(data.targetPos, {name="air"})
return "success"
end
function mkDestroy()
return {
name="destroy",
kind="destroy",
}
end
bt.reset.bash_walls = function(node, data)
end
bt.tick.bash_walls = function(node, data)
local pos = minetest.find_node_near(data.pos, 2, {"default:wood"})
if pos == nil then
return "failed"
end
minetest.set_node(pos, {name="air"})
return "success"
end
function mkBashWalls()
return {
name="destroy",
kind="bash_walls",
}
end

1
depends.txt Normal file
View File

@ -0,0 +1 @@
default

66
giant.lua Normal file
View File

@ -0,0 +1,66 @@
mobs:register_simple_mob("giants:giant", {
type = "monster",
passive = false,
attack_type = "dogfight",
reach = 2,
damage = 1,
hp_min = 4,
hp_max = 20,
armor = 100,
collisionbox = {-0.35,-1.0,-0.35, 0.35,0.8,0.35},
visual = "mesh",
mesh = "character.b3d",
drawtype = "front",
textures = {
{"mobs_npc.png"},
},
makes_footstep_sound = true,
walk_velocity = 1.5,
run_velocity = 4,
view_range = 15,
jump = true,
floats = 0,
drops = {
{name = "default:iron_lump",
chance = 1, min = 3, max = 5},
},
water_damage = 0,
lava_damage = 4,
light_damage = 0,
fear_height = 3,
animation = {
speed_normal = 30,
speed_run = 30,
stand_start = 0,
stand_end = 79,
walk_start = 168,
walk_end = 187,
run_start = 168,
run_end = 187,
punch_start = 200,
punch_end = 219,
},
pre_activate = function(self, s,d)
self.bt = mkRepeat("root", {
mkSequence("snuff torches", {
mkFindNodeNear({"default:torch"}, 20),
mkSelector("seek", {
mkTryApproach(.8),
mkBashWalls(),
mkTryApproach(.8),
}),
mkDestroy(),
})
})
end
})
mobs:register_spawn("giants:giant", {"default:desert_sand"}, 20, 0, 7000, 2, 31000)
mobs:register_egg("giants:giant", "Giant", "default_desert_sand.png", 1)

20
init.lua Normal file
View File

@ -0,0 +1,20 @@
local path = minetest.get_modpath("giants")
-- Mob Api
dofile(path.."/api.lua")
dofile(path.."/behavior.lua")
dofile(path.."/simple_api.lua")
dofile(path.."/giant.lua")
-- Mob Items
--dofile(path.."/crafts.lua")
-- Spawner
--dofile(path.."/spawner.lua")
print ("[MOD] Giants loaded")

21
license.txt Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Krupnov Pavel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

BIN
models/character.b3d Normal file

Binary file not shown.

521
simple_api.lua Normal file
View File

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

BIN
sounds/default_punch.ogg Normal file

Binary file not shown.

127
spawner.lua Normal file
View File

@ -0,0 +1,127 @@
-- mob spawner
local spawner_default = "mobs:pumba 10 15 0"
minetest.register_node("mobs:spawner", {
tiles = {"mob_spawner.png"},
drawtype = "glasslike",
paramtype = "light",
walkable = true,
description = "Mob Spawner",
groups = {cracky = 1},
on_construct = function(pos)
local meta = minetest.get_meta(pos)
-- text entry formspec
meta:set_string("formspec", "field[text;mob_name min_light max_light amount;${command}]")
meta:set_string("infotext", "Spawner Not Active (enter settings)")
meta:set_string("command", spawner_default)
end,
on_right_click = function(pos, placer)
local meta = minetest.get_meta(pos)
end,
on_receive_fields = function(pos, formname, fields, sender)
if not fields.text or fields.text == "" then
return
end
local meta = minetest.get_meta(pos)
local comm = fields.text:split(" ")
local name = sender:get_player_name()
if minetest.is_protected(pos, name) then
minetest.record_protection_violation(pos, name)
return
end
local mob = comm[1]
local mlig = tonumber(comm[2])
local xlig = tonumber(comm[3])
local num = tonumber(comm[4])
if mob and mob ~= ""
and num and num >= 0 and num <= 10
and mlig and mlig >= 0 and mlig <= 15
and xlig and xlig >= 0 and xlig <= 15 then
meta:set_string("command", fields.text)
meta:set_string("infotext", "Spawner Active (" .. mob .. ")")
else
minetest.chat_send_player(name, "Mob Spawner settings failed!")
end
end,
})
-- spawner abm
minetest.register_abm({
nodenames = {"mobs:spawner"},
interval = 10,
chance = 4,
catch_up = false,
action = function(pos, node, active_object_count, active_object_count_wider)
-- check objects inside 9x9 area around spawner
local objs = minetest.get_objects_inside_radius(pos, 9)
-- get meta and command
local meta = minetest.get_meta(pos)
local comm = meta:get_string("command"):split(" ")
-- get settings from command
local mob = comm[1]
local mlig = tonumber(comm[2])
local xlig = tonumber(comm[3])
local num = tonumber(comm[4])
-- if amount is 0 then do nothing
if num == 0 then
return
end
local count = 0
local ent = nil
-- count objects of same type in area
for k, obj in pairs(objs) do
ent = obj:get_luaentity()
if ent and ent.name == mob then
count = count + 1
end
end
-- is there too many of same type?
if count >= num then
return
end
-- find air blocks within 5 nodes of spawner
local air = minetest.find_nodes_in_area(
{x = pos.x - 5, y = pos.y, z = pos.z - 5},
{x = pos.x + 5, y = pos.y, z = pos.z + 5},
{"air"})
-- spawn in random air block
if air and #air > 0 then
local pos2 = air[math.random(#air)]
local lig = minetest.get_node_light(pos2)
pos2.y = pos2.y + 0.5
-- only if light levels are within range
if lig and lig >= mlig and lig <= xlig then
minetest.add_entity(pos2, mob)
end
end
end
})

BIN
textures/mob_spawner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

BIN
textures/mobs_blood.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

BIN
textures/mobs_npc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 B

BIN
textures/mobs_npc2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1018 B

BIN
textures/mobs_npc_baby.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B