general
6b53a21c96
* the wrokaround is done waithing until mod loads using minetest.after, at older engines we only can callit one time at load, but now the error generates in other place.. check for more elaborate solution at https://codeberg.org/minetest-stuffs/minetest-mod-tsm_pyramids/issues/2#issuecomment-1712924 * reported at https://codeberg.org/Wuzzy/minetest_tsm_pyramids/issues/2 * related to https://github.com/minetest/minetest/issues/10385#issuecomment-1674162787
595 lines
16 KiB
Lua
595 lines
16 KiB
Lua
local S = tsm_pyramids.S
|
|
|
|
local mod_cmi = minetest.get_modpath("cmi") ~= nil
|
|
|
|
local mummy_walk_limit = 1
|
|
local mummy_chillaxin_speed = 1
|
|
local mummy_animation_speed = 10
|
|
-- Note: This is currently broken due to a bug in Irrlicht, leave at 0
|
|
local mummy_animation_blend = 0
|
|
|
|
-- Default player appearance
|
|
local mummy_mesh = "tsm_pyramids_mummy.x"
|
|
local mummy_texture = {"tsm_pyramids_mummy.png"}
|
|
local mummy_hp = 20
|
|
local mummy_drop = "default:papyrus"
|
|
|
|
local spawner_entity_offset = -0.28
|
|
|
|
local sound_normal = "mummy"
|
|
local sound_hit = "mummy_hurt"
|
|
local sound_dead = "mummy_death"
|
|
|
|
local spawner_check_range = 17
|
|
local spawner_max_mobs = 6
|
|
|
|
local function get_animations()
|
|
return {
|
|
stand_START = 74,
|
|
stand_END = 74,
|
|
sit_START = 81,
|
|
sit_END = 160,
|
|
lay_START = 162,
|
|
lay_END = 166,
|
|
walk_START = 74,
|
|
walk_END = 105,
|
|
mine_START = 74,
|
|
mine_END = 105,
|
|
walk_mine_START = 74,
|
|
walk_mine_END = 105
|
|
}
|
|
end
|
|
|
|
local npc_model = {}
|
|
local npc_anim = {}
|
|
local npc_sneak = {}
|
|
local ANIM_STAND = 1
|
|
local ANIM_SIT = 2
|
|
local ANIM_LAY = 3
|
|
local ANIM_WALK = 4
|
|
local ANIM_WALK_MINE = 5
|
|
local ANIM_MINE = 6
|
|
|
|
local function hit(self)
|
|
self.object:set_texture_mod("^tsm_pyramids_hit.png")
|
|
minetest.after(0.4, function(self)
|
|
local prop = {textures = mummy_texture,}
|
|
if self ~= nil and self.object ~= nil then
|
|
self.object:set_texture_mod("")
|
|
end
|
|
end, self)
|
|
end
|
|
|
|
local function mummy_update_visuals_def(self)
|
|
npc_anim = 0 -- Animation will be set further below immediately
|
|
local prop = {
|
|
textures = mummy_texture,
|
|
}
|
|
self.object:set_properties(prop)
|
|
end
|
|
|
|
local MUMMY_DEF = {
|
|
initial_properties = {
|
|
hp_max = mummy_hp,
|
|
physical = true,
|
|
collisionbox = {-0.4, -0.01, -0.4, 0.4, 1.9, 0.4},
|
|
visual = "mesh",
|
|
visual_size = {x=8,y=8},
|
|
mesh = mummy_mesh,
|
|
textures = mummy_texture,
|
|
makes_footstep_sound = true,
|
|
},
|
|
npc_anim = 0,
|
|
timer = 0,
|
|
turn_timer = 0,
|
|
vec = 0,
|
|
yaw = 0,
|
|
yawwer = 0,
|
|
state = 1,
|
|
jump_timer = 0,
|
|
punch_timer = 0,
|
|
sound_timer = 0,
|
|
envdmg_timer = 0,
|
|
attacker = "",
|
|
attacking_timer = 0,
|
|
|
|
-- CMI stuff
|
|
-- Track last cause of damage for cmi.notify_die
|
|
last_damage_cause = { type = "unknown" },
|
|
_cmi_is_mob = true,
|
|
description = S("Mummy"),
|
|
}
|
|
|
|
-- Returns true if a mummy spawner entity was found at pos.
|
|
-- If self is provided, this object does not count.
|
|
local function check_if_mummy_spawner_entity_exists(pos, self)
|
|
local ents = minetest.get_objects_inside_radius(pos, 0.5)
|
|
for e=1, #ents do
|
|
if (not self) or (ents[e] ~= ents[e]) then
|
|
local lua = ents[e]:get_luaentity()
|
|
if lua then
|
|
if lua.name == "tsm_pyramids:mummy_spawner" then
|
|
-- entity found
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local spawner_DEF = {
|
|
initial_properties = {
|
|
hp_max = 1,
|
|
physical = false,
|
|
pointable = false,
|
|
visual = "mesh",
|
|
visual_size = {x=3.3,y=3.3},
|
|
mesh = mummy_mesh,
|
|
textures = mummy_texture,
|
|
makes_footstep_sound = false,
|
|
automatic_rotate = math.pi * 2.9,
|
|
},
|
|
timer = 0,
|
|
}
|
|
|
|
spawner_DEF.on_activate = function(self)
|
|
local pos = self.object:get_pos()
|
|
local spos = vector.new(pos.x, pos.y + spawner_entity_offset, pos.z)
|
|
if check_if_mummy_spawner_entity_exists(spos, self) then
|
|
-- Remove possible duplicate entity
|
|
self.object:remove()
|
|
return
|
|
end
|
|
mummy_update_visuals_def(self)
|
|
self.object:set_velocity({x=0, y=0, z=0})
|
|
self.object:set_acceleration({x=0, y=0, z=0})
|
|
self.object:set_armor_groups({immortal=1})
|
|
|
|
end
|
|
|
|
-- Regularily check if entity is still inside spawner
|
|
spawner_DEF.on_step = function(self, dtime)
|
|
self.timer = self.timer + dtime
|
|
local pos = self.object:get_pos()
|
|
pos.y = pos.y - spawner_entity_offset
|
|
local n = minetest.get_node_or_nil(pos)
|
|
if self.timer > 50 then
|
|
if n and n.name and n.name ~= "tsm_pyramids:spawner_mummy" then
|
|
self.object:remove()
|
|
return
|
|
end
|
|
self.timer = 0
|
|
end
|
|
end
|
|
|
|
spawner_DEF.on_punch = function(self, hitter)
|
|
|
|
end
|
|
|
|
MUMMY_DEF.on_activate = function(self, staticdata, dtime_s)
|
|
if mod_cmi then
|
|
cmi.notify_activate(self, dtime_s)
|
|
end
|
|
mummy_update_visuals_def(self)
|
|
self.anim = get_animations()
|
|
self.object:set_animation({x=self.anim.stand_START,y=self.anim.stand_END}, mummy_animation_speed, mummy_animation_blend)
|
|
self.npc_anim = ANIM_STAND
|
|
self.object:set_acceleration({x=0,y=-20,z=0})--20
|
|
self.state = 1
|
|
self.object:set_armor_groups({fleshy=130})
|
|
end
|
|
|
|
MUMMY_DEF.on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
|
|
if mod_cmi then
|
|
cmi.notify_punch(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
|
|
end
|
|
self.attacker = puncher
|
|
|
|
if damage and damage > 0 then
|
|
self.last_damage = {
|
|
type = "punch",
|
|
puncher = puncher,
|
|
}
|
|
end
|
|
if puncher ~= nil then
|
|
minetest.sound_play(sound_hit, {pos = self.object:get_pos(), loop = false, max_hear_distance = 10, gain = 0.4}, true)
|
|
if time_from_last_punch >= 0.45 then
|
|
hit(self)
|
|
self.direction = {x=self.object:get_velocity().x, y=self.object:get_velocity().y, z=self.object:get_velocity().z}
|
|
self.punch_timer = 0
|
|
self.object:set_velocity({x=dir.x*mummy_chillaxin_speed,y=5,z=dir.z*mummy_chillaxin_speed})
|
|
if self.state == 1 then
|
|
self.state = 8
|
|
elseif self.state >= 2 then
|
|
self.state = 9
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
MUMMY_DEF.on_death = function(self, killer)
|
|
minetest.sound_play(sound_dead, {pos = self.object:get_pos(), max_hear_distance = 10 , gain = 0.3}, true)
|
|
-- Drop item on death
|
|
local count = math.random(0,3)
|
|
if count > 0 then
|
|
local pos = self.object:get_pos()
|
|
pos.y = pos.y + 1.0
|
|
minetest.add_item(pos, mummy_drop .. " " .. count)
|
|
end
|
|
if mod_cmi then
|
|
cmi.notify_die(self, self.last_damage)
|
|
end
|
|
end
|
|
|
|
MUMMY_DEF.on_step = function(self, dtime)
|
|
if mod_cmi then
|
|
cmi.notify_step(self, dtime)
|
|
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 + dtime + 0.01
|
|
|
|
local current_pos = self.object:get_pos()
|
|
local current_node = minetest.get_node(current_pos)
|
|
if self.time_passed == nil then
|
|
self.time_passed = 0
|
|
end
|
|
|
|
-- Environment damage
|
|
local def = minetest.registered_nodes[current_node.name]
|
|
local dps = def.damage_per_second
|
|
local dmg = 0
|
|
local dmg_node, dmg_pos
|
|
if dps ~= nil and dps > 0 then
|
|
dmg = dps
|
|
end
|
|
-- Damage from node
|
|
if dmg < 4 and (minetest.get_item_group(current_node.name, "water") ~= 0 or minetest.get_item_group(current_node.name, "lava") ~= 0) then
|
|
dmg = 4
|
|
end
|
|
-- Damage by suffocation
|
|
if (def.walkable == nil or def.walkable == true)
|
|
and (def.drowning == nil or def.drowning == 0)
|
|
and (def.damage_per_second == nil or def.damage_per_second <= 0)
|
|
and (def.collision_box == nil or def.collision_box.type == "regular")
|
|
and (def.node_box == nil or def.node_box.type == "regular")
|
|
and (def.groups and def.groups.disable_suffocation ~= 1) then
|
|
dmg = dmg + 1
|
|
end
|
|
self.envdmg_timer = self.envdmg_timer + dtime
|
|
if dmg > 0 then
|
|
if self.envdmg_timer >= 1 then
|
|
local new_hp = self.object:get_hp() - dmg
|
|
if new_hp <= 0 then
|
|
if self.on_death then
|
|
self.on_death(self)
|
|
end
|
|
self.object:remove()
|
|
return
|
|
else
|
|
self.envdmg_timer = 0
|
|
self.object:set_hp(new_hp)
|
|
self.last_damage = {
|
|
type = "environment",
|
|
pos = current_pos,
|
|
node = current_node,
|
|
}
|
|
hit(self)
|
|
self.sound_timer = 0
|
|
minetest.sound_play(sound_hit, {pos = current_pos, max_hear_distance = 10, gain = 0.4}, true)
|
|
end
|
|
end
|
|
else
|
|
self.time_passed = 0
|
|
end
|
|
|
|
--update moving state every 1 or 2 seconds
|
|
if self.state < 3 then
|
|
if self.timer > math.random(1,2) then
|
|
if self.attacker == "" then
|
|
self.state = math.random(1,2)
|
|
else self.state = 1 end
|
|
self.timer = 0
|
|
end
|
|
end
|
|
|
|
--play sound
|
|
if self.sound_timer > math.random(5,35) then
|
|
minetest.sound_play(sound_normal, {pos = current_pos, max_hear_distance = 10, gain = 0.2}, true)
|
|
self.sound_timer = 0
|
|
end
|
|
|
|
--after punched
|
|
if self.state >= 8 then
|
|
if self.punch_timer > 0.15 then
|
|
if self.state == 9 then
|
|
self.object:set_velocity({x=self.direction.x*mummy_chillaxin_speed,y=-20,z=self.direction.z*mummy_chillaxin_speed})
|
|
self.state = 2
|
|
elseif self.state == 8 then
|
|
self.object:set_velocity({x=0,y=-20,z=0})
|
|
self.state = 1
|
|
end
|
|
end
|
|
end
|
|
|
|
--STANDING
|
|
if self.state == 1 then
|
|
self.yawwer = true
|
|
self.attacker = ""
|
|
local pos_obj = self.object:get_pos()
|
|
if pos_obj then
|
|
for _,object in ipairs(minetest.get_objects_inside_radius(pos_obj, 4)) do
|
|
if object:is_player() then
|
|
self.yawwer = false
|
|
local NPC = self.object:get_pos()
|
|
local PLAYER = object:get_pos()
|
|
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:set_yaw(self.yaw)
|
|
self.attacker = object
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.attacker == "" and self.turn_timer > math.random(1,4) then
|
|
self.yaw = 360 * math.random()
|
|
self.object:set_yaw(self.yaw)
|
|
self.turn_timer = 0
|
|
self.direction = {x = math.sin(self.yaw)*-1, y = -20, z = math.cos(self.yaw)}
|
|
end
|
|
local old_vel = self.object:get_velocity()
|
|
self.object:set_velocity({x=0,y=old_vel and old_vel.y or 0,z=0})
|
|
if self.npc_anim ~= ANIM_STAND then
|
|
self.anim = get_animations()
|
|
self.object:set_animation({x=self.anim.stand_START,y=self.anim.stand_END}, mummy_animation_speed, mummy_animation_blend)
|
|
self.npc_anim = 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
|
|
--WALKING
|
|
if self.state == 2 then
|
|
|
|
if self.direction ~= nil then
|
|
local old_vel = self.object:get_velocity()
|
|
self.object:set_velocity({
|
|
x=self.direction.x*mummy_chillaxin_speed,
|
|
y=old_vel and old_vel.y or 0,
|
|
z=self.direction.z*mummy_chillaxin_speed,
|
|
})
|
|
end
|
|
if self.turn_timer > math.random(1,4) and not self.attacker then
|
|
self.yaw = 360 * math.random()
|
|
self.object:set_yaw(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_WALK then
|
|
self.anim = get_animations()
|
|
self.object:set_animation({x=self.anim.walk_START,y=self.anim.walk_END}, mummy_animation_speed, mummy_animation_blend)
|
|
self.npc_anim = ANIM_WALK
|
|
end
|
|
|
|
if self.attacker ~= "" and minetest.settings:get_bool("enable_damage") then
|
|
local s = self.object:get_pos()
|
|
local p = self.attacker:get_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
|
|
|
|
if dist < 2 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
|
|
end
|
|
end
|
|
end
|
|
|
|
minetest.register_entity("tsm_pyramids:mummy", MUMMY_DEF)
|
|
minetest.register_entity("tsm_pyramids:mummy_spawner", spawner_DEF)
|
|
|
|
|
|
--spawn-egg/spawner
|
|
|
|
minetest.register_craftitem("tsm_pyramids:spawn_egg", {
|
|
description = S("Mummy Spawn Egg"),
|
|
_doc_items_longdesc = S("Can be used to create a hostile mummy."),
|
|
_doc_items_usagehelp = S("Place the egg to create a mummy on this spot. Careful, it will probably attack immediately!"),
|
|
inventory_image = "tsm_pyramids_mummy_egg.png",
|
|
liquids_pointable = false,
|
|
stack_max = 99,
|
|
on_place = function(itemstack, placer, pointed_thing)
|
|
if pointed_thing.type ~= "node" then
|
|
return itemstack
|
|
end
|
|
|
|
-- am I clicking on something with existing on_rightclick function?
|
|
local node = minetest.get_node(pointed_thing.under)
|
|
if placer and not placer:get_player_control().sneak then
|
|
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
|
|
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
|
|
end
|
|
end
|
|
|
|
minetest.add_entity(pointed_thing.above,"tsm_pyramids:mummy")
|
|
if not minetest.settings:get_bool("creative_mode") then
|
|
itemstack:take_item()
|
|
end
|
|
return itemstack
|
|
end,
|
|
|
|
})
|
|
|
|
-- Spawn a mummy at position
|
|
function tsm_pyramids.spawn_mummy_at(pos, number)
|
|
local node = minetest.get_node(pos)
|
|
if node.name ~= "air" then
|
|
return
|
|
end
|
|
for _=1, number do
|
|
minetest.add_entity(pos,"tsm_pyramids:mummy")
|
|
end
|
|
end
|
|
|
|
local spawnersounds
|
|
if default.node_sound_metal_defaults then
|
|
spawnersounds = default.node_sound_metal_defaults()
|
|
else
|
|
spawnersounds = default.node_sound_stone_defaults()
|
|
end
|
|
|
|
local spawn_mummy_spawner_entity = function(pos)
|
|
local spos = vector.new(pos.x, pos.y+spawner_entity_offset, pos.z)
|
|
minetest.add_entity(spos, "tsm_pyramids:mummy_spawner")
|
|
end
|
|
|
|
-- Respawn mummy spawner entity at pos if none exists
|
|
local respawn_mummy_spawner_entity = function(pos)
|
|
local spos = vector.new(pos.x, pos.y + spawner_entity_offset, pos.z)
|
|
if check_if_mummy_spawner_entity_exists(spos) then
|
|
return
|
|
end
|
|
spawn_mummy_spawner_entity(pos)
|
|
end
|
|
|
|
minetest.register_node("tsm_pyramids:spawner_mummy", {
|
|
description = S("Mummy Spawner"),
|
|
_doc_items_longdesc = S("A mummy spawner causes hostile mummies to appear in its vicinity as long it exists."),
|
|
paramtype = "light",
|
|
tiles = {"tsm_pyramids_spawner.png"},
|
|
is_ground_content = false,
|
|
drawtype = "allfaces",
|
|
groups = {cracky=1,level=1},
|
|
drop = "",
|
|
on_construct = function(pos)
|
|
spawn_mummy_spawner_entity(pos)
|
|
end,
|
|
on_punch = function(pos)
|
|
respawn_mummy_spawner_entity(pos)
|
|
end,
|
|
on_destruct = function(pos)
|
|
for _,obj in ipairs(minetest.get_objects_inside_radius(pos, 0.5)) do
|
|
if obj ~= nil and not obj:is_player() then
|
|
if obj:get_luaentity().name == "tsm_pyramids:mummy_spawner" then
|
|
obj:remove()
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
sounds = spawnersounds,
|
|
})
|
|
|
|
-- Neccessary in case the spawner entity got lost due to /clearobjects
|
|
minetest.register_lbm({
|
|
label = "Respawn mummy spawner entity",
|
|
name = "tsm_pyramids:respawn_mummy_spawner_entity",
|
|
nodenames = { "tsm_pyramids:spawner_mummy" },
|
|
run_at_every_load = true,
|
|
action = function(pos, node)
|
|
respawn_mummy_spawner_entity(pos)
|
|
end,
|
|
})
|
|
|
|
-- Attempt to spawn a mummy at a random appropriate position around pos.
|
|
-- Criteria:
|
|
-- * Must be close to pos
|
|
-- * Not in sunlight
|
|
-- * Must be air on top of a non-air block
|
|
-- * No more than 6 mummies in area
|
|
-- * Player must be near is player_near_required is true
|
|
function tsm_pyramids.attempt_mummy_spawn(pos, player_near_required)
|
|
local player_near = false
|
|
local mobs = 0
|
|
for _,obj in ipairs(minetest.get_objects_inside_radius(pos, spawner_check_range)) do
|
|
if obj:is_player() then
|
|
player_near = true
|
|
else
|
|
if obj:get_luaentity() and obj:get_luaentity().name == "tsm_pyramids:mummy" then
|
|
mobs = mobs + 1
|
|
end
|
|
end
|
|
end
|
|
if player_near or (not player_near_required) then
|
|
if mobs < spawner_max_mobs then
|
|
local offset = {x=5,y=2,z=5}
|
|
local nposses = minetest.find_nodes_in_area(vector.subtract(pos, offset), vector.add(pos,offset), "air")
|
|
local tries = math.min(6, #nposses)
|
|
for i=1, tries do
|
|
local r = math.random(1, #nposses)
|
|
local npos = nposses[r]
|
|
-- Check if mummy has 2 nodes of free space
|
|
local two_space = false
|
|
-- Check if mummy has something to walk on
|
|
local footing = false
|
|
-- Find the lowest node
|
|
for y=-1, -5, -1 do
|
|
npos.y = npos.y - 1
|
|
local below = minetest.get_node(npos)
|
|
if minetest.registered_items[below.name].liquidtype ~= "none" then
|
|
break
|
|
end
|
|
if below.name ~= "air" then
|
|
if y < -1 then
|
|
two_space = true
|
|
end
|
|
npos.y = npos.y + 1
|
|
footing = true
|
|
break
|
|
end
|
|
end
|
|
local light = minetest.get_node_light(npos, 0.5)
|
|
if not two_space then
|
|
local above = minetest.get_node({x=npos.x, y=npos.y+1, z=npos.z})
|
|
if above.name == "air" then
|
|
two_space = true
|
|
end
|
|
end
|
|
if footing and two_space and light < 15 then
|
|
tsm_pyramids.spawn_mummy_at(npos, 1)
|
|
break
|
|
else
|
|
table.remove(nposses, r)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if not minetest.settings:get_bool("only_peaceful_mobs") then
|
|
minetest.register_abm({
|
|
nodenames = {"tsm_pyramids:spawner_mummy"},
|
|
interval = 2.0,
|
|
chance = 20,
|
|
action = function(pos, node, active_object_count, active_object_count_wider)
|
|
tsm_pyramids.attempt_mummy_spawn(pos, true)
|
|
end,
|
|
})
|
|
end
|
|
|
|
if minetest.get_modpath("awards") then
|
|
awards.register_achievement("tsm_pyramids_no_mummy_spawner", {
|
|
title = S("No more mummies!"),
|
|
description = S("Destroy a mummy spawner by digging."),
|
|
secret = true,
|
|
icon = "tsm_pyramids_spawner.png",
|
|
trigger = {
|
|
type = "dig",
|
|
node = "tsm_pyramids:spawner_mummy",
|
|
target = 1
|
|
}
|
|
})
|
|
end
|