Occupations: Add schedule check function, allow enqueuing of schedule check
This commit is contained in:
parent
0f931d273c
commit
5a93800e77
@ -13,6 +13,8 @@
|
|||||||
|
|
||||||
npc.actions = {}
|
npc.actions = {}
|
||||||
|
|
||||||
|
npc.actions.default_interval = 1
|
||||||
|
|
||||||
-- Describes actions with doors or openable nodes
|
-- Describes actions with doors or openable nodes
|
||||||
npc.actions.const = {
|
npc.actions.const = {
|
||||||
doors = {
|
doors = {
|
||||||
@ -50,7 +52,9 @@ npc.actions.cmd = {
|
|||||||
USE_FURNACE = 11,
|
USE_FURNACE = 11,
|
||||||
USE_BED = 12,
|
USE_BED = 12,
|
||||||
USE_SITTABLE = 13,
|
USE_SITTABLE = 13,
|
||||||
WALK_TO_POS = 14
|
WALK_TO_POS = 14,
|
||||||
|
DIG = 15,
|
||||||
|
PLACE = 16
|
||||||
}
|
}
|
||||||
|
|
||||||
--npc.actions.one_nps_speed = 0.98
|
--npc.actions.one_nps_speed = 0.98
|
||||||
@ -60,6 +64,10 @@ npc.actions.one_nps_speed = 1
|
|||||||
npc.actions.one_half_nps_speed = 1.5
|
npc.actions.one_half_nps_speed = 1.5
|
||||||
npc.actions.two_nps_speed = 2
|
npc.actions.two_nps_speed = 2
|
||||||
|
|
||||||
|
npc.actions.take_from_inventory = "take_from_inventory"
|
||||||
|
npc.actions.take_from_inventory_forced = "take_from_inventory_forced"
|
||||||
|
npc.actions.force_place = "force_place"
|
||||||
|
|
||||||
-- Executor --
|
-- Executor --
|
||||||
--------------
|
--------------
|
||||||
-- Function references aren't reliable in Minetest entities. Objects get serialized
|
-- Function references aren't reliable in Minetest entities. Objects get serialized
|
||||||
@ -115,6 +123,12 @@ function npc.actions.execute(self, command, args)
|
|||||||
-- Call walk to position task
|
-- Call walk to position task
|
||||||
--minetest.log("Self: "..dump(self)..", Command: "..dump(command)..", args: "..dump(args))
|
--minetest.log("Self: "..dump(self)..", Command: "..dump(command)..", args: "..dump(args))
|
||||||
return npc.actions.walk_to_pos(self, args)
|
return npc.actions.walk_to_pos(self, args)
|
||||||
|
elseif command == npc.actions.cmd.DIG then
|
||||||
|
-- Call dig node action
|
||||||
|
return npc.actions.dig(self, args)
|
||||||
|
elseif command == npc.actions.cmd.PLACE then
|
||||||
|
-- Call place node action
|
||||||
|
return npc.actions.place(self, args)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -146,6 +160,85 @@ function npc.actions.freeze(self, args)
|
|||||||
return not(freeze_mobs_api)
|
return not(freeze_mobs_api)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- This action digs the node at the given position
|
||||||
|
-- If 'add_to_inventory' is true, it will put the digged node in the NPC
|
||||||
|
-- inventory.
|
||||||
|
-- Returns true if dig is successful, otherwise false
|
||||||
|
function npc.actions.dig(self, args)
|
||||||
|
local pos = args.pos
|
||||||
|
local add_to_inventory = args.add_to_inventory
|
||||||
|
local bypass_protection = args.bypass_protection
|
||||||
|
local node = minetest.get_node_or_nil(pos)
|
||||||
|
if node then
|
||||||
|
-- Check if protection not enforced
|
||||||
|
if not force_dig then
|
||||||
|
-- Try to dig node
|
||||||
|
if minetest.dig_node(pos) then
|
||||||
|
-- Add to inventory the node drops
|
||||||
|
if add_to_inventory then
|
||||||
|
-- Get node drop
|
||||||
|
local drop = minetest.registered_nodes[node.name].drop
|
||||||
|
-- Add to NPC inventory
|
||||||
|
npc.npc.add_item_to_inventory(self, drop, 1)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Add to inventory
|
||||||
|
if add_to_inventory then
|
||||||
|
-- Get node drop
|
||||||
|
local drop = minetest.registered_nodes[node.name].drop
|
||||||
|
-- Add to NPC inventory
|
||||||
|
npc.npc.add_item_to_inventory(self, drop, 1)
|
||||||
|
end
|
||||||
|
-- Dig node
|
||||||
|
minetest.set_node(pos, {name="air"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- This action places a given node at the given position
|
||||||
|
-- There are three ways to source the node:
|
||||||
|
-- 1. take_from_inventory: takes node from inventory. If not in inventory,
|
||||||
|
-- node isn't placed.
|
||||||
|
-- 2. take_from_inventory_forced: takes node from inventory. If not in
|
||||||
|
-- inventory, node will be placed anyways.
|
||||||
|
-- 3. force_place: places node regardless of inventory - will not touch
|
||||||
|
-- the NPCs inventory
|
||||||
|
function npc.actions.place(self, args)
|
||||||
|
local pos = args.pos
|
||||||
|
local node = args.node
|
||||||
|
local source = args.source
|
||||||
|
local bypass_protection = args.bypass_protection
|
||||||
|
local node_at_pos = minetest.get_node_or_nil(pos)
|
||||||
|
-- Check if position is empty or has a node that can be built to
|
||||||
|
if node_at_pos and
|
||||||
|
(node_at_pos.name == "air" or minetest.registered_nodes(node_at_pos.name).buildable_to == true) then
|
||||||
|
-- Check protection
|
||||||
|
if (not bypass_protection and not minetest.is_protected(pos, self.npc_name))
|
||||||
|
or bypass_protection == true then
|
||||||
|
-- Take from inventory if necessary
|
||||||
|
local place_item = false
|
||||||
|
if source == npc.actions.take_from_inventory then
|
||||||
|
if npc.take_item_from_inventory(self, node.name, 1) then
|
||||||
|
place_item = true
|
||||||
|
end
|
||||||
|
elseif source == npc.actions.take_from_inventory_forced then
|
||||||
|
npc.take_item_from_inventory(self, node.name, 1)
|
||||||
|
place_item = true
|
||||||
|
elseif source == npc.actions.force_place then
|
||||||
|
place_item = true
|
||||||
|
end
|
||||||
|
-- Place node
|
||||||
|
if place_item then
|
||||||
|
minetest.set_node(pos, node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- This action is to rotate to mob to a specifc direction. Currently, the code
|
-- This action is to rotate to mob to a specifc direction. Currently, the code
|
||||||
-- contains also for diagonals, but remaining in the orthogonal domain is preferrable.
|
-- contains also for diagonals, but remaining in the orthogonal domain is preferrable.
|
||||||
function npc.actions.rotate(self, args)
|
function npc.actions.rotate(self, args)
|
||||||
|
@ -72,6 +72,9 @@ npc.places.PLACE_TYPE = {
|
|||||||
OPENABLE = {
|
OPENABLE = {
|
||||||
HOME_ENTRANCE_DOOR = "home_entrance_door"
|
HOME_ENTRANCE_DOOR = "home_entrance_door"
|
||||||
},
|
},
|
||||||
|
SCHEDULE = {
|
||||||
|
TARGET = "schedule_target_pos"
|
||||||
|
},
|
||||||
OTHER = {
|
OTHER = {
|
||||||
HOME_PLOTMARKER = "home_plotmarker",
|
HOME_PLOTMARKER = "home_plotmarker",
|
||||||
HOME_INSIDE = "home_inside",
|
HOME_INSIDE = "home_inside",
|
||||||
@ -87,7 +90,7 @@ function npc.places.add_owned(self, place_name, place_type, pos, access_node)
|
|||||||
self.places_map[place_name] = {type=place_type, pos=pos, access_node=access_node or pos, status="owned"}
|
self.places_map[place_name] = {type=place_type, pos=pos, access_node=access_node or pos, status="owned"}
|
||||||
end
|
end
|
||||||
|
|
||||||
function npc.places.add_unowned_accessible_place(self, nodes, place_type)
|
function npc.places.add_owned_accessible_place(self, nodes, place_type)
|
||||||
for i = 1, #nodes do
|
for i = 1, #nodes do
|
||||||
-- Check if node has owner
|
-- Check if node has owner
|
||||||
if nodes[i].owner == "" then
|
if nodes[i].owner == "" then
|
||||||
@ -109,14 +112,18 @@ function npc.places.add_unowned_accessible_place(self, nodes, place_type)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function npc.places.add_shared_accessible_place(self, nodes, place_type)
|
-- Override flag allows to overwrite a place in the places_map.
|
||||||
|
-- The only valid use right now is for schedules - don't use this
|
||||||
|
-- anywhere else unless you have a position that changes over time.
|
||||||
|
function npc.places.add_shared_accessible_place(self, nodes, place_type, override)
|
||||||
|
if not override then
|
||||||
for i = 1, #nodes do
|
for i = 1, #nodes do
|
||||||
-- Check of not adding same owned sit
|
-- Check if not adding same owned place
|
||||||
if nodes[i].owner ~= self.npc_id then
|
if nodes[i].owner ~= self.npc_id then
|
||||||
-- Check if it is accessible
|
-- Check if it is accessible
|
||||||
local empty_nodes = npc.places.find_node_orthogonally(
|
local empty_nodes = npc.places.find_node_orthogonally(
|
||||||
nodes[i].node_pos, {"air"}, 0)
|
nodes[i].node_pos, {"air"}, 0)
|
||||||
-- Check if bed is accessible
|
-- Check if node is accessible
|
||||||
if #empty_nodes > 0 then
|
if #empty_nodes > 0 then
|
||||||
-- Assign node to NPC
|
-- Assign node to NPC
|
||||||
npc.places.add_shared(self, place_type..dump(i),
|
npc.places.add_shared(self, place_type..dump(i),
|
||||||
@ -124,6 +131,18 @@ function npc.places.add_shared_accessible_place(self, nodes, place_type)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
elseif override then
|
||||||
|
-- Note: Nodes is only *one* node in case override = true
|
||||||
|
-- Check if it is accessible
|
||||||
|
local empty_nodes = npc.places.find_node_orthogonally(
|
||||||
|
nodes.node_pos, {"air"}, 0)
|
||||||
|
-- Check if node is accessible
|
||||||
|
if #empty_nodes > 0 then
|
||||||
|
-- Nodes is only one node
|
||||||
|
npc.places.add_shared(self, place_type, place_type,
|
||||||
|
nodes.node_pos, empty_nodes[1].pos)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function npc.places.get_by_type(self, place_type)
|
function npc.places.get_by_type(self, place_type)
|
||||||
@ -396,8 +415,8 @@ function npc.places.find_node_in_front_of_door(door_pos)
|
|||||||
elseif door.param2 == 2 then
|
elseif door.param2 == 2 then
|
||||||
-- Looking north
|
-- Looking north
|
||||||
return {x=door_pos.x, y=door_pos.y, z=door_pos.z + 1}
|
return {x=door_pos.x, y=door_pos.y, z=door_pos.z + 1}
|
||||||
-- Looking west
|
|
||||||
elseif door.param2 == 3 then
|
elseif door.param2 == 3 then
|
||||||
|
-- Looking west
|
||||||
return {x=door_pos.x + 1, y=door_pos.y, z=door_pos.z}
|
return {x=door_pos.x + 1, y=door_pos.y, z=door_pos.z}
|
||||||
end
|
end
|
||||||
end
|
end
|
2
init.lua
2
init.lua
@ -34,10 +34,12 @@ dofile(path .. "/actions/actions.lua")
|
|||||||
dofile(path .. "/actions/places.lua")
|
dofile(path .. "/actions/places.lua")
|
||||||
dofile(path .. "/actions/pathfinder.lua")
|
dofile(path .. "/actions/pathfinder.lua")
|
||||||
dofile(path .. "/actions/node_registry.lua")
|
dofile(path .. "/actions/node_registry.lua")
|
||||||
|
dofile(path .. "/occupations/occupations.lua")
|
||||||
-- Load random data definitions
|
-- Load random data definitions
|
||||||
dofile(path .. "/random_data.lua")
|
dofile(path .. "/random_data.lua")
|
||||||
dofile(path .. "/random_data/dialogues_data.lua")
|
dofile(path .. "/random_data/dialogues_data.lua")
|
||||||
dofile(path .. "/random_data/gift_items_data.lua")
|
dofile(path .. "/random_data/gift_items_data.lua")
|
||||||
dofile(path .. "/random_data/names_data.lua")
|
dofile(path .. "/random_data/names_data.lua")
|
||||||
|
dofile(path .. "/random_data/occupations_data.lua")
|
||||||
|
|
||||||
print (S("[Mod] Advanced NPC loaded"))
|
print (S("[Mod] Advanced NPC loaded"))
|
||||||
|
235
npc.lua
235
npc.lua
@ -24,6 +24,8 @@ npc.ANIMATION_LAY_START = 162
|
|||||||
npc.ANIMATION_LAY_END = 166
|
npc.ANIMATION_LAY_END = 166
|
||||||
npc.ANIMATION_WALK_START = 168
|
npc.ANIMATION_WALK_START = 168
|
||||||
npc.ANIMATION_WALK_END = 187
|
npc.ANIMATION_WALK_END = 187
|
||||||
|
npc.ANIMATION_MINE_START = 189
|
||||||
|
npc.ANIMATION_MINE_END =198
|
||||||
|
|
||||||
npc.direction = {
|
npc.direction = {
|
||||||
north = 0,
|
north = 0,
|
||||||
@ -175,6 +177,11 @@ function npc.get_random_texture_from_array(age, sex, textures)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Check if there are no textures
|
||||||
|
if #filtered_textures == 0 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
return filtered_textures[math.random(1, #filtered_textures)]
|
return filtered_textures[math.random(1, #filtered_textures)]
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -226,7 +233,7 @@ end
|
|||||||
|
|
||||||
-- Spawn function. Initializes all variables that the
|
-- Spawn function. Initializes all variables that the
|
||||||
-- NPC will have and choose random, starting values
|
-- NPC will have and choose random, starting values
|
||||||
function npc.initialize(entity, pos, is_lua_entity, npc_stats)
|
function npc.initialize(entity, pos, is_lua_entity, npc_stats, occupation_name)
|
||||||
npc.log("INFO", "Initializing NPC at "..minetest.pos_to_string(pos))
|
npc.log("INFO", "Initializing NPC at "..minetest.pos_to_string(pos))
|
||||||
|
|
||||||
-- Get variables
|
-- Get variables
|
||||||
@ -249,12 +256,13 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats)
|
|||||||
-- - Age: For each two adults, the chance of spawning a child next will be 50%
|
-- - Age: For each two adults, the chance of spawning a child next will be 50%
|
||||||
-- If there's a child for two adults, the chance of spawning a child goes to
|
-- If there's a child for two adults, the chance of spawning a child goes to
|
||||||
-- 40% and keeps decreasing unless two adults have no child.
|
-- 40% and keeps decreasing unless two adults have no child.
|
||||||
|
-- Use NPC stats if provided
|
||||||
if npc_stats then
|
if npc_stats then
|
||||||
-- Default chances
|
-- Default chances
|
||||||
local male_s, male_e = 0, 50
|
local male_s, male_e = 0, 50
|
||||||
local female_s, female_e = 51, 100
|
local female_s, female_e = 51, 100
|
||||||
local adult_s, adult_e = 0, 90
|
local adult_s, adult_e = 0, 85
|
||||||
local child_s, child_e = 91, 100
|
local child_s, child_e = 86, 100
|
||||||
-- Determine sex probabilities
|
-- Determine sex probabilities
|
||||||
if npc_stats[npc.FEMALE].total > npc_stats[npc.MALE].total then
|
if npc_stats[npc.FEMALE].total > npc_stats[npc.MALE].total then
|
||||||
male_e = 75
|
male_e = 75
|
||||||
@ -293,8 +301,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.60,
|
x = 0.65,
|
||||||
y = 0.60
|
y = 0.65
|
||||||
}
|
}
|
||||||
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
|
||||||
@ -321,6 +329,7 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats)
|
|||||||
else
|
else
|
||||||
ent.sex = npc.MALE
|
ent.sex = npc.MALE
|
||||||
end
|
end
|
||||||
|
ent.age = npc.age.adult
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Nametag is initialized to blank
|
-- Nametag is initialized to blank
|
||||||
@ -406,7 +415,7 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats)
|
|||||||
action_timer = 0,
|
action_timer = 0,
|
||||||
-- Determines the interval for each action in the action queue
|
-- Determines the interval for each action in the action queue
|
||||||
-- Default is 1. This can be changed via actions
|
-- Default is 1. This can be changed via actions
|
||||||
action_interval = 1,
|
action_interval = npc.actions.default_interval,
|
||||||
-- Avoid the execution of the action timer
|
-- Avoid the execution of the action timer
|
||||||
action_timer_lock = false,
|
action_timer_lock = false,
|
||||||
-- Defines the state of the current action
|
-- Defines the state of the current action
|
||||||
@ -451,15 +460,26 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats)
|
|||||||
-- Used to calculate dependencies
|
-- Used to calculate dependencies
|
||||||
temp_executed_queue = {},
|
temp_executed_queue = {},
|
||||||
-- An array of schedules, meant to be one per day at some point
|
-- An array of schedules, meant to be one per day at some point
|
||||||
-- when calendars are implemented. Allows for only 7 schedules,
|
--- when calendars are implemented. Allows for only 7 schedules,
|
||||||
-- one for each day of the week
|
-- one for each day of the week
|
||||||
generic = {},
|
generic = {},
|
||||||
-- An array of schedules, meant to be for specific dates in the
|
-- An array of schedules, meant to be for specific dates in the
|
||||||
-- year. Can contain as many as possible. The keys will be strings
|
-- year. Can contain as many as possible. The keys will be strings
|
||||||
-- in the format MM:DD
|
-- in the format MM:DD
|
||||||
date_based = {}
|
date_based = {},
|
||||||
|
-- The following holds the check parameters provided by the
|
||||||
|
-- current schedule
|
||||||
|
current_check_params = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- If occupation name given, override properties with
|
||||||
|
-- occupation values and initialize schedules
|
||||||
|
minetest.log("Entity age: "..dump(ent.age)..", afult? "..dump(ent.age==npc.age.adult))
|
||||||
|
if occupation_name and occupation_name ~= "" and ent.age == npc.age.adult then
|
||||||
|
npc.occupations.initialize_occupation_values(ent, occupation_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO: Remove this - do inside occupation
|
||||||
-- Dedicated trade test
|
-- Dedicated trade test
|
||||||
ent.trader_data.trade_list.both = {
|
ent.trader_data.trade_list.both = {
|
||||||
["default:tree"] = {},
|
["default:tree"] = {},
|
||||||
@ -477,16 +497,18 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats)
|
|||||||
["default:axe_stone"] = {}
|
["default:axe_stone"] = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- Generate trade offers
|
||||||
npc.trade.generate_trade_offers_by_status(ent)
|
npc.trade.generate_trade_offers_by_status(ent)
|
||||||
|
|
||||||
-- Add a custom trade offer
|
-- Add a custom trade offer
|
||||||
local offer1 = npc.trade.create_custom_sell_trade_offer("Do you want me to fix your steel sword?", "Fix steel sword", "Fix steel sword", "default:sword_steel", {"default:sword_steel", "default:iron_lump 5"})
|
-- local offer1 = npc.trade.create_custom_sell_trade_offer("Do you want me to fix your steel sword?", "Fix steel sword", "Fix steel sword", "default:sword_steel", {"default:sword_steel", "default:iron_lump 5"})
|
||||||
table.insert(ent.trader_data.custom_trades, offer1)
|
-- table.insert(ent.trader_data.custom_trades, offer1)
|
||||||
--local offer2 = npc.trade.create_custom_sell_trade_offer("Do you want me to fix your mese sword?", "Fix mese sword", "Fix mese sword", "default:sword_mese", {"default:sword_mese", "default:copper_lump 10"})
|
--local offer2 = npc.trade.create_custom_sell_trade_offer("Do you want me to fix your mese sword?", "Fix mese sword", "Fix mese sword", "default:sword_mese", {"default:sword_mese", "default:copper_lump 10"})
|
||||||
--table.insert(ent.trader_data.custom_trades, offer2)
|
--table.insert(ent.trader_data.custom_trades, offer2)
|
||||||
|
|
||||||
|
-- Set initialized flag on
|
||||||
ent.initialized = true
|
ent.initialized = true
|
||||||
--minetest.log(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))
|
||||||
@ -693,9 +715,19 @@ function npc.execute_action(self)
|
|||||||
return self.freeze
|
return self.freeze
|
||||||
end
|
end
|
||||||
local action_obj = self.actions.queue[1]
|
local action_obj = self.actions.queue[1]
|
||||||
|
-- Check if action is null
|
||||||
if action_obj.action == nil then
|
if action_obj.action == nil then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
-- Check if action is an schedule check
|
||||||
|
if action_obj.action == "schedule_check" then
|
||||||
|
-- Execute schedule check
|
||||||
|
npc.schedule_check(self)
|
||||||
|
-- Remove table entry
|
||||||
|
table.remove(self.actions.queue, 1)
|
||||||
|
-- Return
|
||||||
|
return false
|
||||||
|
end
|
||||||
-- If the entry is a task, then push all this new operations in
|
-- If the entry is a task, then push all this new operations in
|
||||||
-- stack fashion
|
-- stack fashion
|
||||||
if action_obj.is_task == true then
|
if action_obj.is_task == true then
|
||||||
@ -722,7 +754,6 @@ function npc.execute_action(self)
|
|||||||
self.actions.state_before_lock.pos = self.object:getpos()
|
self.actions.state_before_lock.pos = self.object:getpos()
|
||||||
-- Execute action as normal
|
-- Execute action as normal
|
||||||
result = npc.actions.execute(self, action_obj.action, action_obj.args)
|
result = npc.actions.execute(self, action_obj.action, action_obj.args)
|
||||||
--result = action_obj.action(self, action_obj.args)
|
|
||||||
-- Remove task
|
-- Remove task
|
||||||
table.remove(self.actions.queue, 1)
|
table.remove(self.actions.queue, 1)
|
||||||
-- Set state
|
-- Set state
|
||||||
@ -961,6 +992,141 @@ function npc.schedule_change_property(self, property, args)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function npc.add_schedule_check(self)
|
||||||
|
table.insert(self.actions.queue, {action="schedule_check", args={}, is_task=false})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Range: integer, radius in which nodes will be searched. Recommended radius is
|
||||||
|
-- between 1-3
|
||||||
|
-- Nodes: array of node names
|
||||||
|
-- Actions: map of node names to entries {action=<action_enum>, args={}}.
|
||||||
|
-- Arguments can be empty - the check function will try to determine most
|
||||||
|
-- arguments anyways (like pos and dir).
|
||||||
|
-- Special node "any" will execute those actions on any node except the
|
||||||
|
-- already specified ones.
|
||||||
|
-- None-action: array of entries {action=<action_enum>, args={}}.
|
||||||
|
-- Will be executed when no node is found.
|
||||||
|
function npc.schedule_check(self)
|
||||||
|
local range = self.schedules.current_check_params.range
|
||||||
|
local nodes = self.schedules.current_check_params.nodes
|
||||||
|
local actions = self.schedules.current_check_params.actions
|
||||||
|
local none_actions = self.schedules.current_check_params.none_actions
|
||||||
|
-- Get NPC position
|
||||||
|
local start_pos = self.object:getpos()
|
||||||
|
-- Search nodes
|
||||||
|
local found_nodes = npc.places.find_node_nearby(start_pos, nodes, range)
|
||||||
|
-- Check if any node was found
|
||||||
|
if found_nodes then
|
||||||
|
-- Pick a random node to act upon
|
||||||
|
local node_pos = found_nodes[math.random(1, #found_nodes)]
|
||||||
|
local node = minetest.get_node(node_pos)
|
||||||
|
-- Set node as a place
|
||||||
|
-- Note: Code below isn't *adding* a node, but overwriting the
|
||||||
|
-- place with "schedule_target_pos" place type
|
||||||
|
npc.places.add_shared_accessible_place(
|
||||||
|
self, node, npc.places.PLACE_TYPE.SCHEDULE.TARGET, true)
|
||||||
|
-- Get actions related to node and enqueue them
|
||||||
|
for i = 1, #actions[node.name] do
|
||||||
|
local args = {}
|
||||||
|
local action = nil
|
||||||
|
-- Calculate arguments for the following supported actions:
|
||||||
|
-- - Dig
|
||||||
|
-- - Place
|
||||||
|
-- - Walk step
|
||||||
|
-- - Walk to position
|
||||||
|
-- - Use furnace
|
||||||
|
if actions[node.name][i].action == npc.actions.cmd.DIG then
|
||||||
|
-- Defaults: items will be added to inventory if not specified
|
||||||
|
-- otherwise, and protection will be respected, if not specified
|
||||||
|
-- otherwise
|
||||||
|
args = {
|
||||||
|
pos = node_pos,
|
||||||
|
add_to_inventory = action[node.name][i].args.add_to_inventory or true,
|
||||||
|
bypass_protection = action[node.name][i].args.bypass_protection or false
|
||||||
|
}
|
||||||
|
elseif actions[node.name][i].action == npc.actions.cmd.PLACE then
|
||||||
|
-- Position: providing node_pos is because the currently planned
|
||||||
|
-- behavior for placing nodes is replacing digged nodes. A NPC farmer,
|
||||||
|
-- for instance, might dig a plant node and plant another one on the
|
||||||
|
-- same position.
|
||||||
|
-- Defaults: items will be taken from inventory if existing,
|
||||||
|
-- if not will be force-placed (item comes from thin air)
|
||||||
|
-- Protection will be respected
|
||||||
|
args = {
|
||||||
|
pos = action[node.name][i].args.pos or node_pos,
|
||||||
|
source = action[node.name][i].args.source or npc.actions.take_from_inventory_forced,
|
||||||
|
node = action[node.name][i].args.node,
|
||||||
|
bypass_protection = action[node.name][i].args.bypass_protection or false
|
||||||
|
}
|
||||||
|
elseif actions[node.name][i].action == npc.actions.cmd.WALK_STEP then
|
||||||
|
-- Defaults: direction is calculated from start node to node_pos.
|
||||||
|
-- Speed is default wandering speed. Target pos is node_pos
|
||||||
|
-- Calculate dir if dir is random
|
||||||
|
local dir = npc.actions.get_direction(start_pos, node_pos)
|
||||||
|
if actions[node.name][i].args.dir == "random" then
|
||||||
|
dir = math.random(0,7)
|
||||||
|
elseif type(actions[node.name][i].args.dir) == "number" then
|
||||||
|
dir = actions[node.name][i].args.dir
|
||||||
|
end
|
||||||
|
args = {
|
||||||
|
dir = dir,
|
||||||
|
speed = actions[node.name][i].args.speed or npc.actions.one_nps_speed,
|
||||||
|
target_pos = actions[node.name][i].args.target_pos or node_pos
|
||||||
|
}
|
||||||
|
elseif actions[node.name][i].action == npc.actions.cmd.WALK_TO_POS then
|
||||||
|
-- Optimize walking -- since distances can be really short,
|
||||||
|
-- a simple walk_step() action can do most of the times. For
|
||||||
|
-- this, however, we need to calculate direction
|
||||||
|
-- First of all, check distance
|
||||||
|
if vector.distance(start_pos, node_pos) < 3 then
|
||||||
|
-- Will do walk_step based instead
|
||||||
|
action = npc.actions.cmd.WALK_STEP
|
||||||
|
args = {
|
||||||
|
dir = npc.actions.get_direction(start_pos, node_pos),
|
||||||
|
speed = npc.actions.one_nps_speed,
|
||||||
|
target_pos = node_pos
|
||||||
|
}
|
||||||
|
else
|
||||||
|
-- Set end pos to be node_pos
|
||||||
|
args = {
|
||||||
|
end_pos = actions[node.name][i].args.end_pos or node_pos,
|
||||||
|
walkable = actions[node.name][i].args.walkable or {}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
elseif actions[node.name][i].action == npc.actions.cmd.USE_FURNACE then
|
||||||
|
-- Defaults: pos is node_pos. Freeze is true
|
||||||
|
args = {
|
||||||
|
pos = actions[node.name][i].args.pos or node_pos,
|
||||||
|
item = actions[node.name][i].args.item,
|
||||||
|
freeze = actions[node.name][i].args.freeze or true
|
||||||
|
}
|
||||||
|
end
|
||||||
|
-- Enqueue actions
|
||||||
|
npc.add_action(self, action or actions[node.name][i].action, args or actions[node.name][i].args)
|
||||||
|
end
|
||||||
|
-- Enqueue next schedule check
|
||||||
|
if self.schedules.current_check_params.execution_count
|
||||||
|
< self.schedules.current_check_params.execution_times then
|
||||||
|
npc.add_schedule_check()
|
||||||
|
end
|
||||||
|
-- Nodes found
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
-- No nodes found, enqueue none_actions
|
||||||
|
for i = 1, #none_actions do
|
||||||
|
-- Enqueue actions
|
||||||
|
npc.add_action(self, none_actions[i].action, none_actions[i].args)
|
||||||
|
end
|
||||||
|
-- Enqueue next schedule check
|
||||||
|
if self.schedules.current_check_params.execution_count
|
||||||
|
< self.schedules.current_check_params.execution_times then
|
||||||
|
npc.add_schedule_check()
|
||||||
|
end
|
||||||
|
-- No nodes found
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------
|
||||||
-- NPC Definition
|
-- NPC Definition
|
||||||
---------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------
|
||||||
@ -977,7 +1143,8 @@ mobs:register_mob("advanced_npc:npc", {
|
|||||||
hp_min = 10,
|
hp_min = 10,
|
||||||
hp_max = 20,
|
hp_max = 20,
|
||||||
armor = 100,
|
armor = 100,
|
||||||
collisionbox = {-0.20,-1.0,-0.20, 0.20,0.8,0.20},
|
collisionbox = {-0.20,0,-0.20, 0.20,1.8,0.20},
|
||||||
|
--collisionbox = {-0.20,-1.0,-0.20, 0.20,0.8,0.20},
|
||||||
--collisionbox = {-0.35,-1.0,-0.35, 0.35,0.8,0.35},
|
--collisionbox = {-0.35,-1.0,-0.35, 0.35,0.8,0.35},
|
||||||
visual = "mesh",
|
visual = "mesh",
|
||||||
mesh = "character.b3d",
|
mesh = "character.b3d",
|
||||||
@ -1043,7 +1210,7 @@ mobs:register_mob("advanced_npc:npc", {
|
|||||||
|
|
||||||
-- 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
|
||||||
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()))
|
--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
|
if self.can_have_relationship
|
||||||
and self.can_receive_gifts
|
and self.can_receive_gifts
|
||||||
and item:to_table() ~= nil then
|
and item:to_table() ~= nil then
|
||||||
@ -1155,8 +1322,9 @@ mobs:register_mob("advanced_npc:npc", {
|
|||||||
self.actions.action_timer = 0
|
self.actions.action_timer = 0
|
||||||
-- Check if NPC is walking
|
-- Check if NPC is walking
|
||||||
if self.actions.walking.is_walking == true then
|
if self.actions.walking.is_walking == true then
|
||||||
|
-- Move NPC to expected position to ensure not getting lost
|
||||||
local pos = self.actions.walking.target_pos
|
local pos = self.actions.walking.target_pos
|
||||||
self.object:moveto({x=pos.x, y=pos.y + 0.5, z=pos.z})
|
self.object:moveto({x=pos.x, y=pos.y-0.5, z=pos.z})
|
||||||
end
|
end
|
||||||
-- Execute action
|
-- Execute action
|
||||||
self.freeze = npc.execute_action(self)
|
self.freeze = npc.execute_action(self)
|
||||||
@ -1186,16 +1354,33 @@ mobs:register_mob("advanced_npc:npc", {
|
|||||||
-- Check if schedule for this time exists
|
-- Check if schedule for this time exists
|
||||||
--minetest.log("Found default schedule")
|
--minetest.log("Found default schedule")
|
||||||
if schedule[time] ~= nil then
|
if schedule[time] ~= nil then
|
||||||
-- Check if schedule has a check function
|
npc.log("WARNING", "Found schedule for time "..dump(time)..": "..dump(schedule[time]))
|
||||||
if schedule[time].check ~= nil then
|
|
||||||
-- Execute check function and then add corresponding action
|
|
||||||
-- to action queue. This is for jobs.
|
|
||||||
-- TODO: Need to implement
|
|
||||||
else
|
|
||||||
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
|
||||||
--minetest.log("schedule[time]: "..dump(schedule[time]))
|
-- Check if schedule has a check function
|
||||||
|
if not schedule[time][i].check then
|
||||||
|
-- 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()))
|
||||||
|
local check_params = schedule[time][i]
|
||||||
|
-- Calculates how many times check will be executed
|
||||||
|
local execution_times = check_params.count
|
||||||
|
if check_params.random_execution_times then
|
||||||
|
execution_times = math.random(check_params.min_count, check_params.max_count)
|
||||||
|
end
|
||||||
|
-- Set current parameters
|
||||||
|
self.schedules.current_check_params = {
|
||||||
|
range = check_params.range,
|
||||||
|
nodes = check_params.nodes,
|
||||||
|
actions = check_params.actions,
|
||||||
|
none_actions = check_params.none_actions,
|
||||||
|
execution_count = 0,
|
||||||
|
execution_times = execution_times
|
||||||
|
}
|
||||||
|
-- Execute check for the first time
|
||||||
|
npc.schedule_check(self)
|
||||||
|
else
|
||||||
|
-- Run usual schedule entry
|
||||||
-- Check chance
|
-- Check chance
|
||||||
local execution_chance = math.random(1, 100)
|
local execution_chance = math.random(1, 100)
|
||||||
if not schedule[time][i].chance or
|
if not schedule[time][i].chance or
|
||||||
@ -1227,11 +1412,15 @@ mobs:register_mob("advanced_npc:npc", {
|
|||||||
table.insert(self.schedules.temp_executed_queue, i)
|
table.insert(self.schedules.temp_executed_queue, i)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
-- TODO: Change to debug
|
||||||
|
npc.log("WARNING", "Skipping schedule entry for time "..dump(time)..": "..dump(schedule[time][i]))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- Clear execution queue
|
-- Clear execution queue
|
||||||
self.schedules.temp_executed_queue = {}
|
self.schedules.temp_executed_queue = {}
|
||||||
npc.log("DEBUG", "New action queue: "..dump(self.actions))
|
npc.log("WARNING", "New action queue: "..dump(self.actions))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
22
spawner.lua
22
spawner.lua
@ -264,7 +264,7 @@ function spawner.assign_places(self, pos)
|
|||||||
-- Assign beds
|
-- Assign beds
|
||||||
if #node_data.bed_type > 0 then
|
if #node_data.bed_type > 0 then
|
||||||
-- Assign a specific sittable node to a NPC.
|
-- Assign a specific sittable node to a NPC.
|
||||||
npc.places.add_unowned_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))
|
||||||
@ -275,7 +275,7 @@ function spawner.assign_places(self, pos)
|
|||||||
-- Check if there are same or more amount of sits as beds
|
-- Check if there are same or more amount of sits as beds
|
||||||
if #node_data.sittable_type >= #node_data.bed_type then
|
if #node_data.sittable_type >= #node_data.bed_type then
|
||||||
-- Assign a specific sittable node to a NPC.
|
-- Assign a specific sittable node to a NPC.
|
||||||
npc.places.add_unowned_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))
|
||||||
@ -291,7 +291,7 @@ function spawner.assign_places(self, pos)
|
|||||||
-- Check if there are same or more amount of furnace as beds
|
-- Check if there are same or more amount of furnace as beds
|
||||||
if #node_data.furnace_type >= #node_data.bed_type then
|
if #node_data.furnace_type >= #node_data.bed_type then
|
||||||
-- Assign a specific furnace node to a NPC.
|
-- Assign a specific furnace node to a NPC.
|
||||||
npc.places.add_unowned_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))
|
||||||
@ -307,7 +307,7 @@ function spawner.assign_places(self, pos)
|
|||||||
-- Check if there are same or more amount of storage as beds
|
-- Check if there are same or more amount of storage as beds
|
||||||
if #node_data.storage_type >= #node_data.bed_type then
|
if #node_data.storage_type >= #node_data.bed_type then
|
||||||
-- Assign a specific storage node to a NPC.
|
-- Assign a specific storage node to a NPC.
|
||||||
npc.places.add_unowned_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))
|
||||||
@ -372,17 +372,19 @@ function npc.spawner.spawn_npc(pos)
|
|||||||
local ent = minetest.add_entity({x=pos.x, y=pos.y+1, z=pos.z}, "advanced_npc: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
|
if ent and ent:get_luaentity() then
|
||||||
ent:get_luaentity().initialized = false
|
ent:get_luaentity().initialized = false
|
||||||
|
-- Determine NPC occupation
|
||||||
|
local occupation_name = "default_basic"
|
||||||
-- Initialize NPC
|
-- Initialize NPC
|
||||||
-- Call with stats if there are NPCs
|
-- Call with stats if there are NPCs
|
||||||
if #npc_table > 0 then
|
if npc_table and #npc_table > 0 then
|
||||||
npc.initialize(ent, pos, false, npc_stats)
|
npc.initialize(ent, pos, false, npc_stats, occupation_name)
|
||||||
else
|
else
|
||||||
npc.initialize(ent, pos)
|
npc.initialize(ent, pos, nil, nil, occupation_name)
|
||||||
end
|
end
|
||||||
-- Assign nodes
|
-- Assign nodes
|
||||||
spawner.assign_places(ent:get_luaentity(), pos)
|
spawner.assign_places(ent:get_luaentity(), pos)
|
||||||
-- Assign schedules
|
-- Assign schedules
|
||||||
spawner.assign_schedules(ent:get_luaentity(), pos)
|
--spawner.assign_schedules(ent:get_luaentity(), pos)
|
||||||
-- Increase NPC spawned count
|
-- Increase NPC spawned count
|
||||||
spawned_npc_count = spawned_npc_count + 1
|
spawned_npc_count = spawned_npc_count + 1
|
||||||
-- Store count into node
|
-- Store count into node
|
||||||
@ -750,8 +752,8 @@ minetest.register_chatcommand("restore_plotmarkers", {
|
|||||||
end
|
end
|
||||||
-- Search for nodes
|
-- Search for nodes
|
||||||
local radius = tonumber(param)
|
local radius = tonumber(param)
|
||||||
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 - 5, 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 + 5, 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,
|
||||||
{"mg_villages:plotmarker"})
|
{"mg_villages:plotmarker"})
|
||||||
-- Check if we have nodes to replace
|
-- Check if we have nodes to replace
|
||||||
|
41
utils.lua
41
utils.lua
@ -1,4 +1,4 @@
|
|||||||
-- Basic utilities to work with array operations in Lua
|
-- Basic utilities to work with table operations in Lua, and specific querying
|
||||||
-- By Zorman2000
|
-- By Zorman2000
|
||||||
|
|
||||||
npc.utils = {}
|
npc.utils = {}
|
||||||
@ -35,3 +35,42 @@ function npc.utils.get_map_keys(map)
|
|||||||
end
|
end
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- This function searches for a node given the conditions specified in the
|
||||||
|
-- query object, starting from the given start_pos and up to a certain, specified
|
||||||
|
-- range.
|
||||||
|
-- Query object:
|
||||||
|
-- search_type: determines the direction to search nodes.
|
||||||
|
-- Valid values are: orthogonal, cross, cube
|
||||||
|
-- - orthogonal search means only nodes which are parallel to the search node's faces
|
||||||
|
-- will be considered. This limits the search to only 6 nodes.
|
||||||
|
-- - cross search will look at the same nodes as orthogonal, plus will also
|
||||||
|
-- check nodes diagonal to the node four horizontal nodes. This search looks at 14 nodes
|
||||||
|
-- - cube search means to look every node surrounding the node, including all diagonals.
|
||||||
|
-- This search looks at 26 nodes.
|
||||||
|
-- search_nodes: array of nodes to search for
|
||||||
|
-- surrounding_nodes: object specifying which neighbor nodes are to be expected and
|
||||||
|
-- at which locations. Valid keys are:
|
||||||
|
-- - North (+Z dir)
|
||||||
|
-- - East (+x dir)
|
||||||
|
-- - South (-Z dir)
|
||||||
|
-- - West (-X dir)
|
||||||
|
-- - Top (+Y dir)
|
||||||
|
-- - Bottom (-Y dir)
|
||||||
|
-- Example: ["bottom"] = {nodes={"default:dirt"}, criteria="all"}
|
||||||
|
-- Each object will contain nodes, and criteria for acceptance.
|
||||||
|
-- Criteria values are:
|
||||||
|
-- - any: true as long as one of the nodes on this side is one of the specified
|
||||||
|
-- in "nodes"
|
||||||
|
-- - all: true when the set of neighboring nodes on this side contain one or many of
|
||||||
|
-- the specified "nodes"
|
||||||
|
-- - all-exact: true when the set of neighboring nodes on this side contain all nodes
|
||||||
|
-- specified in "nodes"
|
||||||
|
-- - shape: true when the set of neighboring nodes on this side contains nodes in
|
||||||
|
-- the exact given shape. If so, nodes will not be an array, but a 2d array
|
||||||
|
-- of three rows and three columns, with the specific shape. Notice that
|
||||||
|
-- the nodes on the side can vary depending on the search type (orthogonal,
|
||||||
|
-- cross, cube)
|
||||||
|
function npc.utils.search_node(query, start_pos, range)
|
||||||
|
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user