mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-25 22:19:30 +01:00
Subsystems meant to represent/handle in-game networks moved under their own subfolder ( no code changes ) (#90561)
## About The Pull Request While working on another PR, I noticed that there wasn't any real distinction between subsystems that function as a network for various logical components/systems in the code, and subsystems that are meant to represent what is actually an in-game network. As such, I moved what seemed to be clear representations of the functions of in-game networks under their own subfolder. This should have no effects otherwise. ## Why It's Good For The Game Per an .md I added in the folder, it helps to clearly demarcate what are backend programmatic networked systems, and what are meant to actually be a network in the game itself; the radio jammer disrupts your suit sensors and headset, not your signal relationships with DCS, and I believe having that distinction be clear at a glance of the file structure is valuable. ## Changelog 🆑 Bisar code: Subsystems that are meant to represent a network in-game have been moved into their own category inside the codebase. /🆑
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# Networks: Subsystems that are conceptually networked IN-GAME
|
||||
### Specifically, these subsystems are for in-game mechanics that are intended to rely on a digital/radio/physical network, such as telecomms servers or the powernet
|
||||
|
||||
The intent of this folder categorization is to be able to quickly reference what are intended to be in-game networks, vs what is logically networked inside the code.
|
||||
|
||||
Knowing what is meant to be conceptually working off of an in-game network serves as guidance for adding or modifying features or fixing oversights and/or bugs.
|
||||
|
||||
For instance, the radio jammer only affecting headsets and suit sensors could benefit from a shorthand awareness of other systems that are represented as a network, such as research. Using the radio jammer near a server or fabricator could cause it to sever its link to the supply silo or the research web.
|
||||
This example is presented only as such, and not a recommendation on any new features or balance changes.
|
||||
@@ -0,0 +1,61 @@
|
||||
#define REDACTED "???"
|
||||
|
||||
SUBSYSTEM_DEF(bitrunning)
|
||||
name = "Bitrunning"
|
||||
flags = SS_NO_FIRE
|
||||
|
||||
var/list/all_domains = list()
|
||||
|
||||
/datum/controller/subsystem/bitrunning/Initialize()
|
||||
InitializeDomains()
|
||||
return SS_INIT_SUCCESS
|
||||
|
||||
/datum/controller/subsystem/bitrunning/proc/InitializeDomains()
|
||||
for(var/path in subtypesof(/datum/lazy_template/virtual_domain))
|
||||
all_domains += new path()
|
||||
|
||||
/// Compiles a list of available domains.
|
||||
/datum/controller/subsystem/bitrunning/proc/get_available_domains(scanner_tier, points)
|
||||
var/list/levels = list()
|
||||
|
||||
for(var/datum/lazy_template/virtual_domain/domain as anything in all_domains)
|
||||
if(domain.test_only)
|
||||
continue
|
||||
var/can_view = domain.difficulty < scanner_tier && domain.cost <= points + 5
|
||||
var/can_view_reward = domain.difficulty < (scanner_tier + 1) && domain.cost <= points + 3
|
||||
|
||||
UNTYPED_LIST_ADD(levels, list(
|
||||
"announce_ghosts" = domain.announce_to_ghosts,
|
||||
"cost" = domain.cost,
|
||||
"desc" = can_view ? domain.desc : "Limited scanning capabilities. Cannot infer domain details.",
|
||||
"difficulty" = domain.difficulty,
|
||||
"id" = domain.key,
|
||||
"is_modular" = domain.is_modular,
|
||||
"has_secondary_objectives" = counterlist_sum(domain.secondary_loot) ? TRUE : FALSE,
|
||||
"name" = can_view ? domain.name : REDACTED,
|
||||
"reward" = can_view_reward ? domain.reward_points : REDACTED,
|
||||
))
|
||||
|
||||
return levels
|
||||
|
||||
/datum/controller/subsystem/bitrunning/proc/pick_secondary_loot(completed_domain)
|
||||
var/datum/lazy_template/virtual_domain/domain = completed_domain
|
||||
var/choice
|
||||
|
||||
if(counterlist_sum(domain.secondary_loot))
|
||||
choice = pick_weight(domain.secondary_loot)
|
||||
domain.secondary_loot[choice] -= 1
|
||||
else
|
||||
choice = /obj/item/paper/paperslip/bitrunning_error
|
||||
CRASH("Virtual domain [domain.name] tried to pick secondary objective loot, but secondary_loot list was empty.")
|
||||
return choice
|
||||
|
||||
/obj/item/paper/paperslip/bitrunning_error
|
||||
name = "Apology Letter"
|
||||
desc = "Something went wrong here."
|
||||
|
||||
/obj/item/paper/paperslip/bitrunning_error/Initialize(mapload)
|
||||
default_raw_text = "Your reward for collecting the encrypted curiosity failed to arrive, please report this to technical support."
|
||||
return ..()
|
||||
|
||||
#undef REDACTED
|
||||
@@ -0,0 +1,86 @@
|
||||
SUBSYSTEM_DEF(circuit_component)
|
||||
name = "Circuit Components"
|
||||
wait = 0.1 SECONDS
|
||||
priority = FIRE_PRIORITY_DEFAULT
|
||||
flags = SS_NO_INIT
|
||||
|
||||
var/list/callbacks_to_invoke = list()
|
||||
var/list/currentrun = list()
|
||||
|
||||
var/list/instant_run_stack = list()
|
||||
|
||||
var/instant_run_tick = 0
|
||||
var/instant_run_start_cpu_usage = 0
|
||||
var/instant_run_max_cpu_usage = 10
|
||||
var/list/instant_run_callbacks_to_run = list()
|
||||
|
||||
/datum/controller/subsystem/circuit_component/fire(resumed)
|
||||
if(!resumed)
|
||||
currentrun = callbacks_to_invoke.Copy()
|
||||
callbacks_to_invoke.Cut()
|
||||
|
||||
while(length(currentrun))
|
||||
var/datum/callback/to_call = currentrun[1]
|
||||
currentrun.Cut(1,2)
|
||||
|
||||
if(QDELETED(to_call))
|
||||
continue
|
||||
|
||||
to_call.user = null
|
||||
to_call.InvokeAsync()
|
||||
|
||||
if(MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
/**
|
||||
* Adds a callback to be invoked when the next fire() is done. Used by the integrated circuit system.
|
||||
*
|
||||
* Prevents race conditions as it acts like a queue system.
|
||||
* Those that registered first will be executed first and those registered last will be executed last.
|
||||
*/
|
||||
/datum/controller/subsystem/circuit_component/proc/add_callback(datum/port/input, datum/callback/to_call)
|
||||
if(instant_run_tick == world.time && (TICK_USAGE - instant_run_start_cpu_usage) <= instant_run_max_cpu_usage)
|
||||
instant_run_callbacks_to_run += to_call
|
||||
return
|
||||
|
||||
callbacks_to_invoke += to_call
|
||||
|
||||
/// Queues any callbacks to be executed instantly instead of using the subsystem.
|
||||
/datum/controller/subsystem/circuit_component/proc/queue_instant_run(start_cpu_time)
|
||||
if(instant_run_tick)
|
||||
instant_run_stack += list(instant_run_callbacks_to_run)
|
||||
// If we're already instantly executing, don't change the start_cpu_time.
|
||||
start_cpu_time = instant_run_start_cpu_usage
|
||||
|
||||
if(!start_cpu_time)
|
||||
start_cpu_time = TICK_USAGE
|
||||
|
||||
instant_run_tick = world.time
|
||||
instant_run_start_cpu_usage = start_cpu_time
|
||||
instant_run_callbacks_to_run = list()
|
||||
|
||||
/**
|
||||
* Instantly executes the stored callbacks and does this in a loop until there are no stored callbacks or it hits tick limit.
|
||||
*
|
||||
* Returns a list containing any values added by any input port.
|
||||
*/
|
||||
/datum/controller/subsystem/circuit_component/proc/execute_instant_run()
|
||||
var/list/received_inputs = list()
|
||||
while(length(instant_run_callbacks_to_run))
|
||||
var/list/instant_run_currentrun = instant_run_callbacks_to_run
|
||||
instant_run_callbacks_to_run = list()
|
||||
while(length(instant_run_currentrun))
|
||||
var/datum/callback/to_call = instant_run_currentrun[1]
|
||||
instant_run_currentrun.Cut(1,2)
|
||||
to_call.user = null
|
||||
to_call.InvokeAsync(received_inputs)
|
||||
|
||||
if(length(instant_run_stack))
|
||||
instant_run_callbacks_to_run = pop(instant_run_stack)
|
||||
else
|
||||
instant_run_tick = 0
|
||||
|
||||
if((TICK_USAGE - instant_run_start_cpu_usage) <= instant_run_max_cpu_usage)
|
||||
return received_inputs
|
||||
else
|
||||
return null
|
||||
@@ -0,0 +1,520 @@
|
||||
/**
|
||||
* Non-processing subsystem that holds various procs and data structures to manage ID cards, trims and access.
|
||||
*/
|
||||
SUBSYSTEM_DEF(id_access)
|
||||
name = "IDs and Access"
|
||||
flags = SS_NO_FIRE
|
||||
|
||||
/// Dictionary of access flags. Keys are accesses. Values are their associated bitflags.
|
||||
var/list/flags_by_access = list()
|
||||
/// Dictionary of access lists. Keys are access flag names. Values are lists of all accesses as part of that access.
|
||||
var/list/accesses_by_flag = list()
|
||||
/// Dictionary of access flag string representations. Keys are bitflags. Values are their associated names.
|
||||
var/list/access_flag_string_by_flag = list()
|
||||
/// Dictionary of trim singletons. Keys are paths. Values are their associated singletons.
|
||||
var/list/trim_singletons_by_path = list()
|
||||
/// Dictionary of wildcard compatibility flags. Keys are strings for the wildcards. Values are their associated flags.
|
||||
var/list/wildcard_flags_by_wildcard = list()
|
||||
/// Dictionary of accesses based on station region. Keys are region strings. Values are lists of accesses.
|
||||
var/list/accesses_by_region = list()
|
||||
/// Specially formatted list for sending access levels to tgui interfaces.
|
||||
var/list/all_region_access_tgui = list()
|
||||
/// Dictionary of access names. Keys are access levels. Values are their associated names.
|
||||
var/list/desc_by_access = list()
|
||||
/// List of accesses for the Heads of each sub-department alongside the regions they control and their job name.
|
||||
var/list/sub_department_managers_tgui = list()
|
||||
/// Helper list containing all trim paths that can be used as job templates. Intended to be used alongside logic for ACCESS_CHANGE_IDS. Grab templates from sub_department_managers_tgui for Head of Staff restrictions.
|
||||
var/list/station_job_templates = list()
|
||||
/// Helper list containing all trim paths that can be used as Centcom templates.
|
||||
var/list/centcom_job_templates = list()
|
||||
/// Helper list containing all PDA paths that can be painted by station machines. Intended to be used alongside logic for ACCESS_CHANGE_IDS. Grab templates from sub_department_managers_tgui for Head of Staff restrictions.
|
||||
var/list/station_pda_templates = list()
|
||||
/// Helper list containing all station regions.
|
||||
var/list/station_regions = list()
|
||||
|
||||
/// The roundstart generated code for the spare ID safe. This is given to the Captain on shift start. If there's no Captain, it's given to the HoP. If there's no HoP
|
||||
var/spare_id_safe_code = ""
|
||||
|
||||
/datum/controller/subsystem/id_access/Initialize()
|
||||
// We use this because creating the trim singletons requires the config to be loaded.
|
||||
setup_access_flags()
|
||||
setup_region_lists()
|
||||
setup_trim_singletons()
|
||||
setup_wildcard_dict()
|
||||
setup_access_descriptions()
|
||||
setup_tgui_lists()
|
||||
|
||||
spare_id_safe_code = "[rand(0,9)][rand(0,9)][rand(0,9)][rand(0,9)][rand(0,9)]"
|
||||
|
||||
return SS_INIT_SUCCESS
|
||||
|
||||
/**
|
||||
* Called by [/datum/controller/subsystem/ticker/proc/setup]
|
||||
*
|
||||
* This runs through every /datum/id_trim/job singleton and ensures that its access is setup according to
|
||||
* appropriate config entries.
|
||||
*/
|
||||
/datum/controller/subsystem/id_access/proc/refresh_job_trim_singletons()
|
||||
for(var/trim in typesof(/datum/id_trim/job))
|
||||
var/datum/id_trim/job/job_trim = trim_singletons_by_path[trim]
|
||||
|
||||
if(QDELETED(job_trim))
|
||||
stack_trace("Trim \[[trim]\] missing from trim singleton list. Reinitialising this trim.")
|
||||
trim_singletons_by_path[trim] = new trim()
|
||||
continue
|
||||
|
||||
job_trim.refresh_trim_access()
|
||||
|
||||
/// Build access flag lists.
|
||||
/datum/controller/subsystem/id_access/proc/setup_access_flags()
|
||||
accesses_by_flag["[ACCESS_FLAG_COMMON]"] = COMMON_ACCESS
|
||||
for(var/access in accesses_by_flag["[ACCESS_FLAG_COMMON]"])
|
||||
flags_by_access |= list("[access]" = ACCESS_FLAG_COMMON)
|
||||
|
||||
accesses_by_flag["[ACCESS_FLAG_COMMAND]"] = COMMAND_ACCESS
|
||||
for(var/access in accesses_by_flag["[ACCESS_FLAG_COMMAND]"])
|
||||
flags_by_access |= list("[access]" = ACCESS_FLAG_COMMAND)
|
||||
|
||||
accesses_by_flag["[ACCESS_FLAG_PRV_COMMAND]"] = PRIVATE_COMMAND_ACCESS
|
||||
for(var/access in accesses_by_flag["[ACCESS_FLAG_PRV_COMMAND]"])
|
||||
flags_by_access |= list("[access]" = ACCESS_FLAG_PRV_COMMAND)
|
||||
|
||||
accesses_by_flag["[ACCESS_FLAG_CAPTAIN]"] = CAPTAIN_ACCESS
|
||||
for(var/access in accesses_by_flag["[ACCESS_FLAG_CAPTAIN]"])
|
||||
flags_by_access |= list("[access]" = ACCESS_FLAG_CAPTAIN)
|
||||
|
||||
accesses_by_flag["[ACCESS_FLAG_CENTCOM]"] = CENTCOM_ACCESS
|
||||
for(var/access in accesses_by_flag["[ACCESS_FLAG_CENTCOM]"])
|
||||
flags_by_access |= list("[access]" = ACCESS_FLAG_CENTCOM)
|
||||
|
||||
accesses_by_flag["[ACCESS_FLAG_SYNDICATE]"] = SYNDICATE_ACCESS
|
||||
for(var/access in accesses_by_flag["[ACCESS_FLAG_SYNDICATE]"])
|
||||
flags_by_access |= list("[access]" = ACCESS_FLAG_SYNDICATE)
|
||||
|
||||
accesses_by_flag["[ACCESS_FLAG_AWAY]"] = AWAY_ACCESS
|
||||
for(var/access in accesses_by_flag["[ACCESS_FLAG_AWAY]"])
|
||||
flags_by_access |= list("[access]" = ACCESS_FLAG_AWAY)
|
||||
|
||||
accesses_by_flag["[ACCESS_FLAG_SPECIAL]"] = CULT_ACCESS
|
||||
for(var/access in accesses_by_flag["[ACCESS_FLAG_SPECIAL]"])
|
||||
flags_by_access |= list("[access]" = ACCESS_FLAG_SPECIAL)
|
||||
|
||||
access_flag_string_by_flag["[ACCESS_FLAG_COMMON]"] = ACCESS_FLAG_COMMON_NAME
|
||||
access_flag_string_by_flag["[ACCESS_FLAG_COMMAND]"] = ACCESS_FLAG_COMMAND_NAME
|
||||
access_flag_string_by_flag["[ACCESS_FLAG_PRV_COMMAND]"] = ACCESS_FLAG_PRV_COMMAND_NAME
|
||||
access_flag_string_by_flag["[ACCESS_FLAG_CAPTAIN]"] = ACCESS_FLAG_CAPTAIN_NAME
|
||||
access_flag_string_by_flag["[ACCESS_FLAG_CENTCOM]"] = ACCESS_FLAG_CENTCOM_NAME
|
||||
access_flag_string_by_flag["[ACCESS_FLAG_SYNDICATE]"] = ACCESS_FLAG_SYNDICATE_NAME
|
||||
access_flag_string_by_flag["[ACCESS_FLAG_AWAY]"] = ACCESS_FLAG_AWAY_NAME
|
||||
access_flag_string_by_flag["[ACCESS_FLAG_SPECIAL]"] = ACCESS_FLAG_SPECIAL_NAME
|
||||
|
||||
/// Populates the region lists with data about which accesses correspond to which regions.
|
||||
/datum/controller/subsystem/id_access/proc/setup_region_lists()
|
||||
accesses_by_region[REGION_ALL_STATION] = REGION_ACCESS_ALL_STATION
|
||||
accesses_by_region[REGION_ALL_GLOBAL] = REGION_ACCESS_ALL_GLOBAL
|
||||
accesses_by_region[REGION_GENERAL] = REGION_ACCESS_GENERAL
|
||||
accesses_by_region[REGION_SECURITY] = REGION_ACCESS_SECURITY
|
||||
accesses_by_region[REGION_MEDBAY] = REGION_ACCESS_MEDBAY
|
||||
accesses_by_region[REGION_RESEARCH] = REGION_ACCESS_RESEARCH
|
||||
accesses_by_region[REGION_ENGINEERING] = REGION_ACCESS_ENGINEERING
|
||||
accesses_by_region[REGION_SUPPLY] = REGION_ACCESS_SUPPLY
|
||||
accesses_by_region[REGION_COMMAND] = REGION_ACCESS_COMMAND
|
||||
accesses_by_region[REGION_CENTCOM] = REGION_ACCESS_CENTCOM
|
||||
|
||||
station_regions = REGION_AREA_STATION
|
||||
|
||||
/// Instantiate trim singletons and add them to a list.
|
||||
/datum/controller/subsystem/id_access/proc/setup_trim_singletons()
|
||||
for(var/trim in typesof(/datum/id_trim))
|
||||
trim_singletons_by_path[trim] = new trim()
|
||||
|
||||
/// Creates various data structures that primarily get fed to tgui interfaces, although these lists are used in other places.
|
||||
/datum/controller/subsystem/id_access/proc/setup_tgui_lists()
|
||||
for(var/region in accesses_by_region)
|
||||
var/list/region_access = accesses_by_region[region]
|
||||
|
||||
var/parsed_accesses = list()
|
||||
|
||||
for(var/access in region_access)
|
||||
var/access_desc = get_access_desc(access)
|
||||
if(!access_desc)
|
||||
continue
|
||||
|
||||
parsed_accesses += list(list(
|
||||
"desc" = replacetext(access_desc, " ", " "),
|
||||
"ref" = access,
|
||||
))
|
||||
|
||||
all_region_access_tgui[region] = list(list(
|
||||
"name" = region,
|
||||
"accesses" = parsed_accesses,
|
||||
))
|
||||
|
||||
sub_department_managers_tgui = list(
|
||||
"[ACCESS_CAPTAIN]" = list(
|
||||
"regions" = list(REGION_COMMAND),
|
||||
"head" = JOB_CAPTAIN,
|
||||
"templates" = list(),
|
||||
"pdas" = list(),
|
||||
),
|
||||
"[ACCESS_HOP]" = list(
|
||||
"regions" = list(REGION_GENERAL),
|
||||
"head" = JOB_HEAD_OF_PERSONNEL,
|
||||
"templates" = list(),
|
||||
"pdas" = list(),
|
||||
),
|
||||
"[ACCESS_HOS]" = list(
|
||||
"regions" = list(REGION_SECURITY),
|
||||
"head" = JOB_HEAD_OF_SECURITY,
|
||||
"templates" = list(),
|
||||
"pdas" = list(),
|
||||
),
|
||||
"[ACCESS_CMO]" = list(
|
||||
"regions" = list(REGION_MEDBAY),
|
||||
"head" = JOB_CHIEF_MEDICAL_OFFICER,
|
||||
"templates" = list(),
|
||||
"pdas" = list(),
|
||||
),
|
||||
"[ACCESS_RD]" = list(
|
||||
"regions" = list(REGION_RESEARCH),
|
||||
"head" = JOB_RESEARCH_DIRECTOR,
|
||||
"templates" = list(),
|
||||
"pdas" = list(),
|
||||
),
|
||||
"[ACCESS_CE]" = list(
|
||||
"regions" = list(REGION_ENGINEERING),
|
||||
"head" = JOB_CHIEF_ENGINEER,
|
||||
"templates" = list(),
|
||||
"pdas" = list(),
|
||||
),
|
||||
"[ACCESS_QM]" = list(
|
||||
"regions" = list(REGION_SUPPLY),
|
||||
"head" = JOB_QUARTERMASTER,
|
||||
"templates" = list(),
|
||||
"pdas" = list(),
|
||||
),
|
||||
)
|
||||
|
||||
var/list/station_job_trims = subtypesof(/datum/id_trim/job)
|
||||
for(var/trim_path in station_job_trims)
|
||||
var/datum/id_trim/job/trim = trim_singletons_by_path[trim_path]
|
||||
if(!length(trim.template_access))
|
||||
continue
|
||||
|
||||
station_job_templates[trim_path] = trim.assignment
|
||||
for(var/access in trim.template_access)
|
||||
var/list/manager = sub_department_managers_tgui["[access]"]
|
||||
if(!manager)
|
||||
if(access != ACCESS_CHANGE_IDS)
|
||||
WARNING("Invalid template access access \[[access]\] registered with [trim_path]. Template added to global list anyway.")
|
||||
continue
|
||||
var/list/templates = manager["templates"]
|
||||
templates[trim_path] = trim.assignment
|
||||
|
||||
var/list/centcom_job_trims = typesof(/datum/id_trim/centcom) - typesof(/datum/id_trim/centcom/corpse)
|
||||
for(var/trim_path in centcom_job_trims)
|
||||
var/datum/id_trim/trim = trim_singletons_by_path[trim_path]
|
||||
centcom_job_templates[trim_path] = trim.assignment
|
||||
|
||||
var/list/all_pda_paths = typesof(/obj/item/modular_computer/pda)
|
||||
var/list/pda_regions = PDA_PAINTING_REGIONS
|
||||
for(var/pda_path in all_pda_paths)
|
||||
if(!(pda_path in pda_regions))
|
||||
continue
|
||||
|
||||
var/list/region_whitelist = pda_regions[pda_path]
|
||||
for(var/access_txt in sub_department_managers_tgui)
|
||||
var/list/manager_info = sub_department_managers_tgui[access_txt]
|
||||
var/list/manager_regions = manager_info["regions"]
|
||||
for(var/whitelisted_region in region_whitelist)
|
||||
if(!(whitelisted_region in manager_regions))
|
||||
continue
|
||||
var/list/manager_pdas = manager_info["pdas"]
|
||||
var/obj/item/modular_computer/pda/fake_pda = pda_path
|
||||
manager_pdas[pda_path] = initial(fake_pda.name)
|
||||
station_pda_templates[pda_path] = initial(fake_pda.name)
|
||||
|
||||
/// Set up dictionary to convert wildcard names to flags.
|
||||
/datum/controller/subsystem/id_access/proc/setup_wildcard_dict()
|
||||
wildcard_flags_by_wildcard[WILDCARD_NAME_ALL] = WILDCARD_FLAG_ALL
|
||||
wildcard_flags_by_wildcard[WILDCARD_NAME_COMMON] = WILDCARD_FLAG_COMMON
|
||||
wildcard_flags_by_wildcard[WILDCARD_NAME_COMMAND] = WILDCARD_FLAG_COMMAND
|
||||
wildcard_flags_by_wildcard[WILDCARD_NAME_PRV_COMMAND] = WILDCARD_FLAG_PRV_COMMAND
|
||||
wildcard_flags_by_wildcard[WILDCARD_NAME_CAPTAIN] = WILDCARD_FLAG_CAPTAIN
|
||||
wildcard_flags_by_wildcard[WILDCARD_NAME_CENTCOM] = WILDCARD_FLAG_CENTCOM
|
||||
wildcard_flags_by_wildcard[WILDCARD_NAME_SYNDICATE] = WILDCARD_FLAG_SYNDICATE
|
||||
wildcard_flags_by_wildcard[WILDCARD_NAME_AWAY] = WILDCARD_FLAG_AWAY
|
||||
wildcard_flags_by_wildcard[WILDCARD_NAME_SPECIAL] = WILDCARD_FLAG_SPECIAL
|
||||
wildcard_flags_by_wildcard[WILDCARD_NAME_FORCED] = WILDCARD_FLAG_FORCED
|
||||
|
||||
/// Setup dictionary that converts access levels to text descriptions.
|
||||
/datum/controller/subsystem/id_access/proc/setup_access_descriptions()
|
||||
desc_by_access["[ACCESS_CARGO]"] = "Cargo Bay"
|
||||
desc_by_access["[ACCESS_SECURITY]"] = "Security"
|
||||
desc_by_access["[ACCESS_BRIG]"] = "Holding Cells"
|
||||
desc_by_access["[ACCESS_COURT]"] = "Courtroom"
|
||||
desc_by_access["[ACCESS_DETECTIVE]"] = "Detective Office"
|
||||
desc_by_access["[ACCESS_MEDICAL]"] = "Medical"
|
||||
desc_by_access["[ACCESS_GENETICS]"] = "Genetics Lab"
|
||||
desc_by_access["[ACCESS_MORGUE]"] = "Morgue"
|
||||
desc_by_access["[ACCESS_MORGUE_SECURE]"] = "Coroner"
|
||||
desc_by_access["[ACCESS_SCIENCE]"] = "R&D Lab"
|
||||
desc_by_access["[ACCESS_ORDNANCE]"] = "Ordnance Lab"
|
||||
desc_by_access["[ACCESS_ORDNANCE_STORAGE]"] = "Ordnance Storage"
|
||||
desc_by_access["[ACCESS_PLUMBING]"] = "Chemistry Lab"
|
||||
desc_by_access["[ACCESS_RD]"] = "RD Office"
|
||||
desc_by_access["[ACCESS_BAR]"] = "Bar"
|
||||
desc_by_access["[ACCESS_JANITOR]"] = "Custodial Closet"
|
||||
desc_by_access["[ACCESS_ENGINEERING]"] = "Engineering"
|
||||
desc_by_access["[ACCESS_ENGINE_EQUIP]"] = "Power and Engineering Equipment"
|
||||
desc_by_access["[ACCESS_MAINT_TUNNELS]"] = "Maintenance"
|
||||
desc_by_access["[ACCESS_EXTERNAL_AIRLOCKS]"] = "External Airlocks"
|
||||
desc_by_access["[ACCESS_CHANGE_IDS]"] = "ID Console"
|
||||
desc_by_access["[ACCESS_AI_UPLOAD]"] = "AI Chambers"
|
||||
desc_by_access["[ACCESS_TELEPORTER]"] = "Teleporter"
|
||||
desc_by_access["[ACCESS_EVA]"] = "EVA"
|
||||
desc_by_access["[ACCESS_COMMAND]"] = "Command"
|
||||
desc_by_access["[ACCESS_CAPTAIN]"] = "Captain"
|
||||
desc_by_access["[ACCESS_ALL_PERSONAL_LOCKERS]"] = "Personal Lockers"
|
||||
desc_by_access["[ACCESS_CHAPEL_OFFICE]"] = "Chapel Office"
|
||||
desc_by_access["[ACCESS_TECH_STORAGE]"] = "Technical Storage"
|
||||
desc_by_access["[ACCESS_ATMOSPHERICS]"] = "Atmospherics"
|
||||
desc_by_access["[ACCESS_CREMATORIUM]"] = "Crematorium"
|
||||
desc_by_access["[ACCESS_ARMORY]"] = "Armory"
|
||||
desc_by_access["[ACCESS_CONSTRUCTION]"] = "Construction"
|
||||
desc_by_access["[ACCESS_KITCHEN]"] = "Kitchen"
|
||||
desc_by_access["[ACCESS_HYDROPONICS]"] = "Hydroponics"
|
||||
desc_by_access["[ACCESS_LIBRARY]"] = "Library"
|
||||
desc_by_access["[ACCESS_LAWYER]"] = "Law Office"
|
||||
desc_by_access["[ACCESS_ROBOTICS]"] = "Robotics"
|
||||
desc_by_access["[ACCESS_VIROLOGY]"] = "Virology"
|
||||
desc_by_access["[ACCESS_PSYCHOLOGY]"] = "Psychology"
|
||||
desc_by_access["[ACCESS_CMO]"] = "CMO Office"
|
||||
desc_by_access["[ACCESS_QM]"] = "QM Office"
|
||||
desc_by_access["[ACCESS_SURGERY]"] = "Surgery"
|
||||
desc_by_access["[ACCESS_THEATRE]"] = "Theatre"
|
||||
desc_by_access["[ACCESS_RESEARCH]"] = "Science"
|
||||
desc_by_access["[ACCESS_MINING]"] = "Mining Dock"
|
||||
desc_by_access["[ACCESS_SHIPPING]"] = "Cargo Shipping"
|
||||
desc_by_access["[ACCESS_VAULT]"] = "Main Vault"
|
||||
desc_by_access["[ACCESS_MINING_STATION]"] = "Mining Outpost"
|
||||
desc_by_access["[ACCESS_XENOBIOLOGY]"] = "Xenobiology Lab"
|
||||
desc_by_access["[ACCESS_HOP]"] = "HoP Office"
|
||||
desc_by_access["[ACCESS_HOS]"] = "HoS Office"
|
||||
desc_by_access["[ACCESS_CE]"] = "CE Office"
|
||||
desc_by_access["[ACCESS_PHARMACY]"] = "Pharmacy"
|
||||
desc_by_access["[ACCESS_RC_ANNOUNCE]"] = "RC Announcements"
|
||||
desc_by_access["[ACCESS_KEYCARD_AUTH]"] = "Keycode Auth."
|
||||
desc_by_access["[ACCESS_TCOMMS]"] = "Telecommunications"
|
||||
desc_by_access["[ACCESS_GATEWAY]"] = "Gateway"
|
||||
desc_by_access["[ACCESS_BRIG_ENTRANCE]"] = "Brig"
|
||||
desc_by_access["[ACCESS_MINERAL_STOREROOM]"] = "Mineral Storage"
|
||||
desc_by_access["[ACCESS_MINISAT]"] = "AI Satellite"
|
||||
desc_by_access["[ACCESS_WEAPONS]"] = "Weapon Permit"
|
||||
desc_by_access["[ACCESS_NETWORK]"] = "Network Access"
|
||||
desc_by_access["[ACCESS_MECH_MINING]"] = "Mining Mech Access"
|
||||
desc_by_access["[ACCESS_MECH_MEDICAL]"] = "Medical Mech Access"
|
||||
desc_by_access["[ACCESS_MECH_SECURITY]"] = "Security Mech Access"
|
||||
desc_by_access["[ACCESS_MECH_SCIENCE]"] = "Science Mech Access"
|
||||
desc_by_access["[ACCESS_MECH_ENGINE]"] = "Engineering Mech Access"
|
||||
desc_by_access["[ACCESS_AUX_BASE]"] = "Auxiliary Base"
|
||||
desc_by_access["[ACCESS_SERVICE]"] = "Service Hallway"
|
||||
desc_by_access["[ACCESS_CENT_GENERAL]"] = "Code Grey"
|
||||
desc_by_access["[ACCESS_CENT_THUNDER]"] = "Code Yellow"
|
||||
desc_by_access["[ACCESS_CENT_STORAGE]"] = "Code Orange"
|
||||
desc_by_access["[ACCESS_CENT_LIVING]"] = "Code Green"
|
||||
desc_by_access["[ACCESS_CENT_MEDICAL]"] = "Code White"
|
||||
desc_by_access["[ACCESS_CENT_TELEPORTER]"] = "Code Blue"
|
||||
desc_by_access["[ACCESS_CENT_SPECOPS]"] = "Code Black"
|
||||
desc_by_access["[ACCESS_CENT_CAPTAIN]"] = "Code Gold"
|
||||
desc_by_access["[ACCESS_CENT_BAR]"] = "Code Scotch"
|
||||
desc_by_access["[ACCESS_BIT_DEN]"] = "Bitrunner Den"
|
||||
|
||||
/**
|
||||
* Returns the access bitflags associated with any given access level.
|
||||
*
|
||||
* In proc form due to accesses being stored in the list as text instead of numbers.
|
||||
* Arguments:
|
||||
* * access - Access as either pure number or as a string representation of the number.
|
||||
*/
|
||||
/datum/controller/subsystem/id_access/proc/get_access_flag(access)
|
||||
var/flag = flags_by_access["[access]"]
|
||||
return flag
|
||||
|
||||
/**
|
||||
* Returns the access description associated with any given access level.
|
||||
*
|
||||
* In proc form due to accesses being stored in the list as text instead of numbers.
|
||||
* Arguments:
|
||||
* * access - Access as either pure number or as a string representation of the number.
|
||||
*/
|
||||
/datum/controller/subsystem/id_access/proc/get_access_desc(access)
|
||||
return desc_by_access["[access]"]
|
||||
|
||||
/**
|
||||
* Builds and returns a list of accesses from a list of regions.
|
||||
*
|
||||
* Arguments:
|
||||
* * regions - A list of region defines.
|
||||
*/
|
||||
/datum/controller/subsystem/id_access/proc/get_region_access_list(list/regions)
|
||||
if(!length(regions))
|
||||
return
|
||||
|
||||
var/list/built_region_list = list()
|
||||
|
||||
for(var/region in regions)
|
||||
built_region_list |= accesses_by_region[region]
|
||||
|
||||
return built_region_list
|
||||
|
||||
/**
|
||||
* Returns the list of all accesses associated with any given access flag.
|
||||
*
|
||||
* In proc form due to accesses being stored in the list as text instead of numbers.
|
||||
* Arguments:
|
||||
* * flag - The flag to get access for as either a pure number of string representation of the flag.
|
||||
*/
|
||||
/datum/controller/subsystem/id_access/proc/get_flag_access_list(flag)
|
||||
return accesses_by_flag["[flag]"]
|
||||
|
||||
/**
|
||||
* Applies a trim singleton to a card.
|
||||
*
|
||||
* Returns FALSE if the trim could not be applied due to being incompatible with the card.
|
||||
* Incompatibility is defined as a card not being able to hold all the trim's required wildcards.
|
||||
* Returns TRUE otherwise.
|
||||
* Arguments:
|
||||
* * id_card - ID card to apply the trim_path to.
|
||||
* * trim_path - A trim path to apply to the card. Grabs the trim's associated singleton and applies it.
|
||||
* * copy_access - Boolean value. If true, the trim's access is also copied to the card.
|
||||
*/
|
||||
/datum/controller/subsystem/id_access/proc/apply_trim_to_card(obj/item/card/id/id_card, trim_path, copy_access = TRUE)
|
||||
var/datum/id_trim/trim = trim_singletons_by_path[trim_path]
|
||||
|
||||
if(!id_card.can_add_wildcards(trim.wildcard_access))
|
||||
return FALSE
|
||||
|
||||
id_card.clear_access()
|
||||
id_card.trim = trim
|
||||
id_card.big_pointer = trim.big_pointer
|
||||
id_card.pointer_color = trim.pointer_color
|
||||
|
||||
if(copy_access)
|
||||
id_card.access = trim.access.Copy()
|
||||
id_card.add_wildcards(trim.wildcard_access)
|
||||
|
||||
|
||||
if(trim.assignment)
|
||||
id_card.assignment = trim.assignment
|
||||
|
||||
var/datum/job/trim_job = trim.find_job()
|
||||
if (!isnull(id_card.registered_account))
|
||||
var/datum/job/old_job = id_card.registered_account.account_job
|
||||
id_card.registered_account.account_job = trim_job
|
||||
id_card.registered_account.update_account_job_lists(trim_job, old_job)
|
||||
|
||||
id_card.update_label()
|
||||
id_card.update_icon()
|
||||
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Removes a trim from an ID card. Also removes all accesses from it too.
|
||||
*
|
||||
* Arguments:
|
||||
* * id_card - The ID card to remove the trim from.
|
||||
*/
|
||||
/datum/controller/subsystem/id_access/proc/remove_trim_from_card(obj/item/card/id/id_card)
|
||||
id_card.trim = null
|
||||
id_card.clear_access()
|
||||
id_card.update_label()
|
||||
id_card.update_icon()
|
||||
|
||||
/**
|
||||
* Applies a trim to a card. This is purely visual, utilising the card's override vars.
|
||||
*
|
||||
* Arguments:
|
||||
* * id_card - The card to apply the trim visuals to.
|
||||
* * trim_path - A trim path to apply to the card. Grabs the trim's associated singleton and applies it.
|
||||
* * check_forged - Boolean value. If TRUE, will not overwrite the card's assignment if the card has been forged.
|
||||
*/
|
||||
/datum/controller/subsystem/id_access/proc/apply_trim_override(obj/item/card/id/advanced/id_card, trim_path, check_forged = TRUE)
|
||||
var/datum/id_trim/trim = trim_singletons_by_path[trim_path]
|
||||
id_card.trim_icon_override = trim.trim_icon
|
||||
id_card.trim_state_override = trim.trim_state
|
||||
id_card.trim_assignment_override = trim.assignment
|
||||
id_card.sechud_icon_state_override = trim.sechud_icon_state
|
||||
id_card.department_color_override = trim.department_color
|
||||
id_card.department_state_override = trim.department_state
|
||||
id_card.subdepartment_color_override = trim.subdepartment_color
|
||||
id_card.big_pointer = trim.big_pointer
|
||||
id_card.pointer_color = trim.pointer_color
|
||||
|
||||
var/obj/item/card/id/advanced/chameleon/cham_id = id_card
|
||||
if (istype(cham_id) && (!check_forged || !cham_id.forged))
|
||||
cham_id.assignment = trim.assignment
|
||||
|
||||
if (ishuman(id_card.loc))
|
||||
var/mob/living/carbon/human/owner = id_card.loc
|
||||
owner.sec_hud_set_ID()
|
||||
|
||||
/**
|
||||
* Removes a trim from a ID card.
|
||||
*
|
||||
* Arguments:
|
||||
* * id_card - The ID card to remove the trim from.
|
||||
*/
|
||||
/datum/controller/subsystem/id_access/proc/remove_trim_override(obj/item/card/id/advanced/id_card)
|
||||
id_card.trim_icon_override = null
|
||||
id_card.trim_state_override = null
|
||||
id_card.trim_assignment_override = null
|
||||
id_card.sechud_icon_state_override = null
|
||||
id_card.department_color_override = null
|
||||
id_card.department_state_override = null
|
||||
id_card.subdepartment_color_override = null
|
||||
id_card.big_pointer = id_card.trim.big_pointer
|
||||
id_card.pointer_color = id_card.trim.pointer_color
|
||||
|
||||
if (ishuman(id_card.loc))
|
||||
var/mob/living/carbon/human/owner = id_card.loc
|
||||
owner.sec_hud_set_ID()
|
||||
|
||||
/**
|
||||
* Adds the accesses associated with a trim to an ID card.
|
||||
*
|
||||
* Clears the card's existing access levels first.
|
||||
* Primarily intended for applying trim templates to cards. Will attempt to add as many ordinary access
|
||||
* levels as it can, without consuming any wildcards. Will then attempt to apply the trim-specific wildcards after.
|
||||
*
|
||||
* Arguments:
|
||||
* * id_card - The ID card to remove the trim from.
|
||||
*/
|
||||
/datum/controller/subsystem/id_access/proc/add_trim_access_to_card(obj/item/card/id/id_card, trim_path)
|
||||
var/datum/id_trim/trim = trim_singletons_by_path[trim_path]
|
||||
|
||||
id_card.clear_access()
|
||||
|
||||
id_card.add_access(trim.access, mode = TRY_ADD_ALL_NO_WILDCARD)
|
||||
id_card.add_wildcards(trim.wildcard_access, mode = TRY_ADD_ALL)
|
||||
if(istype(trim, /datum/id_trim/job))
|
||||
var/datum/id_trim/job/job_trim = trim // Here is where we update a player's paycheck department for the purposes of discounts/paychecks.
|
||||
id_card.registered_account.account_job.paycheck_department = job_trim.job.paycheck_department
|
||||
|
||||
/**
|
||||
* Tallies up all accesses the card has that have flags greater than or equal to the access_flag supplied.
|
||||
*
|
||||
* Returns the number of accesses that have flags matching access_flag or a higher tier access.
|
||||
* Arguments:
|
||||
* * id_card - The ID card to tally up access for.
|
||||
* * access_flag - The minimum access flag required for an access to be tallied up.
|
||||
*/
|
||||
/datum/controller/subsystem/id_access/proc/tally_access(obj/item/card/id/id_card, access_flag = NONE)
|
||||
var/tally = 0
|
||||
|
||||
var/list/id_card_access = id_card.access
|
||||
for(var/access in id_card_access)
|
||||
if(flags_by_access["[access]"] >= access_flag)
|
||||
tally++
|
||||
|
||||
return tally
|
||||
@@ -0,0 +1,176 @@
|
||||
///The maximum amount of logs that can be generated before they start overwriting each other.
|
||||
#define MAX_LOG_COUNT 300
|
||||
|
||||
SUBSYSTEM_DEF(modular_computers)
|
||||
name = "Modular Computers"
|
||||
|
||||
wait = 1 MINUTES
|
||||
runlevels = RUNLEVEL_GAME
|
||||
|
||||
///List of all logs generated by ModPCs through the round.
|
||||
///Stops at MAX_LOG_COUNT and must be purged to keep logging.
|
||||
var/list/modpc_logs = list()
|
||||
|
||||
///List of all programs available to download from the NTNet store.
|
||||
var/list/available_station_software = list()
|
||||
///List of all programs that can be downloaded from an emagged NTNet store.
|
||||
var/list/available_antag_software = list()
|
||||
///List of all chat channels created by Chat Client.
|
||||
var/list/chat_channels = list()
|
||||
|
||||
///Boolean on whether the IDS warning system is enabled
|
||||
var/intrusion_detection_enabled = TRUE
|
||||
///Boolean to show a message warning if there's an active intrusion for Wirecarp users.
|
||||
var/intrusion_detection_alarm = FALSE
|
||||
var/next_picture_id = 0
|
||||
|
||||
///Lazylist of coupons used by the Coupon Master PDA app. e.g. "COUPONCODE25" = coupon_code
|
||||
var/list/discount_coupons
|
||||
///When will the next coupon drop?
|
||||
var/next_discount = 0
|
||||
|
||||
/datum/controller/subsystem/modular_computers/Initialize()
|
||||
build_software_lists()
|
||||
initialized = TRUE
|
||||
return SS_INIT_SUCCESS
|
||||
|
||||
/datum/controller/subsystem/modular_computers/fire(resumed = FALSE)
|
||||
if(discount_coupons && world.time >= next_discount)
|
||||
announce_coupon()
|
||||
|
||||
///Generate new coupon codes that can be redeemed with the Coupon Master App
|
||||
/datum/controller/subsystem/modular_computers/proc/announce_coupon()
|
||||
//If there's no way to announce the coupon, we may as well skip it.
|
||||
var/obj/machinery/announcement_system/announcement_system = get_announcement_system()
|
||||
if(!announcement_system)
|
||||
return
|
||||
|
||||
var/static/list/discounts = list("0.10" = 7, "0.15" = 16, "0.20" = 20, "0.25" = 16, "0.50" = 8, "0.66" = 1)
|
||||
var/static/list/flash_discounts = list("0.30" = 3, "0.40" = 8, "0.50" = 8, "0.66" = 2, "0.75" = 1)
|
||||
///Eliminates non-alphanumeric characters, as well as the word "Single-Pack" or "Pack" or "Crate" from the coupon code
|
||||
var/static/regex/strip_pack_name = regex("\[^a-zA-Z0-9]|(Single-)?Pack|Crate", "g")
|
||||
|
||||
var/datum/supply_pack/discounted_pack = pick(GLOB.discountable_packs[pick_weight(GLOB.pack_discount_odds)])
|
||||
var/pack_name = initial(discounted_pack.name)
|
||||
var/chosen_discount
|
||||
var/expires_in = 0
|
||||
if(prob(75))
|
||||
chosen_discount = text2num(pick_weight(discounts))
|
||||
if(prob(20))
|
||||
expires_in = rand(8,10) MINUTES
|
||||
else
|
||||
chosen_discount = text2num(pick_weight(flash_discounts))
|
||||
expires_in = rand(2, 4) MINUTES
|
||||
var/coupon_code = "[uppertext(strip_pack_name.Replace(pack_name, ""))][chosen_discount*100]"
|
||||
|
||||
var/list/targets = list()
|
||||
for (var/messenger_ref in GLOB.pda_messengers)
|
||||
var/datum/computer_file/program/messenger/messenger = GLOB.pda_messengers[messenger_ref]
|
||||
if(locate(/datum/computer_file/program/coupon) in messenger?.computer.stored_files)
|
||||
targets += messenger
|
||||
|
||||
///Don't go any further if the same coupon code has been done alrady or if there's no recipient for the 'promo'.
|
||||
if((coupon_code in discount_coupons) || !length(targets))
|
||||
return
|
||||
|
||||
var/datum/coupon_code/coupon = new(chosen_discount, discounted_pack, expires_in)
|
||||
|
||||
discount_coupons[coupon_code] = coupon
|
||||
|
||||
///pda message code here
|
||||
var/static/list/promo_messages = list(
|
||||
"A new discount has dropped for %GOODY: %DISCOUNT.",
|
||||
"Check this new offer out: %GOODY, now %DISCOUNT off.",
|
||||
"Now on sales: %GOODY, at %DISCOUNT discount!",
|
||||
"This item is now on sale (%DISCOUNT off): %GOODY.",
|
||||
"Would you look at that! A %DISCOUNT discount on %GOODY!",
|
||||
"Exclusive offer for %GOODY. Only %DISCOUNT! Get it now:",
|
||||
"%GOODY is now %DISCOUNT off.",
|
||||
"*RING* A new discount has dropped: %GOODY, %DISCOUNT off.",
|
||||
"%GOODY - %DISCOUNT off."
|
||||
)
|
||||
var/static/list/code_messages = list(
|
||||
"Here's the code",
|
||||
"Use this code to redeem it",
|
||||
"Open the app to redeem it",
|
||||
"Code",
|
||||
"Redeem it now",
|
||||
"Buy it now",
|
||||
)
|
||||
|
||||
var/chosen_promo_message = replacetext(replacetext(pick(promo_messages), "%GOODY", pack_name), "%DISCOUNT", "[chosen_discount*100]%")
|
||||
var/datum/signal/subspace/messaging/tablet_message/signal = new(announcement_system, list(
|
||||
"fakename" = "Coupon Master",
|
||||
"fakejob" = "Goodies Promotion",
|
||||
"message" = "[chosen_promo_message] [pick(code_messages)]: [coupon_code][expires_in ? " (EXPIRES IN [uppertext(DisplayTimeText(expires_in))])" : ""].",
|
||||
"targets" = targets,
|
||||
"automated" = TRUE,
|
||||
))
|
||||
|
||||
signal.send_to_receivers()
|
||||
|
||||
next_discount = world.time + rand(3, 5) MINUTES
|
||||
|
||||
|
||||
///Finds all downloadable programs and adds them to their respective downloadable list.
|
||||
/datum/controller/subsystem/modular_computers/proc/build_software_lists()
|
||||
for(var/datum/computer_file/program/prog as anything in subtypesof(/datum/computer_file/program))
|
||||
// Has no TGUI file so is not meant to be a downloadable thing.
|
||||
if(!initial(prog.tgui_id) || !initial(prog.filename))
|
||||
continue
|
||||
prog = new prog
|
||||
|
||||
if(prog.program_flags & PROGRAM_ON_NTNET_STORE)
|
||||
available_station_software.Add(prog)
|
||||
if(prog.program_flags & PROGRAM_ON_SYNDINET_STORE)
|
||||
available_antag_software.Add(prog)
|
||||
|
||||
///Attempts to find a new file through searching the available stores with its name.
|
||||
/datum/controller/subsystem/modular_computers/proc/find_ntnet_file_by_name(filename)
|
||||
for(var/datum/computer_file/program/programs as anything in available_station_software + available_antag_software)
|
||||
if(filename == programs.filename)
|
||||
return programs
|
||||
return null
|
||||
|
||||
///Attempts to find a chatorom using the ID of the channel.
|
||||
/datum/controller/subsystem/modular_computers/proc/get_chat_channel_by_id(id)
|
||||
for(var/datum/ntnet_conversation/chan as anything in chat_channels)
|
||||
if(chan.id == id)
|
||||
return chan
|
||||
return null
|
||||
|
||||
/**
|
||||
* Records a message into the station logging system for the network
|
||||
* Arguments:
|
||||
* * log_string - The message being logged
|
||||
*/
|
||||
/datum/controller/subsystem/modular_computers/proc/add_log(log_string)
|
||||
var/list/log_text = list()
|
||||
log_text += "\[[station_time_timestamp()]\]"
|
||||
log_text += "*SYSTEM* - "
|
||||
log_text += log_string
|
||||
log_string = log_text.Join()
|
||||
|
||||
modpc_logs.Add(log_string)
|
||||
|
||||
// We have too many logs, remove the oldest entries until we get into the limit
|
||||
if(modpc_logs.len > MAX_LOG_COUNT)
|
||||
modpc_logs = modpc_logs.Copy(modpc_logs.len - MAX_LOG_COUNT, 0)
|
||||
|
||||
/**
|
||||
* Removes all station logs and leaves it with an alert that it's been wiped.
|
||||
*/
|
||||
/datum/controller/subsystem/modular_computers/proc/purge_logs()
|
||||
modpc_logs = list()
|
||||
add_log("-!- LOGS DELETED BY SYSTEM OPERATOR -!-")
|
||||
|
||||
/**
|
||||
* Returns a name which a /datum/picture can be assigned to.
|
||||
* Use this function to get asset names and to avoid cache duplicates/overwriting.
|
||||
*/
|
||||
/datum/controller/subsystem/modular_computers/proc/get_next_picture_name()
|
||||
var/next_uid = next_picture_id
|
||||
next_picture_id++
|
||||
return "ntos_picture_[next_uid].png"
|
||||
|
||||
#undef MAX_LOG_COUNT
|
||||
@@ -0,0 +1,35 @@
|
||||
SUBSYSTEM_DEF(radio)
|
||||
name = "Radio"
|
||||
flags = SS_NO_FIRE|SS_NO_INIT
|
||||
|
||||
var/list/datum/radio_frequency/frequencies = list()
|
||||
var/list/saymodes = list()
|
||||
|
||||
/datum/controller/subsystem/radio/PreInit()
|
||||
for(var/_SM in subtypesof(/datum/saymode))
|
||||
var/datum/saymode/SM = new _SM()
|
||||
saymodes[SM.key] = SM
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/radio/proc/add_object(obj/device, new_frequency as num, filter = null as text|null)
|
||||
var/f_text = num2text(new_frequency)
|
||||
var/datum/radio_frequency/frequency = frequencies[f_text]
|
||||
if(!frequency)
|
||||
frequencies[f_text] = frequency = new(new_frequency)
|
||||
frequency.add_listener(device, 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)
|
||||
// let's don't delete frequencies in case a non-listener keeps a reference
|
||||
return 1
|
||||
|
||||
/datum/controller/subsystem/radio/proc/return_frequency(new_frequency as num)
|
||||
var/f_text = num2text(new_frequency)
|
||||
var/datum/radio_frequency/frequency = frequencies[f_text]
|
||||
if(!frequency)
|
||||
frequencies[f_text] = frequency = new(new_frequency)
|
||||
return frequency
|
||||
@@ -0,0 +1,357 @@
|
||||
|
||||
SUBSYSTEM_DEF(research)
|
||||
name = "Research"
|
||||
priority = FIRE_PRIORITY_RESEARCH
|
||||
wait = 10
|
||||
dependencies = list(
|
||||
/datum/controller/subsystem/processing/station
|
||||
)
|
||||
//TECHWEB STATIC
|
||||
var/list/techweb_nodes = list() //associative id = node datum
|
||||
var/list/techweb_designs = list() //associative id = node datum
|
||||
var/list/list/datum/design/item_to_design = list() //typepath = list of design datums
|
||||
|
||||
///List of all techwebs, generating points or not.
|
||||
///Autolathes, Mechfabs, and others all have shared techwebs, for example.
|
||||
var/list/datum/techweb/techwebs = list()
|
||||
|
||||
var/datum/techweb_node/error_node/error_node //These two are what you get if a node/design is deleted and somehow still stored in a console.
|
||||
var/datum/design/error_design/error_design
|
||||
|
||||
//ERROR LOGGING
|
||||
///associative id = number of times
|
||||
var/list/invalid_design_ids = list()
|
||||
///associative id = number of times
|
||||
var/list/invalid_node_ids = list()
|
||||
///associative id = error message
|
||||
var/list/invalid_node_boost = list()
|
||||
|
||||
///associative id = TRUE
|
||||
var/list/techweb_nodes_starting = list()
|
||||
///category name = list(node.id = TRUE)
|
||||
var/list/techweb_categories = list()
|
||||
///List of all items that can unlock a node. (node.id = list(items))
|
||||
var/list/techweb_unlock_items = list()
|
||||
///Node ids that should be hidden by default.
|
||||
var/list/techweb_nodes_hidden = list()
|
||||
///Node ids that are exclusive to the BEPIS.
|
||||
var/list/techweb_nodes_experimental = list()
|
||||
///path = list(point type = value)
|
||||
var/list/techweb_point_items = list(
|
||||
/obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS)
|
||||
)
|
||||
var/list/errored_datums = list()
|
||||
///Associated list of all point types that techwebs will have and their respective 'abbreviated' name.
|
||||
var/list/point_types = list(TECHWEB_POINT_TYPE_GENERIC = "Gen. Res.")
|
||||
//----------------------------------------------
|
||||
var/list/single_server_income = list(
|
||||
TECHWEB_POINT_TYPE_GENERIC = TECHWEB_SINGLE_SERVER_INCOME,
|
||||
)
|
||||
//^^^^^^^^ ALL OF THESE ARE PER SECOND! ^^^^^^^^
|
||||
|
||||
//Aiming for 1.5 hours to max R&D
|
||||
//[88nodes * 5000points/node] / [1.5hr * 90min/hr * 60s/min]
|
||||
//Around 450000 points max???
|
||||
|
||||
/// The global list of raw anomaly types that have been refined, for hard limits.
|
||||
var/list/created_anomaly_types = list()
|
||||
/// The hard limits of cores created for each anomaly type. For faster code lookup without switch statements.
|
||||
var/list/anomaly_hard_limit_by_type = list(
|
||||
/obj/item/assembly/signaler/anomaly/bluespace = MAX_CORES_BLUESPACE,
|
||||
/obj/item/assembly/signaler/anomaly/pyro = MAX_CORES_PYRO,
|
||||
/obj/item/assembly/signaler/anomaly/grav = MAX_CORES_GRAVITATIONAL,
|
||||
/obj/item/assembly/signaler/anomaly/vortex = MAX_CORES_VORTEX,
|
||||
/obj/item/assembly/signaler/anomaly/flux = MAX_CORES_FLUX,
|
||||
/obj/item/assembly/signaler/anomaly/hallucination = MAX_CORES_HALLUCINATION,
|
||||
/obj/item/assembly/signaler/anomaly/bioscrambler = MAX_CORES_BIOSCRAMBLER,
|
||||
/obj/item/assembly/signaler/anomaly/dimensional = MAX_CORES_DIMENSIONAL,
|
||||
/obj/item/assembly/signaler/anomaly/ectoplasm = MAX_CORES_ECTOPLASMIC,
|
||||
)
|
||||
|
||||
/// Lookup list for ordnance briefers.
|
||||
var/list/ordnance_experiments = list()
|
||||
/// Lookup list for scipaper partners.
|
||||
var/list/datum/scientific_partner/scientific_partners = list()
|
||||
|
||||
/datum/controller/subsystem/research/Initialize()
|
||||
initialize_all_techweb_designs()
|
||||
initialize_all_techweb_nodes()
|
||||
populate_ordnance_experiments()
|
||||
new /datum/techweb/science
|
||||
new /datum/techweb/admin
|
||||
new /datum/techweb/oldstation
|
||||
autosort_categories()
|
||||
error_design = new
|
||||
error_node = new
|
||||
return SS_INIT_SUCCESS
|
||||
|
||||
/datum/controller/subsystem/research/fire()
|
||||
for(var/datum/techweb/techweb_list as anything in techwebs)
|
||||
if(!techweb_list.should_generate_points)
|
||||
continue
|
||||
var/list/bitcoins = list()
|
||||
for(var/obj/machinery/rnd/server/miner as anything in techweb_list.techweb_servers)
|
||||
if(miner.working)
|
||||
bitcoins = single_server_income.Copy()
|
||||
break //Just need one to work.
|
||||
|
||||
if(!isnull(techweb_list.last_income))
|
||||
var/income_time_difference = world.time - techweb_list.last_income
|
||||
techweb_list.last_bitcoins = bitcoins // Doesn't take tick drift into account
|
||||
for(var/i in bitcoins)
|
||||
bitcoins[i] *= (income_time_difference / 10) * techweb_list.income_modifier
|
||||
techweb_list.add_point_list(bitcoins)
|
||||
|
||||
techweb_list.last_income = world.time
|
||||
|
||||
if(length(techweb_list.research_queue_nodes))
|
||||
techweb_list.research_node_id(techweb_list.research_queue_nodes[1]) // Attempt to research the first node in queue if possible
|
||||
|
||||
for(var/node_id in techweb_list.research_queue_nodes)
|
||||
var/datum/techweb_node/node = SSresearch.techweb_node_by_id(node_id)
|
||||
if(node.is_free(techweb_list)) // Automatically research all free nodes in queue if any
|
||||
techweb_list.research_node(node)
|
||||
|
||||
/datum/controller/subsystem/research/proc/autosort_categories()
|
||||
for(var/i in techweb_nodes)
|
||||
var/datum/techweb_node/I = techweb_nodes[i]
|
||||
if(techweb_categories[I.category])
|
||||
techweb_categories[I.category][I.id] = TRUE
|
||||
else
|
||||
techweb_categories[I.category] = list(I.id = TRUE)
|
||||
|
||||
/datum/controller/subsystem/research/proc/techweb_node_by_id(id)
|
||||
return techweb_nodes[id] || error_node
|
||||
|
||||
/datum/controller/subsystem/research/proc/techweb_design_by_id(id)
|
||||
return techweb_designs[id] || error_design
|
||||
|
||||
/datum/controller/subsystem/research/proc/on_design_deletion(datum/design/D)
|
||||
for(var/i in techweb_nodes)
|
||||
var/datum/techweb_node/TN = techwebs[i]
|
||||
TN.on_design_deletion(TN)
|
||||
for(var/i in techwebs)
|
||||
var/datum/techweb/T = i
|
||||
T.recalculate_nodes(TRUE)
|
||||
|
||||
/datum/controller/subsystem/research/proc/on_node_deletion(datum/techweb_node/TN)
|
||||
for(var/i in techweb_nodes)
|
||||
var/datum/techweb_node/TN2 = techwebs[i]
|
||||
TN2.on_node_deletion(TN)
|
||||
for(var/i in techwebs)
|
||||
var/datum/techweb/T = i
|
||||
T.recalculate_nodes(TRUE)
|
||||
|
||||
/datum/controller/subsystem/research/proc/initialize_all_techweb_nodes(clearall = FALSE)
|
||||
if(islist(techweb_nodes) && clearall)
|
||||
QDEL_LIST(techweb_nodes)
|
||||
if(islist(techweb_nodes_starting && clearall))
|
||||
techweb_nodes_starting.Cut()
|
||||
var/list/returned = list()
|
||||
for(var/path in subtypesof(/datum/techweb_node))
|
||||
var/datum/techweb_node/TN = path
|
||||
if(isnull(initial(TN.id)))
|
||||
continue
|
||||
TN = new path
|
||||
if(returned[initial(TN.id)])
|
||||
stack_trace("WARNING: Techweb node ID clash with ID [initial(TN.id)] detected! Path: [path]")
|
||||
errored_datums[TN] = initial(TN.id)
|
||||
continue
|
||||
returned[initial(TN.id)] = TN
|
||||
if(TN.starting_node)
|
||||
techweb_nodes_starting[TN.id] = TRUE
|
||||
for(var/id in techweb_nodes)
|
||||
var/datum/techweb_node/TN = techweb_nodes[id]
|
||||
TN.Initialize()
|
||||
techweb_nodes = returned
|
||||
if (!verify_techweb_nodes()) //Verify all nodes have ids and such.
|
||||
stack_trace("Invalid techweb nodes detected")
|
||||
calculate_techweb_nodes()
|
||||
calculate_techweb_item_unlocking_requirements()
|
||||
if (!verify_techweb_nodes()) //Verify nodes and designs have been crosslinked properly.
|
||||
CRASH("Invalid techweb nodes detected")
|
||||
|
||||
/datum/controller/subsystem/research/proc/initialize_all_techweb_designs(clearall = FALSE)
|
||||
if(islist(techweb_designs) && clearall)
|
||||
item_to_design = list()
|
||||
QDEL_LIST(techweb_designs)
|
||||
var/list/returned = list()
|
||||
for(var/path in subtypesof(/datum/design))
|
||||
var/datum/design/DN = path
|
||||
if(isnull(initial(DN.id)))
|
||||
stack_trace("WARNING: Design with null ID detected. Build path: [initial(DN.build_path)]")
|
||||
continue
|
||||
else if(initial(DN.id) == DESIGN_ID_IGNORE)
|
||||
continue
|
||||
DN = new path
|
||||
if(returned[initial(DN.id)])
|
||||
stack_trace("WARNING: Design ID clash with ID [initial(DN.id)] detected! Path: [path]")
|
||||
errored_datums[DN] = initial(DN.id)
|
||||
continue
|
||||
var/build_path = initial(DN.build_path)
|
||||
if(!isnull(build_path))
|
||||
if(!(build_path in item_to_design))
|
||||
item_to_design[build_path] = list()
|
||||
item_to_design[build_path] += DN
|
||||
DN.InitializeMaterials() //Initialize the materials in the design
|
||||
returned[initial(DN.id)] = DN
|
||||
techweb_designs = returned
|
||||
verify_techweb_designs()
|
||||
|
||||
|
||||
/datum/controller/subsystem/research/proc/verify_techweb_nodes()
|
||||
. = TRUE
|
||||
for(var/n in techweb_nodes)
|
||||
var/datum/techweb_node/N = techweb_nodes[n]
|
||||
if(!istype(N))
|
||||
WARNING("Invalid research node with ID [n] detected and removed.")
|
||||
techweb_nodes -= n
|
||||
research_node_id_error(n)
|
||||
. = FALSE
|
||||
for(var/p in N.prereq_ids)
|
||||
var/datum/techweb_node/P = techweb_nodes[p]
|
||||
if(!istype(P))
|
||||
WARNING("Invalid research prerequisite node with ID [p] detected in node [N.display_name]\[[N.id]\] removed.")
|
||||
N.prereq_ids -= p
|
||||
research_node_id_error(p)
|
||||
. = FALSE
|
||||
for(var/d in N.design_ids)
|
||||
var/datum/design/D = techweb_designs[d]
|
||||
if(!istype(D))
|
||||
WARNING("Invalid research design with ID [d] detected in node [N.display_name]\[[N.id]\] removed.")
|
||||
N.design_ids -= d
|
||||
design_id_error(d)
|
||||
. = FALSE
|
||||
for(var/u in N.unlock_ids)
|
||||
var/datum/techweb_node/U = techweb_nodes[u]
|
||||
if(!istype(U))
|
||||
WARNING("Invalid research unlock node with ID [u] detected in node [N.display_name]\[[N.id]\] removed.")
|
||||
N.unlock_ids -= u
|
||||
research_node_id_error(u)
|
||||
. = FALSE
|
||||
for(var/p in N.required_items_to_unlock)
|
||||
if(!ispath(p))
|
||||
N.required_items_to_unlock -= p
|
||||
WARNING("[p] is not a valid path.")
|
||||
node_boost_error(N.id, "[p] is not a valid path.")
|
||||
. = FALSE
|
||||
var/list/points = N.required_items_to_unlock[p]
|
||||
if(!isnull(points))
|
||||
N.required_items_to_unlock -= p
|
||||
node_boost_error(N.id, "No valid list.")
|
||||
WARNING("No valid list.")
|
||||
. = FALSE
|
||||
CHECK_TICK
|
||||
|
||||
/datum/controller/subsystem/research/proc/verify_techweb_designs()
|
||||
for(var/d in techweb_designs)
|
||||
var/datum/design/D = techweb_designs[d]
|
||||
if(!istype(D))
|
||||
stack_trace("WARNING: Invalid research design with ID [d] detected and removed.")
|
||||
techweb_designs -= d
|
||||
CHECK_TICK
|
||||
|
||||
/datum/controller/subsystem/research/proc/research_node_id_error(id)
|
||||
if(invalid_node_ids[id])
|
||||
invalid_node_ids[id]++
|
||||
else
|
||||
invalid_node_ids[id] = 1
|
||||
|
||||
/datum/controller/subsystem/research/proc/design_id_error(id)
|
||||
if(invalid_design_ids[id])
|
||||
invalid_design_ids[id]++
|
||||
else
|
||||
invalid_design_ids[id] = 1
|
||||
|
||||
/datum/controller/subsystem/research/proc/calculate_techweb_nodes()
|
||||
for(var/design_id in techweb_designs)
|
||||
var/datum/design/D = techweb_designs[design_id]
|
||||
D.unlocked_by.Cut()
|
||||
for(var/node_id in techweb_nodes)
|
||||
var/datum/techweb_node/node = techweb_nodes[node_id]
|
||||
node.unlock_ids = list()
|
||||
for(var/i in node.design_ids)
|
||||
var/datum/design/D = techweb_designs[i]
|
||||
node.design_ids[i] = TRUE
|
||||
D.unlocked_by += node.id
|
||||
if(node.hidden)
|
||||
techweb_nodes_hidden[node.id] = TRUE
|
||||
if(node.experimental)
|
||||
techweb_nodes_experimental[node.id] = TRUE
|
||||
CHECK_TICK
|
||||
generate_techweb_unlock_linking()
|
||||
|
||||
/datum/controller/subsystem/research/proc/generate_techweb_unlock_linking()
|
||||
for(var/node_id in techweb_nodes) //Clear all unlock links to avoid duplication.
|
||||
var/datum/techweb_node/node = techweb_nodes[node_id]
|
||||
node.unlock_ids = list()
|
||||
for(var/node_id in techweb_nodes)
|
||||
var/datum/techweb_node/node = techweb_nodes[node_id]
|
||||
for(var/prereq_id in node.prereq_ids)
|
||||
var/datum/techweb_node/prereq_node = techweb_node_by_id(prereq_id)
|
||||
prereq_node.unlock_ids[node.id] = node
|
||||
|
||||
/datum/controller/subsystem/research/proc/calculate_techweb_item_unlocking_requirements()
|
||||
for(var/node_id in techweb_nodes)
|
||||
var/datum/techweb_node/node = techweb_nodes[node_id]
|
||||
for(var/path in node.required_items_to_unlock)
|
||||
if(!ispath(path))
|
||||
continue
|
||||
if(length(techweb_unlock_items[path]))
|
||||
techweb_unlock_items[path][node.id] = node.required_items_to_unlock[path]
|
||||
else
|
||||
techweb_unlock_items[path] = list(node.id = node.required_items_to_unlock[path])
|
||||
CHECK_TICK
|
||||
|
||||
/datum/controller/subsystem/research/proc/populate_ordnance_experiments()
|
||||
for (var/datum/experiment/ordnance/experiment_path as anything in subtypesof(/datum/experiment/ordnance))
|
||||
if (initial(experiment_path.experiment_proper))
|
||||
ordnance_experiments += new experiment_path()
|
||||
|
||||
for(var/partner_path in subtypesof(/datum/scientific_partner))
|
||||
var/datum/scientific_partner/partner = new partner_path
|
||||
if(!partner.accepted_experiments.len)
|
||||
for (var/datum/experiment/ordnance/ordnance_experiment as anything in ordnance_experiments)
|
||||
partner.accepted_experiments += ordnance_experiment.type
|
||||
scientific_partners += partner
|
||||
|
||||
/**
|
||||
* Goes through all techwebs and goes through their servers to find ones on a valid z-level
|
||||
* Returns the full list of all techweb servers.
|
||||
*/
|
||||
/datum/controller/subsystem/research/proc/get_available_servers(turf/location)
|
||||
var/list/local_servers = list()
|
||||
if(!location)
|
||||
return local_servers
|
||||
for (var/datum/techweb/individual_techweb as anything in techwebs)
|
||||
var/list/servers = find_valid_servers(location, individual_techweb)
|
||||
if(length(servers))
|
||||
local_servers += servers
|
||||
return local_servers
|
||||
|
||||
/**
|
||||
* Goes through an individual techweb's servers and finds one on a valid z-level
|
||||
* Returns a list of existing ones, or an empty list otherwise.
|
||||
* Args:
|
||||
* - checking_web - The techweb we're checking the servers of.
|
||||
*/
|
||||
/datum/controller/subsystem/research/proc/find_valid_servers(turf/location, datum/techweb/checking_web)
|
||||
var/list/valid_servers = list()
|
||||
for(var/obj/machinery/rnd/server/server as anything in checking_web.techweb_servers)
|
||||
if(!is_valid_z_level(get_turf(server), location))
|
||||
continue
|
||||
valid_servers += server
|
||||
return valid_servers
|
||||
|
||||
/// Returns true if you can make an anomaly core of the provided type
|
||||
/datum/controller/subsystem/research/proc/is_core_available(core_type)
|
||||
if (!ispath(core_type, /obj/item/assembly/signaler/anomaly))
|
||||
return FALSE // The fuck are you checking this random object for?
|
||||
var/already_made = created_anomaly_types[core_type] || 0
|
||||
var/hard_limit = anomaly_hard_limit_by_type[core_type]
|
||||
return already_made < hard_limit
|
||||
|
||||
/// Increase our tracked number of cores of this type
|
||||
/datum/controller/subsystem/research/proc/increment_existing_anomaly_cores(core_type)
|
||||
var/existing = created_anomaly_types[core_type] || 0
|
||||
created_anomaly_types[core_type] = existing + 1
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* This subsystem is to handle creating and storing
|
||||
* composite templates that are used to create composite datatypes
|
||||
* for integrated circuits
|
||||
*
|
||||
* See: https://en.wikipedia.org/wiki/Composite_data_type
|
||||
**/
|
||||
SUBSYSTEM_DEF(wiremod_composite)
|
||||
name = "Wiremod Composite Templates"
|
||||
flags = SS_NO_FIRE
|
||||
/// The templates created and stored
|
||||
var/list/templates = list()
|
||||
|
||||
/datum/controller/subsystem/wiremod_composite/PreInit()
|
||||
. = ..()
|
||||
// This needs to execute before global variables have initialized.
|
||||
for(var/datum/circuit_composite_template/type as anything in subtypesof(/datum/circuit_composite_template))
|
||||
if(!initial(type.datatype))
|
||||
continue
|
||||
templates[initial(type.datatype)] = new type()
|
||||
|
||||
/datum/controller/subsystem/wiremod_composite/Initialize()
|
||||
for(var/type in templates)
|
||||
var/datum/circuit_composite_template/template = templates[type]
|
||||
template.Initialize()
|
||||
return SS_INIT_SUCCESS
|
||||
|
||||
/**
|
||||
* Used to produce a composite datatype using another datatype, or
|
||||
* to get an already existing composite datatype.
|
||||
*/
|
||||
/datum/controller/subsystem/wiremod_composite/proc/composite_datatype(datatype, ...)
|
||||
var/datum/circuit_composite_template/type = templates[datatype]
|
||||
if(!type)
|
||||
return
|
||||
return type.generate_composite_type(args.Copy(2))
|
||||
|
||||
/datum/controller/subsystem/wiremod_composite/proc/get_composite_type(base_type, datatype)
|
||||
var/datum/circuit_composite_template/template = templates[base_type]
|
||||
if(!template)
|
||||
return
|
||||
return template.generated_types[datatype]
|
||||
Reference in New Issue
Block a user