mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-22 12:41:09 +01:00
Merge branch 'master' of https://github.com/tgstation/tgstation into upstream-2025-11-12
This commit is contained in:
@@ -28,3 +28,7 @@
|
||||
if (str_var && str_var[length(str_var)] != "/")
|
||||
str_var += "/"
|
||||
return ..(str_var)
|
||||
|
||||
/datum/config_entry/string/storage_cdn_iframe
|
||||
protection = CONFIG_ENTRY_LOCKED
|
||||
default = "https://tgstation.github.io/byond-client-storage/iframe.html"
|
||||
|
||||
@@ -391,11 +391,11 @@ ADMIN_VERB(cmd_controller_view_ui, R_SERVER|R_DEBUG, "Controller Overview", "Vie
|
||||
// Topological sorting algorithm end
|
||||
|
||||
if(length(subsystems) != length(sorted_subsystems))
|
||||
var/list/circular_dependency = subsystems.Copy() - sorted_subsystems
|
||||
var/list/circular_dependency = subsystems - sorted_subsystems
|
||||
var/list/debug_msg = list()
|
||||
var/list/usr_msg = list()
|
||||
for(var/datum/controller/subsystem/subsystem as anything in circular_dependency)
|
||||
usr_msg += "[subsystem.name]"
|
||||
usr_msg += subsystem.name
|
||||
|
||||
var/list/datum/controller/subsystem/nodes = list(circular_dependency[1])
|
||||
var/list/loop = list()
|
||||
@@ -616,10 +616,19 @@ ADMIN_VERB(cmd_controller_view_ui, R_SERVER|R_DEBUG, "Controller Overview", "Vie
|
||||
if ((SS.flags & (SS_TICKER|SS_BACKGROUND)) == SS_TICKER)
|
||||
tickersubsystems += SS
|
||||
// Timer subsystems aren't allowed to bunch up, so we offset them a bit
|
||||
timer += world.tick_lag * rand(0, 1)
|
||||
timer += TICKS2DS(rand(0, 1))
|
||||
SS.next_fire = timer
|
||||
continue
|
||||
|
||||
// Now, we have to set starting next_fires for all our new non ticker kids
|
||||
if(SS.init_stage == init_stage - 1 && (SS.runlevels & current_runlevel))
|
||||
// Give em a random offset so things don't clump up too bad
|
||||
var/delay = SS.wait
|
||||
if(SS.flags & SS_TICKER)
|
||||
delay = TICKS2DS(delay)
|
||||
// Gotta convert to ticks cause rand needs integers
|
||||
SS.next_fire = world.time + TICKS2DS(rand(0, DS2TICKS(min(delay, 2 SECONDS))))
|
||||
|
||||
var/ss_runlevels = SS.runlevels
|
||||
var/added_to_any = FALSE
|
||||
for(var/I in 1 to GLOB.bitflags.len)
|
||||
@@ -715,7 +724,11 @@ ADMIN_VERB(cmd_controller_view_ui, R_SERVER|R_DEBUG, "Controller Overview", "Vie
|
||||
//we only want to offset it if it's new and also behind
|
||||
if(SS.next_fire > world.time || (SS in old_subsystems))
|
||||
continue
|
||||
SS.next_fire = world.time + world.tick_lag * rand(0, DS2TICKS(min(SS.wait, 2 SECONDS)))
|
||||
// If they're new, give em a random offset so things don't clump up too bad
|
||||
var/delay = SS.wait
|
||||
if(SS.flags & SS_TICKER)
|
||||
delay = TICKS2DS(delay)
|
||||
SS.next_fire = world.time + TICKS2DS(rand(0, DS2TICKS(min(delay, 2 SECONDS))))
|
||||
|
||||
subsystems_to_check = current_runlevel_subsystems
|
||||
else
|
||||
@@ -810,6 +823,8 @@ ADMIN_VERB(cmd_controller_view_ui, R_SERVER|R_DEBUG, "Controller Overview", "Vie
|
||||
if (SS_flags & SS_NO_FIRE)
|
||||
subsystemstocheck -= SS
|
||||
continue
|
||||
// If we're keeping timing and running behind,
|
||||
// fire at most 25% faster then normal to try and make up the gap without spamming
|
||||
if ((SS_flags & (SS_TICKER|SS_KEEP_TIMING)) == SS_KEEP_TIMING && SS.last_fire + (SS.wait * 0.75) > world.time)
|
||||
continue
|
||||
if (SS.postponed_fires >= 1)
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
/// Manages the security cameras and camera chunks
|
||||
SUBSYSTEM_DEF(cameras)
|
||||
name = "Cameras"
|
||||
flags = SS_BACKGROUND
|
||||
priority = FIRE_PRIORITY_CAMERAS
|
||||
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
|
||||
wait = 2 MINUTES
|
||||
dependencies = list(
|
||||
// Required to get plane offset for static images
|
||||
/datum/controller/subsystem/mapping,
|
||||
)
|
||||
|
||||
/// The cameras on the map, no matter if they work or not.
|
||||
/// Updated in obj/machinery/camera.dm in Initialize() and Destroy().
|
||||
var/list/obj/machinery/camera/cameras = list()
|
||||
/// The chunks of the map, mapping the areas that the cameras can see.
|
||||
var/list/chunks = list()
|
||||
/// Chunks that must be updated
|
||||
var/list/chunks_to_update = list()
|
||||
/// List of images cloned by all chunk static images put onto turfs cameras cant see
|
||||
/// Indexed by the plane offset to use
|
||||
var/list/image/obscured_images = list()
|
||||
/// Primarily for debugging, outright prevents all camera chunk updates
|
||||
var/disable_camera_updates = FALSE
|
||||
/// Tracks current subsystem run
|
||||
var/list/current_run = list()
|
||||
|
||||
/datum/controller/subsystem/cameras/Initialize()
|
||||
update_offsets(SSmapping.max_plane_offset)
|
||||
RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, PROC_REF(on_offset_growth))
|
||||
return SS_INIT_SUCCESS
|
||||
|
||||
/datum/controller/subsystem/cameras/fire(resumed = FALSE)
|
||||
if(!resumed)
|
||||
src.current_run = chunks_to_update.Copy()
|
||||
chunks_to_update = list()
|
||||
|
||||
var/list/current_run = src.current_run
|
||||
while(current_run.len)
|
||||
var/datum/camerachunk/chunk = current_run[current_run.len]
|
||||
chunk.force_update(only_if_necessary = TRUE) // Forces an update if necessary
|
||||
current_run.len--
|
||||
if(MC_TICK_CHECK)
|
||||
break
|
||||
|
||||
/datum/controller/subsystem/cameras/stat_entry(msg)
|
||||
msg = "Cams: [length(cameras)] | Chunks: [length(chunks)] | Updating: [length(chunks_to_update)]"
|
||||
return ..()
|
||||
|
||||
/// Updates the images for new plane offsets
|
||||
/datum/controller/subsystem/cameras/proc/update_offsets(new_offset)
|
||||
for(var/i in length(obscured_images) to new_offset)
|
||||
var/image/obscured = new('icons/effects/cameravis.dmi')
|
||||
SET_PLANE_W_SCALAR(obscured, CAMERA_STATIC_PLANE, i)
|
||||
obscured.appearance_flags = RESET_TRANSFORM | RESET_ALPHA | RESET_COLOR | KEEP_APART
|
||||
obscured.override = TRUE
|
||||
obscured_images += obscured
|
||||
|
||||
/datum/controller/subsystem/cameras/proc/on_offset_growth(datum/source, old_offset, new_offset)
|
||||
SIGNAL_HANDLER
|
||||
update_offsets(new_offset)
|
||||
|
||||
/// Checks if a chunk has been generated in x, y, z.
|
||||
/datum/controller/subsystem/cameras/proc/get_camera_chunk(x, y, z)
|
||||
x = GET_CHUNK_COORD(x)
|
||||
y = GET_CHUNK_COORD(y)
|
||||
if(GET_LOWEST_STACK_OFFSET(z) != 0)
|
||||
var/turf/lowest = get_lowest_turf(locate(x, y, z))
|
||||
return chunks["[x],[y],[lowest.z]"]
|
||||
|
||||
return chunks["[x],[y],[z]"]
|
||||
|
||||
// Returns the chunk in the x, y, z.
|
||||
// If there is no chunk, it creates a new chunk and returns that.
|
||||
/datum/controller/subsystem/cameras/proc/generate_chunk(x, y, z)
|
||||
x = GET_CHUNK_COORD(x)
|
||||
y = GET_CHUNK_COORD(y)
|
||||
var/turf/lowest = get_lowest_turf(locate(x, y, z))
|
||||
var/key = "[x],[y],[lowest.z]"
|
||||
. = chunks[key]
|
||||
if(!.)
|
||||
. = new /datum/camerachunk(x, y, lowest.z)
|
||||
chunks[key] = .
|
||||
|
||||
/// Updates what the camera eye can see.
|
||||
/// It is recommended you use this when a camera eye moves or its location is set.
|
||||
/datum/controller/subsystem/cameras/proc/update_eye_chunk(mob/eye/camera/eye)
|
||||
var/list/visibleChunks = list()
|
||||
//Get the eye's turf in case its located in an object like a mecha
|
||||
var/turf/eye_turf = get_turf(eye)
|
||||
if(eye.loc)
|
||||
var/static_range = eye.static_visibility_range
|
||||
var/x1 = max(1, eye_turf.x - static_range)
|
||||
var/y1 = max(1, eye_turf.y - static_range)
|
||||
var/x2 = min(world.maxx, eye_turf.x + static_range)
|
||||
var/y2 = min(world.maxy, eye_turf.y + static_range)
|
||||
|
||||
for(var/x = x1; x <= x2; x += CHUNK_SIZE)
|
||||
for(var/y = y1; y <= y2; y += CHUNK_SIZE)
|
||||
visibleChunks |= generate_chunk(x, y, eye_turf.z)
|
||||
|
||||
var/list/remove = eye.visibleCameraChunks - visibleChunks
|
||||
var/list/add = visibleChunks - eye.visibleCameraChunks
|
||||
|
||||
for(var/datum/camerachunk/chunk as anything in remove)
|
||||
chunk.remove(eye)
|
||||
|
||||
for(var/datum/camerachunk/chunk as anything in add)
|
||||
chunk.add(eye)
|
||||
|
||||
/// Used in [/proc/major_chunk_change] - indicates the camera should be removed from the chunk list.
|
||||
#define REMOVE_CAMERA 0
|
||||
/// Used in [/proc/major_chunk_change] - indicates the camera should be added to the chunk list.
|
||||
#define ADD_CAMERA 1
|
||||
/// Used in [/proc/major_chunk_change] - indicates the chunk should be updated without adding/removing a camera.
|
||||
#define IGNORE_CAMERA 2
|
||||
|
||||
/// Updates the chunks that the turf is located in. Use this when obstacles are destroyed or when doors open.
|
||||
/datum/controller/subsystem/cameras/proc/update_visibility(atom/relevant_atom)
|
||||
if(!SSticker)
|
||||
return
|
||||
major_chunk_change(relevant_atom, IGNORE_CAMERA)
|
||||
|
||||
/// Removes a camera from a chunk.
|
||||
/datum/controller/subsystem/cameras/proc/remove_camera_from_chunk(obj/machinery/camera/old_cam)
|
||||
major_chunk_change(old_cam, REMOVE_CAMERA)
|
||||
|
||||
/// Add a camera to a chunk.
|
||||
/datum/controller/subsystem/cameras/proc/add_camera_to_chunk(obj/machinery/camera/new_cam)
|
||||
if(new_cam.can_use())
|
||||
major_chunk_change(new_cam, ADD_CAMERA)
|
||||
|
||||
/**
|
||||
* Used for Cyborg/mecha cameras. Since portable cameras can be in ANY chunk.
|
||||
* update_delay_buffer is passed all the way to queue_update() from their camera updates on movement
|
||||
* to change the time between static updates.
|
||||
*/
|
||||
/datum/controller/subsystem/cameras/proc/update_portable_camera(obj/machinery/camera/updating_camera, update_delay_buffer)
|
||||
if(updating_camera.can_use())
|
||||
major_chunk_change(updating_camera, ADD_CAMERA, update_delay_buffer)
|
||||
|
||||
/**
|
||||
* Never access this proc directly!!!!
|
||||
* This will update the chunk and all the surrounding chunks.
|
||||
* It will also add the atom to the cameras list if you set the choice to 1.
|
||||
* Setting the choice to 0 will remove the camera from the chunks.
|
||||
* If you want to update the chunks around an object, without adding/removing a camera, use choice 2.
|
||||
* update_delay_buffer is passed all the way to queue_update() from portable camera updates on movement
|
||||
* to change the time between static updates.
|
||||
*/
|
||||
/datum/controller/subsystem/cameras/proc/major_chunk_change(atom/center_or_camera, choice = IGNORE_CAMERA, update_delay_buffer = 0)
|
||||
PROTECTED_PROC(TRUE)
|
||||
|
||||
if(QDELETED(center_or_camera) && choice == ADD_CAMERA)
|
||||
CRASH("Tried to add a qdeleting camera to the net")
|
||||
|
||||
var/turf/chunk_turf = get_turf(center_or_camera)
|
||||
if(isnull(chunk_turf))
|
||||
return
|
||||
|
||||
var/x1 = max(1, chunk_turf.x - (CHUNK_SIZE / 2))
|
||||
var/y1 = max(1, chunk_turf.y - (CHUNK_SIZE / 2))
|
||||
var/x2 = min(world.maxx, chunk_turf.x + (CHUNK_SIZE / 2))
|
||||
var/y2 = min(world.maxy, chunk_turf.y + (CHUNK_SIZE / 2))
|
||||
for(var/x = x1; x <= x2; x += CHUNK_SIZE)
|
||||
for(var/y = y1; y <= y2; y += CHUNK_SIZE)
|
||||
var/datum/camerachunk/chunk = get_camera_chunk(x, y, chunk_turf.z)
|
||||
if(isnull(chunk))
|
||||
continue
|
||||
if(choice == REMOVE_CAMERA)
|
||||
// Remove the camera.
|
||||
chunk.cameras[chunk_turf.z] -= center_or_camera
|
||||
if(choice == ADD_CAMERA)
|
||||
// You can't have the same camera in the list twice.
|
||||
chunk.cameras[chunk_turf.z] |= center_or_camera
|
||||
chunk.queue_update(center_or_camera, update_delay_buffer)
|
||||
|
||||
/// A faster, turf only version of [/datum/controller/subsystem/cameras/proc/major_chunk_change]
|
||||
/// For use in sensitive code, be careful with it
|
||||
/datum/controller/subsystem/cameras/proc/bare_major_chunk_change(turf/changed)
|
||||
var/x1 = max(1, changed.x - (CHUNK_SIZE / 2))
|
||||
var/y1 = max(1, changed.y - (CHUNK_SIZE / 2))
|
||||
var/x2 = min(world.maxx, changed.x + (CHUNK_SIZE / 2))
|
||||
var/y2 = min(world.maxy, changed.y + (CHUNK_SIZE / 2))
|
||||
for(var/x = x1; x <= x2; x += CHUNK_SIZE)
|
||||
for(var/y = y1; y <= y2; y += CHUNK_SIZE)
|
||||
var/datum/camerachunk/chunk = get_camera_chunk(x, y, changed.z)
|
||||
chunk?.queue_update(changed, 0)
|
||||
|
||||
/// Will check if an atom is on a viewable turf.
|
||||
/// Returns TRUE if the atom is visible by any camera, FALSE otherwise.
|
||||
/datum/controller/subsystem/cameras/proc/is_visible_by_cameras(atom/target)
|
||||
return turf_visible_by_cameras(get_turf(target))
|
||||
|
||||
/// Checks if the passed turf is visible by any camera.
|
||||
/// Returns TRUE if the turf is visible by any camera, FALSE otherwise.
|
||||
/datum/controller/subsystem/cameras/proc/turf_visible_by_cameras(turf/position)
|
||||
PRIVATE_PROC(TRUE)
|
||||
if(isnull(position))
|
||||
return FALSE
|
||||
var/datum/camerachunk/chunk = generate_chunk(position.x, position.y, position.z)
|
||||
if(isnull(chunk))
|
||||
return FALSE
|
||||
chunk.force_update(only_if_necessary = TRUE) // Update NOW if necessary
|
||||
if(chunk.visibleTurfs[position])
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/// Gets the camera chunk the passed turf is in.
|
||||
/// Returns the chunk if it exists and is visible, null otherwise.
|
||||
/datum/controller/subsystem/cameras/proc/get_turf_camera_chunk(turf/position)
|
||||
RETURN_TYPE(/datum/camerachunk)
|
||||
var/datum/camerachunk/chunk = generate_chunk(position.x, position.y, position.z)
|
||||
if(!chunk)
|
||||
return null
|
||||
chunk.force_update(only_if_necessary = TRUE) // Update NOW if necessary
|
||||
if(chunk.visibleTurfs[position])
|
||||
return chunk
|
||||
return null
|
||||
|
||||
/// Returns list of available cameras, ready to use for UIs displaying list of them
|
||||
/// The format is: list("name" = "camera.c_tag", ref = REF(camera))
|
||||
/datum/controller/subsystem/cameras/proc/get_available_cameras_data(list/networks_available, list/z_levels_available)
|
||||
var/list/available_cameras_data = list()
|
||||
for(var/obj/machinery/camera/camera as anything in get_filtered_and_sorted_cameras(networks_available, z_levels_available))
|
||||
available_cameras_data += list(list(
|
||||
name = camera.c_tag,
|
||||
ref = REF(camera),
|
||||
))
|
||||
|
||||
return available_cameras_data
|
||||
|
||||
/**
|
||||
* get_available_camera_by_tag_list
|
||||
*
|
||||
* Builds a list of all available cameras that can be seen to networks_available and in z_levels_available.
|
||||
* Entries are stored in `c_tag[camera.can_use() ? null : " (Deactivated)"]` => `camera` format
|
||||
* Args:
|
||||
* networks_available - List of networks that we use to see which cameras are visible to it.
|
||||
* z_levels_available - List of z levels to filter camera by. If empty, all z levels are considered valid.
|
||||
* sort_by_ctag - If the resulting list should be sorted by `c_tag`.
|
||||
*/
|
||||
/datum/controller/subsystem/cameras/proc/get_available_camera_by_tag_list(list/networks_available, list/z_levels_available)
|
||||
var/list/available_cameras_by_tag = list()
|
||||
for(var/obj/machinery/camera/camera as anything in get_filtered_and_sorted_cameras(networks_available, z_levels_available))
|
||||
available_cameras_by_tag["[camera.c_tag][camera.can_use() ? null : " (Deactivated)"]"] = camera
|
||||
|
||||
return available_cameras_by_tag
|
||||
|
||||
/// Returns list of all cameras that passed `is_camera_available` filter and sorted by `cmp_camera_ctag_asc`
|
||||
/datum/controller/subsystem/cameras/proc/get_filtered_and_sorted_cameras(list/networks_available, list/z_levels_available)
|
||||
PRIVATE_PROC(TRUE)
|
||||
|
||||
var/list/filtered_cameras = list()
|
||||
for(var/obj/machinery/camera/camera as anything in cameras)
|
||||
if(!is_camera_available(camera, networks_available, z_levels_available))
|
||||
continue
|
||||
|
||||
filtered_cameras += camera
|
||||
|
||||
return sortTim(filtered_cameras, GLOBAL_PROC_REF(cmp_camera_ctag_asc))
|
||||
|
||||
/// Checks if the `camera_to_check` meets the requirements of availability.
|
||||
/datum/controller/subsystem/cameras/proc/is_camera_available(obj/machinery/camera/camera_to_check, list/networks_available, list/z_levels_available)
|
||||
PRIVATE_PROC(TRUE)
|
||||
|
||||
if(!camera_to_check.c_tag)
|
||||
return FALSE
|
||||
|
||||
if(length(z_levels_available) && !(camera_to_check.z in z_levels_available))
|
||||
return FALSE
|
||||
|
||||
return length(camera_to_check.network & networks_available) > 0
|
||||
|
||||
#undef ADD_CAMERA
|
||||
#undef REMOVE_CAMERA
|
||||
#undef IGNORE_CAMERA
|
||||
|
||||
/obj/effect/overlay/camera_static
|
||||
name = "static"
|
||||
icon = null
|
||||
icon_state = null
|
||||
anchored = TRUE // should only appear in vis_contents, but to be safe
|
||||
appearance_flags = RESET_TRANSFORM | TILE_BOUND | LONG_GLIDE
|
||||
// this combination makes the static block clicks to everything below it,
|
||||
// without appearing in the right-click menu for non-AI clients
|
||||
mouse_opacity = MOUSE_OPACITY_ICON
|
||||
invisibility = INVISIBILITY_ABSTRACT
|
||||
|
||||
plane = CAMERA_STATIC_PLANE
|
||||
|
||||
ADMIN_VERB(pause_camera_updates, R_ADMIN, "Toggle Camera Updates", "Stop security cameras from updating, meaning what they see now is what they will see forever.", ADMIN_CATEGORY_DEBUG)
|
||||
SScameras.disable_camera_updates = !SScameras.disable_camera_updates
|
||||
log_admin("[key_name_admin(user)] [SScameras.disable_camera_updates ? "disabled" : "enabled"] camera updates.")
|
||||
message_admins("Admin [key_name_admin(user)] has [SScameras.disable_camera_updates ? "disabled" : "enabled"] camera updates.")
|
||||
BLACKBOX_LOG_ADMIN_VERB("Toggle Camera Updates")
|
||||
@@ -33,7 +33,7 @@
|
||||
* Or
|
||||
* - A single weight for all tiers.
|
||||
*/
|
||||
var/list/weight = 0
|
||||
var/alist/weight = 0
|
||||
/**
|
||||
* The min population for which this ruleset is available.
|
||||
*
|
||||
@@ -43,7 +43,7 @@
|
||||
* Or
|
||||
* - A single min population for all tiers.
|
||||
*/
|
||||
var/list/min_pop = 0
|
||||
var/alist/min_pop = 0
|
||||
/// List of roles that are blacklisted from this ruleset
|
||||
/// For roundstart rulesets, it will prevent players from being selected for this ruleset if they have one of these roles
|
||||
/// For latejoin or midround rulesets, it will prevent players from being assigned to this ruleset if they have one of these roles
|
||||
@@ -121,47 +121,15 @@
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/// Used to create tier lists for weights and min_pop values
|
||||
/// Used to create tier alists for weights and min_pop values
|
||||
/datum/dynamic_ruleset/proc/load_tier_list(list/incoming_list)
|
||||
PRIVATE_PROC(TRUE)
|
||||
|
||||
var/list/tier_list = new /list(4)
|
||||
// loads a list of list("2" = 1, "3" = 3) into a list(null, 1, 3, null)
|
||||
var/alist/tier_list = alist()
|
||||
// loads a list of list("2" = 1, "3" = 3) into an alist(2 = 1, 3 = 3)
|
||||
for(var/tier in incoming_list)
|
||||
tier_list[text2num(tier)] = incoming_list[tier]
|
||||
|
||||
// turn list(null, 1, 3, null) into list(1, 1, 3, null)
|
||||
for(var/i in 1 to length(tier_list))
|
||||
var/val = tier_list[i]
|
||||
if(isnum(val))
|
||||
break
|
||||
for(var/j in i to length(tier_list))
|
||||
var/other_val = tier_list[j]
|
||||
if(!isnum(other_val))
|
||||
continue
|
||||
tier_list[i] = other_val
|
||||
break
|
||||
|
||||
// turn list(1, 1, 3, null) into list(1, 1, 3, 3)
|
||||
for(var/i in length(tier_list) to 1 step -1)
|
||||
var/val = tier_list[i]
|
||||
if(isnum(val))
|
||||
break
|
||||
for(var/j in i to 1 step -1)
|
||||
var/other_val = tier_list[j]
|
||||
if(!isnum(other_val))
|
||||
continue
|
||||
tier_list[i] = other_val
|
||||
break
|
||||
|
||||
// we can assert that tier[1] and tier[4] are not null, but we cannot say the same for tier[2] and tier[3]
|
||||
// this can be happen due to the following setup: list(1, null, null, 4)
|
||||
// (which is an invalid config, and should be fixed by the operator)
|
||||
if(isnull(tier_list[2]))
|
||||
tier_list[2] = tier_list[1]
|
||||
if(isnull(tier_list[3]))
|
||||
tier_list[3] = tier_list[4]
|
||||
|
||||
return tier_list
|
||||
|
||||
/**
|
||||
@@ -170,6 +138,25 @@
|
||||
/datum/dynamic_ruleset/proc/can_be_selected()
|
||||
return TRUE
|
||||
|
||||
/// Gets the list value for the given tier, otherwise use next highest tier,
|
||||
/// or failing that, next lowest
|
||||
/datum/dynamic_ruleset/proc/get_tier_specific_value(alist/values, tier)
|
||||
PRIVATE_PROC(TRUE)
|
||||
if(isnum(values[tier]))
|
||||
return values[tier]
|
||||
|
||||
// search higher tiers
|
||||
for(var/i in tier to 4)
|
||||
if(isnum(values[i]))
|
||||
return values[i]
|
||||
|
||||
// no dice, lower tiers?
|
||||
for(var/i in tier to 1 step -1)
|
||||
if(isnum(values[i]))
|
||||
return values[i]
|
||||
|
||||
return 0
|
||||
|
||||
/**
|
||||
* Calculates the weight of this ruleset for the given tier.
|
||||
*
|
||||
@@ -183,11 +170,11 @@
|
||||
return 0
|
||||
if(!can_be_selected())
|
||||
return 0
|
||||
var/final_minpop = islist(min_pop) ? min_pop[tier] : min_pop
|
||||
var/final_minpop = islist(min_pop) ? get_tier_specific_value(min_pop, tier) : min_pop
|
||||
if(final_minpop > population_size)
|
||||
return 0
|
||||
|
||||
var/final_weight = islist(weight) ? weight[tier] : weight
|
||||
var/final_weight = islist(weight) ? get_tier_specific_value(weight, tier) : weight
|
||||
for(var/datum/dynamic_ruleset/other_ruleset as anything in SSdynamic.executed_rulesets)
|
||||
if(other_ruleset == src)
|
||||
continue
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
midround_type = HEAVY_MIDROUND
|
||||
false_alarm_able = TRUE
|
||||
ruleset_flags = RULESET_INVADER
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 0,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 0,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 1,
|
||||
@@ -271,7 +271,7 @@
|
||||
pref_flag = ROLE_WIZARD_MIDROUND
|
||||
jobban_flag = ROLE_WIZARD
|
||||
ruleset_flags = RULESET_INVADER|RULESET_HIGH_IMPACT
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 0,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 0,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 1,
|
||||
@@ -294,7 +294,7 @@
|
||||
pref_flag = ROLE_OPERATIVE_MIDROUND
|
||||
jobban_flag = ROLE_OPERATIVE
|
||||
ruleset_flags = RULESET_INVADER|RULESET_HIGH_IMPACT
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 0,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 1,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 3,
|
||||
@@ -378,7 +378,7 @@
|
||||
false_alarm_able = TRUE
|
||||
pref_flag = ROLE_BLOB
|
||||
ruleset_flags = RULESET_INVADER
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 0,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 1,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 3,
|
||||
@@ -418,7 +418,7 @@
|
||||
false_alarm_able = TRUE
|
||||
pref_flag = ROLE_ALIEN
|
||||
ruleset_flags = RULESET_INVADER
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 0,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 1,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 5,
|
||||
@@ -505,7 +505,7 @@
|
||||
false_alarm_able = TRUE
|
||||
pref_flag = ROLE_SPACE_DRAGON
|
||||
ruleset_flags = RULESET_INVADER
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 0,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 3,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 5,
|
||||
@@ -575,7 +575,7 @@
|
||||
midround_type = HEAVY_MIDROUND
|
||||
pref_flag = ROLE_NINJA
|
||||
ruleset_flags = RULESET_INVADER
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 0,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 0,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 1,
|
||||
@@ -678,7 +678,7 @@
|
||||
min_antag_cap = 2
|
||||
max_antag_cap = 3
|
||||
repeatable_weight_decrease = 4
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 0,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 3,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 4,
|
||||
@@ -1104,7 +1104,7 @@
|
||||
max_antag_cap = 4
|
||||
repeatable_weight_decrease = 8
|
||||
blacklisted_roles = list()
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 0,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 3,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 8,
|
||||
@@ -1119,7 +1119,7 @@
|
||||
pref_flag = ROLE_MALF_MIDROUND
|
||||
jobban_flag = ROLE_MALF
|
||||
ruleset_flags = RULESET_HIGH_IMPACT
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 0,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 1,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 3,
|
||||
@@ -1147,7 +1147,7 @@
|
||||
midround_type = HEAVY_MIDROUND
|
||||
pref_flag = ROLE_BLOB_INFECTION
|
||||
jobban_flag = ROLE_BLOB
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 0,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 1,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 3,
|
||||
@@ -1171,7 +1171,7 @@
|
||||
midround_type = LIGHT_MIDROUND
|
||||
pref_flag = ROLE_OBSESSED
|
||||
blacklisted_roles = list()
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 5,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 5,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 3,
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
pref_flag = ROLE_MALF
|
||||
preview_antag_datum = /datum/antagonist/malf_ai
|
||||
ruleset_flags = RULESET_HIGH_IMPACT
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 0,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 1,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 3,
|
||||
@@ -116,7 +116,7 @@
|
||||
preview_antag_datum = /datum/antagonist/wizard
|
||||
pref_flag = ROLE_WIZARD
|
||||
ruleset_flags = RULESET_INVADER|RULESET_HIGH_IMPACT
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 0,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 0,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 1,
|
||||
@@ -147,7 +147,7 @@
|
||||
preview_antag_datum = /datum/antagonist/cult
|
||||
pref_flag = ROLE_CULTIST
|
||||
ruleset_flags = RULESET_HIGH_IMPACT
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 0,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 1,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 3,
|
||||
@@ -208,7 +208,7 @@
|
||||
preview_antag_datum = /datum/antagonist/nukeop
|
||||
pref_flag = ROLE_OPERATIVE
|
||||
ruleset_flags = RULESET_INVADER|RULESET_HIGH_IMPACT
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 0,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 1,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 3,
|
||||
@@ -292,7 +292,7 @@
|
||||
preview_antag_datum = /datum/antagonist/rev/head
|
||||
pref_flag = ROLE_REV_HEAD
|
||||
ruleset_flags = RULESET_HIGH_IMPACT
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 0,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 1,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 3,
|
||||
@@ -367,7 +367,7 @@
|
||||
config_tag = "Roundstart Spies"
|
||||
preview_antag_datum = /datum/antagonist/spy
|
||||
pref_flag = ROLE_SPY
|
||||
weight = list(
|
||||
weight = alist(
|
||||
DYNAMIC_TIER_LOW = 0,
|
||||
DYNAMIC_TIER_LOWMEDIUM = 1,
|
||||
DYNAMIC_TIER_MEDIUMHIGH = 3,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
PROCESSING_SUBSYSTEM_DEF(priority_effects)
|
||||
name = "Priority Status Effects"
|
||||
flags = SS_TICKER | SS_KEEP_TIMING | SS_NO_INIT
|
||||
wait = 2 // Not seconds - we're running on SS_TICKER, so this is ticks.
|
||||
flags = SS_KEEP_TIMING | SS_NO_INIT
|
||||
wait = 0.2 SECONDS // Same as SSfastprocess, but can be anything, assuming you refactor all high-priority status effect intervals and durations to be a multiple of it.
|
||||
priority = FIRE_PRIORITY_PRIORITY_EFFECTS
|
||||
stat_tag = "PEFF"
|
||||
|
||||
@@ -7,7 +7,7 @@ PROCESSING_SUBSYSTEM_DEF(station)
|
||||
///A list of currently active station traits
|
||||
var/list/station_traits = list()
|
||||
///Assoc list of trait type || assoc list of traits with weighted value. Used for picking traits from a specific category.
|
||||
var/list/selectable_traits_by_types = list(STATION_TRAIT_POSITIVE = list(), STATION_TRAIT_NEUTRAL = list(), STATION_TRAIT_NEGATIVE = list())
|
||||
var/alist/selectable_traits_by_types = alist(STATION_TRAIT_POSITIVE = list(), STATION_TRAIT_NEUTRAL = list(), STATION_TRAIT_NEGATIVE = list())
|
||||
///Currently active announcer. Starts as a type but gets initialized after traits are selected
|
||||
var/datum/centcom_announcer/announcer = /datum/centcom_announcer/default
|
||||
///A list of trait roles that should be protected from antag
|
||||
|
||||
@@ -34,6 +34,50 @@ SUBSYSTEM_DEF(sounds)
|
||||
/// Any errors from precaching.
|
||||
VAR_PRIVATE/list/precache_errors = list()
|
||||
|
||||
// Comments from https://github.com/DaedalusDock/daedalusdock We love Francinum.
|
||||
/// A list of sound formats that work in byond. Indexed for direct accesing rather then loop itteration or usage of `in`
|
||||
var/static/list/byond_sound_formats = list(
|
||||
"mid" = TRUE, //Midi, 8.3 File Name
|
||||
"midi" = TRUE, //Midi, Long File Name
|
||||
"mod" = TRUE, //Module, Original Amiga Tracker format
|
||||
"it" = TRUE, //Impulse Tracker Module format
|
||||
"s3m" = TRUE, //ScreamTracker 3 Module
|
||||
"xm" = TRUE, //FastTracker 2 Module
|
||||
"oxm" = TRUE, //FastTracker 2 (Vorbis Compressed Samples)
|
||||
"wav" = TRUE, //Waveform Audio File Format, A (R)IFF-class format, and Microsoft's choice in the 80s sound format pissing match.
|
||||
"ogg" = TRUE, //OGG Audio Container, Usually contains Vorbis-compressed Audio
|
||||
//"raw" = TRUE, //On the tin, byond purports to support raw, uncompressed PCM Audio. I actually have no fucking idea how FMOD actually handles these.
|
||||
//since they completely lack all information. As a confusion based anti-footgun, I'm just going to wire this to FALSE for now. It's here though.
|
||||
"wma" = TRUE, //Windows Media Audio container
|
||||
"aiff" = TRUE, //Audio Interchange File Format, Apple's side of the 80s sound format pissing match. It's also (R)IFF in a trenchcoat.
|
||||
"mp3" = TRUE //MPeg Layer 3 Container (And usually, Codec.)
|
||||
)
|
||||
|
||||
/// File types we can sniff the duration from using rustg.
|
||||
var/static/list/safe_formats = list(
|
||||
"ogg" = TRUE,
|
||||
"mp3" = TRUE
|
||||
)
|
||||
|
||||
// Currently set to private as I would prefer you use byond_sound_formats but you can unprivate it if you have a valid use!
|
||||
// Put more common extensions first to speed this up a bit (So only ogg and mp3 lol.)
|
||||
/// Similar to byond_sound_formats, a list of sound formats that work in byond.
|
||||
VAR_PRIVATE/static/list/byond_sound_extensions = list(
|
||||
".ogg",
|
||||
".mp3",
|
||||
".mid",
|
||||
".midi",
|
||||
".mod",
|
||||
".it",
|
||||
".s3m",
|
||||
".xm",
|
||||
".oxm",
|
||||
".wav",
|
||||
//".raw", See byond_sound_formats
|
||||
".wma",
|
||||
".aiff"
|
||||
)
|
||||
|
||||
/datum/controller/subsystem/sounds/Initialize()
|
||||
setup_available_channels()
|
||||
find_all_available_sounds()
|
||||
@@ -63,23 +107,7 @@ SUBSYSTEM_DEF(sounds)
|
||||
|
||||
/datum/controller/subsystem/sounds/proc/find_all_available_sounds()
|
||||
all_sounds = list()
|
||||
// Put more common extensions first to speed this up a bit
|
||||
var/static/list/valid_file_extensions = list(
|
||||
".ogg",
|
||||
".wav",
|
||||
".mid",
|
||||
".midi",
|
||||
".mod",
|
||||
".it",
|
||||
".s3m",
|
||||
".xm",
|
||||
".oxm",
|
||||
".raw",
|
||||
".wma",
|
||||
".aiff",
|
||||
)
|
||||
|
||||
all_sounds = pathwalk("sound/", valid_file_extensions)
|
||||
all_sounds = pathwalk("sound/", byond_sound_extensions)
|
||||
|
||||
/// Removes a channel from using list.
|
||||
/datum/controller/subsystem/sounds/proc/free_sound_channel(channel)
|
||||
|
||||
@@ -231,7 +231,6 @@ SUBSYSTEM_DEF(statpanels)
|
||||
#endif
|
||||
for(var/datum/controller/subsystem/sub_system as anything in Master.subsystems)
|
||||
mc_data[++mc_data.len] = list("\[[sub_system.state_letter()]][sub_system.name]", sub_system.stat_entry(), text_ref(sub_system))
|
||||
mc_data[++mc_data.len] = list("Camera Net", "Cameras: [GLOB.cameranet.cameras.len] | Chunks: [GLOB.cameranet.chunks.len]", text_ref(GLOB.cameranet))
|
||||
|
||||
///immediately update the active statpanel tab of the target client
|
||||
/datum/controller/subsystem/statpanels/proc/immediate_send_stat_data(client/target)
|
||||
|
||||
@@ -39,6 +39,25 @@ SUBSYSTEM_DEF(tgui)
|
||||
|
||||
basehtml = replacetextEx(basehtml, "<!-- tgui:nt-copyright -->", "Nanotrasen (c) 2525-[CURRENT_STATION_YEAR]")
|
||||
|
||||
/datum/controller/subsystem/tgui/OnConfigLoad()
|
||||
var/storage_iframe = CONFIG_GET(string/storage_cdn_iframe)
|
||||
|
||||
if(storage_iframe && storage_iframe != /datum/config_entry/string/storage_cdn_iframe::default)
|
||||
basehtml = replacetext(basehtml, "\[tgui:storagecdn\]", storage_iframe)
|
||||
return
|
||||
|
||||
if(CONFIG_GET(string/asset_transport) == "webroot")
|
||||
var/datum/asset_transport/webroot/webroot = SSassets.transport
|
||||
|
||||
var/datum/asset_cache_item/item = webroot.register_asset("iframe.html", file("tgui/public/iframe.html"))
|
||||
basehtml = replacetext(basehtml, "\[tgui:storagecdn\]", webroot.get_asset_url("iframe.html", item))
|
||||
return
|
||||
|
||||
if(!storage_iframe)
|
||||
return
|
||||
|
||||
basehtml = replacetext(basehtml, "\[tgui:storagecdn\]", storage_iframe)
|
||||
|
||||
|
||||
/datum/controller/subsystem/tgui/Shutdown()
|
||||
close_all_uis()
|
||||
|
||||
@@ -77,21 +77,6 @@ SUBSYSTEM_DEF(ticker)
|
||||
var/discord_alerted = FALSE //SKYRAT EDIT - DISCORD PING SPAM PREVENTION
|
||||
|
||||
/datum/controller/subsystem/ticker/Initialize()
|
||||
var/list/byond_sound_formats = list(
|
||||
"mid" = TRUE,
|
||||
"midi" = TRUE,
|
||||
"mod" = TRUE,
|
||||
"it" = TRUE,
|
||||
"s3m" = TRUE,
|
||||
"xm" = TRUE,
|
||||
"oxm" = TRUE,
|
||||
"wav" = TRUE,
|
||||
"ogg" = TRUE,
|
||||
"raw" = TRUE,
|
||||
"wma" = TRUE,
|
||||
"aiff" = TRUE,
|
||||
)
|
||||
|
||||
var/list/provisional_title_music = flist("[global.config.directory]/title_music/sounds/")
|
||||
var/list/music = list()
|
||||
var/use_rare_music = prob(1)
|
||||
@@ -119,11 +104,8 @@ SUBSYSTEM_DEF(ticker)
|
||||
music -= old_login_music
|
||||
|
||||
for(var/S in music)
|
||||
var/list/L = splittext(S,".")
|
||||
if(L.len >= 2)
|
||||
var/ext = LOWER_TEXT(L[L.len]) //pick the real extension, no 'honk.ogg.exe' nonsense here
|
||||
if(byond_sound_formats[ext])
|
||||
continue
|
||||
if(IS_SOUND_FILE(S))
|
||||
continue
|
||||
music -= S
|
||||
|
||||
if(!length(music))
|
||||
|
||||
@@ -519,30 +519,30 @@ SUBSYSTEM_DEF(timer)
|
||||
#if defined(TIMER_DEBUG)
|
||||
// Generate debug-friendly list for timer, more complex but also more expensive
|
||||
timer_info = list(
|
||||
1 = id,
|
||||
2 = timeToRun,
|
||||
3 = wait,
|
||||
4 = flags,
|
||||
5 = callBack, /* Safe to hold this directly because it's never del'd */
|
||||
6 = "[callBack.object]",
|
||||
7 = text_ref(callBack.object),
|
||||
8 = getcallingtype(),
|
||||
9 = callBack.delegate,
|
||||
10 = callBack.arguments ? callBack.arguments.Copy() : null,
|
||||
11 = "[source]"
|
||||
/* 1 = */ id,
|
||||
/* 2 = */ timeToRun,
|
||||
/* 3 = */ wait,
|
||||
/* 4 = */ flags,
|
||||
/* 5 = */ callBack, /* Safe to hold this directly because it's never del'd */
|
||||
/* 6 = */ "[callBack.object]",
|
||||
/* 7 = */ text_ref(callBack.object),
|
||||
/* 8 = */ getcallingtype(),
|
||||
/* 9 = */ callBack.delegate,
|
||||
/* 10 = */ callBack.arguments ? callBack.arguments.Copy() : null,
|
||||
/* 11 = */ "[source]"
|
||||
)
|
||||
#else
|
||||
// Generate a debuggable list for the timer, simpler but wayyyy cheaper, string generation (and ref/copy memes) is a bitch and this saves a LOT of time
|
||||
timer_info = list(
|
||||
1 = id,
|
||||
2 = timeToRun,
|
||||
3 = wait,
|
||||
4 = flags,
|
||||
5 = callBack, /* Safe to hold this directly because it's never del'd */
|
||||
6 = "[callBack.object]",
|
||||
7 = getcallingtype(),
|
||||
8 = callBack.delegate,
|
||||
9 = "[source]"
|
||||
/* 1 = */ id,
|
||||
/* 2 = */ timeToRun,
|
||||
/* 3 = */ wait,
|
||||
/* 4 = */ flags,
|
||||
/* 5 = */ callBack, /* Safe to hold this directly because it's never del'd */
|
||||
/* 6 = */ "[callBack.object]",
|
||||
/* 7 = */ getcallingtype(),
|
||||
/* 8 = */ callBack.delegate,
|
||||
/* 9 = */ "[source]"
|
||||
)
|
||||
#endif
|
||||
|
||||
|
||||
@@ -209,7 +209,9 @@ SUBSYSTEM_DEF(wardrobe)
|
||||
|
||||
/// Take an existing object, and insert it into our storage
|
||||
/// If we can't or won't take it, it's deleted. You do not own this object after passing it in
|
||||
/datum/controller/subsystem/wardrobe/proc/stash_object(atom/movable/object)
|
||||
/datum/controller/subsystem/wardrobe/proc/stash_object(obj/item/object)
|
||||
if(object.item_flags & DO_NOT_WARDROBE)
|
||||
return
|
||||
var/object_type = object.type
|
||||
var/list/master_info = canon_minimum[object_type]
|
||||
// I will not permit objects you didn't reserve ahead of time
|
||||
|
||||
Reference in New Issue
Block a user