Files
Bubberstation/code/modules/mapping/ruins.dm
SimplyLogan 2faa874b3b Speed up Mapper testing with a prefix "maptest_" in VSCode (#93238)
## About The Pull Request

One of the development hell cycles with mapping is how long it takes to
fix quality or run issues with maps.

By adding a prefix called "maptest_" to some of the unit tests it allows
mappers to only target some tests instead of the usual 350+ tests to run
each time or trying to trigger them individually and faffing.

This does not rename the unit test files themselves to preserve history
but just the "/datum/unit_test/" in each file.

This does not break the current CI or obstruct other tests - A few other
map files that call these tests specifically have been edited to point
at the new datum name.

This assumes you are using the TG testing extension to do this.

| All Tests | maptest_ |
|--------|--------|
| <img width="426" height="106" alt="image"
src="https://github.com/user-attachments/assets/d1d6f81e-16bd-473a-88da-e8b56f8bd3d0"
/> | <img width="434" height="96" alt="image"
src="https://github.com/user-attachments/assets/ea1c47fe-a6ce-40c6-b2cb-65b9c8e94a29"
/> |
| <img width="360" height="886" alt="image"
src="https://github.com/user-attachments/assets/65bcd774-79ad-432e-8211-c67fb9d3e443"
/> | <img width="370" height="833" alt="Screenshot 2025-10-01 204609"
src="https://github.com/user-attachments/assets/ad360796-5698-42fd-bd2e-51de1a02ab87"
/> |
## Why It's Good For The Game

- Should make it easier for mappers to test locally, saving CI/Github
resource for TG
- Mappers can now test their work 56% faster
## Changelog
🆑
code: Mappers can now run just mapping unit tests - Should be 56% faster
- Should have no player impact
/🆑

Co-authored-by: loganuk <falseemail@aol.com>
2025-10-05 16:25:51 -06:00

217 lines
8.7 KiB
Plaintext

/datum/map_template/ruin/proc/try_to_place(z, list/allowed_areas_typecache, turf/forced_turf, clear_below)
var/sanity = forced_turf ? 1 : PLACEMENT_TRIES
if(SSmapping.level_trait(z,ZTRAIT_ISOLATED_RUINS))
return place_on_isolated_level(z)
while(sanity > 0)
sanity--
var/width_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(width / 2)
var/height_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(height / 2)
var/turf/central_turf = forced_turf ? forced_turf : locate(rand(width_border, world.maxx - width_border), rand(height_border, world.maxy - height_border), z)
var/valid = TRUE
var/list/affected_turfs = get_affected_turfs(central_turf,1)
var/list/affected_areas = list()
for(var/turf/check in affected_turfs)
// Use assoc lists to move this out, it's easier that way
if(check.turf_flags & NO_RUINS)
valid = FALSE // set to false before we check
break
var/area/new_area = get_area(check)
affected_areas[new_area] = TRUE
// This is faster yes. Only BARELY but it is faster
for(var/area/affct_area as anything in affected_areas)
if(!allowed_areas_typecache[affct_area.type])
valid = FALSE
break
if(!valid)
continue
testing("Ruin \"[name]\" placed at ([central_turf.x], [central_turf.y], [central_turf.z])")
if(clear_below)
var/static/list/clear_below_typecache = typecacheof(list(
/obj/structure/spawner,
/mob/living/simple_animal,
/obj/structure/flora
))
for(var/turf/T as anything in affected_turfs)
for(var/atom/thing as anything in T)
if(clear_below_typecache[thing.type])
qdel(thing)
load(central_turf,centered = TRUE)
loaded++
for(var/turf/T in affected_turfs)
T.turf_flags |= NO_RUINS
new /obj/effect/landmark/ruin(central_turf, src)
return central_turf
/datum/map_template/ruin/proc/place_on_isolated_level(z)
var/datum/turf_reservation/reservation = SSmapping.request_turf_block_reservation(width, height, 1, z) //Make the new level creation work with different traits.
if(!reservation)
return
var/turf/placement = reservation.bottom_left_turfs[1]
load(placement)
loaded++
for(var/turf/T in get_affected_turfs(placement))
T.turf_flags |= NO_RUINS
var/turf/center = locate(placement.x + round(width/2),placement.y + round(height/2),placement.z)
new /obj/effect/landmark/ruin(center, src)
return center
/**
* Loads the ruins for a given z level.
* @param z_levels The z levels to load ruins on.
* @param budget The budget to spend on ruins. Compare against the cost of the ruins in /datum/map_template/ruin.
* @param whitelist A list of areas to allow ruins to be placed in.
* @param potentialRuins A list of ruins to choose from.
* @param clear_below Whether to clear the area below the ruin. Used for multiz ruins.
* @param mineral_budget The budget to spend on ruins that spawn ore vents. Map templates with vents have that defined by mineral_cost.
* @param mineral_budget_update What type of ore distribution should spawn from ruins picked by this cave generator? This list is copied from ores_spawned.dm into SSore_generation.ore_vent_minerals.
* @param ruin_type The type of ruins that are spawning (ZTRAIT_SPACE_RUINS, ZTRAIT_ICE_RUINS, ZTRAIT_LAVA_RUINS, etc.)
*/
/proc/seedRuins(list/z_levels = null, budget = 0, whitelist = list(/area/space), list/potentialRuins, clear_below = FALSE, mineral_budget = 15, mineral_budget_update, ruins_type = ZTRAIT_STATION)
if(!z_levels || !z_levels.len)
WARNING("No Z levels provided - Not generating ruins")
return
var/list/whitelist_typecache = typecacheof(whitelist)
for(var/zl in z_levels)
var/turf/T = locate(1, 1, zl)
if(!T)
WARNING("Z level [zl] does not exist - Not generating ruins")
return
var/list/ruins = potentialRuins.Copy()
var/placed_ruins = 0 // our count of how many ruins have been placed
var/list/forced_ruins = list() //These go first on the z level associated (same random one by default) or if the assoc value is a turf to the specified turf.
var/list/ruins_available = list() //we can try these in the current pass
if(PERFORM_ALL_TESTS(maptest_log_mapping))
log_mapping("All ruins being loaded for map testing.")
switch(mineral_budget_update) //If we use more map configurations, add another case
if(OREGEN_PRESET_LAVALAND)
SSore_generation.ore_vent_minerals = expand_weights(GLOB.ore_vent_minerals_lavaland)
if(OREGEN_PRESET_TRIPLE_Z)
SSore_generation.ore_vent_minerals = expand_weights(GLOB.ore_vent_minerals_triple_z)
//Set up the starting ruin list
for(var/key in ruins)
var/datum/map_template/ruin/R = ruins[key]
if(PERFORM_ALL_TESTS(maptest_log_mapping))
R.cost = 0
R.allow_duplicates = FALSE // no multiples for testing
R.always_place = !R.unpickable // unpickable ruin means it spawns as a set with another ruin
if(R.cost > budget || R.mineral_cost > mineral_budget) //Why would you do that
continue
if(R.always_place)
forced_ruins[R] = -1
if(R.unpickable)
continue
ruins_available[R] = R.placement_weight
while(((budget > 0 || mineral_budget > 0) && ruins_available.len) || forced_ruins.len)
var/datum/map_template/ruin/current_pick
var/forced = FALSE
var/forced_z //If set we won't pick z level and use this one instead.
var/forced_turf //If set we place the ruin centered on the given turf
if(forced_ruins.len) //We have something we need to load right now, so just pick it
for(var/ruin in forced_ruins)
current_pick = ruin
if(isturf(forced_ruins[ruin]))
var/turf/T = forced_ruins[ruin]
forced_z = T.z //In case of chained ruins
forced_turf = T
else if(forced_ruins[ruin] > 0) //Load into designated z
forced_z = forced_ruins[ruin]
forced = TRUE
break
else //Otherwise just pick random one
current_pick = pick_weight(ruins_available)
var/placement_tries = forced_turf ? 1 : PLACEMENT_TRIES //Only try once if we target specific turf
var/failed_to_place = TRUE
var/target_z = 0
var/turf/placed_turf //Where the ruin ended up if we succeeded
outer:
while(placement_tries > 0)
placement_tries--
target_z = pick(z_levels)
if(forced_z)
target_z = forced_z
if(current_pick.always_spawn_with) //If the ruin has part below, make sure that z exists.
for(var/v in current_pick.always_spawn_with)
if(current_pick.always_spawn_with[v] == PLACE_BELOW)
var/turf/T = locate(1,1,target_z)
if(!GET_TURF_BELOW(T))
if(forced_z)
continue outer
else
break outer
placed_turf = current_pick.try_to_place(target_z,whitelist_typecache,forced_turf,clear_below)
if(!placed_turf)
continue
else
failed_to_place = FALSE
break
//That's done remove from priority even if it failed
if(forced)
//TODO : handle forced ruins with multiple variants
forced_ruins -= current_pick
forced = FALSE
if(failed_to_place)
for(var/datum/map_template/ruin/R in ruins_available)
if(R.id == current_pick.id)
ruins_available -= R
log_mapping("Failed to place [current_pick.name] ruin!")
else
placed_ruins++
budget -= current_pick.cost
mineral_budget -= current_pick.mineral_cost
if(!current_pick.allow_duplicates)
for(var/datum/map_template/ruin/R in ruins_available)
if(R.id == current_pick.id)
ruins_available -= R
if(current_pick.never_spawn_with)
for(var/blacklisted_type in current_pick.never_spawn_with)
for(var/possible_exclusion in ruins_available)
if(istype(possible_exclusion,blacklisted_type))
ruins_available -= possible_exclusion
if(current_pick.always_spawn_with)
for(var/v in current_pick.always_spawn_with)
for(var/ruin_name in SSmapping.ruins_templates) //Because we might want to add space templates as linked of lava templates.
var/datum/map_template/ruin/linked = SSmapping.ruins_templates[ruin_name] //why are these assoc, very annoying.
if(istype(linked,v))
switch(current_pick.always_spawn_with[v])
if(PLACE_SAME_Z)
forced_ruins[linked] = target_z //I guess you might want a chain somehow
if(PLACE_LAVA_RUIN)
forced_ruins[linked] = pick(SSmapping.levels_by_trait(ZTRAIT_LAVA_RUINS))
if(PLACE_SPACE_RUIN)
forced_ruins[linked] = pick(SSmapping.levels_by_trait(ZTRAIT_SPACE_RUINS))
if(PLACE_DEFAULT)
forced_ruins[linked] = -1
if(PLACE_BELOW)
forced_ruins[linked] = GET_TURF_BELOW(placed_turf)
if(PLACE_ISOLATED)
forced_ruins[linked] = SSmapping.get_isolated_ruin_z()
log_mapping("Successfully placed [current_pick.name] ruin.")
//Update the available list
for(var/datum/map_template/ruin/R in ruins_available)
if(R.cost > budget || R.mineral_cost > mineral_budget)
ruins_available -= R
log_world("[ruins_type] loader finished placing [placed_ruins]/[ruins.len] ruins with [budget] left to spend.")