date_palm.lua 27 KB

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