Villager trading: Use per-player trading inventory
[MineClone/MineClone2.git] / mods / ENTITIES / mobs_mc / villager.lua
blobccaeecf181d84c476d29d2ae3f7430cb49ec2e5b
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: Particles
7 -- TODO: 4s Regeneration I after trade unlock
8 -- FIXME: Possible to lock all trades
10 -- intllib
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 = {}
19 --###################
20 --################### VILLAGER
21 --###################
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]
36 end
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 } }
47 end
49 local professions = {
50 farmer = {
51 name = "Farmer",
52 texture = "mobs_mc_villager_farmer.png",
53 trades = {
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,
80 fisherman = {
81 name = "Fisherman",
82 texture = "mobs_mc_villager_farmer.png",
83 trades = {
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
92 fletcher = {
93 name = "Fletcher",
94 texture = "mobs_mc_villager_farmer.png",
95 trades = {
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 } },
107 shepherd ={
108 name = "Shepherd",
109 texture = "mobs_mc_villager_farmer.png",
110 trades = {
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 } },
136 librarian = {
137 name = "Librarian",
138 texture = "mobs_mc_villager_librarian.png",
139 trades = {
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 } },
165 cartographer = {
166 name = "Cartographer",
167 texture = "mobs_mc_villager_librarian.png",
168 trades = {
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
186 armorer = {
187 name = "Armorer",
188 texture = "mobs_mc_villager_smith.png",
189 trades = {
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 },
202 -- TODO: enchant
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 } },
214 leatherworker = {
215 name = "Leatherworker",
216 texture = "mobs_mc_villager_butcher.png",
217 trades = {
219 { { "mcl_mobitems:leather", 9, 12 }, E1 },
220 { { "mcl_core:emerald", 2, 4 }, { "3d_armor:leggings_leather", 2, 4 } },
224 -- TODO: enchant
225 { { "mcl_core:emerald", 7, 12 }, { "3d_armor:chestplate_leather", 1, 1 } },
229 { { "mcl_core:emerald", 8, 10 }, { "mcl_mobitems:saddle", 1, 1 } },
233 butcher = {
234 name = "Butcher",
235 texture = "mobs_mc_villager_butcher.png",
236 trades = {
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 } },
249 weapon_smith = {
250 name = "Weapon Smith",
251 texture = "mobs_mc_villager_smith.png",
252 trades = {
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 },
260 -- TODO: enchant
261 { { "mcl_core:emerald", 9, 10 }, { "mcl_tools:sword_iron", 1, 1 } },
265 { { "mcl_core:diamond", 3, 4 }, E1 },
266 -- TODO: enchant
267 { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:sword_diamond", 1, 1 } },
268 -- TODO: enchant
269 { { "mcl_core:emerald", 9, 12 }, { "mcl_tools:axe_diamond", 1, 1 } },
273 tool_smith = {
274 name = "Tool Smith",
275 texture = "mobs_mc_villager_smith.png",
276 trades = {
278 { { "mcl_core:coal_lump", 16, 24 }, E1 },
279 -- TODO: enchant
280 { { "mcl_core:emerald", 5, 7 }, { "mcl_tools:shovel_iron", 1, 1 } },
284 { { "mcl_core:iron_ingot", 7, 9 }, E1 },
285 -- TODO: enchant
286 { { "mcl_core:emerald", 9, 11 }, { "mcl_tools:pick_iron", 1, 1 } },
290 { { "mcl_core:diamond", 3, 4 }, E1 },
291 -- TODO: enchant
292 { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:pick_diamond", 1, 1 } },
296 cleric = {
297 name = "Cleric",
298 texture = "mobs_mc_villager_priest.png",
299 trades = {
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
319 nitwit = {
320 name = "Nitwit",
321 texture = "mobs_mc_villager.png",
322 -- No trades for nitwit
323 trades = nil,
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]
336 local matches = {}
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
354 -- Empty trades
355 self._trades = false
356 return
359 local max_tier = #trade_tiers
360 local trades = {}
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 }
371 if trade[1][4] then
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, {
378 wanted = wanted,
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)
392 if not trades then
393 init_trades(trader)
394 trades = minetest.deserialize(trader._trades)
395 if not trades then
396 minetest.log("error", "[mobs_mc] Failed to select villager trade!")
397 return
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)
419 else
420 inv:set_stack("wanted", 2, "")
425 local function show_trade_formspec(playername, trader, tradenum)
426 if not trader._trades then
427 return
429 if not tradenum then
430 tradenum = 1
432 local trades = minetest.deserialize(trader._trades)
433 local trade = trades[tradenum]
434 local profession = professions[trader._profession].name
435 local disabled_img = ""
436 if trade.locked then
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)
442 local formspec =
443 "size[9,8.75]"
444 .."background[-0.19,-0.25;9.41,9.49;mobs_mc_trading_formspec_bg.png]"
445 ..disabled_img
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
469 return false
471 local trades = minetest.deserialize(trader._trades)
472 if not trades then
473 return false
475 local trade = trades[tradenum]
476 if not trade then
477 return false
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
485 -- compass.
486 -- TODO: Remove these check functions when compass and clock are implemented
487 -- as single items.
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
497 return true
498 elseif check_input(input2, wanted1, group) then
499 return true
500 else
501 return false
504 return false
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
513 if (
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))
521 if sound then
522 minetest.sound_play("mobs_mc_villager_accept", {to_player = name})
524 return true
525 else
526 inv:set_stack("output", 1, ItemStack(""))
527 if sound then
528 minetest.sound_play("mobs_mc_villager_deny", {to_player = name})
530 return false
534 mobs:register_mob("mobs_mc:villager", {
535 type = "npc",
536 hp_min = 20,
537 hp_max = 20,
538 collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.94, 0.3},
539 visual = "mesh",
540 mesh = "mobs_mc_villager.b3d",
541 textures = {
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,
569 walk_velocity = 1.2,
570 run_velocity = 2.4,
571 drops = {},
572 sounds = {
573 random = "mobs_mc_villager_noise",
574 death = "mobs_mc_villager_death",
575 damage = "mobs_mc_villager_damage",
576 distance = 16,
578 animation = {
579 stand_speed = 25,
580 stand_start = 40,
581 stand_end = 59,
582 walk_speed = 25,
583 walk_start = 0,
584 walk_end = 40,
585 run_speed = 25,
586 run_start = 0,
587 run_end = 40,
588 die_speed = 15,
589 die_start = 210,
590 die_end = 220,
591 die_loop = false,
593 water_damage = 0,
594 lava_damage = 4,
595 light_damage = 0,
596 view_range = 16,
597 fear_height = 4,
598 on_rightclick = function(self, clicker)
599 local name = clicker:get_player_name()
601 init_profession(self)
602 if self._trades == nil then
603 init_trades(self)
605 if self._trades == false then
606 -- Villager has no trades, rightclick is a no-op
607 return
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)
618 end,
620 on_spawn = function(self)
621 init_profession(self)
622 end,
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)
631 else
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)
638 if obj then
639 v.x = v.x*4
640 v.y = v.y*4 + 2
641 v.z = v.z*4
642 obj:setvelocity(v)
643 obj:get_luaentity()._insta_collect = false
646 else
647 -- Fallback for unexpected cases
648 minetest.add_item(pos, itemstack)
650 return 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)
660 stack:clear()
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()
669 if fields.quit then
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
675 return
677 local trades = trader._trades
678 if not trades then
679 return
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
689 return
691 local trades = trader._trades
692 if not trades then
693 return
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])
702 end)
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
708 end)
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
731 local compasses = 0
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()
740 return false
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
748 return -1
749 else
750 -- If less than double the wanted items,
751 -- remove items from output (final trade,
752 -- input runs empty)
753 return count
755 else
756 return 0
758 else
759 return 0
761 end,
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
764 return count
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
768 return count
771 return 0
772 end,
773 allow_put = function(inv, listname, index, stack, player)
774 if listname == "input" then
775 return stack:get_count()
776 else
777 return 0
779 end,
780 on_put = function(inv, listname, index, stack, player)
781 update_offer(inv, player, true)
782 end,
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)
793 end,
794 on_take = function(inv, listname, index, stack, player)
795 local accept
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
806 for n=1, 2 do
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)
811 break
815 -- END OF SPECIAL HANDLING FOR COMPASS
816 local trader = player_trading_with[name]
817 local tradenum = player_tradenum[name]
818 local trades
819 if trader and trader._trades then
820 trades = minetest.deserialize(trader._trades)
822 if trades then
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
828 unlock_stuff = true
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
832 unlock_stuff = true
834 if unlock_stuff then
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
839 for t=1, #trades do
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
846 trade.locked = true
847 elseif trade.trade_counter >= 2 then
848 local r = math.random(1, math.random(4, 10))
849 if r == 1 then
850 trade.locked = true
854 trader._trades = minetest.serialize(trades)
855 if trade.locked then
856 inv:set_stack("output", 1, "")
857 show_trade_formspec(name, trader, tradenum)
859 else
860 minetest.log("error", "[mobs_mc] Player took item from trader output but player_trading_with or player_tradenum is nil!")
863 accept = true
864 elseif listname == "input" then
865 update_offer(inv, player, false)
867 if accept then
868 minetest.sound_play("mobs_mc_villager_accept", {to_player = name})
869 else
870 minetest.sound_play("mobs_mc_villager_deny", {to_player = name})
872 end,
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})
883 if not inv then
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)
890 end)
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)
894 -- compatibility
895 mobs:alias_mob("mobs:villager", "mobs_mc:villager")
897 -- spawn eggs
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")