Merge pull request 'Villager employment system and mob-api enhancements' (#2209) from villagers_jobsites into master

Reviewed-on: https://git.minetest.land/MineClone2/MineClone2/pulls/2209
This commit is contained in:
cora 2022-05-22 13:22:41 +00:00
commit fc428da6fd
34 changed files with 903 additions and 592 deletions

@ -1438,6 +1438,7 @@ end
-- should mob follow what I'm holding ? -- should mob follow what I'm holding ?
local follow_holding = function(self, clicker) local follow_holding = function(self, clicker)
if self.nofollow then return false end
if mobs.invis[clicker:get_player_name()] then if mobs.invis[clicker:get_player_name()] then
return false return false
@ -2317,17 +2318,50 @@ local dogswitch = function(self, dtime)
return self.dogshoot_switch return self.dogshoot_switch
end end
local function go_to_pos(entity,b)
if not entity then return end
local s=entity.object:get_pos()
if vector.distance(b,s) < 1 then
--set_velocity(entity,0)
return true
end
local v = { x = b.x - s.x, z = b.z - s.z }
local yaw = (math.atan(v.z / v.x) + math.pi / 2) - entity.rotate
if b.x > s.x then yaw = yaw + math.pi end
entity.object:set_yaw(yaw)
set_velocity(entity,entity.follow_velocity)
mobs:set_animation(entity, "walk")
end
local function check_doors(self)
local p = self.object:get_pos()
local t = minetest.get_timeofday()
local dd = minetest.find_nodes_in_area(vector.offset(p,-1,-1,-1),vector.offset(p,1,1,1),{"group:door"})
for _,d in pairs(dd) do
local n = minetest.get_node(d)
if n.name:find("_b_") then
local def = minetest.registered_nodes[n.name]
local closed = n.name:find("_b_1")
if t < 0.3 or t > 0.8 then
if not closed then def.on_rightclick(d,n,self) end
else
if closed then def.on_rightclick(d,n,self) end
end
end
end
end
-- execute current state (stand, walk, run, attacks) -- execute current state (stand, walk, run, attacks)
-- returns true if mob has died -- returns true if mob has died
local do_states = function(self, dtime) local do_states = function(self, dtime)
if self.can_open_doors then check_doors(self) end
local yaw = self.object:get_yaw() or 0 local yaw = self.object:get_yaw() or 0
if self.state == "stand" then if self.state == "stand" then
if random(1, 4) == 1 then if random(1, 4) == 1 then
local lp = nil
local s = self.object:get_pos() local s = self.object:get_pos()
local objs = minetest.get_objects_inside_radius(s, 3) local objs = minetest.get_objects_inside_radius(s, 3)
@ -2340,7 +2374,7 @@ local do_states = function(self, dtime)
end end
-- look at any players nearby, otherwise turn randomly -- look at any players nearby, otherwise turn randomly
if lp then if self.look_at_players then
local vec = { local vec = {
x = lp.x - s.x, x = lp.x - s.x,
@ -2375,8 +2409,35 @@ local do_states = function(self, dtime)
end end
end end
elseif self.state == "walk" then elseif self.state == "gowp" then
local p = self.object:get_pos()
if not p or not self._target then return end
if vector.distance(p,self._target) < 2 or ( self.waypoints and #self.waypoints == 0 ) then
self.waypoints = nil
self._target = nil
self.current_target = nil
self.state = "walk"
if self.callback_arrived then return self.callback_arrived(self) end
return true
end
if self.waypoints and ( not self.current_target or vector.distance(p,self.current_target) < 1.5 ) then
self.current_target = table.remove(self.waypoints, 1)
--minetest.log("nextwp:".. tostring(self.current_target) )
elseif self.current_target then
go_to_pos(self,self.current_target)
end
if self.current_target and not minetest.line_of_sight(self.object:get_pos(),self.current_target) then
self.waypoints=minetest.find_path(p,self._target,150,1,4)
self.current_target = nil
return
end
if not self.current_target then
--minetest.log("no path")
self.state = "walk"
end
elseif self.state == "walk" then
local s = self.object:get_pos() local s = self.object:get_pos()
local lp = nil local lp = nil
@ -2880,6 +2941,62 @@ local do_states = function(self, dtime)
end end
end end
local plane_adjacents = {
vector.new(1,0,0),
vector.new(-1,0,0),
vector.new(0,0,1),
vector.new(0,0,-1),
}
function mobs:gopath(self,target,callback_arrived)
local p = self.object:get_pos()
local t = vector.offset(target,0,1,0)
local wp = minetest.find_path(p,t,150,1,4)
if not wp then
local d = minetest.find_node_near(target,16,{"group:door"})
if d then
for _,v in pairs(plane_adjacents) do
local pos = vector.add(d,v)
local n = minetest.get_node(pos)
if n.name == "air" then
wp = minetest.find_path(p,pos,150,1,4)
if wp then break end
end
end
end
end
if wp and #wp > 0 then
self._target = t
self.callback_arrived = callback_arrived
self.waypoints = wp
self.state = "gowp"
return true
else
--minetest.log("no path found")
end
end
local function player_near(pos)
for _,o in pairs(minetest.get_objects_inside_radius(pos,2)) do
if o:is_player() then return true end
end
end
local function check_item_pickup(self)
if self.pick_up and #self.pick_up > 0 then
local p = self.object:get_pos()
for _,o in pairs(minetest.get_objects_inside_radius(p,2)) do
local l=o:get_luaentity()
if l and l.name == "__builtin:item" then
for k,v in pairs(self.pick_up) do
if not player_near(p) and self.on_pick_up and l.itemstring:find(v) then
if self.on_pick_up(self,l) == nil then o:remove() end
end
end
end
end
end
end
-- falling and fall damage -- falling and fall damage
-- returns true if mob died -- returns true if mob died
@ -3481,7 +3598,7 @@ end
-- main mob function -- main mob function
local mob_step = function(self, dtime) local mob_step = function(self, dtime)
check_item_pickup(self)
if not self.fire_resistant then if not self.fire_resistant then
mcl_burning.tick(self.object, dtime, self) mcl_burning.tick(self.object, dtime, self)
end end
@ -3576,8 +3693,7 @@ local mob_step = function(self, dtime)
-- attack timer -- attack timer
self.timer = self.timer + dtime self.timer = self.timer + dtime
if self.state ~= "attack" then if self.state ~= "attack" and self.state ~= "gowp" then
if self.timer < 1 then if self.timer < 1 then
return return
end end
@ -3845,6 +3961,8 @@ minetest.register_entity(name, {
sounds = def.sounds or {}, sounds = def.sounds or {},
animation = def.animation, animation = def.animation,
follow = def.follow, follow = def.follow,
nofollow = def.nofollow,
can_open_doors = def.can_open_doors,
jump = def.jump ~= false, jump = def.jump ~= false,
walk_chance = def.walk_chance or 50, walk_chance = def.walk_chance or 50,
attacks_monsters = def.attacks_monsters or false, attacks_monsters = def.attacks_monsters or false,
@ -3911,6 +4029,7 @@ minetest.register_entity(name, {
texture_mods = {}, texture_mods = {},
shoot_arrow = def.shoot_arrow, shoot_arrow = def.shoot_arrow,
sounds_child = def.sounds_child, sounds_child = def.sounds_child,
pick_up = def.pick_up,
explosion_strength = def.explosion_strength, explosion_strength = def.explosion_strength,
suffocation_timer = 0, suffocation_timer = 0,
follow_velocity = def.follow_velocity or 2.4, follow_velocity = def.follow_velocity or 2.4,
@ -3934,6 +4053,8 @@ minetest.register_entity(name, {
on_grown = def.on_grown, on_grown = def.on_grown,
on_pick_up = def.on_pick_up,
on_detach_child = mob_detach_child, on_detach_child = mob_detach_child,
on_activate = function(self, staticdata, dtime) on_activate = function(self, staticdata, dtime)
@ -4250,7 +4371,7 @@ function mobs:feed_tame(self, clicker, feed_count, breed, tame)
end end
-- can eat/tame with item in hand -- can eat/tame with item in hand
if follow_holding(self, clicker) then if self.nofollow or follow_holding(self, clicker) then
-- if not in creative then take item -- if not in creative then take item
if not mobs.is_creative(clicker:get_player_name()) then if not mobs.is_creative(clicker:get_player_name()) then

@ -194,6 +194,7 @@ functions needed for the mob to work properly which contains the following:
'punch2' animations. 'punch2' animations.
'animation' holds a table containing animation names and settings for use with mesh models: 'animation' holds a table containing animation names and settings for use with mesh models:
{
'stand_start'start frame for when mob stands still. 'stand_start'start frame for when mob stands still.
'stand_end' end frame of stand animation. 'stand_end' end frame of stand animation.
'stand_speed'speed of animation in frames per second. 'stand_speed'speed of animation in frames per second.
@ -219,6 +220,7 @@ functions needed for the mob to work properly which contains the following:
'die_end' 'die_end'
'die_speed' 'die_speed'
'die_loop' when set to false stops the animation looping. 'die_loop' when set to false stops the animation looping.
}
Using '_loop = false' setting will stop any of the above animations from Using '_loop = false' setting will stop any of the above animations from
looping. looping.
@ -237,7 +239,7 @@ functions needed for the mob to work properly which contains the following:
'rain_damage' damage per second if mob is standing in rain (default: 0) 'rain_damage' damage per second if mob is standing in rain (default: 0)
'sunlight_damage' holds the damage per second inflicted to mobs when they 'sunlight_damage' holds the damage per second inflicted to mobs when they
are in direct sunlight are in direct sunlight
'spawn_small_alternative': name of a smaller mob to use as replacement if 'spawn_small_alternative' name of a smaller mob to use as replacement if
spawning fails due to space requirements spawning fails due to space requirements
'glow' same as in entity definition 'glow' same as in entity definition
'child' if true, spawn mob as child 'child' if true, spawn mob as child
@ -253,6 +255,12 @@ functions needed for the mob to work properly which contains the following:
'fire_resistant' If true, the mob can't burn 'fire_resistant' If true, the mob can't burn
'fire_damage_resistant' If true the mob will not take damage when burning 'fire_damage_resistant' If true the mob will not take damage when burning
'ignited_by_sunlight' If true the mod will burn at daytime. (Takes sunlight_damage per second) 'ignited_by_sunlight' If true the mod will burn at daytime. (Takes sunlight_damage per second)
'nofollow' Do not follow players when they wield the "follow" item. For mobs (like villagers)
that are bred in a different way.
'pick_up' table of itemstrings the mob will pick up (e.g. for breeding)
'on_pick_up' function that will be called on item pickup - return true to not pickup the item
mobs:gopath(self,target,callback_arrived) pathfind a way to target and run callback on arrival

@ -169,7 +169,7 @@ mobs_mc.follow = {
dog = { mobs_mc.items.rabbit_raw, mobs_mc.items.rabbit_cooked, mobs_mc.items.mutton_raw, mobs_mc.items.mutton_cooked, mobs_mc.items.beef_raw, mobs_mc.items.beef_cooked, mobs_mc.items.chicken_raw, mobs_mc.items.chicken_cooked, mobs_mc.items.rotten_flesh, dog = { mobs_mc.items.rabbit_raw, mobs_mc.items.rabbit_cooked, mobs_mc.items.mutton_raw, mobs_mc.items.mutton_cooked, mobs_mc.items.beef_raw, mobs_mc.items.beef_cooked, mobs_mc.items.chicken_raw, mobs_mc.items.chicken_cooked, mobs_mc.items.rotten_flesh,
-- Mobs Redo items -- Mobs Redo items
"mobs:meat", "mobs:meat_raw" }, "mobs:meat", "mobs:meat_raw" },
villager = { "mcl_farming:bread" }, villager = { "mcl_farming:bread", "mcl_farming:carrot_item", "mcl_farming:beetroot_item" , "mcl_farming:potato_item" },
} }
-- Contents for replace_what -- Contents for replace_what

@ -9,7 +9,7 @@ local S = minetest.get_translator("mobs_mc")
--################### IRON GOLEM --################### IRON GOLEM
--################### --###################
local etime = 0
mobs:register_mob("mobs_mc:iron_golem", { mobs:register_mob("mobs_mc:iron_golem", {
description = S("Iron Golem"), description = S("Iron Golem"),
@ -41,6 +41,26 @@ mobs:register_mob("mobs_mc:iron_golem", {
group_attack = true, group_attack = true,
attacks_monsters = true, attacks_monsters = true,
attack_type = "dogfight", attack_type = "dogfight",
_got_poppy = false,
pick_up = {"mcl_flowers:poppy"},
on_pick_up = function(self,n)
if n.itemstring:find("mcl_flowers:poppy") then
if not self._got_poppy then
self._got_poppy=true
return
end
return true
end
end,
replace_what = {"mcl_flowers:poppy"},
replace_with = {"air"},
on_replace = function(self, pos, oldnode, newnode)
if not self.got_poppy and oldnode.name == "mcl_flowers:poppy" then
self._got_poppy=true
return
end
return false
end,
drops = { drops = {
{name = mobs_mc.items.iron_ingot, {name = mobs_mc.items.iron_ingot,
chance = 1, chance = 1,
@ -60,6 +80,14 @@ mobs:register_mob("mobs_mc:iron_golem", {
punch_start = 40, punch_end = 50, punch_start = 40, punch_end = 50,
}, },
jump = true, jump = true,
on_step = function(self,dtime)
etime = etime + dtime
if etime > 10 then
if self._home and vector.distance(self._home,self.object:get_pos()) > 50 then
mobs:gopath(self,self._home)
end
end
end,
}) })

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 866 B

After

Width:  |  Height:  |  Size: 989 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 848 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 950 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 933 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 896 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 768 B

After

Width:  |  Height:  |  Size: 921 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 983 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 866 B

After

Width:  |  Height:  |  Size: 941 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

@ -10,14 +10,10 @@
-- TODO: Particles -- TODO: Particles
-- TODO: 4s Regeneration I after trade unlock -- TODO: 4s Regeneration I after trade unlock
-- TODO: Breeding
-- TODO: Baby villagers
-- TODO: Spawning in villages
-- TODO: Behaviour: -- TODO: Behaviour:
-- TODO: Walk around village, but do not leave it intentionally
-- TODO: Run into house on rain or danger, open doors -- TODO: Run into house on rain or danger, open doors
-- TODO: Internal inventory, pick up items, trade with other villagers -- TODO: Internal inventory, trade with other villagers
-- TODO: Farm stuff -- TODO: Schedule stuff (work,sleep,father)
local S = minetest.get_translator("mobs_mc") local S = minetest.get_translator("mobs_mc")
local N = function(s) return s end local N = function(s) return s end
@ -61,15 +57,37 @@ if minetest.get_mapgen_setting("mg_name") == "v6" then
TRADE_V6_BIRCH_SAPLING = { { "mcl_core:emerald", 8, 11 }, { "mcl_core:birchsapling", 1, 1 } } TRADE_V6_BIRCH_SAPLING = { { "mcl_core:emerald", 8, 11 }, { "mcl_core:birchsapling", 1, 1 } }
end end
local tiernames = {
"Novice",
"Apprentice",
"Journeyman",
"Expert",
"Master",
}
local badges = {
"default_wood.png",
"default_steel_block.png",
"default_gold_block.png",
"mcl_core_emerald_block.png",
"default_diamond_block.png",
}
local professions = { local professions = {
unemployed = { unemployed = {
name = N("Unemployed"), name = N("Unemployed"),
texture = "mobs_mc_villager.png", textures = {
"mobs_mc_villager.png",
"mobs_mc_villager.png",
},
trades = nil, trades = nil,
}, },
farmer = { farmer = {
name = N("Farmer"), name = N("Farmer"),
texture = "mobs_mc_villager_farmer.png", textures = {
"mobs_mc_villager_farmer.png",
"mobs_mc_villager_farmer.png",
},
jobsite = "mcl_composters:composter", jobsite = "mcl_composters:composter",
trades = { trades = {
{ {
@ -103,7 +121,10 @@ local professions = {
}, },
fisherman = { fisherman = {
name = N("Fisherman"), name = N("Fisherman"),
texture = "mobs_mc_villager_farmer.png", textures = {
"mobs_mc_villager_fisherman.png",
"mobs_mc_villager_fisherman.png",
},
jobsite = "mcl_barrels:barrel_closed", jobsite = "mcl_barrels:barrel_closed",
trades = { trades = {
{ {
@ -138,7 +159,10 @@ local professions = {
}, },
fletcher = { fletcher = {
name = N("Fletcher"), name = N("Fletcher"),
texture = "mobs_mc_villager_farmer.png", textures = {
"mobs_mc_villager_fletcher.png",
"mobs_mc_villager_fletcher.png",
},
jobsite = "mcl_fletching_table:fletching_table", jobsite = "mcl_fletching_table:fletching_table",
trades = { trades = {
{ {
@ -177,7 +201,10 @@ local professions = {
}, },
shepherd ={ shepherd ={
name = N("Shepherd"), name = N("Shepherd"),
texture = "mobs_mc_villager_farmer.png", textures = {
"mobs_mc_villager_sheperd.png",
"mobs_mc_villager_sheperd.png",
},
jobsite = "mcl_loom:loom", jobsite = "mcl_loom:loom",
trades = { trades = {
{ {
@ -207,8 +234,11 @@ local professions = {
}, },
librarian = { librarian = {
name = N("Librarian"), name = N("Librarian"),
texture = "mobs_mc_villager_librarian.png", textures = {
jobsite = "mcl_villages:stonebrickcarved", --FIXME: lectern "mobs_mc_villager_librarian.png",
"mobs_mc_villager_librarian.png",
},
jobsite = "mcl_books:bookshelf", --FIXME: lectern
trades = { trades = {
{ {
{ { "mcl_core:paper", 24, 36 }, E1 }, { { "mcl_core:paper", 24, 36 }, E1 },
@ -242,7 +272,10 @@ local professions = {
}, },
cartographer = { cartographer = {
name = N("Cartographer"), name = N("Cartographer"),
texture = "mobs_mc_villager_librarian.png", textures = {
"mobs_mc_villager_cartographer.png",
"mobs_mc_villager_cartographer.png",
},
jobsite = "mcl_cartography_table:cartography_table", jobsite = "mcl_cartography_table:cartography_table",
trades = { trades = {
{ {
@ -285,7 +318,10 @@ local professions = {
}, },
armorer = { armorer = {
name = N("Armorer"), name = N("Armorer"),
texture = "mobs_mc_villager_smith.png", textures = {
"mobs_mc_villager_armorer.png",
"mobs_mc_villager_armorer.png",
},
jobsite = "mcl_blast_furnace:blast_furnace", jobsite = "mcl_blast_furnace:blast_furnace",
trades = { trades = {
{ {
@ -298,7 +334,7 @@ local professions = {
{ {
{ { "mcl_core:iron_ingot", 4, 4 }, E1 }, { { "mcl_core:iron_ingot", 4, 4 }, E1 },
--{ { "mcl_core:emerald", 36, 36 }, { "FIXME: Bell", 1, 1 } }, { { "mcl_core:emerald", 36, 36 }, { "mcl_bells:bell", 1, 1 } },
{ { "mcl_core:emerald", 3, 3 }, { "mcl_armor:leggings_chain", 1, 1 } }, { { "mcl_core:emerald", 3, 3 }, { "mcl_armor:leggings_chain", 1, 1 } },
{ { "mcl_core:emerald", 1, 1 }, { "mcl_armor:boots_chain", 1, 1 } }, { { "mcl_core:emerald", 1, 1 }, { "mcl_armor:boots_chain", 1, 1 } },
}, },
@ -322,7 +358,10 @@ local professions = {
}, },
leatherworker = { leatherworker = {
name = N("Leatherworker"), name = N("Leatherworker"),
texture = "mobs_mc_villager_butcher.png", textures = {
"mobs_mc_villager_leatherworker.png",
"mobs_mc_villager_leatherworker.png",
},
jobsite = "mcl_cauldrons:cauldron", jobsite = "mcl_cauldrons:cauldron",
trades = { trades = {
{ {
@ -351,7 +390,10 @@ local professions = {
}, },
butcher = { butcher = {
name = N("Butcher"), name = N("Butcher"),
texture = "mobs_mc_villager_butcher.png", textures = {
"mobs_mc_villager_butcher.png",
"mobs_mc_villager_butcher.png",
},
jobsite = "mcl_smoker:smoker", jobsite = "mcl_smoker:smoker",
trades = { trades = {
{ {
@ -381,8 +423,11 @@ local professions = {
}, },
weapon_smith = { weapon_smith = {
name = N("Weapon Smith"), name = N("Weapon Smith"),
texture = "mobs_mc_villager_smith.png", textures = {
jobsite = "mcl_villages:stonebrickcarved", --FIXME: grindstone "mobs_mc_villager_weaponsmith.png",
"mobs_mc_villager_weaponsmith.png",
},
jobsite = "mcl_furnaces:furnace", --FIXME: grindstone
trades = { trades = {
{ {
{ { "mcl_core:coal_lump", 15, 15 }, E1 }, { { "mcl_core:coal_lump", 15, 15 }, E1 },
@ -392,7 +437,7 @@ local professions = {
{ {
{ { "mcl_core:iron_ingot", 4, 4 }, E1 }, { { "mcl_core:iron_ingot", 4, 4 }, E1 },
--{ { "mcl_core:emerald", 36, 36 }, { "FIXME: Bell", 1, 1 } }, { { "mcl_core:emerald", 36, 36 }, { "mcl_bells:bell", 1, 1 } },
}, },
{ {
{ { "mcl_core:flint", 7, 9 }, E1 }, { { "mcl_core:flint", 7, 9 }, E1 },
@ -409,8 +454,11 @@ local professions = {
}, },
tool_smith = { tool_smith = {
name = N("Tool Smith"), name = N("Tool Smith"),
texture = "mobs_mc_villager_smith.png", textures = {
jobsite = "mcl_villages:stonebrickcarved", --FIXME: smithing table "mobs_mc_villager_toolsmith.png",
"mobs_mc_villager_toolsmith.png",
},
jobsite = "mcl_anvils:anvil", --FIXME: smithing table
trades = { trades = {
{ {
{ { "mcl_core:coal_lump", 15, 15 }, E1 }, { { "mcl_core:coal_lump", 15, 15 }, E1 },
@ -422,7 +470,7 @@ local professions = {
{ {
{ { "mcl_core:iron_ingot", 4, 4 }, E1 }, { { "mcl_core:iron_ingot", 4, 4 }, E1 },
--{ { "mcl_core:emerald", 36, 36 }, { "FIXME: Bell", 1, 1 } }, { { "mcl_core:emerald", 36, 36 }, { "mcl_bells:bell", 1, 1 } },
}, },
{ {
{ { "mcl_core:flint", 30, 30 }, E1 }, { { "mcl_core:flint", 30, 30 }, E1 },
@ -443,8 +491,11 @@ local professions = {
}, },
cleric = { cleric = {
name = N("Cleric"), name = N("Cleric"),
texture = "mobs_mc_villager_priest.png", textures = {
jobsite = "mcl_brewing:stand", "mobs_mc_villager_priest.png",
"mobs_mc_villager_priest.png",
},
jobsite = "mcl_brewing:stand_000",
trades = { trades = {
{ {
{ { "mcl_mobitems:rotten_flesh", 32, 32 }, E1 }, { { "mcl_mobitems:rotten_flesh", 32, 32 }, E1 },
@ -472,7 +523,10 @@ local professions = {
}, },
nitwit = { nitwit = {
name = N("Nitwit"), name = N("Nitwit"),
texture = "mobs_mc_villager.png", textures = {
"mobs_mc_villager_nitwit.png",
"mobs_mc_villager_nitwit.png",
},
-- No trades for nitwit -- No trades for nitwit
trades = nil, trades = nil,
} }
@ -483,48 +537,106 @@ for id, _ in pairs(professions) do
table.insert(profession_names, id) table.insert(profession_names, id)
end end
local stand_still = function(self) local jobsites={}
for _,n in pairs(profession_names) do
table.insert(jobsites,professions[n].jobsite)
end
local function stand_still(self)
self.walk_chance = 0 self.walk_chance = 0
self.jump = false self.jump = false
end end
local function set_velocity(self, v) local function init_trader_vars(self)
local yaw = (self.object:get_yaw() or 0) + self.rotate if not self._max_trade_tier then
self.object:set_velocity({ self._max_trade_tier = 1
x = (math.sin(yaw) * -v), end
y = self.object:get_velocity().y, if not self._locked_trades then
z = (math.cos(yaw) * v), self._locked_trades = 0
}) end
if not self._trading_players then
self._trading_players = {}
end
end end
local function go_to_pos(entity,b) local function get_badge_textures(self)
local s=entity.object:get_pos() local t = professions[self._profession].textures
local v = { x = b.x - s.x, z = b.z - s.z } if self._profession == "unemployed" or self._profession == "nitwit" then return t end
local yaw = (math.atan(v.z / v.x) + math.pi / 2) - entity.rotate local tier = self._max_trade_tier or 1
if b.x > s.x then yaw = yaw + math.pi end return {
entity.object:set_yaw(yaw) "[combine:64x64:0,0="..t[1]..":11,55=".. badges[tier].."\\^[resize\\:2x2",
set_velocity(entity,entity.follow_velocity) t[2]
if vector.distance(b,s) < 5 then }
return true
end end
local function set_textures(self)
self.object:set_properties({textures=get_badge_textures(self)})
end end
local function go_home(entity) local function go_home(entity)
entity.state = "go_home" entity.state = "go_home"
local b=entity.bed local b=entity._bed
if not b then return end if not b then return end
if go_to_pos(entity,b) then mobs:gopath(entity,b,function(entity,b)
if vector.distance(entity.object:get_pos(),b) < 2 then
entity.state = "stand" entity.state = "stand"
set_velocity(entity,0) set_velocity(entity,0)
entity.object:set_pos(b) entity.object:set_pos(b)
local n=minetest.get_node(b) local n=minetest.get_node(b)
if n and n.name ~= "mcl_beds:bed_red_bottom" then if n and n.name ~= "mcl_beds:bed_red_bottom" then
entity.bed=nil --the stormtroopers have killed uncle owen entity._bed=nil --the stormtroopers have killed uncle owen
return false
end
return true
end
end)
end
----- JOBSITE LOGIC
local function get_profession_by_jobsite(js)
for k,v in pairs(professions) do
if v.jobsite == js then return k end
end
end
local function employ(self,jobsite_pos)
local n = minetest.get_node(jobsite_pos)
local m = minetest.get_meta(jobsite_pos)
local p = get_profession_by_jobsite(n.name)
if p and m:get_string("villager") == "" then
self._profession=p
m:set_string("villager",self._id)
self._jobsite = jobsite_pos
set_textures(self)
return true
end
end
local function look_for_job(self)
local p = self.object:get_pos()
local nn = minetest.find_nodes_in_area(vector.offset(p,-48,-48,-48),vector.offset(p,48,48,48),jobsites)
for _,n in pairs(nn) do
local m=minetest.get_meta(n)
if m:get_string("villager") == "" then
--minetest.log("goingt to jobsite "..minetest.pos_to_string(n) )
local gp = mobs:gopath(self,n,function()
--minetest.log("arrived jobsite "..minetest.pos_to_string(n) )
end)
if gp then return end
end end
end end
end end
local update_max_tradenum = function(self) local function get_a_job(self)
local p = self.object:get_pos()
local nn = minetest.find_nodes_in_area(vector.offset(p,-8,-8,-8),vector.offset(p,8,8,8),jobsites)
for _,n in pairs(nn) do
if n and employ(self,n) then return true end
end
if self.state ~= "gowp" then look_for_job(self) end
end
local function update_max_tradenum(self)
if not self._trades then if not self._trades then
return return
end end
@ -539,31 +651,7 @@ local update_max_tradenum = function(self)
self._max_tradenum = #trades self._max_tradenum = #trades
end end
local init_trader_vars = function(self) local function init_trades(self, inv)
if not self._profession then
-- Select random profession from all professions with matching clothing
local texture = self.base_texture[1]
local matches = {}
for prof_id, prof in pairs(professions) do
if texture == prof.texture then
table.insert(matches, prof_id)
end
end
local p = math.random(1, #matches)
self._profession = matches[p]
end
if not self._max_trade_tier then
self._max_trade_tier = 1
end
if not self._locked_trades then
self._locked_trades = 0
end
if not self._trading_players then
self._trading_players = {}
end
end
local init_trades = function(self, inv)
local profession = professions[self._profession] local profession = professions[self._profession]
local trade_tiers = profession.trades local trade_tiers = profession.trades
if trade_tiers == nil then if trade_tiers == nil then
@ -614,7 +702,7 @@ local init_trades = function(self, inv)
minetest.deserialize(self._trades) minetest.deserialize(self._trades)
end end
local set_trade = function(trader, player, inv, concrete_tradenum) local function set_trade(trader, player, inv, concrete_tradenum)
local trades = minetest.deserialize(trader._trades) local trades = minetest.deserialize(trader._trades)
if not trades then if not trades then
init_trades(trader) init_trades(trader)
@ -688,12 +776,17 @@ local function show_trade_formspec(playername, trader, tradenum)
w2_formspec = "item_image[3,1;1,1;"..wanted2:to_string().."]" w2_formspec = "item_image[3,1;1,1;"..wanted2:to_string().."]"
.."tooltip[3,1;0.8,0.8;"..F(wanted2:get_description()).."]" .."tooltip[3,1;0.8,0.8;"..F(wanted2:get_description()).."]"
end end
local tiername = tiernames[trader._max_trade_tier]
if tiername then
tiername = S(tiername)
else
tiername = S("Master")
end
local formspec = local formspec =
"size[9,8.75]" "size[9,8.75]"
.."background[-0.19,-0.25;9.41,9.49;mobs_mc_trading_formspec_bg.png]" .."background[-0.19,-0.25;9.41,9.49;mobs_mc_trading_formspec_bg.png]"
..disabled_img ..disabled_img
.."label[4,0;"..F(minetest.colorize("#313131", S(profession))).."]" .."label[3,0;"..F(minetest.colorize("#313131", S(profession).." - "..tiername)) .."]"
.."list[current_player;main;0,4.5;9,3;9]" .."list[current_player;main;0,4.5;9,3;9]"
.."list[current_player;main;0,7.74;9,1;]" .."list[current_player;main;0,7.74;9,1;]"
..b_prev..b_next ..b_prev..b_next
@ -713,7 +806,7 @@ local function show_trade_formspec(playername, trader, tradenum)
minetest.show_formspec(playername, tradeinv_name, formspec) minetest.show_formspec(playername, tradeinv_name, formspec)
end end
local update_offer = function(inv, player, sound) local function update_offer(inv, player, sound)
local name = player:get_player_name() local name = player:get_player_name()
local trader = player_trading_with[name] local trader = player_trading_with[name]
local tradenum = player_tradenum[name] local tradenum = player_tradenum[name]
@ -737,12 +830,12 @@ local update_offer = function(inv, player, sound)
-- compass. -- compass.
-- TODO: Remove these check functions when compass and clock are implemented -- TODO: Remove these check functions when compass and clock are implemented
-- as single items. -- as single items.
local check_special = function(special_item, group, wanted1, wanted2, input1, input2) local function check_special(special_item, group, wanted1, wanted2, input1, input2)
if minetest.registered_aliases[special_item] then if minetest.registered_aliases[special_item] then
special_item = minetest.registered_aliases[special_item] special_item = minetest.registered_aliases[special_item]
end end
if wanted1:get_name() == special_item then if wanted1:get_name() == special_item then
local check_input = function(input, wanted, group) local function check_input(input, wanted, group)
return minetest.get_item_group(input:get_name(), group) ~= 0 and input:get_count() >= wanted:get_count() return minetest.get_item_group(input:get_name(), group) ~= 0 and input:get_count() >= wanted:get_count()
end end
if check_input(input1, wanted1, group) then if check_input(input1, wanted1, group) then
@ -757,7 +850,7 @@ local update_offer = function(inv, player, sound)
end end
-- Apply above function to all items which we consider special. -- Apply above function to all items which we consider special.
-- This function succeeds if ANY item check succeeds. -- This function succeeds if ANY item check succeeds.
local check_specials = function(wanted1, wanted2, input1, input2) local function check_specials(wanted1, wanted2, input1, input2)
return check_special(COMPASS, "compass", wanted1, wanted2, input1, input2) return check_special(COMPASS, "compass", wanted1, wanted2, input1, input2)
end end
-- END OF SPECIAL HANDLING OF COMPASS -- END OF SPECIAL HANDLING OF COMPASS
@ -811,7 +904,7 @@ local function return_item(itemstack, dropper, pos, inv_p)
return itemstack return itemstack
end end
local return_fields = function(player) local function return_fields(player)
local name = player:get_player_name() local name = player:get_player_name()
local inv_t = minetest.get_inventory({type="detached", name = "mobs_mc:trade_"..name}) local inv_t = minetest.get_inventory({type="detached", name = "mobs_mc:trade_"..name})
local inv_p = player:get_inventory() local inv_p = player:get_inventory()
@ -877,7 +970,7 @@ minetest.register_on_leaveplayer(function(player)
end) end)
-- Return true if player is trading with villager, and the villager entity exists -- Return true if player is trading with villager, and the villager entity exists
local trader_exists = function(playername) local function trader_exists(playername)
local trader = player_trading_with[playername] local trader = player_trading_with[playername]
return trader ~= nil and trader.object:get_luaentity() ~= nil return trader ~= nil and trader.object:get_luaentity() ~= nil
end end
@ -904,7 +997,7 @@ local trade_inventory = {
wanted1:set_count(wanted1:get_count()*2) wanted1:set_count(wanted1:get_count()*2)
wanted2:set_count(wanted2:get_count()*2) wanted2:set_count(wanted2:get_count()*2)
-- BEGIN OF SPECIAL HANDLING FOR COMPASS -- BEGIN OF SPECIAL HANDLING FOR COMPASS
local special_checks = function(wanted1, input1, input2) local function special_checks(wanted1, input1, input2)
if wanted1:get_name() == COMPASS then if wanted1:get_name() == COMPASS then
local compasses = 0 local compasses = 0
if (minetest.get_item_group(input1:get_name(), "compass") ~= 0) then if (minetest.get_item_group(input1:get_name(), "compass") ~= 0) then
@ -1021,6 +1114,10 @@ local trade_inventory = {
-- First-time trade unlock all trades and unlock next trade tier -- First-time trade unlock all trades and unlock next trade tier
if trade.tier + 1 > trader._max_trade_tier then if trade.tier + 1 > trader._max_trade_tier then
trader._max_trade_tier = trader._max_trade_tier + 1 trader._max_trade_tier = trader._max_trade_tier + 1
if trader._max_trade_tier > 5 then
trader._max_trade_tier = 5
end
set_textures(trader)
update_max_tradenum(trader) update_max_tradenum(trader)
update_formspec = true update_formspec = true
end end
@ -1113,31 +1210,9 @@ mobs:register_mob("mobs_mc:villager", {
visual = "mesh", visual = "mesh",
mesh = "mobs_mc_villager.b3d", mesh = "mobs_mc_villager.b3d",
textures = { textures = {
{
"mobs_mc_villager.png", "mobs_mc_villager.png",
"mobs_mc_villager.png", --hat "mobs_mc_villager.png", --hat
}, },
{
"mobs_mc_villager_farmer.png",
"mobs_mc_villager_farmer.png", --hat
},
{
"mobs_mc_villager_priest.png",
"mobs_mc_villager_priest.png", --hat
},
{
"mobs_mc_villager_librarian.png",
"mobs_mc_villager_librarian.png", --hat
},
{
"mobs_mc_villager_butcher.png",
"mobs_mc_villager_butcher.png", --hat
},
{
"mobs_mc_villager_smith.png",
"mobs_mc_villager_smith.png", --hat
},
},
visual_size = {x=2.75, y=2.75}, visual_size = {x=2.75, y=2.75},
makes_footstep_sound = true, makes_footstep_sound = true,
walk_velocity = 1.2, walk_velocity = 1.2,
@ -1165,23 +1240,45 @@ mobs:register_mob("mobs_mc:villager", {
die_loop = false, die_loop = false,
}, },
follow = mobs_mc.follow.villager, follow = mobs_mc.follow.villager,
nofollow = true,
view_range = 16, view_range = 16,
fear_height = 4, fear_height = 4,
jump = true, jump = true,
walk_chance = DEFAULT_WALK_CHANCE, walk_chance = DEFAULT_WALK_CHANCE,
on_rightclick = function(self, clicker) _bed = nil,
if clicker:get_wielded_item():get_name() == "mcl_farming:bread" then _id = nil,
if mobs:feed_tame(self, clicker, 1, true, true) then return end _profession = "unemployed",
if mobs:protect(self, clicker) then return end look_at_player = true,
pick_up = mobs_mc.follow.villager,
can_open_doors = true,
on_pick_up = function(self,itementity)
local clicker
for _,p in pairs(minetest.get_connected_players()) do
if vector.distance(p:get_pos(),self.object:get_pos()) < 10 then
clicker = p
end end
if self.child then end
if clicker then
mobs:feed_tame(self, clicker, 1, true, false)
return
end
return true --do not pick up
end,
on_rightclick = function(self, clicker)
local trg=vector.new(0,9,0)
if self._jobsite then
mobs:gopath(self,self._jobsite,function()
--minetest.log("arrived at jobsite")
end)
end
if self.child or self._profession == "unemployed" then
return return
end end
-- Initiate trading -- Initiate trading
init_trader_vars(self)
local name = clicker:get_player_name() local name = clicker:get_player_name()
self._trading_players[name] = true self._trading_players[name] = true
init_trader_vars(self)
if self._trades == nil then if self._trades == nil then
init_trades(self) init_trades(self)
end end
@ -1219,10 +1316,6 @@ mobs:register_mob("mobs_mc:villager", {
self._player_scan_timer = 0 self._player_scan_timer = 0
end end
if self.bed and ( self.state == "go_home" or vector.distance(self.object:get_pos(),self.bed) > 50 ) then
go_home(self)
end
self._player_scan_timer = self._player_scan_timer + dtime self._player_scan_timer = self._player_scan_timer + dtime
-- Check infrequently to keep CPU load low -- Check infrequently to keep CPU load low
if self._player_scan_timer > PLAYER_SCAN_INTERVAL then if self._player_scan_timer > PLAYER_SCAN_INTERVAL then
@ -1244,15 +1337,31 @@ mobs:register_mob("mobs_mc:villager", {
self.walk_chance = DEFAULT_WALK_CHANCE self.walk_chance = DEFAULT_WALK_CHANCE
self.jump = true self.jump = true
end end
if self._bed and ( self.state ~= "go_home" and vector.distance(self.object:get_pos(),self._bed) > 50 ) then
go_home(self)
end
if self._profession == "unemployed" then
get_a_job(self)
end
end end
end, end,
on_spawn = function(self) on_spawn = function(self)
init_trader_vars(self) if self._id then
set_textures(self)
return
end
self._id=minetest.sha1(minetest.get_gametime()..minetest.pos_to_string(self.object:get_pos())..tostring(math.random()))
self._profession = "unemployed"
if math.random(100) == 1 then
self._profession = "nitwit"
end
set_textures(self)
end, end,
on_die = function(self, pos) on_die = function(self, pos)
-- Close open trade formspecs and give input back to players -- Close open trade formspecs and give input back to players
local trading_players = self._trading_players local trading_players = self._trading_players
if trading_players then
for name, _ in pairs(trading_players) do for name, _ in pairs(trading_players) do
minetest.close_formspec(name, "mobs_mc:trade_"..name) minetest.close_formspec(name, "mobs_mc:trade_"..name)
local player = minetest.get_player_by_name(name) local player = minetest.get_player_by_name(name)
@ -1260,6 +1369,7 @@ mobs:register_mob("mobs_mc:villager", {
return_fields(player) return_fields(player)
end end
end end
end
end, end,
}) })

@ -5,7 +5,13 @@ mcl_bells = {}
local has_mcl_wip = minetest.get_modpath("mcl_wip") local has_mcl_wip = minetest.get_modpath("mcl_wip")
function mcl_bells.ring_once(pos) function mcl_bells.ring_once(pos)
minetest.sound_play( "mcl_bells_bell_stroke", { pos = pos, gain = 1.5, max_hear_distance = 300,}); minetest.sound_play( "mcl_bells_bell_stroke", { pos = pos, gain = 1.5, max_hear_distance = 150,})
local vv=minetest.get_objects_inside_radius(pos,150)
for _,o in pairs(vv) do
if o.type == "npc" then
mobs:gopath(o:get_luaentity(),pos,function() end)
end
end
end end
minetest.register_node("mcl_bells:bell", { minetest.register_node("mcl_bells:bell", {

@ -188,11 +188,41 @@ local function construct_node(p1, p2, name)
end end
minetest.log("warning", "[mcl_villages] Attempt to 'construct' inexistant nodes: " .. name) minetest.log("warning", "[mcl_villages] Attempt to 'construct' inexistant nodes: " .. name)
end end
local function spawn_iron_golem(pos)
local p = minetest.find_node_near(pos,50,"mcl_core:grass_path")
if p then
local l=minetest.add_entity(p,"mobs_mc:iron_golem"):get_luaentity()
if l then
l._home = p
end
end
end
local function spawn_villagers(minp,maxp)
local beds=minetest.find_nodes_in_area(vector.offset(minp,-20,-20,-20),vector.offset(maxp,20,20,20),{"mcl_beds:bed_red_bottom"})
for _,bed in pairs(beds) do
local m = minetest.get_meta(bed)
if m:get_string("villager") == "" then
local v=minetest.add_entity(bed,"mobs_mc:villager")
if v then
local l=v:get_luaentity()
l._bed = bed
m:set_string("villager",l._id)
end
end
end
end
local function init_nodes(p1, p2, size, rotation, pr) local function init_nodes(p1, p2, size, rotation, pr)
construct_node(p1, p2, "mcl_itemframes:item_frame") construct_node(p1, p2, "mcl_itemframes:item_frame")
construct_node(p1, p2, "mcl_furnaces:furnace") construct_node(p1, p2, "mcl_furnaces:furnace")
construct_node(p1, p2, "mcl_anvils:anvil") construct_node(p1, p2, "mcl_anvils:anvil")
construct_node(p1, p2, "mcl_smoker:smoker")
construct_node(p1, p2, "mcl_barrels:barrel_closed")
construct_node(p1, p2, "mcl_blast_furnace:blast_furnace")
construct_node(p1, p2, "mcl_brewing:stand_000")
local nodes = construct_node(p1, p2, "mcl_chests:chest") local nodes = construct_node(p1, p2, "mcl_chests:chest")
if nodes and #nodes > 0 then if nodes and #nodes > 0 then
for p=1, #nodes do for p=1, #nodes do
@ -201,9 +231,30 @@ local function init_nodes(p1, p2, size, rotation, pr)
end end
end end
end end
function settlements.place_schematics(settlement_info, pr) function settlements.place_schematics(settlement_info, pr)
local building_all_info local building_all_info
--attempt to place one belltower in the center of the village - this doesn't always work out great but it's a lot better than doing it first or last.
local belltower = table.remove(settlement_info,math.floor(#settlement_info/2))
if belltower then
mcl_structures.place_schematic(
vector.offset(belltower["pos"],0,0,0),
settlements.modpath.."/schematics/belltower.mts",
belltower["rotation"],
nil,
true,
nil,
function(p1, p2, size, rotation, pr)
spawn_iron_golem(p1)
end,
pr
)
end
for i, built_house in ipairs(settlement_info) do for i, built_house in ipairs(settlement_info) do
local is_last = i == #settlement_info
for j, schem in ipairs(settlements.schematic_table) do for j, schem in ipairs(settlements.schematic_table) do
if settlement_info[i]["name"] == schem["name"] then if settlement_info[i]["name"] == schem["name"] then
building_all_info = schem building_all_info = schem
@ -271,7 +322,10 @@ function settlements.place_schematics(settlement_info, pr)
nil, nil,
true, true,
nil, nil,
init_nodes, function(p1, p2, size, rotation, pr)
init_nodes(p1, p2, size, rotation, pr)
spawn_villagers(p1,p2)
end,
pr pr
) )
end end

@ -21,7 +21,6 @@ minetest.register_node("mcl_villages:stonebrickcarved", {
description = ("Chiseled Stone Village Bricks"), description = ("Chiseled Stone Village Bricks"),
_doc_items_longdesc = doc.sub.items.temp.build, _doc_items_longdesc = doc.sub.items.temp.build,
tiles = {"mcl_core_stonebrick_carved.png"}, tiles = {"mcl_core_stonebrick_carved.png"},
stack_max = 64,
drop = "mcl_core:stonebrickcarved", drop = "mcl_core:stonebrickcarved",
groups = {pickaxey=1, stone=1, stonebrick=1, building_block=1, material_stone=1}, groups = {pickaxey=1, stone=1, stonebrick=1, building_block=1, material_stone=1},
sounds = mcl_sounds.node_sound_stone_defaults(), sounds = mcl_sounds.node_sound_stone_defaults(),
@ -50,17 +49,6 @@ if minetest.get_modpath("mobs_mc") then
end end
--]] --]]
local function spawn_villagers(minp,maxp)
local beds=minetest.find_nodes_in_area(minp,maxp,{"mcl_beds:bed_red_bottom"})
for _,bed in pairs(beds) do
minetest.get_meta(bed):set_string("villagebed","true")
local v=minetest.add_entity(bed,"mobs_mc:villager")
if v then
v:get_luaentity().bed = bed
end
end
end
-- --
-- on map generation, try to build a settlement -- on map generation, try to build a settlement
-- --
@ -79,10 +67,6 @@ local function build_a_settlement(minp, maxp, blockseed)
-- evaluate settlement_info and place schematics -- evaluate settlement_info and place schematics
settlements.place_schematics(settlement_info, pr) settlements.place_schematics(settlement_info, pr)
minetest.after(60,function()
spawn_villagers(minp,maxp)
end) --give the village some time to fully generate
end end
local function ecb_village(blockpos, action, calls_remaining, param) local function ecb_village(blockpos, action, calls_remaining, param)

Binary file not shown.