745 lines
24 KiB
Lua
745 lines
24 KiB
Lua
local S, PS = minetest.get_translator("lzr_parrot_npc")
|
||
local FS = function(...) return minetest.formspec_escape(S(...)) end
|
||
local NS = function(s) return s end
|
||
|
||
local F = minetest.formspec_escape
|
||
|
||
local mod_storage = minetest.get_mod_storage()
|
||
|
||
lzr_parrot_npc = {}
|
||
|
||
--~ Parrot name. The player’s main companion and hint-giver. Named after gold
|
||
local GOLDIE_NAME = NS("Goldie the Parrot")
|
||
--~ Parrot name. The player’s main companion and hint-giver. Named after gold
|
||
local GOLDIE_NAME_SHORT = NS("Goldie")
|
||
|
||
--~ @1 is a parrot name
|
||
local SAYS = NS("@1 says:")
|
||
|
||
local HIDDEN_PARROT_NOT_FOUND = 0
|
||
local HIDDEN_PARROT_FOUND = 1
|
||
|
||
local HIDDEN_PARROT_FLY_HEIGHT = 20
|
||
local HIDDEN_PARROT_FLY_SPEED = 3
|
||
local HIDDEN_PARROT_FLY_DESPAWN_DISTANCE = 200
|
||
local HIDDEN_PARROT_FLY_DELAY = 2.0
|
||
|
||
-- Mininum and maximum delay in second between idle parrot animations
|
||
local PARROT_ANIMATION_DELAY_MIN = 2.0
|
||
local PARROT_ANIMATION_DELAY_MAX = 8.0
|
||
|
||
-- Check if parrot is stuck in solid block every this many seconds
|
||
local PARROT_STUCK_CHECK_INTERVAL = 1.0
|
||
-- Vertical fly speed of parrot to try to get unstuck
|
||
local PARROT_UNSTUCK_FLY_SPEED = 2.0
|
||
-- Vertical fly speed of parrot if falling
|
||
local PARROT_FALL_FLY_SPEED = -1.0
|
||
|
||
-- Pitch of parrot call sound when scorched
|
||
local PARROT_SCORCHED_CALL_PITCH = 0.8
|
||
|
||
-- Holds name and object of hidden parrot speaker who last spawned a speak dialog
|
||
local last_hidden_speaker = nil
|
||
local last_hidden_speaker_object = nil
|
||
|
||
local PARROT_ANIMS = {
|
||
idle = { frame_range = { x=0, y=0 }, frame_speed = 2 },
|
||
flap_small = { frame_range = { x=0, y=2 }, frame_speed = 6 },
|
||
flap_big = { frame_range = { x=2, y=4 }, frame_speed = 20 },
|
||
head_bounce = { frame_range = { x=4, y=6 }, frame_speed = 10 },
|
||
head_roll_right = { frame_range = { x=6, y=8 }, frame_speed = 2 },
|
||
head_roll_left = { frame_range = { x=8, y=10 }, frame_speed = 2 },
|
||
head_roll_leftright = { frame_range = { x=6, y=10 }, frame_speed = 2 },
|
||
}
|
||
|
||
local function make_call(to_player, pitch)
|
||
minetest.sound_play({name="lzr_parrot_npc_curr"}, {to_player=to_player:get_player_name(), pitch=pitch})
|
||
end
|
||
|
||
local speaker_portraits = {
|
||
goldie = "lzr_parrot_npc_goldie_portrait.png",
|
||
ruby = "lzr_parrot_npc_ruby_portrait.png",
|
||
emmy = "lzr_parrot_npc_emmy_portrait.png",
|
||
saphie = "lzr_parrot_npc_saphie_portrait.png",
|
||
garnie = "lzr_parrot_npc_garnie_portrait.png",
|
||
tuckie = "lzr_parrot_npc_tuckie_portrait.png",
|
||
dimey = "lzr_parrot_npc_dimey_portrait.png",
|
||
}
|
||
|
||
-- List of hidden parrots. These parrots, together with Goldie,
|
||
-- replicate the 7 laser colors.
|
||
local hidden_parrots = {
|
||
"ruby",
|
||
"emmy",
|
||
"saphie",
|
||
"garnie",
|
||
"tuckie",
|
||
"dimey",
|
||
}
|
||
local hidden_parrot_names = {
|
||
--~ Parrot name; named after ruby, a red gemstone
|
||
ruby = NS("Ruby the Hidden Parrot"),
|
||
--~ Parrot name; named after emerald, a green gemstone
|
||
emmy = NS("Emmy the Hidden Parrot"),
|
||
--~ Parrot name; named after saphire, a blue gemstone
|
||
saphie = NS("Saphie the Hidden Parrot"),
|
||
--~ Parrot name; named after garnet, a magenta gemstone
|
||
garnie = NS("Garnie the Hidden Parrot"),
|
||
--~ Parrot name; named after turquoise, a gemstone
|
||
tuckie = NS("Tuckie the Hidden Parrot"),
|
||
--~ Parrot name; named after diamond
|
||
dimey = NS("Dimey the Hidden Parrot"),
|
||
}
|
||
local hidden_parrot_names_short = {
|
||
--~ Parrot name; named after ruby, a red gemstone
|
||
ruby = NS("Ruby"),
|
||
--~ Parrot name; named after emerald, a green gemstone
|
||
emmy = NS("Emmy"),
|
||
--~ Parrot name; named after saphire, a blue gemstone
|
||
saphie = NS("Saphie"),
|
||
--~ Parrot name; named after garnet, a magenta gemstone
|
||
garnie = NS("Garnie"),
|
||
--~ Parrot name; named after turquoise, a gemstone
|
||
tuckie = NS("Tuckie"),
|
||
--~ Parrot name; named after diamond
|
||
dimey = NS("Dimey"),
|
||
}
|
||
|
||
lzr_parrot_npc.get_hidden_parrot_name = function(num)
|
||
return hidden_parrots[num]
|
||
end
|
||
|
||
lzr_parrot_npc.was_hidden_parrot_found = function(parrot_name)
|
||
local found = mod_storage:get_int("lzr_parrot_npc:hidden_parrot_"..parrot_name)
|
||
if found == HIDDEN_PARROT_FOUND then
|
||
return true
|
||
else
|
||
return false
|
||
end
|
||
end
|
||
|
||
lzr_parrot_npc.were_all_hidden_parrots_found = function()
|
||
for p=1, #hidden_parrots do
|
||
if not lzr_parrot_npc.was_hidden_parrot_found(hidden_parrots[p]) then
|
||
return false
|
||
end
|
||
end
|
||
return true
|
||
end
|
||
|
||
lzr_parrot_npc.count_found_hidden_parrots = function()
|
||
local found = 0
|
||
for p=1, #hidden_parrots do
|
||
if lzr_parrot_npc.was_hidden_parrot_found(hidden_parrots[p]) then
|
||
found = found + 1
|
||
end
|
||
end
|
||
return found
|
||
end
|
||
|
||
lzr_parrot_npc.speak = function(player, message, speaker, is_scorched)
|
||
local portrait
|
||
if is_scorched then
|
||
portrait = "lzr_parrot_npc_scorched_portrait.png"
|
||
else
|
||
portrait = speaker_portraits[speaker]
|
||
end
|
||
local title
|
||
if speaker == "goldie" then
|
||
title = FS(SAYS, S(GOLDIE_NAME))
|
||
else
|
||
title = FS(SAYS, S(hidden_parrot_names[speaker]))
|
||
end
|
||
local form = "formspec_version[7]size[10,5.5]"..
|
||
"box[0,0;10,0.8;#0000004f]"..
|
||
"label[0.4,0.4;"..title.."]"..
|
||
"box[0.5,1;2.0,3;#0000002f]"..
|
||
"image[0.75,1;1.5,3;"..portrait.."]"..
|
||
"box[3,1;6.5,3;#ffffff1f]"..
|
||
"textarea[3,1;6.5,3;;;"..F(message).."]"..
|
||
"button_exit[3.5,4.4;3,0.8;ok;"..FS("OK").."]"
|
||
minetest.show_formspec(player:get_player_name(), "lzr_parrot_npc:speech", form)
|
||
|
||
local pitch
|
||
if is_scorched then
|
||
pitch = PARROT_SCORCHED_CALL_PITCH
|
||
end
|
||
make_call(player, pitch)
|
||
end
|
||
|
||
-- Make parrot face player
|
||
local face_player = function(parrot, player)
|
||
local papos = parrot.object:get_pos()
|
||
local plpos = player:get_pos()
|
||
papos.y = 0
|
||
plpos.y = 0
|
||
local dir = vector.direction(papos, plpos)
|
||
local yaw = minetest.dir_to_yaw(dir)
|
||
parrot.object:set_yaw(yaw)
|
||
end
|
||
|
||
local texture_repeat = function(texture)
|
||
local textures = {}
|
||
for t=1, 10 do
|
||
table.insert(textures, texture)
|
||
end
|
||
return textures
|
||
end
|
||
|
||
-- Make the given parrot object react to player.
|
||
-- Shows associated text of current level (if present),
|
||
-- show
|
||
local react = function(parrot, player)
|
||
if player and player:is_player() then
|
||
local state = lzr_gamestate.get_state()
|
||
local spoken = false
|
||
if parrot._scorched then
|
||
--~ Parrot speech indicating a parrot is coughing after an explosion
|
||
lzr_parrot_npc.speak(player, S("*cough* *cough*"), "goldie", true)
|
||
spoken = true
|
||
elseif state == lzr_gamestate.LEVEL or state == lzr_gamestate.LEVEL_COMPLETE then
|
||
local speeches = lzr_levels.get_npc_texts()
|
||
if speeches and speeches.goldie and speeches.goldie ~= "" then
|
||
lzr_parrot_npc.speak(player, speeches.goldie, "goldie")
|
||
spoken = true
|
||
end
|
||
elseif state == lzr_gamestate.MENU then
|
||
local treasures = lzr_levels.count_total_collected_treasures()
|
||
local text
|
||
if treasures == 0 then
|
||
text = S("I’m sad because we don’t have any treasures.")
|
||
else
|
||
text = PS("We have @1 gold block in our possession.", "We have @1 gold blocks in our possession.", treasures, treasures)
|
||
end
|
||
lzr_parrot_npc.speak(player, text, "goldie")
|
||
spoken = true
|
||
end
|
||
if not spoken then
|
||
make_call(player)
|
||
end
|
||
|
||
face_player(parrot, player)
|
||
|
||
-- Bounce head
|
||
if parrot._animation_timer > 0.5 and not parrot._falling and not parrot._unstucking then
|
||
local anim = PARROT_ANIMS.head_bounce
|
||
parrot.object:set_animation(anim.frame_range, anim.frame_speed, 0, false)
|
||
parrot._animation_timer = 0
|
||
parrot._next_animation_at = math.random(PARROT_ANIMATION_DELAY_MIN, PARROT_ANIMATION_DELAY_MAX)
|
||
end
|
||
end
|
||
end
|
||
|
||
local pos_needs_unstuck = function(pos)
|
||
local node = minetest.get_node(pos)
|
||
local def = minetest.registered_nodes[node.name]
|
||
if def and def.walkable and minetest.get_item_group(node.name, "takable") ~= 0 then
|
||
local apos = vector.offset(pos, 0, 1, 0)
|
||
local anode = minetest.get_node(apos)
|
||
local adef = minetest.registered_nodes[anode.name]
|
||
if adef and adef.walkable then
|
||
if minetest.get_item_group(anode.name, "takable") ~= 0 then
|
||
return true
|
||
else
|
||
return false
|
||
end
|
||
else
|
||
return true
|
||
end
|
||
else
|
||
return false
|
||
end
|
||
end
|
||
local is_on_solid = function(pos)
|
||
local bpos = vector.offset(pos, 0, -0.05, 0)
|
||
local node = minetest.get_node(bpos)
|
||
local def = minetest.registered_nodes[node.name]
|
||
if not def or def.walkable then
|
||
return true
|
||
else
|
||
return false
|
||
end
|
||
end
|
||
|
||
local parrot_check_stuck = function(self)
|
||
if not self._unstucking and not self._falling then
|
||
local pos = self.object:get_pos()
|
||
if pos_needs_unstuck(pos) then
|
||
self._unstucking = true
|
||
self._falling = false
|
||
self.object:set_velocity({x=0, y=PARROT_UNSTUCK_FLY_SPEED, z=0})
|
||
local anim = PARROT_ANIMS.flap_big
|
||
self.object:set_animation(anim.frame_range, anim.frame_speed, 0, true)
|
||
elseif not is_on_solid(pos) then
|
||
self._falling = true
|
||
self._unstucking = false
|
||
self.object:set_velocity({x=0, y=PARROT_FALL_FLY_SPEED, z=0})
|
||
local anim = PARROT_ANIMS.flap_big
|
||
self.object:set_animation(anim.frame_range, anim.frame_speed, 0, true)
|
||
end
|
||
self._stuck_check_timer = 0
|
||
end
|
||
end
|
||
|
||
local parrot_activate = function(self)
|
||
self.object:set_armor_groups({immortal=1})
|
||
self._next_animation_at = math.random(PARROT_ANIMATION_DELAY_MIN, PARROT_ANIMATION_DELAY_MAX)
|
||
end
|
||
local parrot_activate_goldie = function(self)
|
||
parrot_activate(self)
|
||
self.object:set_properties({
|
||
infotext = S(GOLDIE_NAME_SHORT)
|
||
})
|
||
end
|
||
|
||
local parrot_step_idle = function(self, dtime)
|
||
if self._scorched then
|
||
self._scorched_timer = self._scorched_timer + dtime
|
||
if self._scorched_timer >= self._unscorched_time then
|
||
self.object:set_properties({
|
||
textures = self._base_textures,
|
||
})
|
||
self._scorched = false
|
||
end
|
||
end
|
||
self._stuck_check_timer = self._stuck_check_timer + dtime
|
||
if self._stuck_check_timer >= PARROT_STUCK_CHECK_INTERVAL then
|
||
parrot_check_stuck(self)
|
||
end
|
||
if self._unstucking then
|
||
local pos = self.object:get_pos()
|
||
if not pos_needs_unstuck(pos) then
|
||
self.object:set_velocity({x=0, y=0, z=0})
|
||
self.object:set_animation(PARROT_ANIMS.idle.frame_range, PARROT_ANIMS.idle.frame_speed, 0, false)
|
||
self._falling = false
|
||
self._unstucking = false
|
||
end
|
||
return
|
||
elseif self._falling then
|
||
local pos = self.object:get_pos()
|
||
if is_on_solid(pos) then
|
||
self.object:set_velocity({x=0, y=0, z=0})
|
||
self.object:set_animation(PARROT_ANIMS.idle.frame_range, PARROT_ANIMS.idle.frame_speed, 0, false)
|
||
self._falling = false
|
||
self._unstucking = false
|
||
end
|
||
return
|
||
end
|
||
|
||
-- Play random animation in random intervals
|
||
self._animation_timer = self._animation_timer + dtime
|
||
if self._animation_timer >= self._next_animation_at then
|
||
local idle_anims = {
|
||
"flap_small",
|
||
"flap_small",
|
||
"flap_small",
|
||
"head_bounce",
|
||
"head_roll_right",
|
||
"head_roll_right",
|
||
"head_roll_right",
|
||
"head_roll_left",
|
||
"head_roll_left",
|
||
"head_roll_left",
|
||
}
|
||
local a = math.random(1, #idle_anims)
|
||
local anim = PARROT_ANIMS[idle_anims[a]]
|
||
self.object:set_animation(anim.frame_range, anim.frame_speed, 0, false)
|
||
|
||
self._animation_timer = 0
|
||
self._next_animation_at = math.random(PARROT_ANIMATION_DELAY_MIN, PARROT_ANIMATION_DELAY_MAX)
|
||
end
|
||
end
|
||
|
||
local parrot_scorch = function(self, time)
|
||
self.object:set_properties({
|
||
textures=texture_repeat("lzr_parrot_npc_scorched.png"),
|
||
})
|
||
self._scorched = true
|
||
self._scorched_timer = 0
|
||
self._unscorched_time = time
|
||
end
|
||
|
||
minetest.register_entity("lzr_parrot_npc:parrot", {
|
||
initial_properties = {
|
||
visual = "mesh",
|
||
mesh = "lzr_parrot_npc_parrot.gltf",
|
||
visual_size = { x=1, y=1, z=1 },
|
||
textures = texture_repeat("lzr_parrot_npc_goldie.png"),
|
||
backface_culling = false,
|
||
static_save = false,
|
||
physical = false,
|
||
collide_with_objects = false,
|
||
selectionbox = {
|
||
-0.15, 0, -0.15, 0.15, 0.65, 0.3, rotate = true,
|
||
},
|
||
},
|
||
_animation_timer = 0,
|
||
_next_animation_at = nil,
|
||
_stuck_check_timer = 0,
|
||
_unstucking = false,
|
||
_scorched = false,
|
||
_scorched_timer = nil,
|
||
_unchorched_time = nil,
|
||
_scorch = parrot_scorch,
|
||
_base_textures = texture_repeat("lzr_parrot_npc_goldie.png"),
|
||
|
||
on_activate = parrot_activate_goldie,
|
||
on_step = parrot_step_idle,
|
||
on_rightclick = function(self, clicker)
|
||
react(self, clicker)
|
||
end,
|
||
on_punch = function(self, puncher)
|
||
react(self, puncher)
|
||
end,
|
||
})
|
||
|
||
local mark_hidden_parrot_as_found = function(player, parrot_name)
|
||
local found = mod_storage:get_int("lzr_parrot_npc:hidden_parrot_"..parrot_name)
|
||
if found == HIDDEN_PARROT_FOUND then
|
||
if lzr_parrot_npc.were_all_hidden_parrots_found() then
|
||
lzr_menu.place_painting("parrot_finder")
|
||
end
|
||
return false
|
||
else
|
||
mod_storage:set_int("lzr_parrot_npc:hidden_parrot_"..parrot_name, HIDDEN_PARROT_FOUND)
|
||
lzr_menu.spawn_hidden_parrot(parrot_name)
|
||
minetest.log("action", "[lzr_parrot_npc] Player found hidden parrot '"..parrot_name.."'")
|
||
if lzr_parrot_npc.were_all_hidden_parrots_found() then
|
||
lzr_menu.place_painting("parrot_finder")
|
||
end
|
||
return true
|
||
end
|
||
end
|
||
|
||
local react_hidden = function(parrot, player)
|
||
if player and player:is_player() then
|
||
local hidden_parrot_identity = parrot._hidden_id
|
||
local call_pitch
|
||
if hidden_parrot_identity and mark_hidden_parrot_as_found(player, hidden_parrot_identity) then
|
||
local found = lzr_parrot_npc.count_found_hidden_parrots()
|
||
local text
|
||
|
||
-- First found parrot
|
||
if found == 1 then
|
||
local friends = {}
|
||
for h=1, #hidden_parrots do
|
||
if hidden_parrots[h] ~= hidden_parrot_identity then
|
||
table.insert(friends, S(hidden_parrot_names_short[hidden_parrots[h]]))
|
||
end
|
||
end
|
||
local f1, f2, f3, f4, f5 = friends[1], friends[2], friends[3], friends[4], friends[5]
|
||
text = S("Hey, you found my secret hideout! Now it’s useless, such a shame …").."\n"..
|
||
S("I’ve got an idea. Let me come with you, okay?").."\n\n"..
|
||
S("I have five friends scattered around the world, I want to meet them again. Their names are @1, @2, @3, @4 and @5.", f1, f2, f3, f4, f5)
|
||
-- Last found parrot
|
||
elseif found == #hidden_parrots then
|
||
text = S("Incredible! It’s you! I’ve heard a lot from your journeys. Now the whole gang is re-united!").."\n"
|
||
if hidden_parrot_identity == "tuckie" then
|
||
text = text .. S("I’ve got a beautiful portrait of myself. You can have it. How nice is that?")
|
||
else
|
||
text = text .. S("I’ve found a beautiful portrait of our friend Tuckie. You can have it. How nice is that?")
|
||
end
|
||
-- Found parrot, neither first or last
|
||
else
|
||
local remaining = #hidden_parrots - found
|
||
text = S("You found my secret hiding spot!").."\n"..
|
||
PS("Only @1 parrot is still in hiding.", "There are still @1 hidden parrots left to go.", remaining, remaining)
|
||
end
|
||
|
||
last_hidden_speaker = hidden_parrot_identity
|
||
last_hidden_speaker_object = parrot
|
||
lzr_parrot_npc.speak(player, text, hidden_parrot_identity, parrot._scorched == true)
|
||
face_player(parrot, player)
|
||
return
|
||
else
|
||
make_call(player, call_pitch)
|
||
end
|
||
if parrot._flying_home_phase == 0 then
|
||
face_player(parrot, player)
|
||
-- Bounce head
|
||
if parrot._animation_timer > 0.5 then
|
||
local anim = PARROT_ANIMS.head_bounce
|
||
parrot.object:set_animation(anim.frame_range, anim.frame_speed, 0, false)
|
||
parrot._animation_timer = 0
|
||
parrot._next_animation_at = math.random(PARROT_ANIMATION_DELAY_MIN, PARROT_ANIMATION_DELAY_MAX)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
local hidden_parrot_fly_home = function(parrot)
|
||
if parrot._flying_home_phase < 1 then
|
||
parrot._flying_home_phase = 1
|
||
parrot._fly_timer = 0
|
||
minetest.log("action", "[lzr_parrot_npc] Hidden parrot will fly home soon: "..tostring(last_hidden_speaker))
|
||
end
|
||
end
|
||
|
||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||
if formname == "lzr_parrot_npc:speech" and fields.quit and last_hidden_speaker then
|
||
local parrot = last_hidden_speaker_object
|
||
hidden_parrot_fly_home(parrot)
|
||
end
|
||
end)
|
||
|
||
local parrot_step_hidden = function(self, dtime)
|
||
if self._flying_home_phase ~= 0 then
|
||
if self._scorched and self._base_textures then
|
||
self._scorched_timer = self._scorched_timer + dtime
|
||
if self._scorched_timer >= self._unscorched_time then
|
||
self.object:set_properties({
|
||
textures = self._base_textures,
|
||
})
|
||
self._scorched = false
|
||
end
|
||
end
|
||
|
||
-- Phase 1: Wait for a short time before lifting off
|
||
if self._flying_home_phase == 1 then
|
||
self._fly_timer = self._fly_timer + dtime
|
||
if self._fly_timer > HIDDEN_PARROT_FLY_DELAY then
|
||
self._flying_home_phase = 2
|
||
self._fly_start_pos = self.object:get_pos()
|
||
self.object:set_velocity({x=0,y=3,z=0})
|
||
local anim = PARROT_ANIMS.flap_big
|
||
self.object:set_animation(anim.frame_range, anim.frame_speed, 0, true)
|
||
minetest.log("action", "[lzr_parrot_npc] Hidden parrot lifts off: "..tostring(self._hidden_id))
|
||
end
|
||
-- Phase 2: Liftoff! Fly upwards until we reach a certain height
|
||
elseif self._flying_home_phase == 2 then
|
||
local pos = self.object:get_pos()
|
||
local fly_height = pos.y - self._fly_start_pos.y
|
||
if fly_height >= HIDDEN_PARROT_FLY_HEIGHT then
|
||
self.object:set_velocity(vector.zero())
|
||
self._flying_home_phase = 3
|
||
local hpos = table.copy(pos)
|
||
hpos.y = 0
|
||
local spos = table.copy(lzr_globals.MENU_SHIP_POS)
|
||
spos.y = 0
|
||
local dir = vector.direction(hpos, spos)
|
||
local vel = vector.multiply(dir, HIDDEN_PARROT_FLY_SPEED)
|
||
self.object:set_velocity(vel)
|
||
local yaw = minetest.dir_to_yaw(dir)
|
||
self.object:set_yaw(yaw)
|
||
minetest.log("action", "[lzr_parrot_npc] Hidden parrot flies home: "..tostring(self._hidden_id))
|
||
return
|
||
end
|
||
-- Phase 3: Fly horizontally towards the main ship (rough direction)
|
||
elseif self._flying_home_phase == 3 then
|
||
local pos = self.object:get_pos()
|
||
if vector.distance(pos, self._fly_start_pos) > HIDDEN_PARROT_FLY_DESPAWN_DISTANCE then
|
||
minetest.log("action", "[lzr_parrot_npc] Hidden parrot has flown enough and despawns: "..tostring(self._hidden_id))
|
||
self.object:remove()
|
||
return
|
||
end
|
||
end
|
||
else
|
||
parrot_step_idle(self, dtime)
|
||
end
|
||
end
|
||
|
||
-- Hidden Parrot NPC. One of the game's hidden parrots.
|
||
-- When interacted with, the parrot counts as found.
|
||
minetest.register_entity("lzr_parrot_npc:hidden_parrot", {
|
||
initial_properties = {
|
||
visual = "mesh",
|
||
mesh = "lzr_parrot_npc_parrot.gltf",
|
||
visual_size = { x=1, y=1, z=1 },
|
||
textures = {
|
||
texture_repeat("lzr_parrot_npc_ruby.png"),
|
||
},
|
||
static_save = false,
|
||
physical = false,
|
||
collide_with_objects = false,
|
||
selectionbox = {
|
||
-0.15, 0, -0.15, 0.15, 0.65, 0.3, rotate = true,
|
||
},
|
||
backface_culling = false,
|
||
},
|
||
_animation_timer = 0,
|
||
_next_animation_at = nil,
|
||
_stuck_check_timer = 0,
|
||
_unstucking = false,
|
||
_scorched = false,
|
||
_scorched_timer = nil,
|
||
_unchorched_time = nil,
|
||
_scorch = parrot_scorch,
|
||
_base_textures = texture_repeat("lzr_parrot_npc_ruby.png"),
|
||
|
||
_fly_start_pos = nil,
|
||
_fly_timer = 0,
|
||
_flying_home_phase = 0,
|
||
-- Hidden parrot ID
|
||
_hidden_id = nil,
|
||
-- Initialize hidden parrot with the given ID (parrot identifier).
|
||
-- MUST be called right after spawning entity.
|
||
_init = function(self, hidden_id)
|
||
self._hidden_id = hidden_id
|
||
local name = S(hidden_parrot_names_short[hidden_id])
|
||
local textures = texture_repeat("lzr_parrot_npc_"..hidden_id..".png")
|
||
self.object:set_properties({
|
||
textures = textures,
|
||
infotext = name,
|
||
})
|
||
self._base_textures = textures
|
||
end,
|
||
|
||
on_activate = parrot_activate,
|
||
on_step = parrot_step_hidden,
|
||
on_rightclick = function(self, clicker)
|
||
react_hidden(self, clicker)
|
||
end,
|
||
on_punch = function(self, puncher)
|
||
react_hidden(self, puncher)
|
||
end,
|
||
})
|
||
|
||
minetest.register_node("lzr_parrot_npc:stand", {
|
||
description = S("Parrot Stand"),
|
||
drawtype = "nodebox",
|
||
paramtype = "light",
|
||
paramtype2 = "4dir",
|
||
sunlight_propagates = true,
|
||
is_ground_content = false,
|
||
node_box = {
|
||
type = "fixed",
|
||
fixed = {
|
||
{ -0.5, -0.5, -0.5, 0.5, 1/16, 0.5 }, -- base (slab-like)
|
||
{ -1/16, 1/16, -1/16, 1/16, 7/16, 1/16 }, -- vertical stick
|
||
{ -6/16, 7/16, -1/16, 6/16, 0.5, 1/16 }, -- parrot stick
|
||
},
|
||
},
|
||
tiles = { { name = "default_wood.png", align_style = "world" } },
|
||
groups = { breakable = 1 },
|
||
sounds = lzr_sounds.node_sound_wood_defaults(),
|
||
})
|
||
|
||
minetest.register_node("lzr_parrot_npc:parrot_spawner", {
|
||
--~ A block that spawns a normal parrot
|
||
description = S("Parrot Spawner"),
|
||
_tt_help = S("Goldie the Parrot will spawn here").."\n"..
|
||
S("(only one per level allowed)"),
|
||
tiles = {
|
||
{ name = "lzr_parrot_npc_goldie_spawner_top.png", backface_culling = true },
|
||
{ name = "lzr_parrot_npc_goldie_spawner_bottom.png", backface_culling = true },
|
||
{ name = "lzr_parrot_npc_goldie_spawner_left.png", backface_culling = true },
|
||
{ name = "lzr_parrot_npc_goldie_spawner_right.png", backface_culling = true },
|
||
{ name = "lzr_parrot_npc_goldie_spawner_back.png", backface_culling = true },
|
||
{ name = "lzr_parrot_npc_goldie_spawner_front.png", backface_culling = true },
|
||
},
|
||
paramtype2 = "degrotate",
|
||
drawtype = "mesh",
|
||
mesh = "lzr_parrot_npc_cube.obj",
|
||
selection_box = {
|
||
type = "fixed",
|
||
fixed = { -0.4, -0.4, -0.4, 0.4, 0.4, 0.4 },
|
||
},
|
||
visual_scale = 0.8,
|
||
wield_scale = { x=0.8, y=0.8, z=0.8 },
|
||
paramtype = "light",
|
||
sunlight_propagates = true,
|
||
walkable = false,
|
||
groups = { breakable = 1, rotatable = 3, spawner = 1 },
|
||
use_texture_alpha = "clip",
|
||
sounds = {
|
||
_rotate = "",
|
||
},
|
||
})
|
||
|
||
minetest.register_node("lzr_parrot_npc:hidden_parrot_spawner", {
|
||
--~ A block that spawns a hidden parrot
|
||
description = S("Hidden Parrot Spawner"),
|
||
_tt_help = S("A hidden parrot will spawn here"),
|
||
tiles = {
|
||
{ name = "lzr_parrot_npc_hidden_parrot_spawner_top.png", backface_culling = true },
|
||
{ name = "lzr_parrot_npc_hidden_parrot_spawner_bottom.png", backface_culling = true },
|
||
{ name = "lzr_parrot_npc_hidden_parrot_spawner_left.png", backface_culling = true },
|
||
{ name = "lzr_parrot_npc_hidden_parrot_spawner_right.png", backface_culling = true },
|
||
{ name = "lzr_parrot_npc_hidden_parrot_spawner_back.png", backface_culling = true },
|
||
{ name = "lzr_parrot_npc_hidden_parrot_spawner_front.png", backface_culling = true },
|
||
},
|
||
paramtype2 = "color4dir",
|
||
drawtype = "mesh",
|
||
palette = "lzr_parrot_npc_hidden_parrot_spawner_palette.png",
|
||
mesh = "lzr_parrot_npc_cube.obj",
|
||
selection_box = {
|
||
type = "fixed",
|
||
fixed = { -0.4, -0.4, -0.4, 0.4, 0.4, 0.4 },
|
||
},
|
||
visual_scale = 0.8,
|
||
wield_scale = { x=0.8, y=0.8, z=0.8 },
|
||
paramtype = "light",
|
||
sunlight_propagates = true,
|
||
walkable = false,
|
||
-- hide the hidden parrot spawner ;)
|
||
groups = { breakable = 1, rotatable = 3, spawner = 1, not_in_creative_inventory = 1 },
|
||
use_texture_alpha = "clip",
|
||
sounds = {
|
||
_rotate = "",
|
||
},
|
||
drop = "lzr_parrot_npc:hidden_parrot_spawner",
|
||
})
|
||
|
||
function lzr_parrot_npc.clear_hidden_parrot_progress()
|
||
for h=1, #hidden_parrots do
|
||
mod_storage:set_int("lzr_parrot_npc:hidden_parrot_"..hidden_parrots[h], HIDDEN_PARROT_NOT_FOUND)
|
||
end
|
||
lzr_menu.remove_painting("parrot_finder")
|
||
|
||
minetest.log("action", "[lzr_parrot_npc] Hidden parrot progress was cleared")
|
||
end
|
||
|
||
local function spawn_menu_parrots()
|
||
for p=1, #hidden_parrots do
|
||
local parrot_name = hidden_parrots[p]
|
||
local found = mod_storage:get_int("lzr_parrot_npc:hidden_parrot_"..parrot_name)
|
||
if found == HIDDEN_PARROT_FOUND then
|
||
lzr_menu.spawn_hidden_parrot(parrot_name)
|
||
end
|
||
end
|
||
end
|
||
|
||
lzr_menu.register_on_ship_built(function()
|
||
spawn_menu_parrots()
|
||
end)
|
||
|
||
lzr_menu.register_on_ship_rebuilt(function()
|
||
spawn_menu_parrots()
|
||
if lzr_parrot_npc.were_all_hidden_parrots_found() then
|
||
lzr_menu.place_painting("parrot_finder")
|
||
end
|
||
end)
|
||
|
||
lzr_menu.register_on_player_ship_enter(function()
|
||
spawn_menu_parrots()
|
||
end)
|
||
|
||
-- Notify parrots to instantly trigger unstuck algorithm when a node was placed
|
||
minetest.register_on_placenode(function(pos, node)
|
||
local def = minetest.registered_nodes[node.name]
|
||
if def and def.walkable then
|
||
local objs = minetest.get_objects_inside_radius(pos, 1)
|
||
for o=1, #objs do
|
||
local obj = objs[o]
|
||
local ent = obj:get_luaentity()
|
||
if ent and (ent.name == "lzr_parrot_npc:parrot" or ent.name == "lzr_parrot_npc:hidden_parrot") then
|
||
parrot_check_stuck(ent)
|
||
end
|
||
end
|
||
end
|
||
end)
|
||
-- Notify parrots to instantly trigger unstuck algorithm when a node was dug
|
||
minetest.register_on_dignode(function(pos, node)
|
||
local apos = vector.offset(pos, 0, 1, 0)
|
||
local objs = minetest.get_objects_inside_radius(apos, 1)
|
||
for o=1, #objs do
|
||
local obj = objs[o]
|
||
local ent = obj:get_luaentity()
|
||
if ent and (ent.name == "lzr_parrot_npc:parrot" or ent.name == "lzr_parrot_npc:hidden_parrot") then
|
||
parrot_check_stuck(ent)
|
||
end
|
||
end
|
||
end)
|
||
|
||
|