diff --git a/code/__DEFINES/maps.dm b/code/__DEFINES/maps.dm index 6db07a1c7e4..bf9ed51f9dc 100644 --- a/code/__DEFINES/maps.dm +++ b/code/__DEFINES/maps.dm @@ -76,3 +76,13 @@ Last space-z level = empty #define CAMERA_LOCK_MINING 2 #define CAMERA_LOCK_CENTCOM 4 #define CAMERA_LOCK_REEBE 8 + + +//Ruin Generation + +#define PLACEMENT_TRIES 100 //How many times we try to fit the ruin somewhere until giving up (really should just swap to some packing algo) + +#define PLACE_DEFAULT "random" +#define PLACE_SAME_Z "same" +#define PLACE_SPACE_RUIN "space" +#define PLACE_LAVA_RUIN "lavaland" \ No newline at end of file diff --git a/code/datums/ruins.dm b/code/datums/ruins.dm index aa46c08a6ce..2b33a9b843b 100644 --- a/code/datums/ruins.dm +++ b/code/datums/ruins.dm @@ -6,8 +6,13 @@ How is there a wooden container filled with 18th century coinage in the middle of a lavawracked hellscape? \ It is clearly a mystery." - var/cost = null //negative numbers will always be placed, with lower (negative) numbers being placed first; positive and 0 numbers will be placed randomly + var/unpickable = FALSE //If TRUE these won't be placed automatically (can still be forced or loaded with another ruin) + var/always_place = FALSE //Will skip the whole weighting process and just plop this down, ideally you want the ruins of this kind to have no cost. + var/placement_weight = 1 //How often should this ruin appear + var/cost = 0 //Cost in ruin budget placement system var/allow_duplicates = TRUE + var/list/always_spawn_with = null //These ruin types will be spawned along with it (where dependent on the flag) eg list(/datum/map_template/ruin/space/teleporter_space = SPACERUIN_Z) + var/list/never_spawn_with = null //If this ruin is spawned these will not eg list(/datum/map_template/ruin/base_alternate) var/prefix = null var/suffix = null diff --git a/code/datums/ruins/lavaland.dm b/code/datums/ruins/lavaland.dm index 5c646491c83..8817a1ff96c 100644 --- a/code/datums/ruins/lavaland.dm +++ b/code/datums/ruins/lavaland.dm @@ -120,7 +120,7 @@ id = "hierophant" description = "A strange, square chunk of metal of massive size. Inside awaits only death and many, many squares." suffix = "lavaland_surface_hierophant.dmm" - cost = -1 + always_place = TRUE allow_duplicates = FALSE /datum/map_template/ruin/lavaland/blood_drunk_miner diff --git a/code/modules/mapping/ruins.dm b/code/modules/mapping/ruins.dm index 89d9d07a14b..996d89a1e2a 100644 --- a/code/modules/mapping/ruins.dm +++ b/code/modules/mapping/ruins.dm @@ -1,3 +1,39 @@ +/datum/map_template/ruin/proc/try_to_place(z,allowed_areas) + var/sanity = PLACEMENT_TRIES + 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 = locate(rand(width_border, world.maxx - width_border), rand(height_border, world.maxy - height_border), z) + var/valid = TRUE + + for(var/turf/check in get_affected_turfs(central_turf,1)) + var/area/new_area = get_area(check) + if(!(istype(new_area, allowed_areas)) || check.flags_1 & NO_RUINS_1) + valid = FALSE + break + + if(!valid) + continue + + log_world("Ruin \"[name]\" placed at ([central_turf.x], [central_turf.y], [central_turf.z])") + + for(var/i in get_affected_turfs(central_turf, 1)) + var/turf/T = i + for(var/mob/living/simple_animal/monster in T) + qdel(monster) + for(var/obj/structure/flora/ash/plant in T) + qdel(plant) + + load(central_turf,centered = TRUE) + loaded++ + + for(var/turf/T in get_affected_turfs(central_turf, 1)) + T.flags_1 |= NO_RUINS_1 + + new /obj/effect/landmark/ruin(central_turf, src) + return TRUE + return FALSE /proc/seedRuins(list/z_levels = null, budget = 0, whitelist = /area/space, list/potentialRuins) @@ -11,98 +47,89 @@ WARNING("Z level [zl] does not exist - Not generating ruins") return - var/overall_sanity = 100 var/list/ruins = potentialRuins.Copy() - var/is_picking = FALSE - var/last_checked_ruin_index = 0 - while(budget > 0 && overall_sanity > 0) - // Pick a ruin - var/datum/map_template/ruin/ruin = null - if(ruins && ruins.len) - last_checked_ruin_index++ //ruins with no cost come first in the ruin list, so they'll get picked really often - if(is_picking) - ruin = ruins[pick(ruins)] - else - var/ruin_key = ruins[last_checked_ruin_index] //get the ruin's key via index - ruin = ruins[ruin_key] //use that key to get the ruin datum itself - if(ruin.cost >= 0) //if it has a non-negative cost, cancel out and pick another, to ensure true randomness - is_picking = TRUE - ruin = ruins[pick(ruins)] - else - log_world("Ruin loader had no ruins to pick from with [budget] left to spend.") - break - // Can we afford it - if(ruin.cost > budget) - overall_sanity-- + var/list/forced_ruins = list() //These go first on the z level associated (same random one by default) + var/list/ruins_availible = list() //we can try these in the current pass + var/forced_z //If set we won't pick z level and use this one instead. + + //Set up the starting ruin list + for(var/key in ruins) + var/datum/map_template/ruin/R = ruins[key] + if(R.cost > budget) //Why would you do that continue - // If so, try to place it - var/sanity = 100 - // And if we can't fit it anywhere, give up, try again + if(R.always_place) + forced_ruins[R] = -1 + if(R.unpickable) + continue + ruins_availible[R] = R.placement_weight - while(sanity > 0) - sanity-- - var/width_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(ruin.width / 2) - var/height_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(ruin.height / 2) - var/z_level = pick(z_levels) - var/turf/T = locate(rand(width_border, world.maxx - width_border), rand(height_border, world.maxy - height_border), z_level) - var/valid = TRUE + while(budget > 0 && (ruins_availible.len || forced_ruins.len)) + var/datum/map_template/ruin/current_pick + var/forced = FALSE + 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(forced_ruins[ruin] > 0) //Load into designated z + forced_z = forced_ruins[ruin] + forced = TRUE + break + else //Otherwise just pick random one + current_pick = pickweight(ruins_availible) - for(var/turf/check in ruin.get_affected_turfs(T,1)) - var/area/new_area = get_area(check) - if(!(istype(new_area, whitelist)) || check.flags_1 & NO_RUINS_1) - valid = FALSE - break - - if(!valid) + var/placement_tries = PLACEMENT_TRIES + var/failed_to_place = TRUE + var/z_placed = 0 + while(placement_tries > 0) + placement_tries-- + z_placed = pick(z_levels) + if(!current_pick.try_to_place(forced_z ? forced_z : z_placed,whitelist)) continue + else + failed_to_place = FALSE + break - log_world("Ruin \"[ruin.name]\" placed at ([T.x], [T.y], [T.z])") + //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_availible) + if(R.id == current_pick.id) + ruins_availible -= R + log_world("Failed to place [current_pick.name] ruin.") + else + budget -= current_pick.cost + if(!current_pick.allow_duplicates) + for(var/datum/map_template/ruin/R in ruins_availible) + if(R.id == current_pick.id) + ruins_availible -= R + if(current_pick.never_spawn_with) + for(var/blacklisted_type in current_pick.never_spawn_with) + for(var/possible_exclusion in ruins_availible) + if(istype(possible_exclusion,blacklisted_type)) + ruins_availible -= 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] = forced_z ? forced_z : z_placed //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 + forced_z = 0 - var/obj/effect/ruin_loader/R = new /obj/effect/ruin_loader(T) - R.Load(ruins,ruin) - if(ruin.cost >= 0) - budget -= ruin.cost - if(!ruin.allow_duplicates) - for(var/m in ruins) - var/datum/map_template/ruin/ruin_to_remove = ruins[m] - if(ruin_to_remove.id == ruin.id) //remove all ruins with the same ID, to make sure that ruins with multiple variants work properly - ruins -= ruin_to_remove.name - last_checked_ruin_index-- - break - - if(!overall_sanity) - log_world("Ruin loader gave up with [budget] left to spend.") - - -/obj/effect/ruin_loader - name = "random ruin" - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "syndballoon" - invisibility = 0 - -/obj/effect/ruin_loader/proc/Load(list/potentialRuins, datum/map_template/template) - var/list/possible_ruins = list() - for(var/A in potentialRuins) - var/datum/map_template/T = potentialRuins[A] - if(!T.loaded) - possible_ruins += T - if(!template && possible_ruins.len) - template = safepick(possible_ruins) - if(!template) - return FALSE - var/turf/central_turf = get_turf(src) - for(var/i in template.get_affected_turfs(central_turf, 1)) - var/turf/T = i - for(var/mob/living/simple_animal/monster in T) - qdel(monster) - for(var/obj/structure/flora/ash/plant in T) - qdel(plant) - template.load(central_turf,centered = TRUE) - template.loaded++ - var/datum/map_template/ruin = template - if(istype(ruin)) - new /obj/effect/landmark/ruin(central_turf, ruin) - - qdel(src) - return TRUE + //Update the availible list + for(var/datum/map_template/ruin/R in ruins_availible) + if(R.cost > budget) + ruins_availible -= R + + log_world("Ruin loader finished with [budget] left to spend.")