Files
Batrachophreno 3c2bd9bb45 Overmap hazard rebalance (#21571)
# Summary

This PR rebalances the spawn count and radius of overmap hazard
clusters, as well as adjusts some of their overmap movement behavior.

## Justification

Mining trips to exoplanets distant to the Horizon can force miners to
spend large portions of a given round just trying to navigate around
overmap hazards; in the worst cases, the sheer number of dead ends and
blind spots can result in their effective removal from the first 60-90
minutes of a round, and the worst delays can obviate the need for their
materials in the first place. While reducing these hazards would also
benefit the Horizon and offships, the primary motivation was to reduce
mining's "dead time" in any given round.

## Details

Most overmap hazard types have had their 'count' (the number of clusters
spawned) and radius reduced, to open up more navigable space on the
overmap. This has been compensated for by modifying movement
probabilities, speeds, and behaviors of overmap hazards.

The intervals between overmap hazards moving (their effective speeds)
has been made variable; different clusters of movable hazards will no
longer always proc movement at the same time.

Carp continue to have a random chance to move in a random direction
every interval. Space dust and meteors now also have a chance to spawn
in as movable hazards, but they will only ever move in one direction.
While more dangerous to passing shuttles and ships than carp, they are
also much more predictable.

The current numbers were based on observation of several randomly
generated maps during testing, but will likely need adjustment as it
faces actual miners and we accumulate their feedback. I erred on the
side of 'less difficult' to avoid overtuned rocks from smashing crew to
pieces every round.
2025-11-13 18:42:06 +00:00

357 lines
16 KiB
Plaintext

/datum/map
var/name = "Unnamed Map"
var/full_name = "Unnamed Map"
/// Basic info about the map. Shows up in the new player options.
var/description
var/path
/**
* A list of traits for the zlevels of the map
*
* Each element is one zlevel, starting from the bottom one up
*/
var/list/traits = list()
/// Z-levels for admin functionality (Centcom, shuttle transit, etc)
var/list/admin_levels = list()
/// Z-levels that can be contacted from the station, for eg announcements
var/list/contact_levels = list()
/// Z-levels a character can typically reach
var/list/player_levels = list()
/// Z-levels that don't allow random transit at edge
var/list/sealed_levels = list()
/// Z-levels that dont allow ghosts to randomly move around
var/list/restricted_levels = list()
/// Empty Z-levels that may be used for various things (currently used by bluespace jump)
var/list/empty_levels = null
/// Z-levels available to various consoles, such as the crew monitor. Defaults to station_levels if unset.
var/list/map_levels
/// Custom base turf by Z-level. Defaults to world.turf for unlisted Z-levels
var/list/base_turf_by_z = list()
/// This list contains the z-level numbers which can be accessed via space travel and the percentile chances to get there.
var/list/accessible_z_levels = list()
var/list/allowed_jobs
//Job datums to use.
//Works a lot better so if we get to a point where three-ish maps are used
//We don't have to C&P ones that are only common between two of them
//That doesn't mean we have to include them with the rest of the jobs though, especially for map specific ones.
//Also including them lets us override already created jobs, letting us keep the datums to a minimum mostly.
//This is probably a lot longer explanation than it needs to be.
var/station_name = "BAD Station"
var/station_short = "Baddy"
var/dock_name = "THE PirateBay"
var/dock_short = "Piratebay"
var/boss_name = "Captain Roger"
var/boss_short = "Cap'"
var/company_name = "BadMan"
var/company_short = "BM"
var/station_type = "station"
var/command_spawn_enabled = FALSE
var/command_spawn_message = "Someone didn't fill this in."
var/list/spawn_types
/// if true, calling crew transfer or evac just restarts the round in ten minute
var/shuttle_call_restarts = FALSE
var/shuttle_call_restart_timer
var/shuttle_docked_message
var/shuttle_leaving_dock
var/shuttle_called_message
var/shuttle_recall_message
var/emergency_shuttle_docked_message
var/emergency_shuttle_leaving_dock
var/emergency_shuttle_recall_message
var/emergency_shuttle_called_message
var/bluespace_docked_message
var/bluespace_leaving_dock
var/bluespace_called_message
var/bluespace_recall_message
/// The typepath of the visitable our main map is, for example /obj/effect/overmap/visitable/ship/sccv_horizon
var/overmap_visitable_type
/// If this map has ports of call and refuels there. Crew are implied to be able to leave to these ports.
/// Ports of call are taken from the current map sector.
var/ports_of_call = FALSE
var/evac_controller_type = /datum/evacuation_controller
/// Camera networks that will show up on the console.
var/list/station_networks = list()
/// map of string ids to /datum/holodeck_program instances
var/list/holodeck_programs = list()
/**
* map of maps - first level maps from list-of-programs string id (e.g. "BarPrograms") to another map
* this is in order to support multiple holodeck program listings for different holodecks
* second level maps from program friendly display names ("Picnic Area") to program string ids ("picnicarea")
* as defined in holodeck_programs
*/
var/list/holodeck_supported_programs = list()
/// as above... but EVIL!
var/list/holodeck_restricted_programs = list()
var/force_spawnpoint = FALSE
var/allowed_spawns = list("Arrivals Shuttle","Gateway", "Cryogenic Storage", "Cyborg Storage")
var/default_spawn = "Arrivals Shuttle"
var/list/lobby_icons = list() // The icons which contains the lobby images. A dmi is picked at random.
var/lobby_icon // This is what the game uses to store the chosen dmi.
var/list/lobby_screens = list() // The list of lobby screen to pick() from. Leave this unset to fill from the lobby icon DMI.
/// If a number, transition between the lobby screens with this delay instead of picking just one.
var/lobby_transitions = FALSE
/// If overmap should be used (including overmap space travel override)
var/use_overmap = FALSE
/// Dimensions of overmap zlevel if overmap is used.
var/overmap_size = 20
/// If 0 will generate overmap zlevel on init. Otherwise will populate the zlevel provided.
var/overmap_z = 0
/// How many event "clouds" will be generated
var/overmap_event_areas = 0
/// A list of all our shuttles.
var/list/map_shuttles = list()
/// What is the default space sector for this map
var/default_sector = SECTOR_ROMANOVICH
//event messages
var/meteors_detected_message = "A meteor storm has been detected on collision course with the station. Estimated three minutes until impact, please activate station shields, and seek shelter in the central ring."
var/meteor_contact_message = "Contact with meteor wave imminent, all hands brace for impact! Repeat, all hands brace for impact!"
var/meteor_end_message = "The station has cleared the meteor shower, please return to your stations."
var/ship_meteor_contact_message = "The NDV Icarus reports that it has downed an unknown vessel that was approaching your station. Prepare for debris impact - please evacuate the surface level if needed."
var/ship_detected_end_message = "Ship debris colliding now, all hands brace for impact."
var/ship_meteor_end_message = "The last of the ship debris has hit or passed by the station, it is now safe to commence repairs."
var/dust_detected_message = "A belt of space dust is approaching the station."
var/dust_contact_message = "The station is now passing through a belt of space dust."
var/dust_end_message = "The station has now passed through the belt of space dust."
var/radiation_detected_message = "High levels of radiation detected near the station. Please evacuate into one of the shielded maintenance tunnels."
var/radiation_contact_message = "The station has entered the radiation belt. Please remain in a sheltered area until we have passed the radiation belt."
var/radiation_end_message = "The station has passed the radiation belt. Please report to medbay if you experience any unusual symptoms. Maintenance will lose all-access again shortly."
var/list/rogue_drone_detected_messages = list("A combat drone wing operating out of the NDV Icarus has failed to return from a sweep of this sector, if any are sighted approach with caution.",
"Contact has been lost with a combat drone wing operating out of the NDV Icarus. If any are sighted in the area, approach with caution.",
"Unidentified hackers have targetted a combat drone wing deployed from the NDV Icarus. If any are sighted in the area, approach with caution.")
var/rogue_drone_end_message = "Icarus drone control reports the malfunctioning wing has been recovered safely."
var/rogue_drone_destroyed_message = "Icarus drone control registers disappointment at the loss of the drones, but the survivors have been recovered."
var/num_exoplanets = 0
///Dimensions of planet zlevel, defaults to world size. Due to how maps are generated, must be (2^n+1) e.g. 17,33,65,129 etc. Map will just round up to those if set to anything other.
var/list/planet_size
var/min_offmap_players = 0
var/away_site_budget = 0
var/away_ship_budget = 0
///How much higher the budgets can randomly go
var/away_variance = 0
///This controls if borgs can leave the station or ship without exploding
var/allow_borgs_to_leave = FALSE
///This controls where the cargospawner tries to populate warehouse items
var/area/warehouse_basearea
/// used to handle spawnpoints for the packages that spawned after Initialize. See: `receptacle.dm`.
var/area/warehouse_packagearea
/**
* A list of the shuttles on this map, used by the Shuttle Manifest program to populate itself.
* Formatted with the shuttle name as an index, followed by a list containing the color and icon to be used for the shuttle's drop down
* On the manifest. i.e. "SCCV Intrepid" = list("color" = "purple", "icon" = "compass")
*/
var/list/shuttle_manifests = list()
/**
* A list of the missions shuttles can select for their assignment in the Shuttle Manifest program.
*/
var/list/shuttle_missions = list()
/datum/map/New()
if(!map_levels)
map_levels = SSmapping.levels_by_trait(ZTRAIT_STATION)
if(!allowed_jobs)
allowed_jobs = subtypesof(/datum/job)
for(var/thing in EVENT_ROLES) //ideally this should prevent event roles from being open on the horizon
allowed_jobs.Remove(thing)
if (!spawn_types)
spawn_types = subtypesof(/datum/spawnpoint)
if(!LAZYLEN(planet_size))
planet_size = list(world.maxx, world.maxy)
// Override to set custom access requirements for camera networks.
/datum/map/proc/get_network_access(var/network)
return 0
// By default transition randomly to another zlevel
/datum/map/proc/get_transit_zlevel(var/current_z_level)
var/list/candidates = SSatlas.current_map.accessible_z_levels.Copy()
candidates.Remove(num2text(current_z_level))
if(!candidates.len)
return current_z_level
return text2num(pickweight(candidates))
/datum/map/proc/get_empty_zlevel()
if(empty_levels == null)
var/datum/space_level/empty_level = SSmapping.add_new_zlevel("Empty Level", ZTRAITS_AWAY, contain_turfs = FALSE)
empty_levels = list(empty_level.z_value)
return pick(empty_levels)
/datum/map/proc/setup_shuttles()
return
/datum/map/proc/build_exoplanets()
if(!use_overmap)
return
if(!GLOB.config.exoplanets["enable_loading"])
log_admin("Not building exoplanets because the config specifies not to")
return
var/datum/space_sector/sector = SSatlas.current_sector
var/list/possible_exoplanets = sector.possible_exoplanets
var/list/guaranteed_exoplanets = sector.guaranteed_exoplanets
if(length(guaranteed_exoplanets))
for(var/j in guaranteed_exoplanets)
var/guaranteed_exoplanet_type = j
log_module_exoplanets("Building new exoplanet with type: [guaranteed_exoplanet_type] and size: [planet_size[1]] [planet_size[2]]")
var/obj/effect/overmap/visitable/sector/exoplanet/P = new guaranteed_exoplanet_type(null, planet_size[1], planet_size[2])
P.build_level()
if(!length(possible_exoplanets))
log_module_exoplanets("No possible exoplanets found!")
return
var/exoplanets_budget = isnum(GLOB.config.exoplanets["exoplanets_budget"]) ? (GLOB.config.exoplanets["exoplanets_budget"]) : (min(possible_exoplanets.len, num_exoplanets))
for(var/i = 0, i < exoplanets_budget, i++)
//Check that we didn't ran out of exoplanets to make
if(!length(possible_exoplanets))
break
var/exoplanet_type = pick_n_take(possible_exoplanets)
log_module_exoplanets("Building new exoplanet with type: [exoplanet_type] and size: [planet_size[1]] [planet_size[2]]")
var/obj/effect/overmap/visitable/sector/exoplanet/new_planet = new exoplanet_type(null, planet_size[1], planet_size[2])
new_planet.build_level()
/* It is perfectly possible to create loops with TEMPLATE_FLAG_ALLOW_DUPLICATES and force/allow. Don't. */
/proc/resolve_site_selection(datum/map_template/ruin/away_site/site, list/selected, list/available, list/unavailable, list/by_type)
var/spawn_cost = 0
var/player_cost = 0
var/ship_cost = 0
if (site in selected)
if (!(site.template_flags & TEMPLATE_FLAG_ALLOW_DUPLICATES))
return list(spawn_cost, player_cost, ship_cost)
if (!(site.template_flags & TEMPLATE_FLAG_ALLOW_DUPLICATES))
available -= site
spawn_cost += site.spawn_cost
player_cost += site.player_cost
ship_cost += site.ship_cost
selected += site
for (var/forced_type in site.force_ruins)
var/list/costs = resolve_site_selection(by_type[forced_type], selected, available, unavailable, by_type)
spawn_cost += costs[1]
player_cost += costs[2]
ship_cost += costs[3]
for (var/banned_type in site.ban_ruins)
var/datum/map_template/ruin/away_site/banned = by_type[banned_type]
if (banned in unavailable)
continue
unavailable += banned
available -= banned
for (var/allowed_type in site.allow_ruins)
var/datum/map_template/ruin/away_site/allowed = by_type[allowed_type]
if (allowed in available)
continue
if (allowed in unavailable)
continue
if (allowed in selected)
if (!(allowed.template_flags & TEMPLATE_FLAG_ALLOW_DUPLICATES))
continue
available[allowed] = allowed.spawn_weight
return list(spawn_cost, player_cost, ship_cost)
/datum/map/proc/send_welcome()
return
/datum/map/proc/load_holodeck_programs()
return
/datum/map/proc/build_away_sites()
#ifdef UNIT_TEST
log_admin("Unit testing, so not loading away sites")
return // don't build away sites during unit testing
#else
if(!GLOB.config.awaysites["enable_loading"])
log_admin("Not loading away sites because the config specifies not to")
return
log_admin("Loading away sites...")
var/list/guaranteed = list()
var/list/selected = list()
var/list/available = list()
var/list/unavailable = list()
var/list/by_type = list()
for (var/site_id in SSmapping.away_sites_templates)
var/datum/map_template/ruin/away_site/site = SSmapping.away_sites_templates[site_id]
if (((site.template_flags & TEMPLATE_FLAG_SPAWN_GUARANTEED) && (site.spawns_in_current_sector())) || (site_id in GLOB.config.awaysites["guaranteed_sites"]))
guaranteed += site
if ((site.template_flags & TEMPLATE_FLAG_ALLOW_DUPLICATES) && !(site.template_flags & TEMPLATE_FLAG_RUIN_STARTS_DISALLOWED))
available[site] = site.spawn_weight
else if((site.template_flags & TEMPLATE_FLAG_PORT_SPAWN) && (site.spawns_in_current_sector()))
if(SSatlas.is_port_call_day()) //we check here as we only want sites with PORT_SPAWN flag to spawn if this is true, else we want it not considered.
guaranteed += site
if ((site.template_flags & TEMPLATE_FLAG_ALLOW_DUPLICATES) && !(site.template_flags & TEMPLATE_FLAG_RUIN_STARTS_DISALLOWED))
available[site] = site.spawn_weight
else if (!(site.template_flags & TEMPLATE_FLAG_RUIN_STARTS_DISALLOWED) && (site.spawns_in_current_sector()))
available[site] = site.spawn_weight
by_type[site.type] = site
var/points = isnum(GLOB.config.awaysites["away_site_budget"]) ? (GLOB.config.awaysites["away_site_budget"]) : (rand(away_site_budget, away_site_budget + away_variance))
var/players = -min_offmap_players
var/shippoints = isnum(GLOB.config.awaysites["away_ship_budget"]) ? (GLOB.config.awaysites["away_ship_budget"]) : (rand(away_ship_budget, away_ship_budget + away_variance))
var/totalbudget = shippoints + points
for (var/client/C)
++players
for (var/datum/map_template/ruin/away_site/site in guaranteed)
var/list/costs = resolve_site_selection(site, selected, available, unavailable, by_type)
points -= costs[1]
players -= costs[2]
shippoints -= costs[3]
while ((points > 0 || shippoints > 0) && length(available))
var/datum/map_template/ruin/away_site/site = pickweight(available)
if ((site.spawn_cost && site.spawn_cost > points) || (site.player_cost && site.player_cost > players) || (site.ship_cost && site.ship_cost > shippoints))
unavailable += site
available -= site
continue
var/list/costs = resolve_site_selection(site, selected, available, unavailable, by_type)
points -= costs[1]
players -= costs[2]
shippoints -= costs[3]
log_admin("Finished selecting away sites ([english_list(selected)]) for [totalbudget - (points + shippoints)] cost of [totalbudget] budget.")
for(var/datum/map_template/template in selected)
var/bounds = template.load_new_z()
if(bounds)
log_admin("Loaded away site [template]!")
else
log_admin("Failed loading away site [template]!")
#endif