-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathcloudlands.lua
3036 lines (2589 loc) · 159 KB
/
cloudlands.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
local ALTITUDE = 200 -- average altitude of islands
local ALTITUDE_AMPLITUDE = 40 -- rough island altitude variance (plus or minus)
local GENERATE_ORES = false -- set to true for island core stone to contain patches of dirt and sand etc.
local LOWLAND_BIOMES = false or -- If true then determine an island's biome using the biome at altitude "LOWLAND_BIOME_ALTITUDE"
minetest.get_modpath("ethereal") ~= nil -- Ethereal has an alpine biome above altitude 40, so default to lowland biomes
local LOWLAND_BIOME_ALTITUDE = 10 -- Higher than beaches, lower than mountains (See LOWLAND_BIOMES)
local VINE_COVERAGE = 0.3 -- set to 0 to turn off vines
local REEF_RARITY = 0.015 -- Chance of a viable island having a reef or atoll
local TREE_RARITY = 0.08 -- Chance of a viable island having a giant tree growing out of it
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)
local BIOLUMINESCENCE = false or -- Allow giant trees variants which have glowing parts
minetest.get_modpath("glowtest") ~= nil or
minetest.get_modpath("ethereal") ~= nil or
minetest.get_modpath("glow") ~= nil or
minetest.get_modpath("nsspf") ~= nil or
minetest.get_modpath("nightscape") ~= nil or
minetest.get_modpath("moonflower") ~= nil -- a world using any of these mods is OK with bioluminescence
local ENABLE_PORTALS = true -- Whether to allow players to build portals to islands. Portals require the Nether mod.
local USE_NETHER_BASALT = true -- Whether to use "nether:basalt_chiselled" as the portalstone instead of adding "cloudlands:ancient_portalstone" to the Nether.
local EDDYFIELD_SIZE = 1 -- size of the "eddy field-lines" that smaller islands follow
local ISLANDS_SEED = 1000 -- You only need to change this if you want to try different island layouts without changing the map seed
-- Some lists of known node aliases (any nodes which can't be found won't be used).
local NODENAMES_STONE = {"mapgen_stone", "mcl_core:stone", "default:stone", "main:stone"}
local NODENAMES_WATER = {"mapgen_water_source", "mcl_core:water_source", "default:water_source", "main:water"}
local NODENAMES_ICE = {"mapgen_ice", "mcl_core:ice", "pedology:ice_white", "default:ice", "main:ice"}
local NODENAMES_GRAVEL = {"mapgen_gravel", "mcl_core:gravel", "default:gravel", "main:gravel"}
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
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
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.
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.
local NODENAMES_HANGINGVINE = {"vines:vine_end"}
local NODENAMES_HANGINGROOT = {"vines:root_end"}
local NODENAMES_TREEWOOD = {"mcl_core:tree", "default:tree", "mapgen_tree", "main:tree"}
local NODENAMES_TREELEAVES = {"mcl_core:leaves", "default:leaves", "mapgen_leaves", "main:leaves"}
local NODENAMES_FRAMEGLASS = {"xpanes:obsidian_pane_flat", "xpanes:pane_flat", "default:glass", "xpanes:pane_natural_flat", "mcl_core:glass", "walls:window"}
local NODENAMES_WOOD = {"default:wood", "mcl_core:wood", "main:wood"}
local MODNAME = minetest.get_current_modname()
local VINES_REQUIRED_HUMIDITY = 49
local VINES_REQUIRED_TEMPERATURE = 40
local ICE_REQUIRED_TEMPERATURE = 8
local DEBUG = false -- dev logging
local DEBUG_GEOMETRIC = false -- turn off noise from island shapes
local DEBUG_SKYTREES = false -- dev logging
-- OVERDRAW can be set to 1 to cause a y overdraw of one node above the chunk, to avoid creating a dirt "surface"
-- at the top of the chunk that trees mistakenly grow on when the chunk is decorated.
-- However, it looks like that tree problem has been solved by either engine or biome updates, and overdraw causes
-- it's own issues (e.g. nodeId_top not getting set correctly), so I'm leaving overdraw off (i.e. zero) until I
-- notice problems requiring it.
local OVERDRAW = 0
local S = minetest.get_translator(MODNAME)
cloudlands = {} -- API functions can be accessed via this global:
-- cloudlands.get_island_details(minp, maxp) -- returns an array of island-information-tables, y is ignored.
-- cloudlands.find_nearest_island(x, z, search_radius) -- returns a single island-information-table, or nil
-- cloudlands.get_height_at(x, z, [island-information-tables]) -- returns (y, isWater), or nil if no island here
cloudlands.coreTypes = {
{
territorySize = 200,
coresPerTerritory = 3,
radiusMax = 96,
depthMax = 50,
thicknessMax = 8,
frequency = 0.1,
pondWallBuffer = 0.03,
requiresNexus = true,
exclusive = false
},
{
territorySize = 60,
coresPerTerritory = 1,
radiusMax = 40,
depthMax = 40,
thicknessMax = 4,
frequency = 0.1,
pondWallBuffer = 0.06,
requiresNexus = false,
exclusive = true
},
{
territorySize = 30,
coresPerTerritory = 3,
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
depthMax = 16,
thicknessMax = 2,
frequency = 0.1,
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
requiresNexus = false,
exclusive = true
}
}
if minetest.get_biome_data == nil then error(MODNAME .. " requires Minetest v5.0 or greater", 0) end
local function fromSettings(settings_name, default_value)
local result
if type(default_value) == "number" then
result = tonumber(minetest.settings:get(settings_name) or default_value)
elseif type(default_value) == "boolean" then
result = minetest.settings:get_bool(settings_name, default_value)
end
return result
end
-- override any settings with user-specified values before these values are needed
ALTITUDE = fromSettings(MODNAME .. "_altitude", ALTITUDE)
ALTITUDE_AMPLITUDE = fromSettings(MODNAME .. "_altitude_amplitude", ALTITUDE_AMPLITUDE)
GENERATE_ORES = fromSettings(MODNAME .. "_generate_ores", GENERATE_ORES)
VINE_COVERAGE = fromSettings(MODNAME .. "_vine_coverage", VINE_COVERAGE * 100) / 100
LOWLAND_BIOMES = fromSettings(MODNAME .. "_use_lowland_biomes", LOWLAND_BIOMES)
TREE_RARITY = fromSettings(MODNAME .. "_giant_tree_rarety", TREE_RARITY * 100) / 100
BIOLUMINESCENCE = fromSettings(MODNAME .. "_bioluminescence", BIOLUMINESCENCE)
ENABLE_PORTALS = fromSettings(MODNAME .. "_enable_portals", ENABLE_PORTALS)
USE_NETHER_BASALT = fromSettings(MODNAME .. "_use_nether_basalt", USE_NETHER_BASALT)
local noiseparams_eddyField = {
offset = -1,
scale = 2,
spread = {x = 350 * EDDYFIELD_SIZE, y = 350 * EDDYFIELD_SIZE, z= 350 * EDDYFIELD_SIZE},
seed = ISLANDS_SEED, --WARNING! minetest.get_perlin() will add the server map's seed to this value
octaves = 2,
persistence = 0.7,
lacunarity = 2.0,
}
local noiseparams_heightMap = {
offset = 0,
scale = ALTITUDE_AMPLITUDE,
spread = {x = 160, y = 160, z= 160},
seed = ISLANDS_SEED, --WARNING! minetest.get_perlin() will add the server map's seed to this value
octaves = 3,
persistence = 0.5,
lacunarity = 2.0,
}
local DENSITY_OFFSET = 0.7
local noiseparams_density = {
offset = DENSITY_OFFSET,
scale = .3,
spread = {x = 25, y = 25, z= 25},
seed = 1000, --WARNING! minetest.get_perlin() will add the server map's seed to this value
octaves = 4,
persistence = 0.5,
lacunarity = 2.0,
}
local SURFACEMAP_OFFSET = 0.5
local noiseparams_surfaceMap = {
offset = SURFACEMAP_OFFSET,
scale = .5,
spread = {x = 40, y = 40, z= 40},
seed = ISLANDS_SEED, --WARNING! minetest.get_perlin() will add the server map's seed to this value
octaves = 4,
persistence = 0.5,
lacunarity = 2.0,
}
local noiseparams_skyReef = {
offset = .3,
scale = .9,
spread = {x = 3, y = 3, z= 3},
seed = 1000,
octaves = 2,
persistence = 0.5,
lacunarity = 2.0,
}
local noiseAngle = -15 --degrees to rotate eddyField noise, so that the vertical and horizontal tendencies are off-axis
local ROTATE_COS = math.cos(math.rad(noiseAngle))
local ROTATE_SIN = math.sin(math.rad(noiseAngle))
local noise_eddyField
local noise_heightMap
local noise_density
local noise_surfaceMap
local noise_skyReef
local worldSeed
local nodeId_ignore = minetest.CONTENT_IGNORE
local nodeId_air
local nodeId_stone
local nodeId_grass
local nodeId_dirt
local nodeId_water
local nodeId_ice
local nodeId_silt
local nodeId_gravel
local nodeId_vine
local nodeName_vine
local nodeName_ignore = minetest.get_name_from_content_id(nodeId_ignore)
local nodeName_portalStone -- not set until all mods are loaded
local REQUIRED_DENSITY = 0.4
local randomNumbers = {} -- array of 0-255 random numbers with values between 0 and 1 (inclusive)
local data = {} -- reuse the massive VoxelManip memory buffers instead of creating on every on_generate()
local biomes = {}
-- optional region specified in settings to restrict islands too
local region_restrictions = false
local region_min_x, region_min_z, region_max_x, region_max_z = -32000, -32000, 32000, 32000
-- optional biomes specified in settings to restrict islands too
local limit_to_biomes = nil
local limit_to_biomes_altitude = nil
--[[==============================
Math functions
==============================]]--
-- avoid having to perform table lookups each time a common math function is invoked
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
local function clip(value, minValue, maxValue)
if value <= minValue then
return minValue
elseif value >= maxValue then
return maxValue
else
return value
end
end
local function round(value)
return math_floor(0.5 + value)
end
--[[==============================
Interop functions
==============================]]--
local get_heat, get_humidity = minetest.get_heat, minetest.get_humidity
local biomeinfoAvailable = minetest.get_modpath("biomeinfo") ~= nil and minetest.global_exists("biomeinfo")
local isMapgenV6 = minetest.get_mapgen_setting("mg_name") == "v6"
if isMapgenV6 then
if not biomeinfoAvailable then
-- The biomeinfo mod by Wuzzy can be found at https://repo.or.cz/minetest_biomeinfo.git
minetest.log("warning", MODNAME .. " detected mapgen v6: Full mapgen v6 support requires adding the biomeinfo mod.")
else
get_heat = function(pos)
return biomeinfo.get_v6_heat(pos) * 100
end
get_humidity = function(pos)
return biomeinfo.get_v6_humidity(pos) * 100
end
end
end
local interop = {}
-- returns the id of the first nodename in the list that resolves to a node id, or nodeId_ignore if not found
interop.find_node_id = function (node_contender_names)
local result = nodeId_ignore
for _,contenderName in ipairs(node_contender_names) do
local nonAliasName = minetest.registered_aliases[contenderName] or contenderName
if minetest.registered_nodes[nonAliasName] ~= nil then
result = minetest.get_content_id(nonAliasName)
end
--if DEBUG then minetest.log("info", contenderName .. " returned " .. result .. " (" .. minetest.get_name_from_content_id(result) .. ")") end
if result ~= nodeId_ignore then return result end
end
return result
end
-- returns the name of the first nodename in the list that resolves to a node id, or 'ignore' if not found
interop.find_node_name = function (node_contender_names)
return minetest.get_name_from_content_id(interop.find_node_id(node_contender_names))
end
interop.get_first_element_in_table = function(tbl)
for k,v in pairs(tbl) do return v end
return nil
end
-- returns the top-texture name of the first nodename in the list that's a registered node, or nil if not found
interop.find_node_texture = function (node_contender_names)
local result = nil
local nodeName = minetest.get_name_from_content_id(interop.find_node_id(node_contender_names))
if nodeName ~= nil then
local node = minetest.registered_nodes[nodeName]
if node ~= nil then
result = node.tiles
if type(result) == "table" then result = result["name"] or interop.get_first_element_in_table(result) end -- incase it's not a string
if type(result) == "table" then result = result["name"] or interop.get_first_element_in_table(result) end -- incase multiple tile definitions
end
end
return result
end
-- returns the node name of the clone node.
interop.register_clone = function(node_name, clone_name)
local node = minetest.registered_nodes[node_name]
if node == nil then
minetest.log("error", "cannot clone " .. node_name)
return nil
else
if clone_name == nil then clone_name = MODNAME .. ":" .. string.gsub(node.name, ":", "_") end
if minetest.registered_nodes[clone_name] == nil then
if DEBUG then minetest.log("info", "attempting to register: " .. clone_name) end
local clone = {}
for key, value in pairs(node) do clone[key] = value end
clone.name = clone_name
minetest.register_node(clone_name, clone)
--minetest.log("info", clone_name .. " id: " .. minetest.get_content_id(clone_name))
--minetest.log("info", clone_name .. ": " .. dump(minetest.registered_nodes[clone_name]))
end
return clone_name
end
end
-- converts "modname:nodename" into (modname, nodename), if no colon is found then modname is nil
interop.split_nodename = function(nodeName)
local result_modname = nil
local result_nodename = nodeName
local pos = nodeName:find(':')
if pos ~= nil then
result_modname = nodeName:sub(0, pos - 1)
result_nodename = nodeName:sub(pos + 1)
end
return result_modname, result_nodename
end
-- returns a unique id for the biome, normally this is numeric but with mapgen v6 it can be a string name.
interop.get_biome_key = function(pos)
if isMapgenV6 and biomeinfoAvailable then
return biomeinfo.get_v6_biome(pos)
else
return minetest.get_biome_data(pos).biome
end
end
-- returns true if filename is a file that exists.
interop.file_exists = function(filename)
local f = io.open(filename, "r")
if f == nil then
return false
else
f:close()
return true
end
end
-- returns a written book item (technically an item stack), or nil if no books mod available
interop.write_book = function(title, author, text, description)
local stackName_writtenBook
if minetest.get_modpath("mcl_books") then
stackName_writtenBook = "mcl_books:written_book"
text = title .. "\n\n" .. text -- MineClone2 books doen't show a title (or author)
elseif minetest.get_modpath("book") ~= nil then
stackName_writtenBook = "book:book_written"
text = "\n\n" .. text -- Crafter books put the text immediately under the title
elseif minetest.get_modpath("default") ~= nil then
stackName_writtenBook = "default:book_written"
else
return nil
end
local book_itemstack = ItemStack(stackName_writtenBook)
local book_data = {}
book_data.title = title
book_data.text = text
book_data.owner = author
book_data.author = author
book_data.description = description
book_data.page = 1
book_data.page_max = 1
book_data.generation = 0
book_data["book.book_title"] = title -- Crafter book title
book_data["book.book_text"] = text -- Crafter book text
book_itemstack:get_meta():from_table({fields = book_data})
return book_itemstack
end
--[[==============================
Portals
==============================]]--
local addDetail_ancientPortal = nil
local bookOfPortalsText_AncientPortalstone =
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" ..
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.")
local bookOfPortalsText_NetherChiselledBasalt =
S("Construction requires 14 blocks of chiselled Nether basalt, which can be crafted from hewn Nether basalt. The only source we are aware of for Nether basalt are the islands of basalt columns found in the magma ocean deep within the Nether, but to find the magma ocean Mantle requires finding a passageway through the netherrack. A finished frame is four blocks wide, five blocks high, and stands vertically, like a doorway.") .. "\n\n" ..
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.")
if ENABLE_PORTALS and minetest.get_modpath("nether") ~= nil and minetest.global_exists("nether") and nether.register_portal ~= nil then
-- The Portals API is available
-- Register a player-buildable portal to Hallelujah Mountains.
-- returns a position on the island which is suitable for a portal to be placed, or nil if none can be found
-- player_name is optional, allowing a player to spawn a remote portal in their own protected areas.
local function find_potential_portal_location_on_island(island_info, player_name)
local result = nil
if island_info ~= nil then
local searchRadius = island_info.radius * 0.6 -- islands normally don't reach their full radius, and lets not put portals too near the edge
local coreList = cloudlands.get_island_details(
{x = island_info.x - searchRadius, z = island_info.z - searchRadius},
{x = island_info.x + searchRadius, z = island_info.z + searchRadius}
)
-- Deterministically sample the island for a low location that isn't water.
-- Seed the prng so this function always returns the same coords for the island
local prng = PcgRandom(island_info.x * 65732 + island_info.z * 729 + minetest.get_mapgen_setting("seed") * 3)
local positions = {}
for attempt = 1, 15 do -- how many attempts we'll make at finding a good location
local angle = (prng:next(0, 10000) / 10000) * 2 * PI
local distance = math_sqrt(prng:next(0, 10000) / 10000) * searchRadius
if attempt == 1 then distance = 0 end -- Always sample the middle of the island, as it's the safest fallback location
local x = round(island_info.x + math_cos(angle) * distance)
local z = round(island_info.z + math_sin(angle) * distance)
local y, isWater = cloudlands.get_height_at(x, z, coreList)
if y ~= nil then
local weight = 0
if not isWater then weight = weight + 1 end -- avoid putting portals in ponds
if y >= island_info.y + ALTITUDE then weight = weight + 2 end -- avoid putting portals down the sides of eroded cliffs
positions[#positions + 1] = {x = x, y = y + 1, z = z, weight = weight}
end
end
-- Order the locations by how good they are
local compareFn = function(pos_a, pos_b)
if pos_a.weight > pos_b.weight then return true end
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.
return false
end
table.sort(positions, compareFn)
-- nether.volume_is_natural() was deprecated in favor of nether.volume_is_natural_and_unprotected()
local volume_is_natural_and_unprotected = nether.volume_is_natural_and_unprotected or nether.volume_is_natural
-- Now the locations are sorted by how good they are, find the first/best that doesn't
-- grief a player build.
-- Ancient Portalstone has is_ground_content set to true, so we won't have to worry about old/broken
-- portal frames interfering with the results of nether.volume_is_natural_and_unprotected()
for _, position in ipairs(positions) do
-- Unfortunately, at this point we don't know the orientation of the portal, so use worst case
local minp = {x = position.x - 2, y = position.y, z = position.z - 2}
local maxp = {x = position.x + 3, y = position.y + 4, z = position.z + 3}
if volume_is_natural_and_unprotected(minp, maxp, player_name) then
result = position
break
end
end
end
return result
end
-- returns nil if no suitable location could be found, otherwise returns (portal_pos, island_info)
-- player_name is optional, allowing a player to spawn a remote portal in their own protected areas.
local function find_nearest_island_location_for_portal(surface_x, surface_z, player_name)
local result = nil
local island = cloudlands.find_nearest_island(surface_x, surface_z, 75)
if island == nil then island = cloudlands.find_nearest_island(surface_x, surface_z, 150) end
if island == nil then island = cloudlands.find_nearest_island(surface_x, surface_z, 400) end
if island ~= nil then
result = find_potential_portal_location_on_island(island, player_name)
end
return result, island
end
-- "Ancient Portalstone" was a tempory placeholder "portalstone" until the Nether mod started
-- providing a block obtainable by exploring the Nether and earmarked for mods like this one
-- to use for portals.
-- The Nether now provides various basalts as portalstones, but "Ancient Portalstone" will
-- still be registered for backwards compatibility.
-- The Portals API is currently provided by nether, which depends on default, so we can assume default textures are available
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
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"
minetest.register_node(MODNAME .. ":ancient_portalstone", {
description = S("Ancient Portalstone"),
tiles = {portalstone_end, portalstone_end, portalstone_side, portalstone_side, portalstone_side, portalstone_side},
paramtype2 = "facedir",
sounds = default.node_sound_stone_defaults(),
groups = {cracky = 1, level = 2},
on_blast = function() --[[blast proof]] end
})
local _ = {name = "air", prob = 0}
local A = {name = "air", prob = 255, force_place = true}
local PU = {name = "portalstone", param2 = 0, prob = 255, force_place = true}
local PW = {name = "portalstone", param2 = 12, prob = 255, force_place = true}
local PN = {name = "portalstone", param2 = 4, prob = 255, force_place = true}
local islandsWithPortals = {}
-- this uses place_schematic() without minetest.after(), so should be called after vm:write_to_map()
addDetail_ancientPortal = function(core)
if (core.radius < 8 or PORTAL_RARITY == 0) then return false end -- avoid portals hanging off the side of small islands
local fastHash = 3
fastHash = (37 * fastHash) + 9354 -- to keep this probability distinct from reefs and atols
fastHash = (37 * fastHash) + ISLANDS_SEED
fastHash = (37 * fastHash) + core.x
fastHash = (37 * fastHash) + core.z
fastHash = (37 * fastHash) + math_floor(core.radius)
fastHash = (37 * fastHash) + math_floor(core.depth)
if (PORTAL_RARITY * 10000) < math_floor((math_abs(fastHash)) % 10000) then return false end
if islandsWithPortals[fastHash] then
-- This is a hack to stop multiple portals appearing on the island.
-- It will fail if neightboring chunks generate the same core in different server sessions.
-- I think what happens is mods like plantlife_modpack prevent nether.volume_is_natural_and_unprotected() from
-- returning a deterministic result during chunk generation, thus find_potential_portal_location_on_island()
-- doesn't always return the same spot on the island?
return
end
islandsWithPortals[fastHash] = true
local portalPos = find_potential_portal_location_on_island(core, nil)
if portalPos ~= nil then
local orientation = (fastHash % 2) * 90
portalPos.y = portalPos.y - ((core.x + core.z) % 3) -- partially bury some ancient portals
minetest.place_schematic(
portalPos,
{
size = {x = 4, y = 5, z = 1},
data = {
PN, PW, PW, PN,
PU, _, _, PU,
PU, _, _, PU,
PU, _, _, PU,
PN, PW, PW, PN
},
},
orientation,
{ -- node replacements
["portalstone"] = nodeName_portalStone,
},
true
)
end
end
-- A wrapper for nether.register_portal() where only the frameNodeName and corresponding bookOfPortalsText needs to be specified
local function register_portal(frameNodeName, bookOfPortalsText)
return nether.register_portal("cloudlands_portal", {
shape = nether.PortalShape_Traditional,
frame_node_name = frameNodeName,
wormhole_node_color = 2, -- 2 is blue
particle_color = "#77F",
particle_texture = {
name = "nether_particle_anim1.png",
animation = {
type = "vertical_frames",
aspect_w = 7,
aspect_h = 7,
length = 1,
},
scale = 1.5
},
title = S("Hallelujah Mountains Portal"),
book_of_portals_pagetext = bookOfPortalsText,
is_within_realm = function(pos)
-- return true if pos is in the cloudlands
-- I'm doing this based off height for speed, so it sometimes gets it wrong when the
-- Hallelujah mountains start reaching the ground.
if noise_heightMap == nil then cloudlands.init() end
local largestCoreType = cloudlands.coreTypes[1] -- the first island type is the biggest/thickest
local island_bottom = ALTITUDE - (largestCoreType.depthMax * 0.66) + round(noise_heightMap:get2d({x = pos.x, y = pos.z}))
return pos.y > math_max(40, island_bottom)
end,
find_realm_anchorPos = function(surface_anchorPos, player_name)
-- Find the nearest island and obtain a suitable surface position on it
local destination_pos, island = find_nearest_island_location_for_portal(surface_anchorPos.x, surface_anchorPos.z, player_name)
if island ~= nil then
-- Allow any existing or player-positioned portal on the island to be linked to
-- first before resorting to the island's default portal position
local existing_portal_location, existing_portal_orientation = nether.find_nearest_working_portal(
"cloudlands_portal",
{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.
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.
0 -- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are in the Cloudlands realm)
)
if existing_portal_location ~= nil then
return existing_portal_location, existing_portal_orientation
end
end
return destination_pos
end,
find_surface_anchorPos = function(realm_anchorPos)
-- This function isn't needed since find_surface_target_y() will be used by default,
-- but by implementing it I can look for any existing nearby portals before falling
-- back to find_surface_target_y.
-- Using -100000 for y to ensure the position is outside the cloudlands realm and so
-- find_nearest_working_portal() will only returns ground portals.
-- a y_factor of 0 makes the search ignore the -100000 altitude of the portals (as
-- long as they are outside the cloudlands realm)
local existing_portal_location, existing_portal_orientation =
nether.find_nearest_working_portal("cloudlands_portal", {x = realm_anchorPos.x, y = -100000, z = realm_anchorPos.z}, 150, 0)
if existing_portal_location ~= nil then
return existing_portal_location, existing_portal_orientation
else
local y = nether.find_surface_target_y(realm_anchorPos.x, realm_anchorPos.z, "cloudlands_portal")
return {x = realm_anchorPos.x, y = y, z = realm_anchorPos.z}
end
end,
on_ignite = function(portalDef, anchorPos, orientation)
-- make some sparks fly on ignition
local p1, p2 = portalDef.shape:get_p1_and_p2_from_anchorPos(anchorPos, orientation)
local pos = vector.divide(vector.add(p1, p2), 2)
local textureName = portalDef.particle_texture
if type(textureName) == "table" then textureName = textureName.name end
local velocity
if orientation == 0 then
velocity = {x = 0, y = 0, z = 7}
else
velocity = {x = 7, y = 0, z = 0}
end
local particleSpawnerDef = {
amount = 180,
time = 0.15,
minpos = {x = pos.x - 1, y = pos.y - 1.5, z = pos.z - 1},
maxpos = {x = pos.x + 1, y = pos.y + 1.5, z = pos.z + 1},
minvel = velocity,
maxvel = velocity,
minacc = {x = 0, y = 0, z = 0},
maxacc = {x = 0, y = 0, z = 0},
minexptime = 0.1,
maxexptime = 0.5,
minsize = 0.3 * portalDef.particle_texture_scale,
maxsize = 0.8 * portalDef.particle_texture_scale,
collisiondetection = false,
texture = textureName .. "^[colorize:#99F:alpha",
animation = portalDef.particle_texture_animation,
glow = 8
}
minetest.add_particlespawner(particleSpawnerDef)
velocity = vector.multiply(velocity, -1)
particleSpawnerDef.minvel, particleSpawnerDef.maxvel = velocity, velocity
minetest.add_particlespawner(particleSpawnerDef)
end
}) -- end of nether.register_portal() invocation
end -- end of local function register_portal()
minetest.register_on_mods_loaded(function()
-- wait until all the other mods are loaded to see if "nether:basalt_chiselled" is still available to use as a portalstone
-- (another mod might be using it for portals)
local useBasalt = false
if USE_NETHER_BASALT and
minetest.registered_nodes["nether:basalt_chiselled"] ~= nil and
register_portal("nether:basalt_chiselled", bookOfPortalsText_NetherChiselledBasalt) then
-- Chiselled basalt is available for cloudlands to use as portal stone,
-- so there's no need to add any ancient portalstone to the Nether.
useBasalt = true
end
if useBasalt then
nodeName_portalStone = "nether:basalt_chiselled"
-- We want to alias any legacy ancient_portalstone to be nether:basalt_chiselled.
-- Players may have already built portals out of "cloudlands:ancient_portalstone" and we
-- want those portals to continue to work.
-- use _force() to Unregister ancient_portalstone and make it an alias of nether:basalt_chiselled
minetest.register_alias_force(MODNAME .. ":ancient_portalstone", nodeName_portalStone)
else
nodeName_portalStone = MODNAME .. ":ancient_portalstone"
register_portal(nodeName_portalStone, bookOfPortalsText_AncientPortalstone)
-- Ensure Ancient Portalstone can be obtained from the Nether
minetest.register_ore({
ore_type = "scatter",
ore = MODNAME .. ":ancient_portalstone",
wherein = "nether:rack",
clust_scarcity = 32 * 32 * 32,
clust_num_ores = 6,
clust_size = 3,
y_max = nether.DEPTH_CEILING or nether.DEPTH,
y_min = nether.DEPTH_FLOOR or -32000,
})
minetest.register_decoration({
name = "Ancient broken portal",
deco_type = "schematic",
place_on = "nether:rack",
sidelen = 80,
fill_ratio = 0.00018,
biomes = {"nether_caverns"},
y_max = nether.DEPTH_CEILING or nether.DEPTH,
y_min = nether.DEPTH_FLOOR or -32000,
schematic = {
size = {x = 4, y = 4, z = 1},
data = {
PN, A, PW, PN,
PU, A, A, PU,
A, _, _, PU,
_, _, _, PU
},
yslice_prob = {
{ypos = 3, prob = 92},
{ypos = 1, prob = 30},
}
},
place_offset_y = 1,
{ -- node replacements
["portalstone"] = nodeName_portalStone,
},
flags = "force_placement,all_floors",
rotation = "random"
})
end
end)
end
--[[==============================
SkyTrees
==============================]]--
-- If splitting SkyTrees into a seperate mod, perhaps schemlib would be of help - https://forum.minetest.net/viewtopic.php?t=18084
if not minetest.global_exists("SkyTrees") then -- If SkyTrees added into other mods, this may have already been defined
local TREE1_FILE = 'cloudlands_tree1.mts'
local TREE2_FILE = 'cloudlands_tree2.mts'
local BARK_SUFFIX = '_bark'
local GLOW_SUFFIX = '_glow'
SkyTrees = {
-- Order the trees in this schematicInfo array from the largest island requirements to smallest
-- The data in each schematicInfo must exactly match what's in the .mts file or things will break
schematicInfo = {
{
filename = TREE1_FILE,
size = {x = 81, y = 106, z = 111},
center = {x = 37, y = 11, z = 73},
requiredIslandDepth = 20,
requiredIslandRadius = 40,
nodesWithConstructor = {
{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},
{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}
}
},
{
filename = TREE2_FILE,
size = {x = 62, y = 65, z = 65},
center = {x = 30, y = 12, z = 36},
requiredIslandDepth = 16,
requiredIslandRadius = 24,
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} },
}
},
MODNAME = minetest.get_current_modname() -- don't hardcode incase it's copied into other mods
}
-- Must be called during mod load time, as it uses minetest.register_node()
-- (add an optional dependency for any mod where the tree & leaf textures might be
-- sourced from, to ensure they are loaded before this is called)
SkyTrees.init = function()
SkyTrees.minimumIslandRadius = 100000
SkyTrees.minimumIslandDepth = 100000
SkyTrees.maximumYOffset = 0
SkyTrees.maximumHeight = 0
SkyTrees.nodeName_sideVines = interop.find_node_name(NODENAMES_VINES)
SkyTrees.nodeName_hangingVine = interop.find_node_name(NODENAMES_HANGINGVINE)
SkyTrees.nodeName_hangingRoot = interop.find_node_name(NODENAMES_HANGINGROOT)
for i,tree in pairs(SkyTrees.schematicInfo) do
local fullFilename = minetest.get_modpath(SkyTrees.MODNAME) .. DIR_DELIM .. tree.filename
if not interop.file_exists(fullFilename) then
-- remove the schematic from the list
SkyTrees.schematicInfo[i] = nil
else
SkyTrees.minimumIslandRadius = math_min(SkyTrees.minimumIslandRadius, tree.requiredIslandRadius)
SkyTrees.minimumIslandDepth = math_min(SkyTrees.minimumIslandDepth, tree.requiredIslandDepth)
SkyTrees.maximumYOffset = math_max(SkyTrees.maximumYOffset, tree.center.y)
SkyTrees.maximumHeight = math_max(SkyTrees.maximumHeight, tree.size.y)
tree.theme = {}
SkyTrees.schematicInfo[tree.filename] = tree -- so schematicInfo of trees can be indexed by name
end
end
local function generate_woodTypes(nodeName_templateWood, overlay, barkoverlay, nodesuffix, description, dropsTemplateWood)
local trunkNode = minetest.registered_nodes[nodeName_templateWood]
local newTrunkNode = {}
for key, value in pairs(trunkNode) do newTrunkNode[key] = value end
newTrunkNode.name = SkyTrees.MODNAME .. ":" .. nodesuffix
newTrunkNode.description = description
if newTrunkNode.paramtype2 == nil then newTrunkNode.paramtype2 = "facedir" end
if newTrunkNode.on_dig ~= nil and minetest.get_modpath("main") then
newTrunkNode.on_dig = nil -- Crafter has special trunk auto-digging logic that doesn't make sense for giant trees
end
if dropsTemplateWood then
newTrunkNode.drop = nodeName_templateWood
if newTrunkNode.groups == nil then newTrunkNode.groups = {} end
newTrunkNode.groups.not_in_creative_inventory = 1
else
newTrunkNode.drop = nil
end
local tiles = trunkNode.tiles
if type(tiles) == "table" then
newTrunkNode.tiles = {}
for key, value in pairs(tiles) do newTrunkNode.tiles[key] = value .. overlay end
else
newTrunkNode.tiles = tiles .. overlay
end
local newBarkNode = {}
for key, value in pairs(newTrunkNode) do newBarkNode[key] = value end
newBarkNode.name = newBarkNode.name .. BARK_SUFFIX
newBarkNode.description = S("Bark of @1", newBarkNode.description)
-- .drop: leave the bark nodes dropping the trunk wood
tiles = trunkNode.tiles
if type(tiles) == "table" then
newBarkNode.tiles = { tiles[#tiles] .. barkoverlay }
end
--minetest.log("info", newTrunkNode.name .. ": " .. dump(newTrunkNode))
minetest.register_node(newTrunkNode.name, newTrunkNode)
minetest.register_node(newBarkNode.name, newBarkNode)
return newTrunkNode.name
end
local function generate_leafTypes(nodeName_templateLeaf, overlay, nodesuffix, description, dropsTemplateLeaf, glowVariantBrightness)
local leafNode = minetest.registered_nodes[nodeName_templateLeaf]
local newLeafNode = {}
for key, value in pairs(leafNode) do newLeafNode[key] = value end
newLeafNode.name = SkyTrees.MODNAME .. ":" .. nodesuffix
newLeafNode.description = description
newLeafNode.sunlight_propagates = true -- soo many leaves they otherwise blot out the sun.
if dropsTemplateLeaf then
newLeafNode.drop = nodeName_templateLeaf
if newLeafNode.groups == nil then newLeafNode.groups = {} end
newLeafNode.groups.not_in_creative_inventory = 1
else
newLeafNode.drop = nil
end
local tiles = leafNode.tiles
if type(tiles) == "table" then
newLeafNode.tiles = {}
for key, value in pairs(tiles) do newLeafNode.tiles[key] = value .. overlay end
else
newLeafNode.tiles = tiles .. overlay
end
minetest.register_node(newLeafNode.name, newLeafNode)
if glowVariantBrightness ~= nil and glowVariantBrightness > 0 and BIOLUMINESCENCE then
local glowingLeafNode = {}
for key, value in pairs(newLeafNode) do glowingLeafNode[key] = value end
glowingLeafNode.name = newLeafNode.name .. GLOW_SUFFIX
glowingLeafNode.description = S("Glowing @1", description)
glowingLeafNode.light_source = glowVariantBrightness
minetest.register_node(glowingLeafNode.name, glowingLeafNode)
end
return newLeafNode.name
end
local templateWood = interop.find_node_name(NODENAMES_TREEWOOD)
if templateWood == 'ignore' then
SkyTrees.disabled = "Could not find any tree nodes"
return
end
local normalwood = generate_woodTypes(templateWood, "", "", "tree", S("Giant tree"), true)
local darkwood = generate_woodTypes(templateWood, "^[colorize:black:205", "^[colorize:black:205", "darkwood", S("Giant Ziricote"), false)
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
local templateLeaf = interop.find_node_name(NODENAMES_TREELEAVES)
if templateLeaf == 'ignore' then
SkyTrees.disabled = "Could not find any treeleaf nodes"
return
end
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
local greenleaf2 = generate_leafTypes(templateLeaf, "^[colorize:#00FF00:16", "leaves2", S("Leaves of a giant tree"), false)
local greenleaf3 = generate_leafTypes(templateLeaf, "^[colorize:#90FF60:28", "leaves3", S("Leaves of a giant tree"), false)
local whiteblossom1 = generate_leafTypes(templateLeaf, "^[colorize:#fffdfd:alpha", "blossom_white1", S("Blossom"), false)
local whiteblossom2 = generate_leafTypes(templateLeaf, "^[colorize:#fff0f0:alpha", "blossom_white2", S("Blossom"), false)
local pinkblossom = generate_leafTypes(templateLeaf, "^[colorize:#FFE3E8:alpha", "blossom_whitepink", S("Blossom"), false, 5)
local sakurablossom1 = generate_leafTypes(templateLeaf, "^[colorize:#ea327c:alpha", "blossom_red", S("Sakura blossom"), false, 5)
local sakurablossom2 = generate_leafTypes(templateLeaf, "^[colorize:#ffc3dd:alpha", "blossom_pink", S("Sakura blossom"), false)
local wisteriaBlossom1 = generate_leafTypes(templateLeaf, "^[colorize:#8087ec:alpha", "blossom_wisteria1", S("Wisteria blossom"), false)
local wisteriaBlossom2 = generate_leafTypes(templateLeaf, "^[colorize:#ccc9ff:alpha", "blossom_wisteria2", S("Wisteria blossom"), false, 7)
local tree = SkyTrees.schematicInfo[TREE1_FILE]
if tree ~= nil then
tree.defaultThemeName = "Green foliage"
tree.theme[tree.defaultThemeName] = {
relativeProbability = 5,
trunk = normalwood,
leaves1 = greenleaf1,
leaves2 = greenleaf2,
leaves_special = greenleaf3,
vineflags = { leaves = true, hanging_leaves = true },
init = function(self, position)
-- if it's hot and humid then add vines
local viney = get_heat(position) >= VINES_REQUIRED_TEMPERATURE and get_humidity(position) >= VINES_REQUIRED_HUMIDITY
if viney then
local flagSeed = position.x * 3 + position.z + ISLANDS_SEED
self.vineflags.hanging_leaves = (flagSeed % 10) <= 3 or (flagSeed % 10) >= 8
self.vineflags.leaves = (flagSeed % 10) <= 5
self.vineflags.bark = (flagSeed % 10) <= 2
self.vineflags.hanging_bark = (flagSeed % 10) <= 1
end
end
}
tree.theme["Haunted"] = {
relativeProbability = 2,
trunk = darkwood,
vineflags = { hanging_roots = true },
hasHeart = false,
hasSoil = false,
init = function(self, position)
-- 60% of these trees are a hanging roots variant
self.vineflags.hanging_roots = (position.x * 3 + position.y + position.z + ISLANDS_SEED) % 10 < 60
end
}
tree.theme["Dead"] = {
relativeProbability = 0, -- 0 because this theme will be chosen based on location, rather than chance.
trunk = deadwood,
hasHeart = false
}
tree.theme["Sakura"] = {
relativeProbability = 2,
trunk = darkwood,
leaves1 = sakurablossom2,
leaves2 = whiteblossom2,
leaves_special = sakurablossom1,
init = function(self, position)
-- 40% of these trees are a glowing variant
self.glowing = (position.x * 3 + position.z + ISLANDS_SEED) % 10 <= 3 and BIOLUMINESCENCE
self.leaves_special = sakurablossom1
if self.glowing then self.leaves_special = sakurablossom1 .. GLOW_SUFFIX end
end
}
end