Compare commits
15 Commits
1.0-alpha-
...
1.0.0-alph
Author | SHA1 | Date | |
---|---|---|---|
4c0e2b574a | |||
bf935fd091 | |||
6141af11aa | |||
e70888c3e5 | |||
cc56446206 | |||
7110c49b42 | |||
fd4cec0d63 | |||
3df43ab580 | |||
c19ea70242 | |||
a3b428fe14 | |||
4814c16ba0 | |||
2530918fe9 | |||
fb549e7f93 | |||
e1bf931064 | |||
3e006ac828 |
25
README.md
@ -4,25 +4,40 @@ advanced_npc
|
|||||||
Introduction
|
Introduction
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Advanced NPC is a mod for Minetest, based on mobs_redo.
|
Advanced NPC is a mod for Minetest using mobs_redo API.
|
||||||
The goal of this mod is to be able to have live villages in Minetest. These NPCs are highly inspired by the typical NPCs of Harvest Moon games. The general idea is that on almost all buildings of a village there are NPCs that are kind of intelligent: they have daily tasks they perform, can speak to players, can trade with the player, can use their own items (chests and furnaces for example), know where to go around their house and village, can be lumbers, miners or any other Minetest-suitable profession and can ultimately engage into relationships with the player. And while basically only players are mentioned here, the ultimate goal is that they can do all of this also among themselves, so that villages are alive and evolving by themselves, without player intervention.
|
The goal of this mod is to be able to have live villages in Minetest. These NPCs are highly inspired by the typical NPCs of _Harvest Moon_ games. The general idea is that on almost all buildings of a village there are NPCs that are kind of intelligent: they have daily tasks they perform, can speak to players, can trade with the player, can use their own items (chests and furnaces for example), know where to go around their house and village, can be lumbers, miners or any other Minetest-suitable profession and can ultimately engage into relationships with the player. And while basically only players are mentioned here, the ultimate goal is that they can do all of this also among themselves, so that villages are alive and evolving by themselves, without player intervention.
|
||||||
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
__NOTE__: Advanced NPC is still under development. While the mod is largely stable, it lacks one of the most important pieces: spawning. Currently, NPCs will spawn on stone (default:stone) and the mg_villages' plotmarkers (mg_villages:plotmarker). The spawning is not controlled, so you will have several of them walking around. This is not how it is planned and is just for testing purposes. In the future, only a handful of NPCs should spawn at village house's plotmarker and they will know their way around the house and have specific jobs.
|
__NOTE__: Advanced NPC is still under development. While the mod is largely stable, it lacks one of the most important pieces: spawning. Currently, NPCs can be spawned using eggs (found in creative inventory as 'NPC') and by themselves on villages of the [mg_villages mod](https://forum.minetest.net/viewtopic.php?t=13589). NPCs will spawn automatically on mg_villages villages and over time will populate the entire village. If something goes wrong, you can reset the village by:
|
||||||
|
- Clearing all objects (in chat, type /clearobjects quick)
|
||||||
|
- Restore original plotmarkers (in chat, type /restore_plotmarkers radius)
|
||||||
|
- The radius can be any number, but it is recommended you use a not so large number. 200 is suitable. So stand in the middle of the village and then run that command.
|
||||||
|
This will actually restore the village and will slowly make NPCs spawn again. Currently there's no way to disable NPCs spawning on village, except by going to `spawner.lua` and commenting out all of `minetest.register_abm()` code.
|
||||||
|
|
||||||
Download the mod [here](https://github.com/hkzorman/advanced_npc/archive/master.zip) (link always pointing to latest version)
|
__Download__ the mod [here](https://github.com/hkzorman/advanced_npc/archive/master.zip) (link always pointing to latest version)
|
||||||
|
|
||||||
For this mod to work correctly, you also need to install the [mobs_redo](https://github.com/tenplus1/mobs_redo) mod. After installation, make sure you enable it in your world.
|
For this mod to work correctly, you also need to install the [mobs_redo](https://github.com/tenplus1/mobs_redo) mod. After installation, make sure you enable it in your world.
|
||||||
|
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|
||||||
__advanced_npc__ is Copyright (C) 2016-2017 Hector Franqui (zorman2000), licensed under the GPLv3 license. See `license.txt` for details.
|
__advanced_npc__ is Copyright (C) 2016-2017 Hector Franqui (zorman2000), licensed under the GPLv3 license. See `license.txt` for details.
|
||||||
|
|
||||||
The `jumper.lua` file contains code based on the [Jumper library](https://github.com/Yonaba/Jumper), which is Copyright (c) 2012-2013 Roland Yonaba, licensed under MIT license. See `actions/jumper.lua` for details.
|
The `pathfinder.lua` file contains code slighlty modified from the [pathfinder mod](https://github.com/MarkuBu/pathfinder) by MarkBu, which is licensed as WTFPL. See `actions/pathfinder.lua` for details.
|
||||||
|
|
||||||
|
Current NPC textures are from mobs_redo mod.
|
||||||
|
The following textures are by Zorman2000:
|
||||||
|
- marriage_ring.png - CC BY-SA
|
||||||
|
|
||||||
|
|
||||||
|
Documentation and API
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
This mod requires a good user manual, and also is planned to have an extensive API, properly documented. Unfortunately, these still aren't ready. A very very very WIP manual can be found in the [wiki](https://github.com/hkzorman/advanced_npc/wiki/Concept%3A-Dialogues)
|
||||||
|
|
||||||
|
|
||||||
Roadmap
|
Roadmap
|
||||||
|
@ -599,11 +599,15 @@ function npc.actions.use_bed(self, args)
|
|||||||
end
|
end
|
||||||
local action = args.action
|
local action = args.action
|
||||||
local node = minetest.get_node(pos)
|
local node = minetest.get_node(pos)
|
||||||
minetest.log(dump(node))
|
--minetest.log(dump(node))
|
||||||
local dir = minetest.facedir_to_dir(node.param2)
|
local dir = minetest.facedir_to_dir(node.param2)
|
||||||
|
|
||||||
if action == npc.actions.const.beds.LAY then
|
if action == npc.actions.const.beds.LAY then
|
||||||
-- Get position
|
-- Get position
|
||||||
|
-- Error here due to ignore. Need to come up with better solution
|
||||||
|
if node.name == "ignore" then
|
||||||
|
return
|
||||||
|
end
|
||||||
local bed_pos = npc.actions.nodes.beds[node.name].get_lay_pos(pos, dir)
|
local bed_pos = npc.actions.nodes.beds[node.name].get_lay_pos(pos, dir)
|
||||||
-- Sit down on bed, rotate to correct direction
|
-- Sit down on bed, rotate to correct direction
|
||||||
npc.add_action(self, npc.actions.cmd.SIT, {pos=bed_pos, dir=(node.param2 + 2) % 4})
|
npc.add_action(self, npc.actions.cmd.SIT, {pos=bed_pos, dir=(node.param2 + 2) % 4})
|
||||||
@ -611,6 +615,10 @@ function npc.actions.use_bed(self, args)
|
|||||||
npc.add_action(self, npc.actions.cmd.LAY, {})
|
npc.add_action(self, npc.actions.cmd.LAY, {})
|
||||||
else
|
else
|
||||||
-- Calculate position to get up
|
-- Calculate position to get up
|
||||||
|
-- Error here due to ignore. Need to come up with better solution
|
||||||
|
if node.name == "ignore" then
|
||||||
|
return
|
||||||
|
end
|
||||||
local bed_pos_y = npc.actions.nodes.beds[node.name].get_lay_pos(pos, dir).y
|
local bed_pos_y = npc.actions.nodes.beds[node.name].get_lay_pos(pos, dir).y
|
||||||
local bed_pos = {x = pos.x, y = bed_pos_y, z = pos.z}
|
local bed_pos = {x = pos.x, y = bed_pos_y, z = pos.z}
|
||||||
-- Sit up
|
-- Sit up
|
||||||
@ -662,6 +670,10 @@ function npc.actions.use_sittable(self, args)
|
|||||||
|
|
||||||
if action == npc.actions.const.sittable.SIT then
|
if action == npc.actions.const.sittable.SIT then
|
||||||
-- Calculate position depending on bench
|
-- Calculate position depending on bench
|
||||||
|
-- Error here due to ignore. Need to come up with better solution
|
||||||
|
if node.name == "ignore" then
|
||||||
|
return
|
||||||
|
end
|
||||||
local sit_pos = npc.actions.nodes.sittable[node.name].get_sit_pos(pos, node.param2)
|
local sit_pos = npc.actions.nodes.sittable[node.name].get_sit_pos(pos, node.param2)
|
||||||
-- Sit down on bench/chair/stairs
|
-- Sit down on bench/chair/stairs
|
||||||
npc.add_action(self, npc.actions.cmd.SIT, {pos=sit_pos, dir=(node.param2 + 2) % 4})
|
npc.add_action(self, npc.actions.cmd.SIT, {pos=sit_pos, dir=(node.param2 + 2) % 4})
|
||||||
@ -746,20 +758,9 @@ function npc.actions.walk_to_pos(self, args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Find path
|
-- Find path
|
||||||
--local path = pathfinder.find_path(start_pos, end_pos, 20, walkable_nodes)
|
local path = npc.pathfinder.find_path(start_pos, end_pos, self, true)
|
||||||
local path = pathfinder.find_path(start_pos, end_pos, self)
|
|
||||||
|
|
||||||
if path ~= nil and #path > 1 then
|
if path ~= nil and #path > 1 then
|
||||||
-- Get details from path nodes
|
|
||||||
-- This might get moved to proper place, pathfinder.lua code
|
|
||||||
local path_detail = {}
|
|
||||||
for i = 1, #path do
|
|
||||||
local node = minetest.get_node(path[i])
|
|
||||||
table.insert(path_detail, {pos=path[i], type=npc.pathfinder.is_good_node(node, {})})
|
|
||||||
end
|
|
||||||
path = path_detail
|
|
||||||
|
|
||||||
npc.log("DEBUG", "Detailed path: "..dump(path))
|
|
||||||
npc.log("INFO", "walk_to_pos Found path to node: "..minetest.pos_to_string(end_pos))
|
npc.log("INFO", "walk_to_pos Found path to node: "..minetest.pos_to_string(end_pos))
|
||||||
-- Store path
|
-- Store path
|
||||||
self.actions.walking.path = path
|
self.actions.walking.path = path
|
||||||
|
1349
actions/jumper.lua
@ -1,14 +1,13 @@
|
|||||||
-- Pathfinding code by Zorman2000
|
-- Pathfinding code by MarkBu, original can be found here:
|
||||||
|
-- https://github.com/MarkuBu/pathfinder
|
||||||
|
--
|
||||||
|
-- Modifications by Zorman2000
|
||||||
|
-- This version is slightly modified to use another "walkable" function,
|
||||||
|
-- plus add a "decorating" path function which allows to know the type
|
||||||
|
-- of nodes in the path.
|
||||||
---------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------
|
||||||
-- Pathfinding functionality
|
-- Pathfinding functionality
|
||||||
---------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------
|
||||||
-- This class contains functions that allows to map the 3D map of Minetest into
|
|
||||||
-- a 2D array (basically by ignoring the y coordinate for the moment being) in order
|
|
||||||
-- to use the A* pathfinding algorithm to find the shortest path from one node to
|
|
||||||
-- another. The A* algorithm implementation is in the 'jumper.lua' file which a
|
|
||||||
-- reduced and slightly modified version of the Jumper library, by Roland Yonaba
|
|
||||||
-- (https://github.com/Yonaba/Jumper).
|
|
||||||
-- Mapping algorithm: transforms a Minetest map surface to a 2d grid.
|
|
||||||
|
|
||||||
npc.pathfinder = {}
|
npc.pathfinder = {}
|
||||||
|
|
||||||
@ -30,57 +29,15 @@ npc.pathfinder.nodes = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
-- This function uses the mapping functions and the A* algorithm implementation
|
|
||||||
-- of the Jumper library to find a path from start_pos to end_pos. The range is
|
|
||||||
-- an extra amount of nodes to search in both the x and z coordinates.
|
|
||||||
function npc.pathfinder.find_path(start_pos, end_pos, range, walkable_nodes)
|
|
||||||
-- Check that start and end position are not the same
|
|
||||||
if start_pos.x == end_pos.x and start_pos.z == end_pos.z then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
-- Set walkable nodes to empty if parameter wasn't used
|
|
||||||
if walkable_nodes == nil then
|
|
||||||
walkable_nodes = {}
|
|
||||||
end
|
|
||||||
-- Map the Minetest area to a 2D array
|
|
||||||
local map = pathfinder.create_map(start_pos, end_pos, range, walkable_nodes)
|
|
||||||
-- Find start and end positions
|
|
||||||
local pos = pathfinder.find_start_and_end_pos(map)
|
|
||||||
-- Normalize the map
|
|
||||||
local normalized_map = pathfinder.normalize_map(map)
|
|
||||||
-- Create pathfinder object
|
|
||||||
local grid_object = Grid(normalized_map)
|
|
||||||
-- Define what is a walkable node
|
|
||||||
local walkable = 0
|
|
||||||
|
|
||||||
-- Pathfinder object using A* algorithm
|
|
||||||
local finder = Pathfinder(grid_object, "ASTAR", walkable)
|
|
||||||
-- Set orthogonal mode meaning it will not move in diagonal directions
|
|
||||||
finder:setMode("ORTHOGONAL")
|
|
||||||
|
|
||||||
-- Calculates the path, and its length
|
|
||||||
local path = finder:getPath(pos.start_pos.x, pos.start_pos.z, pos.end_pos.x, pos.end_pos.z)
|
|
||||||
|
|
||||||
--minetest.log("Found path: "..dump(path))
|
|
||||||
-- Pretty-printing the results
|
|
||||||
if path then
|
|
||||||
return pathfinder.get_path(map, path:nodes())
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- This function is used to determine if a node is walkable
|
-- This function is used to determine if a node is walkable
|
||||||
-- or openable, in which case is good to use when finding a path
|
-- or openable, in which case is good to use when finding a path
|
||||||
|
function pathfinder.is_good_node(node, exceptions)
|
||||||
function npc.pathfinder.is_good_node(node, exceptions)
|
|
||||||
--local function is_good_node(node, exceptions)
|
--local function is_good_node(node, exceptions)
|
||||||
-- Is openable is to support doors, fence gates and other
|
-- Is openable is to support doors, fence gates and other
|
||||||
-- doors from other mods. Currently, default doors, gates
|
-- doors from other mods. Currently, default doors, gates
|
||||||
-- and cottages doors are supported.
|
-- and cottages doors are supported.
|
||||||
--minetest.log("Is good node: "..dump(node))
|
|
||||||
local is_openable = false
|
local is_openable = false
|
||||||
for _,node_prefix in pairs(npc.pathfinder.nodes.openable_prefix) do
|
for _,node_prefix in pairs(npc.pathfinder.nodes.openable_prefix) do
|
||||||
--for _,node_prefix in pairs(pathfinder.nodes.openable_prefix) do
|
|
||||||
local start_i,end_i = string.find(node.name, node_prefix)
|
local start_i,end_i = string.find(node.name, node_prefix)
|
||||||
if start_i ~= nil then
|
if start_i ~= nil then
|
||||||
is_openable = true
|
is_openable = true
|
||||||
@ -88,159 +45,350 @@ function npc.pathfinder.is_good_node(node, exceptions)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
if node ~= nil and node.name ~= nil and not minetest.registered_nodes[node.name].walkable then
|
if node ~= nil and node.name ~= nil and not minetest.registered_nodes[node.name].walkable then
|
||||||
--return pathfinder.node_types.walkable
|
|
||||||
return npc.pathfinder.node_types.walkable
|
return npc.pathfinder.node_types.walkable
|
||||||
elseif is_openable then
|
elseif is_openable then
|
||||||
return npc.pathfinder.node_types.openable
|
return npc.pathfinder.node_types.openable
|
||||||
--return pathfinder.node_types.openable
|
|
||||||
else
|
else
|
||||||
for i = 1, #exceptions do
|
for i = 1, #exceptions do
|
||||||
if node.name == exceptions[i] then
|
if node.name == exceptions[i] then
|
||||||
return npc.pathfinder.node_types.walkable
|
return npc.pathfinder.node_types.walkable
|
||||||
--return pathfinder.node_types.walkable
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return npc.pathfinder.node_types.non_walkable
|
return npc.pathfinder.node_types.non_walkable
|
||||||
--return pathfinder.node_types.non_walkable
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function pathfinder.create_map(start_pos, end_pos, extra_range, walkables)
|
function pathfinder.get_decorated_path(path)
|
||||||
|
-- Get details from path nodes
|
||||||
minetest.log("Start pos: "..minetest.pos_to_string(start_pos))
|
local path_detail = {}
|
||||||
minetest.log("End pos: "..minetest.pos_to_string(end_pos))
|
for i = 1, #path do
|
||||||
|
local node = minetest.get_node(path[i])
|
||||||
-- Calculate all signs to ensure:
|
table.insert(path_detail, {pos=path[i], type=pathfinder.is_good_node(node, {})})
|
||||||
-- 1. Correct area calculation
|
|
||||||
-- 2. Iterate in the correct direction
|
|
||||||
local start_x_sign = (start_pos.x - end_pos.x) / math.abs(start_pos.x - end_pos.x)
|
|
||||||
local start_z_sign = (start_pos.z - end_pos.z) / math.abs(start_pos.z - end_pos.z)
|
|
||||||
local end_x_sign = (end_pos.x - start_pos.x) / math.abs(end_pos.x - start_pos.x)
|
|
||||||
local end_z_sign = (end_pos.z - start_pos.z) / math.abs(end_pos.z - start_pos.z)
|
|
||||||
|
|
||||||
-- Correct the signs if they are nan
|
|
||||||
if math.abs(start_pos.x - end_pos.x) == 0 then
|
|
||||||
start_x_sign = -1
|
|
||||||
end_x_sign = 1
|
|
||||||
end
|
|
||||||
if math.abs(start_pos.z - end_pos.z) == 0 then
|
|
||||||
start_z_sign = -1
|
|
||||||
end_z_sign = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Get starting and ending positions, adding the extra nodes to the area
|
|
||||||
local pos1 = {x=start_pos.x + (extra_range * start_x_sign), y = start_pos.y - 1, z=start_pos.z + (extra_range * start_z_sign)}
|
|
||||||
local pos2 = {x=end_pos.x + (extra_range * end_x_sign), y = end_pos.y, z=end_pos.z + (extra_range * end_z_sign)}
|
|
||||||
|
|
||||||
local grid = {}
|
|
||||||
|
|
||||||
-- Loop through the area and classify nodes
|
|
||||||
for z = 1, math.abs(pos1.z - pos2.z) do
|
|
||||||
local current_row = {}
|
|
||||||
for x = 1, math.abs(pos1.x - pos2.x) do
|
|
||||||
-- Calculate current position
|
|
||||||
local current_pos = {x=pos1.x + (x*end_x_sign), y=pos1.y, z=pos1.z + (z*end_z_sign)}
|
|
||||||
-- Check if this is the starting position
|
|
||||||
if current_pos.x == start_pos.x and current_pos.z == start_pos.z then
|
|
||||||
-- Is start position
|
|
||||||
table.insert(current_row, {pos=current_pos, type=pathfinder.node_types.start})
|
|
||||||
elseif current_pos.x == end_pos.x and current_pos.z == end_pos.z then
|
|
||||||
-- Is ending position or goal position
|
|
||||||
table.insert(current_row, {pos=current_pos, type=pathfinder.node_types.goal})
|
|
||||||
else
|
|
||||||
-- Check if node is walkable
|
|
||||||
local node = minetest.get_node(current_pos)
|
|
||||||
-- Check node has air above it
|
|
||||||
local node_above = minetest.get_node({x=current_pos.x, y=current_pos.y+1, z=current_pos.z})
|
|
||||||
if node.name == "air" then
|
|
||||||
-- Check if node above is air
|
|
||||||
if node.name == "air" then
|
|
||||||
-- If air do no more checks
|
|
||||||
table.insert(current_row, {pos=current_pos, type=pathfinder.node_types.walkable})
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- Check if it is of a walkable or openable type
|
|
||||||
table.insert(current_row, {pos=current_pos, type=is_good_node(node, walkables)})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
-- Insert the converted row into the grid
|
|
||||||
table.insert(grid, current_row)
|
npc.log("DEBUG", "Detailed path: "..dump(path_detail))
|
||||||
|
return path_detail
|
||||||
|
end
|
||||||
|
|
||||||
|
function npc.pathfinder.find_path(start_pos, end_pos, entity, decorate_path)
|
||||||
|
local path = pathfinder.find_path(start_pos, end_pos, entity)
|
||||||
|
if path then
|
||||||
|
if decorate_path then
|
||||||
|
path = pathfinder.get_decorated_path(path)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
npc.log("ERROR", "Couldn't find path from "..minetest.pos_to_string(start_pos)
|
||||||
|
.." to "..minetest.pos_to_string(end_pos))
|
||||||
end
|
end
|
||||||
|
return path
|
||||||
return grid
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Utility function to print the created map to the console.
|
-- From this point onwards is MarkBu's original pathfinder code,
|
||||||
-- Used for debug.
|
-- except for the "walkable" function, which is modified by Zorman2000
|
||||||
local function print_map(map)
|
-- to include doors and other "walkable" nodes.
|
||||||
for z,row in pairs(map) do
|
-- The version here is exactly this:
|
||||||
local row_string = "["
|
-- https://github.com/MarkuBu/pathfinder/commit/ca0b433bf5efde5da545b11b2691fa7f7e53dc30
|
||||||
for x,node in pairs(row) do
|
|
||||||
if node.type == 2 then
|
--[[
|
||||||
row_string = row_string.."- "
|
minetest.get_content_id(name)
|
||||||
else
|
minetest.registered_nodes
|
||||||
row_string = row_string..node.type.." "
|
minetest.get_name_from_content_id(id)
|
||||||
end
|
local ivm = a:index(pos.x, pos.y, pos.z)
|
||||||
-- Use the following if the coordinates are also needed
|
local ivm = a:indexp(pos)
|
||||||
--row_string = row_string..node.type..": {"..node.pos.x..", "..node.pos.y..", "..node.pos.z.."}, "
|
minetest.hash_node_position({x=,y=,z=})
|
||||||
end
|
minetest.get_position_from_hash(hash)
|
||||||
row_string = row_string.."]"
|
|
||||||
print(row_string)
|
start_index, target_index, current_index
|
||||||
end
|
^ Hash of position
|
||||||
|
|
||||||
|
current_value
|
||||||
|
^ {int:hCost, int:gCost, int:fCost, hash:parent, vect:pos}
|
||||||
|
]]--
|
||||||
|
|
||||||
|
local openSet = {}
|
||||||
|
local closedSet = {}
|
||||||
|
|
||||||
|
local function get_distance(start_pos, end_pos)
|
||||||
|
local distX = math.abs(start_pos.x - end_pos.x)
|
||||||
|
local distZ = math.abs(start_pos.z - end_pos.z)
|
||||||
|
|
||||||
|
if distX > distZ then
|
||||||
|
return 14 * distZ + 10 * (distX - distZ)
|
||||||
|
else
|
||||||
|
return 14 * distX + 10 * (distZ - distX)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function get_distance_to_neighbor(start_pos, end_pos)
|
||||||
|
local distX = math.abs(start_pos.x - end_pos.x)
|
||||||
|
local distY = math.abs(start_pos.y - end_pos.y)
|
||||||
|
local distZ = math.abs(start_pos.z - end_pos.z)
|
||||||
|
|
||||||
-- This function find the starting and ending points in the
|
if distX > distZ then
|
||||||
-- map representation, and returns the coordinates in the map
|
return (14 * distZ + 10 * (distX - distZ)) * (distY + 1)
|
||||||
-- for the pathfinding algorithm to use
|
else
|
||||||
function pathfinder.find_start_and_end_pos(map)
|
return (14 * distX + 10 * (distZ - distX)) * (distY + 1)
|
||||||
-- This is for debug
|
end
|
||||||
--print_map(map)
|
end
|
||||||
local result = {}
|
|
||||||
for z,row in pairs(map) do
|
-- This function is used to determine if a node is walkable
|
||||||
for x,node in pairs(row) do
|
-- or openable, in which case is good to use when finding a path
|
||||||
if node.type == pathfinder.node_types.start then
|
local function walkable(node, exceptions)
|
||||||
--minetest.log("Start node: "..dump(node))
|
local exceptions = exceptions or {}
|
||||||
result["start_pos"] = {x=x, z=z}
|
-- Is openable is to support doors, fence gates and other
|
||||||
elseif node.type == pathfinder.node_types.goal then
|
-- doors from other mods. Currently, default doors, gates
|
||||||
--minetest.log("End node: "..dump(node))
|
-- and cottages doors are supported.
|
||||||
result["end_pos"] = {x=x, z=z}
|
--minetest.log("Is good node: "..dump(node))
|
||||||
end
|
local is_openable = false
|
||||||
|
for _,node_prefix in pairs(npc.pathfinder.nodes.openable_prefix) do
|
||||||
|
local start_i,end_i = string.find(node.name, node_prefix)
|
||||||
|
if start_i ~= nil then
|
||||||
|
is_openable = true
|
||||||
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
--minetest.log("Found start and end positions: ("..result.start_pos.)..", "..minetest.pos_to_string(result.end_pos))
|
-- Detect mg_villages ceilings usage of thin wood nodeboxes
|
||||||
return result
|
-- TODO: Improve
|
||||||
end
|
local is_mg_villages_ceiling = false
|
||||||
|
if node.name == "cottages:wood_flat" then
|
||||||
-- This function transforms the grid into binary values
|
is_mg_villages_ceiling = true
|
||||||
-- (0 walkable, 1 non-walkable) for the pathfinding algorithm.
|
end
|
||||||
function pathfinder.normalize_map(map)
|
if node ~= nil
|
||||||
local result = {}
|
and node.name ~= nil
|
||||||
for _,row in pairs(map) do
|
and node.name ~= "ignore"
|
||||||
local result_row = {}
|
and minetest.registered_nodes[node.name]
|
||||||
for _,node in pairs(row) do
|
and not minetest.registered_nodes[node.name].walkable then
|
||||||
if node.type ~= pathfinder.node_types.non_walkable then
|
return false
|
||||||
table.insert(result_row, 0)
|
elseif is_openable then
|
||||||
else
|
return false
|
||||||
table.insert(result_row, 1)
|
elseif is_mg_villages_ceiling then
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
for i = 1, #exceptions do
|
||||||
|
if node.name == exceptions[i] then
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
table.insert(result, result_row)
|
return true
|
||||||
end
|
end
|
||||||
return result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- This function returns an array of tables with two parameters: type and pos.
|
local function check_clearance(cpos, x, z, height)
|
||||||
-- The position parameter is the actual coordinate on the Minetest map. The
|
for i = 1, height do
|
||||||
-- type is the type of the node at the coordinate defined as pathfinder.node_types.
|
local n_name = minetest.get_node({x = cpos.x + x, y = cpos.y + i, z = cpos.z + z}).name
|
||||||
function pathfinder.get_path(map, path_nodes)
|
local c_name = minetest.get_node({x = cpos.x, y = cpos.y + i, z = cpos.z}).name
|
||||||
local result = {}
|
--~ print(i, n_name, c_name)
|
||||||
for node, count in path_nodes do
|
if walkable(n_name) or walkable(c_name) then
|
||||||
table.insert(result, map[node:getY()][node:getX()])
|
return false
|
||||||
-- For debug
|
end
|
||||||
--minetest.log("Node: "..dump(map[node:getY()][node:getX()]))
|
end
|
||||||
--print(('Step: %d - x: %d - y: %d'):format(count, node:getX(), node:getY()))
|
return true
|
||||||
end
|
end
|
||||||
return result
|
|
||||||
|
local function get_neighbor_ground_level(pos, jump_height, fall_height)
|
||||||
|
local node = minetest.get_node(pos)
|
||||||
|
local height = 0
|
||||||
|
if walkable(node) then
|
||||||
|
repeat
|
||||||
|
height = height + 1
|
||||||
|
if height > jump_height then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
pos.y = pos.y + 1
|
||||||
|
node = minetest.get_node(pos)
|
||||||
|
until not walkable(node)
|
||||||
|
return pos
|
||||||
|
else
|
||||||
|
repeat
|
||||||
|
height = height + 1
|
||||||
|
if height > fall_height then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
pos.y = pos.y - 1
|
||||||
|
node = minetest.get_node(pos)
|
||||||
|
until walkable(node)
|
||||||
|
return {x = pos.x, y = pos.y + 1, z = pos.z}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function pathfinder.find_path(pos, endpos, entity)
|
||||||
|
local start_index = minetest.hash_node_position(pos)
|
||||||
|
local target_index = minetest.hash_node_position(endpos)
|
||||||
|
local count = 1
|
||||||
|
|
||||||
|
openSet = {}
|
||||||
|
closedSet = {}
|
||||||
|
|
||||||
|
local h_start = get_distance(pos, endpos)
|
||||||
|
openSet[start_index] = {hCost = h_start, gCost = 0, fCost = h_start, parent = nil, pos = pos}
|
||||||
|
|
||||||
|
-- Entity values
|
||||||
|
local entity_height = math.ceil(entity.collisionbox[5] - entity.collisionbox[2])
|
||||||
|
local entity_fear_height = entity.fear_height or 2
|
||||||
|
local entity_jump_height = entity.jump_height or 1
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local current_index
|
||||||
|
local current_values
|
||||||
|
|
||||||
|
-- Get one index as reference from openSet
|
||||||
|
for i, v in pairs(openSet) do
|
||||||
|
current_index = i
|
||||||
|
current_values = v
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Search for lowest fCost
|
||||||
|
for i, v in pairs(openSet) do
|
||||||
|
if v.fCost < openSet[current_index].fCost or v.fCost == current_values.fCost and v.hCost < current_values.hCost then
|
||||||
|
current_index = i
|
||||||
|
current_values = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
openSet[current_index] = nil
|
||||||
|
closedSet[current_index] = current_values
|
||||||
|
count = count - 1
|
||||||
|
|
||||||
|
if current_index == target_index then
|
||||||
|
-- print("Success")
|
||||||
|
local path = {}
|
||||||
|
local reverse_path = {}
|
||||||
|
repeat
|
||||||
|
if not closedSet[current_index] then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
table.insert(path, closedSet[current_index].pos)
|
||||||
|
current_index = closedSet[current_index].parent
|
||||||
|
if #path > 100 then
|
||||||
|
-- print("path to long")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
until start_index == current_index
|
||||||
|
repeat
|
||||||
|
table.insert(reverse_path, table.remove(path))
|
||||||
|
until #path == 0
|
||||||
|
-- print("path lenght: "..#reverse_path)
|
||||||
|
return reverse_path
|
||||||
|
end
|
||||||
|
|
||||||
|
local current_pos = current_values.pos
|
||||||
|
|
||||||
|
local neighbors = {}
|
||||||
|
local neighbors_index = 1
|
||||||
|
for z = -1, 1 do
|
||||||
|
for x = -1, 1 do
|
||||||
|
local neighbor_pos = {x = current_pos.x + x, y = current_pos.y, z = current_pos.z + z}
|
||||||
|
local neighbor = minetest.get_node(neighbor_pos)
|
||||||
|
local neighbor_ground_level = get_neighbor_ground_level(neighbor_pos, entity_jump_height, entity_fear_height)
|
||||||
|
local neighbor_clearance = false
|
||||||
|
if neighbor_ground_level then
|
||||||
|
-- print(neighbor_ground_level.y - current_pos.y)
|
||||||
|
--minetest.set_node(neighbor_ground_level, {name = "default:dry_shrub"})
|
||||||
|
local node_above_head = minetest.get_node(
|
||||||
|
{x = current_pos.x, y = current_pos.y + entity_height, z = current_pos.z})
|
||||||
|
if neighbor_ground_level.y - current_pos.y > 0 and not walkable(node_above_head) then
|
||||||
|
local height = -1
|
||||||
|
repeat
|
||||||
|
height = height + 1
|
||||||
|
local node = minetest.get_node(
|
||||||
|
{x = neighbor_ground_level.x,
|
||||||
|
y = neighbor_ground_level.y + height,
|
||||||
|
z = neighbor_ground_level.z})
|
||||||
|
until walkable(node) or height > entity_height
|
||||||
|
if height >= entity_height then
|
||||||
|
neighbor_clearance = true
|
||||||
|
end
|
||||||
|
elseif neighbor_ground_level.y - current_pos.y > 0 and walkable(node_above_head) then
|
||||||
|
neighbors[neighbors_index] = {
|
||||||
|
hash = nil,
|
||||||
|
pos = nil,
|
||||||
|
clear = nil,
|
||||||
|
walkable = nil,
|
||||||
|
}
|
||||||
|
else
|
||||||
|
local height = -1
|
||||||
|
repeat
|
||||||
|
height = height + 1
|
||||||
|
local node = minetest.get_node(
|
||||||
|
{x = neighbor_ground_level.x,
|
||||||
|
y = current_pos.y + height,
|
||||||
|
z = neighbor_ground_level.z})
|
||||||
|
until walkable(node) or height > entity_height
|
||||||
|
if height >= entity_height then
|
||||||
|
neighbor_clearance = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
neighbors[neighbors_index] = {
|
||||||
|
hash = minetest.hash_node_position(neighbor_ground_level),
|
||||||
|
pos = neighbor_ground_level,
|
||||||
|
clear = neighbor_clearance,
|
||||||
|
walkable = walkable(neighbor),
|
||||||
|
}
|
||||||
|
else
|
||||||
|
neighbors[neighbors_index] = {
|
||||||
|
hash = nil,
|
||||||
|
pos = nil,
|
||||||
|
clear = nil,
|
||||||
|
walkable = nil,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
neighbors_index = neighbors_index + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for id, neighbor in pairs(neighbors) do
|
||||||
|
-- don't cut corners
|
||||||
|
local cut_corner = false
|
||||||
|
if id == 1 then
|
||||||
|
if not neighbors[id + 1].clear or not neighbors[id + 3].clear
|
||||||
|
or neighbors[id + 1].walkable or neighbors[id + 3].walkable then
|
||||||
|
cut_corner = true
|
||||||
|
end
|
||||||
|
elseif id == 3 then
|
||||||
|
if not neighbors[id - 1].clear or not neighbors[id + 3].clear
|
||||||
|
or neighbors[id - 1].walkable or neighbors[id + 3].walkable then
|
||||||
|
cut_corner = true
|
||||||
|
end
|
||||||
|
elseif id == 7 then
|
||||||
|
if not neighbors[id + 1].clear or not neighbors[id - 3].clear
|
||||||
|
or neighbors[id + 1].walkable or neighbors[id - 3].walkable then
|
||||||
|
cut_corner = true
|
||||||
|
end
|
||||||
|
elseif id == 9 then
|
||||||
|
if not neighbors[id - 1].clear or not neighbors[id - 3].clear
|
||||||
|
or neighbors[id - 1].walkable or neighbors[id - 3].walkable then
|
||||||
|
cut_corner = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if neighbor.hash ~= current_index and not closedSet[neighbor.hash] and neighbor.clear and not cut_corner then
|
||||||
|
local move_cost_to_neighbor = current_values.gCost + get_distance_to_neighbor(current_values.pos, neighbor.pos)
|
||||||
|
local gCost = 0
|
||||||
|
if openSet[neighbor.hash] then
|
||||||
|
gCost = openSet[neighbor.hash].gCost
|
||||||
|
end
|
||||||
|
if move_cost_to_neighbor < gCost or not openSet[neighbor.hash] then
|
||||||
|
if not openSet[neighbor.hash] then
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
local hCost = get_distance(neighbor.pos, endpos)
|
||||||
|
openSet[neighbor.hash] = {
|
||||||
|
gCost = move_cost_to_neighbor,
|
||||||
|
hCost = hCost,
|
||||||
|
fCost = move_cost_to_neighbor + hCost,
|
||||||
|
parent = current_index,
|
||||||
|
pos = neighbor.pos
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if count > 100 then
|
||||||
|
-- print("fail")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
until count < 1
|
||||||
|
-- print("count < 1")
|
||||||
|
return {pos}
|
||||||
end
|
end
|
||||||
|
@ -22,8 +22,10 @@ npc.places.nodes = {
|
|||||||
},
|
},
|
||||||
SITTABLE_TYPE = {
|
SITTABLE_TYPE = {
|
||||||
"cottages:bench",
|
"cottages:bench",
|
||||||
|
-- Currently commented out since some NPCs
|
||||||
|
-- were sitting at stairs that are actually staircases
|
||||||
-- TODO: Register other stair types
|
-- TODO: Register other stair types
|
||||||
"stairs:stair_wood"
|
--"stairs:stair_wood"
|
||||||
},
|
},
|
||||||
STORAGE_TYPE = {
|
STORAGE_TYPE = {
|
||||||
"default:chest",
|
"default:chest",
|
||||||
@ -56,7 +58,16 @@ npc.places.PLACE_TYPE = {
|
|||||||
PRIMARY = "bed_primary"
|
PRIMARY = "bed_primary"
|
||||||
},
|
},
|
||||||
SITTABLE = {
|
SITTABLE = {
|
||||||
PRIMARY = "sit_primary"
|
PRIMARY = "sit_primary",
|
||||||
|
SHARED = "sit_shared"
|
||||||
|
},
|
||||||
|
FURNACE = {
|
||||||
|
PRIMARY = "furnace_primary",
|
||||||
|
SHARED = "furnace_shared"
|
||||||
|
},
|
||||||
|
STORAGE = {
|
||||||
|
PRIMARY = "storage_primary",
|
||||||
|
SHARED = "storage_shared"
|
||||||
},
|
},
|
||||||
OPENABLE = {
|
OPENABLE = {
|
||||||
HOME_ENTRANCE_DOOR = "home_entrance_door"
|
HOME_ENTRANCE_DOOR = "home_entrance_door"
|
||||||
@ -68,27 +79,51 @@ npc.places.PLACE_TYPE = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function npc.places.add_public(self, place_name, place_type, pos, access_node)
|
function npc.places.add_shared(self, place_name, place_type, pos, access_node)
|
||||||
--minetest.log("Place name: "..dump(place_name)..", type: "..dump(place_type))
|
|
||||||
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
|
||||||
|
|
||||||
-- Adds a specific node to the NPC places, and modifies the
|
|
||||||
-- node metadata to identify the NPC as the owner. This allows
|
|
||||||
-- other NPCs to avoid to take this as their own.
|
|
||||||
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)
|
||||||
-- Get node metadata
|
|
||||||
--local meta = minetest.get_meta(pos)
|
|
||||||
-- Check if it is owned by an NPC?
|
|
||||||
--if meta:get_string("npc_owner") == "" then
|
|
||||||
-- Set owned by NPC
|
|
||||||
--meta:set_string("npc_owner", self.npc_id)
|
|
||||||
-- Add place to list
|
|
||||||
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"}
|
||||||
--npc.places.add_public(self, place_name, place_type, pos)
|
end
|
||||||
return true
|
|
||||||
--end
|
function npc.places.add_unowned_accessible_place(self, nodes, place_type)
|
||||||
--return false
|
for i = 1, #nodes do
|
||||||
|
-- Check if node has owner
|
||||||
|
if nodes[i].owner == "" then
|
||||||
|
-- If node has no owner, check if it is accessible
|
||||||
|
local empty_nodes = npc.places.find_node_orthogonally(
|
||||||
|
nodes[i].node_pos, {"air"}, 0)
|
||||||
|
-- Check if node is accessible
|
||||||
|
if #empty_nodes > 0 then
|
||||||
|
-- Set owner to this NPC
|
||||||
|
nodes[i].owner = self.npc_id
|
||||||
|
-- Assign node to NPC
|
||||||
|
npc.places.add_owned(self, place_type, place_type,
|
||||||
|
nodes[i].node_pos, empty_nodes[1].pos)
|
||||||
|
npc.log("DEBUG", "Added node at "..minetest.pos_to_string(nodes[i].node_pos)
|
||||||
|
.." to NPC "..dump(self.npc_name))
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function npc.places.add_shared_accessible_place(self, nodes, place_type)
|
||||||
|
for i = 1, #nodes do
|
||||||
|
-- Check of not adding same owned sit
|
||||||
|
if nodes[i].owner ~= self.npc_id then
|
||||||
|
-- Check if it is accessible
|
||||||
|
local empty_nodes = npc.places.find_node_orthogonally(
|
||||||
|
nodes[i].node_pos, {"air"}, 0)
|
||||||
|
-- Check if bed is accessible
|
||||||
|
if #empty_nodes > 0 then
|
||||||
|
-- Assign node to NPC
|
||||||
|
npc.places.add_shared(self, place_type..dump(i),
|
||||||
|
place_type, nodes[i].node_pos, empty_nodes[1].pos)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function npc.places.get_by_type(self, place_type)
|
function npc.places.get_by_type(self, place_type)
|
||||||
@ -196,7 +231,7 @@ function npc.places.find_entrance_from_openable_nodes(all_openable_nodes, marker
|
|||||||
entity.collisionbox = {-0.20,-1.0,-0.20, 0.20,0.8,0.20}
|
entity.collisionbox = {-0.20,-1.0,-0.20, 0.20,0.8,0.20}
|
||||||
--minetest.log("Start pos: "..minetest.pos_to_string(start_pos))
|
--minetest.log("Start pos: "..minetest.pos_to_string(start_pos))
|
||||||
--minetest.log("End pos: "..minetest.pos_to_string(end_pos))
|
--minetest.log("End pos: "..minetest.pos_to_string(end_pos))
|
||||||
local path = pathfinder.find_path(start_pos, end_pos, entity)
|
local path = npc.pathfinder.find_path(start_pos, end_pos, entity, false)
|
||||||
--minetest.log("Found path: "..dump(path))
|
--minetest.log("Found path: "..dump(path))
|
||||||
if path ~= nil then
|
if path ~= nil then
|
||||||
--minetest.log("Path distance: "..dump(#path))
|
--minetest.log("Path distance: "..dump(#path))
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
default
|
default
|
||||||
mobs
|
mobs
|
||||||
|
mg_villages?
|
||||||
intllib?
|
intllib?
|
||||||
pathfinder
|
|
||||||
|
@ -1 +1 @@
|
|||||||
Adds simple NPC and Trader.
|
Adds NPCs which are smart, have homes, can talk, trade and even establish friendships and more with you!
|
94
dialogue.lua
@ -45,10 +45,10 @@ npc.dialogue.dialogue_results = {
|
|||||||
-- 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,
|
dialogue,
|
||||||
dismiss_option_label,
|
dismiss_option_label,
|
||||||
player_name)
|
player_name)
|
||||||
local responses = dialogue.responses
|
local responses = dialogue.responses
|
||||||
local options_length = table.getn(responses) + 1
|
local options_length = table.getn(responses) + 1
|
||||||
local formspec_height = (options_length * 0.7) + 0.4
|
local formspec_height = (options_length * 0.7) + 0.4
|
||||||
local formspec = "size[7,"..tostring(formspec_height).."]"
|
local formspec = "size[7,"..tostring(formspec_height).."]"
|
||||||
@ -81,22 +81,22 @@ end
|
|||||||
-- This function is used for showing a yes/no dialogue formspec
|
-- This function is used for showing a yes/no dialogue formspec
|
||||||
function npc.dialogue.show_yes_no_dialogue(self,
|
function npc.dialogue.show_yes_no_dialogue(self,
|
||||||
prompt,
|
prompt,
|
||||||
positive_answer_label,
|
positive_answer_label,
|
||||||
positive_callback,
|
positive_callback,
|
||||||
negative_answer_label,
|
negative_answer_label,
|
||||||
negative_callback,
|
negative_callback,
|
||||||
player_name)
|
player_name)
|
||||||
|
|
||||||
npc.lock_actions(self)
|
npc.lock_actions(self)
|
||||||
|
|
||||||
local formspec = "size[7,3]"..
|
local formspec = "size[7,3]"..
|
||||||
"label[0.5,0.1;"..prompt.."]"..
|
"label[0.5,0.1;"..prompt.."]"..
|
||||||
"button_exit[0.5,1.15;6,0.5;yes_option;"..positive_answer_label.."]"..
|
"button_exit[0.5,1.15;6,0.5;yes_option;"..positive_answer_label.."]"..
|
||||||
"button_exit[0.5,1.95;6,0.5;no_option;"..negative_answer_label.."]"
|
"button_exit[0.5,1.95;6,0.5;no_option;"..negative_answer_label.."]"
|
||||||
|
|
||||||
-- Create entry into responses table
|
-- Create entry into responses table
|
||||||
npc.dialogue.dialogue_results.yes_no_dialogue[player_name] = {
|
npc.dialogue.dialogue_results.yes_no_dialogue[player_name] = {
|
||||||
npc = self,
|
npc = self,
|
||||||
yes_callback = positive_callback,
|
yes_callback = positive_callback,
|
||||||
no_callback = negative_callback
|
no_callback = negative_callback
|
||||||
}
|
}
|
||||||
@ -418,8 +418,8 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
|
|||||||
if fields then
|
if fields then
|
||||||
local player_response = npc.dialogue.dialogue_results.yes_no_dialogue[player_name]
|
local player_response = npc.dialogue.dialogue_results.yes_no_dialogue[player_name]
|
||||||
|
|
||||||
-- 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)
|
||||||
|
|
||||||
if fields.yes_option then
|
if fields.yes_option then
|
||||||
player_response.yes_callback()
|
player_response.yes_callback()
|
||||||
@ -437,11 +437,11 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
|
|||||||
-- Get player response
|
-- Get player response
|
||||||
local player_response = npc.dialogue.dialogue_results.options_dialogue[player_name]
|
local player_response = npc.dialogue.dialogue_results.options_dialogue[player_name]
|
||||||
|
|
||||||
-- Check if the player hit the negative option
|
-- Check if the player hit the negative option or esc button
|
||||||
if fields["exit"] then
|
if fields["exit"] or fields["quit"] == "true" then
|
||||||
-- 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
|
||||||
|
|
||||||
for i = 1, #player_response.options do
|
for i = 1, #player_response.options do
|
||||||
local button_label = "opt"..tostring(i)
|
local button_label = "opt"..tostring(i)
|
||||||
@ -476,36 +476,36 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
|
|||||||
.responses[player_response.options[i].response_id]
|
.responses[player_response.options[i].response_id]
|
||||||
.action(player_response.npc, player)
|
.action(player_response.npc, player)
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
elseif player_response.is_dedicated_trade_dialogue == true then
|
elseif player_response.is_dedicated_trade_dialogue == true then
|
||||||
-- Get the functions for a dedicated trader prompt
|
-- Get the functions for a dedicated trader prompt
|
||||||
npc.trade.DEDICATED_TRADER_PROMPT
|
npc.trade.DEDICATED_TRADER_PROMPT
|
||||||
.responses[player_response.options[i].response_id]
|
.responses[player_response.options[i].response_id]
|
||||||
.action(player_response.npc, player)
|
.action(player_response.npc, player)
|
||||||
return
|
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 it is created in memory
|
||||||
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 dialogues for sex and phase
|
||||||
local dialogues = npc.data.DIALOGUES[player_response.npc.sex][phase]
|
local dialogues = npc.data.DIALOGUES[player_response.npc.sex][phase]
|
||||||
|
|
||||||
minetest.log("Object: "..dump(dialogues[player_response.options[i].dialogue_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)
|
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))
|
minetest.log("Found: "..dump(response))
|
||||||
|
|
||||||
-- Execute function
|
-- Execute function
|
||||||
response.action(player_response.npc, player)
|
response.action(player_response.npc, player)
|
||||||
|
|
||||||
-- Execute function
|
-- Execute function
|
||||||
-- dialogues[player_response.options[i].dialogue_id]
|
-- dialogues[player_response.options[i].dialogue_id]
|
||||||
-- .responses[player_response.options[i].response_id]
|
-- .responses[player_response.options[i].response_id]
|
||||||
-- .action(player_response.npc, player)
|
-- .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
|
||||||
|
1
init.lua
@ -32,7 +32,6 @@ dofile(path .. "/trade/prices.lua")
|
|||||||
dofile(path .. "/actions/actions.lua")
|
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/jumper.lua")
|
|
||||||
dofile(path .. "/actions/node_registry.lua")
|
dofile(path .. "/actions/node_registry.lua")
|
||||||
dofile(path .. "/random_data.lua")
|
dofile(path .. "/random_data.lua")
|
||||||
|
|
||||||
|
113
npc.lua
@ -44,14 +44,14 @@ npc.action_state = {
|
|||||||
|
|
||||||
npc.log_level = {
|
npc.log_level = {
|
||||||
INFO = true,
|
INFO = true,
|
||||||
WARNING = false,
|
WARNING = true,
|
||||||
ERROR = true,
|
ERROR = true,
|
||||||
DEBUG = false
|
DEBUG = false
|
||||||
}
|
}
|
||||||
|
|
||||||
npc.texture_check = {
|
npc.texture_check = {
|
||||||
timer = 0,
|
timer = 0,
|
||||||
interval = 0
|
interval = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
---------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------
|
||||||
@ -149,9 +149,9 @@ local function get_random_texture(sex, age)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Choose whether NPC can have relationships. Only 30% of NPCs cannot have relationships
|
-- Choose whether NPC can have relationships. Only 30% of NPCs cannot have relationships
|
||||||
local function can_have_relationships(age)
|
local function can_have_relationships(is_child)
|
||||||
-- Children can't have relationships
|
-- Children can't have relationships
|
||||||
if not age then
|
if is_child then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
local chance = math.random(1,10)
|
local chance = math.random(1,10)
|
||||||
@ -264,8 +264,8 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats)
|
|||||||
elseif child_s <= age_chance and age_chance <= child_e then
|
elseif child_s <= age_chance and age_chance <= child_e then
|
||||||
selected_age = npc.age.child
|
selected_age = npc.age.child
|
||||||
ent.visual_size = {
|
ent.visual_size = {
|
||||||
x = 0.5,
|
x = 0.75,
|
||||||
y = 0.5
|
y = 0.75
|
||||||
}
|
}
|
||||||
ent.collisionbox = {-0.10,-0.50,-0.10, 0.10,0.40,0.10}
|
ent.collisionbox = {-0.10,-0.50,-0.10, 0.10,0.40,0.10}
|
||||||
ent.is_child = true
|
ent.is_child = true
|
||||||
@ -310,6 +310,11 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats)
|
|||||||
-- Flag that determines if NPC can have a relationship
|
-- Flag that determines if NPC can have a relationship
|
||||||
ent.can_have_relationship = can_have_relationships(ent.is_child)
|
ent.can_have_relationship = can_have_relationships(ent.is_child)
|
||||||
|
|
||||||
|
ent.infotext = "Interested in relationships: "..dump(ent.can_have_relationship)
|
||||||
|
|
||||||
|
-- Flag to determine if NPC can receive gifts
|
||||||
|
ent.can_receive_gifts = ent.can_have_relationship
|
||||||
|
|
||||||
-- Initialize relationships object
|
-- Initialize relationships object
|
||||||
ent.relationships = {}
|
ent.relationships = {}
|
||||||
|
|
||||||
@ -455,6 +460,24 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats)
|
|||||||
ent.object:set_properties(ent)
|
ent.object:set_properties(ent)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---------------------------------------------------------------------------------------
|
||||||
|
-- Trading functions
|
||||||
|
---------------------------------------------------------------------------------------
|
||||||
|
function npc.generate_trade_list_from_inventory(self)
|
||||||
|
local list = {}
|
||||||
|
for i = 1, #self.inventory do
|
||||||
|
list[npc.get_item_name(self.inventory[i])] = {}
|
||||||
|
end
|
||||||
|
self.trader_data.trade_list.both = list
|
||||||
|
end
|
||||||
|
|
||||||
|
function npc.set_trading_status(self, status)
|
||||||
|
-- Set status
|
||||||
|
self.trader_data.trader_status = status
|
||||||
|
-- Re-generate trade offers
|
||||||
|
npc.trade.generate_trade_offers_by_status(self)
|
||||||
|
end
|
||||||
|
|
||||||
---------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------
|
||||||
-- Inventory functions
|
-- Inventory functions
|
||||||
---------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------
|
||||||
@ -743,6 +766,14 @@ npc.schedule_types = {
|
|||||||
["date_based"] = "date_based"
|
["date_based"] = "date_based"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
npc.schedule_properties = {
|
||||||
|
put_item = "put_item",
|
||||||
|
put_multiple_items = "put_multiple_items",
|
||||||
|
take_item = "take_item",
|
||||||
|
trader_status = "trader_status",
|
||||||
|
can_receive_gifts = "can_receive_gifts"
|
||||||
|
}
|
||||||
|
|
||||||
local function get_time_in_hours()
|
local function get_time_in_hours()
|
||||||
return minetest.get_timeofday() * 24
|
return minetest.get_timeofday() * 24
|
||||||
end
|
end
|
||||||
@ -861,6 +892,41 @@ function npc.delete_schedule_entry(self, schedule_type, date, time)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function npc.schedule_change_property(self, property, args)
|
||||||
|
if property == npc.schedule_properties.trader_status then
|
||||||
|
-- Get status from args
|
||||||
|
local status = args.status
|
||||||
|
-- Set status to NPC
|
||||||
|
npc.set_trading_status(self, status)
|
||||||
|
elseif property == npc.schedule_properties.put_item then
|
||||||
|
local itemstring = args.itemstring
|
||||||
|
-- Add item
|
||||||
|
npc.add_item_to_inventory_itemstring(self, itemstring)
|
||||||
|
elseif property == npc.schedule_properties.put_multiple_items then
|
||||||
|
local itemlist = args.itemlist
|
||||||
|
for i = 1, #itemlist do
|
||||||
|
local itemlist_entry = itemlist[i]
|
||||||
|
local current_itemstring = itemlist[i].name
|
||||||
|
if itemlist_entry.random == true then
|
||||||
|
current_itemstring = current_itemstring
|
||||||
|
.." "..dump(math.random(itemlist_entry.min, itemlist_entry.max))
|
||||||
|
else
|
||||||
|
current_itemstring = current_itemstring.." "..tostring(itemlist_entry.count)
|
||||||
|
end
|
||||||
|
-- Add item to inventory
|
||||||
|
npc.add_item_to_inventory_itemstring(self, current_itemstring)
|
||||||
|
end
|
||||||
|
elseif property == npc.schedule_properties.take_item then
|
||||||
|
local itemstring = args.itemstring
|
||||||
|
-- Add item
|
||||||
|
npc.take_item_from_inventory_itemstring(self, itemstring)
|
||||||
|
elseif property == npc.schedule_properties.can_receive_gifts then
|
||||||
|
local value = args.can_receive_gifts
|
||||||
|
-- Set status
|
||||||
|
self.can_receive_gifts = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------
|
||||||
-- NPC Definition
|
-- NPC Definition
|
||||||
---------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------
|
||||||
@ -884,12 +950,17 @@ mobs:register_mob("advanced_npc:npc", {
|
|||||||
drawtype = "front",
|
drawtype = "front",
|
||||||
textures = {
|
textures = {
|
||||||
{"npc_male1.png"},
|
{"npc_male1.png"},
|
||||||
|
--{"npc_male2.png"},
|
||||||
|
--{"npc_male3.png"},
|
||||||
|
--{"npc_male4.png"},
|
||||||
|
--{"npc_male5.png"},
|
||||||
|
--{"npc_male6.png"},
|
||||||
{"npc_female1.png"}, -- female by nuttmeg20
|
{"npc_female1.png"}, -- female by nuttmeg20
|
||||||
},
|
},
|
||||||
child_texture = {
|
child_texture = {
|
||||||
{"npc_baby_male1.png"},
|
{"npc_baby_male1.png"},
|
||||||
{"npc_baby_female1.png"},
|
{"npc_baby_female1.png"},
|
||||||
},
|
},
|
||||||
makes_footstep_sound = true,
|
makes_footstep_sound = true,
|
||||||
sounds = {},
|
sounds = {},
|
||||||
-- Added walk chance
|
-- Added walk chance
|
||||||
@ -898,7 +969,7 @@ mobs:register_mob("advanced_npc:npc", {
|
|||||||
stepheight = 0.6,
|
stepheight = 0.6,
|
||||||
walk_velocity = 1,
|
walk_velocity = 1,
|
||||||
run_velocity = 3,
|
run_velocity = 3,
|
||||||
jump = true,
|
jump = false,
|
||||||
drops = {
|
drops = {
|
||||||
{name = "default:wood", chance = 1, min = 1, max = 3},
|
{name = "default:wood", chance = 1, min = 1, max = 3},
|
||||||
{name = "default:apple", chance = 2, min = 1, max = 2},
|
{name = "default:apple", chance = 2, min = 1, max = 2},
|
||||||
@ -934,16 +1005,14 @@ mobs:register_mob("advanced_npc:npc", {
|
|||||||
local item = clicker:get_wielded_item()
|
local item = clicker:get_wielded_item()
|
||||||
local name = clicker:get_player_name()
|
local name = clicker:get_player_name()
|
||||||
|
|
||||||
--self.child = true
|
|
||||||
--self.textures = {"mobs_npc_child_male1.png"}
|
|
||||||
--self.base_texture = "mobs_npc_child_male1.png"
|
|
||||||
--self.object:set_properties(self)
|
|
||||||
|
|
||||||
npc.log("DEBUG", "Right-clicked NPC: "..dump(self))
|
npc.log("DEBUG", "Right-clicked NPC: "..dump(self))
|
||||||
|
|
||||||
-- Receive gift or start chat. If player has no item in hand
|
-- Receive gift or start chat. If player has no item in hand
|
||||||
-- then it is going to start chat directly
|
-- then it is going to start chat directly
|
||||||
if self.can_have_relationship and item:to_table() ~= nil then
|
minetest.log("self.can_have_relationship: "..dump(self.can_have_relationship)..", self.can_receive_gifts: "..dump(self.can_receive_gifts)..", table: "..dump(item:to_table()))
|
||||||
|
if self.can_have_relationship
|
||||||
|
and self.can_receive_gifts
|
||||||
|
and item:to_table() ~= nil then
|
||||||
-- Get item name
|
-- Get item name
|
||||||
local item = minetest.registered_items[item:get_name()]
|
local item = minetest.registered_items[item:get_name()]
|
||||||
local item_name = item.description
|
local item_name = item.description
|
||||||
@ -980,13 +1049,19 @@ mobs:register_mob("advanced_npc:npc", {
|
|||||||
-- NPC is initialized, check other variables
|
-- NPC is initialized, check other variables
|
||||||
-- Check child texture issues
|
-- Check child texture issues
|
||||||
if self.is_child then
|
if self.is_child then
|
||||||
|
-- Check texture
|
||||||
npc.texture_check.timer = npc.texture_check.timer + dtime
|
npc.texture_check.timer = npc.texture_check.timer + dtime
|
||||||
if npc.texture_check.timer > npc.texture_check.interval then
|
if npc.texture_check.timer > npc.texture_check.interval then
|
||||||
-- Reset timer
|
-- Reset timer
|
||||||
npc.texture_check.timer = 0
|
npc.texture_check.timer = 0
|
||||||
|
-- Set hornytimer to zero every 60 seconds so that children
|
||||||
|
-- don't grow automatically
|
||||||
|
self.hornytimer = 0
|
||||||
-- Set correct textures
|
-- Set correct textures
|
||||||
self.texture = {self.selected_texture}
|
self.texture = {self.selected_texture}
|
||||||
self.base_texture = {self.selected_texture}
|
self.base_texture = {self.selected_texture}
|
||||||
|
self.object:set_properties(self)
|
||||||
|
npc.log("WARNING", "Corrected textures on NPC child "..dump(self.npc_name))
|
||||||
-- Set interval to large interval so this code isn't called frequently
|
-- Set interval to large interval so this code isn't called frequently
|
||||||
npc.texture_check.interval = 60
|
npc.texture_check.interval = 60
|
||||||
end
|
end
|
||||||
@ -1086,12 +1161,16 @@ mobs:register_mob("advanced_npc:npc", {
|
|||||||
npc.log("DEBUG", "Adding actions to action queue")
|
npc.log("DEBUG", "Adding actions to action queue")
|
||||||
-- Add to action queue all actions on schedule
|
-- Add to action queue all actions on schedule
|
||||||
for i = 1, #schedule[time] do
|
for i = 1, #schedule[time] do
|
||||||
if schedule[time][i].action == nil then
|
--minetest.log("schedule[time]: "..dump(schedule[time]))
|
||||||
|
if schedule[time][i].task ~= nil then
|
||||||
-- Add task
|
-- Add task
|
||||||
npc.add_task(self, schedule[time][i].task, schedule[time][i].args)
|
npc.add_task(self, schedule[time][i].task, schedule[time][i].args)
|
||||||
else
|
elseif schedule[time][i].action ~= nil then
|
||||||
-- Add action
|
-- Add action
|
||||||
npc.add_action(self, schedule[time][i].action, schedule[time][i].args)
|
npc.add_action(self, schedule[time][i].action, schedule[time][i].args)
|
||||||
|
elseif schedule[time][i].property ~= nil then
|
||||||
|
-- Change NPC property
|
||||||
|
npc.schedule_change_property(self, schedule[time][i].property, schedule[time][i].args)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
npc.log("DEBUG", "New action queue: "..dump(self.actions))
|
npc.log("DEBUG", "New action queue: "..dump(self.actions))
|
||||||
|
@ -27,8 +27,8 @@ npc.relationships = {}
|
|||||||
npc.relationships.ITEM_GIFT_EFFECT = 2.5
|
npc.relationships.ITEM_GIFT_EFFECT = 2.5
|
||||||
|
|
||||||
-- Expected values for these are 720 each respectively
|
-- Expected values for these are 720 each respectively
|
||||||
npc.relationships.GIFT_TIMER_INTERVAL = 2
|
npc.relationships.GIFT_TIMER_INTERVAL = 360
|
||||||
npc.relationships.RELATIONSHIP_DECREASE_TIMER_INTERVAL = 60
|
npc.relationships.RELATIONSHIP_DECREASE_TIMER_INTERVAL = 720
|
||||||
|
|
||||||
npc.relationships.RELATIONSHIP_PHASE = {}
|
npc.relationships.RELATIONSHIP_PHASE = {}
|
||||||
-- Define phases
|
-- Define phases
|
||||||
@ -57,7 +57,8 @@ npc.relationships.MARRIED_NPC_DIALOGUE = {
|
|||||||
response_id = 2,
|
response_id = 2,
|
||||||
action = function(self, player)
|
action = function(self, player)
|
||||||
self.order = "stand"
|
self.order = "stand"
|
||||||
minetest.chat_send_player(player:get_player_name(), S("Ok dear, I will wait here for you."))
|
npc.chat(self.npc_name, player:get_player_name(),
|
||||||
|
S("Ok dear, I will wait here for you."))
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
[3] = {
|
[3] = {
|
||||||
@ -66,7 +67,7 @@ npc.relationships.MARRIED_NPC_DIALOGUE = {
|
|||||||
response_id = 3,
|
response_id = 3,
|
||||||
action = function(self, player)
|
action = function(self, player)
|
||||||
self.order = "follow"
|
self.order = "follow"
|
||||||
minetest.chat_send_player(player:get_player_name(), S("Ok, let's go!"))
|
npc.chat(self.npc_name, player:get_player_name(), S("Ok, let's go!"))
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,7 +115,7 @@ function npc.relationships.get_response_for_disliked_item(item_name, sex)
|
|||||||
for i = 1, #items do
|
for i = 1, #items do
|
||||||
minetest.log(dump(items[i]))
|
minetest.log(dump(items[i]))
|
||||||
if items[i].item == item_name then
|
if items[i].item == item_name then
|
||||||
minetest.log("Returning: "..dump(items[i].response))
|
--minetest.log("Returning: "..dump(items[i].response))
|
||||||
return items[i].response
|
return items[i].response
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -350,16 +351,16 @@ local function show_receive_gift_reaction(self, item_name, modifier, clicker_nam
|
|||||||
end
|
end
|
||||||
-- 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 message_to_send =
|
||||||
npc.relationships.get_response_for_favorite_item(item_name, self.sex, phase)
|
npc.relationships.get_response_for_favorite_item(item_name, self.sex, phase)
|
||||||
minetest.chat_send_player(clicker_name, message_to_send)
|
npc.chat(self.npc_name, clicker_name, message_to_send)
|
||||||
-- 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, "smoke.png")
|
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)
|
local message_to_send = npc.relationships.get_response_for_disliked_item(item_name, self.sex)
|
||||||
minetest.chat_send_player(clicker_name, message_to_send)
|
npc.chat(self.npc_name, clicker_name, message_to_send)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@ -381,7 +382,7 @@ function npc.relationships.receive_gift(self, clicker)
|
|||||||
|
|
||||||
-- If NPC received a gift from this person, then reject any more gifts for now
|
-- If NPC received a gift from this person, then reject any more gifts for now
|
||||||
if check_npc_can_receive_gift(self, clicker_name) == false then
|
if check_npc_can_receive_gift(self, clicker_name) == false then
|
||||||
minetest.chat_send_player(clicker_name, "Thanks, but I don't need anything for now")
|
npc.chat(self.npc_name, clicker_name, "Thanks, but I don't need anything for now")
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -392,9 +393,9 @@ function npc.relationships.receive_gift(self, clicker)
|
|||||||
npc.relationships.RELATIONSHIP_PHASE["phase5"].limit
|
npc.relationships.RELATIONSHIP_PHASE["phase5"].limit
|
||||||
and self.owner ~= clicker_name
|
and self.owner ~= clicker_name
|
||||||
and item:get_name() ~= "advanced_npc:marriage_ring" then
|
and item:get_name() ~= "advanced_npc:marriage_ring" then
|
||||||
minetest.chat_send_player(clicker_name,
|
npc.chat(self.npc_name, clicker_name,
|
||||||
"Thank you my love, but I think that you have given me")
|
"Thank you my love, but I think that you have given me")
|
||||||
minetest.chat_send_player(clicker_name,
|
npc.chat(self.npc_name, clicker_name,
|
||||||
"enough gifts for now. Maybe we should go a step further")
|
"enough gifts for now. Maybe we should go a step further")
|
||||||
-- Reset gift timer
|
-- Reset gift timer
|
||||||
reset_gift_timer(self, clicker_name)
|
reset_gift_timer(self, clicker_name)
|
||||||
@ -407,7 +408,7 @@ function npc.relationships.receive_gift(self, clicker)
|
|||||||
local receive_chance = math.random(1, 10)
|
local receive_chance = math.random(1, 10)
|
||||||
-- Receive ring and get married
|
-- Receive ring and get married
|
||||||
if receive_chance < 6 then
|
if receive_chance < 6 then
|
||||||
minetest.chat_send_player(clicker_name,
|
npc.chat(self.npc_name, clicker_name,
|
||||||
"Oh, oh you make me so happy! Yes! I will marry you!")
|
"Oh, oh you make me so happy! Yes! I will marry you!")
|
||||||
-- Get ring
|
-- Get ring
|
||||||
item:take_item()
|
item:take_item()
|
||||||
@ -422,7 +423,7 @@ function npc.relationships.receive_gift(self, clicker)
|
|||||||
self.owner = clicker_name
|
self.owner = clicker_name
|
||||||
-- Reject ring for now
|
-- Reject ring for now
|
||||||
else
|
else
|
||||||
minetest.chat_send_player(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
|
||||||
@ -446,7 +447,7 @@ function npc.relationships.receive_gift(self, clicker)
|
|||||||
show_receive_gift_reaction(self, item:get_name(), modifier, clicker_name, false)
|
show_receive_gift_reaction(self, item:get_name(), modifier, clicker_name, false)
|
||||||
else
|
else
|
||||||
-- Neutral item reaction
|
-- Neutral item reaction
|
||||||
minetest.chat_send_player(clicker_name, "Thank you honey!")
|
npc.chat(self.npc_name, clicker_name, "Thank you honey!")
|
||||||
end
|
end
|
||||||
-- Take item
|
-- Take item
|
||||||
item:take_item()
|
item:take_item()
|
||||||
@ -480,9 +481,9 @@ function npc.relationships.receive_gift(self, clicker)
|
|||||||
-- if 70%
|
-- if 70%
|
||||||
local receive_chance = math.random(1,10)
|
local receive_chance = math.random(1,10)
|
||||||
if receive_chance < 7 then
|
if receive_chance < 7 then
|
||||||
minetest.chat_send_player(clicker_name, "Thanks. I will find some use for this.")
|
npc.chat(self.npc_name, clicker_name, "Thanks. I will find some use for this.")
|
||||||
else
|
else
|
||||||
minetest.chat_send_player(clicker_name, "Thank you, but no, I have no use for this.")
|
npc.chat(self.npc_name, clicker_name, "Thank you, but no, I have no use for this.")
|
||||||
take = false
|
take = false
|
||||||
end
|
end
|
||||||
show_reaction = false
|
show_reaction = false
|
||||||
@ -502,7 +503,7 @@ function npc.relationships.receive_gift(self, clicker)
|
|||||||
clicker:set_wielded_item(item)
|
clicker:set_wielded_item(item)
|
||||||
end
|
end
|
||||||
|
|
||||||
minetest.log(dump(self))
|
npc.log("DEBUG", "NPC: "..dump(self))
|
||||||
-- Reset gift timer
|
-- Reset gift timer
|
||||||
reset_gift_timer(self, clicker_name)
|
reset_gift_timer(self, clicker_name)
|
||||||
return true
|
return true
|
||||||
|
243
spawner.lua
@ -88,16 +88,72 @@ local function get_basic_schedule()
|
|||||||
-- Allow mobs_redo wandering
|
-- Allow mobs_redo wandering
|
||||||
[3] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}}
|
[3] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}}
|
||||||
},
|
},
|
||||||
|
-- Noon actions: go inside the house
|
||||||
|
-- This will be executed around 12 PM MTG time
|
||||||
|
noon_actions = {
|
||||||
|
-- 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"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
-- 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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
-- Stay put into place
|
||||||
|
[3] = {action = npc.actions.cmd.FREEZE, args = {freeze = true}}
|
||||||
|
},
|
||||||
|
-- Afternoon actions: go inside the house
|
||||||
|
-- This will be executed around 1 PM MTG time
|
||||||
|
afternoon_actions = {
|
||||||
|
-- Get up of the sit
|
||||||
|
[1] = {task = npc.actions.cmd.USE_SITTABLE, args = {
|
||||||
|
pos = npc.places.PLACE_TYPE.SITTABLE.PRIMARY,
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
-- Change trader status to "trader"
|
||||||
|
[3] = {property = npc.schedule_properties.trader_status, args = {
|
||||||
|
status = npc.trade.TRADER
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[4] = {property = npc.schedule_properties.can_receive_gifts, args = {
|
||||||
|
value = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
-- Allow mobs_redo wandering
|
||||||
|
[5] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}}
|
||||||
|
},
|
||||||
-- Afternoon actions: go inside the house
|
-- Afternoon actions: go inside the house
|
||||||
-- This will be executed around 6 PM MTG time
|
-- This will be executed around 6 PM MTG time
|
||||||
afternoon_actions = {
|
late_afternoon_actions = {
|
||||||
|
-- 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
|
-- Get inside home
|
||||||
[1] = {task = npc.actions.cmd.WALK_TO_POS, args = {
|
[3] = {task = npc.actions.cmd.WALK_TO_POS, args = {
|
||||||
end_pos = npc.places.PLACE_TYPE.OTHER.HOME_INSIDE,
|
end_pos = npc.places.PLACE_TYPE.OTHER.HOME_INSIDE,
|
||||||
walkable = {}}
|
walkable = {}}
|
||||||
},
|
},
|
||||||
-- Allow mobs_redo wandering
|
-- Allow mobs_redo wandering
|
||||||
[2] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}}
|
[4] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}}
|
||||||
},
|
},
|
||||||
-- Evening actions: walk to bed and use it.
|
-- Evening actions: walk to bed and use it.
|
||||||
-- This will be executed around 10 PM MTG time
|
-- This will be executed around 10 PM MTG time
|
||||||
@ -164,6 +220,7 @@ function spawner.scan_area(pos1, pos2)
|
|||||||
|
|
||||||
result.bed_type = spawner.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.BED_TYPE)
|
result.bed_type = spawner.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.BED_TYPE)
|
||||||
result.sittable_type = spawner.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.SITTABLE_TYPE)
|
result.sittable_type = spawner.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.SITTABLE_TYPE)
|
||||||
|
-- Filter out
|
||||||
result.furnace_type = spawner.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.FURNACE_TYPE)
|
result.furnace_type = spawner.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.FURNACE_TYPE)
|
||||||
result.storage_type = spawner.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.STORAGE_TYPE)
|
result.storage_type = spawner.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.STORAGE_TYPE)
|
||||||
result.openable_type = spawner.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.OPENABLE_TYPE)
|
result.openable_type = spawner.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.OPENABLE_TYPE)
|
||||||
@ -190,49 +247,77 @@ function spawner.assign_places(self, pos)
|
|||||||
local node_data = minetest.deserialize(meta:get_string("node_data"))
|
local node_data = minetest.deserialize(meta:get_string("node_data"))
|
||||||
|
|
||||||
-- Assign plotmarker
|
-- Assign plotmarker
|
||||||
npc.places.add_public(self, npc.places.PLACE_TYPE.OTHER.HOME_PLOTMARKER,
|
npc.places.add_shared(self, npc.places.PLACE_TYPE.OTHER.HOME_PLOTMARKER,
|
||||||
npc.places.PLACE_TYPE.OTHER.HOME_PLOTMARKER, pos)
|
npc.places.PLACE_TYPE.OTHER.HOME_PLOTMARKER, pos)
|
||||||
|
|
||||||
-- Assign entrance door and related locations
|
-- Assign entrance door and related locations
|
||||||
if entrance ~= nil and entrance.node_pos ~= nil then
|
if entrance ~= nil and entrance.node_pos ~= nil then
|
||||||
npc.places.add_public(self, npc.places.PLACE_TYPE.OPENABLE.HOME_ENTRANCE_DOOR, npc.places.PLACE_TYPE.OPENABLE.HOME_ENTRANCE_DOOR, entrance.node_pos)
|
npc.places.add_shared(self, npc.places.PLACE_TYPE.OPENABLE.HOME_ENTRANCE_DOOR, npc.places.PLACE_TYPE.OPENABLE.HOME_ENTRANCE_DOOR, entrance.node_pos)
|
||||||
-- Find the position inside and outside the door
|
-- Find the position inside and outside the door
|
||||||
local entrance_inside = npc.places.find_node_behind_door(entrance.node_pos)
|
local entrance_inside = npc.places.find_node_behind_door(entrance.node_pos)
|
||||||
local entrance_outside = npc.places.find_node_in_front_of_door(entrance.node_pos)
|
local entrance_outside = npc.places.find_node_in_front_of_door(entrance.node_pos)
|
||||||
-- Assign these places to NPC
|
-- Assign these places to NPC
|
||||||
npc.places.add_public(self, npc.places.PLACE_TYPE.OTHER.HOME_INSIDE, npc.places.PLACE_TYPE.OTHER.HOME_INSIDE, entrance_inside)
|
npc.places.add_shared(self, npc.places.PLACE_TYPE.OTHER.HOME_INSIDE, npc.places.PLACE_TYPE.OTHER.HOME_INSIDE, entrance_inside)
|
||||||
npc.places.add_public(self, npc.places.PLACE_TYPE.OTHER.HOME_OUTSIDE, npc.places.PLACE_TYPE.OTHER.HOME_OUTSIDE, entrance_outside)
|
npc.places.add_shared(self, npc.places.PLACE_TYPE.OTHER.HOME_OUTSIDE, npc.places.PLACE_TYPE.OTHER.HOME_OUTSIDE, entrance_outside)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Assign beds
|
-- Assign beds
|
||||||
if #node_data.bed_type > 0 then
|
if #node_data.bed_type > 0 then
|
||||||
-- Find unowned bed
|
-- Assign a specific sittable node to a NPC.
|
||||||
for i = 1, #node_data.bed_type do
|
npc.places.add_unowned_accessible_place(self, node_data.bed_type,
|
||||||
-- Check if bed has owner
|
npc.places.PLACE_TYPE.BED.PRIMARY)
|
||||||
--minetest.log("Node: "..dump(node_data.bed_type[i]))
|
-- Store changes to node_data
|
||||||
if node_data.bed_type[i].owner == "" then
|
meta:set_string("node_data", minetest.serialize(node_data))
|
||||||
-- If bed has no owner, check if it is accessible
|
end
|
||||||
local empty_nodes = npc.places.find_node_orthogonally(
|
|
||||||
node_data.bed_type[i].node_pos, {"air"}, 0)
|
-- Assign sits
|
||||||
-- Check if bed is accessible
|
if #node_data.sittable_type > 0 then
|
||||||
if #empty_nodes > 0 then
|
-- Check if there are same or more amount of sits as beds
|
||||||
-- Set owner to this NPC
|
if #node_data.sittable_type >= #node_data.bed_type then
|
||||||
node_data.bed_type[i].owner = self.npc_id
|
-- Assign a specific sittable node to a NPC.
|
||||||
-- Assign node to NPC
|
npc.places.add_unowned_accessible_place(self, node_data.sittable_type,
|
||||||
npc.places.add_owned(self, npc.places.PLACE_TYPE.BED.PRIMARY,
|
npc.places.PLACE_TYPE.SITTABLE.PRIMARY)
|
||||||
npc.places.PLACE_TYPE.BED.PRIMARY, node_data.bed_type[i].node_pos, empty_nodes[1].pos)
|
-- Store changes to node_data
|
||||||
-- Store changes to node_data
|
meta:set_string("node_data", minetest.serialize(node_data))
|
||||||
meta:set_string("node_data", minetest.serialize(node_data))
|
end
|
||||||
npc.log("DEBUG", "Added bed at "..minetest.pos_to_string(node_data.bed_type[i].node_pos)
|
-- Add all sits to places as shared since NPC should be able to sit
|
||||||
.." to NPC "..dump(self.npc_name))
|
-- at any accessible sit
|
||||||
break
|
npc.places.add_shared_accessible_place(self, node_data.sittable_type,
|
||||||
end
|
npc.places.PLACE_TYPE.SITTABLE.SHARED)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
-- Assign furnaces
|
||||||
|
if #node_data.furnace_type > 0 then
|
||||||
|
-- Check if there are same or more amount of furnace as beds
|
||||||
|
if #node_data.furnace_type >= #node_data.bed_type then
|
||||||
|
-- Assign a specific furnace node to a NPC.
|
||||||
|
npc.places.add_unowned_accessible_place(self, node_data.furnace_type,
|
||||||
|
npc.places.PLACE_TYPE.FURNACE.PRIMARY)
|
||||||
|
-- Store changes to node_data
|
||||||
|
meta:set_string("node_data", minetest.serialize(node_data))
|
||||||
|
end
|
||||||
|
-- Add all furnaces to places as shared since NPC should be able to use
|
||||||
|
-- any accessible furnace
|
||||||
|
npc.places.add_shared_accessible_place(self, node_data.furnace_type,
|
||||||
|
npc.places.PLACE_TYPE.FURNACE.SHARED)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Assign storage nodes
|
||||||
|
if #node_data.storage_type > 0 then
|
||||||
|
-- Check if there are same or more amount of storage as beds
|
||||||
|
if #node_data.storage_type >= #node_data.bed_type then
|
||||||
|
-- Assign a specific storage node to a NPC.
|
||||||
|
npc.places.add_unowned_accessible_place(self, node_data.storage_type,
|
||||||
|
npc.places.PLACE_TYPE.STORAGE.PRIMARY)
|
||||||
|
-- Store changes to node_data
|
||||||
|
meta:set_string("node_data", minetest.serialize(node_data))
|
||||||
|
end
|
||||||
|
-- Add all storage-types to places as shared since NPC should be able
|
||||||
|
-- to use other storage nodes as well.
|
||||||
|
npc.places.add_shared_accessible_place(self, node_data.storage_type,
|
||||||
|
npc.places.PLACE_TYPE.STORAGE.SHARED)
|
||||||
end
|
end
|
||||||
|
|
||||||
--local plot_info = minetest.deserialize(meta:get_string("plot_info"))
|
|
||||||
--minetest.log("Plot info:"..dump(plot_info))
|
|
||||||
npc.log("DEBUG", "Places for NPC "..self.npc_name..": "..dump(self.places_map))
|
npc.log("DEBUG", "Places for NPC "..self.npc_name..": "..dump(self.places_map))
|
||||||
|
|
||||||
-- Make NPC go into their house
|
-- Make NPC go into their house
|
||||||
@ -253,8 +338,14 @@ function spawner.assign_schedules(self, pos)
|
|||||||
-- Add schedule entry for morning actions
|
-- Add schedule entry for morning actions
|
||||||
npc.add_schedule_entry(self, npc.schedule_types.generic, 0, 8, nil, basic_schedule.morning_actions)
|
npc.add_schedule_entry(self, npc.schedule_types.generic, 0, 8, nil, basic_schedule.morning_actions)
|
||||||
|
|
||||||
|
-- Add schedule entry for noon actions
|
||||||
|
npc.add_schedule_entry(self, npc.schedule_types.generic, 0, 12, nil, basic_schedule.noon_actions)
|
||||||
|
|
||||||
-- Add schedule entry for afternoon actions
|
-- Add schedule entry for afternoon actions
|
||||||
npc.add_schedule_entry(self, npc.schedule_types.generic, 0, 18, nil, basic_schedule.afternoon_actions)
|
npc.add_schedule_entry(self, npc.schedule_types.generic, 0, 13, nil, basic_schedule.afternoon_actions)
|
||||||
|
|
||||||
|
-- Add schedule entry for late afternoon actions
|
||||||
|
npc.add_schedule_entry(self, npc.schedule_types.generic, 0, 18, nil, basic_schedule.late_afternoon_actions)
|
||||||
|
|
||||||
-- Add schedule entry for evening actions
|
-- Add schedule entry for evening actions
|
||||||
npc.add_schedule_entry(self, npc.schedule_types.generic, 0, 22, nil, basic_schedule.evening_actions)
|
npc.add_schedule_entry(self, npc.schedule_types.generic, 0, 22, nil, basic_schedule.evening_actions)
|
||||||
@ -348,17 +439,15 @@ end
|
|||||||
|
|
||||||
-- This function takes care of calculating how many NPCs will be spawn
|
-- This function takes care of calculating how many NPCs will be spawn
|
||||||
function spawner.calculate_npc_spawning(pos)
|
function spawner.calculate_npc_spawning(pos)
|
||||||
-- Check node
|
|
||||||
local node = minetest.get_node(pos)
|
|
||||||
if node.name ~= "advanced_npc:plotmarker_auto_spawner" then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
-- Check node metadata
|
-- Check node metadata
|
||||||
local meta = minetest.get_meta(pos)
|
local meta = minetest.get_meta(pos)
|
||||||
|
if meta:get_string("replaced") ~= "true" then
|
||||||
|
return
|
||||||
|
end
|
||||||
-- Get nodes for this building
|
-- Get nodes for this building
|
||||||
local node_data = minetest.deserialize(meta:get_string("node_data"))
|
local node_data = minetest.deserialize(meta:get_string("node_data"))
|
||||||
if node_data == nil then
|
if node_data == nil then
|
||||||
npc.log("ERROR", "Mis-configured advanced_npc:plotmarker_auto_spawner at position: "..minetest.pos_to_string(pos))
|
npc.log("ERROR", "Mis-configured mg_villages:plotmarker at position: "..minetest.pos_to_string(pos))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
-- Check number of beds
|
-- Check number of beds
|
||||||
@ -468,6 +557,11 @@ function spawner.replace_mg_villages_plotmarker(pos)
|
|||||||
local village_id = meta:get_string("village_id")
|
local village_id = meta:get_string("village_id")
|
||||||
local plot_nr = meta:get_int("plot_nr")
|
local plot_nr = meta:get_int("plot_nr")
|
||||||
local infotext = meta:get_string("infotext")
|
local infotext = meta:get_string("infotext")
|
||||||
|
-- Check for nil values above
|
||||||
|
if (not village_id or (village and village == ""))
|
||||||
|
or (not plot_nr or (plot_nr and plot_nr == 0)) then
|
||||||
|
return
|
||||||
|
end
|
||||||
-- Following line from mg_villages mod, protection.lua
|
-- Following line from mg_villages mod, protection.lua
|
||||||
local btype = mg_villages.all_villages[village_id].to_add_data.bpos[plot_nr].btype
|
local btype = mg_villages.all_villages[village_id].to_add_data.bpos[plot_nr].btype
|
||||||
local building_data = mg_villages.BUILDINGS[btype]
|
local building_data = mg_villages.BUILDINGS[btype]
|
||||||
@ -479,7 +573,7 @@ function spawner.replace_mg_villages_plotmarker(pos)
|
|||||||
|
|
||||||
npc.log("INFO", "Replacing mg_villages:plotmarker at "..minetest.pos_to_string(pos))
|
npc.log("INFO", "Replacing mg_villages:plotmarker at "..minetest.pos_to_string(pos))
|
||||||
-- Replace the plotmarker for auto-spawner
|
-- Replace the plotmarker for auto-spawner
|
||||||
minetest.set_node(pos, {name="advanced_npc:plotmarker_auto_spawner"})
|
--minetest.set_node(pos, {name="advanced_npc:plotmarker_auto_spawner"})
|
||||||
-- Store old plotmarker metadata again
|
-- Store old plotmarker metadata again
|
||||||
meta:set_string("village_id", village_id)
|
meta:set_string("village_id", village_id)
|
||||||
meta:set_int("plot_nr", plot_nr)
|
meta:set_int("plot_nr", plot_nr)
|
||||||
@ -525,9 +619,12 @@ function spawner.replace_mg_villages_plotmarker(pos)
|
|||||||
child_total = 0
|
child_total = 0
|
||||||
}
|
}
|
||||||
meta:set_string("npc_stats", minetest.serialize(npc_stats))
|
meta:set_string("npc_stats", minetest.serialize(npc_stats))
|
||||||
|
-- Set replaced
|
||||||
|
meta:set_string("replaced", "true")
|
||||||
|
-- Calculate how many NPCs will spawn
|
||||||
|
spawner.calculate_npc_spawning(pos)
|
||||||
-- Stop searching for building type
|
-- Stop searching for building type
|
||||||
break
|
break
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -539,19 +636,19 @@ if minetest.get_modpath("mg_villages") ~= nil then
|
|||||||
-- Node registration
|
-- Node registration
|
||||||
-- This node is currently a slightly modified mg_villages:plotmarker
|
-- This node is currently a slightly modified mg_villages:plotmarker
|
||||||
-- TODO: Change formspec to a more detailed one.
|
-- TODO: Change formspec to a more detailed one.
|
||||||
minetest.register_node("advanced_npc:plotmarker_auto_spawner", {
|
minetest.override_item("mg_villages:plotmarker", {
|
||||||
description = "Automatic NPC Spawner",
|
-- description = "Automatic NPC Spawner",
|
||||||
drawtype = "nodebox",
|
-- drawtype = "nodebox",
|
||||||
tiles = {"default_stone.png"},
|
-- tiles = {"default_stone.png"},
|
||||||
paramtype = "light",
|
-- paramtype = "light",
|
||||||
paramtype2 = "facedir",
|
-- paramtype2 = "facedir",
|
||||||
node_box = {
|
-- node_box = {
|
||||||
type = "fixed",
|
-- type = "fixed",
|
||||||
fixed = {
|
-- fixed = {
|
||||||
{-0.5+2/16, -0.5, -0.5+2/16, 0.5-2/16, -0.5+2/16, 0.5-2/16},
|
-- {-0.5+2/16, -0.5, -0.5+2/16, 0.5-2/16, -0.5+2/16, 0.5-2/16},
|
||||||
--{-0.5+0/16, -0.5, -0.5+0/16, 0.5-0/16, -0.5+0/16, 0.5-0/16},
|
-- --{-0.5+0/16, -0.5, -0.5+0/16, 0.5-0/16, -0.5+0/16, 0.5-0/16},
|
||||||
}
|
-- }
|
||||||
},
|
-- },
|
||||||
walkable = false,
|
walkable = false,
|
||||||
groups = {cracky=3,stone=2},
|
groups = {cracky=3,stone=2},
|
||||||
|
|
||||||
@ -563,7 +660,7 @@ if minetest.get_modpath("mg_villages") ~= nil then
|
|||||||
--minetest.log("First-floor beds: "..dump(spawner.filter_first_floor_nodes(nodedata.bed_type, pos)))
|
--minetest.log("First-floor beds: "..dump(spawner.filter_first_floor_nodes(nodedata.bed_type, pos)))
|
||||||
--local entrance = npc.places.find_entrance_from_openable_nodes(nodedata.openable_type, pos)
|
--local entrance = npc.places.find_entrance_from_openable_nodes(nodedata.openable_type, pos)
|
||||||
--minetest.log("Found entrance: "..dump(entrance))
|
--minetest.log("Found entrance: "..dump(entrance))
|
||||||
|
minetest.log("Replaced: "..dump(minetest.get_meta(pos):get_string("replaced")))
|
||||||
-- for i = 1, #nodedata.bed_type do
|
-- for i = 1, #nodedata.bed_type do
|
||||||
-- nodedata.bed_type[i].owner = ""
|
-- nodedata.bed_type[i].owner = ""
|
||||||
-- end
|
-- end
|
||||||
@ -574,22 +671,22 @@ if minetest.get_modpath("mg_villages") ~= nil then
|
|||||||
return mg_villages.plotmarker_formspec( pos, nil, {}, clicker )
|
return mg_villages.plotmarker_formspec( pos, nil, {}, clicker )
|
||||||
end,
|
end,
|
||||||
|
|
||||||
on_receive_fields = function(pos, formname, fields, sender)
|
-- on_receive_fields = function(pos, formname, fields, sender)
|
||||||
return mg_villages.plotmarker_formspec( pos, formname, fields, sender );
|
-- return mg_villages.plotmarker_formspec( pos, formname, fields, sender );
|
||||||
end,
|
-- end,
|
||||||
|
|
||||||
on_timer = function(pos, elapsed)
|
on_timer = function(pos, elapsed)
|
||||||
npc.spawner.spawn_npc(pos)
|
npc.spawner.spawn_npc(pos)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
-- protect against digging
|
-- protect against digging
|
||||||
can_dig = function(pos, player)
|
-- can_dig = function(pos, player)
|
||||||
local meta = minetest.get_meta(pos);
|
-- local meta = minetest.get_meta(pos);
|
||||||
if (meta and meta:get_string("village_id") ~= "" and meta:get_int("plot_nr") and meta:get_int("plot_nr") > 0 ) then
|
-- if (meta and meta:get_string("village_id") ~= "" and meta:get_int("plot_nr") and meta:get_int("plot_nr") > 0 ) then
|
||||||
return false;
|
-- return false;
|
||||||
end
|
-- end
|
||||||
return true;
|
-- return true;
|
||||||
end
|
-- end
|
||||||
})
|
})
|
||||||
|
|
||||||
-- LBM Registration
|
-- LBM Registration
|
||||||
@ -615,21 +712,25 @@ if minetest.get_modpath("mg_villages") ~= nil then
|
|||||||
label = "Replace mg_villages:plotmarker with Advanced NPC auto spawners",
|
label = "Replace mg_villages:plotmarker with Advanced NPC auto spawners",
|
||||||
nodenames = {"mg_villages:plotmarker"},
|
nodenames = {"mg_villages:plotmarker"},
|
||||||
interval = 10,--npc.spawner.replacement_interval,
|
interval = 10,--npc.spawner.replacement_interval,
|
||||||
chance = 1, --5,
|
chance = 1,--5,
|
||||||
catch_up = true,
|
catch_up = true,
|
||||||
action = function(pos, node, active_object_count, active_object_count_wider)
|
action = function(pos, node, active_object_count, active_object_count_wider)
|
||||||
|
-- Check if replacement is needed
|
||||||
|
if minetest.get_meta(pos):get_string("replaced") == "true" then
|
||||||
|
return
|
||||||
|
end
|
||||||
-- Check if replacement is activated
|
-- Check if replacement is activated
|
||||||
if npc.spawner.replace_activated then
|
if npc.spawner.replace_activated then
|
||||||
-- Replace mg_villages:plotmarker
|
-- Replace mg_villages:plotmarker
|
||||||
spawner.replace_mg_villages_plotmarker(pos)
|
spawner.replace_mg_villages_plotmarker(pos)
|
||||||
-- Set NPCs to spawn
|
|
||||||
spawner.calculate_npc_spawning(pos)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--minetest.register_alias_force("mg_villages:plotmarker", )
|
||||||
|
|
||||||
-- Chat commands to manage spawners
|
-- Chat commands to manage spawners
|
||||||
minetest.register_chatcommand("restore_plotmarkers", {
|
minetest.register_chatcommand("restore_plotmarkers", {
|
||||||
description = "Replaces all advanced_npc:plotmarker_auto_spawner with mg_villages:plotmarker in the specified radius.",
|
description = "Replaces all advanced_npc:plotmarker_auto_spawner with mg_villages:plotmarker in the specified radius.",
|
||||||
@ -652,7 +753,7 @@ minetest.register_chatcommand("restore_plotmarkers", {
|
|||||||
local start_pos = {x=pos.x - radius, y=pos.y - radius, z=pos.z - radius}
|
local start_pos = {x=pos.x - radius, y=pos.y - radius, z=pos.z - radius}
|
||||||
local end_pos = {x=pos.x + radius, y=pos.y + radius, z=pos.z + radius}
|
local end_pos = {x=pos.x + radius, y=pos.y + radius, z=pos.z + radius}
|
||||||
local nodes = minetest.find_nodes_in_area_under_air(start_pos, end_pos,
|
local nodes = minetest.find_nodes_in_area_under_air(start_pos, end_pos,
|
||||||
{"advanced_npc:plotmarker_auto_spawner"})
|
{"mg_villages:plotmarker"})
|
||||||
-- Check if we have nodes to replace
|
-- Check if we have nodes to replace
|
||||||
minetest.chat_send_player(name, "Found "..dump(#nodes).." nodes to replace...")
|
minetest.chat_send_player(name, "Found "..dump(#nodes).." nodes to replace...")
|
||||||
if #nodes == 0 then
|
if #nodes == 0 then
|
||||||
@ -660,13 +761,10 @@ minetest.register_chatcommand("restore_plotmarkers", {
|
|||||||
end
|
end
|
||||||
-- Replace all nodes
|
-- Replace all nodes
|
||||||
for i = 1, #nodes do
|
for i = 1, #nodes do
|
||||||
--minetest.log(dump(nodes[i]))
|
|
||||||
local meta = minetest.get_meta(nodes[i])
|
local meta = minetest.get_meta(nodes[i])
|
||||||
local village_id = meta:get_string("village_id")
|
local village_id = meta:get_string("village_id")
|
||||||
local plot_nr = meta:get_int("plot_nr")
|
local plot_nr = meta:get_int("plot_nr")
|
||||||
local infotext = meta:get_string("infotext")
|
local infotext = meta:get_string("infotext")
|
||||||
-- Replace node
|
|
||||||
minetest.set_node(nodes[i], {name="mg_villages:plotmarker"})
|
|
||||||
-- Set metadata
|
-- Set metadata
|
||||||
meta = minetest.get_meta(nodes[i])
|
meta = minetest.get_meta(nodes[i])
|
||||||
meta:set_string("village_id", village_id)
|
meta:set_string("village_id", village_id)
|
||||||
@ -676,6 +774,7 @@ minetest.register_chatcommand("restore_plotmarkers", {
|
|||||||
meta:set_string("node_data", nil)
|
meta:set_string("node_data", nil)
|
||||||
meta:set_string("npcs", nil)
|
meta:set_string("npcs", nil)
|
||||||
meta:set_string("npc_stats", nil)
|
meta:set_string("npc_stats", nil)
|
||||||
|
meta:set_string("replaced", "false")
|
||||||
end
|
end
|
||||||
minetest.chat_send_player(name, "Finished replacement of "..dump(#nodes).." auto-spawners successfully")
|
minetest.chat_send_player(name, "Finished replacement of "..dump(#nodes).." auto-spawners successfully")
|
||||||
end
|
end
|
||||||
|
Before Width: | Height: | Size: 323 B |
Before Width: | Height: | Size: 267 B |
Before Width: | Height: | Size: 783 B |
Before Width: | Height: | Size: 783 B |
Before Width: | Height: | Size: 779 B |
Before Width: | Height: | Size: 901 B After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 202 B |