Merge branch 'master' of https://github.com/tgstation/tgstation into upstream-2025-11-12

This commit is contained in:
Roxy
2025-11-12 16:44:13 -05:00
379 changed files with 37535 additions and 33147 deletions
@@ -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"
+19 -4
View File
@@ -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)
+296
View File
@@ -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
+45 -17
View File
@@ -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)
-1
View File
@@ -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)
+19
View File
@@ -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()
+2 -20
View File
@@ -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))
+20 -20
View File
@@ -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
+3 -1
View File
@@ -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