Store available trades in villager entity
[MineClone/MineClone2.git] / mods / ENTITIES / mobs_mc / villager.lua
blob6611393a54cef18662529385603ba963359f5752
1 --MCmobs v0.4
2 --maikerumine
3 --made for MC like Survival game
4 --License for code WTFPL and otherwise stated in readmes
6 -- intllib
7 local MP = minetest.get_modpath(minetest.get_current_modname())
8 local S, NS = dofile(MP.."/intllib.lua")
10 --###################
11 --################### VILLAGER
12 --###################
14 -- LIST OF VILLAGES PROFESSIONS AND TRADES
15 local E1 = { "mcl_core:emerald", 1, 1 } -- one emerald
16 local professions = {
17 farmer = {
18 id = "farmer",
19 name = "Farmer",
20 trades = {
22 { { "mcl_farming:wheat_item", 18, 22, }, E1 },
23 { { "mcl_farming:potato_item", 15, 15, }, E1 },
24 { { "mcl_farming:carrot_item", 15, 19, }, E1 },
25 { E1, { "mcl_farming:bread", 2, 4 } },
29 { { "mcl_farming:pumpkin_face", 8, 13 }, E1 },
30 { E1, { "mcl_farming:pumpkin_pie", 2, 3} },
34 { { "mcl_farming:melon", 7, 12 }, E1 },
35 { E1, { "mcl_core:apple", 5, 7 }, },
39 { E1, { "mcl_farming:cookie", 6, 10 } },
40 { E1, { "mcl_cake:cake", 1, 1 } },
44 fisherman = {
45 id = "fisherman",
46 name = "Fisherman",
47 trades = {
49 { { "mcl_fishing:fish_raw", 6, 6, "mcl_core:emerald", 1, 1 }, { "mcl_fishing:fish_cooked", 6, 6 } },
50 { { "mcl_mobitems:string", 15, 20 }, E1 },
51 { { "mcl_core:coal_lump", 16, 24 }, E1 },
53 -- TODO: enchanted fishing rod
56 fletcher = {
57 id = "fletcher",
58 name = "Fletcher",
59 trades = {
61 { { "mcl_mobitems:string", 15, 20 }, E1 },
62 { E1, { "mcl_bows:arrow", 8, 12 } },
66 { { "mcl_core:gravel", 10, 10, "mcl_core:emerald", 1, 1 }, { "mcl_core:flint", 6, 10 } },
67 { { "mcl_core:emerald", 2, 3 }, { "mcl_bows:bow", 1, 1 } },
71 shepherd ={
72 id = "shepherd",
73 name = "Shepherd",
74 trades = {
76 { { "mcl_wool:white", 16, 22 }, E1 },
77 { { "mcl_core:emerald", 3, 4 }, { "mcl_tools:shears", 1, 1 } },
81 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:white", 1, 1 } },
82 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:grey", 1, 1 } },
83 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:silver", 1, 1 } },
84 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:yellow", 1, 1 } },
85 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:red", 1, 1 } },
86 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:purple", 1, 1 } },
87 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:blue", 1, 1 } },
88 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:light_blue", 1, 1 } },
89 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:brown", 1, 1 } },
90 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:lime", 1, 1 } },
91 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:green", 1, 1 } },
92 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:magenta", 1, 1 } },
93 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:black", 1, 1 } },
94 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:cyan", 1, 1 } },
95 { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:pink", 1, 1 } },
99 librarian = {
100 id = "librarian",
101 name = "Librarian",
102 trades = {
104 { { "mcl_core:paper", 24, 36 }, E1 },
105 -- TODO: enchanted book
106 { { "mcl_books:book", 8, 10 }, E1 },
107 { { "mcl_core:emerald", 10, 12 }, { "mcl_compass:compass", 1 ,1 }},
108 { { "mcl_core:emerald", 3, 4 }, { "mcl_books:bookshelf", 1 ,1 }},
112 { { "mcl_books:written_book", 2, 2 }, E1 },
113 { { "mcl_core:emerald", 10, 12 }, { "mcl_clock:clock", 1, 1 } },
114 { E1, { "mcl_core:glass", 3, 5 } },
118 { E1, { "mcl_core:glass", 3, 5 } },
121 -- TODO: 2 enchanted book tiers
124 { { "mcl_core:emerald", 20, 22 }, { "mcl_mobs:nametag", 1, 1 } },
128 cartographer = {
129 id = "cartographer",
130 name = "Cartographer",
131 trades = {
133 { { "mcl_core:paper", 24, 36 }, E1 },
137 { { "mcl_compass:compass", 1, 1 }, E1 },
141 -- TODO: replace with empty map
142 { { "mcl_core:emerald", 7, 11}, { "mcl_maps:filled_map", 1, 1 } },
145 -- TODO: special maps
148 armorer = {
149 id = "armorer",
150 name = "Armorer",
151 trades = {
153 { { "mcl_core:coal_lump", 16, 24 }, E1 },
154 { { "mcl_core:emerald", 6, 8 }, { "3d_armor:helmet_iron", 1, 1 } },
158 { { "mcl_core:iron_ingot", 7, 9 }, E1 },
159 { { "mcl_core:emerald", 10, 14 }, { "3d_armor:chestplate_iron", 1, 1 } },
163 { { "mcl_core:diamond", 3, 4 }, E1 },
164 -- TODO: enchant
165 { { "mcl_core:emerald", 16, 19 }, { "3d_armor:chestplate_diamond", 1, 1 } },
169 { { "mcl_core:emerald", 5, 7 }, { "3d_armor:boots_chain", 1, 1 } },
170 { { "mcl_core:emerald", 9, 11 }, { "3d_armor:leggings_chain", 1, 1 } },
171 { { "mcl_core:emerald", 5, 7 }, { "3d_armor:helmet_chain", 1, 1 } },
172 { { "mcl_core:emerald", 11, 15 }, { "3d_armor:chestplate_chain", 1, 1 } },
176 leatherworker = {
177 name = "Leatherworker",
178 trades = {
180 { { "mcl_mobitems:leather", 9, 12 }, E1 },
181 { { "mcl_core:emerald", 2, 4 }, { "3d_armor:leggings_leather", 2, 4 } },
185 -- TODO: enchant
186 { { "mcl_core:emerald", 7, 12 }, { "3d_armor:chestplate_leather", 1, 1 } },
190 { { "mcl_core:emerald", 8, 10 }, { "mcl_mobitems:saddle", 1, 1 } },
194 butcher = {
195 name = "Butcher",
196 trades = {
198 { { "mcl_mobitems:beef", 14, 18 }, E1 },
199 { { "mcl_mobitems:chicken", 14, 18 }, E1 },
203 { { "mcl_core:coal_lump", 16, 24 }, E1 },
204 { E1, { "mcl_mobitems:cooked_beef", 5, 7 } },
205 { E1, { "mcl_mobitems:cooked_chicken", 6, 8 } },
209 weapon_smith = {
210 name = "Weapon Smith",
211 trades = {
213 { { "mcl_core:coal_lump", 16, 24 }, E1 },
214 { { "mcl_core:emerald", 6, 8 }, { "mcl_tools:axe_iron", 1, 1 } },
218 { { "mcl_core:iron_ingot", 7, 9 }, E1 },
219 -- TODO: enchant
220 { { "mcl_core:emerald", 9, 10 }, { "mcl_tools:sword_iron", 1, 1 } },
224 { { "mcl_core:diamond", 3, 4 }, E1 },
225 -- TODO: enchant
226 { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:sword_diamond", 1, 1 } },
227 -- TODO: enchant
228 { { "mcl_core:emerald", 9, 12 }, { "mcl_tools:axe_diamond", 1, 1 } },
232 tool_smith = {
233 name = "Tool Smith",
234 trades = {
236 { { "mcl_core:coal_lump", 16, 24 }, E1 },
237 -- TODO: enchant
238 { { "mcl_core:emerald", 5, 7 }, { "mcl_tools:shovel_iron", 1, 1 } },
242 { { "mcl_core:iron_ingot", 7, 9 }, E1 },
243 -- TODO: enchant
244 { { "mcl_core:emerald", 9, 11 }, { "mcl_tools:pick_iron", 1, 1 } },
248 { { "mcl_core:diamond", 3, 4 }, E1 },
249 -- TODO: enchant
250 { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:pick_diamond", 1, 1 } },
254 cleric = {
255 name = "Cleric",
256 trades = {
258 { { "mcl_mobitems:rotten_flesh", 36, 40 }, E1 },
259 { { "mcl_core:gold_ingot", 8, 10 }, E1 },
263 { E1, { "mesecons:redstone", 1, 4 } },
264 { E1, { "mcl_dye:blue", 1, 2 } },
268 { E1, { "mcl_nether:glowstone", 1, 3 } },
269 { { "mcl_core:emerald", 4, 7 }, { "mcl_throwing:ender_pearl", 1, 1 } },
272 -- TODO: Bottle 'o enchanting
275 -- TODO: Nitwit
278 local profession_names = {}
279 for id, _ in pairs(professions) do
280 table.insert(profession_names, id)
283 local init_profession = function(self)
284 if not self._profession then
285 local p = math.random(1, #profession_names)
286 self._profession = profession_names[p]
288 if not self._max_trade_tier then
289 -- TODO: Randomize
290 self._max_trade_tier = 10
294 local update_trades = function(self, inv)
295 local profession = professions[self._profession]
296 local trade_tiers = profession.trades
297 if trade_tiers == nil then
298 return
301 local max_tier = math.min(#trade_tiers, self._max_trade_tier)
302 local trades = {}
303 for tiernum=1, max_tier do
304 local tier = trade_tiers[tiernum]
305 for tradenum=1, #tier do
306 local trade = tier[tradenum]
307 local wanted1_item = trade[1][1]
308 local wanted1_count = math.random(trade[1][2], trade[1][3])
309 local offered_item = trade[2][1]
310 local offered_count = math.random(trade[2][2], trade[2][3])
312 local wanted = { wanted1_item .. " " ..wanted1_count }
313 if trade[1][4] then
314 local wanted2_item = trade[1][4]
315 local wanted2_count = math.random(trade[1][5], trade[1][6])
316 table.insert(wanted, wanted2_item .. " " ..wanted2_count)
319 table.insert(trades, {
320 wanted = wanted,
321 offered = offered_item .. " " .. offered_count,
322 tier = tiernum,
326 self._trades = minetest.serialize(trades)
329 local set_trade = function(self, inv, concrete_tradenum)
330 local trades = minetest.deserialize(self._trades)
331 if not trades then
332 update_trades(self)
333 trades = minetest.deserialize(self._trades)
334 if not trades then
335 minetest.log("error", "[mobs_mc] Failed to select villager trade!")
336 return
340 if concrete_tradenum > #trades then
341 concrete_tradenum = #trades
343 local trade = trades[concrete_tradenum]
344 inv:set_stack("wanted", 1, ItemStack(trade.wanted[1]))
345 inv:set_stack("offered", 1, ItemStack(trade.offered))
346 if trade.wanted[2] then
347 local wanted2 = ItemStack(trade.wanted[2])
348 inv:set_stack("wanted", 2, wanted2)
349 else
350 inv:set_stack("wanted", 2, "")
355 mobs:register_mob("mobs_mc:villager", {
356 type = "npc",
357 hp_min = 20,
358 hp_max = 20,
359 collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.94, 0.3},
360 visual = "mesh",
361 mesh = "mobs_mc_villager.b3d",
362 textures = {
364 "mobs_mc_villager.png",
365 "mobs_mc_villager.png", --hat
368 "mobs_mc_villager_farmer.png",
369 "mobs_mc_villager_farmer.png", --hat
372 "mobs_mc_villager_priest.png",
373 "mobs_mc_villager_priest.png", --hat
376 "mobs_mc_villager_librarian.png",
377 "mobs_mc_villager_librarian.png", --hat
380 "mobs_mc_villager_butcher.png",
381 "mobs_mc_villager_butcher.png", --hat
384 "mobs_mc_villager_smith.png",
385 "mobs_mc_villager_smith.png", --hat
388 visual_size = {x=3, y=3},
389 makes_footstep_sound = true,
390 walk_velocity = 1.2,
391 run_velocity = 2.4,
392 drops = {},
393 sounds = {
394 random = "mobs_mc_villager_noise",
395 death = "mobs_mc_villager_death",
396 damage = "mobs_mc_villager_damage",
397 distance = 16,
399 animation = {
400 stand_speed = 25,
401 stand_start = 40,
402 stand_end = 59,
403 walk_speed = 25,
404 walk_start = 0,
405 walk_end = 40,
406 run_speed = 25,
407 run_start = 0,
408 run_end = 40,
409 die_speed = 15,
410 die_start = 210,
411 die_end = 220,
412 die_loop = false,
414 water_damage = 0,
415 lava_damage = 4,
416 light_damage = 0,
417 view_range = 16,
418 fear_height = 4,
419 on_rightclick = function(self, clicker)
420 local inv
421 inv = minetest.get_inventory({type="detached", name="mobs_mc:trade"})
423 local update_offer = function(inv, player, sound)
424 if inv:contains_item("input", inv:get_stack("wanted", 1)) and
425 (inv:get_stack("wanted", 2):is_empty() or inv:contains_item("input", inv:get_stack("wanted", 2))) then
426 inv:set_stack("output", 1, inv:get_stack("offered", 1))
427 if sound then
428 minetest.sound_play("mobs_mc_villager_accept", {to_player = player:get_player_name()})
430 return true
431 else
432 inv:set_stack("output", 1, ItemStack(""))
433 if sound then
434 minetest.sound_play("mobs_mc_villager_deny", {to_player = player:get_player_name()})
436 return false
440 if not inv then
441 inv = minetest.create_detached_inventory("mobs_mc:trade", {
442 allow_take = function(inv, listname, index, stack, player)
443 if listname == "input" or listname == "output" then
444 return stack:get_count()
445 else
446 return 0
448 end,
449 allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
450 if from_list == "wanted" or from_list == "offered" or to_list == "wanted" or to_list == "offered" then
451 return 0
452 elseif from_list == "output" and inv:get_stack(to_list, to_index):is_empty() then
453 return count
454 elseif from_list == "input" then
455 return count
456 else
457 return 0
459 end,
460 allow_put = function(inv, listname, index, stack, player)
461 if listname == "input" then
462 return stack:get_count()
463 else
464 return 0
466 end,
467 on_put = function(inv, listname, index, stack, player)
468 update_offer(inv, player, true)
469 end,
470 on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
471 update_offer(inv, player, true)
472 end,
473 on_take = function(inv, listname, index, stack, player)
474 local accept
475 if listname == "output" then
476 inv:remove_item("input", inv:get_stack("wanted", 1))
477 local wanted2 = inv:get_stack("wanted", 2)
478 if not wanted2:is_empty() then
479 inv:remove_item("input", inv:get_stack("wanted", 2))
481 accept = true
483 if accept then
484 minetest.sound_play("mobs_mc_villager_accept", {to_player = player:get_player_name()})
485 else
486 minetest.sound_play("mobs_mc_villager_deny", {to_player = player:get_player_name()})
489 update_offer(inv, player, false)
490 end,
493 inv:set_size("input", 2)
494 inv:set_size("output", 1)
495 inv:set_size("wanted", 2)
496 inv:set_size("offered", 1)
498 init_profession(self)
499 if not self._trades then
500 update_trades(self)
502 set_trade(self, inv, 1)
504 local formspec =
505 "size[9,8.75]"..
506 "background[-0.19,-0.25;9.41,9.49;mobs_mc_trading_formspec_bg.png]"..
507 mcl_vars.inventory_header..
508 "list[current_player;main;0,4.5;9,3;9]"
509 .."list[current_player;main;0,7.74;9,1;]"
510 .."button[1,1;0.5,1;prev_trade;<]"
511 .."button[7.26,1;0.5,1;next_trade;>]"
512 .."list[detached:mobs_mc:trade;wanted;2,1;2,1;]"
513 .."list[detached:mobs_mc:trade;offered;5.76,1;1,1;]"
514 .."list[detached:mobs_mc:trade;input;2,2.5;2,1;]"
515 .."list[detached:mobs_mc:trade;output;5.76,2.55;1,1;]"
516 .."listring[detached:mobs_mc:trade;output]"
517 .."listring[current_player;main]"
518 .."listring[detached:mobs_mc:trade;input]"
519 .."listring[current_player;main]"
520 minetest.sound_play("mobs_mc_villager_trade", {to_player = clicker:get_player_name()})
521 minetest.show_formspec(clicker:get_player_name(), "mobs_mc:trade", formspec)
522 end,
524 on_spawn = function(self)
525 init_profession(self)
526 end,
529 -- Returns a single itemstack in the given inventory to the player's main inventory, or drop it when there's no space left
530 local function return_item(itemstack, dropper, pos, inv_p)
531 if dropper:is_player() then
532 -- Return to main inventory
533 if inv_p:room_for_item("main", itemstack) then
534 inv_p:add_item("main", itemstack)
535 else
536 -- Drop item on the ground
537 local v = dropper:get_look_dir()
538 local p = {x=pos.x, y=pos.y+1.2, z=pos.z}
539 p.x = p.x+(math.random(1,3)*0.2)
540 p.z = p.z+(math.random(1,3)*0.2)
541 local obj = minetest.add_item(p, itemstack)
542 if obj then
543 v.x = v.x*4
544 v.y = v.y*4 + 2
545 v.z = v.z*4
546 obj:setvelocity(v)
547 obj:get_luaentity()._insta_collect = false
550 else
551 -- Fallback for unexpected cases
552 minetest.add_item(pos, itemstack)
554 return itemstack
557 local return_fields = function(player)
558 local inv_t = minetest.get_inventory({type="detached", name = "mobs_mc:trade"})
559 local inv_p = player:get_inventory()
560 for i=1, inv_t:get_size("input") do
561 local stack = inv_t:get_stack("input", i)
562 return_item(stack, player, player:get_pos(), inv_p)
563 stack:clear()
564 inv_t:set_stack("input", i, stack)
566 inv_t:set_stack("output", 1, "")
569 minetest.register_on_player_receive_fields(function(player, formname, fields)
570 if formname == "mobs_mc:trade" then
571 if fields.quit then
572 return_fields(player)
573 elseif fields.next_trade then
575 elseif fields.prev_trade then
579 end)
581 minetest.register_on_leaveplayer(function(player)
582 return_fields(player)
583 end)
585 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)
587 -- compatibility
588 mobs:alias_mob("mobs:villager", "mobs_mc:villager")
590 -- spawn eggs
591 mobs:register_egg("mobs_mc:villager", S("Villager"), "mobs_mc_spawn_icon_villager.png", 0)
593 if minetest.settings:get_bool("log_mods") then
594 minetest.log("action", "MC mobs loaded")