Compare commits
73 Commits
1.0.0-alph
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
97da2dc191 | ||
|
dfaa904ba8 | ||
|
166db4b5e5 | ||
|
2dc2400b38 | ||
|
c7fad7d6c6 | ||
|
5f9afae5d9 | ||
|
ec0392096f | ||
|
205bdf6eb1 | ||
|
5bdc3c3c29 | ||
|
4a4dc724cd | ||
|
d95c8504ec | ||
|
8a5e80e2cb | ||
|
d92b729e19 | ||
|
43c69ffee4 | ||
|
164e09bed5 | ||
|
5a79b9a119 | ||
|
d55ffd06fc | ||
|
c94edbc649 | ||
|
cb218fc9f9 | ||
|
c37c4dd868 | ||
|
cf77ab5299 | ||
|
175d07476d | ||
|
ee38dfe46b | ||
|
7f9fedba9d | ||
|
5fed6e3a90 | ||
|
c3d5a995a6 | ||
|
a527bcd746 | ||
|
e1a7efe9ab | ||
|
2a979ef1fe | ||
|
580792284d | ||
|
561b11f8fe | ||
|
db415dee97 | ||
|
b02a78de6d | ||
|
8cf1e932bc | ||
|
bcd6327a13 | ||
|
866b25c63c | ||
|
70fd62825c | ||
|
770125fae9 | ||
|
70c7a3c96f | ||
|
245c2c32c2 | ||
|
ce47958144 | ||
|
2a0b0aa538 | ||
|
6c3988a731 | ||
|
8e5d6d03f4 | ||
|
fe5a155177 | ||
|
cd8e3c09cb | ||
|
48eb8078b3 | ||
|
939181284b | ||
|
40ac2a55a1 | ||
|
ba6348663b | ||
|
c9eb3b0ef4 | ||
|
706a5cf188 | ||
|
e85a8161c3 | ||
|
337f8c46b7 | ||
|
4ec8ad7f7f | ||
|
df56e44bbd | ||
|
59bb430e62 | ||
|
0ddb30c0f9 | ||
|
19c5ca1d0c | ||
|
d347b6fad5 | ||
|
ceae61f553 | ||
|
a4fd06d1c6 | ||
|
ad9032ec5b | ||
|
11e871a932 | ||
|
4c102a70a4 | ||
|
698d247aba | ||
|
5a93800e77 | ||
|
0f931d273c | ||
|
3edc959d3a | ||
|
b5dc9926cd | ||
|
bff013bc44 | ||
|
5eceb09cdb | ||
|
e79fb91ff3 |
0
.idea/.gitignore
generated
vendored
Normal file
9
.idea/advanced_npc.iml
generated
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/advanced_npc.iml" filepath="$PROJECT_DIR$/.idea/advanced_npc.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/preferred-vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PreferredVcsStorage">
|
||||
<preferredVcsName>ApexVCS</preferredVcsName>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
@ -13,6 +13,51 @@
|
||||
|
||||
npc.actions = {}
|
||||
|
||||
npc.actions.default_interval = 1
|
||||
|
||||
npc.actions.dir_data = {
|
||||
-- North
|
||||
[0] = {
|
||||
yaw = 0,
|
||||
vel = {x=0, y=0, z=1}
|
||||
},
|
||||
-- East
|
||||
[1] = {
|
||||
yaw = (3 * math.pi) / 2,
|
||||
vel = {x=1, y=0, z=0}
|
||||
},
|
||||
-- South
|
||||
[2] = {
|
||||
yaw = math.pi,
|
||||
vel = {x=0, y=0, z=-1}
|
||||
},
|
||||
-- West
|
||||
[3] = {
|
||||
yaw = math.pi / 2,
|
||||
vel = {x=-1, y=0, z=0}
|
||||
},
|
||||
-- North east
|
||||
[4] = {
|
||||
yaw = (7 * math.pi) / 4,
|
||||
vel = {x=1, y=0, z=1}
|
||||
},
|
||||
-- North west
|
||||
[5] = {
|
||||
yaw = math.pi / 4,
|
||||
vel = {x=-1, y=0, z=1}
|
||||
},
|
||||
-- South east
|
||||
[6] = {
|
||||
yaw = (5 * math.pi) / 4,
|
||||
vel = {x=1, y=0, z=-1}
|
||||
},
|
||||
-- South west
|
||||
[7] = {
|
||||
yaw = (3 * math.pi) / 4,
|
||||
vel = {x=-1, y=0, z=-1}
|
||||
}
|
||||
}
|
||||
|
||||
-- Describes actions with doors or openable nodes
|
||||
npc.actions.const = {
|
||||
doors = {
|
||||
@ -50,7 +95,9 @@ npc.actions.cmd = {
|
||||
USE_FURNACE = 11,
|
||||
USE_BED = 12,
|
||||
USE_SITTABLE = 13,
|
||||
WALK_TO_POS = 14
|
||||
WALK_TO_POS = 14,
|
||||
DIG = 15,
|
||||
PLACE = 16
|
||||
}
|
||||
|
||||
--npc.actions.one_nps_speed = 0.98
|
||||
@ -60,6 +107,11 @@ npc.actions.one_nps_speed = 1
|
||||
npc.actions.one_half_nps_speed = 1.5
|
||||
npc.actions.two_nps_speed = 2
|
||||
|
||||
npc.actions.take_from_inventory = "take_from_inventory"
|
||||
npc.actions.take_from_inventory_forced = "take_from_inventory_forced"
|
||||
npc.actions.force_place = "force_place"
|
||||
|
||||
--------------
|
||||
-- Executor --
|
||||
--------------
|
||||
-- Function references aren't reliable in Minetest entities. Objects get serialized
|
||||
@ -115,6 +167,12 @@ function npc.actions.execute(self, command, args)
|
||||
-- Call walk to position task
|
||||
--minetest.log("Self: "..dump(self)..", Command: "..dump(command)..", args: "..dump(args))
|
||||
return npc.actions.walk_to_pos(self, args)
|
||||
elseif command == npc.actions.cmd.DIG then
|
||||
-- Call dig node action
|
||||
return npc.actions.dig(self, args)
|
||||
elseif command == npc.actions.cmd.PLACE then
|
||||
-- Call place node action
|
||||
return npc.actions.place(self, args)
|
||||
end
|
||||
end
|
||||
|
||||
@ -141,16 +199,188 @@ 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
|
||||
|
||||
-- This action digs the node at the given position
|
||||
-- If 'add_to_inventory' is true, it will put the digged node in the NPC
|
||||
-- inventory.
|
||||
-- Returns true if dig is successful, otherwise false
|
||||
function npc.actions.dig(self, args)
|
||||
local pos = args.pos
|
||||
local add_to_inventory = args.add_to_inventory
|
||||
local bypass_protection = args.bypass_protection
|
||||
local play_sound = args.play_sound or true
|
||||
local node = minetest.get_node_or_nil(pos)
|
||||
if node then
|
||||
-- Set mine animation
|
||||
self.object:set_animation({
|
||||
x = npc.ANIMATION_MINE_START,
|
||||
y = npc.ANIMATION_MINE_END},
|
||||
self.animation.speed_normal, 0)
|
||||
|
||||
-- Play dig sound
|
||||
if play_sound == true then
|
||||
minetest.sound_play(
|
||||
minetest.registered_nodes[node.name].sounds.dug,
|
||||
{
|
||||
max_hear_distance = 10,
|
||||
object = self.object
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
-- Check if protection not enforced
|
||||
if not bypass_protection then
|
||||
-- Try to dig node
|
||||
if minetest.dig_node(pos) then
|
||||
-- Add to inventory the node drops
|
||||
if add_to_inventory then
|
||||
-- Get node drop
|
||||
local drop = minetest.registered_nodes[node.name].drop
|
||||
local drop_itemname = node.name
|
||||
if drop and drop.items then
|
||||
local random_item = drop.items[math.random(1, #drop.items)]
|
||||
if random_item then
|
||||
drop_itemname = random_item.items[1]
|
||||
end
|
||||
end
|
||||
-- Add to NPC inventory
|
||||
npc.add_item_to_inventory(self, drop_itemname, 1)
|
||||
end
|
||||
--return true
|
||||
return
|
||||
end
|
||||
else
|
||||
-- Add to inventory
|
||||
if add_to_inventory then
|
||||
-- Get node drop
|
||||
local drop = minetest.registered_nodes[node.name].drop
|
||||
local drop_itemname = node.name
|
||||
if drop and drop.items then
|
||||
local random_item = drop.items[math.random(1, #drop.items)]
|
||||
if random_item then
|
||||
drop_itemname = random_item.items[1]
|
||||
end
|
||||
end
|
||||
-- Add to NPC inventory
|
||||
npc.add_item_to_inventory(self, drop_itemname, 1)
|
||||
end
|
||||
-- Dig node
|
||||
minetest.set_node(pos, {name="air"})
|
||||
end
|
||||
end
|
||||
--return false
|
||||
end
|
||||
|
||||
|
||||
-- This action places a given node at the given position
|
||||
-- There are three ways to source the node:
|
||||
-- 1. take_from_inventory: takes node from inventory. If not in inventory,
|
||||
-- node isn't placed.
|
||||
-- 2. take_from_inventory_forced: takes node from inventory. If not in
|
||||
-- inventory, node will be placed anyways.
|
||||
-- 3. force_place: places node regardless of inventory - will not touch
|
||||
-- the NPCs inventory
|
||||
function npc.actions.place(self, args)
|
||||
local pos = args.pos
|
||||
local node = args.node
|
||||
local source = args.source
|
||||
local bypass_protection = args.bypass_protection
|
||||
local play_sound = args.play_sound or true
|
||||
local node_at_pos = minetest.get_node_or_nil(pos)
|
||||
-- Check if position is empty or has a node that can be built to
|
||||
if node_at_pos and
|
||||
(node_at_pos.name == "air" or minetest.registered_nodes[node_at_pos.name].buildable_to == true) then
|
||||
-- Check protection
|
||||
if (not bypass_protection and not minetest.is_protected(pos, self.npc_name))
|
||||
or bypass_protection == true then
|
||||
-- Take from inventory if necessary
|
||||
local place_item = false
|
||||
if source == npc.actions.take_from_inventory then
|
||||
if npc.take_item_from_inventory(self, node, 1) then
|
||||
place_item = true
|
||||
end
|
||||
elseif source == npc.actions.take_from_inventory_forced then
|
||||
npc.take_item_from_inventory(self, node, 1)
|
||||
place_item = true
|
||||
elseif source == npc.actions.force_place then
|
||||
place_item = true
|
||||
end
|
||||
-- Place node
|
||||
if place_item == true then
|
||||
-- Set mine animation
|
||||
self.object:set_animation({
|
||||
x = npc.ANIMATION_MINE_START,
|
||||
y = npc.ANIMATION_MINE_END},
|
||||
self.animation.speed_normal, 0)
|
||||
-- Place node
|
||||
minetest.set_node(pos, {name=node})
|
||||
-- Play place sound
|
||||
if play_sound == true then
|
||||
minetest.sound_play(
|
||||
minetest.registered_nodes[node].sounds.place,
|
||||
{
|
||||
max_hear_distance = 10,
|
||||
object = self.object
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- This function allows to move into directions that are walkable. It
|
||||
-- avoids fences and allows to move on plants.
|
||||
-- This will make for nice wanderings, making the NPC move smartly instead
|
||||
-- of just getting stuck at places
|
||||
local function random_dir_helper(start_pos, speed, dir_start, dir_end)
|
||||
-- Limit the number of tries - otherwise it could become an infinite loop
|
||||
for i = 1, 8 do
|
||||
local dir = math.random(dir_start, dir_end)
|
||||
local vel = vector.multiply(npc.actions.dir_data[dir].vel, speed)
|
||||
local pos = vector.add(start_pos, vel)
|
||||
local node = minetest.get_node(pos)
|
||||
if node then
|
||||
if node.name == "air"
|
||||
-- Any walkable node except fences
|
||||
or (minetest.registered_nodes[node.name].walkable == true
|
||||
and minetest.registered_nodes[node.name].groups.fence ~= 1)
|
||||
-- Farming plants
|
||||
or minetest.registered_nodes[node.name].groups.plant == 1 then
|
||||
return dir
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Return -1 signaling that no good direction could be found
|
||||
return -1
|
||||
end
|
||||
|
||||
-- This action is to rotate to mob to a specifc direction. Currently, the code
|
||||
-- contains also for diagonals, but remaining in the orthogonal domain is preferrable.
|
||||
function npc.actions.rotate(self, args)
|
||||
local dir = args.dir
|
||||
local yaw = 0
|
||||
local yaw = args.yaw or 0
|
||||
local start_pos = args.start_pos
|
||||
local end_pos = args.end_pos
|
||||
-- Calculate dir if positions are given
|
||||
if start_pos and end_pos and not dir then
|
||||
dir = npc.actions.get_direction(start_pos, end_pos)
|
||||
end
|
||||
-- Only yaw was given
|
||||
if yaw and not dir and not start_pos and not end_pos then
|
||||
self.object:setyaw(yaw)
|
||||
return
|
||||
end
|
||||
|
||||
self.rotate = 0
|
||||
if dir == npc.direction.north then
|
||||
yaw = 0
|
||||
@ -177,20 +407,24 @@ end
|
||||
-- true if it can move on that direction, and false if there is an obstacle
|
||||
function npc.actions.walk_step(self, args)
|
||||
local dir = args.dir
|
||||
local step_into_air_only = args.step_into_air_only
|
||||
local speed = args.speed
|
||||
local target_pos = args.target_pos
|
||||
local start_pos = args.start_pos
|
||||
local vel = {}
|
||||
|
||||
-- Set default node per seconds
|
||||
if speed == nil then
|
||||
speed = npc.actions.one_nps_speed
|
||||
end
|
||||
-- If there is a target position to reach, set it
|
||||
if target_pos ~= nil then
|
||||
self.actions.walking.target_pos = target_pos
|
||||
end
|
||||
|
||||
-- Set is_walking = true
|
||||
self.actions.walking.is_walking = true
|
||||
-- Check if dir should be random
|
||||
if dir == "random_all" or dir == "random" then
|
||||
dir = random_dir_helper(start_pos, speed, 0, 7)
|
||||
end
|
||||
if dir == "random_orthogonal" then
|
||||
dir = random_dir_helper(start_pos, speed, 0, 3)
|
||||
end
|
||||
|
||||
if dir == npc.direction.north then
|
||||
vel = {x=0, y=0, z=speed}
|
||||
@ -207,45 +441,56 @@ function npc.actions.walk_step(self, args)
|
||||
elseif dir == npc.direction.west then
|
||||
vel = {x=-speed, y=0, z=0}
|
||||
elseif dir == npc.direction.north_west then
|
||||
vel = {x=-speed, y=0, z=speed}
|
||||
vel = {x=-speed, y=0, z=speed }
|
||||
else
|
||||
-- No direction provided or NPC is trapped, don't move NPC
|
||||
vel = {x=0, y=0, z=0}
|
||||
end
|
||||
|
||||
-- If there is a target position to reach, set it and set walking to true
|
||||
if target_pos ~= nil then
|
||||
self.actions.walking.target_pos = target_pos
|
||||
-- Set is_walking = true
|
||||
self.actions.walking.is_walking = true
|
||||
end
|
||||
|
||||
-- Rotate NPC
|
||||
npc.actions.rotate(self, {dir=dir})
|
||||
-- Set velocity so that NPC walks
|
||||
self.object:setvelocity(vel)
|
||||
-- Set walk animation
|
||||
self.object:set_animation({
|
||||
x = npc.ANIMATION_WALK_START,
|
||||
y = npc.ANIMATION_WALK_END},
|
||||
self.animation.speed_normal, 0)
|
||||
x = npc.ANIMATION_WALK_START,
|
||||
y = npc.ANIMATION_WALK_END},
|
||||
self.animation.speed_normal, 0)
|
||||
end
|
||||
|
||||
-- This action makes the NPC stand and remain like that
|
||||
function npc.actions.stand(self, args)
|
||||
local pos = args.pos
|
||||
local dir = args.dir
|
||||
-- Set is_walking = true
|
||||
self.actions.walking.is_walking = false
|
||||
-- Set is_walking = false
|
||||
self.actions.walking.is_walking = false
|
||||
-- Stop NPC
|
||||
self.object:setvelocity({x=0, y=0, z=0})
|
||||
-- If position given, set to that position
|
||||
if pos ~= nil then
|
||||
self.object:moveto(pos)
|
||||
end
|
||||
-- If dir given, set to that dir
|
||||
-- If dir given, set to that dir
|
||||
if dir ~= nil then
|
||||
npc.actions.rotate(self, {dir=dir})
|
||||
end
|
||||
-- Set stand animation
|
||||
self.object:set_animation({
|
||||
x = npc.ANIMATION_STAND_START,
|
||||
y = npc.ANIMATION_STAND_END},
|
||||
self.animation.speed_normal, 0)
|
||||
x = npc.ANIMATION_STAND_START,
|
||||
y = npc.ANIMATION_STAND_END},
|
||||
self.animation.speed_normal, 0)
|
||||
end
|
||||
|
||||
-- This action makes the NPC sit on the node where it is
|
||||
function npc.actions.sit(self, args)
|
||||
local pos = args.pos
|
||||
local pos = args.pos
|
||||
local dir = args.dir
|
||||
-- Stop NPC
|
||||
self.object:setvelocity({x=0, y=0, z=0})
|
||||
@ -259,9 +504,9 @@ function npc.actions.sit(self, args)
|
||||
end
|
||||
-- Set sit animation
|
||||
self.object:set_animation({
|
||||
x = npc.ANIMATION_SIT_START,
|
||||
y = npc.ANIMATION_SIT_END},
|
||||
self.animation.speed_normal, 0)
|
||||
x = npc.ANIMATION_SIT_START,
|
||||
y = npc.ANIMATION_SIT_END},
|
||||
self.animation.speed_normal, 0)
|
||||
end
|
||||
|
||||
-- This action makes the NPC lay on the node where it is
|
||||
@ -275,9 +520,9 @@ function npc.actions.lay(self, args)
|
||||
end
|
||||
-- Set sit animation
|
||||
self.object:set_animation({
|
||||
x = npc.ANIMATION_LAY_START,
|
||||
y = npc.ANIMATION_LAY_END},
|
||||
self.animation.speed_normal, 0)
|
||||
x = npc.ANIMATION_LAY_START,
|
||||
y = npc.ANIMATION_LAY_END},
|
||||
self.animation.speed_normal, 0)
|
||||
end
|
||||
|
||||
-- Inventory functions for players and for nodes
|
||||
@ -309,7 +554,7 @@ function npc.actions.put_item_on_external_inventory(self, args)
|
||||
end
|
||||
-- Add items to external inventory
|
||||
inv:add_item(inv_list, item)
|
||||
|
||||
|
||||
-- If this is a furnace, start furnace timer
|
||||
if is_furnace == true then
|
||||
minetest.get_node_timer(pos):start(1.0)
|
||||
@ -368,11 +613,18 @@ end
|
||||
|
||||
-- TODO: Refactor this function so that it uses a table to check
|
||||
-- for doors instead of having separate logic for each door type
|
||||
function npc.actions.get_openable_node_state(node, npc_dir)
|
||||
function npc.actions.get_openable_node_state(node, pos, npc_dir)
|
||||
--minetest.log("Node name: "..dump(node.name))
|
||||
local state = npc.actions.const.doors.state.CLOSED
|
||||
-- Check for default doors and gates
|
||||
local a_i1, a_i2 = string.find(node.name, "_a")
|
||||
-- Check for MTG doors and gates
|
||||
local mtg_door_closed = false
|
||||
if minetest.get_item_group(node.name, "door") > 0 then
|
||||
local back_pos = vector.add(pos, minetest.facedir_to_dir(node.param2))
|
||||
local back_node = minetest.get_node(back_pos)
|
||||
if back_node.name == "air" or minetest.registered_nodes[back_node.name].walkable == false then
|
||||
mtg_door_closed = true
|
||||
end
|
||||
end
|
||||
-- Check for cottages gates
|
||||
local open_i1, open_i2 = string.find(node.name, "_close")
|
||||
-- Check for cottages half door
|
||||
@ -380,7 +632,7 @@ function npc.actions.get_openable_node_state(node, npc_dir)
|
||||
if node.name == "cottages:half_door" then
|
||||
half_door_is_closed = (node.param2 + 2) % 4 == npc_dir
|
||||
end
|
||||
if a_i1 == nil and open_i1 == nil and not half_door_is_closed then
|
||||
if mtg_door_closed == false and open_i1 == nil and half_door_is_closed == false then
|
||||
state = npc.actions.const.doors.state.OPEN
|
||||
end
|
||||
--minetest.log("Door state: "..dump(state))
|
||||
@ -395,9 +647,9 @@ function npc.actions.use_openable(self, args)
|
||||
local action = args.action
|
||||
local dir = args.dir
|
||||
local node = minetest.get_node(pos)
|
||||
local state = npc.actions.get_openable_node_state(node, dir)
|
||||
local state = npc.actions.get_openable_node_state(node, pos, dir)
|
||||
|
||||
local clicker = self.object
|
||||
local clicker = self.object.npc_name
|
||||
if action ~= state then
|
||||
minetest.registered_nodes[node.name].on_rightclick(pos, node, clicker, nil, nil)
|
||||
end
|
||||
@ -411,7 +663,7 @@ end
|
||||
-- walking from one place to another, operating a furnace, storing or taking
|
||||
-- items from a chest, are provided here.
|
||||
|
||||
local function get_pos_argument(self, pos)
|
||||
local function get_pos_argument(self, pos, use_access_node)
|
||||
--minetest.log("Type of pos: "..dump(type(pos)))
|
||||
-- Check which type of position argument we received
|
||||
if type(pos) == "table" then
|
||||
@ -422,25 +674,48 @@ local function get_pos_argument(self, pos)
|
||||
return pos
|
||||
elseif pos.place_type ~= nil then
|
||||
-- Received table in the following format:
|
||||
-- {place_type = "", index = 1, use_access_node = false}
|
||||
-- {
|
||||
-- place_category = "",
|
||||
-- place_type = "",
|
||||
-- index = 1,
|
||||
-- use_access_node = false|true,
|
||||
-- try_alternative_if_used = true|false
|
||||
-- }
|
||||
local index = pos.index or 1
|
||||
local use_access_node = pos.use_access_node or false
|
||||
local try_alternative_if_used = pos.try_alternative_if_used or false
|
||||
local places = npc.places.get_by_type(self, pos.place_type)
|
||||
--minetest.log("Place type: "..dump(pos.place_type))
|
||||
--minetest.log("Places: "..dump(places))
|
||||
-- Check index is valid on the places map
|
||||
if #places >= index then
|
||||
local place = places[index]
|
||||
-- Check if place is used, and if it is, find alternative if required
|
||||
if try_alternative_if_used == true then
|
||||
place = npc.places.find_unused_place(self, pos.place_category, pos.place_type, place)
|
||||
|
||||
--minetest.log("Mark as used? "..dump(pos.mark_target_as_used))
|
||||
if pos.mark_target_as_used == true then
|
||||
--minetest.log("Marking as used: "..minetest.pos_to_string(place.pos))
|
||||
npc.places.mark_place_used(place.pos, npc.places.USE_STATE.USED)
|
||||
end
|
||||
|
||||
npc.places.add_shared_accessible_place(
|
||||
self, {owner="", node_pos=place.pos}, npc.places.PLACE_TYPE.CALCULATED.TARGET, true, {})
|
||||
end
|
||||
-- Check if access node is desired
|
||||
if use_access_node then
|
||||
if use_access_node == true then
|
||||
-- Return actual node pos
|
||||
return places[index].access_node
|
||||
return place.access_node, place.pos
|
||||
else
|
||||
-- Return node pos that allows access to node
|
||||
return places[index].pos
|
||||
return place.pos
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif type(pos) == "string" then
|
||||
-- Received name of place, so we are going to look for the actual pos
|
||||
local places_pos = npc.places.get_by_type(self, pos)
|
||||
local places_pos = npc.places.get_by_type(self, pos, false)
|
||||
-- Return nil if no position found
|
||||
if places_pos == nil or #places_pos == 0 then
|
||||
return nil
|
||||
@ -450,13 +725,21 @@ local function get_pos_argument(self, pos)
|
||||
-- Check all places, return owned if existent, else return the first one
|
||||
for i = 1, #places_pos do
|
||||
if places_pos[i].status == "owned" then
|
||||
return places_pos[i].pos
|
||||
if use_access_node == true then
|
||||
return places_pos[i].access_node, places_pos[i].pos
|
||||
else
|
||||
return places_pos[i].pos
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Return the first position only if it couldn't find an owned
|
||||
-- place, or if it there is only oneg
|
||||
return places_pos[1].pos
|
||||
-- place, or if it there is only one
|
||||
if use_access_node == true then
|
||||
return places_pos[1].access_node, places_pos[1].pos
|
||||
else
|
||||
return places_pos[1].pos
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -468,24 +751,25 @@ end
|
||||
function npc.actions.use_furnace(self, args)
|
||||
local pos = get_pos_argument(self, args.pos)
|
||||
if pos == nil then
|
||||
npc.log("WARNING", "Got nil position in 'use_furnace' using args.pos: "..dump(args.pos))
|
||||
-- npc.log("WARNING", "Got nil position in 'use_furnace' using args.pos: "..dump(args.pos))
|
||||
return
|
||||
end
|
||||
|
||||
local enable_usage_marking = args.enable_usage_marking or true
|
||||
local item = args.item
|
||||
local freeze = args.freeze
|
||||
-- Define which items are usable as fuels. The NPC
|
||||
-- will mainly use this as fuels to avoid getting useful
|
||||
-- items (such as coal lumps) for burning
|
||||
local fuels = {"default:leaves",
|
||||
"default:pine_needles",
|
||||
"default:tree",
|
||||
"default:acacia_tree",
|
||||
"default:aspen_tree",
|
||||
"default:jungletree",
|
||||
"default:pine_tree",
|
||||
"default:coalblock",
|
||||
"farming:straw"}
|
||||
local fuels = {"default:leaves",
|
||||
"default:pine_needles",
|
||||
"default:tree",
|
||||
"default:acacia_tree",
|
||||
"default:aspen_tree",
|
||||
"default:jungletree",
|
||||
"default:pine_tree",
|
||||
"default:coalblock",
|
||||
"farming:straw"}
|
||||
|
||||
-- Check if NPC has item to cook
|
||||
local src_item = npc.inventory_contains(self, npc.get_item_name(item))
|
||||
@ -496,23 +780,23 @@ function npc.actions.use_furnace(self, args)
|
||||
|
||||
-- Check if NPC has a fuel item
|
||||
for i = 1,9 do
|
||||
local fuel_item = npc.inventory_contains(self, fuels[i])
|
||||
|
||||
local fuel_item = npc.inventory_contains(self, fuels[i])
|
||||
|
||||
if fuel_item ~= nil then
|
||||
-- Get fuel item's burn time
|
||||
local fuel_time =
|
||||
minetest.get_craft_result({method="fuel", width=1, items={ItemStack(fuel_item.item_string)}}).time
|
||||
local fuel_time =
|
||||
minetest.get_craft_result({method="fuel", width=1, items={ItemStack(fuel_item.item_string)}}).time
|
||||
local total_fuel_time = fuel_time * npc.get_item_count(fuel_item.item_string)
|
||||
npc.log("DEBUG", "Fuel time: "..dump(fuel_time))
|
||||
--npc.log("DEBUG", "Fuel time: "..dump(fuel_time))
|
||||
|
||||
-- Get item to cook's cooking time
|
||||
local cook_result =
|
||||
local cook_result =
|
||||
minetest.get_craft_result({method="cooking", width=1, items={ItemStack(src_item.item_string)}})
|
||||
local total_cook_time = cook_result.time * npc.get_item_count(item)
|
||||
npc.log("DEBUG", "Cook: "..dump(cook_result))
|
||||
--npc.log("DEBUG", "Cook: "..dump(cook_result))
|
||||
|
||||
npc.log("DEBUG", "Total cook time: "..total_cook_time
|
||||
..", total fuel burn time: "..dump(total_fuel_time))
|
||||
-- npc.log("DEBUG", "Total cook time: "..total_cook_time
|
||||
-- ..", total fuel burn time: "..dump(total_fuel_time))
|
||||
|
||||
-- Check if there is enough fuel to cook all items
|
||||
if total_cook_time > total_fuel_time then
|
||||
@ -525,6 +809,12 @@ function npc.actions.use_furnace(self, args)
|
||||
return cook_result.time - fuel_time
|
||||
end
|
||||
|
||||
-- Set furnace as used if flag is enabled
|
||||
if enable_usage_marking then
|
||||
-- Set place as used
|
||||
npc.places.mark_place_used(pos, npc.places.USE_STATE.USED)
|
||||
end
|
||||
|
||||
-- Calculate how much fuel is needed
|
||||
local fuel_amount = total_cook_time / fuel_time
|
||||
if fuel_amount < 1 then
|
||||
@ -535,26 +825,26 @@ function npc.actions.use_furnace(self, args)
|
||||
|
||||
-- Put this item on the fuel inventory list of the furnace
|
||||
local args = {
|
||||
player = nil,
|
||||
pos = pos,
|
||||
inv_list = "fuel",
|
||||
item_name = npc.get_item_name(fuel_item.item_string),
|
||||
count = fuel_amount
|
||||
player = nil,
|
||||
pos = pos,
|
||||
inv_list = "fuel",
|
||||
item_name = npc.get_item_name(fuel_item.item_string),
|
||||
count = fuel_amount
|
||||
}
|
||||
npc.add_action(self, npc.actions.cmd.PUT_ITEM, args)
|
||||
-- Put the item that we want to cook on the furnace
|
||||
args = {
|
||||
player = nil,
|
||||
pos = pos,
|
||||
inv_list = "src",
|
||||
item_name = npc.get_item_name(src_item.item_string),
|
||||
count = npc.get_item_count(item),
|
||||
is_furnace = true
|
||||
player = nil,
|
||||
pos = pos,
|
||||
inv_list = "src",
|
||||
item_name = npc.get_item_name(src_item.item_string),
|
||||
count = npc.get_item_count(item),
|
||||
is_furnace = true
|
||||
}
|
||||
npc.add_action(self, npc.actions.cmd.PUT_ITEM, args)
|
||||
|
||||
-- Now, set NPC to wait until furnace is done.
|
||||
npc.log("DEBUG", "Setting wait action for "..dump(total_cook_time))
|
||||
-- npc.log("DEBUG", "Setting wait action for "..dump(total_cook_time))
|
||||
npc.add_action(self, npc.actions.cmd.SET_INTERVAL, {interval=total_cook_time, freeze=freeze})
|
||||
|
||||
-- Reset timer
|
||||
@ -563,24 +853,30 @@ function npc.actions.use_furnace(self, args)
|
||||
-- If freeze is false, then we will have to find the way back to the furnace
|
||||
-- once cooking is done.
|
||||
if freeze == false then
|
||||
npc.log("DEBUG", "Adding walk to position to wandering: "..dump(pos))
|
||||
-- npc.log("DEBUG", "Adding walk to position to wandering: "..dump(pos))
|
||||
npc.add_task(self, npc.actions.cmd.WALK_TO_POS, {end_pos=pos, walkable={}})
|
||||
end
|
||||
|
||||
-- Take cooked items back
|
||||
args = {
|
||||
|
||||
player = nil,
|
||||
pos = pos,
|
||||
inv_list = "dst",
|
||||
item_name = cook_result.item:get_name(),
|
||||
count = npc.get_item_count(item),
|
||||
is_furnace = false
|
||||
|
||||
player = nil,
|
||||
pos = pos,
|
||||
inv_list = "dst",
|
||||
item_name = cook_result.item:get_name(),
|
||||
count = npc.get_item_count(item),
|
||||
is_furnace = false
|
||||
}
|
||||
npc.log("DEBUG", "Taking item back: "..minetest.pos_to_string(pos))
|
||||
npc.add_action(self, npc.actions.cmd.TAKE_ITEM, args)
|
||||
|
||||
npc.log("DEBUG", "Inventory: "..dump(self.inventory))
|
||||
-- npc.log("DEBUG", "Inventory: "..dump(self.inventory))
|
||||
|
||||
-- Set furnace as unused if flag is enabled
|
||||
if enable_usage_marking then
|
||||
-- Set place as used
|
||||
npc.places.mark_place_used(pos, npc.places.USE_STATE.NOT_USED)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
@ -594,10 +890,11 @@ end
|
||||
function npc.actions.use_bed(self, args)
|
||||
local pos = get_pos_argument(self, args.pos)
|
||||
if pos == nil then
|
||||
npc.log("WARNING", "Got nil position in 'use_bed' using args.pos: "..dump(args.pos))
|
||||
-- npc.log("WARNING", "Got nil position in 'use_bed' using args.pos: "..dump(args.pos))
|
||||
return
|
||||
end
|
||||
local action = args.action
|
||||
local enable_usage_marking = args.enable_usage_marking or true
|
||||
local node = minetest.get_node(pos)
|
||||
--minetest.log(dump(node))
|
||||
local dir = minetest.facedir_to_dir(node.param2)
|
||||
@ -613,6 +910,11 @@ function npc.actions.use_bed(self, args)
|
||||
npc.add_action(self, npc.actions.cmd.SIT, {pos=bed_pos, dir=(node.param2 + 2) % 4})
|
||||
-- Lay down
|
||||
npc.add_action(self, npc.actions.cmd.LAY, {})
|
||||
if enable_usage_marking then
|
||||
-- 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
|
||||
@ -620,7 +922,7 @@ function npc.actions.use_bed(self, args)
|
||||
return
|
||||
end
|
||||
local bed_pos_y = npc.actions.nodes.beds[node.name].get_lay_pos(pos, dir).y
|
||||
local bed_pos = {x = pos.x, y = bed_pos_y, z = pos.z}
|
||||
local bed_pos = {x = pos.x, y = bed_pos_y, z = pos.z}
|
||||
-- Sit up
|
||||
npc.add_action(self, npc.actions.cmd.SIT, {pos=bed_pos})
|
||||
-- Initialize direction: Default is front of bottom of bed
|
||||
@ -631,29 +933,38 @@ function npc.actions.use_bed(self, args)
|
||||
if npc.actions.nodes.beds[node.name].type == "mat" then
|
||||
y_adjustment = 0
|
||||
end
|
||||
|
||||
local pos_out_of_bed = pos
|
||||
local empty_nodes = npc.places.find_node_orthogonally(bed_pos, {"air", "cottages:bench"}, y_adjustment)
|
||||
if empty_nodes ~= nil then
|
||||
if empty_nodes ~= nil and #empty_nodes > 0 then
|
||||
-- Get direction to the empty node
|
||||
dir = npc.actions.get_direction(bed_pos, empty_nodes[1].pos)
|
||||
end
|
||||
-- Calculate position to get out of bed
|
||||
local pos_out_of_bed =
|
||||
|
||||
-- Calculate position to get out of bed
|
||||
pos_out_of_bed =
|
||||
{x=empty_nodes[1].pos.x, y=empty_nodes[1].pos.y + 1, z=empty_nodes[1].pos.z}
|
||||
-- Account for benches if they are present to avoid standing over them
|
||||
if empty_nodes[1].name == "cottages:bench" then
|
||||
pos_out_of_bed = {x=empty_nodes[1].pos.x, y=empty_nodes[1].pos.y + 1, z=empty_nodes[1].pos.z}
|
||||
if empty_nodes[1].param2 == 0 then
|
||||
pos_out_of_bed.z = pos_out_of_bed.z - 0.3
|
||||
elseif empty_nodes[1].param2 == 1 then
|
||||
pos_out_of_bed.x = pos_out_of_bed.x - 0.3
|
||||
elseif empty_nodes[1].param2 == 2 then
|
||||
pos_out_of_bed.z = pos_out_of_bed.z + 0.3
|
||||
elseif empty_nodes[1].param2 == 3 then
|
||||
pos_out_of_bed.x = pos_out_of_bed.x + 0.3
|
||||
-- Account for benches if they are present to avoid standing over them
|
||||
if empty_nodes[1].name == "cottages:bench" then
|
||||
pos_out_of_bed = {x=empty_nodes[1].pos.x, y=empty_nodes[1].pos.y + 1, z=empty_nodes[1].pos.z}
|
||||
if empty_nodes[1].param2 == 0 then
|
||||
pos_out_of_bed.z = pos_out_of_bed.z - 0.3
|
||||
elseif empty_nodes[1].param2 == 1 then
|
||||
pos_out_of_bed.x = pos_out_of_bed.x - 0.3
|
||||
elseif empty_nodes[1].param2 == 2 then
|
||||
pos_out_of_bed.z = pos_out_of_bed.z + 0.3
|
||||
elseif empty_nodes[1].param2 == 3 then
|
||||
pos_out_of_bed.x = pos_out_of_bed.x + 0.3
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
-- Stand out of bed
|
||||
npc.add_action(self, npc.actions.cmd.STAND, {pos=pos_out_of_bed, dir=dir})
|
||||
if enable_usage_marking then
|
||||
-- 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
|
||||
|
||||
@ -662,10 +973,11 @@ end
|
||||
function npc.actions.use_sittable(self, args)
|
||||
local pos = get_pos_argument(self, args.pos)
|
||||
if pos == nil then
|
||||
npc.log("WARNING", "Got nil position in 'use_sittable' using args.pos: "..dump(args.pos))
|
||||
-- npc.log("WARNING", "Got nil position in 'use_sittable' using args.pos: "..dump(args.pos))
|
||||
return
|
||||
end
|
||||
local action = args.action
|
||||
local enable_usage_marking = args.enable_usage_marking or true
|
||||
local node = minetest.get_node(pos)
|
||||
|
||||
if action == npc.actions.const.sittable.SIT then
|
||||
@ -677,7 +989,16 @@ function npc.actions.use_sittable(self, args)
|
||||
local sit_pos = npc.actions.nodes.sittable[node.name].get_sit_pos(pos, node.param2)
|
||||
-- Sit down on bench/chair/stairs
|
||||
npc.add_action(self, npc.actions.cmd.SIT, {pos=sit_pos, dir=(node.param2 + 2) % 4})
|
||||
if enable_usage_marking then
|
||||
-- Set place as used
|
||||
npc.places.mark_place_used(pos, npc.places.USE_STATE.USED)
|
||||
end
|
||||
self.actions.move_state.is_sitting = true
|
||||
else
|
||||
if self.actions.move_state.is_sitting == false then
|
||||
npc.log("DEBUG_ACTION", "NPC "..self.npc_name.." attempted to get up from sit when it is not sitting.")
|
||||
return
|
||||
end
|
||||
-- Find empty areas around chair
|
||||
local dir = node.param2 + 2 % 4
|
||||
-- Default it to the current position in case it can't find empty
|
||||
@ -696,13 +1017,20 @@ function npc.actions.use_sittable(self, args)
|
||||
end
|
||||
-- Stand
|
||||
npc.add_action(self, npc.actions.cmd.STAND, {pos=pos_out_of_sittable, dir=dir})
|
||||
minetest.log("Setting sittable at "..minetest.pos_to_string(pos).." as not used")
|
||||
if enable_usage_marking then
|
||||
-- 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
|
||||
|
||||
-- This function returns the direction enum
|
||||
-- for the moving from v1 to v2
|
||||
function npc.actions.get_direction(v1, v2)
|
||||
local dir = vector.subtract(v2, v1)
|
||||
local vector_dir = vector.direction(v1, v2)
|
||||
local dir = vector.round(vector_dir)
|
||||
|
||||
if dir.x ~= 0 and dir.z ~= 0 then
|
||||
if dir.x > 0 and dir.z > 0 then
|
||||
@ -736,10 +1064,11 @@ end
|
||||
-- going to be considered walkable for the algorithm to find a
|
||||
-- path.
|
||||
function npc.actions.walk_to_pos(self, args)
|
||||
-- Get arguments for this task
|
||||
local end_pos = get_pos_argument(self, args.end_pos)
|
||||
-- Get arguments for this task
|
||||
local use_access_node = args.use_access_node or true
|
||||
local end_pos, node_pos = get_pos_argument(self, args.end_pos, use_access_node)
|
||||
if end_pos == nil then
|
||||
npc.log("WARNING", "Got nil position in 'walk_to_pos' using args.pos: "..dump(args.end_pos))
|
||||
-- npc.log("WARNING", "Got nil position in 'walk_to_pos' using args.pos: "..dump(args.end_pos))
|
||||
return
|
||||
end
|
||||
local enforce_move = args.enforce_move or true
|
||||
@ -747,11 +1076,22 @@ function npc.actions.walk_to_pos(self, args)
|
||||
|
||||
-- Round start_pos to make sure it can find start and end
|
||||
local start_pos = vector.round(self.object:getpos())
|
||||
-- Use y of end_pos (this can only be done assuming flat terrain)
|
||||
--start_pos.y = self.object:getpos().y
|
||||
npc.log("DEBUG", "walk_to_pos: Start pos: "..minetest.pos_to_string(start_pos))
|
||||
npc.log("DEBUG", "walk_to_pos: End pos: "..minetest.pos_to_string(end_pos))
|
||||
|
||||
-- Check if start_pos and end_pos are the same
|
||||
if vector.equals(start_pos, end_pos) == true then
|
||||
-- Check if it was using access node, if it was, add action to
|
||||
-- rotate NPC into that direction
|
||||
if use_access_node == true then
|
||||
local dir = npc.actions.get_direction(end_pos, node_pos)
|
||||
npc.add_action(self, npc.actions.cmd.STAND, {dir = dir})
|
||||
end
|
||||
npc.log("WARNING", "walk_to_pos Found start_pos == end_pos")
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
-- Set walkable nodes to empty if the parameter hasn't been used
|
||||
if walkable_nodes == nil then
|
||||
walkable_nodes = {}
|
||||
@ -785,6 +1125,11 @@ function npc.actions.walk_to_pos(self, args)
|
||||
-- Add the last step
|
||||
npc.add_action(self, npc.actions.cmd.WALK_STEP, {dir = dir, speed = speed, target_pos = path[i+1].pos})
|
||||
-- Add stand animation at end
|
||||
if use_access_node == true then
|
||||
dir = npc.actions.get_direction(end_pos, node_pos)
|
||||
end
|
||||
-- minetest.log("Dir: "..dump(dir))
|
||||
-- Change dir if using access_node
|
||||
npc.add_action(self, npc.actions.cmd.STAND, {dir = dir})
|
||||
break
|
||||
end
|
||||
@ -794,13 +1139,15 @@ function npc.actions.walk_to_pos(self, args)
|
||||
if path[i+1].type == npc.pathfinder.node_types.openable then
|
||||
-- Check if door is already open
|
||||
local node = minetest.get_node(path[i+1].pos)
|
||||
if npc.actions.get_openable_node_state(node, dir) == npc.actions.const.doors.state.CLOSED then
|
||||
if npc.actions.get_openable_node_state(node, path[i+1].pos, dir) == npc.actions.const.doors.state.CLOSED then
|
||||
--minetest.log("Opening action to open door")
|
||||
-- Stop to open door, this avoids misplaced movements later on
|
||||
npc.add_action(self, npc.actions.cmd.STAND, {dir=dir})
|
||||
-- Open door
|
||||
npc.add_action(self, npc.actions.cmd.USE_OPENABLE, {pos=path[i+1].pos, dir=dir, action=npc.actions.const.doors.action.OPEN})
|
||||
|
||||
door_opened = true
|
||||
else
|
||||
door_opened = true
|
||||
end
|
||||
|
||||
@ -825,9 +1172,10 @@ function npc.actions.walk_to_pos(self, args)
|
||||
-- Add extra walk step to ensure that one is standing at other side of openable node
|
||||
-- npc.add_action(self, npc.actions.cmd.WALK_STEP, {dir = dir, speed = speed, target_pos = path[i+2].pos})
|
||||
-- Stop to close the door
|
||||
npc.add_action(self, npc.actions.cmd.STAND, {dir=(dir + 2) % 4 })--, pos=pos_on_close})
|
||||
--npc.add_action(self, npc.actions.cmd.STAND, {dir=(dir + 2) % 4 })--, pos=pos_on_close})
|
||||
npc.add_action(self, npc.actions.cmd.STAND, {dir=dir })--, pos=pos_on_close})
|
||||
-- Close door
|
||||
npc.add_action(self, npc.actions.cmd.USE_OPENABLE, {pos=path[i+1].pos, action=npc.actions.const.doors.action.CLOSE})
|
||||
npc.add_action(self, npc.actions.cmd.USE_OPENABLE, {pos=path[i+1].pos, dir=dir, action=npc.actions.const.doors.action.CLOSE})
|
||||
|
||||
door_opened = false
|
||||
end
|
||||
@ -845,6 +1193,6 @@ function npc.actions.walk_to_pos(self, args)
|
||||
if enforce_move then
|
||||
-- Move to end pos
|
||||
self.object:moveto({x=end_pos.x, y=end_pos.y+1, z=end_pos.z})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -14,60 +14,61 @@ npc.pathfinder = {}
|
||||
local pathfinder = {}
|
||||
|
||||
npc.pathfinder.node_types = {
|
||||
start = 0,
|
||||
goal = 1,
|
||||
walkable = 2,
|
||||
openable = 3,
|
||||
non_walkable = 4
|
||||
start = 0,
|
||||
goal = 1,
|
||||
walkable = 2,
|
||||
openable = 3,
|
||||
non_walkable = 4
|
||||
}
|
||||
|
||||
npc.pathfinder.nodes = {
|
||||
openable_prefix = {
|
||||
"doors:",
|
||||
"cottages:gate",
|
||||
"cottages:half_door"
|
||||
}
|
||||
}
|
||||
openable_prefix = {
|
||||
"doors:",
|
||||
"cottages:gate",
|
||||
"cottages:half_door"
|
||||
}
|
||||
}
|
||||
|
||||
-- This function is used to determine if a node is walkable
|
||||
-- or openable, in which case is good to use when finding a path
|
||||
function pathfinder.is_good_node(node, exceptions)
|
||||
--local function is_good_node(node, exceptions)
|
||||
-- Is openable is to support doors, fence gates and other
|
||||
-- doors from other mods. Currently, default doors, gates
|
||||
-- and cottages doors are supported.
|
||||
local is_openable = false
|
||||
for _,node_prefix in pairs(npc.pathfinder.nodes.openable_prefix) do
|
||||
local start_i,end_i = string.find(node.name, node_prefix)
|
||||
if start_i ~= nil then
|
||||
is_openable = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if node ~= nil and node.name ~= nil and not minetest.registered_nodes[node.name].walkable then
|
||||
return npc.pathfinder.node_types.walkable
|
||||
elseif is_openable then
|
||||
return npc.pathfinder.node_types.openable
|
||||
else
|
||||
for i = 1, #exceptions do
|
||||
if node.name == exceptions[i] then
|
||||
return npc.pathfinder.node_types.walkable
|
||||
end
|
||||
end
|
||||
return npc.pathfinder.node_types.non_walkable
|
||||
end
|
||||
--local function is_good_node(node, exceptions)
|
||||
-- Is openable is to support doors, fence gates and other
|
||||
-- doors from other mods. Currently, default doors, gates
|
||||
-- and cottages doors are supported.
|
||||
local is_openable = false
|
||||
for _,node_prefix in pairs(npc.pathfinder.nodes.openable_prefix) do
|
||||
local start_i,end_i = string.find(node.name, node_prefix)
|
||||
if start_i ~= nil then
|
||||
is_openable = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if node ~= nil and node.name ~= nil and not minetest.registered_nodes[node.name].walkable then
|
||||
return npc.pathfinder.node_types.walkable
|
||||
elseif is_openable then
|
||||
return npc.pathfinder.node_types.openable
|
||||
else
|
||||
for i = 1, #exceptions do
|
||||
if node.name == exceptions[i] then
|
||||
return npc.pathfinder.node_types.walkable
|
||||
end
|
||||
end
|
||||
return npc.pathfinder.node_types.non_walkable
|
||||
end
|
||||
end
|
||||
|
||||
function pathfinder.get_decorated_path(path)
|
||||
-- Get details from path nodes
|
||||
local path_detail = {}
|
||||
for i = 1, #path do
|
||||
local node = minetest.get_node(path[i])
|
||||
table.insert(path_detail, {pos=path[i], type=pathfinder.is_good_node(node, {})})
|
||||
end
|
||||
-- Get details from path nodes
|
||||
local path_detail = {}
|
||||
for i = 1, #path do
|
||||
local node = minetest.get_node(path[i])
|
||||
table.insert(path_detail, {pos={x=path[i].x, y=path[i].y-0.5, z=path[i].z},
|
||||
type=pathfinder.is_good_node(node, {})})
|
||||
end
|
||||
|
||||
npc.log("DEBUG", "Detailed path: "..dump(path_detail))
|
||||
return path_detail
|
||||
--npc.log("DEBUG", "Detailed path: "..dump(path_detail))
|
||||
return path_detail
|
||||
end
|
||||
|
||||
function npc.pathfinder.find_path(start_pos, end_pos, entity, decorate_path)
|
||||
@ -78,7 +79,7 @@ function npc.pathfinder.find_path(start_pos, end_pos, entity, decorate_path)
|
||||
end
|
||||
else
|
||||
npc.log("ERROR", "Couldn't find path from "..minetest.pos_to_string(start_pos)
|
||||
.." to "..minetest.pos_to_string(end_pos))
|
||||
.." to "..minetest.pos_to_string(end_pos))
|
||||
end
|
||||
return path
|
||||
end
|
||||
@ -135,42 +136,42 @@ end
|
||||
-- or openable, in which case is good to use when finding a path
|
||||
local function walkable(node, exceptions)
|
||||
local exceptions = exceptions or {}
|
||||
-- Is openable is to support doors, fence gates and other
|
||||
-- doors from other mods. Currently, default doors, gates
|
||||
-- and cottages doors are supported.
|
||||
--minetest.log("Is good node: "..dump(node))
|
||||
local is_openable = false
|
||||
for _,node_prefix in pairs(npc.pathfinder.nodes.openable_prefix) do
|
||||
local start_i,end_i = string.find(node.name, node_prefix)
|
||||
if start_i ~= nil then
|
||||
is_openable = true
|
||||
break
|
||||
end
|
||||
end
|
||||
-- Detect mg_villages ceilings usage of thin wood nodeboxes
|
||||
-- TODO: Improve
|
||||
local is_mg_villages_ceiling = false
|
||||
if node.name == "cottages:wood_flat" then
|
||||
is_mg_villages_ceiling = true
|
||||
end
|
||||
if node ~= nil
|
||||
and node.name ~= nil
|
||||
and node.name ~= "ignore"
|
||||
and minetest.registered_nodes[node.name]
|
||||
and not minetest.registered_nodes[node.name].walkable then
|
||||
return false
|
||||
elseif is_openable then
|
||||
return false
|
||||
elseif is_mg_villages_ceiling then
|
||||
return false
|
||||
else
|
||||
for i = 1, #exceptions do
|
||||
if node.name == exceptions[i] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
-- Is openable is to support doors, fence gates and other
|
||||
-- doors from other mods. Currently, default doors, gates
|
||||
-- and cottages doors are supported.
|
||||
--minetest.log("Is good node: "..dump(node))
|
||||
local is_openable = false
|
||||
for _,node_prefix in pairs(npc.pathfinder.nodes.openable_prefix) do
|
||||
local start_i,end_i = string.find(node.name, node_prefix)
|
||||
if start_i ~= nil then
|
||||
is_openable = true
|
||||
break
|
||||
end
|
||||
end
|
||||
-- Detect mg_villages ceilings usage of thin wood nodeboxes
|
||||
-- TODO: Improve
|
||||
local is_mg_villages_ceiling = false
|
||||
if node.name == "cottages:wood_flat" then
|
||||
is_mg_villages_ceiling = true
|
||||
end
|
||||
if node ~= nil
|
||||
and node.name ~= nil
|
||||
and node.name ~= "ignore"
|
||||
and minetest.registered_nodes[node.name]
|
||||
and not minetest.registered_nodes[node.name].walkable then
|
||||
return false
|
||||
elseif is_openable then
|
||||
return false
|
||||
elseif is_mg_villages_ceiling then
|
||||
return false
|
||||
else
|
||||
for i = 1, #exceptions do
|
||||
if node.name == exceptions[i] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local function check_clearance(cpos, x, z, height)
|
||||
@ -277,65 +278,65 @@ function pathfinder.find_path(pos, endpos, entity)
|
||||
local neighbors = {}
|
||||
local neighbors_index = 1
|
||||
for z = -1, 1 do
|
||||
for x = -1, 1 do
|
||||
local neighbor_pos = {x = current_pos.x + x, y = current_pos.y, z = current_pos.z + z}
|
||||
local neighbor = minetest.get_node(neighbor_pos)
|
||||
local neighbor_ground_level = get_neighbor_ground_level(neighbor_pos, entity_jump_height, entity_fear_height)
|
||||
local neighbor_clearance = false
|
||||
if neighbor_ground_level then
|
||||
-- print(neighbor_ground_level.y - current_pos.y)
|
||||
--minetest.set_node(neighbor_ground_level, {name = "default:dry_shrub"})
|
||||
local node_above_head = minetest.get_node(
|
||||
for x = -1, 1 do
|
||||
local neighbor_pos = {x = current_pos.x + x, y = current_pos.y, z = current_pos.z + z}
|
||||
local neighbor = minetest.get_node(neighbor_pos)
|
||||
local neighbor_ground_level = get_neighbor_ground_level(neighbor_pos, entity_jump_height, entity_fear_height)
|
||||
local neighbor_clearance = false
|
||||
if neighbor_ground_level then
|
||||
-- print(neighbor_ground_level.y - current_pos.y)
|
||||
--minetest.set_node(neighbor_ground_level, {name = "default:dry_shrub"})
|
||||
local node_above_head = minetest.get_node(
|
||||
{x = current_pos.x, y = current_pos.y + entity_height, z = current_pos.z})
|
||||
if neighbor_ground_level.y - current_pos.y > 0 and not walkable(node_above_head) then
|
||||
local height = -1
|
||||
repeat
|
||||
height = height + 1
|
||||
local node = minetest.get_node(
|
||||
if neighbor_ground_level.y - current_pos.y > 0 and not walkable(node_above_head) then
|
||||
local height = -1
|
||||
repeat
|
||||
height = height + 1
|
||||
local node = minetest.get_node(
|
||||
{x = neighbor_ground_level.x,
|
||||
y = neighbor_ground_level.y + height,
|
||||
z = neighbor_ground_level.z})
|
||||
until walkable(node) or height > entity_height
|
||||
if height >= entity_height then
|
||||
neighbor_clearance = true
|
||||
end
|
||||
elseif neighbor_ground_level.y - current_pos.y > 0 and walkable(node_above_head) then
|
||||
neighbors[neighbors_index] = {
|
||||
y = neighbor_ground_level.y + height,
|
||||
z = neighbor_ground_level.z})
|
||||
until walkable(node) or height > entity_height
|
||||
if height >= entity_height then
|
||||
neighbor_clearance = true
|
||||
end
|
||||
elseif neighbor_ground_level.y - current_pos.y > 0 and walkable(node_above_head) then
|
||||
neighbors[neighbors_index] = {
|
||||
hash = nil,
|
||||
pos = nil,
|
||||
clear = nil,
|
||||
walkable = nil,
|
||||
}
|
||||
else
|
||||
local height = -1
|
||||
repeat
|
||||
height = height + 1
|
||||
local node = minetest.get_node(
|
||||
}
|
||||
else
|
||||
local height = -1
|
||||
repeat
|
||||
height = height + 1
|
||||
local node = minetest.get_node(
|
||||
{x = neighbor_ground_level.x,
|
||||
y = current_pos.y + height,
|
||||
z = neighbor_ground_level.z})
|
||||
until walkable(node) or height > entity_height
|
||||
if height >= entity_height then
|
||||
neighbor_clearance = true
|
||||
y = current_pos.y + height,
|
||||
z = neighbor_ground_level.z})
|
||||
until walkable(node) or height > entity_height
|
||||
if height >= entity_height then
|
||||
neighbor_clearance = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
neighbors[neighbors_index] = {
|
||||
neighbors[neighbors_index] = {
|
||||
hash = minetest.hash_node_position(neighbor_ground_level),
|
||||
pos = neighbor_ground_level,
|
||||
clear = neighbor_clearance,
|
||||
walkable = walkable(neighbor),
|
||||
}
|
||||
else
|
||||
neighbors[neighbors_index] = {
|
||||
}
|
||||
else
|
||||
neighbors[neighbors_index] = {
|
||||
hash = nil,
|
||||
pos = nil,
|
||||
clear = nil,
|
||||
walkable = nil,
|
||||
}
|
||||
}
|
||||
end
|
||||
neighbors_index = neighbors_index + 1
|
||||
end
|
||||
neighbors_index = neighbors_index + 1
|
||||
end
|
||||
end
|
||||
|
||||
for id, neighbor in pairs(neighbors) do
|
||||
@ -343,22 +344,22 @@ function pathfinder.find_path(pos, endpos, entity)
|
||||
local cut_corner = false
|
||||
if id == 1 then
|
||||
if not neighbors[id + 1].clear or not neighbors[id + 3].clear
|
||||
or neighbors[id + 1].walkable or neighbors[id + 3].walkable then
|
||||
or neighbors[id + 1].walkable or neighbors[id + 3].walkable then
|
||||
cut_corner = true
|
||||
end
|
||||
elseif id == 3 then
|
||||
if not neighbors[id - 1].clear or not neighbors[id + 3].clear
|
||||
or neighbors[id - 1].walkable or neighbors[id + 3].walkable then
|
||||
or neighbors[id - 1].walkable or neighbors[id + 3].walkable then
|
||||
cut_corner = true
|
||||
end
|
||||
elseif id == 7 then
|
||||
if not neighbors[id + 1].clear or not neighbors[id - 3].clear
|
||||
or neighbors[id + 1].walkable or neighbors[id - 3].walkable then
|
||||
or neighbors[id + 1].walkable or neighbors[id - 3].walkable then
|
||||
cut_corner = true
|
||||
end
|
||||
elseif id == 9 then
|
||||
if not neighbors[id - 1].clear or not neighbors[id - 3].clear
|
||||
or neighbors[id - 1].walkable or neighbors[id - 3].walkable then
|
||||
or neighbors[id - 1].walkable or neighbors[id - 3].walkable then
|
||||
cut_corner = true
|
||||
end
|
||||
end
|
||||
@ -375,11 +376,11 @@ function pathfinder.find_path(pos, endpos, entity)
|
||||
end
|
||||
local hCost = get_distance(neighbor.pos, endpos)
|
||||
openSet[neighbor.hash] = {
|
||||
gCost = move_cost_to_neighbor,
|
||||
hCost = hCost,
|
||||
fCost = move_cost_to_neighbor + hCost,
|
||||
parent = current_index,
|
||||
pos = neighbor.pos
|
||||
gCost = move_cost_to_neighbor,
|
||||
hCost = hCost,
|
||||
fCost = move_cost_to_neighbor + hCost,
|
||||
parent = current_index,
|
||||
pos = neighbor.pos
|
||||
}
|
||||
end
|
||||
end
|
||||
|
1065
actions/places.lua
127
data/dialogues_data.lua
Normal file
@ -0,0 +1,127 @@
|
||||
|
||||
|
||||
|
||||
-- Phase 1 dialogues, unisex
|
||||
|
||||
npc.dialogue.register_dialogue({
|
||||
text = "Hello there!",
|
||||
tags = {"unisex", "phase1"}
|
||||
})
|
||||
|
||||
npc.dialogue.register_dialogue({
|
||||
text = "How are you doing?",
|
||||
tags = {"unisex", "phase1"}
|
||||
})
|
||||
|
||||
npc.dialogue.register_dialogue({
|
||||
text = "Just living another day...",
|
||||
tags = {"unisex", "phase1"}
|
||||
})
|
||||
|
||||
-- Phase 1 dialogues, female
|
||||
|
||||
npc.dialogue.register_dialogue({
|
||||
text = "Is there any woman in this area more beautiful than I am?",
|
||||
tags = {"female", "phase1"}
|
||||
})
|
||||
|
||||
npc.dialogue.register_dialogue({
|
||||
text = "Hello! Have you been to the sea?",
|
||||
tags = {"female", "phase1"},
|
||||
responses = {
|
||||
[1] = {
|
||||
text = "No, never before",
|
||||
action_type = "function",
|
||||
action = function(self, player)
|
||||
minetest.chat_send_player(player:get_player_name(), "Oh, never? How come! You should."..
|
||||
"\nHere, take this. It will guide you to the sea...")
|
||||
end
|
||||
},
|
||||
[2] = {
|
||||
text = "Yes, sure",
|
||||
action_type = "dialogue",
|
||||
action = {
|
||||
text = "It's so beautiful, and big, and large, and infinite, and..."
|
||||
}
|
||||
},
|
||||
[3] = {
|
||||
text = "Of course! And to all the seas in the world!",
|
||||
action_type = "dialogue",
|
||||
action = {
|
||||
text = "Awww you are no fun then! Go on then know-it-all!"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
npc.dialogue.register_dialogue({
|
||||
text = "Hello there, could you help me?",
|
||||
tags = {"phase1", "female"},
|
||||
flag = {name="received_money_help", value=false},
|
||||
responses = {
|
||||
[1] = {
|
||||
text = "Yes, how can I help?",
|
||||
action_type = "dialogue",
|
||||
action = {
|
||||
text = "Could you please give me 3 "..npc.trade.prices.currency.tier3.name.."?",
|
||||
responses = {
|
||||
[1] = {
|
||||
text = "Yes, ok, here",
|
||||
action_type = "function",
|
||||
action = function(self, player)
|
||||
-- Take item
|
||||
if npc.actions.execute(self, npc.actions.cmd.TAKE_ITEM, {
|
||||
player=player:get_player_name(),
|
||||
pos=nil,
|
||||
inv_list="main",
|
||||
item_name=npc.trade.prices.currency.tier3.string,
|
||||
count=3
|
||||
}) then
|
||||
-- Send message
|
||||
npc.chat(self.npc_name, player:get_player_name(), "Thank you, thank you so much!")
|
||||
-- Set flag
|
||||
npc.add_flag(self, "received_money_help", true)
|
||||
-- Add chat line
|
||||
--table.insert(self.dialogues.normal, npc.data.DIALOGUES.female["phase1"][8])
|
||||
else
|
||||
npc.chat(self.npc_name, player:get_player_name(), "Looks like you don't have that amount of money...")
|
||||
end
|
||||
end
|
||||
},
|
||||
[2] = {
|
||||
text = "No, I'm sorry",
|
||||
action_type = "dialogue",
|
||||
action = {
|
||||
text = "Oh..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[2] = {
|
||||
text = "No, I'm sorry, can't now",
|
||||
action_type = "function",
|
||||
action = function(self, player)
|
||||
npc.chat(self.npc_name, player:get_player_name(), "Oh, ok...")
|
||||
end
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
npc.dialogue.register_dialogue({
|
||||
text = "Thank you so much for your help, thank you!",
|
||||
flag = {name="received_money_help", value=true},
|
||||
tags = {"phase1", "female"}
|
||||
})
|
||||
|
||||
-- Phase 1 dialogues, male
|
||||
|
||||
npc.dialogue.register_dialogue({
|
||||
text = "Hunting is the best pasttime!",
|
||||
tags = {"male", "phase1"}
|
||||
})
|
||||
|
||||
npc.dialogue.register_dialogue({
|
||||
text = "I hope my wheat grows well this harvest.",
|
||||
tags = {"male", "default_farmer"}
|
||||
})
|
239
data/gift_items_data.lua
Normal file
@ -0,0 +1,239 @@
|
||||
------------------------------------------------------------------------------
|
||||
-- Gift Items data definitions
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
-- PHASE 1
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
npc.relationships.register_favorite_item("default:apple", "phase1", "female", {
|
||||
responses = {"Hey, I really wanted an apple, thank you!"},
|
||||
hints = {"I could really do with an apple..."}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("farming:bread", "phase1", "female", {
|
||||
responses = {"Thanks, you didn't have to, but thanks..."},
|
||||
hints = {"Some fresh bread would be good!"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("farming:seed_cotton", "phase1", "female", {
|
||||
responses = {"Thank you, I will plant this really soon"},
|
||||
hints = {"I would like to have some cotton plants around"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("farming:seed_wheat", "phase1", "female", {
|
||||
responses = {"Thank you! These seeds will make a good wheat plant!"},
|
||||
hints = {"I've been thinking I should get wheat seeds"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("flowers:rose", "phase1", "female", {
|
||||
responses = {"Thanks..."},
|
||||
hints = {"Red roses make a nice gift!"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("flowers:geranium", "phase1", "female", {
|
||||
responses = {"Oh, for me? Thank you!"},
|
||||
hints = {"Blue geraniums are so beautiful"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("default:clay_lump", "phase1", "female", {
|
||||
responses = {"Thanks! Now, what can I do with this..."},
|
||||
hints = {"If I had some clay lump, I may do some pottery"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("mobs:meat_raw", "phase1", "female", {
|
||||
responses = {"This will be great for tonight! Thanks"},
|
||||
hints = {"A good dinner always have meat"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("mobs:leather", "phase1", "female", {
|
||||
responses = {"Thank you! I needed this!"},
|
||||
hints = {"If only I could get some leather"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("default:sapling", "phase1", "female", {
|
||||
responses = {"Now I can plant that tree..."},
|
||||
hints = {"I really would like an apple tree close by."}
|
||||
})
|
||||
|
||||
|
||||
npc.relationships.register_favorite_item("farming:cotton", "phase2", "female", {
|
||||
responses = {"This is going to be very helpful, thank you!"},
|
||||
hints = {"If I just had some cotton lying around..."}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("wool:white", "phase2", "female", {
|
||||
responses = {"Thanks, you didn't have to, but thanks..."},
|
||||
hints = {"Have you seen a sheep? I wish I had some white wool..."}
|
||||
})
|
||||
|
||||
|
||||
npc.relationships.register_favorite_item("default:apple", "phase3", "female", {
|
||||
responses = {"Hey, I really wanted an apple, thank you!"},
|
||||
hints = {"I could really do with an apple..."}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("farming:bread", "phase3", "female", {
|
||||
responses = {"Thanks, you didn't have to, but thanks..."},
|
||||
hints = {"Some fresh bread would be good!"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("default:apple", "phase4", "female", {
|
||||
responses = {"Hey, I really wanted an apple, thank you!"},
|
||||
hints = {"I could really do with an apple..."}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("farming:bread", "phase4", "female", {
|
||||
responses = {"Thanks, you didn't have to, but thanks..."},
|
||||
hints = {"Some fresh bread would be good!"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("default:apple", "phase5", "female", {
|
||||
responses = {"Hey, I really wanted an apple, thank you!"},
|
||||
hints = {"I could really do with an apple..."}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("farming:bread", "phase5", "female", {
|
||||
responses = {"Thanks, you didn't have to, but thanks..."},
|
||||
hints = {"Some fresh bread would be good!"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("default:apple", "phase6", "female", {
|
||||
responses = {"Hey, I really wanted an apple, thank you!"},
|
||||
hints = {"I could really do with an apple..."}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("farming:bread", "phase6", "female", {
|
||||
responses = {"Thanks, you didn't have to, but thanks..."},
|
||||
hints = {"Some fresh bread would be good!"}
|
||||
})
|
||||
|
||||
|
||||
-- Male
|
||||
npc.relationships.register_favorite_item("default:apple", "phase1", "male", {
|
||||
responses = {"Good apple, thank you!"},
|
||||
hints = {"I could do with an apple..."}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("farming:bread", "phase1", "male", {
|
||||
responses = {"Thank you! I was hungry."},
|
||||
hints = {"Some fresh bread would be good!"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("farming:seed_cotton", "phase1", "male", {
|
||||
responses = {"Thank you, I will plant this soon"},
|
||||
hints = {"I would like to have some cotton plants around."}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("farming:seed_wheat", "phase1", "male", {
|
||||
responses = {"Thank you! These seeds will make a good wheat plant!"},
|
||||
hints = {"I've been thinking I should get wheat seeds."}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("default:wood", "phase1", "male", {
|
||||
responses = {"Thanks, I needed this."},
|
||||
hints = {"Some wood without having to cut a tree would be good.}"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("default:tree", "phase1", "male", {
|
||||
responses = {"Excellent to get that furnace going!"},
|
||||
hints = {"I'm looking for some logs"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("default:clay_lump", "phase1", "male", {
|
||||
responses = {"Thanks! Now, what can I do with this..."},
|
||||
hints = {"Now, some clay would be good."}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("mobs:meat_raw", "phase1", "male", {
|
||||
responses = {"This makes a great meal. Thank you"},
|
||||
hints = {"Meat is always great"},
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("mobs:leather", "phase1", "male", {
|
||||
responses = {"Time to tan some leathers!"},
|
||||
hints = {"I have been needing leather these days."}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("default:sapling", "phase1", "male", {
|
||||
responses = {"Thanks, I will plant this right now"},
|
||||
hints = {"I really would like an apple tree close by."}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("default:apple", "phase2", "male", {
|
||||
responses = {"Good apple, thank you!"},
|
||||
hints = {"I could do with an apple..."}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("farming:bread", "phase2", "male", {
|
||||
responses = {"Thank you! I was hungry."},
|
||||
hints = {"Some fresh bread would be good!"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("default:apple", "phase3", "male", {
|
||||
responses = {"Good apple, thank you!"},
|
||||
hints = {"I could do with an apple..."}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("farming:bread", "phase3", "male", {
|
||||
responses = {"Thank you! I was hungry."},
|
||||
hints = {"Some fresh bread would be good!"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("default:apple", "phase4", "male", {
|
||||
responses = {"Good apple, thank you!"},
|
||||
hints = {"I could do with an apple..."}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("farming:bread", "phase4", "male", {
|
||||
responses = {"Thank you! I was hungry."},
|
||||
hints = {"Some fresh bread would be good!"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("default:apple", "phase5", "male", {
|
||||
responses = {"Good apple, thank you!"},
|
||||
hints = {"I could do with an apple..."}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("farming:bread", "phase5", "male", {
|
||||
responses = {"Thank you! I was hungry."},
|
||||
hints = {"Some fresh bread would be good!"}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("default:apple", "phase6", "male", {
|
||||
responses = {"Good apple, thank you!"},
|
||||
hints = {"I could do with an apple..."}
|
||||
})
|
||||
|
||||
npc.relationships.register_favorite_item("farming:bread", "phase6", "male", {
|
||||
responses = {"Thank you! I was hungry."},
|
||||
hints = {"Some fresh bread would be good!"}
|
||||
})
|
||||
|
||||
-- Disliked items
|
||||
-- Female
|
||||
npc.relationships.register_disliked_item("default:stone", "female", {
|
||||
responses = {"A stone, oh... why do you give this to me?"},
|
||||
hints = {"Why would someone want a stone?"}
|
||||
})
|
||||
|
||||
npc.relationships.register_disliked_item("default:cobble", "female", {
|
||||
responses = {"Cobblestone? No, no, why?"},
|
||||
hints = {"Anything worst than stone is cobblestone."}
|
||||
})
|
||||
|
||||
-- Male
|
||||
npc.relationships.register_disliked_item("default:stone", "male", {
|
||||
responses = {"Good apple, thank you!"},
|
||||
hints = {"I could do with an apple..."}
|
||||
})
|
||||
|
||||
npc.relationships.register_disliked_item("default:cobble", "male", {
|
||||
responses = {"Cobblestone!? Wow, you sure think a lot before giving a gift..."},
|
||||
hints = {"If I really hate something, that's cobblestone!"}
|
||||
})
|
||||
|
||||
--npc.log("DEBUG", "Registered gift items: "..dump(npc.relationships.gift_items))
|
||||
--npc.log("DEBUG", "Registered dialogues: "..dump(npc.dialogue.registered_dialogues))
|
||||
npc.log("INFO", "Registered gift items count: "..dump(#npc.relationships.gift_items))
|
||||
npc.log("INFO", "Registered dialogues count: "..dump(#npc.dialogue.registered_dialogues))
|
0
data/names_data.lua
Normal file
166
data/occupations/default.lua
Normal file
@ -0,0 +1,166 @@
|
||||
----------------------------------------------------
|
||||
-- Default occupation for Advanced NPC
|
||||
-- By Zorman2000
|
||||
----------------------------------------------------
|
||||
-- The default "occupation" gives some schedule entries to the NPCs
|
||||
-- which don't have any occupation. The rest is left as randomly
|
||||
-- initialized.
|
||||
|
||||
local basic_def = {
|
||||
-- Use random textures
|
||||
textures = {},
|
||||
-- Use random dialogues
|
||||
dialogues = {},
|
||||
-- Initialize inventory with random items
|
||||
initial_inventory = {},
|
||||
-- Initialize schedule
|
||||
schedules_entries = {
|
||||
-- Schedule entry for 7 in the morning
|
||||
[7] = {
|
||||
-- Change trader status to "none"
|
||||
[1] = {
|
||||
property = npc.schedule_properties.trader_status,
|
||||
args = {
|
||||
status = npc.trade.NONE
|
||||
}
|
||||
},
|
||||
-- Get out of bed
|
||||
[1] = {task = npc.actions.cmd.USE_BED, args = {
|
||||
pos = npc.places.PLACE_TYPE.BED.PRIMARY,
|
||||
action = npc.actions.const.beds.GET_UP
|
||||
}
|
||||
},
|
||||
-- Walk to home inside
|
||||
[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, disable_rightclick = false}}
|
||||
},
|
||||
-- Schedule entry for 8 in the morning
|
||||
[8] = {
|
||||
-- Walk to outside of home
|
||||
[1] = {task = npc.actions.cmd.WALK_TO_POS, args = {
|
||||
end_pos = npc.places.PLACE_TYPE.OTHER.HOME_OUTSIDE,
|
||||
walkable = {}
|
||||
},
|
||||
chance = 75
|
||||
},
|
||||
-- Allow mobs_redo wandering
|
||||
[2] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}}
|
||||
},
|
||||
-- Schedule entry for 12 midday
|
||||
[12] = {
|
||||
-- Walk to a sittable node
|
||||
[1] = {task = npc.actions.cmd.WALK_TO_POS,
|
||||
args = {
|
||||
end_pos = {
|
||||
place_category=npc.places.PLACE_TYPE.CATEGORIES.SITTABLE,
|
||||
place_type=npc.places.PLACE_TYPE.SITTABLE.PRIMARY,
|
||||
use_access_node=true,
|
||||
try_alternative_if_used=true,
|
||||
mark_target_as_used = true
|
||||
},
|
||||
walkable = {"cottages:bench"}
|
||||
},
|
||||
chance = 75
|
||||
},
|
||||
-- Sit on the node
|
||||
[2] = {task = npc.actions.cmd.USE_SITTABLE,
|
||||
args = {
|
||||
pos = npc.places.PLACE_TYPE.CALCULATED.TARGET,
|
||||
action = npc.actions.const.sittable.SIT
|
||||
},
|
||||
depends = {1}
|
||||
},
|
||||
-- Stay put into place
|
||||
[3] = {
|
||||
action = npc.actions.cmd.FREEZE, args = {freeze = true},
|
||||
depends = {2}
|
||||
}
|
||||
},
|
||||
-- Schedule entry for 1 in the afternoon
|
||||
[13] = {
|
||||
-- Get up from sit
|
||||
[1] = {
|
||||
action = npc.actions.cmd.USE_SITTABLE, args = {
|
||||
pos = npc.places.PLACE_TYPE.CALCULATED.TARGET,
|
||||
action = npc.actions.const.sittable.GET_UP
|
||||
},
|
||||
},
|
||||
-- Give NPC money to buy from player
|
||||
[2] = {
|
||||
property = npc.schedule_properties.put_multiple_items,
|
||||
args = {
|
||||
itemlist = {
|
||||
{name="default:iron_lump", random=true, min=2, max=4}
|
||||
}
|
||||
},
|
||||
chance = 75
|
||||
},
|
||||
-- Change trader status to "casual trader"
|
||||
[3] = {
|
||||
property = npc.schedule_properties.trader_status,
|
||||
args = {
|
||||
status = npc.trade.CASUAL
|
||||
},
|
||||
chance = 75
|
||||
},
|
||||
[4] = {
|
||||
property = npc.schedule_properties.can_receive_gifts,
|
||||
args = {
|
||||
can_receive_gifts = false
|
||||
},
|
||||
depends = {1}
|
||||
},
|
||||
-- Allow mobs_redo wandering
|
||||
[5] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}}
|
||||
},
|
||||
-- Schedule entry for 6 in the evening
|
||||
[18] = {
|
||||
-- Change trader status to "none"
|
||||
[1] = {property = npc.schedule_properties.trader_status, args = {
|
||||
status = npc.trade.NONE
|
||||
}
|
||||
},
|
||||
-- Enable gift receiving again
|
||||
[2] = {property = npc.schedule_properties.can_receive_gifts, args = {
|
||||
can_receive_gifts = true
|
||||
}
|
||||
},
|
||||
-- Get inside home
|
||||
[3] = {task = npc.actions.cmd.WALK_TO_POS, args = {
|
||||
end_pos = npc.places.PLACE_TYPE.OTHER.HOME_INSIDE,
|
||||
walkable = {}
|
||||
}
|
||||
},
|
||||
-- Allow mobs_redo wandering
|
||||
[4] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}}
|
||||
},
|
||||
-- Schedule entry for 10 in the evening
|
||||
[22] = {
|
||||
[1] = {task = npc.actions.cmd.WALK_TO_POS, args = {
|
||||
end_pos = {place_type=npc.places.PLACE_TYPE.BED.PRIMARY, use_access_node=true},
|
||||
walkable = {}
|
||||
}
|
||||
},
|
||||
-- Use bed
|
||||
[2] = {task = npc.actions.cmd.USE_BED,
|
||||
args = {
|
||||
pos = npc.places.PLACE_TYPE.BED.PRIMARY,
|
||||
action = npc.actions.const.beds.LAY
|
||||
}
|
||||
},
|
||||
-- Stay put on bed
|
||||
[3] = {action = npc.actions.cmd.FREEZE, args = {freeze = true, disable_rightclick = true}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-- Register default occupation
|
||||
npc.occupations.register_occupation(npc.occupations.basic_name, basic_def)
|
558
data/occupations/default_farmer.lua
Normal file
@ -0,0 +1,558 @@
|
||||
----------------------------------------------------
|
||||
-- Test farmer occupation for Advanced NPC
|
||||
-- By Zorman2000
|
||||
----------------------------------------------------
|
||||
-- This farmer implementation is still WIP. It is supposed to spawn
|
||||
-- on buildings that have plots or there are fields nearby. Also, it
|
||||
-- work on its crops during the morning, and sell some of them on the
|
||||
-- afternoon.
|
||||
|
||||
local farming_plants = {
|
||||
"farming:cotton_1",
|
||||
"farming:cotton_2",
|
||||
"farming:cotton_3",
|
||||
"farming:cotton_4",
|
||||
"farming:cotton_5",
|
||||
"farming:cotton_6",
|
||||
"farming:cotton_7",
|
||||
"farming:cotton_8",
|
||||
"farming:wheat_1",
|
||||
"farming:wheat_2",
|
||||
"farming:wheat_3",
|
||||
"farming:wheat_4",
|
||||
"farming:wheat_5",
|
||||
"farming:wheat_6",
|
||||
"farming:wheat_7",
|
||||
"farming:wheat_8"
|
||||
}
|
||||
|
||||
local farmer_def = {
|
||||
dialogues = {},
|
||||
textures = {},
|
||||
building_types = {
|
||||
"farm_tiny", "farm_full"
|
||||
},
|
||||
surrounding_building_types = {
|
||||
{type="field", origin_building_types={"hut", "house", "lumberjack"}}
|
||||
},
|
||||
walkable_nodes = farming_plants,
|
||||
initial_inventory = {
|
||||
{name="farming:seed_cotton", count=6}
|
||||
},
|
||||
schedules_entries = {
|
||||
[6] = {
|
||||
-- Get out of bed
|
||||
[1] = {
|
||||
task = npc.actions.cmd.USE_BED, args = {
|
||||
pos = npc.places.PLACE_TYPE.BED.PRIMARY,
|
||||
action = npc.actions.const.beds.GET_UP
|
||||
}
|
||||
},
|
||||
-- Walk to home inside
|
||||
[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}}
|
||||
},
|
||||
[7] = {
|
||||
[1] =
|
||||
{
|
||||
task = npc.actions.cmd.WALK_TO_POS,
|
||||
args = {
|
||||
end_pos = npc.places.PLACE_TYPE.WORKPLACE.PRIMARY,
|
||||
walkable = {}
|
||||
}
|
||||
},
|
||||
[2] =
|
||||
{
|
||||
check = true,
|
||||
range = 2,
|
||||
random_execution_times = true,
|
||||
min_count = 20,
|
||||
max_count = 25,
|
||||
nodes = farming_plants,
|
||||
prefer_last_acted_upon_node = true,
|
||||
walkable_nodes = farming_plants,
|
||||
actions =
|
||||
{
|
||||
-- Actions for cotton - harvest and replant
|
||||
["farming:cotton_1"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
task = npc.actions.cmd.WALK_TO_POS,
|
||||
args = {
|
||||
end_pos = npc.places.PLACE_TYPE.SCHEDULE.TARGET,
|
||||
walkable = farming_plants
|
||||
}
|
||||
},
|
||||
[2] =
|
||||
{
|
||||
action = npc.actions.cmd.DIG,
|
||||
args = {
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[3] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
},
|
||||
[4] =
|
||||
{
|
||||
action = npc.actions.cmd.PLACE,
|
||||
args =
|
||||
{
|
||||
node = "farming:cotton_2",
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[5] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
}
|
||||
},
|
||||
["farming:cotton_2"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
task = npc.actions.cmd.WALK_TO_POS,
|
||||
args = {
|
||||
end_pos = npc.places.PLACE_TYPE.SCHEDULE.TARGET,
|
||||
walkable = farming_plants
|
||||
}
|
||||
},
|
||||
[2] =
|
||||
{
|
||||
action = npc.actions.cmd.DIG,
|
||||
args = {
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[3] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
},
|
||||
[4] =
|
||||
{
|
||||
action = npc.actions.cmd.PLACE,
|
||||
args =
|
||||
{
|
||||
node = "farming:cotton_3",
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[5] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
}
|
||||
},
|
||||
["farming:cotton_3"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
task = npc.actions.cmd.WALK_TO_POS,
|
||||
args = {
|
||||
end_pos = npc.places.PLACE_TYPE.SCHEDULE.TARGET,
|
||||
walkable = farming_plants
|
||||
}
|
||||
},
|
||||
[2] =
|
||||
{
|
||||
action = npc.actions.cmd.DIG,
|
||||
args = {
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[3] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
},
|
||||
[4] =
|
||||
{
|
||||
action = npc.actions.cmd.PLACE,
|
||||
args =
|
||||
{
|
||||
node = "farming:cotton_4",
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[5] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
}
|
||||
},
|
||||
["farming:cotton_4"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
task = npc.actions.cmd.WALK_TO_POS,
|
||||
args = {
|
||||
end_pos = npc.places.PLACE_TYPE.SCHEDULE.TARGET,
|
||||
walkable = farming_plants
|
||||
}
|
||||
},
|
||||
[2] =
|
||||
{
|
||||
action = npc.actions.cmd.DIG,
|
||||
args = {
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[3] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
},
|
||||
[4] =
|
||||
{
|
||||
action = npc.actions.cmd.PLACE,
|
||||
args =
|
||||
{
|
||||
node = "farming:cotton_5",
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[5] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
}
|
||||
},
|
||||
["farming:cotton_5"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
task = npc.actions.cmd.WALK_TO_POS,
|
||||
args = {
|
||||
end_pos = npc.places.PLACE_TYPE.SCHEDULE.TARGET,
|
||||
walkable = farming_plants
|
||||
}
|
||||
},
|
||||
[2] =
|
||||
{
|
||||
action = npc.actions.cmd.DIG,
|
||||
args = {
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[3] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
},
|
||||
[4] =
|
||||
{
|
||||
action = npc.actions.cmd.PLACE,
|
||||
args =
|
||||
{
|
||||
node = "farming:cotton_6",
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[5] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
}
|
||||
},
|
||||
["farming:cotton_6"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
task = npc.actions.cmd.WALK_TO_POS,
|
||||
args = {
|
||||
end_pos = npc.places.PLACE_TYPE.SCHEDULE.TARGET,
|
||||
walkable = farming_plants
|
||||
}
|
||||
},
|
||||
[2] =
|
||||
{
|
||||
action = npc.actions.cmd.DIG,
|
||||
args = {
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[3] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
},
|
||||
[4] =
|
||||
{
|
||||
action = npc.actions.cmd.PLACE,
|
||||
args =
|
||||
{
|
||||
node = "farming:cotton_7",
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[5] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
}
|
||||
},
|
||||
["farming:cotton_7"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
task = npc.actions.cmd.WALK_TO_POS,
|
||||
args = {
|
||||
end_pos = npc.places.PLACE_TYPE.SCHEDULE.TARGET,
|
||||
walkable = farming_plants
|
||||
}
|
||||
},
|
||||
[2] =
|
||||
{
|
||||
action = npc.actions.cmd.DIG,
|
||||
args = {
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[3] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
},
|
||||
[4] =
|
||||
{
|
||||
action = npc.actions.cmd.PLACE,
|
||||
args =
|
||||
{
|
||||
node = "farming:cotton_8",
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[5] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
}
|
||||
},
|
||||
["farming:cotton_8"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
task = npc.actions.cmd.WALK_TO_POS,
|
||||
args = {
|
||||
end_pos = npc.places.PLACE_TYPE.SCHEDULE.TARGET,
|
||||
walkable = farming_plants
|
||||
}
|
||||
},
|
||||
[2] =
|
||||
{
|
||||
action = npc.actions.cmd.DIG,
|
||||
args = {
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[3] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
},
|
||||
[4] =
|
||||
{
|
||||
action = npc.actions.cmd.PLACE,
|
||||
args =
|
||||
{
|
||||
node = "farming:cotton_1",
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[5] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
}
|
||||
},
|
||||
["farming:wheat_8"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
task = npc.actions.cmd.WALK_TO_POS,
|
||||
args = {
|
||||
end_pos = npc.places.PLACE_TYPE.SCHEDULE.TARGET,
|
||||
walkable = farming_plants
|
||||
}
|
||||
},
|
||||
[2] =
|
||||
{
|
||||
action = npc.actions.cmd.DIG,
|
||||
args = {
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[3] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
},
|
||||
[4] =
|
||||
{
|
||||
action = npc.actions.cmd.PLACE,
|
||||
args =
|
||||
{
|
||||
node = "farming:wheat_1",
|
||||
bypass_protection = true
|
||||
}
|
||||
},
|
||||
[5] =
|
||||
{
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
}
|
||||
}
|
||||
},
|
||||
none_actions =
|
||||
{
|
||||
-- Walk a single step in a random direction
|
||||
[1] = {
|
||||
action = npc.actions.cmd.WALK_STEP,
|
||||
args =
|
||||
{
|
||||
dir = "random_orthogonal"
|
||||
}
|
||||
},
|
||||
[2] = {
|
||||
action = npc.actions.cmd.STAND,
|
||||
args = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[13] = {
|
||||
-- Walk to a sittable node
|
||||
[1] = {
|
||||
task = npc.actions.cmd.WALK_TO_POS, args = {
|
||||
end_pos = {place_type=npc.places.PLACE_TYPE.SITTABLE.PRIMARY, use_access_node=true},
|
||||
walkable = {"cottages:bench"}
|
||||
},
|
||||
chance = 75
|
||||
},
|
||||
-- Sit on the node
|
||||
[2] = {
|
||||
task = npc.actions.cmd.USE_SITTABLE, args = {
|
||||
pos = npc.places.PLACE_TYPE.SITTABLE.PRIMARY,
|
||||
action = npc.actions.const.sittable.SIT
|
||||
},
|
||||
depends = {1}
|
||||
},
|
||||
-- Stay put into place
|
||||
[3] = {
|
||||
action = npc.actions.cmd.SET_INTERVAL, args = {
|
||||
freeze = true,
|
||||
interval = 35
|
||||
},
|
||||
depends = {2}
|
||||
},
|
||||
[4] = {
|
||||
action = npc.actions.cmd.SET_INTERVAL, args = {
|
||||
freeze = true,
|
||||
interval = npc.actions.default_interval
|
||||
},
|
||||
depends = {3}
|
||||
},
|
||||
-- Get up from sit
|
||||
[5] = {
|
||||
action = npc.actions.cmd.USE_SITTABLE, args = {
|
||||
pos = npc.places.PLACE_TYPE.SITTABLE.PRIMARY,
|
||||
action = npc.actions.const.sittable.GET_UP
|
||||
},
|
||||
depends = {4}
|
||||
}
|
||||
},
|
||||
[14] = {
|
||||
-- Give NPC money to buy from player
|
||||
[1] = {
|
||||
property = npc.schedule_properties.put_multiple_items, args = {
|
||||
itemlist = {
|
||||
{name="default:iron_lump", random=true, min=2, max=4}
|
||||
}
|
||||
},
|
||||
chance = 50
|
||||
},
|
||||
-- Set trade list - what NPC will buy and what NPC will sell
|
||||
[2] = {
|
||||
property = npc.schedule_properties.set_trade_list, args = {
|
||||
items = {
|
||||
[1] = {name="farming:seed_cotton", sell=5, keep=5},
|
||||
[2] = {name="farming:cotton", sell=10},
|
||||
[3] = {name="bucket:bucket_empty", buy=2},
|
||||
[4] = {name="farming:hoe_stone", buy=2}
|
||||
}
|
||||
}
|
||||
},
|
||||
-- Change trader status to "trader"
|
||||
[3] = {
|
||||
property = npc.schedule_properties.trader_status, args = {
|
||||
status = npc.trade.TRADER
|
||||
},
|
||||
chance = 90
|
||||
},
|
||||
[4] = {
|
||||
property = npc.schedule_properties.can_receive_gifts, args = {
|
||||
can_receive_gifts = false
|
||||
},
|
||||
depends = {1}
|
||||
},
|
||||
-- Allow mobs_redo wandering
|
||||
[5] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}}
|
||||
},
|
||||
-- Schedule entry for 6 in the evening
|
||||
[18] = {
|
||||
-- Change trader status to "none"
|
||||
[1] = {
|
||||
property = npc.schedule_properties.trader_status, args = {
|
||||
status = npc.trade.NONE
|
||||
}
|
||||
},
|
||||
-- Enable gift receiving again
|
||||
[2] = {
|
||||
property = npc.schedule_properties.can_receive_gifts, args = {
|
||||
can_receive_gifts = true
|
||||
}
|
||||
},
|
||||
-- Get inside home
|
||||
[3] = {
|
||||
task = npc.actions.cmd.WALK_TO_POS, args = {
|
||||
end_pos = npc.places.PLACE_TYPE.BED.PRIMARY,
|
||||
walkable = {}
|
||||
}
|
||||
},
|
||||
-- Allow mobs_redo wandering
|
||||
[4] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}}
|
||||
},
|
||||
[22] = {
|
||||
[1] = {
|
||||
task = npc.actions.cmd.WALK_TO_POS, args = {
|
||||
end_pos = {place_type=npc.places.PLACE_TYPE.BED.PRIMARY, use_access_node=true},
|
||||
walkable = {}
|
||||
}
|
||||
},
|
||||
-- Use bed
|
||||
[2] = {
|
||||
task = npc.actions.cmd.USE_BED, args = {
|
||||
pos = npc.places.PLACE_TYPE.BED.PRIMARY,
|
||||
action = npc.actions.const.beds.LAY
|
||||
}
|
||||
},
|
||||
-- Stay put on bed
|
||||
[3] = {action = npc.actions.cmd.FREEZE, args = {freeze = true} }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-- Register occupation
|
||||
npc.occupations.register_occupation("default_farmer", farmer_def)
|
51
data/occupations/default_miner.lua
Normal file
@ -0,0 +1,51 @@
|
||||
-- WIP miner by NewbProgrammer101 or roboto
|
||||
|
||||
local miner_def = {
|
||||
dialogues = {},
|
||||
textures = {"miner.png"},
|
||||
initial_inventory = {
|
||||
{name="default:pick_steel", chance=1},
|
||||
{name="default:shovel_bronze", chance=1}
|
||||
},
|
||||
schedule_entries = {
|
||||
[7] = {
|
||||
[1] = {
|
||||
task = npc.actions.cmd.WALK_TO_POS,
|
||||
args = {
|
||||
end_pos = npc.places.PLACE_TYPE.OTHER.HOME_OUTSIDE,
|
||||
walkable = {}
|
||||
}
|
||||
},
|
||||
[2] = {
|
||||
check = true,
|
||||
range = 3,
|
||||
random_execution_times = true,
|
||||
min_count = 20,
|
||||
max_count = 99,
|
||||
nodes = {"default:dirt", "default:dirt_with_grass", "default:sand", "default:desert_sand", "default:silver_sand", "default:gravel", "default:clay", "default:snow", "default:snowblock"},
|
||||
actions = {
|
||||
["default:dirt"] = {
|
||||
[1] = {
|
||||
action = npc.actions.cmd.WALK_STEP
|
||||
},
|
||||
[2] = {
|
||||
action = npc.actions.cmd.DIG
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
none_actions = {
|
||||
[1] = {
|
||||
action = npc.actions.cmd.WALK_STEP,
|
||||
args = {
|
||||
dir = "random"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-- Occupation registration
|
||||
npc.occupations.register_occupation("default_miner", miner_def)
|
215
data/occupations/default_priest.lua
Normal file
@ -0,0 +1,215 @@
|
||||
----------------------------------------------------
|
||||
-- Basic priest occupation for Advanced NPC (WIP)
|
||||
-- By Zorman2000
|
||||
----------------------------------------------------
|
||||
-- The basic priest occupation is given to NPCs that spawn on houses
|
||||
-- surrounding churchs. While on the church, the priest provides
|
||||
-- universal wisdom and advice, and also heals the player a limited number of times.
|
||||
-- DISCLAIMER: The "teachings" in this file come from a compilation of 15 principles shared
|
||||
-- among religions around the world. Zorman2000 and other contributors are not
|
||||
-- necessarily aligned with the principles and morals in these teachings, nor affiliated
|
||||
-- to religions that promote them.
|
||||
|
||||
local priest_def = {
|
||||
dialogues = {
|
||||
type = "given",
|
||||
max_count = 5,
|
||||
data = {
|
||||
{
|
||||
text = "Blessings be upon you, my child!",
|
||||
tags = {"unisex"}
|
||||
},
|
||||
{
|
||||
text = "The temple will always open the doors to everyone.",
|
||||
flag = {name="on_church", value=true},
|
||||
tags = {"unisex"}
|
||||
},
|
||||
{
|
||||
text = "Following the teachings is the path to a good life.",
|
||||
tags = {"unisex"}
|
||||
},
|
||||
{
|
||||
text = "Thanks for coming to greet me, I hope you have a blessed day! ",
|
||||
flag = {name="on_church", value=false},
|
||||
tags = {"unisex"}
|
||||
},
|
||||
{
|
||||
text = "Welcome to the temple, how can I help you today?",
|
||||
flag = {name="on_church", value=true},
|
||||
tags = {"unisex"},
|
||||
responses =
|
||||
{
|
||||
[1] = {
|
||||
text = "I'm injured. Can you heal me?",
|
||||
action_type = "function",
|
||||
action = function(self, player)
|
||||
local heal_count = self.flags["heal_count"]
|
||||
if heal_count then
|
||||
-- Increase heal count
|
||||
self.flags["heal_count"] = self.flags["heal_count"] + 1
|
||||
else
|
||||
self.flags["heal_count"] = 1
|
||||
heal_count = 1
|
||||
end
|
||||
-- Check if heal count is achieved
|
||||
if heal_count > 5 then
|
||||
npc.chat(self.npc_name, player:get_player_name(), "I cannot heal you anymore, "
|
||||
.."my child.\nTo mortals like you and me, the power of the Creator is\n"
|
||||
.." limited. Only though learning the teachings we are able to understand more"
|
||||
.."...\nBe safe my child.")
|
||||
else
|
||||
npc.chat(self.npc_name, player:get_player_name(),
|
||||
"Receive the blessings of the Creator!")
|
||||
npc.effect(self.object:getpos(), 20, "default_coral_skeleton.png", 0.1, 0.3, 3, 10)
|
||||
-- Heal one heart
|
||||
player:set_hp(player:get_hp() + 2)
|
||||
end
|
||||
end
|
||||
},
|
||||
[2] = {
|
||||
text = "What are your teachings?",
|
||||
action_type = "function",
|
||||
action = function(self, player)
|
||||
local teachings = {
|
||||
[1] = "Do unto others what you would have them do unto you",
|
||||
[2] = "Honor your Father and Mother. Knowing them is the key to knowing ourselves",
|
||||
[3] = "Sincerity is the way to heaven,\nand to think how to be sincere is the way of the man",
|
||||
[4] = "Generosity, charity and kindness will open an individual to an unbounded reservoir of riches",
|
||||
[5] = "Even as the scent dwells within the flower, so God within thine own heart forever abides",
|
||||
[6] = "Acts of faith, prayer and meditation provide us with the strength that allows love for our fellow man to become an abiding force. Love is unifying.",
|
||||
[7] = "Peacemakers are blessed.\nPeace is the natural result of individuals and nations living in close kinship",
|
||||
[8] = "You reap what you sow.\nEven if it is a mystery, we are all ruled by this inevitable law of nature",
|
||||
[9] = "The blessings of life are deeper than what can be appreciated by the senses",
|
||||
[10] = "Do no harm, as we are part of the whole, and shouldn't perceive others as foreign or separate from ownself",
|
||||
[11] = "The most beautiful thing a man can do is to forgive wrong",
|
||||
[12] = "Judge not, lest ye be judged. Mankind is nothing but a great family and we all spring from common source",
|
||||
[13] = "Anger clouds the mind in the very moments that clarity and objectivity are needed most.",
|
||||
[14] = "Nature, Being, The Absolute, Creator... whatever name man chooses, there is but one force in the universe. All people and things are of one essence",
|
||||
[15] = "Study the words, no doubt, but look behind them to the thought they indicate;\nhaving fond it, throw the words away. Live the spirit of them",
|
||||
[16] = "The wise store up choice food and olive oil, \nbut fools gulp theirs down.",
|
||||
[17] = "An inheritance claimed too soon \nwill not be blessed at the end.",
|
||||
[18] = "Young men give glory in their strength, \nbut old men are honored for their gray hair.",
|
||||
[19] = "Humility is the fear of the Creator, or whatever name man chooses; \nits wages are riches and honor in life.",
|
||||
[20] = "Listen, my child, and be wise, \nand set your heart on the right path.",
|
||||
[21] = "Do not speak to fools, \nfor they will scorn your prudent words.",
|
||||
[22] = "The schemes of folly are sin, \nand people detest a mocker.",
|
||||
[23] = "An honest answer is like a kiss on the lips.",
|
||||
[24] = "Do not envy the wicked, \ndo not desire their company; \nfor their hearts plot violence, \nand their lips talk about making trouble.",
|
||||
[25] = "Do not fret because of evildoers, for the evildoer has no future hope.",
|
||||
[26] = "It is to one's honor to avoid strife, \nbut every fool is quick to quarrel",
|
||||
[27] = "Kill reverence, and you've killed the hero in man.",
|
||||
[28] = "Kill man's sense of value, kill his capacity to recognize greatness or to achieve it, \nand you've killed his will to live.",
|
||||
[29] = "The true hater of man, expects nothing from him and is indiscriminate to his works.",
|
||||
[30] = "Love is a tool for capturing the souls of men. Pretend to love, and he will accept you. \nLove is reverence, and worship, and glory, and the upward glance. Not a bandage for dirty sores. \nBut he doesn’t know it. Those who speak of love most promiscuously are the ones who’ve never felt it. \nThey make some sort of feeble stew out of sympathy, compassion, contempt and general indifference, and they call it love. \nOnce you’ve felt what it means to love, the total passion for the total height—you’re incapable of anything less.",
|
||||
[31] = "If you learn how to rule one single man’s soul, you can get the rest of mankind. \nIt’s the soul, not whips or swords or fire or guns. \nThat’s why the Caesars, the Attilas, the Napoleons were fools and did not last. \nThe soul, is that which can’t be ruled. It must be broken. \nDrive a wedge in, get your fingers on it—and the man is yours.",
|
||||
[32] = "Great and wise men can’t be ruled."
|
||||
}
|
||||
npc.chat(self.npc_name, player:get_player_name(), teachings[math.random(1, #teachings)]
|
||||
..". \nThese are the teachings of our Creator.")
|
||||
end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
textures = {
|
||||
"npc_male_priest.png"
|
||||
},
|
||||
initial_inventory = {
|
||||
{name="farming:bread", count=1}
|
||||
},
|
||||
properties = {
|
||||
initial_trader_status = npc.trade.NONE,
|
||||
enable_gift_items_hints = false,
|
||||
can_receive_gifts = false
|
||||
},
|
||||
building_types = {},
|
||||
surrounding_building_types = {
|
||||
{type="church", origin_building_types={"hut", "house", "farm_tiny", "lumberjack"}}
|
||||
},
|
||||
schedules_entries = {
|
||||
[7] = {
|
||||
-- Get out of bed
|
||||
[1] = {
|
||||
task = npc.actions.cmd.USE_BED,
|
||||
args = {
|
||||
pos = npc.places.PLACE_TYPE.BED.PRIMARY,
|
||||
action = npc.actions.const.beds.GET_UP
|
||||
}
|
||||
},
|
||||
-- Walk to home inside
|
||||
[2] = {
|
||||
task = npc.actions.cmd.WALK_TO_POS,
|
||||
chance = 95,
|
||||
args = {
|
||||
end_pos = npc.places.PLACE_TYPE.OTHER.HOME_INSIDE,
|
||||
walkable = {}
|
||||
}
|
||||
},
|
||||
-- Allow mobs_redo wandering
|
||||
[3] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}}
|
||||
},
|
||||
[8] = {
|
||||
-- Walk to workplace
|
||||
[1] =
|
||||
{
|
||||
task = npc.actions.cmd.WALK_TO_POS,
|
||||
args = {
|
||||
end_pos = npc.places.PLACE_TYPE.WORKPLACE.PRIMARY,
|
||||
walkable = {},
|
||||
use_access_node = true
|
||||
}
|
||||
},
|
||||
[2] =
|
||||
{
|
||||
property = npc.schedule_properties.flag,
|
||||
args = {
|
||||
action = "set",
|
||||
flag_name = "on_church",
|
||||
flag_value = true
|
||||
}
|
||||
}
|
||||
},
|
||||
[17] = {
|
||||
[1] =
|
||||
{
|
||||
property = npc.schedule_properties.flag,
|
||||
args = {
|
||||
action = "set",
|
||||
flag_name = "on_church",
|
||||
flag_value = false
|
||||
}
|
||||
},
|
||||
[2] =
|
||||
{
|
||||
task = npc.actions.cmd.WALK_TO_POS,
|
||||
args = {
|
||||
end_pos = npc.places.PLACE_TYPE.OTHER.HOME_INSIDE,
|
||||
walkable = {}
|
||||
}
|
||||
}
|
||||
},
|
||||
[21] = {
|
||||
[1] = {
|
||||
task = npc.actions.cmd.WALK_TO_POS,
|
||||
args = {
|
||||
end_pos = {place_type=npc.places.PLACE_TYPE.BED.PRIMARY, use_access_node=true},
|
||||
walkable = {}
|
||||
}
|
||||
},
|
||||
-- Use bed
|
||||
[2] = {
|
||||
task = npc.actions.cmd.USE_BED,
|
||||
args = {
|
||||
pos = npc.places.PLACE_TYPE.BED.PRIMARY,
|
||||
action = npc.actions.const.beds.LAY
|
||||
}
|
||||
},
|
||||
-- Stay put on bed
|
||||
[3] = {action = npc.actions.cmd.FREEZE, args = {freeze = true}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-- Register occupation
|
||||
npc.occupations.register_occupation("default_priest", priest_def)
|
736
dialogue.lua
@ -1,20 +1,6 @@
|
||||
-------------------------------------------------------------------------------------
|
||||
-- NPC dialogue code by Zorman2000
|
||||
-- Dialogue definitions:
|
||||
-- TODO: Complete
|
||||
-- {
|
||||
-- text: "",
|
||||
-- ^ The "spoken" dialogue line
|
||||
-- flag:
|
||||
-- ^ If the flag with the specified name has the specified value
|
||||
-- then this dialogue is valid
|
||||
-- {
|
||||
-- name: ""
|
||||
-- ^ Name of the flag
|
||||
-- value:
|
||||
-- ^ Expected value of the flag. A flag can be a function. In such a case, it is
|
||||
-- expected the function will return this value.
|
||||
-- }
|
||||
-- }
|
||||
-------------------------------------------------------------------------------------
|
||||
|
||||
npc.dialogue = {}
|
||||
|
||||
@ -25,10 +11,10 @@ npc.dialogue.MIN_DIALOGUES = 2
|
||||
npc.dialogue.MAX_DIALOGUES = 4
|
||||
|
||||
npc.dialogue.dialogue_type = {
|
||||
married = 1,
|
||||
casual_trade = 2,
|
||||
dedicated_trade = 3,
|
||||
custom_trade = 4
|
||||
married = 1,
|
||||
casual_trade = 2,
|
||||
dedicated_trade = 3,
|
||||
custom_trade = 4
|
||||
}
|
||||
|
||||
-- This table contains the answers of dialogue boxes
|
||||
@ -37,19 +23,220 @@ npc.dialogue.dialogue_results = {
|
||||
yes_no_dialogue = {}
|
||||
}
|
||||
|
||||
---------------------------------------------------------------------------------------
|
||||
npc.dialogue.tags = {
|
||||
UNISEX = "unisex",
|
||||
MALE = "male",
|
||||
FEMALE = "female",
|
||||
-- Relationship based tags - these are one-to-one with the
|
||||
-- phase names.
|
||||
DEFAULT_MARRIED_DIALOGUE = "default_married_dialogue",
|
||||
PHASE_1 = "phase1",
|
||||
PHASE_2 = "phase2",
|
||||
PHASE_3 = "phase3",
|
||||
PHASE_4 = "phase4",
|
||||
PHASE_5 = "phase5",
|
||||
GIFT_ITEM_HINT = "gift_item_hint",
|
||||
GIFT_ITEM_RESPONSE = "gift_item_response",
|
||||
GIFT_ITEM_LIKED = "gift_item_liked",
|
||||
GIFT_ITEM_UNLIKED = "gift_item_unliked",
|
||||
-- Trade-related tags
|
||||
DEFAULT_CASUAL_TRADE = "default_casual_trade_dialogue",
|
||||
DEFAULT_DEDICATED_TRADE = "default_dedicated_trade_dialogue",
|
||||
DEFAULT_BUY_OFFER = "buy_offer",
|
||||
DEFAULT_SELL_OFFER = "sell_offer",
|
||||
-- Occupation-based tags - these are one-to-one with the
|
||||
-- default occupation names
|
||||
BASIC = "basic", -- Dialogues related to the basic occupation should
|
||||
-- use this. As basic occupation is generic, any occupation
|
||||
-- should be able to use these dialogues.
|
||||
DEFAULT_FARMER = "default_farmer",
|
||||
DEFAULT_COOKER = "default_cooker"
|
||||
}
|
||||
|
||||
-- This table will contain all the registered dialogues for NPCs
|
||||
npc.dialogue.registered_dialogues = {}
|
||||
|
||||
npc.dialogue.cache_keys = {
|
||||
CASUAL_BUY_DIALOGUE = {key="CASUAL_BUY_DIALOGUE", tags={npc.dialogue.tags.DEFAULT_CASUAL_TRADE, npc.dialogue.tags.DEFAULT_BUY_OFFER}},
|
||||
CASUAL_SELL_DIALOGUE = {key="CASUAL_SELL_DIALOGUE", tags={npc.dialogue.tags.DEFAULT_CASUAL_TRADE, npc.dialogue.tags.DEFAULT_SELL_OFFER}},
|
||||
DEDICATED_TRADER_DIALOGUE = {key="DEDICATED_TRADER_DIALOGUE", tags={npc.dialogue.tags.DEFAULT_DEDICATED_TRADE}},
|
||||
MARRIED_DIALOGUE = {key="MARRIED_DIALOGUE", tags={npc.dialogue.tags.DEFAULT_MARRIED_DIALOGUE}},
|
||||
}
|
||||
|
||||
npc.dialogue.cache = {}
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
-- Dialogue registration functions
|
||||
-- All dialogues will be registered by providing a definition.
|
||||
-- A unique key will be assigned to them. The dialogue definition is the following:
|
||||
-- {
|
||||
-- text: "",
|
||||
-- ^ The "spoken" dialogue line
|
||||
-- flag:
|
||||
-- ^ If the flag with the specified name has the specified value
|
||||
-- then this dialogue is valid
|
||||
-- {
|
||||
-- name: ""
|
||||
-- ^ Name of the flag
|
||||
-- value:
|
||||
-- ^ Expected value of the flag. A flag can be a function. In such a case, it is
|
||||
-- expected the function will return this value.
|
||||
-- },
|
||||
-- tags = {
|
||||
-- -- Tags are an array of string that allow to classify dialogues
|
||||
-- -- A dialogue can have as many tags as desired and can take any form.
|
||||
-- -- However, for consistency, some predefined tags can be found at
|
||||
-- -- npc.dialogue.tags.
|
||||
-- -- Example:
|
||||
-- "phase1",
|
||||
-- "any"
|
||||
-- }
|
||||
-- responses = {
|
||||
-- -- Array of responses the player can choose. A response can be of
|
||||
-- -- two types: as [1] or as [2] (see example below)
|
||||
-- [1] = {
|
||||
-- text = "Yes",
|
||||
-- -- Text displayed to the player
|
||||
-- action_type = "dialogue",
|
||||
-- -- Type of action that happens when the player chooses this response.
|
||||
-- -- can be "dialogue" or "function". This example shows "dialogue"
|
||||
-- action = {
|
||||
-- text = "It's so beautiful, and big, and large, and infinite, and..."
|
||||
-- },
|
||||
-- },
|
||||
-- -- A table containing a dialogue. This means you can include not only
|
||||
-- -- text but also flag and responses as well. Dialogues are recursive.
|
||||
-- [2] = {
|
||||
-- text = "No",
|
||||
-- action_type = "function",
|
||||
-- action = function(self, player)
|
||||
-- -- A function will have access to self, which is the NPC
|
||||
-- -- and the player, which is the player ObjectRef. You can
|
||||
-- -- pretty much do anything here. The example here is very simple,
|
||||
-- -- just sending a chat message. But you can add items to players
|
||||
-- -- or to NPCs and so on.
|
||||
-- minetest.chat_send_player(player:get_player_name(), "Oh, ok...")
|
||||
-- end,
|
||||
-- },
|
||||
-- }
|
||||
-- }
|
||||
--------------------------------------------------------------------------------------
|
||||
-- This function sets a unique response ID (made of <depth>:<response index>) to
|
||||
-- each response that features a function. This is to be able to locate the
|
||||
-- function easily later
|
||||
local function set_response_ids_recursively(dialogue, depth, dialogue_id)
|
||||
-- Base case: dialogue object with no responses and no responses below it
|
||||
if dialogue.responses == nil
|
||||
and (dialogue.action_type == "dialogue" and dialogue.action.responses == nil) then
|
||||
return
|
||||
elseif dialogue.responses ~= nil then
|
||||
-- Assign a response ID to each response
|
||||
local response_id_prefix = tostring(depth)..":"
|
||||
for key,value in ipairs(dialogue.responses) do
|
||||
if value.action_type == "function" then
|
||||
value.response_id = response_id_prefix..key
|
||||
value.dialogue_id = dialogue_id
|
||||
else
|
||||
-- We have a dialogue action type. Need to check if dialogue has further responses
|
||||
if value.action.responses ~= nil then
|
||||
set_response_ids_recursively(value.action, depth + 1, dialogue_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- The register dialogue function will just receive the definition as
|
||||
-- explained above. The unique key will be the index it gets into the
|
||||
-- array when inserted.
|
||||
function npc.dialogue.register_dialogue(def)
|
||||
-- If def has not tags then apply the default ones
|
||||
if not def.tags then
|
||||
def.tags = {npc.dialogue.tags.UNISEX, npc.dialogue.tags.PHASE_1}
|
||||
end
|
||||
|
||||
local dialogue_id = table.getn(npc.dialogue.registered_dialogues) + 1
|
||||
-- Set the response IDs - required for dialogue objects that
|
||||
-- form trees of dialogues
|
||||
set_response_ids_recursively(def, 0, dialogue_id)
|
||||
|
||||
def.key = dialogue_id
|
||||
|
||||
-- Insert dialogue into table
|
||||
table.insert(npc.dialogue.registered_dialogues, def)
|
||||
return dialogue_id
|
||||
end
|
||||
|
||||
-- This function returns a table of dialogues that meet the given
|
||||
-- tags array. The keys in the table are the keys in
|
||||
-- npc.dialogue.registered_dialogues, therefore you can use them to
|
||||
--retrieve specific dialogues. However, it should be stored by the NPC.
|
||||
function npc.dialogue.search_dialogue_by_tags(tags, find_all)
|
||||
--minetest.log("Tags being searched: "..dump(tags))
|
||||
local result = {}
|
||||
for key, def in pairs(npc.dialogue.registered_dialogues) do
|
||||
-- Check if def.tags have any of the provided tags
|
||||
local tags_found = 0
|
||||
--minetest.log("Tags on dialogue def: "..dump(def.tags))
|
||||
for i = 1, #tags do
|
||||
if npc.utils.array_contains(def.tags, tags[i]) then
|
||||
tags_found = tags_found + 1
|
||||
end
|
||||
end
|
||||
--minetest.log("Tags found: "..dump(tags_found))
|
||||
-- Check if we found all tags
|
||||
if find_all then
|
||||
if tags_found == #tags then
|
||||
-- Add result
|
||||
result[key] = def
|
||||
end
|
||||
elseif not find_all then
|
||||
if tags_found == #tags or tags_found == #def.tags then
|
||||
-- Add result
|
||||
result[key] = def
|
||||
end
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function npc.dialogue.get_cached_dialogue_key(_cache_key, tags)
|
||||
local cache_key = _cache_key
|
||||
if type(_cache_key) == "table" then
|
||||
cache_key = _cache_key.key
|
||||
tags = _cache_key.tags
|
||||
end
|
||||
|
||||
local key = npc.dialogue.cache[cache_key]
|
||||
-- Check if key isn't cached
|
||||
if not key then
|
||||
-- Search for the dialogue
|
||||
local dialogues = npc.dialogue.search_dialogue_by_tags(tags, true)
|
||||
key = npc.utils.get_map_keys(dialogues)[1]
|
||||
-- Populate cache
|
||||
npc.dialogue.cache[cache_key] = key
|
||||
-- Return key
|
||||
return key
|
||||
else
|
||||
-- Return the cached key
|
||||
return key
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
-- Dialogue box definitions
|
||||
-- The dialogue boxes are used for the player to interact with the
|
||||
-- NPC in dialogues.
|
||||
---------------------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------------------------
|
||||
-- Creates and shows a multi-option dialogue based on the number of responses
|
||||
-- that the dialogue object contains
|
||||
function npc.dialogue.show_options_dialogue(self,
|
||||
dialogue,
|
||||
dismiss_option_label,
|
||||
player_name)
|
||||
local responses = dialogue.responses
|
||||
local options_length = table.getn(responses) + 1
|
||||
function npc.dialogue.show_options_dialogue(self,
|
||||
dialogue_key,
|
||||
dialogue,
|
||||
dismiss_option_label,
|
||||
player_name)
|
||||
local responses = dialogue.responses
|
||||
local options_length = table.getn(responses) + 1
|
||||
local formspec_height = (options_length * 0.7) + 0.4
|
||||
local formspec = "size[7,"..tostring(formspec_height).."]"
|
||||
|
||||
@ -59,18 +246,20 @@ function npc.dialogue.show_options_dialogue(self,
|
||||
y = (0.75 * i)
|
||||
end
|
||||
formspec = formspec.."button_exit[0.5,"
|
||||
..(y - 0.5)..";6,0.5;opt"..tostring(i)..";"..responses[i].text.."]"
|
||||
..(y - 0.5)..";6,0.5;opt"..tostring(i)..";"..responses[i].text.."]"
|
||||
end
|
||||
formspec = formspec.."button_exit[0.5,"
|
||||
..(formspec_height - 0.7)..";6,0.5;exit;"..dismiss_option_label.."]"
|
||||
..(formspec_height - 0.7)..";6,0.5;exit;"..dismiss_option_label.."]"
|
||||
|
||||
-- Create entry on options_dialogue table
|
||||
npc.dialogue.dialogue_results.options_dialogue[player_name] = {
|
||||
npc = self,
|
||||
is_married_dialogue = (dialogue.dialogue_type == npc.dialogue.dialogue_type.married),
|
||||
is_casual_trade_dialogue = (dialogue.dialogue_type == npc.dialogue.dialogue_type.casual_trade),
|
||||
is_dedicated_trade_dialogue = (dialogue.dialogue_type == npc.dialogue.dialogue_type.dedicated_trade),
|
||||
is_custom_trade_dialogue = (dialogue.dialogue_type == npc.dialogue.dialogue_type.custom_trade),
|
||||
dialogue = dialogue,
|
||||
dialogue_key = dialogue_key,
|
||||
is_married_dialogue =
|
||||
(dialogue.dialogue_type == npc.dialogue.dialogue_type.married),
|
||||
is_custom_trade_dialogue =
|
||||
(dialogue.dialogue_type == npc.dialogue.dialogue_type.custom_trade),
|
||||
casual_trade_type = dialogue.casual_trade_type,
|
||||
options = responses
|
||||
}
|
||||
@ -80,23 +269,23 @@ end
|
||||
|
||||
-- This function is used for showing a yes/no dialogue formspec
|
||||
function npc.dialogue.show_yes_no_dialogue(self,
|
||||
prompt,
|
||||
positive_answer_label,
|
||||
positive_callback,
|
||||
negative_answer_label,
|
||||
negative_callback,
|
||||
player_name)
|
||||
prompt,
|
||||
positive_answer_label,
|
||||
positive_callback,
|
||||
negative_answer_label,
|
||||
negative_callback,
|
||||
player_name)
|
||||
|
||||
npc.lock_actions(self)
|
||||
npc.lock_actions(self)
|
||||
|
||||
local formspec = "size[7,3]"..
|
||||
"label[0.5,0.1;"..prompt.."]"..
|
||||
"button_exit[0.5,1.15;6,0.5;yes_option;"..positive_answer_label.."]"..
|
||||
"button_exit[0.5,1.95;6,0.5;no_option;"..negative_answer_label.."]"
|
||||
"label[0.5,0.1;"..prompt.."]"..
|
||||
"button_exit[0.5,1.15;6,0.5;yes_option;"..positive_answer_label.."]"..
|
||||
"button_exit[0.5,1.95;6,0.5;no_option;"..negative_answer_label.."]"
|
||||
|
||||
-- Create entry into responses table
|
||||
npc.dialogue.dialogue_results.yes_no_dialogue[player_name] = {
|
||||
npc = self,
|
||||
npc = self,
|
||||
yes_callback = positive_callback,
|
||||
no_callback = negative_callback
|
||||
}
|
||||
@ -104,102 +293,97 @@ function npc.dialogue.show_yes_no_dialogue(self,
|
||||
minetest.show_formspec(player_name, "advanced_npc:yes_no", formspec)
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------------------------
|
||||
-- Dialogue methods
|
||||
---------------------------------------------------------------------------------------
|
||||
-- This function sets a unique response ID (made of <depth>:<response index>) to
|
||||
-- each response that features a function. This is to be able to locate the
|
||||
-- function easily later
|
||||
local function set_response_ids_recursively(dialogue, depth, dialogue_id)
|
||||
-- Base case: dialogue object with no responses and no r,esponses below it
|
||||
if dialogue.responses == nil
|
||||
and (dialogue.action_type == "dialogue" and dialogue.action.responses == nil) then
|
||||
return
|
||||
elseif dialogue.responses ~= nil then
|
||||
-- Assign a response ID to each response
|
||||
local response_id_prefix = tostring(depth)..":"
|
||||
for key,value in ipairs(dialogue.responses) do
|
||||
if value.action_type == "function" then
|
||||
value.response_id = response_id_prefix..key
|
||||
value.dialogue_id = dialogue_id
|
||||
else
|
||||
-- We have a dialogue action type. Need to check if dialogue has further responses
|
||||
if value.action.responses ~= nil then
|
||||
set_response_ids_recursively(value.action, depth + 1, dialogue_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
-- Select random dialogue objects for an NPC based on sex
|
||||
-- and the relationship phase with player
|
||||
function npc.dialogue.select_random_dialogues_for_npc(sex, phase, favorite_items, disliked_items)
|
||||
function npc.dialogue.select_random_dialogues_for_npc(self, phase)
|
||||
local result = {
|
||||
normal = {},
|
||||
hints = {}
|
||||
}
|
||||
|
||||
local dialogues = npc.data.DIALOGUES.female
|
||||
if sex == npc.MALE then
|
||||
dialogues = npc.data.DIALOGUES.male
|
||||
local phase_tag = "phase1"
|
||||
if phase then
|
||||
phase_tag = phase
|
||||
end
|
||||
dialogues = dialogues[phase]
|
||||
|
||||
local search_tags = {
|
||||
"unisex",
|
||||
self.sex,
|
||||
phase_tag,
|
||||
self.occupation
|
||||
}
|
||||
|
||||
local dialogues = npc.dialogue.search_dialogue_by_tags(search_tags)
|
||||
local keys = npc.utils.get_map_keys(dialogues)
|
||||
|
||||
-- Determine how many dialogue lines the NPC will have
|
||||
local number_of_dialogues = math.random(npc.dialogue.MIN_DIALOGUES, npc.dialogue.MAX_DIALOGUES)
|
||||
|
||||
for i = 1,number_of_dialogues do
|
||||
local dialogue_id = math.random(1, #dialogues)
|
||||
result.normal[i] = dialogues[dialogue_id]
|
||||
|
||||
set_response_ids_recursively(result.normal[i], 0, dialogue_id)
|
||||
for i = 1, number_of_dialogues do
|
||||
local key_id = math.random(1, #keys)
|
||||
result.normal[i] = keys[key_id]
|
||||
--npc.log("DEBUG", "Adding dialogue: "..dump(dialogues[keys[key_id]]))
|
||||
end
|
||||
|
||||
-- Add item hints.
|
||||
-- Favorite items
|
||||
for i = 1, 2 do
|
||||
result.hints[i] = {}
|
||||
result.hints[i].text =
|
||||
npc.relationships.get_hint_for_favorite_item(favorite_items["fav"..tostring(i)], sex, phase)
|
||||
local hints = npc.relationships.get_dialogues_for_gift_item(
|
||||
self.gift_data.favorite_items["fav"..tostring(i)],
|
||||
npc.dialogue.tags.GIFT_ITEM_HINT,
|
||||
npc.dialogue.tags.GIFT_ITEM_LIKED,
|
||||
self.sex,
|
||||
phase_tag)
|
||||
for key, value in pairs(hints) do
|
||||
result.hints[i] = key
|
||||
end
|
||||
end
|
||||
|
||||
-- Disliked items
|
||||
for i = 3, 4 do
|
||||
result.hints[i] = {}
|
||||
result.hints[i].text =
|
||||
npc.relationships.get_hint_for_disliked_item(disliked_items["dis"..tostring(i-2)], sex)
|
||||
local hints = npc.relationships.get_dialogues_for_gift_item(
|
||||
self.gift_data.disliked_items["dis"..tostring(i-2)],
|
||||
npc.dialogue.tags.GIFT_ITEM_HINT,
|
||||
npc.dialogue.tags.GIFT_ITEM_UNLIKED,
|
||||
self.sex)
|
||||
for key, value in pairs(hints) do
|
||||
result.hints[i] = key
|
||||
end
|
||||
end
|
||||
|
||||
--npc.log("DEBUG", "Dialogue results:"..dump(result))
|
||||
return result
|
||||
end
|
||||
|
||||
-- This function creates a multi-option dialogue from the custom trades that the
|
||||
-- NPC have.
|
||||
function npc.dialogue.create_custom_trade_options(self, player)
|
||||
-- Create the action for each option
|
||||
local actions = {}
|
||||
for i = 1, #self.trader_data.custom_trades do
|
||||
table.insert(actions, function() npc.trade.show_custom_trade_offer(self, player, self.trader_data.custom_trades[i]) end)
|
||||
end
|
||||
-- Default text to be shown for dialogue prompt
|
||||
local text = npc.trade.CUSTOM_TRADES_PROMPT_TEXT
|
||||
-- Get the options from each custom trade entry
|
||||
local options = {}
|
||||
if #self.trader_data.custom_trades == 1 then
|
||||
table.insert(options, self.trader_data.custom_trades[1].button_prompt)
|
||||
text = self.trader_data.custom_trades[1].option_prompt
|
||||
else
|
||||
for i = 1, #self.trader_data.custom_trades do
|
||||
table.insert(options, self.trader_data.custom_trades[i].button_prompt)
|
||||
end
|
||||
end
|
||||
-- Create dialogue object
|
||||
local dialogue = npc.dialogue.create_option_dialogue(text, options, actions)
|
||||
dialogue.dialogue_type = npc.dialogue.dialogue_type.custom_trade
|
||||
-- Create the action for each option
|
||||
local actions = {}
|
||||
for i = 1, #self.trader_data.custom_trades do
|
||||
table.insert(actions,
|
||||
function()
|
||||
npc.trade.show_custom_trade_offer(self, player, self.trader_data.custom_trades[i])
|
||||
end)
|
||||
end
|
||||
-- Default text to be shown for dialogue prompt
|
||||
local text = npc.trade.CUSTOM_TRADES_PROMPT_TEXT
|
||||
-- Get the options from each custom trade entry
|
||||
local options = {}
|
||||
if #self.trader_data.custom_trades == 1 then
|
||||
table.insert(options, self.trader_data.custom_trades[1].button_prompt)
|
||||
text = self.trader_data.custom_trades[1].option_prompt
|
||||
else
|
||||
for i = 1, #self.trader_data.custom_trades do
|
||||
table.insert(options, self.trader_data.custom_trades[i].button_prompt)
|
||||
end
|
||||
end
|
||||
-- Create dialogue object
|
||||
local dialogue = npc.dialogue.create_option_dialogue(text, options, actions)
|
||||
dialogue.dialogue_type = npc.dialogue.dialogue_type.custom_trade
|
||||
|
||||
return dialogue
|
||||
return dialogue
|
||||
end
|
||||
|
||||
-- This function will choose randomly a dialogue from the NPC data
|
||||
@ -210,127 +394,147 @@ function npc.dialogue.start_dialogue(self, player, show_married_dialogue)
|
||||
|
||||
-- Construct dialogue for marriage
|
||||
if npc.relationships.get_relationship_phase(self, player:get_player_name()) == "phase6"
|
||||
and show_married_dialogue == true then
|
||||
and show_married_dialogue == true then
|
||||
dialogue = npc.relationships.MARRIED_NPC_DIALOGUE
|
||||
npc.dialogue.process_dialogue(self, dialogue, player:get_player_name())
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Show options dialogue for dedicated trader
|
||||
if self.trader_data.trader_status == npc.trade.TRADER then
|
||||
dialogue = npc.trade.DEDICATED_TRADER_PROMPT
|
||||
npc.dialogue.process_dialogue(self, dialogue, player:get_player_name())
|
||||
return
|
||||
end
|
||||
-- Show options dialogue for dedicated trader
|
||||
if self.trader_data.trader_status == npc.trade.TRADER then
|
||||
dialogue = npc.dialogue.get_cached_dialogue_key(npc.dialogue.cache_keys.DEDICATED_TRADER_DIALOGUE)
|
||||
npc.dialogue.process_dialogue(self, dialogue, player:get_player_name())
|
||||
return
|
||||
end
|
||||
|
||||
local chance = math.random(1, 100)
|
||||
minetest.log("Chance: "..dump(chance))
|
||||
--minetest.log("Chance: "..dump(chance))
|
||||
if chance < 30 then
|
||||
-- If NPC is a casual trader, show a sell or buy dialogue 30% of the time, depending
|
||||
-- on the state of the casual trader.
|
||||
if self.trader_data.trader_status == npc.trade.NONE then
|
||||
-- Show custom trade options if available
|
||||
if table.getn(self.trader_data.custom_trades) > 0 then
|
||||
-- Show custom trade options
|
||||
dialogue = npc.dialogue.create_custom_trade_options(self, player)
|
||||
end
|
||||
elseif self.trader_data.trader_status == npc.trade.CASUAL then
|
||||
local max_trade_chance = 2
|
||||
if table.getn(self.trader_data.custom_trades) > 0 then
|
||||
max_trade_chance = 3
|
||||
end
|
||||
-- Show buy/sell with 50% chance each
|
||||
local trade_chance = math.random(1, max_trade_chance)
|
||||
if trade_chance == 1 then
|
||||
-- Show casual buy dialogue
|
||||
dialogue = npc.trade.CASUAL_TRADE_BUY_DIALOGUE
|
||||
elseif trade_chance == 2 then
|
||||
-- Show casual sell dialogue
|
||||
dialogue = npc.trade.CASUAL_TRADE_SELL_DIALOGUE
|
||||
elseif trade_chance == 3 then
|
||||
-- Show custom trade options
|
||||
dialogue = npc.dialogue.create_custom_trade_options(self, player)
|
||||
end
|
||||
end
|
||||
-- Show trading options for casual traders
|
||||
-- If NPC has custom trading options, these will be
|
||||
-- shown as well with equal chance as the casual
|
||||
-- buy/sell options
|
||||
if self.trader_data.trader_status == npc.trade.NONE then
|
||||
-- Show custom trade options if available
|
||||
if table.getn(self.trader_data.custom_trades) > 0 then
|
||||
-- Show custom trade options
|
||||
dialogue = npc.dialogue.create_custom_trade_options(self, player)
|
||||
else
|
||||
-- If not available, choose normal dialogue
|
||||
dialogue = self.dialogues.normal[math.random(1, #self.dialogues.normal)]
|
||||
end
|
||||
elseif self.trader_data.trader_status == npc.trade.CASUAL then
|
||||
local max_trade_chance = 2
|
||||
if table.getn(self.trader_data.custom_trades) > 0 then
|
||||
max_trade_chance = 3
|
||||
end
|
||||
-- Show buy/sell with 50% chance each
|
||||
local trade_chance = math.random(1, max_trade_chance)
|
||||
if trade_chance == 1 then
|
||||
-- Show casual buy dialogue
|
||||
dialogue = npc.dialogue.get_cached_dialogue_key(npc.dialogue.cache_keys.CASUAL_BUY_DIALOGUE)
|
||||
elseif trade_chance == 2 then
|
||||
-- Show casual sell dialogue
|
||||
dialogue = npc.dialogue.get_cached_dialogue_key(npc.dialogue.cache_keys.CASUAL_SELL_DIALOGUE)
|
||||
elseif trade_chance == 3 then
|
||||
-- Show custom trade options
|
||||
dialogue = npc.dialogue.create_custom_trade_options(self, player)
|
||||
end
|
||||
end
|
||||
elseif chance >= 30 and chance < 90 then
|
||||
-- Choose a random dialogue from the common ones
|
||||
-- Choose a random dialogue from the common ones
|
||||
dialogue = self.dialogues.normal[math.random(1, #self.dialogues.normal)]
|
||||
elseif chance >= 90 then
|
||||
-- Choose a random dialogue line from the favorite/disliked item hints
|
||||
dialogue = self.dialogues.hints[math.random(1, 4)]
|
||||
-- Check if gift items hints are enabled
|
||||
--minetest.log("Self gift data enable: "..dump(self.gift_data.enable_gift_items_hints))
|
||||
if self.gift_data.enable_gift_items_hints then
|
||||
-- Choose a random dialogue line from the favorite/disliked item hints
|
||||
dialogue = self.dialogues.hints[math.random(1, 4)]
|
||||
else
|
||||
-- Choose a random dialogue from the common ones
|
||||
dialogue = self.dialogues.normal[math.random(1, #self.dialogues.normal)]
|
||||
end
|
||||
end
|
||||
|
||||
local dialogue_result = npc.dialogue.process_dialogue(self, dialogue, player:get_player_name())
|
||||
if dialogue_result == false then
|
||||
-- Try to find another dialogue line
|
||||
npc.dialogue.start_dialogue(self, player, show_married_dialogue)
|
||||
end
|
||||
if dialogue_result == false then
|
||||
-- Try to find another dialogue line
|
||||
npc.dialogue.start_dialogue(self, player, show_married_dialogue)
|
||||
end
|
||||
end
|
||||
|
||||
-- This function processes a dialogue object and performs
|
||||
-- actions depending on what is defined in the object
|
||||
-- actions depending on what is defined in the object
|
||||
function npc.dialogue.process_dialogue(self, dialogue, player_name)
|
||||
-- Freeze NPC actions
|
||||
npc.lock_actions(self)
|
||||
|
||||
-- Freeze NPC actions
|
||||
npc.lock_actions(self)
|
||||
local dialogue_key = -1
|
||||
|
||||
-- Check if this dialogue has a flag definition
|
||||
if dialogue.flag then
|
||||
-- Check if the NPC has this flag
|
||||
local flag_value = npc.get_flag(self, dialogue.flag.name)
|
||||
if flag_value ~= nil then
|
||||
-- Check if value of the flag is equal to the expected value
|
||||
if flag_value ~= dialogue.flag.value then
|
||||
-- Do not process this dialogue
|
||||
return false
|
||||
end
|
||||
else
|
||||
|
||||
if (type(dialogue.flag.value) == "boolean" and dialogue.flag.value ~= false)
|
||||
or (type(dialogue.flag.value) == "number" and dialogue.flag.value > 0) then
|
||||
-- Do not process this dialogue
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
if type(dialogue) ~= "table" then
|
||||
dialogue_key = dialogue
|
||||
dialogue = npc.dialogue.registered_dialogues[dialogue]
|
||||
--minetest.log("Found dialogue: "..dump(dialogue))
|
||||
end
|
||||
|
||||
-- Check if this dialogue has a flag definition
|
||||
if dialogue.flag then
|
||||
-- Check if the NPC has this flag
|
||||
local flag_value = npc.get_flag(self, dialogue.flag.name)
|
||||
if flag_value ~= nil then
|
||||
-- Check if value of the flag is equal to the expected value
|
||||
if flag_value ~= dialogue.flag.value then
|
||||
-- Do not process this dialogue
|
||||
return false
|
||||
end
|
||||
else
|
||||
|
||||
if (type(dialogue.flag.value) == "boolean" and dialogue.flag.value ~= false)
|
||||
or (type(dialogue.flag.value) == "number" and dialogue.flag.value > 0) then
|
||||
-- Do not process this dialogue
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Send dialogue line
|
||||
if dialogue.text then
|
||||
npc.chat(self.npc_name, player_name, dialogue.text)
|
||||
end
|
||||
|
||||
-- Check if dialogue has responses. If it doesn't, unlock the actions
|
||||
-- queue and reset actions timer.'
|
||||
if not dialogue.responses then
|
||||
npc.unlock_actions(self)
|
||||
end
|
||||
-- Check if dialogue has responses. If it doesn't, unlock the actions
|
||||
-- queue and reset actions timer.'
|
||||
if not dialogue.responses then
|
||||
npc.unlock_actions(self)
|
||||
end
|
||||
|
||||
-- Check if there are responses, then show multi-option dialogue if there are
|
||||
if dialogue.responses then
|
||||
npc.dialogue.show_options_dialogue(
|
||||
self,
|
||||
dialogue_key,
|
||||
dialogue,
|
||||
npc.dialogue.NEGATIVE_ANSWER_LABEL,
|
||||
player_name
|
||||
)
|
||||
end
|
||||
|
||||
-- Dialogue object processed successfully
|
||||
return true
|
||||
-- Dialogue object processed successfully
|
||||
return true
|
||||
end
|
||||
|
||||
function npc.dialogue.create_option_dialogue(prompt, options, actions)
|
||||
local result = {}
|
||||
result.text = prompt
|
||||
result.responses = {}
|
||||
for i = 1, #options do
|
||||
table.insert(result.responses, {text = options[i], action_type="function", action=actions[i]})
|
||||
end
|
||||
return result
|
||||
local result = {}
|
||||
result.text = prompt
|
||||
result.responses = {}
|
||||
for i = 1, #options do
|
||||
table.insert(result.responses, {text = options[i], action_type="function", action=actions[i]})
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Functions for rotating NPC to look at player
|
||||
-- Functions for rotating NPC to look at player
|
||||
-- (taken from the mobs_redo API)
|
||||
-----------------------------------------------------------------------------
|
||||
local atan = function(x)
|
||||
@ -346,7 +550,7 @@ function npc.dialogue.rotate_npc_to_player(self)
|
||||
local objs = minetest.get_objects_inside_radius(s, 4)
|
||||
local lp = nil
|
||||
local yaw = 0
|
||||
|
||||
|
||||
for n = 1, #objs do
|
||||
if objs[n]:is_player() then
|
||||
lp = objs[n]:getpos()
|
||||
@ -374,38 +578,38 @@ end
|
||||
---------------------------------------------------------------------------------------
|
||||
-- This function locates a response object that has function on the dialogue tree.
|
||||
local function get_response_object_by_id_recursive(dialogue, current_depth, response_id)
|
||||
if dialogue.responses == nil
|
||||
and (dialogue.action_type == "dialogue" and dialoge.action.responses == nil) then
|
||||
return nil
|
||||
elseif dialogue.responses ~= nil then
|
||||
-- Get current depth and response ID
|
||||
local d_i1, d_i2 = string.find(response_id, ":")
|
||||
minetest.log("N1: "..dump(string.sub(response_id, 0, d_i1))..", N2: "..dump(string.sub(response_id, 1, d_i1-1)))
|
||||
local depth = tonumber(string.sub(response_id, 0, d_i1-1))
|
||||
local id = tonumber(string.sub(response_id, d_i2 + 1))
|
||||
minetest.log("Depth: "..dump(depth)..", id: "..dump(id))
|
||||
-- Check each response
|
||||
for key,value in ipairs(dialogue.responses) do
|
||||
minetest.log("Key: "..dump(key)..", value: "..dump(value)..", comp1: "..dump(current_depth == depth))
|
||||
if value.action_type == "function" then
|
||||
-- Check if we are on correct response and correct depth
|
||||
if current_depth == depth then
|
||||
if key == id then
|
||||
return value
|
||||
end
|
||||
end
|
||||
else
|
||||
minetest.log("Entering again...")
|
||||
-- We have a dialogue action type. Need to check if dialogue has further responses
|
||||
if value.action.responses ~= nil then
|
||||
local response = get_response_object_by_id_recursive(value.action, current_depth + 1, response_id)
|
||||
if response ~= nil then
|
||||
return response
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if dialogue.responses == nil
|
||||
and (dialogue.action_type == "dialogue" and dialoge.action.responses == nil) then
|
||||
return nil
|
||||
elseif dialogue.responses ~= nil then
|
||||
-- Get current depth and response ID
|
||||
local d_i1, d_i2 = string.find(response_id, ":")
|
||||
--minetest.log("N1: "..dump(string.sub(response_id, 0, d_i1))..", N2: "..dump(string.sub(response_id, 1, d_i1-1)))
|
||||
local depth = tonumber(string.sub(response_id, 0, d_i1-1))
|
||||
local id = tonumber(string.sub(response_id, d_i2 + 1))
|
||||
--minetest.log("Depth: "..dump(depth)..", id: "..dump(id))
|
||||
-- Check each response
|
||||
for key,value in ipairs(dialogue.responses) do
|
||||
--minetest.log("Key: "..dump(key)..", value: "..dump(value)..", comp1: "..dump(current_depth == depth))
|
||||
if value.action_type == "function" then
|
||||
-- Check if we are on correct response and correct depth
|
||||
if current_depth == depth then
|
||||
if key == id then
|
||||
return value
|
||||
end
|
||||
end
|
||||
else
|
||||
--minetest.log("Entering again...")
|
||||
-- We have a dialogue action type. Need to check if dialogue has further responses
|
||||
if value.action.responses ~= nil then
|
||||
local response = get_response_object_by_id_recursive(value.action, current_depth + 1, response_id)
|
||||
if response ~= nil then
|
||||
return response
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Handler for dialogue formspec
|
||||
@ -418,8 +622,8 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
|
||||
if fields then
|
||||
local player_response = npc.dialogue.dialogue_results.yes_no_dialogue[player_name]
|
||||
|
||||
-- Unlock queue, reset action timer and unfreeze NPC.
|
||||
npc.unlock_actions(player_response.npc)
|
||||
-- Unlock queue, reset action timer and unfreeze NPC.
|
||||
npc.unlock_actions(player_response.npc)
|
||||
|
||||
if fields.yes_option then
|
||||
player_response.yes_callback()
|
||||
@ -437,76 +641,46 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
|
||||
-- Get player response
|
||||
local player_response = npc.dialogue.dialogue_results.options_dialogue[player_name]
|
||||
|
||||
-- Check if the player hit the negative option or esc button
|
||||
if fields["exit"] or fields["quit"] == "true" then
|
||||
-- Unlock queue, reset action timer and unfreeze NPC.
|
||||
npc.unlock_actions(player_response.npc)
|
||||
end
|
||||
-- Check if the player hit the negative option or esc button
|
||||
if fields["exit"] or fields["quit"] == "true" then
|
||||
-- Unlock queue, reset action timer and unfreeze NPC.
|
||||
npc.unlock_actions(player_response.npc)
|
||||
end
|
||||
|
||||
for i = 1, #player_response.options do
|
||||
local button_label = "opt"..tostring(i)
|
||||
if fields[button_label] then
|
||||
if player_response.options[i].action_type == "dialogue" then
|
||||
-- Process dialogue object
|
||||
npc.dialogue.process_dialogue(player_response.npc,
|
||||
player_response.options[i].action,
|
||||
player_name)
|
||||
npc.dialogue.process_dialogue(player_response.npc,
|
||||
player_response.options[i].action,
|
||||
player_name)
|
||||
elseif player_response.options[i].action_type == "function" then
|
||||
-- Execute function - get it directly from definition
|
||||
-- Find NPC relationship phase with player
|
||||
local phase =
|
||||
npc.relationships.get_relationship_phase(player_response.npc, player_name)
|
||||
local phase =
|
||||
npc.relationships.get_relationship_phase(player_response.npc, player_name)
|
||||
-- Check if NPC is married and the married NPC dialogue should be shown
|
||||
if phase == "phase6" and player_response.is_married_dialogue == true then
|
||||
-- Get the function definitions from the married dialogue
|
||||
npc.relationships.MARRIED_NPC_DIALOGUE
|
||||
.responses[player_response.options[i].response_id]
|
||||
.action(player_response.npc, player)
|
||||
|
||||
elseif player_response.is_casual_trade_dialogue == true then
|
||||
-- Check if trade is casual buy or sell
|
||||
if player_response.casual_trade_type == npc.trade.OFFER_BUY then
|
||||
-- Get functions from casual buy dialogue
|
||||
npc.trade.CASUAL_TRADE_BUY_DIALOGUE
|
||||
.responses[player_response.options[i].response_id]
|
||||
.action(player_response.npc, player)
|
||||
elseif player_response.casual_trade_type == npc.trade.OFFER_SELL == true then
|
||||
-- Get functions from casual sell dialogue
|
||||
npc.trade.CASUAL_TRADE_SELL_DIALOGUE
|
||||
.responses[player_response.options[i].response_id]
|
||||
.action(player_response.npc, player)
|
||||
end
|
||||
return
|
||||
elseif player_response.is_dedicated_trade_dialogue == true then
|
||||
-- Get the functions for a dedicated trader prompt
|
||||
npc.trade.DEDICATED_TRADER_PROMPT
|
||||
.responses[player_response.options[i].response_id]
|
||||
.action(player_response.npc, player)
|
||||
return
|
||||
elseif player_response.is_custom_trade_dialogue == true then
|
||||
-- Functions for a custom trade should be available from the same dialogue
|
||||
-- object as it is created in memory
|
||||
minetest.log("Player response: "..dump(player_response.options[i]))
|
||||
player_response.options[i].action(player_response.npc, player)
|
||||
else
|
||||
-- Get dialogues for sex and phase
|
||||
local dialogues = npc.data.DIALOGUES[player_response.npc.sex][phase]
|
||||
.responses[player_response.options[i].response_id]
|
||||
.action(player_response.npc, player)
|
||||
elseif player_response.is_custom_trade_dialogue == true then
|
||||
-- Functions for a custom trade should be available from the same dialogue
|
||||
-- object as they are created on demand
|
||||
--minetest.log("Player response: "..dump(player_response.options[i]))
|
||||
player_response.options[i].action(player_response.npc, player)
|
||||
else
|
||||
-- Get dialogue from registered dialogues
|
||||
local dialogue = npc.dialogue.registered_dialogues[player_response.options[i].dialogue_id]
|
||||
local response = get_response_object_by_id_recursive(dialogue, 0, player_response.options[i].response_id)
|
||||
|
||||
minetest.log("Object: "..dump(dialogues[player_response.options[i].dialogue_id]))
|
||||
local response = get_response_object_by_id_recursive(dialogues[player_response.options[i].dialogue_id], 0, player_response.options[i].response_id)
|
||||
minetest.log("Found: "..dump(response))
|
||||
|
||||
-- Execute function
|
||||
response.action(player_response.npc, player)
|
||||
|
||||
-- Execute function
|
||||
-- dialogues[player_response.options[i].dialogue_id]
|
||||
-- .responses[player_response.options[i].response_id]
|
||||
-- .action(player_response.npc, player)
|
||||
|
||||
-- Unlock queue, reset action timer and unfreeze NPC.
|
||||
npc.unlock_actions(player_response.npc)
|
||||
-- Execute function
|
||||
response.action(player_response.npc, player)
|
||||
|
||||
-- Unlock queue, reset action timer and unfreeze NPC.
|
||||
npc.unlock_actions(player_response.npc)
|
||||
end
|
||||
end
|
||||
return
|
||||
|
78
doc/actions_and_tasks.md
Normal file
@ -0,0 +1,78 @@
|
||||
Actions and Tasks
|
||||
Advanced_NPC Alpha-2 (DEV)
|
||||
==========================
|
||||
|
||||
IMPORTANT: In this documentation is only the explanation of the particular operation of each predefined
|
||||
action and task. Read reference documentation for details about API operation at [api.md](api.md).
|
||||
|
||||
Action (`add_action`)
|
||||
---------------------
|
||||
|
||||
#### `SET_INTERVAL`
|
||||
Set the interval at which the `action` are executed.
|
||||
|
||||
{
|
||||
interval = 1, -- A decimal number, in seconds (default is 1 second)
|
||||
freeze = false, -- if true, mobs_redo API will not execute until interval is set
|
||||
}
|
||||
|
||||
#### `FREEZE`
|
||||
This action allows to stop/execute mobs_redo API.
|
||||
This is good for stopping the NPC from fighting, wandering, etc.
|
||||
|
||||
{
|
||||
freeze = false, -- Boolean, if true, mobs_redo API will not execute.
|
||||
}
|
||||
|
||||
Tasks (`add_task`)
|
||||
------------------
|
||||
|
||||
#### `USE_BED`
|
||||
Sequence of actions that allows the NPC to use a bed.
|
||||
|
||||
{
|
||||
pos = {x=0,y=0,z=0}, --[[
|
||||
^ Position of bed to be used.
|
||||
^ Can be a coordinate x,y,z.
|
||||
^ Can be a place name of the NPC place map.
|
||||
Example: "bed_primary" ]]
|
||||
|
||||
action = action, --[[
|
||||
^ Whether to get up or lay on bed
|
||||
^ Defined in npc.actions.const.beds.action
|
||||
^ Available options:
|
||||
* npc.actions.const.beds.LAY : lay
|
||||
* npc.actions.const.beds.GET_UP : get up
|
||||
}
|
||||
|
||||
#### `WALK_TO_POS`
|
||||
NPC will walk to the given position. This task uses the pathfinder to calculate the nodes
|
||||
in the path that the NPC will walk through, then enqueues walk_step actions, combined with
|
||||
correct directional rotations and opening/closing of doors on the path.
|
||||
|
||||
{
|
||||
end_pos = {x=0,y=0,z=0}, --[[
|
||||
^ Destination position to reach.
|
||||
^ Can be a coordinate x,y,z.
|
||||
^ Can be a place name of the NPC place map.
|
||||
The position must be walkable for the npc to stop in,
|
||||
or in the access position of the place.
|
||||
Example: "home_inside" ]]
|
||||
|
||||
walkable = {}, --[[
|
||||
^ An array of node names to consider as walkable nodes
|
||||
for finding the path to the destination. ]]
|
||||
|
||||
use_access_node = true, --[[
|
||||
^ Boolean, if true, when using places, it will find path
|
||||
to the "accessible" node (empty or walkable node around
|
||||
the target node) instead of to the target node.
|
||||
^ Default is true. ]]
|
||||
|
||||
enforce_move = true, --[[
|
||||
^ Boolean, if true and no path is found from the NPC's
|
||||
position to the end_pos, the NPC will be teleported
|
||||
to the destination (or, if use_access_node == true it will
|
||||
teleport to the access position)
|
||||
^ Default is true. ]]
|
||||
}
|
424
doc/api.md
Normal file
@ -0,0 +1,424 @@
|
||||
Advanced_NPC API Reference Alpha-2 (DEV)
|
||||
=========================================
|
||||
* More information at <https://github.com/hkzorman/advanced_npc/wiki>
|
||||
|
||||
IMPORTANT: This WIP & unfinished file contains the definitions of current advanced_npc functions
|
||||
(Some documentation is lacking, so please bear in mind that this WIP file is just to enhance it)
|
||||
|
||||
Introduction
|
||||
------------
|
||||
You can consult this document for help on API of behaviors for the NPCs.
|
||||
The goal is to be able to have NPCs that have the same functionality as normal players.
|
||||
The NPCs make Sokomine's mg_villages in Minetest alive although they can
|
||||
be manually spawned outside the village and work as good as new.
|
||||
Here is some information about the API methods and systems.
|
||||
* npc.lua also uses methods and functions from the dependency: mobs_redo <https://github.com/tenplus1/mobs_redo>
|
||||
|
||||
|
||||
Initialize NPC
|
||||
--------------
|
||||
The API works with some variables into Lua Entity that represent a NPC,
|
||||
then you should initialize the Lua Entity before that it really assume
|
||||
a controled behavior.
|
||||
|
||||
### Methods
|
||||
* `npc.initialize(entity, pos, is_lua_entity, npc_stats, occupation_name)` : Initialize a NPC
|
||||
|
||||
The simplest way to start a mob (of mobs_redo API) is by using the `on_spawn` function
|
||||
Note: currently this call is unduly repeated (mobs_redo problem), so you should check if npc has already been initialized.
|
||||
|
||||
on_spawn = function(self)
|
||||
if self.initialized == nil then
|
||||
npc.initialize(self, self.object:getpos(), true)
|
||||
self.tamed = false
|
||||
end
|
||||
end
|
||||
|
||||
Or after add in the world
|
||||
|
||||
local obj = minetest.add_entity({x=0, y=10, z=0}, "mobs:sheep", {naked = true})
|
||||
local luaentity = get_luaentity(obj)
|
||||
npc.initialize(luaentity, luaentity.object:getpos(), true)
|
||||
luaentity.tamed = false
|
||||
|
||||
|
||||
NPC Steps
|
||||
---------
|
||||
The API works with NPC steps, then `on_step` callback need run the
|
||||
`npc.on_step(luaentity)`. This function process the NPC actions
|
||||
and return the freeze state, which is used for stop mobs_redo behavior.
|
||||
|
||||
Example:
|
||||
|
||||
on_step = function(self, dtime)
|
||||
npc.step(self, dtime)
|
||||
end
|
||||
|
||||
Mobs of Mobs_Redo API uses `do_custom` function instead of `on_step` callback
|
||||
and it needs return the freeze state to stop mobs_redo behavior.
|
||||
Here is a recommended code.
|
||||
|
||||
do_custom = function(self, dtime)
|
||||
|
||||
-- Here is my "do_custom" code
|
||||
|
||||
-- Process the NPC action and return freeze state
|
||||
return npc.step(self, dtime)
|
||||
end
|
||||
|
||||
|
||||
Actions and Tasks Queue
|
||||
-----------------------
|
||||
Actions are "atomic" executable actions the NPC can perform. Tasks are
|
||||
sequences of actions that are common enough to be supported by default.
|
||||
Each action or task is wrapped on a Lua table which tells the action/task
|
||||
to be executed and the arguments to be used. However, this is encapsulated
|
||||
to the user in the following two methods for a NPCs:
|
||||
|
||||
### Methods
|
||||
* `npc.add_action(luaentity, action, {action definition})`: Add action into NPC actions queue
|
||||
* `npc.add_task(luaentity, task, {task definition})`: Add task into NPC actions queue
|
||||
|
||||
For both of the above, `action`/`task` is a constant defined in
|
||||
`npc.actions.cmd`, and `{task/action definition}` is a Lua table specific arguments
|
||||
to each `action`/`task`.
|
||||
|
||||
Example
|
||||
|
||||
npc.add_task(self, npc.actions.cmd.USE_BED, {
|
||||
pos = {x=0,y=0,z=0},
|
||||
action = npc.actions.const.beds.LAY
|
||||
})
|
||||
npc.add_action(self, npc.actions.cmd.SET_INTERVAL, {
|
||||
interval = 10,
|
||||
freeze = true,
|
||||
})
|
||||
npc.add_task(self, npc.actions.cmd.USE_BED, {
|
||||
pos = {x=0,y=0,z=0},
|
||||
action = npc.actions.const.beds.GET_UP
|
||||
})
|
||||
|
||||
See more in [actions_and_tasks.md](actions_and_tasks.md) documentation.
|
||||
|
||||
|
||||
Schedules
|
||||
---------
|
||||
The interesting part of Advanced NPC is its ability to simulate realistic
|
||||
behavior in NPCs. Realistic behavior is defined simply as being able to
|
||||
perform tasks at a certain time of the day, like usually people do. This
|
||||
allow the NPC to go to bed, sleep, get up from it, sit in benches, etc.
|
||||
All of this is simulated through a structured code using action and tasks.
|
||||
|
||||
The implementation resembles a rough OS process scheduling algorithm where
|
||||
only one process is allowed at a time. The processes or tasks are held in
|
||||
a queue, where they are executed one at a time in queue fashion.
|
||||
Interruptions are allowed, and the interrupted action is re-started once
|
||||
the interruption is finished.
|
||||
|
||||
### Schedule commands
|
||||
Schedule commands are an array of actions and tasks that the NPC.
|
||||
There are 4 possible commands:
|
||||
|
||||
* action
|
||||
```
|
||||
{
|
||||
action = action, -- Is a constant defined in `npc.actions.cmd`
|
||||
args = {} -- action arguments
|
||||
}
|
||||
```
|
||||
* task
|
||||
```
|
||||
{
|
||||
task = task, -- Is a constant defined in `npc.actions.cmd`
|
||||
args = {} -- task arguments
|
||||
}
|
||||
```
|
||||
* Property change
|
||||
```
|
||||
{
|
||||
???
|
||||
}
|
||||
```
|
||||
* Schedule query/check
|
||||
```
|
||||
{
|
||||
schedule query/check definition
|
||||
}
|
||||
```
|
||||
### Schedule time
|
||||
Only integer value 0 until 23
|
||||
* 0: 0/24000 - 999
|
||||
* 1: 1000 - 1999
|
||||
* 2: 2000 - 2999
|
||||
* ...
|
||||
* 22: 22000 - 22999
|
||||
* 23: 23000 - 23999
|
||||
|
||||
### Schedule Type
|
||||
* "generic" : Returns nil if there are already seven schedules, one for each
|
||||
day of the week or if the schedule attempting to add already exists.
|
||||
The date parameter is the day of the week it represents as follows:
|
||||
Note: Currently only one schedule is supported, for day 0
|
||||
1: Monday
|
||||
2: Tuesday
|
||||
3: Wednesday
|
||||
4: Thursday
|
||||
5: Friday
|
||||
6: Saturday
|
||||
7: Sunday
|
||||
* "date_based" : The date parameter should be a string of the format "MM:DD".
|
||||
If it already exists, function retuns nil
|
||||
|
||||
### Methods
|
||||
* `npc.create_schedule(luaentity, schedule_type, day)` : Create a schedule for a NPC
|
||||
* `npc.delete_schedule(luaentity, schedule_type, date)` : Delete a schedule for a NPC
|
||||
* `npc.add_schedule_entry(luaentity, schedule_type, date, time, check, commands)` : Add a schedule entry for a time
|
||||
* `npc.get_schedule_entry(luaentity, schedule_type, date, time)` : Get a schedule entry
|
||||
* `npc.update_schedule_entry(luaentity, schedule_type, date, time, check, commands)` : Update a schedule entry
|
||||
|
||||
### Examples
|
||||
|
||||
-- Schedule entry for 7 in the morning
|
||||
npc.add_schedule_entry(self, "generic", 0, 7, nil, {
|
||||
-- Get out of bed
|
||||
[1] = {
|
||||
task = npc.actions.cmd.USE_BED,
|
||||
args = {
|
||||
pos = "bed_primary",
|
||||
action = npc.actions.const.beds.GET_UP
|
||||
}
|
||||
},
|
||||
-- Allow mobs_redo wandering
|
||||
[2] = {
|
||||
action = npc.actions.cmd.FREEZE,
|
||||
args = {
|
||||
freeze = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Occupations
|
||||
-----------
|
||||
NPCs need an occupation or job in order to simulate being alive.
|
||||
This functionality is built on top of the schedules functionality.
|
||||
Occupations are essentially specific schedules, that can have slight
|
||||
random variations to provide diversity and make specific occupations
|
||||
less predictable. Occupations are associated with textures, dialogues,
|
||||
specific initial items, type of building (and surroundings) where NPC
|
||||
lives, etc.
|
||||
|
||||
### Methods
|
||||
* `npc.occupations.register_occupation(occupation_name, {occupation definition})` : Register an occupation
|
||||
* `npc.occupations.initialize_occupation_values(luaentity, occupation_name)` : Initialize an occupation for a NPC
|
||||
|
||||
Places Map
|
||||
----------
|
||||
Places map define which NPCs can access which places.
|
||||
Places are separated into different types.
|
||||
|
||||
### Place types
|
||||
Current place types
|
||||
* `bed_primary` : the bed of a NPC
|
||||
* `sit_primary`
|
||||
* `sit_shared`
|
||||
* `furnace_primary`
|
||||
* `furnace_shared`
|
||||
* `storage_primary`
|
||||
* `storage_shared`
|
||||
* `home_entrance_door`
|
||||
* `schedule_target_pos` : used in the schedule actions
|
||||
* `calculated_target_pos`
|
||||
* `workplace_primary`
|
||||
* `workplace_tool`
|
||||
* `home_plotmarker`
|
||||
* `home_inside`
|
||||
* `home_outside`
|
||||
|
||||
### Methods
|
||||
* `npc.places.add_owned(luaentity, place_name, place_type, pos, access_pos)` : Add owned place.
|
||||
`luaentity` npc owner.
|
||||
`place_name` a specific place name.
|
||||
`place_type` place typing.
|
||||
`pos` is a position of a node to be owned.
|
||||
`access_pos` is the coordinate where npc must be to initiate the access.
|
||||
Place is added for the NPC.
|
||||
* `npc.places.add_shared(luaentity, place_name, place_type, pos, access_node)` : Add shared place
|
||||
|
||||
|
||||
Dialogues
|
||||
---------
|
||||
Dialogs can be registered to be spoken by NPCs.
|
||||
|
||||
### Tags
|
||||
The flags or marks of the dialogue text. Tags can be used for ....
|
||||
|
||||
* "unisex" : Both male and female NPCs can say the defined text.
|
||||
* "phase1" : NPCs in phase 1 of a relationship can say the defined text.
|
||||
|
||||
### Methods
|
||||
* `set_response_ids_recursively()` : A local function that assigns unique
|
||||
key IDs to dialogue responses.
|
||||
* `npc.dialogue.register_dialogue({dialogue definition})` : Defines and
|
||||
registers dialogues.
|
||||
* `npc.dialogue.search_dialogue_by_tags({search_tags})` : A method returning
|
||||
a table of dialogues if called.
|
||||
|
||||
|
||||
Definition tables
|
||||
-----------------
|
||||
|
||||
### Occupation definition (`register_occupation`)
|
||||
|
||||
{
|
||||
dialogues = {
|
||||
enable_gift_item_dialogues = true, --[[
|
||||
^ This flag enables/disables gift item dialogues.
|
||||
^ If not set, it defaults to true. ]]
|
||||
type = "", -- The type can be "given", "mix" or "tags"
|
||||
data = {}, --[[
|
||||
^ Array of dialogue definitions. This will have dialogue
|
||||
if the type is either "mix" or "given" ]]
|
||||
tags = {}, --[[
|
||||
^ Array of tags to search for. This will have tags
|
||||
if the type is either "mix" or "tags" ]]
|
||||
},
|
||||
|
||||
textures = {}, --[[
|
||||
^ Textures are an array of textures, as usually given on
|
||||
an entity definition. If given, the NPC will be guaranteed
|
||||
to have one of the given textures. Also, ensure they have sex
|
||||
as well in the filename so they can be chosen appropriately.
|
||||
^ If left empty, it can spawn with any texture. ]]
|
||||
|
||||
walkable_nodes = {}, -- Walkable nodes
|
||||
|
||||
building_types = {}, --[[
|
||||
^ An array of string where each string is the type of building
|
||||
where the NPC can spawn with this occupation.
|
||||
^ Example: building_type = {"farm", "house"}
|
||||
^ If left empty or nil, NPC can spawn in any building ]]
|
||||
|
||||
surrounding_building_types = {}, --[[
|
||||
^ An array of string where each string is the type of building
|
||||
that is an immediate neighbor of the NPC's home which can also
|
||||
be suitable for this occupation. Example, if NPC is farmer and
|
||||
spawns on house, then it has to be because there is a field
|
||||
nearby.
|
||||
^ If left empty or nil, surrounding buildings doesn't matter. ]]
|
||||
|
||||
workplace_nodes = {}, --[[
|
||||
^ An array of string where each string is a node the NPC works with.
|
||||
^ These are useful for assigning workplaces and work work nodes. ]]
|
||||
|
||||
initial_inventory = {}, --[[
|
||||
^ An array of entries like the following:
|
||||
{name="", count=1} -- or
|
||||
{name="", random=true, min=1, max=10}
|
||||
^ This will initialize the inventory for the NPC with the given
|
||||
items and the specified count, or, a count between min and max
|
||||
when the entry contains random=true
|
||||
^ If left empty, it will initialize with random items. ]]
|
||||
|
||||
initial_trader_status = "", --[[
|
||||
^ String that specifies initial trader value.
|
||||
^ Valid values are: "casual", "trader", "none" ]]
|
||||
|
||||
schedules_entries = {},
|
||||
^ This is a table of tables in the following format:
|
||||
{
|
||||
[<time number>] = {
|
||||
[<command number>] = {
|
||||
command
|
||||
}
|
||||
}
|
||||
}
|
||||
^ Example:
|
||||
{
|
||||
[1] = {
|
||||
[1] = schedule command
|
||||
},
|
||||
[13] = {
|
||||
[1] = schedule command,
|
||||
[2] = schedule command
|
||||
},
|
||||
[23] = {
|
||||
[1] = schedule command
|
||||
}
|
||||
}
|
||||
The numbers, [1], [13] and [23] are the times when the entries
|
||||
corresponding to each are supposed to happen. The tables with
|
||||
[1], [1],[2] and [1] actions respectively are the entries that
|
||||
will happen at time 1, 13 and 23. ]]
|
||||
}
|
||||
|
||||
### Dialogue definition (`register_dialogue`)
|
||||
|
||||
{
|
||||
text = "Hello.", --[[
|
||||
^ The dialogue text itself.
|
||||
^ It must be included in the method.]]
|
||||
|
||||
tags = {"tag1", "tag2"} --[[
|
||||
^ The flags or marks of the dialogue text.
|
||||
^ The object can be excluded. ]]
|
||||
}
|
||||
|
||||
### Schedule query/check definition (schedule command)
|
||||
|
||||
{
|
||||
check = true, -- Indicates that this is a schedule query/check
|
||||
|
||||
range = 2, -- Range of checked area in blocks.
|
||||
|
||||
count = 20, -- How many checks will be performed.
|
||||
|
||||
random_execution_times = true, --[[
|
||||
^ Randomizes the number of checks that will be performed.
|
||||
^ min_count and max_count is required ]]
|
||||
|
||||
min_count = 20, -- minimum of checks
|
||||
max_count = 25, -- maximum of checks
|
||||
|
||||
nodes = {"itemstring1", "itemstring2"}, --[[
|
||||
^ Nodes to be found for the actions.
|
||||
^ When a node is found, it is add in the npc place map
|
||||
with the place name "schedule_target_pos"
|
||||
|
||||
prefer_last_acted_upon_node = true, -- If prefer to act on nodes already acted upon
|
||||
|
||||
walkable_nodes = {"itemstring1", "itemstring2"}, -- Walkable nodes
|
||||
|
||||
actions = { --[[
|
||||
^ Table where index is a itemstring of the node to be found,
|
||||
and value is an array of actions and tasks to be performed
|
||||
when found the node. ]]
|
||||
|
||||
["itemstring1"] = {
|
||||
[1] = action or task in schedule command format,
|
||||
[2] = action or task in schedule command format,
|
||||
[3] = action or task in schedule command format
|
||||
},
|
||||
["itemstring2"] = {
|
||||
[1] = action or task in schedule command format,
|
||||
[2] = action or task in schedule command format
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
Examples:
|
||||
|
||||
Syntax example 1:
|
||||
|
||||
npc.dialogue.register_dialogue({
|
||||
text = "Hello.", -- "Hello." will be said by the NPC upon rightclick and displayed in the messages section.
|
||||
tags = {"unisex", "phase1"} -- The flags that define the conditions of who and what can say the text.
|
||||
})
|
||||
|
||||
Syntax example 2:
|
||||
|
||||
npc.dialogue.register_dialogue({
|
||||
text = "Hello again."
|
||||
-- The tags object is excluded, meaning that any NPC can say "Hello again." upon rightclick under no condition.
|
||||
})
|
10
init.lua
@ -24,6 +24,7 @@ end
|
||||
mobs.intllib = S
|
||||
|
||||
dofile(path .. "/npc.lua")
|
||||
dofile(path .. "/utils.lua")
|
||||
dofile(path .. "/spawner.lua")
|
||||
dofile(path .. "/relationships.lua")
|
||||
dofile(path .. "/dialogue.lua")
|
||||
@ -33,6 +34,15 @@ dofile(path .. "/actions/actions.lua")
|
||||
dofile(path .. "/actions/places.lua")
|
||||
dofile(path .. "/actions/pathfinder.lua")
|
||||
dofile(path .. "/actions/node_registry.lua")
|
||||
dofile(path .. "/occupations/occupations.lua")
|
||||
-- Load random data definitions
|
||||
dofile(path .. "/random_data.lua")
|
||||
dofile(path .. "/data/dialogues_data.lua")
|
||||
dofile(path .. "/data/gift_items_data.lua")
|
||||
dofile(path .. "/data/names_data.lua")
|
||||
dofile(path .. "/data/occupations/default.lua")
|
||||
dofile(path .. "/data/occupations/default_farmer.lua")
|
||||
dofile(path .. "/data/occupations/default_priest.lua")
|
||||
dofile(path .. "/data/occupations/default_miner.lua")
|
||||
|
||||
print (S("[Mod] Advanced NPC loaded"))
|
||||
|
468
occupations/occupations.lua
Normal file
@ -0,0 +1,468 @@
|
||||
-- Occupations/jobs functionality by Zorman2000
|
||||
-----------------------------------------------
|
||||
-- Occupations functionality
|
||||
-- NPCs need an occupation or job in order to simulate being alive.
|
||||
-- This functionality is built on top of the schedules functionality.
|
||||
-- Occupations are essentially specific schedules, that can have slight
|
||||
-- random variations to provide diversity and make specific occupations
|
||||
-- less predictable. Occupations are associated with textures, dialogues,
|
||||
-- specific initial items, type of building (and surroundings) where NPC
|
||||
-- lives, etc.
|
||||
-- Example of an occupation: farmer
|
||||
-- The farmer will have to live in a farm, or just beside a field.
|
||||
-- It will have the following schedule:
|
||||
-- 6AM - get out of bed, walk to home inside, goes to chest, retrieves
|
||||
-- seeds and wander
|
||||
-- 7AM - goes out to the field and randomly start harvesting and planting
|
||||
-- crops that are already fully grown
|
||||
-- 12PM - gets a random but moderate (5-15) amount of seeds and harvested
|
||||
-- - crops. Goes into the house, stores 1/4 of the amount in a chest,
|
||||
-- - gets all currency items it has, and sits into a bench
|
||||
-- 1PM - goes outside the house and becomes trader, sells the remaining
|
||||
-- - seeds and crops
|
||||
-- 6PM - goes inside the house. Stores all currency items it has, all
|
||||
-- - remainin seeds and crops, and sits on a bench
|
||||
-- 8PM - gets out of the bench, wanders inside home
|
||||
-- 10PM - goes to bed
|
||||
|
||||
-- Implementation:
|
||||
-- A function, npc.register_occupation(), will be provided to register an
|
||||
-- occupation that can be used to initialize NPCs. The format is the following:
|
||||
-- {
|
||||
-- dialogues = {
|
||||
-- enable_gift_item_dialogues = true,
|
||||
-- -- This flag enables/disables gift item dialogues.
|
||||
-- -- If not set, it defaults to true.
|
||||
-- type = "",
|
||||
-- -- The type can be "given", "mix" or "tags"
|
||||
-- data = {},
|
||||
-- -- Array of dialogue definitions. This will have dialogue
|
||||
-- -- if the type is either "mix" or "given"
|
||||
-- tags = {},
|
||||
-- -- Array of tags to search for. This will have tags
|
||||
-- -- if the type is either "mix" or "tags"
|
||||
--
|
||||
-- },
|
||||
-- textures = {},
|
||||
-- -- Textures are an array of textures, as usually given on
|
||||
-- -- an entity definition. If given, the NPC will be guaranteed
|
||||
-- -- to have one of the given textures. Also, ensure they have sex
|
||||
-- -- as well in the filename so they can be chosen appropriately.
|
||||
-- -- If left empty, it can spawn with any texture.
|
||||
-- building_types = {},
|
||||
-- -- An array of string where each string is the type of building
|
||||
-- -- where the NPC can spawn with this occupation.
|
||||
-- -- Example: building_type = {"farm", "house"}
|
||||
-- -- If left empty or nil, NPC can spawn in any building
|
||||
-- surrounding_building_types = {},
|
||||
-- -- An array of string where each string is the type of building
|
||||
-- -- that is an immediate neighbor of the NPC's home which can also
|
||||
-- -- be suitable for this occupation. Example, if NPC is farmer and
|
||||
-- -- spawns on house, then it has to be because there is a field
|
||||
-- -- nearby. If left empty or nil, surrounding buildings doesn't
|
||||
-- -- matter
|
||||
-- workplace_nodes = {},
|
||||
-- -- An array of string where each string is a node the NPC
|
||||
-- -- works with. These are useful for assigning workplaces and work
|
||||
-- -- work nodes.
|
||||
-- initial_inventory = {},
|
||||
-- -- An array of entries like the following:
|
||||
-- -- {name="", count=1} -- or
|
||||
-- -- {name="", random=true, min=1, max=10}
|
||||
-- -- This will initialize the inventory for the NPC with the given
|
||||
-- -- items and the specified count, or, a count between min and max
|
||||
-- -- when the entry contains random=true
|
||||
-- -- If left empty, it will initialize with random items.
|
||||
-- initial_trader_status = "",
|
||||
-- -- String that specifies initial trader value. Valid values are:
|
||||
-- -- "casual", "trader", "none"
|
||||
-- schedules_entries = {},
|
||||
-- -- This is a table of tables in the following format:
|
||||
-- -- {
|
||||
-- [1] = {[1] = action = npc.action.cmd.freeze, args={freeze=true}},
|
||||
-- [13] = {[1] = action = npc.action.cmd.freeze, args={freeze=false},
|
||||
-- [2] = action = npc.action.cmd.freeze, args={freeze=true}
|
||||
-- },
|
||||
-- [23] = {[1] = action=npc.action.cmd.freeze, args={freeze=false}}
|
||||
-- -- }
|
||||
-- -- The numbers, [1], [13] and [23] are the times when the entries
|
||||
-- -- corresponding to each are supposed to happen. The tables with
|
||||
-- -- [1], [1],[2] and [1] actions respectively are the entries that
|
||||
-- -- will happen at time 1, 13 and 23.
|
||||
-- }
|
||||
|
||||
-- Public API
|
||||
npc.occupations = {}
|
||||
|
||||
-- Private API
|
||||
local occupations = {}
|
||||
|
||||
-- This array contains all the registered occupations.
|
||||
-- The key is the name of the occupation.
|
||||
npc.occupations.registered_occupations = {}
|
||||
|
||||
-- Basic occupation name
|
||||
npc.occupations.basic_name = "default_basic"
|
||||
|
||||
-- This is the basic occupation definition, this is for all NPCs that
|
||||
-- don't have a specific occupation. It serves as an example.
|
||||
npc.occupations.basic_def = {
|
||||
-- Use random textures
|
||||
textures = {},
|
||||
-- Use random dialogues
|
||||
dialogues = {},
|
||||
-- Initialize inventory with random items
|
||||
initial_inventory = {},
|
||||
-- Initialize schedule
|
||||
schedules_entries = {
|
||||
-- Schedule entry for 7 in the morning
|
||||
[7] = {
|
||||
-- Get out of bed
|
||||
[1] = {task = npc.actions.cmd.USE_BED, args = {
|
||||
pos = npc.places.PLACE_TYPE.BED.PRIMARY,
|
||||
action = npc.actions.const.beds.GET_UP
|
||||
}
|
||||
},
|
||||
-- Walk to home inside
|
||||
[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}}
|
||||
},
|
||||
-- Schedule entry for 7 in the morning
|
||||
[8] = {
|
||||
-- Walk to outside of home
|
||||
[1] = {task = npc.actions.cmd.WALK_TO_POS, args = {
|
||||
end_pos = npc.places.PLACE_TYPE.OTHER.HOME_OUTSIDE,
|
||||
walkable = {}
|
||||
},
|
||||
chance = 75
|
||||
},
|
||||
-- Allow mobs_redo wandering
|
||||
[2] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}}
|
||||
},
|
||||
-- Schedule entry for 12 midday
|
||||
[12] = {
|
||||
-- Walk to a sittable node
|
||||
[1] = {task = npc.actions.cmd.WALK_TO_POS, args = {
|
||||
end_pos = {place_type=npc.places.PLACE_TYPE.SITTABLE.PRIMARY, use_access_node=true},
|
||||
walkable = {"cottages:bench"}
|
||||
},
|
||||
chance = 75
|
||||
},
|
||||
-- Sit on the node
|
||||
[2] = {task = npc.actions.cmd.USE_SITTABLE, args = {
|
||||
pos = npc.places.PLACE_TYPE.SITTABLE.PRIMARY,
|
||||
action = npc.actions.const.sittable.SIT
|
||||
},
|
||||
depends = {1}
|
||||
},
|
||||
-- Stay put into place
|
||||
[3] = {action = npc.actions.cmd.SET_INTERVAL, args = {
|
||||
freeze = true,
|
||||
interval = 35
|
||||
},
|
||||
depends = {2}
|
||||
},
|
||||
[4] = {action = npc.actions.cmd.SET_INTERVAL, args = {
|
||||
freeze = true,
|
||||
interval = npc.actions.default_interval
|
||||
},
|
||||
depends = {3}
|
||||
},
|
||||
-- Get up from sit
|
||||
[5] = {action = npc.actions.cmd.USE_SITTABLE, args = {
|
||||
pos = npc.places.PLACE_TYPE.SITTABLE.PRIMARY,
|
||||
action = npc.actions.const.sittable.GET_UP
|
||||
},
|
||||
depends = {4}
|
||||
}
|
||||
},
|
||||
-- Schedule entry for 1 in the afternoon
|
||||
[13] = {
|
||||
-- Give NPC money to buy from player
|
||||
[1] = {property = npc.schedule_properties.put_multiple_items, args = {
|
||||
itemlist = {
|
||||
{name="default:iron_lump", random=true, min=2, max=4}
|
||||
}
|
||||
},
|
||||
chance = 75
|
||||
},
|
||||
-- Change trader status to "trader"
|
||||
[2] = {property = npc.schedule_properties.trader_status, args = {
|
||||
status = npc.trade.TRADER
|
||||
},
|
||||
chance = 75
|
||||
},
|
||||
[3] = {property = npc.schedule_properties.can_receive_gifts, args = {
|
||||
can_receive_gifts = false
|
||||
},
|
||||
depends = {1}
|
||||
},
|
||||
-- Allow mobs_redo wandering
|
||||
[4] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}}
|
||||
},
|
||||
-- Schedule entry for 6 in the evening
|
||||
[18] = {
|
||||
-- Change trader status to "none"
|
||||
[1] = {property = npc.schedule_properties.trader_status, args = {
|
||||
status = npc.trade.NONE
|
||||
}
|
||||
},
|
||||
-- Enable gift receiving again
|
||||
[2] = {property = npc.schedule_properties.can_receive_gifts, args = {
|
||||
can_receive_gifts = true
|
||||
}
|
||||
},
|
||||
-- Get inside home
|
||||
[3] = {task = npc.actions.cmd.WALK_TO_POS, args = {
|
||||
end_pos = npc.places.PLACE_TYPE.OTHER.HOME_INSIDE,
|
||||
walkable = {}
|
||||
}
|
||||
},
|
||||
-- Allow mobs_redo wandering
|
||||
[4] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}}
|
||||
},
|
||||
-- Schedule entry for 10 in the evening
|
||||
[22] = {
|
||||
[1] = {task = npc.actions.cmd.WALK_TO_POS, args = {
|
||||
end_pos = {place_type=npc.places.PLACE_TYPE.BED.PRIMARY, use_access_node=true},
|
||||
walkable = {}
|
||||
}
|
||||
},
|
||||
-- Use bed
|
||||
[2] = {task = npc.actions.cmd.USE_BED, args = {
|
||||
pos = npc.places.PLACE_TYPE.BED.PRIMARY,
|
||||
action = npc.actions.const.beds.LAY
|
||||
}
|
||||
},
|
||||
-- Stay put on bed
|
||||
[3] = {action = npc.actions.cmd.FREEZE, args = {freeze = true}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-- This function registers an occupation
|
||||
function npc.occupations.register_occupation(name, def)
|
||||
-- Register all dialogues per definition
|
||||
local dialogue_keys = {}
|
||||
if def.dialogues then
|
||||
-- Check which type of dialogues we have
|
||||
if def.dialogues.type == "given" then
|
||||
-- We have been given the dialogues, so def.dialogues.data contains
|
||||
-- an array of dialogues
|
||||
for _, dialogue in pairs(def.dialogues.data) do
|
||||
-- Add to the dialogue tags the "occupation name"
|
||||
table.insert(dialogue.tags, name)
|
||||
-- Register dialogue
|
||||
--npc.log("INFO", "Registering dialogue for occupation "..dump(name)..": "..dump(dialogue))
|
||||
local key = npc.dialogue.register_dialogue(dialogue)
|
||||
-- Add key to set of dialogue keys
|
||||
table.insert(dialogue_keys, key)
|
||||
end
|
||||
elseif def.dialogues.type == "mix" then
|
||||
-- We have been given the dialogues, so def.dialogues.data contains
|
||||
-- an array of dialogues and def.dialogues.tags contains an array of
|
||||
-- tags. Currently only registering will be performed.
|
||||
-- Register dialogues
|
||||
for _, dialogue in pairs(def.dialogues.data) do
|
||||
-- Add to the dialogue tags the "occupation name"
|
||||
table.insert(dialogue.tags, name)
|
||||
-- Register dialogue
|
||||
local key = npc.dialogue.register_dialogue(dialogue)
|
||||
-- Add key to set of dialogue keys
|
||||
table.insert(dialogue_keys, key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Save into the definition the dialogue keys
|
||||
def.dialogues["keys"] = dialogue_keys
|
||||
|
||||
-- Save the definition
|
||||
npc.occupations.registered_occupations[name] = def
|
||||
|
||||
--npc.log("INFO", "Successfully registered occupation with name: "..dump(name))
|
||||
end
|
||||
|
||||
-- This function scans all registered occupations and filter them by
|
||||
-- building type and surrounding building type, returning an array
|
||||
-- of occupation names (strings)
|
||||
-- BEWARE! Below this lines lies ugly, incomprehensible code!
|
||||
function npc.occupations.get_for_building(building_type, surrounding_building_types)
|
||||
local result = {}
|
||||
for name,def in pairs(npc.occupations.registered_occupations) do
|
||||
-- Check for empty or nil building types, in that case, any building
|
||||
if def.building_types == nil or def.building_types == {}
|
||||
and def.surrounding_building_types == nil or def.surrounding_building_types == {} then
|
||||
--minetest.log("Empty")
|
||||
-- Empty building types, add to result
|
||||
table.insert(result, name)
|
||||
elseif def.building_types ~= nil and #def.building_types > 0 then
|
||||
-- Check if building type is contained in the def's building types
|
||||
if npc.utils.array_contains(def.building_types, building_type) then
|
||||
table.insert(result, name)
|
||||
end
|
||||
end
|
||||
-- Check for empty or nil surrounding building types
|
||||
if def.surrounding_building_types ~= nil
|
||||
and #def.surrounding_building_types > 0 then
|
||||
-- -- Add this occupation
|
||||
-- --table.insert(result, name)
|
||||
-- else
|
||||
-- Surrounding buildings is not empty, loop though them and compare
|
||||
-- to the given ones
|
||||
for i = 1, #surrounding_building_types do
|
||||
for j = 1, #def.surrounding_building_types do
|
||||
-- Check if the definition's surrounding building type is the same
|
||||
-- as the given one
|
||||
if def.surrounding_building_types[j].type
|
||||
== surrounding_building_types[i].type then
|
||||
-- Check if the origin buildings contain the expected type
|
||||
if npc.utils.array_contains(def.surrounding_building_types[j].origin_building_types,
|
||||
surrounding_building_types[i].origin_building_type) then
|
||||
-- Add this occupation
|
||||
table.insert(result, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- This function will initialize entities values related to
|
||||
-- the occupation: textures, dialogues, inventory items and
|
||||
-- will set schedules accordingly.
|
||||
function npc.occupations.initialize_occupation_values(self, occupation_name)
|
||||
-- Get occupation definition
|
||||
local def = npc.occupations.registered_occupations[occupation_name]
|
||||
|
||||
if not def then
|
||||
--npc.log("WARNING", "No definition found for occupation name: "..dump(occupation_name))
|
||||
return
|
||||
end
|
||||
|
||||
--npc.log("INFO", "Overriding NPC values using occupation '"..dump(occupation_name).."' values")
|
||||
|
||||
-- Initialize textures, else it will leave the current textures
|
||||
if def.textures and table.getn(def.textures) > 0 then
|
||||
self.selected_texture =
|
||||
npc.get_random_texture_from_array(self.sex, self.age, def.textures)
|
||||
-- Set texture if it found for sex and age
|
||||
-- If an array was returned, select a random texture from it
|
||||
if type(self.selected_texture) == "table" then
|
||||
local selected_texture = self.selected_texture[math.random(1, #self.selected_texture)]
|
||||
self.selected_texture = selected_texture
|
||||
end
|
||||
-- Set texture and base texture
|
||||
self.textures = {self.selected_texture}
|
||||
self.base_texture = {self.selected_texture }
|
||||
-- Assign sex based on texture
|
||||
self.sex = npc.assign_sex_from_texture(self)
|
||||
-- Refresh entity
|
||||
self.object:set_properties(self)
|
||||
end
|
||||
|
||||
-- Initialize inventory
|
||||
if def.initial_inventory and table.getn(def.initial_inventory) > 0 then
|
||||
for i = 1, #def.initial_inventory do
|
||||
local item = def.initial_inventory[i]
|
||||
-- Check if item count is randomized
|
||||
if item.random and item.min and item.max then
|
||||
npc.add_item_to_inventory(self, item.name, math.random(item.min, item.max))
|
||||
else
|
||||
-- Add item with the given count
|
||||
npc.add_item_to_inventory(self, item.name, item.count)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Initialize dialogues
|
||||
if def.dialogues then
|
||||
-- Check for gift item dialogues enable
|
||||
if def.dialogues.disable_gift_item_dialogues then
|
||||
self.dialogues.hints = {}
|
||||
end
|
||||
|
||||
local dialogue_keys = {}
|
||||
-- Check which type of dialogues we have
|
||||
if def.dialogues.type == "given" and def.dialogues.keys then
|
||||
-- We have been given the dialogues, so def.dialogues.data contains
|
||||
-- an array of dialogues. These dialogues were registered, therefore we need
|
||||
-- just the keys
|
||||
for i = 1, #def.dialogues.keys do
|
||||
table.insert(dialogue_keys, def.dialogues.keys[i])
|
||||
end
|
||||
elseif def.dialogues.type == "mix" then
|
||||
-- We have been given the dialogues, so def.dialogues.data contains
|
||||
-- an array of dialogues and def.dialogues.tags contains an array of
|
||||
-- tags that we will use to search
|
||||
if def.dialogues.keys then
|
||||
-- Add the registered dialogues
|
||||
for i = 1, #def.dialogues.keys do
|
||||
table.insert(dialogue_keys, def.dialogues.keys[i])
|
||||
end
|
||||
end
|
||||
-- Find dialogues using tags
|
||||
local dialogues = npc.search_dialogue_by_tags(def.dialogues.tags, true)
|
||||
-- Add keys to set of dialogue keys
|
||||
for _, key in pairs(npc.utils.get_map_keys(dialogues)) do
|
||||
table.insert(dialogue_keys, key)
|
||||
end
|
||||
elseif def.dialogues.type == "tags" then
|
||||
-- We need to find the dialogues from tags. def.dialogues.tags contains
|
||||
-- an array of tags that we will use to search.
|
||||
local dialogues = npc.search_dialogue_by_tags(def.dialogues.tags, true)
|
||||
-- Add keys to set of dialogue keys
|
||||
dialogue_keys = npc.utils.get_map_keys(dialogues)
|
||||
end
|
||||
-- Add dialogues to NPC
|
||||
-- Check if there is a max of dialogues to be added
|
||||
local max_dialogue_count = npc.dialogue.MAX_DIALOGUES
|
||||
if def.dialogues.max_count and def.dialogues.max_count > 0 then
|
||||
max_dialogue_count = def.dialogues.max_count
|
||||
end
|
||||
-- Add dialogues to the normal dialogues for NPC
|
||||
if #dialogue_keys > 0 then
|
||||
self.dialogues.normal = {}
|
||||
for i = 1, math.min(max_dialogue_count, #dialogue_keys) do
|
||||
self.dialogues.normal[i] = dialogue_keys[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Initialize properties
|
||||
--minetest.log("def.properties: "..dump(def.properties))
|
||||
if def.properties then
|
||||
-- Initialize trader status
|
||||
if def.properties.initial_trader_status then
|
||||
self.trader_data.trader_status = def.properties.initial_trader_status
|
||||
end
|
||||
-- Enable/disable gift items hints
|
||||
if def.properties.enable_gift_items_hints ~= nil then
|
||||
self.gift_data.enable_gift_items_hints = def.properties.enable_gift_items_hints
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Initialize schedule entries
|
||||
if def.schedules_entries and table.getn(npc.utils.get_map_keys(def.schedules_entries)) > 0 then
|
||||
-- Create schedule in NPC
|
||||
npc.create_schedule(self, npc.schedule_types.generic, 0)
|
||||
-- Traverse schedules
|
||||
for time, entries in pairs(def.schedules_entries) do
|
||||
-- Add schedule entry for each time
|
||||
npc.add_schedule_entry(self, npc.schedule_types.generic, 0, time, nil, entries)
|
||||
end
|
||||
end
|
||||
|
||||
npc.log("INFO", "Successfully initialized NPC with occupation values")
|
||||
|
||||
end
|
@ -374,7 +374,7 @@ npc.data.FIRST_NAMES = {
|
||||
"Arianne",
|
||||
"Lizzy",
|
||||
"Amy",
|
||||
"Chole",
|
||||
"Chloe",
|
||||
"Alisson"
|
||||
},
|
||||
male = {
|
||||
@ -396,8 +396,7 @@ npc.FAVORITE_ITEMS = {
|
||||
female = {},
|
||||
male = {}
|
||||
}
|
||||
-- Define items by phase
|
||||
-- Female
|
||||
|
||||
npc.FAVORITE_ITEMS.female["phase1"] = {
|
||||
{item = "default:apple",
|
||||
response = "Hey, I really wanted an apple, thank you!",
|
||||
@ -452,7 +451,7 @@ npc.FAVORITE_ITEMS.female["phase4"] = {
|
||||
hint = "I could really do with an apple..."},
|
||||
{item = "farming:bread",
|
||||
response = "Thanks, you didn't have to, but thanks...",
|
||||
hint = "SOme fresh bread would be good!"}
|
||||
hint = "Some fresh bread would be good!"}
|
||||
}
|
||||
npc.FAVORITE_ITEMS.female["phase5"] = {
|
||||
{item = "default:apple",
|
||||
|
1696
spawner.lua
3
spawner_marker.lua
Normal file
@ -0,0 +1,3 @@
|
||||
-- Spawner markers
|
||||
-- Specialized functionality to allow players do NPC spawning
|
||||
-- on their own custom buildings.
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
BIN
textures/npc_female10.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
textures/npc_female11.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
textures/npc_female2.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
textures/npc_female3.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
textures/npc_female4.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
textures/npc_female5.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
textures/npc_female6.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
textures/npc_female7.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
textures/npc_female8.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
textures/npc_female9.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
textures/npc_male10.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
textures/npc_male11.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
textures/npc_male12.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
textures/npc_male13.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
textures/npc_male14.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
textures/npc_male2.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
textures/npc_male3.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
textures/npc_male4.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
textures/npc_male5.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
textures/npc_male6.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
textures/npc_male7.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
textures/npc_male8.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
textures/npc_male9.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
textures/npc_male_priest.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
@ -35,8 +35,9 @@ npc.trade.prices.table["mobs:leather"] = {tier = npc.trade.prices.curre
|
||||
npc.trade.prices.table["default:sword_stone"] = {tier = npc.trade.prices.currency.tier3.string, count = 6}
|
||||
npc.trade.prices.table["default:shovel_stone"] = {tier = npc.trade.prices.currency.tier3.string, count = 6}
|
||||
npc.trade.prices.table["default:axe_stone"] = {tier = npc.trade.prices.currency.tier3.string, count = 6}
|
||||
npc.trade.prices.table["default:hoe_stone"] = {tier = npc.trade.prices.currency.tier3.string, count = 6}
|
||||
npc.trade.prices.table["farming:hoe_stone"] = {tier = npc.trade.prices.currency.tier3.string, count = 6}
|
||||
npc.trade.prices.table["default:pick_stone"] = {tier = npc.trade.prices.currency.tier3.string, count = 7}
|
||||
npc.trade.prices.table["bucket:bucket_empty"] = {tier = npc.trade.prices.currency.tier3.string, count = 10}
|
||||
npc.trade.prices.table["farming:cotton"] = {tier = npc.trade.prices.currency.tier3.string, count = 15}
|
||||
npc.trade.prices.table["farming:bread"] = {tier = npc.trade.prices.currency.tier3.string, count = 20}
|
||||
|
||||
|
1173
trade/trade.lua
97
utils.lua
Normal file
@ -0,0 +1,97 @@
|
||||
-- Basic utilities to work with table operations in Lua, and specific querying
|
||||
-- By Zorman2000
|
||||
|
||||
npc.utils = {}
|
||||
|
||||
function npc.utils.split(inputstr, sep)
|
||||
if sep == nil then
|
||||
sep = "%s"
|
||||
end
|
||||
local t={}
|
||||
local i=1
|
||||
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
|
||||
t[i] = str
|
||||
i = i + 1
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
function npc.utils.array_contains(array, item)
|
||||
--minetest.log("Array: "..dump(array))
|
||||
--minetest.log("Item being searched: "..dump(item))
|
||||
for i = 1, #array do
|
||||
--minetest.log("Equals? "..dump(array[i] == item))
|
||||
if array[i] == item then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function npc.utils.array_is_subset_of_array(set, subset)
|
||||
local match_count = 0
|
||||
for j = 1, #subset do
|
||||
for k = 1, #set do
|
||||
if subset[j] == set[k] then
|
||||
match_count = match_count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Check match count
|
||||
return match_count == #subset
|
||||
end
|
||||
|
||||
function npc.utils.get_map_keys(map)
|
||||
local result = {}
|
||||
for key, _ in pairs(map) do
|
||||
table.insert(result, key)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function npc.utils.get_map_values(map)
|
||||
local result = {}
|
||||
for _, value in pairs(map) do
|
||||
table.insert(result, value)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- This function searches for a node given the conditions specified in the
|
||||
-- query object, starting from the given start_pos and up to a certain, specified
|
||||
-- range.
|
||||
-- Query object:
|
||||
-- search_type: determines the direction to search nodes.
|
||||
-- Valid values are: orthogonal, cross, cube
|
||||
-- - orthogonal search means only nodes which are parallel to the search node's faces
|
||||
-- will be considered. This limits the search to only 6 nodes.
|
||||
-- - cross search will look at the same nodes as orthogonal, plus will also
|
||||
-- check nodes diagonal to the node four horizontal nodes. This search looks at 14 nodes
|
||||
-- - cube search means to look every node surrounding the node, including all diagonals.
|
||||
-- This search looks at 26 nodes.
|
||||
-- search_nodes: array of nodes to search for
|
||||
-- surrounding_nodes: object specifying which neighbor nodes are to be expected and
|
||||
-- at which locations. Valid keys are:
|
||||
-- - North (+Z dir)
|
||||
-- - East (+x dir)
|
||||
-- - South (-Z dir)
|
||||
-- - West (-X dir)
|
||||
-- - Top (+Y dir)
|
||||
-- - Bottom (-Y dir)
|
||||
-- Example: ["bottom"] = {nodes={"default:dirt"}, criteria="all"}
|
||||
-- Each object will contain nodes, and criteria for acceptance.
|
||||
-- Criteria values are:
|
||||
-- - any: true as long as one of the nodes on this side is one of the specified
|
||||
-- in "nodes"
|
||||
-- - all: true when the set of neighboring nodes on this side contain one or many of
|
||||
-- the specified "nodes"
|
||||
-- - all-exact: true when the set of neighboring nodes on this side contain all nodes
|
||||
-- specified in "nodes"
|
||||
-- - shape: true when the set of neighboring nodes on this side contains nodes in
|
||||
-- the exact given shape. If so, nodes will not be an array, but a 2d array
|
||||
-- of three rows and three columns, with the specific shape. Notice that
|
||||
-- the nodes on the side can vary depending on the search type (orthogonal,
|
||||
-- cross, cube)
|
||||
function npc.utils.search_node(query, start_pos, range)
|
||||
|
||||
end
|