Add a few v6-exclusive trades to villagers
[MineClone/MineClone2.git] / mods / ENTITIES / mobs_mc / villager.lua
blob0a8458360a69619d916a35357d88315a04031068
1 --MCmobs v0.4
2 --maikerumine
3 --made for MC like Survival game
4 --License for code WTFPL and otherwise stated in readmes
6 -- TODO/FIXME: Per-player trading inventories
7 -- TODO: Particles
8 -- TODO: 4s Regeneration I after trade unlock
9 -- FIXME: Possible to lock all trades
11 -- intllib
12 local MP = minetest.get_modpath(minetest.get_current_modname())
13 local S, NS = dofile(MP.."/intllib.lua")
15 -- playername-indexed table containing the previously used tradenum
16 local player_tradenum = {}
17 -- playername-indexed table containing the objectref of trader, if trading formspec is open
18 local player_trading_with = {}
20 --###################
21 --################### VILLAGER
22 --###################
24 -- LIST OF VILLAGER PROFESSIONS AND TRADES
26 -- TECHNICAL RESTRICTIONS (FIXME):
27 -- * You can't use a clock as requested item
28 -- * You can't use a compass as requested item if its stack size > 1
29 -- * You can't use a compass in the second requested slot
30 -- This is a problem in the mcl_compass and mcl_clock mods,
31 -- these items should be implemented as single items, then everything
32 -- will be much easier.
34 local COMPASS = "mcl_compass:compass"
35 if minetest.registered_aliases[COMPASS] then
36 COMPASS = minetest.registered_aliases[COMPASS]
37 end
39 local E1 = { "mcl_core:emerald", 1, 1 } -- one emerald
41 -- Special trades for v6 only
42 local TRADE_V6_RED_SANDSTONE, TRADE_V6_DARK_OAK_SAPLING, TRADE_V6_ACACIA_SAPLING, TRADE_V6_BIRCH_SAPLING
43 if minetest.get_mapgen_setting("mg_name") == "v6" then
44 TRADE_V6_RED_SANDSTONE = { E1, { "mcl_core:redsandstone", 12, 16 } }
45 TRADE_V6_DARK_OAK_SAPLING = { { "mcl_core:emerald", 6, 9 }, { "mcl_core:darksapling", 1, 1 } }
46 TRADE_V6_ACACIA_SAPLING = { { "mcl_core:emerald", 14, 17 }, { "mcl_core:acaciasapling", 1, 1 } }
47 TRADE_V6_BIRCH_SAPLING = { { "mcl_core:emerald", 8, 11 }, { "mcl_core:birchsapling", 1, 1 } }
48 end
50 local professions = {
51 farmer = {
52 name = "Farmer",
53 texture = "mobs_mc_villager_farmer.png",
54 trades = {
56 { { "mcl_farming:wheat_item", 18, 22, }, E1 },
57 { { "mcl_farming:potato_item", 15, 19, }, E1 },
58 { { "mcl_farming:carrot_item", 15, 19, }, E1 },
59 { E1, { "mcl_farming:bread", 2, 4 } },
63 { { "mcl_farming:pumpkin_face", 8, 13 }, E1 },
64 { E1, { "mcl_farming:pumpkin_pie", 2, 3} },
68 { { "mcl_farming:melon", 7, 12 }, E1 },
69 { E1, { "mcl_core:apple", 5, 7 }, },
73 { E1, { "mcl_farming:cookie", 6, 10 } },
74 { E1, { "mcl_cake:cake", 1, 1 } },
75 TRADE_V6_BIRCH_SAPLING,
76 TRADE_V6_DARK_OAK_SAPLING,
77 TRADE_V6_ACACIA_SAPLING,
81 fisherman = {
82 name = "Fisherman",
83 texture = "mobs_mc_villager_farmer.png",
84 trades = {
86 { { "mcl_fishing:fish_raw", 6, 6, "mcl_core:emerald", 1, 1 }, { "mcl_fishing:fish_cooked", 6, 6 } },
87 { { "mcl_mobitems:string", 15, 20 }, E1 },
88 { { "mcl_core:coal_lump", 16, 24 }, E1 },
90 -- TODO: enchanted fishing rod
93 fletcher = {
94 name = "Fletcher",
95 texture = "mobs_mc_villager_farmer.png",
96 trades = {
98 { { "mcl_mobitems:string", 15, 20 }, E1 },
99 { E1, { "mcl_bows:arrow", 8, 12 } },
103 { { "mcl_core:gravel", 10, 10, "mcl_core:emerald", 1, 1 }, { "mcl_core:flint", 6, 10 } },
104 { { "mcl_core:emerald", 2, 3 }, { "mcl_bows:bow", 1, 1 } },
108 shepherd ={
109 name = "Shepherd",
110 texture = "mobs_mc_villager_farmer.png",
111 trades = {
113 { { "mcl_wool:white", 16, 22 }, E1 },
114 { { "mcl_core:emerald", 3, 4 }, { "mcl_tools:shears", 1, 1 } },
118 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:white", 1, 1 } },
119 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:grey", 1, 1 } },
120 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:silver", 1, 1 } },
121 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:black", 1, 1 } },
122 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:yellow", 1, 1 } },
123 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:orange", 1, 1 } },
124 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:red", 1, 1 } },
125 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:magenta", 1, 1 } },
126 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:purple", 1, 1 } },
127 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:blue", 1, 1 } },
128 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:cyan", 1, 1 } },
129 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:lime", 1, 1 } },
130 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:green", 1, 1 } },
131 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:pink", 1, 1 } },
132 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:light_blue", 1, 1 } },
133 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:brown", 1, 1 } },
137 librarian = {
138 name = "Librarian",
139 texture = "mobs_mc_villager_librarian.png",
140 trades = {
142 { { "mcl_core:paper", 24, 36 }, E1 },
143 -- TODO: enchanted book
144 { { "mcl_books:book", 8, 10 }, E1 },
145 { { "mcl_core:emerald", 10, 12 }, { "mcl_compass:compass", 1 ,1 }},
146 { { "mcl_core:emerald", 3, 4 }, { "mcl_books:bookshelf", 1 ,1 }},
150 { { "mcl_books:written_book", 2, 2 }, E1 },
151 { { "mcl_core:emerald", 10, 12 }, { "mcl_clock:clock", 1, 1 } },
152 { E1, { "mcl_core:glass", 3, 5 } },
156 { E1, { "mcl_core:glass", 3, 5 } },
159 -- TODO: 2 enchanted book tiers
162 { { "mcl_core:emerald", 20, 22 }, { "mcl_mobs:nametag", 1, 1 } },
166 cartographer = {
167 name = "Cartographer",
168 texture = "mobs_mc_villager_librarian.png",
169 trades = {
171 { { "mcl_core:paper", 24, 36 }, E1 },
175 -- subject to special checks
176 { { "mcl_compass:compass", 1, 1 }, E1 },
180 -- TODO: replace with empty map
181 { { "mcl_core:emerald", 7, 11}, { "mcl_maps:filled_map", 1, 1 } },
184 -- TODO: special maps
187 armorer = {
188 name = "Armorer",
189 texture = "mobs_mc_villager_smith.png",
190 trades = {
192 { { "mcl_core:coal_lump", 16, 24 }, E1 },
193 { { "mcl_core:emerald", 6, 8 }, { "3d_armor:helmet_iron", 1, 1 } },
197 { { "mcl_core:iron_ingot", 7, 9 }, E1 },
198 { { "mcl_core:emerald", 10, 14 }, { "3d_armor:chestplate_iron", 1, 1 } },
202 { { "mcl_core:diamond", 3, 4 }, E1 },
203 -- TODO: enchant
204 { { "mcl_core:emerald", 16, 19 }, { "3d_armor:chestplate_diamond", 1, 1 } },
208 { { "mcl_core:emerald", 5, 7 }, { "3d_armor:boots_chain", 1, 1 } },
209 { { "mcl_core:emerald", 9, 11 }, { "3d_armor:leggings_chain", 1, 1 } },
210 { { "mcl_core:emerald", 5, 7 }, { "3d_armor:helmet_chain", 1, 1 } },
211 { { "mcl_core:emerald", 11, 15 }, { "3d_armor:chestplate_chain", 1, 1 } },
215 leatherworker = {
216 name = "Leatherworker",
217 texture = "mobs_mc_villager_butcher.png",
218 trades = {
220 { { "mcl_mobitems:leather", 9, 12 }, E1 },
221 { { "mcl_core:emerald", 2, 4 }, { "3d_armor:leggings_leather", 2, 4 } },
225 -- TODO: enchant
226 { { "mcl_core:emerald", 7, 12 }, { "3d_armor:chestplate_leather", 1, 1 } },
230 { { "mcl_core:emerald", 8, 10 }, { "mcl_mobitems:saddle", 1, 1 } },
234 butcher = {
235 name = "Butcher",
236 texture = "mobs_mc_villager_butcher.png",
237 trades = {
239 { { "mcl_mobitems:beef", 14, 18 }, E1 },
240 { { "mcl_mobitems:chicken", 14, 18 }, E1 },
244 { { "mcl_core:coal_lump", 16, 24 }, E1 },
245 { E1, { "mcl_mobitems:cooked_beef", 5, 7 } },
246 { E1, { "mcl_mobitems:cooked_chicken", 6, 8 } },
250 weapon_smith = {
251 name = "Weapon Smith",
252 texture = "mobs_mc_villager_smith.png",
253 trades = {
255 { { "mcl_core:coal_lump", 16, 24 }, E1 },
256 { { "mcl_core:emerald", 6, 8 }, { "mcl_tools:axe_iron", 1, 1 } },
260 { { "mcl_core:iron_ingot", 7, 9 }, E1 },
261 -- TODO: enchant
262 { { "mcl_core:emerald", 9, 10 }, { "mcl_tools:sword_iron", 1, 1 } },
266 { { "mcl_core:diamond", 3, 4 }, E1 },
267 -- TODO: enchant
268 { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:sword_diamond", 1, 1 } },
269 -- TODO: enchant
270 { { "mcl_core:emerald", 9, 12 }, { "mcl_tools:axe_diamond", 1, 1 } },
274 tool_smith = {
275 name = "Tool Smith",
276 texture = "mobs_mc_villager_smith.png",
277 trades = {
279 { { "mcl_core:coal_lump", 16, 24 }, E1 },
280 -- TODO: enchant
281 { { "mcl_core:emerald", 5, 7 }, { "mcl_tools:shovel_iron", 1, 1 } },
285 { { "mcl_core:iron_ingot", 7, 9 }, E1 },
286 -- TODO: enchant
287 { { "mcl_core:emerald", 9, 11 }, { "mcl_tools:pick_iron", 1, 1 } },
291 { { "mcl_core:diamond", 3, 4 }, E1 },
292 -- TODO: enchant
293 { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:pick_diamond", 1, 1 } },
297 cleric = {
298 name = "Cleric",
299 texture = "mobs_mc_villager_priest.png",
300 trades = {
302 { { "mcl_mobitems:rotten_flesh", 36, 40 }, E1 },
303 { { "mcl_core:gold_ingot", 8, 10 }, E1 },
307 { E1, { "mesecons:redstone", 1, 4 } },
308 { E1, { "mcl_dye:blue", 1, 2 } },
312 { E1, { "mcl_nether:glowstone", 1, 3 } },
313 TRADE_V6_RED_SANDSTONE,
314 { { "mcl_core:emerald", 4, 7 }, { "mcl_throwing:ender_pearl", 1, 1 } },
317 -- TODO: Bottle 'o enchanting
320 nitwit = {
321 name = "Nitwit",
322 texture = "mobs_mc_villager.png",
323 -- No trades for nitwit
324 trades = nil,
328 local profession_names = {}
329 for id, _ in pairs(professions) do
330 table.insert(profession_names, id)
333 local init_profession = function(self)
334 if not self._profession then
335 -- Select random profession from all professions with matching clothing
336 local texture = self.base_texture[1]
337 local matches = {}
338 for prof_id, prof in pairs(professions) do
339 if texture == prof.texture then
340 table.insert(matches, prof_id)
343 local p = math.random(1, #matches)
344 self._profession = matches[p]
346 if not self._max_trade_tier then
347 self._max_trade_tier = 1
351 local init_trades = function(self, inv)
352 local profession = professions[self._profession]
353 local trade_tiers = profession.trades
354 if trade_tiers == nil then
355 -- Empty trades
356 self._trades = false
357 return
360 local max_tier = #trade_tiers
361 local trades = {}
362 for tiernum=1, max_tier do
363 local tier = trade_tiers[tiernum]
364 for tradenum=1, #tier do
365 local trade = tier[tradenum]
366 local wanted1_item = trade[1][1]
367 local wanted1_count = math.random(trade[1][2], trade[1][3])
368 local offered_item = trade[2][1]
369 local offered_count = math.random(trade[2][2], trade[2][3])
371 local wanted = { wanted1_item .. " " ..wanted1_count }
372 if trade[1][4] then
373 local wanted2_item = trade[1][4]
374 local wanted2_count = math.random(trade[1][5], trade[1][6])
375 table.insert(wanted, wanted2_item .. " " ..wanted2_count)
378 table.insert(trades, {
379 wanted = wanted,
380 offered = offered_item .. " " .. offered_count,
381 tier = tiernum, -- tier of this trade
382 traded_once = false, -- true if trade was traded at least once
383 trade_counter = 0, -- how often the this trade was mate after the last time it got unlocked
384 locked = false, -- if this trade is locked. Locked trades can't be used
388 self._trades = minetest.serialize(trades)
391 local set_trade = function(trader, player, inv, concrete_tradenum)
392 local trades = minetest.deserialize(trader._trades)
393 if not trades then
394 init_trades(trader)
395 trades = minetest.deserialize(trader._trades)
396 if not trades then
397 minetest.log("error", "[mobs_mc] Failed to select villager trade!")
398 return
402 if concrete_tradenum > #trades then
403 concrete_tradenum = 1
404 player_tradenum[player:get_player_name()] = concrete_tradenum
405 elseif concrete_tradenum < 1 then
406 concrete_tradenum = #trades
407 player_tradenum[player:get_player_name()] = concrete_tradenum
409 local trade = trades[concrete_tradenum]
410 if trader._max_trade_tier < trade.tier then
411 concrete_tradenum = 1
412 player_tradenum[player:get_player_name()] = concrete_tradenum
413 trade = trades[concrete_tradenum]
415 inv:set_stack("wanted", 1, ItemStack(trade.wanted[1]))
416 inv:set_stack("offered", 1, ItemStack(trade.offered))
417 if trade.wanted[2] then
418 local wanted2 = ItemStack(trade.wanted[2])
419 inv:set_stack("wanted", 2, wanted2)
420 else
421 inv:set_stack("wanted", 2, "")
426 local function show_trade_formspec(playername, trader, tradenum)
427 if not trader._trades then
428 return
430 if not tradenum then
431 tradenum = 1
433 local trades = minetest.deserialize(trader._trades)
434 local trade = trades[tradenum]
435 local profession = professions[trader._profession].name
436 local disabled_img = ""
437 if trade.locked then
438 disabled_img = "image[4.3,2.52;1,1;mobs_mc_trading_formspec_disabled.png]"..
439 "image[4.3,1.1;1,1;mobs_mc_trading_formspec_disabled.png]"
441 local formspec =
442 "size[9,8.75]"
443 .."background[-0.19,-0.25;9.41,9.49;mobs_mc_trading_formspec_bg.png]"
444 ..disabled_img
445 ..mcl_vars.inventory_header
446 .."label[4,0;"..minetest.formspec_escape(profession).."]"
447 .."list[current_player;main;0,4.5;9,3;9]"
448 .."list[current_player;main;0,7.74;9,1;]"
449 .."button[1,1;0.5,1;prev_trade;<]"
450 .."button[7.26,1;0.5,1;next_trade;>]"
451 .."list[detached:mobs_mc:trade;wanted;2,1;2,1;]"
452 .."list[detached:mobs_mc:trade;offered;5.76,1;1,1;]"
453 .."list[detached:mobs_mc:trade;input;2,2.5;2,1;]"
454 .."list[detached:mobs_mc:trade;output;5.76,2.55;1,1;]"
455 .."listring[detached:mobs_mc:trade;output]"
456 .."listring[current_player;main]"
457 .."listring[detached:mobs_mc:trade;input]"
458 .."listring[current_player;main]"
459 minetest.sound_play("mobs_mc_villager_trade", {to_player = playername})
460 minetest.show_formspec(playername, "mobs_mc:trade", formspec)
463 local update_offer = function(inv, player, sound)
464 local name = player:get_player_name()
465 local trader = player_trading_with[name]
466 local tradenum = player_tradenum[name]
467 if not trader or not tradenum then
468 return false
470 local trades = minetest.deserialize(trader._trades)
471 if not trades then
472 return false
474 local trade = trades[tradenum]
475 if not trade then
476 return false
478 local wanted1, wanted2 = inv:get_stack("wanted", 1), inv:get_stack("wanted", 2)
479 local input1, input2 = inv:get_stack("input", 1), inv:get_stack("input", 2)
481 -- BEGIN OF SPECIAL HANDLING OF COMPASS
482 -- These 2 functions are a complicated check to check if the input contains a
483 -- special item which we cannot check directly against their name, like
484 -- compass.
485 -- TODO: Remove these check functions when compass and clock are implemented
486 -- as single items.
487 local check_special = function(special_item, group, wanted1, wanted2, input1, input2)
488 if minetest.registered_aliases[special_item] then
489 special_item = minetest.registered_aliases[special_item]
491 if wanted1:get_name() == special_item then
492 local check_input = function(input, wanted, group)
493 return minetest.get_item_group(input:get_name(), group) ~= 0 and input:get_count() >= wanted:get_count()
495 if check_input(input1, wanted1, group) then
496 return true
497 elseif check_input(input2, wanted1, group) then
498 return true
499 else
500 return false
503 return false
505 -- Apply above function to all items which we consider special.
506 -- This function succeeds if ANY item check succeeds.
507 local check_specials = function(wanted1, wanted2, input1, input2)
508 return check_special(COMPASS, "compass", wanted1, wanted2, input1, input2)
510 -- END OF SPECIAL HANDLING OF COMPASS
512 if (
513 ((inv:contains_item("input", wanted1) and
514 (wanted2:is_empty() or inv:contains_item("input", wanted2))) or
515 -- BEGIN OF SPECIAL HANDLING OF COMPASS
516 check_specials(wanted1, wanted2, input1, input2)) and
517 -- END OF SPECIAL HANDLING OF COMPASS
518 (trade.locked == false)) then
519 inv:set_stack("output", 1, inv:get_stack("offered", 1))
520 if sound then
521 minetest.sound_play("mobs_mc_villager_accept", {to_player = name})
523 return true
524 else
525 inv:set_stack("output", 1, ItemStack(""))
526 if sound then
527 minetest.sound_play("mobs_mc_villager_deny", {to_player = name})
529 return false
533 mobs:register_mob("mobs_mc:villager", {
534 type = "npc",
535 hp_min = 20,
536 hp_max = 20,
537 collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.94, 0.3},
538 visual = "mesh",
539 mesh = "mobs_mc_villager.b3d",
540 textures = {
542 "mobs_mc_villager.png",
543 "mobs_mc_villager.png", --hat
546 "mobs_mc_villager_farmer.png",
547 "mobs_mc_villager_farmer.png", --hat
550 "mobs_mc_villager_priest.png",
551 "mobs_mc_villager_priest.png", --hat
554 "mobs_mc_villager_librarian.png",
555 "mobs_mc_villager_librarian.png", --hat
558 "mobs_mc_villager_butcher.png",
559 "mobs_mc_villager_butcher.png", --hat
562 "mobs_mc_villager_smith.png",
563 "mobs_mc_villager_smith.png", --hat
566 visual_size = {x=3, y=3},
567 makes_footstep_sound = true,
568 walk_velocity = 1.2,
569 run_velocity = 2.4,
570 drops = {},
571 sounds = {
572 random = "mobs_mc_villager_noise",
573 death = "mobs_mc_villager_death",
574 damage = "mobs_mc_villager_damage",
575 distance = 16,
577 animation = {
578 stand_speed = 25,
579 stand_start = 40,
580 stand_end = 59,
581 walk_speed = 25,
582 walk_start = 0,
583 walk_end = 40,
584 run_speed = 25,
585 run_start = 0,
586 run_end = 40,
587 die_speed = 15,
588 die_start = 210,
589 die_end = 220,
590 die_loop = false,
592 water_damage = 0,
593 lava_damage = 4,
594 light_damage = 0,
595 view_range = 16,
596 fear_height = 4,
597 on_rightclick = function(self, clicker)
598 local name = clicker:get_player_name()
600 init_profession(self)
601 if self._trades == nil then
602 init_trades(self)
604 if self._trades == false then
605 -- Villager has no trades, rightclick is a no-op
606 return
609 player_trading_with[name] = self
611 -- TODO: Create per-player trading inventories
612 local inv = minetest.get_inventory({type="detached", name="mobs_mc:trade"})
613 if not inv then
614 inv = minetest.create_detached_inventory("mobs_mc:trade", {
615 allow_take = function(inv, listname, index, stack, player)
616 if listname == "input" then
617 return stack:get_count()
618 elseif listname == "output" then
619 -- Only allow taking full stack
620 local count = stack:get_count()
621 if count == inv:get_stack(listname, index):get_count() then
622 -- Also update output stack again.
623 -- If input has double the wanted items, the
624 -- output will stay because there will be still
625 -- enough items in input after the trade
626 local wanted1 = inv:get_stack("wanted", 1)
627 local wanted2 = inv:get_stack("wanted", 2)
628 local input1 = inv:get_stack("input", 1)
629 local input2 = inv:get_stack("input", 2)
630 wanted1:set_count(wanted1:get_count()*2)
631 wanted2:set_count(wanted2:get_count()*2)
632 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
633 local special_checks = function(wanted1, input1, input2)
634 if wanted1:get_name() == COMPASS then
635 local compasses = 0
636 if (minetest.get_item_group(input1:get_name(), "compass") ~= 0) then
637 compasses = compasses + input1:get_count()
639 if (minetest.get_item_group(input2:get_name(), "compass") ~= 0) then
640 compasses = compasses + input2:get_count()
642 return compasses >= wanted1:get_count()
644 return false
646 -- END OF SPECIAL HANDLING FOR COMPASS
647 if (inv:contains_item("input", wanted1) and
648 (wanted2:is_empty() or inv:contains_item("input", wanted2)))
649 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
650 or special_checks(wanted1, input1, input2) then
651 -- END OF SPECIAL HANDLING FOR COMPASS
652 return -1
653 else
654 -- If less than double the wanted items,
655 -- remove items from output (final trade,
656 -- input runs empty)
657 return count
659 else
660 return 0
662 else
663 return 0
665 end,
666 allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
667 if from_list == "input" and to_list == "input" then
668 return count
669 elseif from_list == "output" and to_list == "input" then
670 local move_stack = inv:get_stack(from_list, from_index)
671 if inv:get_stack(to_list, to_index):item_fits(move_stack) then
672 return count
675 return 0
676 end,
677 allow_put = function(inv, listname, index, stack, player)
678 if listname == "input" then
679 return stack:get_count()
680 else
681 return 0
683 end,
684 on_put = function(inv, listname, index, stack, player)
685 update_offer(inv, player, true)
686 end,
687 on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
688 if from_list == "output" and to_list == "input" then
689 inv:remove_item("input", inv:get_stack("wanted", 1))
690 local wanted2 = inv:get_stack("wanted", 2)
691 if not wanted2:is_empty() then
692 inv:remove_item("input", inv:get_stack("wanted", 2))
694 minetest.sound_play("mobs_mc_villager_accept", {to_player = player:get_player_name()})
696 update_offer(inv, player, true)
697 end,
698 on_take = function(inv, listname, index, stack, player)
699 local accept
700 local name = player:get_player_name()
701 if listname == "output" then
702 local wanted1 = inv:get_stack("wanted", 1)
703 inv:remove_item("input", wanted1)
704 local wanted2 = inv:get_stack("wanted", 2)
705 if not wanted2:is_empty() then
706 inv:remove_item("input", inv:get_stack("wanted", 2))
708 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
709 if wanted1:get_name() == COMPASS then
710 for n=1, 2 do
711 local input = inv:get_stack("input", n)
712 if minetest.get_item_group(input:get_name(), "compass") ~= 0 then
713 input:set_count(input:get_count() - wanted1:get_count())
714 inv:set_stack("input", n, input)
715 break
719 -- END OF SPECIAL HANDLING FOR COMPASS
720 local trader = player_trading_with[name]
721 local tradenum = player_tradenum[name]
722 local trades
723 if trader and trader._trades then
724 trades = minetest.deserialize(trader._trades)
726 if trades then
727 local trade = trades[tradenum]
728 local unlock_stuff = false
729 if not trade.traded_once then
730 -- Unlock all the things if something was traded
731 -- for the first time ever
732 unlock_stuff = true
733 trade.traded_once = true
734 elseif trade.trade_counter == 0 and math.random(1,5) == 1 then
735 -- Otherwise, 20% chance to unlock if used freshly reset trade
736 unlock_stuff = true
738 if unlock_stuff then
739 -- First-time trade unlock all trades and unlock next trade tier
740 if trade.tier + 1 > trader._max_trade_tier then
741 trader._max_trade_tier = trader._max_trade_tier + 1
743 for t=1, #trades do
744 trades[t].locked = false
745 trades[t].trade_counter = 0
748 trade.trade_counter = trade.trade_counter + 1
749 if trade.trade_counter >= 12 then
750 trade.locked = true
751 elseif trade.trade_counter >= 2 then
752 local r = math.random(1, math.random(4, 10))
753 if r == 1 then
754 trade.locked = true
758 trader._trades = minetest.serialize(trades)
759 if trade.locked then
760 inv:set_stack("output", 1, "")
761 show_trade_formspec(name, trader, tradenum)
763 else
764 minetest.log("error", "[mobs_mc] Player took item from trader output but player_trading_with or player_tradenum is nil!")
767 accept = true
768 elseif listname == "input" then
769 update_offer(inv, player, false)
771 if accept then
772 minetest.sound_play("mobs_mc_villager_accept", {to_player = name})
773 else
774 minetest.sound_play("mobs_mc_villager_deny", {to_player = name})
776 end,
779 inv:set_size("input", 2)
780 inv:set_size("output", 1)
781 inv:set_size("wanted", 2)
782 inv:set_size("offered", 1)
784 player_tradenum[name] = 1
785 set_trade(self, clicker, inv, player_tradenum[name], player_tradenum[name])
787 show_trade_formspec(name, self)
788 end,
790 on_spawn = function(self)
791 init_profession(self)
792 end,
795 -- Returns a single itemstack in the given inventory to the player's main inventory, or drop it when there's no space left
796 local function return_item(itemstack, dropper, pos, inv_p)
797 if dropper:is_player() then
798 -- Return to main inventory
799 if inv_p:room_for_item("main", itemstack) then
800 inv_p:add_item("main", itemstack)
801 else
802 -- Drop item on the ground
803 local v = dropper:get_look_dir()
804 local p = {x=pos.x, y=pos.y+1.2, z=pos.z}
805 p.x = p.x+(math.random(1,3)*0.2)
806 p.z = p.z+(math.random(1,3)*0.2)
807 local obj = minetest.add_item(p, itemstack)
808 if obj then
809 v.x = v.x*4
810 v.y = v.y*4 + 2
811 v.z = v.z*4
812 obj:setvelocity(v)
813 obj:get_luaentity()._insta_collect = false
816 else
817 -- Fallback for unexpected cases
818 minetest.add_item(pos, itemstack)
820 return itemstack
823 local return_fields = function(player)
824 local inv_t = minetest.get_inventory({type="detached", name = "mobs_mc:trade"})
825 local inv_p = player:get_inventory()
826 for i=1, inv_t:get_size("input") do
827 local stack = inv_t:get_stack("input", i)
828 return_item(stack, player, player:get_pos(), inv_p)
829 stack:clear()
830 inv_t:set_stack("input", i, stack)
832 inv_t:set_stack("output", 1, "")
835 minetest.register_on_player_receive_fields(function(player, formname, fields)
836 if formname == "mobs_mc:trade" then
837 local name = player:get_player_name()
838 if fields.quit then
839 return_fields(player)
840 player_trading_with[name] = nil
841 elseif fields.next_trade then
842 local trader = player_trading_with[name]
843 if not trader or not trader.object:get_luaentity() then
844 return
846 local trades = trader._trades
847 if not trades then
848 return
850 player_tradenum[name] = player_tradenum[name] + 1
851 local inv = minetest.get_inventory({type="detached", name="mobs_mc:trade"})
852 set_trade(trader, player, inv, player_tradenum[name])
853 update_offer(inv, player, false)
854 show_trade_formspec(name, trader, player_tradenum[name])
855 elseif fields.prev_trade then
856 local trader = player_trading_with[name]
857 if not trader or not trader.object:get_luaentity() then
858 return
860 local trades = trader._trades
861 if not trades then
862 return
864 player_tradenum[name] = player_tradenum[name] - 1
865 local inv = minetest.get_inventory({type="detached", name="mobs_mc:trade"})
866 set_trade(trader, player, inv, player_tradenum[name])
867 update_offer(inv, player, false)
868 show_trade_formspec(name, trader, player_tradenum[name])
871 end)
873 minetest.register_on_leaveplayer(function(player)
874 return_fields(player)
875 player_tradenum[player:get_player_name()] = nil
876 player_trading_with[player:get_player_name()] = nil
877 end)
879 minetest.register_on_joinplayer(function(player)
880 player_tradenum[player:get_player_name()] = 1
881 player_trading_with[player:get_player_name()] = nil
882 end)
884 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)
886 -- compatibility
887 mobs:alias_mob("mobs:villager", "mobs_mc:villager")
889 -- spawn eggs
890 mobs:register_egg("mobs_mc:villager", S("Villager"), "mobs_mc_spawn_icon_villager.png", 0)
892 if minetest.settings:get_bool("log_mods") then
893 minetest.log("action", "MC mobs loaded")