Heal trader for unlocking trades
[MineClone/MineClone2.git] / mods / ENTITIES / mobs_mc / villager.lua
blobe569ccbe02439f1298e312ecffb69681890aa12f
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 update_max_tradenum = function(self)
333 if not self._trades then
334 return
336 local trades = minetest.deserialize(self._trades)
337 for t=1, #trades do
338 local trade = trades[t]
339 if trade.tier > self._max_trade_tier then
340 self._max_tradenum = t - 1
341 return
344 self._max_tradenum = #trades
347 local init_profession = function(self)
348 if not self._profession then
349 -- Select random profession from all professions with matching clothing
350 local texture = self.base_texture[1]
351 local matches = {}
352 for prof_id, prof in pairs(professions) do
353 if texture == prof.texture then
354 table.insert(matches, prof_id)
357 local p = math.random(1, #matches)
358 self._profession = matches[p]
360 if not self._max_trade_tier then
361 self._max_trade_tier = 1
365 local init_trades = function(self, inv)
366 local profession = professions[self._profession]
367 local trade_tiers = profession.trades
368 if trade_tiers == nil then
369 -- Empty trades
370 self._trades = false
371 return
374 local max_tier = #trade_tiers
375 local trades = {}
376 for tiernum=1, max_tier do
377 local tier = trade_tiers[tiernum]
378 for tradenum=1, #tier do
379 local trade = tier[tradenum]
380 local wanted1_item = trade[1][1]
381 local wanted1_count = math.random(trade[1][2], trade[1][3])
382 local offered_item = trade[2][1]
383 local offered_count = math.random(trade[2][2], trade[2][3])
385 local wanted = { wanted1_item .. " " ..wanted1_count }
386 if trade[1][4] then
387 local wanted2_item = trade[1][4]
388 local wanted2_count = math.random(trade[1][5], trade[1][6])
389 table.insert(wanted, wanted2_item .. " " ..wanted2_count)
392 table.insert(trades, {
393 wanted = wanted,
394 offered = offered_item .. " " .. offered_count,
395 tier = tiernum, -- tier of this trade
396 traded_once = false, -- true if trade was traded at least once
397 trade_counter = 0, -- how often the this trade was mate after the last time it got unlocked
398 locked = false, -- if this trade is locked. Locked trades can't be used
402 self._trades = minetest.serialize(trades)
405 local set_trade = function(trader, player, inv, concrete_tradenum)
406 local trades = minetest.deserialize(trader._trades)
407 if not trades then
408 init_trades(trader)
409 trades = minetest.deserialize(trader._trades)
410 if not trades then
411 minetest.log("error", "[mobs_mc] Failed to select villager trade!")
412 return
415 local name = player:get_player_name()
417 -- Stop tradenum from advancing into locked tiers or out-of-range areas
418 if concrete_tradenum > trader._max_tradenum then
419 concrete_tradenum = trader._max_tradenum
420 elseif concrete_tradenum < 1 then
421 concrete_tradenum = 1
423 player_tradenum[name] = concrete_tradenum
424 local trade = trades[concrete_tradenum]
425 inv:set_stack("wanted", 1, ItemStack(trade.wanted[1]))
426 inv:set_stack("offered", 1, ItemStack(trade.offered))
427 if trade.wanted[2] then
428 local wanted2 = ItemStack(trade.wanted[2])
429 inv:set_stack("wanted", 2, wanted2)
430 else
431 inv:set_stack("wanted", 2, "")
436 local function show_trade_formspec(playername, trader, tradenum)
437 if not trader._trades then
438 return
440 if not tradenum then
441 tradenum = 1
443 local trades = minetest.deserialize(trader._trades)
444 local trade = trades[tradenum]
445 local profession = professions[trader._profession].name
446 local disabled_img = ""
447 if trade.locked then
448 disabled_img = "image[4.3,2.52;1,1;mobs_mc_trading_formspec_disabled.png]"..
449 "image[4.3,1.1;1,1;mobs_mc_trading_formspec_disabled.png]"
451 local tradeinv_name = "mobs_mc:trade_"..playername
452 local tradeinv = minetest.formspec_escape("detached:"..tradeinv_name)
454 local b_prev, b_next = "", ""
455 if #trades > 1 then
456 if tradenum > 1 then
457 b_prev = "button[1,1;0.5,1;prev_trade;<]"
459 if tradenum < trader._max_tradenum then
460 b_next = "button[7.26,1;0.5,1;next_trade;>]"
464 local formspec =
465 "size[9,8.75]"
466 .."background[-0.19,-0.25;9.41,9.49;mobs_mc_trading_formspec_bg.png]"
467 ..disabled_img
468 ..mcl_vars.inventory_header
469 .."label[4,0;"..minetest.formspec_escape(profession).."]"
470 .."list[current_player;main;0,4.5;9,3;9]"
471 .."list[current_player;main;0,7.74;9,1;]"
472 ..b_prev..b_next
473 .."list["..tradeinv..";wanted;2,1;2,1;]"
474 .."list["..tradeinv..";offered;5.76,1;1,1;]"
475 .."list["..tradeinv..";input;2,2.5;2,1;]"
476 .."list["..tradeinv..";output;5.76,2.55;1,1;]"
477 .."listring["..tradeinv..";output]"
478 .."listring[current_player;main]"
479 .."listring["..tradeinv..";input]"
480 .."listring[current_player;main]"
481 minetest.sound_play("mobs_mc_villager_trade", {to_player = playername})
482 minetest.show_formspec(playername, tradeinv_name, formspec)
485 local update_offer = function(inv, player, sound)
486 local name = player:get_player_name()
487 local trader = player_trading_with[name]
488 local tradenum = player_tradenum[name]
489 if not trader or not tradenum then
490 return false
492 local trades = minetest.deserialize(trader._trades)
493 if not trades then
494 return false
496 local trade = trades[tradenum]
497 if not trade then
498 return false
500 local wanted1, wanted2 = inv:get_stack("wanted", 1), inv:get_stack("wanted", 2)
501 local input1, input2 = inv:get_stack("input", 1), inv:get_stack("input", 2)
503 -- BEGIN OF SPECIAL HANDLING OF COMPASS
504 -- These 2 functions are a complicated check to check if the input contains a
505 -- special item which we cannot check directly against their name, like
506 -- compass.
507 -- TODO: Remove these check functions when compass and clock are implemented
508 -- as single items.
509 local check_special = function(special_item, group, wanted1, wanted2, input1, input2)
510 if minetest.registered_aliases[special_item] then
511 special_item = minetest.registered_aliases[special_item]
513 if wanted1:get_name() == special_item then
514 local check_input = function(input, wanted, group)
515 return minetest.get_item_group(input:get_name(), group) ~= 0 and input:get_count() >= wanted:get_count()
517 if check_input(input1, wanted1, group) then
518 return true
519 elseif check_input(input2, wanted1, group) then
520 return true
521 else
522 return false
525 return false
527 -- Apply above function to all items which we consider special.
528 -- This function succeeds if ANY item check succeeds.
529 local check_specials = function(wanted1, wanted2, input1, input2)
530 return check_special(COMPASS, "compass", wanted1, wanted2, input1, input2)
532 -- END OF SPECIAL HANDLING OF COMPASS
534 if (
535 ((inv:contains_item("input", wanted1) and
536 (wanted2:is_empty() or inv:contains_item("input", wanted2))) or
537 -- BEGIN OF SPECIAL HANDLING OF COMPASS
538 check_specials(wanted1, wanted2, input1, input2)) and
539 -- END OF SPECIAL HANDLING OF COMPASS
540 (trade.locked == false)) then
541 inv:set_stack("output", 1, inv:get_stack("offered", 1))
542 if sound then
543 minetest.sound_play("mobs_mc_villager_accept", {to_player = name})
545 return true
546 else
547 inv:set_stack("output", 1, ItemStack(""))
548 if sound then
549 minetest.sound_play("mobs_mc_villager_deny", {to_player = name})
551 return false
555 mobs:register_mob("mobs_mc:villager", {
556 type = "npc",
557 hp_min = 20,
558 hp_max = 20,
559 collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.94, 0.3},
560 visual = "mesh",
561 mesh = "mobs_mc_villager.b3d",
562 textures = {
564 "mobs_mc_villager.png",
565 "mobs_mc_villager.png", --hat
568 "mobs_mc_villager_farmer.png",
569 "mobs_mc_villager_farmer.png", --hat
572 "mobs_mc_villager_priest.png",
573 "mobs_mc_villager_priest.png", --hat
576 "mobs_mc_villager_librarian.png",
577 "mobs_mc_villager_librarian.png", --hat
580 "mobs_mc_villager_butcher.png",
581 "mobs_mc_villager_butcher.png", --hat
584 "mobs_mc_villager_smith.png",
585 "mobs_mc_villager_smith.png", --hat
588 visual_size = {x=3, y=3},
589 makes_footstep_sound = true,
590 walk_velocity = 1.2,
591 run_velocity = 2.4,
592 drops = {},
593 sounds = {
594 random = "mobs_mc_villager_noise",
595 death = "mobs_mc_villager_death",
596 damage = "mobs_mc_villager_damage",
597 distance = 16,
599 animation = {
600 stand_speed = 25,
601 stand_start = 40,
602 stand_end = 59,
603 walk_speed = 25,
604 walk_start = 0,
605 walk_end = 40,
606 run_speed = 25,
607 run_start = 0,
608 run_end = 40,
609 die_speed = 15,
610 die_start = 210,
611 die_end = 220,
612 die_loop = false,
614 water_damage = 0,
615 lava_damage = 4,
616 light_damage = 0,
617 view_range = 16,
618 fear_height = 4,
619 on_rightclick = function(self, clicker)
620 local name = clicker:get_player_name()
622 init_profession(self)
623 if self._trades == nil then
624 init_trades(self)
626 update_max_tradenum(self)
627 if self._trades == false then
628 -- Villager has no trades, rightclick is a no-op
629 return
632 player_trading_with[name] = self
634 local inv = minetest.get_inventory({type="detached", name="mobs_mc:trade_"..name})
636 set_trade(self, clicker, inv, 1)
638 show_trade_formspec(name, self)
639 end,
641 on_spawn = function(self)
642 init_profession(self)
643 end,
646 -- Returns a single itemstack in the given inventory to the player's main inventory, or drop it when there's no space left
647 local function return_item(itemstack, dropper, pos, inv_p)
648 if dropper:is_player() then
649 -- Return to main inventory
650 if inv_p:room_for_item("main", itemstack) then
651 inv_p:add_item("main", itemstack)
652 else
653 -- Drop item on the ground
654 local v = dropper:get_look_dir()
655 local p = {x=pos.x, y=pos.y+1.2, z=pos.z}
656 p.x = p.x+(math.random(1,3)*0.2)
657 p.z = p.z+(math.random(1,3)*0.2)
658 local obj = minetest.add_item(p, itemstack)
659 if obj then
660 v.x = v.x*4
661 v.y = v.y*4 + 2
662 v.z = v.z*4
663 obj:setvelocity(v)
664 obj:get_luaentity()._insta_collect = false
667 else
668 -- Fallback for unexpected cases
669 minetest.add_item(pos, itemstack)
671 return itemstack
674 local return_fields = function(player)
675 local name = player:get_player_name()
676 local inv_t = minetest.get_inventory({type="detached", name = "mobs_mc:trade_"..name})
677 local inv_p = player:get_inventory()
678 for i=1, inv_t:get_size("input") do
679 local stack = inv_t:get_stack("input", i)
680 return_item(stack, player, player:get_pos(), inv_p)
681 stack:clear()
682 inv_t:set_stack("input", i, stack)
684 inv_t:set_stack("output", 1, "")
687 minetest.register_on_player_receive_fields(function(player, formname, fields)
688 if string.sub(formname, 1, 14) == "mobs_mc:trade_" then
689 local name = player:get_player_name()
690 if fields.quit then
691 return_fields(player)
692 player_trading_with[name] = nil
693 elseif fields.next_trade or fields.prev_trade then
694 local trader = player_trading_with[name]
695 if not trader or not trader.object:get_luaentity() then
696 return
698 local trades = trader._trades
699 if not trades then
700 return
702 local dir = 1
703 if fields.prev_trade then
704 dir = -1
706 local tradenum = player_tradenum[name] + dir
707 local inv = minetest.get_inventory({type="detached", name="mobs_mc:trade_"..name})
708 set_trade(trader, player, inv, tradenum)
709 update_offer(inv, player, false)
710 show_trade_formspec(name, trader, player_tradenum[name])
713 end)
715 minetest.register_on_leaveplayer(function(player)
716 return_fields(player)
717 player_tradenum[player:get_player_name()] = nil
718 player_trading_with[player:get_player_name()] = nil
719 end)
721 local trade_inventory = {
722 allow_take = function(inv, listname, index, stack, player)
723 if listname == "input" then
724 return stack:get_count()
725 elseif listname == "output" then
726 -- Only allow taking full stack
727 local count = stack:get_count()
728 if count == inv:get_stack(listname, index):get_count() then
729 -- Also update output stack again.
730 -- If input has double the wanted items, the
731 -- output will stay because there will be still
732 -- enough items in input after the trade
733 local wanted1 = inv:get_stack("wanted", 1)
734 local wanted2 = inv:get_stack("wanted", 2)
735 local input1 = inv:get_stack("input", 1)
736 local input2 = inv:get_stack("input", 2)
737 wanted1:set_count(wanted1:get_count()*2)
738 wanted2:set_count(wanted2:get_count()*2)
739 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
740 local special_checks = function(wanted1, input1, input2)
741 if wanted1:get_name() == COMPASS then
742 local compasses = 0
743 if (minetest.get_item_group(input1:get_name(), "compass") ~= 0) then
744 compasses = compasses + input1:get_count()
746 if (minetest.get_item_group(input2:get_name(), "compass") ~= 0) then
747 compasses = compasses + input2:get_count()
749 return compasses >= wanted1:get_count()
751 return false
753 -- END OF SPECIAL HANDLING FOR COMPASS
754 if (inv:contains_item("input", wanted1) and
755 (wanted2:is_empty() or inv:contains_item("input", wanted2)))
756 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
757 or special_checks(wanted1, input1, input2) then
758 -- END OF SPECIAL HANDLING FOR COMPASS
759 return -1
760 else
761 -- If less than double the wanted items,
762 -- remove items from output (final trade,
763 -- input runs empty)
764 return count
766 else
767 return 0
769 else
770 return 0
772 end,
773 allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
774 if from_list == "input" and to_list == "input" then
775 return count
776 elseif from_list == "output" and to_list == "input" then
777 local move_stack = inv:get_stack(from_list, from_index)
778 if inv:get_stack(to_list, to_index):item_fits(move_stack) then
779 return count
782 return 0
783 end,
784 allow_put = function(inv, listname, index, stack, player)
785 if listname == "input" then
786 return stack:get_count()
787 else
788 return 0
790 end,
791 on_put = function(inv, listname, index, stack, player)
792 update_offer(inv, player, true)
793 end,
794 on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
795 if from_list == "output" and to_list == "input" then
796 inv:remove_item("input", inv:get_stack("wanted", 1))
797 local wanted2 = inv:get_stack("wanted", 2)
798 if not wanted2:is_empty() then
799 inv:remove_item("input", inv:get_stack("wanted", 2))
801 minetest.sound_play("mobs_mc_villager_accept", {to_player = player:get_player_name()})
803 update_offer(inv, player, true)
804 end,
805 on_take = function(inv, listname, index, stack, player)
806 local accept
807 local name = player:get_player_name()
808 if listname == "output" then
809 local wanted1 = inv:get_stack("wanted", 1)
810 inv:remove_item("input", wanted1)
811 local wanted2 = inv:get_stack("wanted", 2)
812 if not wanted2:is_empty() then
813 inv:remove_item("input", inv:get_stack("wanted", 2))
815 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
816 if wanted1:get_name() == COMPASS then
817 for n=1, 2 do
818 local input = inv:get_stack("input", n)
819 if minetest.get_item_group(input:get_name(), "compass") ~= 0 then
820 input:set_count(input:get_count() - wanted1:get_count())
821 inv:set_stack("input", n, input)
822 break
826 -- END OF SPECIAL HANDLING FOR COMPASS
827 local trader = player_trading_with[name]
828 local tradenum = player_tradenum[name]
829 local trades
830 if trader and trader._trades then
831 trades = minetest.deserialize(trader._trades)
833 if trades then
834 local trade = trades[tradenum]
835 local unlock_stuff = false
836 if not trade.traded_once then
837 -- Unlock all the things if something was traded
838 -- for the first time ever
839 unlock_stuff = true
840 trade.traded_once = true
841 elseif trade.trade_counter == 0 and math.random(1,5) == 1 then
842 -- Otherwise, 20% chance to unlock if used freshly reset trade
843 unlock_stuff = true
845 if unlock_stuff then
846 -- First-time trade unlock all trades and unlock next trade tier
847 if trade.tier + 1 > trader._max_trade_tier then
848 trader._max_trade_tier = trader._max_trade_tier + 1
849 update_max_tradenum(trader)
850 show_trade_formspec(name, trader, tradenum)
852 for t=1, #trades do
853 trades[t].locked = false
854 trades[t].trade_counter = 0
856 -- Also heal trader for unlocking stuff
857 -- TODO: Replace by Regeneration I
858 trader.health = math.min(trader.hp_max, trader.health + 4)
860 trade.trade_counter = trade.trade_counter + 1
861 if trade.trade_counter >= 12 then
862 trade.locked = true
863 elseif trade.trade_counter >= 2 then
864 local r = math.random(1, math.random(4, 10))
865 if r == 1 then
866 trade.locked = true
870 trader._trades = minetest.serialize(trades)
871 if trade.locked then
872 inv:set_stack("output", 1, "")
873 show_trade_formspec(name, trader, tradenum)
875 else
876 minetest.log("error", "[mobs_mc] Player took item from trader output but player_trading_with or player_tradenum is nil!")
879 accept = true
880 elseif listname == "input" then
881 update_offer(inv, player, false)
883 if accept then
884 minetest.sound_play("mobs_mc_villager_accept", {to_player = name})
885 else
886 minetest.sound_play("mobs_mc_villager_deny", {to_player = name})
888 end,
892 minetest.register_on_joinplayer(function(player)
893 local name = player:get_player_name()
894 player_tradenum[name] = 1
895 player_trading_with[name] = nil
897 -- Create or get player-specific trading inventory
898 local inv = minetest.get_inventory({type="detached", name="mobs_mc:trade_"..name})
899 if not inv then
900 inv = minetest.create_detached_inventory("mobs_mc:trade_"..name, trade_inventory, name)
902 inv:set_size("input", 2)
903 inv:set_size("output", 1)
904 inv:set_size("wanted", 2)
905 inv:set_size("offered", 1)
906 end)
908 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)
910 -- compatibility
911 mobs:alias_mob("mobs:villager", "mobs_mc:villager")
913 -- spawn eggs
914 mobs:register_egg("mobs_mc:villager", S("Villager"), "mobs_mc_spawn_icon_villager.png", 0)
916 if minetest.settings:get_bool("log_mods") then
917 minetest.log("action", "MC mobs loaded")