[MDB IGNORE] Adds new features and tiles for lavaland biomes (#95955)

## About The Pull Request

Adds new floor types and natural decor to both Siderite and Shale
biomes, as well as makes flora and fauna spawns differ between said
biomes.

<img width="1216" height="997" alt="dreamseeker_uIv1OgVkpD"
src="https://github.com/user-attachments/assets/327d9caa-a58c-4d70-acbe-477da69b4d71"
/>

---

Basalt biome now spawns more legions and goliaths, but less watchers and
no brimdemons. You can rarely find new volcanic pore rocks which will
spew out lava when destroyed.

<img width="505" height="361" alt="dreamseeker_TA9rXzOgad"
src="https://github.com/user-attachments/assets/fb19a60a-c927-441b-abad-161ce5022a60"
/>


Siderite biome has a lot of rock formations and may sometimes spawn new
stalagmites. You can expect to see a ton of bileworms, raptors and
watchers there.


<img width="747" height="613" alt="dreamseeker_uZgRX8qqh3"
src="https://github.com/user-attachments/assets/d356f1c6-371a-4b8b-899b-470edb0d2790"
/>


Shale is full of lobstrocities, goldgrubs and brimdemons, and home to a
new glowgrowth "plant" - colony of bioluminescent fungi growing on
natural air vents.

<img width="274" height="220" alt="dreamseeker_EtxiS4X9M7"
src="https://github.com/user-attachments/assets/cbefba67-b618-4a65-ad12-9ee5182b650c"
/>


While it cannot be planted by itself, it contains luminescent fluid
inside which will grant whoever eats it minor night vision as long as it
is inside their bloodstream (as well as cyan-glowing eyes)

---

Ruins now properly respect their biome's rock (and now floor as well)
type, since it was broken for some of them previously.

<details>
<summary>More images under the dropdown</summary>

<img width="1216" height="997" alt="dreamseeker_fpXXV0Rt3D"
src="https://github.com/user-attachments/assets/fa790ca1-7045-490e-bcb2-c1f9da9f277c"
/>
<img width="414" height="303" alt="dreamseeker_l6E8S2fzCe"
src="https://github.com/user-attachments/assets/8b10dcca-c546-41ce-9363-a12b0f225881"
/>
<img width="784" height="587" alt="dreamseeker_RUWQo0unNp"
src="https://github.com/user-attachments/assets/d7364abc-ec68-4daf-b3cb-a03b8ba7e5de"
/>
<img width="1216" height="997" alt="dreamseeker_zoltABvkBw"
src="https://github.com/user-attachments/assets/324f50f8-83c9-4451-8eff-33ebf81c1d0d"
/>
<img width="1125" height="571" alt="dreamseeker_ky5qvl2XtC"
src="https://github.com/user-attachments/assets/ff08d22f-fc15-4961-b76e-7c10d7814096"
/>

</details>

Credit for original versions of new flora/feature sprites to
SentryPrimis on the /tg/station discord

## Why It's Good For The Game

Makes biomes more distinct and interesting, as well as gives them unique
atmosphere, since right now they only differ in rock type and mineral
distribution.

## Changelog
🆑 SmArtKar, SentryPrimis
add: Siderite and Shale lavaland biomes now have unique floors, as well
as flora, features and fauna!
fix: Fixed some ruins generating with default basalt walls instead of
their biome's walls
/🆑
This commit is contained in:
SmArtKar
2026-05-07 10:20:43 +01:00
committed by GitHub
parent 60c5dc15ed
commit 940d91bb62
60 changed files with 1006 additions and 545 deletions
+48 -7
View File
@@ -41,6 +41,8 @@
/// of biome-related operations. Is populated through
/// `generate_terrain_with_biomes()`.
var/list/generated_turfs_per_biome = list()
/// Same as generated_turfs_per_biome, but additionally indexed by area
var/list/generated_turfs_per_area_biome = list()
/// 2D list of all biomes based on heat and humidity combos. Associative by
/// `BIOME_X_HEAT` and then by `BIOME_X_HUMIDITY` (i.e.
/// `possible_biomes[BIOME_LOW_HEAT][BIOME_LOWMEDIUM_HUMIDITY]`).
@@ -65,14 +67,16 @@
var/shared_seed = TRUE
/// Stamp size for DBP noise, aka frequency
var/biome_stamp_size = 75
/// Stored noise maps per z level if we use a shared seed
var/static_biome_maps = list()
///Base chance of spawning a mob
/// Base chance of spawning a mob
var/mob_spawn_chance = 6
///Base chance of spawning flora
/// Base chance of spawning flora
var/flora_spawn_chance = 2
///Base chance of spawning features
/// Base chance of spawning features
var/feature_spawn_chance = 0.25
///Unique ID for this spawner
/// Unique ID for this spawner
var/string_gen
/// Radius around features within which we avoid spawning other features
@@ -196,6 +200,14 @@
heat_gen[BIOME_HIGH_HEAT] = rustg_dbp_generate("[heat_seed]", "60", "[biome_stamp_size]", "[world.maxx]", "[high_heat_threshold]", "1.1")
heat_gen[BIOME_MEDIUM_HEAT] = rustg_dbp_generate("[heat_seed]", "60", "[biome_stamp_size]", "[world.maxx]", "[medium_heat_threshold]", "[high_heat_threshold]")
if (shared_seed)
static_biome_maps["[turfs[1].z]"] = list(
BIOME_HIGH_HUMIDITY = humidity_gen[BIOME_HIGH_HUMIDITY],
BIOME_MEDIUM_HUMIDITY = humidity_gen[BIOME_MEDIUM_HUMIDITY],
BIOME_HIGH_HEAT = heat_gen[BIOME_HIGH_HEAT],
BIOME_MEDIUM_HEAT = heat_gen[BIOME_MEDIUM_HEAT],
)
var/list/to_generate = list()
for(var/turf/gen_turf as anything in turfs) //Go through all the turfs and generate them
var/closed = string_gen[world.maxx * (gen_turf.y - 1) + gen_turf.x] != "0"
@@ -222,12 +234,40 @@
for(var/biome in to_generate)
var/datum/biome/generating_biome = SSmapping.biomes[biome]
var/list/turf/generated_turfs = generating_biome.generate_turfs_for_terrain(to_generate[biome])
generated_turfs_per_biome[biome] = generated_turfs
generated_turfs_per_biome[biome] = (generated_turfs_per_biome[biome] || list()) + generated_turfs
var/list/area_list = generated_turfs_per_area_biome[biome]
if (!area_list)
area_list = list()
generated_turfs_per_area_biome[biome] = area_list
area_list[generate_in] = generated_turfs
var/message = "[name] terrain generation finished in [(REALTIMEOFDAY - start_time)/10]s!"
to_chat(world, span_boldannounce("[message]"), MESSAGE_TYPE_DEBUG)
log_world(message)
/// Returns a biome datum that the turf was initialized with, or would be if it is present on our Z level and we use a consistent shared seed
/// Not consistent between calls by itself due to using RNG in perlin zoom, needs a static coordinate-based formula
/datum/map_generator/cave_generator/proc/get_biome_for_turf(turf/target)
for (var/biome in generated_turfs_per_biome)
var/list/generated_turfs = generated_turfs_per_biome[biome]
if (generated_turfs[target])
return biome
var/list/biome_map = static_biome_maps["[target.z]"]
if (!shared_seed || !biome_map)
return null
var/drift_x = clamp((target.x + rand(-BIOME_RANDOM_SQUARE_DRIFT, BIOME_RANDOM_SQUARE_DRIFT)), 1, world.maxx)
var/drift_y = clamp((target.y + rand(-BIOME_RANDOM_SQUARE_DRIFT, BIOME_RANDOM_SQUARE_DRIFT)), 2, world.maxy)
var/coordinate = world.maxx * (drift_y - 1) + drift_x
var/humidity_level = text2num(biome_map[BIOME_HIGH_HUMIDITY][coordinate]) ? \
BIOME_HIGH_HUMIDITY : text2num(biome_map[BIOME_MEDIUM_HUMIDITY][coordinate]) ? BIOME_MEDIUM_HUMIDITY : BIOME_LOW_HUMIDITY
var/heat_level = text2num(biome_map[BIOME_HIGH_HEAT][coordinate]) ? \
BIOME_HIGH_HEAT : text2num(biome_map[BIOME_MEDIUM_HEAT][coordinate]) ? BIOME_MEDIUM_HEAT : BIOME_LOW_HEAT
return possible_biomes[heat_level][humidity_level]
/datum/map_generator/cave_generator/populate_terrain(list/turfs, area/generate_in)
if (biome_population && length(possible_biomes))
return populate_terrain_with_biomes(turfs, generate_in)
@@ -354,9 +394,10 @@
log_world(message)
return
for(var/biome in generated_turfs_per_biome)
for(var/biome in generated_turfs_per_area_biome)
var/datum/biome/generating_biome = SSmapping.biomes[biome]
generating_biome.populate_turfs(generated_turfs_per_biome[biome], flora_allowed, features_allowed, fauna_allowed)
var/list/areas_list = generated_turfs_per_area_biome[biome]
generating_biome.populate_turfs(areas_list[generate_in], flora_allowed, features_allowed, fauna_allowed)
CHECK_TICK
@@ -20,50 +20,12 @@
),
)
biome_population = FALSE
high_heat_threshold = 0.15
high_humidity_threshold = 0.15
biome_stamp_size = 60
weighted_mob_spawn_list = list(
SPAWN_MEGAFAUNA = 2,
/obj/effect/spawner/random/lavaland_mob/goliath = 50,
/obj/effect/spawner/random/lavaland_mob/legion = 30,
/obj/effect/spawner/random/lavaland_mob/watcher = 40,
/mob/living/basic/mining/bileworm = 20,
/mob/living/basic/mining/brimdemon = 20,
/mob/living/basic/mining/lobstrosity/lava = 20,
/obj/effect/spawner/random/lavaland_mob/raptor = 15,
/mob/living/basic/mining/goldgrub = 15,
/obj/structure/spawner/lavaland = 2,
/obj/structure/spawner/lavaland/goliath = 3,
/obj/structure/spawner/lavaland/legion = 3,
)
weighted_flora_spawn_list = list(
/obj/structure/flora/ash/cacti = 1,
/obj/structure/flora/ash/cap_shroom = 2,
/obj/structure/flora/ash/fireblossom = 2,
/obj/structure/flora/ash/leaf_shroom = 2,
/obj/structure/flora/ash/seraka = 2,
/obj/structure/flora/ash/stem_shroom = 2,
/obj/structure/flora/ash/tall_shroom = 2,
)
///Note that this spawn list is also in the icemoon generator
weighted_feature_spawn_list = list(
/obj/structure/geyser/hollowwater = 8,
/obj/structure/geyser/plasma_oxide = 8,
/obj/structure/geyser/protozine = 8,
/obj/structure/geyser/random = 2,
/obj/structure/geyser/wittel = 8,
/obj/structure/geyser/chiral_buffer = 8,
/obj/structure/ore_vent/boss = 1,
)
smoothing_iterations = 50
/datum/map_generator/cave_generator/lavaland/ruin_version
biome_population = FALSE
weighted_open_turf_types = list(/turf/open/misc/asteroid/basalt/lava_land_surface/no_ruins = 1)
weighted_closed_turf_types = list(/turf/closed/mineral/volcanic/lava_land_surface/do_not_chasm = 1)
+92 -41
View File
@@ -1,3 +1,8 @@
#define CAVE_SPAWN_MOB "mob"
#define CAVE_SPAWN_FEATURE "feature"
#define CAVE_SPAWN_TENDRIL "tendril"
#define CAVE_SPAWN_MEGAFAUNA "megafauna"
///This datum handles the transitioning from a turf to a specific biome, and handles spawning decorative structures and mobs.
/datum/biome
/// Type of turf this biome creates for open turfs
@@ -19,7 +24,15 @@
/// Weighted list of type paths of fauna that can be spawned when the
/// turf spawns fauna.
var/list/fauna_types = list()
/// Weighted list of megafauna that can spawn from SPAWN_MEGAFAUNA fauna entry
/// Defaults to GLOB.megafauna_spawn_list
var/list/megafauna_types = null
/// Radius around features within which we avoid spawning other features
var/feature_exclusion_radius = 7
/// Radius around mobs which we avoid spawning other mobs
var/mob_exclusion_radius = 12
/// Radius around megafauna within which we avoid spawning tendrils
var/megafauna_exclusion_radius = 7
/datum/biome/New()
. = ..()
@@ -32,6 +45,8 @@
if(length(feature_types))
feature_types = expand_weights(feature_types)
if(isnull(megafauna_types))
megafauna_types = GLOB.megafauna_spawn_list
///This proc handles the creation of a turf of a specific biome type
/datum/biome/proc/generate_turf(turf/gen_turf, closed)
@@ -107,57 +122,93 @@
if(!has_flora && !has_features && !has_fauna)
return
// Assoc list of spawned categories to
// Static as to be shared between all biomes
var/static/list/spawn_data
if (isnull(spawn_data))
spawn_data = list(
CAVE_SPAWN_FEATURE = list(),
CAVE_SPAWN_TENDRIL = list(),
CAVE_SPAWN_MOB = list(),
CAVE_SPAWN_MEGAFAUNA = list(),
)
for(var/turf/target_turf as anything in target_turfs)
// We do the CHECK_TICK here because there's a bunch of continue calls
// in this.
// Only put stuff on open turfs we generated, so closed walls and rivers and stuff are skipped
if(!istype(target_turf, open_turf_type))
continue
CHECK_TICK
if (!(target_turf.turf_flags & TURF_BLOCKS_POPULATE_TERRAIN_FLORAFEATURES))
if (flora_allowed && prob(flora_density))
var/flora_type = pick(flora_types)
new flora_type(target_turf)
continue
if(istype(target_turf, closed_turf_type))
if (features_allowed && prob(feature_density))
var/can_spawn = TRUE
for(var/turf/spawn_turf as anything in spawn_data[CAVE_SPAWN_FEATURE])
if (get_dist(spawn_turf, target_turf) <= feature_exclusion_radius)
can_spawn = FALSE
break
if (can_spawn)
var/picked_feature = pick(feature_types)
new picked_feature(target_turf)
spawn_data[CAVE_SPAWN_FEATURE] += target_turf
continue
if (!fauna_allowed || !prob(fauna_density))
continue
if(has_flora && prob(flora_density))
var/obj/structure/flora = pick(flora_types)
new flora(target_turf)
continue
var/picked_mob = pick(fauna_types)
var/is_megafauna = FALSE
if (picked_mob == SPAWN_MEGAFAUNA)
picked_mob = pick_weight(megafauna_types)
is_megafauna = TRUE
if(has_features && prob(feature_density))
var/can_spawn = TRUE
var/atom/picked_feature = pick(feature_types)
var/list/features_in_range = range(7, target_turf)
for(var/obj/structure/existing_feature in features_in_range)
if(istype(existing_feature, picked_feature))
var/can_spawn = TRUE
if(ispath(picked_mob, /obj/structure/spawner/lavaland))
// Prevents tendrils spawning in each other's collapse range
for(var/turf/spawn_turf as anything in spawn_data[CAVE_SPAWN_TENDRIL])
if (get_dist(spawn_turf, target_turf) <= 2)
can_spawn = FALSE
break
if(can_spawn)
new picked_feature(target_turf)
continue
if(has_fauna && prob(fauna_density))
var/mob/picked_mob = pick(fauna_types)
// prevents tendrils spawning in each other's collapse range
if(ispath(picked_mob, /obj/structure/spawner/lavaland))
var/blocked = FALSE
for(var/obj/structure/spawner/lavaland/spawn_blocker in range(2, target_turf))
blocked = TRUE
// Also avoid spawning them next to megafauna
for(var/turf/spawn_turf as anything in spawn_data[CAVE_SPAWN_MEGAFAUNA])
if (get_dist(spawn_turf, target_turf) <= megafauna_exclusion_radius)
can_spawn = FALSE
break
else if (is_megafauna)
// Megafauna can spawn wherever it wants as long as its not next to another mega
for(var/turf/spawn_turf as anything in spawn_data[CAVE_SPAWN_MEGAFAUNA])
if (get_dist(spawn_turf, target_turf) <= megafauna_exclusion_radius)
can_spawn = FALSE
break
else
for(var/turf/spawn_turf as anything in spawn_data[CAVE_SPAWN_MOB])
if (get_dist(spawn_turf, target_turf) <= mob_exclusion_radius)
can_spawn = FALSE
break
if(blocked)
continue
if (!can_spawn)
continue
// if the random is not a tendril (hopefully meaning it is a mob), avoid spawning if there's another one within 12 tiles
else
var/list/things_in_range = range(12, target_turf)
var/blocked = FALSE
for(var/mob/living/mob_blocker in things_in_range)
if(ismining(mob_blocker))
blocked = TRUE
break
if (ispath(picked_mob, /obj/structure/spawner/lavaland))
spawn_data[CAVE_SPAWN_TENDRIL] += target_turf
else
if (is_megafauna)
spawn_data[CAVE_SPAWN_MEGAFAUNA] += target_turf
spawn_data[CAVE_SPAWN_MOB] += target_turf
if(blocked)
continue
new picked_mob(target_turf)
new picked_mob(target_turf)
// There can be only one Bubblegum, so don't waste spawns on it
if(ispath(picked_mob, /mob/living/simple_animal/hostile/megafauna/bubblegum))
megafauna_types.Remove(picked_mob)
#undef CAVE_SPAWN_MOB
#undef CAVE_SPAWN_FEATURE
#undef CAVE_SPAWN_TENDRIL
#undef CAVE_SPAWN_MEGAFAUNA
+103
View File
@@ -2,11 +2,114 @@
open_turf_type = /turf/open/misc/asteroid/basalt/lava_land_surface
closed_turf_type = /turf/closed/mineral/volcanic
/datum/biome/lavaland/basalt
closed_turf_type = /turf/closed/mineral/random/volcanic
fauna_density = 6
flora_density = 2.5
feature_density = 0.25
// Legions and goliaths, but fewer watchers and no demons
fauna_types = list(
SPAWN_MEGAFAUNA = 2,
/obj/effect/spawner/random/lavaland_mob/goliath = 50,
/obj/effect/spawner/random/lavaland_mob/legion = 40,
/obj/effect/spawner/random/lavaland_mob/watcher = 20,
/mob/living/basic/mining/bileworm = 10,
/mob/living/basic/mining/lobstrosity/lava = 20,
/obj/effect/spawner/random/lavaland_mob/raptor = 15,
/mob/living/basic/mining/goldgrub = 15,
/obj/structure/spawner/lavaland = 1,
/obj/structure/spawner/lavaland/goliath = 3,
/obj/structure/spawner/lavaland/legion = 3,
)
flora_types = list(
/obj/structure/flora/ash/cap_shroom = 3,
/obj/structure/flora/ash/fireblossom = 1,
/obj/structure/flora/ash/stem_shroom = 1,
/obj/structure/flora/rock/style_random = 1,
/obj/structure/flora/rock/pile/style_random = 3,
/obj/structure/flora/rock/volcano = 1,
)
feature_types = list(
/obj/structure/geyser/plasma_oxide = 8,
/obj/structure/geyser/protozine = 8,
/obj/structure/geyser/wittel = 8,
/obj/structure/geyser/random = 3,
/obj/structure/ore_vent/boss = 1,
)
/datum/biome/lavaland/shale
open_turf_type = /turf/open/misc/asteroid/basalt/smooth/shale/lava_land_surface
closed_turf_type = /turf/closed/mineral/random/volcanic/shale
flora_density = 5
// Higher chance of lobstrocities, goldgrubs and brimdemons, but no bileworms
fauna_types = list(
SPAWN_MEGAFAUNA = 2,
/obj/effect/spawner/random/lavaland_mob/goliath = 40,
/obj/effect/spawner/random/lavaland_mob/legion = 20,
/obj/effect/spawner/random/lavaland_mob/watcher = 30,
/mob/living/basic/mining/brimdemon = 40,
/mob/living/basic/mining/lobstrosity/lava = 40,
/obj/effect/spawner/random/lavaland_mob/raptor = 15,
/mob/living/basic/mining/goldgrub = 35,
/obj/structure/spawner/lavaland = 2,
/obj/structure/spawner/lavaland/goliath = 3,
/obj/structure/spawner/lavaland/legion = 3,
)
flora_types = list(
/obj/structure/flora/ash/fireblossom = 2,
/obj/structure/flora/ash/leaf_shroom = 2,
/obj/structure/flora/ash/seraka = 2,
/obj/structure/flora/ash/glowgrowth = 2,
/obj/structure/flora/rock/pile/shale/style_random = 5,
)
feature_types = list(
/obj/structure/geyser/hollowwater = 12,
/obj/structure/geyser/plasma_oxide = 12,
/obj/structure/geyser/random = 3,
/obj/structure/ore_vent/boss = 1,
)
/datum/biome/lavaland/red_rock
open_turf_type = /turf/open/misc/asteroid/basalt/smooth/siderite/lava_land_surface
closed_turf_type = /turf/closed/mineral/random/volcanic/red_rock
flora_density = 4
// Bileworms, raptors and watchers, but few goliaths
fauna_types = list(
SPAWN_MEGAFAUNA = 2,
/obj/effect/spawner/random/lavaland_mob/goliath = 20,
/obj/effect/spawner/random/lavaland_mob/legion = 25,
/obj/effect/spawner/random/lavaland_mob/watcher = 55,
/mob/living/basic/mining/bileworm = 35,
/mob/living/basic/mining/brimdemon = 15,
/mob/living/basic/mining/lobstrosity/lava = 25,
/obj/effect/spawner/random/lavaland_mob/raptor = 20,
/mob/living/basic/mining/goldgrub = 15,
/obj/structure/spawner/lavaland = 2,
/obj/structure/spawner/lavaland/goliath = 1,
/obj/structure/spawner/lavaland/legion = 3,
)
flora_types = list(
/obj/structure/flora/ash/cacti = 2,
/obj/structure/flora/ash/cap_shroom = 1,
/obj/structure/flora/ash/tall_shroom = 2,
/obj/structure/flora/ash/stem_shroom = 2,
/obj/structure/flora/rock/pile/siderite/style_random = 4,
/obj/structure/flora/rock/siderite_growth = 1,
)
feature_types = list(
/obj/structure/geyser/protozine = 12,
/obj/structure/geyser/chiral_buffer = 12,
/obj/structure/geyser/random = 3,
/obj/structure/ore_vent/boss = 1,
)