Spawner: Large code refactor to remove dependency on plotmarkers.
Most spawner functions can now be called without giving a plotmarker. Move scanning functions to places.lua. Places: Cleanup and add more area-scanning functions. Schedules: Bugfix where schedules weren't being executed due to wrong "end" order in the do_custom() function. Data: Moved random data to "data" folder. Textures: Add 14 male textures and 10 female textures. Occupations: Small tweaks to "default_basic" occupation.
@ -155,6 +155,12 @@ function npc.places.get_by_type(self, place_type)
|
|||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---------------------------------------------------------------------------------------
|
||||||
|
-- Utility functions
|
||||||
|
---------------------------------------------------------------------------------------
|
||||||
|
-- The following are utility functions that are used to operate on nodes for
|
||||||
|
-- specific conditions
|
||||||
|
|
||||||
-- This function searches on a squared are of the given radius
|
-- This function searches on a squared are of the given radius
|
||||||
-- for nodes of the given type. The type should be npc.places.nodes
|
-- for nodes of the given type. The type should be npc.places.nodes
|
||||||
function npc.places.find_node_nearby(pos, type, radius)
|
function npc.places.find_node_nearby(pos, type, radius)
|
||||||
@ -188,11 +194,64 @@ function npc.places.find_node_orthogonally(pos, nodes, y_adjustment)
|
|||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Wrapper around minetest.find_nodes_in_area()
|
||||||
|
-- TODO: Verify if this wrapper is actually needed
|
||||||
function npc.places.find_node_in_area(start_pos, end_pos, type)
|
function npc.places.find_node_in_area(start_pos, end_pos, type)
|
||||||
local nodes = minetest.find_nodes_in_area(start_pos, end_pos, type)
|
local nodes = minetest.find_nodes_in_area(start_pos, end_pos, type)
|
||||||
return nodes
|
return nodes
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Function used to filter all nodes in the first floor of a building
|
||||||
|
-- If floor height isn't given, it will assume 2
|
||||||
|
-- Notice that nodes is an array of entries {node_pos={}, type={}}
|
||||||
|
function npc.places.filter_first_floor_nodes(nodes, ground_pos, floor_height)
|
||||||
|
local height = floor_height or 2
|
||||||
|
local result = {}
|
||||||
|
for _,node in pairs(nodes) do
|
||||||
|
if node.node_pos.y <= ground_pos.y + height then
|
||||||
|
table.insert(result, node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Creates an array of {pos=<node_pos>, owner=''} for managing
|
||||||
|
-- which NPC owns what
|
||||||
|
function npc.places.get_nodes_by_type(start_pos, end_pos, type)
|
||||||
|
local result = {}
|
||||||
|
local nodes = npc.places.find_node_in_area(start_pos, end_pos, type)
|
||||||
|
--minetest.log("Found "..dump(#nodes).." nodes of type: "..dump(type))
|
||||||
|
for _,node_pos in pairs(nodes) do
|
||||||
|
local entry = {}
|
||||||
|
entry["node_pos"] = node_pos
|
||||||
|
entry["owner"] = ''
|
||||||
|
table.insert(result, entry)
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Scans an area for the supported nodes: beds, benches,
|
||||||
|
-- furnaces, storage (e.g. chests) and openable (e.g. doors).
|
||||||
|
-- Returns a table with these classifications
|
||||||
|
function npc.places.scan_area_for_usable_nodes(pos1, pos2)
|
||||||
|
local result = {
|
||||||
|
bed_type = {},
|
||||||
|
sittable_type = {},
|
||||||
|
furnace_type = {},
|
||||||
|
storage_type = {},
|
||||||
|
openable_type = {}
|
||||||
|
}
|
||||||
|
local start_pos, end_pos = vector.sort(pos1, pos2)
|
||||||
|
|
||||||
|
result.bed_type = npc.places.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.BED_TYPE)
|
||||||
|
result.sittable_type = npc.places.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.SITTABLE_TYPE)
|
||||||
|
result.furnace_type = npc.places.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.FURNACE_TYPE)
|
||||||
|
result.storage_type = npc.places.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.STORAGE_TYPE)
|
||||||
|
result.openable_type = npc.places.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.OPENABLE_TYPE)
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
-- Specialized function to find doors that are an entrance to a building.
|
-- Specialized function to find doors that are an entrance to a building.
|
||||||
-- The definition of an entrance is:
|
-- The definition of an entrance is:
|
||||||
-- The openable node with the shortest path to the plotmarker node
|
-- The openable node with the shortest path to the plotmarker node
|
||||||
@ -231,18 +290,8 @@ function npc.places.find_entrance_from_openable_nodes(all_openable_nodes, marker
|
|||||||
local start_pos = {x=open_pos.x, y=open_pos.y, z=open_pos.z}
|
local start_pos = {x=open_pos.x, y=open_pos.y, z=open_pos.z}
|
||||||
local end_pos = {x=marker_pos.x, y=marker_pos.y, z=marker_pos.z}
|
local end_pos = {x=marker_pos.x, y=marker_pos.y, z=marker_pos.z}
|
||||||
|
|
||||||
-- Check if there's any difference in vertical position
|
|
||||||
-- minetest.log("Openable node pos: "..minetest.pos_to_string(open_pos))
|
-- minetest.log("Openable node pos: "..minetest.pos_to_string(open_pos))
|
||||||
-- minetest.log("Plotmarker node pos: "..minetest.pos_to_string(marker_pos))
|
-- minetest.log("Plotmarker node pos: "..minetest.pos_to_string(marker_pos))
|
||||||
-- NOTE: Commented out while testing MarkBu's pathfinder
|
|
||||||
--if start_pos.y ~= end_pos.y then
|
|
||||||
-- Adjust to make pathfinder find nodes one node above
|
|
||||||
-- end_pos.y = start_pos.y
|
|
||||||
--end
|
|
||||||
|
|
||||||
-- This adjustment allows the map to be created correctly
|
|
||||||
--start_pos.y = start_pos.y + 1
|
|
||||||
--end_pos.y = end_pos.y + 1
|
|
||||||
|
|
||||||
-- Find path from the openable node to the plotmarker
|
-- Find path from the openable node to the plotmarker
|
||||||
--local path = pathfinder.find_path(start_pos, end_pos, 20, {})
|
--local path = pathfinder.find_path(start_pos, end_pos, 20, {})
|
||||||
|
8
init.lua
@ -37,9 +37,9 @@ dofile(path .. "/actions/node_registry.lua")
|
|||||||
dofile(path .. "/occupations/occupations.lua")
|
dofile(path .. "/occupations/occupations.lua")
|
||||||
-- Load random data definitions
|
-- Load random data definitions
|
||||||
dofile(path .. "/random_data.lua")
|
dofile(path .. "/random_data.lua")
|
||||||
dofile(path .. "/random_data/dialogues_data.lua")
|
dofile(path .. "/data/dialogues_data.lua")
|
||||||
dofile(path .. "/random_data/gift_items_data.lua")
|
dofile(path .. "/data/gift_items_data.lua")
|
||||||
dofile(path .. "/random_data/names_data.lua")
|
dofile(path .. "/data/names_data.lua")
|
||||||
dofile(path .. "/random_data/occupations_data.lua")
|
dofile(path .. "/data/occupations_data.lua")
|
||||||
|
|
||||||
print (S("[Mod] Advanced NPC loaded"))
|
print (S("[Mod] Advanced NPC loaded"))
|
||||||
|
39
npc.lua
@ -474,7 +474,8 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats, occupation_name)
|
|||||||
|
|
||||||
-- If occupation name given, override properties with
|
-- If occupation name given, override properties with
|
||||||
-- occupation values and initialize schedules
|
-- occupation values and initialize schedules
|
||||||
minetest.log("Entity age: "..dump(ent.age)..", afult? "..dump(ent.age==npc.age.adult))
|
--minetest.log("Entity age: "..dump(ent.age)..", afult? "..dump(ent.age==npc.age.adult))
|
||||||
|
npc.log("INFO", "Overriding NPC values with occupation: "..dump(occupation_name))
|
||||||
if occupation_name and occupation_name ~= "" and ent.age == npc.age.adult then
|
if occupation_name and occupation_name ~= "" and ent.age == npc.age.adult then
|
||||||
npc.occupations.initialize_occupation_values(ent, occupation_name)
|
npc.occupations.initialize_occupation_values(ent, occupation_name)
|
||||||
end
|
end
|
||||||
@ -508,7 +509,7 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats, occupation_name)
|
|||||||
|
|
||||||
-- Set initialized flag on
|
-- Set initialized flag on
|
||||||
ent.initialized = true
|
ent.initialized = true
|
||||||
npc.log("WARNING", "Spawned entity: "..dump(ent))
|
--npc.log("WARNING", "Spawned entity: "..dump(ent))
|
||||||
npc.log("INFO", "Successfully initialized NPC with name "..dump(ent.npc_name)
|
npc.log("INFO", "Successfully initialized NPC with name "..dump(ent.npc_name)
|
||||||
..", sex: "..ent.sex..", is child: "..dump(ent.is_child)
|
..", sex: "..ent.sex..", is child: "..dump(ent.is_child)
|
||||||
..", texture: "..dump(ent.textures))
|
..", texture: "..dump(ent.textures))
|
||||||
@ -1156,7 +1157,26 @@ mobs:register_mob("advanced_npc:npc", {
|
|||||||
{"npc_male4.png"},
|
{"npc_male4.png"},
|
||||||
{"npc_male5.png"},
|
{"npc_male5.png"},
|
||||||
{"npc_male6.png"},
|
{"npc_male6.png"},
|
||||||
|
{"npc_male7.png"},
|
||||||
|
{"npc_male8.png"},
|
||||||
|
{"npc_male9.png"},
|
||||||
|
{"npc_male10.png"},
|
||||||
|
{"npc_male11.png"},
|
||||||
|
{"npc_male12.png"},
|
||||||
|
{"npc_male13.png"},
|
||||||
|
{"npc_male14.png"},
|
||||||
|
{"npc_male15.png"},
|
||||||
{"npc_female1.png"}, -- female by nuttmeg20
|
{"npc_female1.png"}, -- female by nuttmeg20
|
||||||
|
{"npc_female2.png"},
|
||||||
|
{"npc_female3.png"},
|
||||||
|
{"npc_female4.png"},
|
||||||
|
{"npc_female5.png"},
|
||||||
|
{"npc_female6.png"},
|
||||||
|
{"npc_female7.png"},
|
||||||
|
{"npc_female8.png"},
|
||||||
|
{"npc_female9.png"},
|
||||||
|
{"npc_female10.png"},
|
||||||
|
{"npc_female11.png"},
|
||||||
},
|
},
|
||||||
child_texture = {
|
child_texture = {
|
||||||
{"npc_child_male1.png"},
|
{"npc_child_male1.png"},
|
||||||
@ -1206,7 +1226,7 @@ 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()
|
||||||
|
|
||||||
npc.log("DEBUG", "Right-clicked NPC: "..dump(self))
|
npc.log("INFO", "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
|
||||||
@ -1235,7 +1255,6 @@ mobs:register_mob("advanced_npc:npc", {
|
|||||||
else
|
else
|
||||||
npc.start_dialogue(self, clicker, true)
|
npc.start_dialogue(self, clicker, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
end,
|
end,
|
||||||
do_custom = function(self, dtime)
|
do_custom = function(self, dtime)
|
||||||
if self.initialized == nil then
|
if self.initialized == nil then
|
||||||
@ -1266,6 +1285,7 @@ mobs:register_mob("advanced_npc:npc", {
|
|||||||
-- 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
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Timer function for casual traders to reset their trade offers
|
-- Timer function for casual traders to reset their trade offers
|
||||||
@ -1341,25 +1361,24 @@ mobs:register_mob("advanced_npc:npc", {
|
|||||||
-- Get time of day
|
-- Get time of day
|
||||||
local time = get_time_in_hours()
|
local time = get_time_in_hours()
|
||||||
-- Check if time is an hour
|
-- Check if time is an hour
|
||||||
if time % 1 < 0.1 and self.schedules.lock == false then
|
if ((time % 1) < dtime) and self.schedules.lock == false then
|
||||||
-- Activate lock to avoid more than one entry to this code
|
-- Activate lock to avoid more than one entry to this code
|
||||||
self.schedules.lock = true
|
self.schedules.lock = true
|
||||||
-- Get integer part of time
|
-- Get integer part of time
|
||||||
time = (time) - (time % 1)
|
time = (time) - (time % 1)
|
||||||
-- Check if there is a schedule entry for this time
|
-- Check if there is a schedule entry for this time
|
||||||
-- Note: Currently only one schedule is supported, for day 0
|
-- Note: Currently only one schedule is supported, for day 0
|
||||||
--minetest.log("Time: "..dump(time))
|
minetest.log("Time: "..dump(time))
|
||||||
local schedule = self.schedules.generic[0]
|
local schedule = self.schedules.generic[0]
|
||||||
if schedule ~= nil then
|
if schedule ~= nil then
|
||||||
-- Check if schedule for this time exists
|
-- Check if schedule for this time exists
|
||||||
--minetest.log("Found default schedule")
|
|
||||||
if schedule[time] ~= nil then
|
if schedule[time] ~= nil then
|
||||||
npc.log("WARNING", "Found schedule for time "..dump(time)..": "..dump(schedule[time]))
|
npc.log("WARNING", "Found schedule for time "..dump(time)..": "..dump(schedule[time]))
|
||||||
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
|
||||||
-- Check if schedule has a check function
|
-- Check if schedule has a check function
|
||||||
if not schedule[time][i].check then
|
if schedule[time][i].check then
|
||||||
-- Add parameters for check function and run for first time
|
-- Add parameters for check function and run for first time
|
||||||
npc.log("INFO", "NPC "..dump(self.npc_name).." is starting check on "..minetest.pos_to_string(self.object:getpos()))
|
npc.log("INFO", "NPC "..dump(self.npc_name).." is starting check on "..minetest.pos_to_string(self.object:getpos()))
|
||||||
local check_params = schedule[time][i]
|
local check_params = schedule[time][i]
|
||||||
@ -1380,6 +1399,7 @@ mobs:register_mob("advanced_npc:npc", {
|
|||||||
-- Execute check for the first time
|
-- Execute check for the first time
|
||||||
npc.schedule_check(self)
|
npc.schedule_check(self)
|
||||||
else
|
else
|
||||||
|
npc.log("INFO", "Executing schedule entry: "..dump(schedule[time][i]))
|
||||||
-- Run usual schedule entry
|
-- Run usual schedule entry
|
||||||
-- Check chance
|
-- Check chance
|
||||||
local execution_chance = math.random(1, 100)
|
local execution_chance = math.random(1, 100)
|
||||||
@ -1423,10 +1443,9 @@ mobs:register_mob("advanced_npc:npc", {
|
|||||||
npc.log("WARNING", "New action queue: "..dump(self.actions))
|
npc.log("WARNING", "New action queue: "..dump(self.actions))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
else
|
else
|
||||||
-- Check if lock can be released
|
-- Check if lock can be released
|
||||||
if time % 1 > 0.1 then
|
if (time % 1) > dtime then
|
||||||
-- Release lock
|
-- Release lock
|
||||||
self.schedules.lock = false
|
self.schedules.lock = false
|
||||||
end
|
end
|
||||||
|
@ -95,10 +95,7 @@ npc.occupations.registered_occupations = {}
|
|||||||
-- don't have a specific occupation. It serves as an example.
|
-- don't have a specific occupation. It serves as an example.
|
||||||
npc.occupations.basic_def = {
|
npc.occupations.basic_def = {
|
||||||
-- Use random textures
|
-- Use random textures
|
||||||
textures = {
|
textures = {},
|
||||||
{"npc_male1.png"},
|
|
||||||
{"npc_child_male1.png"}
|
|
||||||
},
|
|
||||||
-- Use random dialogues
|
-- Use random dialogues
|
||||||
dialogues = {},
|
dialogues = {},
|
||||||
-- Initialize inventory with random items
|
-- Initialize inventory with random items
|
||||||
@ -240,6 +237,7 @@ npc.occupations.basic_def = {
|
|||||||
-- This function registers an occupation
|
-- This function registers an occupation
|
||||||
function npc.occupations.register_occupation(name, def)
|
function npc.occupations.register_occupation(name, def)
|
||||||
npc.occupations.registered_occupations[name] = def
|
npc.occupations.registered_occupations[name] = def
|
||||||
|
npc.log("INFO", "Successfully registered occupation with name: "..dump(name))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- This function scans all registered occupations and filter them by
|
-- This function scans all registered occupations and filter them by
|
||||||
|
592
spawner.lua
@ -1,8 +1,12 @@
|
|||||||
-- Advanced NPC spawner by Zorman2000
|
-- Advanced NPC spawner by Zorman2000
|
||||||
-- The advanced spawner will contain functionality to spawn NPC correctly on
|
-- The advanced spawner will contain functionality to spawn NPC correctly on
|
||||||
-- mg_villages building. The spawn node will be the mg_villages:plotmarker.
|
-- custom places, as well as in mg_villages building.
|
||||||
-- This node will be replaced with one that will perform the following functions:
|
-- This works by using a special node to spawn NPCs on either a custom building or
|
||||||
--
|
-- on mg_villages building.
|
||||||
|
|
||||||
|
-- mg_villages functionality:
|
||||||
|
-- The spawn node for mg_villages will be the mg_villages:plotmarker.
|
||||||
|
-- Based on this node, the following things will be performed
|
||||||
-- - Scan the current building, check if it is of type:
|
-- - Scan the current building, check if it is of type:
|
||||||
-- - House
|
-- - House
|
||||||
-- - Farm
|
-- - Farm
|
||||||
@ -67,173 +71,252 @@ npc.spawner.spawn_data = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
local function get_basic_schedule()
|
-- Array of nodes that serve as plotmarker of a plot, and therefore
|
||||||
return {
|
-- as auto-spawners
|
||||||
-- Create schedule entries
|
spawner.plotmarker_nodes = {}
|
||||||
-- Morning actions: get out of bed, walk to outside of house
|
-- Array of items that are used to spawn NPCs
|
||||||
-- This will be executed around 8 AM MTG time
|
spawner.spawn_eggs = {}
|
||||||
morning_actions = {
|
|
||||||
-- Get out of bed
|
|
||||||
[1] = {task = npc.actions.cmd.USE_BED, args = {
|
|
||||||
pos = npc.places.PLACE_TYPE.BED.PRIMARY,
|
|
||||||
action = npc.actions.const.beds.GET_UP
|
|
||||||
}
|
|
||||||
},
|
|
||||||
-- Walk outside
|
|
||||||
[2] = {task = npc.actions.cmd.WALK_TO_POS, args = {
|
|
||||||
end_pos = npc.places.PLACE_TYPE.OTHER.HOME_OUTSIDE,
|
|
||||||
walkable = {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
-- Allow mobs_redo wandering
|
|
||||||
[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
|
|
||||||
-- This will be executed around 6 PM MTG time
|
|
||||||
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
|
|
||||||
[3] = {task = npc.actions.cmd.WALK_TO_POS, args = {
|
|
||||||
end_pos = npc.places.PLACE_TYPE.OTHER.HOME_INSIDE,
|
|
||||||
walkable = {}}
|
|
||||||
},
|
|
||||||
-- Allow mobs_redo wandering
|
|
||||||
[4] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}}
|
|
||||||
},
|
|
||||||
-- Evening actions: walk to bed and use it.
|
|
||||||
-- This will be executed around 10 PM MTG time
|
|
||||||
evening_actions = {
|
|
||||||
[1] = {task = npc.actions.cmd.WALK_TO_POS, args = {
|
|
||||||
end_pos = {place_type=npc.places.PLACE_TYPE.BED.PRIMARY, use_access_node=true},
|
|
||||||
walkable = {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
-- Use bed
|
|
||||||
[2] = {task = npc.actions.cmd.USE_BED, args = {
|
|
||||||
pos = npc.places.PLACE_TYPE.BED.PRIMARY,
|
|
||||||
action = npc.actions.const.beds.LAY
|
|
||||||
}
|
|
||||||
},
|
|
||||||
-- Stay put on bed
|
|
||||||
[3] = {action = npc.actions.cmd.FREEZE, args = {freeze = true}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
---------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------
|
||||||
-- Scanning functions
|
-- Scanning functions
|
||||||
---------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
function spawner.filter_first_floor_nodes(nodes, ground_pos)
|
-- This function scans a 3D area that encloses a building and tries to identify:
|
||||||
local result = {}
|
-- - Entrance door
|
||||||
for _,node in pairs(nodes) do
|
-- - Beds
|
||||||
if node.node_pos.y <= ground_pos.y + 2 then
|
-- - Storage nodes (chests, etc.)
|
||||||
table.insert(result, node)
|
-- - Furnace nodes
|
||||||
end
|
-- - Sittable nodes
|
||||||
end
|
-- It will return a table with all information gathered
|
||||||
return result
|
-- Playername should be provided if manual spawning
|
||||||
end
|
function npc.spawner.scan_area_for_spawn(start_pos, end_pos, player_name)
|
||||||
|
|
||||||
-- Creates an array of {pos=<node_pos>, owner=''} for managing
|
|
||||||
-- which NPC owns what
|
|
||||||
function spawner.get_nodes_by_type(start_pos, end_pos, type)
|
|
||||||
local result = {}
|
|
||||||
local nodes = npc.places.find_node_in_area(start_pos, end_pos, type)
|
|
||||||
--minetest.log("Found "..dump(#nodes).." nodes of type: "..dump(type))
|
|
||||||
for _,node_pos in pairs(nodes) do
|
|
||||||
local entry = {}
|
|
||||||
entry["node_pos"] = node_pos
|
|
||||||
entry["owner"] = ''
|
|
||||||
table.insert(result, entry)
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Scans an area for the supported nodes: beds, benches,
|
|
||||||
-- furnaces, storage (e.g. chests) and openable (e.g. doors).
|
|
||||||
-- Returns a table with these classifications
|
|
||||||
function spawner.scan_area(pos1, pos2)
|
|
||||||
|
|
||||||
local result = {
|
local result = {
|
||||||
bed_type = {},
|
building_type = "",
|
||||||
sittable_type = {},
|
building_plot_info = {},
|
||||||
furnace_type = {},
|
building_entrance = {},
|
||||||
storage_type = {},
|
building_usable_nodes = {},
|
||||||
openable_type = {}
|
building_npcs = {},
|
||||||
|
building_npc_stats = {}
|
||||||
}
|
}
|
||||||
local start_pos, end_pos = vector.sort(pos1, pos2)
|
|
||||||
|
|
||||||
result.bed_type = spawner.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.BED_TYPE)
|
-- Set building_type
|
||||||
result.sittable_type = spawner.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.SITTABLE_TYPE)
|
result.building_type = "custom"
|
||||||
-- Filter out
|
-- Get min pos and max pos
|
||||||
result.furnace_type = spawner.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.FURNACE_TYPE)
|
local minp, maxp = vector.sort(start_pos, end_pos)
|
||||||
result.storage_type = spawner.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.STORAGE_TYPE)
|
-- Set plot info
|
||||||
result.openable_type = spawner.get_nodes_by_type(start_pos, end_pos, npc.places.nodes.OPENABLE_TYPE)
|
result.building_plot_info = {
|
||||||
|
-- TODO: Check this and see if it is accurate!
|
||||||
|
xsize = maxp.x - minp.x,
|
||||||
|
ysize = maxp.y - minp.y,
|
||||||
|
zsize = maxp.z - minp.z,
|
||||||
|
start_pos = start_pos,
|
||||||
|
end_pos = end_pos
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Scan building nodes
|
||||||
|
-- Scan building for nodes
|
||||||
|
local usable_nodes = npc.places.scan_area_for_usable_nodes(start_pos, end_pos)
|
||||||
|
-- Get all doors
|
||||||
|
local doors = usable_nodes.openable_type
|
||||||
|
|
||||||
|
-- Find entrance node - this is very tricky when no outside position
|
||||||
|
-- is given. So to this end, three things will happen:
|
||||||
|
-- - First, we will check for plotmarker nodes. A plotmarker node should
|
||||||
|
-- be set at the left of the front door of the building. If this node is
|
||||||
|
-- found, it will assume it is at that location and use it.
|
||||||
|
-- - Second, we are going to search for an entrance marker. The entrance marker
|
||||||
|
-- will be directly in the posiition of the entrance node, so no search
|
||||||
|
-- is needed.
|
||||||
|
-- - Third, will assume that the start_pos is always at the left side of
|
||||||
|
-- the front of the building, where the entrance is
|
||||||
|
local outside_pos = start_pos
|
||||||
|
-- Check if there is a plotmarker or spawner node
|
||||||
|
local candidate_nodes = minetest.find_nodes_in_area_under_air(start_pos, end_pos,
|
||||||
|
{"mg_villages:plotmarker", "advanced_npc:auto_spawner"})
|
||||||
|
if table.getn(candidate_nodes) > 0 then
|
||||||
|
-- Found plotmarker, use it as outside_pos. Ideally should be only one
|
||||||
|
outside_pos = candidate_nodes[1]
|
||||||
|
elseif npc.spawner_marker and player_name then
|
||||||
|
-- Get entrance from spawner marker1
|
||||||
|
if npc.spawner_marker.entrance_markers[player_name] then
|
||||||
|
outside_pos = npc.spawner_marker.entrance_markers[player_name]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Try to find entrance
|
||||||
|
local entrance = npc.places.find_entrance_from_openable_nodes(doors, outside_pos)
|
||||||
|
if entrance then
|
||||||
|
npc.log("INFO", "Found building entrance at: "..minetest.pos_to_string(entrance.node_pos))
|
||||||
|
-- Set building entrance
|
||||||
|
result.building_entrance = entrance
|
||||||
|
else
|
||||||
|
npc.log("ERROR", "Unable to find building entrance!")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set node_data
|
||||||
|
result.building_usable_nodes = usable_nodes
|
||||||
|
|
||||||
|
-- Initialize NPC stats
|
||||||
|
-- Initialize NPC stats
|
||||||
|
local npc_stats = {
|
||||||
|
male = {
|
||||||
|
total = 0,
|
||||||
|
adult = 0,
|
||||||
|
child = 0
|
||||||
|
},
|
||||||
|
female = {
|
||||||
|
total = 0,
|
||||||
|
adult = 0,
|
||||||
|
child = 0
|
||||||
|
},
|
||||||
|
adult_total = 0,
|
||||||
|
child_total = 0
|
||||||
|
}
|
||||||
|
result.building_npc_stats = npc_stats
|
||||||
|
|
||||||
--minetest.log("Found nodes inside area: "..dump(result))
|
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---------------------------------------------------------------------------------------
|
||||||
|
-- Spawning functions
|
||||||
|
---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- This function is called when the node timer for spawning NPC
|
||||||
|
-- is expired. Can be called manually by supplying either:
|
||||||
|
-- - Position of mg_villages plotmarker, or,
|
||||||
|
-- - position of custom building spawner
|
||||||
|
-- Prerequisite for calling this function is:
|
||||||
|
-- - In case of mg_villages, spawner.adapt_mg_villages_plotmarker(), or,
|
||||||
|
-- - in case of custom buildings, npc.spawner.scan_area_for_spawn()
|
||||||
|
function npc.spawner.spawn_npc_on_plotmarker(pos)
|
||||||
|
-- Get timer
|
||||||
|
local timer = minetest.get_node_timer(pos)
|
||||||
|
-- Get metadata
|
||||||
|
local meta = minetest.get_meta(pos)
|
||||||
|
-- Get current NPC info
|
||||||
|
local area_info = {}
|
||||||
|
area_info["npcs"] = minetest.deserialize(meta:get_string("npcs"))
|
||||||
|
-- Get NPC stats
|
||||||
|
area_info["npc_stats"] = minetest.deserialize(meta:get_string("npc_stats"))
|
||||||
|
-- Get node data
|
||||||
|
area_info["entrance"] = minetest.deserialize(meta:get_string("entrance"))
|
||||||
|
area_info["node_data"] = minetest.deserialize(meta:get_string("node_data"))
|
||||||
|
-- Check amount of NPCs that should be spawned
|
||||||
|
area_info["npc_count"] = meta:get_int("npc_count")
|
||||||
|
area_info["spawned_npc_count"] = meta:get_int("spawned_npc_count")
|
||||||
|
|
||||||
|
-- TODO: Get occupation name
|
||||||
|
local metadata = npc.spawner.spawn_npc(pos, area_info)
|
||||||
|
|
||||||
|
-- Set all metadata back into the node
|
||||||
|
-- Increase NPC spawned count
|
||||||
|
area_info.spawned_npc_count = area_info.spawned_npc_count + 1
|
||||||
|
-- Store count into node
|
||||||
|
meta:set_int("spawned_npc_count", area_info.spawned_npc_count)
|
||||||
|
-- Store spawned NPC info
|
||||||
|
meta:set_string("npcs", minetest.serialize(area_info.npcs))
|
||||||
|
-- Store NPC stats
|
||||||
|
meta:set_string("npc_stats", minetest.serialize(area_info.npc_stats))
|
||||||
|
|
||||||
|
-- Check if there are more NPCs to spawn
|
||||||
|
if area_info.spawned_npc_count >= area_info.npc_count then
|
||||||
|
-- Stop timer
|
||||||
|
npc.log("INFO", "No more NPCs to spawn at this location")
|
||||||
|
timer:stop()
|
||||||
|
else
|
||||||
|
-- Start another timer to spawn more NPC
|
||||||
|
local new_delay = math.random(npc.spawner.spawn_delay)
|
||||||
|
npc.log("INFO", "Spawning one more NPC in "..dump(npc.spawner.spawn_delay).."s")
|
||||||
|
timer:start(new_delay)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- This function spawns a NPC into the given pos.
|
||||||
|
-- If area_info is given, updated area_info is returned at end
|
||||||
|
function npc.spawner.spawn_npc(pos, area_info, occupation_name)
|
||||||
|
-- Get current NPC info
|
||||||
|
local npc_table = area_info.npcs
|
||||||
|
-- Get NPC stats
|
||||||
|
local npc_stats = area_info.npc_stats
|
||||||
|
-- Get building entrance
|
||||||
|
local entrance = area_info.entrance
|
||||||
|
-- Get node data
|
||||||
|
local node_data = area_info.node_data
|
||||||
|
-- Check amount of NPCs that should be spawned
|
||||||
|
local npc_count = area_info.npc_count
|
||||||
|
local spawned_npc_count = area_info.spawned_npc_count
|
||||||
|
-- Check if we actually have these variables - if we don't, it is because
|
||||||
|
-- this is a manually spawned NPC
|
||||||
|
local can_spawn = false
|
||||||
|
if npc_count and spawned_npc_count then
|
||||||
|
npc.log("INFO", "Currently spawned "..dump(spawned_npc_count).." of "..dump(npc_count).." NPCs")
|
||||||
|
if spawned_npc_count < npc_count then
|
||||||
|
can_spawn = true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Manually spawned
|
||||||
|
can_spawn = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if can_spawn then
|
||||||
|
npc.log("INFO", "Spawning NPC at "..minetest.pos_to_string(pos))
|
||||||
|
-- Spawn a NPC
|
||||||
|
local ent = minetest.add_entity({x=pos.x, y=pos.y+1, z=pos.z}, "advanced_npc:npc")
|
||||||
|
if ent and ent:get_luaentity() then
|
||||||
|
ent:get_luaentity().initialized = false
|
||||||
|
-- Determine NPC occupation - use given or default
|
||||||
|
local occupation = occupation_name or "default_basic"
|
||||||
|
-- Initialize NPC
|
||||||
|
-- Call with stats if there are NPCs
|
||||||
|
if npc_table and #npc_table > 0 then
|
||||||
|
npc.initialize(ent, pos, false, npc_stats, occupation)
|
||||||
|
else
|
||||||
|
npc.initialize(ent, pos, nil, nil, occupation)
|
||||||
|
end
|
||||||
|
-- If entrance and node_data are present, assign nodes
|
||||||
|
if entrance and node_data then
|
||||||
|
npc.spawner.assign_places(ent:get_luaentity(), entrance, node_data, pos)
|
||||||
|
end
|
||||||
|
-- Store spawned NPC data and stats into node
|
||||||
|
local age = npc.age.adult
|
||||||
|
if ent:get_luaentity().child then
|
||||||
|
age = npc.age.child
|
||||||
|
end
|
||||||
|
-- TODO: Add more information here at some time...
|
||||||
|
local entry = {
|
||||||
|
status = npc.spawner.spawn_data.status.alive,
|
||||||
|
name = ent:get_luaentity().name,
|
||||||
|
id = ent:get_luaentity().npc_id,
|
||||||
|
sex = ent:get_luaentity().sex,
|
||||||
|
age = age,
|
||||||
|
born_day = minetest.get_day_count()
|
||||||
|
}
|
||||||
|
minetest.log("Area info: "..dump(area_info))
|
||||||
|
table.insert(area_info.npcs, entry)
|
||||||
|
-- Update and store stats
|
||||||
|
-- Increase total of NPCs for specific sex
|
||||||
|
npc_stats[ent:get_luaentity().sex].total =
|
||||||
|
npc_stats[ent:get_luaentity().sex].total + 1
|
||||||
|
-- Increase total number of NPCs by age
|
||||||
|
npc_stats[age.."_total"] = npc_stats[age.."_total"] + 1
|
||||||
|
-- Increase number of NPCs by age and sex
|
||||||
|
npc_stats[ent:get_luaentity().sex][age] =
|
||||||
|
npc_stats[ent:get_luaentity().sex][age] + 1
|
||||||
|
area_info.npc_stats = npc_stats
|
||||||
|
-- Return
|
||||||
|
npc.log("INFO", "Spawning successful!")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
npc.log("ERROR", "Spawning failed!")
|
||||||
|
ent:remove()
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- This function will assign places to every NPC that belongs to a specific
|
-- This function will assign places to every NPC that belongs to a specific
|
||||||
-- house/building. It will use the resources of the house and give them
|
-- house/building. It will use the resources of the building and give them
|
||||||
-- until there's no more. Call this function after NPCs are initialized
|
-- until there's no more. Call this function after NPCs are initialized
|
||||||
-- The basic assumption:
|
-- The basic assumption:
|
||||||
-- - Use only items that are up to y+3 (first floor of building) for now
|
|
||||||
-- - Tell the NPC where the furnaces are
|
-- - Tell the NPC where the furnaces are
|
||||||
-- - Assign a unique bed to the NPC
|
-- - Assign a unique bed to the NPC
|
||||||
-- - If there are as many chests as beds, assign one to a NPC
|
-- - If there are as many chests as beds, assign one to a NPC
|
||||||
@ -241,14 +324,23 @@ end
|
|||||||
-- - If there are as many benches as beds, assign one to a NPC
|
-- - If there are as many benches as beds, assign one to a NPC
|
||||||
-- - Else, just let the NPC know one of the benches, but not own them
|
-- - Else, just let the NPC know one of the benches, but not own them
|
||||||
-- - Let the NPC know all doors to the house. Identify the front one as the entrance
|
-- - Let the NPC know all doors to the house. Identify the front one as the entrance
|
||||||
function spawner.assign_places(self, pos)
|
-- Self is the NPC lua entity object, pos is the position of the NPC spawner.
|
||||||
local meta = minetest.get_meta(pos)
|
-- Prerequisite for using this function is to have called either
|
||||||
local entrance = minetest.deserialize(meta:get_string("entrance"))
|
-- - In case of mg_villages, spawner.adapt_mg_villages_plotmarker(), or,
|
||||||
local node_data = minetest.deserialize(meta:get_string("node_data"))
|
-- - in case of custom buildings, npc.spawner.scan_area_for_spawn()
|
||||||
|
-- Both function set the required metadata for this function
|
||||||
|
-- For mg_villages, this will be the position of the plotmarker node.
|
||||||
|
function npc.spawner.assign_places(self, entrance, node_data, pos)
|
||||||
|
minetest.log("Received node_data: "..dump(node_data))
|
||||||
|
--local meta = minetest.get_meta(pos)
|
||||||
|
--local entrance = minetest.deserialize(meta:get_string("entrance"))
|
||||||
|
--local node_data = minetest.deserialize(meta:get_string("node_data"))
|
||||||
|
|
||||||
-- Assign plotmarker
|
-- Assign plotmarker
|
||||||
|
if pos then
|
||||||
npc.places.add_shared(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)
|
||||||
|
end
|
||||||
|
|
||||||
-- 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
|
||||||
@ -267,7 +359,7 @@ function spawner.assign_places(self, pos)
|
|||||||
npc.places.add_owned_accessible_place(self, node_data.bed_type,
|
npc.places.add_owned_accessible_place(self, node_data.bed_type,
|
||||||
npc.places.PLACE_TYPE.BED.PRIMARY)
|
npc.places.PLACE_TYPE.BED.PRIMARY)
|
||||||
-- 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
|
end
|
||||||
|
|
||||||
-- Assign sits
|
-- Assign sits
|
||||||
@ -278,7 +370,7 @@ function spawner.assign_places(self, pos)
|
|||||||
npc.places.add_owned_accessible_place(self, node_data.sittable_type,
|
npc.places.add_owned_accessible_place(self, node_data.sittable_type,
|
||||||
npc.places.PLACE_TYPE.SITTABLE.PRIMARY)
|
npc.places.PLACE_TYPE.SITTABLE.PRIMARY)
|
||||||
-- 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
|
end
|
||||||
-- Add all sits to places as shared since NPC should be able to sit
|
-- Add all sits to places as shared since NPC should be able to sit
|
||||||
-- at any accessible sit
|
-- at any accessible sit
|
||||||
@ -294,7 +386,7 @@ function spawner.assign_places(self, pos)
|
|||||||
npc.places.add_owned_accessible_place(self, node_data.furnace_type,
|
npc.places.add_owned_accessible_place(self, node_data.furnace_type,
|
||||||
npc.places.PLACE_TYPE.FURNACE.PRIMARY)
|
npc.places.PLACE_TYPE.FURNACE.PRIMARY)
|
||||||
-- 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
|
end
|
||||||
-- Add all furnaces to places as shared since NPC should be able to use
|
-- Add all furnaces to places as shared since NPC should be able to use
|
||||||
-- any accessible furnace
|
-- any accessible furnace
|
||||||
@ -310,7 +402,7 @@ function spawner.assign_places(self, pos)
|
|||||||
npc.places.add_owned_accessible_place(self, node_data.storage_type,
|
npc.places.add_owned_accessible_place(self, node_data.storage_type,
|
||||||
npc.places.PLACE_TYPE.STORAGE.PRIMARY)
|
npc.places.PLACE_TYPE.STORAGE.PRIMARY)
|
||||||
-- 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
|
end
|
||||||
-- Add all storage-types to places as shared since NPC should be able
|
-- Add all storage-types to places as shared since NPC should be able
|
||||||
-- to use other storage nodes as well.
|
-- to use other storage nodes as well.
|
||||||
@ -321,6 +413,8 @@ function spawner.assign_places(self, pos)
|
|||||||
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
|
||||||
|
-- If entrance is available let NPC
|
||||||
|
if entrance then
|
||||||
npc.add_task(self,
|
npc.add_task(self,
|
||||||
npc.actions.cmd.WALK_TO_POS,
|
npc.actions.cmd.WALK_TO_POS,
|
||||||
{end_pos=npc.places.PLACE_TYPE.OTHER.HOME_INSIDE,
|
{end_pos=npc.places.PLACE_TYPE.OTHER.HOME_INSIDE,
|
||||||
@ -328,119 +422,14 @@ function spawner.assign_places(self, pos)
|
|||||||
npc.add_action(self, npc.actions.cmd.FREEZE, {freeze = false})
|
npc.add_action(self, npc.actions.cmd.FREEZE, {freeze = false})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return node_data
|
||||||
function spawner.assign_schedules(self, pos)
|
|
||||||
-- TODO: In the future, this needs to actually take into account
|
|
||||||
-- type of building and different schedules, e.g. farmers, traders, etc.
|
|
||||||
local basic_schedule = get_basic_schedule()
|
|
||||||
-- Add a simple schedule for testing
|
|
||||||
npc.create_schedule(self, npc.schedule_types.generic, 0)
|
|
||||||
-- Add schedule entry for 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
|
|
||||||
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
|
|
||||||
npc.add_schedule_entry(self, npc.schedule_types.generic, 0, 22, nil, basic_schedule.evening_actions)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- This function is called when the node timer for spawning NPC
|
|
||||||
-- is expired
|
|
||||||
function npc.spawner.spawn_npc(pos)
|
|
||||||
-- Get timer
|
|
||||||
local timer = minetest.get_node_timer(pos)
|
|
||||||
-- Get metadata
|
|
||||||
local meta = minetest.get_meta(pos)
|
|
||||||
-- Get current NPC info
|
|
||||||
local npc_table = minetest.deserialize(meta:get_string("npcs"))
|
|
||||||
-- Get NPC stats
|
|
||||||
local npc_stats = minetest.deserialize(meta:get_string("npc_stats"))
|
|
||||||
-- Check amount of NPCs that should be spawned
|
|
||||||
local npc_count = meta:get_int("npc_count")
|
|
||||||
local spawned_npc_count = meta:get_int("spawned_npc_count")
|
|
||||||
npc.log("INFO", "Currently spawned "..dump(spawned_npc_count).." of "..dump(npc_count).." NPCs")
|
|
||||||
if spawned_npc_count < npc_count then
|
|
||||||
npc.log("INFO", "Spawning NPC at "..minetest.pos_to_string(pos))
|
|
||||||
-- Spawn a NPC
|
|
||||||
local ent = minetest.add_entity({x=pos.x, y=pos.y+1, z=pos.z}, "advanced_npc:npc")
|
|
||||||
if ent and ent:get_luaentity() then
|
|
||||||
ent:get_luaentity().initialized = false
|
|
||||||
-- Determine NPC occupation
|
|
||||||
local occupation_name = "default_basic"
|
|
||||||
-- Initialize NPC
|
|
||||||
-- Call with stats if there are NPCs
|
|
||||||
if npc_table and #npc_table > 0 then
|
|
||||||
npc.initialize(ent, pos, false, npc_stats, occupation_name)
|
|
||||||
else
|
|
||||||
npc.initialize(ent, pos, nil, nil, occupation_name)
|
|
||||||
end
|
|
||||||
-- Assign nodes
|
|
||||||
spawner.assign_places(ent:get_luaentity(), pos)
|
|
||||||
-- Assign schedules
|
|
||||||
--spawner.assign_schedules(ent:get_luaentity(), pos)
|
|
||||||
-- Increase NPC spawned count
|
|
||||||
spawned_npc_count = spawned_npc_count + 1
|
|
||||||
-- Store count into node
|
|
||||||
meta:set_int("spawned_npc_count", spawned_npc_count)
|
|
||||||
-- Store spawned NPC data and stats into node
|
|
||||||
local age = npc.age.adult
|
|
||||||
if ent:get_luaentity().child then
|
|
||||||
age = npc.age.child
|
|
||||||
end
|
|
||||||
-- TODO: Add more information here at some time...
|
|
||||||
local entry = {
|
|
||||||
status = npc.spawner.spawn_data.status.alive,
|
|
||||||
name = ent:get_luaentity().name,
|
|
||||||
id = ent:get_luaentity().npc_id,
|
|
||||||
sex = ent:get_luaentity().sex,
|
|
||||||
age = age,
|
|
||||||
born_day = minetest.get_day_count()
|
|
||||||
}
|
|
||||||
table.insert(npc_table, entry)
|
|
||||||
meta:set_string("npcs", minetest.serialize(npc_table))
|
|
||||||
-- Update and store stats
|
|
||||||
-- Increase total of NPCs for specific sex
|
|
||||||
npc_stats[ent:get_luaentity().sex].total =
|
|
||||||
npc_stats[ent:get_luaentity().sex].total + 1
|
|
||||||
-- Increase total number of NPCs by age
|
|
||||||
npc_stats[age.."_total"] = npc_stats[age.."_total"] + 1
|
|
||||||
-- Increase number of NPCs by age and sex
|
|
||||||
npc_stats[ent:get_luaentity().sex][age] =
|
|
||||||
npc_stats[ent:get_luaentity().sex][age] + 1
|
|
||||||
meta:set_string("npc_stats", minetest.serialize(npc_stats))
|
|
||||||
-- Temp
|
|
||||||
--meta:set_string("infotext", meta:get_string("infotext")..", "..spawned_npc_count)
|
|
||||||
npc.log("INFO", "Spawning successful!")
|
|
||||||
-- Check if there are more NPCs to spawn
|
|
||||||
if spawned_npc_count >= npc_count then
|
|
||||||
-- Stop timer
|
|
||||||
npc.log("INFO", "No more NPCs to spawn at this location")
|
|
||||||
timer:stop()
|
|
||||||
else
|
|
||||||
-- Start another timer to spawn more NPC
|
|
||||||
local new_delay = math.random(npc.spawner.spawn_delay)
|
|
||||||
npc.log("INFO", "Spawning one more NPC in "..dump(npc.spawner.spawn_delay).."s")
|
|
||||||
timer:start(new_delay)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
npc.log("ERROR", "Spawning failed!")
|
|
||||||
ent:remove()
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
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)
|
-- Prerequisite for calling this function is:
|
||||||
|
-- - In case of mg_villages, spawner.adapt_mg_villages_plotmarker(), or,
|
||||||
|
-- - in case of custom buildings, npc.spawner.scan_area_for_spawn()
|
||||||
|
function npc.spawner.calculate_npc_spawning_on_plotmarker(pos)
|
||||||
-- 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
|
if meta:get_string("replaced") ~= "true" then
|
||||||
@ -449,7 +438,7 @@ function spawner.calculate_npc_spawning(pos)
|
|||||||
-- 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 mg_villages:plotmarker at position: "..minetest.pos_to_string(pos))
|
npc.log("ERROR", "Mis-configured spawner at position: "..minetest.pos_to_string(pos))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
-- Check number of beds
|
-- Check number of beds
|
||||||
@ -477,9 +466,22 @@ function spawner.calculate_npc_spawning(pos)
|
|||||||
timer:start(delay)
|
timer:start(delay)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---------------------------------------------------------------------------------------
|
||||||
|
-- Spawner nodes and items
|
||||||
|
---------------------------------------------------------------------------------------
|
||||||
|
-- The following are included:
|
||||||
|
-- - Auto-spawner: Basically a custom mg_villages:plotmarker that can be used
|
||||||
|
-- for custom buildings
|
||||||
|
-- - Manual spawner: This custom spawn item (egg) will show a formspec when used.
|
||||||
|
-- The formspec will allow the player the name of the NPC, the occupation and
|
||||||
|
-- the plot, entrance and workplace of the NPC. All of these are optional and
|
||||||
|
-- default values will be chosen whenever no input is provided.
|
||||||
|
|
||||||
|
|
||||||
---------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------
|
||||||
-- Support code for mg_villages mods
|
-- Support code for mg_villages mods
|
||||||
---------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------
|
||||||
|
if minetest.get_modpath("mg_villages") ~= nil then
|
||||||
|
|
||||||
-- This function creates a table of the scannable nodes inside
|
-- This function creates a table of the scannable nodes inside
|
||||||
-- a mg_villages building. It needs the plotmarker position for a start
|
-- a mg_villages building. It needs the plotmarker position for a start
|
||||||
@ -544,26 +546,26 @@ function spawner.scan_mg_villages_building(pos, building_data)
|
|||||||
|
|
||||||
npc.log("DEBUG", "Calculated end pos: "..minetest.pos_to_string(end_pos))
|
npc.log("DEBUG", "Calculated end pos: "..minetest.pos_to_string(end_pos))
|
||||||
|
|
||||||
return spawner.scan_area(start_pos, end_pos)
|
return npc.places.scan_area_for_usable_nodes(start_pos, end_pos)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- This function replaces an existent mg_villages:plotmarker with
|
-- This function "adapts" an existent mg_villages:plotmarker for NPC spawning.
|
||||||
-- and advanced_npc:auto_spawner. The existing metadata will be kept,
|
-- The existing metadata will be kept, to allow compatibility. A new formspec
|
||||||
-- to allow compatibility. A new formspec will appear on right-click,
|
-- will appear on right-click, however it will as well allow to buy or manage
|
||||||
-- however it will as well allow to buy or manage the plot.
|
-- the plot. Also, the building is scanned for NPC-usable nodes and the amount
|
||||||
-- Also, the building is scanned for NPC-usable nodes and the amount
|
|
||||||
-- of NPCs to spawn and the interval is calculated.
|
-- of NPCs to spawn and the interval is calculated.
|
||||||
function spawner.replace_mg_villages_plotmarker(pos)
|
function spawner.adapt_mg_villages_plotmarker(pos)
|
||||||
-- Get the meta at the current position
|
-- Get the meta at the current position
|
||||||
local meta = minetest.get_meta(pos)
|
local meta = minetest.get_meta(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
|
-- Check for nil values above
|
||||||
if (not village_id or (village and village == ""))
|
if (not village_id or (village_id and village_id == ""))
|
||||||
or (not plot_nr or (plot_nr and plot_nr == 0)) then
|
or (not plot_nr or (plot_nr and plot_nr == 0)) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
-- TODO: This should be replaced with new mg_villages API call
|
||||||
-- 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]
|
||||||
@ -574,12 +576,11 @@ function spawner.replace_mg_villages_plotmarker(pos)
|
|||||||
if building_type == value then
|
if building_type == value then
|
||||||
|
|
||||||
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
|
-- Store plotmarker metadata again
|
||||||
--minetest.set_node(pos, {name="advanced_npc:plotmarker_auto_spawner"})
|
|
||||||
-- 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)
|
||||||
meta:set_string("infotext", infotext)
|
meta:set_string("infotext", infotext)
|
||||||
|
|
||||||
-- Store building type in metadata
|
-- Store building type in metadata
|
||||||
meta:set_string("building_type", building_type)
|
meta:set_string("building_type", building_type)
|
||||||
-- Store plot information
|
-- Store plot information
|
||||||
@ -624,17 +625,13 @@ function spawner.replace_mg_villages_plotmarker(pos)
|
|||||||
-- Set replaced
|
-- Set replaced
|
||||||
meta:set_string("replaced", "true")
|
meta:set_string("replaced", "true")
|
||||||
-- Calculate how many NPCs will spawn
|
-- Calculate how many NPCs will spawn
|
||||||
spawner.calculate_npc_spawning(pos)
|
npc.spawner.calculate_npc_spawning_on_plotmarker(pos)
|
||||||
-- Stop searching for building type
|
-- Stop searching for building type
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Only register the node, the ABM and the LBM if mg_villages mod
|
|
||||||
-- is present
|
|
||||||
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.
|
||||||
@ -678,7 +675,7 @@ if minetest.get_modpath("mg_villages") ~= nil then
|
|||||||
-- end,
|
-- end,
|
||||||
|
|
||||||
on_timer = function(pos, elapsed)
|
on_timer = function(pos, elapsed)
|
||||||
npc.spawner.spawn_npc(pos)
|
npc.spawner.spawn_npc_on_plotmarker(pos)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
-- protect against digging
|
-- protect against digging
|
||||||
@ -718,13 +715,21 @@ if minetest.get_modpath("mg_villages") ~= nil then
|
|||||||
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
|
-- Check if replacement is needed
|
||||||
|
local meta = minetest.get_meta(pos)
|
||||||
|
if meta then
|
||||||
|
-- minetest.log("------ Plotmarker metadata -------")
|
||||||
|
-- local plot_nr = meta:get_int("plot_nr")
|
||||||
|
-- local village_id = meta:get_string("village_id")
|
||||||
|
-- minetest.log("Plot nr: "..dump(plot_nr)..", village ID: "..dump(village_id))
|
||||||
|
-- minetest.log(dump(mg_villages.get_plot_and_building_data( village_id, plot_nr )))
|
||||||
|
end
|
||||||
if minetest.get_meta(pos):get_string("replaced") == "true" then
|
if minetest.get_meta(pos):get_string("replaced") == "true" then
|
||||||
return
|
return
|
||||||
end
|
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.adapt_mg_villages_plotmarker(pos)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
@ -739,8 +744,9 @@ minetest.register_chatcommand("restore_plotmarkers", {
|
|||||||
privs = {server=true},
|
privs = {server=true},
|
||||||
func = function(name, param)
|
func = function(name, param)
|
||||||
-- Check if radius is null
|
-- Check if radius is null
|
||||||
if param == nil then
|
if param == nil and type(param) ~= "number" then
|
||||||
minetest.chat_send_player(name, "Need to enter a radius as an integer number. Ex. /restore_plotmarkers 10 for a radius of 10")
|
minetest.chat_send_player(name, "Need to enter a radius as an integer number. Ex. /restore_plotmarkers 10 for a radius of 10")
|
||||||
|
return
|
||||||
end
|
end
|
||||||
-- Get player position
|
-- Get player position
|
||||||
local pos = {}
|
local pos = {}
|
||||||
|
3
spawner_marker.lua
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
-- Spawner markers
|
||||||
|
-- Specialized functionality to allow players do NPC spawning
|
||||||
|
-- on their own custom buildings.
|
BIN
textures/npc_female10.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
textures/npc_female11.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
textures/npc_female2.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
textures/npc_female3.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
textures/npc_female4.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
textures/npc_female5.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
textures/npc_female6.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
textures/npc_female7.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
textures/npc_female8.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
textures/npc_female9.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
textures/npc_male10.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
textures/npc_male11.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
textures/npc_male12.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
textures/npc_male13.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
textures/npc_male14.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
textures/npc_male15.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
textures/npc_male8.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
textures/npc_male9.png
Normal file
After Width: | Height: | Size: 2.4 KiB |