2016-12-12 02:52:57 +01:00
|
|
|
-- Advanced NPC by Zorman2000
|
|
|
|
-- Based on original NPC by Tenplus1
|
2016-11-12 13:06:09 +01:00
|
|
|
|
|
|
|
local S = mobs.intllib
|
|
|
|
|
2016-11-14 12:28:37 +01:00
|
|
|
npc = {}
|
|
|
|
|
|
|
|
-- Constants
|
|
|
|
npc.FEMALE = "female"
|
|
|
|
npc.MALE = "male"
|
2016-12-04 21:00:55 +01:00
|
|
|
|
2016-12-06 02:22:33 +01:00
|
|
|
npc.INVENTORY_ITEM_MAX_STACK = 99
|
|
|
|
|
2017-01-09 17:34:38 +01:00
|
|
|
npc.ANIMATION_STAND_START = 0
|
|
|
|
npc.ANIMATION_STAND_END = 79
|
2016-12-18 18:32:39 +01:00
|
|
|
npc.ANIMATION_SIT_START = 81
|
|
|
|
npc.ANIMATION_SIT_END = 160
|
|
|
|
npc.ANIMATION_LAY_START = 162
|
|
|
|
npc.ANIMATION_LAY_END = 166
|
2017-01-09 17:34:38 +01:00
|
|
|
npc.ANIMATION_WALK_START = 168
|
|
|
|
npc.ANIMATION_WALK_END = 187
|
2016-12-18 18:32:39 +01:00
|
|
|
|
|
|
|
npc.direction = {
|
2016-12-30 03:34:15 +01:00
|
|
|
north = 0,
|
|
|
|
east = 1,
|
|
|
|
south = 2,
|
|
|
|
west = 3
|
2016-12-18 18:32:39 +01:00
|
|
|
}
|
|
|
|
|
2017-01-19 01:34:02 +01:00
|
|
|
npc.action_state = {
|
|
|
|
none = 0,
|
|
|
|
executing = 1,
|
|
|
|
interrupted = 2
|
|
|
|
}
|
|
|
|
|
2016-12-12 02:52:57 +01:00
|
|
|
---------------------------------------------------------------------------------------
|
2016-11-14 12:28:37 +01:00
|
|
|
-- General functions
|
2016-12-12 02:52:57 +01:00
|
|
|
---------------------------------------------------------------------------------------
|
2016-11-14 12:28:37 +01:00
|
|
|
-- Gets name of player or NPC
|
2016-11-29 20:05:09 +01:00
|
|
|
function npc.get_entity_name(entity)
|
2016-11-14 12:28:37 +01:00
|
|
|
if entity:is_player() then
|
|
|
|
return entity:get_player_name()
|
|
|
|
else
|
|
|
|
return entity:get_luaentity().nametag
|
|
|
|
end
|
2016-11-12 13:06:09 +01:00
|
|
|
end
|
|
|
|
|
2016-11-14 12:28:37 +01:00
|
|
|
-- Returns the item "wielded" by player or NPC
|
|
|
|
-- TODO: Implement NPC
|
2016-12-12 02:52:57 +01:00
|
|
|
function npc.get_entity_wielded_item(entity)
|
2016-11-14 12:28:37 +01:00
|
|
|
if entity:is_player() then
|
|
|
|
return entity:get_wielded_item()
|
|
|
|
end
|
2016-11-12 13:06:09 +01:00
|
|
|
end
|
|
|
|
|
2016-12-12 02:52:57 +01:00
|
|
|
---------------------------------------------------------------------------------------
|
2016-12-06 02:22:33 +01:00
|
|
|
-- Inventory functions
|
2016-12-12 02:52:57 +01:00
|
|
|
---------------------------------------------------------------------------------------
|
2016-12-06 02:22:33 +01:00
|
|
|
-- NPCs inventories are restrained to 16 slots.
|
|
|
|
-- Each slot can hold one item up to 99 count.
|
|
|
|
|
|
|
|
-- Utility function to get item name from a string
|
2016-12-17 18:01:35 +01:00
|
|
|
function npc.get_item_name(item_string)
|
|
|
|
return ItemStack(item_string):get_name()
|
2016-12-06 02:22:33 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- Utility function to get item count from a string
|
2016-12-17 18:01:35 +01:00
|
|
|
function npc.get_item_count(item_string)
|
|
|
|
return ItemStack(item_string):get_count()
|
2016-12-06 02:22:33 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
local function initialize_inventory()
|
|
|
|
return {
|
|
|
|
[1] = "", [2] = "", [3] = "", [4] = "",
|
|
|
|
[5] = "", [6] = "", [7] = "", [8] = "",
|
|
|
|
[9] = "", [10] = "", [11] = "", [12] = "",
|
|
|
|
[13] = "", [14] = "", [15] = "", [16] = "",
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Add an item to inventory. Returns true if add successful
|
2016-12-12 02:52:57 +01:00
|
|
|
-- These function can be used to give items to other NPCs
|
|
|
|
-- given that the "self" variable can be any NPC
|
2016-12-06 02:22:33 +01:00
|
|
|
function npc.add_item_to_inventory(self, item_name, count)
|
|
|
|
-- Check if NPC already has item
|
|
|
|
local existing_item = npc.inventory_contains(self, item_name)
|
2016-12-15 02:03:51 +01:00
|
|
|
if existing_item ~= nil and existing_item.item_string ~= nil then
|
2016-12-06 02:22:33 +01:00
|
|
|
-- NPC already has item. Get count and see
|
2016-12-17 18:01:35 +01:00
|
|
|
local existing_count = npc.get_item_count(existing_item.item_string)
|
2016-12-06 02:22:33 +01:00
|
|
|
if (existing_count + count) < npc.INVENTORY_ITEM_MAX_STACK then
|
|
|
|
-- Set item here
|
|
|
|
self.inventory[existing_item.slot] =
|
2016-12-17 18:01:35 +01:00
|
|
|
npc.get_item_name(existing_item.item_string).." "..tostring(existing_count + count)
|
2016-12-06 02:22:33 +01:00
|
|
|
return true
|
|
|
|
else
|
|
|
|
--Find next free slot
|
2016-12-15 02:03:51 +01:00
|
|
|
for i = 1, #self.inventory do
|
2016-12-06 02:22:33 +01:00
|
|
|
if self.inventory[i] == "" then
|
|
|
|
-- Found slot, set item
|
|
|
|
self.inventory[i] =
|
|
|
|
item_name.." "..tostring((existing_count + count) - npc.INVENTORY_ITEM_MAX_STACK)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- No free slot found
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
else
|
|
|
|
-- Find a free slot
|
2016-12-15 02:03:51 +01:00
|
|
|
for i = 1, #self.inventory do
|
2016-12-06 02:22:33 +01:00
|
|
|
if self.inventory[i] == "" then
|
|
|
|
-- Found slot, set item
|
|
|
|
self.inventory[i] = item_name.." "..tostring(count)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- No empty slot found
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-12-16 02:51:06 +01:00
|
|
|
-- Same add method but with itemstring for convenience
|
|
|
|
function npc.add_item_to_inventory_itemstring(self, item_string)
|
2016-12-17 18:01:35 +01:00
|
|
|
local item_name = npc.get_item_name(item_string)
|
|
|
|
local item_count = npc.get_item_count(item_string)
|
2016-12-16 02:51:06 +01:00
|
|
|
npc.add_item_to_inventory(self, item_name, item_count)
|
|
|
|
end
|
|
|
|
|
2016-12-06 02:22:33 +01:00
|
|
|
-- Checks if an item is contained in the inventory. Returns
|
|
|
|
-- the item string or nil if not found
|
|
|
|
function npc.inventory_contains(self, item_name)
|
|
|
|
for key,value in pairs(self.inventory) do
|
2016-12-15 02:03:51 +01:00
|
|
|
if value ~= "" and string.find(value, item_name) then
|
2016-12-06 02:22:33 +01:00
|
|
|
return {slot=key, item_string=value}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- Item not found
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Removes the item from an NPC inventory and returns the item
|
|
|
|
-- with its count (as a string, e.g. "default:apple 2"). Returns
|
|
|
|
-- nil if unable to get the item.
|
|
|
|
function npc.take_item_from_inventory(self, item_name, count)
|
|
|
|
local existing_item = npc.inventory_contains(self, item_name)
|
|
|
|
if existing_item ~= nil then
|
|
|
|
-- Found item
|
2016-12-29 04:10:06 +01:00
|
|
|
local existing_count = npc.get_item_count(existing_item.item_string)
|
2016-12-06 02:22:33 +01:00
|
|
|
local new_count = existing_count
|
|
|
|
if existing_count - count < 0 then
|
|
|
|
-- Remove item first
|
2016-12-12 02:52:57 +01:00
|
|
|
self.inventory[existing_item.slot] = ""
|
2016-12-06 02:22:33 +01:00
|
|
|
-- TODO: Support for retrieving from next stack. Too complicated
|
|
|
|
-- and honestly might be unecessary.
|
|
|
|
return item_name.." "..tostring(new_count)
|
|
|
|
else
|
|
|
|
new_count = existing_count - count
|
2016-12-29 04:10:06 +01:00
|
|
|
if new_count == 0 then
|
|
|
|
self.inventory[existing_item.slot] = ""
|
|
|
|
else
|
|
|
|
self.inventory[existing_item.slot] = item_name.." "..new_count
|
|
|
|
end
|
2016-12-06 02:22:33 +01:00
|
|
|
return item_name.." "..tostring(count)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
-- Not able to take item because not found
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-12-17 20:27:23 +01:00
|
|
|
-- Same take method but with itemstring for convenience
|
|
|
|
function npc.take_item_from_inventory_itemstring(self, item_string)
|
|
|
|
local item_name = npc.get_item_name(item_string)
|
|
|
|
local item_count = npc.get_item_count(item_string)
|
|
|
|
npc.take_item_from_inventory(self, item_name, item_count)
|
2016-12-04 21:00:55 +01:00
|
|
|
end
|
|
|
|
|
2017-01-26 18:55:04 +01:00
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
-- Flag functionality
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
-- TODO: Consider removing them as they are pretty simple and straight forward.
|
|
|
|
-- Generic variables or function that help drive some functionality for the NPC.
|
|
|
|
function npc.add_flag(self, flag_name, value)
|
|
|
|
self.flags[flag_name] = value
|
|
|
|
end
|
|
|
|
|
|
|
|
function npc.update_flag(self, flag_name, value)
|
|
|
|
self.flags[flag_name] = value
|
|
|
|
end
|
|
|
|
|
|
|
|
function npc.get_flag(self, flag_name)
|
|
|
|
return self.flags[flag_name]
|
|
|
|
end
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
-- Dialogue functionality
|
|
|
|
---------------------------------------------------------------------------------------
|
2016-12-04 21:00:55 +01:00
|
|
|
function npc.start_dialogue(self, clicker, show_married_dialogue)
|
2016-12-03 00:39:06 +01:00
|
|
|
|
2016-12-17 20:27:23 +01:00
|
|
|
-- Call dialogue function as normal
|
2016-12-04 21:00:55 +01:00
|
|
|
npc.dialogue.start_dialogue(self, clicker, show_married_dialogue)
|
2016-12-03 00:39:06 +01:00
|
|
|
|
|
|
|
-- Check and update relationship if needed
|
2016-12-12 02:52:57 +01:00
|
|
|
npc.relationships.dialogue_relationship_update(self, clicker)
|
2016-12-03 00:39:06 +01:00
|
|
|
|
2016-11-14 12:28:37 +01:00
|
|
|
end
|
2016-11-12 13:06:09 +01:00
|
|
|
|
2016-12-18 18:32:39 +01:00
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
-- Action functionality
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
-- This function adds a function to the action queue.
|
|
|
|
-- Actions should be added in strict order for tasks to work as expected.
|
|
|
|
function npc.add_action(self, action, arguments)
|
2017-01-14 04:54:27 +01:00
|
|
|
local action_entry = {action=action, args=arguments, is_task=false}
|
2016-12-18 18:32:39 +01:00
|
|
|
table.insert(self.actions.queue, action_entry)
|
|
|
|
end
|
|
|
|
|
2017-01-14 04:54:27 +01:00
|
|
|
-- This function adds task actions in-place, as opposed to
|
|
|
|
-- at the end of the queue. This allows for continued order
|
|
|
|
function npc.add_task(self, task, args)
|
|
|
|
local action_entry = {action=task, args=args, is_task=true}
|
|
|
|
table.insert(self.actions.queue, action_entry)
|
|
|
|
end
|
|
|
|
|
2016-12-18 18:32:39 +01:00
|
|
|
-- This function removes the first action in the action queue
|
2017-01-14 04:54:27 +01:00
|
|
|
-- and then executes it
|
2016-12-18 18:32:39 +01:00
|
|
|
function npc.execute_action(self)
|
2017-01-19 01:34:02 +01:00
|
|
|
-- Check if an action was interrupted
|
|
|
|
if self.actions.current_action_state == npc.action_state.interrupted then
|
|
|
|
minetest.log("Inserting interrupted action: ")
|
|
|
|
-- Insert into queue the interrupted action
|
|
|
|
table.insert(self.actions.queue, 1, self.actions.state_before_lock.interrupted_action)
|
|
|
|
-- Clear the action
|
|
|
|
self.actions.state_before_lock.interrupted_action = {}
|
|
|
|
-- Clear the position
|
|
|
|
self.actions.state_before_lock.pos = {}
|
|
|
|
end
|
2017-01-14 04:54:27 +01:00
|
|
|
local result = nil
|
2016-12-18 18:32:39 +01:00
|
|
|
if table.getn(self.actions.queue) == 0 then
|
2017-01-19 01:34:02 +01:00
|
|
|
-- Set state to none
|
|
|
|
self.actions.current_action_state = npc.action_state.none
|
2017-01-13 13:12:13 +01:00
|
|
|
-- Keep state the same if there are no more actions in actions queue
|
|
|
|
return self.freeze
|
2016-12-18 18:32:39 +01:00
|
|
|
end
|
|
|
|
local action_obj = self.actions.queue[1]
|
2017-01-14 04:54:27 +01:00
|
|
|
-- If the entry is a task, then push all this new operations in
|
|
|
|
-- stack fashion
|
|
|
|
if action_obj.is_task == true then
|
|
|
|
minetest.log("Executing task")
|
|
|
|
-- Backup current queue
|
|
|
|
local backup_queue = self.actions.queue
|
2017-01-19 01:34:02 +01:00
|
|
|
-- Remove this "task" action from queue
|
|
|
|
table.remove(self.actions.queue, 1)
|
2017-01-14 04:54:27 +01:00
|
|
|
-- Clear queue
|
|
|
|
self.actions.queue = {}
|
|
|
|
-- Now, execute the task with its arguments
|
2017-01-20 02:42:46 +01:00
|
|
|
action_obj.action(self, action_obj.args)
|
2017-01-14 04:54:27 +01:00
|
|
|
-- After all new actions has been added by task, add the previously
|
|
|
|
-- queued actions back
|
|
|
|
for i = 1, #backup_queue do
|
|
|
|
table.insert(self.actions.queue, backup_queue[i])
|
|
|
|
end
|
|
|
|
else
|
|
|
|
minetest.log("Executing action")
|
2017-01-19 01:34:02 +01:00
|
|
|
-- Store the action that is being executed
|
|
|
|
self.actions.state_before_lock.interrupted_action = action_obj
|
|
|
|
-- Store current position
|
|
|
|
self.actions.state_before_lock.pos = self.object:getpos()
|
2017-01-14 04:54:27 +01:00
|
|
|
-- Execute action as normal
|
2017-01-20 02:42:46 +01:00
|
|
|
result = action_obj.action(self, action_obj.args)
|
2017-01-19 01:34:02 +01:00
|
|
|
-- Remove task
|
2017-01-14 04:54:27 +01:00
|
|
|
table.remove(self.actions.queue, 1)
|
2017-01-19 01:34:02 +01:00
|
|
|
-- Set state
|
|
|
|
self.actions.current_action_state = npc.action_state.executing
|
2017-01-14 04:54:27 +01:00
|
|
|
end
|
2017-01-13 13:12:13 +01:00
|
|
|
return result
|
2016-12-18 18:32:39 +01:00
|
|
|
end
|
|
|
|
|
2017-01-19 01:34:02 +01:00
|
|
|
function npc.lock_actions(self)
|
|
|
|
|
|
|
|
-- Avoid re-locking if already locked
|
|
|
|
if self.actions.action_timer_lock == true then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local pos = self.object:getpos()
|
|
|
|
|
|
|
|
if self.freeze == false then
|
|
|
|
-- Round current pos to avoid the NPC being stopped on positions
|
|
|
|
-- where later on can't walk to the correct positions
|
|
|
|
-- Choose which position is to be taken as start position
|
|
|
|
if self.actions.state_before_lock.pos ~= {} then
|
|
|
|
pos = vector.round(self.actions.state_before_lock.pos)
|
|
|
|
else
|
|
|
|
pos = vector.round(self.object:getpos())
|
|
|
|
end
|
|
|
|
pos.y = self.object:getpos().y
|
|
|
|
end
|
|
|
|
-- Stop NPC
|
2017-01-20 03:31:40 +01:00
|
|
|
npc.actions.stand(self, {pos=pos})
|
2017-01-19 01:34:02 +01:00
|
|
|
-- Avoid all timer execution
|
|
|
|
self.actions.action_timer_lock = true
|
|
|
|
-- Reset timer so that it has some time after interaction is done
|
|
|
|
self.actions.action_timer = 0
|
|
|
|
-- Check if there are is an action executing
|
|
|
|
if self.actions.current_action_state == npc.action_state.executing
|
|
|
|
and self.freeze == false then
|
|
|
|
-- Store the current action state
|
|
|
|
self.actions.state_before_lock.action_state = self.actions.current_action_state
|
|
|
|
-- Set current action state to interrupted
|
|
|
|
self.actions.current_action_state = npc.action_state.interrupted
|
|
|
|
end
|
|
|
|
-- Store the current freeze variable
|
|
|
|
self.actions.state_before_lock.freeze = self.freeze
|
|
|
|
-- Freeze mobs_redo API
|
|
|
|
self.freeze = false
|
|
|
|
|
|
|
|
minetest.log("Locking")
|
|
|
|
end
|
|
|
|
|
|
|
|
function npc.unlock_actions(self)
|
|
|
|
-- Allow timers to execute
|
|
|
|
self.actions.action_timer_lock = false
|
|
|
|
-- Restore the value of self.freeze
|
|
|
|
self.freeze = self.actions.state_before_lock.freeze
|
|
|
|
|
|
|
|
if table.getn(self.actions.queue) == 0 then
|
|
|
|
-- Allow mobs_redo API to execute since action queue is empty
|
|
|
|
self.freeze = true
|
|
|
|
end
|
|
|
|
|
|
|
|
minetest.log("Unlocked")
|
|
|
|
end
|
2016-12-17 20:27:23 +01:00
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
-- Spawning functions
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
-- These functions are used at spawn time to determine several
|
|
|
|
-- random attributes for the NPC in case they are not already
|
|
|
|
-- defined. On a later phase, pre-defining many of the NPC values
|
|
|
|
-- will be allowed.
|
|
|
|
|
|
|
|
-- This function checks for "female" text on the texture name
|
|
|
|
local function is_female_texture(textures)
|
|
|
|
for i = 1, #textures do
|
|
|
|
if string.find(textures[i], "female") ~= nil then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Choose whether NPC can have relationships. Only 30% of NPCs cannot have relationships
|
|
|
|
local function can_have_relationships()
|
|
|
|
local chance = math.random(1,10)
|
|
|
|
return chance > 3
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Choose a maximum of two items that the NPC will have at spawn time
|
|
|
|
-- These items are chosen from the favorite items list.
|
|
|
|
local function choose_spawn_items(self)
|
|
|
|
local number_of_items_to_add = math.random(1, 2)
|
|
|
|
local number_of_items = #npc.FAVORITE_ITEMS[self.sex].phase1
|
|
|
|
|
|
|
|
for i = 1, number_of_items_to_add do
|
|
|
|
npc.add_item_to_inventory(
|
|
|
|
self,
|
|
|
|
npc.FAVORITE_ITEMS[self.sex].phase1[math.random(1, number_of_items)].item,
|
|
|
|
math.random(1,5)
|
|
|
|
)
|
|
|
|
end
|
|
|
|
-- Add currency to the items spawned with. Will add 5-10 tier 3
|
|
|
|
-- currency items
|
|
|
|
local currency_item_count = math.random(5, 10)
|
2017-01-26 18:55:04 +01:00
|
|
|
npc.add_item_to_inventory(self, npc.trade.prices.currency.tier3.string, currency_item_count)
|
2016-12-17 20:27:23 +01:00
|
|
|
|
2017-01-14 04:54:27 +01:00
|
|
|
-- For test
|
|
|
|
npc.add_item_to_inventory(self, "default:tree", 10)
|
|
|
|
npc.add_item_to_inventory(self, "default:cobble", 10)
|
|
|
|
|
2016-12-17 20:27:23 +01:00
|
|
|
minetest.log("Initial inventory: "..dump(self.inventory))
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Creates new single buy and sell offers for NPCs that
|
|
|
|
-- trade casually.
|
|
|
|
local function select_casual_trade_offers(self)
|
|
|
|
self.trader_data.buy_offers = {
|
|
|
|
[1] = npc.trade.get_casual_trade_offer(self, npc.trade.OFFER_BUY)
|
|
|
|
}
|
|
|
|
self.trader_data.sell_offers = {
|
|
|
|
[1] = npc.trade.get_casual_trade_offer(self, npc.trade.OFFER_SELL)
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Spawn function. Initializes all variables that the
|
|
|
|
-- NPC will have and choose random, starting values
|
|
|
|
local function npc_spawn(self, pos)
|
|
|
|
minetest.log("Spawning new NPC:")
|
2016-12-18 18:32:39 +01:00
|
|
|
|
|
|
|
-- Get Lua Entity
|
2016-12-17 20:27:23 +01:00
|
|
|
local ent = self:get_luaentity()
|
2016-12-18 18:32:39 +01:00
|
|
|
|
2017-01-20 02:42:46 +01:00
|
|
|
-- Avoid NPC to be removed by mobs_redo API
|
|
|
|
ent.remove_ok = false
|
|
|
|
|
2016-12-18 18:32:39 +01:00
|
|
|
-- Set name
|
2017-01-19 01:34:02 +01:00
|
|
|
ent.nametag = "Kio"
|
2016-12-18 18:32:39 +01:00
|
|
|
|
|
|
|
-- Set ID
|
|
|
|
ent.npc_id = tostring(math.random(1000, 9999))..":"..ent.nametag
|
2016-12-17 20:27:23 +01:00
|
|
|
|
|
|
|
-- Determine sex based on textures
|
|
|
|
if (is_female_texture(ent.base_texture)) then
|
|
|
|
ent.sex = npc.FEMALE
|
|
|
|
else
|
|
|
|
ent.sex = npc.MALE
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Initialize all gift data
|
|
|
|
ent.gift_data = {
|
|
|
|
-- Choose favorite items. Choose phase1 per default
|
|
|
|
favorite_items = npc.relationships.select_random_favorite_items(ent.sex, "phase1"),
|
|
|
|
-- Choose disliked items. Choose phase1 per default
|
|
|
|
disliked_items = npc.relationships.select_random_disliked_items(ent.sex),
|
|
|
|
}
|
|
|
|
|
|
|
|
-- Flag that determines if NPC can have a relationship
|
|
|
|
ent.can_have_relationship = can_have_relationships()
|
|
|
|
|
|
|
|
-- Initialize relationships object
|
|
|
|
ent.relationships = {}
|
|
|
|
|
|
|
|
-- Determines if NPC is married or not
|
|
|
|
ent.is_married_to = nil
|
|
|
|
|
|
|
|
-- Initialize dialogues
|
|
|
|
ent.dialogues = npc.dialogue.select_random_dialogues_for_npc(ent.sex,
|
|
|
|
"phase1",
|
|
|
|
ent.gift_data.favorite_items,
|
|
|
|
ent.gift_data.disliked_items)
|
2017-01-26 18:55:04 +01:00
|
|
|
|
|
|
|
-- Declare NPC inventory
|
|
|
|
ent.inventory = initialize_inventory()
|
|
|
|
|
|
|
|
-- Choose items to spawn with
|
|
|
|
choose_spawn_items(ent)
|
|
|
|
|
|
|
|
-- Flags: generic booleans or functions that help drive functionality
|
|
|
|
ent.flags = {}
|
|
|
|
|
2016-12-17 20:27:23 +01:00
|
|
|
-- Declare trade data
|
|
|
|
ent.trader_data = {
|
|
|
|
-- Type of trader
|
|
|
|
trader_status = npc.trade.get_random_trade_status(),
|
|
|
|
-- Current buy offers
|
|
|
|
buy_offers = {},
|
|
|
|
-- Current sell offers
|
|
|
|
sell_offers = {},
|
|
|
|
-- Items to buy change timer
|
|
|
|
change_offers_timer = 0,
|
|
|
|
-- Items to buy change timer interval
|
2017-01-26 18:55:04 +01:00
|
|
|
change_offers_timer_interval = 60,
|
|
|
|
-- Trading list: a list of item names the trader is expected to trade in.
|
|
|
|
-- It is mostly related to its occupation.
|
|
|
|
-- If empty, the NPC will revert to casual trading
|
|
|
|
-- If not, it will try to sell those that it have, and buy the ones it not.
|
|
|
|
trade_list = {}
|
2016-12-17 20:27:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
-- Initialize trading offers if NPC is casual trader
|
|
|
|
if ent.trader_data.trader_status == npc.trade.CASUAL then
|
|
|
|
select_casual_trade_offers(ent)
|
|
|
|
end
|
|
|
|
|
2017-01-19 01:34:02 +01:00
|
|
|
-- Actions data
|
2016-12-18 18:32:39 +01:00
|
|
|
ent.actions = {
|
|
|
|
-- The queue is a queue of actions to be performed on each interval
|
|
|
|
queue = {},
|
|
|
|
-- Current value of the action timer
|
|
|
|
action_timer = 0,
|
|
|
|
-- Determines the interval for each action in the action queue
|
2017-01-13 13:12:13 +01:00
|
|
|
-- Default is 1. This can be changed via actions
|
2017-01-19 01:34:02 +01:00
|
|
|
action_interval = 1,
|
|
|
|
-- Avoid the execution of the action timer
|
|
|
|
action_timer_lock = false,
|
|
|
|
-- Defines the state of the current action
|
|
|
|
current_action_state = npc.action_state.none,
|
|
|
|
-- Store information about action on state before lock
|
|
|
|
state_before_lock = {
|
|
|
|
-- State of the mobs_redo API
|
|
|
|
freeze = false,
|
|
|
|
-- State of execution
|
|
|
|
action_state = npc.action_state.none,
|
|
|
|
-- Action executed while on lock
|
|
|
|
interrupted_action = {}
|
|
|
|
}
|
2016-12-18 18:32:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
-- This flag is checked on every step. If it is true, the rest of
|
|
|
|
-- Mobs Redo API is not executed
|
2017-01-13 13:12:13 +01:00
|
|
|
ent.freeze = nil
|
2016-12-18 18:32:39 +01:00
|
|
|
|
|
|
|
-- This map will hold all the places for the NPC
|
|
|
|
-- Map entries should be like: "bed" = {x=1, y=1, z=1}
|
|
|
|
ent.places_map = {}
|
|
|
|
|
|
|
|
-- Temporary initialization of actions for testing
|
2017-01-14 04:54:27 +01:00
|
|
|
local nodes = npc.places.find_node_nearby(ent.object:getpos(), {"default:furnace"}, 20)
|
2017-01-06 13:57:42 +01:00
|
|
|
minetest.log("Found nodes: "..dump(nodes))
|
|
|
|
|
|
|
|
--local path = pathfinder.find_path(ent.object:getpos(), nodes[1], 20)
|
|
|
|
--minetest.log("Path to node: "..dump(path))
|
2016-12-30 01:02:25 +01:00
|
|
|
--npc.add_action(ent, npc.actions.use_door, {self = ent, pos = nodes[1], action = npc.actions.door_action.OPEN})
|
2016-12-29 23:26:09 +01:00
|
|
|
--npc.add_action(ent, npc.actions.stand, {self = ent})
|
2017-01-06 13:57:42 +01:00
|
|
|
--npc.add_action(ent, npc.actions.stand, {self = ent})
|
2017-01-13 01:40:20 +01:00
|
|
|
if nodes[1] ~= nil then
|
2017-01-20 02:42:46 +01:00
|
|
|
npc.add_task(ent, npc.actions.walk_to_pos, {end_pos=nodes[1], walkable={}})
|
2017-01-19 01:34:02 +01:00
|
|
|
npc.actions.use_furnace(ent, nodes[1], "default:cobble 5", false)
|
2017-01-14 04:54:27 +01:00
|
|
|
--npc.add_action(ent, npc.actions.sit, {self = ent})
|
2017-01-13 01:40:20 +01:00
|
|
|
-- npc.add_action(ent, npc.actions.lay, {self = ent})
|
|
|
|
-- npc.add_action(ent, npc.actions.lay, {self = ent})
|
|
|
|
-- npc.add_action(ent, npc.actions.lay, {self = ent})
|
2017-01-14 04:54:27 +01:00
|
|
|
--npc.actions.use_sittable(ent, nodes[1], npc.actions.const.sittable.GET_UP)
|
|
|
|
--npc.add_action(ent, npc.actions.set_interval, {self=ent, interval=10, freeze=true})
|
2017-01-13 13:12:13 +01:00
|
|
|
npc.add_action(ent, npc.actions.freeze, {freeze = false})
|
2017-01-13 01:40:20 +01:00
|
|
|
end
|
|
|
|
|
2016-12-18 18:32:39 +01:00
|
|
|
-- npc.add_action(ent, npc.action.stand, {self = ent})
|
|
|
|
-- npc.add_action(ent, npc.action.stand, {self = ent})
|
|
|
|
-- npc.add_action(ent, npc.action.walk_step, {self = ent, dir = npc.direction.east})
|
|
|
|
-- npc.add_action(ent, npc.action.walk_step, {self = ent, dir = npc.direction.east})
|
|
|
|
-- npc.add_action(ent, npc.action.walk_step, {self = ent, dir = npc.direction.east})
|
|
|
|
-- npc.add_action(ent, npc.action.walk_step, {self = ent, dir = npc.direction.east})
|
|
|
|
-- npc.add_action(ent, npc.action.walk_step, {self = ent, dir = npc.direction.east})
|
|
|
|
-- npc.add_action(ent, npc.action.walk_step, {self = ent, dir = npc.direction.east})
|
|
|
|
-- npc.add_action(ent, npc.action.sit, {self = ent})
|
|
|
|
-- npc.add_action(ent, npc.action.rotate, {self = ent, dir = npc.direction.south})
|
|
|
|
-- npc.add_action(ent, npc.action.lay, {self = ent})
|
|
|
|
|
|
|
|
-- Temporary initialization of places
|
2016-12-30 03:34:15 +01:00
|
|
|
-- local bed_nodes = npc.places.find_new_nearby(ent, npc.places.nodes.BEDS, 8)
|
|
|
|
-- minetest.log("Number of bed nodes: "..dump(#bed_nodes))
|
|
|
|
-- if #bed_nodes > 0 then
|
|
|
|
-- npc.places.add_owned(ent, "bed1", npc.places.PLACE_TYPE.OWN_BED, bed_nodes[1])
|
|
|
|
-- end
|
2016-12-18 18:32:39 +01:00
|
|
|
|
2016-12-17 20:27:23 +01:00
|
|
|
minetest.log(dump(ent))
|
|
|
|
|
|
|
|
-- Refreshes entity
|
|
|
|
ent.object:set_properties(ent)
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2016-12-12 02:52:57 +01:00
|
|
|
---------------------------------------------------------------------------------------
|
2016-12-17 20:27:23 +01:00
|
|
|
-- NPC Definition
|
2016-12-12 02:52:57 +01:00
|
|
|
---------------------------------------------------------------------------------------
|
2016-11-14 12:28:37 +01:00
|
|
|
mobs:register_mob("advanced_npc:npc", {
|
2016-11-12 13:06:09 +01:00
|
|
|
type = "npc",
|
|
|
|
passive = false,
|
|
|
|
damage = 3,
|
|
|
|
attack_type = "dogfight",
|
|
|
|
attacks_monsters = true,
|
|
|
|
-- Added group attack
|
|
|
|
group_attack = true,
|
|
|
|
--pathfinding = true,
|
|
|
|
pathfinding = 1,
|
|
|
|
hp_min = 10,
|
|
|
|
hp_max = 20,
|
|
|
|
armor = 100,
|
2016-12-29 21:09:30 +01:00
|
|
|
collisionbox = {-0.20,-1.0,-0.20, 0.20,0.8,0.20},
|
|
|
|
--collisionbox = {-0.35,-1.0,-0.35, 0.35,0.8,0.35},
|
2016-11-12 13:06:09 +01:00
|
|
|
visual = "mesh",
|
|
|
|
mesh = "character.b3d",
|
|
|
|
drawtype = "front",
|
|
|
|
textures = {
|
2016-11-14 12:28:37 +01:00
|
|
|
{"mobs_npc_male1.png"},
|
|
|
|
{"mobs_npc_female1.png"}, -- female by nuttmeg20
|
2016-11-12 13:06:09 +01:00
|
|
|
},
|
|
|
|
child_texture = {
|
2016-11-14 12:28:37 +01:00
|
|
|
{"mobs_npc_baby_male1.png"}, -- derpy baby by AmirDerAssassine
|
2016-11-12 13:06:09 +01:00
|
|
|
},
|
|
|
|
makes_footstep_sound = true,
|
|
|
|
sounds = {},
|
|
|
|
-- Added walk chance
|
|
|
|
walk_chance = 30,
|
|
|
|
-- Added stepheight
|
2017-01-16 18:16:42 +01:00
|
|
|
stepheight = 0.,
|
|
|
|
walk_velocity = 1,
|
2016-11-12 13:06:09 +01:00
|
|
|
run_velocity = 3,
|
|
|
|
jump = true,
|
|
|
|
drops = {
|
|
|
|
{name = "default:wood", chance = 1, min = 1, max = 3},
|
|
|
|
{name = "default:apple", chance = 2, min = 1, max = 2},
|
|
|
|
{name = "default:axe_stone", chance = 5, min = 1, max = 1},
|
|
|
|
},
|
|
|
|
water_damage = 0,
|
|
|
|
lava_damage = 2,
|
|
|
|
light_damage = 0,
|
2016-11-14 12:28:37 +01:00
|
|
|
--follow = {"farming:bread", "mobs:meat", "default:diamond"},
|
2016-11-12 13:06:09 +01:00
|
|
|
view_range = 15,
|
|
|
|
owner = "",
|
|
|
|
order = "follow",
|
|
|
|
--order = "stand",
|
|
|
|
fear_height = 3,
|
|
|
|
animation = {
|
|
|
|
speed_normal = 30,
|
|
|
|
speed_run = 30,
|
|
|
|
stand_start = 0,
|
|
|
|
stand_end = 79,
|
|
|
|
walk_start = 168,
|
|
|
|
walk_end = 187,
|
|
|
|
run_start = 168,
|
|
|
|
run_end = 187,
|
|
|
|
punch_start = 200,
|
|
|
|
punch_end = 219,
|
|
|
|
},
|
|
|
|
on_rightclick = function(self, clicker)
|
|
|
|
|
2017-01-19 01:34:02 +01:00
|
|
|
-- Rotate NPC toward its clicker
|
|
|
|
npc.dialogue.rotate_npc_to_player(self)
|
|
|
|
|
|
|
|
-- Get information from clicker
|
2016-11-12 13:06:09 +01:00
|
|
|
local item = clicker:get_wielded_item()
|
|
|
|
local name = clicker:get_player_name()
|
2016-11-14 12:28:37 +01:00
|
|
|
|
2017-01-26 18:55:04 +01:00
|
|
|
minetest.log(dump(self))
|
2016-11-14 12:28:37 +01:00
|
|
|
|
2016-11-30 05:53:46 +01:00
|
|
|
-- Receive gift or start chat. If player has no item in hand
|
|
|
|
-- then it is going to start chat directly
|
|
|
|
if self.can_have_relationship and item:to_table() ~= nil then
|
2016-12-01 15:26:57 +01:00
|
|
|
-- Get item name
|
|
|
|
local item = minetest.registered_items[item:get_name()]
|
|
|
|
local item_name = item.description
|
2016-12-02 14:09:14 +01:00
|
|
|
|
2016-11-29 20:05:09 +01:00
|
|
|
-- Show dialogue to confirm that player is giving item as gift
|
2016-11-30 05:53:46 +01:00
|
|
|
npc.dialogue.show_yes_no_dialogue(
|
2017-01-19 01:34:02 +01:00
|
|
|
self,
|
2016-12-01 15:26:57 +01:00
|
|
|
"Do you want to give "..item_name.." to "..self.nametag.."?",
|
|
|
|
npc.dialogue.POSITIVE_GIFT_ANSWER_PREFIX..item_name,
|
2016-11-30 05:53:46 +01:00
|
|
|
function()
|
2016-12-12 02:52:57 +01:00
|
|
|
npc.relationships.receive_gift(self, clicker)
|
2016-11-30 05:53:46 +01:00
|
|
|
end,
|
|
|
|
npc.dialogue.NEGATIVE_ANSWER_LABEL,
|
|
|
|
function()
|
2016-12-04 21:00:55 +01:00
|
|
|
npc.start_dialogue(self, clicker, true)
|
2016-11-30 05:53:46 +01:00
|
|
|
end,
|
|
|
|
name
|
|
|
|
)
|
2016-11-19 19:19:59 +01:00
|
|
|
else
|
2016-12-04 21:00:55 +01:00
|
|
|
npc.start_dialogue(self, clicker, true)
|
2016-11-19 19:19:59 +01:00
|
|
|
end
|
2016-11-12 13:06:09 +01:00
|
|
|
|
|
|
|
end,
|
2016-11-15 00:39:41 +01:00
|
|
|
do_custom = function(self, dtime)
|
2016-12-18 18:32:39 +01:00
|
|
|
|
2017-01-20 02:42:46 +01:00
|
|
|
-- Timer function for casual traders to reset their trade offers
|
|
|
|
self.trader_data.change_offers_timer = self.trader_data.change_offers_timer + dtime
|
|
|
|
-- Check if time has come to change offers
|
|
|
|
if self.trader_data.trader_status == npc.trade.CASUAL and
|
|
|
|
self.trader_data.change_offers_timer >= self.trader_data.change_offers_timer_interval then
|
|
|
|
-- Reset timer
|
|
|
|
self.trader_data.change_offers_timer = 0
|
|
|
|
-- Re-select casual trade offers
|
|
|
|
select_casual_trade_offers(self)
|
2016-12-17 20:27:23 +01:00
|
|
|
end
|
2017-01-20 02:42:46 +01:00
|
|
|
|
2016-11-15 00:39:41 +01:00
|
|
|
-- Timer function for gifts
|
2016-11-19 18:34:41 +01:00
|
|
|
for i = 1, #self.relationships do
|
|
|
|
local relationship = self.relationships[i]
|
|
|
|
-- Gift timer check
|
|
|
|
if relationship.gift_timer_value < relationship.gift_interval then
|
|
|
|
relationship.gift_timer_value = relationship.gift_timer_value + dtime
|
2016-12-03 00:39:06 +01:00
|
|
|
elseif relationship.talk_timer_value < relationship.gift_interval then
|
|
|
|
-- Relationship talk timer - only allows players to increase relationship
|
|
|
|
-- by talking on the same intervals as gifts
|
|
|
|
relationship.talk_timer_value = relationship.talk_timer_value + dtime
|
2016-11-19 18:34:41 +01:00
|
|
|
else
|
2016-11-29 20:05:09 +01:00
|
|
|
-- Relationship decrease timer
|
2016-11-19 18:34:41 +01:00
|
|
|
if relationship.relationship_decrease_timer_value
|
|
|
|
< relationship.relationship_decrease_interval then
|
|
|
|
relationship.relationship_decrease_timer_value =
|
|
|
|
relationship.relationship_decrease_timer_value + dtime
|
|
|
|
else
|
|
|
|
-- Check if married to decrease half
|
|
|
|
if relationship.phase == "phase6" then
|
|
|
|
-- Avoid going below the marriage phase limit
|
2016-12-12 02:52:57 +01:00
|
|
|
if (relationship.points - 0.5) >=
|
|
|
|
npc.relationships.RELATIONSHIP_PHASE["phase5"].limit then
|
2016-11-19 18:34:41 +01:00
|
|
|
relationship.points = relationship.points - 0.5
|
|
|
|
end
|
|
|
|
else
|
|
|
|
relationship.points = relationship.points - 1
|
|
|
|
end
|
|
|
|
relationship.relationship_decrease_timer_value = 0
|
2016-12-29 04:10:06 +01:00
|
|
|
--minetest.log(dump(self))
|
2016-11-19 18:34:41 +01:00
|
|
|
end
|
|
|
|
end
|
2016-12-18 18:32:39 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- Action queue timer
|
2017-01-19 01:34:02 +01:00
|
|
|
-- Check if actions and timers aren't locked
|
|
|
|
if self.actions.action_timer_lock == false then
|
|
|
|
-- Increment action timer
|
|
|
|
self.actions.action_timer = self.actions.action_timer + dtime
|
|
|
|
if self.actions.action_timer >= self.actions.action_interval then
|
|
|
|
-- Reset action timer
|
|
|
|
self.actions.action_timer = 0
|
|
|
|
-- Execute action
|
|
|
|
self.freeze = npc.execute_action(self)
|
2017-01-20 02:11:11 +01:00
|
|
|
-- Check if there are still remaining actions in the queue
|
2017-01-19 01:34:02 +01:00
|
|
|
if self.freeze == nil and table.getn(self.actions.queue) > 0 then
|
|
|
|
self.freeze = false
|
|
|
|
end
|
2016-12-18 18:32:39 +01:00
|
|
|
end
|
2017-01-19 01:34:02 +01:00
|
|
|
end
|
|
|
|
|
2017-01-13 13:12:13 +01:00
|
|
|
return self.freeze
|
2016-11-15 00:39:41 +01:00
|
|
|
end
|
2016-11-12 13:06:09 +01:00
|
|
|
})
|
|
|
|
|
2016-11-14 12:28:37 +01:00
|
|
|
-- Spawn
|
|
|
|
mobs:spawn({
|
|
|
|
name = "advanced_npc:npc",
|
2017-01-06 13:57:42 +01:00
|
|
|
nodes = {"mg_villages:plotmarker", "default:stone"},
|
2016-11-14 12:28:37 +01:00
|
|
|
min_light = 3,
|
|
|
|
active_object_count = 1,
|
|
|
|
interval = 5,
|
|
|
|
chance = 1,
|
|
|
|
--max_height = 0,
|
|
|
|
on_spawn = npc_spawn,
|
2016-11-12 13:06:09 +01:00
|
|
|
})
|
|
|
|
|
2016-12-17 20:27:23 +01:00
|
|
|
-------------------------------------------------------------------------
|
|
|
|
-- Item definitions
|
|
|
|
-------------------------------------------------------------------------
|
|
|
|
|
2016-12-18 18:32:39 +01:00
|
|
|
mobs:register_egg("advanced_npc:npc", S("NPC"), "default_brick.png", 1)
|
2016-11-12 13:06:09 +01:00
|
|
|
|
|
|
|
-- compatibility
|
2016-11-14 12:28:37 +01:00
|
|
|
mobs:alias_mob("mobs:npc", "advanced_npc:npc")
|
|
|
|
|
|
|
|
-- Marriage ring
|
|
|
|
minetest.register_craftitem("advanced_npc:marriage_ring", {
|
|
|
|
description = S("Marriage Ring"),
|
2016-12-16 03:33:57 +01:00
|
|
|
inventory_image = "marriage_ring.png",
|
2016-11-14 12:28:37 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
-- Marriage ring craft recipe
|
|
|
|
minetest.register_craft({
|
|
|
|
output = "advanced_npc:marriage_ring",
|
|
|
|
recipe = { {"", "", ""},
|
|
|
|
{"", "default:diamond", ""},
|
|
|
|
{"", "default:gold_ingot", ""} },
|
|
|
|
})
|