Fix unlock_stuff being always false in villager
[MineClone/MineClone2.git] / mods / ENTITIES / mobs_mc / villager.lua
blobd1913569ce9ec5d7b19b1e51650a43b3cca4181c
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
9 -- intllib
10 local MP = minetest.get_modpath(minetest.get_current_modname())
11 local S, NS = dofile(MP.."/intllib.lua")
13 -- playername-indexed table containing the previously used tradenum
14 local player_tradenum = {}
15 -- playername-indexed table containing the objectref of trader, if trading formspec is open
16 local player_trading_with = {}
18 --###################
19 --################### VILLAGER
20 --###################
22 -- LIST OF VILLAGER PROFESSIONS AND TRADES
24 -- TECHNICAL RESTRICTIONS (FIXME):
25 -- * You can't use a clock as requested item
26 -- * You can't use a compass as requested item if its stack size > 1
27 -- * You can't use a compass in the second requested slot
28 -- This is a problem in the mcl_compass and mcl_clock mods,
29 -- these items should be implemented as single items, then everything
30 -- will be much easier.
32 local COMPASS = "mcl_compass:compass"
33 if minetest.registered_aliases[COMPASS] then
34 COMPASS = minetest.registered_aliases[COMPASS]
35 end
37 local E1 = { "mcl_core:emerald", 1, 1 } -- one emerald
39 -- Special trades for v6 only
40 local TRADE_V6_RED_SANDSTONE, TRADE_V6_DARK_OAK_SAPLING, TRADE_V6_ACACIA_SAPLING, TRADE_V6_BIRCH_SAPLING
41 if minetest.get_mapgen_setting("mg_name") == "v6" then
42 TRADE_V6_RED_SANDSTONE = { E1, { "mcl_core:redsandstone", 12, 16 } }
43 TRADE_V6_DARK_OAK_SAPLING = { { "mcl_core:emerald", 6, 9 }, { "mcl_core:darksapling", 1, 1 } }
44 TRADE_V6_ACACIA_SAPLING = { { "mcl_core:emerald", 14, 17 }, { "mcl_core:acaciasapling", 1, 1 } }
45 TRADE_V6_BIRCH_SAPLING = { { "mcl_core:emerald", 8, 11 }, { "mcl_core:birchsapling", 1, 1 } }
46 end
48 local professions = {
49 farmer = {
50 name = "Farmer",
51 texture = "mobs_mc_villager_farmer.png",
52 trades = {
54 { { "mcl_farming:wheat_item", 18, 22, }, E1 },
55 { { "mcl_farming:potato_item", 15, 19, }, E1 },
56 { { "mcl_farming:carrot_item", 15, 19, }, E1 },
57 { E1, { "mcl_farming:bread", 2, 4 } },
61 { { "mcl_farming:pumpkin_face", 8, 13 }, E1 },
62 { E1, { "mcl_farming:pumpkin_pie", 2, 3} },
66 { { "mcl_farming:melon", 7, 12 }, E1 },
67 { E1, { "mcl_core:apple", 5, 7 }, },
71 { E1, { "mcl_farming:cookie", 6, 10 } },
72 { E1, { "mcl_cake:cake", 1, 1 } },
73 TRADE_V6_BIRCH_SAPLING,
74 TRADE_V6_DARK_OAK_SAPLING,
75 TRADE_V6_ACACIA_SAPLING,
79 fisherman = {
80 name = "Fisherman",
81 texture = "mobs_mc_villager_farmer.png",
82 trades = {
84 { { "mcl_fishing:fish_raw", 6, 6, "mcl_core:emerald", 1, 1 }, { "mcl_fishing:fish_cooked", 6, 6 } },
85 { { "mcl_mobitems:string", 15, 20 }, E1 },
86 { { "mcl_core:coal_lump", 16, 24 }, E1 },
88 -- TODO: enchanted fishing rod
91 fletcher = {
92 name = "Fletcher",
93 texture = "mobs_mc_villager_farmer.png",
94 trades = {
96 { { "mcl_mobitems:string", 15, 20 }, E1 },
97 { E1, { "mcl_bows:arrow", 8, 12 } },
101 { { "mcl_core:gravel", 10, 10, "mcl_core:emerald", 1, 1 }, { "mcl_core:flint", 6, 10 } },
102 { { "mcl_core:emerald", 2, 3 }, { "mcl_bows:bow", 1, 1 } },
106 shepherd ={
107 name = "Shepherd",
108 texture = "mobs_mc_villager_farmer.png",
109 trades = {
111 { { "mcl_wool:white", 16, 22 }, E1 },
112 { { "mcl_core:emerald", 3, 4 }, { "mcl_tools:shears", 1, 1 } },
116 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:white", 1, 1 } },
117 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:grey", 1, 1 } },
118 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:silver", 1, 1 } },
119 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:black", 1, 1 } },
120 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:yellow", 1, 1 } },
121 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:orange", 1, 1 } },
122 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:red", 1, 1 } },
123 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:magenta", 1, 1 } },
124 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:purple", 1, 1 } },
125 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:blue", 1, 1 } },
126 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:cyan", 1, 1 } },
127 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:lime", 1, 1 } },
128 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:green", 1, 1 } },
129 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:pink", 1, 1 } },
130 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:light_blue", 1, 1 } },
131 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:brown", 1, 1 } },
135 librarian = {
136 name = "Librarian",
137 texture = "mobs_mc_villager_librarian.png",
138 trades = {
140 { { "mcl_core:paper", 24, 36 }, E1 },
141 -- TODO: enchanted book
142 { { "mcl_books:book", 8, 10 }, E1 },
143 { { "mcl_core:emerald", 10, 12 }, { "mcl_compass:compass", 1 ,1 }},
144 { { "mcl_core:emerald", 3, 4 }, { "mcl_books:bookshelf", 1 ,1 }},
148 { { "mcl_books:written_book", 2, 2 }, E1 },
149 { { "mcl_core:emerald", 10, 12 }, { "mcl_clock:clock", 1, 1 } },
150 { E1, { "mcl_core:glass", 3, 5 } },
154 { E1, { "mcl_core:glass", 3, 5 } },
157 -- TODO: 2 enchanted book tiers
160 { { "mcl_core:emerald", 20, 22 }, { "mcl_mobs:nametag", 1, 1 } },
164 cartographer = {
165 name = "Cartographer",
166 texture = "mobs_mc_villager_librarian.png",
167 trades = {
169 { { "mcl_core:paper", 24, 36 }, E1 },
173 -- subject to special checks
174 { { "mcl_compass:compass", 1, 1 }, E1 },
178 -- TODO: replace with empty map
179 { { "mcl_core:emerald", 7, 11}, { "mcl_maps:filled_map", 1, 1 } },
182 -- TODO: special maps
185 armorer = {
186 name = "Armorer",
187 texture = "mobs_mc_villager_smith.png",
188 trades = {
190 { { "mcl_core:coal_lump", 16, 24 }, E1 },
191 { { "mcl_core:emerald", 6, 8 }, { "3d_armor:helmet_iron", 1, 1 } },
195 { { "mcl_core:iron_ingot", 7, 9 }, E1 },
196 { { "mcl_core:emerald", 10, 14 }, { "3d_armor:chestplate_iron", 1, 1 } },
200 { { "mcl_core:diamond", 3, 4 }, E1 },
201 -- TODO: enchant
202 { { "mcl_core:emerald", 16, 19 }, { "3d_armor:chestplate_diamond", 1, 1 } },
206 { { "mcl_core:emerald", 5, 7 }, { "3d_armor:boots_chain", 1, 1 } },
207 { { "mcl_core:emerald", 9, 11 }, { "3d_armor:leggings_chain", 1, 1 } },
208 { { "mcl_core:emerald", 5, 7 }, { "3d_armor:helmet_chain", 1, 1 } },
209 { { "mcl_core:emerald", 11, 15 }, { "3d_armor:chestplate_chain", 1, 1 } },
213 leatherworker = {
214 name = "Leatherworker",
215 texture = "mobs_mc_villager_butcher.png",
216 trades = {
218 { { "mcl_mobitems:leather", 9, 12 }, E1 },
219 { { "mcl_core:emerald", 2, 4 }, { "3d_armor:leggings_leather", 2, 4 } },
223 -- TODO: enchant
224 { { "mcl_core:emerald", 7, 12 }, { "3d_armor:chestplate_leather", 1, 1 } },
228 { { "mcl_core:emerald", 8, 10 }, { "mcl_mobitems:saddle", 1, 1 } },
232 butcher = {
233 name = "Butcher",
234 texture = "mobs_mc_villager_butcher.png",
235 trades = {
237 { { "mcl_mobitems:beef", 14, 18 }, E1 },
238 { { "mcl_mobitems:chicken", 14, 18 }, E1 },
242 { { "mcl_core:coal_lump", 16, 24 }, E1 },
243 { E1, { "mcl_mobitems:cooked_beef", 5, 7 } },
244 { E1, { "mcl_mobitems:cooked_chicken", 6, 8 } },
248 weapon_smith = {
249 name = "Weapon Smith",
250 texture = "mobs_mc_villager_smith.png",
251 trades = {
253 { { "mcl_core:coal_lump", 16, 24 }, E1 },
254 { { "mcl_core:emerald", 6, 8 }, { "mcl_tools:axe_iron", 1, 1 } },
258 { { "mcl_core:iron_ingot", 7, 9 }, E1 },
259 -- TODO: enchant
260 { { "mcl_core:emerald", 9, 10 }, { "mcl_tools:sword_iron", 1, 1 } },
264 { { "mcl_core:diamond", 3, 4 }, E1 },
265 -- TODO: enchant
266 { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:sword_diamond", 1, 1 } },
267 -- TODO: enchant
268 { { "mcl_core:emerald", 9, 12 }, { "mcl_tools:axe_diamond", 1, 1 } },
272 tool_smith = {
273 name = "Tool Smith",
274 texture = "mobs_mc_villager_smith.png",
275 trades = {
277 { { "mcl_core:coal_lump", 16, 24 }, E1 },
278 -- TODO: enchant
279 { { "mcl_core:emerald", 5, 7 }, { "mcl_tools:shovel_iron", 1, 1 } },
283 { { "mcl_core:iron_ingot", 7, 9 }, E1 },
284 -- TODO: enchant
285 { { "mcl_core:emerald", 9, 11 }, { "mcl_tools:pick_iron", 1, 1 } },
289 { { "mcl_core:diamond", 3, 4 }, E1 },
290 -- TODO: enchant
291 { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:pick_diamond", 1, 1 } },
295 cleric = {
296 name = "Cleric",
297 texture = "mobs_mc_villager_priest.png",
298 trades = {
300 { { "mcl_mobitems:rotten_flesh", 36, 40 }, E1 },
301 { { "mcl_core:gold_ingot", 8, 10 }, E1 },
305 { E1, { "mesecons:redstone", 1, 4 } },
306 { E1, { "mcl_dye:blue", 1, 2 } },
310 TRADE_V6_RED_SANDSTONE,
311 { E1, { "mcl_nether:glowstone", 1, 3 } },
312 { { "mcl_core:emerald", 4, 7 }, { "mcl_throwing:ender_pearl", 1, 1 } },
315 -- TODO: Bottle 'o enchanting
318 nitwit = {
319 name = "Nitwit",
320 texture = "mobs_mc_villager.png",
321 -- No trades for nitwit
322 trades = nil,
326 local profession_names = {}
327 for id, _ in pairs(professions) do
328 table.insert(profession_names, id)
331 local update_max_tradenum = function(self)
332 if not self._trades then
333 return
335 local trades = minetest.deserialize(self._trades)
336 for t=1, #trades do
337 local trade = trades[t]
338 if trade.tier > self._max_trade_tier then
339 self._max_tradenum = t - 1
340 return
343 self._max_tradenum = #trades
346 local init_profession = function(self)
347 if not self._profession then
348 -- Select random profession from all professions with matching clothing
349 local texture = self.base_texture[1]
350 local matches = {}
351 for prof_id, prof in pairs(professions) do
352 if texture == prof.texture then
353 table.insert(matches, prof_id)
356 local p = math.random(1, #matches)
357 self._profession = matches[p]
359 if not self._max_trade_tier then
360 self._max_trade_tier = 1
362 if not self._locked_trades then
363 self._locked_trades = 0
367 local init_trades = function(self, inv)
368 local profession = professions[self._profession]
369 local trade_tiers = profession.trades
370 if trade_tiers == nil then
371 -- Empty trades
372 self._trades = false
373 return
376 local max_tier = #trade_tiers
377 local trades = {}
378 for tiernum=1, max_tier do
379 local tier = trade_tiers[tiernum]
380 for tradenum=1, #tier do
381 local trade = tier[tradenum]
382 local wanted1_item = trade[1][1]
383 local wanted1_count = math.random(trade[1][2], trade[1][3])
384 local offered_item = trade[2][1]
385 local offered_count = math.random(trade[2][2], trade[2][3])
387 local wanted = { wanted1_item .. " " ..wanted1_count }
388 if trade[1][4] then
389 local wanted2_item = trade[1][4]
390 local wanted2_count = math.random(trade[1][5], trade[1][6])
391 table.insert(wanted, wanted2_item .. " " ..wanted2_count)
394 table.insert(trades, {
395 wanted = wanted,
396 offered = offered_item .. " " .. offered_count,
397 tier = tiernum, -- tier of this trade
398 traded_once = false, -- true if trade was traded at least once
399 trade_counter = 0, -- how often the this trade was mate after the last time it got unlocked
400 locked = false, -- if this trade is locked. Locked trades can't be used
404 self._trades = minetest.serialize(trades)
407 local set_trade = function(trader, player, inv, concrete_tradenum)
408 local trades = minetest.deserialize(trader._trades)
409 if not trades then
410 init_trades(trader)
411 trades = minetest.deserialize(trader._trades)
412 if not trades then
413 minetest.log("error", "[mobs_mc] Failed to select villager trade!")
414 return
417 local name = player:get_player_name()
419 -- Stop tradenum from advancing into locked tiers or out-of-range areas
420 if concrete_tradenum > trader._max_tradenum then
421 concrete_tradenum = trader._max_tradenum
422 elseif concrete_tradenum < 1 then
423 concrete_tradenum = 1
425 player_tradenum[name] = concrete_tradenum
426 local trade = trades[concrete_tradenum]
427 inv:set_stack("wanted", 1, ItemStack(trade.wanted[1]))
428 inv:set_stack("offered", 1, ItemStack(trade.offered))
429 if trade.wanted[2] then
430 local wanted2 = ItemStack(trade.wanted[2])
431 inv:set_stack("wanted", 2, wanted2)
432 else
433 inv:set_stack("wanted", 2, "")
438 local function show_trade_formspec(playername, trader, tradenum)
439 if not trader._trades then
440 return
442 if not tradenum then
443 tradenum = 1
445 local trades = minetest.deserialize(trader._trades)
446 local trade = trades[tradenum]
447 local profession = professions[trader._profession].name
448 local disabled_img = ""
449 if trade.locked then
450 disabled_img = "image[4.3,2.52;1,1;mobs_mc_trading_formspec_disabled.png]"..
451 "image[4.3,1.1;1,1;mobs_mc_trading_formspec_disabled.png]"
453 local tradeinv_name = "mobs_mc:trade_"..playername
454 local tradeinv = minetest.formspec_escape("detached:"..tradeinv_name)
456 local b_prev, b_next = "", ""
457 if #trades > 1 then
458 if tradenum > 1 then
459 b_prev = "button[1,1;0.5,1;prev_trade;<]"
461 if tradenum < trader._max_tradenum then
462 b_next = "button[7.26,1;0.5,1;next_trade;>]"
466 local formspec =
467 "size[9,8.75]"
468 .."background[-0.19,-0.25;9.41,9.49;mobs_mc_trading_formspec_bg.png]"
469 ..disabled_img
470 ..mcl_vars.inventory_header
471 .."label[4,0;"..minetest.formspec_escape(profession).."]"
472 .."list[current_player;main;0,4.5;9,3;9]"
473 .."list[current_player;main;0,7.74;9,1;]"
474 ..b_prev..b_next
475 .."list["..tradeinv..";wanted;2,1;2,1;]"
476 .."list["..tradeinv..";offered;5.76,1;1,1;]"
477 .."list["..tradeinv..";input;2,2.5;2,1;]"
478 .."list["..tradeinv..";output;5.76,2.55;1,1;]"
479 .."listring["..tradeinv..";output]"
480 .."listring[current_player;main]"
481 .."listring["..tradeinv..";input]"
482 .."listring[current_player;main]"
483 minetest.sound_play("mobs_mc_villager_trade", {to_player = playername})
484 minetest.show_formspec(playername, tradeinv_name, formspec)
487 local update_offer = function(inv, player, sound)
488 local name = player:get_player_name()
489 local trader = player_trading_with[name]
490 local tradenum = player_tradenum[name]
491 if not trader or not tradenum then
492 return false
494 local trades = minetest.deserialize(trader._trades)
495 if not trades then
496 return false
498 local trade = trades[tradenum]
499 if not trade then
500 return false
502 local wanted1, wanted2 = inv:get_stack("wanted", 1), inv:get_stack("wanted", 2)
503 local input1, input2 = inv:get_stack("input", 1), inv:get_stack("input", 2)
505 -- BEGIN OF SPECIAL HANDLING OF COMPASS
506 -- These 2 functions are a complicated check to check if the input contains a
507 -- special item which we cannot check directly against their name, like
508 -- compass.
509 -- TODO: Remove these check functions when compass and clock are implemented
510 -- as single items.
511 local check_special = function(special_item, group, wanted1, wanted2, input1, input2)
512 if minetest.registered_aliases[special_item] then
513 special_item = minetest.registered_aliases[special_item]
515 if wanted1:get_name() == special_item then
516 local check_input = function(input, wanted, group)
517 return minetest.get_item_group(input:get_name(), group) ~= 0 and input:get_count() >= wanted:get_count()
519 if check_input(input1, wanted1, group) then
520 return true
521 elseif check_input(input2, wanted1, group) then
522 return true
523 else
524 return false
527 return false
529 -- Apply above function to all items which we consider special.
530 -- This function succeeds if ANY item check succeeds.
531 local check_specials = function(wanted1, wanted2, input1, input2)
532 return check_special(COMPASS, "compass", wanted1, wanted2, input1, input2)
534 -- END OF SPECIAL HANDLING OF COMPASS
536 if (
537 ((inv:contains_item("input", wanted1) and
538 (wanted2:is_empty() or inv:contains_item("input", wanted2))) or
539 -- BEGIN OF SPECIAL HANDLING OF COMPASS
540 check_specials(wanted1, wanted2, input1, input2)) and
541 -- END OF SPECIAL HANDLING OF COMPASS
542 (trade.locked == false)) then
543 inv:set_stack("output", 1, inv:get_stack("offered", 1))
544 if sound then
545 minetest.sound_play("mobs_mc_villager_accept", {to_player = name})
547 return true
548 else
549 inv:set_stack("output", 1, ItemStack(""))
550 if sound then
551 minetest.sound_play("mobs_mc_villager_deny", {to_player = name})
553 return false
557 mobs:register_mob("mobs_mc:villager", {
558 type = "npc",
559 hp_min = 20,
560 hp_max = 20,
561 collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.94, 0.3},
562 visual = "mesh",
563 mesh = "mobs_mc_villager.b3d",
564 textures = {
566 "mobs_mc_villager.png",
567 "mobs_mc_villager.png", --hat
570 "mobs_mc_villager_farmer.png",
571 "mobs_mc_villager_farmer.png", --hat
574 "mobs_mc_villager_priest.png",
575 "mobs_mc_villager_priest.png", --hat
578 "mobs_mc_villager_librarian.png",
579 "mobs_mc_villager_librarian.png", --hat
582 "mobs_mc_villager_butcher.png",
583 "mobs_mc_villager_butcher.png", --hat
586 "mobs_mc_villager_smith.png",
587 "mobs_mc_villager_smith.png", --hat
590 visual_size = {x=3, y=3},
591 makes_footstep_sound = true,
592 walk_velocity = 1.2,
593 run_velocity = 2.4,
594 drops = {},
595 sounds = {
596 random = "mobs_mc_villager_noise",
597 death = "mobs_mc_villager_death",
598 damage = "mobs_mc_villager_damage",
599 distance = 16,
601 animation = {
602 stand_speed = 25,
603 stand_start = 40,
604 stand_end = 59,
605 walk_speed = 25,
606 walk_start = 0,
607 walk_end = 40,
608 run_speed = 25,
609 run_start = 0,
610 run_end = 40,
611 die_speed = 15,
612 die_start = 210,
613 die_end = 220,
614 die_loop = false,
616 water_damage = 0,
617 lava_damage = 4,
618 light_damage = 0,
619 view_range = 16,
620 fear_height = 4,
621 on_rightclick = function(self, clicker)
622 local name = clicker:get_player_name()
624 init_profession(self)
625 if self._trades == nil then
626 init_trades(self)
628 update_max_tradenum(self)
629 if self._trades == false then
630 -- Villager has no trades, rightclick is a no-op
631 return
634 player_trading_with[name] = self
636 local inv = minetest.get_inventory({type="detached", name="mobs_mc:trade_"..name})
638 set_trade(self, clicker, inv, 1)
640 show_trade_formspec(name, self)
641 end,
643 on_spawn = function(self)
644 init_profession(self)
645 end,
648 -- Returns a single itemstack in the given inventory to the player's main inventory, or drop it when there's no space left
649 local function return_item(itemstack, dropper, pos, inv_p)
650 if dropper:is_player() then
651 -- Return to main inventory
652 if inv_p:room_for_item("main", itemstack) then
653 inv_p:add_item("main", itemstack)
654 else
655 -- Drop item on the ground
656 local v = dropper:get_look_dir()
657 local p = {x=pos.x, y=pos.y+1.2, z=pos.z}
658 p.x = p.x+(math.random(1,3)*0.2)
659 p.z = p.z+(math.random(1,3)*0.2)
660 local obj = minetest.add_item(p, itemstack)
661 if obj then
662 v.x = v.x*4
663 v.y = v.y*4 + 2
664 v.z = v.z*4
665 obj:setvelocity(v)
666 obj:get_luaentity()._insta_collect = false
669 else
670 -- Fallback for unexpected cases
671 minetest.add_item(pos, itemstack)
673 return itemstack
676 local return_fields = function(player)
677 local name = player:get_player_name()
678 local inv_t = minetest.get_inventory({type="detached", name = "mobs_mc:trade_"..name})
679 local inv_p = player:get_inventory()
680 for i=1, inv_t:get_size("input") do
681 local stack = inv_t:get_stack("input", i)
682 return_item(stack, player, player:get_pos(), inv_p)
683 stack:clear()
684 inv_t:set_stack("input", i, stack)
686 inv_t:set_stack("output", 1, "")
689 minetest.register_on_player_receive_fields(function(player, formname, fields)
690 if string.sub(formname, 1, 14) == "mobs_mc:trade_" then
691 local name = player:get_player_name()
692 if fields.quit then
693 return_fields(player)
694 player_trading_with[name] = nil
695 elseif fields.next_trade or fields.prev_trade then
696 local trader = player_trading_with[name]
697 if not trader or not trader.object:get_luaentity() then
698 return
700 local trades = trader._trades
701 if not trades then
702 return
704 local dir = 1
705 if fields.prev_trade then
706 dir = -1
708 local tradenum = player_tradenum[name] + dir
709 local inv = minetest.get_inventory({type="detached", name="mobs_mc:trade_"..name})
710 set_trade(trader, player, inv, tradenum)
711 update_offer(inv, player, false)
712 show_trade_formspec(name, trader, player_tradenum[name])
715 end)
717 minetest.register_on_leaveplayer(function(player)
718 return_fields(player)
719 player_tradenum[player:get_player_name()] = nil
720 player_trading_with[player:get_player_name()] = nil
721 end)
723 local trade_inventory = {
724 allow_take = function(inv, listname, index, stack, player)
725 if listname == "input" then
726 return stack:get_count()
727 elseif listname == "output" then
728 -- Only allow taking full stack
729 local count = stack:get_count()
730 if count == inv:get_stack(listname, index):get_count() then
731 -- Also update output stack again.
732 -- If input has double the wanted items, the
733 -- output will stay because there will be still
734 -- enough items in input after the trade
735 local wanted1 = inv:get_stack("wanted", 1)
736 local wanted2 = inv:get_stack("wanted", 2)
737 local input1 = inv:get_stack("input", 1)
738 local input2 = inv:get_stack("input", 2)
739 wanted1:set_count(wanted1:get_count()*2)
740 wanted2:set_count(wanted2:get_count()*2)
741 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
742 local special_checks = function(wanted1, input1, input2)
743 if wanted1:get_name() == COMPASS then
744 local compasses = 0
745 if (minetest.get_item_group(input1:get_name(), "compass") ~= 0) then
746 compasses = compasses + input1:get_count()
748 if (minetest.get_item_group(input2:get_name(), "compass") ~= 0) then
749 compasses = compasses + input2:get_count()
751 return compasses >= wanted1:get_count()
753 return false
755 -- END OF SPECIAL HANDLING FOR COMPASS
756 if (inv:contains_item("input", wanted1) and
757 (wanted2:is_empty() or inv:contains_item("input", wanted2)))
758 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
759 or special_checks(wanted1, input1, input2) then
760 -- END OF SPECIAL HANDLING FOR COMPASS
761 return -1
762 else
763 -- If less than double the wanted items,
764 -- remove items from output (final trade,
765 -- input runs empty)
766 return count
768 else
769 return 0
771 else
772 return 0
774 end,
775 allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
776 if from_list == "input" and to_list == "input" then
777 return count
778 elseif from_list == "output" and to_list == "input" then
779 local move_stack = inv:get_stack(from_list, from_index)
780 if inv:get_stack(to_list, to_index):item_fits(move_stack) then
781 return count
784 return 0
785 end,
786 allow_put = function(inv, listname, index, stack, player)
787 if listname == "input" then
788 return stack:get_count()
789 else
790 return 0
792 end,
793 on_put = function(inv, listname, index, stack, player)
794 update_offer(inv, player, true)
795 end,
796 on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
797 if from_list == "output" and to_list == "input" then
798 inv:remove_item("input", inv:get_stack("wanted", 1))
799 local wanted2 = inv:get_stack("wanted", 2)
800 if not wanted2:is_empty() then
801 inv:remove_item("input", inv:get_stack("wanted", 2))
803 minetest.sound_play("mobs_mc_villager_accept", {to_player = player:get_player_name()})
805 update_offer(inv, player, true)
806 end,
807 on_take = function(inv, listname, index, stack, player)
808 local accept
809 local name = player:get_player_name()
810 if listname == "output" then
811 local wanted1 = inv:get_stack("wanted", 1)
812 inv:remove_item("input", wanted1)
813 local wanted2 = inv:get_stack("wanted", 2)
814 if not wanted2:is_empty() then
815 inv:remove_item("input", inv:get_stack("wanted", 2))
817 -- BEGIN OF SPECIAL HANDLING FOR COMPASS
818 if wanted1:get_name() == COMPASS then
819 for n=1, 2 do
820 local input = inv:get_stack("input", n)
821 if minetest.get_item_group(input:get_name(), "compass") ~= 0 then
822 input:set_count(input:get_count() - wanted1:get_count())
823 inv:set_stack("input", n, input)
824 break
828 -- END OF SPECIAL HANDLING FOR COMPASS
829 local trader = player_trading_with[name]
830 local tradenum = player_tradenum[name]
831 local trades
832 if trader and trader._trades then
833 trades = minetest.deserialize(trader._trades)
835 if trades then
836 local trade = trades[tradenum]
837 local unlock_stuff = false
838 if not trade.traded_once then
839 -- Unlock all the things if something was traded
840 -- for the first time ever
841 unlock_stuff = true
842 trade.traded_once = true
843 elseif trade.trade_counter == 0 and math.random(1,5) == 1 then
844 -- Otherwise, 20% chance to unlock if used freshly reset trade
845 unlock_stuff = true
847 local update_formspec = false
848 if unlock_stuff then
849 -- First-time trade unlock all trades and unlock next trade tier
850 if trade.tier + 1 > trader._max_trade_tier then
851 trader._max_trade_tier = trader._max_trade_tier + 1
852 update_max_tradenum(trader)
853 update_formspec = true
855 for t=1, #trades do
856 trades[t].locked = false
857 trades[t].trade_counter = 0
859 trader._locked_trades = 0
860 -- Also heal trader for unlocking stuff
861 -- TODO: Replace by Regeneration I
862 trader.health = math.min(trader.hp_max, trader.health + 4)
864 trade.trade_counter = trade.trade_counter + 1
865 -- Semi-randomly lock trade for repeated trade (not if there's only 1 trade)
866 if trader._max_tradenum > 1 then
867 if trade.trade_counter >= 12 then
868 trade.locked = true
869 elseif trade.trade_counter >= 2 then
870 local r = math.random(1, math.random(4, 10))
871 if r == 1 then
872 trade.locked = true
877 if trade.locked then
878 inv:set_stack("output", 1, "")
879 update_formspec = true
880 trader._locked_trades = trader._locked_trades + 1
881 -- Check if we managed to lock ALL available trades. Rare but possible.
882 if trader._locked_trades >= trader._max_tradenum then
883 -- Emergency unlock! Unlock all other trades except the current one
884 for t=1, #trades do
885 if t ~= tradenum then
886 trades[t].locked = false
887 trades[t].trade_counter = 0
890 trader._locked_trades = 1
891 -- Also heal trader for unlocking stuff
892 -- TODO: Replace by Regeneration I
893 trader.health = math.min(trader.hp_max, trader.health + 4)
896 trader._trades = minetest.serialize(trades)
897 if update_formspec then
898 show_trade_formspec(name, trader, tradenum)
900 else
901 minetest.log("error", "[mobs_mc] Player took item from trader output but player_trading_with or player_tradenum is nil!")
904 accept = true
905 elseif listname == "input" then
906 update_offer(inv, player, false)
908 if accept then
909 minetest.sound_play("mobs_mc_villager_accept", {to_player = name})
910 else
911 minetest.sound_play("mobs_mc_villager_deny", {to_player = name})
913 end,
917 minetest.register_on_joinplayer(function(player)
918 local name = player:get_player_name()
919 player_tradenum[name] = 1
920 player_trading_with[name] = nil
922 -- Create or get player-specific trading inventory
923 local inv = minetest.get_inventory({type="detached", name="mobs_mc:trade_"..name})
924 if not inv then
925 inv = minetest.create_detached_inventory("mobs_mc:trade_"..name, trade_inventory, name)
927 inv:set_size("input", 2)
928 inv:set_size("output", 1)
929 inv:set_size("wanted", 2)
930 inv:set_size("offered", 1)
931 end)
933 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)
935 -- compatibility
936 mobs:alias_mob("mobs:villager", "mobs_mc:villager")
938 -- spawn eggs
939 mobs:register_egg("mobs_mc:villager", S("Villager"), "mobs_mc_spawn_icon_villager.png", 0)
941 if minetest.settings:get_bool("log_mods") then
942 minetest.log("action", "MC mobs loaded")