Make villager stand still if there is near player
[MineClone/MineClone2.git] / mods / ENTITIES / mobs_mc / villager.lua
blob9ff90f4b015525efb37370f94c59bcf475236c73
1 --MCmobs v0.4
2 --maikerumine
3 --made for MC like Survival game
4 --License for code WTFPL and otherwise stated in readmes
6 --###################
7 --################### VILLAGER
8 --###################
10 -- TODO: Particles
11 -- TODO: 4s Regeneration I after trade unlock
13 -- intllib
14 local MP = minetest.get_modpath(minetest.get_current_modname())
15 local S, NS = dofile(MP.."/intllib.lua")
17 -- playername-indexed table containing the previously used tradenum
18 local player_tradenum = {}
19 -- playername-indexed table containing the objectref of trader, if trading formspec is open
20 local player_trading_with = {}
22 local DEFAULT_WALK_CHANCE = 33 -- chance to walk in percent, if no player nearby
23 local PLAYER_SCAN_INTERVAL = 5 -- every X seconds, villager looks for players nearby
24 local PLAYER_SCAN_RADIUS = 4 -- scan radius for looking for nearby players
26 -- LIST OF VILLAGER PROFESSIONS AND TRADES
28 -- TECHNICAL RESTRICTIONS (FIXME):
29 -- * You can't use a clock as requested item
30 -- * You can't use a compass as requested item if its stack size > 1
31 -- * You can't use a compass in the second requested slot
32 -- This is a problem in the mcl_compass and mcl_clock mods,
33 -- these items should be implemented as single items, then everything
34 -- will be much easier.
36 local COMPASS = "mcl_compass:compass"
37 if minetest.registered_aliases[COMPASS] then
38 COMPASS = minetest.registered_aliases[COMPASS]
39 end
41 local E1 = { "mcl_core:emerald", 1, 1 } -- one emerald
43 -- Special trades for v6 only
44 local TRADE_V6_RED_SANDSTONE, TRADE_V6_DARK_OAK_SAPLING, TRADE_V6_ACACIA_SAPLING, TRADE_V6_BIRCH_SAPLING
45 if minetest.get_mapgen_setting("mg_name") == "v6" then
46 TRADE_V6_RED_SANDSTONE = { E1, { "mcl_core:redsandstone", 12, 16 } }
47 TRADE_V6_DARK_OAK_SAPLING = { { "mcl_core:emerald", 6, 9 }, { "mcl_core:darksapling", 1, 1 } }
48 TRADE_V6_ACACIA_SAPLING = { { "mcl_core:emerald", 14, 17 }, { "mcl_core:acaciasapling", 1, 1 } }
49 TRADE_V6_BIRCH_SAPLING = { { "mcl_core:emerald", 8, 11 }, { "mcl_core:birchsapling", 1, 1 } }
50 end
52 local professions = {
53 farmer = {
54 name = "Farmer",
55 texture = "mobs_mc_villager_farmer.png",
56 trades = {
58 { { "mcl_farming:wheat_item", 18, 22, }, E1 },
59 { { "mcl_farming:potato_item", 15, 19, }, E1 },
60 { { "mcl_farming:carrot_item", 15, 19, }, E1 },
61 { E1, { "mcl_farming:bread", 2, 4 } },
65 { { "mcl_farming:pumpkin_face", 8, 13 }, E1 },
66 { E1, { "mcl_farming:pumpkin_pie", 2, 3} },
70 { { "mcl_farming:melon", 7, 12 }, E1 },
71 { E1, { "mcl_core:apple", 5, 7 }, },
75 { E1, { "mcl_farming:cookie", 6, 10 } },
76 { E1, { "mcl_cake:cake", 1, 1 } },
77 TRADE_V6_BIRCH_SAPLING,
78 TRADE_V6_DARK_OAK_SAPLING,
79 TRADE_V6_ACACIA_SAPLING,
83 fisherman = {
84 name = "Fisherman",
85 texture = "mobs_mc_villager_farmer.png",
86 trades = {
88 { { "mcl_fishing:fish_raw", 6, 6, "mcl_core:emerald", 1, 1 }, { "mcl_fishing:fish_cooked", 6, 6 } },
89 { { "mcl_mobitems:string", 15, 20 }, E1 },
90 { { "mcl_core:coal_lump", 16, 24 }, E1 },
92 -- TODO: enchanted fishing rod
95 fletcher = {
96 name = "Fletcher",
97 texture = "mobs_mc_villager_farmer.png",
98 trades = {
100 { { "mcl_mobitems:string", 15, 20 }, E1 },
101 { E1, { "mcl_bows:arrow", 8, 12 } },
105 { { "mcl_core:gravel", 10, 10, "mcl_core:emerald", 1, 1 }, { "mcl_core:flint", 6, 10 } },
106 { { "mcl_core:emerald", 2, 3 }, { "mcl_bows:bow", 1, 1 } },
110 shepherd ={
111 name = "Shepherd",
112 texture = "mobs_mc_villager_farmer.png",
113 trades = {
115 { { "mcl_wool:white", 16, 22 }, E1 },
116 { { "mcl_core:emerald", 3, 4 }, { "mcl_tools:shears", 1, 1 } },
120 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:white", 1, 1 } },
121 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:grey", 1, 1 } },
122 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:silver", 1, 1 } },
123 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:black", 1, 1 } },
124 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:yellow", 1, 1 } },
125 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:orange", 1, 1 } },
126 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:red", 1, 1 } },
127 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:magenta", 1, 1 } },
128 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:purple", 1, 1 } },
129 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:blue", 1, 1 } },
130 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:cyan", 1, 1 } },
131 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:lime", 1, 1 } },
132 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:green", 1, 1 } },
133 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:pink", 1, 1 } },
134 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:light_blue", 1, 1 } },
135 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:brown", 1, 1 } },
139 librarian = {
140 name = "Librarian",
141 texture = "mobs_mc_villager_librarian.png",
142 trades = {
144 { { "mcl_core:paper", 24, 36 }, E1 },
145 -- TODO: enchanted book
146 { { "mcl_books:book", 8, 10 }, E1 },
147 { { "mcl_core:emerald", 10, 12 }, { "mcl_compass:compass", 1 ,1 }},
148 { { "mcl_core:emerald", 3, 4 }, { "mcl_books:bookshelf", 1 ,1 }},
152 { { "mcl_books:written_book", 2, 2 }, E1 },
153 { { "mcl_core:emerald", 10, 12 }, { "mcl_clock:clock", 1, 1 } },
154 { E1, { "mcl_core:glass", 3, 5 } },
158 { E1, { "mcl_core:glass", 3, 5 } },
161 -- TODO: 2 enchanted book tiers
164 { { "mcl_core:emerald", 20, 22 }, { "mcl_mobs:nametag", 1, 1 } },
168 cartographer = {
169 name = "Cartographer",
170 texture = "mobs_mc_villager_librarian.png",
171 trades = {
173 { { "mcl_core:paper", 24, 36 }, E1 },
177 -- subject to special checks
178 { { "mcl_compass:compass", 1, 1 }, E1 },
182 -- TODO: replace with empty map
183 { { "mcl_core:emerald", 7, 11}, { "mcl_maps:filled_map", 1, 1 } },
186 -- TODO: special maps
189 armorer = {
190 name = "Armorer",
191 texture = "mobs_mc_villager_smith.png",
192 trades = {
194 { { "mcl_core:coal_lump", 16, 24 }, E1 },
195 { { "mcl_core:emerald", 6, 8 }, { "3d_armor:helmet_iron", 1, 1 } },
199 { { "mcl_core:iron_ingot", 7, 9 }, E1 },
200 { { "mcl_core:emerald", 10, 14 }, { "3d_armor:chestplate_iron", 1, 1 } },
204 { { "mcl_core:diamond", 3, 4 }, E1 },
205 -- TODO: enchant
206 { { "mcl_core:emerald", 16, 19 }, { "3d_armor:chestplate_diamond", 1, 1 } },
210 { { "mcl_core:emerald", 5, 7 }, { "3d_armor:boots_chain", 1, 1 } },
211 { { "mcl_core:emerald", 9, 11 }, { "3d_armor:leggings_chain", 1, 1 } },
212 { { "mcl_core:emerald", 5, 7 }, { "3d_armor:helmet_chain", 1, 1 } },
213 { { "mcl_core:emerald", 11, 15 }, { "3d_armor:chestplate_chain", 1, 1 } },
217 leatherworker = {
218 name = "Leatherworker",
219 texture = "mobs_mc_villager_butcher.png",
220 trades = {
222 { { "mcl_mobitems:leather", 9, 12 }, E1 },
223 { { "mcl_core:emerald", 2, 4 }, { "3d_armor:leggings_leather", 2, 4 } },
227 -- TODO: enchant
228 { { "mcl_core:emerald", 7, 12 }, { "3d_armor:chestplate_leather", 1, 1 } },
232 { { "mcl_core:emerald", 8, 10 }, { "mcl_mobitems:saddle", 1, 1 } },
236 butcher = {
237 name = "Butcher",
238 texture = "mobs_mc_villager_butcher.png",
239 trades = {
241 { { "mcl_mobitems:beef", 14, 18 }, E1 },
242 { { "mcl_mobitems:chicken", 14, 18 }, E1 },
246 { { "mcl_core:coal_lump", 16, 24 }, E1 },
247 { E1, { "mcl_mobitems:cooked_beef", 5, 7 } },
248 { E1, { "mcl_mobitems:cooked_chicken", 6, 8 } },
252 weapon_smith = {
253 name = "Weapon Smith",
254 texture = "mobs_mc_villager_smith.png",
255 trades = {
257 { { "mcl_core:coal_lump", 16, 24 }, E1 },
258 { { "mcl_core:emerald", 6, 8 }, { "mcl_tools:axe_iron", 1, 1 } },
262 { { "mcl_core:iron_ingot", 7, 9 }, E1 },
263 -- TODO: enchant
264 { { "mcl_core:emerald", 9, 10 }, { "mcl_tools:sword_iron", 1, 1 } },
268 { { "mcl_core:diamond", 3, 4 }, E1 },
269 -- TODO: enchant
270 { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:sword_diamond", 1, 1 } },
271 -- TODO: enchant
272 { { "mcl_core:emerald", 9, 12 }, { "mcl_tools:axe_diamond", 1, 1 } },
276 tool_smith = {
277 name = "Tool Smith",
278 texture = "mobs_mc_villager_smith.png",
279 trades = {
281 { { "mcl_core:coal_lump", 16, 24 }, E1 },
282 -- TODO: enchant
283 { { "mcl_core:emerald", 5, 7 }, { "mcl_tools:shovel_iron", 1, 1 } },
287 { { "mcl_core:iron_ingot", 7, 9 }, E1 },
288 -- TODO: enchant
289 { { "mcl_core:emerald", 9, 11 }, { "mcl_tools:pick_iron", 1, 1 } },
293 { { "mcl_core:diamond", 3, 4 }, E1 },
294 -- TODO: enchant
295 { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:pick_diamond", 1, 1 } },
299 cleric = {
300 name = "Cleric",
301 texture = "mobs_mc_villager_priest.png",
302 trades = {
304 { { "mcl_mobitems:rotten_flesh", 36, 40 }, E1 },
305 { { "mcl_core:gold_ingot", 8, 10 }, E1 },
309 { E1, { "mesecons:redstone", 1, 4 } },
310 { E1, { "mcl_dye:blue", 1, 2 } },
314 TRADE_V6_RED_SANDSTONE,
315 { E1, { "mcl_nether:glowstone", 1, 3 } },
316 { { "mcl_core:emerald", 4, 7 }, { "mcl_throwing:ender_pearl", 1, 1 } },
319 -- TODO: Bottle 'o enchanting
322 nitwit = {
323 name = "Nitwit",
324 texture = "mobs_mc_villager.png",
325 -- No trades for nitwit
326 trades = nil,
330 local profession_names = {}
331 for id, _ in pairs(professions) do
332 table.insert(profession_names, id)
335 local stand_still = function(self)
336 self.walk_chance = 0
337 self.jump = false
340 local update_max_tradenum = function(self)
341 if not self._trades then
342 return
344 local trades = minetest.deserialize(self._trades)
345 for t=1, #trades do
346 local trade = trades[t]
347 if trade.tier > self._max_trade_tier then
348 self._max_tradenum = t - 1
349 return
352 self._max_tradenum = #trades
355 local init_profession = function(self)
356 if not self._profession then
357 -- Select random profession from all professions with matching clothing
358 local texture = self.base_texture[1]
359 local matches = {}
360 for prof_id, prof in pairs(professions) do
361 if texture == prof.texture then
362 table.insert(matches, prof_id)
365 local p = math.random(1, #matches)
366 self._profession = matches[p]
368 if not self._max_trade_tier then
369 self._max_trade_tier = 1
371 if not self._locked_trades then
372 self._locked_trades = 0
376 local init_trades = function(self, inv)
377 local profession = professions[self._profession]
378 local trade_tiers = profession.trades
379 if trade_tiers == nil then
380 -- Empty trades
381 self._trades = false
382 return
385 local max_tier = #trade_tiers
386 local trades = {}
387 for tiernum=1, max_tier do
388 local tier = trade_tiers[tiernum]
389 for tradenum=1, #tier do
390 local trade = tier[tradenum]
391 local wanted1_item = trade[1][1]
392 local wanted1_count = math.random(trade[1][2], trade[1][3])
393 local offered_item = trade[2][1]
394 local offered_count = math.random(trade[2][2], trade[2][3])
396 local wanted = { wanted1_item .. " " ..wanted1_count }
397 if trade[1][4] then
398 local wanted2_item = trade[1][4]
399 local wanted2_count = math.random(trade[1][5], trade[1][6])
400 table.insert(wanted, wanted2_item .. " " ..wanted2_count)
403 table.insert(trades, {
404 wanted = wanted,
405 offered = offered_item .. " " .. offered_count,
406 tier = tiernum, -- tier of this trade
407 traded_once = false, -- true if trade was traded at least once
408 trade_counter = 0, -- how often the this trade was mate after the last time it got unlocked
409 locked = false, -- if this trade is locked. Locked trades can't be used
413 self._trades = minetest.serialize(trades)
416 local set_trade = function(trader, player, inv, concrete_tradenum)
417 local trades = minetest.deserialize(trader._trades)
418 if not trades then
419 init_trades(trader)
420 trades = minetest.deserialize(trader._trades)
421 if not trades then
422 minetest.log("error", "[mobs_mc] Failed to select villager trade!")
423 return
426 local name = player:get_player_name()
428 -- Stop tradenum from advancing into locked tiers or out-of-range areas
429 if concrete_tradenum > trader._max_tradenum then
430 concrete_tradenum = trader._max_tradenum
431 elseif concrete_tradenum < 1 then
432 concrete_tradenum = 1
434 player_tradenum[name] = concrete_tradenum
435 local trade = trades[concrete_tradenum]
436 inv:set_stack("wanted", 1, ItemStack(trade.wanted[1]))
437 inv:set_stack("offered", 1, ItemStack(trade.offered))
438 if trade.wanted[2] then
439 local wanted2 = ItemStack(trade.wanted[2])
440 inv:set_stack("wanted", 2, wanted2)
441 else
442 inv:set_stack("wanted", 2, "")
447 local function show_trade_formspec(playername, trader, tradenum)
448 if not trader._trades then
449 return
451 if not tradenum then
452 tradenum = 1
454 local trades = minetest.deserialize(trader._trades)
455 local trade = trades[tradenum]
456 local profession = professions[trader._profession].name
457 local disabled_img = ""
458 if trade.locked then
459 disabled_img = "image[4.3,2.52;1,1;mobs_mc_trading_formspec_disabled.png]"..
460 "image[4.3,1.1;1,1;mobs_mc_trading_formspec_disabled.png]"
462 local tradeinv_name = "mobs_mc:trade_"..playername
463 local tradeinv = minetest.formspec_escape("detached:"..tradeinv_name)
465 local b_prev, b_next = "", ""
466 if #trades > 1 then
467 if tradenum > 1 then
468 b_prev = "button[1,1;0.5,1;prev_trade;<]"
470 if tradenum < trader._max_tradenum then
471 b_next = "button[7.26,1;0.5,1;next_trade;>]"
475 local formspec =
476 "size[9,8.75]"
477 .."background[-0.19,-0.25;9.41,9.49;mobs_mc_trading_formspec_bg.png]"
478 ..disabled_img
479 ..mcl_vars.inventory_header
480 .."label[4,0;"..minetest.formspec_escape(profession).."]"
481 .."list[current_player;main;0,4.5;9,3;9]"
482 .."list[current_player;main;0,7.74;9,1;]"
483 ..b_prev..b_next
484 .."list["..tradeinv..";wanted;2,1;2,1;]"
485 .."list["..tradeinv..";offered;5.76,1;1,1;]"
486 .."list["..tradeinv..";input;2,2.5;2,1;]"
487 .."list["..tradeinv..";output;5.76,2.55;1,1;]"
488 .."listring["..tradeinv..";output]"
489 .."listring[current_player;main]"
490 .."listring["..tradeinv..";input]"
491 .."listring[current_player;main]"
492 minetest.sound_play("mobs_mc_villager_trade", {to_player = playername})
493 minetest.show_formspec(playername, tradeinv_name, formspec)
496 local update_offer = function(inv, player, sound)
497 local name = player:get_player_name()
498 local trader = player_trading_with[name]
499 local tradenum = player_tradenum[name]
500 if not trader or not tradenum then
501 return false
503 local trades = minetest.deserialize(trader._trades)
504 if not trades then
505 return false
507 local trade = trades[tradenum]
508 if not trade then
509 return false
511 local wanted1, wanted2 = inv:get_stack("wanted", 1), inv:get_stack("wanted", 2)
512 local input1, input2 = inv:get_stack("input", 1), inv:get_stack("input", 2)
514 -- BEGIN OF SPECIAL HANDLING OF COMPASS
515 -- These 2 functions are a complicated check to check if the input contains a
516 -- special item which we cannot check directly against their name, like
517 -- compass.
518 -- TODO: Remove these check functions when compass and clock are implemented
519 -- as single items.
520 local check_special = function(special_item, group, wanted1, wanted2, input1, input2)
521 if minetest.registered_aliases[special_item] then
522 special_item = minetest.registered_aliases[special_item]
524 if wanted1:get_name() == special_item then
525 local check_input = function(input, wanted, group)
526 return minetest.get_item_group(input:get_name(), group) ~= 0 and input:get_count() >= wanted:get_count()
528 if check_input(input1, wanted1, group) then
529 return true
530 elseif check_input(input2, wanted1, group) then
531 return true
532 else
533 return false
536 return false
538 -- Apply above function to all items which we consider special.
539 -- This function succeeds if ANY item check succeeds.
540 local check_specials = function(wanted1, wanted2, input1, input2)
541 return check_special(COMPASS, "compass", wanted1, wanted2, input1, input2)
543 -- END OF SPECIAL HANDLING OF COMPASS
545 if (
546 ((inv:contains_item("input", wanted1) and
547 (wanted2:is_empty() or inv:contains_item("input", wanted2))) or
548 -- BEGIN OF SPECIAL HANDLING OF COMPASS
549 check_specials(wanted1, wanted2, input1, input2)) and
550 -- END OF SPECIAL HANDLING OF COMPASS
551 (trade.locked == false)) then
552 inv:set_stack("output", 1, inv:get_stack("offered", 1))
553 if sound then
554 minetest.sound_play("mobs_mc_villager_accept", {to_player = name})
556 return true
557 else
558 inv:set_stack("output", 1, ItemStack(""))
559 if sound then
560 minetest.sound_play("mobs_mc_villager_deny", {to_player = name})
562 return false
566 mobs:register_mob("mobs_mc:villager", {
567 type = "npc",
568 hp_min = 20,
569 hp_max = 20,
570 collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.94, 0.3},
571 visual = "mesh",
572 mesh = "mobs_mc_villager.b3d",
573 textures = {
575 "mobs_mc_villager.png",
576 "mobs_mc_villager.png", --hat
579 "mobs_mc_villager_farmer.png",
580 "mobs_mc_villager_farmer.png", --hat
583 "mobs_mc_villager_priest.png",
584 "mobs_mc_villager_priest.png", --hat
587 "mobs_mc_villager_librarian.png",
588 "mobs_mc_villager_librarian.png", --hat
591 "mobs_mc_villager_butcher.png",
592 "mobs_mc_villager_butcher.png", --hat
595 "mobs_mc_villager_smith.png",
596 "mobs_mc_villager_smith.png", --hat
599 visual_size = {x=3, y=3},
600 makes_footstep_sound = true,
601 walk_velocity = 1.2,
602 run_velocity = 2.4,
603 drops = {},
604 sounds = {
605 random = "mobs_mc_villager_noise",
606 death = "mobs_mc_villager_death",
607 damage = "mobs_mc_villager_damage",
608 distance = 16,
610 animation = {
611 stand_speed = 25,
612 stand_start = 40,
613 stand_end = 59,
614 walk_speed = 25,
615 walk_start = 0,
616 walk_end = 40,
617 run_speed = 25,
618 run_start = 0,
619 run_end = 40,
620 die_speed = 15,
621 die_start = 210,
622 die_end = 220,
623 die_loop = false,
625 water_damage = 0,
626 lava_damage = 4,
627 light_damage = 0,
628 view_range = 16,
629 fear_height = 4,
630 jump = true,
631 walk_chance = DEFAULT_WALK_CHANCE,
632 on_rightclick = function(self, clicker)
633 -- Initiate trading
634 local name = clicker:get_player_name()
636 init_profession(self)
637 if self._trades == nil then
638 init_trades(self)
640 update_max_tradenum(self)
641 if self._trades == false then
642 -- Villager has no trades, rightclick is a no-op
643 return
646 player_trading_with[name] = self
648 local inv = minetest.get_inventory({type="detached", name="mobs_mc:trade_"..name})
650 set_trade(self, clicker, inv, 1)
652 show_trade_formspec(name, self)
654 -- Behaviour stuff:
655 -- Make villager look at player and stand still
656 local selfpos = self.object:get_pos()
657 local clickerpos = clicker:get_pos()
658 local dir = vector.direction(selfpos, clickerpos)
659 self.object:set_yaw(minetest.dir_to_yaw(dir))
660 stand_still(self)
661 end,
662 _player_scan_timer = 0,
663 do_custom = function(self, dtime)
664 -- Stand still if player is nearby.
665 if not self._player_scan_timer then
666 self._player_scan_timer = 0
668 self._player_scan_timer = self._player_scan_timer + dtime
669 -- Check infrequently to keep CPU load low
670 if self._player_scan_timer > PLAYER_SCAN_INTERVAL then
671 self._player_scan_timer = 0
672 local selfpos = self.object:get_pos()
673 local objects = minetest.get_objects_inside_radius(selfpos, PLAYER_SCAN_RADIUS)
674 local has_player = false
675 for o, obj in pairs(objects) do
676 if obj:is_player() then
677 has_player = true
678 break
681 if has_player then
682 minetest.log("verbose", "[mobs_mc] Player near villager found!")
683 stand_still(self)
684 else
685 minetest.log("verbose", "[mobs_mc] No player near villager found!")
686 self.walk_chance = DEFAULT_WALK_CHANCE
687 self.jump = true
690 end,
692 on_spawn = function(self)
693 init_profession(self)
694 end,
697 -- Returns a single itemstack in the given inventory to the player's main inventory, or drop it when there's no space left
698 local function return_item(itemstack, dropper, pos, inv_p)
699 if dropper:is_player() then
700 -- Return to main inventory
701 if inv_p:room_for_item("main", itemstack) then
702 inv_p:add_item("main", itemstack)
703 else
704 -- Drop item on the ground
705 local v = dropper:get_look_dir()
706 local p = {x=pos.x, y=pos.y+1.2, z=pos.z}
707 p.x = p.x+(math.random(1,3)*0.2)
708 p.z = p.z+(math.random(1,3)*0.2)
709 local obj = minetest.add_item(p, itemstack)
710 if obj then
711 v.x = v.x*4
712 v.y = v.y*4 + 2
713 v.z = v.z*4
714 obj:setvelocity(v)
715 obj:get_luaentity()._insta_collect = false
718 else
719 -- Fallback for unexpected cases
720 minetest.add_item(pos, itemstack)
722 return itemstack
725 local return_fields = function(player)
726 local name = player:get_player_name()
727 local inv_t = minetest.get_inventory({type="detached", name = "mobs_mc:trade_"..name})
728 local inv_p = player:get_inventory()
729 for i=1, inv_t:get_size("input") do
730 local stack = inv_t:get_stack("input", i)
731 return_item(stack, player, player:get_pos(), inv_p)
732 stack:clear()
733 inv_t:set_stack("input", i, stack)
735 inv_t:set_stack("output", 1, "")
738 minetest.register_on_player_receive_fields(function(player, formname, fields)
739 if string.sub(formname, 1, 14) == "mobs_mc:trade_" then
740 local name = player:get_player_name()
741 if fields.quit then
742 return_fields(player)
743 player_trading_with[name] = nil
744 elseif fields.next_trade or fields.prev_trade then
745 local trader = player_trading_with[name]
746 if not trader or not trader.object:get_luaentity() then
747 return
749 local trades = trader._trades
750 if not trades then
751 return
753 local dir = 1
754 if fields.prev_trade then
755 dir = -1
757 local tradenum = player_tradenum[name] + dir
758 local inv = minetest.get_inventory({type="detached", name="mobs_mc:trade_"..name})
759 set_trade(trader, player, inv, tradenum)
760 update_offer(inv, player, false)
761 show_trade_formspec(name, trader, player_tradenum[name])
764 end)
766 minetest.register_on_leaveplayer(function(player)
767 return_fields(player)
768 player_tradenum[player:get_player_name()] = nil
769 player_trading_with[player:get_player_name()] = nil
770 end)
772 local trade_inventory = {
773 allow_take = function(inv, listname, index, stack, player)
774 if listname == "input" then
775 return stack:get_count()
776 elseif listname == "output" then
777 -- Only allow taking full stack
778 local count = stack:get_count()
779 if count == inv:get_stack(listname, index):get_count() then
780 -- Also update output stack again.
781 -- If input has double the wanted items, the
782 -- output will stay because there will be still
783 -- enough items in input after the trade
784 local wanted1 = inv:get_stack("wanted", 1)
785 local wanted2 = inv:get_stack("wanted", 2)
786 local input1 = inv:get_stack("input", 1)
787 local input2 = inv:get_stack("input", 2)
788 wanted1:set_count(wanted1:get_count()*2)
789 wanted2:set_count(wanted2:get_count()*2)
790 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
791 local special_checks = function(wanted1, input1, input2)
792 if wanted1:get_name() == COMPASS then
793 local compasses = 0
794 if (minetest.get_item_group(input1:get_name(), "compass") ~= 0) then
795 compasses = compasses + input1:get_count()
797 if (minetest.get_item_group(input2:get_name(), "compass") ~= 0) then
798 compasses = compasses + input2:get_count()
800 return compasses >= wanted1:get_count()
802 return false
804 -- END OF SPECIAL HANDLING FOR COMPASS
805 if (inv:contains_item("input", wanted1) and
806 (wanted2:is_empty() or inv:contains_item("input", wanted2)))
807 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
808 or special_checks(wanted1, input1, input2) then
809 -- END OF SPECIAL HANDLING FOR COMPASS
810 return -1
811 else
812 -- If less than double the wanted items,
813 -- remove items from output (final trade,
814 -- input runs empty)
815 return count
817 else
818 return 0
820 else
821 return 0
823 end,
824 allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
825 if from_list == "input" and to_list == "input" then
826 return count
827 elseif from_list == "output" and to_list == "input" then
828 local move_stack = inv:get_stack(from_list, from_index)
829 if inv:get_stack(to_list, to_index):item_fits(move_stack) then
830 return count
833 return 0
834 end,
835 allow_put = function(inv, listname, index, stack, player)
836 if listname == "input" then
837 return stack:get_count()
838 else
839 return 0
841 end,
842 on_put = function(inv, listname, index, stack, player)
843 update_offer(inv, player, true)
844 end,
845 on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
846 if from_list == "output" and to_list == "input" then
847 inv:remove_item("input", inv:get_stack("wanted", 1))
848 local wanted2 = inv:get_stack("wanted", 2)
849 if not wanted2:is_empty() then
850 inv:remove_item("input", inv:get_stack("wanted", 2))
852 minetest.sound_play("mobs_mc_villager_accept", {to_player = player:get_player_name()})
854 update_offer(inv, player, true)
855 end,
856 on_take = function(inv, listname, index, stack, player)
857 local accept
858 local name = player:get_player_name()
859 if listname == "output" then
860 local wanted1 = inv:get_stack("wanted", 1)
861 inv:remove_item("input", wanted1)
862 local wanted2 = inv:get_stack("wanted", 2)
863 if not wanted2:is_empty() then
864 inv:remove_item("input", inv:get_stack("wanted", 2))
866 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
867 if wanted1:get_name() == COMPASS then
868 for n=1, 2 do
869 local input = inv:get_stack("input", n)
870 if minetest.get_item_group(input:get_name(), "compass") ~= 0 then
871 input:set_count(input:get_count() - wanted1:get_count())
872 inv:set_stack("input", n, input)
873 break
877 -- END OF SPECIAL HANDLING FOR COMPASS
878 local trader = player_trading_with[name]
879 local tradenum = player_tradenum[name]
880 local trades
881 if trader and trader._trades then
882 trades = minetest.deserialize(trader._trades)
884 if trades then
885 local trade = trades[tradenum]
886 local unlock_stuff = false
887 if not trade.traded_once then
888 -- Unlock all the things if something was traded
889 -- for the first time ever
890 unlock_stuff = true
891 trade.traded_once = true
892 elseif trade.trade_counter == 0 and math.random(1,5) == 1 then
893 -- Otherwise, 20% chance to unlock if used freshly reset trade
894 unlock_stuff = true
896 local update_formspec = false
897 if unlock_stuff then
898 -- First-time trade unlock all trades and unlock next trade tier
899 if trade.tier + 1 > trader._max_trade_tier then
900 trader._max_trade_tier = trader._max_trade_tier + 1
901 update_max_tradenum(trader)
902 update_formspec = true
904 for t=1, #trades do
905 trades[t].locked = false
906 trades[t].trade_counter = 0
908 trader._locked_trades = 0
909 -- Also heal trader for unlocking stuff
910 -- TODO: Replace by Regeneration I
911 trader.health = math.min(trader.hp_max, trader.health + 4)
913 trade.trade_counter = trade.trade_counter + 1
914 -- Semi-randomly lock trade for repeated trade (not if there's only 1 trade)
915 if trader._max_tradenum > 1 then
916 if trade.trade_counter >= 12 then
917 trade.locked = true
918 elseif trade.trade_counter >= 2 then
919 local r = math.random(1, math.random(4, 10))
920 if r == 1 then
921 trade.locked = true
926 if trade.locked then
927 inv:set_stack("output", 1, "")
928 update_formspec = true
929 trader._locked_trades = trader._locked_trades + 1
930 -- Check if we managed to lock ALL available trades. Rare but possible.
931 if trader._locked_trades >= trader._max_tradenum then
932 -- Emergency unlock! Unlock all other trades except the current one
933 for t=1, #trades do
934 if t ~= tradenum then
935 trades[t].locked = false
936 trades[t].trade_counter = 0
939 trader._locked_trades = 1
940 -- Also heal trader for unlocking stuff
941 -- TODO: Replace by Regeneration I
942 trader.health = math.min(trader.hp_max, trader.health + 4)
945 trader._trades = minetest.serialize(trades)
946 if update_formspec then
947 show_trade_formspec(name, trader, tradenum)
949 else
950 minetest.log("error", "[mobs_mc] Player took item from trader output but player_trading_with or player_tradenum is nil!")
953 accept = true
954 elseif listname == "input" then
955 update_offer(inv, player, false)
957 if accept then
958 minetest.sound_play("mobs_mc_villager_accept", {to_player = name})
959 else
960 minetest.sound_play("mobs_mc_villager_deny", {to_player = name})
962 end,
966 minetest.register_on_joinplayer(function(player)
967 local name = player:get_player_name()
968 player_tradenum[name] = 1
969 player_trading_with[name] = nil
971 -- Create or get player-specific trading inventory
972 local inv = minetest.get_inventory({type="detached", name="mobs_mc:trade_"..name})
973 if not inv then
974 inv = minetest.create_detached_inventory("mobs_mc:trade_"..name, trade_inventory, name)
976 inv:set_size("input", 2)
977 inv:set_size("output", 1)
978 inv:set_size("wanted", 2)
979 inv:set_size("offered", 1)
980 end)
982 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)
984 -- compatibility
985 mobs:alias_mob("mobs:villager", "mobs_mc:villager")
987 -- spawn eggs
988 mobs:register_egg("mobs_mc:villager", S("Villager"), "mobs_mc_spawn_icon_villager.png", 0)
990 if minetest.settings:get_bool("log_mods") then
991 minetest.log("action", "MC mobs loaded")