MineClone 2, a voxel-based sandbox game for Minetest https://forum.minetest.net/viewtopic.php?f=50&t=16407
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

villager.lua 30KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046
  1. --MCmobs v0.4
  2. --maikerumine
  3. --made for MC like Survival game
  4. --License for code WTFPL and otherwise stated in readmes
  5. --###################
  6. --################### VILLAGER
  7. --###################
  8. -- Summary: Villagers are complex NPCs, their main feature allows players to trade with them.
  9. -- TODO: Particles
  10. -- TODO: 4s Regeneration I after trade unlock
  11. -- TODO: Breeding
  12. -- TODO: Baby villagers
  13. -- TODO: Spawning in villages
  14. -- TODO: Behaviour:
  15. -- TODO: Walk around village, but do not leave it intentionally
  16. -- TODO: Run into house on rain or danger, open doors
  17. -- TODO: Internal inventory, pick up items, trade with other villagers
  18. -- TODO: Farm stuff
  19. local S = minetest.get_translator("mobs_mc")
  20. local N = function(s) return s end
  21. -- playername-indexed table containing the previously used tradenum
  22. local player_tradenum = {}
  23. -- playername-indexed table containing the objectref of trader, if trading formspec is open
  24. local player_trading_with = {}
  25. local DEFAULT_WALK_CHANCE = 33 -- chance to walk in percent, if no player nearby
  26. local PLAYER_SCAN_INTERVAL = 5 -- every X seconds, villager looks for players nearby
  27. local PLAYER_SCAN_RADIUS = 4 -- scan radius for looking for nearby players
  28. --[=======[ TRADING ]=======]
  29. -- LIST OF VILLAGER PROFESSIONS AND TRADES
  30. -- TECHNICAL RESTRICTIONS (FIXME):
  31. -- * You can't use a clock as requested item
  32. -- * You can't use a compass as requested item if its stack size > 1
  33. -- * You can't use a compass in the second requested slot
  34. -- This is a problem in the mcl_compass and mcl_clock mods,
  35. -- these items should be implemented as single items, then everything
  36. -- will be much easier.
  37. local COMPASS = "mcl_compass:compass"
  38. if minetest.registered_aliases[COMPASS] then
  39. COMPASS = minetest.registered_aliases[COMPASS]
  40. end
  41. local E1 = { "mcl_core:emerald", 1, 1 } -- one emerald
  42. -- Special trades for v6 only
  43. -- NOTE: These symbols MUST only be added at the end of a tier
  44. local TRADE_V6_RED_SANDSTONE, TRADE_V6_DARK_OAK_SAPLING, TRADE_V6_ACACIA_SAPLING, TRADE_V6_BIRCH_SAPLING
  45. if minetest.get_mapgen_setting("mg_name") == "v6" then
  46. TRADE_V6_RED_SANDSTONE = { E1, { "mcl_core:redsandstone", 12, 16 } }
  47. TRADE_V6_DARK_OAK_SAPLING = { { "mcl_core:emerald", 6, 9 }, { "mcl_core:darksapling", 1, 1 } }
  48. TRADE_V6_ACACIA_SAPLING = { { "mcl_core:emerald", 14, 17 }, { "mcl_core:acaciasapling", 1, 1 } }
  49. TRADE_V6_BIRCH_SAPLING = { { "mcl_core:emerald", 8, 11 }, { "mcl_core:birchsapling", 1, 1 } }
  50. end
  51. local professions = {
  52. farmer = {
  53. name = N("Farmer"),
  54. texture = "mobs_mc_villager_farmer.png",
  55. trades = {
  56. {
  57. { { "mcl_farming:wheat_item", 18, 22, }, E1 },
  58. { { "mcl_farming:potato_item", 15, 19, }, E1 },
  59. { { "mcl_farming:carrot_item", 15, 19, }, E1 },
  60. { E1, { "mcl_farming:bread", 2, 4 } },
  61. },
  62. {
  63. { { "mcl_farming:pumpkin_face", 8, 13 }, E1 },
  64. { E1, { "mcl_farming:pumpkin_pie", 2, 3} },
  65. },
  66. {
  67. { { "mcl_farming:melon", 7, 12 }, E1 },
  68. { E1, { "mcl_core:apple", 5, 7 }, },
  69. },
  70. {
  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,
  76. },
  77. }
  78. },
  79. fisherman = {
  80. name = N("Fisherman"),
  81. texture = "mobs_mc_villager_farmer.png",
  82. trades = {
  83. {
  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. -- TODO: replace with enchanted fishing rod
  87. { { "mcl_core:emerald", 3, 11 }, { "mcl_fishing:fishing_rod", 1, 1} },
  88. },
  89. },
  90. },
  91. fletcher = {
  92. name = N("Fletcher"),
  93. texture = "mobs_mc_villager_farmer.png",
  94. trades = {
  95. {
  96. { { "mcl_mobitems:string", 15, 20 }, E1 },
  97. { E1, { "mcl_bows:arrow", 8, 12 } },
  98. },
  99. {
  100. { { "mcl_core:gravel", 10, 10, "mcl_core:emerald", 1, 1 }, { "mcl_core:flint", 6, 10 } },
  101. { { "mcl_core:emerald", 2, 3 }, { "mcl_bows:bow", 1, 1 } },
  102. },
  103. }
  104. },
  105. shepherd ={
  106. name = N("Shepherd"),
  107. texture = "mobs_mc_villager_farmer.png",
  108. trades = {
  109. {
  110. { { "mcl_wool:white", 16, 22 }, E1 },
  111. { { "mcl_core:emerald", 3, 4 }, { "mcl_tools:shears", 1, 1 } },
  112. },
  113. {
  114. { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:white", 1, 1 } },
  115. { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:grey", 1, 1 } },
  116. { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:silver", 1, 1 } },
  117. { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:black", 1, 1 } },
  118. { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:yellow", 1, 1 } },
  119. { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:orange", 1, 1 } },
  120. { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:red", 1, 1 } },
  121. { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:magenta", 1, 1 } },
  122. { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:purple", 1, 1 } },
  123. { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:blue", 1, 1 } },
  124. { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:cyan", 1, 1 } },
  125. { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:lime", 1, 1 } },
  126. { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:green", 1, 1 } },
  127. { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:pink", 1, 1 } },
  128. { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:light_blue", 1, 1 } },
  129. { { "mcl_core:emerald", 1, 2 }, { "mcl_wool:brown", 1, 1 } },
  130. },
  131. },
  132. },
  133. librarian = {
  134. name = N("Librarian"),
  135. texture = "mobs_mc_villager_librarian.png",
  136. trades = {
  137. {
  138. { { "mcl_core:paper", 24, 36 }, E1 },
  139. -- TODO: enchanted book
  140. { { "mcl_books:book", 8, 10 }, E1 },
  141. { { "mcl_core:emerald", 10, 12 }, { "mcl_compass:compass", 1 ,1 }},
  142. { { "mcl_core:emerald", 3, 4 }, { "mcl_books:bookshelf", 1 ,1 }},
  143. },
  144. {
  145. { { "mcl_books:written_book", 2, 2 }, E1 },
  146. { { "mcl_core:emerald", 10, 12 }, { "mcl_clock:clock", 1, 1 } },
  147. { E1, { "mcl_core:glass", 3, 5 } },
  148. },
  149. {
  150. { E1, { "mcl_core:glass", 3, 5 } },
  151. },
  152. -- TODO: 2 enchanted book tiers
  153. {
  154. { { "mcl_core:emerald", 20, 22 }, { "mcl_mobs:nametag", 1, 1 } },
  155. }
  156. },
  157. },
  158. cartographer = {
  159. name = N("Cartographer"),
  160. texture = "mobs_mc_villager_librarian.png",
  161. trades = {
  162. {
  163. { { "mcl_core:paper", 24, 36 }, E1 },
  164. },
  165. {
  166. -- subject to special checks
  167. { { "mcl_compass:compass", 1, 1 }, E1 },
  168. },
  169. {
  170. -- TODO: replace with empty map
  171. { { "mcl_core:emerald", 7, 11}, { "mcl_maps:filled_map", 1, 1 } },
  172. },
  173. -- TODO: special maps
  174. },
  175. },
  176. armorer = {
  177. name = N("Armorer"),
  178. texture = "mobs_mc_villager_smith.png",
  179. trades = {
  180. {
  181. { { "mcl_core:coal_lump", 16, 24 }, E1 },
  182. { { "mcl_core:emerald", 4, 6 }, { "3d_armor:helmet_iron", 1, 1 } },
  183. },
  184. {
  185. { { "mcl_core:iron_ingot", 7, 9 }, E1 },
  186. { { "mcl_core:emerald", 10, 14 }, { "3d_armor:chestplate_iron", 1, 1 } },
  187. },
  188. {
  189. { { "mcl_core:diamond", 3, 4 }, E1 },
  190. -- TODO: enchant
  191. { { "mcl_core:emerald", 16, 19 }, { "3d_armor:chestplate_diamond", 1, 1 } },
  192. },
  193. {
  194. { { "mcl_core:emerald", 5, 7 }, { "3d_armor:boots_chain", 1, 1 } },
  195. { { "mcl_core:emerald", 9, 11 }, { "3d_armor:leggings_chain", 1, 1 } },
  196. { { "mcl_core:emerald", 5, 7 }, { "3d_armor:helmet_chain", 1, 1 } },
  197. { { "mcl_core:emerald", 11, 15 }, { "3d_armor:chestplate_chain", 1, 1 } },
  198. },
  199. },
  200. },
  201. leatherworker = {
  202. name = N("Leatherworker"),
  203. texture = "mobs_mc_villager_butcher.png",
  204. trades = {
  205. {
  206. { { "mcl_mobitems:leather", 9, 12 }, E1 },
  207. { { "mcl_core:emerald", 2, 4 }, { "3d_armor:leggings_leather", 2, 4 } },
  208. },
  209. {
  210. -- TODO: enchant
  211. { { "mcl_core:emerald", 7, 12 }, { "3d_armor:chestplate_leather", 1, 1 } },
  212. },
  213. {
  214. { { "mcl_core:emerald", 8, 10 }, { "mcl_mobitems:saddle", 1, 1 } },
  215. },
  216. },
  217. },
  218. butcher = {
  219. name = N("Butcher"),
  220. texture = "mobs_mc_villager_butcher.png",
  221. trades = {
  222. {
  223. { { "mcl_mobitems:beef", 14, 18 }, E1 },
  224. { { "mcl_mobitems:chicken", 14, 18 }, E1 },
  225. },
  226. {
  227. { { "mcl_core:coal_lump", 16, 24 }, E1 },
  228. { E1, { "mcl_mobitems:cooked_beef", 5, 7 } },
  229. { E1, { "mcl_mobitems:cooked_chicken", 6, 8 } },
  230. },
  231. },
  232. },
  233. weapon_smith = {
  234. name = N("Weapon Smith"),
  235. texture = "mobs_mc_villager_smith.png",
  236. trades = {
  237. {
  238. { { "mcl_core:coal_lump", 16, 24 }, E1 },
  239. { { "mcl_core:emerald", 6, 8 }, { "mcl_tools:axe_iron", 1, 1 } },
  240. },
  241. {
  242. { { "mcl_core:iron_ingot", 7, 9 }, E1 },
  243. -- TODO: enchant
  244. { { "mcl_core:emerald", 9, 10 }, { "mcl_tools:sword_iron", 1, 1 } },
  245. },
  246. {
  247. { { "mcl_core:diamond", 3, 4 }, E1 },
  248. -- TODO: enchant
  249. { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:sword_diamond", 1, 1 } },
  250. -- TODO: enchant
  251. { { "mcl_core:emerald", 9, 12 }, { "mcl_tools:axe_diamond", 1, 1 } },
  252. },
  253. },
  254. },
  255. tool_smith = {
  256. name = N("Tool Smith"),
  257. texture = "mobs_mc_villager_smith.png",
  258. trades = {
  259. {
  260. { { "mcl_core:coal_lump", 16, 24 }, E1 },
  261. -- TODO: enchant
  262. { { "mcl_core:emerald", 5, 7 }, { "mcl_tools:shovel_iron", 1, 1 } },
  263. },
  264. {
  265. { { "mcl_core:iron_ingot", 7, 9 }, E1 },
  266. -- TODO: enchant
  267. { { "mcl_core:emerald", 9, 11 }, { "mcl_tools:pick_iron", 1, 1 } },
  268. },
  269. {
  270. { { "mcl_core:diamond", 3, 4 }, E1 },
  271. -- TODO: enchant
  272. { { "mcl_core:emerald", 12, 15 }, { "mcl_tools:pick_diamond", 1, 1 } },
  273. },
  274. },
  275. },
  276. cleric = {
  277. name = N("Cleric"),
  278. texture = "mobs_mc_villager_priest.png",
  279. trades = {
  280. {
  281. { { "mcl_mobitems:rotten_flesh", 36, 40 }, E1 },
  282. { { "mcl_core:gold_ingot", 8, 10 }, E1 },
  283. },
  284. {
  285. { E1, { "mesecons:redstone", 1, 4 } },
  286. { E1, { "mcl_dye:blue", 1, 2 } },
  287. },
  288. {
  289. { E1, { "mcl_nether:glowstone", 1, 3 } },
  290. { { "mcl_core:emerald", 4, 7 }, { "mcl_throwing:ender_pearl", 1, 1 } },
  291. TRADE_V6_RED_SANDSTONE,
  292. },
  293. -- TODO: Bottle 'o enchanting
  294. },
  295. },
  296. nitwit = {
  297. name = N("Nitwit"),
  298. texture = "mobs_mc_villager.png",
  299. -- No trades for nitwit
  300. trades = nil,
  301. }
  302. }
  303. local profession_names = {}
  304. for id, _ in pairs(professions) do
  305. table.insert(profession_names, id)
  306. end
  307. local stand_still = function(self)
  308. self.walk_chance = 0
  309. self.jump = false
  310. end
  311. local update_max_tradenum = function(self)
  312. if not self._trades then
  313. return
  314. end
  315. local trades = minetest.deserialize(self._trades)
  316. for t=1, #trades do
  317. local trade = trades[t]
  318. if trade.tier > self._max_trade_tier then
  319. self._max_tradenum = t - 1
  320. return
  321. end
  322. end
  323. self._max_tradenum = #trades
  324. end
  325. local init_trader_vars = function(self)
  326. if not self._profession then
  327. -- Select random profession from all professions with matching clothing
  328. local texture = self.base_texture[1]
  329. local matches = {}
  330. for prof_id, prof in pairs(professions) do
  331. if texture == prof.texture then
  332. table.insert(matches, prof_id)
  333. end
  334. end
  335. local p = math.random(1, #matches)
  336. self._profession = matches[p]
  337. end
  338. if not self._max_trade_tier then
  339. self._max_trade_tier = 1
  340. end
  341. if not self._locked_trades then
  342. self._locked_trades = 0
  343. end
  344. if not self._trading_players then
  345. self._trading_players = {}
  346. end
  347. end
  348. local init_trades = function(self, inv)
  349. local profession = professions[self._profession]
  350. local trade_tiers = profession.trades
  351. if trade_tiers == nil then
  352. -- Empty trades
  353. self._trades = false
  354. return
  355. end
  356. local max_tier = #trade_tiers
  357. local trades = {}
  358. for tiernum=1, max_tier do
  359. local tier = trade_tiers[tiernum]
  360. for tradenum=1, #tier do
  361. local trade = tier[tradenum]
  362. local wanted1_item = trade[1][1]
  363. local wanted1_count = math.random(trade[1][2], trade[1][3])
  364. local offered_item = trade[2][1]
  365. local offered_count = math.random(trade[2][2], trade[2][3])
  366. local wanted = { wanted1_item .. " " ..wanted1_count }
  367. if trade[1][4] then
  368. local wanted2_item = trade[1][4]
  369. local wanted2_count = math.random(trade[1][5], trade[1][6])
  370. table.insert(wanted, wanted2_item .. " " ..wanted2_count)
  371. end
  372. table.insert(trades, {
  373. wanted = wanted,
  374. offered = offered_item .. " " .. offered_count,
  375. tier = tiernum, -- tier of this trade
  376. traded_once = false, -- true if trade was traded at least once
  377. trade_counter = 0, -- how often the this trade was mate after the last time it got unlocked
  378. locked = false, -- if this trade is locked. Locked trades can't be used
  379. })
  380. end
  381. end
  382. self._trades = minetest.serialize(trades)
  383. end
  384. local set_trade = function(trader, player, inv, concrete_tradenum)
  385. local trades = minetest.deserialize(trader._trades)
  386. if not trades then
  387. init_trades(trader)
  388. trades = minetest.deserialize(trader._trades)
  389. if not trades then
  390. minetest.log("error", "[mobs_mc] Failed to select villager trade!")
  391. return
  392. end
  393. end
  394. local name = player:get_player_name()
  395. -- Stop tradenum from advancing into locked tiers or out-of-range areas
  396. if concrete_tradenum > trader._max_tradenum then
  397. concrete_tradenum = trader._max_tradenum
  398. elseif concrete_tradenum < 1 then
  399. concrete_tradenum = 1
  400. end
  401. player_tradenum[name] = concrete_tradenum
  402. local trade = trades[concrete_tradenum]
  403. inv:set_stack("wanted", 1, ItemStack(trade.wanted[1]))
  404. inv:set_stack("offered", 1, ItemStack(trade.offered))
  405. if trade.wanted[2] then
  406. local wanted2 = ItemStack(trade.wanted[2])
  407. inv:set_stack("wanted", 2, wanted2)
  408. else
  409. inv:set_stack("wanted", 2, "")
  410. end
  411. end
  412. local function show_trade_formspec(playername, trader, tradenum)
  413. if not trader._trades then
  414. return
  415. end
  416. if not tradenum then
  417. tradenum = 1
  418. end
  419. local trades = minetest.deserialize(trader._trades)
  420. local trade = trades[tradenum]
  421. local profession = professions[trader._profession].name
  422. local disabled_img = ""
  423. if trade.locked then
  424. disabled_img = "image[4.3,2.52;1,1;mobs_mc_trading_formspec_disabled.png]"..
  425. "image[4.3,1.1;1,1;mobs_mc_trading_formspec_disabled.png]"
  426. end
  427. local tradeinv_name = "mobs_mc:trade_"..playername
  428. local tradeinv = minetest.formspec_escape("detached:"..tradeinv_name)
  429. local b_prev, b_next = "", ""
  430. if #trades > 1 then
  431. if tradenum > 1 then
  432. b_prev = "button[1,1;0.5,1;prev_trade;<]"
  433. end
  434. if tradenum < trader._max_tradenum then
  435. b_next = "button[7.26,1;0.5,1;next_trade;>]"
  436. end
  437. end
  438. local formspec =
  439. "size[9,8.75]"
  440. .."background[-0.19,-0.25;9.41,9.49;mobs_mc_trading_formspec_bg.png]"
  441. ..disabled_img
  442. ..mcl_vars.inventory_header
  443. .."label[4,0;"..minetest.formspec_escape(minetest.colorize("#313131", S(profession))).."]"
  444. .."list[current_player;main;0,4.5;9,3;9]"
  445. .."list[current_player;main;0,7.74;9,1;]"
  446. ..b_prev..b_next
  447. .."list["..tradeinv..";wanted;2,1;2,1;]"
  448. .."list["..tradeinv..";offered;5.76,1;1,1;]"
  449. .."list["..tradeinv..";input;2,2.5;2,1;]"
  450. .."list["..tradeinv..";output;5.76,2.55;1,1;]"
  451. .."listring["..tradeinv..";output]"
  452. .."listring[current_player;main]"
  453. .."listring["..tradeinv..";input]"
  454. .."listring[current_player;main]"
  455. minetest.sound_play("mobs_mc_villager_trade", {to_player = playername})
  456. minetest.show_formspec(playername, tradeinv_name, formspec)
  457. end
  458. local update_offer = function(inv, player, sound)
  459. local name = player:get_player_name()
  460. local trader = player_trading_with[name]
  461. local tradenum = player_tradenum[name]
  462. if not trader or not tradenum then
  463. return false
  464. end
  465. local trades = minetest.deserialize(trader._trades)
  466. if not trades then
  467. return false
  468. end
  469. local trade = trades[tradenum]
  470. if not trade then
  471. return false
  472. end
  473. local wanted1, wanted2 = inv:get_stack("wanted", 1), inv:get_stack("wanted", 2)
  474. local input1, input2 = inv:get_stack("input", 1), inv:get_stack("input", 2)
  475. -- BEGIN OF SPECIAL HANDLING OF COMPASS
  476. -- These 2 functions are a complicated check to check if the input contains a
  477. -- special item which we cannot check directly against their name, like
  478. -- compass.
  479. -- TODO: Remove these check functions when compass and clock are implemented
  480. -- as single items.
  481. local check_special = function(special_item, group, wanted1, wanted2, input1, input2)
  482. if minetest.registered_aliases[special_item] then
  483. special_item = minetest.registered_aliases[special_item]
  484. end
  485. if wanted1:get_name() == special_item then
  486. local check_input = function(input, wanted, group)
  487. return minetest.get_item_group(input:get_name(), group) ~= 0 and input:get_count() >= wanted:get_count()
  488. end
  489. if check_input(input1, wanted1, group) then
  490. return true
  491. elseif check_input(input2, wanted1, group) then
  492. return true
  493. else
  494. return false
  495. end
  496. end
  497. return false
  498. end
  499. -- Apply above function to all items which we consider special.
  500. -- This function succeeds if ANY item check succeeds.
  501. local check_specials = function(wanted1, wanted2, input1, input2)
  502. return check_special(COMPASS, "compass", wanted1, wanted2, input1, input2)
  503. end
  504. -- END OF SPECIAL HANDLING OF COMPASS
  505. if (
  506. ((inv:contains_item("input", wanted1) and
  507. (wanted2:is_empty() or inv:contains_item("input", wanted2))) or
  508. -- BEGIN OF SPECIAL HANDLING OF COMPASS
  509. check_specials(wanted1, wanted2, input1, input2)) and
  510. -- END OF SPECIAL HANDLING OF COMPASS
  511. (trade.locked == false)) then
  512. inv:set_stack("output", 1, inv:get_stack("offered", 1))
  513. if sound then
  514. minetest.sound_play("mobs_mc_villager_accept", {to_player = name})
  515. end
  516. return true
  517. else
  518. inv:set_stack("output", 1, ItemStack(""))
  519. if sound then
  520. minetest.sound_play("mobs_mc_villager_deny", {to_player = name})
  521. end
  522. return false
  523. end
  524. end
  525. -- Returns a single itemstack in the given inventory to the player's main inventory, or drop it when there's no space left
  526. local function return_item(itemstack, dropper, pos, inv_p)
  527. if dropper:is_player() then
  528. -- Return to main inventory
  529. if inv_p:room_for_item("main", itemstack) then
  530. inv_p:add_item("main", itemstack)
  531. else
  532. -- Drop item on the ground
  533. local v = dropper:get_look_dir()
  534. local p = {x=pos.x, y=pos.y+1.2, z=pos.z}
  535. p.x = p.x+(math.random(1,3)*0.2)
  536. p.z = p.z+(math.random(1,3)*0.2)
  537. local obj = minetest.add_item(p, itemstack)
  538. if obj then
  539. v.x = v.x*4
  540. v.y = v.y*4 + 2
  541. v.z = v.z*4
  542. obj:set_velocity(v)
  543. obj:get_luaentity()._insta_collect = false
  544. end
  545. end
  546. else
  547. -- Fallback for unexpected cases
  548. minetest.add_item(pos, itemstack)
  549. end
  550. return itemstack
  551. end
  552. local return_fields = function(player)
  553. local name = player:get_player_name()
  554. local inv_t = minetest.get_inventory({type="detached", name = "mobs_mc:trade_"..name})
  555. local inv_p = player:get_inventory()
  556. for i=1, inv_t:get_size("input") do
  557. local stack = inv_t:get_stack("input", i)
  558. return_item(stack, player, player:get_pos(), inv_p)
  559. stack:clear()
  560. inv_t:set_stack("input", i, stack)
  561. end
  562. inv_t:set_stack("output", 1, "")
  563. end
  564. minetest.register_on_player_receive_fields(function(player, formname, fields)
  565. if string.sub(formname, 1, 14) == "mobs_mc:trade_" then
  566. local name = player:get_player_name()
  567. if fields.quit then
  568. -- Get input items back
  569. return_fields(player)
  570. -- Reset internal "trading with" state
  571. local trader = player_trading_with[name]
  572. if trader then
  573. trader._trading_players[name] = nil
  574. end
  575. player_trading_with[name] = nil
  576. elseif fields.next_trade or fields.prev_trade then
  577. local trader = player_trading_with[name]
  578. if not trader or not trader.object:get_luaentity() then
  579. return
  580. end
  581. local trades = trader._trades
  582. if not trades then
  583. return
  584. end
  585. local dir = 1
  586. if fields.prev_trade then
  587. dir = -1
  588. end
  589. local tradenum = player_tradenum[name] + dir
  590. local inv = minetest.get_inventory({type="detached", name="mobs_mc:trade_"..name})
  591. set_trade(trader, player, inv, tradenum)
  592. update_offer(inv, player, false)
  593. show_trade_formspec(name, trader, player_tradenum[name])
  594. end
  595. end
  596. end)
  597. minetest.register_on_leaveplayer(function(player)
  598. local name = player:get_player_name()
  599. return_fields(player)
  600. player_tradenum[name] = nil
  601. local trader = player_trading_with[name]
  602. if trader then
  603. trader._trading_players[name] = nil
  604. end
  605. player_trading_with[name] = nil
  606. end)
  607. -- Return true if player is trading with villager, and the villager entity exists
  608. local trader_exists = function(playername)
  609. local trader = player_trading_with[playername]
  610. return trader ~= nil and trader.object:get_luaentity() ~= nil
  611. end
  612. local trade_inventory = {
  613. allow_take = function(inv, listname, index, stack, player)
  614. if listname == "input" then
  615. return stack:get_count()
  616. elseif listname == "output" then
  617. if not trader_exists(player:get_player_name()) then
  618. return 0
  619. end
  620. -- Only allow taking full stack
  621. local count = stack:get_count()
  622. if count == inv:get_stack(listname, index):get_count() then
  623. -- Also update output stack again.
  624. -- If input has double the wanted items, the
  625. -- output will stay because there will be still
  626. -- enough items in input after the trade
  627. local wanted1 = inv:get_stack("wanted", 1)
  628. local wanted2 = inv:get_stack("wanted", 2)
  629. local input1 = inv:get_stack("input", 1)
  630. local input2 = inv:get_stack("input", 2)
  631. wanted1:set_count(wanted1:get_count()*2)
  632. wanted2:set_count(wanted2:get_count()*2)
  633. -- BEGIN OF SPECIAL HANDLING FOR COMPASS
  634. local special_checks = function(wanted1, input1, input2)
  635. if wanted1:get_name() == COMPASS then
  636. local compasses = 0
  637. if (minetest.get_item_group(input1:get_name(), "compass") ~= 0) then
  638. compasses = compasses + input1:get_count()
  639. end
  640. if (minetest.get_item_group(input2:get_name(), "compass") ~= 0) then
  641. compasses = compasses + input2:get_count()
  642. end
  643. return compasses >= wanted1:get_count()
  644. end
  645. return false
  646. end
  647. -- END OF SPECIAL HANDLING FOR COMPASS
  648. if (inv:contains_item("input", wanted1) and
  649. (wanted2:is_empty() or inv:contains_item("input", wanted2)))
  650. -- BEGIN OF SPECIAL HANDLING FOR COMPASS
  651. or special_checks(wanted1, input1, input2) then
  652. -- END OF SPECIAL HANDLING FOR COMPASS
  653. return -1
  654. else
  655. -- If less than double the wanted items,
  656. -- remove items from output (final trade,
  657. -- input runs empty)
  658. return count
  659. end
  660. else
  661. return 0
  662. end
  663. else
  664. return 0
  665. end
  666. end,
  667. allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
  668. if from_list == "input" and to_list == "input" then
  669. return count
  670. elseif from_list == "output" and to_list == "input" then
  671. if not trader_exists(player:get_player_name()) then
  672. return 0
  673. end
  674. local move_stack = inv:get_stack(from_list, from_index)
  675. if inv:get_stack(to_list, to_index):item_fits(move_stack) then
  676. return count
  677. end
  678. end
  679. return 0
  680. end,
  681. allow_put = function(inv, listname, index, stack, player)
  682. if listname == "input" then
  683. if not trader_exists(player:get_player_name()) then
  684. return 0
  685. else
  686. return stack:get_count()
  687. end
  688. else
  689. return 0
  690. end
  691. end,
  692. on_put = function(inv, listname, index, stack, player)
  693. update_offer(inv, player, true)
  694. end,
  695. on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
  696. if from_list == "output" and to_list == "input" then
  697. inv:remove_item("input", inv:get_stack("wanted", 1))
  698. local wanted2 = inv:get_stack("wanted", 2)
  699. if not wanted2:is_empty() then
  700. inv:remove_item("input", inv:get_stack("wanted", 2))
  701. end
  702. minetest.sound_play("mobs_mc_villager_accept", {to_player = player:get_player_name()})
  703. end
  704. update_offer(inv, player, true)
  705. end,
  706. on_take = function(inv, listname, index, stack, player)
  707. local accept
  708. local name = player:get_player_name()
  709. if listname == "output" then
  710. local wanted1 = inv:get_stack("wanted", 1)
  711. inv:remove_item("input", wanted1)
  712. local wanted2 = inv:get_stack("wanted", 2)
  713. if not wanted2:is_empty() then
  714. inv:remove_item("input", inv:get_stack("wanted", 2))
  715. end
  716. -- BEGIN OF SPECIAL HANDLING FOR COMPASS
  717. if wanted1:get_name() == COMPASS then
  718. for n=1, 2 do
  719. local input = inv:get_stack("input", n)
  720. if minetest.get_item_group(input:get_name(), "compass") ~= 0 then
  721. input:set_count(input:get_count() - wanted1:get_count())
  722. inv:set_stack("input", n, input)
  723. break
  724. end
  725. end
  726. end
  727. -- END OF SPECIAL HANDLING FOR COMPASS
  728. local trader = player_trading_with[name]
  729. local tradenum = player_tradenum[name]
  730. local trades
  731. if trader and trader._trades then
  732. trades = minetest.deserialize(trader._trades)
  733. end
  734. if trades then
  735. local trade = trades[tradenum]
  736. local unlock_stuff = false
  737. if not trade.traded_once then
  738. -- Unlock all the things if something was traded
  739. -- for the first time ever
  740. unlock_stuff = true
  741. trade.traded_once = true
  742. elseif trade.trade_counter == 0 and math.random(1,5) == 1 then
  743. -- Otherwise, 20% chance to unlock if used freshly reset trade
  744. unlock_stuff = true
  745. end
  746. local update_formspec = false
  747. if unlock_stuff then
  748. -- First-time trade unlock all trades and unlock next trade tier
  749. if trade.tier + 1 > trader._max_trade_tier then
  750. trader._max_trade_tier = trader._max_trade_tier + 1
  751. update_max_tradenum(trader)
  752. update_formspec = true
  753. end
  754. for t=1, #trades do
  755. trades[t].locked = false
  756. trades[t].trade_counter = 0
  757. end
  758. trader._locked_trades = 0
  759. -- Also heal trader for unlocking stuff
  760. -- TODO: Replace by Regeneration I
  761. trader.health = math.min(trader.hp_max, trader.health + 4)
  762. end
  763. trade.trade_counter = trade.trade_counter + 1
  764. -- Semi-randomly lock trade for repeated trade (not if there's only 1 trade)
  765. if trader._max_tradenum > 1 then
  766. if trade.trade_counter >= 12 then
  767. trade.locked = true
  768. elseif trade.trade_counter >= 2 then
  769. local r = math.random(1, math.random(4, 10))
  770. if r == 1 then
  771. trade.locked = true
  772. end
  773. end
  774. end
  775. if trade.locked then
  776. inv:set_stack("output", 1, "")
  777. update_formspec = true
  778. trader._locked_trades = trader._locked_trades + 1
  779. -- Check if we managed to lock ALL available trades. Rare but possible.
  780. if trader._locked_trades >= trader._max_tradenum then
  781. -- Emergency unlock! Unlock all other trades except the current one
  782. for t=1, #trades do
  783. if t ~= tradenum then
  784. trades[t].locked = false
  785. trades[t].trade_counter = 0
  786. end
  787. end
  788. trader._locked_trades = 1
  789. -- Also heal trader for unlocking stuff
  790. -- TODO: Replace by Regeneration I
  791. trader.health = math.min(trader.hp_max, trader.health + 4)
  792. end
  793. end
  794. trader._trades = minetest.serialize(trades)
  795. if update_formspec then
  796. show_trade_formspec(name, trader, tradenum)
  797. end
  798. else
  799. minetest.log("error", "[mobs_mc] Player took item from trader output but player_trading_with or player_tradenum is nil!")
  800. end
  801. accept = true
  802. elseif listname == "input" then
  803. update_offer(inv, player, false)
  804. end
  805. if accept then
  806. minetest.sound_play("mobs_mc_villager_accept", {to_player = name})
  807. else
  808. minetest.sound_play("mobs_mc_villager_deny", {to_player = name})
  809. end
  810. end,
  811. }
  812. minetest.register_on_joinplayer(function(player)
  813. local name = player:get_player_name()
  814. player_tradenum[name] = 1
  815. player_trading_with[name] = nil
  816. -- Create or get player-specific trading inventory
  817. local inv = minetest.get_inventory({type="detached", name="mobs_mc:trade_"..name})
  818. if not inv then
  819. inv = minetest.create_detached_inventory("mobs_mc:trade_"..name, trade_inventory, name)
  820. end
  821. inv:set_size("input", 2)
  822. inv:set_size("output", 1)
  823. inv:set_size("wanted", 2)
  824. inv:set_size("offered", 1)
  825. end)
  826. --[=======[ MOB REGISTRATION AND SPAWNING ]=======]
  827. mobs:register_mob("mobs_mc:villager", {
  828. type = "npc",
  829. hp_min = 20,
  830. hp_max = 20,
  831. collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.94, 0.3},
  832. visual = "mesh",
  833. mesh = "mobs_mc_villager.b3d",
  834. textures = {
  835. {
  836. "mobs_mc_villager.png",
  837. "mobs_mc_villager.png", --hat
  838. },
  839. {
  840. "mobs_mc_villager_farmer.png",
  841. "mobs_mc_villager_farmer.png", --hat
  842. },
  843. {
  844. "mobs_mc_villager_priest.png",
  845. "mobs_mc_villager_priest.png", --hat
  846. },
  847. {
  848. "mobs_mc_villager_librarian.png",
  849. "mobs_mc_villager_librarian.png", --hat
  850. },
  851. {
  852. "mobs_mc_villager_butcher.png",
  853. "mobs_mc_villager_butcher.png", --hat
  854. },
  855. {
  856. "mobs_mc_villager_smith.png",
  857. "mobs_mc_villager_smith.png", --hat
  858. },
  859. },
  860. visual_size = {x=3, y=3},
  861. makes_footstep_sound = true,
  862. walk_velocity = 1.2,
  863. run_velocity = 2.4,
  864. drops = {},
  865. -- TODO: sounds
  866. animation = {
  867. stand_speed = 25,
  868. stand_start = 40,
  869. stand_end = 59,
  870. walk_speed = 25,
  871. walk_start = 0,
  872. walk_end = 40,
  873. run_speed = 25,
  874. run_start = 0,
  875. run_end = 40,
  876. die_speed = 15,
  877. die_start = 210,
  878. die_end = 220,
  879. die_loop = false,
  880. },
  881. water_damage = 0,
  882. lava_damage = 4,
  883. light_damage = 0,
  884. view_range = 16,
  885. fear_height = 4,
  886. jump = true,
  887. walk_chance = DEFAULT_WALK_CHANCE,
  888. on_rightclick = function(self, clicker)
  889. -- Initiate trading
  890. local name = clicker:get_player_name()
  891. self._trading_players[name] = true
  892. init_trader_vars(self)
  893. if self._trades == nil then
  894. init_trades(self)
  895. end
  896. update_max_tradenum(self)
  897. if self._trades == false then
  898. -- Villager has no trades, rightclick is a no-op
  899. return
  900. end
  901. player_trading_with[name] = self
  902. local inv = minetest.get_inventory({type="detached", name="mobs_mc:trade_"..name})
  903. set_trade(self, clicker, inv, 1)
  904. show_trade_formspec(name, self)
  905. -- Behaviour stuff:
  906. -- Make villager look at player and stand still
  907. local selfpos = self.object:get_pos()
  908. local clickerpos = clicker:get_pos()
  909. local dir = vector.direction(selfpos, clickerpos)
  910. self.object:set_yaw(minetest.dir_to_yaw(dir))
  911. stand_still(self)
  912. end,
  913. _player_scan_timer = 0,
  914. _trading_players = {}, -- list of playernames currently trading with villager (open formspec)
  915. do_custom = function(self, dtime)
  916. -- Stand still if player is nearby.
  917. if not self._player_scan_timer then
  918. self._player_scan_timer = 0
  919. end
  920. self._player_scan_timer = self._player_scan_timer + dtime
  921. -- Check infrequently to keep CPU load low
  922. if self._player_scan_timer > PLAYER_SCAN_INTERVAL then
  923. self._player_scan_timer = 0
  924. local selfpos = self.object:get_pos()
  925. local objects = minetest.get_objects_inside_radius(selfpos, PLAYER_SCAN_RADIUS)
  926. local has_player = false
  927. for o, obj in pairs(objects) do
  928. if obj:is_player() then
  929. has_player = true
  930. break
  931. end
  932. end
  933. if has_player then
  934. minetest.log("verbose", "[mobs_mc] Player near villager found!")
  935. stand_still(self)
  936. else
  937. minetest.log("verbose", "[mobs_mc] No player near villager found!")
  938. self.walk_chance = DEFAULT_WALK_CHANCE
  939. self.jump = true
  940. end
  941. end
  942. end,
  943. on_spawn = function(self)
  944. init_trader_vars(self)
  945. end,
  946. on_die = function(self, pos)
  947. -- Close open trade formspecs and give input back to players
  948. local trading_players = self._trading_players
  949. for name, _ in pairs(trading_players) do
  950. minetest.close_formspec(name, "mobs_mc:trade_"..name)
  951. local player = minetest.get_player_by_name(name)
  952. if player then
  953. return_fields(player)
  954. end
  955. end
  956. end,
  957. })
  958. 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)
  959. -- compatibility
  960. mobs:alias_mob("mobs:villager", "mobs_mc:villager")
  961. -- spawn eggs
  962. mobs:register_egg("mobs_mc:villager", S("Villager"), "mobs_mc_spawn_icon_villager.png", 0)
  963. if minetest.settings:get_bool("log_mods") then
  964. minetest.log("action", "MC mobs loaded")
  965. end