3 --made for MC like Survival game
4 --License for code WTFPL and otherwise stated in readmes
7 --################### VILLAGER
9 -- Summary: Villagers are complex NPCs, their main feature allows players to trade with them.
12 -- TODO: 4s Regeneration I after trade unlock
14 -- TODO: Baby villagers
15 -- TODO: Spawning in villages
17 -- TODO: Walk around village, but do not leave it intentionally
18 -- TODO: Run into house on rain or danger, open doors
19 -- TODO: Internal inventory, pick up items, trade with other villagers
22 local S
= minetest
.get_translator("mobs_mc")
23 local N
= function(s
) return s
end
24 local F
= minetest
.formspec_escape
26 -- playername-indexed table containing the previously used tradenum
27 local player_tradenum
= {}
28 -- playername-indexed table containing the objectref of trader, if trading formspec is open
29 local player_trading_with
= {}
31 local DEFAULT_WALK_CHANCE
= 33 -- chance to walk in percent, if no player nearby
32 local PLAYER_SCAN_INTERVAL
= 5 -- every X seconds, villager looks for players nearby
33 local PLAYER_SCAN_RADIUS
= 4 -- scan radius for looking for nearby players
35 --[=======[ TRADING ]=======]
37 -- LIST OF VILLAGER PROFESSIONS AND TRADES
39 -- TECHNICAL RESTRICTIONS (FIXME):
40 -- * You can't use a clock as requested item
41 -- * You can't use a compass as requested item if its stack size > 1
42 -- * You can't use a compass in the second requested slot
43 -- This is a problem in the mcl_compass and mcl_clock mods,
44 -- these items should be implemented as single items, then everything
45 -- will be much easier.
47 local COMPASS
= "mcl_compass:compass"
48 if minetest
.registered_aliases
[COMPASS
] then
49 COMPASS
= minetest
.registered_aliases
[COMPASS
]
52 local E1
= { "mcl_core:emerald", 1, 1 } -- one emerald
54 -- Special trades for v6 only
55 -- NOTE: These symbols MUST only be added at the end of a tier
56 local TRADE_V6_RED_SANDSTONE
, TRADE_V6_DARK_OAK_SAPLING
, TRADE_V6_ACACIA_SAPLING
, TRADE_V6_BIRCH_SAPLING
57 if minetest
.get_mapgen_setting("mg_name") == "v6" then
58 TRADE_V6_RED_SANDSTONE
= { E1
, { "mcl_core:redsandstone", 12, 16 } }
59 TRADE_V6_DARK_OAK_SAPLING
= { { "mcl_core:emerald", 6, 9 }, { "mcl_core:darksapling", 1, 1 } }
60 TRADE_V6_ACACIA_SAPLING
= { { "mcl_core:emerald", 14, 17 }, { "mcl_core:acaciasapling", 1, 1 } }
61 TRADE_V6_BIRCH_SAPLING
= { { "mcl_core:emerald", 8, 11 }, { "mcl_core:birchsapling", 1, 1 } }
67 texture
= "mobs_mc_villager_farmer.png",
70 { { "mcl_farming:wheat_item", 18, 22, }, E1
},
71 { { "mcl_farming:potato_item", 15, 19, }, E1
},
72 { { "mcl_farming:carrot_item", 15, 19, }, E1
},
73 { E1
, { "mcl_farming:bread", 2, 4 } },
77 { { "mcl_farming:pumpkin_face", 8, 13 }, E1
},
78 { E1
, { "mcl_farming:pumpkin_pie", 2, 3} },
82 { { "mcl_farming:melon", 7, 12 }, E1
},
83 { E1
, { "mcl_core:apple", 5, 7 }, },
87 { E1
, { "mcl_farming:cookie", 6, 10 } },
88 { E1
, { "mcl_cake:cake", 1, 1 } },
89 TRADE_V6_BIRCH_SAPLING
,
90 TRADE_V6_DARK_OAK_SAPLING
,
91 TRADE_V6_ACACIA_SAPLING
,
96 name
= N("Fisherman"),
97 texture
= "mobs_mc_villager_farmer.png",
100 { { "mcl_fishing:fish_raw", 6, 6, "mcl_core:emerald", 1, 1 }, { "mcl_fishing:fish_cooked", 6, 6 } },
101 { { "mcl_mobitems:string", 15, 20 }, E1
},
102 -- TODO: replace with enchanted fishing rod
103 { { "mcl_core:emerald", 3, 11 }, { "mcl_fishing:fishing_rod", 1, 1} },
108 name
= N("Fletcher"),
109 texture
= "mobs_mc_villager_farmer.png",
112 { { "mcl_mobitems:string", 15, 20 }, E1
},
113 { E1
, { "mcl_bows:arrow", 8, 12 } },
117 { { "mcl_core:gravel", 10, 10, "mcl_core:emerald", 1, 1 }, { "mcl_core:flint", 6, 10 } },
118 { { "mcl_core:emerald", 2, 3 }, { "mcl_bows:bow", 1, 1 } },
123 name
= N("Shepherd"),
124 texture
= "mobs_mc_villager_farmer.png",
127 { { "mcl_wool:white", 16, 22 }, E1
},
128 { { "mcl_core:emerald", 3, 4 }, { "mcl_tools:shears", 1, 1 } },
132 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:white", 1, 1 } },
133 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:grey", 1, 1 } },
134 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:silver", 1, 1 } },
135 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:black", 1, 1 } },
136 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:yellow", 1, 1 } },
137 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:orange", 1, 1 } },
138 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:red", 1, 1 } },
139 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:magenta", 1, 1 } },
140 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:purple", 1, 1 } },
141 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:blue", 1, 1 } },
142 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:cyan", 1, 1 } },
143 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:lime", 1, 1 } },
144 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:green", 1, 1 } },
145 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:pink", 1, 1 } },
146 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:light_blue", 1, 1 } },
147 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:brown", 1, 1 } },
152 name
= N("Librarian"),
153 texture
= "mobs_mc_villager_librarian.png",
156 { { "mcl_core:paper", 24, 36 }, E1
},
157 -- TODO: enchanted book
158 { { "mcl_books:book", 8, 10 }, E1
},
159 { { "mcl_core:emerald", 10, 12 }, { "mcl_compass:compass", 1 ,1 }},
160 { { "mcl_core:emerald", 3, 4 }, { "mcl_books:bookshelf", 1 ,1 }},
164 { { "mcl_books:written_book", 2, 2 }, E1
},
165 { { "mcl_core:emerald", 10, 12 }, { "mcl_clock:clock", 1, 1 } },
166 { E1
, { "mcl_core:glass", 3, 5 } },
170 { E1
, { "mcl_core:glass", 3, 5 } },
173 -- TODO: 2 enchanted book tiers
176 { { "mcl_core:emerald", 20, 22 }, { "mcl_mobs:nametag", 1, 1 } },
181 name
= N("Cartographer"),
182 texture
= "mobs_mc_villager_librarian.png",
185 { { "mcl_core:paper", 24, 36 }, E1
},
189 -- subject to special checks
190 { { "mcl_compass:compass", 1, 1 }, E1
},
194 -- TODO: replace with empty map
195 { { "mcl_core:emerald", 7, 11}, { "mcl_maps:filled_map", 1, 1 } },
198 -- TODO: special maps
203 texture
= "mobs_mc_villager_smith.png",
206 { { "mcl_core:coal_lump", 16, 24 }, E1
},
207 { { "mcl_core:emerald", 4, 6 }, { "mcl_armor:helmet_iron", 1, 1 } },
211 { { "mcl_core:iron_ingot", 7, 9 }, E1
},
212 { { "mcl_core:emerald", 10, 14 }, { "mcl_armor:chestplate_iron", 1, 1 } },
216 { { "mcl_core:diamond", 3, 4 }, E1
},
218 { { "mcl_core:emerald", 16, 19 }, { "mcl_armor:chestplate_diamond", 1, 1 } },
222 { { "mcl_core:emerald", 5, 7 }, { "mcl_armor:boots_chain", 1, 1 } },
223 { { "mcl_core:emerald", 9, 11 }, { "mcl_armor:leggings_chain", 1, 1 } },
224 { { "mcl_core:emerald", 5, 7 }, { "mcl_armor:helmet_chain", 1, 1 } },
225 { { "mcl_core:emerald", 11, 15 }, { "mcl_armor:chestplate_chain", 1, 1 } },
230 name
= N("Leatherworker"),
231 texture
= "mobs_mc_villager_butcher.png",
234 { { "mcl_mobitems:leather", 9, 12 }, E1
},
235 { { "mcl_core:emerald", 2, 4 }, { "mcl_armor:leggings_leather", 2, 4 } },
240 { { "mcl_core:emerald", 7, 12 }, { "mcl_armor:chestplate_leather", 1, 1 } },
244 { { "mcl_core:emerald", 8, 10 }, { "mcl_mobitems:saddle", 1, 1 } },
250 texture
= "mobs_mc_villager_butcher.png",
253 { { "mcl_mobitems:beef", 14, 18 }, E1
},
254 { { "mcl_mobitems:chicken", 14, 18 }, E1
},
258 { { "mcl_core:coal_lump", 16, 24 }, E1
},
259 { E1
, { "mcl_mobitems:cooked_beef", 5, 7 } },
260 { E1
, { "mcl_mobitems:cooked_chicken", 6, 8 } },
265 name
= N("Weapon Smith"),
266 texture
= "mobs_mc_villager_smith.png",
269 { { "mcl_core:coal_lump", 16, 24 }, E1
},
270 { { "mcl_core:emerald", 6, 8 }, { "mcl_tools:axe_iron", 1, 1 } },
274 { { "mcl_core:iron_ingot", 7, 9 }, E1
},
276 { { "mcl_core:emerald", 9, 10 }, { "mcl_tools:sword_iron", 1, 1 } },
280 { { "mcl_core:diamond", 3, 4 }, E1
},
282 { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:sword_diamond", 1, 1 } },
284 { { "mcl_core:emerald", 9, 12 }, { "mcl_tools:axe_diamond", 1, 1 } },
289 name
= N("Tool Smith"),
290 texture
= "mobs_mc_villager_smith.png",
293 { { "mcl_core:coal_lump", 16, 24 }, E1
},
295 { { "mcl_core:emerald", 5, 7 }, { "mcl_tools:shovel_iron", 1, 1 } },
299 { { "mcl_core:iron_ingot", 7, 9 }, E1
},
301 { { "mcl_core:emerald", 9, 11 }, { "mcl_tools:pick_iron", 1, 1 } },
305 { { "mcl_core:diamond", 3, 4 }, E1
},
307 { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:pick_diamond", 1, 1 } },
313 texture
= "mobs_mc_villager_priest.png",
316 { { "mcl_mobitems:rotten_flesh", 36, 40 }, E1
},
317 { { "mcl_core:gold_ingot", 8, 10 }, E1
},
321 { E1
, { "mesecons:redstone", 1, 4 } },
322 { E1
, { "mcl_dye:blue", 1, 2 } },
326 { E1
, { "mcl_nether:glowstone", 1, 3 } },
327 { { "mcl_core:emerald", 4, 7 }, { "mcl_throwing:ender_pearl", 1, 1 } },
328 TRADE_V6_RED_SANDSTONE
,
331 -- TODO: Bottle 'o enchanting
336 texture
= "mobs_mc_villager.png",
337 -- No trades for nitwit
342 local profession_names
= {}
343 for id
, _
in pairs(professions
) do
344 table.insert(profession_names
, id
)
347 local stand_still
= function(self
)
352 local update_max_tradenum
= function(self
)
353 if not self
._trades
then
356 local trades
= minetest
.deserialize(self
._trades
)
358 local trade
= trades
[t
]
359 if trade
.tier
> self
._max_trade_tier
then
360 self
._max_tradenum
= t
- 1
364 self
._max_tradenum
= #trades
367 local init_trader_vars
= function(self
)
368 if not self
._profession
then
369 -- Select random profession from all professions with matching clothing
370 local texture
= self
.base_texture
[1]
372 for prof_id
, prof
in pairs(professions
) do
373 if texture
== prof
.texture
then
374 table.insert(matches
, prof_id
)
377 local p
= math
.random(1, #matches
)
378 self
._profession
= matches
[p
]
380 if not self
._max_trade_tier
then
381 self
._max_trade_tier
= 1
383 if not self
._locked_trades
then
384 self
._locked_trades
= 0
386 if not self
._trading_players
then
387 self
._trading_players
= {}
391 local init_trades
= function(self
, inv
)
392 local profession
= professions
[self
._profession
]
393 local trade_tiers
= profession
.trades
394 if trade_tiers
== nil then
400 local max_tier
= #trade_tiers
402 for tiernum
=1, max_tier
do
403 local tier
= trade_tiers
[tiernum
]
404 for tradenum
=1, #tier
do
405 local trade
= tier
[tradenum
]
406 local wanted1_item
= trade
[1][1]
407 local wanted1_count
= math
.random(trade
[1][2], trade
[1][3])
408 local offered_item
= trade
[2][1]
409 local offered_count
= math
.random(trade
[2][2], trade
[2][3])
411 local wanted
= { wanted1_item
.. " " ..wanted1_count
}
413 local wanted2_item
= trade
[1][4]
414 local wanted2_count
= math
.random(trade
[1][5], trade
[1][6])
415 table.insert(wanted
, wanted2_item
.. " " ..wanted2_count
)
418 table.insert(trades
, {
420 offered
= offered_item
.. " " .. offered_count
,
421 tier
= tiernum
, -- tier of this trade
422 traded_once
= false, -- true if trade was traded at least once
423 trade_counter
= 0, -- how often the this trade was mate after the last time it got unlocked
424 locked
= false, -- if this trade is locked. Locked trades can't be used
428 self
._trades
= minetest
.serialize(trades
)
431 local set_trade
= function(trader
, player
, inv
, concrete_tradenum
)
432 local trades
= minetest
.deserialize(trader
._trades
)
435 trades
= minetest
.deserialize(trader
._trades
)
437 minetest
.log("error", "[mobs_mc] Failed to select villager trade!")
441 local name
= player
:get_player_name()
443 -- Stop tradenum from advancing into locked tiers or out-of-range areas
444 if concrete_tradenum
> trader
._max_tradenum
then
445 concrete_tradenum
= trader
._max_tradenum
446 elseif concrete_tradenum
< 1 then
447 concrete_tradenum
= 1
449 player_tradenum
[name
] = concrete_tradenum
450 local trade
= trades
[concrete_tradenum
]
451 inv
:set_stack("wanted", 1, ItemStack(trade
.wanted
[1]))
452 inv
:set_stack("offered", 1, ItemStack(trade
.offered
))
453 if trade
.wanted
[2] then
454 local wanted2
= ItemStack(trade
.wanted
[2])
455 inv
:set_stack("wanted", 2, wanted2
)
457 inv
:set_stack("wanted", 2, "")
462 local function show_trade_formspec(playername
, trader
, tradenum
)
463 if not trader
._trades
then
469 local trades
= minetest
.deserialize(trader
._trades
)
470 local trade
= trades
[tradenum
]
471 local profession
= professions
[trader
._profession
].name
472 local disabled_img
= ""
474 disabled_img
= "image[4.3,2.52;1,1;mobs_mc_trading_formspec_disabled.png]"..
475 "image[4.3,1.1;1,1;mobs_mc_trading_formspec_disabled.png]"
477 local tradeinv_name
= "mobs_mc:trade_"..playername
478 local tradeinv
= F("detached:"..tradeinv_name
)
480 local b_prev
, b_next
= "", ""
483 b_prev
= "button[1,1;0.5,1;prev_trade;<]"
485 if tradenum
< trader
._max_tradenum
then
486 b_next
= "button[7.26,1;0.5,1;next_trade;>]"
490 local inv
= minetest
.get_inventory({type="detached", name
="mobs_mc:trade_"..playername
})
494 local wanted1
= inv
:get_stack("wanted", 1)
495 local wanted2
= inv
:get_stack("wanted", 2)
496 local offered
= inv
:get_stack("offered", 1)
498 local w2_formspec
= ""
499 if not wanted2
:is_empty() then
500 w2_formspec
= "item_image[3,1;1,1;"..wanted2
:to_string().."]"
501 .."tooltip[3,1;0.8,0.8;"..F(wanted2
:get_description()).."]"
506 .."background[-0.19,-0.25;9.41,9.49;mobs_mc_trading_formspec_bg.png]"
508 .."label[4,0;"..F(minetest
.colorize("#313131", S(profession
))).."]"
509 .."list[current_player;main;0,4.5;9,3;9]"
510 .."list[current_player;main;0,7.74;9,1;]"
512 .."["..tradeinv
..";wanted;2,1;2,1;]"
513 .."item_image[2,1;1,1;"..wanted1
:to_string().."]"
514 .."tooltip[2,1;0.8,0.8;"..F(wanted1
:get_description()).."]"
516 .."item_image[5.76,1;1,1;"..offered
:to_string().."]"
517 .."tooltip[5.76,1;0.8,0.8;"..F(offered
:get_description()).."]"
518 .."list["..tradeinv
..";input;2,2.5;2,1;]"
519 .."list["..tradeinv
..";output;5.76,2.55;1,1;]"
520 .."listring["..tradeinv
..";output]"
521 .."listring[current_player;main]"
522 .."listring["..tradeinv
..";input]"
523 .."listring[current_player;main]"
524 minetest
.sound_play("mobs_mc_villager_trade", {to_player
= playername
}, true)
525 minetest
.show_formspec(playername
, tradeinv_name
, formspec
)
528 local update_offer
= function(inv
, player
, sound
)
529 local name
= player
:get_player_name()
530 local trader
= player_trading_with
[name
]
531 local tradenum
= player_tradenum
[name
]
532 if not trader
or not tradenum
then
535 local trades
= minetest
.deserialize(trader
._trades
)
539 local trade
= trades
[tradenum
]
543 local wanted1
, wanted2
= inv
:get_stack("wanted", 1), inv
:get_stack("wanted", 2)
544 local input1
, input2
= inv
:get_stack("input", 1), inv
:get_stack("input", 2)
546 -- BEGIN OF SPECIAL HANDLING OF COMPASS
547 -- These 2 functions are a complicated check to check if the input contains a
548 -- special item which we cannot check directly against their name, like
550 -- TODO: Remove these check functions when compass and clock are implemented
552 local check_special
= function(special_item
, group
, wanted1
, wanted2
, input1
, input2
)
553 if minetest
.registered_aliases
[special_item
] then
554 special_item
= minetest
.registered_aliases
[special_item
]
556 if wanted1
:get_name() == special_item
then
557 local check_input
= function(input
, wanted
, group
)
558 return minetest
.get_item_group(input
:get_name(), group
) ~= 0 and input
:get_count() >= wanted
:get_count()
560 if check_input(input1
, wanted1
, group
) then
562 elseif check_input(input2
, wanted1
, group
) then
570 -- Apply above function to all items which we consider special.
571 -- This function succeeds if ANY item check succeeds.
572 local check_specials
= function(wanted1
, wanted2
, input1
, input2
)
573 return check_special(COMPASS
, "compass", wanted1
, wanted2
, input1
, input2
)
575 -- END OF SPECIAL HANDLING OF COMPASS
578 ((inv
:contains_item("input", wanted1
) and
579 (wanted2
:is_empty() or inv
:contains_item("input", wanted2
))) or
580 -- BEGIN OF SPECIAL HANDLING OF COMPASS
581 check_specials(wanted1
, wanted2
, input1
, input2
)) and
582 -- END OF SPECIAL HANDLING OF COMPASS
583 (trade
.locked
== false)) then
584 inv
:set_stack("output", 1, inv
:get_stack("offered", 1))
586 minetest
.sound_play("mobs_mc_villager_accept", {to_player
= name
}, true)
590 inv
:set_stack("output", 1, ItemStack(""))
592 minetest
.sound_play("mobs_mc_villager_deny", {to_player
= name
}, true)
598 -- Returns a single itemstack in the given inventory to the player's main inventory, or drop it when there's no space left
599 local function return_item(itemstack
, dropper
, pos
, inv_p
)
600 if dropper
:is_player() then
601 -- Return to main inventory
602 if inv_p
:room_for_item("main", itemstack
) then
603 inv_p
:add_item("main", itemstack
)
605 -- Drop item on the ground
606 local v
= dropper
:get_look_dir()
607 local p
= {x
=pos
.x
, y
=pos
.y
+1.2, z
=pos
.z
}
608 p
.x
= p
.x
+(math
.random(1,3)*0.2)
609 p
.z
= p
.z
+(math
.random(1,3)*0.2)
610 local obj
= minetest
.add_item(p
, itemstack
)
616 obj
:get_luaentity()._insta_collect
= false
620 -- Fallback for unexpected cases
621 minetest
.add_item(pos
, itemstack
)
626 local return_fields
= function(player
)
627 local name
= player
:get_player_name()
628 local inv_t
= minetest
.get_inventory({type="detached", name
= "mobs_mc:trade_"..name
})
629 local inv_p
= player
:get_inventory()
630 if not inv_t
or not inv_p
then
633 for i
=1, inv_t
:get_size("input") do
634 local stack
= inv_t
:get_stack("input", i
)
635 return_item(stack
, player
, player
:get_pos(), inv_p
)
637 inv_t
:set_stack("input", i
, stack
)
639 inv_t
:set_stack("output", 1, "")
642 minetest
.register_on_player_receive_fields(function(player
, formname
, fields
)
643 if string.sub(formname
, 1, 14) == "mobs_mc:trade_" then
644 local name
= player
:get_player_name()
646 -- Get input items back
647 return_fields(player
)
648 -- Reset internal "trading with" state
649 local trader
= player_trading_with
[name
]
651 trader
._trading_players
[name
] = nil
653 player_trading_with
[name
] = nil
654 elseif fields
.next_trade
or fields
.prev_trade
then
655 local trader
= player_trading_with
[name
]
656 if not trader
or not trader
.object
:get_luaentity() then
659 local trades
= trader
._trades
664 if fields
.prev_trade
then
667 local tradenum
= player_tradenum
[name
] + dir
668 local inv
= minetest
.get_inventory({type="detached", name
="mobs_mc:trade_"..name
})
672 set_trade(trader
, player
, inv
, tradenum
)
673 update_offer(inv
, player
, false)
674 show_trade_formspec(name
, trader
, player_tradenum
[name
])
679 minetest
.register_on_leaveplayer(function(player
)
680 local name
= player
:get_player_name()
681 return_fields(player
)
682 player_tradenum
[name
] = nil
683 local trader
= player_trading_with
[name
]
685 trader
._trading_players
[name
] = nil
687 player_trading_with
[name
] = nil
691 -- Return true if player is trading with villager, and the villager entity exists
692 local trader_exists
= function(playername
)
693 local trader
= player_trading_with
[playername
]
694 return trader
~= nil and trader
.object
:get_luaentity() ~= nil
697 local trade_inventory
= {
698 allow_take
= function(inv
, listname
, index
, stack
, player
)
699 if listname
== "input" then
700 return stack
:get_count()
701 elseif listname
== "output" then
702 if not trader_exists(player
:get_player_name()) then
705 -- Only allow taking full stack
706 local count
= stack
:get_count()
707 if count
== inv
:get_stack(listname
, index
):get_count() then
708 -- Also update output stack again.
709 -- If input has double the wanted items, the
710 -- output will stay because there will be still
711 -- enough items in input after the trade
712 local wanted1
= inv
:get_stack("wanted", 1)
713 local wanted2
= inv
:get_stack("wanted", 2)
714 local input1
= inv
:get_stack("input", 1)
715 local input2
= inv
:get_stack("input", 2)
716 wanted1
:set_count(wanted1
:get_count()*2)
717 wanted2
:set_count(wanted2
:get_count()*2)
718 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
719 local special_checks
= function(wanted1
, input1
, input2
)
720 if wanted1
:get_name() == COMPASS
then
722 if (minetest
.get_item_group(input1
:get_name(), "compass") ~= 0) then
723 compasses
= compasses
+ input1
:get_count()
725 if (minetest
.get_item_group(input2
:get_name(), "compass") ~= 0) then
726 compasses
= compasses
+ input2
:get_count()
728 return compasses
>= wanted1
:get_count()
732 -- END OF SPECIAL HANDLING FOR COMPASS
733 if (inv
:contains_item("input", wanted1
) and
734 (wanted2
:is_empty() or inv
:contains_item("input", wanted2
)))
735 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
736 or special_checks(wanted1
, input1
, input2
) then
737 -- END OF SPECIAL HANDLING FOR COMPASS
740 -- If less than double the wanted items,
741 -- remove items from output (final trade,
752 allow_move
= function(inv
, from_list
, from_index
, to_list
, to_index
, count
, player
)
753 if from_list
== "input" and to_list
== "input" then
755 elseif from_list
== "output" and to_list
== "input" then
756 if not trader_exists(player
:get_player_name()) then
759 local move_stack
= inv
:get_stack(from_list
, from_index
)
760 if inv
:get_stack(to_list
, to_index
):item_fits(move_stack
) then
766 allow_put
= function(inv
, listname
, index
, stack
, player
)
767 if listname
== "input" then
768 if not trader_exists(player
:get_player_name()) then
771 return stack
:get_count()
777 on_put
= function(inv
, listname
, index
, stack
, player
)
778 update_offer(inv
, player
, true)
780 on_move
= function(inv
, from_list
, from_index
, to_list
, to_index
, count
, player
)
781 if from_list
== "output" and to_list
== "input" then
782 inv
:remove_item("input", inv
:get_stack("wanted", 1))
783 local wanted2
= inv
:get_stack("wanted", 2)
784 if not wanted2
:is_empty() then
785 inv
:remove_item("input", inv
:get_stack("wanted", 2))
787 minetest
.sound_play("mobs_mc_villager_accept", {to_player
= player
:get_player_name()}, true)
789 update_offer(inv
, player
, true)
791 on_take
= function(inv
, listname
, index
, stack
, player
)
793 local name
= player
:get_player_name()
794 if listname
== "output" then
795 local wanted1
= inv
:get_stack("wanted", 1)
796 inv
:remove_item("input", wanted1
)
797 local wanted2
= inv
:get_stack("wanted", 2)
798 if not wanted2
:is_empty() then
799 inv
:remove_item("input", inv
:get_stack("wanted", 2))
801 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
802 if wanted1
:get_name() == COMPASS
then
804 local input
= inv
:get_stack("input", n
)
805 if minetest
.get_item_group(input
:get_name(), "compass") ~= 0 then
806 input
:set_count(input
:get_count() - wanted1
:get_count())
807 inv
:set_stack("input", n
, input
)
812 -- END OF SPECIAL HANDLING FOR COMPASS
813 local trader
= player_trading_with
[name
]
814 local tradenum
= player_tradenum
[name
]
816 if trader
and trader
._trades
then
817 trades
= minetest
.deserialize(trader
._trades
)
820 local trade
= trades
[tradenum
]
821 local unlock_stuff
= false
822 if not trade
.traded_once
then
823 -- Unlock all the things if something was traded
824 -- for the first time ever
826 trade
.traded_once
= true
827 elseif trade
.trade_counter
== 0 and math
.random(1,5) == 1 then
828 -- Otherwise, 20% chance to unlock if used freshly reset trade
831 local update_formspec
= false
833 -- First-time trade unlock all trades and unlock next trade tier
834 if trade
.tier
+ 1 > trader
._max_trade_tier
then
835 trader
._max_trade_tier
= trader
._max_trade_tier
+ 1
836 update_max_tradenum(trader
)
837 update_formspec
= true
840 trades
[t
].locked
= false
841 trades
[t
].trade_counter
= 0
843 trader
._locked_trades
= 0
844 -- Also heal trader for unlocking stuff
845 -- TODO: Replace by Regeneration I
846 trader
.health
= math
.min(trader
.hp_max
, trader
.health
+ 4)
848 trade
.trade_counter
= trade
.trade_counter
+ 1
849 -- Semi-randomly lock trade for repeated trade (not if there's only 1 trade)
850 if trader
._max_tradenum
> 1 then
851 if trade
.trade_counter
>= 12 then
853 elseif trade
.trade_counter
>= 2 then
854 local r
= math
.random(1, math
.random(4, 10))
862 inv
:set_stack("output", 1, "")
863 update_formspec
= true
864 trader
._locked_trades
= trader
._locked_trades
+ 1
865 -- Check if we managed to lock ALL available trades. Rare but possible.
866 if trader
._locked_trades
>= trader
._max_tradenum
then
867 -- Emergency unlock! Unlock all other trades except the current one
869 if t
~= tradenum
then
870 trades
[t
].locked
= false
871 trades
[t
].trade_counter
= 0
874 trader
._locked_trades
= 1
875 -- Also heal trader for unlocking stuff
876 -- TODO: Replace by Regeneration I
877 trader
.health
= math
.min(trader
.hp_max
, trader
.health
+ 4)
880 trader
._trades
= minetest
.serialize(trades
)
881 if update_formspec
then
882 show_trade_formspec(name
, trader
, tradenum
)
885 minetest
.log("error", "[mobs_mc] Player took item from trader output but player_trading_with or player_tradenum is nil!")
889 elseif listname
== "input" then
890 update_offer(inv
, player
, false)
893 minetest
.sound_play("mobs_mc_villager_accept", {to_player
= name
}, true)
895 minetest
.sound_play("mobs_mc_villager_deny", {to_player
= name
}, true)
900 minetest
.register_on_joinplayer(function(player
)
901 local name
= player
:get_player_name()
902 player_tradenum
[name
] = 1
903 player_trading_with
[name
] = nil
905 -- Create or get player-specific trading inventory
906 local inv
= minetest
.get_inventory({type="detached", name
="mobs_mc:trade_"..name
})
908 inv
= minetest
.create_detached_inventory("mobs_mc:trade_"..name
, trade_inventory
, name
)
910 inv
:set_size("input", 2)
911 inv
:set_size("output", 1)
912 inv
:set_size("wanted", 2)
913 inv
:set_size("offered", 1)
916 --[=======[ MOB REGISTRATION AND SPAWNING ]=======]
918 mobs
:register_mob("mobs_mc:villager", {
920 spawn_class
= "passive",
923 collisionbox
= {-0.3, -0.01, -0.3, 0.3, 1.94, 0.3},
925 mesh
= "mobs_mc_villager.b3d",
928 "mobs_mc_villager.png",
929 "mobs_mc_villager.png", --hat
932 "mobs_mc_villager_farmer.png",
933 "mobs_mc_villager_farmer.png", --hat
936 "mobs_mc_villager_priest.png",
937 "mobs_mc_villager_priest.png", --hat
940 "mobs_mc_villager_librarian.png",
941 "mobs_mc_villager_librarian.png", --hat
944 "mobs_mc_villager_butcher.png",
945 "mobs_mc_villager_butcher.png", --hat
948 "mobs_mc_villager_smith.png",
949 "mobs_mc_villager_smith.png", --hat
952 visual_size
= {x
=3, y
=3},
953 makes_footstep_sound
= true,
976 walk_chance
= DEFAULT_WALK_CHANCE
,
977 on_rightclick
= function(self
, clicker
)
979 local name
= clicker
:get_player_name()
980 self
._trading_players
[name
] = true
982 init_trader_vars(self
)
983 if self
._trades
== nil then
986 update_max_tradenum(self
)
987 if self
._trades
== false then
988 -- Villager has no trades, rightclick is a no-op
992 player_trading_with
[name
] = self
994 local inv
= minetest
.get_inventory({type="detached", name
="mobs_mc:trade_"..name
})
999 set_trade(self
, clicker
, inv
, 1)
1001 show_trade_formspec(name
, self
)
1004 -- Make villager look at player and stand still
1005 local selfpos
= self
.object
:get_pos()
1006 local clickerpos
= clicker
:get_pos()
1007 local dir
= vector
.direction(selfpos
, clickerpos
)
1008 self
.object
:set_yaw(minetest
.dir_to_yaw(dir
))
1012 _player_scan_timer
= 0,
1013 _trading_players
= {}, -- list of playernames currently trading with villager (open formspec)
1014 do_custom
= function(self
, dtime
)
1015 -- Stand still if player is nearby.
1016 if not self
._player_scan_timer
then
1017 self
._player_scan_timer
= 0
1019 self
._player_scan_timer
= self
._player_scan_timer
+ dtime
1020 -- Check infrequently to keep CPU load low
1021 if self
._player_scan_timer
> PLAYER_SCAN_INTERVAL
then
1022 self
._player_scan_timer
= 0
1023 local selfpos
= self
.object
:get_pos()
1024 local objects
= minetest
.get_objects_inside_radius(selfpos
, PLAYER_SCAN_RADIUS
)
1025 local has_player
= false
1026 for o
, obj
in pairs(objects
) do
1027 if obj
:is_player() then
1033 minetest
.log("verbose", "[mobs_mc] Player near villager found!")
1036 minetest
.log("verbose", "[mobs_mc] No player near villager found!")
1037 self
.walk_chance
= DEFAULT_WALK_CHANCE
1043 on_spawn
= function(self
)
1044 init_trader_vars(self
)
1046 on_die
= function(self
, pos
)
1047 -- Close open trade formspecs and give input back to players
1048 local trading_players
= self
._trading_players
1049 for name
, _
in pairs(trading_players
) do
1050 minetest
.close_formspec(name
, "mobs_mc:trade_"..name
)
1051 local player
= minetest
.get_player_by_name(name
)
1053 return_fields(player
)
1056 mobs
.death_effect(pos
, self
.collisionbox
)
1062 mobs
:spawn_specific("mobs_mc:villager", mobs_mc
.spawn
.village
, {"air"}, 0, minetest
.LIGHT_MAX
+1, 30, 8000, 4, mobs_mc
.spawn_height
.water
+1, mobs_mc
.spawn_height
.overworld_max
)
1065 mobs
:register_egg("mobs_mc:villager", S("Villager"), "mobs_mc_spawn_icon_villager.png", 0)