75 Commits

Author SHA1 Message Date
5b9c072c03 Removed .idea folder 2018-01-13 10:20:35 -05:00
18f07ce50f Actions: Added description.
Fully implement helper methods for validating and evaluating expressions.
Full implementation of declarative commands.
2018-01-13 10:12:17 -05:00
ff82f83c0b NPC: Add execution environment
Actions: Rename 'actions' to 'command'
Add skeleton method for declarative and control commands
2018-01-09 20:04:20 -05:00
9125aa334a Actions: Replace "action" for "command".
Add declarative and control commands comments and function signature (no implementation yet).
2018-01-09 19:04:51 -05:00
221cfa3105 NPC: Add execution context. 2018-01-09 18:43:33 -05:00
2dc2400b38 Api doc update (#31)
* Update api.md

* Add schedules
* Add occupation
* Update places map
* Fix `step` and `initialize`

* Add schedule commands

* Add WALK_TO_POS task

* orthographic correction
2017-12-20 14:40:36 -05:00
c7fad7d6c6 Initiating documentation (#25)
* Add API

* Add Actions and Tasks documentation

* Convert to .MD docs

* Add links

* Fix links

* Remove some terms
2017-11-06 08:57:59 -05:00
5f9afae5d9 NPC: Support backward compatibility. 2017-10-19 21:51:16 -04:00
ec0392096f NPC: After interacting with NPC, original yaw is restored. 2017-10-19 21:47:35 -04:00
205bdf6eb1 NPC: Use after_activate() callback to fix animation issue. 2017-10-18 15:09:00 -04:00
5bdc3c3c29 NPC: Fix animation state not persistent on interaction/game restart.
Action lock/unlock doesn't perform lock if movement state is sitting or laying.
Right-click interaction can now be disabled in freeze action command.
Upon NPC activation, animation is restored.
2017-10-18 09:05:08 -04:00
4a4dc724cd NPC: Fix issues with female and male skins.
Fix naming issues with male skins.
Improve coloring on male skins.
2017-10-16 18:48:02 -04:00
d95c8504ec Places: Add truly owned nodes. NPCs shouldn't override ownership
anymore.
Spawner: Add command "/restore_area" to clear up node ownership & usage in manually spawned places.
"/restore_plotmarkers" now clean up node ownership & usage in mg_villages.
2017-10-07 11:28:38 -04:00
8a5e80e2cb Places: Allow finding alternative nodes when nodes are being used.
Add owned and used properties to each owned/shared node.
Actions: Allow to find alternative sittable/bed/furnace node if currently
being used.
Default occupation: Add alternative sitting finding.
NPC: Log cleanup.
2017-10-05 18:53:46 -04:00
d92b729e19 Fixed bugs related to door positioning, opening/closing, and finding
positions in general.
2017-09-23 10:43:51 -04:00
43c69ffee4 Spawner: Add search radius and height to manual spawn egg. 2017-09-21 20:03:01 -04:00
164e09bed5 Farmer: Add trading list.
Add 6 cotton seeds as initial inventory.
2017-09-21 18:55:29 -04:00
5a79b9a119 Trade: Fix name for stone hoe. 2017-09-21 18:55:02 -04:00
d55ffd06fc Schedules: Allow to set trade list as a schedule property.
Trade: Trading list now supports setting maximum sell count, buy count and how much to keep (in case of sell).
Add price for empty bucket.
2017-09-21 18:53:43 -04:00
c94edbc649 Log cleanup. 2017-09-21 13:45:34 -04:00
cb218fc9f9 Actions: Fix bug with not finding walkable positions around bed. 2017-09-21 13:45:09 -04:00
c37c4dd868 Default occupation: Perform casual trading during afternoon, none during
the rest of the day.
2017-09-21 09:02:41 -04:00
cf77ab5299 Farmer: Add other schedule entries for commonplace tasks.
Priest: Fixed missing dialogue issue.
Miner: Fixed indentation.
2017-09-18 01:13:20 -04:00
175d07476d Remove workspace.xml 2017-09-18 00:33:24 -04:00
ee38dfe46b Update init.lua 2017-09-17 12:42:55 -04:00
7f9fedba9d Update default_miner.lua 2017-09-17 12:42:41 -04:00
5fed6e3a90 Update default_miner.lua 2017-09-17 12:42:24 -04:00
c3d5a995a6 Create default_miner.lua 2017-09-17 12:41:30 -04:00
a527bcd746 Update default_farmer.lua 2017-09-17 12:40:40 -04:00
e1a7efe9ab Update default_priest.lua 2017-09-17 12:31:31 -04:00
2a979ef1fe Actions: Add sounds to dig/place actions. 2017-09-17 12:11:31 -04:00
580792284d Spawner: Fix undefined node 2017-09-15 18:29:38 -04:00
561b11f8fe Spawner: Fix typo 2017-09-15 18:17:19 -04:00
db415dee97 Occupations: Change building type format to be more flexible.
Spawner: Huge rework on logic to determine occupations. Warning! Completely incomprehensible code.
Schedule: Improve schedule check enqueue and execution.
Optimize walk_to_pos if used on distances < 3
Huge amounts of log cleanup.
Places: Improve adding shared places.
Improve orthogonal node scanning by using walkables and not one single node.
2017-09-15 14:51:37 -04:00
b02a78de6d Default Farmer (WIP):
Improve schedule entries, schedule check.
Change to new building type format.
2017-09-15 14:47:04 -04:00
8cf1e932bc Default Priest:
Change building type definitions to new format.
2017-09-15 14:46:08 -04:00
bcd6327a13 Merge branch 'master' of https://github.com/hkzorman/advanced_npc 2017-09-15 14:44:45 -04:00
866b25c63c Pathfinder: Fix indentation.
Improve path decorator to avoid jumping NPCs (in Minetest 0.5-dev)
2017-09-13 09:09:08 -04:00
70fd62825c Actions: Fix bugs in dig and place actions.
Fix bug in get_pos_argument() function.
Optimize walk_to_pos task and improve usage of access node.
Improve get_direction() by using vector.direction() and vector.round()
2017-09-13 09:06:58 -04:00
770125fae9 Log cleanup. 2017-09-12 09:09:15 -04:00
70c7a3c96f Actions: Add mine animation to dig/place actions.
Fix bug enabling wandering after dig action.
Improved "random" dir functionality for walk_step to avoid getting NPC stuck.
General formatting fixes.
2017-09-12 09:08:44 -04:00
245c2c32c2 My first pull request. (#15)
* Delete occupations_data.lua
* Update default_priest.lua
* Update init.lua
2017-09-08 08:31:19 -04:00
ce47958144 Actions: Fix bugs and improve dig and place action commands. 2017-09-07 08:51:50 -04:00
2a0b0aa538 Occupations: Refactor all occupation defs into separate Lua files.
Add WIP priest occupation.
Add WIP (very WIP) farmer occupation.
2017-09-05 22:47:03 -04:00
6c3988a731 Occupation: Add ability to set some of the NPCs properties.
Currently, you can set:
 - Trader status
 - Whether to show or hide gift items hints
NPC: Add enable/disable gift item hints flag
Schedules: Allow to set the enable/disable gift item hints flag
Dialogues: Add ability to choose from normal dialogues if hint dialogues are disabled.
2017-09-05 19:02:50 -04:00
8e5d6d03f4 Spawner: Fix issue when "npcs" table is not present in restore_plotmarkers. 2017-09-01 18:27:17 -04:00
fe5a155177 Fix bug with dialogues being cleared for NPCs on occupation init. 2017-09-01 18:15:53 -04:00
cd8e3c09cb Priest occupation (WIP) 2017-09-01 14:15:23 -04:00
48eb8078b3 Schedules: Add support to change flags in schedules. 2017-09-01 14:15:01 -04:00
939181284b Spawner: Add workplaces properly to the usable nodes. 2017-09-01 14:14:35 -04:00
40ac2a55a1 Actions: Fix bug and add support for using access node. 2017-09-01 14:13:13 -04:00
ba6348663b Dialogues: Fix bug when not having custom trade offers. 2017-08-31 17:59:46 -05:00
c9eb3b0ef4 Spawner: Workplaces are properly assigned to NPC.
If a workplace is assigned to a NPC, it is not used by any other NPC.
"/restore_plotmarkers" command also restores the workplace so it can
be used by other NPCs.
Occupations: Dialogues are registered on occupation registration.
2017-08-31 09:11:43 -04:00
706a5cf188 Add occupation name to entity properties. 2017-08-29 18:16:48 -05:00
e85a8161c3 Change names to match convention. 2017-08-29 18:12:21 -05:00
337f8c46b7 Small bug fixes and log cleanups. 2017-08-29 14:55:38 -04:00
4ec8ad7f7f Add priest texture (WIP) 2017-08-29 14:55:21 -04:00
df56e44bbd Add workplaces support.
Places:
Workplaces are now usable nodes.
Plotmarkers around a building can be scanned and their information stored into current plotmarker.
Slightly optimize plotmarker search.
Occupations:
Add test "priest" occupation definition.
Fix lots of bugs in initialization code.
Spawner:
Add ability to determine occupation name out of surrounding workplaces.
Assign occupation to spawned NPC using simple algorithm.
Others:
Reduce log noise a bit.
Fix some warnings.
2017-08-29 14:54:57 -04:00
59bb430e62 Spawner: Nearby plotmarkers are now scanned for all mg_villages:plotmarkers.
Places: Usable nodes now include workplaces
Small bug fixes.
Relationships: Small bugfix with gift item response.
Occupations: Add work node descriptions.
Experimental "priest" occupation.
2017-08-28 15:43:57 -04:00
0ddb30c0f9 Finish implementation to find nearby plotmarkers. 2017-08-27 14:46:48 -04:00
19c5ca1d0c Add plotmarker search to adapt_mg_villages_plotmarker (WIP)
Small code cleanup.
2017-08-27 12:50:53 -04:00
d347b6fad5 Finish implementation of npc.places.find_plotmarkers.
Small code refactor.
2017-08-27 12:32:04 -04:00
ceae61f553 Add gitignore to IntelliJ folder 2017-08-27 11:41:35 -04:00
a4fd06d1c6 Add nearby plotmarkers search 2017-08-27 11:28:40 -04:00
ad9032ec5b Add scan functions and node definitions for workplaces. (WIP) 2017-08-25 19:48:34 -04:00
11e871a932 Log cleanups. 2017-08-25 09:33:25 -04:00
4c102a70a4 Spawner: Large code refactor to remove dependency on plotmarkers.
Most spawner functions can now be called without giving a plotmarker.
Move scanning functions to places.lua.
Places: Cleanup and add more area-scanning functions.
Schedules: Bugfix where schedules weren't being executed due to wrong
"end" order in the do_custom() function.
Data: Moved random data to "data" folder.
Textures: Add 14 male textures and 10 female textures.
Occupations: Small tweaks to "default_basic" occupation.
2017-08-25 09:31:45 -04:00
698d247aba Fix misc bugs 2017-08-11 15:41:50 -04:00
5a93800e77 Occupations: Add schedule check function, allow enqueuing of schedule check 2017-08-11 12:03:45 -04:00
0f931d273c Occupations: Add schedule check function, allow enqueuing of schedule check.
Add simple test farmer occupation (WIP).
2017-08-11 12:01:44 -04:00
3edc959d3a Dialogues: Finish implementation of registered dialogues.
Ported all trade dialogues to use new dialogue system.
2017-07-19 14:00:29 -04:00
b5dc9926cd Add 5 additional male textures.
Rename child textures to be more representative.
2017-07-13 19:05:39 -04:00
bff013bc44 Utilities: Add simple utility code to work with arrays and Lua tables 2017-07-13 19:04:00 -04:00
5eceb09cdb Random data: Separate random data in different files for better classification. Add registrations as well. 2017-07-13 19:02:40 -04:00
e79fb91ff3 Dialogues: Re-write dialogues to allow Minetest's like registration system instead of the actual table lookup system. In general, dialogues now are registered in similar fashion as nodes, entities and others are registered in Minetest.
Relationships: Re-write to the way favorite/disliked items are defined, now allow Minetest's like registration as well.
Random data: Random data now includes registrations for gift items and dialogues.
2017-07-13 19:01:28 -04:00
50 changed files with 8886 additions and 3912 deletions

File diff suppressed because it is too large Load Diff

View File

@ -63,7 +63,8 @@ function pathfinder.get_decorated_path(path)
local path_detail = {} local path_detail = {}
for i = 1, #path do for i = 1, #path do
local node = minetest.get_node(path[i]) local node = minetest.get_node(path[i])
table.insert(path_detail, {pos=path[i], type=pathfinder.is_good_node(node, {})}) 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 end
npc.log("DEBUG", "Detailed path: "..dump(path_detail)) npc.log("DEBUG", "Detailed path: "..dump(path_detail))

View File

@ -49,11 +49,31 @@ npc.places.nodes = {
"cottages:gate_open", "cottages:gate_open",
"cottages:gate_closed", "cottages:gate_closed",
"cottages:half_door" "cottages:half_door"
},
PLOTMARKER_TYPE = {
"mg_villages:plotmarker",
"advanced_npc:plotmarker"
},
WORKPLACE_TYPE = {
-- TODO: Do we have an advanced_npc workplace?
"mg_villages:mob_workplace_marker",
"advanced_npc:workplace_marker"
} }
} }
npc.places.PLACE_TYPE = { npc.places.PLACE_TYPE = {
CATEGORIES = {
BED = "BED",
SITTABLE = "SITTABLE",
FURNACE = "FURNACE",
STORAGE = "STORAGE",
OPENABLE = "OPENABLE",
SCHEDULE = "SCHEDULE",
CALCULATED = "CALCULATED",
WORKPLACE = "WORKPLACE",
OTHER = "OTHER"
},
BED = { BED = {
PRIMARY = "bed_primary" PRIMARY = "bed_primary"
}, },
@ -72,28 +92,86 @@ npc.places.PLACE_TYPE = {
OPENABLE = { OPENABLE = {
HOME_ENTRANCE_DOOR = "home_entrance_door" HOME_ENTRANCE_DOOR = "home_entrance_door"
}, },
SCHEDULE = {
TARGET = "schedule_target_pos"
},
CALCULATED = {
TARGET = "calculated_target_pos"
},
WORKPLACE = {
PRIMARY = "workplace_primary",
TOOL = "workplace_tool"
},
OTHER = { OTHER = {
HOME_PLOTMARKER = "home_plotmarker", HOME_PLOTMARKER = "home_plotmarker",
HOME_INSIDE = "home_inside", HOME_INSIDE = "home_inside",
HOME_OUTSIDE = "home_outside" HOME_OUTSIDE = "home_outside"
},
is_primary = function(place_type)
local p1,p2 = string.find(place_type, "primary")
return p1 ~= nil
end,
-- Only works for place types where there is a "primary" and a "shared"
get_alternative = function(place_category, place_type)
local result = {}
local place_types = npc.places.PLACE_TYPE[place_category]
-- Determine search type
local search_shared = false
if npc.places.PLACE_TYPE.is_primary(place_type) then
search_shared = true
end
for key,place_type in pairs(place_types) do
if search_shared == true then
if npc.places.PLACE_TYPE.is_primary(place_type) == false then
return place_type
end
else
if npc.places.PLACE_TYPE.is_primary(place_type) == true then
return place_type
end
end
end
end
} }
npc.places.USE_STATE = {
USED = "true",
NOT_USED = "false"
} }
function npc.places.add_shared(self, place_name, place_type, pos, access_node) function npc.places.add_shared(self, place_name, place_type, pos, access_node)
-- Set metadata of node
local meta = minetest.get_meta(pos)
if not meta:get_string("advanced_npc:used") then
meta:set_string("advanced_npc:used", npc.places.USE_STATE.NOT_USED)
end
meta:set_string("advanced_npc:owner", "")
-- This avoid lags
meta:mark_as_private({"advanced_npc:used", "advanced_npc:owner"})
self.places_map[place_name] = {type=place_type, pos=pos, access_node=access_node or pos, status="shared"} self.places_map[place_name] = {type=place_type, pos=pos, access_node=access_node or pos, status="shared"}
end end
function npc.places.add_owned(self, place_name, place_type, pos, access_node) function npc.places.add_owned(self, place_name, place_type, pos, access_node)
-- Set metadata of node
local meta = minetest.get_meta(pos)
if not meta:get_string("advanced_npc:used") then
meta:set_string("advanced_npc:used", npc.places.USE_STATE.NOT_USED)
end
meta:set_string("advanced_npc:owner", self.npc_id)
-- This avoid lags
meta:mark_as_private({"advanced_npc:used", "advanced_npc:owner"})
self.places_map[place_name] = {type=place_type, pos=pos, access_node=access_node or pos, status="owned"} self.places_map[place_name] = {type=place_type, pos=pos, access_node=access_node or pos, status="owned"}
end end
function npc.places.add_unowned_accessible_place(self, nodes, place_type) function npc.places.add_owned_accessible_place(self, nodes, place_type, walkables)
for i = 1, #nodes do for i = 1, #nodes do
-- Check if node has owner -- Check if node has owner
if nodes[i].owner == "" then local owner = minetest.get_meta(nodes[i].node_pos):get_string("advanced_npc:owner")
--minetest.log("Condition: "..dump(owner == ""))
if owner == "" then
-- If node has no owner, check if it is accessible -- If node has no owner, check if it is accessible
local empty_nodes = npc.places.find_node_orthogonally( local empty_nodes = npc.places.find_orthogonal_accessible_node(
nodes[i].node_pos, {"air"}, 0) nodes[i].node_pos, nil, 0, true, walkables)
-- Check if node is accessible -- Check if node is accessible
if #empty_nodes > 0 then if #empty_nodes > 0 then
-- Set owner to this NPC -- Set owner to this NPC
@ -109,33 +187,121 @@ function npc.places.add_unowned_accessible_place(self, nodes, place_type)
end end
end end
function npc.places.add_shared_accessible_place(self, nodes, place_type) -- Override flag allows to overwrite a place in the places_map.
-- The only valid use right now is for schedules - don't use this
-- anywhere else unless you have a position that changes over time.
function npc.places.add_shared_accessible_place(self, nodes, place_type, override, walkables)
if not override or (override and override == false) then
for i = 1, #nodes do for i = 1, #nodes do
-- Check of not adding same owned sit -- Check if not adding same owned place
if nodes[i].owner ~= self.npc_id then if nodes[i].owner ~= self.npc_id then
-- Check if it is accessible -- Check if it is accessible
local empty_nodes = npc.places.find_node_orthogonally( local empty_nodes = npc.places.find_orthogonal_accessible_node(
nodes[i].node_pos, {"air"}, 0) nodes[i].node_pos, nil, 0, true, walkables)
-- Check if bed is accessible -- Check if node is accessible
if #empty_nodes > 0 then if #empty_nodes > 0 then
-- Assign node to NPC -- Assign node to NPC
npc.places.add_shared(self, place_type..dump(i), npc.places.add_shared(self, place_type..dump(i),
place_type, nodes[i].node_pos, empty_nodes[1].pos) place_type, nodes[i].node_pos, empty_nodes[1].pos)
else
npc.log("WARNING", "Found non-accessible place at pos: "..minetest.pos_to_string(nodes[i].node_pos))
end end
end end
end end
elseif override == true then
-- Note: Nodes is only *one* node in case override = true
-- Check if it is accessible
local empty_nodes = npc.places.find_orthogonal_accessible_node(
nodes.node_pos, nil, 0, true, walkables)
-- Check if node is accessible
if #empty_nodes > 0 then
-- Nodes is only one node
npc.places.add_shared(self, place_type, place_type,
nodes.node_pos, empty_nodes[1].pos)
end
end
end end
function npc.places.get_by_type(self, place_type) function npc.places.mark_place_used(pos, value)
local meta = minetest.get_meta(pos)
local used = meta:get_string("advanced_npc:used")
if value == used then
npc.log("WARNING", "Attempted to set 'used' property of node at "
..minetest.pos_to_string(pos).." to the same value: '"..dump(value).."'")
return false
else
meta:set_string("advanced_npc:used", value)
npc.log("DEBUG", "'Used' value at pos "..minetest.pos_to_string(pos)..": "..dump(meta:get_string("advanced_npc:used")))
return true
end
end
-- This function is to find an alternative place if the original is
-- not usable. If the original place is a "primary" place, it will
-- try to find a "shared" place. If it is a "shared" place, it will try
-- to find a "primary" place. If none is found, it retuns the given type.
function npc.places.find_unused_place(self, place_category, place_type, original_place)
local result = {} local result = {}
for place_name, place_entry in pairs(self.places_map) do -- Check if node is being used
if place_entry.type == place_type then local meta = minetest.get_meta(original_place.pos)
local used = meta:get_string("advanced_npc:used")
if used == npc.places.USE_STATE.USED then
-- Node is being used, try to find alternative
local alternative_place_type = npc.places.PLACE_TYPE.get_alternative(place_category, place_type)
--minetest.log("Alternative place type: "..dump(alternative_place_type))
local alternative_places = npc.places.get_by_type(self, alternative_place_type)
--minetest.log("Alternatives: "..dump(alternative_places))
for i = 1, #alternative_places do
meta = minetest.get_meta(alternative_places[i].pos)
local used = meta:get_string("advanced_npc:used")
if used == npc.places.USE_STATE.NOT_USED then
return alternative_places[i]
end
end
else
result = original_place
end
return result
end
function npc.places.get_by_type(self, place_type, exact_match)
local result = {}
for _, place_entry in pairs(self.places_map) do
-- Check if place_type matches any stored place
-- local condition = false
-- if exact_match then
-- -- If no exact match, search if place_type is contained
-- if exact_match == false then
-- local s, _ = string.find(place_entry.type, place_type)
-- if s ~= nil then
-- condition = true
-- end
-- else
-- -- Exact match
-- if place_entry.type == place_type then
-- condition = true
-- end
-- end
-- end
-- if condition == true
local s, _ = string.find(place_entry.type, place_type)
--minetest.log("place_entry: "..dump(place_entry))
--minetest.log("place_type: "..dump(place_type))
--minetest.log("S: "..dump(s))
if s ~= nil then
table.insert(result, place_entry) table.insert(result, place_entry)
end end
end end
return result return result
end end
---------------------------------------------------------------------------------------
-- Utility functions
---------------------------------------------------------------------------------------
-- The following are utility functions that are used to operate on nodes for
-- specific conditions
-- This function searches on a squared are of the given radius -- This function searches on a squared are of the given radius
-- for nodes of the given type. The type should be npc.places.nodes -- for nodes of the given type. The type should be npc.places.nodes
function npc.places.find_node_nearby(pos, type, radius) function npc.places.find_node_nearby(pos, type, radius)
@ -150,6 +316,12 @@ end
-- TODO: This function can be improved to support a radius greater than 1. -- TODO: This function can be improved to support a radius greater than 1.
function npc.places.find_node_orthogonally(pos, nodes, y_adjustment) function npc.places.find_node_orthogonally(pos, nodes, y_adjustment)
-- Call the more generic function with appropriate params
return npc.places.find_orthogonal_accessible_node(pos, nodes, y_adjustment, nil, nil)
end
-- TODO: This function can be improved to support a radius greater than 1.
function npc.places.find_orthogonal_accessible_node(pos, nodes, y_adjustment, include_walkables, extra_walkables)
-- Calculate orthogonal points -- Calculate orthogonal points
local points = {} local points = {}
table.insert(points, {x=pos.x+1,y=pos.y+y_adjustment,z=pos.z}) table.insert(points, {x=pos.x+1,y=pos.y+y_adjustment,z=pos.z})
@ -159,28 +331,272 @@ function npc.places.find_node_orthogonally(pos, nodes, y_adjustment)
local result = {} local result = {}
for _,point in pairs(points) do for _,point in pairs(points) do
local node = minetest.get_node(point) local node = minetest.get_node(point)
--minetest.log("Found node: "..dump(node)..", at pos: "..dump(point)) -- Search for specific node names
if nodes then
for _,node_name in pairs(nodes) do for _,node_name in pairs(nodes) do
if node.name == node_name then if node.name == node_name then
table.insert(result, {name=node.name, pos=point, param2=node.param2}) table.insert(result, {name=node.name, pos=point, param2=node.param2})
end end
end end
else
-- Search for air, walkable nodes, or any node availble in the extra_walkables array
if node.name == "air"
or (include_walkables == true
and minetest.registered_nodes[node.name].walkable == false
and minetest.registered_nodes[node.name].groups.fence ~= 1)
or (extra_walkables and npc.utils.array_contains(extra_walkables, node.name)) then
table.insert(result, {name=node.name, pos=point, param2=node.param2})
end
end
end end
return result return result
end end
-- Wrapper around minetest.find_nodes_in_area()
-- TODO: Verify if this wrapper is actually needed
function npc.places.find_node_in_area(start_pos, end_pos, type) function npc.places.find_node_in_area(start_pos, end_pos, type)
local nodes = minetest.find_nodes_in_area(start_pos, end_pos, type) local nodes = minetest.find_nodes_in_area(start_pos, end_pos, type)
return nodes return nodes
end end
-- Function used to filter all nodes in the first floor of a building
-- If floor height isn't given, it will assume 2
-- Notice that nodes is an array of entries {node_pos={}, type={}}
function npc.places.filter_first_floor_nodes(nodes, ground_pos, floor_height)
local height = floor_height or 2
local result = {}
for _,node in pairs(nodes) do
if node.node_pos.y <= ground_pos.y + height then
table.insert(result, node)
end
end
return result
end
-- Creates an array of {pos=<node_pos>, owner=''} for managing
-- which NPC owns what
function npc.places.get_nodes_by_type(start_pos, end_pos, type)
local result = {}
local nodes = npc.places.find_node_in_area(start_pos, end_pos, type)
--minetest.log("Found "..dump(#nodes).." nodes of type: "..dump(type))
for _,node_pos in pairs(nodes) do
local entry = {}
entry["node_pos"] = node_pos
entry["owner"] = ''
table.insert(result, entry)
end
return result
end
-- Function to get mg_villages building data
if minetest.get_modpath("mg_villages") ~= nil then
function npc.places.get_mg_villages_building_data(pos)
local result = {
village_id = "",
plot_nr = -1,
building_data = {},
building_type = "",
}
local meta = minetest.get_meta(pos)
result.plot_nr = meta:get_int("plot_nr")
result.village_id = meta:get_string("village_id")
-- Get building data
if mg_villages.get_plot_and_building_data then
local all_data = mg_villages.get_plot_and_building_data(result.village_id, result.plot_nr)
result.building_data = all_data.building_data
result.building_type = result.building_data.typ
result["building_pos_data"] = all_data.bpos
else
-- Following line from mg_villages mod, protection.lua
local btype = mg_villages.all_villages[result.village_id].to_add_data.bpos[result.plot_nr].btype
result.building_data = mg_villages.BUILDINGS[btype]
result.building_type = result.building_data.typ
end
return result
end
-- Pre-requisite: only run this function on mg_villages:plotmarker that has been adapted
-- by using spawner.adapt_mg_villages_plotmarker
function npc.places.get_all_workplaces_from_plotmarker(pos)
local result = {}
local meta = minetest.get_meta(pos)
local pos_data = minetest.deserialize(meta:get_string("building_pos_data"))
if pos_data then
local workplaces = pos_data.workplaces
if workplaces then
-- Insert all workplaces in this plotmarker
for i = 1, #workplaces do
table.insert(result,
{
workplace=workplaces[i],
building_type=meta:get_string("building_type"),
surrounding_workplace = false,
node_pos= {
x=workplaces[i].x,
y=workplaces[i].y,
z=workplaces[i].z
}
})
end
end
end
-- Check the other plotmarkers as well
local nearby_plotmarkers = minetest.deserialize(meta:get_string("nearby_plotmarkers"))
if nearby_plotmarkers then
for i = 1, #nearby_plotmarkers do
if nearby_plotmarkers[i].workplaces then
-- Insert all workplaces in this plotmarker
for j = 1, #nearby_plotmarkers[i].workplaces do
--minetest.log("Nearby plotmarker workplace #"..dump(j)..": "..dump(nearby_plotmarkers[i].workplaces[j]))
table.insert(result, {
workplace=nearby_plotmarkers[i].workplaces[j],
building_type = nearby_plotmarkers[i].building_type,
surrounding_workplace = true,
node_pos = {
x=nearby_plotmarkers[i].workplaces[j].x,
y=nearby_plotmarkers[i].workplaces[j].y,
z=nearby_plotmarkers[i].workplaces[j].z
}
})
end
end
end
end
return result
end
end
-- This function will search for nodes of type plotmarker and,
-- in case of being an mg_villages plotmarker, it will fetch building
-- information and include in result.
function npc.places.find_plotmarkers(pos, radius, exclude_current_pos)
local result = {}
local start_pos = {x=pos.x - radius, y=pos.y - 1, z=pos.z - radius}
local end_pos = {x=pos.x + radius, y=pos.y + 1, z=pos.z + radius}
local nodes = minetest.find_nodes_in_area(start_pos, end_pos,
npc.places.nodes.PLOTMARKER_TYPE)
-- Scan nodes
for i = 1, #nodes do
-- Check if current plotmarker is to be excluded from the list
local exclude = false
if exclude_current_pos then
if pos.x == nodes[i].x and pos.y == nodes[i].y and pos.z == nodes[i].z then
exclude = true
end
end
-- Analyze and include node if not excluded
if not exclude then
local node = minetest.get_node(nodes[i])
local def = {}
def["pos"] = nodes[i]
def["name"] = node.name
if node.name == "mg_villages:plotmarker" and npc.places.get_mg_villages_building_data then
local data = npc.places.get_mg_villages_building_data(nodes[i])
def["plot_nr"] = data.plot_nr
def["village_id"] = data.village_id
def["building_data"] = data.building_data
def["building_type"] = data.building_type
if data.building_pos_data then
def["building_pos_data"] = data.building_pos_data
def["workplaces"] = data.building_pos_data.workplaces
end
end
-- Add building
--minetest.log("Adding building: "..dump(def))
table.insert(result, def)
end
end
return result
end
-- Scans an area for the supported nodes: beds, benches,
-- furnaces, storage (e.g. chests) and openable (e.g. doors).
-- Returns a table with these classifications
function npc.places.scan_area_for_usable_nodes(pos1, pos2)
local result = {
bed_type = {},
sittable_type = {},
furnace_type = {},
storage_type = {},
openable_type = {},
workplace_type = {}
}
local start_pos, end_pos = vector.sort(pos1, pos2)
result.bed_type = npc.places.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.BED_TYPE)
result.sittable_type = npc.places.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.SITTABLE_TYPE)
result.furnace_type = npc.places.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.FURNACE_TYPE)
result.storage_type = npc.places.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.STORAGE_TYPE)
result.openable_type = npc.places.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.OPENABLE_TYPE)
-- Find workplace nodes: if mg_villages:plotmarker is given as start pos, take it from there.
-- If not, search for them.
local node = minetest.get_node(pos1)
if node.name == "mg_villages:plotmarker" then
if npc.places.get_all_workplaces_from_plotmarker then
result.workplace_type = npc.places.get_all_workplaces_from_plotmarker(pos1)
end
else
-- Just search for workplace nodes
-- The search radius is increased by 2
result.workplace_type = npc.places.get_nodes_by_type(
{x=start_pos.x-20, y=start_pos.y, z=start_pos.z-20},
{x=end_pos.x+20, y=end_pos.y, z=end_pos.z+20},
npc.places.nodes.WORKPLACE_TYPE)
-- Find out building type and add it to the result
for i = 1, #result.workplace_type do
local meta = minetest.get_meta(result.workplace_type[i].node_pos)
local building_type = meta:get_string("building_type") or "none"
local surrounding_workplace = meta:get_string("surrounding_workplace") or "false"
result.workplace_type[i]["surrounding_workplace"] = minetest.is_yes(surrounding_workplace)
result.workplace_type[i]["building_type"] = building_type
end
end
return result
end
-- Helper function to clear metadata in an array of nodes
-- Metadata that will be cleared is:
-- advanced_npc:used
-- advanced_npc:owner
local function clear_metadata(nodes)
local c = 0
for i = 1, #nodes do
local meta = minetest.get_meta(nodes[i].node_pos)
meta:set_string("advanced_npc:owner", "")
meta:set_string("advanced_npc:used", npc.places.USE_STATE.NOT_USED)
c = c + 1
end
return c
end
function npc.places.clear_metadata_usable_nodes_in_area(node_data)
local count = 0
count = count + clear_metadata(node_data.bed_type)
count = count + clear_metadata(node_data.sittable_type)
count = count + clear_metadata(node_data.furnace_type)
count = count + clear_metadata(node_data.storage_type)
count = count + clear_metadata(node_data.openable_type)
-- Clear workplace nodes
for i = 1, #node_data.workplace_type do
local meta = minetest.get_meta(node_data.workplace_type[i].node_pos)
meta:set_string("work_data", nil)
count = count + 1
end
return count
end
-- Specialized function to find doors that are an entrance to a building. -- Specialized function to find doors that are an entrance to a building.
-- The definition of an entrance is: -- The definition of an entrance is:
-- The openable node with the shortest path to the plotmarker node -- The openable node with the shortest path to the plotmarker node
-- Based on this definition, other entrances aren't going to be used -- Based on this definition, other entrances aren't going to be used
-- by the NPC to get into the building -- by the NPC to get into the building
function npc.places.find_entrance_from_openable_nodes(all_openable_nodes, marker_pos) function npc.places.find_entrance_from_openable_nodes(all_openable_nodes, marker_pos)
local result = nil local result
local openable_nodes = {} local openable_nodes = {}
local min = 100 local min = 100
@ -191,13 +607,12 @@ function npc.places.find_entrance_from_openable_nodes(all_openable_nodes, marker
-- which NPCs love to confuse with the right building entrance. -- which NPCs love to confuse with the right building entrance.
for i = 1, #all_openable_nodes do for i = 1, #all_openable_nodes do
local name = minetest.get_node(all_openable_nodes[i].node_pos).name local name = minetest.get_node(all_openable_nodes[i].node_pos).name
local doors_st, doors_en = string.find(name, "doors:") local doors_st, _ = string.find(name, "doors:")
if doors_st ~= nil then if doors_st ~= nil then
table.insert(openable_nodes, all_openable_nodes[i]) table.insert(openable_nodes, all_openable_nodes[i])
end end
end end
for i = 1, #openable_nodes do for i = 1, #openable_nodes do
local open_pos = openable_nodes[i].node_pos local open_pos = openable_nodes[i].node_pos
@ -205,25 +620,15 @@ function npc.places.find_entrance_from_openable_nodes(all_openable_nodes, marker
-- Get node name - check if this node is a 'door'. The way to check -- Get node name - check if this node is a 'door'. The way to check
-- is by explicitly checking for 'door' string -- is by explicitly checking for 'door' string
local name = minetest.get_node(open_pos).name local name = minetest.get_node(open_pos).name
local start_i, end_i = string.find(name, "door") local start_i, _ = string.find(name, "door")
if start_i ~= nil then if start_i ~= nil then
-- Define start and end pos -- Define start and end pos
local start_pos = {x=open_pos.x, y=open_pos.y, z=open_pos.z} local start_pos = {x=open_pos.x, y=open_pos.y, z=open_pos.z}
local end_pos = {x=marker_pos.x, y=marker_pos.y, z=marker_pos.z} local end_pos = {x=marker_pos.x, y=marker_pos.y, z=marker_pos.z}
-- Check if there's any difference in vertical position
-- minetest.log("Openable node pos: "..minetest.pos_to_string(open_pos)) -- minetest.log("Openable node pos: "..minetest.pos_to_string(open_pos))
-- minetest.log("Plotmarker node pos: "..minetest.pos_to_string(marker_pos)) -- minetest.log("Plotmarker node pos: "..minetest.pos_to_string(marker_pos))
-- NOTE: Commented out while testing MarkBu's pathfinder
--if start_pos.y ~= end_pos.y then
-- Adjust to make pathfinder find nodes one node above
-- end_pos.y = start_pos.y
--end
-- This adjustment allows the map to be created correctly
--start_pos.y = start_pos.y + 1
--end_pos.y = end_pos.y + 1
-- Find path from the openable node to the plotmarker -- Find path from the openable node to the plotmarker
--local path = pathfinder.find_path(start_pos, end_pos, 20, {}) --local path = pathfinder.find_path(start_pos, end_pos, 20, {})
@ -246,9 +651,9 @@ function npc.places.find_entrance_from_openable_nodes(all_openable_nodes, marker
-- plotmarker, but not being the building entrance. MTG doors -- plotmarker, but not being the building entrance. MTG doors
-- are usually the entrance... so yes, hackity hack. -- are usually the entrance... so yes, hackity hack.
-- Get the name of the currently mininum-distance door -- Get the name of the currently mininum-distance door
min_node_name = minetest.get_node(result.node_pos).name local min_node_name = minetest.get_node(result.node_pos).name
-- Check if this is a door from MTG's doors. -- Check if this is a door from MTG's doors.
local doors_st, doors_en = string.find(name, "doors:") local doors_st, _ = string.find(name, "doors:")
-- Check if min-distance door is a cottages door -- Check if min-distance door is a cottages door
-- while we have a MTG door -- while we have a MTG door
if min_node_name == "cottages:half_door" and doors_st ~= nil then if min_node_name == "cottages:half_door" and doors_st ~= nil then
@ -279,7 +684,7 @@ function npc.places.find_sittable_nodes_nearby(pos, radius)
for i = 1, #nodes do for i = 1, #nodes do
-- Get node name, try to avoid using the staircase check if not a stair node -- Get node name, try to avoid using the staircase check if not a stair node
local node = minetest.get_node(nodes[i]) local node = minetest.get_node(nodes[i])
local i1, i2 = string.find(node.name, "stairs:") local i1, _ = string.find(node.name, "stairs:")
if i1 ~= nil then if i1 ~= nil then
if npc.places.is_in_staircase(nodes[i]) < 1 then if npc.places.is_in_staircase(nodes[i]) < 1 then
table.insert(result, nodes[i]) table.insert(result, nodes[i])
@ -309,7 +714,7 @@ npc.places.staircase = {
function npc.places.is_in_staircase(pos) function npc.places.is_in_staircase(pos)
local node = minetest.get_node(pos) local node = minetest.get_node(pos)
-- Verify node is actually from default stairs mod -- Verify node is actually from default stairs mod
local p1, p2 = string.find(node.name, "stairs:") local p1, _ = string.find(node.name, "stairs:")
if p1 ~= nil then if p1 ~= nil then
-- Calculate the logical position to the lower and upper stairs node location -- Calculate the logical position to the lower and upper stairs node location
local up_x_adj, up_z_adj = 0, 0 local up_x_adj, up_z_adj = 0, 0
@ -339,8 +744,8 @@ function npc.places.is_in_staircase(pos)
local lower_node = minetest.get_node(lower_pos) local lower_node = minetest.get_node(lower_pos)
--minetest.log("Next node: "..dump(upper_pos)) --minetest.log("Next node: "..dump(upper_pos))
-- Check if next node is also a stairs node -- Check if next node is also a stairs node
local up_p1, up_p2 = string.find(upper_node.name, "stairs:") local up_p1, _ = string.find(upper_node.name, "stairs:")
local lo_p1, lo_p2 = string.find(lower_node.name, "stairs:") local lo_p1, _ = string.find(lower_node.name, "stairs:")
if up_p1 ~= nil then if up_p1 ~= nil then
-- By default, think this is bottom of staircase. -- By default, think this is bottom of staircase.
@ -365,39 +770,56 @@ end
-- Specialized function to find the node position right behind -- Specialized function to find the node position right behind
-- a door. Used to make NPCs enter buildings. -- a door. Used to make NPCs enter buildings.
function npc.places.find_node_behind_door(door_pos) function npc.places.find_node_in_front_and_behind_door(door_pos)
local door = minetest.get_node(door_pos) local door = minetest.get_node(door_pos)
if door.param2 == 0 then
-- Looking south local facedir_vector = minetest.facedir_to_dir(door.param2)
return {x=door_pos.x, y=door_pos.y, z=door_pos.z + 1} local back_pos = vector.add(door_pos, facedir_vector)
elseif door.param2 == 1 then local back_node = minetest.get_node(back_pos)
-- Looking east local front_pos
return {x=door_pos.x + 1, y=door_pos.y, z=door_pos.z} if back_node.name == "air" or minetest.registered_nodes[back_node.name].walkable == false then
elseif door.param2 == 2 then -- Door is closed, so back_pos is the actual behind position.
-- Looking north -- Calculate front node
return {x=door_pos.x, y=door_pos.y, z=door_pos.z - 1} front_pos = vector.add(door_pos, vector.multiply(facedir_vector, -1))
-- Looking west else
elseif door.param2 == 3 then -- Door is open, need to find the front and back pos
return {x=door_pos.x - 1, y=door_pos.y, z=door_pos.z} facedir_vector = minetest.facedir_to_dir((door.param2 + 2) % 4)
back_pos = vector.add(door_pos, facedir_vector)
front_pos = vector.add(door_pos, vector.multiply(facedir_vector, -1))
end end
return back_pos, front_pos
-- if door.param2 == 0 then
-- -- Looking south
-- return {x=door_pos.x, y=door_pos.y, z=door_pos.z + 1}
-- elseif door.param2 == 1 then
-- -- Looking east
-- return {x=door_pos.x + 1, y=door_pos.y, z=door_pos.z}
-- elseif door.param2 == 2 then
-- -- Looking north
-- return {x=door_pos.x, y=door_pos.y, z=door_pos.z - 1}
-- -- Looking west
-- elseif door.param2 == 3 then
-- return {x=door_pos.x - 1, y=door_pos.y, z=door_pos.z}
-- end
end end
-- Specialized function to find the node position right in -- Specialized function to find the node position right in
-- front of a door. Used to make NPCs exit buildings. -- front of a door. Used to make NPCs exit buildings.
function npc.places.find_node_in_front_of_door(door_pos) --function npc.places.find_node_in_front_of_door(door_pos)
local door = minetest.get_node(door_pos) -- local door = minetest.get_node(door_pos)
--minetest.log("Param2 of door: "..dump(door.param2)) -- --minetest.log("Param2 of door: "..dump(door.param2))
if door.param2 == 0 then -- if door.param2 == 0 then
-- Looking south -- -- Looking south
return {x=door_pos.x, y=door_pos.y, z=door_pos.z - 1} -- return {x=door_pos.x, y=door_pos.y, z=door_pos.z - 1}
elseif door.param2 == 1 then -- elseif door.param2 == 1 then
-- Looking east -- -- Looking east
return {x=door_pos.x - 1, y=door_pos.y, z=door_pos.z} -- return {x=door_pos.x - 1, y=door_pos.y, z=door_pos.z}
elseif door.param2 == 2 then -- elseif door.param2 == 2 then
-- Looking north -- -- Looking north
return {x=door_pos.x, y=door_pos.y, z=door_pos.z + 1} -- return {x=door_pos.x, y=door_pos.y, z=door_pos.z + 1}
-- Looking west -- elseif door.param2 == 3 then
elseif door.param2 == 3 then -- -- Looking west
return {x=door_pos.x + 1, y=door_pos.y, z=door_pos.z} -- return {x=door_pos.x + 1, y=door_pos.y, z=door_pos.z}
end -- end
end --end

127
data/dialogues_data.lua Normal file
View 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
View 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
View File

View 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)

View 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)

View 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)

View 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 doesnt know it. Those who speak of love most promiscuously are the ones whove never felt it. \nThey make some sort of feeble stew out of sympathy, compassion, contempt and general indifference, and they call it love. \nOnce youve felt what it means to love, the total passion for the total height—youre incapable of anything less.",
[31] = "If you learn how to rule one single mans soul, you can get the rest of mankind. \nIts the soul, not whips or swords or fire or guns. \nThats why the Caesars, the Attilas, the Napoleons were fools and did not last. \nThe soul, is that which cant 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 cant 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)

View File

@ -1,20 +1,6 @@
-------------------------------------------------------------------------------------
-- NPC dialogue code by Zorman2000 -- 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 = {} npc.dialogue = {}
@ -37,14 +23,215 @@ npc.dialogue.dialogue_results = {
yes_no_dialogue = {} 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 -- Dialogue box definitions
-- The dialogue boxes are used for the player to interact with the -- The dialogue boxes are used for the player to interact with the
-- NPC in dialogues. -- NPC in dialogues.
--------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------
-- Creates and shows a multi-option dialogue based on the number of responses -- Creates and shows a multi-option dialogue based on the number of responses
-- that the dialogue object contains -- that the dialogue object contains
function npc.dialogue.show_options_dialogue(self, function npc.dialogue.show_options_dialogue(self,
dialogue_key,
dialogue, dialogue,
dismiss_option_label, dismiss_option_label,
player_name) player_name)
@ -67,10 +254,12 @@ function npc.dialogue.show_options_dialogue(self,
-- Create entry on options_dialogue table -- Create entry on options_dialogue table
npc.dialogue.dialogue_results.options_dialogue[player_name] = { npc.dialogue.dialogue_results.options_dialogue[player_name] = {
npc = self, npc = self,
is_married_dialogue = (dialogue.dialogue_type == npc.dialogue.dialogue_type.married), dialogue = dialogue,
is_casual_trade_dialogue = (dialogue.dialogue_type == npc.dialogue.dialogue_type.casual_trade), dialogue_key = dialogue_key,
is_dedicated_trade_dialogue = (dialogue.dialogue_type == npc.dialogue.dialogue_type.dedicated_trade), is_married_dialogue =
is_custom_trade_dialogue = (dialogue.dialogue_type == npc.dialogue.dialogue_type.custom_trade), (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, casual_trade_type = dialogue.casual_trade_type,
options = responses options = responses
} }
@ -104,74 +293,66 @@ function npc.dialogue.show_yes_no_dialogue(self,
minetest.show_formspec(player_name, "advanced_npc:yes_no", formspec) minetest.show_formspec(player_name, "advanced_npc:yes_no", formspec)
end end
--------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------
-- Dialogue methods -- 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 -- Select random dialogue objects for an NPC based on sex
-- and the relationship phase with player -- 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 = { local result = {
normal = {}, normal = {},
hints = {} hints = {}
} }
local dialogues = npc.data.DIALOGUES.female local phase_tag = "phase1"
if sex == npc.MALE then if phase then
dialogues = npc.data.DIALOGUES.male phase_tag = phase
end 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 -- Determine how many dialogue lines the NPC will have
local number_of_dialogues = math.random(npc.dialogue.MIN_DIALOGUES, npc.dialogue.MAX_DIALOGUES) local number_of_dialogues = math.random(npc.dialogue.MIN_DIALOGUES, npc.dialogue.MAX_DIALOGUES)
for i = 1, number_of_dialogues do for i = 1, number_of_dialogues do
local dialogue_id = math.random(1, #dialogues) local key_id = math.random(1, #keys)
result.normal[i] = dialogues[dialogue_id] result.normal[i] = keys[key_id]
npc.log("DEBUG", "Adding dialogue: "..dump(dialogues[keys[key_id]]))
set_response_ids_recursively(result.normal[i], 0, dialogue_id)
end end
-- Add item hints. -- Add item hints.
-- Favorite items
for i = 1, 2 do for i = 1, 2 do
result.hints[i] = {} local hints = npc.relationships.get_dialogues_for_gift_item(
result.hints[i].text = self.gift_data.favorite_items["fav"..tostring(i)],
npc.relationships.get_hint_for_favorite_item(favorite_items["fav"..tostring(i)], sex, phase) 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 end
-- Disliked items
for i = 3, 4 do for i = 3, 4 do
result.hints[i] = {} local hints = npc.relationships.get_dialogues_for_gift_item(
result.hints[i].text = self.gift_data.disliked_items["dis"..tostring(i-2)],
npc.relationships.get_hint_for_disliked_item(disliked_items["dis"..tostring(i-2)], sex) 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 end
npc.log("DEBUG", "Dialogue results:"..dump(result))
return result return result
end end
@ -181,7 +362,10 @@ function npc.dialogue.create_custom_trade_options(self, player)
-- Create the action for each option -- Create the action for each option
local actions = {} local actions = {}
for i = 1, #self.trader_data.custom_trades do 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) table.insert(actions,
function()
npc.trade.show_custom_trade_offer(self, player, self.trader_data.custom_trades[i])
end)
end end
-- Default text to be shown for dialogue prompt -- Default text to be shown for dialogue prompt
local text = npc.trade.CUSTOM_TRADES_PROMPT_TEXT local text = npc.trade.CUSTOM_TRADES_PROMPT_TEXT
@ -218,21 +402,26 @@ function npc.dialogue.start_dialogue(self, player, show_married_dialogue)
-- Show options dialogue for dedicated trader -- Show options dialogue for dedicated trader
if self.trader_data.trader_status == npc.trade.TRADER then if self.trader_data.trader_status == npc.trade.TRADER then
dialogue = npc.trade.DEDICATED_TRADER_PROMPT dialogue = npc.dialogue.get_cached_dialogue_key(npc.dialogue.cache_keys.DEDICATED_TRADER_DIALOGUE)
npc.dialogue.process_dialogue(self, dialogue, player:get_player_name()) npc.dialogue.process_dialogue(self, dialogue, player:get_player_name())
return return
end end
local chance = math.random(1, 100) local chance = math.random(1, 100)
minetest.log("Chance: "..dump(chance)) --minetest.log("Chance: "..dump(chance))
if chance < 30 then if chance < 30 then
-- If NPC is a casual trader, show a sell or buy dialogue 30% of the time, depending -- Show trading options for casual traders
-- on the state of the casual trader. -- 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 if self.trader_data.trader_status == npc.trade.NONE then
-- Show custom trade options if available -- Show custom trade options if available
if table.getn(self.trader_data.custom_trades) > 0 then if table.getn(self.trader_data.custom_trades) > 0 then
-- Show custom trade options -- Show custom trade options
dialogue = npc.dialogue.create_custom_trade_options(self, player) 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 end
elseif self.trader_data.trader_status == npc.trade.CASUAL then elseif self.trader_data.trader_status == npc.trade.CASUAL then
local max_trade_chance = 2 local max_trade_chance = 2
@ -243,10 +432,10 @@ function npc.dialogue.start_dialogue(self, player, show_married_dialogue)
local trade_chance = math.random(1, max_trade_chance) local trade_chance = math.random(1, max_trade_chance)
if trade_chance == 1 then if trade_chance == 1 then
-- Show casual buy dialogue -- Show casual buy dialogue
dialogue = npc.trade.CASUAL_TRADE_BUY_DIALOGUE dialogue = npc.dialogue.get_cached_dialogue_key(npc.dialogue.cache_keys.CASUAL_BUY_DIALOGUE)
elseif trade_chance == 2 then elseif trade_chance == 2 then
-- Show casual sell dialogue -- Show casual sell dialogue
dialogue = npc.trade.CASUAL_TRADE_SELL_DIALOGUE dialogue = npc.dialogue.get_cached_dialogue_key(npc.dialogue.cache_keys.CASUAL_SELL_DIALOGUE)
elseif trade_chance == 3 then elseif trade_chance == 3 then
-- Show custom trade options -- Show custom trade options
dialogue = npc.dialogue.create_custom_trade_options(self, player) dialogue = npc.dialogue.create_custom_trade_options(self, player)
@ -256,8 +445,15 @@ function npc.dialogue.start_dialogue(self, player, show_married_dialogue)
-- 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)] dialogue = self.dialogues.normal[math.random(1, #self.dialogues.normal)]
elseif chance >= 90 then elseif chance >= 90 then
-- 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 -- Choose a random dialogue line from the favorite/disliked item hints
dialogue = self.dialogues.hints[math.random(1, 4)] 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 end
local dialogue_result = npc.dialogue.process_dialogue(self, dialogue, player:get_player_name()) local dialogue_result = npc.dialogue.process_dialogue(self, dialogue, player:get_player_name())
@ -270,10 +466,17 @@ end
-- This function processes a dialogue object and performs -- 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) function npc.dialogue.process_dialogue(self, dialogue, player_name)
-- Freeze NPC actions -- Freeze NPC actions
npc.lock_actions(self) npc.lock_actions(self)
local dialogue_key = -1
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 -- Check if this dialogue has a flag definition
if dialogue.flag then if dialogue.flag then
-- Check if the NPC has this flag -- Check if the NPC has this flag
@ -309,6 +512,7 @@ function npc.dialogue.process_dialogue(self, dialogue, player_name)
if dialogue.responses then if dialogue.responses then
npc.dialogue.show_options_dialogue( npc.dialogue.show_options_dialogue(
self, self,
dialogue_key,
dialogue, dialogue,
npc.dialogue.NEGATIVE_ANSWER_LABEL, npc.dialogue.NEGATIVE_ANSWER_LABEL,
player_name player_name
@ -380,13 +584,13 @@ local function get_response_object_by_id_recursive(dialogue, current_depth, resp
elseif dialogue.responses ~= nil then elseif dialogue.responses ~= nil then
-- Get current depth and response ID -- Get current depth and response ID
local d_i1, d_i2 = string.find(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))) --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 depth = tonumber(string.sub(response_id, 0, d_i1-1))
local id = tonumber(string.sub(response_id, d_i2 + 1)) local id = tonumber(string.sub(response_id, d_i2 + 1))
minetest.log("Depth: "..dump(depth)..", id: "..dump(id)) --minetest.log("Depth: "..dump(depth)..", id: "..dump(id))
-- Check each response -- Check each response
for key,value in ipairs(dialogue.responses) do for key,value in ipairs(dialogue.responses) do
minetest.log("Key: "..dump(key)..", value: "..dump(value)..", comp1: "..dump(current_depth == depth)) --minetest.log("Key: "..dump(key)..", value: "..dump(value)..", comp1: "..dump(current_depth == depth))
if value.action_type == "function" then if value.action_type == "function" then
-- Check if we are on correct response and correct depth -- Check if we are on correct response and correct depth
if current_depth == depth then if current_depth == depth then
@ -395,7 +599,7 @@ local function get_response_object_by_id_recursive(dialogue, current_depth, resp
end end
end end
else else
minetest.log("Entering again...") --minetest.log("Entering again...")
-- We have a dialogue action type. Need to check if dialogue has further responses -- We have a dialogue action type. Need to check if dialogue has further responses
if value.action.responses ~= nil then if value.action.responses ~= nil then
local response = get_response_object_by_id_recursive(value.action, current_depth + 1, response_id) local response = get_response_object_by_id_recursive(value.action, current_depth + 1, response_id)
@ -462,51 +666,21 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
npc.relationships.MARRIED_NPC_DIALOGUE npc.relationships.MARRIED_NPC_DIALOGUE
.responses[player_response.options[i].response_id] .responses[player_response.options[i].response_id]
.action(player_response.npc, player) .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 elseif player_response.is_custom_trade_dialogue == true then
-- Functions for a custom trade should be available from the same dialogue -- Functions for a custom trade should be available from the same dialogue
-- object as it is created in memory -- object as they are created on demand
minetest.log("Player response: "..dump(player_response.options[i])) minetest.log("Player response: "..dump(player_response.options[i]))
player_response.options[i].action(player_response.npc, player) player_response.options[i].action(player_response.npc, player)
else else
-- Get dialogues for sex and phase -- Get dialogue from registered dialogues
local dialogues = npc.data.DIALOGUES[player_response.npc.sex][phase] 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 -- Execute function
response.action(player_response.npc, player) 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. -- Unlock queue, reset action timer and unfreeze NPC.
npc.unlock_actions(player_response.npc) npc.unlock_actions(player_response.npc)
end end
end end
return return

78
doc/actions_and_tasks.md Normal file
View 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
View 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.
})

90
doc/new_api.md Normal file
View File

@ -0,0 +1,90 @@
Advanced NPC 1.0 proposal
-------------------------
While Advanced NPC provides functionality and a level of intelligence that no other mob mod can, it is still limited in some features and to its ultimate purpose of creating functional towns and/or simulated communities. The following are the areas that has been identified as lacking:
- Idle/wandering
- When NPCs aren't executing actions, their movement is very dumb. They wander aimlessly, constantly and usually bump into obstacles and keep walking nevertheless. They get stuck at places they shouldn't.
- Relationships
- Relationships are very hardcoded, and there's no flexibility on them.
- Unable to add more functionality
- All actions are hardcoded. While the essentials are in, making a NPC operate another node that is not a furnace/chest/door is almost impossible. If a mod adds a node and wants NPC to be able to operate it, it is certainly very hard.
- More randomness in schedules
- While schedules are all about making NPCs do actions at certain times, it is not flexible enough to make it look more realistic. One morning a NPC can get up and make breakfast or not, put some music on a music player or not, go outside their home and wander around, etc.
- Unable to react to certain triggers
- When NPCs are punched, `mobs_redo` takes over and controls the NPC. Also, NPCs are unable to scan an area for certain things and perform actions continually based on it.
The above are all playability issues and deficiencies. Some technical issues has to be addressed as well regarding the API. Given all these, the following is a proposal to move the mod towards the correct direction.
##Proposed changes:
- Unify the actions/tasks/schedule property change/schedule query API into a `commands` API
- Add new commands to bring the NPC interaction level closer to that of a player
- Rename `flags` to `properties`
- Allow registering scripts, or collections of commands for external mods to provide extra functionality
Unified Commands API
--------------------
The goal of this API is to provide consistency and extensibility to the actions a NPC can perform. First of all, rename actions/tasks/property change/query to `commands`. Each command will have the following properties that determines how it is to be executed and what it does:
- Type: specifies the type of command. The following are valid types:
- `instruction`: Used for fundamental, atomic operations. This type maps directly to what are called now `actions`, which are for example, walk one step, dig, place, etc.
- `control`: Used for specific commands that are flow control statements. Example: If-else, for loops. The conditional statements is a Lua boolean expression.
- `script`: Used for collections of commands, executed on a sequential structure. This type maps directly to `tasks`.
- Execution: specifies how the command is to be executed. The following are valid valuesf for this parameter:
- `immediate`: Will execute this command immediately, without any enqueing. Very little commands should be able to do this. The `control` commands should be executed immediately as they need to enqueue certain commands depending on their conditions.
- `default`: Command will be enqueued and executed on the global command timer call.
- Interruptable: specifies whether the global command timer and/or the scheduler can interrupt the command. Boolean value, can be set to false or true.
- _Important_: Non-interruptable commands should be able to finish by themselves. The API will execute the default command once a non-interruptable command is done and if it doesn't executes another command.
- Parameters: a Lua table with all the parameters that the command requires. Depending on the type, some parameters are required. Below is a list of required parameters per type:
- `instruction`: Requires just the parameters required by the instruction to execute.
- `control`: Requires different parameters depending on the type of control.
- Required:
- `condition`: The condition to be evaluated. This is a Lua boolean expression.
- `match_commands`: A Lua array with the commands to be executed if condition evaluates to `true`.
- Dependent on type:
- `operation`: Only required in `for-loop` command. Operation to execute on the loop variable (e.g. increase/decrease)
- `repetition`: Optional for `for-loop` command. Can't be used together with `max` and `min`.
- `max`: Optional for `for-loop` command. Can't be used together with `repetition`. Requires `min`. Randomizes a loop execution and sets the upper bound of how many times the loop will execute.
- `min`: Optional for `for-loop` command. Can't be used together with `repetition`. Requires `max`. Randomizes a loop execution and sets the lower bound of how many times the loop will execute.
- `else_commands`: Only required in `if-else` command. A Lua array with the commands to be executed if condition evaluates to `false`.
- `script`: A Lua array of commands to execute, in order
The following `instruction` commands will be added to the default set:
- `do_punch`: Executes the `on_punch` function of a node, object or player
- `do_rightclick`: Executes the `on_rightclick` function of a node, object or player
- `set_property`: Sets the value of a variable in the `self.properties` object. If the variable doesn't exists, it is created. This command is executed immediately and is not enqueued.
- Parameters:
- `key`: The property key-name. This is a variable in the `self.properties` object
- `value`: The property value.
- `get_property`: Returns the value of a given property. This command is executed immediately and is not enqueued.
- Parameters:
- `key`: The property key-name.
- `set_internal_property`: Sets the value of a limited set of internal properties related to the NPC trading and personality variables.
- `get_internal_property`: Gets the value of a limited set of internal properties related to the NPC trading and personality variables.
- `add_item_to_npc`: Adds an item to the NPC inventory, without any specific source.
- `remove_item_from_npc`: Removes a specific item from the NPC inventory.
- `query`: Executes a query for nodes or objects. Returns a Lua table with none, single or many positions.
The following `control` commands will be added to the default set:
- `if-else`: An if-else control statement that will execute immediately. It will evaluate the given `condition` parameter and execute commands depending on the evaluation of the `condition`.
- Parameters:
- `condition`: A Lua boolean expression to be evaluated.
- `true-commands`: A Lua array of commands to be executed if `condition` evaluates to `true`.
- `else-commands`: A Lua array of commands to be executed if `condition` evaluates to `false`.
- `loop`: A flexible loop command. Supports for-loop and while-loops. The amount of loops done will be available in `npc.commands.current_loop_count`. Executes immediately, it is not enqueued.
- Parameters:
##Extensibility
Once the above commands has been added, it is possible to safely build scripts which don't touch directly many of the internal NPC mechanisms. An API will be provided for external mods to register scripts that let NPCs perform actions related to those mods, e.g. operating a node provided by the mod. The API for this will be:
`npc.commands.register_script(name, script)`
All registered scripts have the following properties:
- They are interruptable by the command queue/scheduler
- They are not immediately executed
The `script` parameter is a Lua array of commands that will be executed when the script is executed.

View File

@ -24,6 +24,7 @@ end
mobs.intllib = S mobs.intllib = S
dofile(path .. "/npc.lua") dofile(path .. "/npc.lua")
dofile(path .. "/utils.lua")
dofile(path .. "/spawner.lua") dofile(path .. "/spawner.lua")
dofile(path .. "/relationships.lua") dofile(path .. "/relationships.lua")
dofile(path .. "/dialogue.lua") dofile(path .. "/dialogue.lua")
@ -33,6 +34,15 @@ dofile(path .. "/actions/actions.lua")
dofile(path .. "/actions/places.lua") dofile(path .. "/actions/places.lua")
dofile(path .. "/actions/pathfinder.lua") dofile(path .. "/actions/pathfinder.lua")
dofile(path .. "/actions/node_registry.lua") dofile(path .. "/actions/node_registry.lua")
dofile(path .. "/occupations/occupations.lua")
-- Load random data definitions
dofile(path .. "/random_data.lua") 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")) print (S("[Mod] Advanced NPC loaded"))

875
npc.lua

File diff suppressed because it is too large Load Diff

468
occupations/occupations.lua Normal file
View 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

View File

@ -374,7 +374,7 @@ npc.data.FIRST_NAMES = {
"Arianne", "Arianne",
"Lizzy", "Lizzy",
"Amy", "Amy",
"Chole", "Chloe",
"Alisson" "Alisson"
}, },
male = { male = {
@ -396,8 +396,7 @@ npc.FAVORITE_ITEMS = {
female = {}, female = {},
male = {} male = {}
} }
-- Define items by phase
-- Female
npc.FAVORITE_ITEMS.female["phase1"] = { npc.FAVORITE_ITEMS.female["phase1"] = {
{item = "default:apple", {item = "default:apple",
response = "Hey, I really wanted an apple, thank you!", 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..."}, hint = "I could really do with an apple..."},
{item = "farming:bread", {item = "farming:bread",
response = "Thanks, you didn't have to, but thanks...", 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"] = { npc.FAVORITE_ITEMS.female["phase5"] = {
{item = "default:apple", {item = "default:apple",

View File

@ -38,6 +38,40 @@ npc.relationships.RELATIONSHIP_PHASE["phase3"] = {limit = 45}
npc.relationships.RELATIONSHIP_PHASE["phase4"] = {limit = 70} npc.relationships.RELATIONSHIP_PHASE["phase4"] = {limit = 70}
npc.relationships.RELATIONSHIP_PHASE["phase5"] = {limit = 100} npc.relationships.RELATIONSHIP_PHASE["phase5"] = {limit = 100}
npc.relationships.GIFT_ITEM_LIKED = "liked"
npc.relationships.GIFT_ITEM_DISLIKED = "disliked"
npc.relationships.GIFT_ITEM_HINT = "hint"
npc.relationships.GIFT_ITEM_RESPONSE = "response"
-- Favorite and disliked items tables
npc.relationships.gift_items = {
liked = {
female = {
["phase1"] = {},
["phase2"] = {},
["phase3"] = {},
["phase4"] = {},
["phase5"] = {},
["phase6"] = {}
},
male = {
["phase1"] = {},
["phase2"] = {},
["phase3"] = {},
["phase4"] = {},
["phase5"] = {},
["phase6"] = {}
}
},
disliked = {
female = {},
male = {}
}
}
npc.relationships.DEFAULT_RESPONSE_NO_GIFT_RECEIVE =
"Thank you, but I don't need anything for now."
-- Married NPC dialogue definition -- Married NPC dialogue definition
npc.relationships.MARRIED_NPC_DIALOGUE = { npc.relationships.MARRIED_NPC_DIALOGUE = {
text = "Hi darling!", text = "Hi darling!",
@ -62,7 +96,7 @@ npc.relationships.MARRIED_NPC_DIALOGUE = {
end end
}, },
[3] = { [3] = {
text = "Come with me, please!", text = "Please, come with me!",
action_type = "function", action_type = "function",
response_id = 3, response_id = 3,
action = function(self, player) action = function(self, player)
@ -90,6 +124,96 @@ function npc.relationships.get_relationship_phase_by_points(points)
end end
end end
-- Registration functions
-----------------------------------------------------------------------------
-- Items can be registered to be part of the gift system using the
-- below function. The def is the following:
-- {
-- dialogues = {
-- liked = {
-- -- ^ This is an array of the following table:
-- [1] = {dialogue_type="", sex="", text=""}
-- -- ^ dialogue_type: defines is this is a hint or a response.
-- -- valid values are: "hint", "response"
-- -- sex: valid values are: "male", female"
-- -- text: the dialogue text
-- },
-- disliked = {
-- -- ^ This is an array with the same type of tables as above
-- }
-- }
-- }
function npc.relationships.register_favorite_item(item_name, phase, sex, def)
local dialogues = {}
-- Register dialogues based on the hints and responses
-- Liked
for i = 1, #def.hints do
table.insert(dialogues, {
text = def.hints[i],
tags = {phase, item_name, sex,
npc.dialogue.tags.GIFT_ITEM_HINT, npc.dialogue.tags.GIFT_ITEM_LIKED}
})
end
for i = 1, #def.responses do
table.insert(dialogues, {
text = def.responses[i],
tags = {phase, item_name, sex,
npc.dialogue.tags.GIFT_ITEM_RESPONSE, npc.dialogue.tags.GIFT_ITEM_LIKED}
})
end
-- Register all dialogues
for i = 1, #dialogues do
npc.dialogue.register_dialogue(dialogues[i])
end
-- Insert item into table
table.insert(npc.relationships.gift_items.liked[sex][phase], item_name)
end
function npc.relationships.register_disliked_item(item_name, sex, def)
local dialogues = {}
-- Register dialogues based on the hints and responses
-- Liked
for i = 1, #def.hints do
table.insert(dialogues, {
text = def.hints[i],
tags = {item_name, sex,
npc.dialogue.tags.GIFT_ITEM_HINT, npc.dialogue.tags.GIFT_ITEM_UNLIKED}
})
end
for i = 1, #def.responses do
table.insert(dialogues, {
text = def.responses[i],
tags = {item_name, sex,
npc.dialogue.tags.GIFT_ITEM_RESPONSE, npc.dialogue.tags.GIFT_ITEM_UNLIKED}
})
end
-- Register all dialogues
for i = 1, #dialogues do
npc.dialogue.register_dialogue(dialogues[i])
end
-- Insert item into table
table.insert(npc.relationships.gift_items.disliked[sex], item_name)
end
function npc.relationships.get_dialogues_for_gift_item(item_name, dialogue_type, item_type, sex, phase)
local tags = {
[1] = item_name,
[2] = dialogue_type,
[3] = item_type,
[4] = sex
}
if phase ~= nil then
tags[5] = phase
end
npc.log("DEBUG","Searching with tags: "..dump(tags))
return npc.dialogue.search_dialogue_by_tags(tags, true)
end
-- Returns the response message for a given item -- Returns the response message for a given item
function npc.relationships.get_response_for_favorite_item(item_name, sex, phase) function npc.relationships.get_response_for_favorite_item(item_name, sex, phase)
local items = npc.FAVORITE_ITEMS.female local items = npc.FAVORITE_ITEMS.female
@ -144,7 +268,7 @@ end
-- Relationship functions -- Relationship functions
--------------------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- This function selects two random items from the npc.favorite_items table -- This function selects two random items from the npc.favorite_items table
-- It checks for sex and phase for choosing the items -- It checks for sex and phase for choosing the items
@ -152,18 +276,19 @@ function npc.relationships.select_random_favorite_items(sex, phase)
local result = {} local result = {}
local items = {} local items = {}
-- Filter sex -- -- Filter sex
if sex == npc.FEMALE then -- if sex == npc.FEMALE then
items = npc.FAVORITE_ITEMS.female -- items = npc.FAVORITE_ITEMS.female
else -- else
items = npc.FAVORITE_ITEMS.male -- items = npc.FAVORITE_ITEMS.male
end -- end
-- Select the phase -- Select the phase
items = items[phase] -- items = items[phase]
items = npc.relationships.gift_items.liked[sex][phase]
result.fav1 = items[math.random(1, #items)].item result.fav1 = items[math.random(1, #items)]
result.fav2 = items[math.random(1, #items)].item result.fav2 = items[math.random(1, #items)]
return result return result
end end
@ -174,15 +299,16 @@ function npc.relationships.select_random_disliked_items(sex)
local result = {} local result = {}
local items = {} local items = {}
-- Filter sex -- -- Filter sex
if sex == npc.FEMALE then -- if sex == npc.FEMALE then
items = npc.DISLIKED_ITEMS.female -- items = npc.DISLIKED_ITEMS.female
else -- else
items = npc.DISLIKED_ITEMS.male -- items = npc.DISLIKED_ITEMS.male
end -- end
items = npc.relationships.gift_items.disliked[sex]
result.dis1 = items[math.random(1, #items)].item result.dis1 = items[math.random(1, #items)]
result.dis2 = items[math.random(1, #items)].item result.dis2 = items[math.random(1, #items)]
return result return result
end end
@ -233,10 +359,8 @@ local function update_relationship(self, clicker_name, modifier)
npc.relationships.select_random_favorite_items(self.sex, self.relationships[i].phase) npc.relationships.select_random_favorite_items(self.sex, self.relationships[i].phase)
-- Re-select dialogues per new -- Re-select dialogues per new
self.dialogues = self.dialogues =
npc.dialogue.select_random_dialogues_for_npc(self.sex, npc.dialogue.select_random_dialogues_for_npc(self,
self.relationships[i].phase, self.relationships[i].phase)
self.gift_data.favorite_items,
self.gift_data.disliked_items)
return true return true
end end
return false return false
@ -337,13 +461,13 @@ local function show_receive_gift_reaction(self, item_name, modifier, clicker_nam
if modifier >= 0 then if modifier >= 0 then
local phase = npc.relationships.get_relationship_phase_by_points(points) local phase = npc.relationships.get_relationship_phase_by_points(points)
if phase == "phase3" then if phase == "phase3" then
effect({x = pos.x, y = pos.y + 1, z = pos.z}, 2, "heart.png") npc.effect({x = pos.x, y = pos.y + 1, z = pos.z}, 2, "heart.png")
elseif phase == "phase4" then elseif phase == "phase4" then
effect({x = pos.x, y = pos.y + 1, z = pos.z}, 4, "heart.png") npc.effect({x = pos.x, y = pos.y + 1, z = pos.z}, 4, "heart.png")
elseif phase == "phase5" then elseif phase == "phase5" then
effect({x = pos.x, y = pos.y + 1, z = pos.z}, 6, "heart.png") npc.effect({x = pos.x, y = pos.y + 1, z = pos.z}, 6, "heart.png")
elseif phase == "phase6" then elseif phase == "phase6" then
effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png") npc.effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png")
end end
if phase_change then if phase_change then
local number_code = phase:byte(phase:len()) - 1 local number_code = phase:byte(phase:len()) - 1
@ -352,15 +476,30 @@ local function show_receive_gift_reaction(self, item_name, modifier, clicker_nam
-- Send message -- Send message
-- TODO: There might be an error with getting the message... -- TODO: There might be an error with getting the message...
--minetest.log("Item_name: "..dump(item_name)..", sex: "..dump(self.sex)..", phase: "..dump(phase)) --minetest.log("Item_name: "..dump(item_name)..", sex: "..dump(self.sex)..", phase: "..dump(phase))
local message_to_send = local dialogues_found = npc.relationships.get_dialogues_for_gift_item(
npc.relationships.get_response_for_favorite_item(item_name, self.sex, phase) item_name,
npc.chat(self.npc_name, clicker_name, message_to_send) npc.dialogue.tags.GIFT_ITEM_RESPONSE,
npc.dialogue.tags.GIFT_ITEM_LIKED,
self.sex,
phase)
for _, item_dialogue in pairs(dialogues_found) do
npc.chat(self.npc_name, clicker_name, item_dialogue.text)
end
-- Disliked items reactions -- Disliked items reactions
elseif modifier < 0 then elseif modifier < 0 then
effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "default_item_smoke.png") npc.effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "default_item_smoke.png")
--minetest.log("Item name: "..item_name..", sex: "..self.sex) --minetest.log("Item name: "..item_name..", sex: "..self.sex)
local message_to_send = npc.relationships.get_response_for_disliked_item(item_name, self.sex) -- There should be only one dialogue, however, it returns a key-value
npc.chat(self.npc_name, clicker_name, message_to_send) -- result where we will have to do one loop
local dialogues_found = npc.relationships.get_dialogues_for_gift_item(
item_name,
npc.dialogue.tags.GIFT_ITEM_RESPONSE,
npc.dialogue.tags.GIFT_ITEM_UNLIKED,
self.sex)
-- minetest.log("Message: "..dump(message_to_send))
for _, item_dialogue in pairs(dialogues_found) do
npc.chat(self.npc_name, clicker_name, item_dialogue.text)
end
end end
end end
@ -425,21 +564,20 @@ function npc.relationships.receive_gift(self, clicker)
else else
npc.chat(self.npc_name, clicker_name, npc.chat(self.npc_name, clicker_name,
"Dear, I feel the same as you. But maybe not yet...") "Dear, I feel the same as you. But maybe not yet...")
end end
-- Reset gift timer -- Reset gift timer
reset_gift_timer(self, clicker_name) reset_gift_timer(self, clicker_name)
return true return true
end end
-- Marriage gifts: except for disliked items, all product a 0.5 * npc.ITEM_GIFT_EFFECT -- Marriage gifts: except for disliked items, all product a 0.5 * npc.ITEM_GIFT_EFFECT
-- Disliked items cause only a -1 point effect -- Disliked items cause only a -0.5 point effect
if get_relationship_points(self, clicker_name) >= if get_relationship_points(self, clicker_name) >=
npc.relationships.RELATIONSHIP_PHASE["phase5"].limit then npc.relationships.RELATIONSHIP_PHASE["phase5"].limit then
local modifier = 0.5 * npc.ITEM_GIFT_EFFECT local modifier = 0.5 * npc.ITEM_GIFT_EFFECT
-- Check for disliked items -- Check for disliked items
if item:get_name() == self.gift_data.disliked_items.dis1 if item:get_name() == self.gift_data.disliked_items.dis1
or item:get_name() == self.gift_data.disliked_items.dis2 then or item:get_name() == self.gift_data.disliked_items.dis2 then
modifier = -1 modifier = -0.5
show_receive_gift_reaction(self, item:get_name(), modifier, clicker_name, false) show_receive_gift_reaction(self, item:get_name(), modifier, clicker_name, false)
elseif item:get_name() == self.gift_data.favorite_items.fav1 elseif item:get_name() == self.gift_data.favorite_items.fav1
or item:get_name() == self.gift_data.favorite_items.fav2 then or item:get_name() == self.gift_data.favorite_items.fav2 then

File diff suppressed because it is too large Load Diff

3
spawner_marker.lua Normal file
View File

@ -0,0 +1,3 @@
-- Spawner markers
-- Specialized functionality to allow players do NPC spawning
-- on their own custom buildings.

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
textures/npc_female10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
textures/npc_female11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
textures/npc_female2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
textures/npc_female3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
textures/npc_female4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
textures/npc_female5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
textures/npc_female6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
textures/npc_female7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
textures/npc_female8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
textures/npc_female9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
textures/npc_male10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
textures/npc_male11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
textures/npc_male12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
textures/npc_male13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
textures/npc_male14.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
textures/npc_male2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
textures/npc_male3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
textures/npc_male4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
textures/npc_male5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
textures/npc_male6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
textures/npc_male7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
textures/npc_male8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
textures/npc_male9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -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: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: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: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["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: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} npc.trade.prices.table["farming:bread"] = {tier = npc.trade.prices.currency.tier3.string, count = 20}

View File

@ -26,10 +26,11 @@ npc.trade.CUSTOM_TRADES_PROMPT_TEXT = "Hi there, how can I help you today?"
-- Casual trader NPC dialogues definition -- Casual trader NPC dialogues definition
-- Casual buyer -- Casual buyer
npc.trade.CASUAL_TRADE_BUY_DIALOGUE = { npc.dialogue.register_dialogue({
text = "I'm looking to buy some items, are you interested?", text = "I'm looking to buy some items, are you interested?",
casual_trade_type = npc.trade.OFFER_BUY, --casual_trade_type = npc.trade.OFFER_BUY,
dialogue_type = npc.dialogue.dialogue_type.casual_trade, tags = {"default_casual_trade_dialogue", "buy_offer"},
--dialogue_type = npc.dialogue.dialogue_type.casual_trade,
responses = { responses = {
[1] = { [1] = {
text = "Sell", text = "Sell",
@ -40,13 +41,30 @@ npc.trade.CASUAL_TRADE_BUY_DIALOGUE = {
end end
} }
} }
} })
-- npc.trade.CASUAL_TRADE_BUY_DIALOGUE = {
-- text = "I'm looking to buy some items, are you interested?",
-- casual_trade_type = npc.trade.OFFER_BUY,
-- dialogue_type = npc.dialogue.dialogue_type.casual_trade,
-- responses = {
-- [1] = {
-- text = "Sell",
-- action_type = "function",
-- response_id = 1,
-- action = function(self, player)
-- npc.trade.show_trade_offer_formspec(self, player, npc.trade.OFFER_BUY)
-- end
-- }
-- }
-- }
-- Casual seller -- Casual seller
npc.trade.CASUAL_TRADE_SELL_DIALOGUE = { npc.dialogue.register_dialogue({
text = "I have some items to sell, are you interested?", text = "I have some items to sell, are you interested?",
dialogue_type = npc.dialogue.dialogue_type.casual_trade, --dialogue_type = npc.dialogue.dialogue_type.casual_trade,
casual_trade_type = npc.trade.OFFER_SELL, tags = {"default_casual_trade_dialogue", "sell_offer"},
--casual_trade_type = npc.trade.OFFER_SELL,
responses = { responses = {
[1] = { [1] = {
text = "Buy", text = "Buy",
@ -57,11 +75,28 @@ npc.trade.CASUAL_TRADE_SELL_DIALOGUE = {
end end
} }
} }
} })
-- npc.trade.CASUAL_TRADE_SELL_DIALOGUE = {
-- text = "I have some items to sell, are you interested?",
-- dialogue_type = npc.dialogue.dialogue_type.casual_trade,
-- casual_trade_type = npc.trade.OFFER_SELL,
-- responses = {
-- [1] = {
-- text = "Buy",
-- action_type = "function",
-- response_id = 1,
-- action = function(self, player)
-- npc.trade.show_trade_offer_formspec(self, player, npc.trade.OFFER_SELL)
-- end
-- }
-- }
-- }
-- Dedicated trade dialogue prompt -- Dedicated trade dialogue prompt
npc.trade.DEDICATED_TRADER_PROMPT = { npc.dialogue.register_dialogue({
text = "Hello there, would you like to trade?", text = "Hello there, would you like to trade?",
tags = {npc.dialogue.tags.DEFAULT_DEDICATED_TRADE},
dialogue_type = npc.dialogue.dialogue_type.dedicated_trade, dialogue_type = npc.dialogue.dialogue_type.dedicated_trade,
responses = { responses = {
[1] = { [1] = {
@ -90,7 +125,38 @@ npc.trade.DEDICATED_TRADER_PROMPT = {
end end
} }
} }
} })
-- npc.trade.DEDICATED_TRADER_PROMPT = {
-- text = "Hello there, would you like to trade?",
-- dialogue_type = npc.dialogue.dialogue_type.dedicated_trade,
-- responses = {
-- [1] = {
-- text = "Buy",
-- action_type = "function",
-- response_id = 1,
-- action = function(self, player)
-- npc.trade.show_dedicated_trade_formspec(self, player, npc.trade.OFFER_SELL)
-- end
-- },
-- [2] = {
-- text = "Sell",
-- action_type = "function",
-- response_id = 2,
-- action = function(self, player)
-- npc.trade.show_dedicated_trade_formspec(self, player, npc.trade.OFFER_BUY)
-- end
-- },
-- [3] = {
-- text = "Other",
-- action_type = "function",
-- response_id = 3,
-- action = function(self, player)
-- local dialogue = npc.dialogue.create_custom_trade_options(self, player)
-- npc.dialogue.process_dialogue(self, dialogue, player:get_player_name())
-- end
-- }
-- }
-- }
function npc.trade.show_trade_offer_formspec(self, player, offer_type) function npc.trade.show_trade_offer_formspec(self, player, offer_type)
@ -348,6 +414,8 @@ function npc.trade.get_casual_trade_offer(self, offer_type)
end end
end end
end end
-- Check if there are no sellable items to avoid crash
if #sellable_items > 0 then
-- Choose a random item from the sellable items -- Choose a random item from the sellable items
local item = sellable_items[math.random(#sellable_items)] local item = sellable_items[math.random(#sellable_items)]
-- Choose how many of this item will be sold to player -- Choose how many of this item will be sold to player
@ -355,6 +423,7 @@ function npc.trade.get_casual_trade_offer(self, offer_type)
-- Create trade offer -- Create trade offer
result = npc.trade.create_offer(npc.trade.OFFER_SELL, npc.get_item_name(item), nil, nil, count) result = npc.trade.create_offer(npc.trade.OFFER_SELL, npc.get_item_name(item), nil, nil, count)
end end
end
return result return result
end end
@ -369,7 +438,9 @@ function npc.trade.get_dedicated_trade_offers(self)
buy = {} buy = {}
} }
local trade_list = self.trader_data.trade_list.both local trade_list = self.trader_data.trade_list
npc.log("INFO", "NPC Inventory: "..dump(self.inventory))
for item_name, trade_info in pairs(trade_list) do for item_name, trade_info in pairs(trade_list) do
-- Abort if more than 12 buy or sell offers are made -- Abort if more than 12 buy or sell offers are made
@ -380,8 +451,17 @@ function npc.trade.get_dedicated_trade_offers(self)
-- If it is, create a sell offer, else create a buy offer if possible. -- If it is, create a sell offer, else create a buy offer if possible.
-- Also, avoid creating sell offers immediately if the item was just bought -- Also, avoid creating sell offers immediately if the item was just bought
local item = npc.inventory_contains(self, item_name) local item = npc.inventory_contains(self, item_name)
minetest.log("Searched item: "..dump(item_name))
minetest.log("Found: "..dump(item))
if item ~= nil and trade_info.last_offer_type ~= npc.trade.OFFER_BUY then if item ~= nil and trade_info.last_offer_type ~= npc.trade.OFFER_BUY then
-- Check if item can be sold
if trade_info.item_sold_count == nil
or (trade_info.item_sold_count ~= nil
and (trade_info.max_item_sell_count
and trade_info.item_sold_count < trade_info.max_item_sell_count)) then
-- This check makes sure that the NPC will keep max_item_sell_count at any time.
if trade_info.amount_to_keep == nil or (trade_info.amount_to_keep ~= nil
and trade_info.amount_to_keep < ItemStack(item.item_string):get_count()) then
-- Create sell offer for this item. Currently, traders will offer to sell only -- Create sell offer for this item. Currently, traders will offer to sell only
-- one of their items to allow the fine control for players to buy what they want. -- one of their items to allow the fine control for players to buy what they want.
-- This requires, however, that the trade offers are re-generated everytime a -- This requires, however, that the trade offers are re-generated everytime a
@ -395,6 +475,11 @@ function npc.trade.get_dedicated_trade_offers(self)
) )
-- Set last offer type -- Set last offer type
trade_info.last_offer_type = npc.trade.OFFER_SELL trade_info.last_offer_type = npc.trade.OFFER_SELL
end
else
-- Clear the trade info for this item
trade_info.item_sold_count = 0
end
else else
-- Avoid flipping an item to the buy side if the stock was just depleted -- Avoid flipping an item to the buy side if the stock was just depleted
@ -415,12 +500,10 @@ function npc.trade.get_dedicated_trade_offers(self)
-- If item found, create a buy offer for this item -- If item found, create a buy offer for this item
-- Again, offers are created for one item only. Buy offers should be removed -- Again, offers are created for one item only. Buy offers should be removed
-- after the NPC has bought a certain quantity, say, 5 items. -- after the NPC has bought a certain quantity, say, 5 items.
--minetest.log("Item: "..item_name)
--minetest.log("Trade info: "..dump(trade_info))
--minetest.log("Logic: "..dump(trade_info.item_bought_count == nil
-- or (trade_info.item_bought_count ~= nil and trade_info.item_bought_count <= npc.trade.DEDICATED_MAX_BUY_AMOUNT)))
if trade_info.item_bought_count == nil if trade_info.item_bought_count == nil
or (trade_info.item_bought_count ~= nil and trade_info.item_bought_count <= npc.trade.DEDICATED_MAX_BUY_AMOUNT) then or (trade_info.item_bought_count ~= nil
and (trade_info.max_item_buy_count and trade_info.item_bought_count <= trade_info.max_item_buy_count
or trade_info.item_bought_count <= npc.trade.DEDICATED_MAX_BUY_AMOUNT)) then
-- Create trade offer for this item -- Create trade offer for this item
table.insert(offers.buy, npc.trade.create_offer( table.insert(offers.buy, npc.trade.create_offer(
npc.trade.OFFER_BUY, npc.trade.OFFER_BUY,
@ -628,15 +711,27 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
-- Increase the item bought count -- Increase the item bought count
local offer_item_name = npc.get_item_name(trade_offers[i].item) local offer_item_name = npc.get_item_name(trade_offers[i].item)
--minetest.log("Bought item name: "..dump(offer_item_name)) --minetest.log("Bought item name: "..dump(offer_item_name))
--minetest.log(dump(player_response.npc.trader_data.trade_list.both[offer_item_name])) --minetest.log(dump(player_response.npc.trader_data.trade_list[offer_item_name]))
-- Check if this item has been bought before -- Check if this item has been bought before
if player_response.npc.trader_data.trade_list.both[offer_item_name].item_bought_count == nil then if player_response.npc.trader_data.trade_list[offer_item_name].item_bought_count == nil then
-- Set first count to 1 -- Set first count to 1
player_response.npc.trader_data.trade_list.both[offer_item_name].item_bought_count = 1 player_response.npc.trader_data.trade_list[offer_item_name].item_bought_count = 1
else else
-- Increase count -- Increase count
player_response.npc.trader_data.trade_list.both[offer_item_name].item_bought_count player_response.npc.trader_data.trade_list[offer_item_name].item_bought_count
= player_response.npc.trader_data.trade_list.both[offer_item_name].item_bought_count + 1 = player_response.npc.trader_data.trade_list[offer_item_name].item_bought_count + 1
end
else
-- Also count how many items are sold
local offer_item_name = npc.get_item_name(trade_offers[i].item)
-- Check if this item has been sold before
if player_response.npc.trader_data.trade_list[offer_item_name].item_sold_count == nil then
-- Set first count to 1
player_response.npc.trader_data.trade_list[offer_item_name].item_sold_count = 1
else
-- Increase count
player_response.npc.trader_data.trade_list[offer_item_name].item_sold_count
= player_response.npc.trader_data.trade_list[offer_item_name].item_sold_count + 1
end end
end end
-- Re-generate trade offers -- Re-generate trade offers

97
utils.lua Normal file
View 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