From 5bdc3c3c290c4e4b9b880273de46a42fc882687c Mon Sep 17 00:00:00 2001 From: Hector Franqui Date: Wed, 18 Oct 2017 09:05:08 -0400 Subject: [PATCH] NPC: Fix animation state not persistent on interaction/game restart. Action lock/unlock doesn't perform lock if movement state is sitting or laying. Right-click interaction can now be disabled in freeze action command. Upon NPC activation, animation is restored. --- actions/actions.lua | 13 +- data/occupations/default.lua | 16 ++- npc.lua | 247 +++++++++++++++++++++++++++++------ 3 files changed, 229 insertions(+), 47 deletions(-) diff --git a/actions/actions.lua b/actions/actions.lua index fce6f47..3b84641 100644 --- a/actions/actions.lua +++ b/actions/actions.lua @@ -199,8 +199,13 @@ end -- and the NPC is allowed to roam freely. function npc.actions.freeze(self, args) local freeze_mobs_api = args.freeze - --minetest.log("Received: "..dump(freeze_mobs_api)) - --minetest.log("Returning: "..dump(not(freeze_mobs_api))) + local disable_rightclick = args.disable_rightclick + if disable_rightclick ~= nil then + npc.log("INFO", "Enabling interactions for NPC "..self.npc_name..": "..dump(not(disable_rightclick))) + self.enable_rightclick_interaction = not(disable_rightclick) + end +-- minetest.log("Received: "..dump(freeze_mobs_api)) +-- minetest.log("Returning: "..dump(not(freeze_mobs_api))) return not(freeze_mobs_api) end @@ -904,6 +909,7 @@ function npc.actions.use_bed(self, args) -- Set place as used npc.places.mark_place_used(pos, npc.places.USE_STATE.USED) end + self.actions.move_state.is_laying = true else -- Calculate position to get up -- Error here due to ignore. Need to come up with better solution @@ -953,6 +959,7 @@ function npc.actions.use_bed(self, args) -- Set place as unused npc.places.mark_place_used(pos, npc.places.USE_STATE.NOT_USED) end + self.actions.move_state.is_laying = false end end @@ -981,6 +988,7 @@ function npc.actions.use_sittable(self, args) -- Set place as used npc.places.mark_place_used(pos, npc.places.USE_STATE.USED) end + self.actions.move_state.is_sitting = true else -- Find empty areas around chair local dir = node.param2 + 2 % 4 @@ -1005,6 +1013,7 @@ function npc.actions.use_sittable(self, args) -- Set place as unused npc.places.mark_place_used(pos, npc.places.USE_STATE.NOT_USED) end + self.actions.move_state.is_sitting = false end end diff --git a/data/occupations/default.lua b/data/occupations/default.lua index caa2965..a930d26 100644 --- a/data/occupations/default.lua +++ b/data/occupations/default.lua @@ -31,16 +31,18 @@ local basic_def = { } }, -- Walk to home inside - [2] = {task = npc.actions.cmd.WALK_TO_POS, args = { - end_pos = npc.places.PLACE_TYPE.OTHER.HOME_INSIDE, - walkable = {} - }, + [2] = { + task = npc.actions.cmd.WALK_TO_POS, + args = { + end_pos = npc.places.PLACE_TYPE.OTHER.HOME_INSIDE, + walkable = {} + }, chance = 75 }, -- Allow mobs_redo wandering - [3] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}} + [3] = {action = npc.actions.cmd.FREEZE, args = {freeze = false, disable_rightclick = false}} }, - -- Schedule entry for 7 in the morning + -- Schedule entry for 8 in the morning [8] = { -- Walk to outside of home [1] = {task = npc.actions.cmd.WALK_TO_POS, args = { @@ -157,7 +159,7 @@ local basic_def = { } }, -- Stay put on bed - [3] = {action = npc.actions.cmd.FREEZE, args = {freeze = true}} + [3] = {action = npc.actions.cmd.FREEZE, args = {freeze = true, disable_rightclick = true}} } } } diff --git a/npc.lua b/npc.lua index 7547988..6a1704f 100755 --- a/npc.lua +++ b/npc.lua @@ -285,6 +285,10 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats, occupation_name) -- Avoid NPC to be removed by mobs_redo API ent.remove_ok = false + -- Flag that enables/disables right-click interaction - good for moments where NPC + -- can't be disturbed + ent.enable_rightclick_interaction = true + -- Determine sex and age -- If there's no previous NPC data, sex and age will be randomly chosen. -- - Sex: Female or male will have each 50% of spawning @@ -454,6 +458,13 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats, occupation_name) -- Action executed while on lock interrupted_action = {} }, + -- Variables that allows preserving the movement state and NPC animation + move_state = { + -- Whether a NPC is sitted or not + is_sitting = false, + -- Whether a NPC is laying or not + is_laying = false + }, -- Walking variables -- required for implementing accurate movement code walking = { -- Defines whether NPC is walking to specific position or not @@ -464,7 +475,7 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats, occupation_name) -- This is NOT the end of the path, but the next position in the path -- relative to the last position target_pos = {} - } + }, } -- This flag is checked on every step. If it is true, the rest of @@ -806,6 +817,12 @@ function npc.lock_actions(self) if self.actions.action_timer_lock == true then return end + -- Check if NPC is in unmovable state + if self.actions.move_state + and (self.actions.move_state.is_sitting == true or self.actions.move_state.is_laying == true) then + -- Can't lock actions since NPC is in a non-movable state + return + end local pos = self.object:getpos() @@ -843,6 +860,12 @@ function npc.lock_actions(self) end function npc.unlock_actions(self) + -- Check if the NPC is sitting or laying states + if self.actions.move_state + and (self.actions.move_state.is_sitting == true or self.actions.move_state.is_laying == true) then + -- Can't unlock actions since NPC is in a non-movable state + return + end -- Allow timers to execute self.actions.action_timer_lock = false -- Restore the value of self.freeze @@ -1263,6 +1286,51 @@ function npc.schedule_check(self) end end +--------------------------------------------------------------------------------------- +-- NPC Lua object functions +--------------------------------------------------------------------------------------- +-- The following functions make up the definitions of on_rightclick(), do_custom() +-- and other functions that are assigned to the Lua entity definition +function npc.rightclick_interaction(self, clicker) + -- Rotate NPC toward its clicker + npc.dialogue.rotate_npc_to_player(self) + + -- Get information from clicker + local item = clicker:get_wielded_item() + local name = clicker:get_player_name() + + npc.log("DEBUG", "Right-clicked NPC: "..dump(self)) + + -- Receive gift or start chat. If player has no item in hand + -- then it is going to start chat directly + --minetest.log("self.can_have_relationship: "..dump(self.can_have_relationship)..", self.can_receive_gifts: "..dump(self.can_receive_gifts)..", table: "..dump(item:to_table())) + if self.can_have_relationship + and self.can_receive_gifts + and item:to_table() ~= nil then + -- Get item name + local item = minetest.registered_items[item:get_name()] + local item_name = item.description + + -- Show dialogue to confirm that player is giving item as gift + npc.dialogue.show_yes_no_dialogue( + self, + "Do you want to give "..item_name.." to "..self.npc_name.."?", + npc.dialogue.POSITIVE_GIFT_ANSWER_PREFIX..item_name, + function() + npc.relationships.receive_gift(self, clicker) + end, + npc.dialogue.NEGATIVE_ANSWER_LABEL, + function() + npc.start_dialogue(self, clicker, true) + end, + name + ) + else + npc.start_dialogue(self, clicker, true) + end +end + + --------------------------------------------------------------------------------------- -- NPC Definition --------------------------------------------------------------------------------------- @@ -1319,7 +1387,7 @@ mobs:register_mob("advanced_npc:npc", { makes_footstep_sound = true, sounds = {}, -- Added walk chance - walk_chance = 30, + walk_chance = 20, -- Added stepheight stepheight = 0.6, walk_velocity = 1, @@ -1352,42 +1420,9 @@ mobs:register_mob("advanced_npc:npc", { punch_end = 219, }, on_rightclick = function(self, clicker) - - -- Rotate NPC toward its clicker - npc.dialogue.rotate_npc_to_player(self) - - -- Get information from clicker - local item = clicker:get_wielded_item() - local name = clicker:get_player_name() - - npc.log("DEBUG", "Right-clicked NPC: "..dump(self)) - - -- Receive gift or start chat. If player has no item in hand - -- then it is going to start chat directly - --minetest.log("self.can_have_relationship: "..dump(self.can_have_relationship)..", self.can_receive_gifts: "..dump(self.can_receive_gifts)..", table: "..dump(item:to_table())) - if self.can_have_relationship - and self.can_receive_gifts - and item:to_table() ~= nil then - -- Get item name - local item = minetest.registered_items[item:get_name()] - local item_name = item.description - - -- Show dialogue to confirm that player is giving item as gift - npc.dialogue.show_yes_no_dialogue( - self, - "Do you want to give "..item_name.." to "..self.npc_name.."?", - npc.dialogue.POSITIVE_GIFT_ANSWER_PREFIX..item_name, - function() - npc.relationships.receive_gift(self, clicker) - end, - npc.dialogue.NEGATIVE_ANSWER_LABEL, - function() - npc.start_dialogue(self, clicker, true) - end, - name - ) - else - npc.start_dialogue(self, clicker, true) + -- Check if right-click interaction is enabled + if self.enable_rightclick_interaction == true then + npc.rightclick_interaction(self, clicker) end end, do_custom = function(self, dtime) @@ -1593,6 +1628,142 @@ mobs:register_mob("advanced_npc:npc", { end }) +function npc.activate(self, staticdata, def, dtime) + --minetest.log("Def: "..dump(def)) + -- remove monsters in peaceful mode, or when no data + if (self.type == "monster" and peaceful_only) then + + self.object:remove() + + return + end + + -- load entity variables + local tmp = minetest.deserialize(staticdata) + + if tmp then + for _,stat in pairs(tmp) do + self[_] = stat + end + end + + -- select random texture, set model and size + if not self.base_texture then + + -- compatiblity with old simple mobs textures + if type(def.textures[1]) == "string" then + def.textures = {def.textures} + end + + 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 * .5, + y = self.base_size.y * .5, + } + + if def.child_texture then + textures = def.child_texture[1] + end + + colbox = { + self.base_colbox[1] * .5, + self.base_colbox[2] * .5, + self.base_colbox[3] * .5, + self.base_colbox[4] * .5, + self.base_colbox[5] * .5, + self.base_colbox[6] * .5 + } + end + + if self.health == 0 then + self.health = math.random (self.hp_min, self.hp_max) + end + + -- rnd: pathfinding init + self.path = {} + self.path.way = {} -- path to follow, table of positions + self.path.lastpos = {x = 0, y = 0, z = 0} + self.path.stuck = false + self.path.following = false -- currently following path? + self.path.stuck_timer = 0 -- if stuck for too long search for path + -- end init + + self.object:set_armor_groups({immortal = 1, fleshy = self.armor}) + self.old_y = self.object:get_pos().y + self.old_health = self.health + self.sounds.distance = self.sounds.distance or 10 + self.textures = textures + self.mesh = mesh + self.collisionbox = colbox + self.visual_size = vis_size + self.standing_in = "" + + -- check existing nametag + if not self.nametag then + self.nametag = def.nametag + end + + -- set anything changed above + self.object:set_properties(self) + + -- Reset animation + if self.actions and self.actions.move_state then + if self.actions.move_state.is_sitting == true then + npc.actions.sit(self, {pos=self.object:getpos()}) + elseif self.actions.move_state.is_laying == true then + npc.actions.lay(self, {pos=self.object:getpos()}) + end + end + + -- run on_spawn function if found + if self.on_spawn and not self.on_spawn_run then + if self.on_spawn(self) then + self.on_spawn_run = true -- if true, set flag to run once only + end + end + + minetest.log("Executed good activate") +end + +function npc.add_initiate_animation_state() + local def = minetest.registered_entities["advanced_npc:npc"] + def.textures = def.texture_list + --minetest.log("Def: "..dump(def)) + def.on_activate = function(self, staticdata, dtime) + return npc.activate(self, staticdata, def, dtime) + end +end + + +-- Run the fix +npc.add_initiate_animation_state() + -- Spawn -- mobs:spawn({ -- name = "advanced_npc:npc",