Harvest Update

This commit is contained in:
ElCeejo 2022-10-17 17:23:26 -07:00
parent d1453b9501
commit 4642fb63fd
56 changed files with 1689 additions and 972 deletions

View file

@ -315,6 +315,22 @@ end
-- Mobs --
----------
function animalia.get_dropped_food(self, item)
local pos = self.object:get_pos()
if not pos then return end
local objects = minetest.get_objects_inside_radius(pos, self.tracking_range)
for _, object in ipairs(objects) do
local ent = object:get_luaentity()
if ent
and ent.name == "__builtin:item"
and ent.itemstring
and ((item and ent.itemstring:match(item))
or self:follow_item(ItemStack(ent.itemstring))) then
return object, object:get_pos()
end
end
end
function animalia.protect_from_despawn(self)
self._despawn = self:memorize("_despawn", false)
self.despawn_after = self:memorize("despawn_after", false)
@ -334,7 +350,7 @@ function animalia.set_nametag(self, clicker)
return
end
self.nametag = self:memorize("nametag", name)
self.despawn_after = self:memorize("despawn_after", nil)
self.despawn_after = self:memorize("despawn_after", false)
activate_nametag(self)
if not minetest.is_creative_enabled(plyr_name) then
item:take_item()
@ -343,7 +359,6 @@ function animalia.set_nametag(self, clicker)
return true
end
function animalia.initialize_api(self)
self.gender = self:recall("gender") or nil
if not self.gender then
@ -448,8 +463,9 @@ function animalia.feed(self, clicker, breed, tame)
local item, item_name = self:follow_wielded_item(clicker)
if item_name then
-- Eat Animation
local offset_h = self.head_data.pivot_h or 0.5
local offset_v = self.head_data.pivot_v or 0.5
local head = self.head_data
local offset_h = (head and head.pivot_h) or 0.5
local offset_v = (head and head.pivot_v) or 0.5
local head_pos = {
x = pos.x + sin(yaw) * -offset_h,
y = pos.y + offset_v,
@ -546,6 +562,15 @@ function animalia.mount(self, player, params)
end)
end
function animalia.punch(self, puncher, ...)
creatura.basic_punch_func(self, puncher, ...)
self._puncher = puncher
if self.flee_puncher
and (self:get_utility() or "") ~= "animalia:flee_from_target" then
self:clear_utility()
end
end
--------------
-- Spawning --
--------------
@ -668,4 +693,4 @@ animalia.register_biome_group("common", {
min_humidity = 20,
max_humidity = 80,
min_height = 1
})
})

View file

@ -36,6 +36,7 @@ end
local vec_dir = vector.direction
local vec_dist = vector.distance
local vec_divide = vector.divide
local vec_len = vector.length
local vec_normal = vector.normalize
local vec_round = vector.round
@ -50,6 +51,22 @@ local yaw2dir = minetest.yaw_to_dir
-- Local Tools --
-----------------
local farming_enabled = minetest.get_modpath("farming") and farming.registered_plants
if farming_enabled then
minetest.register_on_mods_loaded(function()
for name, def in pairs(minetest.registered_nodes) do
local item_string = name:sub(1, #name - 2)
local item_name = item_string:split(":")[2]
if farming.registered_plants[item_string]
or farming.registered_plants[item_name] then
def.groups.crop = 2
end
minetest.register_node(":" .. name, def)
end
end)
end
local animate_player = {}
if minetest.get_modpath("default")
@ -108,6 +125,38 @@ local function add_break_particle(pos)
})
end
local function add_eat_particle(self, item_name)
local pos, yaw = self.object:get_pos(), self.object:get_yaw()
if not pos then return end
local head = self.head_data
local offset_h = (head and head.pivot_h) or self.width
local offset_v = (head and head.pivot_v) or self.height
local head_pos = {
x = pos.x + sin(yaw) * -offset_h,
y = pos.y + offset_v,
z = pos.z + cos(yaw) * offset_h
}
local def = minetest.registered_items[item_name]
local image = def.inventory_image
if def.tiles then
image = def.tiles[1].name or def.tiles[1]
end
if image then
local crop = "^[sheet:4x4:" .. random(4) .. "," .. random(4)
minetest.add_particlespawner({
pos = head_pos,
time = 0.5,
amount = 12,
collisiondetection = true,
collision_removal = true,
vel = {min = {x = -1, y = 1, z = -1}, max = {x = 1, y = 2, z = 1}},
acc = {x = 0, y = -9.8, z = 0},
size = {min = 1, max = 2},
texture = image .. crop
})
end
end
local function get_group_positions(self)
local objects = creatura.get_nearby_objects(self, self.name)
local group = {}
@ -118,6 +167,11 @@ local function get_group_positions(self)
return group
end
local function reset_attack_vals(self)
self.punch_cooldown = 0
self.target = nil
end
--------------
-- Movement --
--------------
@ -157,46 +211,32 @@ creatura.register_movement_method("animalia:fly_simple", function(self)
end)
creatura.register_movement_method("animalia:fly_obstacle_avoidance", function(self)
local box = clamp(self.width, 0.5, 1.5)
local steer_to
local steer_timer = 0.25
local init_dist
self:set_gravity(0)
local function func(_self, goal, speed_factor)
local pos = _self.object:get_pos()
if not pos then return end
-- Return true when goal is reached
if vec_dist(pos, goal) < box * 1.33 then
local dist = vec_dist(pos, goal)
init_dist = dist
if dist < clamp(self.width, 0.5, 1) + 1.5 then
_self:halt()
return true
end
steer_timer = steer_timer - self.dtime
if steer_timer <= 0 then
steer_to = get_avoidance_dir(_self)
end
-- Get movement direction
local goal_dir = vec_dir(pos, goal)
if steer_to then
steer_to.y = goal_dir.y
goal_dir = steer_to
end
local yaw = _self.object:get_yaw()
local goal_yaw = dir2yaw(goal_dir)
-- Calculate Movement
steer_timer = (steer_timer > 0 and steer_timer - self.dtime) or 0.25
steer_to = (steer_timer <= 0 and creatura.get_context_steering(self, goal, 4, true)) or steer_to
local speed = abs(_self.speed or 2) * speed_factor or 0.5
local turn_rate = abs(_self.turn_rate or 5)
-- Movement
local yaw_diff = abs(diff(yaw, goal_yaw))
if yaw_diff < pi * 0.25
or steer_to then
_self:set_forward_velocity(speed)
else
_self:set_forward_velocity(speed * 0.33)
end
self:set_vertical_velocity(speed * goal_dir.y)
_self:turn_to(goal_yaw, turn_rate)
if _self.touching_ground
or _self.in_liquid then
_self.object:add_velocity({x = 0, y = 2, z = 0})
end
local dist_weight = (init_dist - dist) / init_dist
-- Apply Movement
local dir = (steer_to or vec_dir(pos, goal))
speed = speed - (speed * 0.75) * dist_weight
turn_rate = turn_rate + turn_rate * dist_weight
_self:set_forward_velocity(speed)
_self:set_vertical_velocity(speed * dir.y)
_self:turn_to(dir2yaw(dir), turn_rate)
end
return func
end)
@ -223,7 +263,7 @@ creatura.register_movement_method("animalia:swim_simple", function(self)
if yaw_diff < pi * 0.25 then
_self:set_forward_velocity(speed)
else
_self:set_forward_velocity(speed * 0.33)
_self:set_forward_velocity(speed * 0.66)
end
self:set_vertical_velocity(speed * goal_dir.y)
_self:turn_to(goal_yaw, turn_rate)
@ -280,7 +320,7 @@ function animalia.action_pursue(self, target, timeout, method, speed_factor, ani
local timer = timeout or 4
local goal
local function func(_self)
local target_alive, line_of_sight, tgt_pos = self:get_target(target)
local target_alive, line_of_sight, tgt_pos = _self:get_target(target)
if not target_alive then
return true
end
@ -307,6 +347,92 @@ function animalia.action_pursue(self, target, timeout, method, speed_factor, ani
self:set_action(func)
end
function animalia.action_pursue_glide(self, target, timeout, method, speed_factor, anim)
local timer = timeout or 4
local goal
local speed_x = speed_factor
local function func(_self)
local target_alive, line_of_sight, tgt_pos = _self:get_target(target)
if not target_alive then
return true
end
goal = goal or tgt_pos
timer = timer - _self.dtime
self:animate(anim or "walk")
if line_of_sight
and vec_dist(goal, tgt_pos) > 3 then
goal = tgt_pos
end
local vel = self.object:get_velocity()
if vel.y < 0 and speed_x < speed_factor + 0.5 then speed_x = speed_x + self.dtime * 0.5 end
if vel.y >= 0 and speed_x > speed_factor then speed_x = speed_x - self.dtime * 0.25 end
if timer <= 0
or _self:move_to(goal, method or "animalia:fly_obstacle_avoidance", speed_x) then
return true
end
end
self:set_action(func)
end
function animalia.action_flight_attack(self, target, timeout)
local anim = self.animations["fly_punch"]
local anim_len = (anim.range.y - anim.range.x) / anim.speed
local anim_time = 0
local timer = timeout or 12
local cooldown = 0
local speed_x = 0.5
local goal
local function func(_self)
local pos = _self.stand_pos
if timer <= 0 then return true end
local target_alive, los, tgt_pos = _self:get_target(target)
if not target_alive then return true end
local dist = vec_dist(pos, tgt_pos)
if dist > 32 then return true end
local vel = self.object:get_velocity()
if vel.y < 0 and speed_x < 1 then speed_x = speed_x + self.dtime * 0.5 end
if vel.y >= 0 and speed_x > 0.5 then speed_x = speed_x - self.dtime end
if anim_time > 0 then
_self:animate("fly_punch")
anim_time = anim_time - _self.dtime
else
if speed_x > 0.6 then
_self:animate("glide")
else
_self:animate("fly")
end
end
if cooldown > 0 then
goal = goal or _self:get_wander_pos_3d(3, 6, nil, 1)
cooldown = cooldown - _self.dtime
else
goal = nil
cooldown = 0
end
if goal
and _self:move_to(goal, "animalia:fly_obstacle_avoidance", speed_x) then
goal = nil
end
if not goal
and _self:move_to(tgt_pos, "animalia:fly_obstacle_avoidance", speed_x) then
if dist < _self.width + 1 then
_self:punch_target(target)
cooldown = timeout / 3
anim_time = anim_len
end
end
timer = timer - _self.dtime
end
self:set_action(func)
end
function animalia.action_move_flock(self, pos2, timeout, method, speed_factor, anim, boid_steer)
local old_boids = (self._movement_data and self._movement_data.boids) or {}
local boids = (#old_boids > 2 and old_boids) or creatura.get_boid_members(self.object:get_pos(), 12, self.name)
@ -357,6 +483,55 @@ function animalia.action_move_flock(self, pos2, timeout, method, speed_factor, a
self:set_action(func)
end
function animalia.action_move_boid(self, pos2, timeout, method, speed_factor, anim, water)
local timer = timeout or 4
local goal
local function func(_self)
local pos, vel = self.object:get_pos(), self.object:get_velocity()
if not pos then return end
local move_data = self._movement_data or {}
-- Tick down timer
timer = timer - _self.dtime
-- Check if goal is safe
local safe = true
local max_fall = (_self.max_fall or 0) > 0 and _self.max_fall
if max_fall then
safe = _self:is_pos_safe(goal)
end
-- Boid calculation
local boid_dir, boids = move_data.boid_dir or creatura.get_boid_dir(self)
if boid_dir then
local heading = vec_dir(pos, pos2)
boid_dir.y = boid_dir.y + heading.y / 2
goal = vec_add(pos, vec_multi(boid_dir, self.width + 1))
if max_fall then
goal = creatura.get_ground_level(goal, 2)
end
if boids then
for _, obj in ipairs(boids) do
local ent = obj and obj:get_luaentity()
if ent then
ent._movement_data.boid_dir = boid_dir
end
end
end
else
goal = pos2
end
local steer_dir = creatura.get_context_steering(self, goal, 1, water)
goal = (steer_dir and vec_add(pos, steer_dir)) or goal
-- Main movement
if timer <= 0
or not safe
or _self:move_to(goal, method or "creatura:obstacle_avoidance", speed_factor or 0.5) then
return true
end
self:animate(anim or "walk")
self._movement_data.boid_dir = nil
end
self:set_action(func)
end
function animalia.action_float(self, time, anim)
local timer = time
local function func(_self)
@ -528,7 +703,7 @@ creatura.register_utility("animalia:swim_to_land", function(self)
_self:set_gravity(-9.8)
_self:set_forward_velocity(_self.speed * 0.66)
_self:animate("walk")
if vector.distance(pos, tpos) < 1
if vec_dist(pos, tpos) < 1
or (not _self.in_liquid
and _self.touching_ground) then
return true
@ -569,7 +744,7 @@ creatura.register_utility("animalia:wander_group", function(self)
local center = self.object:get_pos()
if not center then return end
local group_tick = 500
local move = self.wander_action or animalia.action_move_flock
local move = self.wander_action or animalia.action_move_boid
local function func(_self)
group_tick = group_tick - 1
if group_tick <= 0 then
@ -631,6 +806,7 @@ creatura.register_utility("animalia:aerial_wander", function(self)
local center = self.object:get_pos()
if not center then return end
local height_tick = 0
local move = self.wander_action or creatura.action_move
local function func(_self)
local pos = self.object:get_pos()
if not pos then return end
@ -643,7 +819,7 @@ creatura.register_utility("animalia:aerial_wander", function(self)
if not _self:get_action() then
local move_dir = (vec_dist(pos, center) > 8 and vec_dir(pos, center)) or nil
local pos2 = _self:get_wander_pos_3d(2, 5, move_dir)
animalia.action_move_flock(_self, pos2, 3, "animalia:fly_simple", 1, "fly", true)
move(_self, pos2, 3, "animalia:fly_simple", 1, "fly", true)
end
end
self:set_utility(func)
@ -652,25 +828,24 @@ end)
creatura.register_utility("animalia:fly_to_roost", function(self)
local home = self.home_position
local roost = self.roost_action or creatura.action_idle
local is_home = self.is_roost or function(pos, home_pos)
if abs(pos.x - home_pos.x) < 0.5
and abs(pos.z - home_pos.z) < 0.5
and abs(pos.y - home_pos.y) < 0.75 then
return true
end
return false
end
local function func(_self)
local pos = _self.object:get_pos()
if not pos then return end
if not home then return true end
if not _self:get_action() then
if abs(pos.x - home.x) < 0.5
and abs(pos.z - home.z) < 0.5 then
local y_diff = abs(pos.y - home.y)
if y_diff < 0.7 then
roost(_self, 1, "stand")
return
end
if y_diff <= 2
and not minetest.line_of_sight(pos, home) then
self.home_positon = nil
self:forget("home_position")
end
if is_home(pos, home) then
roost(_self, 1, "stand")
return
end
creatura.action_move(_self, home, 3, "animalia:fly_simple", 1, "fly")
creatura.action_move(_self, home, 3, "animalia:fly_obstacle_avoidance", 1, "fly")
end
end
self:set_utility(func)
@ -714,7 +889,7 @@ creatura.register_utility("animalia:aquatic_wander_school", function(self)
water_nodes = minetest.find_nodes_in_area(vec_sub(center, 4), vec_add(center, 4), {"group:water"})
end
if not _self:get_action() then
animalia.action_move_flock(_self, water_nodes[random(#water_nodes)], 3, "animalia:swim_simple", 1, "swim", true)
animalia.action_move_boid(_self, water_nodes[random(#water_nodes)], 3, "animalia:swim_simple", 1, "swim")
end
end
self:set_utility(func)
@ -844,7 +1019,7 @@ creatura.register_utility("animalia:follow_player", function(self, player, force
local dist = vec_dist(pos, plyr_pos)
if not _self:get_action() then
if dist > width + 1 then
animalia.action_pursue(_self, player, 3, "creatura:obstacle_avoidance", 0.75)
animalia.action_pursue(_self, player, 3, "creatura:context_based_steering", 0.75)
else
creatura.action_idle(_self, 1)
end
@ -872,7 +1047,7 @@ creatura.register_utility("animalia:flee_from_target", function(self, target)
local flee_dir = vec_dir(tgt_pos, pos)
local pos2 = _self:get_wander_pos(2, 3, flee_dir)
local anim = (_self.animations["run"] and "run") or "walk"
creatura.action_move(_self, pos2, 2, "creatura:obstacle_avoidance", 1, anim)
creatura.action_move(_self, pos2, 2, "creatura:context_based_steering", 1, anim)
end
end
self:set_utility(func)
@ -985,8 +1160,9 @@ creatura.register_utility("animalia:attack_target", function(self, target)
local pos = _self.object:get_pos()
if not pos then return end
local tgt_alive, _, tgt_pos = _self:get_target(target)
if not tgt_alive then return true end
if not tgt_alive then reset_attack_vals(self) return true end
local dist = vec_dist(pos, tgt_pos)
if dist > self.tracking_range then reset_attack_vals(self) return true end
local punch_cooldown = self.punch_cooldown or 0
if punch_cooldown > 0 then
punch_cooldown = punch_cooldown - self.dtime
@ -997,16 +1173,40 @@ creatura.register_utility("animalia:attack_target", function(self, target)
and not punch_init then
punch_init = true
animalia.action_punch(_self, target)
self.punch_cooldown = 3
self.punch_cooldown = 1.5
end
if not _self:get_action() then
if punch_init then return true end
if punch_init then reset_attack_vals(self) return true end
animalia.action_pursue(_self, target, 3, "creatura:pathfind", 0.75)
end
end
self:set_utility(func)
end)
creatura.register_utility("animalia:glide_attack_target", function(self, target)
local hidden_timer = 1
local attack_init = false
local function func(_self)
local pos, yaw = _self.object:get_pos(), _self.object:get_yaw()
if not pos then return end
local target_alive, los, tgt_pos = _self:get_target(target)
if not target_alive then
_self._target = nil
return true
end
if not _self:get_action() then
if attack_init then return true end
local dist = vec_dist(pos, tgt_pos)
if dist > 14 then
creatura.action_move(_self, tgt_pos, 3, "animalia:fly_obstacle_avoidance", 0.5, "fly")
else
animalia.action_flight_attack(_self, target, 12)
end
end
end
self:set_utility(func)
end)
creatura.register_utility("animalia:breed", function(self)
local mate = animalia.get_nearby_mate(self, self.name)
if not mate then self.breeding = false return end
@ -1050,6 +1250,174 @@ creatura.register_utility("animalia:breed", function(self)
self:set_utility(func)
end)
creatura.register_utility("animalia:fly_to_food", function(self, food_item)
local eat_init = false
local function func(_self)
local pos, tgt_pos = _self.object:get_pos(), food_item and food_item:get_pos()
if not pos then return end
if not tgt_pos and not eat_init then return true end
local dist = vec_dist(pos, tgt_pos or pos)
if dist < 1
and not eat_init then
eat_init = true
local food_ent = food_item:get_luaentity()
local stack = ItemStack(food_ent.itemstring)
if stack
and stack:get_count() > 1 then
stack:take_item()
food_ent.itemstring = stack:to_string()
else
food_item:remove()
end
self.object:get_yaw(dir2yaw(vec_dir(pos, tgt_pos)))
add_eat_particle(self, "animalia:rat_raw")
creatura.action_idle(_self, 1, "eat")
self.eat_cooldown = 60
if self.on_eat_drop then
self:on_eat_drop()
end
end
if not _self:get_action() then
if eat_init then return true end
creatura.action_move(_self, tgt_pos, 3, "animalia:fly_simple", 1, "fly")
end
end
self:set_utility(func)
end)
creatura.register_utility("animalia:walk_to_food", function(self, food_item)
local eat_init = false
local function func(_self)
local pos, tgt_pos = _self.object:get_pos(), food_item and food_item:get_pos()
if not pos then return end
if not tgt_pos and not eat_init then return true end
local dist = vec_dist(pos, tgt_pos or pos)
if dist < 1
and not eat_init then
eat_init = true
local food_ent = food_item:get_luaentity()
local stack = ItemStack(food_ent.itemstring)
if stack
and stack:get_count() > 1 then
stack:take_item()
food_ent.itemstring = stack:to_string()
else
food_item:remove()
end
self.object:get_yaw(dir2yaw(vec_dir(pos, tgt_pos)))
add_eat_particle(self, "animalia:rat_raw")
local anim = (self.animations["eat"] and "eat") or "stand"
creatura.action_idle(_self, 1, anim)
self.eat_cooldown = 60
if self.on_eat_drop then
self:on_eat_drop()
end
end
if not _self:get_action() then
if eat_init then return true end
creatura.action_move(_self, tgt_pos, 3, "creatura:context_based_steering", 0.5, "walk")
end
end
self:set_utility(func)
end)
-- Pest Behavior
creatura.register_utility("animalia:eat_crop", function(self)
local center = self.object:get_pos()
if not center then return end
local width = self.width
local timeout = 8
local nodes = minetest.find_nodes_in_area(vec_sub(center, 6), vec_add(center, 6), "group:crop") or {}
local pos2
local anim_init = false
local function func(_self)
local pos = _self.object:get_pos()
if not pos then return end
if #nodes < 1 then return true end
if not _self:get_action() then
if anim_init then return true end
pos2 = pos2 or nodes[random(#nodes)]
local node_name = minetest.get_node(pos2).name
local dist = vec_dist(pos, pos2)
if dist < width + 0.5 then
local new_name = node_name:sub(1, #node_name - 1) .. (tonumber(node_name:sub(-1)) or 2) - 1
local new_def = minetest.registered_nodes[new_name]
if not new_def then return true end
local p2 = new_def.place_param2 or 1
minetest.set_node(pos2, {name = new_name, param2 = p2})
add_eat_particle(self, new_name)
anim_init = true
creatura.action_idle(_self, 0.5, "eat")
else
creatura.action_move(_self, pos2, 4, "creatura:context_based_steering")
end
end
timeout = timeout - _self.dtime
if timeout <= 0 then return true end
end
self:set_utility(func)
end)
local function take_food_from_chest(self, pos)
local inv = minetest.get_inventory({type = "node", pos = pos})
if inv
and inv:get_list("main") then
for i, stack in ipairs(inv:get_list("main")) do
local item_name = stack:get_name()
local def = minetest.registered_items[item_name]
for group in pairs(def.groups) do
if group:match("food_") then
stack:take_item()
inv:set_stack("main", i, stack)
add_eat_particle(self, item_name)
return true
end
end
end
end
end
creatura.register_utility("animalia:steal_from_chest", function(self)
local center = self.object:get_pos()
if not center then return end
local width = self.width
local timeout = 8
local nodes = minetest.find_nodes_with_meta(vec_sub(center, 6), vec_add(center, 6)) or {}
local pos2
for _, node_pos in ipairs(nodes) do
local meta = minetest.get_meta(node_pos)
if meta:get_string("owner") == "" then
local inv = minetest.get_inventory({type = "node", pos = node_pos})
if inv
and inv:get_list("main") then
pos2 = node_pos
end
end
end
local anim_init = false
local function func(_self)
local pos = _self.object:get_pos()
if not pos then return end
if not pos2 then return true end
if not _self:get_action() then
if anim_init then return true end
local dist = vec_dist(pos, pos2)
if dist < width + 1.1 then
anim_init = true
if take_food_from_chest(self, pos2) then
creatura.action_idle(_self, 0.5, "eat")
end
else
creatura.action_move(_self, pos2, 2, "creatura:context_based_steering")
end
end
timeout = timeout - _self.dtime
if timeout <= 0 then return true end
end
self:set_utility(func)
end)
-- Domesticated Behavior
creatura.register_utility("animalia:stay", function(self)
@ -1189,7 +1557,7 @@ creatura.register_utility("animalia:mount_horse", function(self, player)
and vel.y < 1 then
_self.object:add_velocity({
x = 0,
y = _self.jump_power + (abs(_self._movement_data.gravity) * 0.33),
y = _self.jump_power,
z = 0
})
elseif not _self.touching_ground then
@ -1229,4 +1597,36 @@ creatura.register_utility("animalia:flop", function(self)
_self:set_gravity(-9.8)
end
self:set_utility(func)
end)
end)
-- Global Utilities
animalia.global_utils = {
["basic_follow"] = {
utility = "animalia:follow_player",
get_score = function(self)
local lasso_tgt = self._lassod_to
local lasso = type(lasso_tgt) == "string" and minetest.get_player_by_name(lasso_tgt)
local force = lasso and lasso ~= false
local player = (force and lasso) or creatura.get_nearby_player(self)
if player
and (self:follow_wielded_item(player)
or force) then
return 0.4, {self, player, force}
end
return 0
end
},
["basic_flee"] = {
utility = "animalia:flee_from_target",
get_score = function(self)
local puncher = self._puncher
if puncher
and puncher:get_pos() then
return 0.6, {self, puncher}
end
self._puncher = nil
return 0
end
}
}

View file

@ -3,359 +3,253 @@
-----------
local abs = math.abs
local asin = math.asin
local atan2 = math.atan2
local cos = math.cos
local deg = math.deg
local sin = math.sin
function animalia.initialize_lasso(self)
self.lasso_origin = self:recall("lasso_origin") or nil
if self.lasso_origin then
self.caught_with_lasso = true
if type(self.lasso_origin) == "table"
and minetest.get_item_group(minetest.get_node(self.lasso_origin).name, "fence") > 0 then
local object = minetest.add_entity(self.lasso_origin, "animalia:lasso_fence_ent")
object:get_luaentity().parent = self.object
elseif type(self.lasso_origin) == "string"
and minetest.get_player_by_name(self.lasso_origin) then
self.lasso_origin = minetest.get_player_by_name(self.lasso_origin)
else
self:forget("lasso_origin")
end
end
local function diff(a, b) -- Get difference between 2 angles
return atan2(sin(b - a), cos(b - a))
end
function animalia.set_lasso_visual(self, target)
if not creatura.is_alive(self)
or (self.lasso_visual
and self.lasso_visual:get_luaentity()) then return end
local pos = self.object:get_pos()
local object = minetest.add_entity(pos, "animalia:lasso_visual")
local ent = object:get_luaentity()
self.lasso_visual = object
self.lasso_origin = target
ent.parent = self.object
ent.lasso_origin = target
return object
local vec_add, vec_dir, vec_dist, vec_len = vector.add, vector.direction, vector.distance, vector.length
local dir2yaw, dir2rot = minetest.dir_to_yaw, vector.dir_to_rotation
-- Entities --
local using_lasso = {}
minetest.register_entity("animalia:lasso_entity", {
visual = "mesh",
mesh = "animalia_lasso_entity.b3d",
textures = {"animalia_lasso_entity.png"},
pointable = false,
on_activate = function(self)
self.object:set_armor_groups({immortal = 1})
end,
_scale = 1,
on_step = function(self, dtime)
local pos, parent = self.object:get_pos(), (self.object:get_attach() or self._attached)
local pointed_ent = self._point_to and self._point_to:get_luaentity()
local point_to = self._point_to and self._point_to:get_pos()
if not pos or not parent or not point_to then self.object:remove() return end
if type(parent) == "string" then
parent = minetest.get_player_by_name(parent)
local tgt_pos = parent:get_pos()
local dist = vec_dist(pos, tgt_pos)
if dist > 0.5 then
self.object:set_pos(tgt_pos)
else
self.object:move_to(tgt_pos)
end
self.object:set_rotation(dir2rot(vec_dir(tgt_pos, point_to)))
elseif parent.x then
point_to.y = point_to.y + pointed_ent.height * 0.5
self.object:set_rotation(dir2rot(vec_dir(pos, point_to)))
else
self.object:remove()
return
end
local size = vec_dist(pos, point_to)
if abs(size - self._scale) > 0.1 then
self.object:set_properties({
visual_size = {x = 1, y = 1, z = size}
})
self._scale = size
end
end
})
local function remove_from_fence(self)
local pos = self.object:get_pos()
local mob = self._mob and self._mob:get_luaentity()
if not mob then
self.object:remove()
return
end
mob._lassod_to = nil
mob:forget("_lassod_to")
mob._lasso_ent:remove()
local dirs = {
{x = 0.5, y = 0, z = 0},
{x = -0.5, y = 0, z = 0},
{x = 0, y = 0.5, z = 0},
{x = 0, y = -0.5, z = 0},
{x = 0, y = 0, z = 0.5},
{x = 0, y = 0, z = -0.5}
}
for i = 1, 6 do
local i_pos = vec_add(pos, dirs[i])
if not creatura.get_node_def(i_pos).walkable then
minetest.add_item(i_pos, "animalia:lasso")
break
end
end
self.object:remove()
end
minetest.register_entity("animalia:tied_lasso_entity", {
collisionbox = {-0.25,-0.25,-0.25, 0.25,0.25,0.25},
visual = "cube",
visual_size = {x = 0.3, y = 0.3},
mesh = "model",
textures = {
"animalia_tied_lasso_entity.png",
"animalia_tied_lasso_entity.png",
"animalia_tied_lasso_entity.png",
"animalia_tied_lasso_entity.png",
"animalia_tied_lasso_entity.png",
"animalia_tied_lasso_entity.png",
},
on_activate = function(self)
self.object:set_armor_groups({immortal = 1})
end,
on_step = function(self)
local mob = self._mob and self._mob:get_luaentity()
if not mob then remove_from_fence(self) return end
end,
on_rightclick = remove_from_fence,
on_punch = remove_from_fence
})
-- API --
local function add_lasso(self, origin)
local pos = self.object:get_pos()
if not pos then return end
local object = minetest.add_entity(pos, "animalia:lasso_entity")
local ent = object and object:get_luaentity()
if not ent then return end
-- Attachment point of entity
ent._attached = origin
if type(origin) == "string" then
local player = minetest.get_player_by_name(origin)
--object:set_attach(player)
else
object:set_pos(origin)
end
self._lassod_to = origin
ent._point_to = self.object
self:memorize("_lassod_to", origin)
return object
end
local function get_rope_velocity(pos1, pos2, dist)
local force = dist / 10
local vel = vector.new((pos2.x - pos1.x) * force, ((pos2.y - pos1.y) / (24 + dist)), (pos2.z - pos1.z) * force)
return vel
end
function animalia.initialize_lasso(self)
self._lassod_to = self:recall("_lassod_to") or self:recall("lasso_origin")
if self._lassod_to then
local origin = self._lassod_to
if type(origin) == "table"
and minetest.get_item_group(minetest.get_node(origin).name, "fence") > 0 then
local object = minetest.add_entity(origin, "animalia:tied_lasso_entity")
object:get_luaentity()._mob = self.object
self._lasso_ent = add_lasso(self, origin)
elseif type(origin) == "string" then
self._lassod_to = origin
self._lasso_ent = add_lasso(self, origin)
else
self:forget("_lassod_to")
end
end
end
function animalia.update_lasso_effects(self)
if not creatura.is_alive(self) then return end
if self.caught_with_lasso
and self.lasso_origin then
local pos = self.object:get_pos()
pos.y = pos.y + (self:get_height() * 0.5)
animalia.set_lasso_visual(self, self.lasso_origin)
if type(self.lasso_origin) == "userdata"
or type(self.lasso_origin) == "string" then
if type(self.lasso_origin) == "string" then
self.lasso_origin = minetest.get_player_by_name(self.lasso_origin)
if not self.lasso_origin then
self.caught_with_lasso = nil
self.lasso_origin = nil
self:forget("lasso_origin")
if self.lasso_visual then
self.lasso_visual:remove()
self.lasso_visual = nil
end
return
end
end
self:memorize("lasso_origin", self.lasso_origin:get_player_name())
-- Get distance to lasso player
local player = self.lasso_origin
local lasso_origin = player:get_pos()
lasso_origin.y = lasso_origin.y + 1
local dist = vector.distance(pos, lasso_origin)
if player:get_wielded_item():get_name() ~= "animalia:lasso"
or vector.distance(pos, lasso_origin) > 16 then
self.caught_with_lasso = nil
self.lasso_origin = nil
self:forget("lasso_origin")
if self.lasso_visual then
self.lasso_visual:remove()
self.lasso_visual = nil
end
end
-- Apply physics
if dist > 6
or abs(lasso_origin.y - pos.y) > 8 then
local p_target = vector.add(pos, vector.multiply(vector.direction(pos, lasso_origin), dist * 0.8))
local g = -0.18
local v = vector.new(0, 0, 0)
v.x = (1.0 + (0.005 * dist)) * (p_target.x - pos.x) / dist
v.y = -((1.0 + (0.03 * dist)) * ((lasso_origin.y - 4) - pos.y) / (dist * (g * dist)))
v.z = (1.0 + (0.005 * dist)) * (p_target.z - pos.z) / dist
self.object:add_velocity(v)
end
elseif type(self.lasso_origin) == "table" then
self:memorize("lasso_origin", self.lasso_origin)
local lasso_origin = self.lasso_origin
local dist = vector.distance(pos, lasso_origin)
if dist > 6
or abs(lasso_origin.y - pos.y) > 8 then
local p_target = vector.add(pos, vector.multiply(vector.direction(pos, lasso_origin), dist * 0.8))
local g = -0.18
local v = vector.new(0, 0, 0)
v.x = (1.0 + (0.005 * dist)) * (p_target.x - pos.x) / dist
v.y = -((1.0 + (0.03 * dist)) * ((lasso_origin.y - 4) - pos.y) / (dist * (g * dist)))
v.z = (1.0 + (0.005 * dist)) * (p_target.z - pos.z) / dist
self.object:add_velocity(v)
end
local objects = minetest.get_objects_inside_radius(lasso_origin, 1)
local is_lasso_attached = false
for _, object in ipairs(objects) do
if object
and object:get_luaentity()
and object:get_luaentity().name == "animalia:lasso_fence_ent" then
is_lasso_attached = true
end
end
if not is_lasso_attached then
self.caught_with_lasso = nil
self.lasso_origin = nil
self:forget("lasso_origin")
if self.lasso_visual then
self.lasso_visual:remove()
self.lasso_visual = nil
end
end
else
local objects = minetest.get_objects_inside_radius(self.lasso_origin, 0.4)
for _, object in ipairs(objects) do
if object
and object:get_luaentity()
and object:get_luaentity().name == "animalia:lasso_fence_ent" then
minetest.add_item(object:get_pos(), "animalia:lasso")
object:remove()
end
end
self.caught_with_lasso = nil
self.lasso_origin = nil
self:forget("lasso_origin")
if self.lasso_visual then
self.lasso_visual:remove()
self.lasso_visual = nil
end
end
end
local pos = self.object:get_pos()
if not creatura.is_alive(self) then return end
if self._lassod_to then
local lasso = self._lassod_to
self._lasso_ent = self._lasso_ent or add_lasso(self, lasso)
if type(lasso) == "string" then
using_lasso[lasso] = self
local name = lasso
lasso = minetest.get_player_by_name(lasso)
if lasso:get_wielded_item():get_name() ~= "animalia:lasso" then
using_lasso[name] = nil
self._lasso_ent:remove()
self._lasso_ent = nil
self._lassod_to = nil
self:forget("_lassod_to")
return
end
local lasso_pos = lasso:get_pos()
local dist = vec_dist(pos, lasso_pos)
local vel = self.object:get_velocity()
if not vel or dist < 8 and self.touching_ground then return end
if vec_len(vel) < 8 then
self.object:add_velocity(get_rope_velocity(pos, lasso_pos, dist))
end
return
elseif type(lasso) == "table" then
local dist = vec_dist(pos, lasso)
local vel = self.object:get_velocity()
if not vel or dist < 8 and self.touching_ground then return end
if vec_len(vel) < 8 then
self.object:add_velocity(get_rope_velocity(pos, lasso, dist))
end
return
end
end
if self._lasso_ent then
self._lasso_ent:remove()
self._lasso_ent = nil
self._lassod_to = nil
self:forget("_lassod_to")
end
end
local function is_lasso_in_use(player)
for _, ent in pairs(minetest.luaentities) do
if ent.name
and ent.name:match("^animalia:") then
if ent.lasso_origin
and type(ent.lasso_origin) == "userdata"
and ent.lasso_origin == player then
return true
end
end
end
return false
end
local function update_lasso_rotation(self)
if not self.parent
or not self.lasso_origin then self.object:remove() return end
local lasso_origin = self.lasso_origin
if type(lasso_origin) == "userdata" then
lasso_origin = lasso_origin:get_pos()
lasso_origin.y = lasso_origin.y + 1
end
local object = self.parent
if not object then return end
local pos = object:get_pos()
pos.y = pos.y + object:get_luaentity():get_height()
local rot = vector.dir_to_rotation(vector.direction(lasso_origin, pos))
self.object:set_pos(lasso_origin)
self.object:set_rotation(rot)
self.object:set_properties({
visual_size = {x = 6, z = 10 * vector.distance(pos, lasso_origin), y = 6}
})
end
minetest.register_entity("animalia:lasso_visual", {
hp_max = 1,
physical = false,
collisionbox = {0, 0, 0, 0, 0, 0},
visual = "mesh",
mesh = "animalia_lasso.b3d",
visual_size = {x = 2, y = 2},
textures = {"animalia_lasso_cube.png"},
is_visible = true,
makes_footstep_sound = false,
glow = 1,
on_step = function(self)
self.object:set_armor_groups({immortal = 1})
if not self.parent
or not self.lasso_origin
or (self.parent
and (not creatura.is_alive(self.parent)
or not self.parent:get_luaentity().caught_with_lasso)) then
self.object:remove()
return
end
update_lasso_rotation(self)
end
})
minetest.register_entity("animalia:frog_tongue_visual", {
hp_max = 1,
physical = false,
collisionbox = {0, 0, 0, 0, 0, 0},
visual = "mesh",
mesh = "animalia_lasso.b3d",
visual_size = {x = 2, y = 2},
textures = {"animalia_frog_tongue.png"},
is_visible = true,
makes_footstep_sound = false,
on_step = function(self)
self.object:set_armor_groups({immortal = 1})
if not self.parent
or not self.lasso_origin
or (self.parent
and (not creatura.is_alive(self.parent)
or not self.parent:get_luaentity().caught_with_lasso)) then
self.object:remove()
return
end
update_lasso_rotation(self)
end
})
minetest.register_entity("animalia:lasso_fence_ent", {
physical = false,
collisionbox = {-0.25,-0.25,-0.25, 0.25,0.25,0.25},
visual = "cube",
visual_size = {x = 0.3, y = 0.3},
mesh = "model",
textures = {
"animalia_lasso_cube.png",
"animalia_lasso_cube.png",
"animalia_lasso_cube.png",
"animalia_lasso_cube.png",
"animalia_lasso_cube.png",
"animalia_lasso_cube.png",
},
makes_footstep_sound = false,
on_step = function(self)
if not self.parent
or not self.parent:get_luaentity()
or not self.parent:get_luaentity().lasso_origin then
self.object:remove()
return
end
local pos = self.object:get_pos()
local node = minetest.get_node(pos)
if not minetest.registered_nodes[node.name].walkable
or minetest.get_item_group(node.name, "fence") < 1 then
local ent = self.parent:get_luaentity()
ent.lasso_origin = ent:memorize("lasso_origin", nil)
ent.caught_with_lasso = nil
if ent.lasso_visual then
ent.lasso_visual:remove()
ent.lasso_visual = nil
end
minetest.add_item(self.object:get_pos(), "animalia:lasso")
self.object:remove()
return
end
end,
on_rightclick = function(self)
if self.parent then
local ent = self.parent:get_luaentity()
ent.lasso_origin = ent:memorize("lasso_origin", nil)
ent.caught_with_lasso = nil
if ent.lasso_visual then
ent.lasso_visual:remove()
ent.lasso_visual = nil
end
end
local dirs = {
vector.new(1, 0, 0),
vector.new(-1, 0, 0),
vector.new(0, 1, 0),
vector.new(0, -1, 0),
vector.new(0, 0, 1),
vector.new(0, 0, -1),
}
for i = 1, 6 do
local pos = vector.add(self.object:get_pos(), dirs[i])
local name = minetest.get_node(pos).name
if not minetest.registered_nodes[name].walkable then
minetest.add_item(pos, "animalia:lasso")
break
end
end
self.object:remove()
end,
on_punch = function(self)
if self.parent then
local ent = self.parent:get_luaentity()
ent.lasso_origin = ent:memorize("lasso_origin", nil)
ent.caught_with_lasso = nil
if ent.lasso_visual then
ent.lasso_visual:remove()
ent.lasso_visual = nil
end
end
local dirs = {
vector.new(1, 0, 0),
vector.new(-1, 0, 0),
vector.new(0, 1, 0),
vector.new(0, -1, 0),
vector.new(0, 0, 1),
vector.new(0, 0, -1),
}
for i = 1, 6 do
local pos = vector.add(self.object:get_pos(), dirs[i])
local name = minetest.get_node(pos).name
if not minetest.registered_nodes[name].walkable then
minetest.add_item(pos, "animalia:lasso")
break
end
end
self.object:remove()
end
})
-- Item
minetest.register_craftitem("animalia:lasso", {
description = "Lasso",
inventory_image = "animalia_lasso.png",
on_secondary_use = function(_, placer, pointed_thing)
if pointed_thing.type == "object" then
if pointed_thing.ref:is_player() then return end
local ent = pointed_thing.ref:get_luaentity()
if not ent.catch_with_lasso then return end
if not ent.caught_with_lasso
and not is_lasso_in_use(placer) then
ent.caught_with_lasso = true
ent.lasso_origin = placer
elseif ent.lasso_origin
and ent.lasso_origin == placer then
ent.caught_with_lasso = nil
ent.lasso_origin = nil
end
end
end,
on_place = function(itemstack, placer, pointed_thing)
if pointed_thing.type == "node" then
local pos = minetest.get_pointed_thing_position(pointed_thing)
if minetest.get_item_group(minetest.get_node(pos).name, "fence") > 0 then
local objects = minetest.get_objects_inside_radius(placer:get_pos(), 21)
for _, obj in ipairs(objects) do
if obj:get_luaentity()
and obj:get_luaentity().lasso_origin
and obj:get_luaentity().lasso_visual
and type(obj:get_luaentity().lasso_origin) == "userdata"
and obj:get_luaentity().lasso_origin == placer then
obj:get_luaentity().lasso_visual:get_luaentity().lasso_origin = pos
obj:get_luaentity().lasso_origin = pos
local object = minetest.add_entity(pos, "animalia:lasso_fence_ent")
object:get_luaentity().parent = obj
itemstack:take_item(1)
break
end
end
end
end
return itemstack
end
})
description = "Lasso",
inventory_image = "animalia_lasso.png",
on_secondary_use = function(_, placer, pointed)
local ent = pointed.ref and pointed.ref:get_luaentity()
if ent
and (ent.name:match("^animalia:")
or ent.name:match("^monstrum:")) then
if not ent.catch_with_lasso then return end
local name = placer:get_player_name()
if not ent._lassod_to
and not using_lasso[name] then
using_lasso[name] = ent
ent._lassod_to = name
ent:memorize("_lassod_to", name)
elseif ent._lassod_to
and ent._lassod_to == name then
using_lasso[name] = nil
ent._lassod_to = nil
ent:forget("_lassod_to")
end
end
end,
on_place = function(itemstack, placer, pointed_thing)
if pointed_thing.type == "node" then
local pos = minetest.get_pointed_thing_position(pointed_thing)
if minetest.get_item_group(minetest.get_node(pos).name, "fence") > 0 then
local name = placer:get_player_name()
local ent = using_lasso[name]
if ent
and ent._lassod_to
and ent._lassod_to == name then
using_lasso[name] = nil
ent._lasso_ent:set_detach()
ent._lasso_ent:set_pos(pos)
ent._lasso_ent:get_luaentity()._attached = pos
ent._lassod_to = pos
ent:memorize("_lassod_to", pos)
local fence_obj = minetest.add_entity(pos, "animalia:tied_lasso_entity")
fence_obj:get_luaentity()._mob = ent.object
fence_obj:get_luaentity()._lasso_obj = ent._lasso_ent
itemstack:take_item(1)
end
end
end
return itemstack
end
})

View file

@ -56,20 +56,12 @@ creatura.register_mob_spawn("animalia:cow", {
biomes = animalia.registered_biome_groups["grassland"].biomes
})
creatura.register_mob_spawn("animalia:frog", {
chance = 2,
min_radius = 4,
max_radius = 16,
min_light = 0,
min_height = -32,
max_height = 8,
min_group = 2,
max_group = 6,
biomes = frog_biomes,
spawn_cluster = true,
spawn_in_nodes = true,
creatura.register_mob_spawn("animalia:fox", {
chance = 4,
min_group = 1,
max_group = 2,
spawn_on_gen = true,
nodes = {"default:water_source"},
biomes = animalia.registered_biome_groups["boreal"].biomes
})
creatura.register_mob_spawn("animalia:horse", {
@ -80,6 +72,28 @@ creatura.register_mob_spawn("animalia:horse", {
biomes = animalia.registered_biome_groups["grassland"].biomes
})
creatura.register_abm_spawn("animalia:rat", {
chance = 2000,
interval = 30,
min_height = -1,
max_height = 1024,
min_group = 1,
max_group = 3,
spawn_in_nodes = true,
nodes = {"group:crop"}
})
creatura.register_abm_spawn("animalia:owl", {
chance = 30000,
interval = 60,
min_height = 3,
max_height = 1024,
min_group = 1,
max_group = 1,
spawn_cap = 1,
nodes = {"group:leaves"}
})
creatura.register_mob_spawn("animalia:pig", {
chance = 3,
min_group = 2,
@ -119,37 +133,6 @@ creatura.register_mob_spawn("animalia:wolf", {
biomes = animalia.registered_biome_groups["boreal"].biomes
})
creatura.register_mob_spawn("animalia:bird", {
chance = 1,
min_light = 0,
min_group = 12,
max_group = 16,
biomes = animalia.registered_biome_groups["common"].biomes,
spawn_cluster = true,
nodes = {"group:leaves"}
})
creatura.register_on_spawn("animalia:bird", function(self, pos)
local node = minetest.get_node(pos)
if node.name == "air" then
minetest.set_node(pos, {name = "animalia:nest_song_bird"})
self.home_position = self:memorize("home_position", pos)
self.despawn_after = self:memorize("despawn_after", nil)
else
local nodes = minetest.find_nodes_in_area_under_air(
{x = pos.x - 3, y = pos.y - 3, z = pos.z - 3},
{x = pos.x + 3, y = pos.y + 7, z = pos.z + 3},
"group:leaves"
)
if nodes[1] then
pos = nodes[1]
minetest.set_node({x = pos.x, y = pos.y + 1, z = pos.z}, {name = "animalia:nest_song_bird"})
self.home_position = self:memorize("home_position", {x = pos.x, y = pos.y + 1, z = pos.z})
self.despawn_after = self:memorize("despawn_after", nil)
end
end
end)
creatura.register_mob_spawn("animalia:tropical_fish", {
chance = 3,
min_height = -128,
@ -160,4 +143,70 @@ creatura.register_mob_spawn("animalia:tropical_fish", {
spawn_in_nodes = true,
spawn_on_gen = true,
nodes = {"default:water_source"}
})
-- Ambient Spawning
creatura.register_abm_spawn("animalia:bat", {
chance = 10000,
interval = 30,
min_light = 0,
min_height = -31000,
max_height = 1,
min_group = 3,
max_group = 5,
spawn_cap = 6,
nodes = {"group:stone"}
})
creatura.register_abm_spawn("animalia:bird", {
chance = 15000,
interval = 60,
min_light = 0,
min_height = 1,
max_height = 1024,
min_group = 12,
max_group = 16,
spawn_cap = 12,
nodes = {"group:leaves"}
})
creatura.register_on_spawn("animalia:bird", function(self, pos)
local nests = minetest.find_nodes_in_area_under_air(
{x = pos.x - 12, y = pos.y - 12, z = pos.z - 12},
{x = pos.x + 12, y = pos.y + 12, z = pos.z + 12},
"animalia:nest_song_bird"
)
if nests[1] then
self.home_position = self:memorize("home_position", nests[1])
return
end
local node = minetest.get_node(pos)
if node.name == "air" then
minetest.set_node(pos, {name = "animalia:nest_song_bird"})
self.home_position = self:memorize("home_position", pos)
else
local nodes = minetest.find_nodes_in_area_under_air(
{x = pos.x - 3, y = pos.y - 3, z = pos.z - 3},
{x = pos.x + 3, y = pos.y + 7, z = pos.z + 3},
"group:leaves"
)
if nodes[1] then
pos = nodes[1]
minetest.set_node({x = pos.x, y = pos.y + 1, z = pos.z}, {name = "animalia:nest_song_bird"})
self.home_position = self:memorize("home_position", {x = pos.x, y = pos.y + 1, z = pos.z})
end
end
end)
creatura.register_abm_spawn("animalia:frog", {
chance = 7500,
interval = 60,
min_light = 0,
min_height = -1,
max_height = 8,
min_group = 2,
max_group = 4,
neighbors = {"group:water"},
nodes = {"group:soil"}
})