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.

751 lines
27KB

  1. -- Date palms.
  2. --
  3. -- Date palms grow in hot and dry desert, but they require water. This makes them
  4. -- a bit harder to find. If found in the middle of the desert, their presence
  5. -- indicates a water source below the surface.
  6. --
  7. -- As an additional feature (which can be disabled), dates automatically regrow after
  8. -- harvesting (provided a male tree is sufficiently nearby).
  9. -- If regrowing is enabled, then ripe dates will not hang forever. Most will disappear
  10. -- (e.g. eaten by birds, ...), and a small fraction will drop as items.
  11. -- © 2016, Rogier <rogier777@gmail.com>
  12. local S = moretrees.intllib
  13. -- Some constants
  14. local dates_drop_ichance = 4
  15. local stems_drop_ichance = 4
  16. local flowers_wither_ichance = 3
  17. -- implementation
  18. local dates_regrow_prob
  19. if moretrees.dates_regrow_unpollinated_percent <= 0 then
  20. dates_regrow_prob = 0
  21. elseif moretrees.dates_regrow_unpollinated_percent >= 100 then
  22. dates_regrow_prob = 1
  23. else
  24. dates_regrow_prob = 1 - math.pow(moretrees.dates_regrow_unpollinated_percent/100, 1/flowers_wither_ichance)
  25. end
  26. -- Make the date palm fruit trunk a real trunk (it is generated as a fruit)
  27. local trunk = minetest.registered_nodes["moretrees:date_palm_trunk"]
  28. local ftrunk = {}
  29. local fftrunk = {}
  30. local mftrunk = {}
  31. for k,v in pairs(trunk) do
  32. ftrunk[k] = v
  33. end
  34. ftrunk.tiles = {}
  35. for k,v in pairs(trunk.tiles) do
  36. ftrunk.tiles[k] = v
  37. end
  38. ftrunk.drop = "moretrees:date_palm_trunk"
  39. ftrunk.after_destruct = function(pos, oldnode)
  40. local dates = minetest.find_nodes_in_area({x=pos.x-2, y=pos.y, z=pos.z-2}, {x=pos.x+2, y=pos.y, z=pos.z+2}, {"group:moretrees_dates"})
  41. for _,datespos in pairs(dates) do
  42. -- minetest.dig_node(datespos) does not cause nearby dates to be dropped :-( ...
  43. local items = minetest.get_node_drops(minetest.get_node(datespos).name)
  44. minetest.swap_node(datespos, biome_lib.air)
  45. for _, itemname in pairs(items) do
  46. minetest.add_item(datespos, itemname)
  47. end
  48. end
  49. end
  50. for k,v in pairs(ftrunk) do
  51. mftrunk[k] = v
  52. fftrunk[k] = v
  53. end
  54. fftrunk.tiles = {}
  55. mftrunk.tiles = {}
  56. for k,v in pairs(trunk.tiles) do
  57. fftrunk.tiles[k] = v
  58. mftrunk.tiles[k] = v
  59. end
  60. -- Make the different types of trunk distinguishable (but not too easily)
  61. ftrunk.tiles[1] = "moretrees_date_palm_trunk_top.png^[transformR180"
  62. ftrunk.description = ftrunk.description.." (gen)"
  63. fftrunk.tiles[1] = "moretrees_date_palm_trunk_top.png^[transformR90"
  64. mftrunk.tiles[1] = "moretrees_date_palm_trunk_top.png^[transformR-90"
  65. minetest.register_node("moretrees:date_palm_fruit_trunk", ftrunk)
  66. minetest.register_node("moretrees:date_palm_ffruit_trunk", fftrunk)
  67. minetest.register_node("moretrees:date_palm_mfruit_trunk", mftrunk)
  68. -- ABM to grow new date blossoms
  69. local date_regrow_abm_spec = {
  70. nodenames = { "moretrees:date_palm_ffruit_trunk", "moretrees:date_palm_mfruit_trunk" },
  71. interval = moretrees.dates_flower_interval,
  72. chance = moretrees.dates_flower_chance,
  73. action = function(pos, node, active_object_count, active_object_count_wider)
  74. local dates = minetest.find_nodes_in_area({x=pos.x-2, y=pos.y, z=pos.z-2}, {x=pos.x+2, y=pos.y, z=pos.z+2}, "group:moretrees_dates")
  75. -- New blossom interval increases exponentially with number of dates already hanging
  76. -- In addition: if more dates are hanging, the chance of picking an empty spot decreases as well...
  77. if math.random(2^#dates) <= 2 then
  78. -- Grow in area of 5x5 round trunk; higher probability in 3x3 area close to trunk
  79. local dx=math.floor((math.random(50)-18)/16)
  80. local dz=math.floor((math.random(50)-18)/16)
  81. local datepos = {x=pos.x+dx, y=pos.y, z=pos.z+dz}
  82. local datenode = minetest.get_node(datepos)
  83. if datenode.name == "air" then
  84. if node.name == "moretrees:date_palm_ffruit_trunk" then
  85. minetest.swap_node(datepos, {name="moretrees:dates_f0"})
  86. else
  87. minetest.swap_node(datepos, {name="moretrees:dates_m0"})
  88. end
  89. end
  90. end
  91. end
  92. }
  93. if moretrees.dates_regrow_pollinated or moretrees.dates_regrow_unpollinated_percent > 0 then
  94. minetest.register_abm(date_regrow_abm_spec)
  95. end
  96. -- Choose male or female palm, and spawn initial dates
  97. -- (Instead of dates, a dates fruit trunk is generated with the tree. This
  98. -- ABM converts the trunk to a female or male fruit trunk, and spawns some
  99. -- hanging dates)
  100. minetest.register_abm({
  101. nodenames = { "moretrees:date_palm_fruit_trunk" },
  102. interval = 1,
  103. chance = 1,
  104. action = function(pos, node, active_object_count, active_object_count_wider)
  105. local type
  106. if math.random(100) <= moretrees.dates_female_percent then
  107. type = "f"
  108. minetest.swap_node(pos, {name="moretrees:date_palm_ffruit_trunk"})
  109. else
  110. type = "m"
  111. minetest.swap_node(pos, {name="moretrees:date_palm_mfruit_trunk"})
  112. end
  113. local dates1 = minetest.find_nodes_in_area({x=pos.x-1, y=pos.y, z=pos.z-1}, {x=pos.x+1, y=pos.y, z=pos.z+1}, "air")
  114. local genpos
  115. for _,genpos in pairs(dates1) do
  116. if math.random(100) <= 20 then
  117. if type == "m" then
  118. minetest.swap_node(genpos, {name = "moretrees:dates_n"})
  119. else
  120. minetest.swap_node(genpos, {name = "moretrees:dates_f4"})
  121. end
  122. end
  123. end
  124. local dates2 = minetest.find_nodes_in_area({x=pos.x-2, y=pos.y, z=pos.z-2}, {x=pos.x+2, y=pos.y, z=pos.z+2}, "air")
  125. for _,genpos in pairs(dates2) do
  126. if math.random(100) <= 5 then
  127. if type == "m" then
  128. minetest.swap_node(genpos, {name = "moretrees:dates_n"})
  129. else
  130. minetest.swap_node(genpos, {name = "moretrees:dates_f4"})
  131. end
  132. end
  133. end
  134. end,
  135. })
  136. -- Dates growing functions.
  137. -- This is a bit complex, as the purpose is to find male flowers at horizontal distances of over
  138. -- 100 nodes. As searching such a large area is time consuming, this is optimized in four ways:
  139. -- - The search result (the locations of male trees) is cached, so that it can be used again
  140. -- - Only 1/9th of the desired area is searched at a time. A new search is only performed if no male
  141. -- flowers are found in the previously searched parts.
  142. -- - Search results are shared with other female palms nearby.
  143. -- - If previous searches for male palms have consumed too much CPU time, the search is skipped
  144. -- (This means no male palms will be found, and the pollination of the flowers affected will be
  145. -- delayed. If this happens repeatedly, eventually, the female flowers will wither...)
  146. -- A caching method was selected that is suited for the case where most date trees are long-lived,
  147. -- and where the number of trees nearby is limited:
  148. -- - Locations of male palms are stored as metadata for every female palm. This means that a player
  149. -- visiting a remote area with some date palms will not cause extensive searches for male palms as
  150. -- long overdue blossoming ABMs are triggered for every date palm.
  151. -- - Even when male palms *are* cut down, a cache refill will only be performed if the cached results do not
  152. -- contain a male palm with blossoms.
  153. -- The method will probably perform suboptimally:
  154. -- - If female palms are frequently chopped down and replanted.
  155. -- Freshly grown palms will need to search for male palms again
  156. -- (this is mitigated by the long blossoming interval, which increases the chance that search
  157. -- results have already been shared)
  158. -- - If an area contains a large number of male and female palms.
  159. -- In this area, every female palm will have an almost identical list of male palm locations
  160. -- as metadata.
  161. -- - If all male palms within range of a number of female palms have been chopped down (with possibly
  162. -- new ones planted). Although an attempt was made to share search results in this case as well,
  163. -- a number of similar searches will unavoidably be performed by the different female palms.
  164. -- - If no male palms are in range of a female palm. In that case, there will be frequent searches
  165. -- for newly-grown male palms.
  166. -- Search statistics - used to limit the search load.
  167. local sect_search_stats = {} -- Search statistics - server-wide
  168. local function reset_sect_search_stats()
  169. sect_search_stats.count = 0 -- # of searches
  170. sect_search_stats.skip = 0 -- # of times skipped
  171. sect_search_stats.sum = 0 -- total time spent
  172. sect_search_stats.min = 999999999 -- min time spent
  173. sect_search_stats.max = 0 -- max time spent
  174. end
  175. reset_sect_search_stats()
  176. sect_search_stats.last_us = 0 -- last time a search was done (microseconds, max: 2^32)
  177. sect_search_stats.last_s = 0 -- last time a search was done (system time in seconds)
  178. -- Find male trunks in one section (=1/9 th) of the searchable area.
  179. -- sect is -4 to 4, where 0 is the center section
  180. local function find_fruit_trunks_near(ftpos, sect)
  181. local r = moretrees.dates_pollination_distance + 2 * math.sqrt(2)
  182. local sect_hr = math.floor(r / 3 + 0.9999)
  183. local sect_vr = math.floor(r / 2 + 0.9999)
  184. local t0us = core.get_us_time()
  185. local t0s = os.time()
  186. -- Compute elapsed time since last search.
  187. -- Unfortunately, the time value wraps after about 71 minutes (2^32 microseconds),
  188. -- so it must be corrected to obtain the actual elapsed time.
  189. if t0us < sect_search_stats.last_us then
  190. -- Correct a simple wraparound.
  191. -- This is not sufficient, as the time value may have wrapped more than once...
  192. sect_search_stats.last_us = sect_search_stats.last_us - 2^32
  193. end
  194. if t0s - sect_search_stats.last_s > 2^32/1000000 then
  195. -- One additional correction is enough for our purposes.
  196. -- For exact results, more corrections may be needed though...
  197. -- (and even not applying this correction at all would still only yield
  198. -- a minimal risk of a non-serious miscalculation...)
  199. sect_search_stats.last_us = sect_search_stats.last_us - 2^32
  200. end
  201. -- Skip the search if it is consuming too much CPU time
  202. if sect_search_stats.count > 0 and moretrees.dates_blossom_search_iload > 0
  203. and sect_search_stats.sum / sect_search_stats.count > moretrees.dates_blossom_search_time_treshold
  204. and t0us - sect_search_stats.last_us < moretrees.dates_blossom_search_iload * (sect_search_stats.sum / sect_search_stats.count) then
  205. sect_search_stats.skip = sect_search_stats.skip + 1
  206. return nil
  207. end
  208. local basevec = { x = ftpos.x + 2 * sect.x * sect_hr,
  209. y = ftpos.y,
  210. z = ftpos.z + 2 * sect.z * sect_hr}
  211. -- find_nodes_in_area is limited to 82^3, make sure to not overrun it
  212. local sizevec = { x = sect_hr, y = sect_vr, z = sect_hr }
  213. if sect_hr * sect_hr * sect_vr > 41^3 then
  214. sizevec = vector.apply(sizevec, function(a) return math.min(a, 41) end)
  215. end
  216. local all_palms = minetest.find_nodes_in_area(
  217. vector.subtract(basevec, sizevec),
  218. vector.add(basevec, sizevec),
  219. {"moretrees:date_palm_mfruit_trunk", "moretrees:date_palm_ffruit_trunk"})
  220. -- Collect different palms in separate lists.
  221. local female_palms = {}
  222. local male_palms = {}
  223. local all_male_palms = {}
  224. for _, pos in pairs(all_palms) do
  225. if pos.x ~= ftpos.x or pos.y ~= ftpos.y or pos.z ~= ftpos.z then
  226. local node = minetest.get_node(pos)
  227. if node and node.name == "moretrees:date_palm_ffruit_trunk" then
  228. table.insert(female_palms,pos)
  229. elseif node then
  230. table.insert(all_male_palms,pos)
  231. -- In sector 0, all palms are of interest.
  232. -- In other sectors, forget about palms that are too far away.
  233. if sect == 0 then
  234. table.insert(male_palms,pos)
  235. else
  236. local ssq = 0
  237. for _, c in pairs({"x", "z"}) do
  238. local dc = pos[c] - ftpos[c]
  239. ssq = ssq + dc * dc
  240. end
  241. if math.sqrt(ssq) <= r then
  242. table.insert(male_palms,pos)
  243. end
  244. end
  245. end
  246. end
  247. end
  248. -- Update search statistics
  249. local t1us = core.get_us_time()
  250. if t1us < t0us then
  251. -- Wraparound. Assume the search lasted less than 2^32 microseconds (~71 min)
  252. -- (so no need to apply another correction)
  253. t0us = t0us - 2^32
  254. end
  255. sect_search_stats.last_us = t0us
  256. sect_search_stats.last_s = t0s
  257. sect_search_stats.count = sect_search_stats.count + 1
  258. sect_search_stats.sum = sect_search_stats.sum + t1us-t0us
  259. if t1us - t0us < sect_search_stats.min then
  260. sect_search_stats.min = t1us - t0us
  261. end
  262. if t1us - t0us > sect_search_stats.max then
  263. sect_search_stats.max = t1us - t0us
  264. end
  265. return male_palms, female_palms, all_male_palms
  266. end
  267. local function dates_print_search_stats(log)
  268. local stats
  269. if sect_search_stats.count > 0 then
  270. stats = string.format("Male date tree searching stats: searches: %d/%d: average: %d µs (%d..%d)",
  271. sect_search_stats.count, sect_search_stats.count + sect_search_stats.skip,
  272. sect_search_stats.sum/sect_search_stats.count, sect_search_stats.min, sect_search_stats.max)
  273. else
  274. stats = string.format("Male date tree searching stats: searches: 0/0: average: (no searches yet)")
  275. end
  276. if log then
  277. minetest.log("action", "[moretrees] " .. stats)
  278. end
  279. return true, stats
  280. end
  281. minetest.register_chatcommand("dates_stats", {
  282. description = "Print male date palm search statistics",
  283. params = "|chat|log|reset",
  284. privs = { server = true },
  285. func = function(name, param)
  286. param = string.lower(string.trim(param))
  287. if param == "" or param == "chat" then
  288. return dates_print_search_stats(false)
  289. elseif param == "log" then
  290. return dates_print_search_stats(true)
  291. elseif param == "reset" then
  292. reset_sect_search_stats()
  293. return true
  294. else
  295. return false, "Invalid subcommand; expected: '' or 'chat' or 'log' or 'reset'"
  296. end
  297. end,
  298. })
  299. -- Find the female trunk near the female flowers to be pollinated
  300. local function find_female_trunk(fbpos)
  301. local trunks = minetest.find_nodes_in_area({x=fbpos.x-2, y=fbpos.y, z=fbpos.z-2},
  302. {x=fbpos.x+2, y=fbpos.y, z=fbpos.z+2},
  303. "moretrees:date_palm_ffruit_trunk")
  304. local ftpos
  305. local d = 99
  306. for x, pos in pairs(trunks) do
  307. local ssq = 0
  308. for _, c in pairs({"x", "z"}) do
  309. local dc = pos[c] - fbpos[c]
  310. ssq = ssq + dc * dc
  311. end
  312. if math.sqrt(ssq) < d then
  313. ftpos = pos
  314. d = math.sqrt(ssq)
  315. end
  316. end
  317. return ftpos
  318. end
  319. -- Find male blossom near a male trunk,
  320. -- the male blossom must be in range of a specific female blossom as well
  321. local function find_male_blossom_near_trunk(fbpos, mtpos)
  322. local r = moretrees.dates_pollination_distance
  323. local blossoms = minetest.find_nodes_in_area({x=mtpos.x-2, y=mtpos.y, z=mtpos.z-2},
  324. {x=mtpos.x+2, y=mtpos.y, z=mtpos.z+2},
  325. "moretrees:dates_m0")
  326. for x, mbpos in pairs(blossoms) do
  327. local ssq = 0
  328. for _, c in pairs({"x", "z"}) do
  329. local dc = mbpos[c] - fbpos[c]
  330. ssq = ssq + dc * dc
  331. end
  332. if math.sqrt(ssq) <= r then
  333. return mbpos
  334. end
  335. end
  336. end
  337. -- Find a male blossom in range of a specific female blossom,
  338. -- using a nested list of male blossom positions
  339. local function find_male_blossom_in_mpalms(ftpos, fbpos, mpalms)
  340. -- Process the elements of mpalms.sect (index -4 .. 4) in random order
  341. -- First, compute the order in which the sectors will be searched
  342. local sect_index = {}
  343. local sect_rnd = {}
  344. for i = -4,4 do
  345. local n = math.random(1023)
  346. sect_index[n] = i
  347. table.insert(sect_rnd, n)
  348. end
  349. table.sort(sect_rnd)
  350. -- Search the sectors
  351. local sect_old = 0
  352. local sect_time = minetest.get_gametime()
  353. for _, n in pairs(sect_rnd) do
  354. -- Record the oldest sector, so that it can be searched if no male
  355. -- blossoms were found
  356. if not mpalms.sect_time[sect_index[n]] then
  357. sect_old = sect_index[n]
  358. sect_time = 0
  359. elseif mpalms.sect_time[sect_index[n]] < sect_time then
  360. sect_old = sect_index[n]
  361. sect_time = mpalms.sect_time[sect_index[n]]
  362. end
  363. if mpalms.sect[sect_index[n]] and #mpalms.sect[sect_index[n]] then
  364. for px, mtpos in pairs(mpalms.sect[sect_index[n]]) do
  365. local node = minetest.get_node(mtpos)
  366. if node and node.name == "moretrees:date_palm_mfruit_trunk" then
  367. local mbpos = find_male_blossom_near_trunk(fbpos, mtpos)
  368. if mbpos then
  369. return mbpos
  370. end
  371. elseif node and node.name ~= "ignore" then
  372. -- no more male trunk here.
  373. mpalms.sect[sect_index[n]][px] = nil
  374. end
  375. end
  376. end
  377. end
  378. return nil, sect_old
  379. end
  380. -- Find a male blossom in range of a specific female blossom,
  381. -- using the cache associated with the given female trunk
  382. -- If necessary, recompute part of the cache
  383. local last_search_result = {}
  384. local function find_male_blossom_with_ftrunk(fbpos,ftpos)
  385. local meta = minetest.get_meta(ftpos)
  386. local mpalms
  387. local cache_changed = true
  388. -- Load cache. If distance has changed, start with empty cache instead.
  389. local mpalms_dist = meta:get_int("male_palms_dist")
  390. if mpalms_dist and mpalms_dist == moretrees.dates_pollination_distance then
  391. mpalms = meta:get_string("male_palms")
  392. if mpalms and mpalms ~= "" then
  393. mpalms = minetest.deserialize(mpalms)
  394. cache_changed = false
  395. end
  396. end
  397. if not mpalms or not mpalms.sect then
  398. mpalms = {}
  399. mpalms.sect = {}
  400. mpalms.sect_time = {}
  401. meta:set_int("male_palms_dist", moretrees.dates_pollination_distance)
  402. cache_changed = true
  403. end
  404. local fpalms_list
  405. local all_mpalms_list
  406. local sector0_searched = false
  407. -- Always make sure that sector 0 is cached
  408. if not mpalms.sect[0] then
  409. mpalms.sect[0], fpalms_list, all_mpalms_list = find_fruit_trunks_near(ftpos, {x = 0, z = 0})
  410. mpalms.sect_time[0] = minetest.get_gametime()
  411. sector0_searched = true
  412. cache_changed = true
  413. last_search_result.female = fpalms_list
  414. last_search_result.male = all_mpalms_list
  415. end
  416. -- Find male palms
  417. local mbpos, sect_old = find_male_blossom_in_mpalms(ftpos, fbpos, mpalms)
  418. -- If not found, (re)generate the cache for an additional sector. But don't search it yet (for performance reasons)
  419. -- (Use the globally cached results if possible)
  420. if not mbpos and not sector0_searched then
  421. if not mpalms.sect_time[0] or mpalms.sect_time[0] == 0 or math.random(3) == 1 then
  422. -- Higher probability of re-searching the center sector
  423. sect_old = 0
  424. end
  425. -- Use globally cached result if possible
  426. mpalms.sect[sect_old] = nil
  427. if sect_old == 0 and mpalms.sect_time[0] and mpalms.sect_time[0] > 0
  428. and last_search_result.male and #last_search_result.male then
  429. for _, pos in pairs(last_search_result.female) do
  430. if pos.x == ftpos.x and pos.y == ftpos.y and pos.z == ftpos.z then
  431. mpalms.sect[sect_old] = last_search_result.male
  432. -- Next time, don't use the cached result
  433. mpalms.sect_time[sect_old] = nil
  434. cache_changed = true
  435. end
  436. end
  437. end
  438. -- Else do a new search
  439. if not mpalms.sect[sect_old] then
  440. mpalms.sect[sect_old], fpalms_list, all_mpalms_list = find_fruit_trunks_near(ftpos, {x = (sect_old + 4) % 3 - 1, z = (sect_old + 4) / 3 - 1})
  441. cache_changed = true
  442. if sect_old == 0 then
  443. -- Save the results if it is sector 0
  444. -- (chance of reusing results from another sector are smaller)
  445. last_search_result.female = fpalms_list
  446. last_search_result.male = all_mpalms_list
  447. end
  448. if mpalms.sect[sect_old] then
  449. mpalms.sect_time[sect_old] = minetest.get_gametime()
  450. else
  451. mpalms.sect_time[sect_old] = nil
  452. end
  453. end
  454. end
  455. -- Share search results with other female trunks in the same area
  456. -- Note that the list of female trunks doesn't (shouldn't :-) contain the current female trunk.
  457. if fpalms_list and #fpalms_list and #all_mpalms_list then
  458. local all_mpalms = {}
  459. all_mpalms.sect = {}
  460. all_mpalms.sect_time = {}
  461. all_mpalms.sect[0] = all_mpalms_list
  462. -- Don't set sect_time[0], so that the cached sector will be re-searched soon (if necessary)
  463. local all_mpalms_serialized = minetest.serialize(all_mpalms)
  464. for _, pos in pairs(fpalms_list) do
  465. local fmeta = minetest.get_meta(pos)
  466. local fdist = fmeta:get_int("male_palms_dist")
  467. if not fdist or fdist ~= moretrees.dates_pollination_distance then
  468. fmeta:set_string("male_palms", all_mpalms_serialized)
  469. fmeta:set_int("male_palms_dist", moretrees.dates_pollination_distance)
  470. end
  471. end
  472. end
  473. -- Save cache.
  474. if cache_changed then
  475. meta:set_string("male_palms", minetest.serialize(mpalms))
  476. end
  477. return mbpos
  478. end
  479. -- Find a male blossom in range of a specific female blossom
  480. local function find_male_blossom(fbpos)
  481. local ftpos = find_female_trunk(fbpos)
  482. if ftpos then
  483. return find_male_blossom_with_ftrunk(fbpos, ftpos)
  484. end
  485. return nil
  486. end
  487. -- Growing function for dates
  488. local dates_growfn = function(pos, elapsed)
  489. local node = minetest.get_node(pos)
  490. local delay = moretrees.dates_grow_interval
  491. local r = moretrees.dates_pollination_distance
  492. local action
  493. if not node then
  494. return
  495. elseif not moretrees.dates_regrow_pollinated and dates_regrow_prob == 0 then
  496. -- Regrowing of dates is disabled.
  497. if string.find(node.name, "moretrees:dates_f") then
  498. minetest.swap_node(pos, {name="moretrees:dates_f4"})
  499. elseif string.find(node.name, "moretrees:dates_m") then
  500. minetest.swap_node(pos, {name="moretrees:dates_n"})
  501. else
  502. minetest.swap_node(pos, biome_lib.air)
  503. end
  504. return
  505. elseif node.name == "moretrees:dates_f0" and math.random(100) <= 100 * dates_regrow_prob then
  506. -- Dates grow unpollinated
  507. minetest.swap_node(pos, {name="moretrees:dates_f1"})
  508. action = "nopollinate"
  509. elseif node.name == "moretrees:dates_f0" and moretrees.dates_regrow_pollinated and find_male_blossom(pos) then
  510. -- Pollinate flowers
  511. minetest.swap_node(pos, {name="moretrees:dates_f1"})
  512. action = "pollinate"
  513. elseif string.match(node.name, "0$") then
  514. -- Make female unpollinated and male flowers last a bit longer
  515. if math.random(flowers_wither_ichance) == 1 then
  516. if node.name == "moretrees:dates_f0" then
  517. minetest.swap_node(pos, {name="moretrees:dates_fn"})
  518. else
  519. minetest.swap_node(pos, {name="moretrees:dates_n"})
  520. end
  521. action = "wither"
  522. else
  523. action = "nowither"
  524. end
  525. elseif node.name == "moretrees:dates_f4" then
  526. -- Remove dates, and optionally drop them as items
  527. if math.random(dates_drop_ichance) == 1 then
  528. if moretrees.dates_item_drop_ichance > 0 and math.random(moretrees.dates_item_drop_ichance) == 1 then
  529. local items = minetest.get_node_drops(minetest.get_node(pos).name)
  530. for _, itemname in pairs(items) do
  531. minetest.add_item(pos, itemname)
  532. end
  533. end
  534. minetest.swap_node(pos, {name="moretrees:dates_n"})
  535. action = "drop"
  536. else
  537. action = "nodrop"
  538. end
  539. elseif string.match(node.name, "n$") then
  540. -- Remove stems.
  541. if math.random(stems_drop_ichance) == 1 then
  542. minetest.swap_node(pos, biome_lib.air)
  543. return "stemdrop"
  544. end
  545. action = "nostemdrop"
  546. else
  547. -- Grow dates
  548. local offset = 18
  549. local n = string.sub(node.name, offset)
  550. minetest.swap_node(pos, {name=string.sub(node.name, 1, offset-1)..n+1})
  551. action = "grow"
  552. end
  553. -- Don't catch up when elapsed time is large. Regular visits are needed for growth...
  554. local timer = minetest.get_node_timer(pos)
  555. timer:start(delay + math.random(moretrees.dates_grow_interval))
  556. return action
  557. end
  558. -- Alternate growth function for dates.
  559. -- It calls the primary growth function, but also measures CPU time consumed.
  560. -- Use this function to analyze date growing performance.
  561. local stat = {}
  562. stat.count = 0
  563. local dates_growfn_profiling = function(pos, elapsed)
  564. local t0 = core.get_us_time()
  565. local action = dates_growfn(pos, elapsed)
  566. local t1 = core.get_us_time()
  567. if t1 < t0 then
  568. t1 = t1 + 2^32
  569. end
  570. stat.count = stat.count + 1
  571. if not stat[action] then
  572. stat[action] = {}
  573. stat[action].count = 0
  574. stat[action].sum = 0
  575. stat[action].min = 9999999999
  576. stat[action].max = 0
  577. end
  578. stat[action].count = stat[action].count + 1
  579. stat[action].sum = stat[action].sum + t1-t0
  580. if t1-t0 < stat[action].min then
  581. stat[action].min = t1-t0
  582. end
  583. if t1-t0 > stat[action].max then
  584. stat[action].max = t1-t0
  585. end
  586. if stat.count % 10 == 0 then
  587. io.write(".")
  588. io.flush()
  589. end
  590. if stat.count % 100 == 0 then
  591. print(string.format("Date grow statistics %5d:", stat.count))
  592. local sum = 0
  593. local count = 0
  594. if sect_search_stats.count > 0 and stat.pollinate and stat.pollinate.count > 0 then
  595. print(string.format("\t%-12s: %6d (%4.1f%%): %6dus (%d..%d)",
  596. "search", sect_search_stats.count,
  597. 100*sect_search_stats.count/stat.pollinate.count,
  598. sect_search_stats.sum/sect_search_stats.count,
  599. sect_search_stats.min, sect_search_stats.max))
  600. else
  601. print(string.format("\t%-12s: %6d (%4.1f%%): %6dus (%d..%d)",
  602. "search", sect_search_stats.count,
  603. 0, 0, 0, 0))
  604. end
  605. for action,data in pairs(stat) do
  606. if action ~= "count" then
  607. count = count + data.count
  608. sum = sum + data.sum
  609. print(string.format("\t%-12s: %6d (%4.1f%%): %6dus (%d..%d)",
  610. action, data.count,
  611. 100*data.count/stat.count, data.sum/data.count,
  612. data.min, data.max))
  613. end
  614. end
  615. print(string.format("\t%-12s: %6d ( 100%%): %6dus",
  616. "TOTAL", count, sum/count))
  617. end
  618. end
  619. -- Register dates
  620. local dates_starttimer = function(pos, elapsed)
  621. local timer = minetest.get_node_timer(pos)
  622. local base_interval = moretrees.dates_grow_interval * 2 / 3
  623. timer:set(base_interval + math.random(base_interval), elapsed or 0)
  624. end
  625. local dates_drop = {
  626. items = {
  627. {items = { "moretrees:date" }},
  628. {items = { "moretrees:date" }},
  629. {items = { "moretrees:date" }},
  630. {items = { "moretrees:date" }},
  631. {items = { "moretrees:date" }, rarity = 2 },
  632. {items = { "moretrees:date" }, rarity = 2 },
  633. {items = { "moretrees:date" }, rarity = 2 },
  634. {items = { "moretrees:date" }, rarity = 2 },
  635. {items = { "moretrees:date" }, rarity = 5 },
  636. {items = { "moretrees:date" }, rarity = 5 },
  637. {items = { "moretrees:date" }, rarity = 5 },
  638. {items = { "moretrees:date" }, rarity = 5 },
  639. {items = { "moretrees:date" }, rarity = 20 },
  640. {items = { "moretrees:date" }, rarity = 20 },
  641. {items = { "moretrees:date" }, rarity = 20 },
  642. {items = { "moretrees:date" }, rarity = 20 },
  643. }
  644. }
  645. for _,suffix in ipairs({"f0", "f1", "f2", "f3", "f4", "m0", "fn", "n"}) do
  646. local name
  647. if suffix == "f0" or suffix == "m0" then
  648. name = S("Date Flowers")
  649. elseif suffix == "n" or suffix == "fn" then
  650. name = S("Date Stem")
  651. else
  652. name = S("Dates")
  653. end
  654. local dropfn = suffix == "f4" and dates_drop or ""
  655. local datedef = {
  656. description = name,
  657. tiles = {"moretrees_dates_"..suffix..".png"},
  658. visual_scale = 2,
  659. drawtype = "plantlike",
  660. paramtype = "light",
  661. sunlight_propagates = true,
  662. walkable = false,
  663. groups = { fleshy=3, dig_immediate=3, flammable=2, moretrees_dates=1 },
  664. inventory_image = "moretrees_dates_"..suffix..".png^[transformR0",
  665. wield_image = "moretrees_dates_"..suffix..".png^[transformR90",
  666. sounds = default.node_sound_defaults(),
  667. drop = dropfn,
  668. selection_box = {
  669. type = "fixed",
  670. fixed = {-0.3, -0.3, -0.3, 0.3, 3.5, 0.3}
  671. },
  672. on_timer = dates_growfn,
  673. on_construct = (moretrees.dates_regrow_pollinated or moretrees.dates_regrow_unpollinated_percent > 0)
  674. and dates_starttimer,
  675. }
  676. minetest.register_node("moretrees:dates_"..suffix, datedef)
  677. end
  678. -- If regrowing was previously disabled, but is enabled now, make sure timers are started for existing dates
  679. if moretrees.dates_regrow_pollinated or moretrees.dates_regrow_unpollinated_percent > 0 then
  680. local spec = {
  681. name = "moretrees:restart_dates_regrow_timer",
  682. nodenames = "group:moretrees_dates",
  683. action = function(pos, node, active_object_count, active_object_count_wider)
  684. local timer = minetest.get_node_timer(pos)
  685. if not timer:is_started() then
  686. dates_starttimer(pos)
  687. else
  688. local timeout = timer:get_timeout()
  689. local elapsed = timer:get_elapsed()
  690. if timeout - elapsed > moretrees.dates_grow_interval * 4/3 then
  691. dates_starttimer(pos, math.random(moretrees.dates_grow_interval * 4/3))
  692. end
  693. end
  694. end,
  695. }
  696. if minetest.register_lbm then
  697. minetest.register_lbm(spec)
  698. else
  699. spec.interval = 3557
  700. spec.chance = 10
  701. minetest.register_abm(spec)
  702. end
  703. end