3 --made for MC like Survival game
4 --License for code WTFPL and otherwise stated in readmes
7 -- TODO: 4s Regeneration I after trade unlock
8 -- FIXME: Possible to lock all trades
11 local MP
= minetest
.get_modpath(minetest
.get_current_modname())
12 local S
, NS
= dofile(MP
.."/intllib.lua")
14 -- playername-indexed table containing the previously used tradenum
15 local player_tradenum
= {}
16 -- playername-indexed table containing the objectref of trader, if trading formspec is open
17 local player_trading_with
= {}
20 --################### VILLAGER
23 -- LIST OF VILLAGER PROFESSIONS AND TRADES
25 -- TECHNICAL RESTRICTIONS (FIXME):
26 -- * You can't use a clock as requested item
27 -- * You can't use a compass as requested item if its stack size > 1
28 -- * You can't use a compass in the second requested slot
29 -- This is a problem in the mcl_compass and mcl_clock mods,
30 -- these items should be implemented as single items, then everything
31 -- will be much easier.
33 local COMPASS
= "mcl_compass:compass"
34 if minetest
.registered_aliases
[COMPASS
] then
35 COMPASS
= minetest
.registered_aliases
[COMPASS
]
38 local E1
= { "mcl_core:emerald", 1, 1 } -- one emerald
40 -- Special trades for v6 only
41 local TRADE_V6_RED_SANDSTONE
, TRADE_V6_DARK_OAK_SAPLING
, TRADE_V6_ACACIA_SAPLING
, TRADE_V6_BIRCH_SAPLING
42 if minetest
.get_mapgen_setting("mg_name") == "v6" then
43 TRADE_V6_RED_SANDSTONE
= { E1
, { "mcl_core:redsandstone", 12, 16 } }
44 TRADE_V6_DARK_OAK_SAPLING
= { { "mcl_core:emerald", 6, 9 }, { "mcl_core:darksapling", 1, 1 } }
45 TRADE_V6_ACACIA_SAPLING
= { { "mcl_core:emerald", 14, 17 }, { "mcl_core:acaciasapling", 1, 1 } }
46 TRADE_V6_BIRCH_SAPLING
= { { "mcl_core:emerald", 8, 11 }, { "mcl_core:birchsapling", 1, 1 } }
52 texture
= "mobs_mc_villager_farmer.png",
55 { { "mcl_farming:wheat_item", 18, 22, }, E1
},
56 { { "mcl_farming:potato_item", 15, 19, }, E1
},
57 { { "mcl_farming:carrot_item", 15, 19, }, E1
},
58 { E1
, { "mcl_farming:bread", 2, 4 } },
62 { { "mcl_farming:pumpkin_face", 8, 13 }, E1
},
63 { E1
, { "mcl_farming:pumpkin_pie", 2, 3} },
67 { { "mcl_farming:melon", 7, 12 }, E1
},
68 { E1
, { "mcl_core:apple", 5, 7 }, },
72 { E1
, { "mcl_farming:cookie", 6, 10 } },
73 { E1
, { "mcl_cake:cake", 1, 1 } },
74 TRADE_V6_BIRCH_SAPLING
,
75 TRADE_V6_DARK_OAK_SAPLING
,
76 TRADE_V6_ACACIA_SAPLING
,
82 texture
= "mobs_mc_villager_farmer.png",
85 { { "mcl_fishing:fish_raw", 6, 6, "mcl_core:emerald", 1, 1 }, { "mcl_fishing:fish_cooked", 6, 6 } },
86 { { "mcl_mobitems:string", 15, 20 }, E1
},
87 { { "mcl_core:coal_lump", 16, 24 }, E1
},
89 -- TODO: enchanted fishing rod
94 texture
= "mobs_mc_villager_farmer.png",
97 { { "mcl_mobitems:string", 15, 20 }, E1
},
98 { E1
, { "mcl_bows:arrow", 8, 12 } },
102 { { "mcl_core:gravel", 10, 10, "mcl_core:emerald", 1, 1 }, { "mcl_core:flint", 6, 10 } },
103 { { "mcl_core:emerald", 2, 3 }, { "mcl_bows:bow", 1, 1 } },
109 texture
= "mobs_mc_villager_farmer.png",
112 { { "mcl_wool:white", 16, 22 }, E1
},
113 { { "mcl_core:emerald", 3, 4 }, { "mcl_tools:shears", 1, 1 } },
117 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:white", 1, 1 } },
118 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:grey", 1, 1 } },
119 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:silver", 1, 1 } },
120 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:black", 1, 1 } },
121 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:yellow", 1, 1 } },
122 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:orange", 1, 1 } },
123 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:red", 1, 1 } },
124 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:magenta", 1, 1 } },
125 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:purple", 1, 1 } },
126 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:blue", 1, 1 } },
127 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:cyan", 1, 1 } },
128 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:lime", 1, 1 } },
129 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:green", 1, 1 } },
130 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:pink", 1, 1 } },
131 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:light_blue", 1, 1 } },
132 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:brown", 1, 1 } },
138 texture
= "mobs_mc_villager_librarian.png",
141 { { "mcl_core:paper", 24, 36 }, E1
},
142 -- TODO: enchanted book
143 { { "mcl_books:book", 8, 10 }, E1
},
144 { { "mcl_core:emerald", 10, 12 }, { "mcl_compass:compass", 1 ,1 }},
145 { { "mcl_core:emerald", 3, 4 }, { "mcl_books:bookshelf", 1 ,1 }},
149 { { "mcl_books:written_book", 2, 2 }, E1
},
150 { { "mcl_core:emerald", 10, 12 }, { "mcl_clock:clock", 1, 1 } },
151 { E1
, { "mcl_core:glass", 3, 5 } },
155 { E1
, { "mcl_core:glass", 3, 5 } },
158 -- TODO: 2 enchanted book tiers
161 { { "mcl_core:emerald", 20, 22 }, { "mcl_mobs:nametag", 1, 1 } },
166 name
= "Cartographer",
167 texture
= "mobs_mc_villager_librarian.png",
170 { { "mcl_core:paper", 24, 36 }, E1
},
174 -- subject to special checks
175 { { "mcl_compass:compass", 1, 1 }, E1
},
179 -- TODO: replace with empty map
180 { { "mcl_core:emerald", 7, 11}, { "mcl_maps:filled_map", 1, 1 } },
183 -- TODO: special maps
188 texture
= "mobs_mc_villager_smith.png",
191 { { "mcl_core:coal_lump", 16, 24 }, E1
},
192 { { "mcl_core:emerald", 6, 8 }, { "3d_armor:helmet_iron", 1, 1 } },
196 { { "mcl_core:iron_ingot", 7, 9 }, E1
},
197 { { "mcl_core:emerald", 10, 14 }, { "3d_armor:chestplate_iron", 1, 1 } },
201 { { "mcl_core:diamond", 3, 4 }, E1
},
203 { { "mcl_core:emerald", 16, 19 }, { "3d_armor:chestplate_diamond", 1, 1 } },
207 { { "mcl_core:emerald", 5, 7 }, { "3d_armor:boots_chain", 1, 1 } },
208 { { "mcl_core:emerald", 9, 11 }, { "3d_armor:leggings_chain", 1, 1 } },
209 { { "mcl_core:emerald", 5, 7 }, { "3d_armor:helmet_chain", 1, 1 } },
210 { { "mcl_core:emerald", 11, 15 }, { "3d_armor:chestplate_chain", 1, 1 } },
215 name
= "Leatherworker",
216 texture
= "mobs_mc_villager_butcher.png",
219 { { "mcl_mobitems:leather", 9, 12 }, E1
},
220 { { "mcl_core:emerald", 2, 4 }, { "3d_armor:leggings_leather", 2, 4 } },
225 { { "mcl_core:emerald", 7, 12 }, { "3d_armor:chestplate_leather", 1, 1 } },
229 { { "mcl_core:emerald", 8, 10 }, { "mcl_mobitems:saddle", 1, 1 } },
235 texture
= "mobs_mc_villager_butcher.png",
238 { { "mcl_mobitems:beef", 14, 18 }, E1
},
239 { { "mcl_mobitems:chicken", 14, 18 }, E1
},
243 { { "mcl_core:coal_lump", 16, 24 }, E1
},
244 { E1
, { "mcl_mobitems:cooked_beef", 5, 7 } },
245 { E1
, { "mcl_mobitems:cooked_chicken", 6, 8 } },
250 name
= "Weapon Smith",
251 texture
= "mobs_mc_villager_smith.png",
254 { { "mcl_core:coal_lump", 16, 24 }, E1
},
255 { { "mcl_core:emerald", 6, 8 }, { "mcl_tools:axe_iron", 1, 1 } },
259 { { "mcl_core:iron_ingot", 7, 9 }, E1
},
261 { { "mcl_core:emerald", 9, 10 }, { "mcl_tools:sword_iron", 1, 1 } },
265 { { "mcl_core:diamond", 3, 4 }, E1
},
267 { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:sword_diamond", 1, 1 } },
269 { { "mcl_core:emerald", 9, 12 }, { "mcl_tools:axe_diamond", 1, 1 } },
275 texture
= "mobs_mc_villager_smith.png",
278 { { "mcl_core:coal_lump", 16, 24 }, E1
},
280 { { "mcl_core:emerald", 5, 7 }, { "mcl_tools:shovel_iron", 1, 1 } },
284 { { "mcl_core:iron_ingot", 7, 9 }, E1
},
286 { { "mcl_core:emerald", 9, 11 }, { "mcl_tools:pick_iron", 1, 1 } },
290 { { "mcl_core:diamond", 3, 4 }, E1
},
292 { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:pick_diamond", 1, 1 } },
298 texture
= "mobs_mc_villager_priest.png",
301 { { "mcl_mobitems:rotten_flesh", 36, 40 }, E1
},
302 { { "mcl_core:gold_ingot", 8, 10 }, E1
},
306 { E1
, { "mesecons:redstone", 1, 4 } },
307 { E1
, { "mcl_dye:blue", 1, 2 } },
311 TRADE_V6_RED_SANDSTONE
,
312 { E1
, { "mcl_nether:glowstone", 1, 3 } },
313 { { "mcl_core:emerald", 4, 7 }, { "mcl_throwing:ender_pearl", 1, 1 } },
316 -- TODO: Bottle 'o enchanting
321 texture
= "mobs_mc_villager.png",
322 -- No trades for nitwit
327 local profession_names
= {}
328 for id
, _
in pairs(professions
) do
329 table.insert(profession_names
, id
)
332 local init_profession
= function(self
)
333 if not self
._profession
then
334 -- Select random profession from all professions with matching clothing
335 local texture
= self
.base_texture
[1]
337 for prof_id
, prof
in pairs(professions
) do
338 if texture
== prof
.texture
then
339 table.insert(matches
, prof_id
)
342 local p
= math
.random(1, #matches
)
343 self
._profession
= matches
[p
]
345 if not self
._max_trade_tier
then
346 self
._max_trade_tier
= 1
350 local init_trades
= function(self
, inv
)
351 local profession
= professions
[self
._profession
]
352 local trade_tiers
= profession
.trades
353 if trade_tiers
== nil then
359 local max_tier
= #trade_tiers
361 for tiernum
=1, max_tier
do
362 local tier
= trade_tiers
[tiernum
]
363 for tradenum
=1, #tier
do
364 local trade
= tier
[tradenum
]
365 local wanted1_item
= trade
[1][1]
366 local wanted1_count
= math
.random(trade
[1][2], trade
[1][3])
367 local offered_item
= trade
[2][1]
368 local offered_count
= math
.random(trade
[2][2], trade
[2][3])
370 local wanted
= { wanted1_item
.. " " ..wanted1_count
}
372 local wanted2_item
= trade
[1][4]
373 local wanted2_count
= math
.random(trade
[1][5], trade
[1][6])
374 table.insert(wanted
, wanted2_item
.. " " ..wanted2_count
)
377 table.insert(trades
, {
379 offered
= offered_item
.. " " .. offered_count
,
380 tier
= tiernum
, -- tier of this trade
381 traded_once
= false, -- true if trade was traded at least once
382 trade_counter
= 0, -- how often the this trade was mate after the last time it got unlocked
383 locked
= false, -- if this trade is locked. Locked trades can't be used
387 self
._trades
= minetest
.serialize(trades
)
390 local set_trade
= function(trader
, player
, inv
, concrete_tradenum
)
391 local trades
= minetest
.deserialize(trader
._trades
)
394 trades
= minetest
.deserialize(trader
._trades
)
396 minetest
.log("error", "[mobs_mc] Failed to select villager trade!")
401 if concrete_tradenum
> #trades
then
402 concrete_tradenum
= 1
403 player_tradenum
[player
:get_player_name()] = concrete_tradenum
404 elseif concrete_tradenum
< 1 then
405 concrete_tradenum
= #trades
406 player_tradenum
[player
:get_player_name()] = concrete_tradenum
408 local trade
= trades
[concrete_tradenum
]
409 if trader
._max_trade_tier
< trade
.tier
then
410 concrete_tradenum
= 1
411 player_tradenum
[player
:get_player_name()] = concrete_tradenum
412 trade
= trades
[concrete_tradenum
]
414 inv
:set_stack("wanted", 1, ItemStack(trade
.wanted
[1]))
415 inv
:set_stack("offered", 1, ItemStack(trade
.offered
))
416 if trade
.wanted
[2] then
417 local wanted2
= ItemStack(trade
.wanted
[2])
418 inv
:set_stack("wanted", 2, wanted2
)
420 inv
:set_stack("wanted", 2, "")
425 local function show_trade_formspec(playername
, trader
, tradenum
)
426 if not trader
._trades
then
432 local trades
= minetest
.deserialize(trader
._trades
)
433 local trade
= trades
[tradenum
]
434 local profession
= professions
[trader
._profession
].name
435 local disabled_img
= ""
437 disabled_img
= "image[4.3,2.52;1,1;mobs_mc_trading_formspec_disabled.png]"..
438 "image[4.3,1.1;1,1;mobs_mc_trading_formspec_disabled.png]"
440 local tradeinv_name
= "mobs_mc:trade_"..playername
441 local tradeinv
= minetest
.formspec_escape("detached:"..tradeinv_name
)
444 .."background[-0.19,-0.25;9.41,9.49;mobs_mc_trading_formspec_bg.png]"
446 ..mcl_vars
.inventory_header
447 .."label[4,0;"..minetest
.formspec_escape(profession
).."]"
448 .."list[current_player;main;0,4.5;9,3;9]"
449 .."list[current_player;main;0,7.74;9,1;]"
450 .."button[1,1;0.5,1;prev_trade;<]"
451 .."button[7.26,1;0.5,1;next_trade;>]"
452 .."list["..tradeinv
..";wanted;2,1;2,1;]"
453 .."list["..tradeinv
..";offered;5.76,1;1,1;]"
454 .."list["..tradeinv
..";input;2,2.5;2,1;]"
455 .."list["..tradeinv
..";output;5.76,2.55;1,1;]"
456 .."listring["..tradeinv
..";output]"
457 .."listring[current_player;main]"
458 .."listring["..tradeinv
..";input]"
459 .."listring[current_player;main]"
460 minetest
.sound_play("mobs_mc_villager_trade", {to_player
= playername
})
461 minetest
.show_formspec(playername
, tradeinv_name
, formspec
)
464 local update_offer
= function(inv
, player
, sound
)
465 local name
= player
:get_player_name()
466 local trader
= player_trading_with
[name
]
467 local tradenum
= player_tradenum
[name
]
468 if not trader
or not tradenum
then
471 local trades
= minetest
.deserialize(trader
._trades
)
475 local trade
= trades
[tradenum
]
479 local wanted1
, wanted2
= inv
:get_stack("wanted", 1), inv
:get_stack("wanted", 2)
480 local input1
, input2
= inv
:get_stack("input", 1), inv
:get_stack("input", 2)
482 -- BEGIN OF SPECIAL HANDLING OF COMPASS
483 -- These 2 functions are a complicated check to check if the input contains a
484 -- special item which we cannot check directly against their name, like
486 -- TODO: Remove these check functions when compass and clock are implemented
488 local check_special
= function(special_item
, group
, wanted1
, wanted2
, input1
, input2
)
489 if minetest
.registered_aliases
[special_item
] then
490 special_item
= minetest
.registered_aliases
[special_item
]
492 if wanted1
:get_name() == special_item
then
493 local check_input
= function(input
, wanted
, group
)
494 return minetest
.get_item_group(input
:get_name(), group
) ~= 0 and input
:get_count() >= wanted
:get_count()
496 if check_input(input1
, wanted1
, group
) then
498 elseif check_input(input2
, wanted1
, group
) then
506 -- Apply above function to all items which we consider special.
507 -- This function succeeds if ANY item check succeeds.
508 local check_specials
= function(wanted1
, wanted2
, input1
, input2
)
509 return check_special(COMPASS
, "compass", wanted1
, wanted2
, input1
, input2
)
511 -- END OF SPECIAL HANDLING OF COMPASS
514 ((inv
:contains_item("input", wanted1
) and
515 (wanted2
:is_empty() or inv
:contains_item("input", wanted2
))) or
516 -- BEGIN OF SPECIAL HANDLING OF COMPASS
517 check_specials(wanted1
, wanted2
, input1
, input2
)) and
518 -- END OF SPECIAL HANDLING OF COMPASS
519 (trade
.locked
== false)) then
520 inv
:set_stack("output", 1, inv
:get_stack("offered", 1))
522 minetest
.sound_play("mobs_mc_villager_accept", {to_player
= name
})
526 inv
:set_stack("output", 1, ItemStack(""))
528 minetest
.sound_play("mobs_mc_villager_deny", {to_player
= name
})
534 mobs
:register_mob("mobs_mc:villager", {
538 collisionbox
= {-0.3, -0.01, -0.3, 0.3, 1.94, 0.3},
540 mesh
= "mobs_mc_villager.b3d",
543 "mobs_mc_villager.png",
544 "mobs_mc_villager.png", --hat
547 "mobs_mc_villager_farmer.png",
548 "mobs_mc_villager_farmer.png", --hat
551 "mobs_mc_villager_priest.png",
552 "mobs_mc_villager_priest.png", --hat
555 "mobs_mc_villager_librarian.png",
556 "mobs_mc_villager_librarian.png", --hat
559 "mobs_mc_villager_butcher.png",
560 "mobs_mc_villager_butcher.png", --hat
563 "mobs_mc_villager_smith.png",
564 "mobs_mc_villager_smith.png", --hat
567 visual_size
= {x
=3, y
=3},
568 makes_footstep_sound
= true,
573 random = "mobs_mc_villager_noise",
574 death
= "mobs_mc_villager_death",
575 damage
= "mobs_mc_villager_damage",
598 on_rightclick
= function(self
, clicker
)
599 local name
= clicker
:get_player_name()
601 init_profession(self
)
602 if self
._trades
== nil then
605 if self
._trades
== false then
606 -- Villager has no trades, rightclick is a no-op
610 player_trading_with
[name
] = self
612 local inv
= minetest
.get_inventory({type="detached", name
="mobs_mc:trade_"..name
})
614 player_tradenum
[name
] = 1
615 set_trade(self
, clicker
, inv
, player_tradenum
[name
], player_tradenum
[name
])
617 show_trade_formspec(name
, self
)
620 on_spawn
= function(self
)
621 init_profession(self
)
625 -- Returns a single itemstack in the given inventory to the player's main inventory, or drop it when there's no space left
626 local function return_item(itemstack
, dropper
, pos
, inv_p
)
627 if dropper
:is_player() then
628 -- Return to main inventory
629 if inv_p
:room_for_item("main", itemstack
) then
630 inv_p
:add_item("main", itemstack
)
632 -- Drop item on the ground
633 local v
= dropper
:get_look_dir()
634 local p
= {x
=pos
.x
, y
=pos
.y
+1.2, z
=pos
.z
}
635 p
.x
= p
.x
+(math
.random(1,3)*0.2)
636 p
.z
= p
.z
+(math
.random(1,3)*0.2)
637 local obj
= minetest
.add_item(p
, itemstack
)
643 obj
:get_luaentity()._insta_collect
= false
647 -- Fallback for unexpected cases
648 minetest
.add_item(pos
, itemstack
)
653 local return_fields
= function(player
)
654 local name
= player
:get_player_name()
655 local inv_t
= minetest
.get_inventory({type="detached", name
= "mobs_mc:trade_"..name
})
656 local inv_p
= player
:get_inventory()
657 for i
=1, inv_t
:get_size("input") do
658 local stack
= inv_t
:get_stack("input", i
)
659 return_item(stack
, player
, player
:get_pos(), inv_p
)
661 inv_t
:set_stack("input", i
, stack
)
663 inv_t
:set_stack("output", 1, "")
666 minetest
.register_on_player_receive_fields(function(player
, formname
, fields
)
667 if string.sub(formname
, 1, 14) == "mobs_mc:trade_" then
668 local name
= player
:get_player_name()
670 return_fields(player
)
671 player_trading_with
[name
] = nil
672 elseif fields
.next_trade
then
673 local trader
= player_trading_with
[name
]
674 if not trader
or not trader
.object
:get_luaentity() then
677 local trades
= trader
._trades
681 player_tradenum
[name
] = player_tradenum
[name
] + 1
682 local inv
= minetest
.get_inventory({type="detached", name
="mobs_mc:trade_"..name
})
683 set_trade(trader
, player
, inv
, player_tradenum
[name
])
684 update_offer(inv
, player
, false)
685 show_trade_formspec(name
, trader
, player_tradenum
[name
])
686 elseif fields
.prev_trade
then
687 local trader
= player_trading_with
[name
]
688 if not trader
or not trader
.object
:get_luaentity() then
691 local trades
= trader
._trades
695 player_tradenum
[name
] = player_tradenum
[name
] - 1
696 local inv
= minetest
.get_inventory({type="detached", name
="mobs_mc:trade_"..name
})
697 set_trade(trader
, player
, inv
, player_tradenum
[name
])
698 update_offer(inv
, player
, false)
699 show_trade_formspec(name
, trader
, player_tradenum
[name
])
704 minetest
.register_on_leaveplayer(function(player
)
705 return_fields(player
)
706 player_tradenum
[player
:get_player_name()] = nil
707 player_trading_with
[player
:get_player_name()] = nil
710 local trade_inventory
= {
711 allow_take
= function(inv
, listname
, index
, stack
, player
)
712 if listname
== "input" then
713 return stack
:get_count()
714 elseif listname
== "output" then
715 -- Only allow taking full stack
716 local count
= stack
:get_count()
717 if count
== inv
:get_stack(listname
, index
):get_count() then
718 -- Also update output stack again.
719 -- If input has double the wanted items, the
720 -- output will stay because there will be still
721 -- enough items in input after the trade
722 local wanted1
= inv
:get_stack("wanted", 1)
723 local wanted2
= inv
:get_stack("wanted", 2)
724 local input1
= inv
:get_stack("input", 1)
725 local input2
= inv
:get_stack("input", 2)
726 wanted1
:set_count(wanted1
:get_count()*2)
727 wanted2
:set_count(wanted2
:get_count()*2)
728 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
729 local special_checks
= function(wanted1
, input1
, input2
)
730 if wanted1
:get_name() == COMPASS
then
732 if (minetest
.get_item_group(input1
:get_name(), "compass") ~= 0) then
733 compasses
= compasses
+ input1
:get_count()
735 if (minetest
.get_item_group(input2
:get_name(), "compass") ~= 0) then
736 compasses
= compasses
+ input2
:get_count()
738 return compasses
>= wanted1
:get_count()
742 -- END OF SPECIAL HANDLING FOR COMPASS
743 if (inv
:contains_item("input", wanted1
) and
744 (wanted2
:is_empty() or inv
:contains_item("input", wanted2
)))
745 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
746 or special_checks(wanted1
, input1
, input2
) then
747 -- END OF SPECIAL HANDLING FOR COMPASS
750 -- If less than double the wanted items,
751 -- remove items from output (final trade,
762 allow_move
= function(inv
, from_list
, from_index
, to_list
, to_index
, count
, player
)
763 if from_list
== "input" and to_list
== "input" then
765 elseif from_list
== "output" and to_list
== "input" then
766 local move_stack
= inv
:get_stack(from_list
, from_index
)
767 if inv
:get_stack(to_list
, to_index
):item_fits(move_stack
) then
773 allow_put
= function(inv
, listname
, index
, stack
, player
)
774 if listname
== "input" then
775 return stack
:get_count()
780 on_put
= function(inv
, listname
, index
, stack
, player
)
781 update_offer(inv
, player
, true)
783 on_move
= function(inv
, from_list
, from_index
, to_list
, to_index
, count
, player
)
784 if from_list
== "output" and to_list
== "input" then
785 inv
:remove_item("input", inv
:get_stack("wanted", 1))
786 local wanted2
= inv
:get_stack("wanted", 2)
787 if not wanted2
:is_empty() then
788 inv
:remove_item("input", inv
:get_stack("wanted", 2))
790 minetest
.sound_play("mobs_mc_villager_accept", {to_player
= player
:get_player_name()})
792 update_offer(inv
, player
, true)
794 on_take
= function(inv
, listname
, index
, stack
, player
)
796 local name
= player
:get_player_name()
797 if listname
== "output" then
798 local wanted1
= inv
:get_stack("wanted", 1)
799 inv
:remove_item("input", wanted1
)
800 local wanted2
= inv
:get_stack("wanted", 2)
801 if not wanted2
:is_empty() then
802 inv
:remove_item("input", inv
:get_stack("wanted", 2))
804 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
805 if wanted1
:get_name() == COMPASS
then
807 local input
= inv
:get_stack("input", n
)
808 if minetest
.get_item_group(input
:get_name(), "compass") ~= 0 then
809 input
:set_count(input
:get_count() - wanted1
:get_count())
810 inv
:set_stack("input", n
, input
)
815 -- END OF SPECIAL HANDLING FOR COMPASS
816 local trader
= player_trading_with
[name
]
817 local tradenum
= player_tradenum
[name
]
819 if trader
and trader
._trades
then
820 trades
= minetest
.deserialize(trader
._trades
)
823 local trade
= trades
[tradenum
]
824 local unlock_stuff
= false
825 if not trade
.traded_once
then
826 -- Unlock all the things if something was traded
827 -- for the first time ever
829 trade
.traded_once
= true
830 elseif trade
.trade_counter
== 0 and math
.random(1,5) == 1 then
831 -- Otherwise, 20% chance to unlock if used freshly reset trade
835 -- First-time trade unlock all trades and unlock next trade tier
836 if trade
.tier
+ 1 > trader
._max_trade_tier
then
837 trader
._max_trade_tier
= trader
._max_trade_tier
+ 1
840 trades
[t
].locked
= false
841 trades
[t
].trade_counter
= 0
844 trade
.trade_counter
= trade
.trade_counter
+ 1
845 if trade
.trade_counter
>= 12 then
847 elseif trade
.trade_counter
>= 2 then
848 local r
= math
.random(1, math
.random(4, 10))
854 trader
._trades
= minetest
.serialize(trades
)
856 inv
:set_stack("output", 1, "")
857 show_trade_formspec(name
, trader
, tradenum
)
860 minetest
.log("error", "[mobs_mc] Player took item from trader output but player_trading_with or player_tradenum is nil!")
864 elseif listname
== "input" then
865 update_offer(inv
, player
, false)
868 minetest
.sound_play("mobs_mc_villager_accept", {to_player
= name
})
870 minetest
.sound_play("mobs_mc_villager_deny", {to_player
= name
})
876 minetest
.register_on_joinplayer(function(player
)
877 local name
= player
:get_player_name()
878 player_tradenum
[name
] = 1
879 player_trading_with
[name
] = nil
881 -- Create or get player-specific trading inventory
882 local inv
= minetest
.get_inventory({type="detached", name
="mobs_mc:trade_"..name
})
884 inv
= minetest
.create_detached_inventory("mobs_mc:trade_"..name
, trade_inventory
, name
)
886 inv
:set_size("input", 2)
887 inv
:set_size("output", 1)
888 inv
:set_size("wanted", 2)
889 inv
:set_size("offered", 1)
892 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
)
895 mobs
:alias_mob("mobs:villager", "mobs_mc:villager")
898 mobs
:register_egg("mobs_mc:villager", S("Villager"), "mobs_mc_spawn_icon_villager.png", 0)
900 if minetest
.settings
:get_bool("log_mods") then
901 minetest
.log("action", "MC mobs loaded")