3 --made for MC like Survival game
4 --License for code WTFPL and otherwise stated in readmes
6 -- TODO/FIXME: Per-player trading inventories
8 -- TODO: 4s Regeneration I after trade unlock
9 -- FIXME: Possible to lock all trades
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
= {}
21 --################### VILLAGER
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
]
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 } }
53 texture
= "mobs_mc_villager_farmer.png",
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
,
83 texture
= "mobs_mc_villager_farmer.png",
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
95 texture
= "mobs_mc_villager_farmer.png",
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 } },
110 texture
= "mobs_mc_villager_farmer.png",
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 } },
139 texture
= "mobs_mc_villager_librarian.png",
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 } },
167 name
= "Cartographer",
168 texture
= "mobs_mc_villager_librarian.png",
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
189 texture
= "mobs_mc_villager_smith.png",
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
},
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 } },
216 name
= "Leatherworker",
217 texture
= "mobs_mc_villager_butcher.png",
220 { { "mcl_mobitems:leather", 9, 12 }, E1
},
221 { { "mcl_core:emerald", 2, 4 }, { "3d_armor:leggings_leather", 2, 4 } },
226 { { "mcl_core:emerald", 7, 12 }, { "3d_armor:chestplate_leather", 1, 1 } },
230 { { "mcl_core:emerald", 8, 10 }, { "mcl_mobitems:saddle", 1, 1 } },
236 texture
= "mobs_mc_villager_butcher.png",
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 } },
251 name
= "Weapon Smith",
252 texture
= "mobs_mc_villager_smith.png",
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
},
262 { { "mcl_core:emerald", 9, 10 }, { "mcl_tools:sword_iron", 1, 1 } },
266 { { "mcl_core:diamond", 3, 4 }, E1
},
268 { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:sword_diamond", 1, 1 } },
270 { { "mcl_core:emerald", 9, 12 }, { "mcl_tools:axe_diamond", 1, 1 } },
276 texture
= "mobs_mc_villager_smith.png",
279 { { "mcl_core:coal_lump", 16, 24 }, E1
},
281 { { "mcl_core:emerald", 5, 7 }, { "mcl_tools:shovel_iron", 1, 1 } },
285 { { "mcl_core:iron_ingot", 7, 9 }, E1
},
287 { { "mcl_core:emerald", 9, 11 }, { "mcl_tools:pick_iron", 1, 1 } },
291 { { "mcl_core:diamond", 3, 4 }, E1
},
293 { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:pick_diamond", 1, 1 } },
299 texture
= "mobs_mc_villager_priest.png",
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
322 texture
= "mobs_mc_villager.png",
323 -- No trades for nitwit
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]
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
360 local max_tier
= #trade_tiers
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
}
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
, {
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
)
395 trades
= minetest
.deserialize(trader
._trades
)
397 minetest
.log("error", "[mobs_mc] Failed to select villager trade!")
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
)
421 inv
:set_stack("wanted", 2, "")
426 local function show_trade_formspec(playername
, trader
, tradenum
)
427 if not trader
._trades
then
433 local trades
= minetest
.deserialize(trader
._trades
)
434 local trade
= trades
[tradenum
]
435 local profession
= professions
[trader
._profession
].name
436 local disabled_img
= ""
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]"
443 .."background[-0.19,-0.25;9.41,9.49;mobs_mc_trading_formspec_bg.png]"
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
470 local trades
= minetest
.deserialize(trader
._trades
)
474 local trade
= trades
[tradenum
]
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
485 -- TODO: Remove these check functions when compass and clock are implemented
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
497 elseif check_input(input2
, wanted1
, group
) then
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
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))
521 minetest
.sound_play("mobs_mc_villager_accept", {to_player
= name
})
525 inv
:set_stack("output", 1, ItemStack(""))
527 minetest
.sound_play("mobs_mc_villager_deny", {to_player
= name
})
533 mobs
:register_mob("mobs_mc:villager", {
537 collisionbox
= {-0.3, -0.01, -0.3, 0.3, 1.94, 0.3},
539 mesh
= "mobs_mc_villager.b3d",
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,
572 random = "mobs_mc_villager_noise",
573 death
= "mobs_mc_villager_death",
574 damage
= "mobs_mc_villager_damage",
597 on_rightclick
= function(self
, clicker
)
598 local name
= clicker
:get_player_name()
600 init_profession(self
)
601 if self
._trades
== nil then
604 if self
._trades
== false then
605 -- Villager has no trades, rightclick is a no-op
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"})
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
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()
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
654 -- If less than double the wanted items,
655 -- remove items from output (final trade,
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
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
677 allow_put
= function(inv
, listname
, index
, stack
, player
)
678 if listname
== "input" then
679 return stack
:get_count()
684 on_put
= function(inv
, listname
, index
, stack
, player
)
685 update_offer(inv
, player
, true)
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)
698 on_take
= function(inv
, listname
, index
, stack
, player
)
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
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
)
719 -- END OF SPECIAL HANDLING FOR COMPASS
720 local trader
= player_trading_with
[name
]
721 local tradenum
= player_tradenum
[name
]
723 if trader
and trader
._trades
then
724 trades
= minetest
.deserialize(trader
._trades
)
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
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
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
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
751 elseif trade
.trade_counter
>= 2 then
752 local r
= math
.random(1, math
.random(4, 10))
758 trader
._trades
= minetest
.serialize(trades
)
760 inv
:set_stack("output", 1, "")
761 show_trade_formspec(name
, trader
, tradenum
)
764 minetest
.log("error", "[mobs_mc] Player took item from trader output but player_trading_with or player_tradenum is nil!")
768 elseif listname
== "input" then
769 update_offer(inv
, player
, false)
772 minetest
.sound_play("mobs_mc_villager_accept", {to_player
= name
})
774 minetest
.sound_play("mobs_mc_villager_deny", {to_player
= name
})
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
)
790 on_spawn
= function(self
)
791 init_profession(self
)
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
)
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
)
813 obj
:get_luaentity()._insta_collect
= false
817 -- Fallback for unexpected cases
818 minetest
.add_item(pos
, 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
)
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()
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
846 local trades
= trader
._trades
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
860 local trades
= trader
._trades
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
])
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
879 minetest
.register_on_joinplayer(function(player
)
880 player_tradenum
[player
:get_player_name()] = 1
881 player_trading_with
[player
:get_player_name()] = nil
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
)
887 mobs
:alias_mob("mobs:villager", "mobs_mc:villager")
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")