mob_trading.lua 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514
  1. ----------------------------------------------------------------------------
  2. -- The only relevant function for other mods to call here is:
  3. -- mob_trading.show_trader_formspec( self, player, menu_path, fields )
  4. -- All other functions are more or less internal.
  5. ----------------------------------------------------------------------------
  6. -- Note: group:bla would be handy for prices, but implementing that costs too much
  7. -- while not gaining much (there are alternate prices after all).
  8. -- Note: A limit of x items/hour would be handy as well; except that it can be doe
  9. -- with less effort if the trader uses a trade chest that handles the
  10. -- refilling after some time.
  11. -- contains mostly defines and functions
  12. mob_trading = {};
  13. mob_trading.MAX_OFFERS = 24; -- up to that many diffrent offers are supported by the trader of the type 'individual'
  14. mob_trading.MAX_ALTERNATE_PAYMENTS = 6; -- up to 6 diffrent payments are possible for each good offered
  15. -- how many nodes does the trader of the type individual search for locked chests?
  16. mob_trading.LOCKED_CHEST_SEARCH_RANGE = 3;
  17. mob_trading.KNOWN_LOCKED_CHESTS = {'default:chest_locked', 'locks:shared_locked_chest',
  18. 'mobf_trader:large_chest',
  19. 'technic:iron_locked_chest', 'technic:copper_locked_chest',
  20. 'technic:silver_locked_chest', 'technic:gold_locked_chest'};
  21. -- pseudo-item so that something can be entered when money from the money mod (which does not exist as an item) is requested
  22. mob_trading.MONEY_ITEM = 'money';
  23. -- same for the money2 mod
  24. mob_trading.MONEY2_ITEM = 'money2';
  25. -- store temporal lists, especially for limits (sell if more than/buy if less than)
  26. mob_trading.tmp_lists = {};
  27. mob_trading.get_trader_goods = function( self, trader_goods, player )
  28. if( not( self )) then
  29. return {};
  30. end
  31. if(not( trader_goods )
  32. and self
  33. and self.trader_typ
  34. and mob_basics.mob_types[ 'trader' ][ self.trader_typ ] ) then
  35. trader_goods = mob_basics.mob_types[ 'trader' ][ self.trader_typ ].goods;
  36. end
  37. -- which goods does this trader trade?
  38. if( self.trader_typ == 'random'
  39. or self.trader_stock
  40. or (mobf_trader.ALL_TRADERS_RANDOM and self.trader_typ ~= 'individual' and trader_goods and #trader_goods>0)) then
  41. -- give each trader at least one item for trade
  42. if( not( self.trader_stock ) or #self.trader_stock < 1 ) then
  43. mobf_trader.trader_with_stock_add_random_offer( self, 2, trader_goods );
  44. end
  45. trader_goods = mobf_trader.trader_with_stock_get_goods( self, player, trader_goods );
  46. if( self.trader_stock and #self.trader_stock > 0 and trader_goods and #trader_goods < 1 ) then
  47. self.trader_stock = {};
  48. mobf_trader.trader_with_stock_add_random_offer( self, 2, trader_goods );
  49. end
  50. elseif( self.trader_typ == 'individual' or not( trader_goods ) or #trader_goods < 1 ) then
  51. trader_goods = self.trader_goods;
  52. end
  53. return trader_goods;
  54. end
  55. -------------------------------------------------------------------------------
  56. -- main formspec of the trader
  57. -------------------------------------------------------------------------------
  58. -- the trader entity turns towards the player
  59. -- usually shows the goods the trader has to offer plus trade details once an offer has been selected
  60. --
  61. -- self HAS to contain:
  62. -- self.trader_id unique id of the trader (unique for the entire map)
  63. -- self.trader_name i.e. 'Fritz'; used only for the greeting
  64. -- self.object traders of the type individual need to find locked chests in their environment
  65. -- self.trader_typ traders with the type 'individual' are handled specially (they trade for players);
  66. -- self.trader_goods required for traders of the typ individual; else determined through self.trader_typ
  67. -- self.trader_owner required for traders of the typ individual
  68. -- self.trader_sold used for collecting statistics
  69. -- self.trader_stock array of {id,amount] values representing the trader's stock
  70. -- Optional:
  71. -- self.trader_inv helper variable that will contain a reference to the trader chest's inventory; will be set automaticly
  72. -- self.trader_limit will be used if set; may contain self.trader_limit.sell_if_more[ item name ] and self.trader_limit.buy_if_less[ item name ]
  73. mob_trading.show_trader_formspec = function( self, player, menu_path, fields, trader_goods )
  74. if( not( self ) or not( player )) then
  75. return;
  76. end
  77. local pname = player:get_player_name();
  78. local npc_id = self.trader_id;
  79. if( not( npc_id )) then
  80. return;
  81. end
  82. trader_goods = mob_trading.get_trader_goods( self, trader_goods, player );
  83. -- update what the trader wields/shows
  84. if( mobf_trader.mesh == "3d_armor_character.b3d" and trader_goods[1]) then
  85. mob_basics.update_texture( self, "trader", trader_goods );
  86. end
  87. local formspec = 'size[10,11]'..
  88. 'list[current_player;main;1,7;8,4;]'..
  89. 'button_exit[7.5,6.3;2,0.5;quit;End trade]';
  90. -- indicate to the owner of the trader which fields of the owner's inventory are taken as input fields
  91. -- for new trade offers; for this purpose, colored boxes are drawn around the relevant inventory slots
  92. if( (self.trader_owner and self.trader_owner == pname ) and self.trader_typ=='individual') then
  93. formspec = formspec..
  94. 'label[-0.25,6.90;When adding]'..
  95. 'label[-0.25,7.10;a new offer,]'..
  96. 'label[-0.25,7.30;suggest this:]'..
  97. 'label[1.1,6.5;Sell:]'..
  98. 'box[0.95,6.7;0.95,4.35;#00AA00]'..
  99. 'label[2.1,6.5;for:]'..
  100. 'box[1.95,6.7;0.95,4.35;#0000CC]'..
  101. -- the Add button is also shown next to the player's inventory that provides the names
  102. 'button[9,7.1;1,0.5;'..npc_id..'_add;Add]'..
  103. 'button[1.1,10.9;3.9,0.5;'..npc_id..'_addm;Add offer based on these colored slots]';
  104. for i = 3, mob_trading.MAX_ALTERNATE_PAYMENTS do
  105. local boxcolor = '#0000CC';
  106. formspec = formspec..'label['..(tostring(i)+0.1)..',6.5;or:]';
  107. -- complex offers of up to 4 items allow only 3 alternate payments
  108. if( i<=4 ) then
  109. boxcolor = '#0000CC';
  110. if(( i%2 )==1 ) then
  111. boxcolor = '#AAAAAA';
  112. end
  113. formspec = formspec..'box['..( i-0.05 )..',6.7;0.95,4.35;'..boxcolor..']';
  114. else
  115. boxcolor = '#000077';
  116. if(( i%2 )==1 ) then
  117. boxcolor = '#777777';
  118. end
  119. formspec = formspec..'box['..( i-0.05 )..',6.7;0.95,1.25;'..boxcolor..']';
  120. end
  121. end
  122. end
  123. -- back to main menu (player clicked 'Abort' in the add/edit new offer menu)
  124. if( menu_path and #menu_path > 1 and menu_path[2]=='main') then
  125. menu_path[2] = nil;
  126. end
  127. -- configure the trade limits for a trader
  128. if( menu_path and #menu_path > 1 and (menu_path[2]=='limits' or menu_path[2]=='limitlist' or menu_path[2]=='limitstore')) then
  129. -- find out how many of each item is availabe
  130. local counted_inv = nil;
  131. if( self.trader_typ == 'individual' and self.trader_owner) then
  132. mob_trading.find_trader_inv( self );
  133. counted_inv = mob_trading.count_trader_inv( self );
  134. end
  135. mob_trading.show_trader_formspec_limits( self, player, menu_path, fields, trader_goods, npc_id, pname, counted_inv );
  136. -- the function above already displayed a formspec; nothing more to do here
  137. return;
  138. end
  139. -- the player wants to delete a trade offer
  140. if( menu_path and #menu_path > 1 and menu_path[2]=='delete') then
  141. if( not(trader_goods )) then
  142. trader_goods = {};
  143. end
  144. local edit_nr = tonumber( menu_path[3] );
  145. -- store the modified offer
  146. if( edit_nr and edit_nr > 0 and edit_nr <= #trader_goods ) then
  147. table.remove( trader_goods, edit_nr );
  148. self.trader_goods = trader_goods;
  149. minetest.chat_send_player( pname, self.trader_name..': Deleted. This trade is no longer offered.');
  150. mob_basics.update( self, 'trader' ); -- save the new goods list
  151. end
  152. -- display all offers (minus the deleted one)
  153. menu_path[2] = nil;
  154. end
  155. -- the player wants to store a new trade offer or change an existing one
  156. if( menu_path and #menu_path > 1 and (menu_path[2]=='storenew' or menu_path[2]=='storenewm' or menu_path[2]=='storechange')) then
  157. local error_msg = mob_trading.store_trade_offer_changes( self, pname, menu_path, fields, trader_goods );
  158. -- in case of error: display the input again so that the player can edit it
  159. if( error_msg ~= '' ) then
  160. if( menu_path[2]=='storenewm') then
  161. menu_path[2] = 'addm';
  162. elseif( menu_path[2]=='storenew' ) then
  163. menu_path[2] = 'add';
  164. elseif( menu_path[2]=='storechange' ) then
  165. menu_path[2] = 'edit';
  166. end
  167. -- show the error
  168. formspec = formspec..
  169. 'textarea[1.0,1.6;9,0.5;info;;'..minetest.formspec_escape( error_msg )..']';
  170. -- send the player a chat message as well
  171. minetest.chat_send_player( pname, self.trader_name..': '..error_msg );
  172. end
  173. end
  174. -- add a new trade offer for the individual trader
  175. if( menu_path and #menu_path > 1 and (menu_path[2]=='add' or menu_path[2]=='edit' or menu_path[2]=='addm')) then
  176. mob_trading.show_trader_formspec_edit( self, player, menu_path, fields, trader_goods, formspec, npc_id, pname );
  177. -- the function above already displayed a formspec; nothing more to do here
  178. return;
  179. end
  180. -- it is possible to display up to 4 items which may together form one offer; this needs a diffrent sort of visualization
  181. local offer_packages = false;
  182. for j,k in ipairs( trader_goods ) do
  183. for i,v in ipairs( k ) do
  184. if( not( offer_packages ) and type( v )=='table' ) then
  185. offer_packages = true;
  186. end
  187. end
  188. end
  189. -- move some entries up in order to have enough space
  190. local m_up = 0;
  191. local p_up = 0;
  192. if( not( menu_path ) or #menu_path == 1 ) then
  193. m_up = 1.0;
  194. elseif( menu_path and #menu_path == 2 ) then
  195. -- displaying a package with up to 4 items takes more space
  196. if( offer_packages ) then
  197. m_up = 0.0;
  198. p_up = 0.5;
  199. else
  200. m_up = 0.5;
  201. p_up = 1.0;
  202. end
  203. elseif( menu_path and #menu_path > 2 ) then
  204. if( offer_packages ) then
  205. m_up = -1.0;
  206. p_up = -1.0;
  207. else
  208. m_up = 0.0;
  209. p_up = 0.0;
  210. end
  211. end
  212. -- intorduce the trader
  213. if( not( self.trader_name )) then
  214. self.trader_name = 'Nameless trader';
  215. end
  216. local greeting1 = 'My name is '..tostring( self.trader_name or 'uniportant')..'.';
  217. local greeting2 = 'I sell the following:';
  218. local greeting3 = '';
  219. if( self.trader_owner and self.trader_owner ~= '' ) then
  220. if( self.trader_owner == pname ) then
  221. greeting3 = 'You are my employer.';
  222. else
  223. greeting3 = tostring( self.trader_owner )..' is my employer.';
  224. end
  225. else
  226. greeting3 = 'I work for myself.';
  227. end
  228. if( menu_path and menu_path[1] ) then
  229. formspec = formspec..'button[4.5,6.3;2,0.5;'..npc_id..'_main;Show goods]';
  230. end
  231. formspec = formspec..'label[0.5,'..(0.5+m_up)..';'..minetest.formspec_escape( greeting1 )..']'..
  232. 'label[3.5,'..(0.5+m_up)..';'..minetest.formspec_escape( greeting2 )..']'..
  233. 'label[6.5,'..(0.5+m_up)..';'..minetest.formspec_escape( greeting3 )..']'..
  234. 'label[0.2,'..(1.5+m_up)..';Goods:]';
  235. -- the owner and people with the mob_pickup priv can pick the trader up
  236. -- (he will end up in the inventory and can then be placed elsewhere)
  237. if( (self.trader_owner and self.trader_owner == pname)
  238. or minetest.check_player_privs( pname, {mob_pickup=true})) then
  239. formspec = formspec..'button_exit[9,0.5;1,0.5;'..npc_id..'_take;Take]';
  240. end
  241. -- only the owner can edit the limits
  242. if( self.trader_owner and self.trader_owner == pname ) then
  243. if( self.trader_typ=='individual') then
  244. formspec = formspec..'button_exit[9,1.5;1,0.5;'..npc_id..'_limits;Limits]';
  245. end
  246. -- admin traders can now be configured as well
  247. formspec = formspec..'button_exit[9,1.0;1,0.5;'..npc_id..'_config;Config]';
  248. end
  249. -- find out how many of each item is availabe
  250. local counted_inv = nil;
  251. if( self.trader_typ == 'individual' and self.trader_owner) then
  252. mob_trading.find_trader_inv( self );
  253. counted_inv = mob_trading.count_trader_inv( self );
  254. end
  255. -- give information about a specific good
  256. if( menu_path and #menu_path >= 2 ) then
  257. local choice1 = tonumber( menu_path[2] );
  258. -- in case the client sends invalid input
  259. if( not( choice1 ) or choice1 > #trader_goods ) then
  260. choice1 = 1;
  261. end
  262. local trade_details = trader_goods[ choice1 ];
  263. -- when offering 6 diffrent methods of payment, we can't display 4 items per payment - there's simply not enough space
  264. if( offer_packages and #trade_details > 4 ) then
  265. offer_packages = false;
  266. end
  267. if( #menu_path >= 3 ) then
  268. local res = mob_trading.do_trade( self, player, menu_path, trade_details, counted_inv );
  269. if( res.msg ) then
  270. formspec = formspec..
  271. 'textarea[1.0,5.1;8,1.0;info;;'..( minetest.formspec_escape( res.msg ))..']';
  272. minetest.chat_send_player( pname, self.trader_name..': '..res.msg );
  273. end
  274. local allow_repeat = true;
  275. if( res.success ) then
  276. -- if the trader is one that has no inv but a limited stock, then substract that stock
  277. if( self.trader_stock and self.trader_stock[ choice1 ]) then
  278. self.trader_stock[ choice1 ][2] = self.trader_stock[ choice1 ][2] - 1;
  279. -- callback function used to get new stock
  280. mobf_trader.trader_with_stock_after_sale( self, player, menu_path, trade_details, trader_goods );
  281. if( self.trader_stock[ choice1 ][2] < 1 ) then
  282. table.remove( self.trader_stock, choice1 );
  283. -- the trader has no more of that; choice1 points to a diffrent trade offer now
  284. allow_repeat = false;
  285. -- the old selected trade is no longer availabel
  286. menu_path = {menu_path[1]};
  287. -- update the goods the trader has on offer
  288. trader_goods = mobf_trader.trader_with_stock_get_goods( self, player, trader_goods );
  289. end
  290. -- update the inventory of traders of the type individual as well
  291. elseif( self.trader_typ == 'individual' and self.trader_owner) then
  292. counted_inv = mob_trading.count_trader_inv( self );
  293. end
  294. if( allow_repeat ) then
  295. formspec = formspec..
  296. 'button[1.0,5.8;2.5,0.5;'..menu_path[1]..'_'..menu_path[2]..'_'..menu_path[3]..';Repeat the trade]';
  297. end
  298. end
  299. if( allow_repeat ) then
  300. formspec = formspec..'button[1.5,6.3;2,0.5;'..menu_path[1]..'_'..menu_path[2]..';Show prices]';
  301. end
  302. end
  303. if( #menu_path >= 2 ) then
  304. -- if that button is clicked, show the same formspec again
  305. if( offer_packages ) then
  306. formspec = formspec..
  307. 'label[0.3,'..(5.1+p_up)..';Get all of this]'..
  308. 'label[2.0,'..(4.1+p_up)..';for]'..
  309. 'label[3.0,'..(3.00+p_up)..';Select what you want to give:]'..
  310. mob_trading.show_trader_formspec_item_list(
  311. 0, 4.0+p_up-0.6, trade_details[1], menu_path[2], npc_id, 1.0, 1.0, 1, counted_inv, true, self )..
  312. 'box[-0.15,'..(4.0+p_up-0.70)..';2.1,2.4;#00AA00]';
  313. else
  314. formspec = formspec..
  315. 'label[0.3,'..(4.0+p_up)..';Get]'..
  316. 'box[0.25,'..(3.8+p_up)..';1.75,1.10;#00AA00]'..
  317. mob_trading.show_trader_formspec_item_list(
  318. 0.5, 4.0+p_up-0.6, trade_details[1], menu_path[2], npc_id, 1.0, 1.0, 1, counted_inv, true, self );
  319. end
  320. if( (self.trader_owner and self.trader_owner == pname ) and self.trader_typ=='individual') then
  321. formspec = formspec..'button[9,'..(3.7+p_up)..';1,0.5;'..npc_id..'_delete_'..menu_path[2]..';Delete]'..
  322. 'button[9,'..(4.7+p_up)..';1,0.5;'..npc_id..'_edit_'.. menu_path[2]..';Edit]';
  323. end
  324. -- the real options here are the prices
  325. local npc_id_det = npc_id..'_'..menu_path[2];
  326. local or_or_for = 'for';
  327. for i,v in ipairs( trade_details ) do
  328. local boxcolor = '#0000CC';
  329. -- the first entry is the good that is offered; all subsequent ones are prices
  330. if( i > 1 ) then
  331. if( i > 2 ) then
  332. or_or_for = 'or';
  333. end
  334. if( i%2==1 ) then
  335. boxcolor = '#AAAAAA';
  336. end
  337. if( offer_packages ) then
  338. formspec = formspec..
  339. 'label['..((i-1)*2.40+0.0)..','..(5.1+p_up)..';'..or_or_for..']'..
  340. 'button['..((i-1)*2.40+0.3)..','..(5.3+p_up)..';1.5,0.5;'..
  341. npc_id_det..'_'..tostring( i )..';Payment '..tostring(i-1)..']'..
  342. mob_trading.show_trader_formspec_item_list(
  343. ((i-1)*2.40), 4.0+p_up-0.6, trade_details[i], i, npc_id_det, 1.0, 1.0, 1, counted_inv, false, self )..
  344. 'box['..(-0.15+((i-1)*2.40))..','..(4.0+p_up-0.70)..';2.1,2.4;'..boxcolor..']';
  345. else
  346. formspec = formspec..
  347. 'label['..((i)*1.2-0.3)..','..(4.0+p_up)..';'..or_or_for..']'..
  348. 'box['..((i*1.2)-0.34)..','..(3.8+p_up)..';1.15,1.10;'..boxcolor..']'..
  349. mob_trading.show_trader_formspec_item_list(
  350. ((i*1.2)-0.5), 4.0+p_up-0.6, trade_details[i], i, npc_id_det, 1.0, 1.0, 1, counted_inv, false, self );
  351. end
  352. end
  353. end
  354. end
  355. -- show the amount of sold items after the purchase
  356. if( menu_path and #menu_path >= 2 ) then
  357. -- show how much of these items/packages have been sold already
  358. if( self.trader_sold
  359. and self.trader_sold[ trade_details[ 1 ]]) then
  360. formspec = formspec..'label[9.0,'..(3.9+p_up)..';Sold: '..
  361. tostring( self.trader_sold[ trade_details[ 1 ]] )..']';
  362. else
  363. formspec = formspec..'label[9.0,'..(3.9+p_up)..';Sold: -]';
  364. end
  365. if( self.trader_stock
  366. and self.trader_stock[ choice1 ]
  367. and self.trader_stock[ choice1 ][2] ) then
  368. formspec = formspec..'label[9.0,'..(3.7+p_up)..';Stock: '..
  369. tostring( math.max( 0, self.trader_stock[ choice1 ][2] ))..']';
  370. end
  371. end
  372. end
  373. -- show the goods the trader has to offer
  374. for i,v in ipairs( trader_goods ) do
  375. formspec = formspec..mob_trading.show_trader_formspec_item_list(
  376. ((i-1)%8)+1, math.floor((i-1)/8)*1.2+(1.0+m_up), v[1], i, npc_id, 0.4, 0.4, 0.6, counted_inv, true, self);
  377. end
  378. minetest.show_formspec( pname, "mob_trading:trader", formspec );
  379. end
  380. -- TODO: use can_trade instead of duplicating checks here?
  381. --------------------------------------------------------------------------
  382. -- show an image button for *one* item stack that is part of a trade offer
  383. --------------------------------------------------------------------------
  384. -- helper function;
  385. -- if there is enough space (only one stack offered or one offer selected), the amount of items in that stack will be shown as a label;
  386. -- additional information such as "SOLD OUT" is added so that it becomes immmediately obvious if the trade is not possible;
  387. -- self.trader.trader_limit is used
  388. mob_trading.show_trader_formspec_item = function( offset_x, offset_y, text_offset_x, text_offset_y, stack_desc, nr, prefix, size, counted_inv, is_offer, self )
  389. local stack = ItemStack( stack_desc );
  390. local anz = stack:get_count();
  391. local name = stack:get_name();
  392. local label = '';
  393. local label_amount = '';
  394. -- show the label with the amount of item showed only if more than one is sold and the button is large enough
  395. if( anz > 1 and size>0.7) then
  396. label_amount = '\n\n\t\t'..tostring( anz )..'x';
  397. else
  398. label = '';
  399. end
  400. -- TODO: in case of money, there is no check yet weather the side concerned can afford the trade
  401. if( name==mob_trading.MONEY_ITEM or name==mob_trading.MONEY2_ITEM) then
  402. return { text='image_button['..offset_x..','..offset_y..';'..size..','..size..';'..
  403. 'mobf_trader_money.png;'..
  404. prefix..'_'..tostring( nr )..';;;]'..
  405. label,
  406. anz_avail = 0 };
  407. end
  408. -- do not show unknown blocks
  409. if( not( minetest.registered_items[ name ] )) then
  410. return { text='', anz_avail=0};
  411. end
  412. local anz_avail = 0;
  413. if( counted_inv and self and self.trader_inv) then
  414. if( is_offer
  415. -- no more in stock
  416. and ( (not(counted_inv[name]) or counted_inv[name]<anz )
  417. or ( self.trader_limit
  418. and self.trader_limit.sell_if_more
  419. and self.trader_limit.sell_if_more[ name ]
  420. -- ...or less than what the trader is supposed to keep as a reserve
  421. and self.trader_limit.sell_if_more[ name ] > (counted_inv[name]-anz) ))) then
  422. label_amount = "SOLD\nOUT";
  423. anz_avail = 0;
  424. elseif( is_offer ) then
  425. -- how many more of these items are on sale?
  426. if ( self.trader_limit
  427. and self.trader_limit.sell_if_more
  428. and self.trader_limit.sell_if_more[ name ] ) then
  429. anz_avail = math.floor( (counted_inv[name]-self.trader_limit.sell_if_more[ name ]) / anz );
  430. else
  431. anz_avail = math.floor( (counted_inv[name]) / anz );
  432. end
  433. -- is there enough free space?
  434. elseif( not(is_offer) and not( self.trader_inv:room_for_item( 'main', stack ))) then
  435. label_amount = "NO\nSPACE\nLEFT";
  436. -- upper storage limit reached
  437. elseif( not(is_offer)
  438. and self.trader_limit
  439. and self.trader_limit.buy_if_less
  440. and self.trader_limit.buy_if_less[ name ]
  441. -- only buy up to buy_if_less items of this kind
  442. and self.trader_limit.buy_if_less[ name ] < (counted_inv[name]+anz)) then
  443. label_amount = "NO MORE\nWANTED";
  444. end
  445. end
  446. return { text='item_image_button['..offset_x..','..offset_y..';'..size..','..size..';'..
  447. ( name or '?')..';'..
  448. prefix..'_'..tostring( nr )..';'..label_amount..']'..
  449. -- SOLD OUT etc. have to be seperate labels instead of labels of the item_image_buttons because
  450. -- the item_image_buttons can't handle multiple lines of text
  451. label,
  452. anz_avail = anz_avail };
  453. end
  454. --------------------------------------------------------------------------
  455. -- shows the complete offer (consisting of up to 4 seperate stacks)
  456. --------------------------------------------------------------------------
  457. -- set stretch_x and stretch_y to 0.4 each plus quarter_botton_size to 0.6 in order to make everything fit into one formspec
  458. mob_trading.show_trader_formspec_item_list = function( offset_x, offset_y, stack_desc, nr, prefix, stretch_x, stretch_y, quarter_button_size, counted_inv, is_offer, self )
  459. -- show multiple items
  460. if( type( stack_desc )=='table') then
  461. local formspec = '';
  462. -- display first image at the lower left corner
  463. local a = 0;
  464. local b = 1;
  465. -- formspec = formspec..'label['..(offset_x)..','..(offset_y+0.6)..';Package]';
  466. -- we can display no more than 4 items
  467. local k = math.min( 4, #stack_desc );
  468. -- if there are only 2 items, display them centralized
  469. if( k==2 ) then
  470. offset_x = offset_x + 0.5*stretch_x;
  471. end
  472. local error_msg = '';
  473. local error_msg_offset = 0;
  474. local anz_avail = 1000;
  475. if( stretch_x > 0.7 ) then
  476. error_msg_offset = 0.65;
  477. end
  478. for i = 1,k do
  479. local res = mob_trading.show_trader_formspec_item(offset_x+(a*stretch_x), offset_y+(b*stretch_y),
  480. offset_x+error_msg_offset, offset_y+error_msg_offset,
  481. stack_desc[i], nr, prefix, quarter_button_size, counted_inv, is_offer, self );
  482. formspec = formspec..res.text;
  483. -- the item of which the least amount is available determines how many packages can be sold
  484. if( res.anz_avail < anz_avail ) then
  485. anz_avail = res.anz_avail;
  486. end
  487. if( i==1) then a = 1; b = 0; -- 2nd: upper right corner
  488. elseif(i==2) then a = 1; b = 1; -- 3rd: lower right corner
  489. elseif(i==3) then a = 0; b = 0; -- 4rd: upper left corner
  490. end
  491. -- 2 items: always display centralized
  492. if( k==2 ) then
  493. a = 0;
  494. end
  495. end
  496. if( stretch_x > 0.7 and is_offer and anz_avail>0) then
  497. formspec = formspec..'label[9.0,'..(offset_y+0.75)..';Left: '..tostring(anz_avail)..']';
  498. end
  499. return formspec;
  500. else
  501. -- put the only image we have to display in a central position
  502. if( quarter_button_size==1 ) then
  503. offset_x = offset_x + quarter_button_size/2;
  504. offset_y = offset_y + quarter_button_size/2;
  505. end
  506. local res = mob_trading.show_trader_formspec_item( offset_x, offset_y, offset_x, offset_y, stack_desc, nr, prefix, 1, counted_inv, is_offer, self );
  507. if( stretch_x > 0.7 and is_offer and res.anz_avail>0) then
  508. res.text = res.text..'label[9.0,'..(offset_y+0.25)..';Left: '..tostring(res.anz_avail)..']';
  509. end
  510. return res.text;
  511. end
  512. end
  513. --------------------------------------------------
  514. -- Store new trade offer or change existing one
  515. --------------------------------------------------
  516. -- changes trader_goods if the add or edit succeeded
  517. -- changes menu_path so that the newly added/edited offer is displayed
  518. -- sends a chat message to the player in case of success and returns ''; else returns error message
  519. mob_trading.store_trade_offer_changes = function( self, pname, menu_path, fields, trader_goods )
  520. local offer = {};
  521. local i = 1;
  522. local j = 1;
  523. -- t1 has to be filled in - it has to contain the stack the player wants to offer
  524. if( (not( fields[ 't1' ] ) or fields[ 't1'] == '' )
  525. and (not( fields[ 't2' ] ) or fields[ 't2'] == '' )
  526. and (not( fields[ 't3' ] ) or fields[ 't3'] == '' )
  527. and (not( fields[ 't4' ] ) or fields[ 't4'] == '' )) then
  528. return 'Error: What do you want to offer? Please enter something after \'Sell:\'!';
  529. end
  530. for i=1,mob_trading.MAX_ALTERNATE_PAYMENTS do
  531. local offer_one_side = {};
  532. for j=1,4 do
  533. local text = fields[ 't'..tostring(((i-1)*4)+j) ];
  534. if( text and text ~= '' ) then
  535. local help = text:split( ' ' );
  536. -- if no amount is given, assume 1
  537. if( #help < 2 ) then
  538. help[2] = 1;
  539. end
  540. -- the amount of items can only be positive
  541. help[2] = tonumber( help[2] );
  542. if( not( help[2] ) or help[2]<1 ) then
  543. return 'Error: Negative amounts are not supported: \''..( text )..'\'.';
  544. end
  545. -- money and money2 are acceptable as well
  546. if( not( minetest.registered_items[ help[1] ] ) and help[1]~=mob_trading.MONEY_ITEM and help[1]~=mob_trading.MONEY2_ITEM) then
  547. return 'Error: \''..tostring( help[1] )..'\' is not a valid item. Please check your spelling.';
  548. end
  549. -- do not allow stacks that are larger than max stack size (this is not relevant for money)
  550. if( minetest.registered_items[ help[1] ] ) then
  551. local stack = ItemStack( text );
  552. if( stack:get_count() > stack:get_stack_max() ) then
  553. return 'Error: \''..tostring( help[1] )..'\' can only be traded in stacks of up to '..
  554. tostring( stack:get_stack_max() )..' pieces at a time.';
  555. end
  556. end
  557. table.insert( offer_one_side, text );
  558. end
  559. end
  560. -- use a string to store
  561. if( #offer_one_side==1) then
  562. table.insert( offer, offer_one_side[1] );
  563. -- use a table to store (necessary when up to four items are bundled)
  564. elseif( #offer_one_side>1) then
  565. table.insert( offer, offer_one_side );
  566. end
  567. end
  568. if( #offer < 2 ) then
  569. return 'Please provide at least one form of payment.';
  570. end
  571. if( #trader_goods >= mob_trading.MAX_OFFERS and (menu_path[2]=='storenew' or menu_path[2]=='storenewm')) then
  572. return 'Sorry. Each trader can only make up to '..tostring( mob_trading.MAX_OFFERS )..' diffrent offers.';
  573. end
  574. if( not(trader_goods )) then
  575. trader_goods = {};
  576. end
  577. if( menu_path[2]=='storenew' or menu_path[2]=='storenewm') then
  578. -- TODO: check if a similar offer exists already
  579. table.insert( trader_goods, offer );
  580. -- inform the trader about his new offer
  581. self.trader_goods = trader_goods;
  582. -- display the newly stored offer
  583. minetest.chat_send_player( pname, self.trader_name..': Your new offer has been added.');
  584. mob_basics.update( self, 'trader'); -- store new offer
  585. -- make sure the new offer is selected and displayed when this function here continues
  586. menu_path[2] = #trader_goods;
  587. return '';
  588. elseif( menu_path[2]=='storechange') then
  589. local edit_nr = tonumber( menu_path[3] );
  590. -- store the modified offer
  591. if( edit_nr and edit_nr > 0 and edit_nr <= #trader_goods ) then
  592. trader_goods[ edit_nr ] = offer;
  593. self.trader_goods = trader_goods;
  594. minetest.chat_send_player( pname, self.trader_name..': The offer has been changed.');
  595. end
  596. mob_basics.update( self, 'trader'); -- store changed offer
  597. -- display the modified offer
  598. menu_path[2] = edit_nr;
  599. menu_path[3] = nil;
  600. return '';
  601. end
  602. return 'Error: Unknown command.';
  603. end
  604. -------------------------------------------------------------------------------
  605. -- helper function for mob_trading.show_trader_limits;
  606. -- changes the table items
  607. mob_trading.insert_item_limitation = function( items, k, i, v )
  608. if( i<1 or i>4) then
  609. return;
  610. end
  611. if( not( items[ k ] )) then
  612. -- 0 in stock; sell if more than 0; buy if less than 10000; item is part of a trade offer
  613. items[ k ] = { 0, 0, 10000, false };
  614. end
  615. items[ k ][ i ] = v;
  616. end
  617. -------------------------------------------------------------------------------
  618. -- display and allow configuration of self.trader_limit
  619. -------------------------------------------------------------------------------
  620. mob_trading.show_trader_formspec_limits = function( self, player, menu_path, fields, trader_goods, npc_id, pname, counted_inv )
  621. local items = {};
  622. if( not( self.trader_limit )) then
  623. self.trader_limit = {};
  624. end
  625. if( not( self.trader_limit.sell_if_more )) then
  626. self.trader_limit.sell_if_more = {};
  627. end
  628. if( not( self.trader_limit.buy_if_less )) then
  629. self.trader_limit.buy_if_less = {};
  630. end
  631. local selected = 2;
  632. -- store the new limits
  633. if( #menu_path > 2 and menu_path[2]=='limitstore' and fields['SellIfMoreThan'] and fields['BuyIfLessThan']
  634. and mob_trading.tmp_lists[ pname ] and #mob_trading.tmp_lists[ pname ] >= tonumber( menu_path[3] )
  635. and tonumber( menu_path[3] )>0) then
  636. local selected = mob_trading.tmp_lists[ pname ][ tonumber(menu_path[3]) ];
  637. local anz = tonumber(fields['SellIfMoreThan']);
  638. if( anz > 0 and anz < 10000 ) then
  639. self.trader_limit.sell_if_more[ selected ] = anz;
  640. end
  641. anz = tonumber(fields['BuyIfLessThan']);
  642. if( anz > 0 and anz < 10000 ) then
  643. self.trader_limit.buy_if_less[ selected ] = anz;
  644. end
  645. end
  646. -- everything the trader has in his chest is a candidate for trading
  647. if( not( counted_inv )) then
  648. counted_inv = {};
  649. end
  650. for k,v in pairs( counted_inv ) do
  651. mob_trading.insert_item_limitation( items, k, 1, v );
  652. end
  653. if( self.trader_goods ) then
  654. -- everything that's in one of the offers the trader makes
  655. for j,w in ipairs( self.trader_goods ) do -- for all trade offer
  656. for i,v in ipairs( w ) do -- for one particular trade offer and all possible payments
  657. if( type( v )=='table' ) then
  658. for _,s in ipairs( v ) do -- for all items that are part of a trade
  659. mob_trading.insert_item_limitation( items, ItemStack(s):get_name(), 4, true );
  660. end
  661. else -- only one item is offered
  662. mob_trading.insert_item_limitation( items, ItemStack(v):get_name(), 4, true);
  663. end
  664. end
  665. end
  666. end
  667. -- everything for which there's already a self.trader_limit.sell_if_more limit
  668. for k,v in pairs( self.trader_limit.sell_if_more ) do
  669. mob_trading.insert_item_limitation( items, k, 2, v );
  670. end
  671. -- everything for which there's already a self.trader_limit.buy_if_less limit
  672. for k,v in pairs( self.trader_limit.buy_if_less ) do
  673. mob_trading.insert_item_limitation( items, k, 3, v );
  674. end
  675. -- show the input form for new limits
  676. if( fields[ npc_id..'_limitlist' ] ) then
  677. local selection = minetest.explode_table_event( fields[ npc_id..'_limitlist' ] );
  678. if( selection and selection['row']) then
  679. selected = selection['row'];
  680. end
  681. if( (selection['type'] == 'DCL' or selection['type'] == 'CHG')
  682. and mob_trading.tmp_lists[ pname ] and #mob_trading.tmp_lists[ pname ] >= selected
  683. and mob_trading.tmp_lists[ pname ][ selected ] ~= "" ) then
  684. local selected_item = mob_trading.tmp_lists[ pname ][ selected ];
  685. local formspec = 'size[6,4]'..
  686. 'item_image[0.0,1.0;1.0,1.0;'..selected_item..']'..
  687. 'label[1.0,0.0;Set limits for buy and sell]'..
  688. 'label[1.5,0.5;Description:]'..
  689. 'label[3.5,0.5;'..( minetest.registered_items[ selected_item ].description or '?' )..']'..
  690. 'label[1.5,1.0;Item name:]'..
  691. 'label[3.5,1.0;'..tostring( selected_item )..']'..
  692. 'label[1.5,1.5;In stock:]'..
  693. 'label[3.5,1.5;'..tostring( items[ selected_item ][1] )..']'..
  694. 'label[1.5,2.0;Sell if more than]'..
  695. 'field[3.5,2.5;1.2,0.5;SellIfMoreThan;;'..tostring( items[ selected_item ][2] )..']'..
  696. 'label[4.5,2.0;are in stock.]'..
  697. 'label[1.5,2.5;Buy if less than]'..
  698. 'field[3.5,3.0;1.2,0.5;BuyIfLessThan;;'..tostring( items[ selected_item ][3] )..']'..
  699. 'label[4.5,2.5;are in stock.]'..
  700. 'button[0.5,3.5;2,0.5;'..npc_id..'_limitstore_'..tostring(selected)..';Store]'..
  701. 'button[3.0,3.5;2,0.5;'..npc_id..'_limitlist;Abort]';
  702. minetest.show_formspec( pname, "mob_trading:trader", formspec );
  703. mob_basics.update( self, 'trader'); -- store current limitations
  704. return;
  705. end
  706. end
  707. -- all items for which limitations might possibly be needed have been collected;
  708. -- now display them
  709. local formspec = 'size[12,12]'..
  710. 'button[4.0,2.0;2,0.5;'..npc_id..'_main;Back]'..
  711. 'tablecolumns[' ..
  712. -- 'image;'..
  713. 'text,align=left;'..
  714. 'color;text,align=right;'..
  715. 'color;text,align=center;'..
  716. 'text,align=right;'..
  717. 'color;text,align=center;'..
  718. 'text,align=right;'..
  719. 'color;text,align=left]'..
  720. 'table[0.1,2.7;11.4,8.8;'..npc_id..'_limitlist;';
  721. if( not( mob_trading.tmp_lists[ pname ])) then
  722. mob_trading.tmp_lists[ pname ] = {};
  723. end
  724. local col = 0;
  725. local row = 2;
  726. for k,v in pairs( items ) do
  727. table.insert( mob_trading.tmp_lists[ pname ], k );
  728. local c1 = '#FF0000';
  729. if( v[1] > 0 ) then
  730. c1 = '#BBBBBB';
  731. end
  732. local t1 = 'sell always';
  733. local c2 = '#006600';
  734. if( v[2] > 0 ) then
  735. c2 = '#00FF00';
  736. t1 = 'sell if more than:';
  737. end
  738. local t2 = 'buy always';
  739. local c3 = '#666600';
  740. if( v[3] ~= 10000 ) then
  741. c3 = '#FFFF00';
  742. t2 = 'buy if less than:';
  743. end
  744. local desc = '';
  745. if( k =="" ) then
  746. desc = '<empty inventory slot>';
  747. k = '<nothing>';
  748. elseif( minetest.registered_items[ k ]
  749. and minetest.registered_items[ k ].description ) then
  750. desc = minetest.registered_items[ k ].description;
  751. end
  752. formspec = formspec..
  753. desc..','..
  754. c1..','.. tostring( v[1] )..','..
  755. c2..','..t1..','..tostring( v[2] )..','..
  756. c3..','..t2..','..tostring( v[3] )..',#AAAAAA,'..k..',';
  757. end
  758. if( selected > #items ) then
  759. selected = 1;
  760. end
  761. mob_basics.update( self, 'trader'); -- store current limitations
  762. formspec = formspec..';'..selected..']';
  763. -- display the formspec
  764. minetest.show_formspec( pname, "mob_trading:trader", formspec );
  765. end
  766. -------------------------------------------------------------------------------
  767. -- show a formspec that allows to add a new trade offer or edit an existing one
  768. -------------------------------------------------------------------------------
  769. mob_trading.show_trader_formspec_edit = function( self, player, menu_path, fields, trader_goods, formspec, npc_id, pname )
  770. local player_inv = player:get_inventory();
  771. local edit_nr = 0;
  772. if( menu_path[2]=='add') then
  773. formspec = formspec..
  774. 'button[0.5,6.3;2,0.5;'..npc_id..'_storenew;Store]'..
  775. 'button[3.0,6.3;2,0.5;'..npc_id..'_main;Abort]'..
  776. 'label[3.0,-0.2;Add a new simple trade offer]'..
  777. 'textarea[1.0,0.5;9,1.5;info;;'..( minetest.formspec_escape(
  778. 'Plese enter what you want to trade in exchange for what.\n'..
  779. 'The items in the top row of your inventory serve as sample entries to the fields here.\n'..
  780. 'Please edit the input fields to suit your needs or abort and re-arrange your inventory so\n'..
  781. 'that what you want to offer is leftmost, while trade goods you ask for extend to the right.'))..']';
  782. elseif( menu_path[2]=='addm' ) then
  783. formspec = formspec..
  784. 'button[0.5,6.3;2,0.5;'..npc_id..'_storenewm;Store]'..
  785. 'button[3.0,6.3;2,0.5;'..npc_id..'_main;Abort]'..
  786. 'label[3.0,-0.2;Add a new complex trade offer]'..
  787. 'textarea[1.0,0.5;9,1.5;info;;'..( minetest.formspec_escape(
  788. 'Plese enter which item(s) you want to trade in exchange for which item(s).\n'..
  789. 'The items in the colored columns of your inventory serve as sample entries to the fields here.\n'..
  790. 'Please edit the input fields to suit your needs or abort and re-arrange your inventory.\n'..
  791. 'The green, blue and gray fields form bundles of up to four items.'))..']';
  792. elseif( menu_path[2]=='edit' ) then
  793. formspec = formspec..
  794. 'button_exit[0.5,6.3;2,0.5;'..npc_id..'_storechange_'..menu_path[3]..';Store]'..
  795. 'button_exit[3.0,6.3;2,0.5;'..npc_id..'_main;Abort]'..
  796. 'label[3.0,-0.2;Edit trade offer]'..
  797. 'textarea[1.0,1.5;9,0.5;info;;'..minetest.formspec_escape(
  798. 'Plese edit this trade offer according to your needs.')..']';
  799. edit_nr = tonumber( menu_path[3] );
  800. if( not( edit_nr ) or edit_nr < 1 or edit_nr>#trader_goods ) then
  801. edit_nr = 0;
  802. end
  803. end
  804. local texts = {};
  805. -- add a complex trade with multiple (up to four) items for each side? or is a 1:1 trade sufficient?
  806. local extended = false;
  807. if( menu_path[2]=='addm' ) then
  808. extended = true;
  809. end
  810. for i=1,mob_trading.MAX_ALTERNATE_PAYMENTS do
  811. for j=1,4 do
  812. local text = '';
  813. -- edit input from previous attempt
  814. if( fields and fields[ 't'..tostring(((i-1)*4)+j) ] ) then
  815. text = fields[ 't'..tostring(((i-1)*4)+j) ];
  816. -- edit an existing offer
  817. elseif( edit_nr and edit_nr > 0 and edit_nr <= #trader_goods ) then
  818. if( type( trader_goods[ edit_nr ][i] )=='table' ) then
  819. text = ( trader_goods[ edit_nr ][i][j] or '');
  820. extended = true;
  821. elseif( j==1 ) then
  822. text = ( trader_goods[ edit_nr ][i] or '');
  823. else
  824. text = '';
  825. end
  826. -- take what's in the player's inventory as a base
  827. else
  828. local stack = player_inv:get_stack( 'main', ((j-1)*8)+i );
  829. if( not( stack:is_empty() )) then
  830. text = stack:get_name()..' '..stack:get_count();
  831. else
  832. text = '';
  833. end
  834. end
  835. table.insert( texts, text );
  836. end
  837. end
  838. for i=1,mob_trading.MAX_ALTERNATE_PAYMENTS do
  839. local o = 0;
  840. local ltext = 'or';
  841. local boxcolor = '#0000CC';
  842. -- the 'Sell' is not as far to the right as the rest
  843. if( i==1 ) then
  844. o = -1;
  845. ltext = 'Sell';
  846. boxcolor = '#00AA00';
  847. elseif( i==2 ) then
  848. ltext = 'for';
  849. elseif( i>1 and (i%2==1)) then
  850. boxcolor = '#AAAAAA';
  851. end
  852. if( extended ) then -- distinguish between simple (one item offered, 1 wanted) and complex (up to 4 offered; up to 4 wanted) trades
  853. if( i<5 ) then
  854. formspec = formspec..
  855. 'label['..(1.0+o)..','..( 1.0+(i*1.1))..';'..ltext..']'..
  856. 'box['..( 0.8+o)..','..( 0.86+(i*1.1))..';9.1,1.04;'..boxcolor..']'..
  857. 'field['..(2.1+o)..','..( 1.0+(i*1.1))..';3.9,1.0;t'..tostring((i*4)-3)..';;'..
  858. minetest.formspec_escape( texts[ (i*4)-3] )..']'..
  859. 'field['..(2.1+o)..','..( 1.5+(i*1.1))..';3.9,1.0;t'..tostring((i*4)-2)..';;'..
  860. minetest.formspec_escape( texts[ (i*4)-2] )..']'..
  861. 'field['..(6.2+o)..','..( 1.0+(i*1.1))..';3.9,1.0;t'..tostring((i*4)-1)..';;'..
  862. minetest.formspec_escape( texts[ (i*4)-1] )..']'..
  863. 'field['..(6.2+o)..','..( 1.5+(i*1.1))..';3.9,1.0;t'..tostring((i*4) )..';;'..
  864. minetest.formspec_escape( texts[ (i*4) ] )..']'..
  865. 'label['..(5.5+o)..','..( 1.0+(i*1.1))..';and]';
  866. end
  867. else
  868. -- the colors are a bit darker when offering a simple trade
  869. if( boxcolor=='#AAAAAA' ) then
  870. boxcolor = '#777777';
  871. elseif( boxcolor=='#0000CC' ) then
  872. boxcolor = '#000077';
  873. end
  874. formspec = formspec..
  875. 'label['..(2.0+o)..','..( 2.5+(i*0.5))..';'..ltext..']'..
  876. 'box['..( 1.8+o)..','..( 2.55+(i*0.5))..';8.1,0.51;'..boxcolor..']'..
  877. 'field['..(3.1+o)..','..( 2.7+(i*0.5))..';7,1.0;t'..tostring((i*4)-3)..';;'..
  878. minetest.formspec_escape( texts[ (i*4)-3 ] )..']';
  879. end
  880. end
  881. minetest.show_formspec( pname, "mob_trading:trader", formspec );
  882. end
  883. -----------------------------------------------------------------------------------------------------
  884. -- checks if the deptor can pay the price to the receiver (and if the receiver has enough free space)
  885. -----------------------------------------------------------------------------------------------------
  886. -- If the other side is an admin shop/trader with unlimited supply:
  887. -- receiver_name has to be nil or '' and receiver_inv has to be empty for unlmiited trade
  888. -- The function uses recursion in case of table value for price_stack_str and calls itshelf for each price part.
  889. mob_trading.can_trade = function( price_stack_str, debtor_name, debtor_inv, receiver_name, receiver_inv, player_is_debtor, counted_inv, self )
  890. -- we've got multiple items to care for
  891. if( type( price_stack_str )=='table' ) then
  892. -- sum up requests like 2x99 of one type or multiple requests for money
  893. local items = {};
  894. local anz_diffrent_items = 0;
  895. for _,v in ipairs( price_stack_str ) do
  896. local price_stack = ItemStack( v );
  897. -- get information about the price
  898. local price_stack_name = price_stack:get_name();
  899. local price_stack_count = price_stack:get_count();
  900. if( not( items[ price_stack_name ])) then
  901. items[ price_stack_name ] = price_stack_count;
  902. -- lua can't count....
  903. anz_diffrent_items = anz_diffrent_items + 1;
  904. else
  905. items[ price_stack_name ] = items[ price_stack_name ] + price_stack_count;
  906. end
  907. end
  908. -- check for each part if it can be paid
  909. local price_desc = '';
  910. local price_stacks = {};
  911. local price_types = {};
  912. local free_slots_wanted = #price_stack_str; -- for each price stack, we need a free inventory slot
  913. for k,v in pairs( items ) do
  914. -- recursively check if payment is possible
  915. local res = mob_trading.can_trade( k..' '..tostring( v ), debtor_name, debtor_inv, receiver_name, receiver_inv, player_is_debtor, counted_inv, self );
  916. -- if a part cannot be paid, the whole trade cannot be made
  917. if( res.error_msg ) then
  918. return res;
  919. end
  920. -- store the information about this part of the payment
  921. table.insert( price_stacks, res.price_stacks[1]);
  922. table.insert( price_types, res.price_types[1] );
  923. -- description of first item
  924. if( price_desc == '' ) then
  925. price_desc = res.price_desc;
  926. -- cheat: this is the last price description
  927. elseif( #price_stacks == anz_diffrent_items ) then
  928. price_desc = price_desc..' and '..res.price_desc;
  929. else
  930. price_desc = price_desc..', '..res.price_desc;
  931. end
  932. -- if money/money2 is part of the price, then that will not need a free inventory slot in the trader's chest
  933. if( (res.price_types and res.price_types[1] ~= 'direct' ) or (player_is_debtor and not(counted_inv)) ) then
  934. free_slots_wanted = free_slots_wanted - 1;
  935. end
  936. end
  937. -- with several items as payment, we want a free slot for each payment - else we cannot be sure that the trader can store all of the payment
  938. if( free_slots_wanted > 0 and (not(counted_inv) or not(counted_inv[""]) or counted_inv[""]<free_slots_wanted )) then
  939. return { error_msg = 'Sorry, I do not have enough free inventory slots to ensure that the trade can take place.',
  940. price_desc = price_desc, price_stacks = price_stacks, price_types = price_types };
  941. end
  942. -- if all parts can be paid, the whole payment will be possible
  943. return { error_msg = nil, price_desc = price_desc, price_stacks = price_stacks, price_types = price_types };
  944. end
  945. local price_stack = ItemStack( price_stack_str );
  946. -- get information about the price
  947. local price_desc = '';
  948. local price_stack_name = price_stack:get_name();
  949. local price_stack_count = price_stack:get_count();
  950. -- this is set to a text message in case something can't be paid
  951. local error_msg = '';
  952. -- the trade may contain money (from two diffrent mods) or items; this indicates which tpye was choosen by price_stack_str
  953. local price_type = '?';
  954. -- empty price stacks are pointless for a trader
  955. if( price_stack:is_empty() or price_stack_count < 0) then
  956. error_msg = 'Sorry. This is no exchange of presents. Both sides have to contribute to the trade. The following is not acceptable: '..
  957. tostring( price_stack_str );
  958. price_desc = price_stack_str;
  959. -- in case the money mod is used
  960. elseif( price_stack_name == mob_trading.MONEY_ITEM) then
  961. price_type = 'money';
  962. price_desc = CURRENCY_PREFIX..price_stack_count..CURRENCY_POSTFIX;
  963. if( not( money ) or not( money.exist )) then
  964. error_msg = 'Sorry. There seems to be something wrong with the money mod.';
  965. elseif( debtor_name and debtor_name ~= '' and not( money.exist( debtor_name ))) then
  966. error_msg = 'no_account_debtor';
  967. -- the other party needs an account as well (except for admin shops)
  968. elseif( receiver_name and receiver_name ~= '' and not( money.exist( receiver_name ))) then
  969. error_msg = 'no_account_receiver';
  970. elseif( debtor_name and money.get_money( debtor_name ) < price_stack_count ) then
  971. error_msg = 'no_money';
  972. end
  973. -- in case the money2 mod is used
  974. elseif( price_stack_name == mob_trading.MONEY2_ITEM) then
  975. price_type = 'money2';
  976. price_desc = price_stack_count..' '..( money.currency_name or 'cr' );
  977. if( not( money ) or not( money.has_credit ) or not( money.get )) then
  978. error_msg = 'Sorry. There seems to be something wrong with the money2 mod.';
  979. elseif( debtor_name and debtor_name ~= '' and not( money.has_credit( debtor_name ))) then
  980. error_msg = 'no_account_debtor';
  981. elseif( receiver_name and receiver_name ~= '' and not( money.has_credit( receiver_name ))) then
  982. error_msg = 'no_account_receiver';
  983. elseif( debtor_name and money.get( debtor_name ) < price_stack_count ) then
  984. error_msg = 'no_money';
  985. end
  986. -- item-based trade
  987. else
  988. price_type = 'direct';
  989. if( not( minetest.registered_items[ price_stack_name ] )) then
  990. error_msg = 'There is something wrong with my offer. Seems \''..tostring( price_stack_name )..'\' does not exist anymore.';
  991. price_desc = price_stack_name;
  992. else
  993. price_desc = price_stack_count..'x '..
  994. ( minetest.registered_items[ price_stack_name ].description or price_stack_name);
  995. end
  996. -- does the debtor have the item?
  997. if( debtor_inv and not( debtor_inv:contains_item("main", price_stack ))) then
  998. error_msg = 'no_item';
  999. -- does the trader have more than sell_if_more of the item requested?
  1000. elseif( debtor_inv
  1001. and counted_inv
  1002. and not( player_is_debtor )
  1003. and self.trader_limit
  1004. and self.trader_limit.sell_if_more
  1005. and self.trader_limit.sell_if_more[ price_stack_name ]
  1006. -- less than what the trader is supposed to keep as a reserve
  1007. and self.trader_limit.sell_if_more[ price_stack_name ] > (counted_inv[ price_stack_name ]-price_stack_count) ) then
  1008. error_msg = 'no_intrest';
  1009. -- does the trader want any more of this kind of payment?
  1010. elseif( debtor_inv
  1011. and counted_inv
  1012. and player_is_debtor
  1013. and self.trader_limit
  1014. and self.trader_limit.buy_if_less
  1015. and self.trader_limit.buy_if_less[ price_stack_name ]
  1016. -- less than what the trader is supposed to keep as a reserve
  1017. and self.trader_limit.buy_if_less[ price_stack_name ] < (counted_inv[ price_stack_name ]+price_stack_count) ) then
  1018. error_msg = 'no_intrest';
  1019. -- does the receiver have enough free room to take the item?
  1020. elseif( receiver_inv and not( receiver_inv:room_for_item("main", price_stack ))) then
  1021. error_msg = 'no_space';
  1022. end
  1023. end
  1024. if( error_msg == '' ) then
  1025. return { error_msg = nil, price_desc = price_desc, price_stacks = {price_stack}, price_types = {price_type} };
  1026. end
  1027. -- create extensive error messages, depending on who does lack what in order to finish the trade
  1028. if( error_msg == 'no_account_debtor' ) then
  1029. if( player_is_debtor ) then
  1030. error_msg = 'You do not have a bank account. Please get one so that we can trade.';
  1031. else
  1032. error_msg = 'Sorry. I lost my bank account data. Please contact my owner!';
  1033. end
  1034. elseif( error_msg == 'no_account_receiver') then
  1035. if( not(player_is_debtor)) then
  1036. error_msg = 'You do not have a bank account. Please get one so that we can trade.';
  1037. else
  1038. error_msg = 'Sorry. I lost my bank account data. Please contact my owner!';
  1039. end
  1040. elseif( error_msg == 'no_money' ) then
  1041. if( player_is_debtor) then
  1042. error_msg = 'You do not have enough money. The price is '..tostring( price_desc )..'.';
  1043. else
  1044. error_msg = 'Sorry, my shop ran out of money. I cannot afford to buy. Please come back later!';
  1045. end
  1046. elseif( error_msg == 'no_item' ) then
  1047. if( player_is_debtor) then
  1048. error_msg = 'You do not have '..tostring( price_desc )..'.';
  1049. else
  1050. error_msg = 'Oh. I just noticed that I ran out of '..tostring( price_desc )..'. Please come back later!';
  1051. end
  1052. elseif( error_msg == 'no_space' ) then
  1053. if( player_is_debtor) then
  1054. error_msg = 'Sorry. I do not have any storage space left for '..tostring( price_desc )..'. Please come back later!';
  1055. else
  1056. error_msg = 'You do not have enough free space in your inventory for '..tostring( price_desc )..'.';
  1057. end
  1058. -- in case the self.trader_limit.* values prevent the trader from trading
  1059. elseif( error_msg == 'no_intrest') then
  1060. if( player_is_debtor) then
  1061. error_msg = 'Sorry, I am not intrested in any more '..tostring( price_desc )..' right now. Please come back later!';
  1062. else
  1063. error_msg = 'Sorry, but I do not have any '..tostring( price_desc )..' left I\'m willing to sell. Please come back later!';
  1064. end
  1065. end
  1066. -- price_desc is important for printing out the price to the player
  1067. return { error_msg = error_msg, price_desc = price_desc, price_stacks = {price_stack}, price_types = {price_type} };
  1068. end
  1069. -----------------------------------------------------------------------------------------------------
  1070. -- moves stack from source_inv to target_inv;
  1071. -- if either does not exist, the stack is removed (i.e. with traders that are not of type individual)
  1072. -----------------------------------------------------------------------------------------------------
  1073. mob_trading.move_trade_goods = function( source_inv, target_inv, stack, player, self )
  1074. local stacks_removed = {};
  1075. -- in case of non-individual traders selling something, there might be no source inv
  1076. if( source_inv ) then
  1077. local anz = stack:get_count();
  1078. -- large stacks may have to be split up
  1079. while( anz > 0 ) do
  1080. -- do not create stacks which are larger than get_stack_max
  1081. if( stack:get_stack_max() < anz) then
  1082. stack:set_count( stack:get_stack_max() );
  1083. end
  1084. local removed = source_inv:remove_item( "main", stack );
  1085. if( not(removed) or removed:get_count() < 1 ) then
  1086. -- this error is not supposed to happen - we DID check if everything could get removed; if this error occourse nonetheless,
  1087. -- it requires further invesitation
  1088. minetest.chat_send_player( player:get_player_name(),'Error: Could not transfer all the promised items. Failed to remove '..
  1089. tostring( stack:get_name() )..' '..tostring( stack:get_count() )..'. Please contact an admin!');
  1090. print( '[mob_trading] ERROR: Could not transfer all items; player: '..tostring( player:get_player_name() )..
  1091. ', trading with '..tostring( self.trader_name or '?' )..'; '..tostring( stack:get_name() )..
  1092. ' '..tostring( stack:get_count()..'.'));
  1093. return false;
  1094. end
  1095. anz = anz - removed:get_count();
  1096. stack:set_count( anz );
  1097. table.insert( stacks_removed, removed );
  1098. end
  1099. else
  1100. stacks_removed = { stack };
  1101. end
  1102. -- non-individual traders do not store what they receive; they have no target inv
  1103. if( not( target_inv )) then
  1104. return true;
  1105. end
  1106. for i,v in pairs( stacks_removed ) do
  1107. -- the stack may be larger than max stack size and thus require more than one add_item-call
  1108. local remaining_stack = v;
  1109. while( not( remaining_stack:is_empty() )) do
  1110. -- add as many as possible in one go
  1111. local leftover = target_inv:add_item( 'main', remaining_stack );
  1112. -- in case nothing was added to target_inv: an error occoured (i.e. target_inv full)
  1113. if( not( leftover:is_empty())
  1114. and (leftover:get_count() >= remaining_stack:get_count())) then
  1115. -- find a place between player and trader so that the player can see the items falling down; slightly elevated
  1116. local p1 = player:get_pos();
  1117. local p2 = self.object:get_pos();
  1118. local p3 = {x=p1.x-((p1.x-p2.x)/2), y=p1.y-((p1.y-p2.y)/2)+1.0, z=p1.z-((p1.z-p2.z)/2)};
  1119. -- tell the player to take a look
  1120. minetest.chat_send_player( player:get_player_name(), self.trader_name..': '..
  1121. 'You do not have enough free space in your inventory. '..
  1122. 'Therefore, '..leftover:get_count()..'x '..leftover:get_name()..' have been dropped at where you stand.');
  1123. -- place the item stack at the position where the player is standing
  1124. minetest.add_item( p3, remaining_stack );
  1125. -- the stack was dropped completely
  1126. leftover:set_count(0);
  1127. if( not( leftover:is_empty())) then
  1128. minetest.chat_send_player( player:get_player_name(), self.trader_name..': ERROR: Failed to drop the items at your feet!');
  1129. return;
  1130. end
  1131. end
  1132. remaining_stack = leftover;
  1133. end
  1134. end
  1135. -- everything has been moved
  1136. return true;
  1137. end
  1138. -----------------------------------------------------------------------------------------------------
  1139. -- locates a locked chest owned by the given trader and returns the inventory;
  1140. -- sets self.trader_inv to the inventory
  1141. -----------------------------------------------------------------------------------------------------
  1142. mob_trading.find_trader_inv = function( self )
  1143. if( not( self ) or not( self.object )) then
  1144. return nil;
  1145. end
  1146. local RANGE = mob_trading.LOCKED_CHEST_SEARCH_RANGE;
  1147. local tpos = self.object:get_pos(); -- current position of the trader
  1148. -- search for locked chest from default, locks mod and technic mod chests
  1149. -- ignore technic mithril chests as those are not locked
  1150. local chest_list = minetest.find_nodes_in_area(
  1151. { x=(tpos.x-RANGE), y=(tpos.y-RANGE), z=(tpos.z-RANGE )},
  1152. { x=(tpos.x+RANGE), y=(tpos.y+RANGE), z=(tpos.z+RANGE )},
  1153. mob_trading.KNOWN_LOCKED_CHESTS );
  1154. for _, p in ipairs( chest_list ) do
  1155. local meta = minetest.get_meta( p );
  1156. if( meta and meta:get_string('owner') and meta:get_string('owner')==self.trader_owner ) then
  1157. self.trader_inv = meta:get_inventory();
  1158. return self.trader_inv;
  1159. end
  1160. end
  1161. return nil;
  1162. end
  1163. -----------------------------------------------------------------------------------------------------
  1164. -- count how many items of each type the trader has in his chest
  1165. -----------------------------------------------------------------------------------------------------
  1166. -- empty stacks are counted under the key ""; for other items, the amount of items of each type is counted
  1167. mob_trading.count_trader_inv = function( self )
  1168. if( not( self.trader_inv )) then
  1169. return {};
  1170. end
  1171. local anz = self.trader_inv:get_size('main');
  1172. local stored = {};
  1173. for i=1, anz do
  1174. local stack = self.trader_inv:get_stack('main', i );
  1175. local name = stack:get_name();
  1176. local count = stack:get_count();
  1177. -- count empty stacks
  1178. if( name=="" ) then
  1179. count = 1;
  1180. end
  1181. -- count how much of each item is there
  1182. if( not( stored[ name ])) then
  1183. stored[ name ] = count;
  1184. else
  1185. stored[ name ] = stored[ name ] + count;
  1186. end
  1187. end
  1188. return stored;
  1189. end
  1190. -----------------------------------------------------------------------------------------------------
  1191. -- check if payment and trade are possible; do the actual trade
  1192. -----------------------------------------------------------------------------------------------------
  1193. -- self ought to contain: trader_id, trader_typ, trader_owner, trader_home_pos, trader_sold (optional - for statistics)
  1194. -- traders of the type 'individual' who do have owners will search their environment for chests owned by their owner;
  1195. -- said chests contain the stock of the trader
  1196. mob_trading.do_trade = function( self, player, menu_path, trade_details, counted_inv )
  1197. if( not( self ) or not( player ) or not( menu_path ) or #menu_path < 3) then
  1198. return {msg='', success=false};
  1199. end
  1200. local player_inv = player:get_inventory();
  1201. local pname = player:get_player_name();
  1202. local choice2 = tonumber( menu_path[3] );
  1203. local formspec = '';
  1204. -- the first entry is what is sold
  1205. if( not( choice2 ) or choice2 > #trade_details or choice2 < 2) then
  1206. choice2 = 2;
  1207. end
  1208. local trader_inv = nil;
  1209. -- traders who do have an owner need to have an inventory somewhere
  1210. if( self.trader_owner and self.trader_owner ~= '' and self.trader_typ=='individual') then
  1211. if( not( self.trader_inv )) then
  1212. self.trader_inv = mob_trading.find_trader_inv( self );
  1213. end
  1214. if( not( self.trader_inv )) then
  1215. return {msg='Sorry. I was unable to find my storage chest. Please contact my owner!', success=false};
  1216. end
  1217. end
  1218. -- can the player pay the selected payment to the trader?
  1219. local player_can_trade = mob_trading.can_trade( trade_details[ choice2 ], pname, player_inv, self.trader_owner, self.trader_inv, true, counted_inv, self );
  1220. if( player_can_trade.error_msg ) then
  1221. return {msg=player_can_trade.error_msg, success=false};
  1222. end
  1223. -- can the trader in turn give the player what the player paid for?
  1224. local trader_can_trade = mob_trading.can_trade( trade_details[ 1 ], self.trader_owner, self.trader_inv, pname, player_inv, false, counted_inv, self );
  1225. if( trader_can_trade.error_msg ) then
  1226. return {msg=trader_can_trade.error_msg, success=false};
  1227. end
  1228. -- both sides are able to give what they agreed on - the trade may progress;
  1229. -- traders that use money/money2 need to have an owner for their account
  1230. -- each trade may require the exchange of multiple items
  1231. for i,v in pairs( player_can_trade.price_types ) do
  1232. -- the player pays first
  1233. if( player_can_trade.price_types[i] == 'money' ) then
  1234. local amount = player_can_trade.price_stacks[i]:get_count();
  1235. money.set_money( self.trader_owner, get_money( self.trader_owner ) + amount );
  1236. money.set_money( pname, get_money( pname ) - amount );
  1237. elseif( player_can_trade.price_types[i] == 'money2' ) then
  1238. local res = money.transfer( pname, self.trader_owner, player_can_trade.price_stacks[i]:get_count() );
  1239. if( res ) then
  1240. return {msg='Internal error: Payment failed: '..tostring( res )..'.', success=false};
  1241. end
  1242. elseif( player_can_trade.price_types[i] == 'direct' ) then
  1243. local res = mob_trading.move_trade_goods( player_inv, self.trader_inv, player_can_trade.price_stacks[i], player, self );
  1244. end
  1245. end
  1246. for i,v in pairs( trader_can_trade.price_types ) do
  1247. -- the trader replies
  1248. if( trader_can_trade.price_types[i] == 'money' ) then
  1249. local amount = trader_can_trade.price_stacks[i]:get_count();
  1250. money.set_money( pname, get_money( pname ) + amount );
  1251. money.set_money( self.trader_owner, get_money( self.trader_owner ) - amount );
  1252. elseif( trader_can_trade.price_types[i] == 'money2' ) then
  1253. local res = money.transfer( self.trader_owner, pname, trader_can_trade.price_stacks[i]:get_count() );
  1254. if( not( res )) then
  1255. return {msg='Internal error: Payment failed.', success=false};
  1256. end
  1257. elseif( trader_can_trade.price_types[i] == 'direct' ) then
  1258. local res = mob_trading.move_trade_goods( self.trader_inv, player_inv, trader_can_trade.price_stacks[i], player, self );
  1259. end
  1260. end
  1261. -- let the trader do some statistics
  1262. if( not( self.trader_sold )) then
  1263. self.trader_sold = {};
  1264. end
  1265. if( not( self.trader_sold[ trade_details[ 1 ]] )) then
  1266. self.trader_sold[ trade_details[ 1 ]] = 1;
  1267. else
  1268. self.trader_sold[ trade_details[ 1 ]] = self.trader_sold[ trade_details[ 1 ]] +1;
  1269. end
  1270. -- log the action
  1271. minetest.log("action", '[mob_trading] '..
  1272. player:get_player_name()..
  1273. ' gets '..minetest.serialize( trade_details[ 1 ])..
  1274. ' for ' ..minetest.serialize( trade_details[ choice2 ])..
  1275. ' from '..tostring( self.trader_id )..
  1276. ' at '..minetest.pos_to_string( self.object:get_pos() )..
  1277. ' (owned by '..tostring( self.trader_owner )..')'..
  1278. ' typ:'..tostring( self.trader_typ or '?' )..'.');
  1279. mob_basics.update( self, 'trader'); -- store updated statistic of sold items
  1280. return {msg='You got '..trader_can_trade.price_desc..' for your '..player_can_trade.price_desc..
  1281. '. Thank you! Would you like to trade more?', success=true};
  1282. end