api.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. local FARMING_DEFTIME_MIN = 60*10
  2. local FARMING_DEFTIME_MAX = 60*15
  3. -- Localize for performance.
  4. local math_random = math.random
  5. function farming.notify_soil(pos)
  6. local minp = vector.add(pos, -4)
  7. local maxp = vector.add(pos, 4)
  8. local soils = minetest.find_nodes_in_area(minp, maxp, "group:field")
  9. if soils and #soils > 0 then
  10. for i=1, #soils do
  11. local timer = minetest.get_node_timer(soils[i])
  12. if timer and not timer:is_started() then
  13. timer:start(math_random(1, 60))
  14. end
  15. end
  16. end
  17. end
  18. function farming.notify_soil_single(pos)
  19. local timer = minetest.get_node_timer(pos)
  20. if timer and not timer:is_started() then
  21. timer:start(math_random(1, 60))
  22. end
  23. end
  24. -- Wear out hoes, place soil
  25. -- TODO Ignore group:flower (note to self: why?)
  26. farming.hoe_on_use = function(itemstack, user, pointed_thing, uses)
  27. local pt = pointed_thing
  28. -- check if pointing at a node
  29. if not pt then
  30. return
  31. end
  32. if pt.type ~= "node" then
  33. return
  34. end
  35. local pname = user:get_player_name()
  36. local under = minetest.get_node(pt.under)
  37. local p = {x=pt.under.x, y=pt.under.y+1, z=pt.under.z}
  38. local above = minetest.get_node(p)
  39. -- return if any of the nodes is not registered
  40. if not minetest.reg_ns_nodes[under.name] then
  41. return
  42. end
  43. if not minetest.reg_ns_nodes[above.name] then
  44. return
  45. end
  46. -- Allow 'default:dirt' and 'default:desert_sand' to be hoed, bypassing protection.
  47. -- This is needed because the hoed/soil versions of these two nodes can be
  48. -- trampled bypassing protection, causing them to revert to their base dirt form.
  49. -- Note that hoed dirt/sand can only be trampled if nothing is growing on it.
  50. -- Important: do NOT allow any kind of grass to be hoed without protection permission.
  51. if not (under.name == "default:dirt" or under.name == "default:desert_sand") then
  52. if minetest.is_protected(pt.under, pname) then
  53. return
  54. end
  55. if minetest.is_protected(pt.above, pname) then
  56. return
  57. end
  58. end
  59. -- Let hoes be used to get resources back from planted mese crystals.
  60. -- Note that harvesting a crystal completely yeilds more fragments,
  61. -- but there is a risk that the you won't be able to restore the plant when you're done.
  62. if string.find(under.name, "^mese_crystals:mese_crystal_ore%d") then
  63. user:get_inventory():add_item("main", "default:mese_crystal_fragment 3")
  64. ambiance.sound_play("default_break_glass", pt.under, 0.3, 10)
  65. minetest.remove_node(pt.under)
  66. -- 1/2 chance to get bluerack back; this is because 1 bluerack makes 2 seeds.
  67. -- This way, we don't make it possible to magically duplicate resources.
  68. local p = {x=pt.under.x, y=pt.under.y-1, z=pt.under.z}
  69. if not minetest.test_protection(p, pname) then
  70. if math_random(1, 2) == 1 then
  71. if minetest.get_node(p).name == "default:obsidian" then
  72. minetest.add_node(p, {name="rackstone:bluerack"})
  73. ambiance.sound_play("default_dig_cracky", pt.under, 1.0, 10)
  74. end
  75. end
  76. end
  77. return
  78. end
  79. -- check if the node above the pointed thing is air
  80. if above.name ~= "air" then
  81. return
  82. end
  83. -- check if pointing at soil
  84. if minetest.get_item_group(under.name, "soil") ~= 1 then
  85. return
  86. end
  87. -- check if (wet) soil defined
  88. local ndef = minetest.reg_ns_nodes[under.name]
  89. if ndef.soil == nil or ndef.soil.wet == nil or ndef.soil.dry == nil then
  90. return
  91. end
  92. -- turn the node into soil and play sound
  93. minetest.add_node(pt.under, {name = ndef.soil.dry})
  94. minetest.sound_play("default_dig_crumbly", {
  95. pos = pt.under,
  96. gain = 0.5,
  97. }, true)
  98. farming.notify_soil_single(pt.under)
  99. -- wear tool
  100. local wdef = itemstack:get_definition()
  101. itemstack:add_wear(65535/(uses-1))
  102. -- tool break sound
  103. if itemstack:get_count() == 0 and wdef.sound and wdef.sound.breaks then
  104. minetest.sound_play(wdef.sound.breaks, {pos = pt.above, gain = 0.5}, true)
  105. end
  106. return itemstack
  107. end
  108. -- Register new hoes
  109. farming.register_hoe = function(name, def)
  110. -- Check for : prefix (register new hoes in your mod's namespace)
  111. if name:sub(1,1) ~= ":" then
  112. name = ":" .. name
  113. end
  114. -- Check def table
  115. if def.description == nil then
  116. def.description = "Hoe"
  117. end
  118. if def.inventory_image == nil then
  119. def.inventory_image = "unknown_item.png"
  120. end
  121. if def.recipe == nil then
  122. def.recipe = {
  123. {"air","air",""},
  124. {"","group:stick",""},
  125. {"","group:stick",""}
  126. }
  127. end
  128. if def.max_uses == nil then
  129. def.max_uses = 30
  130. end
  131. -- Register the tool
  132. minetest.register_tool(name, {
  133. description = def.description,
  134. inventory_image = def.inventory_image,
  135. on_use = function(itemstack, user, pointed_thing)
  136. return farming.hoe_on_use(itemstack, user, pointed_thing, def.max_uses)
  137. end,
  138. groups = def.groups,
  139. sound = {breaks = "default_tool_breaks"},
  140. })
  141. -- Register its recipe
  142. if def.material == nil then
  143. minetest.register_craft({
  144. output = name:sub(2),
  145. recipe = def.recipe
  146. })
  147. else
  148. local handle = "group:stick"
  149. if def.handle then
  150. handle = def.handle
  151. end
  152. minetest.register_craft({
  153. output = name:sub(2),
  154. recipe = {
  155. {def.material, def.material, ""},
  156. {"", handle, ""},
  157. {"", handle, ""}
  158. }
  159. })
  160. -- Reverse Recipe
  161. minetest.register_craft({
  162. output = name:sub(2),
  163. recipe = {
  164. {"", def.material, def.material},
  165. {"", handle, ""},
  166. {"", handle, ""}
  167. }
  168. })
  169. end
  170. end
  171. local function tick_multiplier(pos, def)
  172. local minp = vector.subtract(pos, 2)
  173. local maxp = vector.add(pos, 2)
  174. local soil_pos = vector.add(pos, {x=0, y=-1, z=0})
  175. local soil_meta = minetest.get_meta(soil_pos)
  176. local mult = 1
  177. local cold = minetest.find_nodes_in_area(minp, maxp, "group:cold")
  178. mult = mult + (#cold / 2)
  179. -- Plant can disable minerals, if they should not grow any faster when
  180. -- minerals are present.
  181. if not def.farming_minerals_unused then
  182. minp = vector.subtract(pos, 3)
  183. maxp = vector.add(pos, 3)
  184. local minerals = minetest.find_nodes_in_area(minp, maxp, "glowstone:minerals")
  185. mult = mult - (#minerals / 4)
  186. end
  187. -- Sand is very poor soil!
  188. local sand = minetest.find_nodes_in_area(minp, maxp, "group:sand")
  189. mult = mult + (#sand / 4)
  190. -- Soil may be drained of nutrients from growing the same crop over and over again.
  191. mult = mult + (soil_meta:get_int("soil_fatigue") / 10)
  192. -- Trampled soil retards growth a little bit more.
  193. if soil_meta:get_int("trampled") > 0 then
  194. mult = mult + 0.1
  195. end
  196. -- Clamp time-tick multiplier to minimum value.
  197. if mult < 0.2 then mult = 0.2 end
  198. return mult
  199. end
  200. -- how often node timers for plants will tick, +/- some random value
  201. local function tick(pos, def)
  202. local mult = tick_multiplier(pos, def)
  203. local min = (def.farming_growing_time_min or FARMING_DEFTIME_MIN) * mult
  204. local max = (def.farming_growing_time_max or FARMING_DEFTIME_MAX) * mult
  205. local time = math_random(min, max)
  206. minetest.get_node_timer(pos):start(time)
  207. --minetest.get_node_timer(pos):start(1.0) -- Debug
  208. end
  209. -- how often a growth failure tick is retried (e.g. too dark)
  210. local function tick_again(pos, def)
  211. local min = 50
  212. local max = 100
  213. minetest.get_node_timer(pos):start(math_random(min, max))
  214. --minetest.get_node_timer(pos):start(1.0) -- Debug
  215. end
  216. function farming.restart_timer(pos)
  217. local node = minetest.get_node(pos)
  218. local ndef = minetest.registered_nodes[node.name]
  219. if ndef and ndef._farming_next_plant and ndef.on_timer then
  220. tick(pos, ndef)
  221. end
  222. end
  223. -- Seed placement
  224. farming.place_seed = function(itemstack, placer, pointed_thing, plantname)
  225. local pt = pointed_thing
  226. -- check if pointing at a node
  227. if not pt then
  228. return itemstack
  229. end
  230. if pt.type ~= "node" then
  231. return itemstack
  232. end
  233. local under = minetest.get_node(pt.under)
  234. -- Pass through interactions to nodes that define them (like chests).
  235. do
  236. local ndef = minetest.reg_ns_nodes[under.name]
  237. if ndef and ndef.on_rightclick and not placer:get_player_control().sneak then
  238. return ndef.on_rightclick(pt.under, under, placer, itemstack, pt)
  239. end
  240. end
  241. local above = minetest.get_node(pt.above)
  242. -- Permit player to place seed on protected soil (by commenting this code).
  243. -- This allows players to build public farms.
  244. --if minetest.is_protected(pt.under, placer:get_player_name()) then
  245. -- minetest.record_protection_violation(pt.under, placer:get_player_name())
  246. -- return
  247. --end
  248. if minetest.is_protected(pt.above, placer:get_player_name()) then
  249. minetest.record_protection_violation(pt.above, placer:get_player_name())
  250. return
  251. end
  252. -- return if any of the nodes is not registered
  253. if not minetest.reg_ns_nodes[under.name] then
  254. return itemstack
  255. end
  256. if not minetest.reg_ns_nodes[above.name] then
  257. return itemstack
  258. end
  259. -- check if pointing at the top of the node
  260. if pt.above.y ~= pt.under.y+1 then
  261. return itemstack
  262. end
  263. -- check if you can replace the node above the pointed node
  264. local ndef = minetest.reg_ns_nodes[above.name]
  265. if not ndef or not ndef.buildable_to then
  266. return itemstack
  267. end
  268. local pdef = minetest.reg_ns_nodes[plantname]
  269. if not pdef then
  270. return itemstack
  271. end
  272. local have_surface = false
  273. if pdef.soil_nodes then
  274. for k, v in ipairs(pdef.soil_nodes) do
  275. if v == under.name then
  276. have_surface = true
  277. break
  278. end
  279. end
  280. end
  281. -- check if pointing at soil
  282. if minetest.get_item_group(under.name, "soil") < 2 and not have_surface then
  283. return itemstack
  284. end
  285. -- add the node and remove 1 item from the itemstack
  286. -- note: use of `add_node` automatically invokes droplift + dirtspread notifications.
  287. minetest.add_node(pt.above, {name = plantname, param2 = 1})
  288. tick(pt.above, pdef)
  289. itemstack:take_item()
  290. return itemstack
  291. end
  292. function farming.update_soil_fatigue(soil_pos, seed)
  293. -- Wear out soil if used to grow the same thing many times.
  294. -- Worn soil grows plants slower.
  295. -- Growing different plants each time stops the increase of this
  296. -- counter, but does not reduce it.
  297. local soil_meta = minetest.get_meta(soil_pos)
  298. local fatigue_count = soil_meta:get_int("soil_fatigue")
  299. local last_seed = soil_meta:get_string("last_seed")
  300. local seed_table = minetest.deserialize(last_seed) or {}
  301. -- If seed was grown on this soil previously, increase soil fatigue.
  302. for seed_entry, growth_count in pairs(seed_table) do
  303. if seed_entry == seed then
  304. -- Fatigue is increased by the number of times this seed was grown.
  305. fatigue_count = fatigue_count + growth_count
  306. break
  307. end
  308. end
  309. -- Record how many times this particular plant has been grown.
  310. if not seed_table[seed] then
  311. seed_table[seed] = 1
  312. else
  313. seed_table[seed] = seed_table[seed] + 1
  314. end
  315. -- You can of course clear this metadata by spading the dirt, just like you
  316. -- can aerate soil in real life.
  317. soil_meta:set_int("soil_fatigue", fatigue_count)
  318. soil_meta:set_string("last_seed", minetest.serialize(seed_table))
  319. soil_meta:mark_as_private({"soil_fatigue", "last_seed"})
  320. end
  321. -- This should only ever be called from the `on_timer' callback of a node.
  322. farming.grow_plant = function(pos, elapsed)
  323. local node = minetest.get_node(pos)
  324. local name = node.name
  325. local def = minetest.reg_ns_nodes[name]
  326. local soil_pos = {x = pos.x, y = pos.y - 1, z = pos.z}
  327. local soil_node = minetest.get_node_or_nil(soil_pos)
  328. if not soil_node then
  329. tick_again(pos, def)
  330. --minetest.chat_send_all('fail 1')
  331. return
  332. end
  333. if not def._farming_next_plant then
  334. -- disable timer for fully grown plant
  335. --minetest.chat_send_all('fail 2')
  336. return
  337. end
  338. -- Allow to randomly choose the next plant from a variety.
  339. local next_plant = def._farming_next_plant
  340. if type(next_plant) == "table" then
  341. next_plant = next_plant[math.random(1, #next_plant)]
  342. end
  343. -- I have no idea what this code is supposed to do.
  344. -- Probably a bug waiting to happen. >:(
  345. local have_soil = false
  346. if def.soil_nodes then
  347. for k, v in ipairs(def.soil_nodes) do
  348. if v == soil_node.name then
  349. have_soil = true
  350. break
  351. end
  352. end
  353. end
  354. -- grow seed
  355. if not have_soil then
  356. if minetest.get_item_group(node.name, "seed") ~= 0 and def.fertility then
  357. -- omitted is a check for light, we assume seeds can germinate in the dark.
  358. for _, fertility_group in pairs(def.fertility) do
  359. if minetest.get_item_group(soil_node.name, fertility_group) ~= 0 then
  360. local placenode = {name = next_plant}
  361. if def.place_param2 then
  362. placenode.param2 = def.place_param2
  363. end
  364. minetest.swap_node(pos, placenode)
  365. farming.update_soil_fatigue(soil_pos, node.name)
  366. if minetest.reg_ns_nodes[next_plant]._farming_next_plant then
  367. tick(pos, def)
  368. --minetest.chat_send_all('fail 4')
  369. return
  370. end
  371. end
  372. end
  373. --minetest.chat_send_all('fail 8')
  374. return
  375. end
  376. end
  377. -- check if on wet soil
  378. if not have_soil then
  379. if minetest.get_item_group(soil_node.name, "soil") < 3 then
  380. tick_again(pos, def)
  381. --minetest.chat_send_all('fail 5')
  382. return
  383. end
  384. end
  385. -- check light
  386. local light = minetest.get_node_light(pos)
  387. if not light or light < def.minlight or light > def.maxlight then
  388. tick_again(pos, def)
  389. --minetest.chat_send_all('fail 6')
  390. return
  391. end
  392. local npdef = minetest.reg_ns_nodes[next_plant]
  393. -- grow
  394. local placenode = {name = next_plant}
  395. if npdef.place_param2 then
  396. placenode.param2 = npdef.place_param2
  397. elseif npdef.paramtype2 == "degrotate" then
  398. placenode.param2 = math_random(0, 239)
  399. end
  400. minetest.swap_node(pos, placenode)
  401. -- new timer needed?
  402. if npdef._farming_next_plant then
  403. tick(pos, npdef)
  404. elseif npdef._farming_restart_timer then
  405. -- Allow the last plant in a growing
  406. -- sequence to request a timer restart.
  407. tick(pos, npdef)
  408. end
  409. --minetest.chat_send_all('fail 7')
  410. return
  411. end
  412. -- Register plants
  413. farming.register_plant = function(name, def)
  414. local mname = name:split(":")[1]
  415. local pname = name:split(":")[2]
  416. -- Check def table
  417. if not def.description then
  418. def.description = "Seed"
  419. end
  420. if not def.inventory_image then
  421. def.inventory_image = "unknown_item.png"
  422. end
  423. if not def.steps then
  424. return nil
  425. end
  426. if not def.minlight then
  427. def.minlight = 1
  428. end
  429. if not def.maxlight then
  430. def.maxlight = 14
  431. end
  432. if not def.fertility then
  433. def.fertility = {}
  434. end
  435. -- Register seed
  436. local g = {level = 1, seed = 1, seed_oil = 1, snappy = 3, attached_node = 1, flammable = 2, notify_destruct = 1}
  437. for k, v in pairs(def.fertility) do
  438. g[v] = 1
  439. end
  440. local seed_node_name = mname .. ":seed_" .. pname
  441. local craft_item_name = mname .. ":" .. pname
  442. local plant_node_prefix = mname .. ":" .. pname
  443. minetest.register_node(":" .. seed_node_name, {
  444. description = def.description,
  445. tiles = {def.inventory_image},
  446. inventory_image = def.inventory_image,
  447. wield_image = def.inventory_image,
  448. drawtype = "signlike",
  449. groups = g,
  450. paramtype = "light",
  451. paramtype2 = "wallmounted",
  452. place_param2 = def.place_param2 or nil, -- this isn't actually used for placement
  453. walkable = false,
  454. sunlight_propagates = true,
  455. selection_box = {
  456. type = "fixed",
  457. fixed = {-0.5, -0.5, -0.5, 0.5, -5/16, 0.5},
  458. },
  459. fertility = def.fertility,
  460. sounds = default.node_sound_dirt_defaults({
  461. dug = {name = "default_grass_footstep", gain = 0.2},
  462. place = {name = "default_place_node", gain = 0.25},
  463. }),
  464. on_place = function(itemstack, placer, pointed_thing)
  465. local under = pointed_thing.under
  466. local node = minetest.get_node(under)
  467. local udef = minetest.reg_ns_nodes[node.name]
  468. if udef and udef.on_rightclick and
  469. not (placer and placer:get_player_control().sneak) then
  470. return udef.on_rightclick(under, node, placer, itemstack,
  471. pointed_thing) or itemstack
  472. end
  473. return farming.place_seed(itemstack, placer, pointed_thing, seed_node_name)
  474. end,
  475. _farming_next_plant = plant_node_prefix .. "_1",
  476. on_timer = farming.grow_plant,
  477. minlight = def.minlight,
  478. maxlight = def.maxlight,
  479. })
  480. -- Register harvest
  481. minetest.register_craftitem(":" .. craft_item_name, {
  482. description = pname:gsub("^%l", string.upper),
  483. inventory_image = mname .. "_" .. pname .. ".png",
  484. groups = {flammable = 2},
  485. -- Pass through flowerpot data if available.
  486. flowerpot_insert = def.flowerpot_insert,
  487. })
  488. -- Register growing steps
  489. for i = 1, def.steps do
  490. local drop = {
  491. items = {
  492. {items = {craft_item_name}, rarity = 9 - i},
  493. {items = {craft_item_name}, rarity= 18 - i * 2},
  494. {items = {seed_node_name}, rarity = 9 - i},
  495. {items = {seed_node_name}, rarity = 18 - i * 2},
  496. }
  497. }
  498. local nodegroups = utility.dig_groups("crop", {flammable = 2, plant = 1, not_in_creative_inventory = 1, attached_node = 1, notify_destruct = 1})
  499. nodegroups[pname] = i
  500. local next_plant = nil
  501. local prev_plant = nil
  502. local prev_seed = nil
  503. if i == 1 then
  504. prev_seed = seed_node_name
  505. end
  506. if i < def.steps then
  507. next_plant = plant_node_prefix .. "_" .. (i + 1)
  508. end
  509. if i > 1 then
  510. prev_plant = plant_node_prefix .. "_" .. (i - 1)
  511. end
  512. minetest.register_node(":" .. plant_node_prefix .. "_" .. i, {
  513. drawtype = "plantlike",
  514. waving = 1,
  515. tiles = {mname .. "_" .. pname .. "_" .. i .. ".png"},
  516. paramtype = "light",
  517. paramtype2 = def.paramtype2 or nil,
  518. place_param2 = def.place_param2 or nil,
  519. walkable = false,
  520. buildable_to = true,
  521. drop = drop,
  522. selection_box = {
  523. type = "fixed",
  524. fixed = {-0.5, -0.5, -0.5, 0.5, -5/16, 0.5},
  525. },
  526. groups = nodegroups,
  527. sounds = default.node_sound_leaves_defaults(),
  528. _farming_next_plant = next_plant,
  529. _farming_prev_plant = prev_plant,
  530. _farming_prev_seed = prev_seed,
  531. on_timer = farming.grow_plant,
  532. minlight = def.minlight,
  533. maxlight = def.maxlight,
  534. movement_speed_multiplier = default.SLOW_SPEED_PLANTS,
  535. -- Pass through flowerpot data if available.
  536. flowerpot_drop = def.flowerpot_drop,
  537. })
  538. end
  539. -- Return
  540. local r = {
  541. seed = seed_node_name,
  542. harvest = craft_item_name
  543. }
  544. return r
  545. end