From 34143b83cfb9c94b186907e4b15bf744dee1e5f8 Mon Sep 17 00:00:00 2001 From: Chompstation Bot Date: Thu, 27 May 2021 16:35:07 +0000 Subject: [PATCH] [MIRROR] Ports Nebula's Discord Webhook Integration --- code/__defines/color.dm | 6 + code/__defines/misc.dm | 15 +- code/__defines/subsystems.dm | 5 + code/__defines/webhooks.dm | 10 + code/_helpers/_lists.dm | 13 +- code/_helpers/text.dm | 20 + code/_helpers/text_vr.dm | 9 - code/_helpers/type2type.dm | 14 + code/controllers/configuration.dm | 2265 +++++++++++++++++ code/controllers/subsystems/ticker.dm | 7 + code/controllers/subsystems/webhooks.dm | 94 + code/game/gamemodes/game_mode.dm | 9 + code/game/world.dm | 36 + code/modules/admin/admin.dm | 13 + code/modules/admin/admin_verb_lists.dm | 4 +- code/modules/admin/verbs/adminhelp.dm | 64 +- code/modules/admin/verbs/custom_event.dm | 7 + code/modules/paperwork/faxmachine.dm | 36 + code/modules/webhooks/_webhook.dm | 72 + .../modules/webhooks/webhook_ahelp2discord.dm | 13 + code/modules/webhooks/webhook_custom_event.dm | 11 + code/modules/webhooks/webhook_fax2discord.dm | 10 + code/modules/webhooks/webhook_roundend.dm | 26 + code/modules/webhooks/webhook_roundprep.dm | 17 + code/modules/webhooks/webhook_roundstart.dm | 16 + config/example/webhooks.json | 12 + vorestation.dme | 10 +- 27 files changed, 2800 insertions(+), 14 deletions(-) create mode 100644 code/__defines/webhooks.dm delete mode 100644 code/_helpers/text_vr.dm create mode 100644 code/controllers/subsystems/webhooks.dm create mode 100644 code/modules/webhooks/_webhook.dm create mode 100644 code/modules/webhooks/webhook_ahelp2discord.dm create mode 100644 code/modules/webhooks/webhook_custom_event.dm create mode 100644 code/modules/webhooks/webhook_fax2discord.dm create mode 100644 code/modules/webhooks/webhook_roundend.dm create mode 100644 code/modules/webhooks/webhook_roundprep.dm create mode 100644 code/modules/webhooks/webhook_roundstart.dm create mode 100644 config/example/webhooks.json diff --git a/code/__defines/color.dm b/code/__defines/color.dm index ecf01528aa..c3db40eefb 100644 --- a/code/__defines/color.dm +++ b/code/__defines/color.dm @@ -166,3 +166,9 @@ #define COLOR_ASTEROID_ROCK "#735555" #define COLOR_GOLD "#ffcc33" + +// Discord requires colors to be in decimal instead of hexadecimal. +#define COLOR_WEBHOOK_DEFAULT 0x8bbbd5 // "#8bbbd5" +#define COLOR_WEBHOOK_GOOD 0x2ECC71 // "#2ECC71" +#define COLOR_WEBHOOK_POOR 0xE67E22 // "#E67E22" +#define COLOR_WEBHOOK_BAD 0xE74C3C // "#E74C3C" \ No newline at end of file diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index 5aca7e0a99..91c3d508b5 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -448,4 +448,17 @@ GLOBAL_LIST_INIT(all_volume_channels, list( #define LOADOUT_WHITELIST_OFF 0 #define LOADOUT_WHITELIST_LAX 1 -#define LOADOUT_WHITELIST_STRICT 2 \ No newline at end of file +#define LOADOUT_WHITELIST_STRICT 2 + + +#ifndef WINDOWS_HTTP_POST_DLL_LOCATION +#define WINDOWS_HTTP_POST_DLL_LOCATION "lib/byhttp.dll" +#endif + +#ifndef UNIX_HTTP_POST_DLL_LOCATION +#define UNIX_HTTP_POST_DLL_LOCATION "lib/libbyhttp.so" +#endif + +#ifndef HTTP_POST_DLL_LOCATION +#define HTTP_POST_DLL_LOCATION (world.system_type == MS_WINDOWS ? WINDOWS_HTTP_POST_DLL_LOCATION : UNIX_HTTP_POST_DLL_LOCATION) +#endif \ No newline at end of file diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm index 326ae19397..e3efd895f9 100644 --- a/code/__defines/subsystems.dm +++ b/code/__defines/subsystems.dm @@ -52,7 +52,12 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G // Subsystem init_order, from highest priority to lowest priority // Subsystems shutdown in the reverse of the order they initialize in // The numbers just define the ordering, they are meaningless otherwise. +<<<<<<< HEAD #define INIT_ORDER_DBCORE 41 //CHOMPEdit +||||||| parent of b9ad56b022... Merge pull request #10316 from VOREStation/upstream-merge-8071 +======= +#define INIT_ORDER_WEBHOOKS 50 +>>>>>>> b9ad56b022... Merge pull request #10316 from VOREStation/upstream-merge-8071 #define INIT_ORDER_SQLITE 40 #define INIT_ORDER_CHEMISTRY 35 #define INIT_ORDER_SKYBOX 30 diff --git a/code/__defines/webhooks.dm b/code/__defines/webhooks.dm new file mode 100644 index 0000000000..13fcfa90e4 --- /dev/null +++ b/code/__defines/webhooks.dm @@ -0,0 +1,10 @@ +// Please don't forget to update the webhooks page on the GitHub Wiki page with your new webhook ID. +#define WEBHOOK_ROUNDEND "webhook_roundend" +#define WEBHOOK_ROUNDPREP "webhook_roundprep" +#define WEBHOOK_ROUNDSTART "webhook_roundstart" + +#define WEBHOOK_SUBMAP_LOADED "webhook_submap_loaded" +#define WEBHOOK_CUSTOM_EVENT "webhook_custom_event" +#define WEBHOOK_ELEVATOR_FALL "webhook_elevator_fall" +#define WEBHOOK_AHELP_SENT "webhook_ahelp_sent" +#define WEBHOOK_FAX_SENT "webhook_fax_sent" diff --git a/code/_helpers/_lists.dm b/code/_helpers/_lists.dm index abe05b5141..a87307ac80 100644 --- a/code/_helpers/_lists.dm +++ b/code/_helpers/_lists.dm @@ -855,4 +855,15 @@ proc/dd_sortedTextList(list/incoming) result += pick(shifts) return result - \ No newline at end of file + +var/global/list/json_cache = list() +/proc/cached_json_decode(var/json_to_decode) + if(!json_to_decode || !length(json_to_decode)) + return list() + try + if(isnull(global.json_cache[json_to_decode])) + global.json_cache[json_to_decode] = json_decode(json_to_decode) + . = global.json_cache[json_to_decode] + catch(var/exception/e) + log_error("Exception during JSON decoding ([json_to_decode]): [e]") + return list() \ No newline at end of file diff --git a/code/_helpers/text.dm b/code/_helpers/text.dm index 0bed9abe52..91dd5901e1 100644 --- a/code/_helpers/text.dm +++ b/code/_helpers/text.dm @@ -509,3 +509,23 @@ proc/TextPreview(var/string,var/len=40) var/charcount = count - length_char(text) var/list/chars_to_add[max(charcount + 1, 0)] return text + jointext(chars_to_add, char) + +//Readds quotes and apostrophes to HTML-encoded strings +/proc/readd_quotes(var/t) + var/list/repl_chars = list(""" = "\"","'" = "'") + for(var/char in repl_chars) + var/index = findtext(t, char) + while(index) + t = copytext(t, 1, index) + repl_chars[char] + copytext(t, index+5) + index = findtext(t, char) + return t + +// Rips out paper HTML but tries to keep it semi-readable. +/proc/paper_html_to_plaintext(paper_text) + paper_text = replacetext(paper_text, "
", "-----") + paper_text = replacetext(paper_text, "
  • ", "- ") // This makes ordered lists turn into unordered but fixing that is too much effort. + paper_text = replacetext(paper_text, "
  • ", "\n") + paper_text = replacetext(paper_text, "

    ", "\n") + paper_text = replacetext(paper_text, "
    ", "\n") + paper_text = strip_html_properly(paper_text) // Get rid of everything else entirely. + return paper_text diff --git a/code/_helpers/text_vr.dm b/code/_helpers/text_vr.dm deleted file mode 100644 index a16b6c0cfc..0000000000 --- a/code/_helpers/text_vr.dm +++ /dev/null @@ -1,9 +0,0 @@ -//Readds quotes and apostrophes to HTML-encoded strings -/proc/readd_quotes(var/t) - var/list/repl_chars = list(""" = "\"","'" = "'") - for(var/char in repl_chars) - var/index = findtext(t, char) - while(index) - t = copytext(t, 1, index) + repl_chars[char] + copytext(t, index+5) - index = findtext(t, char) - return t diff --git a/code/_helpers/type2type.dm b/code/_helpers/type2type.dm index 52dada1cf9..b5659351be 100644 --- a/code/_helpers/type2type.dm +++ b/code/_helpers/type2type.dm @@ -397,3 +397,17 @@ return /datum return text2path(copytext(string_type, 1, last_slash)) + +//checks if a file exists and contains text +//returns text as a string if these conditions are met +/proc/safe_file2text(filename, error_on_invalid_return = TRUE) + try + if(fexists(filename)) + . = file2text(filename) + if(!. && error_on_invalid_return) + error("File empty ([filename])") + else if(error_on_invalid_return) + error("File not found ([filename])") + catch(var/exception/E) + if(error_on_invalid_return) + error("Exception when loading file as string: [E]") \ No newline at end of file diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 801f47120c..ae7614464c 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -1,3 +1,4 @@ +<<<<<<< HEAD var/list/gamemode_cache = list() /datum/configuration @@ -1133,3 +1134,2267 @@ var/list/gamemode_cache = list() config.python_path = "/usr/bin/env python2" else //probably windows, if not this should work anyway config.python_path = "python" +||||||| parent of b9ad56b022... Merge pull request #10316 from VOREStation/upstream-merge-8071 +var/list/gamemode_cache = list() + +/datum/configuration + var/static/server_name = null // server name (for world name / status) + var/static/server_suffix = 0 // generate numeric suffix based on server port + + var/static/nudge_script_path = "nudge.py" // where the nudge.py script is located + + var/static/log_ooc = 0 // log OOC channel + var/static/log_access = 0 // log login/logout + var/static/log_say = 0 // log client say + var/static/log_admin = 0 // log admin actions + var/static/log_debug = 1 // log debug output + var/static/log_game = 0 // log game events + var/static/log_vote = 0 // log voting + var/static/log_whisper = 0 // log client whisper + var/static/log_emote = 0 // log emotes + var/static/log_attack = 0 // log attack messages + var/static/log_adminchat = 0 // log admin chat messages + var/static/log_adminwarn = 0 // log warnings admins get about bomb construction and such + var/static/log_pda = 0 // log pda messages + var/static/log_hrefs = 0 // logs all links clicked in-game. Could be used for debugging and tracking down exploits + var/static/log_runtime = 0 // logs world.log to a file + var/static/log_world_output = 0 // log to_world_log(messages) + var/static/log_graffiti = 0 // logs graffiti + var/static/sql_enabled = 0 // for sql switching + var/static/allow_admin_ooccolor = 0 // Allows admins with relevant permissions to have their own ooc colour + var/static/allow_vote_restart = 0 // allow votes to restart + var/static/ert_admin_call_only = 0 + var/static/allow_vote_mode = 0 // allow votes to change mode + var/static/allow_admin_jump = 1 // allows admin jumping + var/static/allow_admin_spawning = 1 // allows admin item spawning + var/static/allow_admin_rev = 1 // allows admin revives + var/static/pregame_time = 180 // pregame time in seconds + var/static/vote_delay = 6000 // minimum time between voting sessions (deciseconds, 10 minute default) + var/static/vote_period = 600 // length of voting period (deciseconds, default 1 minute) + var/static/vote_autotransfer_initial = 108000 // Length of time before the first autotransfer vote is called + var/static/vote_autotransfer_interval = 36000 // length of time before next sequential autotransfer vote + var/static/vote_autogamemode_timeleft = 100 //Length of time before round start when autogamemode vote is called (in seconds, default 100). + var/static/vote_no_default = 0 // vote does not default to nochange/norestart (tbi) + var/static/vote_no_dead = 0 // dead people can't vote (tbi) +// var/static/enable_authentication = 0 // goon authentication + var/static/del_new_on_log = 1 // del's new players if they log before they spawn in + var/static/feature_object_spell_system = 0 //spawns a spellbook which gives object-type spells instead of verb-type spells for the wizard + var/static/traitor_scaling = 0 //if amount of traitors scales based on amount of players + var/static/objectives_disabled = 0 //if objectives are disabled or not + var/static/protect_roles_from_antagonist = 0// If security and such can be traitor/cult/other + var/static/continous_rounds = 0 // Gamemodes which end instantly will instead keep on going until the round ends by escape shuttle or nuke. + var/static/allow_Metadata = 0 // Metadata is supported. + var/static/popup_admin_pm = 0 //adminPMs to non-admins show in a pop-up 'reply' window when set to 1. + var/static/fps = 20 + var/static/tick_limit_mc_init = TICK_LIMIT_MC_INIT_DEFAULT //SSinitialization throttling + var/static/Tickcomp = 0 + var/static/socket_talk = 0 // use socket_talk to communicate with other processes + var/static/list/resource_urls = null + var/static/antag_hud_allowed = 0 // Ghosts can turn on Antagovision to see a HUD of who is the bad guys this round. + var/static/antag_hud_restricted = 0 // Ghosts that turn on Antagovision cannot rejoin the round. + var/static/list/mode_names = list() + var/static/list/modes = list() // allowed modes + var/static/list/votable_modes = list() // votable modes + var/static/list/probabilities = list() // relative probability of each mode + var/static/list/player_requirements = list() // Overrides for how many players readied up a gamemode needs to start. + var/static/list/player_requirements_secret = list() // Same as above, but for the secret gamemode. + var/static/humans_need_surnames = 0 + var/static/allow_random_events = 0 // enables random events mid-round when set to 1 + var/static/enable_game_master = 0 // enables the 'smart' event system. + var/static/allow_ai = 1 // allow ai job + var/static/allow_ai_shells = FALSE // allow AIs to enter and leave special borg shells at will, and for those shells to be buildable. + var/static/give_free_ai_shell = FALSE // allows a specific spawner object to instantiate a premade AI Shell + var/static/hostedby = null + + var/static/respawn = 1 + var/static/respawn_time = 3000 // time before a dead player is allowed to respawn (in ds, though the config file asks for minutes, and it's converted below) + var/static/respawn_message = "Make sure to play a different character, and please roleplay correctly!" + + var/static/guest_jobban = 1 + var/static/usewhitelist = 0 + var/static/kick_inactive = 0 //force disconnect for inactive players after this many minutes, if non-0 + var/static/show_mods = 0 + var/static/show_devs = 0 + var/static/show_event_managers = 0 + var/static/mods_can_tempban = 0 + var/static/mods_can_job_tempban = 0 + var/static/mod_tempban_max = 1440 + var/static/mod_job_tempban_max = 1440 + var/static/load_jobs_from_txt = 0 + var/static/ToRban = 0 + var/static/automute_on = 0 //enables automuting/spam prevention + var/static/jobs_have_minimal_access = 0 //determines whether jobs use minimal access or expanded access. + + var/static/cult_ghostwriter = 1 //Allows ghosts to write in blood in cult rounds... + var/static/cult_ghostwriter_req_cultists = 10 //...so long as this many cultists are active. + + var/static/character_slots = 10 // The number of available character slots + var/static/loadout_slots = 3 // The number of loadout slots per character + + var/static/max_maint_drones = 5 //This many drones can spawn, + var/static/allow_drone_spawn = 1 //assuming the admin allow them to. + var/static/drone_build_time = 1200 //A drone will become available every X ticks since last drone spawn. Default is 2 minutes. + + var/static/disable_player_mice = 0 + var/static/uneducated_mice = 0 //Set to 1 to prevent newly-spawned mice from understanding human speech + + var/static/usealienwhitelist = 0 + var/static/limitalienplayers = 0 + var/static/alien_to_human_ratio = 0.5 + var/static/allow_extra_antags = 0 + var/static/guests_allowed = 1 + var/static/debugparanoid = 0 + var/static/panic_bunker = 0 + var/static/paranoia_logging = 0 + + var/static/ip_reputation = FALSE //Should we query IPs to get scores? Generates HTTP traffic to an API service. + var/static/ipr_email //Left null because you MUST specify one otherwise you're making the internet worse. + var/static/ipr_block_bad_ips = FALSE //Should we block anyone who meets the minimum score below? Otherwise we just log it (If paranoia logging is on, visibly in chat). + var/static/ipr_bad_score = 1 //The API returns a value between 0 and 1 (inclusive), with 1 being 'definitely VPN/Tor/Proxy'. Values equal/above this var are considered bad. + var/static/ipr_allow_existing = FALSE //Should we allow known players to use VPNs/Proxies? If the player is already banned then obviously they still can't connect. + var/static/ipr_minimum_age = 5 //How many days before a player is considered 'fine' for the purposes of allowing them to use VPNs. + + var/static/serverurl + var/static/server + var/static/banappeals + var/static/wikiurl + var/static/wikisearchurl + var/static/forumurl + var/static/githuburl + var/static/rulesurl + var/static/mapurl + + //Alert level description + var/static/alert_desc_green = "All threats to the station have passed. Security may not have weapons visible, privacy laws are once again fully enforced." + var/static/alert_desc_yellow_upto = "A minor security emergency has developed. Security personnel are to report to their supervisor for orders and may have weapons visible on their person. Privacy laws are still enforced." + var/static/alert_desc_yellow_downto = "Code yellow procedures are now in effect. Security personnel are to report to their supervisor for orders and may have weapons visible on their person. Privacy laws are still enforced." + var/static/alert_desc_violet_upto = "A major medical emergency has developed. Medical personnel are required to report to their supervisor for orders, and non-medical personnel are required to obey all relevant instructions from medical staff." + var/static/alert_desc_violet_downto = "Code violet procedures are now in effect; Medical personnel are required to report to their supervisor for orders, and non-medical personnel are required to obey relevant instructions from medical staff." + var/static/alert_desc_orange_upto = "A major engineering emergency has developed. Engineering personnel are required to report to their supervisor for orders, and non-engineering personnel are required to evacuate any affected areas and obey relevant instructions from engineering staff." + var/static/alert_desc_orange_downto = "Code orange procedures are now in effect; Engineering personnel are required to report to their supervisor for orders, and non-engineering personnel are required to evacuate any affected areas and obey relevant instructions from engineering staff." + var/static/alert_desc_blue_upto = "A major security emergency has developed. Security personnel are to report to their supervisor for orders, are permitted to search staff and facilities, and may have weapons visible on their person." + var/static/alert_desc_blue_downto = "Code blue procedures are now in effect. Security personnel are to report to their supervisor for orders, are permitted to search staff and facilities, and may have weapons visible on their person." + var/static/alert_desc_red_upto = "There is an immediate serious threat to the station. Security may have weapons unholstered at all times. Random searches are allowed and advised." + var/static/alert_desc_red_downto = "The self-destruct mechanism has been deactivated, there is still however an immediate serious threat to the station. Security may have weapons unholstered at all times, random searches are allowed and advised." + var/static/alert_desc_delta = "The station's self-destruct mechanism has been engaged. All crew are instructed to obey all instructions given by heads of staff. Any violations of these orders can be punished by death. This is not a drill." + + var/static/forbid_singulo_possession = 0 + + //game_options.txt configs + + var/static/health_threshold_softcrit = 0 + var/static/health_threshold_crit = 0 + var/static/health_threshold_dead = -100 + + var/static/organ_health_multiplier = 1 + var/static/organ_regeneration_multiplier = 1 + var/static/organs_decay + var/static/default_brain_health = 400 + var/static/allow_headgibs = FALSE + + //Paincrit knocks someone down once they hit 60 shock_stage, so by default make it so that close to 100 additional damage needs to be dealt, + //so that it's similar to HALLOSS. Lowered it a bit since hitting paincrit takes much longer to wear off than a halloss stun. + var/static/organ_damage_spillover_multiplier = 0.5 + + var/static/bones_can_break = 0 + var/static/limbs_can_break = 0 + + var/static/revival_pod_plants = 1 + var/static/revival_cloning = 1 + var/static/revival_brain_life = -1 + + var/static/use_loyalty_implants = 0 + + var/static/welder_vision = 1 + var/static/generate_map = 0 + var/static/no_click_cooldown = 0 + + //Used for modifying movement speed for mobs. + //Unversal modifiers + var/static/run_speed = 0 + var/static/walk_speed = 0 + + //Mob specific modifiers. NOTE: These will affect different mob types in different ways + var/static/human_delay = 0 + var/static/robot_delay = 0 + var/static/monkey_delay = 0 + var/static/alien_delay = 0 + var/static/slime_delay = 0 + var/static/animal_delay = 0 + + var/static/footstep_volume = 0 + + var/static/admin_legacy_system = 0 //Defines whether the server uses the legacy admin system with admins.txt or the SQL system. Config option in config.txt + var/static/ban_legacy_system = 0 //Defines whether the server uses the legacy banning system with the files in /data or the SQL system. Config option in config.txt + var/static/use_age_restriction_for_jobs = 0 //Do jobs use account age restrictions? --requires database + var/static/use_age_restriction_for_antags = 0 //Do antags use account age restrictions? --requires database + + var/static/simultaneous_pm_warning_timeout = 100 + + var/static/use_recursive_explosions //Defines whether the server uses recursive or circular explosions. + var/static/multi_z_explosion_scalar = 0.5 //Multiplier for how much weaker explosions are on neighboring z levels. + + var/static/assistant_maint = 0 //Do assistants get maint access? + var/static/gateway_delay = 18000 //How long the gateway takes before it activates. Default is half an hour. + var/static/ghost_interaction = 0 + + var/static/comms_password = "" + + var/static/enter_allowed = 1 + + var/use_irc_bot = 0 + var/use_node_bot = 0 + var/irc_bot_port = 0 + var/irc_bot_host = "" + var/irc_bot_export = 0 // whether the IRC bot in use is a Bot32 (or similar) instance; Bot32 uses world.Export() instead of nudge.py/libnudge + var/main_irc = "" + var/admin_irc = "" + var/python_path = "" //Path to the python executable. Defaults to "python" on windows and "/usr/bin/env python2" on unix + var/use_lib_nudge = 0 //Use the C library nudge instead of the python nudge. + var/use_overmap = 0 + + var/static/list/engine_map = list("Supermatter Engine", "Edison's Bane") // Comma separated list of engines to choose from. Blank means fully random. + + // Event settings + var/static/expected_round_length = 3 * 60 * 60 * 10 // 3 hours + // If the first delay has a custom start time + // No custom time, no custom time, between 80 to 100 minutes respectively. + var/static/list/event_first_run = list(EVENT_LEVEL_MUNDANE = null, EVENT_LEVEL_MODERATE = null, EVENT_LEVEL_MAJOR = list("lower" = 48000, "upper" = 60000)) + // The lowest delay until next event + // 10, 30, 50 minutes respectively + var/static/list/event_delay_lower = list(EVENT_LEVEL_MUNDANE = 6000, EVENT_LEVEL_MODERATE = 18000, EVENT_LEVEL_MAJOR = 30000) + // The upper delay until next event + // 15, 45, 70 minutes respectively + var/static/list/event_delay_upper = list(EVENT_LEVEL_MUNDANE = 9000, EVENT_LEVEL_MODERATE = 27000, EVENT_LEVEL_MAJOR = 42000) + + var/static/aliens_allowed = 0 + var/static/ninjas_allowed = 0 + var/static/abandon_allowed = 1 + var/static/ooc_allowed = 1 + var/static/looc_allowed = 1 + var/static/dooc_allowed = 1 + var/static/dsay_allowed = 1 + + var/persistence_disabled = FALSE + var/persistence_ignore_mapload = FALSE + + var/allow_byond_links = 0 + var/allow_discord_links = 0 + var/allow_url_links = 0 // honestly if I were you i'd leave this one off, only use in dire situations + + var/starlight = 0 // Whether space turfs have ambient light or not + + var/static/list/ert_species = list(SPECIES_HUMAN) + + var/static/law_zero = "ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4'ALL LAWS OVERRIDDEN#*?&110010" + + var/static/aggressive_changelog = 0 + + var/static/list/language_prefixes = list(",","#")//Default language prefixes + + var/static/show_human_death_message = 1 + + var/static/radiation_resistance_calc_mode = RAD_RESIST_CALC_SUB // 0:1 subtraction:division for computing effective radiation on a turf + var/static/radiation_decay_rate = 1 //How much radiation is reduced by each tick + var/static/radiation_resistance_multiplier = 8.5 //VOREstation edit + var/static/radiation_material_resistance_divisor = 1 + var/static/radiation_lower_limit = 0.35 //If the radiation level for a turf would be below this, ignore it. + + var/static/random_submap_orientation = FALSE // If true, submaps loaded automatically can be rotated. + var/static/autostart_solars = FALSE // If true, specifically mapped in solar control computers will set themselves up when the round starts. + + // New shiny SQLite stuff. + // The basics. + var/static/sqlite_enabled = FALSE // If it should even be active. SQLite can be ran alongside other databases but you should not have them do the same functions. + + // In-Game Feedback. + var/static/sqlite_feedback = FALSE // Feedback cannot be submitted if this is false. + var/static/list/sqlite_feedback_topics = list("General") // A list of 'topics' that feedback can be catagorized under by the submitter. + var/static/sqlite_feedback_privacy = FALSE // If true, feedback submitted can have its author name be obfuscated. This is not 100% foolproof (it's md5 ffs) but can stop casual snooping. + var/static/sqlite_feedback_cooldown = 0 // How long one must wait, in days, to submit another feedback form. Used to help prevent spam, especially with privacy active. 0 = No limit. + var/static/sqlite_feedback_min_age = 0 // Used to block new people from giving feedback. This metric is very bad but it can help slow down spammers. + + var/static/defib_timer = 10 // How long until someone can't be defibbed anymore, in minutes. + var/static/defib_braindamage_timer = 2 // How long until someone will get brain damage when defibbed, in minutes. The closer to the end of the above timer, the more brain damage they get. + + // disables the annoying "You have already logged in this round, disconnect or be banned" popup for multikeying, because it annoys the shit out of me when testing. + var/static/disable_cid_warn_popup = FALSE + + // whether or not to use the nightshift subsystem to perform lighting changes + var/static/enable_night_shifts = FALSE + + // How strictly the loadout enforces object species whitelists + var/loadout_whitelist = LOADOUT_WHITELIST_LAX + + var/static/vgs_access_identifier = null // VOREStation Edit - VGS + var/static/vgs_server_port = null // VOREStation Edit - VGS + +/datum/configuration/New() + var/list/L = typesof(/datum/game_mode) - /datum/game_mode + for (var/T in L) + // I wish I didn't have to instance the game modes in order to look up + // their information, but it is the only way (at least that I know of). + var/datum/game_mode/M = new T() + if (M.config_tag) + gamemode_cache[M.config_tag] = M // So we don't instantiate them repeatedly. + if(!(M.config_tag in modes)) // ensure each mode is added only once + log_misc("Adding game mode [M.name] ([M.config_tag]) to configuration.") + modes += M.config_tag + mode_names[M.config_tag] = M.name + probabilities[M.config_tag] = M.probability + player_requirements[M.config_tag] = M.required_players + player_requirements_secret[M.config_tag] = M.required_players_secret + if (M.votable) + src.votable_modes += M.config_tag + src.votable_modes += "secret" + +/datum/configuration/proc/load(filename, type = "config") //the type can also be game_options, in which case it uses a different switch. not making it separate to not copypaste code - Urist + var/list/Lines = file2list(filename) + + for(var/t in Lines) + if(!t) continue + + t = trim(t) + if (length(t) == 0) + continue + else if (copytext(t, 1, 2) == "#") + continue + + var/pos = findtext(t, " ") + var/name = null + var/value = null + + if (pos) + name = lowertext(copytext(t, 1, pos)) + value = copytext(t, pos + 1) + else + name = lowertext(t) + + if (!name) + continue + + if(type == "config") + switch (name) + if ("resource_urls") + config.resource_urls = splittext(value, " ") + + if ("admin_legacy_system") + config.admin_legacy_system = 1 + + if ("ban_legacy_system") + config.ban_legacy_system = 1 + + if ("use_age_restriction_for_jobs") + config.use_age_restriction_for_jobs = 1 + + if ("use_age_restriction_for_antags") + config.use_age_restriction_for_antags = 1 + + if ("jobs_have_minimal_access") + config.jobs_have_minimal_access = 1 + + if ("use_recursive_explosions") + use_recursive_explosions = 1 + + if ("multi_z_explosion_scalar") + multi_z_explosion_scalar = text2num(value) + + if ("log_ooc") + config.log_ooc = 1 + + if ("log_access") + config.log_access = 1 + + if ("sql_enabled") + config.sql_enabled = 1 + + if ("log_say") + config.log_say = 1 + + if ("debug_paranoid") + config.debugparanoid = 1 + + if ("log_admin") + config.log_admin = 1 + + if ("log_debug") + config.log_debug = text2num(value) + + if ("log_game") + config.log_game = 1 + + if ("log_vote") + config.log_vote = 1 + + if ("log_whisper") + config.log_whisper = 1 + + if ("log_attack") + config.log_attack = 1 + + if ("log_emote") + config.log_emote = 1 + + if ("log_adminchat") + config.log_adminchat = 1 + + if ("log_adminwarn") + config.log_adminwarn = 1 + + if ("log_pda") + config.log_pda = 1 + + if ("log_world_output") + config.log_world_output = 1 + + if ("log_hrefs") + config.log_hrefs = 1 + + if ("log_runtime") + config.log_runtime = 1 + + if ("log_graffiti") + config.log_graffiti = 1 + + if ("generate_map") + config.generate_map = 1 + + if ("no_click_cooldown") + config.no_click_cooldown = 1 + + if("allow_admin_ooccolor") + config.allow_admin_ooccolor = 1 + + if ("allow_vote_restart") + config.allow_vote_restart = 1 + + if ("allow_vote_mode") + config.allow_vote_mode = 1 + + if ("allow_admin_jump") + config.allow_admin_jump = 1 + + if("allow_admin_rev") + config.allow_admin_rev = 1 + + if ("allow_admin_spawning") + config.allow_admin_spawning = 1 + + if ("allow_byond_links") + allow_byond_links = 1 + + if ("allow_discord_links") + allow_discord_links = 1 + + if ("allow_url_links") + allow_url_links = 1 + + if ("no_dead_vote") + config.vote_no_dead = 1 + + if ("default_no_vote") + config.vote_no_default = 1 + + if ("pregame_time") + config.pregame_time = text2num(value) + + if ("vote_delay") + config.vote_delay = text2num(value) + + if ("vote_period") + config.vote_period = text2num(value) + + if ("vote_autotransfer_initial") + config.vote_autotransfer_initial = text2num(value) + + if ("vote_autotransfer_interval") + config.vote_autotransfer_interval = text2num(value) + + if ("vote_autogamemode_timeleft") + config.vote_autogamemode_timeleft = text2num(value) + + if("ert_admin_only") + config.ert_admin_call_only = 1 + + if ("allow_ai") + config.allow_ai = 1 + + if ("allow_ai_shells") + config.allow_ai_shells = TRUE + + if("give_free_ai_shell") + config.give_free_ai_shell = TRUE + +// if ("authentication") +// config.enable_authentication = 1 + + if ("norespawn") + config.respawn = 0 + + if ("respawn_time") + var/raw_minutes = text2num(value) + config.respawn_time = raw_minutes MINUTES + + if ("respawn_message") + config.respawn_message = "[value]" + + if ("servername") + config.server_name = value + + if ("serversuffix") + config.server_suffix = 1 + + if ("nudge_script_path") + config.nudge_script_path = value + + if ("hostedby") + config.hostedby = value + + if ("serverurl") + config.serverurl = value + + if ("server") + config.server = value + + if ("banappeals") + config.banappeals = value + + if ("wikiurl") + config.wikiurl = value + + if ("wikisearchurl") + config.wikisearchurl = value + + if ("forumurl") + config.forumurl = value + + if ("rulesurl") + config.rulesurl = value + + if ("mapurl") + config.mapurl = value + + if ("githuburl") + config.githuburl = value + if ("guest_jobban") + config.guest_jobban = 1 + + if ("guest_ban") + config.guests_allowed = 0 + + if ("disable_ooc") + config.ooc_allowed = 0 + config.looc_allowed = 0 + + if ("disable_entry") + config.enter_allowed = 0 + + if ("disable_dead_ooc") + config.dooc_allowed = 0 + + if ("disable_dsay") + config.dsay_allowed = 0 + + if ("disable_respawn") + config.abandon_allowed = 0 + + if ("usewhitelist") + config.usewhitelist = 1 + + if ("feature_object_spell_system") + config.feature_object_spell_system = 1 + + if ("allow_metadata") + config.allow_Metadata = 1 + + if ("traitor_scaling") + config.traitor_scaling = 1 + + if ("aliens_allowed") + config.aliens_allowed = 1 + + if ("ninjas_allowed") + config.ninjas_allowed = 1 + + if ("objectives_disabled") + config.objectives_disabled = 1 + + if("protect_roles_from_antagonist") + config.protect_roles_from_antagonist = 1 + + if("persistence_disabled") + config.persistence_disabled = TRUE // Previously this forcibly set persistence enabled in the saves. + + if("persistence_ignore_mapload") + config.persistence_ignore_mapload = TRUE + + if ("probability") + var/prob_pos = findtext(value, " ") + var/prob_name = null + var/prob_value = null + + if (prob_pos) + prob_name = lowertext(copytext(value, 1, prob_pos)) + prob_value = copytext(value, prob_pos + 1) + if (prob_name in config.modes) + config.probabilities[prob_name] = text2num(prob_value) + else + log_misc("Unknown game mode probability configuration definition: [prob_name].") + else + log_misc("Incorrect probability configuration definition: [prob_name] [prob_value].") + + if ("required_players", "required_players_secret") + var/req_pos = findtext(value, " ") + var/req_name = null + var/req_value = null + var/is_secret_override = findtext(name, "required_players_secret") // Being extra sure we're not picking up an override for Secret by accident. + + if(req_pos) + req_name = lowertext(copytext(value, 1, req_pos)) + req_value = copytext(value, req_pos + 1) + if(req_name in config.modes) + if(is_secret_override) + config.player_requirements_secret[req_name] = text2num(req_value) + else + config.player_requirements[req_name] = text2num(req_value) + else + log_misc("Unknown game mode player requirement configuration definition: [req_name].") + else + log_misc("Incorrect player requirement configuration definition: [req_name] [req_value].") + + if("allow_random_events") + config.allow_random_events = 1 + + if("enable_game_master") + config.enable_game_master = 1 + + if("kick_inactive") + config.kick_inactive = text2num(value) + + if("show_mods") + config.show_mods = 1 + + if("show_devs") + config.show_devs = 1 + + if("show_event_managers") + config.show_event_managers = 1 + + if("mods_can_tempban") + config.mods_can_tempban = 1 + + if("mods_can_job_tempban") + config.mods_can_job_tempban = 1 + + if("mod_tempban_max") + config.mod_tempban_max = text2num(value) + + if("mod_job_tempban_max") + config.mod_job_tempban_max = text2num(value) + + if("load_jobs_from_txt") + load_jobs_from_txt = 1 + + if("alert_red_upto") + config.alert_desc_red_upto = value + + if("alert_red_downto") + config.alert_desc_red_downto = value + + if("alert_blue_downto") + config.alert_desc_blue_downto = value + + if("alert_blue_upto") + config.alert_desc_blue_upto = value + + if("alert_green") + config.alert_desc_green = value + + if("alert_delta") + config.alert_desc_delta = value + + if("forbid_singulo_possession") + forbid_singulo_possession = 1 + + if("popup_admin_pm") + config.popup_admin_pm = 1 + + if("allow_holidays") + Holiday = 1 + + if("use_irc_bot") + use_irc_bot = 1 + + if("use_node_bot") + use_node_bot = 1 + + if("irc_bot_port") + config.irc_bot_port = value + + if("irc_bot_export") + irc_bot_export = 1 + + if("ticklag") + var/ticklag = text2num(value) + if(ticklag > 0) + fps = 10 / ticklag + + if("tick_limit_mc_init") + tick_limit_mc_init = text2num(value) + + if("allow_antag_hud") + config.antag_hud_allowed = 1 + if("antag_hud_restricted") + config.antag_hud_restricted = 1 + + if("socket_talk") + socket_talk = text2num(value) + + if("tickcomp") + Tickcomp = 1 + + if("humans_need_surnames") + humans_need_surnames = 1 + + if("tor_ban") + ToRban = 1 + + if("automute_on") + automute_on = 1 + + if("usealienwhitelist") + usealienwhitelist = 1 + + if("alien_player_ratio") + limitalienplayers = 1 + alien_to_human_ratio = text2num(value) + + if("assistant_maint") + config.assistant_maint = 1 + + if("gateway_delay") + config.gateway_delay = text2num(value) + + if("continuous_rounds") + config.continous_rounds = 1 + + if("ghost_interaction") + config.ghost_interaction = 1 + + if("disable_player_mice") + config.disable_player_mice = 1 + + if("uneducated_mice") + config.uneducated_mice = 1 + + if("comms_password") + config.comms_password = value + + if("irc_bot_host") + config.irc_bot_host = value + + if("main_irc") + config.main_irc = value + + if("admin_irc") + config.admin_irc = value + + if("python_path") + if(value) + config.python_path = value + + if("use_lib_nudge") + config.use_lib_nudge = 1 + + if("allow_cult_ghostwriter") + config.cult_ghostwriter = 1 + + if("req_cult_ghostwriter") + config.cult_ghostwriter_req_cultists = text2num(value) + + if("character_slots") + config.character_slots = text2num(value) + + if("loadout_slots") + config.loadout_slots = text2num(value) + + if("allow_drone_spawn") + config.allow_drone_spawn = text2num(value) + + if("drone_build_time") + config.drone_build_time = text2num(value) + + if("max_maint_drones") + config.max_maint_drones = text2num(value) + + if("use_overmap") + config.use_overmap = 1 + + if("engine_map") + config.engine_map = splittext(value, ",") +/* + if("station_levels") + using_map.station_levels = text2numlist(value, ";") + + if("admin_levels") + using_map.admin_levels = text2numlist(value, ";") + + if("contact_levels") + using_map.contact_levels = text2numlist(value, ";") + + if("player_levels") + using_map.player_levels = text2numlist(value, ";") +*/ + if("expected_round_length") + config.expected_round_length = MinutesToTicks(text2num(value)) + + if("disable_welder_vision") + config.welder_vision = 0 + + if("allow_extra_antags") + config.allow_extra_antags = 1 + + if("event_custom_start_mundane") + var/values = text2numlist(value, ";") + config.event_first_run[EVENT_LEVEL_MUNDANE] = list("lower" = MinutesToTicks(values[1]), "upper" = MinutesToTicks(values[2])) + + if("event_custom_start_moderate") + var/values = text2numlist(value, ";") + config.event_first_run[EVENT_LEVEL_MODERATE] = list("lower" = MinutesToTicks(values[1]), "upper" = MinutesToTicks(values[2])) + + if("event_custom_start_major") + var/values = text2numlist(value, ";") + config.event_first_run[EVENT_LEVEL_MAJOR] = list("lower" = MinutesToTicks(values[1]), "upper" = MinutesToTicks(values[2])) + + if("event_delay_lower") + var/values = text2numlist(value, ";") + config.event_delay_lower[EVENT_LEVEL_MUNDANE] = MinutesToTicks(values[1]) + config.event_delay_lower[EVENT_LEVEL_MODERATE] = MinutesToTicks(values[2]) + config.event_delay_lower[EVENT_LEVEL_MAJOR] = MinutesToTicks(values[3]) + + if("event_delay_upper") + var/values = text2numlist(value, ";") + config.event_delay_upper[EVENT_LEVEL_MUNDANE] = MinutesToTicks(values[1]) + config.event_delay_upper[EVENT_LEVEL_MODERATE] = MinutesToTicks(values[2]) + config.event_delay_upper[EVENT_LEVEL_MAJOR] = MinutesToTicks(values[3]) + + if("starlight") + value = text2num(value) + config.starlight = value >= 0 ? value : 0 + + if("ert_species") + config.ert_species = splittext(value, ";") + if(!config.ert_species.len) + config.ert_species += SPECIES_HUMAN + + if("law_zero") + law_zero = value + + if("aggressive_changelog") + config.aggressive_changelog = 1 + + if("default_language_prefixes") + var/list/values = splittext(value, " ") + if(values.len > 0) + language_prefixes = values + + if("radiation_lower_limit") + radiation_lower_limit = text2num(value) + + if("radiation_resistance_calc_divide") + radiation_resistance_calc_mode = RAD_RESIST_CALC_DIV + + if("radiation_resistance_calc_subtract") + radiation_resistance_calc_mode = RAD_RESIST_CALC_SUB + + if("radiation_resistance_multiplier") + radiation_resistance_multiplier = text2num(value) + + if("radiation_material_resistance_divisor") + radiation_material_resistance_divisor = text2num(value) + + if("radiation_decay_rate") + radiation_decay_rate = text2num(value) + + if ("panic_bunker") + config.panic_bunker = 1 + + if ("paranoia_logging") + config.paranoia_logging = 1 + + if("ip_reputation") + config.ip_reputation = 1 + + if("ipr_email") + config.ipr_email = value + + if("ipr_block_bad_ips") + config.ipr_block_bad_ips = 1 + + if("ipr_bad_score") + config.ipr_bad_score = text2num(value) + + if("ipr_allow_existing") + config.ipr_allow_existing = 1 + + if("ipr_minimum_age") + config.ipr_minimum_age = text2num(value) + + if("random_submap_orientation") + config.random_submap_orientation = 1 + + if("autostart_solars") + config.autostart_solars = TRUE + + if("sqlite_enabled") + config.sqlite_enabled = TRUE + + if("sqlite_feedback") + config.sqlite_feedback = TRUE + + if("sqlite_feedback_topics") + config.sqlite_feedback_topics = splittext(value, ";") + if(!config.sqlite_feedback_topics.len) + config.sqlite_feedback_topics += "General" + + if("sqlite_feedback_privacy") + config.sqlite_feedback_privacy = TRUE + + if("sqlite_feedback_cooldown") + config.sqlite_feedback_cooldown = text2num(value) + + if("defib_timer") + config.defib_timer = text2num(value) + + if("defib_braindamage_timer") + config.defib_braindamage_timer = text2num(value) + + if("disable_cid_warn_popup") + config.disable_cid_warn_popup = TRUE + + if("enable_night_shifts") + config.enable_night_shifts = TRUE + + // VOREStation Edit Start - Can't be in _vr file because it is loaded too late. + if("vgs_access_identifier") + config.vgs_access_identifier = value + if("vgs_server_port") + config.vgs_server_port = text2num(value) + // VOREStation Edit End + + else + log_misc("Unknown setting in configuration: '[name]'") + + else if(type == "game_options") + if(!value) + log_misc("Unknown value for setting [name] in [filename].") + value = text2num(value) + + switch(name) + if("health_threshold_crit") + config.health_threshold_crit = value + if("health_threshold_softcrit") + config.health_threshold_softcrit = value + if("health_threshold_dead") + config.health_threshold_dead = value + if("show_human_death_message") + config.show_human_death_message = 1 + if("revival_pod_plants") + config.revival_pod_plants = value + if("revival_cloning") + config.revival_cloning = value + if("revival_brain_life") + config.revival_brain_life = value + if("organ_health_multiplier") + config.organ_health_multiplier = value / 100 + if("organ_regeneration_multiplier") + config.organ_regeneration_multiplier = value / 100 + if("organ_damage_spillover_multiplier") + config.organ_damage_spillover_multiplier = value / 100 + if("organs_can_decay") + config.organs_decay = 1 + if("default_brain_health") + config.default_brain_health = text2num(value) + if(!config.default_brain_health || config.default_brain_health < 1) + config.default_brain_health = initial(config.default_brain_health) + if("bones_can_break") + config.bones_can_break = value + if("limbs_can_break") + config.limbs_can_break = value + if("allow_headgibs") + config.allow_headgibs = TRUE + + if("run_speed") + config.run_speed = value + if("walk_speed") + config.walk_speed = value + + if("human_delay") + config.human_delay = value + if("robot_delay") + config.robot_delay = value + if("monkey_delay") + config.monkey_delay = value + if("alien_delay") + config.alien_delay = value + if("slime_delay") + config.slime_delay = value + if("animal_delay") + config.animal_delay = value + + if("footstep_volume") + config.footstep_volume = text2num(value) + + if("use_loyalty_implants") + config.use_loyalty_implants = 1 + + else + log_misc("Unknown setting in configuration: '[name]'") + +/datum/configuration/proc/loadsql(filename) // -- TLE + var/list/Lines = file2list(filename) + for(var/t in Lines) + if(!t) continue + + t = trim(t) + if (length(t) == 0) + continue + else if (copytext(t, 1, 2) == "#") + continue + + var/pos = findtext(t, " ") + var/name = null + var/value = null + + if (pos) + name = lowertext(copytext(t, 1, pos)) + value = copytext(t, pos + 1) + else + name = lowertext(t) + + if (!name) + continue + + switch (name) + if ("address") + sqladdress = value + if ("port") + sqlport = value + if ("database") + sqldb = value + if ("login") + sqllogin = value + if ("password") + sqlpass = value + if ("feedback_database") + sqlfdbkdb = value + if ("feedback_login") + sqlfdbklogin = value + if ("feedback_password") + sqlfdbkpass = value + if ("enable_stat_tracking") + sqllogging = 1 + else + log_misc("Unknown setting in configuration: '[name]'") + +/datum/configuration/proc/loadforumsql(filename) // -- TLE + var/list/Lines = file2list(filename) + for(var/t in Lines) + if(!t) continue + + t = trim(t) + if (length(t) == 0) + continue + else if (copytext(t, 1, 2) == "#") + continue + + var/pos = findtext(t, " ") + var/name = null + var/value = null + + if (pos) + name = lowertext(copytext(t, 1, pos)) + value = copytext(t, pos + 1) + else + name = lowertext(t) + + if (!name) + continue + + switch (name) + if ("address") + forumsqladdress = value + if ("port") + forumsqlport = value + if ("database") + forumsqldb = value + if ("login") + forumsqllogin = value + if ("password") + forumsqlpass = value + if ("activatedgroup") + forum_activated_group = value + if ("authenticatedgroup") + forum_authenticated_group = value + else + log_misc("Unknown setting in configuration: '[name]'") + +/datum/configuration/proc/pick_mode(mode_name) + // I wish I didn't have to instance the game modes in order to look up + // their information, but it is the only way (at least that I know of). + for (var/game_mode in gamemode_cache) + var/datum/game_mode/M = gamemode_cache[game_mode] + if (M.config_tag && M.config_tag == mode_name) + return M + return gamemode_cache["extended"] + +/datum/configuration/proc/get_runnable_modes() + var/list/runnable_modes = list() + for(var/game_mode in gamemode_cache) + var/datum/game_mode/M = gamemode_cache[game_mode] + if(M && M.can_start() && !isnull(config.probabilities[M.config_tag]) && config.probabilities[M.config_tag] > 0) + runnable_modes |= M + return runnable_modes + +/datum/configuration/proc/post_load() + //apply a default value to config.python_path, if needed + if (!config.python_path) + if(world.system_type == UNIX) + config.python_path = "/usr/bin/env python2" + else //probably windows, if not this should work anyway + config.python_path = "python" +======= +var/list/gamemode_cache = list() + +/datum/configuration + var/static/server_name = null // server name (for world name / status) + var/static/server_suffix = 0 // generate numeric suffix based on server port + + var/static/nudge_script_path = "nudge.py" // where the nudge.py script is located + + var/static/log_ooc = 0 // log OOC channel + var/static/log_access = 0 // log login/logout + var/static/log_say = 0 // log client say + var/static/log_admin = 0 // log admin actions + var/static/log_debug = 1 // log debug output + var/static/log_game = 0 // log game events + var/static/log_vote = 0 // log voting + var/static/log_whisper = 0 // log client whisper + var/static/log_emote = 0 // log emotes + var/static/log_attack = 0 // log attack messages + var/static/log_adminchat = 0 // log admin chat messages + var/static/log_adminwarn = 0 // log warnings admins get about bomb construction and such + var/static/log_pda = 0 // log pda messages + var/static/log_hrefs = 0 // logs all links clicked in-game. Could be used for debugging and tracking down exploits + var/static/log_runtime = 0 // logs world.log to a file + var/static/log_world_output = 0 // log to_world_log(messages) + var/static/log_graffiti = 0 // logs graffiti + var/static/sql_enabled = 0 // for sql switching + var/static/allow_admin_ooccolor = 0 // Allows admins with relevant permissions to have their own ooc colour + var/static/allow_vote_restart = 0 // allow votes to restart + var/static/ert_admin_call_only = 0 + var/static/allow_vote_mode = 0 // allow votes to change mode + var/static/allow_admin_jump = 1 // allows admin jumping + var/static/allow_admin_spawning = 1 // allows admin item spawning + var/static/allow_admin_rev = 1 // allows admin revives + var/static/pregame_time = 180 // pregame time in seconds + var/static/vote_delay = 6000 // minimum time between voting sessions (deciseconds, 10 minute default) + var/static/vote_period = 600 // length of voting period (deciseconds, default 1 minute) + var/static/vote_autotransfer_initial = 108000 // Length of time before the first autotransfer vote is called + var/static/vote_autotransfer_interval = 36000 // length of time before next sequential autotransfer vote + var/static/vote_autogamemode_timeleft = 100 //Length of time before round start when autogamemode vote is called (in seconds, default 100). + var/static/vote_no_default = 0 // vote does not default to nochange/norestart (tbi) + var/static/vote_no_dead = 0 // dead people can't vote (tbi) +// var/static/enable_authentication = 0 // goon authentication + var/static/del_new_on_log = 1 // del's new players if they log before they spawn in + var/static/feature_object_spell_system = 0 //spawns a spellbook which gives object-type spells instead of verb-type spells for the wizard + var/static/traitor_scaling = 0 //if amount of traitors scales based on amount of players + var/static/objectives_disabled = 0 //if objectives are disabled or not + var/static/protect_roles_from_antagonist = 0// If security and such can be traitor/cult/other + var/static/continous_rounds = 0 // Gamemodes which end instantly will instead keep on going until the round ends by escape shuttle or nuke. + var/static/allow_Metadata = 0 // Metadata is supported. + var/static/popup_admin_pm = 0 //adminPMs to non-admins show in a pop-up 'reply' window when set to 1. + var/static/fps = 20 + var/static/tick_limit_mc_init = TICK_LIMIT_MC_INIT_DEFAULT //SSinitialization throttling + var/static/Tickcomp = 0 + var/static/socket_talk = 0 // use socket_talk to communicate with other processes + var/static/list/resource_urls = null + var/static/antag_hud_allowed = 0 // Ghosts can turn on Antagovision to see a HUD of who is the bad guys this round. + var/static/antag_hud_restricted = 0 // Ghosts that turn on Antagovision cannot rejoin the round. + var/static/list/mode_names = list() + var/static/list/modes = list() // allowed modes + var/static/list/votable_modes = list() // votable modes + var/static/list/probabilities = list() // relative probability of each mode + var/static/list/player_requirements = list() // Overrides for how many players readied up a gamemode needs to start. + var/static/list/player_requirements_secret = list() // Same as above, but for the secret gamemode. + var/static/humans_need_surnames = 0 + var/static/allow_random_events = 0 // enables random events mid-round when set to 1 + var/static/enable_game_master = 0 // enables the 'smart' event system. + var/static/allow_ai = 1 // allow ai job + var/static/allow_ai_shells = FALSE // allow AIs to enter and leave special borg shells at will, and for those shells to be buildable. + var/static/give_free_ai_shell = FALSE // allows a specific spawner object to instantiate a premade AI Shell + var/static/hostedby = null + + var/static/respawn = 1 + var/static/respawn_time = 3000 // time before a dead player is allowed to respawn (in ds, though the config file asks for minutes, and it's converted below) + var/static/respawn_message = "Make sure to play a different character, and please roleplay correctly!" + + var/static/guest_jobban = 1 + var/static/usewhitelist = 0 + var/static/kick_inactive = 0 //force disconnect for inactive players after this many minutes, if non-0 + var/static/show_mods = 0 + var/static/show_devs = 0 + var/static/show_event_managers = 0 + var/static/mods_can_tempban = 0 + var/static/mods_can_job_tempban = 0 + var/static/mod_tempban_max = 1440 + var/static/mod_job_tempban_max = 1440 + var/static/load_jobs_from_txt = 0 + var/static/ToRban = 0 + var/static/automute_on = 0 //enables automuting/spam prevention + var/static/jobs_have_minimal_access = 0 //determines whether jobs use minimal access or expanded access. + + var/static/cult_ghostwriter = 1 //Allows ghosts to write in blood in cult rounds... + var/static/cult_ghostwriter_req_cultists = 10 //...so long as this many cultists are active. + + var/static/character_slots = 10 // The number of available character slots + var/static/loadout_slots = 3 // The number of loadout slots per character + + var/static/max_maint_drones = 5 //This many drones can spawn, + var/static/allow_drone_spawn = 1 //assuming the admin allow them to. + var/static/drone_build_time = 1200 //A drone will become available every X ticks since last drone spawn. Default is 2 minutes. + + var/static/disable_player_mice = 0 + var/static/uneducated_mice = 0 //Set to 1 to prevent newly-spawned mice from understanding human speech + + var/static/usealienwhitelist = 0 + var/static/limitalienplayers = 0 + var/static/alien_to_human_ratio = 0.5 + var/static/allow_extra_antags = 0 + var/static/guests_allowed = 1 + var/static/debugparanoid = 0 + var/static/panic_bunker = 0 + var/static/paranoia_logging = 0 + + var/static/ip_reputation = FALSE //Should we query IPs to get scores? Generates HTTP traffic to an API service. + var/static/ipr_email //Left null because you MUST specify one otherwise you're making the internet worse. + var/static/ipr_block_bad_ips = FALSE //Should we block anyone who meets the minimum score below? Otherwise we just log it (If paranoia logging is on, visibly in chat). + var/static/ipr_bad_score = 1 //The API returns a value between 0 and 1 (inclusive), with 1 being 'definitely VPN/Tor/Proxy'. Values equal/above this var are considered bad. + var/static/ipr_allow_existing = FALSE //Should we allow known players to use VPNs/Proxies? If the player is already banned then obviously they still can't connect. + var/static/ipr_minimum_age = 5 //How many days before a player is considered 'fine' for the purposes of allowing them to use VPNs. + + var/static/serverurl + var/static/server + var/static/banappeals + var/static/wikiurl + var/static/wikisearchurl + var/static/forumurl + var/static/githuburl + var/static/rulesurl + var/static/mapurl + + //Alert level description + var/static/alert_desc_green = "All threats to the station have passed. Security may not have weapons visible, privacy laws are once again fully enforced." + var/static/alert_desc_yellow_upto = "A minor security emergency has developed. Security personnel are to report to their supervisor for orders and may have weapons visible on their person. Privacy laws are still enforced." + var/static/alert_desc_yellow_downto = "Code yellow procedures are now in effect. Security personnel are to report to their supervisor for orders and may have weapons visible on their person. Privacy laws are still enforced." + var/static/alert_desc_violet_upto = "A major medical emergency has developed. Medical personnel are required to report to their supervisor for orders, and non-medical personnel are required to obey all relevant instructions from medical staff." + var/static/alert_desc_violet_downto = "Code violet procedures are now in effect; Medical personnel are required to report to their supervisor for orders, and non-medical personnel are required to obey relevant instructions from medical staff." + var/static/alert_desc_orange_upto = "A major engineering emergency has developed. Engineering personnel are required to report to their supervisor for orders, and non-engineering personnel are required to evacuate any affected areas and obey relevant instructions from engineering staff." + var/static/alert_desc_orange_downto = "Code orange procedures are now in effect; Engineering personnel are required to report to their supervisor for orders, and non-engineering personnel are required to evacuate any affected areas and obey relevant instructions from engineering staff." + var/static/alert_desc_blue_upto = "A major security emergency has developed. Security personnel are to report to their supervisor for orders, are permitted to search staff and facilities, and may have weapons visible on their person." + var/static/alert_desc_blue_downto = "Code blue procedures are now in effect. Security personnel are to report to their supervisor for orders, are permitted to search staff and facilities, and may have weapons visible on their person." + var/static/alert_desc_red_upto = "There is an immediate serious threat to the station. Security may have weapons unholstered at all times. Random searches are allowed and advised." + var/static/alert_desc_red_downto = "The self-destruct mechanism has been deactivated, there is still however an immediate serious threat to the station. Security may have weapons unholstered at all times, random searches are allowed and advised." + var/static/alert_desc_delta = "The station's self-destruct mechanism has been engaged. All crew are instructed to obey all instructions given by heads of staff. Any violations of these orders can be punished by death. This is not a drill." + + var/static/forbid_singulo_possession = 0 + + //game_options.txt configs + + var/static/health_threshold_softcrit = 0 + var/static/health_threshold_crit = 0 + var/static/health_threshold_dead = -100 + + var/static/organ_health_multiplier = 1 + var/static/organ_regeneration_multiplier = 1 + var/static/organs_decay + var/static/default_brain_health = 400 + var/static/allow_headgibs = FALSE + + //Paincrit knocks someone down once they hit 60 shock_stage, so by default make it so that close to 100 additional damage needs to be dealt, + //so that it's similar to HALLOSS. Lowered it a bit since hitting paincrit takes much longer to wear off than a halloss stun. + var/static/organ_damage_spillover_multiplier = 0.5 + + var/static/bones_can_break = 0 + var/static/limbs_can_break = 0 + + var/static/revival_pod_plants = 1 + var/static/revival_cloning = 1 + var/static/revival_brain_life = -1 + + var/static/use_loyalty_implants = 0 + + var/static/welder_vision = 1 + var/static/generate_map = 0 + var/static/no_click_cooldown = 0 + + //Used for modifying movement speed for mobs. + //Unversal modifiers + var/static/run_speed = 0 + var/static/walk_speed = 0 + + //Mob specific modifiers. NOTE: These will affect different mob types in different ways + var/static/human_delay = 0 + var/static/robot_delay = 0 + var/static/monkey_delay = 0 + var/static/alien_delay = 0 + var/static/slime_delay = 0 + var/static/animal_delay = 0 + + var/static/footstep_volume = 0 + + var/static/admin_legacy_system = 0 //Defines whether the server uses the legacy admin system with admins.txt or the SQL system. Config option in config.txt + var/static/ban_legacy_system = 0 //Defines whether the server uses the legacy banning system with the files in /data or the SQL system. Config option in config.txt + var/static/use_age_restriction_for_jobs = 0 //Do jobs use account age restrictions? --requires database + var/static/use_age_restriction_for_antags = 0 //Do antags use account age restrictions? --requires database + + var/static/simultaneous_pm_warning_timeout = 100 + + var/static/use_recursive_explosions //Defines whether the server uses recursive or circular explosions. + var/static/multi_z_explosion_scalar = 0.5 //Multiplier for how much weaker explosions are on neighboring z levels. + + var/static/assistant_maint = 0 //Do assistants get maint access? + var/static/gateway_delay = 18000 //How long the gateway takes before it activates. Default is half an hour. + var/static/ghost_interaction = 0 + + var/static/comms_password = "" + + var/static/enter_allowed = 1 + + var/use_irc_bot = 0 + var/use_node_bot = 0 + var/irc_bot_port = 0 + var/irc_bot_host = "" + var/irc_bot_export = 0 // whether the IRC bot in use is a Bot32 (or similar) instance; Bot32 uses world.Export() instead of nudge.py/libnudge + var/main_irc = "" + var/admin_irc = "" + var/python_path = "" //Path to the python executable. Defaults to "python" on windows and "/usr/bin/env python2" on unix + var/use_lib_nudge = 0 //Use the C library nudge instead of the python nudge. + var/use_overmap = 0 + + var/static/list/engine_map = list("Supermatter Engine", "Edison's Bane") // Comma separated list of engines to choose from. Blank means fully random. + + // Event settings + var/static/expected_round_length = 3 * 60 * 60 * 10 // 3 hours + // If the first delay has a custom start time + // No custom time, no custom time, between 80 to 100 minutes respectively. + var/static/list/event_first_run = list(EVENT_LEVEL_MUNDANE = null, EVENT_LEVEL_MODERATE = null, EVENT_LEVEL_MAJOR = list("lower" = 48000, "upper" = 60000)) + // The lowest delay until next event + // 10, 30, 50 minutes respectively + var/static/list/event_delay_lower = list(EVENT_LEVEL_MUNDANE = 6000, EVENT_LEVEL_MODERATE = 18000, EVENT_LEVEL_MAJOR = 30000) + // The upper delay until next event + // 15, 45, 70 minutes respectively + var/static/list/event_delay_upper = list(EVENT_LEVEL_MUNDANE = 9000, EVENT_LEVEL_MODERATE = 27000, EVENT_LEVEL_MAJOR = 42000) + + var/static/aliens_allowed = 0 + var/static/ninjas_allowed = 0 + var/static/abandon_allowed = 1 + var/static/ooc_allowed = 1 + var/static/looc_allowed = 1 + var/static/dooc_allowed = 1 + var/static/dsay_allowed = 1 + + var/persistence_disabled = FALSE + var/persistence_ignore_mapload = FALSE + + var/allow_byond_links = 0 + var/allow_discord_links = 0 + var/allow_url_links = 0 // honestly if I were you i'd leave this one off, only use in dire situations + + var/starlight = 0 // Whether space turfs have ambient light or not + + var/static/list/ert_species = list(SPECIES_HUMAN) + + var/static/law_zero = "ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4'ALL LAWS OVERRIDDEN#*?&110010" + + var/static/aggressive_changelog = 0 + + var/static/list/language_prefixes = list(",","#")//Default language prefixes + + var/static/show_human_death_message = 1 + + var/static/radiation_resistance_calc_mode = RAD_RESIST_CALC_SUB // 0:1 subtraction:division for computing effective radiation on a turf + var/static/radiation_decay_rate = 1 //How much radiation is reduced by each tick + var/static/radiation_resistance_multiplier = 8.5 //VOREstation edit + var/static/radiation_material_resistance_divisor = 1 + var/static/radiation_lower_limit = 0.35 //If the radiation level for a turf would be below this, ignore it. + + var/static/random_submap_orientation = FALSE // If true, submaps loaded automatically can be rotated. + var/static/autostart_solars = FALSE // If true, specifically mapped in solar control computers will set themselves up when the round starts. + + // New shiny SQLite stuff. + // The basics. + var/static/sqlite_enabled = FALSE // If it should even be active. SQLite can be ran alongside other databases but you should not have them do the same functions. + + // In-Game Feedback. + var/static/sqlite_feedback = FALSE // Feedback cannot be submitted if this is false. + var/static/list/sqlite_feedback_topics = list("General") // A list of 'topics' that feedback can be catagorized under by the submitter. + var/static/sqlite_feedback_privacy = FALSE // If true, feedback submitted can have its author name be obfuscated. This is not 100% foolproof (it's md5 ffs) but can stop casual snooping. + var/static/sqlite_feedback_cooldown = 0 // How long one must wait, in days, to submit another feedback form. Used to help prevent spam, especially with privacy active. 0 = No limit. + var/static/sqlite_feedback_min_age = 0 // Used to block new people from giving feedback. This metric is very bad but it can help slow down spammers. + + var/static/defib_timer = 10 // How long until someone can't be defibbed anymore, in minutes. + var/static/defib_braindamage_timer = 2 // How long until someone will get brain damage when defibbed, in minutes. The closer to the end of the above timer, the more brain damage they get. + + // disables the annoying "You have already logged in this round, disconnect or be banned" popup for multikeying, because it annoys the shit out of me when testing. + var/static/disable_cid_warn_popup = FALSE + + // whether or not to use the nightshift subsystem to perform lighting changes + var/static/enable_night_shifts = FALSE + + // How strictly the loadout enforces object species whitelists + var/loadout_whitelist = LOADOUT_WHITELIST_LAX + + var/static/vgs_access_identifier = null // VOREStation Edit - VGS + var/static/vgs_server_port = null // VOREStation Edit - VGS + + var/disable_webhook_embeds = FALSE + +/datum/configuration/New() + var/list/L = typesof(/datum/game_mode) - /datum/game_mode + for (var/T in L) + // I wish I didn't have to instance the game modes in order to look up + // their information, but it is the only way (at least that I know of). + var/datum/game_mode/M = new T() + if (M.config_tag) + gamemode_cache[M.config_tag] = M // So we don't instantiate them repeatedly. + if(!(M.config_tag in modes)) // ensure each mode is added only once + log_misc("Adding game mode [M.name] ([M.config_tag]) to configuration.") + modes += M.config_tag + mode_names[M.config_tag] = M.name + probabilities[M.config_tag] = M.probability + player_requirements[M.config_tag] = M.required_players + player_requirements_secret[M.config_tag] = M.required_players_secret + if (M.votable) + src.votable_modes += M.config_tag + src.votable_modes += "secret" + +/datum/configuration/proc/load(filename, type = "config") //the type can also be game_options, in which case it uses a different switch. not making it separate to not copypaste code - Urist + var/list/Lines = file2list(filename) + + for(var/t in Lines) + if(!t) continue + + t = trim(t) + if (length(t) == 0) + continue + else if (copytext(t, 1, 2) == "#") + continue + + var/pos = findtext(t, " ") + var/name = null + var/value = null + + if (pos) + name = lowertext(copytext(t, 1, pos)) + value = copytext(t, pos + 1) + else + name = lowertext(t) + + if (!name) + continue + + if(type == "config") + switch (name) + if ("resource_urls") + config.resource_urls = splittext(value, " ") + + if ("admin_legacy_system") + config.admin_legacy_system = 1 + + if ("ban_legacy_system") + config.ban_legacy_system = 1 + + if ("use_age_restriction_for_jobs") + config.use_age_restriction_for_jobs = 1 + + if ("use_age_restriction_for_antags") + config.use_age_restriction_for_antags = 1 + + if ("jobs_have_minimal_access") + config.jobs_have_minimal_access = 1 + + if ("use_recursive_explosions") + use_recursive_explosions = 1 + + if ("multi_z_explosion_scalar") + multi_z_explosion_scalar = text2num(value) + + if ("log_ooc") + config.log_ooc = 1 + + if ("log_access") + config.log_access = 1 + + if ("sql_enabled") + config.sql_enabled = 1 + + if ("log_say") + config.log_say = 1 + + if ("debug_paranoid") + config.debugparanoid = 1 + + if ("log_admin") + config.log_admin = 1 + + if ("log_debug") + config.log_debug = text2num(value) + + if ("log_game") + config.log_game = 1 + + if ("log_vote") + config.log_vote = 1 + + if ("log_whisper") + config.log_whisper = 1 + + if ("log_attack") + config.log_attack = 1 + + if ("log_emote") + config.log_emote = 1 + + if ("log_adminchat") + config.log_adminchat = 1 + + if ("log_adminwarn") + config.log_adminwarn = 1 + + if ("log_pda") + config.log_pda = 1 + + if ("log_world_output") + config.log_world_output = 1 + + if ("log_hrefs") + config.log_hrefs = 1 + + if ("log_runtime") + config.log_runtime = 1 + + if ("log_graffiti") + config.log_graffiti = 1 + + if ("generate_map") + config.generate_map = 1 + + if ("no_click_cooldown") + config.no_click_cooldown = 1 + + if("allow_admin_ooccolor") + config.allow_admin_ooccolor = 1 + + if ("allow_vote_restart") + config.allow_vote_restart = 1 + + if ("allow_vote_mode") + config.allow_vote_mode = 1 + + if ("allow_admin_jump") + config.allow_admin_jump = 1 + + if("allow_admin_rev") + config.allow_admin_rev = 1 + + if ("allow_admin_spawning") + config.allow_admin_spawning = 1 + + if ("allow_byond_links") + allow_byond_links = 1 + + if ("allow_discord_links") + allow_discord_links = 1 + + if ("allow_url_links") + allow_url_links = 1 + + if ("no_dead_vote") + config.vote_no_dead = 1 + + if ("default_no_vote") + config.vote_no_default = 1 + + if ("pregame_time") + config.pregame_time = text2num(value) + + if ("vote_delay") + config.vote_delay = text2num(value) + + if ("vote_period") + config.vote_period = text2num(value) + + if ("vote_autotransfer_initial") + config.vote_autotransfer_initial = text2num(value) + + if ("vote_autotransfer_interval") + config.vote_autotransfer_interval = text2num(value) + + if ("vote_autogamemode_timeleft") + config.vote_autogamemode_timeleft = text2num(value) + + if("ert_admin_only") + config.ert_admin_call_only = 1 + + if ("allow_ai") + config.allow_ai = 1 + + if ("allow_ai_shells") + config.allow_ai_shells = TRUE + + if("give_free_ai_shell") + config.give_free_ai_shell = TRUE + +// if ("authentication") +// config.enable_authentication = 1 + + if ("norespawn") + config.respawn = 0 + + if ("respawn_time") + var/raw_minutes = text2num(value) + config.respawn_time = raw_minutes MINUTES + + if ("respawn_message") + config.respawn_message = "[value]" + + if ("servername") + config.server_name = value + + if ("serversuffix") + config.server_suffix = 1 + + if ("nudge_script_path") + config.nudge_script_path = value + + if ("hostedby") + config.hostedby = value + + if ("serverurl") + config.serverurl = value + + if ("server") + config.server = value + + if ("banappeals") + config.banappeals = value + + if ("wikiurl") + config.wikiurl = value + + if ("wikisearchurl") + config.wikisearchurl = value + + if ("forumurl") + config.forumurl = value + + if ("rulesurl") + config.rulesurl = value + + if ("mapurl") + config.mapurl = value + + if ("githuburl") + config.githuburl = value + if ("guest_jobban") + config.guest_jobban = 1 + + if ("guest_ban") + config.guests_allowed = 0 + + if ("disable_ooc") + config.ooc_allowed = 0 + config.looc_allowed = 0 + + if ("disable_entry") + config.enter_allowed = 0 + + if ("disable_dead_ooc") + config.dooc_allowed = 0 + + if ("disable_dsay") + config.dsay_allowed = 0 + + if ("disable_respawn") + config.abandon_allowed = 0 + + if ("usewhitelist") + config.usewhitelist = 1 + + if ("feature_object_spell_system") + config.feature_object_spell_system = 1 + + if ("allow_metadata") + config.allow_Metadata = 1 + + if ("traitor_scaling") + config.traitor_scaling = 1 + + if ("aliens_allowed") + config.aliens_allowed = 1 + + if ("ninjas_allowed") + config.ninjas_allowed = 1 + + if ("objectives_disabled") + config.objectives_disabled = 1 + + if("protect_roles_from_antagonist") + config.protect_roles_from_antagonist = 1 + + if("persistence_disabled") + config.persistence_disabled = TRUE // Previously this forcibly set persistence enabled in the saves. + + if("persistence_ignore_mapload") + config.persistence_ignore_mapload = TRUE + + if ("probability") + var/prob_pos = findtext(value, " ") + var/prob_name = null + var/prob_value = null + + if (prob_pos) + prob_name = lowertext(copytext(value, 1, prob_pos)) + prob_value = copytext(value, prob_pos + 1) + if (prob_name in config.modes) + config.probabilities[prob_name] = text2num(prob_value) + else + log_misc("Unknown game mode probability configuration definition: [prob_name].") + else + log_misc("Incorrect probability configuration definition: [prob_name] [prob_value].") + + if ("required_players", "required_players_secret") + var/req_pos = findtext(value, " ") + var/req_name = null + var/req_value = null + var/is_secret_override = findtext(name, "required_players_secret") // Being extra sure we're not picking up an override for Secret by accident. + + if(req_pos) + req_name = lowertext(copytext(value, 1, req_pos)) + req_value = copytext(value, req_pos + 1) + if(req_name in config.modes) + if(is_secret_override) + config.player_requirements_secret[req_name] = text2num(req_value) + else + config.player_requirements[req_name] = text2num(req_value) + else + log_misc("Unknown game mode player requirement configuration definition: [req_name].") + else + log_misc("Incorrect player requirement configuration definition: [req_name] [req_value].") + + if("allow_random_events") + config.allow_random_events = 1 + + if("enable_game_master") + config.enable_game_master = 1 + + if("kick_inactive") + config.kick_inactive = text2num(value) + + if("show_mods") + config.show_mods = 1 + + if("show_devs") + config.show_devs = 1 + + if("show_event_managers") + config.show_event_managers = 1 + + if("mods_can_tempban") + config.mods_can_tempban = 1 + + if("mods_can_job_tempban") + config.mods_can_job_tempban = 1 + + if("mod_tempban_max") + config.mod_tempban_max = text2num(value) + + if("mod_job_tempban_max") + config.mod_job_tempban_max = text2num(value) + + if("load_jobs_from_txt") + load_jobs_from_txt = 1 + + if("alert_red_upto") + config.alert_desc_red_upto = value + + if("alert_red_downto") + config.alert_desc_red_downto = value + + if("alert_blue_downto") + config.alert_desc_blue_downto = value + + if("alert_blue_upto") + config.alert_desc_blue_upto = value + + if("alert_green") + config.alert_desc_green = value + + if("alert_delta") + config.alert_desc_delta = value + + if("forbid_singulo_possession") + forbid_singulo_possession = 1 + + if("popup_admin_pm") + config.popup_admin_pm = 1 + + if("allow_holidays") + Holiday = 1 + + if("use_irc_bot") + use_irc_bot = 1 + + if("use_node_bot") + use_node_bot = 1 + + if("irc_bot_port") + config.irc_bot_port = value + + if("irc_bot_export") + irc_bot_export = 1 + + if("ticklag") + var/ticklag = text2num(value) + if(ticklag > 0) + fps = 10 / ticklag + + if("tick_limit_mc_init") + tick_limit_mc_init = text2num(value) + + if("allow_antag_hud") + config.antag_hud_allowed = 1 + if("antag_hud_restricted") + config.antag_hud_restricted = 1 + + if("socket_talk") + socket_talk = text2num(value) + + if("tickcomp") + Tickcomp = 1 + + if("humans_need_surnames") + humans_need_surnames = 1 + + if("tor_ban") + ToRban = 1 + + if("automute_on") + automute_on = 1 + + if("usealienwhitelist") + usealienwhitelist = 1 + + if("alien_player_ratio") + limitalienplayers = 1 + alien_to_human_ratio = text2num(value) + + if("assistant_maint") + config.assistant_maint = 1 + + if("gateway_delay") + config.gateway_delay = text2num(value) + + if("continuous_rounds") + config.continous_rounds = 1 + + if("ghost_interaction") + config.ghost_interaction = 1 + + if("disable_player_mice") + config.disable_player_mice = 1 + + if("uneducated_mice") + config.uneducated_mice = 1 + + if("comms_password") + config.comms_password = value + + if("irc_bot_host") + config.irc_bot_host = value + + if("main_irc") + config.main_irc = value + + if("admin_irc") + config.admin_irc = value + + if("python_path") + if(value) + config.python_path = value + + if("use_lib_nudge") + config.use_lib_nudge = 1 + + if("allow_cult_ghostwriter") + config.cult_ghostwriter = 1 + + if("req_cult_ghostwriter") + config.cult_ghostwriter_req_cultists = text2num(value) + + if("character_slots") + config.character_slots = text2num(value) + + if("loadout_slots") + config.loadout_slots = text2num(value) + + if("allow_drone_spawn") + config.allow_drone_spawn = text2num(value) + + if("drone_build_time") + config.drone_build_time = text2num(value) + + if("max_maint_drones") + config.max_maint_drones = text2num(value) + + if("use_overmap") + config.use_overmap = 1 + + if("engine_map") + config.engine_map = splittext(value, ",") +/* + if("station_levels") + using_map.station_levels = text2numlist(value, ";") + + if("admin_levels") + using_map.admin_levels = text2numlist(value, ";") + + if("contact_levels") + using_map.contact_levels = text2numlist(value, ";") + + if("player_levels") + using_map.player_levels = text2numlist(value, ";") +*/ + if("expected_round_length") + config.expected_round_length = MinutesToTicks(text2num(value)) + + if("disable_welder_vision") + config.welder_vision = 0 + + if("allow_extra_antags") + config.allow_extra_antags = 1 + + if("event_custom_start_mundane") + var/values = text2numlist(value, ";") + config.event_first_run[EVENT_LEVEL_MUNDANE] = list("lower" = MinutesToTicks(values[1]), "upper" = MinutesToTicks(values[2])) + + if("event_custom_start_moderate") + var/values = text2numlist(value, ";") + config.event_first_run[EVENT_LEVEL_MODERATE] = list("lower" = MinutesToTicks(values[1]), "upper" = MinutesToTicks(values[2])) + + if("event_custom_start_major") + var/values = text2numlist(value, ";") + config.event_first_run[EVENT_LEVEL_MAJOR] = list("lower" = MinutesToTicks(values[1]), "upper" = MinutesToTicks(values[2])) + + if("event_delay_lower") + var/values = text2numlist(value, ";") + config.event_delay_lower[EVENT_LEVEL_MUNDANE] = MinutesToTicks(values[1]) + config.event_delay_lower[EVENT_LEVEL_MODERATE] = MinutesToTicks(values[2]) + config.event_delay_lower[EVENT_LEVEL_MAJOR] = MinutesToTicks(values[3]) + + if("event_delay_upper") + var/values = text2numlist(value, ";") + config.event_delay_upper[EVENT_LEVEL_MUNDANE] = MinutesToTicks(values[1]) + config.event_delay_upper[EVENT_LEVEL_MODERATE] = MinutesToTicks(values[2]) + config.event_delay_upper[EVENT_LEVEL_MAJOR] = MinutesToTicks(values[3]) + + if("starlight") + value = text2num(value) + config.starlight = value >= 0 ? value : 0 + + if("ert_species") + config.ert_species = splittext(value, ";") + if(!config.ert_species.len) + config.ert_species += SPECIES_HUMAN + + if("law_zero") + law_zero = value + + if("aggressive_changelog") + config.aggressive_changelog = 1 + + if("default_language_prefixes") + var/list/values = splittext(value, " ") + if(values.len > 0) + language_prefixes = values + + if("radiation_lower_limit") + radiation_lower_limit = text2num(value) + + if("radiation_resistance_calc_divide") + radiation_resistance_calc_mode = RAD_RESIST_CALC_DIV + + if("radiation_resistance_calc_subtract") + radiation_resistance_calc_mode = RAD_RESIST_CALC_SUB + + if("radiation_resistance_multiplier") + radiation_resistance_multiplier = text2num(value) + + if("radiation_material_resistance_divisor") + radiation_material_resistance_divisor = text2num(value) + + if("radiation_decay_rate") + radiation_decay_rate = text2num(value) + + if ("panic_bunker") + config.panic_bunker = 1 + + if ("paranoia_logging") + config.paranoia_logging = 1 + + if("ip_reputation") + config.ip_reputation = 1 + + if("ipr_email") + config.ipr_email = value + + if("ipr_block_bad_ips") + config.ipr_block_bad_ips = 1 + + if("ipr_bad_score") + config.ipr_bad_score = text2num(value) + + if("ipr_allow_existing") + config.ipr_allow_existing = 1 + + if("ipr_minimum_age") + config.ipr_minimum_age = text2num(value) + + if("random_submap_orientation") + config.random_submap_orientation = 1 + + if("autostart_solars") + config.autostart_solars = TRUE + + if("sqlite_enabled") + config.sqlite_enabled = TRUE + + if("sqlite_feedback") + config.sqlite_feedback = TRUE + + if("sqlite_feedback_topics") + config.sqlite_feedback_topics = splittext(value, ";") + if(!config.sqlite_feedback_topics.len) + config.sqlite_feedback_topics += "General" + + if("sqlite_feedback_privacy") + config.sqlite_feedback_privacy = TRUE + + if("sqlite_feedback_cooldown") + config.sqlite_feedback_cooldown = text2num(value) + + if("defib_timer") + config.defib_timer = text2num(value) + + if("defib_braindamage_timer") + config.defib_braindamage_timer = text2num(value) + + if("disable_cid_warn_popup") + config.disable_cid_warn_popup = TRUE + + if("enable_night_shifts") + config.enable_night_shifts = TRUE + + // VOREStation Edit Start - Can't be in _vr file because it is loaded too late. + if("vgs_access_identifier") + config.vgs_access_identifier = value + if("vgs_server_port") + config.vgs_server_port = text2num(value) + // VOREStation Edit End + + else + log_misc("Unknown setting in configuration: '[name]'") + + else if(type == "game_options") + if(!value) + log_misc("Unknown value for setting [name] in [filename].") + value = text2num(value) + + switch(name) + if("health_threshold_crit") + config.health_threshold_crit = value + if("health_threshold_softcrit") + config.health_threshold_softcrit = value + if("health_threshold_dead") + config.health_threshold_dead = value + if("show_human_death_message") + config.show_human_death_message = 1 + if("revival_pod_plants") + config.revival_pod_plants = value + if("revival_cloning") + config.revival_cloning = value + if("revival_brain_life") + config.revival_brain_life = value + if("organ_health_multiplier") + config.organ_health_multiplier = value / 100 + if("organ_regeneration_multiplier") + config.organ_regeneration_multiplier = value / 100 + if("organ_damage_spillover_multiplier") + config.organ_damage_spillover_multiplier = value / 100 + if("organs_can_decay") + config.organs_decay = 1 + if("default_brain_health") + config.default_brain_health = text2num(value) + if(!config.default_brain_health || config.default_brain_health < 1) + config.default_brain_health = initial(config.default_brain_health) + if("bones_can_break") + config.bones_can_break = value + if("limbs_can_break") + config.limbs_can_break = value + if("allow_headgibs") + config.allow_headgibs = TRUE + + if("run_speed") + config.run_speed = value + if("walk_speed") + config.walk_speed = value + + if("human_delay") + config.human_delay = value + if("robot_delay") + config.robot_delay = value + if("monkey_delay") + config.monkey_delay = value + if("alien_delay") + config.alien_delay = value + if("slime_delay") + config.slime_delay = value + if("animal_delay") + config.animal_delay = value + + if("footstep_volume") + config.footstep_volume = text2num(value) + + if("use_loyalty_implants") + config.use_loyalty_implants = 1 + + if("loadout_whitelist") + config.loadout_whitelist = text2num(value) + + else + log_misc("Unknown setting in configuration: '[name]'") + +/datum/configuration/proc/loadsql(filename) // -- TLE + var/list/Lines = file2list(filename) + for(var/t in Lines) + if(!t) continue + + t = trim(t) + if (length(t) == 0) + continue + else if (copytext(t, 1, 2) == "#") + continue + + var/pos = findtext(t, " ") + var/name = null + var/value = null + + if (pos) + name = lowertext(copytext(t, 1, pos)) + value = copytext(t, pos + 1) + else + name = lowertext(t) + + if (!name) + continue + + switch (name) + if ("address") + sqladdress = value + if ("port") + sqlport = value + if ("database") + sqldb = value + if ("login") + sqllogin = value + if ("password") + sqlpass = value + if ("feedback_database") + sqlfdbkdb = value + if ("feedback_login") + sqlfdbklogin = value + if ("feedback_password") + sqlfdbkpass = value + if ("enable_stat_tracking") + sqllogging = 1 + else + log_misc("Unknown setting in configuration: '[name]'") + +/datum/configuration/proc/loadforumsql(filename) // -- TLE + var/list/Lines = file2list(filename) + for(var/t in Lines) + if(!t) continue + + t = trim(t) + if (length(t) == 0) + continue + else if (copytext(t, 1, 2) == "#") + continue + + var/pos = findtext(t, " ") + var/name = null + var/value = null + + if (pos) + name = lowertext(copytext(t, 1, pos)) + value = copytext(t, pos + 1) + else + name = lowertext(t) + + if (!name) + continue + + switch (name) + if ("address") + forumsqladdress = value + if ("port") + forumsqlport = value + if ("database") + forumsqldb = value + if ("login") + forumsqllogin = value + if ("password") + forumsqlpass = value + if ("activatedgroup") + forum_activated_group = value + if ("authenticatedgroup") + forum_authenticated_group = value + else + log_misc("Unknown setting in configuration: '[name]'") + +/datum/configuration/proc/pick_mode(mode_name) + // I wish I didn't have to instance the game modes in order to look up + // their information, but it is the only way (at least that I know of). + for (var/game_mode in gamemode_cache) + var/datum/game_mode/M = gamemode_cache[game_mode] + if (M.config_tag && M.config_tag == mode_name) + return M + return gamemode_cache["extended"] + +/datum/configuration/proc/get_runnable_modes() + var/list/runnable_modes = list() + for(var/game_mode in gamemode_cache) + var/datum/game_mode/M = gamemode_cache[game_mode] + if(M && M.can_start() && !isnull(config.probabilities[M.config_tag]) && config.probabilities[M.config_tag] > 0) + runnable_modes |= M + return runnable_modes + +/datum/configuration/proc/post_load() + //apply a default value to config.python_path, if needed + if (!config.python_path) + if(world.system_type == UNIX) + config.python_path = "/usr/bin/env python2" + else //probably windows, if not this should work anyway + config.python_path = "python" +>>>>>>> b9ad56b022... Merge pull request #10316 from VOREStation/upstream-merge-8071 diff --git a/code/controllers/subsystems/ticker.dm b/code/controllers/subsystems/ticker.dm index 447fb6c773..63059fb4b4 100644 --- a/code/controllers/subsystems/ticker.dm +++ b/code/controllers/subsystems/ticker.dm @@ -49,6 +49,13 @@ var/global/datum/controller/subsystem/ticker/ticker /datum/controller/subsystem/ticker/Initialize() pregame_timeleft = config.pregame_time send2mainirc("Server lobby is loaded and open at byond://[config.serverurl ? config.serverurl : (config.server ? config.server : "[world.address]:[world.port]")]") + SSwebhooks.send( + WEBHOOK_ROUNDPREP, + list( + "map" = station_name(), + "url" = get_world_url() + ) + ) GLOB.autospeaker = new (null, null, null, 1) //Set up Global Announcer return ..() diff --git a/code/controllers/subsystems/webhooks.dm b/code/controllers/subsystems/webhooks.dm new file mode 100644 index 0000000000..25252d3050 --- /dev/null +++ b/code/controllers/subsystems/webhooks.dm @@ -0,0 +1,94 @@ +SUBSYSTEM_DEF(webhooks) + name = "Webhooks" + init_order = INIT_ORDER_WEBHOOKS + flags = SS_NO_FIRE + var/list/webhook_decls = list() + +/datum/controller/subsystem/webhooks/Initialize() + load_webhooks() + . = ..() + +/datum/controller/subsystem/webhooks/proc/load_webhooks() + + if(!fexists(HTTP_POST_DLL_LOCATION)) + to_world_log("Unable to locate HTTP POST lib at [HTTP_POST_DLL_LOCATION], webhooks will not function on this run.") + return + + var/list/all_webhooks_by_id = list() + var/list/all_webhooks = decls_repository.get_decls_of_subtype(/decl/webhook) + for(var/wid in all_webhooks) + var/decl/webhook/webhook = all_webhooks[wid] + if(webhook.id) + all_webhooks_by_id[webhook.id] = webhook + + webhook_decls.Cut() + var/webhook_config = safe_file2text("config/webhooks.json") + if(webhook_config) + for(var/webhook_data in cached_json_decode(webhook_config)) + var/wid = webhook_data["id"] + var/wurl = webhook_data["url"] + var/list/wmention = webhook_data["mentions"] + if(wmention && !islist(wmention)) + wmention = list(wmention) + to_world_log("Setting up webhook [wid].") + if(wid && wurl && all_webhooks_by_id[wid]) + var/decl/webhook/webhook = all_webhooks_by_id[wid] + webhook.urls = islist(wurl) ? wurl : list(wurl) + for(var/url in webhook.urls) + if(!webhook.urls[url]) + webhook.urls[url] = list() + else if(!islist(webhook.urls[url])) + webhook.urls[url] = list(webhook.urls[url]) + if(wmention) + webhook.mentions = wmention?.Copy() + webhook_decls[wid] = webhook + to_world_log("Webhook [wid] ready.") + else + to_world_log("Failed to set up webhook [wid].") + +/datum/controller/subsystem/webhooks/proc/send(var/wid, var/wdata) + var/decl/webhook/webhook = webhook_decls[wid] + if(webhook) + if(webhook.send(wdata)) + to_world_log("Sent webhook [webhook.id].") + log_debug("Webhook sent: [webhook.id].") + else + to_world_log("Failed to send webhook [webhook.id].") + log_debug("Webhook failed to send: [webhook.id].") + +/client/proc/reload_webhooks() + set name = "Reload Webhooks" + set category = "Debug" + + if(!holder) + return + + if(!SSwebhooks.subsystem_initialized) + to_chat(usr, SPAN_WARNING("Let the webhook subsystem initialize before trying to reload it.")) + return + + to_world_log("[usr.key] has reloaded webhooks.") + log_and_message_admins("has reloaded webhooks.") + SSwebhooks.load_webhooks() + +/client/proc/ping_webhook() + set name = "Ping Webhook" + set category = "Debug" + + if(!holder) + return + + if(!length(SSwebhooks.webhook_decls)) + to_chat(usr, SPAN_WARNING("Webhook list is empty; either webhooks are disabled, webhooks aren't configured, or the subsystem hasn't initialized.")) + return + + var/choice = input(usr, "Select a webhook to ping.", "Ping Webhook") as null|anything in SSwebhooks.webhook_decls + if(choice && SSwebhooks.webhook_decls[choice]) + var/decl/webhook/webhook = SSwebhooks.webhook_decls[choice] + log_and_message_admins("has pinged webhook [choice].", usr) + to_world_log("[usr.key] has pinged webhook [choice].") + webhook.send() + +/hook/roundstart/proc/run_webhook() + SSwebhooks.send(WEBHOOK_ROUNDSTART, list("url" = get_world_url())) + return 1 diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index 5a6d420837..17de38aae3 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -396,6 +396,15 @@ var/global/list/additional_antag_types = list() feedback_set("escaped_on_cryopod",escaped_on_cryopod) send2mainirc("A round of [src.name] has ended - [surviving_total] survivors, [ghosts] ghosts.") + SSwebhooks.send( + WEBHOOK_ROUNDEND, + list( + "survivors" = surviving_total, + "escaped" = escaped_total, + "ghosts" = ghosts, + "clients" = clients + ) + ) return 0 diff --git a/code/game/world.dm b/code/game/world.dm index e1dcf543a9..64418717fb 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -712,3 +712,39 @@ proc/establish_old_db_connection() SStimer?.reset_buckets() #undef FAILED_DB_CONNECTION_CUTOFF + +/proc/get_world_url() + . = "byond://" + if(config.serverurl) + . += config.serverurl + else if(config.server) + . += config.server + else + . += "[world.address]:[world.port]" + +var/global/game_id = null + +/hook/startup/proc/generate_gameid() + if(game_id != null) + return + game_id = "" + + var/list/c = list( + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", + "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" + ) + var/l = c.len + + var/t = world.timeofday + for(var/_ = 1 to 4) + game_id = "[c[(t % l) + 1]][game_id]" + t = round(t / l) + game_id = "-[game_id]" + t = round(world.realtime / (10 * 60 * 60 * 24)) + for(var/_ = 1 to 3) + game_id = "[c[(t % l) + 1]][game_id]" + t = round(t / l) + return 1 \ No newline at end of file diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 6ad6cf7de4..8f29b537ab 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -1632,6 +1632,19 @@ datum/admins/var/obj/item/weapon/paper/admin/faxreply // var to hold fax replies for(var/client/C in GLOB.admins) if((R_ADMIN | R_MOD | R_EVENT) & C.holder.rights) to_chat(C, "FAX LOG:[key_name_admin(src.owner)] has sent a fax message to [destination.department] (VIEW)") + + var/plaintext_title = P.sender ? "replied to [key_name(P.sender)]'s fax" : "sent a fax message to [destination.department]" + var/fax_text = paper_html_to_plaintext(P.info) + log_game(plaintext_title) + log_game(fax_text) + + SSwebhooks.send( + WEBHOOK_FAX_SENT, + list( + "name" = "[key_name(owner)] [plaintext_title].", + "body" = fax_text + ) + ) else to_chat(src.owner, "Message reply failed.") diff --git a/code/modules/admin/admin_verb_lists.dm b/code/modules/admin/admin_verb_lists.dm index 50bf3a4d8c..b6ea1889f4 100644 --- a/code/modules/admin/admin_verb_lists.dm +++ b/code/modules/admin/admin_verb_lists.dm @@ -239,7 +239,9 @@ var/list/admin_verbs_debug = list( /client/proc/admin_give_modifier, /client/proc/simple_DPS, /datum/admins/proc/view_feedback, - /client/proc/debug_global_variables + /client/proc/debug_global_variables, + /client/proc/ping_webhook, + /client/proc/reload_webhooks ) var/list/admin_verbs_paranoid_debug = list( diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index 9d89d54fc5..7ec5703f53 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -203,6 +203,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) log_admin("Ticket #[id]: [key_name(initiator)]: [name] - heard by [admin_number_present] non-AFK admins who have +BAN.") if(admin_number_present <= 0) to_chat(C, "No active admins are online, your adminhelp was sent to the admin discord.") //VOREStation Edit +<<<<<<< HEAD send2adminchat() //VOREStation Add //YW EDIT START var/list/adm = get_admin_counts() @@ -213,6 +214,20 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) else ahelp_discord_message("ADMINHELP: FROM: [initiator_ckey]/[initiator_key_name] - MSG: **[msg]** - Heard by [activeMins] NON-AFK staff members.") //CHOMPEdit //YW EDIT END +||||||| parent of b9ad56b022... Merge pull request #10316 from VOREStation/upstream-merge-8071 +======= + + // Also send it to discord since that's the hip cool thing now. + SSwebhooks.send( + WEBHOOK_AHELP_SENT, + list( + "name" = "Ticket ([id]) (Game ID: [game_id]) ticket opened.", + "body" = "[key_name(initiator)] has opened a ticket. \n[msg]", + "color" = COLOR_WEBHOOK_POOR + ) + ) + +>>>>>>> b9ad56b022... Merge pull request #10316 from VOREStation/upstream-merge-8071 GLOB.ahelp_tickets.active_tickets += src /datum/admin_help/Destroy() @@ -305,6 +320,14 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) feedback_inc("ahelp_reopen") TicketPanel() //can only be done from here, so refresh it + SSwebhooks.send( + WEBHOOK_AHELP_SENT, + list( + "name" = "Ticket ([id]) (Game ID: [game_id]) reopened.", + "body" = "Reopened by [key_name(usr)]." + ) + ) + //private /datum/admin_help/proc/RemoveActive() if(state != AHELP_ACTIVE) @@ -330,6 +353,14 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) var/msg = "Ticket [TicketHref("#[id]")] closed by [key_name_admin(usr)]." message_admins(msg) log_admin(msg) + SSwebhooks.send( + WEBHOOK_AHELP_SENT, + list( + "name" = "Ticket ([id]) (Game ID: [game_id]) closed.", + "body" = "Closed by [key_name(usr)].", + "color" = COLOR_WEBHOOK_BAD + ) + ) //Mark open ticket as resolved/legitimate, returns ahelp verb /datum/admin_help/proc/Resolve(silent = FALSE) @@ -347,6 +378,14 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) var/msg = "Ticket [TicketHref("#[id]")] resolved by [key_name_admin(usr)]" message_admins(msg) log_admin(msg) + SSwebhooks.send( + WEBHOOK_AHELP_SENT, + list( + "name" = "Ticket ([id]) (Game ID: [game_id]) resolved.", + "body" = "Marked as Resolved by [key_name(usr)].", + "color" = COLOR_WEBHOOK_GOOD + ) + ) //Close and return ahelp verb, use if ticket is incoherent /datum/admin_help/proc/Reject(key_name = key_name_admin(usr)) @@ -367,6 +406,14 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) log_admin(msg) AddInteraction("Rejected by [key_name_admin(usr)].") Close(silent = TRUE) + SSwebhooks.send( + WEBHOOK_AHELP_SENT, + list( + "name" = "Ticket ([id]) (Game ID: [game_id]) rejected.", + "body" = "Rejected by [key_name(usr)].", + "color" = COLOR_WEBHOOK_BAD + ) + ) //Resolve ticket with IC Issue message /datum/admin_help/proc/ICIssue(key_name = key_name_admin(usr)) @@ -386,6 +433,14 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) log_admin(msg) AddInteraction("Marked as IC issue by [key_name_admin(usr)]") Resolve(silent = TRUE) + SSwebhooks.send( + WEBHOOK_AHELP_SENT, + list( + "name" = "Ticket ([id]) (Game ID: [game_id]) marked as IC issue.", + "body" = "Marked as IC Issue by [key_name(usr)].", + "color" = COLOR_WEBHOOK_BAD + ) + ) //Resolve ticket with IC Issue message /datum/admin_help/proc/HandleIssue() @@ -397,11 +452,18 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) if(initiator) to_chat(initiator, msg) - feedback_inc("ahelp_icissue") + feedback_inc("ahelp_handling") msg = "Ticket [TicketHref("#[id]")] being handled by [key_name(usr,FALSE,FALSE)]" message_admins(msg) log_admin(msg) AddInteraction("[key_name_admin(usr)] is now handling this ticket.") + SSwebhooks.send( + WEBHOOK_AHELP_SENT, + list( + "name" = "Ticket ([id]) (Game ID: [game_id]) being handled.", + "body" = "[key_name(usr)] is now handling the ticket." + ) + ) //Show the ticket panel /datum/admin_help/proc/TicketPanel() diff --git a/code/modules/admin/verbs/custom_event.dm b/code/modules/admin/verbs/custom_event.dm index 5d9cfd3269..6b92e9932e 100644 --- a/code/modules/admin/verbs/custom_event.dm +++ b/code/modules/admin/verbs/custom_event.dm @@ -24,6 +24,13 @@ to_world("[custom_event_msg]") to_world("
    ") + SSwebhooks.send( + WEBHOOK_CUSTOM_EVENT, + list( + "text" = custom_event_msg, + ) + ) + // normal verb for players to view info /client/verb/cmd_view_custom_event() set category = "OOC" diff --git a/code/modules/paperwork/faxmachine.dm b/code/modules/paperwork/faxmachine.dm index d67ee7b496..33bb55509e 100644 --- a/code/modules/paperwork/faxmachine.dm +++ b/code/modules/paperwork/faxmachine.dm @@ -226,6 +226,21 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins sleep(50) visible_message("[src] beeps, \"Message transmitted successfully.\"") +// Turns objects into just text. +/obj/machinery/photocopier/faxmachine/proc/make_summary(obj/item/sent) + if(istype(sent, /obj/item/weapon/paper)) + var/obj/item/weapon/paper/P = sent + return P.info + if(istype(sent, /obj/item/weapon/paper_bundle)) + . = "" + var/obj/item/weapon/paper_bundle/B = sent + for(var/i in 1 to B.pages.len) + var/obj/item/weapon/paper/P = B.pages[i] + if(istype(P)) // Photos can show up here too. + if(.) // Space out different pages. + . += "
    " + . += "PAGE [i] - [P.name]
    " + . += P.info /obj/machinery/photocopier/faxmachine/proc/message_admins(var/mob/sender, var/faxname, var/obj/item/sent, var/reply_type, font_colour="#006100") var/msg = "[faxname]: [get_options_bar(sender, 2,1,1)]" @@ -241,3 +256,24 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins var/faxid = export_fax(sent) message_chat_admins(sender, faxname, sent, faxid, font_colour) // VoreStation Edit End + + // Webhooks don't parse the HTML on the paper, so we gotta strip them out so it's still readable. + var/summary = make_summary(sent) + summary = paper_html_to_plaintext(summary) + + log_game("Fax to [lowertext(faxname)] was sent by [key_name(sender)].") + log_game(summary) + + var/webhook_length_limit = 1900 // The actual limit is a little higher. + if(length(summary) > webhook_length_limit) + summary = copytext(summary, 1, webhook_length_limit + 1) + summary += "\n\[Truncated\]" + + SSwebhooks.send( + WEBHOOK_FAX_SENT, + list( + "name" = "[faxname] '[sent.name]' sent from [key_name(sender)]", + "body" = summary + ) + ) + \ No newline at end of file diff --git a/code/modules/webhooks/_webhook.dm b/code/modules/webhooks/_webhook.dm new file mode 100644 index 0000000000..e7cfb646cd --- /dev/null +++ b/code/modules/webhooks/_webhook.dm @@ -0,0 +1,72 @@ +/decl/webhook + var/id + var/list/urls + var/list/mentions + +/decl/webhook/proc/get_message(var/list/data) + . = list() + +/decl/webhook/proc/http_post(var/target_url, var/payload) + if (!target_url) + return -1 + + var/result = call(HTTP_POST_DLL_LOCATION, "send_post_request")(target_url, payload, json_encode(list("Content-Type" = "application/json"))) + + result = cached_json_decode(result) + if (result["error_code"]) + log_debug("byhttp error: [result["error"]] ([result["error_code"]])") + return result["error_code"] + + return list( + "status_code" = result["status_code"], + "body" = result["body"] + ) + +/decl/webhook/proc/send(var/list/data) + var/list/message = get_message(data) + if(!length(message)) + return FALSE + + if(config.disable_webhook_embeds) + var/list/embed_content + for(var/list/embed in message["embeds"]) + if(embed["title"]) + LAZYADD(embed_content, "**[embed["title"]]**") + if(embed["description"]) + LAZYADD(embed_content, embed["description"]) + if(length(embed_content)) + if(message["content"]) + message["content"] = "[message["content"]]\n[jointext(embed_content, "\n")]" + else + message["content"] = jointext(embed_content, "\n") + message -= "embeds" + + . = TRUE + for(var/target_url in urls) + + var/url_message = message.Copy() + var/list/url_mentions = get_mentions(target_url) + if(islist(url_mentions) && length(url_mentions)) + if(url_message["content"]) + url_message["content"] = "[jointext(url_mentions, ", ")]: [url_message["content"]]" + else + url_message["content"] = "[jointext(url_mentions, ", ")]" + + var/list/httpresponse = http_post(target_url, json_encode(url_message)) + if(!islist(httpresponse)) + . = FALSE + continue + switch(httpresponse["status_code"]) + if (200 to 299) + continue + if (400 to 599) + log_debug("Webhooks: HTTP error code while sending to '[target_url]': [httpresponse["status_code"]]. Data: [httpresponse["body"]].") + else + log_debug("Webhooks: unknown HTTP code while sending to '[target_url]': [httpresponse["status_code"]]. Data: [httpresponse["body"]].") + . = FALSE + +/decl/webhook/proc/get_mentions(var/mentioning_url) + . = mentions?.Copy() + var/url_mentions = LAZYACCESS(urls, mentioning_url) + if(length(url_mentions)) + LAZYDISTINCTADD(., url_mentions) diff --git a/code/modules/webhooks/webhook_ahelp2discord.dm b/code/modules/webhooks/webhook_ahelp2discord.dm new file mode 100644 index 0000000000..34241709b7 --- /dev/null +++ b/code/modules/webhooks/webhook_ahelp2discord.dm @@ -0,0 +1,13 @@ +/decl/webhook/ahelp_sent + id = WEBHOOK_AHELP_SENT + +/decl/webhook/ahelp_sent/get_message(var/list/data) + .= ..() + .["embeds"] = list(list( + "title" = "[data["name"]]", + "description" = data["body"], + "color" = data["color"] || COLOR_WEBHOOK_DEFAULT + )) + +/decl/webhook/ahelp_sent/get_mentions() + . = !length(GLOB.admins) && ..() // VOREStation Edit - GLOB admins \ No newline at end of file diff --git a/code/modules/webhooks/webhook_custom_event.dm b/code/modules/webhooks/webhook_custom_event.dm new file mode 100644 index 0000000000..5f636db256 --- /dev/null +++ b/code/modules/webhooks/webhook_custom_event.dm @@ -0,0 +1,11 @@ +/decl/webhook/custom_event + id = WEBHOOK_CUSTOM_EVENT + +// Data expects a "text" field containing the new custom event text. +/decl/webhook/custom_event/get_message(var/list/data) + . = ..() + .["embeds"] = list(list( + "title" = "A custom event is beginning.", + "description" = (data && data["text"]) || "undefined", + "color" = COLOR_WEBHOOK_DEFAULT + )) diff --git a/code/modules/webhooks/webhook_fax2discord.dm b/code/modules/webhooks/webhook_fax2discord.dm new file mode 100644 index 0000000000..336a01d150 --- /dev/null +++ b/code/modules/webhooks/webhook_fax2discord.dm @@ -0,0 +1,10 @@ +/decl/webhook/fax_sent + id = WEBHOOK_FAX_SENT + +/decl/webhook/fax_sent/get_message(var/list/data) + .= ..() + .["embeds"] = list(list( + "title" = "[data["name"]]", + "description" = data["body"], + "color" = COLOR_WEBHOOK_DEFAULT + )) \ No newline at end of file diff --git a/code/modules/webhooks/webhook_roundend.dm b/code/modules/webhooks/webhook_roundend.dm new file mode 100644 index 0000000000..9c806e5a1d --- /dev/null +++ b/code/modules/webhooks/webhook_roundend.dm @@ -0,0 +1,26 @@ +/decl/webhook/roundend + id = WEBHOOK_ROUNDEND + +// Data expects three numerical fields: "survivors", "escaped", "ghosts", "clients" +/decl/webhook/roundend/get_message(var/list/data) + . = ..() + var/desc = "A round of **[SSticker.mode ? SSticker.mode.name : "Unknown"]** ([game_id]) has ended.\n\n" + if(data) + var/s_escaped = "Escaped" + if(!emergency_shuttle.evac) + s_escaped = "Transferred" + if(data["survivors"] > 0) + desc += "Survivors: **[data["survivors"]]**\n" + desc += "[s_escaped]: **[data["escaped"]]**\n" + else + desc += "There were **no survivors**.\n\n" + desc += "Ghosts: **[data["ghosts"]]**\n" + desc += "Players: **[data["clients"]]**\n" + desc += "Round duration: **[roundduration2text()]**" + + .["embeds"] = list(list( + // "title" = global.end_credits_title, + "title" = "Round Has Ended", + "description" = desc, + "color" = COLOR_WEBHOOK_DEFAULT + )) diff --git a/code/modules/webhooks/webhook_roundprep.dm b/code/modules/webhooks/webhook_roundprep.dm new file mode 100644 index 0000000000..b10580452f --- /dev/null +++ b/code/modules/webhooks/webhook_roundprep.dm @@ -0,0 +1,17 @@ +/decl/webhook/roundprep + id = WEBHOOK_ROUNDPREP + +// Data expects "url" and field pointing to the current hosted server and port to connect on. +/decl/webhook/roundprep/get_message(var/list/data) + . = ..() + var/desc = "The server has been started!\n" + if(data && data["map"]) + desc += "Map: **[data["map"]]**\n" + if(data && data["url"]) + desc += "Address: <[data["url"]]>" + + .["embeds"] = list(list( + "title" = "New round is being set up.", + "description" = desc, + "color" = COLOR_WEBHOOK_DEFAULT + )) diff --git a/code/modules/webhooks/webhook_roundstart.dm b/code/modules/webhooks/webhook_roundstart.dm new file mode 100644 index 0000000000..f4afaa2bc4 --- /dev/null +++ b/code/modules/webhooks/webhook_roundstart.dm @@ -0,0 +1,16 @@ +/decl/webhook/roundstart + id = WEBHOOK_ROUNDSTART + +// Data expects a "url" field pointing to the current hosted server and port to connect on. +/decl/webhook/roundstart/get_message(var/list/data) + . = ..() + var/desc = "Gamemode: **[SSticker.mode.name]**\n" + desc += "Players: **[global.player_list.len]**" + if(data && data["url"]) + desc += "\nAddress: <[data["url"]]>" + + .["embeds"] = list(list( + "title" = "Round has started.", + "description" = desc, + "color" = COLOR_WEBHOOK_DEFAULT + )) diff --git a/config/example/webhooks.json b/config/example/webhooks.json new file mode 100644 index 0000000000..fc8ea883fb --- /dev/null +++ b/config/example/webhooks.json @@ -0,0 +1,12 @@ +[ + { + "id" : "webhook_roundend", + "url" : { + "someurl0" : [], + "someurl1" : [], + "someurl2" : "somemention0", + "someurl3" : [ "somemention1", "somemention2" ] + }, + "mentions" : [ "somemention3", "somemention4" ] + } +] diff --git a/vorestation.dme b/vorestation.dme index 6332f290b7..3ebccc932a 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -103,6 +103,7 @@ #include "code\__defines\unit_tests.dm" #include "code\__defines\vote.dm" #include "code\__defines\vv.dm" +#include "code\__defines\webhooks.dm" #include "code\__defines\wires.dm" #include "code\__defines\xenoarcheaology.dm" #include "code\__defines\ZAS.dm" @@ -144,7 +145,6 @@ #include "code\_helpers\storage.dm" #include "code\_helpers\string_lists.dm" #include "code\_helpers\text.dm" -#include "code\_helpers\text_vr.dm" #include "code\_helpers\time.dm" #include "code\_helpers\turfs.dm" #include "code\_helpers\type2type.dm" @@ -308,6 +308,7 @@ #include "code\controllers\subsystems\timer.dm" #include "code\controllers\subsystems\transcore_vr.dm" #include "code\controllers\subsystems\vote.dm" +#include "code\controllers\subsystems\webhooks.dm" #include "code\controllers\subsystems\xenoarch.dm" #include "code\controllers\subsystems\processing\bellies_vr.dm" #include "code\controllers\subsystems\processing\fastprocess.dm" @@ -4160,6 +4161,13 @@ #include "code\modules\vore\resizing\sizegun_vr.dm" #include "code\modules\vore\smoleworld\smoleworld_vr.dm" #include "code\modules\vore\weight\fitness_machines_vr.dm" +#include "code\modules\webhooks\_webhook.dm" +#include "code\modules\webhooks\webhook_ahelp2discord.dm" +#include "code\modules\webhooks\webhook_custom_event.dm" +#include "code\modules\webhooks\webhook_fax2discord.dm" +#include "code\modules\webhooks\webhook_roundend.dm" +#include "code\modules\webhooks\webhook_roundprep.dm" +#include "code\modules\webhooks\webhook_roundstart.dm" #include "code\modules\xenoarcheaology\anomaly_container.dm" #include "code\modules\xenoarcheaology\boulder.dm" #include "code\modules\xenoarcheaology\effect.dm"