[MIRROR] Unit Test rework & Master/Ticker update (#11372)

Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
Co-authored-by: C.L. <killer65311@gmail.com>
This commit is contained in:
Selis
2025-08-12 08:46:46 +02:00
committed by GitHub
parent 0ab6069dcb
commit 386c4f6756
390 changed files with 10560 additions and 5777 deletions

View File

@@ -79,7 +79,7 @@ ADMIN_VERB(debug_controller, R_DEBUG, "Debug Controller", "Debug the various per
//Goon PS stuff, and other yet-to-be-subsystem things.
options["LEGACY: master_controller"] = master_controller
options["LEGACY: job_master"] = job_master
options["LEGACY: radio_controller"] = radio_controller
options["LEGACY: SSradio"] = SSradio
options["LEGACY: emergency_shuttle"] = emergency_shuttle
options["LEGACY: paiController"] = paiController
options["LEGACY: cameranet"] = cameranet

View File

@@ -1,400 +0,0 @@
/*
HOW IT WORKS
The radio_controller is a global object maintaining all radio transmissions, think about it as about "ether".
Note that walkie-talkie, intercoms and headsets handle transmission using nonstandard way.
procs:
add_object(obj/device as obj, var/new_frequency as num, var/radio_filter as text|null = null)
Adds listening object.
parameters:
device - device receiving signals, must have proc receive_signal (see description below).
one device may listen several frequencies, but not same frequency twice.
new_frequency - see possibly frequencies below;
radio_filter - thing for optimization. Optional, but recommended.
All filters should be consolidated in this file, see defines later.
Device without listening filter will receive all signals (on specified frequency).
Device with filter will receive any signals sent without filter.
Device with filter will not receive any signals sent with different filter.
returns:
Reference to frequency object.
remove_object (obj/device, old_frequency)
Obliviously, after calling this proc, device will not receive any signals on old_frequency.
Other frequencies will left unaffected.
return_frequency(var/frequency as num)
returns:
Reference to frequency object. Use it if you need to send and do not need to listen.
radio_frequency is a global object maintaining list of devices that listening specific frequency.
procs:
post_signal(obj/source as obj|null, datum/signal/signal, var/radio_filter as text|null = null, var/range as num|null = null)
Sends signal to all devices that wants such signal.
parameters:
source - object, emitted signal. Usually, devices will not receive their own signals.
signal - see description below.
radio_filter - described above.
range - radius of regular byond's square circle on that z-level. null means everywhere, on all z-levels.
obj/proc/receive_signal(datum/signal/signal, var/receive_method as num, var/receive_param)
Handler from received signals. By default does nothing. Define your own for your object.
Avoid of sending signals directly from this proc, use spawn(-1). DO NOT use sleep() here or call procs that sleep please. If you must, use spawn()
parameters:
signal - see description below. Extract all needed data from the signal before doing sleep(), spawn() or return!
receive_method - may be TRANSMISSION_WIRE or TRANSMISSION_RADIO.
TRANSMISSION_WIRE is currently unused.
receive_param - for TRANSMISSION_RADIO here comes frequency.
datum/signal
vars:
source
an object that emitted signal. Used for debug and bearing.
data
list with transmitting data. Usual use pattern:
data["msg"] = "hello world"
encryption
Some number symbolizing "encryption key".
Note that game actually do not use any cryptography here.
If receiving object don't know right key, it must ignore encrypted signal in its receive_signal.
*/
/*
Frequency range: 1200 to 1600
Radiochat range: 1441 to 1489 (most devices refuse to be tune to other frequency, even during mapmaking)
Radio:
1459 - standard radio chat
1351 - Science
1353 - Command
1355 - Medical
1357 - Engineering
1359 - Security
1341 - deathsquad
1443 - Confession Intercom
1347 - Cargo techs
1349 - Service people
Devices:
1451 - tracking implant
1457 - RSD default
On the map:
1311 for prison shuttle console (in fact, it is not used)
1433 for engine components
1435 for status displays
1437 for atmospherics/fire alerts
1439 for air pumps, air scrubbers, atmo control
1441 for atmospherics - supply tanks
1443 for atmospherics - distribution loop/mixed air tank
1445 for bot nav beacons
1447 for mulebot, secbot and ed209 control
1449 for airlock controls, electropack, magnets
1451 for toxin lab access
1453 for engineering access
1455 for AI access
*/
var/const/RADIO_LOW_FREQ = 1200
var/const/PUBLIC_LOW_FREQ = 1441
var/const/PUBLIC_HIGH_FREQ = 1489
var/const/RADIO_HIGH_FREQ = 1600
var/const/BOT_FREQ = 1447
var/const/COMM_FREQ = 1353
var/const/ERT_FREQ = 1345
var/const/AI_FREQ = 1343
var/const/DTH_FREQ = 1341
var/const/SYND_FREQ = 1213
var/const/RAID_FREQ = 1277
var/const/ENT_FREQ = 1461 //entertainment frequency. This is not a diona exclusive frequency.
var/const/BDCM_FREQ = 1481 // CHOMPEdit
// department channels
var/const/PUB_FREQ = 1459
var/const/SEC_FREQ = 1359
var/const/ENG_FREQ = 1357
var/const/MED_FREQ = 1355
var/const/SCI_FREQ = 1351
var/const/SRV_FREQ = 1349
var/const/SUP_FREQ = 1347
var/const/EXP_FREQ = 1361
// internal department channels
var/const/MED_I_FREQ = 1485
var/const/SEC_I_FREQ = 1475
var/const/TALON_FREQ = 1363 //VOREStation Add
var/const/CSN_FREQ = 1365 //VOREStation Add
var/const/OUT_FREQ = 1367 //CHOMPstation Add
var/list/radiochannels = list(
CHANNEL_COMMON = PUB_FREQ,
CHANNEL_SCIENCE = SCI_FREQ,
CHANNEL_COMMAND = COMM_FREQ,
CHANNEL_MEDICAL = MED_FREQ,
CHANNEL_ENGINEERING = ENG_FREQ,
CHANNEL_SECURITY = SEC_FREQ,
CHANNEL_BODYCAM = BDCM_FREQ, // CHOMPEdit
CHANNEL_RESPONSE_TEAM = ERT_FREQ,
CHANNEL_SPECIAL_OPS = DTH_FREQ,
CHANNEL_MERCENARY = SYND_FREQ,
CHANNEL_RAIDER = RAID_FREQ,
CHANNEL_SUPPLY = SUP_FREQ,
CHANNEL_SERVICE = SRV_FREQ,
CHANNEL_EXPLORATION = EXP_FREQ,
CHANNEL_AI_PRIVATE = AI_FREQ,
CHANNEL_ENTERTAINMENT = ENT_FREQ,
CHANNEL_MEDICAL_1 = MED_I_FREQ,
CHANNEL_SECURITY_1 = SEC_I_FREQ,
CHANNEL_TALON = TALON_FREQ, //VOREStation Add
CHANNEL_CASINO = CSN_FREQ,
CHANNEL_OUTSIDER = OUT_FREQ //CHOMPstation Add
)
// Hey, if anyone ever needs to update tgui/packages/tgui/constants.js with new radio channels
// I've kept this around just for you.
/* /client/verb/generate_tgui_radio_constants()
set name = "Generate TGUI Radio Constants"
set category = "Generate TGUI Radio Constants"
var/list/channel_info = list()
for(var/i in RADIO_LOW_FREQ to RADIO_HIGH_FREQ)
for(var/key in radiochannels)
if(i == radiochannels[key])
channel_info.Add(list(list("name" = key, "freq" = i, "color" = frequency_span_class(i))))
for(var/list/channel in channel_info)
switch(channel["color"])
if("deadsay") channel["color"] = "#530FAD"
if("radio") channel["color"] = "#008000"
if("deptradio") channel["color"] = "#ff00ff"
if("newscaster") channel["color"] = "#750000"
if("comradio") channel["color"] = "#193A7A"
if("syndradio") channel["color"] = "#6D3F40"
if("centradio") channel["color"] = "#5C5C8A"
if("airadio") channel["color"] = "#FF00FF"
if("entradio") channel["color"] = "#339966"
if("secradio") channel["color"] = "#A30000"
if("engradio") channel["color"] = "#A66300"
if("medradio") channel["color"] = "#008160"
if("sciradio") channel["color"] = "#993399"
if("supradio") channel["color"] = "#5F4519"
if("srvradio") channel["color"] = "#6eaa2c"
if("expradio") channel["color"] = "#555555"
to_chat(src, json_encode(channel_info)) */
// central command channels, i.e deathsquid & response teams
var/list/CENT_FREQS = list(ERT_FREQ, DTH_FREQ)
// Antag channels, i.e. Syndicate
var/list/ANTAG_FREQS = list(SYND_FREQ, RAID_FREQ)
//Department channels, arranged lexically
var/list/DEPT_FREQS = list(AI_FREQ, BDCM_FREQ, COMM_FREQ, ENG_FREQ, ENT_FREQ, MED_FREQ, SEC_FREQ, SCI_FREQ, SRV_FREQ, SUP_FREQ) // CHOMPEdit
var/list/OFFMAP_FREQS = list(TALON_FREQ, CSN_FREQ, OUT_FREQ) //VOREStation Add CHOMPEdit: Added outsider
/proc/frequency_span_class(var/frequency)
// Antags!
if (frequency in ANTAG_FREQS)
return "syndradio"
// CentCom channels (deathsquid and ert)
if(frequency in CENT_FREQS)
return "centradio"
// command channel
if(frequency == COMM_FREQ)
return "comradio"
// AI private channel
if(frequency == AI_FREQ)
return "airadio"
// department radio formatting (poorly optimized, ugh)
if(frequency == SEC_FREQ)
return "secradio"
if(frequency == BDCM_FREQ) // CHOMPEdit
return "bdcmradio"
if (frequency == ENG_FREQ)
return "engradio"
if(frequency == SCI_FREQ)
return "sciradio"
if(frequency == MED_FREQ)
return "medradio"
if(frequency == SUP_FREQ) // cargo
return "supradio"
if(frequency == SRV_FREQ) // service
return "srvradio"
if(frequency == EXP_FREQ) // explorer
return "expradio"
if(frequency == ENT_FREQ) // entertainment
return "entradio"
if(frequency in DEPT_FREQS)
return "deptradio"
//VOREStation Add
if(frequency in OFFMAP_FREQS)
return "expradio"
//VOREStation Add End
return "radio"
/* filters */
//When devices register with the radio controller, they might register under a certain filter.
//Other devices can then choose to send signals to only those devices that belong to a particular filter.
//This is done for performance, so we don't send signals to lots of machines unnecessarily.
//This filter is special because devices belonging to default also recieve signals sent to any other filter.
var/const/RADIO_DEFAULT = "radio_default"
var/const/RADIO_TO_AIRALARM = "radio_airalarm" //air alarms
var/const/RADIO_FROM_AIRALARM = "radio_airalarm_rcvr" //devices interested in recieving signals from air alarms
var/const/RADIO_CHAT = "radio_telecoms"
var/const/RADIO_ATMOSIA = "radio_atmos"
var/const/RADIO_NAVBEACONS = "radio_navbeacon"
var/const/RADIO_AIRLOCK = "radio_airlock"
var/const/RADIO_SECBOT = "radio_secbot"
var/const/RADIO_MULEBOT = "radio_mulebot"
var/const/RADIO_MAGNETS = "radio_magnet"
var/global/datum/controller/radio/radio_controller
/hook/startup/proc/createRadioController()
radio_controller = new /datum/controller/radio()
return 1
//callback used by objects to react to incoming radio signals
/obj/proc/receive_signal(datum/signal/signal, receive_method, receive_param)
return null
//The global radio controller
/datum/controller/radio
var/list/datum/radio_frequency/frequencies = list()
/datum/controller/radio/proc/add_object(obj/device as obj, var/new_frequency as num, var/radio_filter = null as text|null)
var/f_text = num2text(new_frequency)
var/datum/radio_frequency/frequency = frequencies[f_text]
if(!frequency)
frequency = new
frequency.frequency = new_frequency
frequencies[f_text] = frequency
frequency.add_listener(device, radio_filter)
return frequency
/datum/controller/radio/proc/remove_object(obj/device, old_frequency)
var/f_text = num2text(old_frequency)
var/datum/radio_frequency/frequency = frequencies[f_text]
if(frequency)
frequency.remove_listener(device)
if(frequency.devices.len == 0)
qdel(frequency)
frequencies -= f_text
return 1
/datum/controller/radio/proc/return_frequency(var/new_frequency as num)
var/f_text = num2text(new_frequency)
var/datum/radio_frequency/frequency = frequencies[f_text]
if(!frequency)
frequency = new
frequency.frequency = new_frequency
frequencies[f_text] = frequency
return frequency
/datum/radio_frequency
var/frequency as num
var/list/list/obj/devices = list()
/datum/radio_frequency/proc/post_signal(obj/source as obj|null, datum/signal/signal, var/radio_filter = null as text|null, var/range = null as num|null)
var/turf/start_point
if(range)
start_point = get_turf(source)
if(!start_point)
qdel(signal)
return 0
if (radio_filter)
send_to_filter(source, signal, radio_filter, start_point, range)
send_to_filter(source, signal, RADIO_DEFAULT, start_point, range)
else
//Broadcast the signal to everyone!
for (var/next_filter in devices)
send_to_filter(source, signal, next_filter, start_point, range)
//Sends a signal to all machines belonging to a given filter. Should be called by post_signal()
/datum/radio_frequency/proc/send_to_filter(obj/source, datum/signal/signal, var/radio_filter, var/turf/start_point = null, var/range = null)
if (range && !start_point)
return
for(var/obj/device in devices[radio_filter])
if(device == source)
continue
if(range)
var/turf/end_point = get_turf(device)
if(!end_point)
continue
if(start_point.z!=end_point.z || get_dist(start_point, end_point) > range)
continue
device.receive_signal(signal, TRANSMISSION_RADIO, frequency)
/datum/radio_frequency/proc/add_listener(obj/device as obj, var/radio_filter as text|null)
if (!radio_filter)
radio_filter = RADIO_DEFAULT
//log_admin("add_listener(device=[device],radio_filter=[radio_filter]) frequency=[frequency]")
var/list/obj/devices_line = devices[radio_filter]
if (!devices_line)
devices_line = new
devices[radio_filter] = devices_line
devices_line+=device
// var/list/obj/devices_line___ = devices[filter_str]
// var/l = devices_line___.len
//log_admin("DEBUG: devices_line.len=[devices_line.len]")
//log_admin("DEBUG: devices(filter_str).len=[l]")
/datum/radio_frequency/proc/remove_listener(obj/device)
for (var/devices_filter in devices)
var/list/devices_line = devices[devices_filter]
devices_line-=device
while (null in devices_line)
devices_line -= null
if (devices_line.len==0)
devices -= devices_filter
/datum/signal
var/obj/source
var/transmission_method = 0 //unused at the moment
//0 = wire
//1 = radio transmission
//2 = subspace transmission
var/list/data = list()
var/encryption
var/frequency = 0
/datum/signal/proc/copy_from(datum/signal/model)
source = model.source
transmission_method = model.transmission_method
data = model.data
encryption = model.encryption
frequency = model.frequency
/datum/signal/proc/debug_print()
if (source)
. = "signal = {source = '[source]' ([source:x],[source:y],[source:z])\n"
else
. = "signal = {source = '[source]' ()\n"
for (var/i in data)
. += "data\[\"[i]\"\] = \"[data[i]]\"\n"
if(islist(data[i]))
var/list/L = data[i]
for(var/t in L)
. += "data\[\"[i]\"\] list has: [t]"

View File

@@ -24,6 +24,9 @@
/// If the configuration is loaded
var/loaded = FALSE
/// If a reload is in progress
var/reload_in_progress = FALSE
/// A regex that matches words blocked IC
var/static/regex/ic_filter_regex
@@ -64,13 +67,23 @@
var/static/list/configuration_errors
/datum/controller/configuration/proc/admin_reload()
if(IsAdminAdvancedProcCall())
if(IsAdminAdvancedProcCall() || !PreConfigReload())
return
log_admin("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.")
message_admins("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.")
full_wipe()
Load(world.params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER])
/datum/controller/configuration/proc/PreConfigReload()
if(reload_in_progress)
to_chat(usr, span_warning("Another user is already reloading the config!"))
return FALSE
reload_in_progress = TRUE
world.TgsTriggerEvent("tg-PreConfigReload", wait_for_completion = TRUE)
reload_in_progress = FALSE
return TRUE
/datum/controller/configuration/proc/Load(_directory)
if(IsAdminAdvancedProcCall()) //If admin proccall is detected down the line it will horribly break everything.
return

View File

@@ -34,6 +34,8 @@
/datum/config_entry/number/walk_speed
default = 0
/datum/config_entry/flag/force_random_names
///Mob specific modifiers. NOTE: These will affect different mob types in different ways
/datum/config_entry/number/human_delay
default = 0

View File

@@ -1,6 +1,18 @@
/// server name (the name of the game window)
/datum/config_entry/string/servername
/// Countdown between lobby and the round starting.
/datum/config_entry/number/lobby_countdown
default = 120
integer = FALSE
min_val = 0
/// Post round murder death kill countdown.
/datum/config_entry/number/round_end_countdown
default = 25
integer = FALSE
min_val = 0
/// generate numeric suffix based on server port
/datum/config_entry/flag/server_suffix
@@ -222,9 +234,6 @@
// var/static/Tickcomp = 0 // FIXME: Unused
/// use socket_talk to communicate with other processes
/datum/config_entry/flag/socket_talk
// var/static/list/resource_urls = null // FIXME: Unused
/// Ghosts can turn on Antagovision to see a HUD of who is the bad guys this round.
@@ -787,6 +796,60 @@
/datum/config_entry/flag/discord_ahelps_all
default = FALSE
/datum/config_entry/number/mc_tick_rate/base_mc_tick_rate
integer = FALSE
default = 1
/datum/config_entry/number/mc_tick_rate/high_pop_mc_tick_rate
integer = FALSE
default = 1.1
/datum/config_entry/number/mc_tick_rate/high_pop_mc_mode_amount
default = 65
/datum/config_entry/number/mc_tick_rate/disable_high_pop_mc_mode_amount
default = 60
/datum/config_entry/number/mc_tick_rate
abstract_type = /datum/config_entry/number/mc_tick_rate
/datum/config_entry/number/mc_tick_rate/ValidateAndSet(str_val)
. = ..()
if (.)
Master.UpdateTickRate()
/datum/config_entry/flag/resume_after_initializations
/datum/config_entry/flag/resume_after_initializations/ValidateAndSet(str_val)
. = ..()
if(. && MC_RUNNING())
world.sleep_offline = !config_entry_value
/datum/config_entry/number/rounds_until_hard_restart
default = -1
min_val = 0
/datum/config_entry/flag/auto_profile
/datum/config_entry/number/profiler_interval
default = 300 SECONDS
/datum/config_entry/number/drift_dump_threshold
default = 4 SECONDS
/datum/config_entry/number/drift_profile_delay
default = 15 SECONDS
/datum/config_entry/flag/forbid_all_profiling
/datum/config_entry/flag/forbid_admin_profiling
/datum/config_entry/flag/toast_notification_on_init
/// If admins with +DEBUG can initialize byond-tracy midround.
/datum/config_entry/flag/allow_tracy_start
protection = CONFIG_ENTRY_LOCKED
/// If admins with +DEBUG can queue byond-tracy to run the next round.
/datum/config_entry/flag/allow_tracy_queue
protection = CONFIG_ENTRY_LOCKED

View File

@@ -161,8 +161,8 @@
/datum/emergency_shuttle_controller/proc/get_shuttle_prep_time()
// During mutiny rounds, the shuttle takes twice as long.
if(ticker && ticker.mode)
return SHUTTLE_PREPTIME * ticker.mode.shuttle_delay
if(SSticker && SSticker.mode)
return SHUTTLE_PREPTIME * SSticker.mode.shuttle_delay
return SHUTTLE_PREPTIME

View File

@@ -24,6 +24,9 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
var/running = TRUE
/datum/controller/failsafe/New()
// Ensure usr is null, to prevent any potential weirdness resulting from the failsafe having a usr if it's manually restarted.
usr = null
// Highlander-style: there can only be one! Kill off the old and replace it with the new.
if(Failsafe != src)
if(istype(Failsafe))
@@ -149,7 +152,7 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
/proc/recover_all_SS_and_recreate_master()
del(Master)
var/list/subsytem_types = subtypesof(/datum/controller/subsystem)
sortTim(subsytem_types, GLOBAL_PROC_REF(cmp_subsystem_init))
sortTim(subsytem_types, GLOBAL_PROC_REF(cmp_subsystem_init_stage))
for(var/I in subsytem_types)
new I
. = Recreate_MC()

View File

@@ -1,9 +1,10 @@
// See initialization order in /code/game/world.dm
GLOBAL_REAL(GLOB, /datum/controller/global_vars)
/datum/controller/global_vars
name = "Global Variables"
var/list/gvars_datum_protected_varlist
var/static/list/gvars_datum_protected_varlist
var/list/gvars_datum_in_built_vars
var/list/gvars_datum_init_order
@@ -13,30 +14,24 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars)
GLOB = src
var/datum/controller/exclude_these = new
gvars_datum_in_built_vars = exclude_these.vars + list("gvars_datum_protected_varlist", "gvars_datum_in_built_vars", "gvars_datum_init_order")
// I know this is dumb but the nested vars list hangs a ref to the datum. This fixes that
// I have an issue report open, lummox has not responded. It might be a FeaTuRE
// Sooo we gotta be dumb
var/list/controller_vars = exclude_these.vars.Copy()
controller_vars["vars"] = null
gvars_datum_in_built_vars = controller_vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order))
log_world("[vars.len - gvars_datum_in_built_vars.len] global variables")
QDEL_IN(exclude_these, 0) //signal logging isn't ready
Initialize(exclude_these)
Initialize()
/datum/controller/global_vars/Destroy(force)
stack_trace("There was an attempt to qdel the global vars holder!")
if(!force)
return QDEL_HINT_LETMELIVE
QDEL_NULL(statclick)
gvars_datum_protected_varlist.Cut()
gvars_datum_in_built_vars.Cut()
GLOB = null
return ..()
// This is done to prevent an exploit where admins can get around protected vars
SHOULD_CALL_PARENT(FALSE)
return QDEL_HINT_IWILLGC
/datum/controller/global_vars/stat_entry(msg)
if(!statclick)
statclick = new/obj/effect/statclick/debug(null, "Initializing...", src)
msg = "GLOB: [statclick.update("Edit")]"
msg = "Edit"
return msg
/datum/controller/global_vars/vv_edit_var(var_name, var_value)
@@ -44,6 +39,14 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars)
return FALSE
return ..()
/*
/datum/controller/global_vars/vv_get_var(var_name)
switch(var_name)
if (NAMEOF(src, vars))
return debug_variable(var_name, list(), 0, src)
return debug_variable(var_name, vars[var_name], 0, src, display_flags = VV_ALWAYS_CONTRACT_LIST)
*/
/datum/controller/global_vars/Initialize()
gvars_datum_init_order = list()
gvars_datum_protected_varlist = list(NAMEOF(src, gvars_datum_protected_varlist) = TRUE)
@@ -55,8 +58,8 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars)
var/list/expected_global_procs = vars - gvars_datum_in_built_vars
for(var/I in global_procs)
expected_global_procs -= replacetext("[I]", "InitGlobal", "")
var/english_missing = expected_global_procs.Join(", ")
log_world("Missing procs: [english_missing]")
log_world("Missing procs: [expected_global_procs.Join(", ")]")
for(var/I in global_procs)
var/start_tick = world.time
call(src, I)()

File diff suppressed because it is too large Load Diff

View File

@@ -42,7 +42,7 @@ var/global/last_tick_duration = 0
transfer_controller = new
admin_notice(span_danger("Initializations complete."), R_DEBUG)
// #if UNIT_TEST
// #if UNIT_TESTS
// # define CHECK_SLEEP_MASTER // For unit tests we don't care about a smooth lobby screen experience. We care about speed.
// #else
// # define CHECK_SLEEP_MASTER if(++initialized_objects > 500) { initialized_objects=0;sleep(world.tick_lag); }

View File

@@ -13,27 +13,48 @@
/// Name of the subsystem - you must change this
name = "fire coderbus"
/// Order of initialization. Higher numbers are initialized first, lower numbers later. Use or create defines such as [INIT_ORDER_DEFAULT] so we can see the order in one file.
var/init_order = INIT_ORDER_DEFAULT
/// Determines which subsystems this subsystem is dependant on to initialize. Will initialize after all specified subsystems.
/// If init_stage is earlier than a dependent subsystem, will throw an error and push the init stage forward to that subsystem.
/// Usage: Put the typepaths of the subsystems that need to init before this one in this list.
var/list/dependencies = list()
/// The inverse of the dependencies. Can be set manually, but will also get evaluated at runtime. Turns into a list of instances at runtime.
/// Usage: Put the typepaths of the subsystems that need to init after this one in this list.
var/list/dependents
/// ID of the subsystem. Set automatically when the dependency graph is evaluated. Used primarily in determining order.
var/ordering_id = 0
/// Do not modify. Automatically set when the dependency graph is evaluated. Similar to ordering_id, but evaluated after init_stage.
var/init_order = 0
/// Time to wait (in deciseconds) between each call to fire(). Must be a positive integer.
var/wait = 20
/// Priority Weight: When mutiple subsystems need to run in the same tick, higher priority subsystems will be given a higher share of the tick before MC_TICK_CHECK triggers a sleep, higher priority subsystems also run before lower priority subsystems.
/// Priority Weight: When multiple subsystems need to run in the same tick, higher priority subsystems will be given a higher share of the tick before MC_TICK_CHECK triggers a sleep, higher priority subsystems also run before lower priority subsystems
var/priority = FIRE_PRIORITY_DEFAULT
/// [Subsystem Flags][SS_NO_INIT] to control binary behavior. Flags must be set at compile time or before preinit finishes to take full effect. (You can also restart the mc to force them to process again)
var/flags = NONE
/// This var is set to TRUE after the subsystem has been initialized.
var/subsystem_initialized = FALSE
/// Which stage does this subsystem init at. Earlier stages can fire while later stages init.
var/init_stage = INITSTAGE_MAIN
/// This var is set to `INITIALIZATION_INNEW_REGULAR` after the subsystem has been initialized.
var/initialized = FALSE
/// Set to 0 to prevent fire() calls, mostly for admin use or subsystems that may be resumed later
/// use the [SS_NO_FIRE] flag instead for systems that never fire to keep it from even being added to list that is checked every tick
var/can_fire = TRUE
///Bitmap of what game states can this subsystem fire at. See [RUNLEVELS_DEFAULT] for more details.
var/runlevels = RUNLEVELS_DEFAULT
var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire
/**
* boolean set by admins. if TRUE then this subsystem will stop the world profiler after ignite() returns and start it again when called.
* used so that you can audit a specific subsystem or group of subsystems' synchronous call chain.
*/
var/profiler_focused = FALSE
/*
* The following variables are managed by the MC and should not be modified directly.
@@ -54,6 +75,9 @@
/// Running average of the amount of tick usage (in percents of a game tick) the subsystem has spent past its allocated time without pausing
var/tick_overrun = 0
/// Flat list of usage and time, every odd index is a log time, every even index is a usage
var/list/rolling_usage = list()
/// How much of a tick (in percents of a tick) were we allocated last fire.
var/tick_allocation_last = 0
@@ -78,6 +102,9 @@
/// Tracks the amount of completed runs for the subsystem
var/times_fired = 0
/// How many fires have we been requested to postpone
var/postponed_fires = 0
/// Time the subsystem entered the queue, (for timing and priority reasons)
var/queued_time = 0
@@ -92,9 +119,13 @@
/// Previous subsystem in the queue of subsystems to run this tick
var/datum/controller/subsystem/queue_prev
/// String to store an applicable error message for a subsystem crashing, used to help debug crashes in contexts such as Continuous Integration/Unit Tests
var/initialization_failure_message = null
//Do not blindly add vars here to the bottom, put it where it goes above
//If your var only has two values, put it in as a flag.
//Do not override
///datum/controller/subsystem/New()
@@ -107,7 +138,7 @@
///This is used so the mc knows when the subsystem sleeps. do not override.
/datum/controller/subsystem/proc/ignite(resumed = FALSE)
SHOULD_NOT_OVERRIDE(TRUE)
set waitfor = 0
set waitfor = FALSE
. = SS_IDLE
tick_allocation_last = Master.current_ticklimit-(TICK_USAGE)
@@ -131,7 +162,7 @@
///Sleeping in here prevents future fires until returned.
/datum/controller/subsystem/proc/fire(resumed = FALSE)
flags |= SS_NO_FIRE
throw EXCEPTION("Subsystem [src]([type]) does not fire() but did not set the SS_NO_FIRE flag. Please add the SS_NO_FIRE flag to any subsystem that doesn't fire so it doesn't get added to the processing list and waste cpu.")
CRASH("Subsystem [src]([type]) does not fire() but did not set the SS_NO_FIRE flag. Please add the SS_NO_FIRE flag to any subsystem that doesn't fire so it doesn't get added to the processing list and waste cpu.")
/datum/controller/subsystem/Destroy()
dequeue()
@@ -141,6 +172,31 @@
Master.subsystems -= src
return ..()
/** Update next_fire for the next run.
* reset_time (bool) - Ignore things that would normally alter the next fire, like tick_overrun, and last_fire. (also resets postpone)
*/
/datum/controller/subsystem/proc/update_nextfire(reset_time = FALSE)
var/queue_node_flags = flags
if (reset_time)
postponed_fires = 0
if (queue_node_flags & SS_TICKER)
next_fire = world.time + (world.tick_lag * wait)
else
next_fire = world.time + wait
return
if (queue_node_flags & SS_TICKER)
next_fire = world.time + (world.tick_lag * wait)
else if (queue_node_flags & SS_POST_FIRE_TIMING)
next_fire = world.time + wait + (world.tick_lag * (tick_overrun/100))
else if (queue_node_flags & SS_KEEP_TIMING)
next_fire += wait
else
next_fire = queued_time + wait + (world.tick_lag * (tick_overrun/100))
///Queue it to run.
/// (we loop thru a linked list until we get to the end or find the right point)
/// (this lets us sort our run order correctly without having to re-sort the entire already sorted list)
@@ -155,8 +211,8 @@
queue_node_priority = queue_node.queued_priority
queue_node_flags = queue_node.flags
if (queue_node_flags & SS_TICKER)
if (!(SS_flags & SS_TICKER))
if (queue_node_flags & (SS_TICKER|SS_BACKGROUND) == SS_TICKER)
if ((SS_flags & (SS_TICKER|SS_BACKGROUND)) != SS_TICKER)
continue
if (queue_node_priority < SS_priority)
break
@@ -207,9 +263,9 @@
queue_next.queue_prev = queue_prev
if (queue_prev)
queue_prev.queue_next = queue_next
if (src == Master.queue_tail)
if (Master && (src == Master.queue_tail))
Master.queue_tail = queue_prev
if (src == Master.queue_head)
if (Master && (src == Master.queue_head))
Master.queue_head = queue_next
queued_time = 0
if (state == SS_QUEUED)
@@ -228,14 +284,13 @@
/datum/controller/subsystem/proc/OnConfigLoad()
/**
* Used to initialize the subsystem. This is expected to be overriden by subtypes.
* Used to initialize the subsystem. This is expected to be overridden by subtypes.
*/
/datum/controller/subsystem/Initialize()
return SS_INIT_NONE
//hook for printing stats to the "MC" statuspanel for admins to see performance and related stats etc.
/datum/controller/subsystem/stat_entry(msg)
if(can_fire && !(SS_NO_FIRE & flags))
if(can_fire && !(SS_NO_FIRE & flags) && init_stage <= Master.init_stage_completed)
msg = "[round(cost,1)]ms|[round(tick_usage,1)]%([round(tick_overrun,1)]%)|[round(ticks,0.1)]\t[msg]"
else
msg = "OFFLINE\t[msg]"
@@ -254,45 +309,30 @@
if (SS_IDLE)
. = " "
//could be used to postpone a costly subsystem for (default one) var/cycles, cycles
//for instance, during cpu intensive operations like explosions
/// Causes the next "cycle" fires to be missed. Effect is accumulative but can reset by calling update_nextfire(reset_time = TRUE)
/datum/controller/subsystem/proc/postpone(cycles = 1)
if(next_fire - world.time < wait)
next_fire += (wait*cycles)
if (can_fire && cycles >= 1)
postponed_fires += cycles
/// Prunes out of date entries in our rolling usage list
/datum/controller/subsystem/proc/prune_rolling_usage()
var/list/rolling_usage = src.rolling_usage
var/cut_to = 0
while(cut_to + 2 <= length(rolling_usage) && rolling_usage[cut_to + 1] < DS2TICKS(world.time - Master.rolling_usage_length))
cut_to += 2
if(cut_to)
rolling_usage.Cut(1, cut_to + 1)
//usually called via datum/controller/subsystem/New() when replacing a subsystem (i.e. due to a recurring crash)
//should attempt to salvage what it can from the old instance of subsystem
/datum/controller/subsystem/Recover()
// Suspends this subsystem from being queued for running. If already in the queue, sleeps until idle. Returns FALSE if the subsystem was already suspended.
/datum/controller/subsystem/proc/suspend()
. = (can_fire > 0) // Return true if we were previously runnable, false if previously suspended.
can_fire = FALSE
// Safely sleep in a loop until the subsystem is idle, (or its been un-suspended somehow)
while(can_fire <= 0 && state != SS_IDLE)
stoplag() // Safely sleep in a loop until
// Wakes a suspended subsystem.
/datum/controller/subsystem/proc/wake()
can_fire = TRUE
// This subsystem has destabilized the game and is being put on warning. At this point there may be
// an opportunity to clean up the subsystem or check it for errors in ways that would otherwise be too slow.
// You should log the errors/cleanup results, so you can fix the problem rather than using this as a crutch.
/datum/controller/subsystem/proc/fail()
var/msg = "[name] subsystem being blamed for MC failure"
log_world(msg)
log_game(msg)
// DO NOT ATTEMPT RECOVERY. Only log debugging info. You should leave the subsystem as it is.
// Attempting recovery here could make things worse, create hard recursions with the MC disabling it every run, etc.
/datum/controller/subsystem/proc/critfail()
var/msg = "[name] subsystem received final blame for MC failure"
log_world(msg)
log_game(msg)
/datum/controller/subsystem/vv_edit_var(var_name, var_value)
switch (var_name)
if (NAMEOF(src, can_fire))
//this is so the subsystem doesn't rapid fire to make up missed ticks causing more lag
if (var_value)
update_nextfire(reset_time = TRUE)
if (NAMEOF(src, queued_priority)) //editing this breaks things.
return FALSE
. = ..()

View File

@@ -3,7 +3,7 @@ GENERAL_PROTECT_DATUM(/datum/controller/subsystem/admin_verbs)
SUBSYSTEM_DEF(admin_verbs)
name = "Admin Verbs"
flags = SS_NO_FIRE
//init_stage = INITSTAGE_EARLY
init_stage = INITSTAGE_EARLY
/// A list of all admin verbs indexed by their type.
var/list/datum/admin_verb/admin_verbs_by_type = list()
/// A list of all admin verbs indexed by their visibility flag.

View File

@@ -1,11 +1,15 @@
SUBSYSTEM_DEF(ai)
name = "AI"
init_order = INIT_ORDER_AI
priority = FIRE_PRIORITY_AI
wait = 2 SECONDS
flags = SS_NO_INIT
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
dependencies = list(
/datum/controller/subsystem/air,
/datum/controller/subsystem/mobs
)
var/list/processing = list()
var/list/currentrun = list()

View File

@@ -1,11 +1,14 @@
SUBSYSTEM_DEF(aifast)
name = "AI (Fast)"
init_order = INIT_ORDER_AI_FAST
priority = FIRE_PRIORITY_AI
wait = 0.25 SECONDS // Every quarter second
flags = SS_NO_INIT
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
dependencies = list(
/datum/controller/subsystem/ai
)
var/list/processing = list()
var/list/currentrun = list()

View File

@@ -65,7 +65,9 @@ Class Procs:
SUBSYSTEM_DEF(air)
name = "Air"
init_order = INIT_ORDER_AIR
dependencies = list(
/datum/controller/subsystem/atoms
)
priority = FIRE_PRIORITY_AIR
wait = 2 SECONDS // seconds (We probably can speed this up actually)
flags = SS_BACKGROUND // TODO - Should this really be background? It might be important.

View File

@@ -6,7 +6,7 @@ SUBSYSTEM_DEF(atc)
priority = FIRE_PRIORITY_ATC
runlevels = RUNLEVEL_GAME
wait = 2 SECONDS
init_order = INIT_ORDER_ATC
init_stage = INITSTAGE_LAST
flags = SS_BACKGROUND
VAR_PRIVATE/next_tick = 0

View File

@@ -16,6 +16,11 @@ SUBSYSTEM_DEF(airflow)
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
priority = FIRE_PRIORITY_AIRFLOW
dependencies = list(
/datum/controller/subsystem/atoms,
/datum/controller/subsystem/air
)
var/list/processing = list()
var/list/currentrun = list()

View File

@@ -2,7 +2,9 @@ SUBSYSTEM_DEF(alarm)
name = "Alarm"
wait = 2 SECONDS
priority = FIRE_PRIORITY_ALARM
init_order = INIT_ORDER_ALARM
dependencies = list(
/datum/controller/subsystem/mapping
)
var/list/datum/alarm/all_handlers
var/tmp/list/currentrun = null
var/static/list/active_alarm_cache = list()

View File

@@ -13,7 +13,6 @@ SUBSYSTEM_DEF(asset_loading)
while(length(generate_queue))
var/datum/asset/to_load = generate_queue[generate_queue.len]
last_queue_len = length(generate_queue)
generate_queue.len--

View File

@@ -1,6 +1,11 @@
SUBSYSTEM_DEF(assets)
name = "Assets"
init_order = INIT_ORDER_ASSETS
dependencies = list(
/datum/controller/subsystem/holomaps,
/datum/controller/subsystem/robot_sprites
///datum/controller/subsystem/persistent_paintings,
///datum/controller/subsystem/greyscale_previews,
)
flags = SS_NO_FIRE
var/list/datum/asset_cache_item/cache = list()
var/list/preload = list()
@@ -32,7 +37,7 @@ SUBSYSTEM_DEF(assets)
transport.Initialize(cache)
subsystem_initialized = TRUE
initialized = TRUE
return SS_INIT_SUCCESS
/datum/controller/subsystem/assets/Recover()

View File

@@ -1,6 +1,14 @@
SUBSYSTEM_DEF(atoms)
name = "Atoms"
init_order = INIT_ORDER_ATOMS
dependencies = list(
/datum/controller/subsystem/garbage,
/datum/controller/subsystem/mapping,
/datum/controller/subsystem/planets,
/datum/controller/subsystem/transcore,
/datum/controller/subsystem/chemistry,
/datum/controller/subsystem/sounds,
/datum/controller/subsystem/job
)
flags = SS_NO_FIRE
/// A stack of list(source, desired initialized state)
@@ -8,7 +16,7 @@ SUBSYSTEM_DEF(atoms)
var/list/initialized_state = list()
var/base_initialized
var/initialized = INITIALIZATION_INSSATOMS
var/atom_initialized = INITIALIZATION_INSSATOMS
var/list/late_loaders = list()
var/list/BadInitializeCalls = list()
@@ -25,19 +33,19 @@ SUBSYSTEM_DEF(atoms)
var/list/mapload_init_times = list()
#endif
initialized = INITIALIZATION_INSSATOMS
atom_initialized = INITIALIZATION_INSSATOMS
/datum/controller/subsystem/atoms/Initialize()
init_start_time = world.time
initialized = INITIALIZATION_INNEW_MAPLOAD
atom_initialized = INITIALIZATION_INNEW_MAPLOAD
InitializeAtoms()
initialized = INITIALIZATION_INNEW_REGULAR
atom_initialized = INITIALIZATION_INNEW_REGULAR
return SS_INIT_SUCCESS
/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms, list/atoms_to_return)
if(initialized == INITIALIZATION_INSSATOMS)
if(atom_initialized == INITIALIZATION_INSSATOMS)
return
// Generate a unique mapload source for this run of InitializeAtoms
@@ -142,9 +150,9 @@ SUBSYSTEM_DEF(atoms)
/// Accepts a state and a source, the most recent state is used, sources exist to prevent overriding old values accidentally
/datum/controller/subsystem/atoms/proc/set_tracked_initalized(state, source)
if(!length(initialized_state))
base_initialized = initialized
base_initialized = atom_initialized
initialized_state += list(list(source, state))
initialized = state
atom_initialized = state
/datum/controller/subsystem/atoms/proc/clear_tracked_initalize(source)
if(!length(initialized_state))
@@ -155,18 +163,18 @@ SUBSYSTEM_DEF(atoms)
break
if(!length(initialized_state))
initialized = base_initialized
atom_initialized = base_initialized
base_initialized = INITIALIZATION_INNEW_REGULAR
return
initialized = initialized_state[length(initialized_state)][2]
atom_initialized = initialized_state[length(initialized_state)][2]
/// Returns TRUE if anything is currently being initialized
/datum/controller/subsystem/atoms/proc/initializing_something()
return length(initialized_state) > 1
/datum/controller/subsystem/atoms/Recover()
initialized = SSatoms.initialized
if(initialized == INITIALIZATION_INNEW_MAPLOAD)
atom_initialized = SSatoms.atom_initialized
if(atom_initialized == INITIALIZATION_INNEW_MAPLOAD)
InitializeAtoms()
initialized_state = SSatoms.initialized_state
BadInitializeCalls = SSatoms.BadInitializeCalls
@@ -187,7 +195,7 @@ SUBSYSTEM_DEF(atoms)
/// Prepares an atom to be deleted once the atoms SS is initialized.
/datum/controller/subsystem/atoms/proc/prepare_deletion(atom/target)
if (initialized == INITIALIZATION_INNEW_REGULAR)
if (atom_initialized == INITIALIZATION_INNEW_REGULAR)
// Atoms SS has already completed, just kill it now.
qdel(target)
else

View File

@@ -1,6 +1,5 @@
SUBSYSTEM_DEF(character_setup)
name = "Character Setup"
init_order = INIT_ORDER_DEFAULT
priority = FIRE_PRIORITY_CHARSETUP
flags = SS_BACKGROUND | SS_NO_INIT
wait = 1 SECOND

View File

@@ -8,7 +8,7 @@ SUBSYSTEM_DEF(chat)
flags = SS_TICKER|SS_NO_INIT
wait = 1
priority = FIRE_PRIORITY_CHAT
init_order = INIT_ORDER_CHAT
init_stage = INITSTAGE_LAST
/// Assosciates a ckey with a list of messages to send to them.
var/list/list/datum/chat_payload/client_to_payloads = list()

View File

@@ -2,7 +2,9 @@ SUBSYSTEM_DEF(chemistry)
name = "Chemistry"
wait = 20
flags = SS_NO_FIRE
init_order = INIT_ORDER_CHEMISTRY
dependencies = list(
/datum/controller/subsystem/garbage
)
var/list/chemical_reactions = list()
var/list/chemical_reactions_by_product = list()

View File

@@ -4,8 +4,12 @@
//
SUBSYSTEM_DEF(circuit)
name = "Circuit"
init_order = INIT_ORDER_CIRCUIT
flags = SS_NO_FIRE
dependencies = list(
/datum/controller/subsystem/atoms
)
var/list/all_components = list() // Associative list of [component_name]:[component_path] pairs
var/list/cached_components = list() // Associative list of [component_path]:[component] pairs
var/list/all_assemblies = list() // Associative list of [assembly_name]:[assembly_path] pairs

View File

@@ -0,0 +1,207 @@
/*
HOW IT WORKS
The SSradio is a global object maintaining all radio transmissions, think about it as about "ether".
Note that walkie-talkie, intercoms and headsets handle transmission using nonstandard way.
procs:
add_object(obj/device as obj, var/new_frequency as num, var/radio_filter as text|null = null)
Adds listening object.
parameters:
device - device receiving signals, must have proc receive_signal (see description below).
one device may listen several frequencies, but not same frequency twice.
new_frequency - see possibly frequencies below;
radio_filter - thing for optimization. Optional, but recommended.
All filters should be consolidated in this file, see defines later.
Device without listening filter will receive all signals (on specified frequency).
Device with filter will receive any signals sent without filter.
Device with filter will not receive any signals sent with different filter.
returns:
Reference to frequency object.
remove_object (obj/device, old_frequency)
Obliviously, after calling this proc, device will not receive any signals on old_frequency.
Other frequencies will left unaffected.
return_frequency(var/frequency as num)
returns:
Reference to frequency object. Use it if you need to send and do not need to listen.
radio_frequency is a global object maintaining list of devices that listening specific frequency.
procs:
post_signal(obj/source as obj|null, datum/signal/signal, var/radio_filter as text|null = null, var/range as num|null = null)
Sends signal to all devices that wants such signal.
parameters:
source - object, emitted signal. Usually, devices will not receive their own signals.
signal - see description below.
radio_filter - described above.
range - radius of regular byond's square circle on that z-level. null means everywhere, on all z-levels.
obj/proc/receive_signal(datum/signal/signal, var/receive_method as num, var/receive_param)
Handler from received signals. By default does nothing. Define your own for your object.
Avoid of sending signals directly from this proc, use spawn(-1). DO NOT use sleep() here or call procs that sleep please. If you must, use spawn()
parameters:
signal - see description below. Extract all needed data from the signal before doing sleep(), spawn() or return!
receive_method - may be TRANSMISSION_WIRE or TRANSMISSION_RADIO.
TRANSMISSION_WIRE is currently unused.
receive_param - for TRANSMISSION_RADIO here comes frequency.
datum/signal
vars:
source
an object that emitted signal. Used for debug and bearing.
data
list with transmitting data. Usual use pattern:
data["msg"] = "hello world"
encryption
Some number symbolizing "encryption key".
Note that game actually do not use any cryptography here.
If receiving object don't know right key, it must ignore encrypted signal in its receive_signal.
*/
SUBSYSTEM_DEF(radio)
name = "Radio"
flags = SS_NO_FIRE
dependencies = list(
/datum/controller/subsystem/machines
)
var/list/datum/radio_frequency/frequencies = list()
/datum/controller/subsystem/radio/Initialize()
GLOB.autospeaker = new (null, FALSE, null, null, TRUE) //Set up Global Announcer
return SS_INIT_SUCCESS
/datum/controller/subsystem/radio/proc/add_object(obj/device as obj, var/new_frequency as num, var/radio_filter = null as text|null)
var/f_text = num2text(new_frequency)
var/datum/radio_frequency/frequency = frequencies[f_text]
if(!frequency)
frequency = new
frequency.frequency = new_frequency
frequencies[f_text] = frequency
frequency.add_listener(device, radio_filter)
return frequency
/datum/controller/subsystem/radio/proc/remove_object(obj/device, old_frequency)
var/f_text = num2text(old_frequency)
var/datum/radio_frequency/frequency = frequencies[f_text]
if(frequency)
frequency.remove_listener(device)
if(frequency.devices.len == 0)
qdel(frequency)
frequencies -= f_text
return 1
/datum/controller/subsystem/radio/proc/return_frequency(var/new_frequency as num)
var/f_text = num2text(new_frequency)
var/datum/radio_frequency/frequency = frequencies[f_text]
if(!frequency)
frequency = new
frequency.frequency = new_frequency
frequencies[f_text] = frequency
return frequency
//Frequency channels
/datum/radio_frequency
var/frequency as num
var/list/list/obj/devices = list()
/datum/radio_frequency/proc/post_signal(obj/source as obj|null, datum/signal/signal, var/radio_filter = null as text|null, var/range = null as num|null)
var/turf/start_point
if(range)
start_point = get_turf(source)
if(!start_point)
qdel(signal)
return 0
if (radio_filter)
send_to_filter(source, signal, radio_filter, start_point, range)
send_to_filter(source, signal, RADIO_DEFAULT, start_point, range)
else
//Broadcast the signal to everyone!
for (var/next_filter in devices)
send_to_filter(source, signal, next_filter, start_point, range)
//Sends a signal to all machines belonging to a given filter. Should be called by post_signal()
/datum/radio_frequency/proc/send_to_filter(obj/source, datum/signal/signal, var/radio_filter, var/turf/start_point = null, var/range = null)
if (range && !start_point)
return
for(var/obj/device in devices[radio_filter])
if(device == source)
continue
if(range)
var/turf/end_point = get_turf(device)
if(!end_point)
continue
if(start_point.z!=end_point.z || get_dist(start_point, end_point) > range)
continue
device.receive_signal(signal, TRANSMISSION_RADIO, frequency)
/datum/radio_frequency/proc/add_listener(obj/device as obj, var/radio_filter as text|null)
if (!radio_filter)
radio_filter = RADIO_DEFAULT
//log_admin("add_listener(device=[device],radio_filter=[radio_filter]) frequency=[frequency]")
var/list/obj/devices_line = devices[radio_filter]
if (!devices_line)
devices_line = new
devices[radio_filter] = devices_line
devices_line+=device
// var/list/obj/devices_line___ = devices[filter_str]
// var/l = devices_line___.len
//log_admin("DEBUG: devices_line.len=[devices_line.len]")
//log_admin("DEBUG: devices(filter_str).len=[l]")
/datum/radio_frequency/proc/remove_listener(obj/device)
for (var/devices_filter in devices)
var/list/devices_line = devices[devices_filter]
devices_line-=device
while (null in devices_line)
devices_line -= null
if (devices_line.len==0)
devices -= devices_filter
/datum/signal
var/obj/source
var/transmission_method = 0 //unused at the moment
//0 = wire
//1 = radio transmission
//2 = subspace transmission
var/list/data = list()
var/encryption
var/frequency = 0
/datum/signal/proc/copy_from(datum/signal/model)
source = model.source
transmission_method = model.transmission_method
data = model.data
encryption = model.encryption
frequency = model.frequency
/datum/signal/proc/debug_print()
if (source)
. = "signal = {source = '[source]' ([source:x],[source:y],[source:z])\n"
else
. = "signal = {source = '[source]' ()\n"
for (var/i in data)
. += "data\[\"[i]\"\] = \"[data[i]]\"\n"
if(islist(data[i]))
var/list/L = data[i]
for(var/t in L)
. += "data\[\"[i]\"\] list has: [t]"
//callback used by objects to react to incoming radio signals
/obj/proc/receive_signal(datum/signal/signal, receive_method, receive_param)
return null

View File

@@ -1,8 +1,10 @@
SUBSYSTEM_DEF(dbcore)
name = "Database"
flags = SS_TICKER
init_stage = INITSTAGE_EARLY
wait = 10 // Not seconds because we're running on SS_TICKER
init_order = INIT_ORDER_DBCORE
runlevels = RUNLEVEL_LOBBY|RUNLEVELS_DEFAULT
priority = FIRE_PRIORITY_DATABASE
var/failed_connection_timeout = 0
@@ -41,6 +43,7 @@ SUBSYSTEM_DEF(dbcore)
//var/db_daemon_started = FALSE
/datum/controller/subsystem/dbcore/Initialize()
Connect()
return SS_INIT_SUCCESS
/datum/controller/subsystem/dbcore/stat_entry(msg)

View File

@@ -1,35 +0,0 @@
/**
* Sends a message to TGS chat channels.
*
* message - The message to send.
* channel_tag - Required. If "", the message with be sent to all connected (Game-type for TGS3) channels. Otherwise, it will be sent to TGS4 channels with that tag (Delimited by ','s).
*/
/proc/send2chat(message, channel_tag)
if(channel_tag == null || !world.TgsAvailable())
return
var/datum/tgs_version/version = world.TgsVersion()
if(channel_tag == "" || version.suite == 3)
world.TgsTargetedChatBroadcast(message, FALSE)
return
var/list/channels_to_use = list()
for(var/I in world.TgsChatChannelInfo())
var/datum/tgs_chat_channel/channel = I
var/list/applicable_tags = splittext(channel.tag, ",")
if(channel_tag in applicable_tags)
channels_to_use += channel
if(channels_to_use.len)
world.TgsChatBroadcast(message, channels_to_use)
/**
* Sends a message to TGS admin chat channels.
*
* category - The category of the mssage.
* message - The message to send.
*/
/proc/send2adminchat(category, message)
category = replacetext(replacetext(category, "\proper", ""), "\improper", "")
message = replacetext(replacetext(message, "\proper", ""), "\improper", "")
world.TgsTargetedChatBroadcast("[category] | [message]", TRUE)

View File

@@ -1,6 +1,7 @@
SUBSYSTEM_DEF(events)
name = "Events" // VOREStation Edit - This is still the main events subsystem for us.
wait = 2 SECONDS
init_stage = INITSTAGE_LAST
var/tmp/list/currentrun = null

View File

@@ -5,6 +5,9 @@ SUBSYSTEM_DEF(event_ticker)
name = "Events (Ticker)"
wait = 2 SECONDS
runlevels = RUNLEVEL_GAME
dependencies = list(
/datum/controller/subsystem/events
)
// List of `/datum/event2/event`s that are currently active, and receiving process() ticks.
var/list/active_events = list()

View File

@@ -98,7 +98,7 @@ SUBSYSTEM_DEF(game_master)
// These are ran before committing to an event.
// Returns TRUE if the system is allowed to procede, otherwise returns FALSE.
/datum/controller/subsystem/game_master/proc/pre_event_checks(quiet = FALSE)
if(!ticker || ticker.current_state != GAME_STATE_PLAYING)
if(!SSticker || SSticker.current_state != GAME_STATE_PLAYING)
if(!quiet)
log_game_master("Unable to start event: Ticker is nonexistent, or the game is not ongoing.")
return FALSE

View File

@@ -27,8 +27,7 @@ SUBSYSTEM_DEF(garbage)
wait = 2 SECONDS
flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
init_order = INIT_ORDER_GARBAGE
// init_stage = INITSTAGE_EARLY
init_stage = INITSTAGE_FIRST
var/list/collection_timeout = list(GC_FILTER_QUEUE, GC_CHECK_QUEUE, GC_DEL_QUEUE) // deciseconds to wait before moving something up in the queue to the next level

View File

@@ -4,8 +4,10 @@
//
SUBSYSTEM_DEF(holomaps)
name = "HoloMiniMaps"
init_order = INIT_ORDER_HOLOMAPS
flags = SS_NO_FIRE
dependencies = list(
/datum/controller/subsystem/atoms
)
var/static/holomaps_initialized = FALSE
var/static/list/holoMiniMaps = list()
var/static/list/extraMiniMaps = list()

View File

@@ -1,7 +1,7 @@
SUBSYSTEM_DEF(input)
name = "Input"
wait = 1 // SS_TICKER means this runs every tick
init_order = INIT_ORDER_INPUT
init_stage = INITSTAGE_EARLY
flags = SS_TICKER | SS_NO_INIT
priority = FIRE_PRIORITY_INPUT
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY

View File

@@ -6,7 +6,12 @@
SUBSYSTEM_DEF(internal_wiki)
name = "Wiki"
wait = 1
init_order = INIT_ORDER_WIKI
//dependencies = list(
// /datum/controller/subsystem/chemistry,
// /datum/controller/subsystem/plants,
// /datum/controller/subsystem/supply
//)
init_stage = INITSTAGE_LAST
flags = SS_NO_FIRE
VAR_PRIVATE/list/pages = list()

View File

@@ -1,6 +1,8 @@
SUBSYSTEM_DEF(job)
name = "Job"
init_order = INIT_ORDER_JOB
dependencies = list(
/datum/controller/subsystem/mapping,
)
flags = SS_NO_FIRE
var/list/occupations = list() //List of all jobs

View File

@@ -1,7 +1,9 @@
SUBSYSTEM_DEF(lighting)
name = "Lighting"
dependencies = list(
/datum/controller/subsystem/machines
)
wait = 1
init_order = INIT_ORDER_LIGHTING
flags = SS_TICKER
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY // Do some work during lobby waiting period. May as well.
var/sun_mult = 1.0
@@ -19,8 +21,8 @@ SUBSYSTEM_DEF(lighting)
/datum/controller/subsystem/lighting/Initialize()
if(!subsystem_initialized)
subsystem_initialized = TRUE
if(!initialized)
initialized = TRUE
create_all_lighting_objects()
for(var/datum/planet/planet in SSplanets.planets)
@@ -177,5 +179,5 @@ SUBSYSTEM_DEF(lighting)
/datum/controller/subsystem/lighting
/datum/controller/subsystem/lighting/Recover()
subsystem_initialized = SSlighting.subsystem_initialized
initialized = SSlighting.initialized
..()

View File

@@ -1,7 +1,6 @@
SUBSYSTEM_DEF(lobby_monitor)
name = "Lobby Art"
init_order = INIT_ORDER_LOBBY
// init_stage = INITSTAGE_EARLY
init_stage = INITSTAGE_EARLY
flags = SS_NO_INIT
wait = 1 SECOND
runlevels = ALL

View File

@@ -9,8 +9,10 @@
SUBSYSTEM_DEF(machines)
name = "Machines"
dependencies = list(
/datum/controller/subsystem/points_of_interest
)
priority = FIRE_PRIORITY_MACHINES
init_order = INIT_ORDER_MACHINES
flags = SS_KEEP_TIMING
runlevels = RUNLEVEL_GAME|RUNLEVEL_POSTGAME

View File

@@ -1,19 +1,32 @@
// Handles map-related tasks, mostly here to ensure it does so after the MC initializes.
SUBSYSTEM_DEF(mapping)
name = "Mapping"
init_order = INIT_ORDER_MAPPING
//dependencies = list(
// /datum/controller/subsystem/job,
///datum/controller/subsystem/processing/station,
// /datum/controller/subsystem/chemistry
// ///datum/controller/subsystem/processing/reagents
//)
dependencies = list(
///datum/controller/subsystem/garbage,
/datum/controller/subsystem/vis_overlays,
/datum/controller/subsystem/chemistry
)
flags = SS_NO_FIRE
var/list/map_templates = list()
var/obj/effect/landmark/engine_loader/engine_loader
var/list/shelter_templates = list()
// TODO: Implement Later
var/datum/map/current_map
/datum/controller/subsystem/mapping/Recover()
flags |= SS_NO_INIT // Make extra sure we don't initialize twice.
shelter_templates = SSmapping.shelter_templates
/datum/controller/subsystem/mapping/Initialize()
if(subsystem_initialized)
if(initialized)
return
world.max_z_changed() // This is to set up the player z-level list, maxz hasn't actually changed (probably)
load_map_templates()
@@ -24,6 +37,7 @@ SUBSYSTEM_DEF(mapping)
// TODO - Other stuff related to maps and areas could be moved here too. Look at /tg
// Lateload Code related to Expedition areas.
if(using_map) // VOREStation Edit: Re-enable this.
current_map = using_map
loadLateMaps()
if(CONFIG_GET(flag/generate_map)) // VOREStation Edit: Re-order this.

View File

@@ -1,7 +1,7 @@
SUBSYSTEM_DEF(media_tracks)
name = "Media Tracks"
flags = SS_NO_FIRE
init_order = INIT_ORDER_MEDIA_TRACKS
init_stage = INITSTAGE_EARLY
/// Every track, including secret
var/list/all_tracks = list()

View File

@@ -12,6 +12,12 @@ SUBSYSTEM_DEF(mobs)
flags = SS_KEEP_TIMING|SS_NO_INIT
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
dependencies = list(
/datum/controller/subsystem/atoms,
/datum/controller/subsystem/points_of_interest,
/datum/controller/subsystem/shuttles
)
var/list/currentrun = list()
var/log_extensively = FALSE
var/list/timelog = list()
@@ -101,22 +107,14 @@ SUBSYSTEM_DEF(mobs)
log_game(msg)
log_world(msg)
/datum/controller/subsystem/mobs/fail()
..()
log_recent()
/datum/controller/subsystem/mobs/critfail()
..()
log_recent()
/datum/controller/subsystem/mobs/proc/report_death(var/mob/living/L)
if(!L)
return
if(!L.key || !L.mind)
return
if(!ticker || !ticker.mode)
if(!SSticker || !SSticker.mode)
return
ticker.mode.check_win()
SSticker.mode.check_win()
// Don't bother with the rest if we've not got a DB to do anything with
if(!CONFIG_GET(flag/enable_stat_tracking) || !CONFIG_GET(flag/sql_enabled))

View File

@@ -1,6 +1,8 @@
SUBSYSTEM_DEF(nightshift)
name = "Night Shift"
init_order = INIT_ORDER_NIGHTSHIFT
dependencies = list(
/datum/controller/subsystem/lighting
)
priority = FIRE_PRIORITY_NIGHTSHIFT
wait = 60 SECONDS
flags = SS_NO_TICK_CHECK

View File

@@ -5,7 +5,9 @@ SUBSYSTEM_DEF(overlays)
flags = SS_TICKER
wait = 1 // SS_TICKER - Ticks
priority = FIRE_PRIORITY_OVERLAYS
init_order = INIT_ORDER_OVERLAY
dependencies = list(
/datum/controller/subsystem/atoms
)
/// The queue of atoms that need overlay updates.
var/static/tmp/list/queue = list()

View File

@@ -4,8 +4,12 @@ Readme at code\modules\awaymissions\overmap_renamer\readme.md
SUBSYSTEM_DEF(overmap_renamer)
name = "Overmap Renamer"
init_order = INIT_ORDER_MAPRENAME //Loaded very late in initializations. Must come before mapping and objs. Uses both as inputs.
runlevels = RUNLEVEL_INIT
//Loaded very late in initializations. Must come before mapping and objs. Uses both as inputs.
init_stage = INITSTAGE_LAST
dependencies = list(
/datum/controller/subsystem/skybox
)
runlevels = RUNLEVEL_SETUP
flags = SS_NO_FIRE

View File

@@ -1,7 +1,12 @@
SUBSYSTEM_DEF(persistence)
name = "Persistence"
init_order = INIT_ORDER_PERSISTENCE
dependencies = list(
/datum/controller/subsystem/mapping,
/datum/controller/subsystem/atoms,
/datum/controller/subsystem/points_of_interest
)
flags = SS_NO_FIRE
var/list/tracking_values = list()
var/list/persistence_datums = list()

View File

@@ -6,7 +6,7 @@
SUBSYSTEM_DEF(ping)
name = "Ping"
priority = FIRE_PRIORITY_PING
// init_stage = INITSTAGE_EARLY
init_stage = INITSTAGE_EARLY
wait = 4 SECONDS
flags = SS_NO_INIT
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT

View File

@@ -1,10 +1,12 @@
SUBSYSTEM_DEF(planets)
name = "Planets"
init_order = INIT_ORDER_PLANETS
priority = FIRE_PRIORITY_PLANETS
wait = 2 SECONDS
flags = SS_BACKGROUND
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
dependencies = list(
/datum/controller/subsystem/plants
)
var/static/list/planets = list()
var/static/list/z_to_planet = list()
@@ -75,7 +77,7 @@ SUBSYSTEM_DEF(planets)
if(MC_TICK_CHECK)
return
#ifndef UNIT_TEST // Don't be updating temperatures and such during unit tests
#ifndef UNIT_TESTS // Don't be updating temperatures and such during unit tests
var/list/needs_temp_update = src.needs_temp_update
while(needs_temp_update.len)
var/datum/planet/P = needs_temp_update[needs_temp_update.len]

View File

@@ -2,7 +2,9 @@
SUBSYSTEM_DEF(plants)
name = "Plants"
init_order = INIT_ORDER_PLANTS
dependencies = list(
/datum/controller/subsystem/mapping
)
priority = FIRE_PRIORITY_PLANTS
wait = PLANT_TICK_TIME

View File

@@ -3,8 +3,10 @@ SUBSYSTEM_DEF(points_of_interest)
name = "Points of Interest"
wait = 1 SECONDS
priority = FIRE_PRIORITY_POIS
init_order = INIT_ORDER_POIS
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT //POIs can be loaded mid-round.
dependencies = list(
/datum/controller/subsystem/holomaps
)
var/list/obj/effect/landmark/poi_loader/poi_queue = list()
/datum/controller/subsystem/points_of_interest/Initialize()

View File

@@ -1,7 +1,6 @@
PROCESSING_SUBSYSTEM_DEF(instruments)
name = "Instruments"
wait = 0.5
init_order = INIT_ORDER_INSTRUMENTS
flags = SS_KEEP_TIMING
priority = FIRE_PRIORITY_INSTRUMENTS
/// List of all instrument data, associative id = datum

View File

@@ -96,14 +96,6 @@ SUBSYSTEM_DEF(processing)
log_game(msg)
log_world(msg)
/datum/controller/subsystem/processing/fail()
..()
log_recent()
/datum/controller/subsystem/processing/critfail()
..()
log_recent()
/datum/proc/DebugSubsystemProcess(var/wait, var/times_fired, var/datum/controller/subsystem/processing/subsystem)
subsystem.debug_last_thing = src
var/start_tick = world.time

View File

@@ -0,0 +1,69 @@
SUBSYSTEM_DEF(profiler)
name = "Profiler"
init_stage = INITSTAGE_FIRST
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
wait = 300 SECONDS
var/fetch_cost = 0
var/write_cost = 0
/datum/controller/subsystem/profiler/stat_entry(msg)
msg += "F:[round(fetch_cost,1)]ms"
msg += "|W:[round(write_cost,1)]ms"
return msg
/datum/controller/subsystem/profiler/Initialize()
if(CONFIG_GET(flag/auto_profile))
StartProfiling()
else
StopProfiling() //Stop the early start profiler
wait = CONFIG_GET(number/profiler_interval)
return SS_INIT_SUCCESS
/datum/controller/subsystem/profiler/OnConfigLoad()
if(CONFIG_GET(flag/auto_profile))
StartProfiling()
can_fire = TRUE
else
StopProfiling()
can_fire = FALSE
/datum/controller/subsystem/profiler/fire()
DumpFile()
/datum/controller/subsystem/profiler/Shutdown()
if(CONFIG_GET(flag/auto_profile))
DumpFile(allow_yield = FALSE)
world.Profile(PROFILE_CLEAR, type = "sendmaps")
return ..()
/datum/controller/subsystem/profiler/proc/StartProfiling()
world.Profile(PROFILE_START)
world.Profile(PROFILE_START, type = "sendmaps")
/datum/controller/subsystem/profiler/proc/StopProfiling()
world.Profile(PROFILE_STOP)
world.Profile(PROFILE_STOP, type = "sendmaps")
/datum/controller/subsystem/profiler/proc/DumpFile(allow_yield = TRUE)
var/timer = TICK_USAGE_REAL
var/current_profile_data = world.Profile(PROFILE_REFRESH, format = "json")
var/current_sendmaps_data = world.Profile(PROFILE_REFRESH, type = "sendmaps", format="json")
fetch_cost = MC_AVERAGE(fetch_cost, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
if(allow_yield)
CHECK_TICK
if(!length(current_profile_data)) //Would be nice to have explicit proc to check this
stack_trace("Warning, profiling stopped manually before dump.")
var/prof_file = file("[GLOB.log_directory]/profiler/profiler-[round(world.time * 0.1, 10)].json")
if(fexists(prof_file))
fdel(prof_file)
if(!length(current_sendmaps_data)) //Would be nice to have explicit proc to check this
stack_trace("Warning, sendmaps profiling stopped manually before dump.")
var/sendmaps_file = file("[GLOB.log_directory]/profiler/sendmaps-[round(world.time * 0.1, 10)].json")
if(fexists(sendmaps_file))
fdel(sendmaps_file)
timer = TICK_USAGE_REAL
WRITE_FILE(prof_file, current_profile_data)
WRITE_FILE(sendmaps_file, current_sendmaps_data)
write_cost = MC_AVERAGE(write_cost, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))

View File

@@ -2,11 +2,13 @@
SUBSYSTEM_DEF(research)
name = "Research"
// priority = FIRE_PRIORITY_RESEARCH
init_order = INIT_ORDER_RESEARCH
wait = 10
// dependencies = list(
// /datum/controller/subsystem/processing/station,
// )
dependencies = list(
/datum/controller/subsystem/mapping
)
//TECHWEB STATIC
var/list/techweb_nodes = list() //associative id = node datum
var/list/techweb_designs = list() //associative id = node datum

View File

@@ -1,6 +1,8 @@
SUBSYSTEM_DEF(robot_sprites)
name = "Robot Sprites"
init_order = INIT_ORDER_ROBOT_SPRITES
dependencies = list(
/datum/controller/subsystem/garbage
)
flags = SS_NO_FIRE
var/list/all_cyborg_sprites = list()
var/list/cyborg_sprites_by_module = list()

View File

@@ -5,8 +5,7 @@ SUBSYSTEM_DEF(server_maint)
wait = 6
flags = SS_POST_FIRE_TIMING
priority = FIRE_PRIORITY_SERVER_MAINT
init_order = INIT_ORDER_SERVER_MAINT
//init_stage = INITSTAGE_EARLY
init_stage = INITSTAGE_FIRST
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
var/list/currentrun
///Associated list of list names to lists to clear of nulls

View File

@@ -8,7 +8,11 @@ SUBSYSTEM_DEF(shuttles)
name = "Shuttles"
wait = 2 SECONDS
priority = FIRE_PRIORITY_SHUTTLES
init_order = INIT_ORDER_SHUTTLES
dependencies = list(
/datum/controller/subsystem/machines,
/datum/controller/subsystem/atoms,
/datum/controller/subsystem/radio
)
flags = SS_KEEP_TIMING|SS_NO_TICK_CHECK
runlevels = RUNLEVEL_GAME|RUNLEVEL_POSTGAME

View File

@@ -2,7 +2,7 @@
//Exists to handle a few global variables that change enough to justify this. Technically a parallax, but it exhibits a skybox effect.
SUBSYSTEM_DEF(skybox)
name = "Space skybox"
init_order = INIT_ORDER_SKYBOX
init_stage = INITSTAGE_LAST
flags = SS_NO_FIRE
var/static/list/skybox_cache = list()
@@ -64,7 +64,7 @@ SUBSYSTEM_DEF(skybox)
speedspace_cache["EW_[i]"] = MA
//Over-the-edge images
for (var/dir in GLOB.alldirs)
for (var/dir in ALL_POSSIBLE_DIRS)
var/mutable_appearance/MA = new(normal_space)
var/matrix/M = matrix()
var/horizontal = (dir & (WEST|EAST))
@@ -93,7 +93,7 @@ SUBSYSTEM_DEF(skybox)
return SS_INIT_SUCCESS
/datum/controller/subsystem/skybox/proc/get_skybox(z)
if(!subsystem_initialized)
if(!initialized)
return // WAIT
if(!skybox_cache["[z]"])
skybox_cache["[z]"] = generate_skybox(z)

View File

@@ -3,7 +3,9 @@
SUBSYSTEM_DEF(sounds)
name = "Sounds"
flags = SS_NO_FIRE
init_order = INIT_ORDER_SOUNDS
dependencies = list(
/datum/controller/subsystem/mapping
)
var/static/using_channels_max = CHANNEL_HIGHEST_AVAILABLE //BYOND max channels
/// Amount of channels to reserve for random usage rather than reservations being allowed to reserve all channels. Also a nice safeguard for when someone screws up.
var/static/random_channels_min = 50

View File

@@ -3,7 +3,9 @@
// however this makes it a lot easier to test, and it is natively supported by BYOND.
SUBSYSTEM_DEF(sqlite)
name = "SQLite"
init_order = INIT_ORDER_SQLITE
dependencies = list(
/datum/controller/subsystem/dbcore
)
flags = SS_NO_FIRE
var/database/sqlite_db = null

View File

@@ -3,9 +3,11 @@
//
SUBSYSTEM_DEF(starmover)
name = "Shuttle Star Movement"
init_order = INIT_ORDER_STARMOVER
priority = FIRE_PRIORITY_STARMOVER
wait = 1 // This needs to be done pretty quickly
dependencies = list(
/datum/controller/subsystem/points_of_interest
)
var/list/zqueue = list()
var/list/current_movement = null
//list used to track which zlevels are being 'moved' by the proc below

View File

@@ -1,8 +1,6 @@
SUBSYSTEM_DEF(statpanels)
name = "Stat Panels"
wait = 4
init_order = INIT_ORDER_STATPANELS
//init_stage = INITSTAGE_EARLY
priority = FIRE_PRIORITY_STATPANEL
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
flags = SS_NO_INIT
@@ -42,6 +40,15 @@ SUBSYSTEM_DEF(statpanels)
var/ETA = emergency_shuttle.get_status_panel_eta()
if(ETA)
global_data += "[ETA]"
if(SSticker.reboot_timer)
var/reboot_time = timeleft(SSticker.reboot_timer)
if(reboot_time)
global_data += "Reboot: [DisplayTimeText(reboot_time, 1)]"
// admin must have delayed round end
else if(SSticker.ready_for_reboot)
global_data += "Reboot: DELAYED"
src.currentrun = GLOB.clients.Copy()
mc_data = null
@@ -140,7 +147,7 @@ SUBSYSTEM_DEF(statpanels)
var/coord_entry = COORD(eye_turf)
if(!mc_data)
generate_mc_data()
target.stat_panel.send_message("update_mc", list(mc_data = mc_data, coord_entry = coord_entry))
target.stat_panel.send_message("update_mc", list("mc_data" = mc_data, "coord_entry" = coord_entry))
/datum/controller/subsystem/statpanels/proc/set_examine_tab(client/target)
var/description_holders = target.description_holders
@@ -206,13 +213,31 @@ SUBSYSTEM_DEF(statpanels)
list("CPU:", world.cpu),
list("Instances:", "[num2text(world.contents.len, 10)]"),
list("World Time:", "[world.time]"),
list("Globals:", GLOB.stat_entry(), "\ref[GLOB]"),
list("[config]:", config.stat_entry(), "\ref[config]"),
list("Globals:", GLOB.stat_entry(), text_ref(GLOB)),
list("[config]:", config.stat_entry(), text_ref(config)),
list("Byond:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%)) (Internal Tick Usage: [round(MAPTICK_LAST_INTERNAL_TICK_USAGE,0.1)]%)"),
list("Master Controller:", Master.stat_entry(), "\ref[Master]"),
list("Failsafe Controller:", Failsafe.stat_entry(), "\ref[Failsafe]"),
list("Master Controller:", Master.stat_entry(), text_ref(Master)),
list("Failsafe Controller:", Failsafe.stat_entry(), text_ref(Failsafe)),
list("","")
)
#if defined(MC_TAB_TRACY_INFO) || defined(SPACEMAN_DMM)
var/static/tracy_dll
var/static/tracy_present
if(isnull(tracy_dll))
tracy_dll = TRACY_DLL_PATH
tracy_present = fexists(tracy_dll)
if(tracy_present)
if(Tracy.enabled)
mc_data.Insert(2, list(list("byond-tracy:", "Active (reason: [Tracy.init_reason || "N/A"])")))
else if(Tracy.error)
mc_data.Insert(2, list(list("byond-tracy:", "Errored ([Tracy.error])")))
else if(fexists(TRACY_ENABLE_PATH))
mc_data.Insert(2, list(list("byond-tracy:", "Queued for next round")))
else
mc_data.Insert(2, list(list("byond-tracy:", "Inactive")))
else
mc_data.Insert(2, list(list("byond-tracy:", "[tracy_dll] not present")))
#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(), "\ref[sub_system]")
mc_data[++mc_data.len] = list("Camera Net", "Cameras: [global.cameranet.cameras.len] | Chunks: [global.cameranet.chunks.len]", "\ref[global.cameranet]")

View File

@@ -1,59 +1,85 @@
//
// Ticker controls the state of the game, being responsible for round start, game mode, and round end.
//
SUBSYSTEM_DEF(ticker)
name = "Gameticker"
wait = 2 SECONDS
init_order = INIT_ORDER_TICKER
name = "Ticker"
priority = FIRE_PRIORITY_TICKER
flags = SS_NO_TICK_CHECK | SS_KEEP_TIMING
runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME // Every runlevel!
flags = SS_KEEP_TIMING
runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME
var/const/restart_timeout = 4 MINUTES // Default time to wait before rebooting in desiseconds.
var/current_state = GAME_STATE_INIT // We aren't even at pregame yet // TODO replace with CURRENT_GAME_STATE
/// state of current round (used by process()) Use the defines GAME_STATE_* !
var/current_state = GAME_STATE_STARTUP
/// Boolean to track if round should be forcibly ended next ticker tick.
/// Set by admin intervention ([ADMIN_FORCE_END_ROUND])
/// or a "round-ending" event, like summoning Nar'Sie, a blob victory, the nuke going off, etc. ([FORCE_END_ROUND])
var/force_ending = END_ROUND_AS_NORMAL
/// If TRUE, there is no lobby phase, the game starts immediately.
var/start_immediately = FALSE
/// Boolean to track and check if our subsystem setup is done.
var/setup_done = FALSE
/* Relies upon the following globals (TODO move those in here) */
// var/GLOB.master_mode = "extended" //The underlying game mode (so "secret" or the voted mode).
// Set by SSvote when VOTE_GAMEMODE finishes.
// var/round_progressing = 1 //Whether the lobby clock is ticking down.
var/hide_mode = FALSE
var/datum/game_mode/mode = null
var/pregame_timeleft = 0 // Time remaining until game starts in seconds. Set by config
var/start_immediately = FALSE // If true there is no lobby phase, the game starts immediately.
var/login_music //music played in pregame lobby
var/round_end_sound //music/jingle played when the world reboots
var/round_end_sound_sent = TRUE //If all clients have loaded it
var/hide_mode = FALSE // If the true game mode should be hidden (because we chose "secret")
var/datum/game_mode/mode = null // The actual gamemode, if selected.
var/list/datum/mind/minds = list() //The characters in the game. Used for objective tracking.
var/end_game_state = END_GAME_NOT_OVER // Track where we are ending game/round
var/restart_timeleft // Time remaining until restart in desiseconds
var/last_restart_notify // world.time of last restart warning.
var/delay_end = FALSE // If set, the round will not restart on its own.
var/delay_end = FALSE //if set true, the round will not restart on it's own
var/admin_delay_notice = "" //a message to display to anyone who tries to restart the world after a delay
var/ready_for_reboot = FALSE //all roundend preparation done with, all that's left is reboot
// var/login_music // music played in pregame lobby // VOREStation Edit - We do music differently
var/tipped = FALSE //Did we broadcast the tip of the day yet?
var/selected_tip // What will be the tip of the day?
var/list/datum/mind/minds = list() // The people in the game. Used for objective tracking.
var/timeLeft //pregame timer
var/start_at
var/random_players = FALSE // If set to nonzero, ALL players who latejoin or declare-ready join will have random appearances/genders
var/gametime_offset = 432000 //Deciseconds to add to world.time for station time.
var/station_time_rate_multiplier = 12 //factor of station time progressal vs real time.
// TODO - Should this go here or in the job subsystem?
var/triai = FALSE // Global flag for Triumvirate AI being enabled
/// Num of players, used for pregame stats on statpanel
var/totalPlayers = 0
/// Num of ready players, used for pregame stats on statpanel (only viewable by admins)
var/totalPlayersReady = 0
/// Num of ready admins, used for pregame stats on statpanel (only viewable by admins)
var/total_admins_ready = 0
//station_explosion used to be a variable for every mob's hud. Which was a waste!
//Now we have a general cinematic centrally held within the gameticker....far more efficient!
var/obj/screen/cinematic = null
var/queue_delay = 0
var/list/queued_players = list() //used for join queues when the server exceeds the hard population cap
/// What is going to be reported to other stations at end of round?
var/news_report
var/roundend_check_paused = FALSE
var/round_start_time = 0
var/list/round_start_events
var/list/round_end_events
var/mode_result = "undefined"
var/end_state = "undefined"
/// People who have been commended and will receive a heart
var/list/hearts
/// Why an emergency shuttle was called
var/emergency_reason
// This global variable exists for legacy support so we don't have to rename every 'ticker' to 'SSticker' yet.
var/global/datum/controller/subsystem/ticker/ticker
/datum/controller/subsystem/ticker/PreInit()
global.ticker = src // TODO - Remove this! Change everything to point at SSticker intead
/// ID of round reboot timer, if it exists
var/reboot_timer = null
/// ### LEGACY VARS ###
/// Default time to wait before rebooting in desiseconds.
var/const/restart_timeout = 4 MINUTES
/// Track where we are ending game/round
var/end_game_state = END_GAME_NOT_OVER
/// Time remaining until restart in desiseconds
var/restart_timeleft
/// world.time of last restart warning.
var/last_restart_notify
/datum/controller/subsystem/ticker/Initialize()
pregame_timeleft = CONFIG_GET(number/pregame_time)
send2mainirc("Server lobby is loaded and open at byond://[CONFIG_GET(string/serverurl) ? CONFIG_GET(string/serverurl) : (CONFIG_GET(string/server) ? CONFIG_GET(string/server) : "[world.address]:[world.port]")]")
start_at = world.time + (CONFIG_GET(number/lobby_countdown) * 10)
SSwebhooks.send(
WEBHOOK_ROUNDPREP,
list(
@@ -61,63 +87,183 @@ var/global/datum/controller/subsystem/ticker/ticker
"url" = get_world_url()
)
)
GLOB.autospeaker = new (null, FALSE, null, null, TRUE) //Set up Global Announcer
return SS_INIT_SUCCESS
/datum/controller/subsystem/ticker/fire(resumed = FALSE)
switch(current_state)
if(GAME_STATE_INIT)
pregame_welcome()
if(GAME_STATE_STARTUP)
if(Master.initializations_finished_with_no_players_logged_in)
start_at = world.time + (CONFIG_GET(number/lobby_countdown) * 10)
for(var/client/C in GLOB.clients)
window_flash(C, ignorepref = TRUE) //let them know lobby has opened up.
to_chat(world, span_notice("<b>Welcome to [station_name()]!</b>"))
current_state = GAME_STATE_PREGAME
SEND_SIGNAL(src, COMSIG_TICKER_ENTER_PREGAME)
fire()
if(GAME_STATE_PREGAME)
pregame_tick()
//lobby stats for statpanels
if(isnull(timeLeft))
timeLeft = max(0,start_at - world.time)
totalPlayers = LAZYLEN(GLOB.new_player_list)
totalPlayersReady = 0
total_admins_ready = 0
for(var/mob/new_player/player as anything in GLOB.new_player_list)
if(player.ready == PLAYER_READY_TO_PLAY)
++totalPlayersReady
if(player.client?.holder)
++total_admins_ready
if(start_immediately)
timeLeft = 0
//countdown
if(timeLeft < 0)
return
timeLeft -= wait
//if(timeLeft <= 300 && !tipped)
// send_tip_of_the_round(world, selected_tip)
// tipped = TRUE
if(timeLeft <= 0)
SEND_SIGNAL(src, COMSIG_TICKER_ENTER_SETTING_UP)
current_state = GAME_STATE_SETTING_UP
Master.SetRunLevel(RUNLEVEL_SETUP)
if(start_immediately)
fire()
if(GAME_STATE_SETTING_UP)
setup_tick()
if(!setup())
//setup failed
current_state = GAME_STATE_STARTUP
start_at = world.time + (CONFIG_GET(number/lobby_countdown) * 10)
timeLeft = null
Master.SetRunLevel(RUNLEVEL_LOBBY)
SEND_SIGNAL(src, COMSIG_TICKER_ERROR_SETTING_UP)
if(GAME_STATE_PLAYING)
playing_tick()
mode.process() // So THIS is where we run mode.process() huh? Okay
if(mode.explosion_in_progress)
return // wait until explosion is done.
if(force_ending)
current_state = GAME_STATE_FINISHED
declare_completion(force_ending)
Master.SetRunLevel(RUNLEVEL_POSTGAME)
else
// Calculate if game and/or mode are finished (Complicated by the continuous_rounds config option)
var/game_finished = FALSE
var/mode_finished = FALSE
if (CONFIG_GET(flag/continuous_rounds)) // Game keeps going after mode ends.
game_finished = (emergency_shuttle.returned() || mode.station_was_nuked)
mode_finished = ((end_game_state >= END_GAME_MODE_FINISHED) || mode.check_finished()) // Short circuit if already finished.
else // Game ends when mode does
game_finished = (mode.check_finished() || (emergency_shuttle.returned() && emergency_shuttle.evac == 1)) || GLOB.universe_has_ended
mode_finished = game_finished
if(game_finished && mode_finished)
end_game_state = END_GAME_READY_TO_END
current_state = GAME_STATE_FINISHED
Master.SetRunLevel(RUNLEVEL_POSTGAME)
INVOKE_ASYNC(src, PROC_REF(declare_completion))
else if (mode_finished && (end_game_state < END_GAME_MODE_FINISHED))
end_game_state = END_GAME_MODE_FINISHED // Only do this cleanup once!
mode.cleanup()
//call a transfer shuttle vote
to_world(span_boldannounce("The round has ended!"))
SSvote.start_vote(new /datum/vote/crew_transfer)
// FIXME: IMPROVE THIS LATER!
if(GAME_STATE_FINISHED)
post_game_tick()
/datum/controller/subsystem/ticker/proc/pregame_welcome()
to_world(span_boldannounce(span_notice("<em>Welcome to the pregame lobby!</em>")))
to_world(span_boldannounce(span_notice("Please set up your character and select ready. The round will start in [pregame_timeleft] seconds.")))
world << sound('sound/misc/server-ready.ogg', volume = 100)
if (world.time - last_restart_notify >= 1 MINUTE && !delay_end)
to_world(span_boldannounce("Restarting in [round(restart_timeleft/600, 1)] minute\s."))
last_restart_notify = world.time
// Called during GAME_STATE_PREGAME (RUNLEVEL_LOBBY)
/datum/controller/subsystem/ticker/proc/pregame_tick()
if(GLOB.round_progressing && last_fire)
pregame_timeleft -= (world.time - last_fire) / (1 SECOND)
/datum/controller/subsystem/ticker/proc/setup()
to_chat(world, span_boldannounce("Starting game..."))
var/init_start = world.timeofday
if(start_immediately)
pregame_timeleft = 0
else if(SSvote.active_vote)
return // vote still going, wait for it.
CHECK_TICK
setup_choose_gamemode()
// TODO
// Time to start the game!
if(pregame_timeleft <= 0)
current_state = GAME_STATE_SETTING_UP
Master.SetRunLevel(RUNLEVEL_SETUP)
if(start_immediately)
fire() // Don't wait for next tick, do it now!
return
CHECK_TICK
setup_economy()
create_characters() //Create player characters
collect_minds()
equip_characters()
//if(pregame_timeleft <= CONFIG_GET(number/vote_autogamemode_timeleft) && !SSvote.gamemode_vote_called) //CHOMPEdit
//SSvote.autogamemode() // Start the game mode vote (if we haven't had one already) //CHOMPEdit
// data_core.manifest()
// Called during GAME_STATE_SETTING_UP (RUNLEVEL_SETUP)
/datum/controller/subsystem/ticker/proc/setup_tick(resumed = FALSE)
round_start_time = world.time // otherwise round_start_time would be 0 for the signals
if(!setup_choose_gamemode())
// It failed, go back to lobby state and re-send the welcome message
pregame_timeleft = CONFIG_GET(number/pregame_time)
// SSvote.gamemode_vote_called = FALSE // Allow another autogamemode vote
current_state = GAME_STATE_PREGAME
Master.SetRunLevel(RUNLEVEL_LOBBY)
pregame_welcome()
return
// If we got this far we succeeded in picking a game mode. Punch it!
setup_startgame()
return
for(var/I in round_start_events)
var/datum/callback/cb = I
cb.InvokeAsync()
LAZYCLEARLIST(round_start_events)
round_start_time = world.time //otherwise round_start_time would be 0 for the signals
SEND_SIGNAL(src, COMSIG_TICKER_ROUND_STARTING, world.time)
callHook("roundstart")
log_world("Game start took [(world.timeofday - init_start)/10]s")
INVOKE_ASYNC(SSdbcore, TYPE_PROC_REF(/datum/controller/subsystem/dbcore,SetRoundStart))
to_chat(world, span_notice(span_bold("Welcome to [station_name()], enjoy your stay!")))
world << sound('sound/AI/welcome.ogg') // Skie
//SEND_SOUND(world, sound(SSstation.announcer.get_rand_welcome_sound()))
current_state = GAME_STATE_PLAYING
Master.SetRunLevel(RUNLEVEL_GAME)
//Holiday Round-start stuff ~Carn
Holiday_Game_Start()
// TODO END
PostSetup()
return TRUE
/datum/controller/subsystem/ticker/proc/PostSetup()
set waitfor = FALSE
mode.post_setup()
// TODO
var/list/adm = get_admin_counts()
var/list/allmins = adm["present"]
// TODO: IMPLEMENT: send2adminchat("Server", "Round [GLOB.round_id ? "#[GLOB.round_id]" : ""] has started[allmins.len ? ".":" with no active admins online!"]")
if(!allmins.len)
send2adminirc("A round has started with no admins online.")
setup_done = TRUE
// TODO START
// TODO END
for(var/obj/effect/landmark/start/S in GLOB.landmarks_list)
//Deleting Startpoints but we need the ai point to AI-ize people later
if (S.name != "AI")
qdel(S)
if(CONFIG_GET(flag/sql_enabled))
statistic_cycle() // Polls population totals regularly and stores them in an SQL DB -- TLE
//These callbacks will fire after roundstart key transfer
/datum/controller/subsystem/ticker/proc/OnRoundstart(datum/callback/cb)
if(!HasRoundStarted())
LAZYADD(round_start_events, cb)
else
cb.InvokeAsync()
//These callbacks will fire before roundend report
/datum/controller/subsystem/ticker/proc/OnRoundend(datum/callback/cb)
if(current_state >= GAME_STATE_FINISHED)
cb.InvokeAsync()
else
LAZYADD(round_end_events, cb)
// Formerly the first half of setup() - The part that chooses the game mode.
// Returns 0 if failed to pick a mode, otherwise 1
@@ -170,74 +316,6 @@ var/global/datum/controller/subsystem/ticker/ticker
src.mode.announce()
return 1
// Formerly the second half of setup() - The part that actually initializes everything and starts the game.
/datum/controller/subsystem/ticker/proc/setup_startgame()
setup_economy()
create_characters() //Create player characters and transfer them.
collect_minds()
equip_characters()
// data_core.manifest()
callHook("roundstart")
for(var/I in round_start_events)
var/datum/callback/cb = I
cb.InvokeAsync()
LAZYCLEARLIST(round_start_events)
spawn(0)//Forking here so we dont have to wait for this to finish
mode.post_setup()
//Cleanup some stuff
for(var/obj/effect/landmark/start/S in GLOB.landmarks_list)
//Deleting Startpoints but we need the ai point to AI-ize people later
if (S.name != "AI")
qdel(S)
to_world(span_boldannounce(span_notice("<em>Enjoy the game!</em>")))
world << sound('sound/AI/welcome.ogg') // Skie
//Holiday Round-start stuff ~Carn
Holiday_Game_Start()
var/list/adm = get_admin_counts()
if(adm["total"] == 0)
send2adminirc("A round has started with no admins online.")
current_state = GAME_STATE_PLAYING
Master.SetRunLevel(RUNLEVEL_GAME)
if(CONFIG_GET(flag/sql_enabled))
statistic_cycle() // Polls population totals regularly and stores them in an SQL DB -- TLE
return 1
// Called during GAME_STATE_PLAYING (RUNLEVEL_GAME)
/datum/controller/subsystem/ticker/proc/playing_tick(resumed = FALSE)
mode.process() // So THIS is where we run mode.process() huh? Okay
if(mode.explosion_in_progress)
return // wait until explosion is done.
// Calculate if game and/or mode are finished (Complicated by the continuous_rounds config option)
var/game_finished = FALSE
var/mode_finished = FALSE
if (CONFIG_GET(flag/continuous_rounds)) // Game keeps going after mode ends.
game_finished = (emergency_shuttle.returned() || mode.station_was_nuked)
mode_finished = ((end_game_state >= END_GAME_MODE_FINISHED) || mode.check_finished()) // Short circuit if already finished.
else // Game ends when mode does
game_finished = (mode.check_finished() || (emergency_shuttle.returned() && emergency_shuttle.evac == 1)) || GLOB.universe_has_ended
mode_finished = game_finished
if(game_finished && mode_finished)
end_game_state = END_GAME_READY_TO_END
current_state = GAME_STATE_FINISHED
Master.SetRunLevel(RUNLEVEL_POSTGAME)
INVOKE_ASYNC(src, PROC_REF(declare_completion))
else if (mode_finished && (end_game_state < END_GAME_MODE_FINISHED))
end_game_state = END_GAME_MODE_FINISHED // Only do this cleanup once!
mode.cleanup()
//call a transfer shuttle vote
to_world(span_boldannounce("The round has ended!"))
SSvote.start_vote(new /datum/vote/crew_transfer)
// Called during GAME_STATE_FINISHED (RUNLEVEL_POSTGAME)
/datum/controller/subsystem/ticker/proc/post_game_tick()
switch(end_game_state)
@@ -259,148 +337,6 @@ var/global/datum/controller/subsystem/ticker/ticker
end_game_state = END_GAME_ENDING
return
if(END_GAME_ENDING)
restart_timeleft -= (world.time - last_fire)
if(delay_end)
to_world(span_boldannounce("An admin has delayed the round end."))
end_game_state = END_GAME_DELAYED
else if(restart_timeleft <= 0)
to_world(span_boldannounce("Restarting world!"))
sleep(5)
world.Reboot()
else if (world.time - last_restart_notify >= 1 MINUTE)
to_world(span_boldannounce("Restarting in [round(restart_timeleft/600, 1)] minute\s."))
last_restart_notify = world.time
return
if(END_GAME_DELAYED)
restart_timeleft -= (world.time - last_fire)
if(!delay_end)
end_game_state = END_GAME_ENDING
else
log_error("Ticker arrived at round end in an unexpected endgame state '[end_game_state]'.")
end_game_state = END_GAME_READY_TO_END
// ----------------------------------------------------------------------
// These two below are not used! But they could be
// Use these preferentially to directly examining ticker.current_state to help prepare for transition to ticker as subsystem!
/datum/controller/subsystem/ticker/proc/PreRoundStart()
return (current_state < GAME_STATE_PLAYING)
/datum/controller/subsystem/ticker/proc/IsSettingUp()
return (current_state == GAME_STATE_SETTING_UP)
/datum/controller/subsystem/ticker/proc/IsRoundInProgress()
return (current_state == GAME_STATE_PLAYING)
/datum/controller/subsystem/ticker/proc/HasRoundStarted()
return (current_state >= GAME_STATE_PLAYING)
// ------------------------------------------------------------------------
// HELPER PROCS!
// ------------------------------------------------------------------------
//Plus it provides an easy way to make cinematics for other events. Just use this as a template :)
/datum/controller/subsystem/ticker/proc/station_explosion_cinematic(var/station_missed=0, var/override = null)
if( cinematic ) return //already a cinematic in progress!
//initialise our cinematic screen object
cinematic = new(src)
cinematic.icon = 'icons/effects/station_explosion.dmi'
cinematic.icon_state = "station_intact"
cinematic.layer = 100
cinematic.plane = PLANE_PLAYER_HUD
cinematic.mouse_opacity = 0
cinematic.screen_loc = "1,0"
var/obj/structure/bed/temp_buckle = new(src)
//Incredibly hackish. It creates a bed within the gameticker (lol) to stop mobs running around
if(station_missed)
for(var/mob/living/M in GLOB.living_mob_list)
M.buckled = temp_buckle //buckles the mob so it can't do anything
if(M.client)
M.client.screen += cinematic //show every client the cinematic
else //nuke kills everyone on z-level 1 to prevent "hurr-durr I survived"
for(var/mob/living/M in GLOB.living_mob_list)
M.buckled = temp_buckle
if(M.client)
M.client.screen += cinematic
switch(M.z)
if(0) //inside a crate or something
var/turf/T = get_turf(M)
if(T && (T.z in using_map.station_levels)) //we don't use M.death(0) because it calls a for(/mob) loop and
M.health = 0
M.set_stat(DEAD)
if(1) //on a z-level 1 turf.
M.health = 0
M.set_stat(DEAD)
//Now animate the cinematic
switch(station_missed)
if(1) //nuke was nearby but (mostly) missed
if( mode && !override )
override = mode.name
switch( override )
if("mercenary") //Nuke wasn't on station when it blew up
flick("intro_nuke",cinematic)
sleep(35)
world << sound('sound/effects/explosionfar.ogg')
flick("station_intact_fade_red",cinematic)
cinematic.icon_state = "summary_nukefail"
else
flick("intro_nuke",cinematic)
sleep(35)
world << sound('sound/effects/explosionfar.ogg')
//flick("end",cinematic)
if(2) //nuke was nowhere nearby //TODO: a really distant explosion animation
sleep(50)
world << sound('sound/effects/explosionfar.ogg')
else //station was destroyed
if( mode && !override )
override = mode.name
switch( override )
if("mercenary") //Nuke Ops successfully bombed the station
flick("intro_nuke",cinematic)
sleep(35)
flick("station_explode_fade_red",cinematic)
world << sound('sound/effects/explosionfar.ogg')
cinematic.icon_state = "summary_nukewin"
if("AI malfunction") //Malf (screen,explosion,summary)
flick("intro_malf",cinematic)
sleep(76)
flick("station_explode_fade_red",cinematic)
world << sound('sound/effects/explosionfar.ogg')
cinematic.icon_state = "summary_malf"
if("blob") //Station nuked (nuke,explosion,summary)
flick("intro_nuke",cinematic)
sleep(35)
flick("station_explode_fade_red",cinematic)
world << sound('sound/effects/explosionfar.ogg')
cinematic.icon_state = "summary_selfdes"
else //Station nuked (nuke,explosion,summary)
flick("intro_nuke",cinematic)
sleep(35)
flick("station_explode_fade_red", cinematic)
world << sound('sound/effects/explosionfar.ogg')
cinematic.icon_state = "summary_selfdes"
for(var/mob/living/M in GLOB.living_mob_list)
if(M.loc.z in using_map.station_levels)
M.death()//No mercy
//If its actually the end of the round, wait for it to end.
//Otherwise if its a verb it will continue on afterwards.
sleep(300)
if(cinematic) qdel(cinematic) //end the cinematic
if(temp_buckle) qdel(temp_buckle) //release everybody
return
/datum/controller/subsystem/ticker/proc/create_characters()
for(var/mob/new_player/player in GLOB.player_list)
@@ -431,12 +367,13 @@ var/global/datum/controller/subsystem/ticker/ticker
// If they're a carbon, they can get manifested
if(J?.mob_type & JOB_CARBON)
GLOB.data_core.manifest_inject(new_char)
CHECK_TICK
/datum/controller/subsystem/ticker/proc/collect_minds()
for(var/mob/living/player in GLOB.player_list)
if(player.mind)
minds += player.mind
CHECK_TICK
/datum/controller/subsystem/ticker/proc/equip_characters()
var/captainless=1
@@ -457,147 +394,107 @@ var/global/datum/controller/subsystem/ticker/ticker
if(imp.handle_implant(player,player.zone_sel.selecting))
imp.post_implant(player)
//VOREStation Addition End
CHECK_TICK
if(captainless)
for(var/mob/M in GLOB.player_list)
if(!isnewplayer(M))
to_chat(M, span_notice("Site Management is not forced on anyone."))
///Whether the game has started, including roundend.
/datum/controller/subsystem/ticker/proc/HasRoundStarted()
return current_state >= GAME_STATE_PLAYING
/datum/controller/subsystem/ticker/proc/declare_completion()
to_world(span_filter_system("<br><br><br><H1>A round of [mode.name] has ended!</H1>"))
for(var/mob/Player in GLOB.player_list)
if(Player.mind && !isnewplayer(Player))
if(Player.stat != DEAD)
var/turf/playerTurf = get_turf(Player)
if(emergency_shuttle.departed && emergency_shuttle.evac)
if(isNotAdminLevel(playerTurf.z))
to_chat(Player, span_filter_system(span_blue(span_bold("You survived the round, but remained on [station_name()] as [Player.real_name]."))))
else
to_chat(Player, span_filter_system(span_green(span_bold("You managed to survive the events on [station_name()] as [Player.real_name]."))))
else if(isAdminLevel(playerTurf.z))
to_chat(Player, span_filter_system(span_green(span_bold("You successfully underwent crew transfer after events on [station_name()] as [Player.real_name]."))))
else if(issilicon(Player))
to_chat(Player, span_filter_system(span_green(span_bold("You remain operational after the events on [station_name()] as [Player.real_name]."))))
else
to_chat(Player, span_filter_system(span_blue(span_bold("You missed the crew transfer after the events on [station_name()] as [Player.real_name]."))))
else
if(isobserver(Player))
var/mob/observer/dead/O = Player
if(!O.started_as_observer)
to_chat(Player, span_filter_system(span_red(span_bold("You did not survive the events on [station_name()]..."))))
else
to_chat(Player, span_filter_system(span_red(span_bold("You did not survive the events on [station_name()]..."))))
to_world(span_filter_system("<br>"))
///Whether the game is currently in progress, excluding roundend
/datum/controller/subsystem/ticker/proc/IsRoundInProgress()
return current_state == GAME_STATE_PLAYING
for (var/mob/living/silicon/ai/aiPlayer in GLOB.mob_list)
if (aiPlayer.stat != 2)
to_world(span_filter_system(span_bold("[aiPlayer.name]'s laws at the end of the round were:"))) // VOREStation edit
else
to_world(span_filter_system(span_bold("[aiPlayer.name]'s laws when it was deactivated were:"))) // VOREStation edit
aiPlayer.show_laws(1)
if (aiPlayer.connected_robots.len)
var/robolist = span_bold("The AI's loyal minions were:") + " "
for(var/mob/living/silicon/robot/robo in aiPlayer.connected_robots)
robolist += "[robo.name][robo.stat?" (Deactivated), ":", "]" // VOREStation edit
to_world(span_filter_system("[robolist]"))
var/dronecount = 0
for (var/mob/living/silicon/robot/robo in GLOB.mob_list)
if(istype(robo, /mob/living/silicon/robot/platform))
var/mob/living/silicon/robot/platform/tank = robo
if(!tank.has_had_player)
continue
if(istype(robo,/mob/living/silicon/robot/drone) && !istype(robo,/mob/living/silicon/robot/drone/swarm))
dronecount++
continue
if (!robo.connected_ai)
if (robo.stat != 2)
to_world(span_filter_system(span_bold("[robo.name] survived as an AI-less stationbound synthetic! Its laws were:"))) // VOREStation edit
else
to_world(span_filter_system(span_bold("[robo.name] was unable to survive the rigors of being a stationbound synthetic without an AI. Its laws were:"))) // VOREStation edit
if(robo) //How the hell do we lose robo between here and the world messages directly above this?
robo.laws.show_laws(world)
if(dronecount)
to_world(span_filter_system(span_bold("There [dronecount>1 ? "were" : "was"] [dronecount] industrious maintenance [dronecount>1 ? "drones" : "drone"] at the end of this round.")))
mode.declare_completion()//To declare normal completion.
//Ask the event manager to print round end information
SSevents.RoundEnd()
//Print a list of antagonists to the server log
var/list/total_antagonists = list()
//Look into all mobs in world, dead or alive
for(var/datum/mind/Mind in minds)
var/temprole = Mind.special_role
if(temprole) //if they are an antagonist of some sort.
if(temprole in total_antagonists) //If the role exists already, add the name to it
total_antagonists[temprole] += ", [Mind.name]([Mind.key])"
else
total_antagonists.Add(temprole) //If the role doesnt exist in the list, create it and add the mob
total_antagonists[temprole] += ": [Mind.name]([Mind.key])"
//Now print them all into the log!
log_game("Antagonists at round end were...")
for(var/i in total_antagonists)
log_game("[i]s[total_antagonists[i]].")
SSdbcore.SetRoundEnd()
return 1
/datum/controller/subsystem/ticker/stat_entry(msg)
switch(current_state)
if(GAME_STATE_INIT)
..()
if(GAME_STATE_PREGAME) // RUNLEVEL_LOBBY
msg = "START [GLOB.round_progressing ? "[round(pregame_timeleft)]s" : "(PAUSED)"]"
if(GAME_STATE_SETTING_UP) // RUNLEVEL_SETUP
msg = "SETUP"
if(GAME_STATE_PLAYING) // RUNLEVEL_GAME
msg = "GAME"
if(GAME_STATE_FINISHED) // RUNLEVEL_POSTGAME
switch(end_game_state)
if(END_GAME_MODE_FINISHED)
msg = "MODE OVER, WAITING"
if(END_GAME_READY_TO_END)
msg = "ENDGAME PROCESSING"
if(END_GAME_ENDING)
msg = "END IN [round(restart_timeleft/10)]s"
if(END_GAME_DELAYED)
msg = "END PAUSED"
else
msg = "ENDGAME ERROR:[end_game_state]"
return ..()
///Whether the game is currently in progress, excluding roundend
/datum/controller/subsystem/ticker/proc/IsPostgame()
return current_state == GAME_STATE_FINISHED
/datum/controller/subsystem/ticker/Recover()
flags |= SS_NO_INIT // Don't initialize again
current_state = SSticker.current_state
mode = SSticker.mode
pregame_timeleft = SSticker.pregame_timeleft
force_ending = SSticker.force_ending
end_game_state = SSticker.end_game_state
delay_end = SSticker.delay_end
restart_timeleft = SSticker.restart_timeleft
login_music = SSticker.login_music
round_end_sound = SSticker.round_end_sound
minds = SSticker.minds
random_players = SSticker.random_players
delay_end = SSticker.delay_end
tipped = SSticker.tipped
selected_tip = SSticker.selected_tip
timeLeft = SSticker.timeLeft
totalPlayers = SSticker.totalPlayers
totalPlayersReady = SSticker.totalPlayersReady
total_admins_ready = SSticker.total_admins_ready
queue_delay = SSticker.queue_delay
queued_players = SSticker.queued_players
round_start_time = SSticker.round_start_time
queue_delay = SSticker.queue_delay
queued_players = SSticker.queued_players
//These callbacks will fire after roundstart key transfer
/datum/controller/subsystem/ticker/proc/OnRoundstart(datum/callback/cb)
if(!HasRoundStarted())
LAZYADD(round_start_events, cb)
else
cb.InvokeAsync()
if (Master) //Set Masters run level if it exists
switch (current_state)
if(GAME_STATE_SETTING_UP)
Master.SetRunLevel(RUNLEVEL_SETUP)
if(GAME_STATE_PLAYING)
Master.SetRunLevel(RUNLEVEL_GAME)
if(GAME_STATE_FINISHED)
Master.SetRunLevel(RUNLEVEL_POSTGAME)
/datum/controller/subsystem/ticker/proc/Reboot(reason, end_string, delay)
set waitfor = FALSE
if(usr && !check_rights(R_SERVER, TRUE))
return
if(!delay)
delay = CONFIG_GET(number/round_end_countdown) * 10
var/skip_delay = check_rights()
if(delay_end && !skip_delay)
to_chat(world, span_boldannounce("An admin has delayed the round end."))
return
to_chat(world, span_boldannounce("Rebooting World in [DisplayTimeText(delay)]. [reason]"))
// We dont have those
//var/statspage = CONFIG_GET(string/roundstatsurl)
//var/gamelogloc = CONFIG_GET(string/gamelogurl)
//if(statspage)
// to_chat(world, span_info("Round statistics and logs can be viewed <a href=\"[statspage][GLOB.round_id]\">at this website!</a>"))
//else if(gamelogloc)
// to_chat(world, span_info("Round logs can be located <a href=\"[gamelogloc]\">at this website!</a>"))
var/start_wait = world.time
UNTIL(round_end_sound_sent || (world.time - start_wait) > (delay * 2)) //don't wait forever
reboot_timer = addtimer(CALLBACK(src, PROC_REF(reboot_callback), reason, end_string), delay - (world.time - start_wait), TIMER_STOPPABLE)
/datum/controller/subsystem/ticker/proc/reboot_callback(reason, end_string)
if(end_string)
end_state = end_string
log_game(span_boldannounce("Rebooting World. [reason]"))
world.Reboot()
/**
* Deletes the current reboot timer and nulls the var
*
* Arguments:
* * user - the user that cancelled the reboot, may be null
*/
/datum/controller/subsystem/ticker/proc/cancel_reboot(mob/user)
if(!reboot_timer)
to_chat(user, span_warning("There is no pending reboot!"))
return FALSE
to_chat(world, span_boldannounce("An admin has delayed the round end."))
deltimer(reboot_timer)
reboot_timer = null
return TRUE

View File

@@ -17,9 +17,11 @@
SUBSYSTEM_DEF(timer)
name = "Timer"
wait = 1 // SS_TICKER subsystem, so wait is in ticks
init_order = INIT_ORDER_TIMER
priority = FIRE_PRIORITY_TIMER
flags = SS_TICKER|SS_NO_INIT
dependencies = list(
/datum/controller/subsystem/machines
)
/// Queue used for storing timers that do not fit into the current buckets
var/list/datum/timedevent/second_queue = list()

View File

@@ -12,7 +12,9 @@ SUBSYSTEM_DEF(transcore)
wait = 3 MINUTES
flags = SS_BACKGROUND
runlevels = RUNLEVEL_GAME
init_order = INIT_ORDER_TRANSCORE
dependencies = list(
/datum/controller/subsystem/mapping
)
// THINGS
var/overdue_time = 6 MINUTES // Has to be a multiple of wait var, or else will just round up anyway.

View File

@@ -78,7 +78,7 @@ SUBSYSTEM_DEF(verb_manager)
//we want unit tests to be able to directly call verbs that attempt to queue, and since unit tests should test internal behavior, we want the queue
//to happen as if it was actually from player input if its called on a mob.
#ifdef UNIT_TEST
#ifdef UNIT_TESTS
if(QDELETED(usr) && ismob(incoming_callback.object))
incoming_callback.user = WEAKREF(incoming_callback.object)
var/datum/callback/new_us = CALLBACK(arglist(list(GLOBAL_PROC, GLOBAL_PROC_REF(_queue_verb)) + args.Copy()))
@@ -120,7 +120,7 @@ SUBSYSTEM_DEF(verb_manager)
return TRUE
if((usr.client?.holder && !can_queue_admin_verbs) \
|| (!subsystem_initialized && !(flags & SS_NO_INIT)) \
|| (!initialized && !(flags & SS_NO_INIT)) \
|| FOR_ADMINS_IF_VERBS_FUCKED_immediately_execute_all_verbs \
|| !(runlevels & Master.current_runlevel))
return FALSE

View File

@@ -2,7 +2,6 @@ SUBSYSTEM_DEF(vis_overlays)
name = "Vis contents overlays"
wait = 1 MINUTES
priority = FIRE_PRIORITY_VIS
init_order = INIT_ORDER_VIS
var/list/vis_overlay_cache
var/list/currentrun

View File

@@ -24,7 +24,7 @@ SUBSYSTEM_DEF(vote)
/datum/controller/subsystem/vote/fire(resumed)
if(mode)
time_remaining = round((started_time + duration - world.time)/10)
if(mode == VOTE_GAMEMODE && ticker.current_state >= GAME_STATE_SETTING_UP)
if(mode == VOTE_GAMEMODE && SSticker.current_state >= GAME_STATE_SETTING_UP)
to_chat(world, span_bold("Gamemode vote aborted: Game has already started."))
reset()
return
@@ -164,7 +164,7 @@ SUBSYSTEM_DEF(vote)
if(VOTE_GAMEMODE)
if(GLOB.master_mode != .)
world.save_mode(.)
if(ticker && ticker.mode)
if(SSticker && SSticker.mode)
restart = 1
else
GLOB.master_mode = .
@@ -216,7 +216,7 @@ SUBSYSTEM_DEF(vote)
if(VOTE_RESTART)
choices.Add("Restart Round", "Continue Playing")
if(VOTE_GAMEMODE)
if(ticker.current_state >= GAME_STATE_SETTING_UP)
if(SSticker.current_state >= GAME_STATE_SETTING_UP)
return 0
choices.Add(config.votable_modes)
for(var/F in choices)
@@ -231,13 +231,13 @@ SUBSYSTEM_DEF(vote)
if(get_security_level() == "red" || get_security_level() == "delta")
to_chat(initiator_key, "The current alert status is too high to call for a crew transfer!")
return 0
if(ticker.current_state <= GAME_STATE_SETTING_UP)
if(SSticker.current_state <= GAME_STATE_SETTING_UP)
to_chat(initiator_key, "The crew transfer button has been disabled!")
return 0
question = "Your PDA beeps with a message from Central. Would you like an additional hour to finish ongoing projects? (OOC Notice: Transfer votes must have a majority (70%) of all votes to initiate transfer.)" //Yawn Wider Edit //CHOMP EDIT: Changed to 'one' hour. Add notice stating transfer must contain 70% of total vote.
choices.Add("Initiate Crew Transfer", "Extend the Shift") //VOREStation Edit
if(VOTE_ADD_ANTAGONIST)
if(!CONFIG_GET(flag/allow_extra_antags) || ticker.current_state >= GAME_STATE_SETTING_UP) // CHOMPEdit
if(!CONFIG_GET(flag/allow_extra_antags) || SSticker.current_state >= GAME_STATE_SETTING_UP) // CHOMPEdit
return 0
for(var/antag_type in all_antag_types)
var/datum/antagonist/antag = all_antag_types[antag_type]

View File

@@ -1,6 +1,8 @@
SUBSYSTEM_DEF(webhooks)
name = "Webhooks"
init_order = INIT_ORDER_WEBHOOKS
dependencies = list(
/datum/controller/subsystem/server_maint,
)
flags = SS_NO_FIRE
var/list/webhook_decls = list()
@@ -63,7 +65,7 @@ SUBSYSTEM_DEF(webhooks)
if(!check_rights_for(src, R_HOLDER))
return
if(!SSwebhooks.subsystem_initialized)
if(!SSwebhooks.initialized)
to_chat(usr, span_warning("Let the webhook subsystem initialize before trying to reload it."))
return

View File

@@ -14,8 +14,8 @@
//
SUBSYSTEM_DEF(xenoarch)
name = "Xenoarch"
init_order = INIT_ORDER_XENOARCH
flags = SS_NO_FIRE
init_stage = INITSTAGE_LAST
var/list/artifact_spawning_turfs = list()
var/list/digsite_spawning_turfs = list()