cloudlands.lua 154 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959
  1. local ALTITUDE = 200 -- average altitude of islands
  2. local ALTITUDE_AMPLITUDE = 40 -- rough island altitude variance (plus or minus)
  3. local GENERATE_ORES = false -- set to true for island core stone to contain patches of dirt and sand etc.
  4. local LOWLAND_BIOMES = false or -- If true then determine an island's biome using the biome at altitude "LOWLAND_BIOME_ALTITUDE"
  5. minetest.get_modpath("ethereal") ~= nil -- Ethereal has an alpine biome above altitude 40, so default to lowland biomes
  6. local LOWLAND_BIOME_ALTITUDE = 10 -- Higher than beaches, lower than mountains (See LOWLAND_BIOMES)
  7. local VINE_COVERAGE = 0.3 -- set to 0 to turn off vines
  8. local REEF_RARITY = 0.015 -- Chance of a viable island having a reef or atoll
  9. local TREE_RARITY = 0.08 -- Chance of a viable island having a giant tree growing out of it
  10. local PORTAL_RARITY = 0.04 -- Chance of a viable island having some ancient portalstone on it (If portals API available and ENABLE_PORTALS is true)
  11. local BIOLUMINESCENCE = false or -- Allow giant trees variants which have glowing parts
  12. minetest.get_modpath("glowtest") ~= nil or
  13. minetest.get_modpath("ethereal") ~= nil or
  14. minetest.get_modpath("glow") ~= nil or
  15. minetest.get_modpath("nsspf") ~= nil or
  16. minetest.get_modpath("nightscape") ~= nil or
  17. minetest.get_modpath("moonflower") ~= nil -- a world using any of these mods is OK with bioluminescence
  18. local ENABLE_PORTALS = true -- Whether to allow players to build portals to islands. Portals require the Nether mod.
  19. local EDDYFIELD_SIZE = 1 -- size of the "eddy field-lines" that smaller islands follow
  20. local ISLANDS_SEED = 1000 -- You only need to change this if you want to try different island layouts without changing the map seed
  21. -- Some lists of known node aliases (any nodes which can't be found won't be used).
  22. local NODENAMES_STONE = {"mapgen_stone", "mcl_core:stone", "default:stone", "main:stone"}
  23. local NODENAMES_WATER = {"mapgen_water_source", "mcl_core:water_source", "default:water_source", "main:water"}
  24. local NODENAMES_ICE = {"mapgen_ice", "mcl_core:ice", "pedology:ice_white", "default:ice", "main:ice"}
  25. local NODENAMES_GRAVEL = {"mapgen_gravel", "mcl_core:gravel", "default:gravel", "main:gravel"}
  26. local NODENAMES_GRASS = {"mapgen_dirt_with_grass", "mcl_core:dirt_with_grass", "default:dirt_with_grass", "main:grass"} -- currently only used with games that don't register biomes, e.g. Hades Revisted
  27. local NODENAMES_DIRT = {"mapgen_dirt", "mcl_core:dirt", "default:dirt", "main:dirt"} -- currently only used with games that don't register biomes, e.g. Hades Revisted
  28. local NODENAMES_SILT = {"mapgen_silt", "default:silt", "aotearoa:silt", "darkage:silt", "mapgen_sand", "mcl_core:sand", "default:sand", "main:sand"} -- silt isn't a thing yet, but perhaps one day it will be. Use sand for the bottom of ponds in the meantime.
  29. local NODENAMES_VINES = {"mcl_core:vine", "vines:side_end", "ethereal:vine", "main:vine"} -- ethereal vines don't grow, so only select that if there's nothing else.
  30. local NODENAMES_HANGINGVINE = {"vines:vine_end"}
  31. local NODENAMES_HANGINGROOT = {"vines:root_end"}
  32. local NODENAMES_TREEWOOD = {"mcl_core:tree", "default:tree", "mapgen_tree", "main:tree"}
  33. local NODENAMES_TREELEAVES = {"mcl_core:leaves", "default:leaves", "mapgen_leaves", "main:leaves"}
  34. local NODENAMES_FRAMEGLASS = {"xpanes:obsidian_pane_flat", "xpanes:pane_flat", "default:glass", "xpanes:pane_natural_flat", "mcl_core:glass", "walls:window"}
  35. local NODENAMES_WOOD = {"default:wood", "mcl_core:wood", "main:wood"}
  36. local MODNAME = minetest.get_current_modname()
  37. local VINES_REQUIRED_HUMIDITY = 49
  38. local VINES_REQUIRED_TEMPERATURE = 40
  39. local ICE_REQUIRED_TEMPERATURE = 8
  40. local DEBUG = false -- dev logging
  41. local DEBUG_GEOMETRIC = false -- turn off noise from island shapes
  42. local DEBUG_SKYTREES = false -- dev logging
  43. -- OVERDRAW can be set to 1 to cause a y overdraw of one node above the chunk, to avoid creating a dirt "surface"
  44. -- at the top of the chunk that trees mistakenly grow on when the chunk is decorated.
  45. -- However, it looks like that tree problem has been solved by either engine or biome updates, and overdraw causes
  46. -- it's own issues (e.g. nodeId_top not getting set correctly), so I'm leaving overdraw off (i.e. zero) until I
  47. -- notice problems requiring it.
  48. local OVERDRAW = 0
  49. local S = minetest.get_translator(MODNAME)
  50. cloudlands = {} -- API functions can be accessed via this global:
  51. -- cloudlands.get_island_details(minp, maxp) -- returns an array of island-information-tables, y is ignored.
  52. -- cloudlands.find_nearest_island(x, z, search_radius) -- returns a single island-information-table, or nil
  53. -- cloudlands.get_height_at(x, z, [island-information-tables]) -- returns (y, isWater), or nil if no island here
  54. cloudlands.coreTypes = {
  55. {
  56. territorySize = 200,
  57. coresPerTerritory = 3,
  58. radiusMax = 96,
  59. depthMax = 50,
  60. thicknessMax = 8,
  61. frequency = 0.1,
  62. pondWallBuffer = 0.03,
  63. requiresNexus = true,
  64. exclusive = false
  65. },
  66. {
  67. territorySize = 60,
  68. coresPerTerritory = 1,
  69. radiusMax = 40,
  70. depthMax = 40,
  71. thicknessMax = 4,
  72. frequency = 0.1,
  73. pondWallBuffer = 0.06,
  74. requiresNexus = false,
  75. exclusive = true
  76. },
  77. {
  78. territorySize = 30,
  79. coresPerTerritory = 3,
  80. radiusMax = 16, -- I feel this and depthMax should be bigger, say 18, and territorySize increased to 34 to match, but I can't change it any more or existing worlds will mismatch along previously emerged chunk boundaries
  81. depthMax = 16,
  82. thicknessMax = 2,
  83. frequency = 0.1,
  84. pondWallBuffer = 0.11, -- larger values will make ponds smaller and further from island edges, so it should be as low as you can get it without the ponds leaking over the edge. A small leak-prone island is at (3160, -2360) on seed 1
  85. requiresNexus = false,
  86. exclusive = true
  87. }
  88. }
  89. if minetest.get_biome_data == nil then error(MODNAME .. " requires Minetest v5.0 or greater", 0) end
  90. local function fromSettings(settings_name, default_value)
  91. local result
  92. if type(default_value) == "number" then
  93. result = tonumber(minetest.settings:get(settings_name) or default_value)
  94. elseif type(default_value) == "boolean" then
  95. result = minetest.settings:get_bool(settings_name, default_value)
  96. end
  97. return result
  98. end
  99. -- override any settings with user-specified values before these values are needed
  100. ALTITUDE = fromSettings(MODNAME .. "_altitude", ALTITUDE)
  101. ALTITUDE_AMPLITUDE = fromSettings(MODNAME .. "_altitude_amplitude", ALTITUDE_AMPLITUDE)
  102. GENERATE_ORES = fromSettings(MODNAME .. "_generate_ores", GENERATE_ORES)
  103. VINE_COVERAGE = fromSettings(MODNAME .. "_vine_coverage", VINE_COVERAGE * 100) / 100
  104. LOWLAND_BIOMES = fromSettings(MODNAME .. "_use_lowland_biomes", LOWLAND_BIOMES)
  105. TREE_RARITY = fromSettings(MODNAME .. "_giant_tree_rarety", TREE_RARITY * 100) / 100
  106. BIOLUMINESCENCE = fromSettings(MODNAME .. "_bioluminescence", BIOLUMINESCENCE)
  107. ENABLE_PORTALS = fromSettings(MODNAME .. "_enable_portals", ENABLE_PORTALS)
  108. local noiseparams_eddyField = {
  109. offset = -1,
  110. scale = 2,
  111. spread = {x = 350 * EDDYFIELD_SIZE, y = 350 * EDDYFIELD_SIZE, z= 350 * EDDYFIELD_SIZE},
  112. seed = ISLANDS_SEED, --WARNING! minetest.get_perlin() will add the server map's seed to this value
  113. octaves = 2,
  114. persistence = 0.7,
  115. lacunarity = 2.0,
  116. }
  117. local noiseparams_heightMap = {
  118. offset = 0,
  119. scale = ALTITUDE_AMPLITUDE,
  120. spread = {x = 160, y = 160, z= 160},
  121. seed = ISLANDS_SEED, --WARNING! minetest.get_perlin() will add the server map's seed to this value
  122. octaves = 3,
  123. persistence = 0.5,
  124. lacunarity = 2.0,
  125. }
  126. local DENSITY_OFFSET = 0.7
  127. local noiseparams_density = {
  128. offset = DENSITY_OFFSET,
  129. scale = .3,
  130. spread = {x = 25, y = 25, z= 25},
  131. seed = 1000, --WARNING! minetest.get_perlin() will add the server map's seed to this value
  132. octaves = 4,
  133. persistence = 0.5,
  134. lacunarity = 2.0,
  135. }
  136. local SURFACEMAP_OFFSET = 0.5
  137. local noiseparams_surfaceMap = {
  138. offset = SURFACEMAP_OFFSET,
  139. scale = .5,
  140. spread = {x = 40, y = 40, z= 40},
  141. seed = ISLANDS_SEED, --WARNING! minetest.get_perlin() will add the server map's seed to this value
  142. octaves = 4,
  143. persistence = 0.5,
  144. lacunarity = 2.0,
  145. }
  146. local noiseparams_skyReef = {
  147. offset = .3,
  148. scale = .9,
  149. spread = {x = 3, y = 3, z= 3},
  150. seed = 1000,
  151. octaves = 2,
  152. persistence = 0.5,
  153. lacunarity = 2.0,
  154. }
  155. local noiseAngle = -15 --degrees to rotate eddyField noise, so that the vertical and horizontal tendencies are off-axis
  156. local ROTATE_COS = math.cos(math.rad(noiseAngle))
  157. local ROTATE_SIN = math.sin(math.rad(noiseAngle))
  158. local noise_eddyField
  159. local noise_heightMap
  160. local noise_density
  161. local noise_surfaceMap
  162. local noise_skyReef
  163. local worldSeed
  164. local nodeId_ignore = minetest.CONTENT_IGNORE
  165. local nodeId_air
  166. local nodeId_stone
  167. local nodeId_grass
  168. local nodeId_dirt
  169. local nodeId_water
  170. local nodeId_ice
  171. local nodeId_silt
  172. local nodeId_gravel
  173. local nodeId_vine
  174. local nodeName_vine
  175. local nodeName_ignore = minetest.get_name_from_content_id(nodeId_ignore)
  176. local REQUIRED_DENSITY = 0.4
  177. local randomNumbers = {} -- array of 0-255 random numbers with values between 0 and 1 (inclusive)
  178. local data = {} -- reuse the massive VoxelManip memory buffers instead of creating on every on_generate()
  179. local biomes = {}
  180. -- optional region specified in settings to restrict islands too
  181. local region_restrictions = false
  182. local region_min_x, region_min_z, region_max_x, region_max_z = -32000, -32000, 32000, 32000
  183. -- optional biomes specified in settings to restrict islands too
  184. local limit_to_biomes = nil
  185. local limit_to_biomes_altitude = nil
  186. --[[==============================
  187. Math functions
  188. ==============================]]--
  189. -- avoid having to perform table lookups each time a common math function is invoked
  190. local math_min, math_max, math_floor, math_sqrt, math_cos, math_sin, math_abs, math_pow, PI = math.min, math.max, math.floor, math.sqrt, math.cos, math.sin, math.abs, math.pow, math.pi
  191. local function clip(value, minValue, maxValue)
  192. if value <= minValue then
  193. return minValue
  194. elseif value >= maxValue then
  195. return maxValue
  196. else
  197. return value
  198. end
  199. end
  200. local function round(value)
  201. return math_floor(0.5 + value)
  202. end
  203. --[[==============================
  204. Interop functions
  205. ==============================]]--
  206. local get_heat, get_humidity = minetest.get_heat, minetest.get_humidity
  207. local biomeinfoAvailable = minetest.get_modpath("biomeinfo") ~= nil and minetest.global_exists("biomeinfo")
  208. local isMapgenV6 = minetest.get_mapgen_setting("mg_name") == "v6"
  209. if isMapgenV6 then
  210. if not biomeinfoAvailable then
  211. -- The biomeinfo mod by Wuzzy can be found at https://repo.or.cz/minetest_biomeinfo.git
  212. minetest.log("warning", MODNAME .. " detected mapgen v6: Full mapgen v6 support requires adding the biomeinfo mod.")
  213. else
  214. get_heat = function(pos)
  215. return biomeinfo.get_v6_heat(pos) * 100
  216. end
  217. get_humidity = function(pos)
  218. return biomeinfo.get_v6_humidity(pos) * 100
  219. end
  220. end
  221. end
  222. local interop = {}
  223. -- returns the id of the first nodename in the list that resolves to a node id, or nodeId_ignore if not found
  224. interop.find_node_id = function (node_contender_names)
  225. local result = nodeId_ignore
  226. for _,contenderName in ipairs(node_contender_names) do
  227. local nonAliasName = minetest.registered_aliases[contenderName] or contenderName
  228. if minetest.registered_nodes[nonAliasName] ~= nil then
  229. result = minetest.get_content_id(nonAliasName)
  230. end
  231. --if DEBUG then minetest.log("info", contenderName .. " returned " .. result .. " (" .. minetest.get_name_from_content_id(result) .. ")") end
  232. if result ~= nodeId_ignore then return result end
  233. end
  234. return result
  235. end
  236. -- returns the name of the first nodename in the list that resolves to a node id, or 'ignore' if not found
  237. interop.find_node_name = function (node_contender_names)
  238. return minetest.get_name_from_content_id(interop.find_node_id(node_contender_names))
  239. end
  240. interop.get_first_element_in_table = function(tbl)
  241. for k,v in pairs(tbl) do return v end
  242. return nil
  243. end
  244. -- returns the top-texture name of the first nodename in the list that's a registered node, or nil if not found
  245. interop.find_node_texture = function (node_contender_names)
  246. local result = nil
  247. local nodeName = minetest.get_name_from_content_id(interop.find_node_id(node_contender_names))
  248. if nodeName ~= nil then
  249. local node = minetest.registered_nodes[nodeName]
  250. if node ~= nil then
  251. result = node.tiles
  252. if type(result) == "table" then result = result["name"] or interop.get_first_element_in_table(result) end -- incase it's not a string
  253. if type(result) == "table" then result = result["name"] or interop.get_first_element_in_table(result) end -- incase multiple tile definitions
  254. end
  255. end
  256. return result
  257. end
  258. -- returns the node name of the clone node.
  259. interop.register_clone = function(node_name, clone_name)
  260. local node = minetest.registered_nodes[node_name]
  261. if node == nil then
  262. minetest.log("error", "cannot clone " .. node_name)
  263. return nil
  264. else
  265. if clone_name == nil then clone_name = MODNAME .. ":" .. string.gsub(node.name, ":", "_") end
  266. if minetest.registered_nodes[clone_name] == nil then
  267. if DEBUG then minetest.log("info", "attempting to register: " .. clone_name) end
  268. local clone = {}
  269. for key, value in pairs(node) do clone[key] = value end
  270. clone.name = clone_name
  271. minetest.register_node(clone_name, clone)
  272. --minetest.log("info", clone_name .. " id: " .. minetest.get_content_id(clone_name))
  273. --minetest.log("info", clone_name .. ": " .. dump(minetest.registered_nodes[clone_name]))
  274. end
  275. return clone_name
  276. end
  277. end
  278. -- converts "modname:nodename" into (modname, nodename), if no colon is found then modname is nil
  279. interop.split_nodename = function(nodeName)
  280. local result_modname = nil
  281. local result_nodename = nodeName
  282. local pos = nodeName:find(':')
  283. if pos ~= nil then
  284. result_modname = nodeName:sub(0, pos - 1)
  285. result_nodename = nodeName:sub(pos + 1)
  286. end
  287. return result_modname, result_nodename
  288. end
  289. -- returns a unique id for the biome, normally this is numeric but with mapgen v6 it can be a string name.
  290. interop.get_biome_key = function(pos)
  291. if isMapgenV6 and biomeinfoAvailable then
  292. return biomeinfo.get_v6_biome(pos)
  293. else
  294. return minetest.get_biome_data(pos).biome
  295. end
  296. end
  297. -- returns true if filename is a file that exists.
  298. interop.file_exists = function(filename)
  299. local f = io.open(filename, "r")
  300. if f == nil then
  301. return false
  302. else
  303. f:close()
  304. return true
  305. end
  306. end
  307. -- returns a written book item (technically an item stack), or nil if no books mod available
  308. interop.write_book = function(title, author, text, description)
  309. local stackName_writtenBook
  310. if minetest.get_modpath("mcl_books") then
  311. stackName_writtenBook = "mcl_books:written_book"
  312. text = title .. "\n\n" .. text -- MineClone2 books doen't show a title (or author)
  313. elseif minetest.get_modpath("book") ~= nil then
  314. stackName_writtenBook = "book:book_written"
  315. text = "\n\n" .. text -- Crafter books put the text immediately under the title
  316. elseif minetest.get_modpath("default") ~= nil then
  317. stackName_writtenBook = "default:book_written"
  318. else
  319. return nil
  320. end
  321. local book_itemstack = ItemStack(stackName_writtenBook)
  322. local book_data = {}
  323. book_data.title = title
  324. book_data.text = text
  325. book_data.owner = author
  326. book_data.author = author
  327. book_data.description = description
  328. book_data.page = 1
  329. book_data.page_max = 1
  330. book_data.generation = 0
  331. book_data["book.book_title"] = title -- Crafter book title
  332. book_data["book.book_text"] = text -- Crafter book text
  333. book_itemstack:get_meta():from_table({fields = book_data})
  334. return book_itemstack
  335. end
  336. --[[==============================
  337. Portals
  338. ==============================]]--
  339. local addDetail_ancientPortal = nil
  340. if ENABLE_PORTALS and minetest.get_modpath("nether") ~= nil and minetest.global_exists("nether") and nether.register_portal ~= nil then
  341. -- The Portals API is available
  342. -- Register a player-buildable portal to Hallelujah Mountains.
  343. -- returns a position on the island which is suitable for a portal to be placed, or nil if none can be found
  344. local function find_potential_portal_location_on_island(island_info)
  345. local result = nil
  346. if island_info ~= nil then
  347. local searchRadius = island_info.radius * 0.6 -- islands normally don't reach their full radius, and lets not put portals too near the edge
  348. local coreList = cloudlands.get_island_details(
  349. {x = island_info.x - searchRadius, z = island_info.z - searchRadius},
  350. {x = island_info.x + searchRadius, z = island_info.z + searchRadius}
  351. )
  352. -- Deterministically sample the island for a low location that isn't water.
  353. -- Seed the prng so this function always returns the same coords for the island
  354. local prng = PcgRandom(island_info.x * 65732 + island_info.z * 729 + minetest.get_mapgen_setting("seed") * 3)
  355. local positions = {}
  356. for attempt = 1, 15 do -- how many attempts we'll make at finding a good location
  357. local angle = (prng:next(0, 10000) / 10000) * 2 * PI
  358. local distance = math_sqrt(prng:next(0, 10000) / 10000) * searchRadius
  359. if attempt == 1 then distance = 0 end -- Always sample the middle of the island, as it's the safest fallback location
  360. local x = round(island_info.x + math_cos(angle) * distance)
  361. local z = round(island_info.z + math_sin(angle) * distance)
  362. local y, isWater = cloudlands.get_height_at(x, z, coreList)
  363. if y ~= nil then
  364. local weight = 0
  365. if not isWater then weight = weight + 1 end -- avoid putting portals in ponds
  366. if y >= island_info.y + ALTITUDE then weight = weight + 2 end -- avoid putting portals down the sides of eroded cliffs
  367. positions[#positions + 1] = {x = x, y = y + 1, z = z, weight = weight}
  368. end
  369. end
  370. -- Order the locations by how good they are
  371. local compareFn = function(pos_a, pos_b)
  372. if pos_a.weight > pos_b.weight then return true end
  373. if pos_a.weight == pos_b.weight and pos_a.y < pos_b.y then return true end -- I can't justify why I think lower positions are better. I'm imagining portals nested in valleys rather than on ridges.
  374. return false
  375. end
  376. table.sort(positions, compareFn)
  377. -- Now the locations are sorted by how good they are, find the first/best that doesn't
  378. -- grief a player build.
  379. -- Ancient Portalstone has is_ground_content set to true, so we won't have to worry about
  380. -- old/broken portal frames interfering with the results of nether.volume_is_natural()
  381. for _, position in ipairs(positions) do
  382. -- Unfortunately, at this point we don't know the orientation of the portal, so use worst case
  383. local minp = {x = position.x - 2, y = position.y, z = position.z - 2}
  384. local maxp = {x = position.x + 3, y = position.y + 4, z = position.z + 3}
  385. if nether.volume_is_natural(minp, maxp) then
  386. result = position
  387. break
  388. end
  389. end
  390. end
  391. return result
  392. end
  393. -- returns nil if no suitable location could be found, otherwise returns (portal_pos, island_info)
  394. local function find_nearest_island_location_for_portal(surface_x, surface_z)
  395. local result = nil
  396. local island = cloudlands.find_nearest_island(surface_x, surface_z, 75)
  397. if island == nil then island = cloudlands.find_nearest_island(surface_x, surface_z, 150) end
  398. if island == nil then island = cloudlands.find_nearest_island(surface_x, surface_z, 400) end
  399. if island ~= nil then
  400. result = find_potential_portal_location_on_island(island)
  401. end
  402. return result, island
  403. end
  404. -- Ideally the Nether mod will provide a block obtainable by exploring the Nether which is
  405. -- earmarked for mods like this one to use for portals, but until this happens I'll create
  406. -- our own tempory placeholder "portalstone".
  407. -- The Portals API is currently provided by nether, which depends on default, so we can assume default textures are available
  408. local portalstone_end = "default_furnace_top.png^(default_ice.png^[opacity:120)^[multiply:#668" -- this gonna look bad with non-default texturepacks, hopefully Nether mod will provide a real block
  409. local portalstone_side = "[combine:16x16:0,0=default_furnace_top.png:4,0=default_furnace_top.png:8,0=default_furnace_top.png:12,0=default_furnace_top.png:^(default_ice.png^[opacity:120)^[multiply:#668"
  410. minetest.register_node("cloudlands:ancient_portalstone", {
  411. description = S("Ancient Portalstone"),
  412. tiles = {portalstone_end, portalstone_end, portalstone_side, portalstone_side, portalstone_side, portalstone_side},
  413. paramtype2 = "facedir",
  414. sounds = default.node_sound_stone_defaults(),
  415. groups = {cracky = 1, level = 2},
  416. on_blast = function() --[[blast proof]] end
  417. })
  418. minetest.register_ore({
  419. ore_type = "scatter",
  420. ore = "cloudlands:ancient_portalstone",
  421. wherein = "nether:rack",
  422. clust_scarcity = 32 * 32 * 32,
  423. clust_num_ores = 6,
  424. clust_size = 3,
  425. y_max = nether.DEPTH_CEILING or nether.DEPTH,
  426. y_min = nether.DEPTH_FLOOR or -32000,
  427. })
  428. local _ = {name = "air", prob = 0}
  429. local A = {name = "air", prob = 255, force_place = true}
  430. local PU = {name = "cloudlands:ancient_portalstone", param2 = 0, prob = 255, force_place = true}
  431. local PW = {name = "cloudlands:ancient_portalstone", param2 = 12, prob = 255, force_place = true}
  432. local PN = {name = "cloudlands:ancient_portalstone", param2 = 4, prob = 255, force_place = true}
  433. minetest.register_decoration({
  434. name = "Ancient broken portal",
  435. deco_type = "schematic",
  436. place_on = "nether:rack",
  437. sidelen = 80,
  438. fill_ratio = 0.00018,
  439. biomes = {"nether_caverns"},
  440. y_max = nether.DEPTH_CEILING or nether.DEPTH,
  441. y_min = nether.DEPTH_FLOOR or -32000,
  442. schematic = {
  443. size = {x = 4, y = 4, z = 1},
  444. data = {
  445. PN, A, PW, PN,
  446. PU, A, A, PU,
  447. A, _, _, PU,
  448. _, _, _, PU
  449. },
  450. yslice_prob = {
  451. {ypos = 3, prob = 92},
  452. {ypos = 1, prob = 30},
  453. }
  454. },
  455. place_offset_y = 1,
  456. flags = "force_placement,all_floors",
  457. rotation = "random"
  458. })
  459. -- this uses place_schematic() without minetest.after(), so should be called after vm:write_to_map()
  460. addDetail_ancientPortal = function(core)
  461. if (core.radius < 8 or PORTAL_RARITY == 0) then return false end -- avoid portals hanging off the side of small islands
  462. local fastHash = 3
  463. fastHash = (37 * fastHash) + 9354 -- to keep this probability distinct from reefs and atols
  464. fastHash = (37 * fastHash) + ISLANDS_SEED
  465. fastHash = (37 * fastHash) + core.x
  466. fastHash = (37 * fastHash) + core.z
  467. fastHash = (37 * fastHash) + math_floor(core.radius)
  468. fastHash = (37 * fastHash) + math_floor(core.depth)
  469. if (PORTAL_RARITY * 10000) < math_floor((math_abs(fastHash)) % 10000) then return false end
  470. local portalPos = find_potential_portal_location_on_island(core)
  471. if portalPos ~= nil then
  472. local orientation = (fastHash % 2) * 90
  473. portalPos.y = portalPos.y - ((core.x + core.z) % 3) -- partially bury some ancient portals
  474. minetest.place_schematic(
  475. portalPos,
  476. {
  477. size = {x = 4, y = 5, z = 1},
  478. data = {
  479. PN, PW, PW, PN,
  480. PU, _, _, PU,
  481. PU, _, _, PU,
  482. PU, _, _, PU,
  483. PN, PW, PW, PN
  484. },
  485. },
  486. orientation,
  487. { -- node replacements
  488. ["default:obsidian"] = "cloudlands:ancient_portalstone",
  489. },
  490. true
  491. )
  492. end
  493. end
  494. nether.register_portal("cloudlands_portal", {
  495. shape = nether.PortalShape_Traditional,
  496. frame_node_name = "cloudlands:ancient_portalstone",
  497. wormhole_node_color = 2, -- 2 is blue
  498. particle_color = "#77F",
  499. particle_texture = {
  500. name = "nether_particle_anim1.png",
  501. animation = {
  502. type = "vertical_frames",
  503. aspect_w = 7,
  504. aspect_h = 7,
  505. length = 1,
  506. },
  507. scale = 1.5
  508. },
  509. title = S("Hallelujah Mountains Portal"),
  510. book_of_portals_pagetext =
  511. S("Construction requires 14 blocks of ancient portalstone. We have no knowledge of how portalstones were created, the means to craft them are likely lost to time, so our only source has been to scavenge the Nether for the remnants of ancient broken portals. A finished frame is four blocks wide, five blocks high, and stands vertically, like a doorway.") .. "\n\n" ..
  512. S("The only portal we managed to scavenge enough portalstone to build took us to a land of floating islands. There were hills and forests and even water up there, but the edges are a perilous drop — a depth of which we cannot even begin to plumb."),
  513. is_within_realm = function(pos)
  514. -- return true if pos is in the cloudlands
  515. -- I'm doing this based off height for speed, so it sometimes gets it wrong when the
  516. -- Hallelujah mountains start reaching the ground.
  517. if noise_heightMap == nil then cloudlands.init() end
  518. local largestCoreType = cloudlands.coreTypes[1] -- the first island type is the biggest/thickest
  519. local island_bottom = ALTITUDE - (largestCoreType.depthMax * 0.66) + round(noise_heightMap:get2d({x = pos.x, y = pos.z}))
  520. return pos.y > math_max(40, island_bottom)
  521. end,
  522. find_realm_anchorPos = function(surface_anchorPos)
  523. -- Find the nearest island and obtain a suitable surface position on it
  524. local destination_pos, island = find_nearest_island_location_for_portal(surface_anchorPos.x, surface_anchorPos.z)
  525. if island ~= nil then
  526. -- Allow any existing or player-positioned portal on the island to be linked to
  527. -- first before resorting to the island's default portal position
  528. local existing_portal_location, existing_portal_orientation = nether.find_nearest_working_portal(
  529. "cloudlands_portal",
  530. {x = island.x, y = 100000, z = island.z}, -- Using 100000 for y to ensure the position is in the cloudlands realm and so find_nearest_working_portal() will only returns island portals.
  531. island.radius * 0.9, -- Islands normally don't reach their full radius. Ensure this distance limit encompasses any location find_nearest_island_location_for_portal() can return.
  532. 0 -- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are in the Cloudlands realm)
  533. )
  534. if existing_portal_location ~= nil then
  535. return existing_portal_location, existing_portal_orientation
  536. end
  537. end
  538. return destination_pos
  539. end,
  540. find_surface_anchorPos = function(realm_anchorPos)
  541. -- This function isn't needed since find_surface_target_y() will be used by default,
  542. -- but by implementing it I can look for any existing nearby portals before falling
  543. -- back to find_surface_target_y.
  544. -- Using -100000 for y to ensure the position is outside the cloudlands realm and so
  545. -- find_nearest_working_portal() will only returns ground portals.
  546. -- a y_factor of 0 makes the search ignore the -100000 altitude of the portals (as
  547. -- long as they are outside the cloudlands realm)
  548. local existing_portal_location, existing_portal_orientation =
  549. nether.find_nearest_working_portal("cloudlands_portal", {x = realm_anchorPos.x, y = -100000, z = realm_anchorPos.z}, 150, 0)
  550. if existing_portal_location ~= nil then
  551. return existing_portal_location, existing_portal_orientation
  552. else
  553. local y = nether.find_surface_target_y(realm_anchorPos.x, realm_anchorPos.z, "cloudlands_portal")
  554. return {x = realm_anchorPos.x, y = y, z = realm_anchorPos.z}
  555. end
  556. end,
  557. on_ignite = function(portalDef, anchorPos, orientation)
  558. -- make some sparks fly on ignition
  559. local p1, p2 = portalDef.shape:get_p1_and_p2_from_anchorPos(anchorPos, orientation)
  560. local pos = vector.divide(vector.add(p1, p2), 2)
  561. local textureName = portalDef.particle_texture
  562. if type(textureName) == "table" then textureName = textureName.name end
  563. local velocity
  564. if orientation == 0 then
  565. velocity = {x = 0, y = 0, z = 7}
  566. else
  567. velocity = {x = 7, y = 0, z = 0}
  568. end
  569. local particleSpawnerDef = {
  570. amount = 180,
  571. time = 0.15,
  572. minpos = {x = pos.x - 1, y = pos.y - 1.5, z = pos.z - 1},
  573. maxpos = {x = pos.x + 1, y = pos.y + 1.5, z = pos.z + 1},
  574. minvel = velocity,
  575. maxvel = velocity,
  576. minacc = {x = 0, y = 0, z = 0},
  577. maxacc = {x = 0, y = 0, z = 0},
  578. minexptime = 0.1,
  579. maxexptime = 0.5,
  580. minsize = 0.3 * portalDef.particle_texture_scale,
  581. maxsize = 0.8 * portalDef.particle_texture_scale,
  582. collisiondetection = false,
  583. texture = textureName .. "^[colorize:#99F:alpha",
  584. animation = portalDef.particle_texture_animation,
  585. glow = 8
  586. }
  587. minetest.add_particlespawner(particleSpawnerDef)
  588. velocity = vector.multiply(velocity, -1)
  589. particleSpawnerDef.minvel, particleSpawnerDef.maxvel = velocity, velocity
  590. minetest.add_particlespawner(particleSpawnerDef)
  591. end
  592. })
  593. end
  594. --[[==============================
  595. SkyTrees
  596. ==============================]]--
  597. -- If splitting SkyTrees into a seperate mod, perhaps schemlib would be of help - https://forum.minetest.net/viewtopic.php?t=18084
  598. if not minetest.global_exists("SkyTrees") then -- If SkyTrees added into other mods, this may have already been defined
  599. local TREE1_FILE = 'cloudlands_tree1.mts'
  600. local TREE2_FILE = 'cloudlands_tree2.mts'
  601. local BARK_SUFFIX = '_bark'
  602. local GLOW_SUFFIX = '_glow'
  603. SkyTrees = {
  604. -- Order the trees in this schematicInfo array from the largest island requirements to smallest
  605. -- The data in each schematicInfo must exactly match what's in the .mts file or things will break
  606. schematicInfo = {
  607. {
  608. filename = TREE1_FILE,
  609. size = {x = 81, y = 106, z = 111},
  610. center = {x = 37, y = 11, z = 73},
  611. requiredIslandDepth = 20,
  612. requiredIslandRadius = 40,
  613. nodesWithConstructor = {
  614. {x=35, y=69, z=1}, {x=61, y=51, z=2}, {x=36, y=68, z=2}, {x=68, y=48, z=3}, {x=61, y=50, z=4}, {x=71, y=50, z=5}, {x=58, y=52, z=5}, {x=65, y=50, z=9}, {x=72, y=53, z=11}, {x=41, y=67, z=12}, {x=63, y=48, z=13}, {x=69, y=52, z=13}, {x=33, y=66, z=14}, {x=39, y=68, z=15}, {x=72, y=68, z=15}, {x=40, y=67, z=16}, {x=39, y=66, z=17}, {x=68, y=45, z=19}, {x=69, y=44, z=20}, {x=72, y=55, z=20}, {x=66, y=56, z=20}, {x=58, y=66, z=20}, {x=71, y=58, z=21}, {x=68, y=45, z=22}, {x=70, y=51, z=22}, {x=73, y=55, z=22}, {x=36, y=62, z=22}, {x=70, y=67, z=22}, {x=21, y=65, z=23}, {x=22, y=66, z=23}, {x=53, y=66, z=23}, {x=70, y=68, z=23}, {x=73, y=54, z=24}, {x=75, y=57, z=24}, {x=37, y=63, z=24}, {x=7, y=68, z=24}, {x=69, y=56, z=25}, {x=34, y=58, z=25}, {x=66, y=62, z=25}, {x=64, y=66, z=25}, {x=6, y=67, z=25}, {x=3, y=68, z=25}, {x=68, y=56, z=26}, {x=65, y=57, z=26}, {x=61, y=63, z=26}, {x=31, y=59, z=27}, {x=48, y=62, z=27}, {x=50, y=63, z=27}, {x=78, y=65, z=27}, {x=78, y=52, z=28}, {x=68, y=57, z=28}, {x=76, y=57, z=28}, {x=31, y=60, z=28}, {x=15, y=63, z=28}, {x=16, y=63, z=28}, {x=66, y=64, z=28}, {x=60, y=65, z=28}, {x=61, y=76, z=28}, {x=63, y=76, z=28}, {x=69, y=59, z=29}, {x=51, y=65, z=29}, {x=72, y=57, z=30}, {x=20, y=60, z=30}, {x=21, y=61, z=30}, {x=49, y=65, z=30}, {x=52, y=53, z=31}, {x=72, y=57, z=31}, {x=36, y=58, z=31}, {x=63, y=60, z=31}, {x=54, y=63, z=31}, {x=45, y=65, z=31}, {x=79, y=66, z=31}, {x=62, y=70, z=31}, {x=55, y=103, z=31}, {x=52, y=53, z=32}, {x=68, y=60, z=32}, {x=19, y=61, z=32}, {x=53, y=63, z=32}, {x=37, y=64, z=32}, {x=21, y=65, z=32}, {x=56, y=65, z=32}, {x=59, y=71, z=32}, {x=35, y=74, z=32}, {x=23, y=75, z=32}, {x=35, y=58, z=33}, {x=62, y=60, z=33}, {x=18, y=63, z=33}, {x=73, y=67, z=33}, {x=37, y=74, z=33}, {x=65, y=75, z=33}, {x=38, y=2, z=34}, {x=67, y=52, z=34}, {x=71, y=60, z=34}, {x=25, y=63, z=34}, {x=19, y=64, z=34}, {x=32, y=66, z=34}, {x=66, y=72, z=34}, {x=41, y=81, z=34}, {x=45, y=93, z=34}, {x=54, y=99, z=34}, {x=38, y=5, z=35}, {x=68, y=48, z=35}, {x=69, y=51, z=35}, {x=48, y=53, z=35}, {x=37, y=57, z=35}, {x=77, y=58, z=35}, {x=32, y=60, z=35}, {x=20, y=61, z=35}, {x=27, y=61, z=35}, {x=33, y=65, z=35}, {x=58, y=65, z=35}, {x=58, y=72, z=35}, {x=60, y=73, z=35}, {x=30, y=74, z=35}, {x=41, y=74, z=35}, {x=41, y=87, z=35}, {x=22, y=58, z=36}, {x=64, y=58, z=36}, {x=39, y=70, z=36}, {x=36, y=77, z=36}, {x=44, y=83, z=36}, {x=40, y=86, z=36}, {x=35, y=56, z=37}, {x=65, y=59, z=37}, {x=66, y=62, z=37}, {x=62, y=67, z=37}, {x=39, y=68, z=37}, {x=40, y=86, z=37}, {x=53, y=88, z=37}, {x=43, y=97, z=37}, {x=52, y=99, z=37}, {x=37, y=3, z=38}, {x=35, y=55, z=38}, {x=38, y=56, z=38}, {x=25, y=57, z=38}, {x=65, y=57, z=38}, {x=71, y=61, z=38}, {x=33, y=65, z=38}, {x=61, y=65, z=38}, {x=50, y=66, z=38}, {x=38, y=68, z=38}, {x=46, y=97, z=38}, {x=44, y=100, z=38}, {x=51, y=102, z=38}, {x=29, y=42, z=39}, {x=27, y=43, z=39}, {x=70, y=48, z=39}, {x=72, y=52, z=39}, {x=23, y=57, z=39}, {x=26, y=57, z=39}, {x=28, y=58, z=39}, {x=55, y=58, z=39}, {x=73, y=59, z=39}, {x=65, y=65, z=39}, {x=41, y=68, z=39}, {x=42, y=81, z=39}, {x=55, y=88, z=39}, {x=43, y=91, z=39}, {x=45, y=100, z=39}, {x=23, y=57, z=40}, {x=29, y=57, z=40}, {x=76, y=58, z=40}, {x=73, y=59, z=40}, {x=78, y=59, z=40}, {x=31, y=60, z=40}, {x=64, y=64, z=40}, {x=41, y=67, z=40}, {x=42, y=75, z=40}, {x=37, y=78, z=40}, {x=42, y=92, z=40}, {x=51, y=101, z=40}, {x=48, y=105, z=40}, {x=75, y=59, z=41}, {x=55, y=63, z=41}, {x=35, y=68, z=41}, {x=35, y=69, z=41}, {x=35, y=71, z=41}, {x=34, y=42, z=42}, {x=29, y=55, z=42}, {x=50, y=61, z=42}, {x=34, y=65, z=42}, {x=57, y=88, z=42}, {x=48, y=89, z=42}, {x=49, y=89, z=42}, {x=27, y=22, z=43}, {x=26, y=28, z=43}, {x=31, y=46, z=43}, {x=66, y=52, z=43}, {x=49, y=57, z=43}, {x=56, y=57, z=43}, {x=41, y=69, z=43}, {x=36, y=52, z=44}, {x=63, y=54, z=44}, {x=51, y=55, z=44}, {x=57, y=56, z=44}, {x=69, y=57, z=44}, {x=64, y=65, z=44}, {x=55, y=90, z=44}, {x=30, y=42, z=45}, {x=31, y=52, z=45}, {x=51, y=54, z=45}, {x=24, y=57, z=45}, {x=70, y=62, z=45}, {x=39, y=69, z=45}, {x=35, y=80, z=45}, {x=29, y=81, z=45}, {x=44, y=85, z=45}, {x=41, y=86, z=45}, {x=33, y=9, z=46}, {x=28, y=44, z=46}, {x=50, y=54, z=46}, {x=47, y=55, z=46}, {x=45, y=56, z=46}, {x=45, y=58, z=46}, {x=47, y=58, z=46}, {x=30, y=63, z=46}, {x=27, y=81, z=46}, {x=28, y=81, z=46}, {x=40, y=86, z=46}, {x=29, y=16, z=47}, {x=32, y=10, z=48}, {x=66, y=49, z=48}, {x=29, y=52, z=48}, {x=53, y=54, z=48}, {x=55, y=54, z=48}, {x=61, y=58, z=48}, {x=59, y=61, z=48}, {x=50, y=63, z=48}, {x=26, y=82, z=48}, {x=43, y=85, z=48}, {x=48, y=86, z=48}, {x=31, y=19, z=49}, {x=30, y=46, z=49}, {x=63, y=51, z=49}, {x=41, y=53, z=49}, {x=31, y=60, z=49}, {x=67, y=1, z=50}, {x=37, y=8, z=50}, {x=40, y=30, z=50}, {x=43, y=57, z=50}, {x=59, y=57, z=50}, {x=60, y=57, z=50}, {x=29, y=61, z=50}, {x=34, y=63, z=50}, {x=49, y=65, z=50}, {x=65, y=3, z=51}, {x=45, y=29, z=51}, {x=41, y=58, z=51}, {x=42, y=60, z=51}, {x=46, y=64, z=51}, {x=47, y=67, z=51}, {x=52, y=68, z=51}, {x=69, y=51, z=52}, {x=53, y=55, z=52}, {x=45, y=62, z=52}, {x=64, y=2, z=53}, {x=3, y=3, z=53}, {x=10, y=6, z=53}, {x=31, y=14, z=53}, {x=37, y=35, z=53}, {x=43, y=48, z=53}, {x=71, y=50, z=53}, {x=52, y=54, z=53}, {x=43, y=57, z=53}, {x=55, y=57, z=53}, {x=52, y=67, z=53}, {x=48, y=72, z=53}, {x=5, y=1, z=54}, {x=9, y=4, z=54}, {x=62, y=4, z=54}, {x=33, y=8, z=54}, {x=42, y=29, z=54}, {x=42, y=32, z=54}, {x=43, y=34, z=54}, {x=41, y=39, z=54}, {x=41, y=57, z=54}, {x=34, y=61, z=54}, {x=58, y=2, z=55}, {x=59, y=3, z=55}, {x=38, y=7, z=55}, {x=40, y=12, z=55}, {x=38, y=39, z=55}, {x=33, y=46, z=55}, {x=28, y=54, z=55}, {x=29, y=55, z=55}, {x=30, y=57, z=55}, {x=54, y=58, z=55}, {x=52, y=63, z=55}, {x=37, y=7, z=56}, {x=55, y=8, z=56}, {x=33, y=45, z=56}, {x=58, y=0, z=57}, {x=9, y=5, z=57}, {x=34, y=7, z=57}, {x=54, y=8, z=57}, {x=17, y=9, z=57}, {x=32, y=12, z=57}, {x=37, y=39, z=57}, {x=41, y=45, z=57}, {x=31, y=46, z=57}, {x=49, y=50, z=57}, {x=50, y=56, z=57}, {x=46, y=59, z=57}, {x=48, y=66, z=57}, {x=51, y=67, z=57}, {x=15, y=3, z=58}, {x=8, y=10, z=58}, {x=41, y=11, z=58}, {x=40, y=13, z=58}, {x=42, y=45, z=58}, {x=50, y=51, z=58}, {x=20, y=5, z=59}, {x=19, y=7, z=59}, {x=22, y=8, z=59}, {x=23, y=9, z=59}, {x=40, y=13, z=59}, {x=33, y=14, z=59}, {x=42, y=41, z=59}, {x=20, y=6, z=60}, {x=9, y=8, z=60}, {x=46, y=8, z=60}, {x=34, y=39, z=60}, {x=30, y=52, z=60}, {x=43, y=57, z=60}, {x=18, y=5, z=61}, {x=11, y=10, z=61}, {x=36, y=36, z=61}, {x=47, y=55, z=61}, {x=38, y=56, z=61}, {x=61, y=59, z=61}, {x=56, y=60, z=61}, {x=36, y=6, z=62}, {x=55, y=7, z=62}, {x=26, y=10, z=62}, {x=29, y=13, z=62}, {x=46, y=13, z=62}, {x=57, y=60, z=62}, {x=18, y=7, z=63}, {x=30, y=11, z=63}, {x=53, y=13, z=63}, {x=45, y=14, z=63}, {x=36, y=32, z=63}, {x=46, y=41, z=63}, {x=29, y=43, z=63}, {x=29, y=44, z=63}, {x=29, y=46, z=63}, {x=29, y=50, z=63}, {x=30, y=52, z=63}, {x=46, y=54, z=63}, {x=19, y=6, z=64}, {x=54, y=8, z=64}, {x=16, y=11, z=64}, {x=42, y=16, z=64}, {x=36, y=25, z=64}, {x=37, y=27, z=64}, {x=36, y=28, z=64}, {x=37, y=29, z=64}, {x=40, y=33, z=64}, {x=30, y=36, z=64}, {x=43, y=39, z=64}, {x=62, y=61, z=64}, {x=21, y=6, z=65}, {x=24, y=6, z=65}, {x=53, y=10, z=65}, {x=52, y=12, z=65}, {x=27, y=17, z=65}, {x=39, y=17, z=65}, {x=29, y=19, z=65}, {x=32, y=22, z=65}, {x=28, y=42, z=65}, {x=60, y=61, z=65}, {x=24, y=6, z=66}, {x=26, y=6, z=66}, {x=19, y=12, z=66}, {x=28, y=20, z=66}, {x=31, y=26, z=66}, {x=39, y=55, z=66}, {x=42, y=6, z=67}, {x=24, y=7, z=67}, {x=20, y=14, z=67}, {x=41, y=21, z=67}, {x=28, y=22, z=67}, {x=29, y=46, z=67},
  615. {x=34, y=52, z=67}, {x=45, y=17, z=68}, {x=42, y=25, z=68}, {x=28, y=43, z=68}, {x=46, y=44, z=68}, {x=29, y=7, z=69}, {x=49, y=12, z=69}, {x=29, y=43, z=69}, {x=48, y=9, z=70}, {x=45, y=17, z=70}, {x=36, y=9, z=71}, {x=47, y=10, z=71}, {x=25, y=11, z=71}, {x=45, y=17, z=71}, {x=42, y=46, z=71}, {x=34, y=47, z=71}, {x=35, y=48, z=71}, {x=45, y=10, z=72}, {x=25, y=12, z=72}, {x=45, y=35, z=72}, {x=45, y=43, z=72}, {x=36, y=52, z=72}, {x=39, y=55, z=72}, {x=26, y=19, z=73}, {x=27, y=21, z=73}, {x=26, y=27, z=73}, {x=26, y=29, z=73}, {x=43, y=31, z=73}, {x=28, y=36, z=73}, {x=42, y=41, z=73}, {x=34, y=46, z=73}, {x=39, y=59, z=73}, {x=24, y=9, z=74}, {x=48, y=9, z=74}, {x=35, y=48, z=74}, {x=35, y=51, z=74}, {x=42, y=53, z=74}, {x=33, y=57, z=74}, {x=30, y=60, z=74}, {x=47, y=8, z=75}, {x=22, y=12, z=75}, {x=45, y=18, z=75}, {x=27, y=30, z=75}, {x=45, y=33, z=75}, {x=36, y=49, z=75}, {x=36, y=1, z=76}, {x=45, y=7, z=76}, {x=21, y=14, z=76}, {x=44, y=23, z=76}, {x=29, y=35, z=76}, {x=38, y=40, z=76}, {x=39, y=42, z=76}, {x=33, y=58, z=76}, {x=34, y=1, z=77}, {x=21, y=7, z=77}, {x=18, y=11, z=77}, {x=26, y=23, z=77}, {x=43, y=25, z=77}, {x=41, y=32, z=77}, {x=36, y=41, z=77}, {x=39, y=47, z=77}, {x=35, y=56, z=77}, {x=35, y=1, z=78}, {x=26, y=3, z=78}, {x=34, y=3, z=78}, {x=18, y=9, z=78}, {x=27, y=23, z=78}, {x=51, y=33, z=78}, {x=41, y=37, z=78}, {x=36, y=1, z=79}, {x=25, y=2, z=79}, {x=18, y=8, z=79}, {x=15, y=10, z=79}, {x=14, y=11, z=79}, {x=27, y=23, z=79}, {x=28, y=25, z=79}, {x=45, y=32, z=79}, {x=33, y=34, z=79}, {x=34, y=34, z=79}, {x=37, y=55, z=79}, {x=40, y=62, z=79}, {x=27, y=0, z=80}, {x=31, y=18, z=80}, {x=30, y=26, z=80}, {x=34, y=61, z=80}, {x=20, y=7, z=81}, {x=51, y=7, z=81}, {x=25, y=8, z=81}, {x=53, y=8, z=81}, {x=42, y=10, z=81}, {x=56, y=12, z=81}, {x=21, y=15, z=81}, {x=37, y=28, z=81}, {x=36, y=29, z=81}, {x=37, y=29, z=81}, {x=44, y=35, z=81}, {x=22, y=7, z=82}, {x=26, y=8, z=82}, {x=29, y=8, z=82}, {x=44, y=9, z=82}, {x=42, y=10, z=82}, {x=32, y=13, z=82}, {x=13, y=14, z=82}, {x=29, y=22, z=82}, {x=31, y=25, z=82}, {x=35, y=27, z=82}, {x=27, y=60, z=82}, {x=41, y=64, z=82}, {x=20, y=8, z=83}, {x=57, y=8, z=83}, {x=24, y=9, z=83}, {x=58, y=9, z=83}, {x=36, y=22, z=83}, {x=32, y=24, z=83}, {x=47, y=8, z=84}, {x=56, y=8, z=84}, {x=59, y=11, z=84}, {x=45, y=13, z=84}, {x=58, y=13, z=84}, {x=17, y=14, z=84}, {x=23, y=14, z=84}, {x=56, y=14, z=84}, {x=29, y=19, z=84}, {x=36, y=19, z=84}, {x=27, y=59, z=84}, {x=35, y=6, z=85}, {x=9, y=8, z=85}, {x=41, y=11, z=85}, {x=50, y=13, z=85}, {x=33, y=58, z=85}, {x=34, y=58, z=85}, {x=33, y=7, z=86}, {x=18, y=10, z=86}, {x=9, y=12, z=86}, {x=41, y=12, z=87}, {x=41, y=60, z=87}, {x=9, y=2, z=88}, {x=7, y=5, z=88}, {x=5, y=10, z=88}, {x=41, y=11, z=88}, {x=62, y=11, z=88}, {x=42, y=68, z=88}, {x=37, y=6, z=89}, {x=66, y=8, z=89}, {x=9, y=10, z=89}, {x=19, y=10, z=89}, {x=58, y=12, z=89}, {x=45, y=62, z=89}, {x=7, y=5, z=90}, {x=67, y=5, z=90}, {x=7, y=9, z=90}, {x=31, y=11, z=90}, {x=62, y=11, z=90}, {x=1, y=2, z=91}, {x=5, y=5, z=91}, {x=69, y=5, z=91}, {x=62, y=8, z=91}, {x=58, y=9, z=91}, {x=63, y=10, z=91}, {x=35, y=7, z=92}, {x=62, y=9, z=92}, {x=33, y=13, z=92}, {x=36, y=62, z=92}, {x=37, y=3, z=93}, {x=37, y=6, z=93}, {x=64, y=6, z=93}, {x=32, y=10, z=93}, {x=34, y=14, z=93}, {x=39, y=57, z=93}, {x=41, y=67, z=93}, {x=33, y=9, z=94}, {x=38, y=57, z=94}, {x=41, y=69, z=94}, {x=40, y=1, z=95}, {x=34, y=7, z=97}, {x=33, y=9, z=97}, {x=33, y=10, z=102}, {x=33, y=7, z=105}, {x=35, y=9, z=107}
  616. }
  617. },
  618. {
  619. filename = TREE2_FILE,
  620. size = {x = 62, y = 65, z = 65},
  621. center = {x = 30, y = 12, z = 36},
  622. requiredIslandDepth = 16,
  623. requiredIslandRadius = 24,
  624. nodesWithConstructor = { {x=35, y=53, z=1}, {x=33, y=59, z=1}, {x=32, y=58, z=3}, {x=31, y=57, z=5}, {x=40, y=58, z=6}, {x=29, y=57, z=7}, {x=39, y=51, z=8}, {x=52, y=53, z=8}, {x=32, y=53, z=9}, {x=25, y=58, z=9}, {x=51, y=51, z=10}, {x=47, y=50, z=11}, {x=50, y=55, z=11}, {x=28, y=57, z=11}, {x=26, y=39, z=12}, {x=30, y=39, z=12}, {x=24, y=40, z=12}, {x=53, y=52, z=12}, {x=29, y=57, z=12}, {x=43, y=59, z=12}, {x=26, y=39, z=13}, {x=36, y=48, z=13}, {x=27, y=39, z=14}, {x=39, y=48, z=14}, {x=33, y=50, z=14}, {x=43, y=50, z=14}, {x=24, y=59, z=14}, {x=41, y=49, z=15}, {x=33, y=12, z=16}, {x=36, y=46, z=16}, {x=50, y=51, z=16}, {x=46, y=57, z=16}, {x=36, y=45, z=17}, {x=27, y=46, z=17}, {x=22, y=48, z=17}, {x=45, y=50, z=17}, {x=31, y=38, z=18}, {x=32, y=38, z=18}, {x=39, y=46, z=18}, {x=51, y=51, z=18}, {x=31, y=11, z=19}, {x=32, y=38, z=19}, {x=39, y=41, z=19}, {x=45, y=57, z=19}, {x=29, y=58, z=19}, {x=28, y=60, z=20}, {x=38, y=40, z=21}, {x=30, y=58, z=21}, {x=31, y=13, z=22}, {x=20, y=41, z=22}, {x=22, y=43, z=22}, {x=20, y=48, z=22}, {x=22, y=39, z=23}, {x=49, y=50, z=23}, {x=52, y=52, z=23}, {x=53, y=53, z=23}, {x=32, y=55, z=23}, {x=36, y=59, z=23}, {x=31, y=60, z=23}, {x=25, y=46, z=24}, {x=40, y=56, z=24}, {x=34, y=58, z=24}, {x=38, y=58, z=24}, {x=32, y=39, z=25}, {x=40, y=46, z=25}, {x=39, y=55, z=25}, {x=36, y=45, z=26}, {x=12, y=7, z=28}, {x=34, y=33, z=28}, {x=31, y=36, z=28}, {x=37, y=41, z=28}, {x=14, y=60, z=28}, {x=19, y=13, z=29}, {x=12, y=43, z=29}, {x=8, y=45, z=29}, {x=31, y=46, z=29}, {x=39, y=47, z=29}, {x=13, y=60, z=29}, {x=22, y=63, z=29}, {x=51, y=9, z=30}, {x=32, y=39, z=30}, {x=33, y=40, z=30}, {x=34, y=44, z=30}, {x=22, y=1, z=31}, {x=24, y=2, z=31}, {x=20, y=7, z=31}, {x=51, y=9, z=31}, {x=16, y=12, z=31}, {x=34, y=27, z=31}, {x=22, y=43, z=31}, {x=27, y=44, z=31}, {x=23, y=51, z=31}, {x=42, y=58, z=31}, {x=9, y=60, z=31}, {x=22, y=5, z=32}, {x=22, y=6, z=32}, {x=50, y=10, z=32}, {x=53, y=11, z=32}, {x=41, y=15, z=32}, {x=43, y=15, z=32}, {x=31, y=21, z=32}, {x=31, y=28, z=32}, {x=12, y=42, z=32}, {x=15, y=42, z=32}, {x=13, y=48, z=32}, {x=37, y=49, z=32}, {x=18, y=59, z=32}, {x=52, y=9, z=33}, {x=40, y=10, z=33}, {x=43, y=10, z=33}, {x=22, y=11, z=33}, {x=27, y=11, z=33}, {x=50, y=11, z=33}, {x=22, y=15, z=33}, {x=36, y=29, z=33}, {x=33, y=37, z=33}, {x=9, y=42, z=33}, {x=14, y=42, z=33}, {x=18, y=43, z=33}, {x=23, y=43, z=33}, {x=33, y=49, z=33}, {x=43, y=53, z=33}, {x=54, y=53, z=33}, {x=31, y=55, z=33}, {x=23, y=58, z=33}, {x=43, y=10, z=34}, {x=44, y=10, z=34}, {x=32, y=12, z=34}, {x=46, y=13, z=34}, {x=28, y=29, z=34}, {x=20, y=42, z=34}, {x=39, y=50, z=34}, {x=51, y=52, z=34}, {x=54, y=52, z=34}, {x=35, y=55, z=34}, {x=51, y=56, z=34}, {x=35, y=5, z=35}, {x=34, y=8, z=35}, {x=33, y=10, z=35}, {x=49, y=10, z=35}, {x=43, y=14, z=35}, {x=36, y=35, z=35}, {x=30, y=47, z=35}, {x=9, y=48, z=35}, {x=39, y=51, z=35}, {x=56, y=52, z=35}, {x=40, y=56, z=35}, {x=13, y=59, z=35}, {x=26, y=62, z=35}, {x=28, y=13, z=36}, {x=38, y=17, z=36}, {x=38, y=20, z=36}, {x=27, y=26, z=36}, {x=38, y=35, z=36}, {x=24, y=39, z=36}, {x=6, y=43, z=36}, {x=13, y=57, z=36}, {x=48, y=7, z=37}, {x=33, y=8, z=37}, {x=50, y=9, z=37}, {x=36, y=11, z=37}, {x=27, y=20, z=37}, {x=27, y=22, z=37}, {x=38, y=24, z=37}, {x=33, y=34, z=37}, {x=9, y=42, z=37}, {x=14, y=42, z=37}, {x=25, y=42, z=37}, {x=53, y=50, z=37}, {x=33, y=53, z=37}, {x=54, y=59, z=37}, {x=28, y=21, z=38}, {x=39, y=34, z=38}, {x=24, y=35, z=38}, {x=8, y=43, z=38}, {x=6, y=47, z=38}, {x=48, y=51, z=38}, {x=61, y=53, z=38}, {x=26, y=57, z=38}, {x=27, y=57, z=38}, {x=32, y=59, z=38}, {x=29, y=62, z=38}, {x=38, y=62, z=38}, {x=33, y=7, z=39}, {x=34, y=9, z=39}, {x=28, y=23, z=39}, {x=34, y=37, z=39}, {x=19, y=42, z=39}, {x=55, y=50, z=39}, {x=47, y=51, z=39}, {x=11, y=54, z=39}, {x=9, y=60, z=39}, {x=33, y=61, z=39}, {x=33, y=4, z=40}, {x=30, y=11, z=40}, {x=39, y=13, z=40}, {x=36, y=23, z=40}, {x=22, y=38, z=40}, {x=54, y=49, z=40}, {x=53, y=50, z=40}, {x=23, y=54, z=40}, {x=28, y=57, z=40}, {x=29, y=57, z=40}, {x=31, y=29, z=41}, {x=27, y=34, z=41}, {x=30, y=37, z=41}, {x=42, y=38, z=41}, {x=12, y=42, z=41}, {x=15, y=42, z=41}, {x=44, y=44, z=41}, {x=28, y=57, z=41}, {x=55, y=57, z=41}, {x=9, y=59, z=41}, {x=30, y=10, z=42}, {x=26, y=15, z=42}, {x=31, y=15, z=42}, {x=34, y=17, z=42}, {x=28, y=36, z=42}, {x=38, y=44, z=42}, {x=42, y=44, z=42}, {x=46, y=44, z=42}, {x=32, y=47, z=42}, {x=52, y=47, z=42}, {x=39, y=55, z=42}, {x=54, y=56, z=42}, {x=34, y=59, z=42}, {x=40, y=11, z=43}, {x=30, y=14, z=43}, {x=28, y=16, z=43}, {x=34, y=31, z=43}, {x=11, y=43, z=43}, {x=14, y=43, z=43}, {x=28, y=47, z=43}, {x=57, y=50, z=43}, {x=61, y=54, z=43}, {x=30, y=58, z=43}, {x=34, y=59, z=43}, {x=7, y=61, z=43}, {x=41, y=10, z=44}, {x=29, y=15, z=44}, {x=36, y=39, z=44}, {x=6, y=43, z=44}, {x=30, y=47, z=44}, {x=57, y=50, z=44}, {x=38, y=10, z=45}, {x=42, y=10, z=45}, {x=11, y=43, z=45}, {x=14, y=43, z=45}, {x=46, y=44, z=45}, {x=32, y=45, z=45}, {x=55, y=45, z=45}, {x=3, y=48, z=45}, {x=31, y=57, z=45}, {x=41, y=3, z=46}, {x=40, y=7, z=46}, {x=28, y=11, z=46}, {x=23, y=13, z=46}, {x=19, y=43, z=46}, {x=24, y=9, z=47}, {x=39, y=9, z=47}, {x=43, y=12, z=47}, {x=5, y=43, z=47}, {x=42, y=43, z=47}, {x=46, y=43, z=47}, {x=24, y=47, z=47}, {x=60, y=52, z=47}, {x=24, y=54, z=47}, {x=37, y=57, z=47}, {x=11, y=60, z=47}, {x=27, y=9, z=48}, {x=27, y=11, z=48}, {x=22, y=14, z=48}, {x=15, y=44, z=48}, {x=51, y=45, z=48}, {x=23, y=49, z=48}, {x=59, y=53, z=48}, {x=9, y=56, z=48}, {x=33, y=59, z=48}, {x=41, y=14, z=49}, {x=8, y=43, z=49}, {x=10, y=43, z=49}, {x=39, y=43, z=49}, {x=34, y=44, z=49}, {x=47, y=44, z=49}, {x=48, y=44, z=49}, {x=24, y=51, z=49}, {x=10, y=55, z=49}, {x=32, y=59, z=49}, {x=20, y=61, z=49}, {x=11, y=63, z=49}, {x=25, y=8, z=50}, {x=22, y=10, z=50}, {x=42, y=14, z=50}, {x=10, y=43, z=50}, {x=43, y=43, z=50}, {x=61, y=46, z=50}, {x=39, y=54, z=50}, {x=24, y=12, z=51}, {x=50, y=44, z=51}, {x=52, y=45, z=51}, {x=54, y=45, z=51}, {x=2, y=46, z=51}, {x=8, y=51, z=51}, {x=7, y=52, z=51}, {x=37, y=58, z=51}, {x=22, y=50, z=52}, {x=25, y=55, z=52}, {x=39, y=58, z=52}, {x=20, y=7, z=53}, {x=40, y=43, z=53}, {x=58, y=45, z=53}, {x=60, y=50, z=53}, {x=22, y=55, z=53}, {x=28, y=56, z=53}, {x=50, y=62, z=53}, {x=54, y=45, z=54}, {x=61, y=46, z=54}, {x=30, y=47, z=54}, {x=30, y=49, z=54}, {x=53, y=53, z=54}, {x=18, y=55, z=54}, {x=51, y=56, z=54}, {x=46, y=62, z=54}, {x=21, y=56, z=55}, {x=24, y=56, z=55}, {x=38, y=61, z=55}, {x=19, y=49, z=56}, {x=46, y=52, z=56}, {x=47, y=53, z=56}, {x=59, y=47, z=57}, {x=26, y=57, z=57}, {x=45, y=43, z=58}, {x=15, y=50, z=58}, {x=11, y=51, z=58}, {x=50, y=44, z=59}, {x=53, y=47, z=59}, {x=43, y=49, z=59}, {x=18, y=50, z=59}, {x=18, y=51, z=60}, {x=38, y=45, z=61}, {x=50, y=47, z=61}, {x=41, y=48, z=61} },
  625. }
  626. },
  627. MODNAME = minetest.get_current_modname() -- don't hardcode incase it's copied into other mods
  628. }
  629. -- Must be called during mod load time, as it uses minetest.register_node()
  630. -- (add an optional dependency for any mod where the tree & leaf textures might be
  631. -- sourced from, to ensure they are loaded before this is called)
  632. SkyTrees.init = function()
  633. SkyTrees.minimumIslandRadius = 100000
  634. SkyTrees.minimumIslandDepth = 100000
  635. SkyTrees.maximumYOffset = 0
  636. SkyTrees.maximumHeight = 0
  637. SkyTrees.nodeName_sideVines = interop.find_node_name(NODENAMES_VINES)
  638. SkyTrees.nodeName_hangingVine = interop.find_node_name(NODENAMES_HANGINGVINE)
  639. SkyTrees.nodeName_hangingRoot = interop.find_node_name(NODENAMES_HANGINGROOT)
  640. for i,tree in pairs(SkyTrees.schematicInfo) do
  641. local fullFilename = minetest.get_modpath(SkyTrees.MODNAME) .. DIR_DELIM .. tree.filename
  642. if not interop.file_exists(fullFilename) then
  643. -- remove the schematic from the list
  644. SkyTrees.schematicInfo[i] = nil
  645. else
  646. SkyTrees.minimumIslandRadius = math_min(SkyTrees.minimumIslandRadius, tree.requiredIslandRadius)
  647. SkyTrees.minimumIslandDepth = math_min(SkyTrees.minimumIslandDepth, tree.requiredIslandDepth)
  648. SkyTrees.maximumYOffset = math_max(SkyTrees.maximumYOffset, tree.center.y)
  649. SkyTrees.maximumHeight = math_max(SkyTrees.maximumHeight, tree.size.y)
  650. tree.theme = {}
  651. SkyTrees.schematicInfo[tree.filename] = tree -- so schematicInfo of trees can be indexed by name
  652. end
  653. end
  654. local function generate_woodTypes(nodeName_templateWood, overlay, barkoverlay, nodesuffix, description, dropsTemplateWood)
  655. local trunkNode = minetest.registered_nodes[nodeName_templateWood]
  656. local newTrunkNode = {}
  657. for key, value in pairs(trunkNode) do newTrunkNode[key] = value end
  658. newTrunkNode.name = SkyTrees.MODNAME .. ":" .. nodesuffix
  659. newTrunkNode.description = description
  660. if newTrunkNode.paramtype2 == nil then newTrunkNode.paramtype2 = "facedir" end
  661. if newTrunkNode.on_dig ~= nil and minetest.get_modpath("main") then
  662. newTrunkNode.on_dig = nil -- Crafter has special trunk auto-digging logic that doesn't make sense for giant trees
  663. end
  664. if dropsTemplateWood then
  665. newTrunkNode.drop = nodeName_templateWood
  666. if newTrunkNode.groups == nil then newTrunkNode.groups = {} end
  667. newTrunkNode.groups.not_in_creative_inventory = 1
  668. else
  669. newTrunkNode.drop = nil
  670. end
  671. local tiles = trunkNode.tiles
  672. if type(tiles) == "table" then
  673. newTrunkNode.tiles = {}
  674. for key, value in pairs(tiles) do newTrunkNode.tiles[key] = value .. overlay end
  675. else
  676. newTrunkNode.tiles = tiles .. overlay
  677. end
  678. local newBarkNode = {}
  679. for key, value in pairs(newTrunkNode) do newBarkNode[key] = value end
  680. newBarkNode.name = newBarkNode.name .. BARK_SUFFIX
  681. newBarkNode.description = S("Bark of @1", newBarkNode.description)
  682. -- .drop: leave the bark nodes dropping the trunk wood
  683. tiles = trunkNode.tiles
  684. if type(tiles) == "table" then
  685. newBarkNode.tiles = { tiles[#tiles] .. barkoverlay }
  686. end
  687. --minetest.log("info", newTrunkNode.name .. ": " .. dump(newTrunkNode))
  688. minetest.register_node(newTrunkNode.name, newTrunkNode)
  689. minetest.register_node(newBarkNode.name, newBarkNode)
  690. return newTrunkNode.name
  691. end
  692. local function generate_leafTypes(nodeName_templateLeaf, overlay, nodesuffix, description, dropsTemplateLeaf, glowVariantBrightness)
  693. local leafNode = minetest.registered_nodes[nodeName_templateLeaf]
  694. local newLeafNode = {}
  695. for key, value in pairs(leafNode) do newLeafNode[key] = value end
  696. newLeafNode.name = SkyTrees.MODNAME .. ":" .. nodesuffix
  697. newLeafNode.description = description
  698. newLeafNode.sunlight_propagates = true -- soo many leaves they otherwise blot out the sun.
  699. if dropsTemplateLeaf then
  700. newLeafNode.drop = nodeName_templateLeaf
  701. if newLeafNode.groups == nil then newLeafNode.groups = {} end
  702. newLeafNode.groups.not_in_creative_inventory = 1
  703. else
  704. newLeafNode.drop = nil
  705. end
  706. local tiles = leafNode.tiles
  707. if type(tiles) == "table" then
  708. newLeafNode.tiles = {}
  709. for key, value in pairs(tiles) do newLeafNode.tiles[key] = value .. overlay end
  710. else
  711. newLeafNode.tiles = tiles .. overlay
  712. end
  713. minetest.register_node(newLeafNode.name, newLeafNode)
  714. if glowVariantBrightness ~= nil and glowVariantBrightness > 0 and BIOLUMINESCENCE then
  715. local glowingLeafNode = {}
  716. for key, value in pairs(newLeafNode) do glowingLeafNode[key] = value end
  717. glowingLeafNode.name = newLeafNode.name .. GLOW_SUFFIX
  718. glowingLeafNode.description = S("Glowing @1", description)
  719. glowingLeafNode.light_source = glowVariantBrightness
  720. minetest.register_node(glowingLeafNode.name, glowingLeafNode)
  721. end
  722. return newLeafNode.name
  723. end
  724. local templateWood = interop.find_node_name(NODENAMES_TREEWOOD)
  725. if templateWood == 'ignore' then
  726. SkyTrees.disabled = "Could not find any tree nodes"
  727. return
  728. end
  729. local normalwood = generate_woodTypes(templateWood, "", "", "tree", S("Giant tree"), true)
  730. local darkwood = generate_woodTypes(templateWood, "^[colorize:black:205", "^[colorize:black:205", "darkwood", S("Giant Ziricote"), false)
  731. local deadwood = generate_woodTypes(templateWood, "^[colorize:#EFE6B9:110", "^[colorize:#E8D0A0:110", "deadbleachedwood", S("Dead bleached wood"), false) -- make use of the bark blocks to introduce some color variance in the tree
  732. local templateLeaf = interop.find_node_name(NODENAMES_TREELEAVES)
  733. if templateLeaf == 'ignore' then
  734. SkyTrees.disabled = "Could not find any treeleaf nodes"
  735. return
  736. end
  737. local greenleaf1 = generate_leafTypes(templateLeaf, "", "leaves", S("Leaves of a giant tree"), true) -- drops templateLeaf because these look close enough to the original leaves that we won't clutter the game & creative-menu with tiny visual variants that other recipes/parts of the game won't know about
  738. local greenleaf2 = generate_leafTypes(templateLeaf, "^[colorize:#00FF00:16", "leaves2", S("Leaves of a giant tree"), false)
  739. local greenleaf3 = generate_leafTypes(templateLeaf, "^[colorize:#90FF60:28", "leaves3", S("Leaves of a giant tree"), false)
  740. local whiteblossom1 = generate_leafTypes(templateLeaf, "^[colorize:#fffdfd:alpha", "blossom_white1", S("Blossom"), false)
  741. local whiteblossom2 = generate_leafTypes(templateLeaf, "^[colorize:#fff0f0:alpha", "blossom_white2", S("Blossom"), false)
  742. local pinkblossom = generate_leafTypes(templateLeaf, "^[colorize:#FFE3E8:alpha", "blossom_whitepink", S("Blossom"), false, 5)
  743. local sakurablossom1 = generate_leafTypes(templateLeaf, "^[colorize:#ea327c:alpha", "blossom_red", S("Sakura blossom"), false, 5)
  744. local sakurablossom2 = generate_leafTypes(templateLeaf, "^[colorize:#ffc3dd:alpha", "blossom_pink", S("Sakura blossom"), false)
  745. local wisteriaBlossom1 = generate_leafTypes(templateLeaf, "^[colorize:#8087ec:alpha", "blossom_wisteria1", S("Wisteria blossom"), false)
  746. local wisteriaBlossom2 = generate_leafTypes(templateLeaf, "^[colorize:#ccc9ff:alpha", "blossom_wisteria2", S("Wisteria blossom"), false, 7)
  747. local tree = SkyTrees.schematicInfo[TREE1_FILE]
  748. if tree ~= nil then
  749. tree.defaultThemeName = "Green foliage"
  750. tree.theme[tree.defaultThemeName] = {
  751. relativeProbability = 5,
  752. trunk = normalwood,
  753. leaves1 = greenleaf1,
  754. leaves2 = greenleaf2,
  755. leaves_special = greenleaf3,
  756. vineflags = { leaves = true, hanging_leaves = true },
  757. init = function(self, position)
  758. -- if it's hot and humid then add vines
  759. local viney = get_heat(position) >= VINES_REQUIRED_TEMPERATURE and get_humidity(position) >= VINES_REQUIRED_HUMIDITY
  760. if viney then
  761. local flagSeed = position.x * 3 + position.z + ISLANDS_SEED
  762. self.vineflags.hanging_leaves = (flagSeed % 10) <= 3 or (flagSeed % 10) >= 8
  763. self.vineflags.leaves = (flagSeed % 10) <= 5
  764. self.vineflags.bark = (flagSeed % 10) <= 2
  765. self.vineflags.hanging_bark = (flagSeed % 10) <= 1
  766. end
  767. end
  768. }
  769. tree.theme["Haunted"] = {
  770. relativeProbability = 2,
  771. trunk = darkwood,
  772. vineflags = { hanging_roots = true },
  773. hasHeart = false,
  774. hasSoil = false,
  775. init = function(self, position)
  776. -- 60% of these trees are a hanging roots variant
  777. self.vineflags.hanging_roots = (position.x * 3 + position.y + position.z + ISLANDS_SEED) % 10 < 60
  778. end
  779. }
  780. tree.theme["Dead"] = {
  781. relativeProbability = 0, -- 0 because this theme will be chosen based on location, rather than chance.
  782. trunk = deadwood,
  783. hasHeart = false
  784. }
  785. tree.theme["Sakura"] = {
  786. relativeProbability = 2,
  787. trunk = darkwood,
  788. leaves1 = sakurablossom2,
  789. leaves2 = whiteblossom2,
  790. leaves_special = sakurablossom1,
  791. init = function(self, position)
  792. -- 40% of these trees are a glowing variant
  793. self.glowing = (position.x * 3 + position.z + ISLANDS_SEED) % 10 <= 3 and BIOLUMINESCENCE
  794. self.leaves_special = sakurablossom1
  795. if self.glowing then self.leaves_special = sakurablossom1 .. GLOW_SUFFIX end
  796. end
  797. }
  798. end
  799. tree = SkyTrees.schematicInfo[TREE2_FILE]
  800. if tree ~= nil then
  801. -- copy the green leaves theme from tree1
  802. tree.defaultThemeName = "Green foliage"
  803. tree.theme[tree.defaultThemeName] = SkyTrees.schematicInfo[TREE1_FILE].theme["Green foliage"]
  804. tree.theme["Wisteria"] = {
  805. relativeProbability = 2.5,
  806. trunk = normalwood,
  807. leaves1 = greenleaf1,
  808. leaves2 = wisteriaBlossom1,
  809. leaves_special = wisteriaBlossom2,
  810. vineflags = { leaves = true, hanging_leaves = true, hanging_bark = true },
  811. init = function(self, position)
  812. -- 40% of these trees are a glowing variant
  813. self.glowing = (position.x * 3 + position.z + ISLANDS_SEED) % 10 <= 3 and BIOLUMINESCENCE
  814. self.leaves_special = wisteriaBlossom2
  815. if self.glowing then self.leaves_special = wisteriaBlossom2 .. GLOW_SUFFIX end
  816. -- if it's hot and humid then allow vines on the trunk as well
  817. self.vineflags.bark = get_heat(position) >= VINES_REQUIRED_TEMPERATURE and get_humidity(position) >= VINES_REQUIRED_HUMIDITY
  818. end
  819. }
  820. tree.theme["Blossom"] = {
  821. relativeProbability = 1.5,
  822. trunk = normalwood,
  823. leaves1 = whiteblossom1,
  824. leaves2 = whiteblossom2,
  825. leaves_special = normalwood..BARK_SUFFIX,
  826. init = function(self, position)
  827. -- 30% of these trees are a glowing variant
  828. self.glowing = (position.x * 3 + position.z + ISLANDS_SEED) % 10 <= 2 and BIOLUMINESCENCE
  829. self.leaves_special = normalwood..BARK_SUFFIX
  830. if self.glowing then self.leaves_special = pinkblossom .. GLOW_SUFFIX end
  831. end
  832. }
  833. end
  834. -- fill in any omitted fields in the themes with default values
  835. for _,treeInfo in pairs(SkyTrees.schematicInfo) do
  836. for _,theme in pairs(treeInfo.theme) do
  837. if theme.bark == nil then theme.bark = theme.trunk .. BARK_SUFFIX end
  838. if theme.leaves1 == nil then theme.leaves1 = 'ignore' end
  839. if theme.leaves2 == nil then theme.leaves2 = 'ignore' end
  840. if theme.leaves_special == nil then theme.leaves_special = theme.leaves1 end
  841. if theme.vineflags == nil then theme.vineflags = {} end
  842. if theme.relativeProbability == nil then theme.relativeProbability = 1.0 end
  843. if theme.glowing == nil then theme.glowing = false end
  844. if theme.hasSoil == nil then theme.hasSoil = true end
  845. if theme.hasHeart == nil then theme.hasHeart = true end
  846. end
  847. end
  848. -- The heart of the Tree
  849. -- The difference between a living tree and and a haunted/darkened husk
  850. --
  851. -- Ideally trees would slowly fizzlefade to/from the Haunted theme depending on
  852. -- whether a player steals or restores the heart, meaning a house hollowed out inside
  853. -- a living tree would need the heart to still be kept inside it, perhaps on its
  854. -- own pedestal (unless wanting an Addam's Family treehouse).
  855. local heartwoodTexture = minetest.registered_nodes[templateWood].tiles
  856. if type(heartwoodTexture) == "table" then heartwoodTexture = heartwoodTexture[1] end
  857. local heartwoodGlow = minetest.LIGHT_MAX -- plants can grow under the heart of the Tree
  858. if not BIOLUMINESCENCE then heartwoodGlow = 0 end -- :(
  859. minetest.register_node(
  860. SkyTrees.MODNAME .. ":HeartWood",
  861. {
  862. tiles = { heartwoodTexture },
  863. description = S("Heart of the Tree"),
  864. groups = {oddly_breakable_by_hand = 3, handy = 1},
  865. _mcl_hardness = 0.4,
  866. drawtype = "nodebox",
  867. paramtype = "light",
  868. light_source = heartwoodGlow, -- plants can grow under the heart of the Tree
  869. node_box = {
  870. type = "fixed",
  871. fixed = {
  872. --[[ Original heart
  873. {-0.38, -0.38, -0.38, 0.38, 0.38, 0.38},
  874. {0.15, 0.15, 0.15, 0.5, 0.5, 0.5},
  875. {-0.5, 0.15, 0.15, -0.15, 0.5, 0.5},
  876. {-0.5, 0.15, -0.5, -0.15, 0.5, -0.15},
  877. {0.15, 0.15, -0.5, 0.5, 0.5, -0.15},
  878. {0.15, -0.5, -0.5, 0.5, -0.15, -0.15},
  879. {-0.5, -0.5, -0.5, -0.15, -0.15, -0.15},
  880. {-0.5, -0.5, 0.15, -0.15, -0.15, 0.5},
  881. {0.15, -0.5, 0.15, 0.5, -0.15, 0.5}
  882. ]]
  883. {-0.38, -0.38, -0.38, 0.38, 0.38, 0.38},
  884. {-0.5, -0.2, -0.2, 0.5, 0.2, 0.2},
  885. {-0.2, -0.5, -0.2, 0.2, 0.5, 0.2},
  886. {-0.2, -0.2, -0.5, 0.2, 0.2, 0.5}
  887. }
  888. }
  889. }
  890. )
  891. end
  892. -- this is hack to work around how place_schematic() never invalidates its cache
  893. -- a unique schematic filename is generated for each unique theme
  894. SkyTrees.getMalleatedFilename = function(schematicInfo, themeName)
  895. -- create a unique id for the theme
  896. local theme = schematicInfo.theme[themeName]
  897. local flags = 0
  898. if theme.glowing then flags = flags + 1 end
  899. if theme.vineflags.leaves then flags = flags + 2 end
  900. if theme.vineflags.hanging_leaves then flags = flags + 4 end
  901. if theme.vineflags.bark then flags = flags + 8 end
  902. if theme.vineflags.hanging_bark then flags = flags + 16 end
  903. if theme.vineflags.hanging_roots then flags = flags + 32 end
  904. if theme.hasSoil then flags = flags + 64 end
  905. if theme.hasHeart then flags = flags + 128 end
  906. local uniqueId = themeName .. flags
  907. if schematicInfo.malleatedFilenames == nil then schematicInfo.malleatedFilenames = {} end
  908. if schematicInfo.malleatedFilenames[uniqueId] == nil then
  909. local malleationCount = 0
  910. for _ in pairs(schematicInfo.malleatedFilenames) do malleationCount = malleationCount + 1 end
  911. local malleatedFilename = minetest.get_modpath(SkyTrees.MODNAME) .. DIR_DELIM
  912. for i = 1, malleationCount do
  913. malleatedFilename = malleatedFilename .. '.' .. DIR_DELIM -- should work on both Linux and Windows
  914. end
  915. malleatedFilename = malleatedFilename .. schematicInfo.filename
  916. schematicInfo.malleatedFilenames[uniqueId] = malleatedFilename
  917. end
  918. --minetest.log("info", "Malleated file name for " .. uniqueId .. " is " .. schematicInfo.malleatedFilenames[uniqueId])
  919. return schematicInfo.malleatedFilenames[uniqueId]
  920. end
  921. -- Returns true if a tree in this location would be dead
  922. -- (checks for desert)
  923. SkyTrees.isDead = function(position)
  924. local heat = get_heat(position)
  925. local humidity = get_humidity(position)
  926. if humidity <= 10 or (humidity <= 20 and heat >= 80) then
  927. return true
  928. end
  929. local biomeId = interop.get_biome_key(position)
  930. local biome = biomes[biomeId]
  931. if biome ~= nil and biome.node_top ~= nil then
  932. local modname, nodename = interop.split_nodename(biome.node_top)
  933. if string.find(nodename, "sand") or string.find(nodename, "desert") then
  934. return true
  935. end
  936. end
  937. end
  938. -- Returns the name of a suitable theme
  939. -- Picks a theme from the schematicInfo automatically, based on the themes' relativeProbability, and location.
  940. SkyTrees.selectTheme = function(position, schematicInfo, choiceSeed)
  941. local deadThemeName = "Dead"
  942. if schematicInfo.theme[deadThemeName] ~= nil then
  943. -- Tree is dead and bleached in desert biomes
  944. if SkyTrees.isDead(position) then
  945. return deadThemeName
  946. end
  947. end
  948. if choiceSeed == nil then choiceSeed = 0 end
  949. -- Use a known PRNG implementation
  950. local prng = PcgRandom(
  951. position.x * 65732 +
  952. position.z * 729 +
  953. schematicInfo.size.x * 3 +
  954. choiceSeed
  955. )
  956. local sumProbabilities = 0
  957. for _,theme in pairs(schematicInfo.theme) do
  958. sumProbabilities = sumProbabilities + theme.relativeProbability
  959. end
  960. local selection = prng:next(0, sumProbabilities * 1000) / 1000
  961. if DEBUG_SKYTREES then minetest.log("info", "Skytrees x: "..position.x.." y: ".. position.y .. " sumProbabilities: " .. sumProbabilities .. ", selection: " .. selection) end
  962. sumProbabilities = 0
  963. for themeName,theme in pairs(schematicInfo.theme) do
  964. if selection <= sumProbabilities + theme.relativeProbability then
  965. return themeName
  966. else
  967. sumProbabilities = sumProbabilities + theme.relativeProbability
  968. end
  969. end
  970. error(SkyTrees.MODNAME .. " - SkyTrees.selectTheme failed to find a theme", 0)
  971. return schematicInfo.defaultThemeName
  972. end
  973. -- position is a vector {x, y, z}
  974. -- rotation must be either 0, 90, 180, or 270
  975. -- schematicInfo must be one of the items in SkyTrees.schematicInfo[]
  976. -- topsoil [optional] is the biome's "node_top" - the ground node of the region.
  977. SkyTrees.placeTree = function(position, rotation, schematicInfo, themeName, topsoil)
  978. if SkyTrees.disabled ~= nil then
  979. error(SkyTrees.MODNAME .. " - SkyTrees are disabled: " .. SkyTrees.disabled, 0)
  980. return
  981. end
  982. -- returns a new position vector, rotated around (0, 0) to match the schematic rotation (provided the schematic_size is correct!)
  983. local function rotatePositon(position, schematic_size, rotation)
  984. local result = vector.new(position)
  985. if rotation == 90 then
  986. result.x = position.z
  987. result.z = schematic_size.x - position.x - 1
  988. elseif rotation == 180 then
  989. result.x = schematic_size.x - position.x - 1
  990. result.z = schematic_size.z - position.z - 1
  991. elseif rotation == 270 then
  992. result.x = schematic_size.z - position.z - 1
  993. result.z = position.x
  994. end
  995. return result
  996. end
  997. local rotatedCenter = rotatePositon(schematicInfo.center, schematicInfo.size, rotation)
  998. local treePos = vector.subtract(position, rotatedCenter)
  999. if themeName == nil then themeName = SkyTrees.selectTheme(position, schematicInfo) end
  1000. local theme = schematicInfo.theme[themeName]
  1001. if theme == nil then error(MODNAME .. ' called SkyTrees.placeTree("' .. schematicInfo.filename .. '") with invalid theme: ' .. themeName, 0) end
  1002. if theme.init ~= nil then theme.init(theme, position) end
  1003. if theme.hasSoil then
  1004. if topsoil == nil then
  1005. topsoil = 'ignore'
  1006. if minetest.get_biome_data == nil then error(SkyTrees.MODNAME .. " requires Minetest v5.0 or greater, or to have minor modifications to support v0.4.x", 0) end
  1007. local treeBiome = biomes[interop.get_biome_key(position)]
  1008. if treeBiome ~= nil and treeBiome.node_top ~= nil then topsoil = treeBiome.node_top end
  1009. end
  1010. else
  1011. topsoil = 'ignore'
  1012. end
  1013. local nodeName_heart = SkyTrees.MODNAME .. ":HeartWood"
  1014. if not theme.hasHeart then nodeName_heart = 'ignore' end
  1015. -- theme.init() may have changed the vineflags, so update the replacement node names
  1016. if theme.vineflags.hanging_leaves == true and SkyTrees.nodeName_hangingVine == 'ignore' then theme.vineflags.leaves = true end -- if there are no hanging vines then substitute side_vines
  1017. if theme.vineflags.leaves == true then theme.leaf_vines = SkyTrees.nodeName_sideVines else theme.leaf_vines = 'ignore' end
  1018. if theme.vineflags.bark == true then theme.bark_vines = SkyTrees.nodeName_sideVines else theme.bark_vines = 'ignore' end
  1019. if theme.vineflags.hanging_leaves == true then theme.hanging_leaf_vines = SkyTrees.nodeName_hangingVine else theme.hanging_leaf_vines = 'ignore' end
  1020. if theme.vineflags.hanging_bark == true then theme.hanging_bark_vines = SkyTrees.nodeName_hangingVine else theme.hanging_bark_vines = 'ignore' end
  1021. if theme.vineflags.hanging_roots == true and SkyTrees.nodeName_hangingRoot ~= 'ignore' then theme.hanging_bark_vines = SkyTrees.nodeName_hangingRoot end
  1022. local replacements = {
  1023. ['treebark\r\n\r\n~~~ Cloudlands_tree mts by Dr.Frankenstone: Amateur Arborist ~~~\r\n\r\n'] = theme.bark, -- because this node name is always replaced, it can double as space for a text header in the file.
  1024. ['default:tree'] = theme.trunk,
  1025. ['default:leaves'] = theme.leaves1,
  1026. ['leaves_alt'] = theme.leaves2,
  1027. ['leaves_special'] = theme.leaves_special,
  1028. ['leaf_vines'] = theme.leaf_vines,
  1029. ['bark_vines'] = theme.bark_vines,
  1030. ['hanging_leaf_vines'] = theme.hanging_leaf_vines,
  1031. ['hanging_bark_vines'] = theme.hanging_bark_vines,
  1032. ['default:dirt'] = topsoil,
  1033. ['heart'] = nodeName_heart
  1034. }
  1035. if minetest.global_exists("schemlib") then
  1036. -- Use schemlib instead minetest.place_schematic(), to avoid bugs in place_schematic()
  1037. local filename = minetest.get_modpath(SkyTrees.MODNAME) .. DIR_DELIM .. schematicInfo.filename
  1038. local plan_obj = schemlib.plan.new()
  1039. plan_obj:read_from_schem_file(filename, replacements)
  1040. plan_obj.data.ground_y = -1 -- prevent read_from_schem_file() from automatically adjusting the height when it encounters dirt in the schematic (SkyTrees sometimes have dirt up in their nooks)
  1041. plan_obj.data.facedir = round(rotation / 90)
  1042. rotatedCenter = plan_obj:get_world_pos(vector.add(vector.multiply(schematicInfo.center, -1), -1), position) -- this function performs the rotation I require, even if it's named/intended for something else.
  1043. plan_obj.data.anchor_pos = rotatedCenter
  1044. if DEBUG_SKYTREES then minetest.log("info", "building tree at " .. dump(position) .. "rotated to " .. dump(treePos) .. "rotatedCenter " .. dump(rotatedCenter) .. ", " .. schematicInfo.filename) end
  1045. plan_obj:set_status("build")
  1046. else -- fall back on minetest.place_schematic()
  1047. local malleatedFilename = SkyTrees.getMalleatedFilename(schematicInfo, themeName)
  1048. if DEBUG_SKYTREES then minetest.log("info", "placing tree at " .. dump(position) .. "rotated to " .. dump(treePos) .. "rotatedCenter " .. dump(rotatedCenter) .. ", " .. schematicInfo.filename) end
  1049. -- Defering minetest.place_schematic() until after the lua emerge seems to reduce the likelyhood of
  1050. -- having it draw the tree with pieces missing.
  1051. minetest.after(
  1052. 0.1,
  1053. function(treePos, malleatedFilename, rotation, replacements, schematicInfo)
  1054. minetest.place_schematic(treePos, malleatedFilename, rotation, replacements, true)
  1055. -- minetest.place_schematic() doesn't invoke node constructors, so use set_node() for any nodes requiring construction
  1056. for i, schematicCoords in pairs(schematicInfo.nodesWithConstructor) do
  1057. if rotation ~= 0 then schematicCoords = rotatePositon(schematicCoords, schematicInfo.size, rotation) end
  1058. local nodePos = vector.add(treePos, schematicCoords)
  1059. local nodeToConstruct = minetest.get_node(nodePos)
  1060. if nodeToConstruct.name == "air" or nodeToConstruct.name == nodeName_ignore then
  1061. --this is now normal - e.g. if vines are set to 'ignore' then the nodeToConstruct won't be there.
  1062. --minetest.log("error", "nodesWithConstructor["..i.."] does not match schematic " .. schematicInfo.filename .. " at " .. nodePos.x..","..nodePos.y..","..nodePos.z.." rotation "..rotation)
  1063. else
  1064. minetest.set_node(nodePos, nodeToConstruct)
  1065. end
  1066. end
  1067. end,
  1068. treePos, malleatedFilename, rotation, replacements, schematicInfo
  1069. )
  1070. end
  1071. end
  1072. end
  1073. SkyTrees.init()
  1074. --[[==============================
  1075. Initialization and Mapgen
  1076. ==============================]]--
  1077. local function init_mapgen()
  1078. -- invoke get_perlin() here, since it can't be invoked before the environment
  1079. -- is created because it uses the world's seed value.
  1080. noise_eddyField = minetest.get_perlin(noiseparams_eddyField)
  1081. noise_heightMap = minetest.get_perlin(noiseparams_heightMap)
  1082. noise_density = minetest.get_perlin(noiseparams_density)
  1083. noise_surfaceMap = minetest.get_perlin(noiseparams_surfaceMap)
  1084. noise_skyReef = minetest.get_perlin(noiseparams_skyReef)
  1085. local prng = PcgRandom(122456 + ISLANDS_SEED)
  1086. for i = 0,255 do randomNumbers[i] = prng:next(0, 0x10000) / 0x10000 end
  1087. if isMapgenV6 then
  1088. biomes["Normal"] = {node_top="mapgen_dirt_with_grass", node_filler="mapgen_dirt", node_stone="mapgen_stone"}
  1089. biomes["Desert"] = {node_top="mapgen_desert_sand", node_filler="mapgen_desert_sand", node_stone="mapgen_desert_stone"}
  1090. biomes["Jungle"] = {node_top="mapgen_dirt_with_grass", node_filler="mapgen_dirt", node_stone="mapgen_stone"}
  1091. biomes["Tundra"] = {node_top="mapgen_dirt_with_snow", node_filler="mapgen_dirt", node_stone="mapgen_stone"}
  1092. biomes["Taiga"] = {node_top="mapgen_dirt_with_snow", node_filler="mapgen_dirt", node_stone="mapgen_stone"}
  1093. else
  1094. for k,v in pairs(minetest.registered_biomes) do
  1095. biomes[minetest.get_biome_id(k)] = v
  1096. end
  1097. end
  1098. if DEBUG then minetest.log("info", "registered biomes: " .. dump(biomes)) end
  1099. nodeId_air = minetest.get_content_id("air")
  1100. nodeId_stone = interop.find_node_id(NODENAMES_STONE)
  1101. nodeId_grass = interop.find_node_id(NODENAMES_GRASS)
  1102. nodeId_dirt = interop.find_node_id(NODENAMES_DIRT)
  1103. nodeId_water = interop.find_node_id(NODENAMES_WATER)
  1104. nodeId_ice = interop.find_node_id(NODENAMES_ICE)
  1105. nodeId_silt = interop.find_node_id(NODENAMES_SILT)
  1106. nodeId_gravel = interop.find_node_id(NODENAMES_GRAVEL)
  1107. nodeId_vine = interop.find_node_id(NODENAMES_VINES)
  1108. nodeName_vine = minetest.get_name_from_content_id(nodeId_vine)
  1109. local regionRectStr = minetest.settings:get(MODNAME .. "_limit_rect")
  1110. if type(regionRectStr) == "string" then
  1111. local minXStr, minZStr, maxXStr, maxZStr = string.match(regionRectStr, '(-?[%d%.]+)[,%s]+(-?[%d%.]+)[,%s]+(-?[%d%.]+)[,%s]+(-?[%d%.]+)')
  1112. if minXStr ~= nil then
  1113. local minX, minZ, maxX, maxZ = tonumber(minXStr), tonumber(minZStr), tonumber(maxXStr), tonumber(maxZStr)
  1114. if minX ~= nil and maxX ~= nil and minX < maxX then
  1115. region_min_x, region_max_x = minX, maxX
  1116. end
  1117. if minZ ~= nil and maxZ ~= nil and minZ < maxZ then
  1118. region_min_z, region_max_z = minZ, maxZ
  1119. end
  1120. end
  1121. end
  1122. local limitToBiomesStr = minetest.settings:get(MODNAME .. "_limit_biome")
  1123. if type(limitToBiomesStr) == "string" and string.len(limitToBiomesStr) > 0 then
  1124. limit_to_biomes = limitToBiomesStr:lower()
  1125. end
  1126. limit_to_biomes_altitude = tonumber(minetest.settings:get(MODNAME .. "_limit_biome_altitude"))
  1127. region_restrictions =
  1128. region_min_x > -32000 or region_min_z > -32000
  1129. or region_max_x < 32000 or region_max_z < 32000
  1130. or limit_to_biomes ~= nil
  1131. end
  1132. -- Updates coreList to include all cores of type coreType within the given bounds
  1133. local function addCores(coreList, coreType, x1, z1, x2, z2)
  1134. -- this function is used by the API functions, so may be invoked without our on_generated
  1135. -- being called
  1136. cloudlands.init()
  1137. for z = math_floor(z1 / coreType.territorySize), math_floor(z2 / coreType.territorySize) do
  1138. for x = math_floor(x1 / coreType.territorySize), math_floor(x2 / coreType.territorySize) do
  1139. -- Use a known PRNG implementation, to make life easier for Amidstest
  1140. local prng = PcgRandom(
  1141. x * 8973896 +
  1142. z * 7467838 +
  1143. worldSeed + 8438 + ISLANDS_SEED
  1144. )
  1145. local coresInTerritory = {}
  1146. for i = 1, coreType.coresPerTerritory do
  1147. local coreX = x * coreType.territorySize + prng:next(0, coreType.territorySize - 1)
  1148. local coreZ = z * coreType.territorySize + prng:next(0, coreType.territorySize - 1)
  1149. -- there's strong vertical and horizontal tendency in 2-octave noise,
  1150. -- so rotate it a little to avoid it lining up with the world axis.
  1151. local noiseX = ROTATE_COS * coreX - ROTATE_SIN * coreZ
  1152. local noiseZ = ROTATE_SIN * coreX + ROTATE_COS * coreZ
  1153. local eddyField = noise_eddyField:get2d({x = noiseX, y = noiseZ})
  1154. if (math_abs(eddyField) < coreType.frequency) then
  1155. local nexusConditionMet = not coreType.requiresNexus
  1156. if not nexusConditionMet then
  1157. -- A 'nexus' is a made up name for a place where the eddyField is flat.
  1158. -- There are often many 'field lines' leading out from a nexus.
  1159. -- Like a saddle in the perlin noise the height "coreType.frequency"
  1160. local eddyField_orthA = noise_eddyField:get2d({x = noiseX + 2, y = noiseZ})
  1161. local eddyField_orthB = noise_eddyField:get2d({x = noiseX, y = noiseZ + 2})
  1162. if math_abs(eddyField - eddyField_orthA) + math_abs(eddyField - eddyField_orthB) < 0.02 then
  1163. nexusConditionMet = true
  1164. end
  1165. end
  1166. if nexusConditionMet then
  1167. local radius = (coreType.radiusMax + prng:next(0, coreType.radiusMax) * 2) / 3 -- give a 33%/66% weighting split between max-radius and random
  1168. local depth = (coreType.depthMax + prng:next(0, coreType.depthMax) * 2) / 2 -- ERROR!! fix this bug! should be dividing by 3. But should not change worldgen now, so adjust depthMax of islands so nothing changes when bug is fixed?
  1169. local thickness = prng:next(0, coreType.thicknessMax)
  1170. if coreX >= x1 and coreX < x2 and coreZ >= z1 and coreZ < z2 then
  1171. local spaceConditionMet = not coreType.exclusive
  1172. if not spaceConditionMet then
  1173. -- see if any other cores occupy this space, and if so then
  1174. -- either deny the core, or raise it
  1175. spaceConditionMet = true
  1176. local minDistSquared = radius * radius * .7
  1177. for _,core in ipairs(coreList) do
  1178. if core.type.radiusMax == coreType.radiusMax then
  1179. -- We've reached the cores of the current type. We can't exclude based on all
  1180. -- cores of the same type as we can't be sure neighboring territories will have been generated.
  1181. break
  1182. end
  1183. if (core.x - coreX)*(core.x - coreX) + (core.z - coreZ)*(core.z - coreZ) <= minDistSquared + core.radius * core.radius then
  1184. spaceConditionMet = false
  1185. break
  1186. end
  1187. end
  1188. if spaceConditionMet then
  1189. for _,core in ipairs(coresInTerritory) do
  1190. -- We can assume all cores of the current type are being generated in this territory,
  1191. -- so we can exclude the core if it overlaps one already in this territory.
  1192. if (core.x - coreX)*(core.x - coreX) + (core.z - coreZ)*(core.z - coreZ) <= minDistSquared + core.radius * core.radius then
  1193. spaceConditionMet = false
  1194. break
  1195. end
  1196. end
  1197. end
  1198. end
  1199. if spaceConditionMet then
  1200. -- all conditions met, we've located a new island core
  1201. --minetest.log("Adding core "..x..","..y..","..z..","..radius)
  1202. local y = round(noise_heightMap:get2d({x = coreX, y = coreZ}))
  1203. local newCore = {
  1204. x = coreX,
  1205. y = y,
  1206. z = coreZ,
  1207. radius = radius,
  1208. thickness = thickness,
  1209. depth = depth,
  1210. type = coreType,
  1211. }
  1212. coreList[#coreList + 1] = newCore
  1213. coresInTerritory[#coreList + 1] = newCore
  1214. end
  1215. else
  1216. -- We didn't test coreX,coreZ against x1,z1,x2,z2 immediately and save all
  1217. -- that extra work, as that would break the determinism of the prng calls.
  1218. -- i.e. if the area was approached from a different direction then a
  1219. -- territory might end up with a different list of cores.
  1220. -- TODO: filter earlier but advance prng?
  1221. end
  1222. end
  1223. end
  1224. end
  1225. end
  1226. end
  1227. end
  1228. -- removes any islands that fall outside region restrictions specified in the options
  1229. local function removeUnwantedIslands(coreList)
  1230. local testBiome = limit_to_biomes ~= nil
  1231. local get_biome_name = nil
  1232. if testBiome then
  1233. -- minetest.get_biome_name() was added in March 2018, we'll ignore the
  1234. -- limit_to_biomes option on versions of Minetest that predate this
  1235. get_biome_name = minetest.get_biome_name
  1236. testBiome = get_biome_name ~= nil
  1237. if get_biome_name == nil then
  1238. minetest.log("warning", MODNAME .. " ignoring " .. MODNAME .. "_limit_biome option as Minetest API version too early to support get_biome_name()")
  1239. limit_to_biomes = nil
  1240. end
  1241. end
  1242. for i = #coreList, 1, -1 do
  1243. local core = coreList[i]
  1244. local coreX = core.x
  1245. local coreZ = core.z
  1246. if coreX < region_min_x or coreX > region_max_x or coreZ < region_min_z or coreZ > region_max_z then
  1247. table.remove(coreList, i)
  1248. elseif testBiome then
  1249. local biomeAltitude
  1250. if (limit_to_biomes_altitude == nil) then biomeAltitude = ALTITUDE + core.y else biomeAltitude = limit_to_biomes_altitude end
  1251. local biomeName = get_biome_name(minetest.get_biome_data({x = coreX, y = biomeAltitude, z = coreZ}).biome)
  1252. if not string.match(limit_to_biomes, biomeName:lower()) then
  1253. table.remove(coreList, i)
  1254. end
  1255. end
  1256. end
  1257. end
  1258. -- gets an array of all cores which may intersect the (minp, maxp) area
  1259. -- y is ignored
  1260. cloudlands.get_island_details = function(minp, maxp)
  1261. local result = {}
  1262. for _,coreType in pairs(cloudlands.coreTypes) do
  1263. addCores(
  1264. result,
  1265. coreType,
  1266. minp.x - coreType.radiusMax,
  1267. minp.z - coreType.radiusMax,
  1268. maxp.x + coreType.radiusMax,
  1269. maxp.z + coreType.radiusMax
  1270. )
  1271. end
  1272. -- remove islands only after cores have all generated to avoid the restriction
  1273. -- settings from rearranging islands.
  1274. if region_restrictions then removeUnwantedIslands(result) end
  1275. return result
  1276. end
  1277. cloudlands.find_nearest_island = function(x, z, search_radius)
  1278. local coreList = {}
  1279. for _,coreType in pairs(cloudlands.coreTypes) do
  1280. addCores(
  1281. coreList,
  1282. coreType,
  1283. x - (search_radius + coreType.radiusMax),
  1284. z - (search_radius + coreType.radiusMax),
  1285. x + (search_radius + coreType.radiusMax),
  1286. z + (search_radius + coreType.radiusMax)
  1287. )
  1288. end
  1289. -- remove islands only after cores have all generated to avoid the restriction
  1290. -- settings from rearranging islands.
  1291. if region_restrictions then removeUnwantedIslands(coreList) end
  1292. local result = nil
  1293. for _,core in ipairs(coreList) do
  1294. local distance = math.hypot(core.x - x, core.z - z)
  1295. if distance >= core.radius then
  1296. core.distance = 1 + distance - core.radius
  1297. else
  1298. -- distance is fractional
  1299. core.distance = distance / (core.radius + 1)
  1300. end
  1301. if result == nil or core.distance < result.distance then result = core end
  1302. end
  1303. return result
  1304. end
  1305. -- coreList can be left as null, but if you wish to sample many heights in a small area
  1306. -- then use cloudlands.get_island_details() to get the coreList for that area and save
  1307. -- having to recalculate it during each call to get_height_at().
  1308. cloudlands.get_height_at = function(x, z, coreList)
  1309. local result, isWater = nil, false
  1310. if coreList == nil then
  1311. local pos = {x = x, z = z}
  1312. coreList = cloudlands.get_island_details(pos, pos)
  1313. end
  1314. for _,core in ipairs(coreList) do
  1315. -- duplicates the code from renderCores() to find surface height
  1316. -- See the renderCores() version for explanatory comments
  1317. local horz_easing
  1318. local distanceSquared = (x - core.x)*(x - core.x) + (z - core.z)*(z - core.z)
  1319. local radiusSquared = core.radius * core.radius
  1320. local noise_weighting = 1
  1321. local shapeType = math_floor(core.depth + core.radius + core.x) % 5
  1322. if shapeType < 2 then -- convex, see renderCores() implementatin for comments
  1323. horz_easing = 1 - distanceSquared / radiusSquared
  1324. elseif shapeType == 2 then -- conical, see renderCores() implementatin for comments
  1325. horz_easing = 1 - math_sqrt(distanceSquared) / core.radius
  1326. else -- concave, see renderCores() implementatin for comments
  1327. local radiusRoot = math_sqrt(core.radius)
  1328. local squared = 1 - distanceSquared / radiusSquared
  1329. local distance = math_sqrt(distanceSquared)
  1330. local distance_normalized = distance / core.radius
  1331. local root = 1 - math_sqrt(distance) / radiusRoot
  1332. horz_easing = math_min(1, 0.8*distance_normalized*squared + 1.2*(1-distance_normalized)*root)
  1333. noise_weighting = 0.63
  1334. end
  1335. if core.radius + core.depth > 80 then noise_weighting = 0.6 end
  1336. if core.radius + core.depth > 120 then noise_weighting = 0.35 end
  1337. local surfaceNoise = noise_surfaceMap:get2d({x = x, y = z})
  1338. if DEBUG_GEOMETRIC then surfaceNoise = SURFACEMAP_OFFSET end
  1339. local coreTop = ALTITUDE + core.y
  1340. local surfaceHeight = coreTop + round(surfaceNoise * 3 * (core.thickness + 1) * horz_easing)
  1341. if result == nil or math_max(coreTop, surfaceHeight) > result then
  1342. local coreBottom = math_floor(coreTop - (core.thickness + core.depth))
  1343. local yBottom = coreBottom
  1344. if result ~= nil then yBottom = math_max(yBottom, result + 1) end
  1345. for y = math_max(coreTop, surfaceHeight), yBottom, -1 do
  1346. local vert_easing = math_min(1, (y - coreBottom) / core.depth)
  1347. local densityNoise = noise_density:get3d({x = x, y = y - coreTop, z = z})
  1348. densityNoise = noise_weighting * densityNoise + (1 - noise_weighting) * DENSITY_OFFSET
  1349. if DEBUG_GEOMETRIC then densityNoise = DENSITY_OFFSET end
  1350. if densityNoise * ((horz_easing + vert_easing) / 2) >= REQUIRED_DENSITY then
  1351. result = y
  1352. isWater = surfaceNoise < 0
  1353. break
  1354. --[[abandoned because do we need to calc the bottom of ponds? It also needs the outer code refactored to work
  1355. if not isWater then
  1356. -- we've found the land height
  1357. break
  1358. else
  1359. -- find the pond bottom, since the water level is already given by (ALTITUDE + island.y)
  1360. local surfaceDensity = densityNoise * ((horz_easing + 1) / 2)
  1361. local onTheEdge = math_sqrt(distanceSquared) + 1 >= core.radius
  1362. if onTheEdge or surfaceDensity > (REQUIRED_DENSITY + core.type.pondWallBuffer) then
  1363. break
  1364. end
  1365. end]]
  1366. end
  1367. end
  1368. end
  1369. end
  1370. return result, isWater
  1371. end
  1372. local function setCoreBiomeData(core)
  1373. local pos = {x = core.x, y = ALTITUDE + core.y, z = core.z}
  1374. if LOWLAND_BIOMES then pos.y = LOWLAND_BIOME_ALTITUDE end
  1375. core.biomeId = interop.get_biome_key(pos)
  1376. core.biome = biomes[core.biomeId]
  1377. core.temperature = get_heat(pos)
  1378. core.humidity = get_humidity(pos)
  1379. if core.temperature == nil then core.temperature = 50 end
  1380. if core.humidity == nil then core.humidity = 50 end
  1381. if core.biome == nil then
  1382. -- Some games don't use the biome list, so come up with some fallbacks
  1383. core.biome = {}
  1384. core.biome.node_top = minetest.get_name_from_content_id(nodeId_grass)
  1385. core.biome.node_filler = minetest.get_name_from_content_id(nodeId_dirt)
  1386. end
  1387. end
  1388. local function addDetail_vines(decoration_list, core, data, area, minp, maxp)
  1389. if VINE_COVERAGE > 0 and nodeId_vine ~= nodeId_ignore then
  1390. local y = ALTITUDE + core.y
  1391. if y >= minp.y and y <= maxp.y then
  1392. -- if core.biome is nil then renderCores() never rendered it, which means it
  1393. -- doesn't instersect this draw region.
  1394. if core.biome ~= nil and core.humidity >= VINES_REQUIRED_HUMIDITY and core.temperature >= VINES_REQUIRED_TEMPERATURE then
  1395. local nodeId_top
  1396. local nodeId_filler
  1397. local nodeId_stoneBase
  1398. local nodeId_dust
  1399. if core.biome.node_top == nil then nodeId_top = nodeId_stone else nodeId_top = minetest.get_content_id(core.biome.node_top) end
  1400. if core.biome.node_filler == nil then nodeId_filler = nodeId_stone else nodeId_filler = minetest.get_content_id(core.biome.node_filler) end
  1401. if core.biome.node_stone == nil then nodeId_stoneBase = nodeId_stone else nodeId_stoneBase = minetest.get_content_id(core.biome.node_stone) end
  1402. if core.biome.node_dust == nil then nodeId_dust = nodeId_stone else nodeId_dust = minetest.get_content_id(core.biome.node_dust) end
  1403. local function isIsland(nodeId)
  1404. return (nodeId == nodeId_filler or nodeId == nodeId_top
  1405. or nodeId == nodeId_stoneBase or nodeId == nodeId_dust
  1406. or nodeId == nodeId_silt or nodeId == nodeId_water)
  1407. end
  1408. local function findHighestNodeFace(y, solidIndex, emptyIndex)
  1409. -- return the highest y value (or maxp.y) where solidIndex is part of an island
  1410. -- and emptyIndex is not
  1411. local yOffset = 1
  1412. while y + yOffset <= maxp.y and isIsland(data[solidIndex + yOffset * area.ystride]) and not isIsland(data[emptyIndex + yOffset * area.ystride]) do
  1413. yOffset = yOffset + 1
  1414. end
  1415. return y + yOffset - 1
  1416. end
  1417. local radius = round(core.radius)
  1418. local xCropped = math_min(maxp.x, math_max(minp.x, core.x))
  1419. local zStart = math_max(minp.z, core.z - radius)
  1420. local vi = area:index(xCropped, y, zStart)
  1421. for z = 0, math_min(maxp.z, core.z + radius) - zStart do
  1422. local searchIndex = vi + z * area.zstride
  1423. if isIsland(data[searchIndex]) then
  1424. -- add vines to east face
  1425. if randomNumbers[(zStart + z + y) % 256] <= VINE_COVERAGE then
  1426. for x = xCropped + 1, maxp.x do
  1427. if not isIsland(data[searchIndex + 1]) then
  1428. local yhighest = findHighestNodeFace(y, searchIndex, searchIndex + 1)
  1429. decoration_list[#decoration_list + 1] = {pos={x=x, y=yhighest, z= zStart + z}, node={name = nodeName_vine, param2 = 3}}
  1430. break
  1431. end
  1432. searchIndex = searchIndex + 1
  1433. end
  1434. end
  1435. -- add vines to west face
  1436. if randomNumbers[(zStart + z + y + 128) % 256] <= VINE_COVERAGE then
  1437. searchIndex = vi + z * area.zstride
  1438. for x = xCropped - 1, minp.x, -1 do
  1439. if not isIsland(data[searchIndex - 1]) then
  1440. local yhighest = findHighestNodeFace(y, searchIndex, searchIndex - 1)
  1441. decoration_list[#decoration_list + 1] = {pos={x=x, y=yhighest, z= zStart + z}, node={name = nodeName_vine, param2 = 2}}
  1442. break
  1443. end
  1444. searchIndex = searchIndex - 1
  1445. end
  1446. end
  1447. end
  1448. end
  1449. local zCropped = math_min(maxp.z, math_max(minp.z, core.z))
  1450. local xStart = math_max(minp.x, core.x - radius)
  1451. local zstride = area.zstride
  1452. vi = area:index(xStart, y, zCropped)
  1453. for x = 0, math_min(maxp.x, core.x + radius) - xStart do
  1454. local searchIndex = vi + x
  1455. if isIsland(data[searchIndex]) then
  1456. -- add vines to north face (make it like moss - grows better on the north side)
  1457. if randomNumbers[(xStart + x + y) % 256] <= (VINE_COVERAGE * 1.2) then
  1458. for z = zCropped + 1, maxp.z do
  1459. if not isIsland(data[searchIndex + zstride]) then
  1460. local yhighest = findHighestNodeFace(y, searchIndex, searchIndex + zstride)
  1461. decoration_list[#decoration_list + 1] = {pos={x=xStart + x, y=yhighest, z=z}, node={name = nodeName_vine, param2 = 5}}
  1462. break
  1463. end
  1464. searchIndex = searchIndex + zstride
  1465. end
  1466. end
  1467. -- add vines to south face (make it like moss - grows better on the north side)
  1468. if randomNumbers[(xStart + x + y + 128) % 256] <= (VINE_COVERAGE * 0.8) then
  1469. searchIndex = vi + x
  1470. for z = zCropped - 1, minp.z, -1 do
  1471. if not isIsland(data[searchIndex - zstride]) then
  1472. local yhighest = findHighestNodeFace(y, searchIndex, searchIndex - zstride)
  1473. decoration_list[#decoration_list + 1] = {pos={x=xStart + x, y=yhighest, z=z}, node={name = nodeName_vine, param2 = 4}}
  1474. break
  1475. end
  1476. searchIndex = searchIndex - zstride
  1477. end
  1478. end
  1479. end
  1480. end
  1481. end
  1482. end
  1483. end
  1484. end
  1485. -- A rare formation of rocks circling or crowning an island
  1486. -- returns true if voxels were changed
  1487. local function addDetail_skyReef(decoration_list, core, data, area, minp, maxp)
  1488. local coreTop = ALTITUDE + core.y
  1489. local overdrawTop = maxp.y + OVERDRAW
  1490. local reefAltitude = math_floor(coreTop - 1 - core.thickness / 2)
  1491. local reefMaxHeight = 12
  1492. local reefMaxUnderhang = 4
  1493. if (maxp.y < reefAltitude - reefMaxUnderhang) or (minp.y > reefAltitude + reefMaxHeight) then
  1494. --no reef here
  1495. return false
  1496. end
  1497. local isReef = core.radius < core.type.radiusMax * 0.4 -- a reef can't extend beyond radiusMax, so needs a small island
  1498. local isAtoll = core.radius > core.type.radiusMax * 0.8
  1499. if not (isReef or isAtoll) then return false end
  1500. local fastHash = 3
  1501. fastHash = (37 * fastHash) + core.x
  1502. fastHash = (37 * fastHash) + core.z
  1503. fastHash = (37 * fastHash) + math_floor(core.radius)
  1504. fastHash = (37 * fastHash) + math_floor(core.depth)
  1505. if ISLANDS_SEED ~= 1000 then fastHash = (37 * fastHash) + ISLANDS_SEED end
  1506. local rarityAdj = 1
  1507. if core.type.requiresNexus and isAtoll then rarityAdj = 4 end -- humongous islands are very rare, and look good as an atoll
  1508. if (REEF_RARITY * rarityAdj * 1000) < math_floor((math_abs(fastHash)) % 1000) then return false end
  1509. local coreX = core.x --save doing a table lookup in the loop
  1510. local coreZ = core.z --save doing a table lookup in the loop
  1511. -- Use a known PRNG implementation
  1512. local prng = PcgRandom(
  1513. coreX * 8973896 +
  1514. coreZ * 7467838 +
  1515. worldSeed + 32564
  1516. )
  1517. local reefUnderhang
  1518. local reefOuterRadius = math_floor(core.type.radiusMax)
  1519. local reefInnerRadius = prng:next(core.type.radiusMax * 0.5, core.type.radiusMax * 0.7)
  1520. local reefWidth = reefOuterRadius - reefInnerRadius
  1521. local noiseOffset = 0
  1522. if isReef then
  1523. reefMaxHeight = round((core.thickness + 4) / 2)
  1524. reefUnderhang = round(reefMaxHeight / 2)
  1525. noiseOffset = -0.1
  1526. end
  1527. if isAtoll then
  1528. -- a crown attached to the island
  1529. reefOuterRadius = math_floor(core.radius * 0.8)
  1530. reefWidth = math_max(4, math_floor(core.radius * 0.15))
  1531. reefInnerRadius = reefOuterRadius - reefWidth
  1532. reefUnderhang = 0
  1533. if maxp.y < reefAltitude - reefUnderhang then return end -- no atoll here
  1534. end
  1535. local reefHalfWidth = reefWidth / 2
  1536. local reefMiddleRadius = (reefInnerRadius + reefOuterRadius) / 2
  1537. local reefOuterRadiusSquared = reefOuterRadius * reefOuterRadius
  1538. local reefInnerRadiusSquared = reefInnerRadius * reefInnerRadius
  1539. local reefMiddleRadiusSquared = reefMiddleRadius * reefMiddleRadius
  1540. local reefHalfWidthSquared = reefHalfWidth * reefHalfWidth
  1541. -- get the biome details for this core
  1542. local nodeId_first
  1543. local nodeId_second
  1544. local nodeId_top
  1545. local nodeId_filler
  1546. if core.biome == nil then setCoreBiomeData(core) end -- We can't assume the core biome has already been resolved, core might not have been big enough to enter the draw region
  1547. if core.biome.node_top == nil then nodeId_top = nodeId_stone else nodeId_top = minetest.get_content_id(core.biome.node_top) end
  1548. if core.biome.node_filler == nil then nodeId_filler = nodeId_stone else nodeId_filler = minetest.get_content_id(core.biome.node_filler) end
  1549. if core.biome.node_dust ~= nil then
  1550. nodeId_first = minetest.get_content_id(core.biome.node_dust)
  1551. nodeId_second = nodeId_top
  1552. else
  1553. nodeId_first = nodeId_top
  1554. nodeId_second = nodeId_filler
  1555. end
  1556. local zStart = round(math_max(core.z - reefOuterRadius, minp.z))
  1557. local zStop = round(math_min(core.z + reefOuterRadius, maxp.z))
  1558. local xStart = round(math_max(core.x - reefOuterRadius, minp.x))
  1559. local xStop = round(math_min(core.x + reefOuterRadius, maxp.x))
  1560. local yCenter = math_min(math_max(reefAltitude, minp.y), maxp.y)
  1561. local pos = {}
  1562. local dataBufferIndex = area:index(xStart, yCenter, zStart)
  1563. local vi = -1
  1564. for z = zStart, zStop do
  1565. local zDistSquared = (z - coreZ) * (z - coreZ)
  1566. pos.y = z
  1567. for x = xStart, xStop do
  1568. local distanceSquared = (x - coreX) * (x - coreX) + zDistSquared
  1569. if distanceSquared < reefOuterRadiusSquared and distanceSquared > reefInnerRadiusSquared then
  1570. pos.x = x
  1571. local offsetEase = math_abs(distanceSquared - reefMiddleRadiusSquared) / reefHalfWidthSquared
  1572. local fineNoise = noise_skyReef:get2d(pos)
  1573. local reefNoise = (noiseOffset* offsetEase) + fineNoise + 0.2 * noise_surfaceMap:get2d(pos)
  1574. if (reefNoise > 0) then
  1575. local distance = math_sqrt(distanceSquared)
  1576. local ease = 1 - math_abs(distance - reefMiddleRadius) / reefHalfWidth
  1577. local yStart = math_max(math_floor(reefAltitude - ease * fineNoise * reefUnderhang), minp.y)
  1578. local yStop = math_min(math_floor(reefAltitude + ease * reefNoise * reefMaxHeight), overdrawTop)
  1579. for y = yStart, yStop do
  1580. vi = dataBufferIndex + (y - yCenter) * area.ystride
  1581. if data[vi] == nodeId_air then
  1582. if y == yStop then
  1583. data[vi] = nodeId_first
  1584. elseif y == yStop - 1 then
  1585. data[vi] = nodeId_second
  1586. else
  1587. data[vi] = nodeId_filler
  1588. end
  1589. end
  1590. end
  1591. end
  1592. end
  1593. dataBufferIndex = dataBufferIndex + 1
  1594. end
  1595. dataBufferIndex = dataBufferIndex + area.zstride - (xStop - xStart + 1)
  1596. end
  1597. return vi >= 0
  1598. end
  1599. -- A rarely occuring giant tree growing from the center of the island
  1600. -- returns true if tree was added
  1601. local function addDetail_skyTree(decoration_list, core, minp, maxp)
  1602. if (core.radius < SkyTrees.minimumIslandRadius) or (core.depth < SkyTrees.minimumIslandDepth) then
  1603. --no tree here
  1604. return false
  1605. end
  1606. local coreTop = ALTITUDE + core.y
  1607. local treeAltitude = math_floor(coreTop + core.thickness)
  1608. if (maxp.y < treeAltitude - SkyTrees.maximumYOffset) or (minp.y > treeAltitude + SkyTrees.maximumHeight) then
  1609. --no tree here
  1610. return false
  1611. elseif SkyTrees.disabled ~= nil then
  1612. -- can't find nodes/textures in this game that are needed to build trees
  1613. return false
  1614. end
  1615. local coreX = core.x --save doing a table lookups
  1616. local coreZ = core.z --save doing a table lookups
  1617. local fastHash = 3
  1618. fastHash = (37 * fastHash) + coreX
  1619. fastHash = (37 * fastHash) + coreZ
  1620. fastHash = (37 * fastHash) + math_floor(core.radius)
  1621. fastHash = (37 * fastHash) + math_floor(core.depth)
  1622. fastHash = (37 * fastHash) + ISLANDS_SEED
  1623. fastHash = (37 * fastHash) + 76276 -- to keep this probability distinct from reefs and atols
  1624. if (TREE_RARITY * 1000) < math_floor((math_abs(fastHash)) % 1000) then return false end
  1625. -- choose a tree that will fit on the island
  1626. local tree
  1627. local skipLargeTree = (fastHash % 10) < 3 -- to allow small trees a chance to spawn on large islands
  1628. if skipLargeTree then
  1629. if SkyTrees.isDead({x = coreX, y = treeAltitude, z = coreZ}) then
  1630. -- small tree currently doesn't have a dead theme, so don't skip the large tree
  1631. skipLargeTree = false
  1632. end
  1633. end
  1634. for i, treeType in pairs(SkyTrees.schematicInfo) do
  1635. if i == 1 and skipLargeTree then
  1636. -- 'continue', to allow small trees a chance to spawn on large islands
  1637. elseif (core.radius >= treeType.requiredIslandRadius) and (core.depth >= treeType.requiredIslandDepth) then
  1638. tree = treeType
  1639. break
  1640. end
  1641. end
  1642. local maxOffsetFromCenter = core.radius - (tree.requiredIslandRadius - 4) -- 4 is an arbitrary number, to allow trees to get closer to the edge
  1643. -- Use a known PRNG implementation
  1644. local prng = PcgRandom(
  1645. coreX * 8973896 +
  1646. coreZ * 7467838 +
  1647. worldSeed + 43786
  1648. )
  1649. local treeAngle = 90 * prng:next(0, 3)
  1650. local treePos = {
  1651. x = coreX + math_floor((prng:next(-maxOffsetFromCenter, maxOffsetFromCenter) + prng:next(-maxOffsetFromCenter, maxOffsetFromCenter)) / 2),
  1652. y = treeAltitude,
  1653. z = coreZ + math_floor((prng:next(-maxOffsetFromCenter, maxOffsetFromCenter) + prng:next(-maxOffsetFromCenter, maxOffsetFromCenter)) / 2)
  1654. }
  1655. if minetest.global_exists("schemlib") then
  1656. -- This check is skipped when not using schemlib, because while redrawing the tree multiple times - every time a chunk it
  1657. -- touches gets emitted - might be slower, it helps work around the bugs in minetest.place_schematic() where large schematics
  1658. -- are spawned incompletely.
  1659. -- The bug in question: https://forum.minetest.net/viewtopic.php?f=6&t=22136
  1660. -- (it isn't an issue if schemlib is used)
  1661. if (maxp.y < treePos.y) or (minp.y > treePos.y) or (maxp.x < treePos.x) or (minp.x > treePos.x) or (maxp.z < treePos.z) or (minp.z > treePos.z) then
  1662. -- Now that we know the exact position of the tree, we know it's spawn point is not in this chunk.
  1663. -- In the interests of only drawing trees once, we only invoke placeTree when the chunk containing treePos is emitted.
  1664. return false
  1665. end
  1666. end
  1667. if tree.theme["Dead"] == nil then
  1668. if SkyTrees.isDead(treePos) then
  1669. -- Trees in this location should be dead, but this tree doesn't have a dead theme, so don't put a tree here
  1670. return false
  1671. end
  1672. end
  1673. if core.biome == nil then setCoreBiomeData(core) end -- We shouldn't assume the core biome has already been resolved, it might be below the emerged chunk and unrendered
  1674. if core.biome.node_top == nil then
  1675. -- solid stone isn't fertile enough for giant trees, and there's a solid stone biome in MT-Game: tundra_highland
  1676. return false
  1677. end
  1678. if DEBUG_SKYTREES then minetest.log("info", "core x: "..coreX.." y: ".. coreZ .. " treePos: " .. treePos.x .. ", y: " .. treePos.y) end
  1679. SkyTrees.placeTree(treePos, treeAngle, tree, nil, core.biome.node_top)
  1680. return true
  1681. end
  1682. ------------------------------------------------------------------------------
  1683. -- Secrets section
  1684. ------------------------------------------------------------------------------
  1685. -- We might not need this stand-in cobweb, but unless we go overboard on listing many
  1686. -- optional dependencies we won't know whether there's a proper cobweb available to
  1687. -- use until after it's too late to register this one.
  1688. local nodeName_standinCobweb = MODNAME .. ":cobweb"
  1689. minetest.register_node(
  1690. nodeName_standinCobweb,
  1691. {
  1692. tiles = {
  1693. -- [Ab]Use the crack texture to avoid needing to include a cobweb texture
  1694. -- crack_anylength.png is required by the engine, so all games will have it.
  1695. "crack_anylength.png^[verticalframe:5:4^[brighten"
  1696. },
  1697. description = S("Cobweb"),
  1698. groups = {snappy = 3, liquid = 3, flammable = 3, not_in_creative_inventory = 1},
  1699. drawtype = "plantlike",
  1700. walkable = false,
  1701. liquid_viscosity = 8,
  1702. liquidtype = "source",
  1703. liquid_alternative_flowing = nodeName_standinCobweb,
  1704. liquid_alternative_source = nodeName_standinCobweb,
  1705. liquid_renewable = false,
  1706. liquid_range = 0,
  1707. sunlight_propagates = true,
  1708. paramtype = "light"
  1709. }
  1710. )
  1711. local nodeName_egg = "secret:fossilized_egg"
  1712. local eggTextureBaseName = interop.find_node_texture({"default:jungleleaves", "mcl_core:jungleleaves", "ethereal:frost_leaves", "main:leaves"})
  1713. -- [Ab]Use a leaf texture. Originally this was to avoid needing to include an egg texture (extra files) and
  1714. -- exposing that the mod contains secrets, however both those reasons are obsolete and the mod could have textures
  1715. -- added in future
  1716. local eggTextureName = eggTextureBaseName.."^[colorize:#280040E0^[noalpha"
  1717. -- Since "secret:fossilized_egg" doesn't use this mod's name for the prefix, we can't assume
  1718. -- another mod isn't also using/providing it
  1719. if minetest.registered_nodes[nodeName_egg] == nil then
  1720. local fossilSounds = nil
  1721. local nodeName_stone = interop.find_node_name(NODENAMES_STONE)
  1722. if nodeName_stone ~= nodeName_ignore then fossilSounds = minetest.registered_nodes[nodeName_stone].sounds end
  1723. minetest.register_node(
  1724. ":"..nodeName_egg,
  1725. {
  1726. tiles = { eggTextureName },
  1727. description = S("Fossilized Egg"),
  1728. groups = {
  1729. oddly_breakable_by_hand = 3, -- MTG
  1730. handy = 1, -- MCL
  1731. stone = 1, -- Crafter needs to know the material in order to be breakable by hand
  1732. not_in_creative_inventory = 1
  1733. },
  1734. _mcl_hardness = 0.4,
  1735. sounds = fossilSounds,
  1736. drawtype = "nodebox",
  1737. paramtype = "light",
  1738. node_box = {
  1739. type = "fixed",
  1740. fixed = {
  1741. {-0.066666, -0.5, -0.066666, 0.066666, 0.5, 0.066666}, -- column1
  1742. {-0.133333, -0.476667, -0.133333, 0.133333, 0.42, 0.133333}, -- column2
  1743. {-0.2, -0.435, -0.2, 0.2, 0.31, 0.2 }, -- column3
  1744. {-0.2, -0.36, -0.28, 0.2, 0.16667, 0.28 }, -- side1
  1745. {-0.28, -0.36, -0.2, 0.28, 0.16667, 0.2 } -- side2
  1746. }
  1747. }
  1748. }
  1749. )
  1750. end
  1751. -- Allow the player to craft their egg into an egg in a display case
  1752. local nodeName_eggDisplay = nodeName_egg .. "_display"
  1753. local nodeName_frameGlass = interop.find_node_name(NODENAMES_FRAMEGLASS)
  1754. local woodTexture = interop.find_node_texture(NODENAMES_WOOD)
  1755. local frameTexture = nil
  1756. if woodTexture ~= nil then
  1757. -- perhaps it's time for cloudlands to contain textures.
  1758. frameTexture = "([combine:16x16:0,0="..woodTexture.."\\^[colorize\\:black\\:170:1,1="..woodTexture.."\\^[colorize\\:#0F0\\:255\\^[resize\\:14x14^[makealpha:0,255,0)"
  1759. --frameTexture = "([combine:16x16:0,0="..woodTexture.."\\^[resize\\:16x16\\^[colorize\\:black\\:170:1,1="..woodTexture.."\\^[colorize\\:#0F0\\:255\\^[resize\\:14x14^[makealpha:0,255,0)"
  1760. --frameTexture = "(("..woodTexture.."^[colorize:black:170)^([combine:16x16:1,1="..woodTexture.."\\^[resize\\:14x14\\^[colorize\\:#0F0\\:255))"
  1761. end
  1762. -- Since "secret:fossilized_egg_display" doesn't use this mod's name as the prefix, we shouldn't
  1763. -- assume another mod isn't also using/providing it.
  1764. if frameTexture ~= nil and nodeName_frameGlass ~= nodeName_ignore and minetest.registered_nodes[nodeName_eggDisplay] == nil then
  1765. minetest.register_node(
  1766. ":"..nodeName_eggDisplay,
  1767. {
  1768. tiles = { eggTextureName .. "^" .. frameTexture },
  1769. description = S("Fossil Display"),
  1770. groups = {
  1771. oddly_breakable_by_hand = 3,
  1772. glass = 1, -- Crafter needs to know the material in order to be breakable by hand
  1773. not_in_creative_inventory = 1},
  1774. _mcl_hardness = 0.2,
  1775. drop = "",
  1776. sounds = minetest.registered_nodes[nodeName_frameGlass].sounds,
  1777. drawtype = "nodebox",
  1778. paramtype = "light",
  1779. node_box = {
  1780. type = "fixed",
  1781. fixed = {
  1782. {-0.066666, -0.5, -0.066666, 0.066666, 0.4375, 0.066666}, -- column1
  1783. {-0.133333, -0.5, -0.133333, 0.133333, 0.375, 0.133333}, -- column2
  1784. {-0.2, -0.4375, -0.2, 0.2, 0.285, 0.2 }, -- column3
  1785. {-0.2, -0.36, -0.28, 0.2, 0.14, 0.28 }, -- side1
  1786. {-0.28, -0.36, -0.2, 0.28, 0.14, 0.2 }, -- side2
  1787. -- corner frame (courtesy of NodeBox Editor Abuse mod)
  1788. {-0.4375, 0.4375, 0.4375, 0.4375, 0.5, 0.5},
  1789. {-0.4375, -0.5, 0.4375, 0.4375, -0.4375, 0.5},
  1790. {-0.5, -0.5, 0.4375, -0.4375, 0.5, 0.5},
  1791. {0.4375, -0.5, 0.4375, 0.5, 0.5, 0.5},
  1792. {-0.5, 0.4375, -0.4375, -0.4375, 0.5, 0.4375},
  1793. {-0.5, -0.5, -0.4375, -0.4375, -0.4375, 0.4375},
  1794. {0.4375, 0.4375, -0.4375, 0.5, 0.5, 0.4375},
  1795. {0.4375, -0.5, -0.4375, 0.5, -0.4375, 0.4375},
  1796. {-0.5, 0.4375, -0.5, 0.5, 0.5, -0.4375},
  1797. {-0.5, -0.5, -0.5, 0.5, -0.4375, -0.4375},
  1798. {0.4375, -0.4375, -0.5, 0.5, 0.4375, -0.4375},
  1799. {-0.5, -0.4375, -0.5, -0.4375, 0.4375, -0.4375}
  1800. }
  1801. },
  1802. after_destruct = function(pos,node)
  1803. minetest.set_node(pos, {name = nodeName_egg, param2 = node.param2})
  1804. end,
  1805. }
  1806. )
  1807. if minetest.get_modpath("xpanes") ~= nil then
  1808. minetest.register_craft({
  1809. output = nodeName_eggDisplay,
  1810. recipe = {
  1811. {"group:stick", "group:pane", "group:stick"},
  1812. {"group:pane", nodeName_egg, "group:pane"},
  1813. {"group:stick", "group:pane", "group:stick"}
  1814. }
  1815. })
  1816. else
  1817. -- Game doesn't have glass panes, so just use glass
  1818. minetest.register_craft({
  1819. output = nodeName_eggDisplay,
  1820. recipe = {
  1821. {"group:stick", nodeName_frameGlass, "group:stick"},
  1822. {nodeName_frameGlass, nodeName_egg, nodeName_frameGlass},
  1823. {"group:stick", nodeName_frameGlass, "group:stick"}
  1824. }
  1825. })
  1826. end
  1827. end
  1828. local nodeId_egg = minetest.get_content_id(nodeName_egg)
  1829. local nodeId_airStandIn = minetest.get_content_id(interop.register_clone("air"))
  1830. -- defer assigning the following until all mods are loaded
  1831. local nodeId_bed_top
  1832. local nodeId_bed_bottom
  1833. local nodeId_torch
  1834. local nodeId_chest
  1835. local nodeId_bookshelf
  1836. local nodeId_junk
  1837. local nodeId_anvil
  1838. local nodeId_workbench
  1839. local nodeId_cobweb
  1840. local nodeName_bookshelf
  1841. local isMineCloneBookshelf
  1842. local function addDetail_secrets(decoration_list, core, data, area, minp, maxp)
  1843. -- if core.biome is nil then renderCores() never rendered it, which means it
  1844. -- doesn't instersect this draw region.
  1845. if core.biome ~= nil and core.radius > 18 and core.depth > 20 and core.radius + core.depth > 60 then
  1846. local territoryX = math_floor(core.x / core.type.territorySize)
  1847. local territoryZ = math_floor(core.z / core.type.territorySize)
  1848. local isPolarOutpost = (core.temperature <= 5) and (core.x % 3 == 0) and noise_surfaceMap:get2d({x = core.x, y = core.z - 8}) >= 0 --make sure steps aren't under a pond
  1849. local isAncientBurrow = core.humidity >= 60 and core.temperature >= 50
  1850. -- only allow a checkerboard pattern of territories to help keep the secrets
  1851. -- spread out, rather than bunching up too much with climate
  1852. if ((territoryX + territoryZ) % 2 == 0) and (isPolarOutpost or isAncientBurrow) then
  1853. local burrowRadius = 7
  1854. local burrowHeight = 5
  1855. local burrowDepth = 12
  1856. local burrowFloor = ALTITUDE + core.y - burrowDepth
  1857. local radiusSquared = burrowRadius * burrowRadius
  1858. local function carve(originp, destp, pattern, height, floorId, floorDistance)
  1859. local direction = vector.direction(originp, destp)
  1860. local vineSearchDirection = {}
  1861. if direction.x > 0 then vineSearchDirection.x = -1 else vineSearchDirection.x = 1 end
  1862. if direction.z > 0 then vineSearchDirection.z = -1 else vineSearchDirection.z = 1 end
  1863. local vinePlacements = {}
  1864. local function placeVine(vi, pos, only_place_on_nodeId)
  1865. if data[vi] == nodeId_air then
  1866. local faces = {}
  1867. local facing
  1868. local function vineCanGrowOnIt(node_id)
  1869. return node_id ~= nodeId_air and node_id ~= nodeId_airStandIn and (node_id == only_place_on_nodeId or only_place_on_nodeId == nil)
  1870. end
  1871. if vineCanGrowOnIt(data[vi + vineSearchDirection.x]) and pos.x + vineSearchDirection.x >= minp.x and pos.x + vineSearchDirection.x <= maxp.x then
  1872. if vineSearchDirection.x > 0 then facing = 2 else facing = 3 end
  1873. faces[#faces + 1] = {solid_vi = vi + vineSearchDirection.x, facing = facing}
  1874. end
  1875. if vineCanGrowOnIt(data[vi + vineSearchDirection.z * area.zstride]) and pos.z + vineSearchDirection.z >= minp.z and pos.z + vineSearchDirection.z <= maxp.z then
  1876. if vineSearchDirection.z > 0 then facing = 4 else facing = 5 end
  1877. faces[#faces + 1] = {solid_vi = vi + vineSearchDirection.z * area.zstride, facing = facing}
  1878. end
  1879. local faceInfo = nil
  1880. if #faces == 1 then
  1881. faceInfo = faces[1]
  1882. elseif #faces == 2 then
  1883. local ratio = math.abs(direction.x) / (math.abs(direction.x) + math.abs(direction.z))
  1884. if randomNumbers[(pos.x + pos.y + pos.z) % 256] <= ratio then faceInfo = faces[1] else faceInfo = faces[2] end
  1885. end
  1886. if faceInfo ~= nil
  1887. and (only_place_on_nodeId == nil or only_place_on_nodeId == data[faceInfo.solid_vi])
  1888. and (data[faceInfo.solid_vi] ~= nodeId_airStandIn) then
  1889. -- find the highest y value (or maxp.y) where solid_vi is solid
  1890. -- and vi is not
  1891. local solid_vi = faceInfo.solid_vi
  1892. local yOffset = 1
  1893. while (pos.y + yOffset <= maxp.y + 1)
  1894. and (data[solid_vi + yOffset * area.ystride] ~= nodeId_air)
  1895. and (data[vi + yOffset * area.ystride] == nodeId_air)
  1896. and (only_place_on_nodeId == nil or only_place_on_nodeId == data[solid_vi + yOffset * area.ystride]) do
  1897. yOffset = yOffset + 1
  1898. end
  1899. -- defer final vine placement until all nodes have been carved
  1900. vinePlacements[#vinePlacements + 1] = function(decoration_list)
  1901. -- retest that the vine is still going in air and still attached to a solid node
  1902. local solidNode = data[solid_vi + (yOffset - 1) * area.ystride]
  1903. if solidNode ~= nodeId_airStandIn and solidNode ~= nodeId_air and data[vi] == nodeId_air then
  1904. decoration_list[#decoration_list + 1] = {pos={x=pos.x, y=pos.y + yOffset - 1, z=pos.z}, node={name = nodeName_vine, param2 = faceInfo.facing}}
  1905. end
  1906. end
  1907. end
  1908. end
  1909. end
  1910. local stampedIndexes = {}
  1911. local function stamp(pos, pattern, height, node_id, isAir_callback)
  1912. local callbackClosures = {}
  1913. local index = -1
  1914. for y = pos.y, pos.y + height - 1 do
  1915. if y >= minp.y and y <= maxp.y then
  1916. if index == -1 then index = area:index(pos.x, y, pos.z) else index = index + area.ystride end
  1917. for _,voxel in ipairs(pattern) do
  1918. local x = pos.x + voxel.x
  1919. local z = pos.z + voxel.z
  1920. if x >= minp.x and x <= maxp.x and z >= minp.z and z <= maxp.z then
  1921. local vi = index + voxel.x + voxel.z * area.zstride
  1922. if data[vi] == nodeId_air then
  1923. if isAir_callback ~= nil then
  1924. callbackClosures[#callbackClosures + 1] = function() isAir_callback(pos, vi, x, y, z) end
  1925. end
  1926. else
  1927. data[vi] = node_id
  1928. stampedIndexes[#stampedIndexes + 1] = vi
  1929. end
  1930. end
  1931. end
  1932. end
  1933. end
  1934. for _,callback in ipairs(callbackClosures) do callback() end
  1935. end
  1936. local function excavate(pos, add_floor, add_vines, add_cobwebs)
  1937. local function onAirNode(stampPos, node_vi, node_x, node_y, node_z)
  1938. if node_y > stampPos.y and node_y + 1 <= maxp.y then
  1939. -- place vines above the entrance, for concealment
  1940. placeVine(node_vi + area.ystride, {x=node_x, y=node_y + 1, z=node_z})
  1941. else
  1942. -- place vines on the floor - perhaps explorers can climb to the burrow
  1943. placeVine(node_vi, {x=node_x, y=node_y, z=node_z}, floorId)
  1944. end
  1945. end
  1946. local onAirNodeCallback = onAirNode
  1947. local fill = nodeId_airStandIn
  1948. if not add_vines or nodeId_vine == nodeId_ignore then onAirNodeCallback = nil end
  1949. if add_cobwebs and nodeId_cobweb ~= nodeId_ignore then fill = nodeId_cobweb end
  1950. stamp(pos, pattern, height, fill, onAirNodeCallback)
  1951. if add_floor and floorId ~= nil then
  1952. stamp({x=pos.x, y=pos.y - 1, z=pos.z}, pattern, 1, floorId, onAirNodeCallback)
  1953. end
  1954. end
  1955. local addVines = core.humidity >= VINES_REQUIRED_HUMIDITY and core.temperature >= VINES_REQUIRED_TEMPERATURE
  1956. if floorDistance == nil then floorDistance = 0 end
  1957. local distance = round(vector.distance(originp, destp))
  1958. local step = vector.divide(vector.subtract(destp, originp), distance)
  1959. local pos = vector.new(originp)
  1960. local newPos = vector.new(originp)
  1961. excavate(originp, 0 >= floorDistance, false)
  1962. for i = 1, distance do
  1963. newPos.x = newPos.x + step.x
  1964. if round(newPos.x) ~= pos.x then
  1965. pos.x = round(newPos.x)
  1966. excavate(pos, i >= floorDistance, addVines, i <= floorDistance - 1 and i >= floorDistance - 2)
  1967. end
  1968. newPos.y = newPos.y + step.y
  1969. if round(newPos.y) ~= pos.y then
  1970. pos.y = round(newPos.y)
  1971. excavate(pos, i >= floorDistance, addVines, i <= floorDistance - 1 and i >= floorDistance - 2)
  1972. end
  1973. newPos.z = newPos.z + step.z
  1974. if round(newPos.z) ~= pos.z then
  1975. pos.z = round(newPos.z)
  1976. excavate(pos, i >= floorDistance, addVines, i <= floorDistance - 1 and i >= floorDistance - 2)
  1977. end
  1978. end
  1979. -- We only place vines after entire burrow entrance has been carved, to avoid placing
  1980. -- vines on blocks which will later be removed.
  1981. for _,vineFunction in ipairs(vinePlacements) do vineFunction(decoration_list) end
  1982. -- Replace airStandIn with real air.
  1983. -- This two-pass process was neccessary because the vine placing algorithm used
  1984. -- the presense of air to determine if a rock was facing outside and should have a vine.
  1985. -- Single-pass solutions result in vines inside the tunnel (where I'd rather overgrowth spawned)
  1986. for _,stampedIndex in ipairs(stampedIndexes) do
  1987. if data[stampedIndex] == nodeId_airStandIn then
  1988. data[stampedIndex] = nodeId_air
  1989. end
  1990. end
  1991. end
  1992. local function placeNode(x, y, z, node_id)
  1993. if (x >= minp.x and x <= maxp.x and z >= minp.z and z <= maxp.z and y >= minp.y and y <= maxp.y) then
  1994. data[area:index(x, y, z)] = node_id
  1995. end
  1996. end
  1997. local function posInBounds(pos)
  1998. return pos.x >= minp.x and pos.x <= maxp.x and pos.z >= minp.z and pos.z <= maxp.z and pos.y >= minp.y and pos.y <= maxp.y
  1999. end
  2000. local zStart = math_max(core.z - burrowRadius, minp.z)
  2001. local xStart = math_max(core.x - burrowRadius, minp.x)
  2002. local xStop = math_min(core.x + burrowRadius, maxp.x)
  2003. local yStart = math_max(burrowFloor, minp.y)
  2004. -- dig burrow
  2005. local dataBufferIndex = area:index(xStart, yStart, zStart)
  2006. for z = zStart, math_min(core.z + burrowRadius, maxp.z) do
  2007. for x = xStart, xStop do
  2008. local distanceSquared = (x - core.x)*(x - core.x) + (z - core.z)*(z - core.z)
  2009. if distanceSquared < radiusSquared then
  2010. local horz_easing = 1 - distanceSquared / radiusSquared
  2011. for y = math_max(minp.y, burrowFloor + math_floor(1.4 - horz_easing)), math_min(maxp.y, burrowFloor + 1 + math_min(burrowHeight - 1, math_floor(0.8 + burrowHeight * horz_easing))) do
  2012. data[dataBufferIndex + (y - yStart) * area.ystride] = nodeId_air
  2013. end
  2014. end
  2015. dataBufferIndex = dataBufferIndex + 1
  2016. end
  2017. dataBufferIndex = dataBufferIndex + area.zstride - (xStop - xStart + 1)
  2018. end
  2019. local floorId
  2020. if core.biome.node_top == nil then floorId = nil else floorId = minetest.get_content_id(core.biome.node_top) end
  2021. if isAncientBurrow then
  2022. -- island overlaps can only happen at territory edges when a coreType has exclusive=true, so
  2023. -- angle the burrow entrance toward the center of the terrority to avoid any overlapping islands.
  2024. local territoryCenter = vector.new(
  2025. core.type.territorySize * math.floor(core.x / core.type.territorySize) + math.floor(0.5 + core.type.territorySize / 2),
  2026. burrowFloor,
  2027. core.type.territorySize * math.floor(core.z / core.type.territorySize) + math.floor(0.5 + core.type.territorySize / 2)
  2028. )
  2029. local burrowStart = vector.new(core.x, burrowFloor, core.z)
  2030. local direction = vector.direction(burrowStart, territoryCenter)
  2031. local directionOffsetZ = 4
  2032. if direction.z < 0 then directionOffsetZ = -directionOffsetZ end
  2033. burrowStart.z = burrowStart.z + directionOffsetZ -- start the burrow enterance off-center
  2034. burrowStart.x = burrowStart.x + 2 -- start the burrow enterance off-center
  2035. direction = vector.direction(burrowStart, territoryCenter)
  2036. if vector.length(direction) == 0 then direction = vector.direction({x=0, y=0, z=0}, {x=2, y=0, z=1}) end
  2037. local path = vector.add(vector.multiply(direction, core.radius), {x=0, y=-4,z=0})
  2038. local floorStartingFrom = 4 + math.floor(0.5 + core.radius * 0.3)
  2039. -- carve burrow entrance
  2040. local pattern = {{x=0,z=0}, {x=-1,z=0}, {x=1,z=0}, {x=0,z=-1}, {x=0,z=1}}
  2041. carve(burrowStart, vector.add(burrowStart, path), pattern, 2, floorId, floorStartingFrom)
  2042. -- place egg in burrow
  2043. local eggX = core.x
  2044. local eggZ = core.z - directionOffsetZ * 0.75 -- move the egg away from where the burrow entrance is carved
  2045. placeNode(eggX, burrowFloor, eggZ, nodeId_egg)
  2046. if nodeId_gravel ~= nodeId_ignore then placeNode(eggX, burrowFloor - 1, eggZ, nodeId_gravel) end
  2047. if nodeId_cobweb ~= nodeId_ignore then
  2048. placeNode(core.x - 6, burrowFloor + 3, core.z - 1, nodeId_cobweb)
  2049. placeNode(core.x + 4, burrowFloor + 4, core.z + 3, nodeId_cobweb)
  2050. placeNode(core.x + 6, burrowFloor + 1, core.z - 3, nodeId_cobweb)
  2051. end
  2052. else
  2053. -- Only attempt this if it can contain beds and a place to store the diary.
  2054. if (nodeId_bookshelf ~= nodeId_ignore or nodeId_chest ~= nodeId_ignore) and nodeId_bed_top ~= nodeId_ignore and nodeId_bed_bottom ~= nodeId_ignore then
  2055. -- carve stairs to the surface
  2056. local stairsStart = vector.new(core.x - 3, burrowFloor, core.z - 7)
  2057. local stairsbottom = vector.add(stairsStart, {x=0,y=0,z=1})
  2058. local stairsMiddle1 = vector.add(stairsStart, {x=8,y=8,z=0})
  2059. local stairsMiddle2 = vector.add(stairsMiddle1, {x=0,y=0,z=-1})
  2060. local stairsEnd = vector.add(stairsMiddle2, {x=-20,y=20,z=0})
  2061. carve(stairsEnd, stairsMiddle2, {{x=0,z=0}}, 3, floorId, 0)
  2062. carve(stairsMiddle1, stairsStart, {{x=0,z=0}}, 2, floorId, 0)
  2063. local pattern = {{x=0,z=0}, {x=1,z=0}, {x=0,z=2}, {x=0,z=1}, {x=1,z=1}}
  2064. carve(stairsbottom, stairsbottom, pattern, 2, floorId, 0)
  2065. -- fill the outpost
  2066. placeNode(core.x + 2, burrowFloor, core.z + 5, nodeId_bed_top)
  2067. placeNode(core.x + 2, burrowFloor, core.z + 4, nodeId_bed_bottom)
  2068. placeNode(core.x + 2, burrowFloor, core.z + 2, nodeId_bed_top)
  2069. placeNode(core.x + 2, burrowFloor, core.z + 1, nodeId_bed_bottom)
  2070. placeNode(core.x + 4, burrowFloor, core.z + 2, nodeId_bed_top)
  2071. placeNode(core.x + 4, burrowFloor, core.z + 1, nodeId_bed_bottom)
  2072. if (nodeId_torch ~= nodeId_ignore) then
  2073. decoration_list[#decoration_list + 1] = {
  2074. pos={x=core.x, y=burrowFloor + 2, z=core.z + 6},
  2075. node={name = minetest.get_name_from_content_id(nodeId_torch), param2 = 4}
  2076. }
  2077. end
  2078. if nodeId_junk ~= nodeId_ignore then placeNode(core.x - 4, burrowFloor + 1, core.z + 5, nodeId_junk) end
  2079. if nodeId_anvil ~= nodeId_ignore then placeNode(core.x - 6, burrowFloor + 1, core.z, nodeId_anvil) end
  2080. if nodeId_workbench ~= nodeId_ignore then placeNode(core.x - 5, burrowFloor, core.z + 2, nodeId_workbench) end
  2081. if nodeId_cobweb ~= nodeId_ignore then placeNode(core.x + 4, burrowFloor + 4, core.z - 3, nodeId_cobweb) end
  2082. local bookshelf_pos
  2083. local invBookshelf = nil
  2084. local invChest = nil
  2085. if nodeId_chest ~= nodeId_ignore then
  2086. local pos = {x = core.x - 3, y = burrowFloor + 1, z = core.z + 6}
  2087. local nodeName_chest = minetest.get_name_from_content_id(nodeId_chest)
  2088. local nodeNameAtPos = minetest.get_node(pos).name
  2089. -- falls back on the nodeNameAtPos:find("chest") check to avoid a race-condition where if the
  2090. -- chest is opened while nearby areas are being generated, the opened chest may be replaced with
  2091. -- a new empty closed one.
  2092. if nodeNameAtPos ~= nodeName_chest and not nodeNameAtPos:find("chest") then minetest.set_node(pos, {name = nodeName_chest}) end
  2093. if posInBounds(pos) then
  2094. data[area:index(pos.x, pos.y, pos.z)] = nodeId_chest
  2095. invChest = minetest.get_inventory({type = "node", pos = pos})
  2096. end
  2097. end
  2098. if nodeId_bookshelf ~= nodeId_ignore then
  2099. local pos = {x = core.x - 2, y = burrowFloor + 1, z = core.z + 6}
  2100. bookshelf_pos = pos
  2101. if minetest.get_node(pos).name ~= nodeName_bookshelf then minetest.set_node(pos, {name = nodeName_bookshelf}) end
  2102. if posInBounds(pos) then
  2103. data[area:index(pos.x, pos.y, pos.z)] = nodeId_bookshelf
  2104. if not isMineCloneBookshelf then -- mineclone bookshelves are decorational (like Minecraft) and don't contain anything
  2105. invBookshelf = minetest.get_inventory({type = "node", pos = pos})
  2106. end
  2107. end
  2108. end
  2109. if invBookshelf ~= nil or invChest ~= nil then
  2110. -- create diary
  2111. local groundDesc = S("rock")
  2112. if core.biome.node_filler ~= nil then
  2113. local earthNames = string.lower(core.biome.node_filler) .. string.lower(core.biome.node_top)
  2114. if string.match(earthNames, "ice") or string.match(earthNames, "snow") or string.match(earthNames, "frozen") then
  2115. groundDesc = S("ice")
  2116. end
  2117. end
  2118. local book_itemstack = interop.write_book(
  2119. S("Weddell Outpost, November 21"), -- title
  2120. S("Bert Shackleton"), -- owner/author
  2121. S([[The aerostat is lost.
  2122. However, salvage attempts throughout the night managed to
  2123. save most provisions before it finally broke apart and fell.
  2124. ---====---
  2125. This island is highly exposed and the weather did not treat
  2126. the tents well. We have enlarged a sheltered crag in the @1,
  2127. but it is laborous work and the condition of some of the party
  2128. is becoming cause for concern.
  2129. Quite a journey is now required, we cannot stay - nobody will
  2130. look for us here. McNish is attempting to strengthen the gliders.
  2131. ---====---]], groundDesc),
  2132. S("Diary of Bert Shackleton") -- description
  2133. )
  2134. if book_itemstack ~= nil then
  2135. if invBookshelf == nil then
  2136. -- mineclone bookshelves are decorational like Minecraft, put the book in the chest instead
  2137. -- (also testing for nil invBookshelf because it can happen. Weird race condition??)
  2138. if invChest ~= nil then invChest:add_item("main", book_itemstack) end
  2139. else
  2140. -- add the book to the bookshelf and manually trigger update_bookshelf() so its
  2141. -- name will reflect the new contents.
  2142. invBookshelf:add_item("books", book_itemstack)
  2143. local dummyPlayer = {}
  2144. dummyPlayer.get_player_name = function() return "server" end
  2145. minetest.registered_nodes[nodeName_bookshelf].on_metadata_inventory_put(bookshelf_pos, "books", 1, book_itemstack, dummyPlayer)
  2146. end
  2147. end
  2148. end
  2149. if invChest ~= nil then
  2150. -- leave some junk from the expedition in the chest
  2151. local stack
  2152. local function addIfFound(item_aliases, amount)
  2153. for _,name in ipairs(item_aliases) do
  2154. if minetest.registered_items[name] ~= nil then
  2155. stack = ItemStack(name .. " " .. amount)
  2156. invChest:add_item("main", stack)
  2157. break
  2158. end
  2159. end
  2160. end
  2161. addIfFound({"mcl_tools:pick_iron", "default:pick_steel", "main:ironpick"}, 1)
  2162. addIfFound({"binoculars:binoculars"}, 1)
  2163. addIfFound(NODENAMES_WOOD, 10)
  2164. addIfFound({"mcl_torches:torch", "default:torch", "torch:torch"}, 3)
  2165. end
  2166. end
  2167. end
  2168. end
  2169. end
  2170. end
  2171. local function init_secrets()
  2172. nodeId_bed_top = interop.find_node_id({"beds:bed_top", "bed:bed_front"})
  2173. nodeId_bed_bottom = interop.find_node_id({"beds:bed_bottom", "bed:bed_back"})
  2174. nodeId_torch = interop.find_node_id({"mcl_torches:torch_wall", "default:torch_wall", "torch:wall"})
  2175. nodeId_chest = interop.find_node_id({"chest", "mcl_chests:chest", "default:chest", "utility:chest"})
  2176. nodeId_junk = interop.find_node_id({"xdecor:barrel", "cottages:barrel", "homedecor:copper_pans", "vessels:steel_bottle", "mcl_flowerpots:flower_pot"})
  2177. nodeId_anvil = interop.find_node_id({"castle:anvil", "cottages:anvil", "mcl_anvils:anvil", "default:anvil", "main:anvil" }) -- "default:anvil" and "main:anvil" aren't a thing, but perhaps one day.
  2178. nodeId_workbench = interop.find_node_id({"homedecor:table", "xdecor:workbench", "mcl_crafting_table:crafting_table", "default:table", "random_buildings:bench", "craftingtable:craftingtable"}) -- "default:table" isn't a thing, but perhaps one day.
  2179. nodeId_cobweb = interop.find_node_id({"mcl_core:cobweb", "xdecor:cobweb", "homedecor:cobweb_plantlike", "default:cobweb", "main:cobweb"})
  2180. local mineCloneBookshelfName = "mcl_books:bookshelf"
  2181. nodeId_bookshelf = interop.find_node_id({mineCloneBookshelfName, "default:bookshelf"})
  2182. nodeName_bookshelf = minetest.get_name_from_content_id(nodeId_bookshelf)
  2183. isMineCloneBookshelf = nodeName_bookshelf == mineCloneBookshelfName
  2184. if nodeId_cobweb ~= nodeId_ignore then
  2185. -- This game has proper cobwebs, replace any cobwebs this mod may have generated
  2186. -- previously (when a cobweb mod wasn't included) with the proper cobwebs.
  2187. minetest.register_alias(nodeName_standinCobweb, minetest.get_name_from_content_id(nodeId_cobweb))
  2188. elseif minetest.registered_nodes[nodeName_standinCobweb] ~= nil then
  2189. -- use a stand-in cobweb created by this mod
  2190. nodeId_cobweb = minetest.get_content_id(nodeName_standinCobweb)
  2191. end
  2192. end
  2193. ------------------------------------------------------------------------------
  2194. -- End of secrets section
  2195. ------------------------------------------------------------------------------
  2196. local function renderCores(cores, minp, maxp, blockseed)
  2197. local voxelsWereManipulated = false
  2198. local vm, emerge_min, emerge_max = minetest.get_mapgen_object("voxelmanip")
  2199. vm:get_data(data) -- put all nodes except the ground surface in this array
  2200. local area = VoxelArea:new{MinEdge=emerge_min, MaxEdge=emerge_max}
  2201. local overdrawTop = maxp.y + OVERDRAW
  2202. local currentBiomeId = -1
  2203. local nodeId_dust
  2204. local nodeId_top
  2205. local nodeId_filler
  2206. local nodeId_stoneBase
  2207. local nodeId_pondBottom
  2208. local depth_top
  2209. local depth_filler
  2210. local fillerFallsWithGravity
  2211. local floodableDepth
  2212. for z = minp.z, maxp.z do
  2213. local dataBufferIndex = area:index(minp.x, minp.y, z)
  2214. for x = minp.x, maxp.x do
  2215. for _,core in pairs(cores) do
  2216. local coreTop = ALTITUDE + core.y
  2217. local distanceSquared = (x - core.x)*(x - core.x) + (z - core.z)*(z - core.z)
  2218. local radius = core.radius
  2219. local radiusSquared = radius * radius
  2220. if distanceSquared <= radiusSquared then
  2221. -- get the biome details for this core
  2222. if core.biome == nil then setCoreBiomeData(core) end
  2223. if currentBiomeId ~= core.biomeId then
  2224. if core.biome.node_top == nil then nodeId_top = nodeId_stone else nodeId_top = minetest.get_content_id(core.biome.node_top) end
  2225. if core.biome.node_filler == nil then nodeId_filler = nodeId_stone else nodeId_filler = minetest.get_content_id(core.biome.node_filler) end
  2226. if core.biome.node_stone == nil then nodeId_stoneBase = nodeId_stone else nodeId_stoneBase = minetest.get_content_id(core.biome.node_stone) end
  2227. if core.biome.node_dust == nil then nodeId_dust = nodeId_ignore else nodeId_dust = minetest.get_content_id(core.biome.node_dust) end
  2228. if core.biome.node_riverbed == nil then nodeId_pondBottom = nodeId_silt else nodeId_pondBottom = minetest.get_content_id(core.biome.node_riverbed) end
  2229. if core.biome.depth_top == nil then depth_top = 1 else depth_top = core.biome.depth_top end
  2230. if core.biome.depth_filler == nil then depth_filler = 3 else depth_filler = core.biome.depth_filler end
  2231. fillerFallsWithGravity = core.biome.node_filler ~= nil and minetest.registered_items[core.biome.node_filler].groups.falling_node == 1
  2232. --[[Commented out as unnecessary, as a supporting node will be added, but uncommenting
  2233. this will make the strata transition less noisey.
  2234. if fillerFallsWithGravity then
  2235. -- the filler node is affected by gravity and can fall if unsupported, so keep that layer thinner than
  2236. -- core.thickness when possible.
  2237. --depth_filler = math_min(depth_filler, math_max(1, core.thickness - 1))
  2238. end--]]
  2239. floodableDepth = 0
  2240. if nodeId_top ~= nodeId_stone and minetest.registered_items[core.biome.node_top].floodable then
  2241. -- nodeId_top is a node that water floods through, so we can't have ponds appearing at this depth
  2242. floodableDepth = depth_top
  2243. end
  2244. currentBiomeId = core.biomeId
  2245. end
  2246. -- decide on a shape
  2247. local horz_easing
  2248. local noise_weighting = 1
  2249. local shapeType = math_floor(core.depth + radius + core.x) % 5
  2250. if shapeType < 2 then
  2251. -- convex
  2252. -- squared easing function, e = 1 - x²
  2253. horz_easing = 1 - distanceSquared / radiusSquared
  2254. elseif shapeType == 2 then
  2255. -- conical
  2256. -- linear easing function, e = 1 - x
  2257. horz_easing = 1 - math_sqrt(distanceSquared) / radius
  2258. else
  2259. -- concave
  2260. -- root easing function blended/scaled with square easing function,
  2261. -- x = normalised distance from center of core
  2262. -- a = 1 - x²
  2263. -- b = 1 - √x
  2264. -- e = 0.8*a*x + 1.2*b*(1 - x)
  2265. local radiusRoot = core.radiusRoot
  2266. if radiusRoot == nil then
  2267. radiusRoot = math_sqrt(radius)
  2268. core.radiusRoot = radiusRoot
  2269. end
  2270. local squared = 1 - distanceSquared / radiusSquared
  2271. local distance = math_sqrt(distanceSquared)
  2272. local distance_normalized = distance / radius
  2273. local root = 1 - math_sqrt(distance) / radiusRoot
  2274. horz_easing = math_min(1, 0.8*distance_normalized*squared + 1.2*(1-distance_normalized)*root)
  2275. -- this seems to be a more delicate shape that gets wiped out by the
  2276. -- density noise, so lower that
  2277. noise_weighting = 0.63
  2278. end
  2279. if radius + core.depth > 80 then
  2280. -- larger islands shapes have a slower easing transition, which leaves large areas
  2281. -- dominated by the density noise, so reduce the density noise when the island is large.
  2282. -- (the numbers here are arbitrary)
  2283. if radius + core.depth > 120 then
  2284. noise_weighting = 0.35
  2285. else
  2286. noise_weighting = math_min(0.6, noise_weighting)
  2287. end
  2288. end
  2289. local surfaceNoise = noise_surfaceMap:get2d({x = x, y = z})
  2290. if DEBUG_GEOMETRIC then surfaceNoise = SURFACEMAP_OFFSET end
  2291. local surface = round(surfaceNoise * 3 * (core.thickness + 1) * horz_easing) -- if you change this formular then update maxSufaceRise in on_generated()
  2292. local coreBottom = math_floor(coreTop - (core.thickness + core.depth))
  2293. local noisyDepthOfFiller = depth_filler
  2294. if noisyDepthOfFiller >= 3 then noisyDepthOfFiller = noisyDepthOfFiller + math_floor(randomNumbers[(x + z) % 256] * 3) - 1 end
  2295. local yBottom = math_max(minp.y, coreBottom - 4) -- the -4 is for rare instances when density noise pushes the bottom of the island deeper
  2296. local yBottomIndex = dataBufferIndex + area.ystride * (yBottom - minp.y) -- equivalent to yBottomIndex = area:index(x, yBottom, z)
  2297. local topBlockIndex = -1
  2298. local bottomBlockIndex = -1
  2299. local vi = yBottomIndex
  2300. local densityNoise = nil
  2301. for y = yBottom, math_min(overdrawTop, coreTop + surface) do
  2302. local vert_easing = math_min(1, (y - coreBottom) / core.depth)
  2303. -- If you change the densityNoise calculation, remember to similarly update the copy of this calculation in the pond code
  2304. densityNoise = noise_density:get3d({x = x, y = y - coreTop, z = z}) -- TODO: Optimize this!!
  2305. densityNoise = noise_weighting * densityNoise + (1 - noise_weighting) * DENSITY_OFFSET
  2306. if DEBUG_GEOMETRIC then densityNoise = DENSITY_OFFSET end
  2307. if densityNoise * ((horz_easing + vert_easing) / 2) >= REQUIRED_DENSITY then
  2308. if vi > topBlockIndex then topBlockIndex = vi end
  2309. if bottomBlockIndex < 0 and y > minp.y then bottomBlockIndex = vi end -- if y==minp.y then we don't know for sure this is the lowest block
  2310. if y > coreTop + surface - depth_top and data[vi] == nodeId_air then
  2311. data[vi] = nodeId_top
  2312. elseif y >= coreTop + surface - (depth_top + noisyDepthOfFiller) then
  2313. data[vi] = nodeId_filler
  2314. else
  2315. data[vi] = nodeId_stoneBase
  2316. end
  2317. end
  2318. vi = vi + area.ystride
  2319. end
  2320. -- ensure nodeId_top blocks also cover the rounded sides of islands (which may be lower
  2321. -- than the flat top), then dust the top surface.
  2322. if topBlockIndex >= 0 then
  2323. voxelsWereManipulated = true
  2324. -- we either have the highest block, or overdrawTop - but we don't want to set overdrawTop nodes to nodeId_top
  2325. -- (we will err on the side of caution when we can't distinguish the top of a island's side from overdrawTop)
  2326. if overdrawTop >= coreTop + surface or vi > topBlockIndex + area.ystride then
  2327. if topBlockIndex > yBottomIndex and data[topBlockIndex - area.ystride] ~= nodeId_air and data[topBlockIndex + area.ystride] == nodeId_air then
  2328. -- We only set a block to nodeId_top if there's a block under it "holding it up" as
  2329. -- it's better to leave 1-deep noise as stone/whatever.
  2330. data[topBlockIndex] = nodeId_top
  2331. end
  2332. if nodeId_dust ~= nodeId_ignore and data[topBlockIndex + area.ystride] == nodeId_air then
  2333. -- Delay writing dust to the data buffer until after decoration so avoid preventing tree growth etc
  2334. if core.dustLocations == nil then core.dustLocations = {} end
  2335. core.dustLocations[#core.dustLocations + 1] = topBlockIndex + area.ystride
  2336. end
  2337. end
  2338. if fillerFallsWithGravity and bottomBlockIndex >= 0 and data[bottomBlockIndex] == nodeId_filler then
  2339. -- the bottom node is affected by gravity and can fall if unsupported, put some support in
  2340. data[bottomBlockIndex] = nodeId_stoneBase
  2341. end
  2342. end
  2343. -- add ponds of water, trying to make sure they're not on an edge.
  2344. -- (the only time a pond needs to be rendered when densityNoise is nil (i.e. when there was no land at this x, z),
  2345. -- is when the pond is at minp.y - i.e. the reason no land was rendered is it was below minp.y)
  2346. if surfaceNoise < 0 and (densityNoise ~= nil or (coreTop + surface < minp.y and coreTop >= minp.y)) and nodeId_water ~= nodeId_ignore then
  2347. local pondWallBuffer = core.type.pondWallBuffer
  2348. local pondBottom = nodeId_filler
  2349. local pondWater = nodeId_water
  2350. if radius > 18 and core.depth > 15 and nodeId_pondBottom ~= nodeId_ignore then
  2351. -- only give ponds a sandbed when islands are large enough for it not to stick out the side or bottom
  2352. pondBottom = nodeId_pondBottom
  2353. end
  2354. if core.temperature <= ICE_REQUIRED_TEMPERATURE and nodeId_ice ~= nodeId_ignore then pondWater = nodeId_ice end
  2355. if densityNoise == nil then
  2356. -- Rare edge case. If the pond is at minp.y, then no land has been rendered, so
  2357. -- densityNoise hasn't been calculated. Calculate it now.
  2358. densityNoise = noise_density:get3d({x = x, y = minp.y, z = z})
  2359. densityNoise = noise_weighting * densityNoise + (1 - noise_weighting) * DENSITY_OFFSET
  2360. if DEBUG_GEOMETRIC then densityNoise = DENSITY_OFFSET end
  2361. end
  2362. local surfaceDensity = densityNoise * ((horz_easing + 1) / 2)
  2363. local onTheEdge = math_sqrt(distanceSquared) + 1 >= radius
  2364. for y = math_max(minp.y, coreTop + surface), math_min(overdrawTop, coreTop - floodableDepth) do
  2365. if surfaceDensity > REQUIRED_DENSITY then
  2366. vi = dataBufferIndex + area.ystride * (y - minp.y) -- this is the same as vi = area:index(x, y, z)
  2367. if surfaceDensity > (REQUIRED_DENSITY + pondWallBuffer) and not onTheEdge then
  2368. data[vi] = pondWater
  2369. if y > minp.y then data[vi - area.ystride] = pondBottom end
  2370. --remove any dust above ponds
  2371. if core.dustLocations ~= nil and core.dustLocations[#core.dustLocations] == vi + area.ystride then core.dustLocations[#core.dustLocations] = nil end
  2372. else
  2373. -- make sure there are some walls to keep the water in
  2374. if y == coreTop then
  2375. data[vi] = nodeId_top -- to let isIsland() know not to put vines here (only seems to be an issue when pond is 2 deep or more)
  2376. else
  2377. data[vi] = nodeId_filler
  2378. end
  2379. end
  2380. end
  2381. end
  2382. end
  2383. end
  2384. end
  2385. dataBufferIndex = dataBufferIndex + 1
  2386. end
  2387. end
  2388. local decorations = {}
  2389. for _,core in ipairs(cores) do
  2390. addDetail_vines(decorations, core, data, area, minp, maxp)
  2391. voxelsWereManipulated = addDetail_skyReef(decorations, core, data, area, minp, maxp) or voxelsWereManipulated
  2392. addDetail_secrets(decorations, core, data, area, minp, maxp)
  2393. end
  2394. if voxelsWereManipulated then
  2395. vm:set_data(data)
  2396. if GENERATE_ORES then minetest.generate_ores(vm) end
  2397. minetest.generate_decorations(vm)
  2398. for _,core in ipairs(cores) do
  2399. addDetail_skyTree(decorations, core, minp, maxp)
  2400. end
  2401. for _,decoration in ipairs(decorations) do
  2402. local nodeAtPos = minetest.get_node(decoration.pos)
  2403. if nodeAtPos.name == "air" or nodeAtPos.name == nodeName_ignore then minetest.set_node(decoration.pos, decoration.node) end
  2404. end
  2405. local dustingInProgress = false
  2406. for _,core in ipairs(cores) do
  2407. if core.dustLocations ~= nil then
  2408. if not dustingInProgress then
  2409. vm:get_data(data)
  2410. dustingInProgress = true
  2411. end
  2412. nodeId_dust = minetest.get_content_id(core.biome.node_dust)
  2413. for _, location in ipairs(core.dustLocations) do
  2414. if data[location] == nodeId_air and data[location - area.ystride] ~= nodeId_air then
  2415. data[location] = nodeId_dust
  2416. end
  2417. end
  2418. end
  2419. end
  2420. if dustingInProgress then vm:set_data(data) end
  2421. -- Lighting is a problem. Two problems really...
  2422. --
  2423. -- Problem 1:
  2424. -- We can't use the usual lua mapgen lighting trick of flags="nolight" e.g.:
  2425. -- minetest.set_mapgen_params({mgname = "singlenode", flags = "nolight"})
  2426. -- (https://forum.minetest.net/viewtopic.php?t=19836)
  2427. --
  2428. -- because the mod is designed to run with other mapgens. So we must set the light
  2429. -- values to zero at islands before calling calc_lighting() to propegate lighting
  2430. -- down from above.
  2431. --
  2432. -- This causes lighting bugs if we zero the whole emerge_min-emerge_max area because
  2433. -- it leaves hard black at the edges of the emerged area (calc_lighting must assume
  2434. -- a value of zero for light outside the region, and be blending that in)
  2435. --
  2436. -- But we can't simply zero only the minp-maxp area instead, because then calc_lighting
  2437. -- reads the daylight values out of the overdraw area and blends those in, cutting
  2438. -- up shadows with lines of daylight along chunk boundaries.
  2439. --
  2440. -- The correct solution is to zero and calculate the whole emerge_min-emerge_max area,
  2441. -- but only write the calculated lighting information from minp-maxp back into the map,
  2442. -- however the API doesn't appear to provide a fast way to do that.
  2443. --
  2444. -- Workaround: zero an area that extends into the overdraw region, but keeps a gap around
  2445. -- the edges to preserve and allow the real light values to propegate in. Then when
  2446. -- calc_lighting is called it will have daylight (or existing values) at the emerge boundary
  2447. -- but not near the chunk boundary. calc_lighting is able to take the edge lighting into
  2448. -- account instead of assuming zero. It's not a perfect solution, but allows shading without
  2449. -- glaringly obvious lighting artifacts, and the minor ill effects should only affect the
  2450. -- islands and be corrected any time lighting is updated.
  2451. --
  2452. --
  2453. -- Problem 2:
  2454. -- We don't want islands to blacken the landscape below them in shadow.
  2455. --
  2456. -- Workaround 1: Instead of zeroing the lighting before propegating from above, set it
  2457. -- to 2, so that shadows are never pitch black. Shadows will still go back to pitch black
  2458. -- though if lighting gets recalculated, e.g. player places a torch then removes it.
  2459. --
  2460. -- Workaround 2: set the bottom of the chunk to full daylight, ensuring that full
  2461. -- daylight is what propegates down below islands. This has the problem of causing a
  2462. -- bright horizontal band of light where islands approach a chunk floor or ceiling,
  2463. -- but Hallelujah Mountains already had that issue due to having propagate_shadow
  2464. -- turned off when calling calc_lighting. This workaround has the same drawback, but
  2465. -- does a much better job of preventing undesired shadows.
  2466. local shadowGap = 1
  2467. local brightMin = {x = emerge_min.x + shadowGap, y = minp.y , z = emerge_min.z + shadowGap}
  2468. local brightMax = {x = emerge_max.x - shadowGap, y = minp.y + 1, z = emerge_max.z - shadowGap}
  2469. local darkMin = {x = emerge_min.x + shadowGap, y = minp.y + 1, z = emerge_min.z + shadowGap}
  2470. local darkMax = {x = emerge_max.x - shadowGap, y = maxp.y , z = emerge_max.z - shadowGap}
  2471. vm:set_lighting({day=2, night=0}, darkMin, darkMax)
  2472. vm:calc_lighting()
  2473. vm:set_lighting({day=15, night=0}, brightMin, brightMax)
  2474. vm:write_to_map() -- seems to be unnecessary when other mods that use vm are running
  2475. for _,core in ipairs(cores) do
  2476. -- place any schematics which should be placed after the landscape
  2477. if addDetail_ancientPortal ~= nil then addDetail_ancientPortal(core) end
  2478. end
  2479. end
  2480. end
  2481. cloudlands.init = function()
  2482. if noise_eddyField == nil then
  2483. init_mapgen()
  2484. init_secrets()
  2485. end
  2486. if noise_eddyField == nil then
  2487. -- See comment in init_mapgen() about when this can be called
  2488. minetest.log("warning", "cloudlands.init() unable to init - was probably invoked before the the environment was created")
  2489. end
  2490. end
  2491. local function on_generated(minp, maxp, blockseed)
  2492. local memUsageT0
  2493. local osClockT0 = os.clock()
  2494. if DEBUG then memUsageT0 = collectgarbage("count") end
  2495. local largestCoreType = cloudlands.coreTypes[1] -- the first island type is the biggest/thickest
  2496. local maxCoreThickness = largestCoreType.thicknessMax
  2497. local maxCoreDepth = largestCoreType.radiusMax * 3 / 2 -- todo: not sure why this is radius based and not maxDepth based??
  2498. local maxSufaceRise = 3 * (maxCoreThickness + 1)
  2499. if minp.y > ALTITUDE + (ALTITUDE_AMPLITUDE + maxSufaceRise + 10) or -- the 10 is an arbitrary number because sometimes the noise values exceed their normal range.
  2500. maxp.y < ALTITUDE - (ALTITUDE_AMPLITUDE + maxCoreThickness + maxCoreDepth + 10) then
  2501. -- Hallelujah Mountains don't generate here
  2502. return
  2503. end
  2504. local cores = cloudlands.get_island_details(minp, maxp)
  2505. if DEBUG then
  2506. minetest.log("info", "Cores for on_generated(): " .. #cores)
  2507. for _,core in pairs(cores) do
  2508. minetest.log("core ("..core.x..","..core.y..","..core.z..") r"..core.radius)
  2509. end
  2510. end
  2511. if #cores > 0 then
  2512. -- voxelmanip has mem-leaking issues, avoid creating one if we're not going to need it
  2513. renderCores(cores, minp, maxp, blockseed)
  2514. if DEBUG then
  2515. minetest.log(
  2516. "info",
  2517. MODNAME .. " took "
  2518. .. round((os.clock() - osClockT0) * 1000)
  2519. .. "ms for " .. #cores .. " cores. Uncollected memory delta: "
  2520. .. round(collectgarbage("count") - memUsageT0) .. " KB"
  2521. )
  2522. end
  2523. end
  2524. end
  2525. minetest.register_on_generated(on_generated)
  2526. minetest.register_on_mapgen_init(
  2527. -- invoked after mods initially run but before the environment is created, while the mapgen is being initialized
  2528. function(mgparams)
  2529. worldSeed = mgparams.seed
  2530. --if DEBUG then minetest.set_mapgen_params({mgname = "singlenode"--[[, flags = "nolight"]]}) end
  2531. end
  2532. )