mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-08-26 05:27:39 +01:00
JSON Logging Refactor (#18252)
* First pass * fixes * more fixes * num2hex length changes * pass 2 * fixed warning * looc log fix * . * update tgui * . * . * . * . * perttier * cleanup * . * . * fix token * no * . * . * . * , * modsay eventsay * . --------- Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
@@ -1,18 +1,24 @@
|
||||
#ifndef OVERRIDE_BAN_SYSTEM
|
||||
//Blocks an attempt to connect before even creating our client datum thing.
|
||||
/world/IsBanned(key,address,computer_id)
|
||||
/world/IsBanned(key, address, computer_id, type, real_bans_only=FALSE)
|
||||
if (!key || (!real_bans_only && (!address || !computer_id)))
|
||||
if(real_bans_only)
|
||||
return FALSE
|
||||
log_access("Failed Login (invalid data): [key] [address]-[computer_id]")
|
||||
return list("reason"="invalid login data", "desc"="Error: Could not check ban status, Please try again. Error message: Your computer provided invalid or blank information to the server on connection (byond username, IP, and Computer ID.) Provided information for reference: Username:'[key]' IP:'[address]' Computer ID:'[computer_id]'. (If you continue to get this error, please restart byond or contact byond support.)")
|
||||
|
||||
if(ckey(key) in GLOB.admin_datums)
|
||||
return ..()
|
||||
|
||||
//Guest Checking
|
||||
if(!CONFIG_GET(flag/guests_allowed) && IsGuestKey(key))
|
||||
log_adminwarn("Failed Login: [key] - Guests not allowed")
|
||||
log_access("Failed Login: [key] - Guests not allowed")
|
||||
message_admins(span_blue("Failed Login: [key] - Guests not allowed"))
|
||||
return list("reason"="guest", "desc"="\nReason: Guests not allowed. Please sign in with a byond account.")
|
||||
|
||||
//check if the IP address is a known TOR node
|
||||
if(config && CONFIG_GET(flag/ToRban) && ToRban_isbanned(address))
|
||||
log_adminwarn("Failed Login: [src] - Banned: ToR")
|
||||
log_access("Failed Login: [src] - Banned: ToR")
|
||||
message_admins(span_blue("Failed Login: [src] - Banned: ToR"))
|
||||
//ban their computer_id and ckey for posterity
|
||||
AddBan(ckey(key), computer_id, "Use of ToR", "Automated Ban", 0, 0)
|
||||
@@ -24,7 +30,7 @@
|
||||
//Ban Checking
|
||||
. = CheckBan( ckey(key), computer_id, address )
|
||||
if(.)
|
||||
log_adminwarn("Failed Login: [key] [computer_id] [address] - Banned [.["reason"]]")
|
||||
log_suspicious_login("Failed Login: [key] [computer_id] [address] - Banned [.["reason"]]")
|
||||
message_admins(span_blue("Failed Login: [key] id:[computer_id] ip:[address] - Banned [.["reason"]]"))
|
||||
return .
|
||||
|
||||
@@ -35,8 +41,9 @@
|
||||
var/ckeytext = ckey(key)
|
||||
|
||||
if(!establish_db_connection())
|
||||
error("Ban database connection failure. Key [ckeytext] not checked")
|
||||
log_misc("Ban database connection failure. Key [ckeytext] not checked")
|
||||
var/msg = "Ban database connection failure. Key [ckeytext] not checked"
|
||||
log_world(msg)
|
||||
message_admins(msg)
|
||||
return
|
||||
|
||||
var/failedcid = 1
|
||||
@@ -53,7 +60,7 @@
|
||||
if(isnum(text2num(computer_id)))
|
||||
cidquery = " OR computerid = '[computer_id]' "
|
||||
else
|
||||
log_misc("Key [ckeytext] cid not checked. Non-Numeric: [computer_id]")
|
||||
log_world("Key [ckeytext] cid not checked. Non-Numeric: [computer_id]")
|
||||
failedcid = 1
|
||||
|
||||
var/datum/db_query/query = SSdbcore.NewQuery("SELECT ckey, ip, computerid, a_ckey, reason, expiration_time, duration, bantime, bantype FROM erro_ban WHERE (ckey = '[ckeytext]' [ipquery] [cidquery]) AND (bantype = 'PERMABAN' OR (bantype = 'TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)")
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
|
||||
/proc/ToRban_update()
|
||||
spawn(0)
|
||||
log_misc("Downloading updated ToR data...")
|
||||
log_world("Downloading updated ToR data...")
|
||||
var/http[] = world.Export("https://check.torproject.org/exit-addresses")
|
||||
|
||||
var/list/rawlist = file2list(http["CONTENT"])
|
||||
var/list/rawlist = world.file2list(http["CONTENT"])
|
||||
if(rawlist.len)
|
||||
fdel(TORFILE)
|
||||
var/savefile/F = new(TORFILE)
|
||||
@@ -36,11 +36,11 @@
|
||||
if(!cleaned) continue
|
||||
F[cleaned] << 1
|
||||
F["last_update"] << world.realtime
|
||||
log_misc("ToR data updated!")
|
||||
log_world("ToR data updated!")
|
||||
if(usr)
|
||||
to_chat(usr, span_filter_adminlog("ToRban updated."))
|
||||
return
|
||||
log_misc("ToR data update aborted: no data.")
|
||||
log_world("ToR data update aborted: no data.")
|
||||
return
|
||||
|
||||
/client/proc/ToRban(task in list("update","toggle","show","remove","remove all","find"))
|
||||
|
||||
+24
-24
@@ -4,7 +4,7 @@ GLOBAL_VAR_INIT(floorIsLava, 0)
|
||||
////////////////////////////////
|
||||
/proc/message_admins(var/msg)
|
||||
msg = span_filter_adminlog(span_log_message(span_prefix("ADMIN LOG:") + span_message("[msg]")))
|
||||
//log_adminwarn(msg) //log_and_message_admins is for this
|
||||
//log_admin_private(msg) //log_and_message_admins is for this
|
||||
|
||||
for(var/client/C in GLOB.admins)
|
||||
if(check_rights_for(C, (R_ADMIN|R_MOD|R_SERVER)))
|
||||
@@ -542,8 +542,8 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_HOLDER, "Show Player Panel", m
|
||||
else
|
||||
dat+="I'm sorry to break your immersion. This shit's bugged. Report this bug to Agouri, polyxenitopalidou@gmail.com"
|
||||
|
||||
//to_world("Channelname: [src.admincaster_feed_channel.channel_name] [src.admincaster_feed_channel.author]")
|
||||
//to_world("Msg: [src.admincaster_feed_message.author] [src.admincaster_feed_message.body]")
|
||||
//to_chat(world, "Channelname: [src.admincaster_feed_channel.channel_name] [src.admincaster_feed_channel.author]")
|
||||
//to_chat(world, "Msg: [src.admincaster_feed_message.author] [src.admincaster_feed_message.body]")
|
||||
|
||||
var/datum/browser/popup = new(owner, "admincaster_main", "Admin Newscaster", 400, 600)
|
||||
popup.add_head_content("<TITLE>Admin Newscaster</TITLE>")
|
||||
@@ -830,9 +830,9 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
|
||||
CONFIG_SET(flag/ooc_allowed, !CONFIG_GET(flag/ooc_allowed))
|
||||
if (CONFIG_GET(flag/ooc_allowed))
|
||||
to_world(span_world("The OOC channel has been globally enabled!"))
|
||||
to_chat(world, span_world("The OOC channel has been globally enabled!"))
|
||||
else
|
||||
to_world(span_world("The OOC channel has been globally disabled!"))
|
||||
to_chat(world, span_world("The OOC channel has been globally disabled!"))
|
||||
log_and_message_admins("toggled OOC.")
|
||||
feedback_add_details("admin_verb","TOOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
@@ -846,9 +846,9 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
|
||||
CONFIG_SET(flag/looc_allowed, !CONFIG_GET(flag/looc_allowed))
|
||||
if (CONFIG_GET(flag/looc_allowed))
|
||||
to_world(span_world("The LOOC channel has been globally enabled!"))
|
||||
to_chat(world, span_world("The LOOC channel has been globally enabled!"))
|
||||
else
|
||||
to_world(span_world("The LOOC channel has been globally disabled!"))
|
||||
to_chat(world, span_world("The LOOC channel has been globally disabled!"))
|
||||
log_and_message_admins("toggled LOOC.")
|
||||
feedback_add_details("admin_verb","TLOOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
@@ -863,9 +863,9 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
|
||||
CONFIG_SET(flag/dsay_allowed, !CONFIG_GET(flag/dsay_allowed))
|
||||
if (CONFIG_GET(flag/dsay_allowed))
|
||||
to_world(span_world("Deadchat has been globally enabled!"))
|
||||
to_chat(world, span_world("Deadchat has been globally enabled!"))
|
||||
else
|
||||
to_world(span_world("Deadchat has been globally disabled!"))
|
||||
to_chat(world, span_world("Deadchat has been globally disabled!"))
|
||||
log_admin("[key_name(usr)] toggled deadchat.")
|
||||
message_admins("[key_name_admin(usr)] toggled deadchat.", 1)
|
||||
feedback_add_details("admin_verb","TDSAY") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc
|
||||
@@ -925,7 +925,7 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
feedback_add_details("admin_verb","SN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
else
|
||||
SSticker.start_immediately = FALSE
|
||||
to_world(span_filter_system(span_blue("Immediate game start canceled. Normal startup resumed.")))
|
||||
to_chat(world, span_filter_system(span_blue("Immediate game start canceled. Normal startup resumed.")))
|
||||
log_and_message_admins("cancelled immediate game start.")
|
||||
|
||||
/datum/admins/proc/toggleenter()
|
||||
@@ -934,9 +934,9 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
set name="Toggle Entering"
|
||||
CONFIG_SET(flag/enter_allowed, !CONFIG_GET(flag/enter_allowed))
|
||||
if (!CONFIG_GET(flag/enter_allowed))
|
||||
to_world(span_world("New players may no longer enter the game."))
|
||||
to_chat(world, span_world("New players may no longer enter the game."))
|
||||
else
|
||||
to_world(span_world("New players may now enter the game."))
|
||||
to_chat(world, span_world("New players may now enter the game."))
|
||||
log_admin("[key_name(usr)] toggled new player game entering.")
|
||||
message_admins(span_blue("[key_name_admin(usr)] toggled new player game entering."), 1)
|
||||
world.update_status()
|
||||
@@ -948,9 +948,9 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
set name="Toggle AI"
|
||||
CONFIG_SET(flag/allow_ai, !CONFIG_GET(flag/allow_ai))
|
||||
if (!CONFIG_GET(flag/allow_ai))
|
||||
to_world(span_world("The AI job is no longer chooseable."))
|
||||
to_chat(world, span_world("The AI job is no longer chooseable."))
|
||||
else
|
||||
to_world(span_world("The AI job is chooseable now."))
|
||||
to_chat(world, span_world("The AI job is chooseable now."))
|
||||
log_admin("[key_name(usr)] toggled AI allowed.")
|
||||
world.update_status()
|
||||
feedback_add_details("admin_verb","TAI") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
@@ -961,9 +961,9 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
set name="Toggle Respawn"
|
||||
CONFIG_SET(flag/abandon_allowed, !CONFIG_GET(flag/abandon_allowed))
|
||||
if(CONFIG_GET(flag/abandon_allowed))
|
||||
to_world(span_world("You may now respawn."))
|
||||
to_chat(world, span_world("You may now respawn."))
|
||||
else
|
||||
to_world(span_world("You may no longer respawn :("))
|
||||
to_chat(world, span_world("You may no longer respawn :("))
|
||||
message_admins(span_blue("[key_name_admin(usr)] toggled respawn to [CONFIG_GET(flag/abandon_allowed) ? "On" : "Off"]."), 1)
|
||||
log_admin("[key_name(usr)] toggled respawn to [CONFIG_GET(flag/abandon_allowed) ? "On" : "Off"].")
|
||||
world.update_status()
|
||||
@@ -985,9 +985,9 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
set name="Toggle Mapload Persistent Data"
|
||||
CONFIG_SET(flag/persistence_ignore_mapload, !CONFIG_GET(flag/persistence_ignore_mapload))
|
||||
if(!CONFIG_GET(flag/persistence_ignore_mapload))
|
||||
to_world(span_world("Persistence is now enabled."))
|
||||
to_chat(world, span_world("Persistence is now enabled."))
|
||||
else
|
||||
to_world(span_world("Persistence is no longer enabled."))
|
||||
to_chat(world, span_world("Persistence is no longer enabled."))
|
||||
message_admins(span_blue("[key_name_admin(usr)] toggled persistence to [CONFIG_GET(flag/persistence_ignore_mapload) ? "Off" : "On"]."), 1)
|
||||
log_admin("[key_name(usr)] toggled persistence to [CONFIG_GET(flag/persistence_ignore_mapload) ? "Off" : "On"].")
|
||||
world.update_status()
|
||||
@@ -1024,10 +1024,10 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
return
|
||||
GLOB.round_progressing = !GLOB.round_progressing
|
||||
if (!GLOB.round_progressing)
|
||||
to_world(span_world("The game start has been delayed."))
|
||||
to_chat(world, span_world("The game start has been delayed."))
|
||||
log_admin("[key_name(usr)] delayed the game.")
|
||||
else
|
||||
to_world(span_world("The game will start soon."))
|
||||
to_chat(world, span_world("The game will start soon."))
|
||||
log_admin("[key_name(usr)] removed the delay.")
|
||||
feedback_add_details("admin_verb","DELAY") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
@@ -1294,9 +1294,9 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
set name="Toggle tinted welding helmets."
|
||||
CONFIG_SET(flag/welder_vision, !CONFIG_GET(flag/welder_vision))
|
||||
if (CONFIG_GET(flag/welder_vision))
|
||||
to_world(span_world("Reduced welder vision has been enabled!"))
|
||||
to_chat(world, span_world("Reduced welder vision has been enabled!"))
|
||||
else
|
||||
to_world(span_world("Reduced welder vision has been disabled!"))
|
||||
to_chat(world, span_world("Reduced welder vision has been disabled!"))
|
||||
log_admin("[key_name(usr)] toggled welder vision.")
|
||||
message_admins("[key_name_admin(usr)] toggled welder vision.", 1)
|
||||
feedback_add_details("admin_verb","TTWH") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
@@ -1307,9 +1307,9 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
set name="Toggle guests"
|
||||
CONFIG_SET(flag/guests_allowed, !CONFIG_GET(flag/guests_allowed))
|
||||
if (!CONFIG_GET(flag/guests_allowed))
|
||||
to_world(span_world("Guests may no longer enter the game."))
|
||||
to_chat(world, span_world("Guests may no longer enter the game."))
|
||||
else
|
||||
to_world(span_world("Guests may now enter the game."))
|
||||
to_chat(world, span_world("Guests may now enter the game."))
|
||||
log_admin("[key_name(usr)] toggled guests game entering [CONFIG_GET(flag/guests_allowed)?"":"dis"]allowed.")
|
||||
message_admins(span_blue("[key_name_admin(usr)] toggled guests game entering [CONFIG_GET(flag/guests_allowed)?"":"dis"]allowed."), 1)
|
||||
feedback_add_details("admin_verb","TGU") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
@@ -207,7 +207,6 @@ var/list/admin_verbs_debug = list(
|
||||
/client/proc/hide_verbs, //hides all our adminverbs,
|
||||
/client/proc/hide_most_verbs, //hides all our hideable adminverbs,
|
||||
/client/proc/cmd_check_new_players, //allows us to see every new player,
|
||||
/datum/admins/proc/view_runtimes,
|
||||
// /client/proc/show_gm_status, //We don't use SSgame_master yet.
|
||||
/datum/admins/proc/set_uplink,
|
||||
/datum/admins/proc/change_weather,
|
||||
|
||||
@@ -191,6 +191,10 @@ ADMIN_VERB(game_panel, R_ADMIN|R_SERVER|R_FUN, "Game Panel", "Look at the state
|
||||
user.holder.Game()
|
||||
feedback_add_details("admin_verb","GP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/// Returns this client's stealthed ckey
|
||||
/client/proc/getStealthKey()
|
||||
return GLOB.stealthminID[ckey]
|
||||
|
||||
/client/proc/findStealthKey(txt)
|
||||
if(txt)
|
||||
for(var/P in GLOB.stealthminID)
|
||||
|
||||
@@ -70,8 +70,7 @@ DEBUG
|
||||
log_admin("jobban_keylist was empty")
|
||||
else
|
||||
if(!establish_db_connection())
|
||||
error("Database connection failed. Reverting to the legacy ban system.")
|
||||
log_misc("Database connection failed. Reverting to the legacy ban system.")
|
||||
log_sql("Database connection failed. Reverting to the legacy ban system.")
|
||||
CONFIG_SET(flag/ban_legacy_system, TRUE)
|
||||
jobban_loadbanfile()
|
||||
return
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
|
||||
body += "<a href='byond://?src=\ref[src];[HrefToken()];adminplayeropts="+ref+"'>PP</a> - "
|
||||
body += "<a href='byond://?src=\ref[src];[HrefToken()];notes=show;mob="+ref+"'>N</a> - "
|
||||
body += "<a href='byond://?_src_=vars;Vars="+ref+"'>VV</a> - "
|
||||
body += "<a href='byond://?_src_=vars;[HrefToken()];Vars="+ref+"'>VV</a> - "
|
||||
body += "<a href='byond://?src=\ref[src];[HrefToken()];traitor="+ref+"'>TP</a> - "
|
||||
body += "<a href='byond://?src=\ref[usr];[HrefToken()];priv_msg=\ref"+ref+"'>PM</a> - "
|
||||
body += "<a href='byond://?src=\ref[src];[HrefToken()];subtlemessage="+ref+"'>SM</a> - "
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
var/msg = !auth ? "no" : "a bad"
|
||||
message_admins("[key_name_admin(usr)] clicked an href with [msg] authorization key!")
|
||||
|
||||
/* Debug code in case one needs to dig missing token HREFS
|
||||
var/debug_admin_hrefs = TRUE // Remove once everything is converted over
|
||||
if(debug_admin_hrefs)
|
||||
message_admins("Debug mode enabled, call not blocked. Please ask your coders to review this round's logs.")
|
||||
log_world("UAH: [href]")
|
||||
return TRUE
|
||||
*/
|
||||
|
||||
log_admin("[key_name(usr)] clicked an href with [msg] authorization key! [href]")
|
||||
|
||||
@@ -864,7 +866,7 @@
|
||||
GLOB.master_mode = href_list["c_mode2"]
|
||||
log_admin("[key_name(usr)] set the mode as [config.mode_names[GLOB.master_mode]].")
|
||||
message_admins(span_blue("[key_name_admin(usr)] set the mode as [config.mode_names[GLOB.master_mode]]."), 1)
|
||||
to_world(span_world(span_blue("The mode is now: [config.mode_names[GLOB.master_mode]]")))
|
||||
to_chat(world, span_world(span_blue("The mode is now: [config.mode_names[GLOB.master_mode]]")))
|
||||
Game() // updates the main game menu
|
||||
world.save_mode(GLOB.master_mode)
|
||||
.(href, list("c_mode"=1))
|
||||
|
||||
@@ -31,4 +31,4 @@
|
||||
var/F = file("broken_icons.txt")
|
||||
fdel(F)
|
||||
F << text
|
||||
to_world(span_filter_system("Completeled successfully and written to [F]"))
|
||||
to_chat(world, span_filter_system("Completeled successfully and written to [F]"))
|
||||
|
||||
@@ -3,7 +3,7 @@ ADMIN_VERB(cmd_admin_say, R_ADMIN, "ASay", "Send a message to other admins", "Ad
|
||||
if(!msg)
|
||||
return
|
||||
|
||||
log_adminsay(msg, user)
|
||||
user.mob.log_talk(message, LOG_ASAY)
|
||||
|
||||
for(var/client/C in GLOB.admins)
|
||||
if(check_rights_for(C, R_ADMIN))
|
||||
@@ -13,7 +13,7 @@ ADMIN_VERB(cmd_admin_say, R_ADMIN, "ASay", "Send a message to other admins", "Ad
|
||||
|
||||
ADMIN_VERB(cmd_mod_say, (R_ADMIN|R_MOD|R_SERVER), "Msay", "Send a message to other mod", "Admin.Chat", message as text)
|
||||
var/msg = sanitize(message)
|
||||
log_modsay(msg, user)
|
||||
log_modsay(msg, list("speaker" = user))
|
||||
|
||||
if (!msg)
|
||||
return
|
||||
@@ -29,7 +29,7 @@ ADMIN_VERB(cmd_mod_say, (R_ADMIN|R_MOD|R_SERVER), "Msay", "Send a message to oth
|
||||
|
||||
ADMIN_VERB(cmd_event_say, (R_ADMIN|R_MOD|R_EVENT|R_SERVER), "Esay", "Send a message to other event manager", "Admin.Chat", message as text)
|
||||
var/msg = sanitize(message)
|
||||
log_eventsay(msg, user)
|
||||
log_eventsay(msg, list("speaker" = user))
|
||||
|
||||
if (!msg)
|
||||
return
|
||||
|
||||
@@ -39,4 +39,4 @@
|
||||
if((M.mind && M.mind.special_role && A && A.can_hear_aooc) || isobserver(M)) // Antags must have their type be allowed to AOOC to see AOOC. This prevents, say, ERT from seeing AOOC.
|
||||
to_chat(M, span_ooc(span_aooc("[create_text_tag("aooc", "Antag-OOC:", M.client)] <EM>[player_display]:</EM> " + span_message("[msg]"))))
|
||||
|
||||
log_aooc(msg,src)
|
||||
src.mob.log_talk("(AOOC) [msg]", LOG_OOC)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ADMIN_VERB(cinematic, R_FUN, "Cinematic", "Show a cinematic to all players.", ADMIN_CATEGORY_FUN)
|
||||
ADMIN_VERB(cinematic, R_FUN, "Cinematic", "Show a cinematic to all players.", "Fun.Do Not")
|
||||
var/datum/cinematic/choice = tgui_input_list(
|
||||
user,
|
||||
"Chose a cinematic to play to everyone in the server.",
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
|
||||
GLOB.custom_event_msg = input
|
||||
|
||||
to_world(span_filter_system("<h1>[span_alert("Custom Event")]</h1>"))
|
||||
to_world(span_filter_system("<h2>[span_alert("A custom event is starting. OOC Info:")]</h2>"))
|
||||
to_world(span_filter_system(span_alert("[GLOB.custom_event_msg]")))
|
||||
to_world(span_filter_system("<br>"))
|
||||
to_chat(world, span_filter_system("<h1>[span_alert("Custom Event")]</h1>"))
|
||||
to_chat(world, span_filter_system("<h2>[span_alert("A custom event is starting. OOC Info:")]</h2>"))
|
||||
to_chat(world, span_filter_system(span_alert("[GLOB.custom_event_msg]")))
|
||||
to_chat(world, span_filter_system("<br>"))
|
||||
|
||||
SSwebhooks.send(
|
||||
WEBHOOK_CUSTOM_EVENT,
|
||||
|
||||
@@ -393,33 +393,33 @@ ADMIN_VERB(cmd_assume_direct_control, (R_DEBUG|R_ADMIN|R_EVENT), "Assume Direct
|
||||
var/list/areas_without_intercom = areas_all - areas_with_intercom
|
||||
var/list/areas_without_camera = areas_all - areas_with_camera
|
||||
|
||||
to_world(span_bold("AREAS WITHOUT AN APC:"))
|
||||
to_chat(world, span_bold("AREAS WITHOUT AN APC:"))
|
||||
for(var/areatype in areas_without_APC)
|
||||
to_world("* [areatype]")
|
||||
to_chat(world, "* [areatype]")
|
||||
|
||||
to_world(span_bold("AREAS WITHOUT AN AIR ALARM:"))
|
||||
to_chat(world, span_bold("AREAS WITHOUT AN AIR ALARM:"))
|
||||
for(var/areatype in areas_without_air_alarm)
|
||||
to_world("* [areatype]")
|
||||
to_chat(world, "* [areatype]")
|
||||
|
||||
to_world(span_bold("AREAS WITHOUT A REQUEST CONSOLE:"))
|
||||
to_chat(world, span_bold("AREAS WITHOUT A REQUEST CONSOLE:"))
|
||||
for(var/areatype in areas_without_RC)
|
||||
to_world("* [areatype]")
|
||||
to_chat(world, "* [areatype]")
|
||||
|
||||
to_world(span_bold("AREAS WITHOUT ANY LIGHTS:"))
|
||||
to_chat(world, span_bold("AREAS WITHOUT ANY LIGHTS:"))
|
||||
for(var/areatype in areas_without_light)
|
||||
to_world("* [areatype]")
|
||||
to_chat(world, "* [areatype]")
|
||||
|
||||
to_world(span_bold("AREAS WITHOUT A LIGHT SWITCH:"))
|
||||
to_chat(world, span_bold("AREAS WITHOUT A LIGHT SWITCH:"))
|
||||
for(var/areatype in areas_without_LS)
|
||||
to_world("* [areatype]")
|
||||
to_chat(world, "* [areatype]")
|
||||
|
||||
to_world(span_bold("AREAS WITHOUT ANY INTERCOMS:"))
|
||||
to_chat(world, span_bold("AREAS WITHOUT ANY INTERCOMS:"))
|
||||
for(var/areatype in areas_without_intercom)
|
||||
to_world("* [areatype]")
|
||||
to_chat(world, "* [areatype]")
|
||||
|
||||
to_world(span_bold("AREAS WITHOUT ANY CAMERAS:"))
|
||||
to_chat(world, span_bold("AREAS WITHOUT ANY CAMERAS:"))
|
||||
for(var/areatype in areas_without_camera)
|
||||
to_world("* [areatype]")
|
||||
to_chat(world, "* [areatype]")
|
||||
|
||||
/datum/admins/proc/cmd_admin_dress(input in getmobs())
|
||||
set category = "Fun.Event Kit"
|
||||
@@ -624,15 +624,17 @@ ADMIN_VERB(cmd_assume_direct_control, (R_DEBUG|R_ADMIN|R_EVENT), "Assume Direct
|
||||
else
|
||||
tgui_alert_async(usr, "Invalid mob")
|
||||
|
||||
/datum/admins/proc/view_runtimes()
|
||||
set category = "Debug.Investigate"
|
||||
set name = "View Runtimes"
|
||||
set desc = "Open the Runtime Viewer"
|
||||
ADMIN_VERB(view_runtimes, R_DEBUG, "View Runtimes", "Opens the runtime viewer.", ADMIN_CATEGORY_DEBUG)
|
||||
GLOB.error_cache.show_to(user)
|
||||
|
||||
if(!check_rights(R_DEBUG))
|
||||
return
|
||||
|
||||
error_cache.showTo(usr)
|
||||
// The runtime viewer has the potential to crash the server if there's a LOT of runtimes
|
||||
// this has happened before, multiple times, so we'll just leave an alert on it
|
||||
if(GLOB.total_runtimes >= 50000) // arbitrary number, I don't know when exactly it happens
|
||||
var/warning = "There are a lot of runtimes, clicking any button (especially \"linear\") can have the potential to lag or crash the server"
|
||||
if(GLOB.total_runtimes >= 100000)
|
||||
warning = "There are a TON of runtimes, clicking any button (especially \"linear\") WILL LIKELY crash the server"
|
||||
// Not using TGUI alert, because it's view runtimes, stuff is probably broken
|
||||
tgui_alert(user, "[warning]. Proceed with caution. If you really need to see the runtimes, download the runtime log and view it in a text editor.", "HEED THIS WARNING CAREFULLY MORTAL")
|
||||
|
||||
/datum/admins/proc/change_weather()
|
||||
set category = "Debug.Events"
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
var/dice = num2text(sum) + "d" + num2text(side)
|
||||
|
||||
if(tgui_alert(usr, "Do you want to inform the world about your game?","Show world?",list("Yes", "No")) == "Yes")
|
||||
to_world("<h2 style=\"color:#A50400\">The dice have been rolled by Gods!</h2>")
|
||||
to_chat(world, "<h2 style=\"color:#A50400\">The dice have been rolled by Gods!</h2>")
|
||||
|
||||
var/result = roll(dice)
|
||||
|
||||
if(tgui_alert(usr, "Do you want to inform the world about the result?","Show world?",list("Yes", "No")) == "Yes")
|
||||
to_world("<h2 style=\"color:#A50400\">Gods rolled [dice], result is [result]</h2>")
|
||||
to_chat(world, "<h2 style=\"color:#A50400\">Gods rolled [dice], result is [result]</h2>")
|
||||
|
||||
message_admins("[key_name_admin(src)] rolled dice [dice], result is [result]", 1)
|
||||
|
||||
@@ -5,7 +5,7 @@ ADMIN_VERB(fix_atmos, (R_ADMIN|R_DEBUG|R_EVENT), "Fix Atmospherics Grief", "View
|
||||
feedback_add_details("admin_verb","FA")
|
||||
|
||||
log_and_message_admins("Full atmosphere reset initiated by [user].")
|
||||
to_world(span_danger("Initiating restart of atmosphere. The server may lag a bit."))
|
||||
to_chat(world, span_danger("Initiating restart of atmosphere. The server may lag a bit."))
|
||||
sleep(10)
|
||||
var/current_time = world.timeofday
|
||||
|
||||
@@ -43,4 +43,4 @@ ADMIN_VERB(fix_atmos, (R_ADMIN|R_DEBUG|R_EVENT), "Fix Atmospherics Grief", "View
|
||||
SSair.RebootZAS()
|
||||
|
||||
to_chat(user, "\[5/5\] - ZAS Rebooted")
|
||||
to_world(span_danger("Atmosphere restart completed in " + span_bold("[(world.timeofday - current_time)/10]") + " seconds."))
|
||||
to_chat(world, span_danger("Atmosphere restart completed in " + span_bold("[(world.timeofday - current_time)/10]") + " seconds."))
|
||||
|
||||
@@ -310,9 +310,9 @@ var/list/debug_verbs = list (
|
||||
if(i*10+j <= atom_list.len)
|
||||
temp_atom = atom_list[i*10+j]
|
||||
line += " no.[i+10+j]@\[[temp_atom.x], [temp_atom.y], [temp_atom.z]\]; "
|
||||
to_world(line)*/
|
||||
to_chat(world, line)*/
|
||||
|
||||
to_world("There are [count] objects of type [type_path] on z-level [num_level]")
|
||||
to_chat(world, "There are [count] objects of type [type_path] on z-level [num_level]")
|
||||
feedback_add_details("admin_verb","mOBJZ") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/count_objects_all()
|
||||
@@ -337,7 +337,7 @@ var/list/debug_verbs = list (
|
||||
if(i*10+j <= atom_list.len)
|
||||
temp_atom = atom_list[i*10+j]
|
||||
line += " no.[i+10+j]@\[[temp_atom.x], [temp_atom.y], [temp_atom.z]\]; "
|
||||
to_world(line)*/
|
||||
to_chat(world, line)*/
|
||||
|
||||
to_world("There are [count] objects of type [type_path] in the game world")
|
||||
to_chat(world, "There are [count] objects of type [type_path] in the game world")
|
||||
feedback_add_details("admin_verb","mOBJ") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
@@ -103,7 +103,7 @@ var/list/sounds_cache = list()
|
||||
if(!check_rights(R_SOUNDS))
|
||||
return
|
||||
|
||||
var/list/sounds = file2list("sound/serversound_list.txt");
|
||||
var/list/sounds = world.file2list("sound/serversound_list.txt");
|
||||
sounds += "--CANCEL--"
|
||||
sounds += sounds_cache
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
to_chat(src, "Your prayers have been received by the gods.", confidential = TRUE)
|
||||
|
||||
feedback_add_details("admin_verb","PR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
log_pray(raw_msg, src)
|
||||
log_prayer("[src.key]/([src.name]): [raw_msg]")
|
||||
|
||||
/proc/CentCom_announce(var/msg, var/mob/Sender, var/iamessage)
|
||||
msg = span_blue(span_bold(span_orange("[uppertext(using_map.boss_short)]M[iamessage ? " IA" : ""]:") + "[key_name(Sender, 1)] [ADMIN_PP(Sender)] [ADMIN_VV(Sender)] [ADMIN_SM(Sender)] ([admin_jump_link(Sender)]) [ADMIN_CA(Sender)] [ADMIN_BSA(Sender)] [ADMIN_CENTCOM_REPLY(Sender)]:") + " [msg]")
|
||||
|
||||
@@ -124,7 +124,7 @@ ADMIN_VERB(drop_everything, R_ADMIN, "Drop Everything", ADMIN_VERB_NO_DESCRIPTIO
|
||||
if (!msg) // We check both before and after, just in case sanitization ended us up with empty message.
|
||||
return
|
||||
|
||||
to_world("[msg]")
|
||||
to_chat(world, "[msg]")
|
||||
log_admin("GlobalNarrate: [key_name(usr)] : [msg]")
|
||||
message_admins(span_blue(span_bold(" GlobalNarrate: [key_name_admin(usr)] : [msg]<BR>")), 1)
|
||||
feedback_add_details("admin_verb","GLN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
@@ -709,7 +709,7 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp
|
||||
if(confirm == "Yes")
|
||||
command_announcement.Announce(input, customname, new_sound = 'sound/AI/commandreport.ogg', msg_sanitized = 1);
|
||||
else
|
||||
to_world(span_red("New [using_map.company_name] Update available at all communication consoles."))
|
||||
to_chat(world, span_red("New [using_map.company_name] Update available at all communication consoles."))
|
||||
world << sound('sound/AI/commandreport.ogg')
|
||||
|
||||
log_admin("[key_name(src)] has created a command report: [input]")
|
||||
@@ -963,7 +963,7 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp
|
||||
message_admins("Admin [key_name_admin(usr)] has forced the players to have random appearances.", 1)
|
||||
|
||||
if(notifyplayers == "Yes")
|
||||
to_world(span_boldannounce(span_blue("Admin [usr.key] has forced the players to have completely random identities!")))
|
||||
to_chat(world, span_boldannounce(span_blue("Admin [usr.key] has forced the players to have completely random identities!")))
|
||||
|
||||
to_chat(usr, "<i>Remember: you can always disable the randomness by using the verb again, assuming the round hasn't started yet</i>.")
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ GLOBAL_VAR(ert_loaded)
|
||||
GLOB.ert_loaded = TRUE
|
||||
var/datum/map_template/MT = SSmapping.map_templates["Special Area - ERT"]
|
||||
if(!istype(MT))
|
||||
error("ERT Area is not a valid map template!")
|
||||
log_mapping("ERT Area is not a valid map template!")
|
||||
else
|
||||
MT.load_new_z(centered = TRUE)
|
||||
log_and_message_admins("Loaded the ERT shuttle just now.")
|
||||
|
||||
@@ -75,7 +75,7 @@ GLOBAL_VAR(trader_loaded)
|
||||
GLOB.trader_loaded = TRUE
|
||||
var/datum/map_template/MT = SSmapping.map_templates["Special Area - Salamander Trader"] //was: "Special Area - Trader"
|
||||
if(!istype(MT))
|
||||
error("Trader is not a valid map template!")
|
||||
log_mapping("Trader is not a valid map template!")
|
||||
else
|
||||
MT.load_new_z(centered = TRUE)
|
||||
log_and_message_admins("Loaded the trade shuttle just now.")
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
if(ai_holder_type)
|
||||
ai_holder = new ai_holder_type(src)
|
||||
if(!ai_holder)
|
||||
log_debug("[src] could not initialize ai_holder of type [ai_holder_type]")
|
||||
log_runtime("[src] could not initialize ai_holder of type [ai_holder_type]")
|
||||
return
|
||||
if(ishuman(src))
|
||||
var/mob/living/carbon/human/H = src
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
var/stance_coloring = FALSE // Colors the mob depending on its stance.
|
||||
|
||||
var/debug_ai = AI_LOG_OFF // The level of debugging information to display to people who can see log_debug().
|
||||
var/debug_ai = AI_LOG_OFF // The level of debugging information to display to people who can see log_world().
|
||||
|
||||
/datum/ai_holder/New()
|
||||
..()
|
||||
@@ -41,7 +41,7 @@
|
||||
if(AI_LOG_TRACE)
|
||||
span_type = "debug_trace"
|
||||
if(ver <= debug_ai)
|
||||
log_debug("<span class='[span_type]'>AI: ([holder]:\ref[holder] | [holder.x],[holder.y],[holder.z])(@[world.time]): [msg] </span>")
|
||||
log_world("<span class='[span_type]'>AI: ([holder]:\ref[holder] | [holder.x],[holder.y],[holder.z])(@[world.time]): [msg] </span>")
|
||||
|
||||
// Colors the mob based on stance, to visually tell what stance it is for debugging.
|
||||
// Probably not something you want for regular use.
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
return
|
||||
var/atom/movable/AM = WF.resolve()
|
||||
if(isnull(AM))
|
||||
log_debug("DEBUG: HasProximity called without reference on [src].")
|
||||
log_runtime("DEBUG: HasProximity called without reference on [src].")
|
||||
return
|
||||
if(a_left)
|
||||
a_left.HasProximity(T, WF, old_loc)
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
return
|
||||
var/atom/movable/AM = WF.resolve()
|
||||
if(isnull(AM))
|
||||
log_debug("DEBUG: HasProximity called without reference on [src].")
|
||||
log_runtime("DEBUG: HasProximity called without reference on [src].")
|
||||
return
|
||||
if (istype(AM, /obj/effect/beam))
|
||||
return
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
hash = md5(fcopy_rsc(file))
|
||||
if (!hash)
|
||||
CRASH("invalid asset sent to asset cache")
|
||||
log_debug("asset cache unexpected success of second fcopy_rsc")
|
||||
log_runtime("asset cache unexpected success of second fcopy_rsc")
|
||||
src.name = name
|
||||
var/extstart = findlasttext(name, ".")
|
||||
if(extstart)
|
||||
|
||||
@@ -38,7 +38,7 @@ GLOBAL_DATUM(gateway_station, /obj/machinery/gateway/centerstation)
|
||||
|
||||
/obj/machinery/gateway/centerstation/Initialize(mapload)
|
||||
if(GLOB.gateway_station)
|
||||
warning("[src] at [x],[y],[z] appears to be an additional station-gateway")
|
||||
WARNING("[src] at [x],[y],[z] appears to be an additional station-gateway")
|
||||
else
|
||||
GLOB.gateway_station = src
|
||||
|
||||
@@ -256,7 +256,7 @@ GLOBAL_DATUM(gateway_away, /obj/machinery/gateway/centeraway)
|
||||
|
||||
/obj/machinery/gateway/centeraway/Initialize(mapload)
|
||||
if(GLOB.gateway_away)
|
||||
warning("[src] at [x],[y],[z] appears to be an additional away-gateway")
|
||||
WARNING("[src] at [x],[y],[z] appears to be an additional away-gateway")
|
||||
else
|
||||
GLOB.gateway_away = src
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
destination = CL
|
||||
|
||||
if(!destination)
|
||||
warning("A gateway is trying to spawn it's mcguffin but there are no mapped in spawner landmarks")
|
||||
WARNING("A gateway is trying to spawn it's mcguffin but there are no mapped in spawner landmarks")
|
||||
destination = get_turf(src)
|
||||
|
||||
key = new mcguffin_type(destination)
|
||||
|
||||
@@ -7,7 +7,7 @@ possible_descriptors are populated by subtypes of /obj/effect/landmark/overmap_r
|
||||
*/
|
||||
/obj/effect/overmap/visitable/proc/modify_descriptors()
|
||||
if(!possible_descriptors || !islist(possible_descriptors) || possible_descriptors == list() || !length(possible_descriptors))
|
||||
error("List of possible descriptors for [name] was empty!")
|
||||
log_mapping("## ERROR List of possible descriptors for [name] was empty!")
|
||||
return
|
||||
|
||||
var/list/chosen_descriptor = pick(possible_descriptors)
|
||||
@@ -22,7 +22,7 @@ possible_descriptors are populated by subtypes of /obj/effect/landmark/overmap_r
|
||||
// testing("Defaulting to default!") //Uncomment when adding a new landmark to confirm it works OK, but recomment before commiting
|
||||
return
|
||||
if(breakWhile > 10 || length(possible_descriptors) < 1)
|
||||
error("No valid descriptors could be found for [name]!") //Checking default separately for sake of error messages
|
||||
log_mapping("## ERROR No valid descriptors could be found for [name]!") //Checking default separately for sake of error messages
|
||||
return
|
||||
|
||||
//Using real_name to ensure get_scan_data() does not override the renamed code
|
||||
@@ -48,9 +48,9 @@ possible_descriptors are populated by subtypes of /obj/effect/landmark/overmap_r
|
||||
/obj/effect/landmark/overmap_renamer/Initialize(mapload)
|
||||
// testing("Loading renamer landmark: [name]") //Uncomment when adding a new POI/Landmark for testing aid.
|
||||
if(LAZYLEN(descriptors) != 3)
|
||||
error("POI [name] renamer landmark is invalid! Make sure its descriptors var is a list of 3 elements!")
|
||||
log_mapping("## ERROR POI [name] renamer landmark is invalid! Make sure its descriptors var is a list of 3 elements!")
|
||||
return
|
||||
if(!istext(descriptors[1]) || !istext(descriptors[2]) || !istext(descriptors[3]))
|
||||
error("POI [name] renamer landmark is invalid! One of the elements is NOT a string!")
|
||||
log_mapping("## ERROR POI [name] renamer landmark is invalid! One of the elements is NOT a string!")
|
||||
return
|
||||
. = ..()
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
var/list/potentialRandomZlevels = list()
|
||||
admin_notice(span_red(span_bold(" Searching for away missions...")), R_DEBUG)
|
||||
var/list/Lines = file2list("maps/RandomZLevels/fileList.txt")
|
||||
var/list/Lines = world.file2list("maps/RandomZLevels/fileList.txt")
|
||||
if(!Lines.len) return
|
||||
for (var/t in Lines)
|
||||
if (!t)
|
||||
@@ -41,12 +41,12 @@
|
||||
admin_notice(span_red(span_bold("Loading away mission...")), R_DEBUG)
|
||||
|
||||
var/map = pick(potentialRandomZlevels)
|
||||
to_world_log("Away mission picked: [map]") //VOREStation Add for debugging
|
||||
log_mapping("Away mission picked: [map]") //VOREStation Add for debugging
|
||||
var/file = file(map)
|
||||
if(isfile(file))
|
||||
var/datum/map_template/template = new(file, "away mission")
|
||||
template.load_new_z()
|
||||
to_world_log("away mission loaded: [map]")
|
||||
log_mapping("away mission loaded: [map]")
|
||||
/* VOREStation Removal - We do this in the special landmark init instead.
|
||||
for(var/obj/effect/landmark/L in GLOB.landmarks_list)
|
||||
if (L.name != "awaystart")
|
||||
|
||||
@@ -162,5 +162,5 @@ var/list/overminds = list()
|
||||
if(dst <= world.view)
|
||||
O.hear_talk(src, message_pieces, "conveys")
|
||||
|
||||
log_say(message, src)
|
||||
log_talk(message, LOG_SAY)
|
||||
return 1
|
||||
|
||||
@@ -86,6 +86,9 @@
|
||||
///Used for limiting the rate of clicks sends by the client to avoid abuse
|
||||
var/list/clicklimiter
|
||||
|
||||
///these persist between logins/logouts during the same round.
|
||||
var/datum/persistent_client/persistent_client
|
||||
|
||||
////////////////////////////////////
|
||||
//things that require the database//
|
||||
////////////////////////////////////
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
////////////
|
||||
//SECURITY//
|
||||
////////////
|
||||
|
||||
GLOBAL_LIST_INIT(blacklisted_builds, list(
|
||||
"1622" = "Bug breaking rendering can lead to wallhacks.",
|
||||
))
|
||||
|
||||
#define UPLOAD_LIMIT 10485760 //Restricts client uploads to the server to 10MB //Boosted this thing. What's the worst that can happen?
|
||||
#define MIN_CLIENT_VERSION 0 //Just an ambiguously low version for now, I don't want to suddenly stop people playing.
|
||||
//I would just like the code ready should it ever need to be used.
|
||||
@@ -68,7 +73,8 @@
|
||||
if (minute != topiclimiter[ADMINSWARNED_AT]) //only one admin message per-minute. (if they spam the admins can just boot/ban them)
|
||||
topiclimiter[ADMINSWARNED_AT] = minute
|
||||
msg += " Administrators have been informed."
|
||||
log_and_message_admins("[key_name(src)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute", src)
|
||||
log_game("[key_name(src)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute")
|
||||
message_admins("[ADMIN_LOOKUPFLW(usr)] [ADMIN_KICK(usr)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute")
|
||||
to_chat(src, span_danger("[msg]"))
|
||||
return
|
||||
|
||||
@@ -87,7 +93,7 @@
|
||||
|
||||
//search the href for script injection
|
||||
if( findtext(href,"<script",1,0) )
|
||||
to_world_log("Attempted use of scripts within a topic call, by [src]")
|
||||
log_world("Attempted use of scripts within a topic call, by [src]")
|
||||
message_admins("Attempted use of scripts within a topic call, by [src]")
|
||||
return
|
||||
|
||||
@@ -155,8 +161,7 @@
|
||||
stat_panel.reinitialize()
|
||||
|
||||
//Logs all hrefs
|
||||
if(config && CONFIG_GET(flag/log_hrefs) && GLOB.href_logfile)
|
||||
WRITE_LOG(GLOB.href_logfile, "[src] (usr:[usr])</small> || [hsrc ? "[hsrc] " : ""][href]")
|
||||
log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]")
|
||||
|
||||
//byond bug ID:2256651
|
||||
if (asset_cache_job && (asset_cache_job in completed_asset_jobs))
|
||||
@@ -223,9 +228,12 @@
|
||||
//CONNECT//
|
||||
///////////
|
||||
/client/New(TopicData)
|
||||
winset(src, null, "browser-options=[DEFAULT_CLIENT_BROWSER_OPTIONS]")
|
||||
TopicData = null //Prevent calls to client.Topic from connect
|
||||
|
||||
TopicData = null //Prevent calls to client.Topic from connect
|
||||
if(connection != "seeker" && connection != "web")//Invalid connection type.
|
||||
return null
|
||||
|
||||
winset(src, null, "browser-options=[DEFAULT_CLIENT_BROWSER_OPTIONS]")
|
||||
|
||||
if(!(connection in list("seeker", "web"))) //Invalid connection type.
|
||||
return null
|
||||
@@ -244,6 +252,14 @@
|
||||
GLOB.clients += src
|
||||
GLOB.directory[ckey] = src
|
||||
|
||||
//var/reconnecting = FALSE we are not using this var yet
|
||||
if(GLOB.persistent_clients_by_ckey[ckey])
|
||||
//reconnecting = TRUE
|
||||
persistent_client = GLOB.persistent_clients_by_ckey[ckey]
|
||||
else
|
||||
persistent_client = new(ckey)
|
||||
persistent_client.set_client(src)
|
||||
|
||||
if (CONFIG_GET(flag/chatlog_database_backend))
|
||||
chatlog_token = vchatlog_generate_token(ckey, GLOB.round_id)
|
||||
|
||||
@@ -259,12 +275,6 @@
|
||||
|
||||
GLOB.tickets.ClientLogin(src)
|
||||
|
||||
//Admin Authorisation
|
||||
holder = GLOB.admin_datums[ckey]
|
||||
if(holder)
|
||||
GLOB.admins += src
|
||||
holder.owner = src
|
||||
|
||||
//preferences datum - also holds some persistant data for the client (because we may as well keep these datums to a minimum)
|
||||
prefs = preferences_datums[ckey]
|
||||
if(prefs)
|
||||
@@ -277,9 +287,42 @@
|
||||
prefs.last_ip = address //these are gonna be used for banning
|
||||
prefs.last_id = computer_id //these are gonna be used for banning
|
||||
|
||||
var/full_version = "[byond_version].[byond_build ? byond_build : "xxx"]"
|
||||
log_access("Login: [key_name(src)] from [address ? address : "localhost"]-[computer_id] || BYOND v[full_version]")
|
||||
|
||||
prefs_vr = new/datum/vore_preferences(src)
|
||||
|
||||
. = ..() //calls mob.Login()
|
||||
|
||||
// Admin Verbs need the client's mob to exist. Must be after ..()
|
||||
var/connecting_admin = FALSE //because de-admined admins connecting should be treated like admins.
|
||||
//Admin Authorisation
|
||||
var/datum/admins/admin_datum = GLOB.admin_datums[ckey]
|
||||
if (!isnull(admin_datum))
|
||||
admin_datum.associate(src)
|
||||
connecting_admin = TRUE
|
||||
else if(GLOB.deadmins[ckey])
|
||||
add_verb(src, /client/proc/readmin)
|
||||
connecting_admin = TRUE
|
||||
|
||||
if (byond_version >= 512)
|
||||
if (!byond_build || byond_build < 1386)
|
||||
message_admins(span_adminnotice("[key_name(src)] has been detected as spoofing their byond version. Connection rejected."))
|
||||
//add_system_note("Spoofed-Byond-Version", "Detected as using a spoofed byond version.")
|
||||
log_suspicious_login("Failed Login: [key] - Spoofed byond version")
|
||||
qdel(src)
|
||||
|
||||
if (num2text(byond_build) in GLOB.blacklisted_builds)
|
||||
log_access("Failed login: [key] - blacklisted byond version")
|
||||
to_chat_immediate(src, span_userdanger("Your version of byond is blacklisted."))
|
||||
to_chat_immediate(src, span_danger("Byond build [byond_build] ([byond_version].[byond_build]) has been blacklisted for the following reason: [GLOB.blacklisted_builds[num2text(byond_build)]]."))
|
||||
to_chat_immediate(src, span_danger("Please download a new version of byond. If [byond_build] is the latest, you can go to <a href=\"https://secure.byond.com/download/build\">BYOND's website</a> to download other versions."))
|
||||
if(connecting_admin)
|
||||
to_chat_immediate(src, "As an admin, you are being allowed to continue using this version, but please consider changing byond versions")
|
||||
else
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
prefs.sanitize_preferences()
|
||||
if(prefs)
|
||||
prefs.selecting_slots = FALSE
|
||||
@@ -367,8 +410,6 @@
|
||||
if (!QDELING(src))
|
||||
stack_trace("Client does not purport to be QDELING, this is going to cause bugs in other places!")
|
||||
|
||||
GLOB.tickets.ClientLogout(src)
|
||||
|
||||
// Yes this is the same as what's found in qdel(). Yes it does need to be here
|
||||
// Get off my back
|
||||
SEND_SIGNAL(src, COMSIG_QDELETING, TRUE)
|
||||
@@ -376,11 +417,15 @@
|
||||
return ..()
|
||||
|
||||
/client/Destroy()
|
||||
GLOB.directory -= ckey
|
||||
GLOB.clients -= src
|
||||
persistent_client.set_client(null)
|
||||
|
||||
log_access("Logout: [key_name(src)]")
|
||||
GLOB.tickets.ClientLogout(src)
|
||||
if(holder)
|
||||
holder.owner = null
|
||||
GLOB.admins -= src
|
||||
GLOB.directory -= ckey
|
||||
GLOB.clients -= src
|
||||
|
||||
QDEL_NULL(loot_panel)
|
||||
..()
|
||||
@@ -471,7 +516,7 @@
|
||||
//Panic bunker code
|
||||
if (isnum(player_age) && player_age == 0) //first connection
|
||||
if (CONFIG_GET(flag/panic_bunker) && !holder && !GLOB.deadmins[key])
|
||||
log_adminwarn("Failed Login: [key] - New account attempting to connect during panic bunker")
|
||||
log_admin_private("Failed Login: [key] - New account attempting to connect during panic bunker")
|
||||
message_admins(span_adminnotice("Failed Login: [key] - New account attempting to connect during panic bunker"))
|
||||
disconnect_with_message("Sorry but the server is currently not accepting connections from never before seen players.")
|
||||
return 0
|
||||
@@ -499,7 +544,6 @@
|
||||
else
|
||||
log_admin("Couldn't perform IP check on [key] with [address]")
|
||||
|
||||
// VOREStation Edit Start - Department Hours
|
||||
var/datum/db_query/query_hours = SSdbcore.NewQuery("SELECT department, hours, total_hours FROM vr_player_hours WHERE ckey = '[sql_ckey]'")
|
||||
if(query_hours.Execute())
|
||||
while(query_hours.NextRow())
|
||||
@@ -507,9 +551,8 @@
|
||||
play_hours[query_hours.item[1]] = text2num(query_hours.item[3])
|
||||
else
|
||||
var/error_message = query_hours.ErrorMsg() // Need this out here since the spawn below will split the stack and who knows what'll happen by the time it runs
|
||||
log_debug("Error loading play hours for [ckey]: [error_message]")
|
||||
log_sql("Error loading play hours for [ckey]: [error_message]")
|
||||
tgui_alert_async(src, "The query to load your existing playtime failed. Screenshot this, give the screenshot to a developer, and reconnect, otherwise you may lose any recorded play hours (which may limit access to jobs). ERROR: [error_message]", "PROBLEMS!!")
|
||||
// VOREStation Edit End - Department Hours
|
||||
qdel(query_hours)
|
||||
|
||||
if(sql_id)
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
|
||||
///assoc list of ckey -> /datum/persistent_client
|
||||
GLOBAL_LIST_EMPTY_TYPED(persistent_clients_by_ckey, /datum/persistent_client)
|
||||
/// A flat list of all persistent clients, for her looping pleasure.
|
||||
GLOBAL_LIST_EMPTY_TYPED(persistent_clients, /datum/persistent_client)
|
||||
|
||||
/// Tracks information about a client between log in and log outs
|
||||
/datum/persistent_client
|
||||
/// The true client
|
||||
var/client/client
|
||||
/// The mob this persistent client is currently bound to.
|
||||
var/mob/mob
|
||||
|
||||
/// Major version of BYOND this client was last using.
|
||||
var/byond_version
|
||||
/// Build number of BYOND this client was last using.
|
||||
var/byond_build
|
||||
|
||||
/// Action datums assigned to this player
|
||||
var/list/datum/action/player_actions = list()
|
||||
/// Tracks client action logging
|
||||
var/list/logging = list()
|
||||
|
||||
/// Callbacks invoked when this client logs in again
|
||||
var/list/post_login_callbacks = list()
|
||||
/// Callbacks invoked when this client logs out
|
||||
var/list/post_logout_callbacks = list()
|
||||
|
||||
/// List of names this key played under this round
|
||||
/// assoc list of name -> mob tag
|
||||
var/list/played_names = list()
|
||||
/// Lazylist of preference slots this client has joined the round under
|
||||
/// Numbers are stored as strings
|
||||
var/list/joined_as_slots
|
||||
|
||||
/// Tracks achievements they have earned
|
||||
//var/datum/achievement_data/achievements
|
||||
|
||||
/// World.time this player last died
|
||||
var/time_of_death = 0
|
||||
|
||||
/datum/persistent_client/New(ckey)
|
||||
//achievements = new(ckey)
|
||||
GLOB.persistent_clients_by_ckey[ckey] = src
|
||||
GLOB.persistent_clients += src
|
||||
|
||||
/datum/persistent_client/Destroy(force)
|
||||
SHOULD_CALL_PARENT(FALSE)
|
||||
. = QDEL_HINT_LETMELIVE
|
||||
CRASH("Who the FUCK tried to delete a persistent client? Get your head checked you leadskull.")
|
||||
|
||||
/// Setter for the client var, updates any vars we have that might be dependent on client state
|
||||
/datum/persistent_client/proc/set_client(client/new_client)
|
||||
if(client == new_client)
|
||||
return
|
||||
|
||||
if(client)
|
||||
client.persistent_client = null
|
||||
client = new_client
|
||||
if(client)
|
||||
client.persistent_client = src
|
||||
byond_build = client.byond_build
|
||||
byond_version = client.byond_version
|
||||
|
||||
/// Setter for the mob var, handles both references.
|
||||
/datum/persistent_client/proc/set_mob(mob/new_mob)
|
||||
if(mob == new_mob)
|
||||
return
|
||||
|
||||
mob?.persistent_client = null
|
||||
new_mob?.persistent_client?.set_mob(null)
|
||||
|
||||
mob = new_mob
|
||||
new_mob?.persistent_client = src
|
||||
|
||||
/// Writes all of the `played_names` into an HTML-escaped string.
|
||||
/datum/persistent_client/proc/get_played_names()
|
||||
var/list/previous_names = list()
|
||||
for(var/previous_name in played_names)
|
||||
previous_names += html_encode("[previous_name] ([played_names[previous_name]])")
|
||||
return previous_names.Join("; ")
|
||||
|
||||
/// Returns the full version string (i.e 515.1642) of the BYOND version and build.
|
||||
/datum/persistent_client/proc/full_byond_version()
|
||||
if(!byond_version)
|
||||
return "Unknown"
|
||||
return "[byond_version].[byond_build || "xxx"]"
|
||||
|
||||
/// Adds the new names to the player's played_names list on their /datum/persistent_client for use of admins.
|
||||
/// `ckey` should be their ckey, and `data` should be an associative list with the keys being the names they played under and the values being the unique mob ID tied to that name.
|
||||
/proc/log_played_names(ckey, data)
|
||||
if(!ckey)
|
||||
return
|
||||
|
||||
var/datum/persistent_client/writable = GLOB.persistent_clients_by_ckey[ckey]
|
||||
if(isnull(writable))
|
||||
return
|
||||
|
||||
for(var/name in data)
|
||||
if(!name)
|
||||
continue
|
||||
var/mob_tag = data[name]
|
||||
var/encoded_name = html_encode(name)
|
||||
if(writable.played_names.Find("[encoded_name]"))
|
||||
continue
|
||||
|
||||
writable.played_names += list("[encoded_name]" = mob_tag)
|
||||
@@ -131,16 +131,16 @@
|
||||
//Neutral traits
|
||||
for(var/datum/trait/path as anything in pref.neu_traits)
|
||||
if(!(path in GLOB.neutral_traits))
|
||||
to_world_log("removing [path] for not being in neutral_traits")
|
||||
log_world("removing [path] for not being in neutral_traits")
|
||||
pref.neu_traits -= path
|
||||
continue
|
||||
if(!(pref.species == SPECIES_CUSTOM) && !(path in GLOB.everyone_traits_neutral))
|
||||
to_world_log("removing [path] for not being a custom species")
|
||||
log_world("removing [path] for not being a custom species")
|
||||
pref.neu_traits -= path
|
||||
continue
|
||||
var/take_flags = initial(path.can_take)
|
||||
if((pref.dirty_synth && !(take_flags & SYNTHETICS)) || (pref.gross_meatbag && !(take_flags & ORGANICS)))
|
||||
to_world_log("removing [path] for being a dirty synth")
|
||||
log_world("removing [path] for being a dirty synth")
|
||||
pref.neu_traits -= path
|
||||
//Negative traits
|
||||
for(var/datum/trait/path as anything in pref.neg_traits)
|
||||
|
||||
@@ -19,13 +19,13 @@ var/list/gear_datums = list()
|
||||
var/use_category = initial(G.sort_category)
|
||||
|
||||
if(!use_name)
|
||||
error("Loadout - Missing display name: [G]")
|
||||
log_world("## ERROR Loadout - Missing display name: [G]")
|
||||
continue
|
||||
if(isnull(initial(G.cost)))
|
||||
error("Loadout - Missing cost: [G]")
|
||||
log_world("## ERROR Loadout - Missing cost: [G]")
|
||||
continue
|
||||
if(!initial(G.path))
|
||||
error("Loadout - Missing path definition: [G]")
|
||||
log_world("## ERROR Loadout - Missing path definition: [G]")
|
||||
continue
|
||||
|
||||
if(!loadout_categories[use_category])
|
||||
|
||||
@@ -354,7 +354,7 @@ var/list/preferences_datums = list()
|
||||
|
||||
var/slotnum = charlist[choice]
|
||||
if(!slotnum)
|
||||
error("Player picked [choice] slot to load, but that wasn't one we sent.")
|
||||
log_world("## ERROR Player picked [choice] slot to load, but that wasn't one we sent.")
|
||||
return
|
||||
|
||||
load_preferences(TRUE)
|
||||
@@ -395,7 +395,7 @@ var/list/preferences_datums = list()
|
||||
|
||||
var/slotnum = charlist[choice]
|
||||
if(!slotnum)
|
||||
error("Player picked [choice] slot to copy to, but that wasn't one we sent.")
|
||||
log_world("## ERROR Player picked [choice] slot to copy to, but that wasn't one we sent.")
|
||||
return
|
||||
|
||||
if(tgui_alert(user, "Are you sure you want to override slot [slotnum], [choice]'s savedata?", "Confirm Override", list("No", "Yes")) == "Yes")
|
||||
|
||||
@@ -41,62 +41,62 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
|
||||
// Migration for client preferences
|
||||
if(current_version < 13)
|
||||
log_debug("[client_ckey] preferences migrating from [current_version] to v13....")
|
||||
log_world("[client_ckey] preferences migrating from [current_version] to v13....")
|
||||
to_chat(client, span_danger("Migrating savefile from version [current_version] to v13..."))
|
||||
|
||||
migration_13_preferences(S)
|
||||
|
||||
log_debug("[client_ckey] preferences successfully migrated from [current_version] to v13.")
|
||||
log_world("[client_ckey] preferences successfully migrated from [current_version] to v13.")
|
||||
to_chat(client, span_danger("v13 savefile migration complete."))
|
||||
|
||||
// Migration for nifs
|
||||
if(current_version < 14)
|
||||
log_debug("[client_ckey] preferences migrating from [current_version] to v14....")
|
||||
log_world("[client_ckey] preferences migrating from [current_version] to v14....")
|
||||
to_chat(client, span_danger("Migrating savefile from version [current_version] to v14..."))
|
||||
|
||||
migration_14_nifs(S)
|
||||
|
||||
log_debug("[client_ckey] preferences successfully migrated from [current_version] to v14.")
|
||||
log_world("[client_ckey] preferences successfully migrated from [current_version] to v14.")
|
||||
to_chat(client, span_danger("v14 savefile migration complete."))
|
||||
|
||||
// Migration for nifs, again, to get rid of the /device path
|
||||
if(current_version < 15)
|
||||
log_debug("[client_ckey] preferences migrating from [current_version] to v15....")
|
||||
log_world("[client_ckey] preferences migrating from [current_version] to v15....")
|
||||
to_chat(client, span_danger("Migrating savefile from version [current_version] to v15..."))
|
||||
|
||||
migration_15_nif_path(S)
|
||||
|
||||
log_debug("[client_ckey] preferences successfully migrated from [current_version] to v15.")
|
||||
log_world("[client_ckey] preferences successfully migrated from [current_version] to v15.")
|
||||
to_chat(client, span_danger("v15 savefile migration complete."))
|
||||
|
||||
// Migration for colors
|
||||
if(current_version < 16)
|
||||
log_debug("[client_ckey] preferences migrating from [current_version] to v16....")
|
||||
log_world("[client_ckey] preferences migrating from [current_version] to v16....")
|
||||
to_chat(client, span_danger("Migrating savefile from version [current_version] to v16..."))
|
||||
|
||||
migration_16_colors(S)
|
||||
|
||||
log_debug("[client_ckey] preferences successfully migrated from [current_version] to v16.")
|
||||
log_world("[client_ckey] preferences successfully migrated from [current_version] to v16.")
|
||||
to_chat(client, span_danger("v16 savefile migration complete."))
|
||||
|
||||
// Migration for old named tails so downstream doesn't have their savefiles borked
|
||||
if(current_version < 17)
|
||||
log_debug("[client_ckey] preferences migrating from [current_version] to v17....")
|
||||
log_world("[client_ckey] preferences migrating from [current_version] to v17....")
|
||||
to_chat(client, span_danger("Migrating savefile from version [current_version] to v17..."))
|
||||
|
||||
migration_17_tails(S)
|
||||
|
||||
log_debug("[client_ckey] preferences successfully migrated from [current_version] to v17.")
|
||||
log_world("[client_ckey] preferences successfully migrated from [current_version] to v17.")
|
||||
to_chat(client, span_danger("v17 savefile migration complete."))
|
||||
|
||||
// Migration for jukebox volume from 0-1 to 0-100
|
||||
if(current_version < 18)
|
||||
log_debug("[client_ckey] preferences migrating from [current_version] to v18....")
|
||||
log_world("[client_ckey] preferences migrating from [current_version] to v18....")
|
||||
to_chat(client, span_danger("Migrating savefile from version [current_version] to v18..."))
|
||||
|
||||
migration_18_jukebox(S)
|
||||
|
||||
log_debug("[client_ckey] preferences successfully migrated from [current_version] to v18.")
|
||||
log_world("[client_ckey] preferences successfully migrated from [current_version] to v18.")
|
||||
to_chat(client, span_danger("v18 savefile migration complete."))
|
||||
/datum/preferences/proc/update_character(current_version, list/save_data)
|
||||
// Migration from BYOND savefiles to JSON: Important milemark.
|
||||
@@ -106,7 +106,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
|
||||
/// Migrates from byond savefile to json savefile
|
||||
/datum/preferences/proc/try_savefile_type_migration()
|
||||
log_debug("[client_ckey] preferences migrating from savefile to JSON...")
|
||||
log_world("[client_ckey] preferences migrating from savefile to JSON...")
|
||||
to_chat(client, span_danger("Savefile migration to JSON in progress..."))
|
||||
|
||||
load_path(client.ckey, "preferences.sav") // old save file
|
||||
@@ -118,7 +118,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
json_savefile.import_byond_savefile(new /savefile(old_path))
|
||||
json_savefile.save()
|
||||
|
||||
log_debug("[client_ckey] preferences successfully migrated from savefile to JSON.")
|
||||
log_world("[client_ckey] preferences successfully migrated from savefile to JSON.")
|
||||
to_chat(client, span_danger("Savefile migration to JSON is complete."))
|
||||
|
||||
return TRUE
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
connected = FALSE
|
||||
else if(type == "error")
|
||||
connected = FALSE
|
||||
log_debug("WebSocket Error [json_encode(payload)]")
|
||||
log_runtime("WebSocket Error [json_encode(payload)]")
|
||||
else if(type == "incomingMessage")
|
||||
if(payload["lastCall"] == "get_devices")
|
||||
available_devices = json_decode(payload["data"])
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
message_admins("[key_name_admin(src)] has attempted to post a link in OOC: [msg]")
|
||||
return
|
||||
|
||||
log_ooc(msg, src)
|
||||
src.mob.log_talk(msg, LOG_OOC)
|
||||
|
||||
if(msg)
|
||||
handle_spam_prevention(MUTE_OOC)
|
||||
@@ -125,7 +125,7 @@
|
||||
message_admins("[key_name_admin(src)] has attempted to post a link in OOC: [msg]")
|
||||
return
|
||||
|
||||
log_looc(msg,src)
|
||||
src.mob.log_message(msg, LOG_LOOC)
|
||||
|
||||
if(msg)
|
||||
handle_spam_prevention(MUTE_LOOC)
|
||||
|
||||
@@ -1172,7 +1172,7 @@
|
||||
if(5) sensor_mode = pick(0,1,2,3) //Select a random setting
|
||||
else
|
||||
sensor_mode = pick(0,1,2,3)
|
||||
log_debug("Invalid switch for suit sensors, defaulting to random. [sensorpref] chosen")
|
||||
log_runtime("Invalid switch for suit sensors, defaulting to random. [sensorpref] chosen")
|
||||
|
||||
/obj/item/clothing/under/proc/update_rolldown_status()
|
||||
var/mob/living/carbon/human/H
|
||||
|
||||
@@ -185,14 +185,14 @@
|
||||
|
||||
// Check for requisite ckey and character name.
|
||||
if((lowertext(citem.assoc_key) != lowertext(M.ckey)) || (lowertext(citem.character_name) != lowertext(M.real_name)))
|
||||
log_debug("Custom Item: [key_name(M)] Ckey or Char name does not match.")
|
||||
// to_chat(world, "Custom Item: [key_name(M)] Ckey or Char name does not match.")
|
||||
continue
|
||||
|
||||
// Check for required access.
|
||||
var/obj/item/card/id/current_id = M.wear_id
|
||||
if(citem.req_access && citem.req_access > 0) // These are numbers, not lists
|
||||
if(!(istype(current_id) && (citem.req_access in current_id.GetAccess())))
|
||||
log_debug("Custom Item: [key_name(M)] Does not have required access.")
|
||||
// to_chat(world, "Custom Item: [key_name(M)] Does not have required access.")
|
||||
continue
|
||||
|
||||
// Check for required job title.
|
||||
@@ -204,7 +204,7 @@
|
||||
has_title = 1
|
||||
break
|
||||
if(!has_title)
|
||||
log_debug("Custom Item: [key_name(M)] Does not have required job.")
|
||||
// to_chat(world, "Custom Item: [key_name(M)] Does not have required job.")
|
||||
continue
|
||||
|
||||
// ID cards and PDAs are applied directly to the existing object rather than spawned fresh.
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
build_the_emote(m_type, message, input, range, runemessage)
|
||||
|
||||
/mob/proc/log_the_emote(m_type, message, input, range, runemessage)
|
||||
log_emote(message,src) //Log before we add junk
|
||||
log_message(message, LOG_EMOTE) //Log before we add junk
|
||||
build_the_emote(m_type, message, input, range, runemessage)
|
||||
|
||||
/mob/proc/build_the_emote(m_type, message, input, range, runemessage)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
// Settings for the error handler and error viewer
|
||||
|
||||
#define ERROR_COOLDOWN 600 // The "cooldown" time for each occurrence of a unique error
|
||||
#define ERROR_LIMIT 9 // How many occurrences before the next will silence them
|
||||
#define ERROR_MAX_COOLDOWN (ERROR_COOLDOWN * ERROR_LIMIT)
|
||||
#define ERROR_SILENCE_TIME 6000 // How long a unique error will be silenced for
|
||||
|
||||
// How long to wait between messaging admins about occurrences of a unique error
|
||||
#define ERROR_MSG_DELAY 50
|
||||
@@ -1,118 +1,156 @@
|
||||
// error_cooldown items will either be positive (cooldown time) or negative (silenced error)
|
||||
// If negative, starts at -1, and goes down by 1 each time that error gets skipped
|
||||
GLOBAL_VAR_INIT(total_runtimes, 0)
|
||||
GLOBAL_VAR_INIT(total_runtimes, GLOB.total_runtimes || 0)
|
||||
GLOBAL_VAR_INIT(total_runtimes_skipped, 0)
|
||||
|
||||
#ifdef USE_CUSTOM_ERROR_HANDLER
|
||||
#define ERROR_USEFUL_LEN 2
|
||||
|
||||
// The ifdef needs to be down here, since the error viewer references total_runtimes
|
||||
#ifdef DEBUG
|
||||
/world/Error(var/exception/e, var/datum/e_src)
|
||||
if(!istype(e)) // Something threw an unusual exception
|
||||
log_error("\[[time_stamp()]] Uncaught exception: [e]")
|
||||
return ..()
|
||||
if(!GLOB.error_last_seen) // A runtime is occurring too early in start-up initialization
|
||||
return ..()
|
||||
/world/Error(exception/E, datum/e_src)
|
||||
GLOB.total_runtimes++
|
||||
|
||||
var/erroruid = "[e.file][e.line]"
|
||||
var/last_seen = GLOB.error_last_seen[erroruid]
|
||||
var/cooldown = GLOB.error_cooldown[erroruid] || 0
|
||||
if(last_seen == null) // A new error!
|
||||
GLOB.error_last_seen[erroruid] = world.time
|
||||
last_seen = world.time
|
||||
if(cooldown < 0)
|
||||
GLOB.error_cooldown[erroruid]-- // Used to keep track of skip count for this error
|
||||
GLOB.total_runtimes_skipped++
|
||||
return // Error is currently silenced, skip handling it
|
||||
if(!istype(E)) //Something threw an unusual exception
|
||||
log_world("uncaught runtime error: [E]")
|
||||
return ..()
|
||||
|
||||
// Handle cooldowns and silencing spammy errors
|
||||
var/silencing = 0
|
||||
// Each occurrence of a unique error adds to its "cooldown" time...
|
||||
cooldown = max(0, cooldown - (world.time - last_seen)) + ERROR_COOLDOWN
|
||||
//this is snowflake because of a byond bug (ID:2306577), do not attempt to call non-builtin procs in this if
|
||||
if(copytext(E.name, 1, 32) == "Maximum recursion level reached")//32 == length() of that string + 1
|
||||
//log to world while intentionally triggering the byond bug.
|
||||
log_world("runtime error: [E.name]\n[E.desc]")
|
||||
//if we got to here without silently ending, the byond bug has been fixed.
|
||||
log_world("The bug with recursion runtimes has been fixed. Please remove the snowflake check from world/Error in [__FILE__]:[__LINE__]")
|
||||
return //this will never happen.
|
||||
|
||||
else if(copytext(E.name, 1, 18) == "Out of resources!")//18 == length() of that string + 1
|
||||
log_world("BYOND out of memory. Restarting ([E?.file]:[E?.line])")
|
||||
TgsEndProcess()
|
||||
. = ..()
|
||||
Reboot(reason = 1)
|
||||
return
|
||||
|
||||
var/static/regex/stack_workaround
|
||||
if(isnull(stack_workaround))
|
||||
stack_workaround = regex("[WORKAROUND_IDENTIFIER](.+?)[WORKAROUND_IDENTIFIER]")
|
||||
var/static/list/error_last_seen = list()
|
||||
var/static/list/error_cooldown = list() /* Error_cooldown items will either be positive(cooldown time) or negative(silenced error)
|
||||
If negative, starts at -1, and goes down by 1 each time that error gets skipped*/
|
||||
|
||||
if(!error_last_seen) // A runtime is occurring too early in start-up initialization
|
||||
return ..()
|
||||
|
||||
if(stack_workaround.Find(E.name))
|
||||
var/list/data = json_decode(stack_workaround.group[1])
|
||||
E.file = data[1]
|
||||
E.line = data[2]
|
||||
E.name = stack_workaround.Replace(E.name, "")
|
||||
|
||||
var/erroruid = "[E.file][E.line]"
|
||||
var/last_seen = error_last_seen[erroruid]
|
||||
var/cooldown = error_cooldown[erroruid] || 0
|
||||
|
||||
if(last_seen == null)
|
||||
error_last_seen[erroruid] = world.time
|
||||
last_seen = world.time
|
||||
|
||||
if(cooldown < 0)
|
||||
error_cooldown[erroruid]-- //Used to keep track of skip count for this error
|
||||
GLOB.total_runtimes_skipped++
|
||||
return //Error is currently silenced, skip handling it
|
||||
//Handle cooldowns and silencing spammy errors
|
||||
var/silencing = FALSE
|
||||
|
||||
// We can runtime before config is initialized because BYOND initialize objs/map before a bunch of other stuff happens.
|
||||
// This is a bunch of workaround code for that. Hooray!
|
||||
var/configured_error_cooldown
|
||||
var/configured_error_limit
|
||||
var/configured_error_silence_time
|
||||
if(config?.entries)
|
||||
configured_error_cooldown = CONFIG_GET(number/error_cooldown)
|
||||
configured_error_limit = CONFIG_GET(number/error_limit)
|
||||
configured_error_silence_time = CONFIG_GET(number/error_silence_time)
|
||||
else
|
||||
var/datum/config_entry/CE = /datum/config_entry/number/error_cooldown
|
||||
configured_error_cooldown = initial(CE.default)
|
||||
CE = /datum/config_entry/number/error_limit
|
||||
configured_error_limit = initial(CE.default)
|
||||
CE = /datum/config_entry/number/error_silence_time
|
||||
configured_error_silence_time = initial(CE.default)
|
||||
|
||||
|
||||
//Each occurence of a unique error adds to its cooldown time...
|
||||
cooldown = max(0, cooldown - (world.time - last_seen)) + configured_error_cooldown
|
||||
// ... which is used to silence an error if it occurs too often, too fast
|
||||
if(cooldown > ERROR_MAX_COOLDOWN)
|
||||
if(cooldown > configured_error_cooldown * configured_error_limit)
|
||||
cooldown = -1
|
||||
silencing = 1
|
||||
silencing = TRUE
|
||||
spawn(0)
|
||||
usr = null
|
||||
sleep(ERROR_SILENCE_TIME)
|
||||
var/skipcount = abs(GLOB.error_cooldown[erroruid]) - 1
|
||||
GLOB.error_cooldown[erroruid] = 0
|
||||
sleep(configured_error_silence_time)
|
||||
var/skipcount = abs(error_cooldown[erroruid]) - 1
|
||||
error_cooldown[erroruid] = 0
|
||||
if(skipcount > 0)
|
||||
log_error("\[[time_stamp()]] Skipped [skipcount] runtimes in [e.file],[e.line].")
|
||||
error_cache.logError(e, skipCount = skipcount)
|
||||
GLOB.error_last_seen[erroruid] = world.time
|
||||
GLOB.error_cooldown[erroruid] = cooldown
|
||||
SEND_TEXT(world.log, "\[[time_stamp()]] Skipped [skipcount] runtimes in [E.file],[E.line].")
|
||||
GLOB.error_cache.log_error(E, skip_count = skipcount)
|
||||
|
||||
error_last_seen[erroruid] = world.time
|
||||
error_cooldown[erroruid] = cooldown
|
||||
|
||||
// The detailed error info needs some tweaking to make it look nice
|
||||
var/list/srcinfo = null
|
||||
var/list/usrinfo = null
|
||||
var/locinfo
|
||||
// First, try to make better src/usr info lines
|
||||
if(istype(e_src))
|
||||
srcinfo = list(" src: [log_info_line(e_src)]")
|
||||
var/atom/atom_e_src = e_src
|
||||
if(istype(atom_e_src))
|
||||
srcinfo += " src.loc: [log_info_line(atom_e_src.loc)]"
|
||||
if(istype(usr))
|
||||
usrinfo = list(" usr: [log_info_line(usr)]")
|
||||
locinfo = log_info_line(usr.loc)
|
||||
usrinfo = list(" usr: [key_name(usr)]")
|
||||
locinfo = loc_name(usr)
|
||||
if(locinfo)
|
||||
usrinfo += " usr.loc: [locinfo]"
|
||||
// The proceeding mess will almost definitely break if error messages are ever changed
|
||||
// I apologize in advance
|
||||
var/list/splitlines = splittext(e.desc, "\n")
|
||||
var/list/splitlines = splittext(E.desc, "\n")
|
||||
var/list/desclines = list()
|
||||
if(splitlines.len > 2) // If there aren't at least three lines, there's no info
|
||||
#ifndef DISABLE_DREAMLUAU
|
||||
var/list/state_stack = GLOB.lua_state_stack
|
||||
var/is_lua_call = length(state_stack)
|
||||
var/list/lua_stacks = list()
|
||||
if(is_lua_call)
|
||||
for(var/level in 1 to state_stack.len)
|
||||
lua_stacks += list(splittext(DREAMLUAU_GET_TRACEBACK(level), "\n"))
|
||||
#endif
|
||||
if(LAZYLEN(splitlines) > ERROR_USEFUL_LEN) // If there aren't at least three lines, there's no info
|
||||
for(var/line in splitlines)
|
||||
if(length(line) < 3)
|
||||
continue // Blank line, skip it
|
||||
if(findtext(line, "source file:"))
|
||||
continue // Redundant, skip it
|
||||
if(findtext(line, "usr.loc:"))
|
||||
continue // Our usr.loc is better, skip it
|
||||
if(LAZYLEN(line) < 3 || findtext(line, "source file:") || findtext(line, "usr.loc:"))
|
||||
continue
|
||||
if(findtext(line, "usr:"))
|
||||
if(usrinfo)
|
||||
desclines.Add(usrinfo)
|
||||
usrinfo = null
|
||||
continue // Our usr info is better, replace it
|
||||
if(srcinfo)
|
||||
if(findtext(line, "src.loc:"))
|
||||
continue
|
||||
if(findtext(line, "src:"))
|
||||
desclines.Add(srcinfo)
|
||||
srcinfo = null
|
||||
continue
|
||||
if(copytext(line, 1, 3) != " ")
|
||||
if(copytext(line, 1, 3) != " ")//3 == length(" ") + 1
|
||||
desclines += (" " + line) // Pad any unpadded lines, so they look pretty
|
||||
else
|
||||
desclines += line
|
||||
if(srcinfo) // If these aren't null, they haven't been added yet
|
||||
desclines.Add(srcinfo)
|
||||
if(usrinfo)
|
||||
if(usrinfo) //If this info isn't null, it hasn't been added yet
|
||||
desclines.Add(usrinfo)
|
||||
#ifndef DISABLE_DREAMLUAU
|
||||
if(is_lua_call)
|
||||
SSlua.log_involved_runtime(E, desclines, lua_stacks)
|
||||
#endif
|
||||
if(silencing)
|
||||
desclines += " (This error will now be silenced for [ERROR_SILENCE_TIME / 600] minutes)"
|
||||
desclines += " (This error will now be silenced for [DisplayTimeText(configured_error_silence_time)])"
|
||||
if(GLOB.error_cache)
|
||||
GLOB.error_cache.log_error(E, desclines)
|
||||
|
||||
// Now to actually output the error info...
|
||||
log_error("\[[time_stamp()]] Runtime in [e.file],[e.line]: [e]")
|
||||
var/main_line = "\[[time_stamp()]] Runtime in [E.file],[E.line]: [E]"
|
||||
SEND_TEXT(world.log, main_line)
|
||||
for(var/line in desclines)
|
||||
log_error(line)
|
||||
if(error_cache)
|
||||
error_cache.logError(e, desclines, e_src = e_src)
|
||||
SEND_TEXT(world.log, line)
|
||||
|
||||
#ifdef UNIT_TESTS
|
||||
if(GLOB.current_test)
|
||||
//good day, sir
|
||||
GLOB.current_test.Fail("[main_line]\n[desclines.Join("\n")]", file = E.file, line = E.line)
|
||||
#endif
|
||||
|
||||
/proc/log_runtime(exception/e, datum/e_src, extra_info)
|
||||
if(!istype(e))
|
||||
world.Error(e, e_src)
|
||||
return
|
||||
//if(Debugger?.enabled)
|
||||
// to_chat(world, span_alertwarning("[main_line]"), type = MESSAGE_TYPE_DEBUG)
|
||||
|
||||
if(extra_info)
|
||||
// Adding extra info adds two newlines, because parsing runtimes is funky
|
||||
if(islist(extra_info))
|
||||
e.desc = " [jointext(extra_info, "\n ")]\n\n" + e.desc
|
||||
else
|
||||
e.desc = " [extra_info]\n\n" + e.desc
|
||||
// This writes the regular format (unwrapping newlines and inserting timestamps as needed).
|
||||
log_runtime("runtime error: [E.name]\n[E.desc]")
|
||||
#endif
|
||||
|
||||
world.Error(e, e_src)
|
||||
#undef ERROR_USEFUL_LEN
|
||||
|
||||
@@ -3,205 +3,193 @@
|
||||
|
||||
// There are 3 different types used here:
|
||||
//
|
||||
// - ErrorCache keeps track of all error sources, as well as all individually
|
||||
// - error_cache keeps track of all error sources, as well as all individually
|
||||
// logged errors. Only one instance of this datum should ever exist, and it's
|
||||
// right here:
|
||||
|
||||
#ifdef DEBUG
|
||||
var/global/datum/ErrorViewer/ErrorCache/error_cache = new()
|
||||
#ifdef USE_CUSTOM_ERROR_HANDLER
|
||||
GLOBAL_DATUM_INIT(error_cache, /datum/error_viewer/error_cache, new)
|
||||
#else
|
||||
// If debugging is disabled, there's nothing useful to log, so don't bother.
|
||||
var/global/datum/ErrorViewer/ErrorCache/error_cache = null
|
||||
GLOBAL_DATUM(error_cache, /datum/error_viewer/error_cache)
|
||||
#endif
|
||||
|
||||
// - ErrorSource datums exist for each line (of code) that generates an error,
|
||||
// - error_source datums exist for each line (of code) that generates an error,
|
||||
// and keep track of all errors generated by that line.
|
||||
//
|
||||
// - ErrorEntry datums exist for each logged error, and keep track of all
|
||||
// - error_entry datums exist for each logged error, and keep track of all
|
||||
// relevant info about that error.
|
||||
|
||||
// Common vars and procs are kept at the ErrorViewer level
|
||||
/datum/ErrorViewer/
|
||||
// Common vars and procs are kept at the error_viewer level
|
||||
/datum/error_viewer
|
||||
var/name = ""
|
||||
|
||||
/datum/ErrorViewer/proc/browseTo(var/user, var/html)
|
||||
if(user)
|
||||
var/datum/browser/popup = new(user, "error_viewer", "Runtime Viewer", 900, 500)
|
||||
popup.add_head_content({"<style>
|
||||
.runtime{
|
||||
background-color: #171717;
|
||||
border: solid 1px #202020;
|
||||
font-family:'Courier New',monospace;
|
||||
font-size:9pt;
|
||||
color: #DDDDDD;
|
||||
}
|
||||
p.runtime_list{
|
||||
font-family:'Courier New',monospace;
|
||||
font-size:9pt;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-indent:-13ch;
|
||||
margin-left:13ch;
|
||||
white-space:nowrap;
|
||||
}
|
||||
</style>"})
|
||||
popup.set_content(html)
|
||||
popup.open(0)
|
||||
/datum/error_viewer/proc/browse_to(client/user, html)
|
||||
var/datum/browser/browser = new(user.mob, "error_viewer", null, 600, 400)
|
||||
browser.set_content(html)
|
||||
browser.set_head_content({"
|
||||
<style>
|
||||
.runtime
|
||||
{
|
||||
background-color: #171717;
|
||||
border: solid 1px #202020;
|
||||
font-family: "Courier New";
|
||||
padding-left: 10px;
|
||||
color: #CCCCCC;
|
||||
}
|
||||
.runtime_line
|
||||
{
|
||||
margin-bottom: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
"})
|
||||
browser.open()
|
||||
|
||||
/datum/ErrorViewer/proc/buildHeader(var/datum/ErrorViewer/back_to, var/linear, var/refreshable)
|
||||
// Common starter HTML for showTo
|
||||
var/html = ""
|
||||
/datum/error_viewer/proc/build_header(datum/error_viewer/back_to, linear)
|
||||
// Common starter HTML for show_to
|
||||
|
||||
if(istype(back_to))
|
||||
html += "[back_to.makeLink("<<<", null, linear)] "
|
||||
if(refreshable)
|
||||
html += "[makeLink("Refresh", null, linear)]"
|
||||
if(html)
|
||||
html += "<br><br>"
|
||||
return html
|
||||
. = ""
|
||||
|
||||
/datum/ErrorViewer/proc/showTo(var/user, var/datum/ErrorViewer/back_to, var/linear)
|
||||
if (istype(back_to))
|
||||
. += back_to.make_link("<b><<<</b>", null, linear)
|
||||
|
||||
. += "[make_link("Refresh")]<br><br>"
|
||||
|
||||
/datum/error_viewer/proc/show_to(user, datum/error_viewer/back_to, linear)
|
||||
// Specific to each child type
|
||||
return
|
||||
|
||||
/datum/ErrorViewer/proc/makeLink(var/linktext, var/datum/ErrorViewer/back_to, var/linear)
|
||||
/datum/error_viewer/proc/make_link(linktext, datum/error_viewer/back_to, linear)
|
||||
var/back_to_param = ""
|
||||
if(!linktext)
|
||||
if (!linktext)
|
||||
linktext = name
|
||||
if(istype(back_to))
|
||||
back_to_param = ";viewruntime_backto=\ref[back_to]"
|
||||
if(linear)
|
||||
|
||||
if (istype(back_to))
|
||||
back_to_param = ";viewruntime_backto=[REF(back_to)]"
|
||||
|
||||
if (linear)
|
||||
back_to_param += ";viewruntime_linear=1"
|
||||
return "<A href='byond://?src=\ref[src];[HrefToken()];viewruntime=\ref[src][back_to_param]'>[html_encode(linktext)]</A>"
|
||||
|
||||
/datum/ErrorViewer/Topic(href, href_list)
|
||||
if(..())
|
||||
return 1
|
||||
if(href_list["viewruntime_backto"])
|
||||
showTo(usr, locate(href_list["viewruntime_backto"]), href_list["viewruntime_linear"])
|
||||
else
|
||||
showTo(usr, null, href_list["viewruntime_linear"])
|
||||
return "<a href='byond://?_src_=holder;[HrefToken()];viewruntime=[REF(src)][back_to_param]'>[linktext]</a>"
|
||||
|
||||
/datum/ErrorViewer/ErrorCache
|
||||
/datum/error_viewer/error_cache
|
||||
var/list/errors = list()
|
||||
var/list/error_sources = list()
|
||||
var/list/errors_silenced = list()
|
||||
|
||||
/datum/ErrorViewer/ErrorCache/showTo(var/user, var/datum/ErrorViewer/back_to, var/linear)
|
||||
var/html = buildHeader(null, linear, refreshable=1)
|
||||
html += "[GLOB.total_runtimes] runtimes, [GLOB.total_runtimes_skipped] skipped<br><br>"
|
||||
if(!linear)
|
||||
html += "organized | [makeLink("linear", null, 1)]<hr>"
|
||||
var/datum/ErrorViewer/ErrorSource/error_source
|
||||
for(var/erroruid in error_sources)
|
||||
/datum/error_viewer/error_cache/show_to(user, datum/error_viewer/back_to, linear)
|
||||
var/html = build_header()
|
||||
html += "<b>[GLOB.total_runtimes]</b> runtimes, <b>[GLOB.total_runtimes_skipped]</b> skipped<br><br>"
|
||||
if (!linear)
|
||||
html += "organized | [make_link("linear", null, 1)]<hr>"
|
||||
var/datum/error_viewer/error_source/error_source
|
||||
for (var/erroruid in error_sources)
|
||||
error_source = error_sources[erroruid]
|
||||
html += "<p class='runtime_list'>[error_source.makeLink(null, src)]<br></p>"
|
||||
else
|
||||
html += "[makeLink("organized", null)] | linear<hr>"
|
||||
for(var/datum/ErrorViewer/ErrorEntry/error_entry in errors)
|
||||
html += "<p class='runtime_list'>[error_entry.makeLink(null, src, 1)]<br></p>"
|
||||
browseTo(user, html)
|
||||
html += "[error_source.make_link(null, src)]<br>"
|
||||
|
||||
/datum/ErrorViewer/ErrorCache/proc/logError(var/exception/e, var/list/desclines, var/skipCount, var/datum/e_src)
|
||||
if(!istype(e))
|
||||
else
|
||||
html += "[make_link("organized", null)] | linear<hr>"
|
||||
for (var/datum/error_viewer/error_entry/error_entry in errors)
|
||||
html += "[error_entry.make_link(null, src, 1)]<br>"
|
||||
|
||||
browse_to(user, html)
|
||||
|
||||
/datum/error_viewer/error_cache/proc/log_error(exception/e, list/desclines, skip_count)
|
||||
if (!istype(e))
|
||||
return // Abnormal exception, don't even bother
|
||||
|
||||
var/erroruid = "[e.file][e.line]"
|
||||
var/datum/ErrorViewer/ErrorSource/error_source = error_sources[erroruid]
|
||||
if(!error_source)
|
||||
var/datum/error_viewer/error_source/error_source = error_sources[erroruid]
|
||||
if (!error_source)
|
||||
error_source = new(e)
|
||||
error_sources[erroruid] = error_source
|
||||
|
||||
var/datum/ErrorViewer/ErrorEntry/error_entry = new(e, desclines, skipCount, e_src)
|
||||
var/datum/error_viewer/error_entry/error_entry = new(e, desclines, skip_count)
|
||||
error_entry.error_source = error_source
|
||||
errors += error_entry
|
||||
error_source.errors += error_entry
|
||||
if(skipCount)
|
||||
return // Skip notifying admins about skipped errors
|
||||
if (skip_count)
|
||||
return // Skip notifying admins about skipped errors.
|
||||
|
||||
// Show the error to admins with debug messages turned on, but only if one
|
||||
// from the same source hasn't been shown too recently
|
||||
// (Also, make sure config is initialized, or log_debug will runtime)
|
||||
if(config && error_source.next_message_at <= world.time)
|
||||
if (error_source.next_message_at <= world.time)
|
||||
var/const/viewtext = "\[view]" // Nesting these in other brackets went poorly
|
||||
log_debug("Runtime in [e.file],[e.line]: [html_encode(e.name)] [error_entry.makeLink(viewtext)]")
|
||||
error_source.next_message_at = world.time + ERROR_MSG_DELAY
|
||||
//to_chat(world, "Runtime in <b>[e.file]</b>, line <b>[e.line]</b>: <b>[html_encode(e.name)]</b> [error_entry.make_link(viewtext)]")
|
||||
var/err_msg_delay
|
||||
if(config?.loaded)
|
||||
err_msg_delay = CONFIG_GET(number/error_msg_delay)
|
||||
else
|
||||
var/datum/config_entry/CE = /datum/config_entry/number/error_msg_delay
|
||||
err_msg_delay = initial(CE.default)
|
||||
error_source.next_message_at = world.time + err_msg_delay
|
||||
|
||||
/datum/ErrorViewer/ErrorSource
|
||||
/datum/error_viewer/error_source
|
||||
var/list/errors = list()
|
||||
var/next_message_at = 0
|
||||
|
||||
/datum/ErrorViewer/ErrorSource/New(var/exception/e)
|
||||
if(!istype(e))
|
||||
/datum/error_viewer/error_source/New(exception/e)
|
||||
if (!istype(e))
|
||||
name = "\[[time_stamp()]] Uncaught exceptions"
|
||||
return
|
||||
name = "\[[time_stamp()]] Runtime in [e.file],[e.line]: [e]"
|
||||
|
||||
/datum/ErrorViewer/ErrorSource/showTo(var/user, var/datum/ErrorViewer/back_to, var/linear)
|
||||
if(!istype(back_to))
|
||||
back_to = error_cache
|
||||
var/html = buildHeader(back_to, refreshable=1)
|
||||
for(var/datum/ErrorViewer/ErrorEntry/error_entry in errors)
|
||||
html += "<p class='runtime_list'>[error_entry.makeLink(null, src)]<br></p>"
|
||||
browseTo(user, html)
|
||||
name = "<b>\[[time_stamp()]]</b> Runtime in <b>[e.file]</b>, line <b>[e.line]</b>: <b>[html_encode(e.name)]</b>"
|
||||
|
||||
/datum/ErrorViewer/ErrorEntry
|
||||
var/datum/ErrorViewer/ErrorSource/error_source
|
||||
/datum/error_viewer/error_source/show_to(user, datum/error_viewer/back_to, linear)
|
||||
if (!istype(back_to))
|
||||
back_to = GLOB.error_cache
|
||||
|
||||
var/html = build_header(back_to)
|
||||
for (var/datum/error_viewer/error_entry/error_entry in errors)
|
||||
html += "[error_entry.make_link(null, src)]<br>"
|
||||
|
||||
browse_to(user, html)
|
||||
|
||||
/datum/error_viewer/error_entry
|
||||
var/datum/error_viewer/error_source/error_source
|
||||
var/exception/exc
|
||||
var/desc = ""
|
||||
var/srcRef
|
||||
var/srcType
|
||||
var/turf/srcLoc
|
||||
var/usrRef
|
||||
var/turf/usrLoc
|
||||
var/isSkipCount
|
||||
var/usr_ref
|
||||
var/turf/usr_loc
|
||||
var/is_skip_count
|
||||
|
||||
/datum/ErrorViewer/ErrorEntry/New(var/exception/e, var/list/desclines, var/skipCount, var/datum/e_src)
|
||||
if(!istype(e))
|
||||
name = "\[[time_stamp()]] Uncaught exception: [e]"
|
||||
/datum/error_viewer/error_entry/New(exception/e, list/desclines, skip_count)
|
||||
if (!istype(e))
|
||||
name = "<b>\[[time_stamp()]]</b> Uncaught exception: <b>[html_encode(e.name)]</b>"
|
||||
return
|
||||
if(skipCount)
|
||||
name = "\[[time_stamp()]] Skipped [skipCount] runtimes in [e.file],[e.line]."
|
||||
isSkipCount = TRUE
|
||||
|
||||
if(skip_count)
|
||||
name = "\[[time_stamp()]] Skipped [skip_count] runtimes in [e.file],[e.line]."
|
||||
is_skip_count = TRUE
|
||||
return
|
||||
name = "\[[time_stamp()]] Runtime in [e.file],[e.line]: [e]"
|
||||
|
||||
name = "<b>\[[time_stamp()]]</b> Runtime in <b>[e.file]</b>, line <b>[e.line]</b>: <b>[html_encode(e.name)]</b>"
|
||||
exc = e
|
||||
if(istype(desclines))
|
||||
for(var/line in desclines)
|
||||
if (istype(desclines))
|
||||
for (var/line in desclines)
|
||||
// There's probably a better way to do this than non-breaking spaces...
|
||||
desc += " " + html_encode(line) + "<br>"
|
||||
if(istype(e_src))
|
||||
srcRef = "\ref[e_src]"
|
||||
srcType = e_src.type
|
||||
srcLoc = get_turf(e_src)
|
||||
if(usr)
|
||||
usrRef = "\ref[usr]"
|
||||
usrLoc = get_turf(usr)
|
||||
desc += "<span class='runtime_line'>[html_encode(line)]</span><br>"
|
||||
|
||||
/datum/ErrorViewer/ErrorEntry/showTo(var/user, var/datum/ErrorViewer/back_to, var/linear)
|
||||
if(!istype(back_to))
|
||||
if (usr)
|
||||
usr_ref = "[REF(usr)]"
|
||||
usr_loc = get_turf(usr)
|
||||
|
||||
/datum/error_viewer/error_entry/show_to(user, datum/error_viewer/back_to, linear)
|
||||
if (!istype(back_to))
|
||||
back_to = error_source
|
||||
var/html = buildHeader(back_to, linear)
|
||||
html += "<div class='runtime'>[html_encode(name)]<br>[desc]</div>"
|
||||
if(srcRef)
|
||||
html += "<br>src: <a href='byond://?_src_=vars;[HrefToken()];Vars=[srcRef]'>VV</a>"
|
||||
if(ispath(srcType, /mob))
|
||||
html += " <a href='byond://?_src_=holder;[HrefToken()];adminplayeropts=[srcRef]'>PP</a>"
|
||||
html += " <a href='byond://?_src_=holder;[HrefToken()];adminplayerobservefollow=[srcRef]'>Follow</a>"
|
||||
if(istype(srcLoc))
|
||||
html += "<br>src.loc: <a href='byond://?_src_=vars;[HrefToken()];Vars=\ref[srcLoc]'>VV</a>"
|
||||
html += " <a href='byond://?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[srcLoc.x];Y=[srcLoc.y];Z=[srcLoc.z]'>JMP</a>"
|
||||
if(usrRef)
|
||||
html += "<br>usr: <a href='byond://?_src_=vars;[HrefToken()];Vars=[usrRef]'>VV</a>"
|
||||
html += " <a href='byond://?_src_=holder;[HrefToken()];adminplayeropts=[usrRef]'>PP</a>"
|
||||
html += " <a href='byond://?_src_=holder;[HrefToken()];adminplayerobservefollow=[usrRef]'>Follow</a>"
|
||||
if(istype(usrLoc))
|
||||
html += "<br>usr.loc: <a href='byond://?_src_=vars;[HrefToken()];Vars=\ref[usrLoc]'>VV</a>"
|
||||
html += " <a href='byond://?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[usrLoc.x];Y=[usrLoc.y];Z=[usrLoc.z]'>JMP</a>"
|
||||
browseTo(user, html)
|
||||
|
||||
/datum/ErrorViewer/ErrorEntry/makeLink(var/linktext, var/datum/ErrorViewer/back_to, var/linear)
|
||||
if(isSkipCount)
|
||||
return html_encode(name)
|
||||
else
|
||||
return ..()
|
||||
var/html = build_header(back_to, linear)
|
||||
html += "[name]<div class='runtime'>[desc]</div>"
|
||||
if (usr_ref)
|
||||
html += "<br><b>usr</b>: <a href='byond://?_src_=vars;[HrefToken()];Vars=[usr_ref]'>VV</a>"
|
||||
html += " <a href='byond://?_src_=holder;[HrefToken()];adminplayeropts=[usr_ref]'>PP</a>"
|
||||
html += " <a href='byond://?_src_=holder;[HrefToken()];adminplayerobservefollow=[usr_ref]'>Follow</a>"
|
||||
if (istype(usr_loc))
|
||||
html += "<br><b>usr.loc</b>: <a href='byond://?_src_=vars;[HrefToken()];Vars=[REF(usr_loc)]'>VV</a>"
|
||||
html += " <a href='byond://?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[usr_loc.x];Y=[usr_loc.y];Z=[usr_loc.z]'>JMP</a>"
|
||||
|
||||
browse_to(user, html)
|
||||
|
||||
/datum/error_viewer/error_entry/make_link(linktext, datum/error_viewer/back_to, linear)
|
||||
return is_skip_count ? name : ..()
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
// Settings for the error handler and error viewer
|
||||
#undef ERROR_COOLDOWN
|
||||
#undef ERROR_LIMIT
|
||||
#undef ERROR_MAX_COOLDOWN
|
||||
#undef ERROR_SILENCE_TIME
|
||||
#undef ERROR_MSG_DELAY
|
||||
@@ -72,7 +72,7 @@
|
||||
for(var/i in 1 to 10)
|
||||
var/area/A = pick(grand_list_of_areas)
|
||||
if(is_area_occupied(A))
|
||||
log_debug("atmos_leak event: Rejected [A] because it is occupied.")
|
||||
log_game("atmos_leak event: Rejected [A] because it is occupied.")
|
||||
continue
|
||||
// A good area, great! Lets try and pick a turf
|
||||
var/list/turfs = list()
|
||||
@@ -80,14 +80,14 @@
|
||||
if(turf_clear(F))
|
||||
turfs += F
|
||||
if(turfs.len == 0)
|
||||
log_debug("atmos_leak event: Rejected [A] because it has no clear turfs.")
|
||||
log_game("atmos_leak event: Rejected [A] because it has no clear turfs.")
|
||||
continue
|
||||
target_area = A
|
||||
target_turf = pick(turfs)
|
||||
|
||||
// If we can't find a good target, give up
|
||||
if(!target_area)
|
||||
log_debug("atmos_leak event: Giving up after too many failures to pick target area")
|
||||
log_game("atmos_leak event: Giving up after too many failures to pick target area")
|
||||
kill()
|
||||
return
|
||||
|
||||
|
||||
@@ -13,18 +13,18 @@
|
||||
for(var/i in 1 to 10)
|
||||
var/obj/machinery/portable_atmospherics/canister/C = pick(all_canisters)
|
||||
if(severity <= EVENT_LEVEL_MUNDANE && area_is_occupied(get_area(C)))
|
||||
log_debug("canister_leak event: Rejecting canister [C] ([C.x],[C.y],[C.z]) because area is occupied")
|
||||
log_game("canister_leak event: Rejecting canister [C] ([C.x],[C.y],[C.z]) because area is occupied")
|
||||
continue
|
||||
// Okay lets break it
|
||||
break_canister(C)
|
||||
return
|
||||
|
||||
// If we got to here we failed to find it
|
||||
log_debug("canister_leak event: Giving up after too many failures to pick target canister")
|
||||
log_game("canister_leak event: Giving up after too many failures to pick target canister")
|
||||
kill()
|
||||
return
|
||||
|
||||
/datum/event/canister_leak/proc/break_canister(var/obj/machinery/portable_atmospherics/canister/C)
|
||||
log_debug("canister_leak event: Canister [C] ([C.x],[C.y],[C.z]) destroyed.")
|
||||
log_game("canister_leak event: Canister [C] ([C.x],[C.y],[C.z]) destroyed.")
|
||||
C.health = 0
|
||||
C.healthcheck()
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
if(shield_gen.deal_shield_damage(30 * severity, SHIELD_DAMTYPE_EM) <= SHIELD_BREACHED_MINOR)
|
||||
return
|
||||
if(!valid_apcs.len)
|
||||
// log_debug("No valid APCs found for electrical storm event ship=[victim]!") // Let's not spam poor people with debug logs on (me)
|
||||
// log_game("No valid APCs found for electrical storm event ship=[victim]!") // Let's not spam poor people with debug logs on (me)
|
||||
return
|
||||
var/list/picked_apcs = list()
|
||||
for(var/i=0, i< severity * 2, i++) // up to 2/4/6 APCs per tick depending on severity
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
new next_event.event_type(next_event) // Events are added and removed from the processing queue in their New/kill procs
|
||||
|
||||
log_debug("Starting event '[next_event.name]' of severity [GLOB.severity_to_string[severity]].")
|
||||
log_game("Starting event '[next_event.name]' of severity [GLOB.severity_to_string[severity]].")
|
||||
next_event = null // When set to null, a random event will be selected next time
|
||||
else
|
||||
// If not, wait for one minute, instead of one tick, before checking again.
|
||||
@@ -114,7 +114,7 @@
|
||||
var/event_delay = rand(CONFIG_GET(number_list/event_delay_lower)[severity] MINUTES, CONFIG_GET(number_list/event_delay_upper)[severity] MINUTES) * playercount_modifier
|
||||
next_event_time = world.time + event_delay
|
||||
|
||||
log_debug("Next event of severity [GLOB.severity_to_string[severity]] in [(next_event_time - world.time)/600] minutes.")
|
||||
log_game("Next event of severity [GLOB.severity_to_string[severity]] in [(next_event_time - world.time)/600] minutes.")
|
||||
|
||||
/datum/event_container/proc/SelectEvent()
|
||||
var/datum/event_meta/EM = tgui_input_list(usr, "Select an event to queue up.", "Event Selection", available_events)
|
||||
|
||||
@@ -102,7 +102,7 @@ GLOBAL_LIST_EMPTY(event_last_fired)
|
||||
for(var/V in possibleEvents)
|
||||
debug_message += "[V]:[possibleEvents[V]]"
|
||||
debug_message += "|||Picked:[picked_event]"
|
||||
log_debug(debug_message)
|
||||
// to_chat(world, debug_message)
|
||||
|
||||
if(!picked_event)
|
||||
return
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
to_chat(A, span_danger("Malicious program detected in the [english_list(areaName)] lighting and airlock control systems by [my_department]."))
|
||||
|
||||
else
|
||||
to_world_log("ERROR: Could not initate grey-tide. Unable to find suitable containment area.")
|
||||
log_world("ERROR: Could not initate grey-tide. Unable to find suitable containment area.")
|
||||
kill()
|
||||
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@
|
||||
src.type_path = type_path
|
||||
src.name = initial(type_path.name)
|
||||
if(!name)
|
||||
log_debug("supply_demand event: Order for thing [type_path] has no name.")
|
||||
log_game("supply_demand event: Order for thing [type_path] has no name.")
|
||||
|
||||
/datum/supply_demand_order/thing/match_item(var/atom/I)
|
||||
if(istype(I, type_path))
|
||||
@@ -207,7 +207,7 @@
|
||||
qty_need = CEILING((qty_need - amount_to_take), 1)
|
||||
return 1
|
||||
else
|
||||
log_debug("supply_demand event: not taking reagent '[reagent_id]': [amount_to_take]")
|
||||
log_game("supply_demand event: not taking reagent '[reagent_id]': [amount_to_take]")
|
||||
return
|
||||
|
||||
//
|
||||
@@ -233,14 +233,14 @@
|
||||
if(!canmix || canmix.total_moles <= 0)
|
||||
return
|
||||
if(canmix.return_pressure() < mixture.return_pressure())
|
||||
log_debug("supply_demand event: canister fails to match [canmix.return_pressure()] kPa < [mixture.return_pressure()] kPa")
|
||||
log_game("supply_demand event: canister fails to match [canmix.return_pressure()] kPa < [mixture.return_pressure()] kPa")
|
||||
return
|
||||
// Make sure ratios are equal
|
||||
for(var/gas in mixture.gas)
|
||||
var/targetPercent = round((mixture.gas[gas] / mixture.total_moles) * 100)
|
||||
var/canPercent = round((canmix.gas[gas] / canmix.total_moles) * 100)
|
||||
if(abs(targetPercent-canPercent) > 1)
|
||||
log_debug("supply_demand event: canister fails to match because '[gas]': [canPercent] != [targetPercent]")
|
||||
log_game("supply_demand event: canister fails to match because '[gas]': [canPercent] != [targetPercent]")
|
||||
return // Fail!
|
||||
// Huh, it actually matches!
|
||||
qty_need -= 1
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
break
|
||||
|
||||
if(!input_plate)
|
||||
log_misc("a [src] didn't find an input plate.")
|
||||
log_world("## MISC a [src] didn't find an input plate.")
|
||||
|
||||
/obj/machinery/gibber/Destroy()
|
||||
occupant = null
|
||||
|
||||
@@ -57,5 +57,5 @@
|
||||
R = safepick(GLOB.data_core.medical)
|
||||
|
||||
if(R)
|
||||
log_debug("Manifest malfunction event is now deleting [R.fields["name"]]'s [record_class_to_delete] record.")
|
||||
log_game("Manifest malfunction event is now deleting [R.fields["name"]]'s [record_class_to_delete] record.")
|
||||
qdel(R)
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
targeted_account = pick(GLOB.all_money_accounts)
|
||||
|
||||
if(!targeted_account)
|
||||
log_debug("Money hacker event could not find an account to hack. Aborting.")
|
||||
log_game("Money hacker event could not find an account to hack. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
@@ -47,11 +47,11 @@
|
||||
if(targeted_account && !targeted_account.suspended) // Hacker wins.
|
||||
message = "The hack attempt has succeeded."
|
||||
hack_account(targeted_account)
|
||||
log_debug("Money hacker event managed to hack the targeted account.")
|
||||
log_game("Money hacker event managed to hack the targeted account.")
|
||||
|
||||
else // Crew wins.
|
||||
message = "The attack has ceased, the affected accounts can now be brought online."
|
||||
log_debug("Money hacker event failed to hack the targeted account due to intervention by the crew.")
|
||||
log_game("Money hacker event failed to hack the targeted account due to intervention by the crew.")
|
||||
|
||||
var/my_department = "[location_name()] Firewall Subroutines"
|
||||
|
||||
|
||||
@@ -31,11 +31,11 @@
|
||||
// taking out loads of money before the event, then depositing it back in after the event fires, feel free to make this check for
|
||||
// roundstart money instead.
|
||||
money_at_start = count_money()
|
||||
log_debug("Funding Drive event logged a sum of [money_at_start] thalers in all station accounts at the start of the event.")
|
||||
log_game("Funding Drive event logged a sum of [money_at_start] thalers in all station accounts at the start of the event.")
|
||||
|
||||
/datum/event2/event/raise_funds/end()
|
||||
var/money_at_end = count_money()
|
||||
log_debug("Funding Drive event logged a sum of [money_at_end] thalers in all station accounts at the end of the event, compared \
|
||||
log_game("Funding Drive event logged a sum of [money_at_end] thalers in all station accounts at the end of the event, compared \
|
||||
to [money_at_start] thalers. A difference of [money_at_end / money_at_start] was calculated.")
|
||||
|
||||
// A number above 1 indicates money was made, while below 1 does the opposite.
|
||||
@@ -50,20 +50,20 @@
|
||||
discussion regarding your future employment prospects will occur.<br><br>\
|
||||
Your facility's current balance of requisition tokens has been revoked."
|
||||
SSsupply.points = 0
|
||||
log_debug("Funding Drive event ended with an abyssmal response, and the loss of all cargo points.")
|
||||
log_game("Funding Drive event ended with an abyssmal response, and the loss of all cargo points.")
|
||||
|
||||
if(0.02 to 0.98) // Bad response.
|
||||
message = "We're very disappointed that \the [location_name()] has ran a deficit since our request. \
|
||||
As such, we will be taking away some requisition tokens to cover the cost of operating your facility."
|
||||
var/points_lost = round(SSsupply.points * rand(0.5, 0.8))
|
||||
SSsupply.points -= points_lost
|
||||
log_debug("Funding Drive event ended with a bad response, and [points_lost] cargo points was taken away.")
|
||||
log_game("Funding Drive event ended with a bad response, and [points_lost] cargo points was taken away.")
|
||||
|
||||
if(0.98 to 1.02) // Neutral response.
|
||||
message = "It is unfortunate that \the [location_name()]'s finances remain at a standstill, however \
|
||||
that is still preferred over having a decicit. We hope that in the future, your facility will be able to be \
|
||||
more profitable."
|
||||
log_debug("Funding Drive event ended with a neutral response.")
|
||||
log_game("Funding Drive event ended with a neutral response.")
|
||||
|
||||
if(1.02 to INFINITY) // Good response.
|
||||
message = "We appreciate the efforts made by \the [location_name()] to run at a surplus. \
|
||||
@@ -75,7 +75,7 @@
|
||||
// Otherwise it would be weird for centcom to go 'thanks for not spending money, your reward is money to spend'.
|
||||
var/point_reward = rand(100, 200)
|
||||
SSsupply.points += point_reward
|
||||
log_debug("Funding Drive event ended with a good response and a bonus of [point_reward] cargo points.")
|
||||
log_game("Funding Drive event ended with a good response and a bonus of [point_reward] cargo points.")
|
||||
|
||||
send_command_report("Budget Followup", message)
|
||||
|
||||
@@ -92,5 +92,5 @@
|
||||
|
||||
/datum/event2/event/raise_funds/proc/send_command_report(title, message)
|
||||
post_comm_message(title, message)
|
||||
to_world(span_danger("New [using_map.company_name] Update available at all communication consoles."))
|
||||
to_chat(world, span_danger("New [using_map.company_name] Update available at all communication consoles."))
|
||||
SEND_SOUND(world, 'sound/AI/commandreport.ogg')
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
/datum/event2/event/airlock_failure/start()
|
||||
var/list/areas = find_random_areas()
|
||||
if(!LAZYLEN(areas))
|
||||
log_debug("Airlock Failure event could not find any areas. Aborting.")
|
||||
log_game("Airlock Failure event could not find any areas. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
addtimer(CALLBACK(src, PROC_REF(break_door), door), 1) // Emagging proc is actually a blocking proc and that's bad for the ticker.
|
||||
door.visible_message(span_danger("\The [door]'s panel sparks!"))
|
||||
playsound(door, "sparks", 50, 1)
|
||||
log_debug("Airlock Failure event has broken \the [door] airlock in [area].")
|
||||
log_game("Airlock Failure event has broken \the [door] airlock in [area].")
|
||||
affected_areas |= area
|
||||
doors_to_break--
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
open_turfs = find_random_turfs(5 + number_of_blobs)
|
||||
|
||||
if(!open_turfs.len)
|
||||
log_debug("Blob infestation event: Giving up after failure to find blob spots.")
|
||||
log_game("Blob infestation event: Giving up after failure to find blob spots.")
|
||||
abort()
|
||||
|
||||
/datum/event2/event/blob/start()
|
||||
@@ -108,7 +108,7 @@
|
||||
var/obj/structure/blob/core/new_blob = new spawn_blob_type(T)
|
||||
blobs += WEAKREF(new_blob)
|
||||
open_turfs -= T // So we can't put two cores on the same tile if doing multiblob.
|
||||
log_debug("Spawned [new_blob.overmind.blob_type.name] blob at [get_area(new_blob)].")
|
||||
log_game("Spawned [new_blob.overmind.blob_type.name] blob at [get_area(new_blob)].")
|
||||
|
||||
/datum/event2/event/blob/should_end()
|
||||
for(var/datum/weakref/weakref as anything in blobs)
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
vending_machines += V
|
||||
|
||||
if(!vending_machines.len)
|
||||
log_debug("Could not find any vending machines on station Z levels. Aborting.")
|
||||
log_game("Brand intelligence event: Could not find any vending machines on station Z levels. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
if(!C.destroyed && (C.z in using_map.station_levels) && C.air_contents.total_moles >= MOLES_CELLSTANDARD)
|
||||
all_canisters += C
|
||||
var/obj/machinery/portable_atmospherics/canister/C = pick(all_canisters)
|
||||
log_debug("canister_leak event: Canister [C] ([C.x],[C.y],[C.z]) destroyed.")
|
||||
log_game("canister_leak event: Canister [C] ([C.x],[C.y],[C.z]) destroyed.")
|
||||
C.health = 0
|
||||
C.healthcheck()
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
var/list/turfs = find_random_turfs()
|
||||
if(!turfs.len)
|
||||
log_debug("Gas Leak event failed to find any available turfs to leak into. Aborting.")
|
||||
log_game("Gas Leak event failed to find any available turfs to leak into. Aborting.")
|
||||
abort()
|
||||
return
|
||||
chosen_turf = pick(turfs)
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
var/turf/candidate = locate(rand(1, world.maxx), rand(1, world.maxy), pick(get_location_z_levels()) )
|
||||
if(istype(candidate, /turf/simulated/wall))
|
||||
origin = candidate
|
||||
log_debug("Wall-rot event has chosen \the [origin] ([origin.loc]) as the origin for the wallrot infestation.")
|
||||
log_game("Wall-rot event has chosen \the [origin] ([origin.loc]) as the origin for the wallrot infestation.")
|
||||
return
|
||||
|
||||
log_debug("Wall-rot event failed to find a valid wall after one hundred tries. Aborting.")
|
||||
log_game("Wall-rot event failed to find a valid wall after one hundred tries. Aborting.")
|
||||
abort()
|
||||
|
||||
/datum/event2/event/wallrot/announce()
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
/datum/event2/event/window_break/set_up()
|
||||
var/list/areas = find_random_areas()
|
||||
if(!LAZYLEN(areas))
|
||||
log_debug("Window Break event could not find any areas. Aborting.")
|
||||
log_game("Window Break event could not find any areas. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
@@ -43,11 +43,11 @@
|
||||
break // Break out of the inner loop.
|
||||
|
||||
if(chosen_turf_with_windows)
|
||||
log_debug("Window Break event has chosen turf '[chosen_turf_with_windows.name]' in [chosen_turf_with_windows.loc].")
|
||||
log_game("Window Break event has chosen turf '[chosen_turf_with_windows.name]' in [chosen_turf_with_windows.loc].")
|
||||
break // Then the outer loop.
|
||||
|
||||
if(!chosen_turf_with_windows)
|
||||
log_debug("Window Break event could not find a turf with valid windows to break. Aborting.")
|
||||
log_game("Window Break event could not find a turf with valid windows to break. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
/datum/event2/event/window_break/end()
|
||||
// If someone fixed the window, then everything is fine.
|
||||
if(chosen_window && chosen_window.anchored && chosen_window.health == chosen_window.maxhealth)
|
||||
log_debug("Window Break event ended with window repaired.")
|
||||
log_game("Window Break event ended with window repaired.")
|
||||
return
|
||||
|
||||
// Otherwise a bunch of windows shatter.
|
||||
@@ -102,7 +102,7 @@
|
||||
var/obj/structure/window/W = collateral_windows[i]
|
||||
W?.shatter()
|
||||
|
||||
log_debug("Window Break event ended with [windows_to_shatter] shattered windows and a breach.")
|
||||
log_game("Window Break event ended with [windows_to_shatter] shattered windows and a breach.")
|
||||
|
||||
// Checks if a window is adjacent to a space tile, and also that the opposite direction is open.
|
||||
// This is done to avoid getting caught in corner parts of windows.
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
/datum/event2/event/comms_blackout/start()
|
||||
if(prob(50))
|
||||
// One in two chance for the radios to turn i%t# t&_)#%, which can be more alarming than radio silence.
|
||||
log_debug("Doing partial outage of telecomms.")
|
||||
log_game("Doing partial outage of telecomms.")
|
||||
for(var/obj/machinery/telecomms/processor/P in telecomms_list)
|
||||
P.emp_act(1)
|
||||
else
|
||||
// Otherwise just shut everything down, madagascar style.
|
||||
log_debug("Doing complete outage of telecomms.")
|
||||
log_game("Doing complete outage of telecomms.")
|
||||
for(var/obj/machinery/telecomms/T in telecomms_list)
|
||||
T.emp_act(1)
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
/datum/event2/event/electrical_fault/event_tick()
|
||||
if(!valid_apcs.len)
|
||||
log_debug("ELECTRICAL EVENT: No valid APCs found for electrical fault event. Aborting.")
|
||||
log_game("ELECTRICAL EVENT: No valid APCs found for electrical fault event. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
/datum/event2/event/electrical_fault/end()
|
||||
command_announcement.Announce("The irregular electrical conditions inside \the [location_name()] power grid has ceased.", "[location_name()] Power Grid Monitoring")
|
||||
log_debug("Electrical Fault event caused [apcs_disabled] APC\s to shut off, \
|
||||
log_game("Electrical Fault event caused [apcs_disabled] APC\s to shut off, \
|
||||
[apcs_overloaded] APC\s to overload lighting, and [apcs_emagged] APC\s to be emagged.")
|
||||
|
||||
/datum/event2/event/electrical_fault/proc/affect_apc(obj/machinery/power/apc/A)
|
||||
@@ -77,7 +77,7 @@
|
||||
// This will actually protect it from further damage.
|
||||
if(prob(25))
|
||||
A.energy_fail(rand(60, 120))
|
||||
// log_debug("ELECTRICAL EVENT: Disabled \the [A]'s power for a temporary amount of time.")
|
||||
// log_game("ELECTRICAL EVENT: Disabled \the [A]'s power for a temporary amount of time.")
|
||||
playsound(A, 'sound/machines/defib_success.ogg', 50, 1)
|
||||
apcs_disabled++
|
||||
return
|
||||
@@ -85,7 +85,7 @@
|
||||
// Decent chance to overload lighting circuit.
|
||||
if(prob(30))
|
||||
A.overload_lighting()
|
||||
// log_debug("ELECTRICAL EVENT: Overloaded \the [A]'s lighting.")
|
||||
// log_game("ELECTRICAL EVENT: Overloaded \the [A]'s lighting.")
|
||||
playsound(A, 'sound/effects/lightningshock.ogg', 50, 1)
|
||||
apcs_overloaded++
|
||||
|
||||
@@ -93,6 +93,6 @@
|
||||
if(prob(5))
|
||||
A.emagged = TRUE
|
||||
A.update_icon()
|
||||
// log_debug("ELECTRICAL EVENT: Emagged \the [A].")
|
||||
// log_game("ELECTRICAL EVENT: Emagged \the [A].")
|
||||
playsound(A, 'sound/machines/chime.ogg', 50, 1)
|
||||
apcs_emagged++
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
/datum/event2/event/infestation/set_up()
|
||||
turfs = find_random_turfs(max_vermin)
|
||||
if(!turfs.len)
|
||||
log_debug("Infestation event failed to find any valid turfs. Aborting.")
|
||||
log_game("Infestation event failed to find any valid turfs. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/datum/event2/event/pda_spam
|
||||
length_lower_bound = 30 MINUTES
|
||||
length_upper_bound = 1 HOUR
|
||||
var/spam_debug = FALSE // If true, notices of the event sending spam go to `log_debug()`.
|
||||
var/spam_debug = FALSE // If true, notices of the event sending spam go to `log_game()`.
|
||||
var/last_spam_time = null // world.time of most recent spam.
|
||||
var/next_spam_attempt_time = 0 // world.time of next attempt to try to spam.
|
||||
var/give_up_after = 5 MINUTES
|
||||
@@ -60,7 +60,7 @@
|
||||
if(!.)
|
||||
// Give up if nobody was reachable for five minutes.
|
||||
if(last_spam_time + give_up_after < world.time)
|
||||
log_debug("PDA Spam event giving up after not being able to spam for awhile.")
|
||||
log_game("PDA Spam event giving up after not being able to spam for awhile.")
|
||||
return TRUE
|
||||
|
||||
/datum/event2/event/pda_spam/proc/can_spam()
|
||||
@@ -134,7 +134,7 @@
|
||||
var/datum/data/pda/app/messenger/PM = P.find_program(/datum/data/pda/app/messenger)
|
||||
PM.notify(span_bold("Message from [sender] (Unknown / spam?), ") + "\"[message]\" (Unable to Reply)", 0)
|
||||
if(spam_debug)
|
||||
log_debug("PDA Spam event sent spam to \the [P].")
|
||||
log_game("PDA Spam event sent spam to \the [P].")
|
||||
|
||||
|
||||
/datum/event2/event/pda_spam/proc/pick_message_server()
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
/datum/event2/event/sudden_weather_shift/set_up()
|
||||
if(!LAZYLEN(SSplanets.planets))
|
||||
log_debug("Weather shift event was ran when no planets exist. Aborting.")
|
||||
log_game("Weather shift event was ran when no planets exist. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
@@ -41,5 +41,5 @@
|
||||
|
||||
// Now choose a new weather.
|
||||
var/new_weather = pickweight(new_weather_weights)
|
||||
log_debug("Sudden weather shift event is now changing [chosen_planet.name]'s weather to [new_weather].")
|
||||
log_game("Sudden weather shift event is now changing [chosen_planet.name]'s weather to [new_weather].")
|
||||
chosen_planet.weather_holder.change_weather(new_weather)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
free_turfs = find_random_turfs(5, desired_turf_areas)
|
||||
|
||||
if(!free_turfs.len)
|
||||
log_debug("Ghost Pod Spawning event failed to find a place to spawn. Aborting.")
|
||||
log_game("Ghost Pod Spawning event failed to find a place to spawn. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
|
||||
@@ -31,6 +31,6 @@
|
||||
continue
|
||||
|
||||
if(H.appendicitis())
|
||||
log_debug("Appendicitis event gave appendicitis to \the [H].")
|
||||
log_game("Appendicitis event gave appendicitis to \the [H].")
|
||||
return
|
||||
log_debug("Appendicitis event could not find a valid victim.")
|
||||
log_game("Appendicitis event could not find a valid victim.")
|
||||
|
||||
@@ -183,7 +183,7 @@
|
||||
areas_to_break += A
|
||||
|
||||
if(!areas_to_break.len)
|
||||
log_debug("Prison Break event failed to find any areas to break. Aborting.")
|
||||
log_game("Prison Break event failed to find any areas to break. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
end_weights -= victim_chosen
|
||||
|
||||
if(!victim)
|
||||
log_debug("Security Screening event failed to find anyone to screen. Aborting.")
|
||||
log_game("Security Screening event failed to find anyone to screen. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
|
||||
@@ -44,6 +44,6 @@
|
||||
while((spiders_to_spawn >= 1) && vents.len)
|
||||
var/obj/vent = pick(vents)
|
||||
new spiderling_to_spawn(vent.loc)
|
||||
log_debug("Spider infestation event spawned a spiderling at [get_area(vent)].")
|
||||
log_game("Spider infestation event spawned a spiderling at [get_area(vent)].")
|
||||
vents -= vent
|
||||
spiders_to_spawn--
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
|
||||
/datum/event2/event/surprise_carp/start()
|
||||
if(!victim)
|
||||
log_debug("Failed to find a target for surprise carp attack. Aborting.")
|
||||
log_game("Failed to find a target for surprise carp attack. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
var/number_of_carp = rand(1, 2)
|
||||
log_debug("Sending [number_of_carp] carp\s after \the [victim].")
|
||||
log_game("Sending [number_of_carp] carp\s after \the [victim].")
|
||||
// Getting off screen tiles is kind of tricky due to potential edge cases that could arise.
|
||||
// The method we're gonna do is make a big square around the victim, then
|
||||
// subtract a smaller square in the middle for the default vision range.
|
||||
@@ -54,7 +54,7 @@
|
||||
// Ask carp to swim onto the victim's screen. The AI will then switch to hostile and try to eat them.
|
||||
C.ai_holder?.give_destination(get_turf(victim))
|
||||
else
|
||||
log_debug("Surprise carp attack failed to find any space turfs offscreen to the victim.")
|
||||
log_game("Surprise carp attack failed to find any space turfs offscreen to the victim.")
|
||||
|
||||
// Gets suitable spots for carp to spawn, without risk of going off the edge of the map.
|
||||
// If there is demand for this proc, then it can easily be made independant and moved into one of the helper files.
|
||||
|
||||
@@ -192,7 +192,7 @@
|
||||
current_program = powerdown_program
|
||||
linkedholodeck = locate(projection_area)
|
||||
if(!linkedholodeck)
|
||||
to_world(span_danger("Holodeck computer at [x],[y],[z] failed to locate projection area."))
|
||||
to_chat(world, span_danger("Holodeck computer at [x],[y],[z] failed to locate projection area."))
|
||||
|
||||
//This could all be done better, but it works for now.
|
||||
/obj/machinery/computer/HolodeckControl/Destroy()
|
||||
|
||||
@@ -213,7 +213,7 @@
|
||||
return
|
||||
|
||||
if(!hud_item || !hud_datum)
|
||||
log_error("Mapping device tried to update with missing hud_item or hud_datum")
|
||||
log_runtime("Mapping device tried to update with missing hud_item or hud_datum")
|
||||
stop_updates()
|
||||
last_run()
|
||||
return
|
||||
|
||||
@@ -28,13 +28,13 @@
|
||||
plantname = planttype
|
||||
|
||||
if(!plantname)
|
||||
log_debug("Plantname not provided and and [src] requires it at [x],[y],[z]")
|
||||
log_runtime("Plantname not provided and [src] requires it at [x],[y],[z]")
|
||||
return INITIALIZE_HINT_QDEL
|
||||
|
||||
seed = SSplants.seeds[plantname]
|
||||
|
||||
if(!seed)
|
||||
log_debug("Plant name '[plantname]' does not exist and [src] requires it at [x],[y],[z]")
|
||||
log_runtime("Plant name '[plantname]' does not exist and [src] requires it at [x],[y],[z]")
|
||||
return INITIALIZE_HINT_QDEL
|
||||
|
||||
name = "[seed.seed_name]"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/datum/seed/proc/diverge_mutate_gene(var/decl/plantgene/G, var/turf/T)
|
||||
if(!istype(G))
|
||||
log_debug("Attempted to mutate [src] with a non-plantgene var.")
|
||||
log_runtime("Attempted to mutate [src] with a non-plantgene var.")
|
||||
return src
|
||||
|
||||
var/datum/seed/S = diverge() //Let's not modify all of the seeds.
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
parent = newparent
|
||||
|
||||
if(!SSplants)
|
||||
to_world(span_danger("Plant controller does not exist and [src] requires it. Aborting."))
|
||||
to_chat(world, span_danger("Plant controller does not exist and [src] requires it. Aborting."))
|
||||
return INITIALIZE_HINT_QDEL
|
||||
|
||||
if(!istype(newseed))
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
return
|
||||
var/atom/movable/AM = WF.resolve()
|
||||
if(isnull(AM))
|
||||
log_debug("DEBUG: HasProximity called without reference on [src].")
|
||||
log_runtime("DEBUG: HasProximity called without reference on [src].")
|
||||
return
|
||||
|
||||
if(!is_mature() || seed.get_trait(TRAIT_SPREAD) != 2)
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
if(!seed.growth_stages)
|
||||
seed.update_growth_stages()
|
||||
if(!seed.growth_stages)
|
||||
to_world(span_danger("Seed type [seed.get_trait(TRAIT_PLANT_ICON)] cannot find a growth stage value."))
|
||||
to_chat(world, span_danger("Seed type [seed.get_trait(TRAIT_PLANT_ICON)] cannot find a growth stage value."))
|
||||
return
|
||||
var/overlay_stage = 1
|
||||
if(age >= seed.get_trait(TRAIT_MATURATION))
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
// Validate input. Must be one (and only one) of the key codes)
|
||||
if(isnull(movekey) || (movekey & ~0xFFF) || (movekey & (movekey - 1)))
|
||||
// log_debug("Client [ckey] sent an illegal movement key up: [movekeyName] ([movekey])") // We forward tgui keys nowadays
|
||||
// log_tgui("Client [ckey] sent an illegal movement key up: [movekeyName] ([movekey])") // We forward tgui keys nowadays
|
||||
return
|
||||
|
||||
// Clear bit indicating we were holding the key
|
||||
|
||||
@@ -152,7 +152,7 @@ var/static/list/fake_sunlight_zs = list()
|
||||
turfs_to_use += T
|
||||
|
||||
if(!turfs_to_use.len)
|
||||
warning("Fake sun placed on a level where it can't find any outdoor turfs to color at [x],[y],[z].")
|
||||
WARNING("Fake sun placed on a level where it can't find any outdoor turfs to color at [x],[y],[z].")
|
||||
return
|
||||
|
||||
sun = new(null)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/datum/log_category/admin
|
||||
category = LOG_CATEGORY_ADMIN
|
||||
config_flag = /datum/config_entry/flag/log_admin
|
||||
|
||||
/datum/log_category/admin_dsay
|
||||
category = LOG_CATEGORY_ADMIN_DSAY
|
||||
master_category = /datum/log_category/admin
|
||||
config_flag = /datum/config_entry/flag/log_admin
|
||||
|
||||
// private categories //
|
||||
|
||||
/datum/log_category/admin_private
|
||||
category = LOG_CATEGORY_ADMIN_PRIVATE
|
||||
config_flag = /datum/config_entry/flag/log_admin
|
||||
secret = TRUE
|
||||
|
||||
/datum/log_category/admin_asay
|
||||
category = LOG_CATEGORY_ADMIN_PRIVATE_ASAY
|
||||
master_category = /datum/log_category/admin_private
|
||||
config_flag = /datum/config_entry/flag/log_adminchat
|
||||
secret = TRUE
|
||||
|
||||
/datum/log_category/admin_msay
|
||||
category = LOG_CATEGORY_ADMIN_PRIVATE_MSAY
|
||||
master_category = /datum/log_category/admin_private
|
||||
config_flag = /datum/config_entry/flag/log_adminchat
|
||||
secret = TRUE
|
||||
|
||||
/datum/log_category/admin_esay
|
||||
category = LOG_CATEGORY_ADMIN_PRIVATE_ESAY
|
||||
master_category = /datum/log_category/admin_private
|
||||
config_flag = /datum/config_entry/flag/log_eventchat
|
||||
secret = TRUE
|
||||
@@ -0,0 +1,7 @@
|
||||
/datum/log_category/game_compat
|
||||
category = LOG_CATEGORY_COMPAT_GAME
|
||||
master_category = /datum/log_category/game
|
||||
config_flag = /datum/config_entry/flag/logging_compat_adminprivate
|
||||
|
||||
/datum/config_entry/flag/logging_compat_adminprivate
|
||||
default = FALSE
|
||||
@@ -0,0 +1,23 @@
|
||||
/datum/log_category/debug
|
||||
category = LOG_CATEGORY_DEBUG
|
||||
|
||||
/datum/log_category/debug_sql
|
||||
category = LOG_CATEGORY_DEBUG_SQL
|
||||
master_category = /datum/log_category/debug
|
||||
|
||||
// This is not in the debug master category on purpose, do not add it
|
||||
/datum/log_category/debug_runtime
|
||||
category = LOG_CATEGORY_RUNTIME
|
||||
|
||||
/datum/log_category/debug_mapping
|
||||
category = LOG_CATEGORY_DEBUG_MAPPING
|
||||
master_category = /datum/log_category/debug
|
||||
|
||||
/datum/log_category/debug_mobtag
|
||||
category = LOG_CATEGORY_DEBUG_MOBTAG
|
||||
master_category = /datum/log_category/debug
|
||||
|
||||
/datum/log_category/debug_asset
|
||||
category = LOG_CATEGORY_DEBUG_ASSET
|
||||
config_flag = /datum/config_entry/flag/log_asset
|
||||
master_category = /datum/log_category/debug
|
||||
@@ -0,0 +1,43 @@
|
||||
/datum/log_category/game
|
||||
category = LOG_CATEGORY_GAME
|
||||
config_flag = /datum/config_entry/flag/log_game
|
||||
|
||||
/datum/log_category/game_vote
|
||||
category = LOG_CATEGORY_GAME_VOTE
|
||||
config_flag = /datum/config_entry/flag/log_vote
|
||||
master_category = /datum/log_category/game
|
||||
|
||||
/datum/log_category/game_emote
|
||||
category = LOG_CATEGORY_GAME_EMOTE
|
||||
config_flag = /datum/config_entry/flag/log_emote
|
||||
master_category = /datum/log_category/game
|
||||
|
||||
/datum/log_category/game_topic
|
||||
category = LOG_CATEGORY_GAME_TOPIC
|
||||
config_flag = /datum/config_entry/flag/log_world_topic
|
||||
master_category = /datum/log_category/game
|
||||
|
||||
/datum/log_category/game_say
|
||||
category = LOG_CATEGORY_GAME_SAY
|
||||
config_flag = /datum/config_entry/flag/log_say
|
||||
master_category = /datum/log_category/game
|
||||
|
||||
/datum/log_category/game_whisper
|
||||
category = LOG_CATEGORY_GAME_WHISPER
|
||||
config_flag = /datum/config_entry/flag/log_whisper
|
||||
master_category = /datum/log_category/game
|
||||
|
||||
/datum/log_category/game_ooc
|
||||
category = LOG_CATEGORY_GAME_OOC
|
||||
config_flag = /datum/config_entry/flag/log_ooc
|
||||
master_category = /datum/log_category/game
|
||||
|
||||
/datum/log_category/game_prayer
|
||||
category = LOG_CATEGORY_GAME_PRAYER
|
||||
config_flag = /datum/config_entry/flag/log_prayer
|
||||
master_category = /datum/log_category/game
|
||||
|
||||
/datum/log_category/game_access
|
||||
category = LOG_CATEGORY_GAME_ACCESS
|
||||
config_flag = /datum/config_entry/flag/log_access
|
||||
master_category = /datum/log_category/game
|
||||
@@ -0,0 +1,6 @@
|
||||
/datum/log_category/href
|
||||
category = LOG_CATEGORY_HREF
|
||||
|
||||
/datum/log_category/href_tgui
|
||||
category = LOG_CATEGORY_HREF_TGUI
|
||||
master_category = /datum/log_category/href
|
||||
@@ -0,0 +1,6 @@
|
||||
/datum/log_category/internal
|
||||
category = LOG_CATEGORY_INTERNAL_ERROR
|
||||
|
||||
/datum/log_category/internal_unknown_category
|
||||
category = LOG_CATEGORY_INTERNAL_CATEGORY_NOT_FOUND
|
||||
master_category = /datum/log_category/internal
|
||||
@@ -0,0 +1,20 @@
|
||||
/datum/log_category/attack
|
||||
category = LOG_CATEGORY_ATTACK
|
||||
config_flag = /datum/config_entry/flag/log_attack
|
||||
|
||||
/datum/log_category/supicious_login
|
||||
category = LOG_CATEGORY_SUSPICIOUS_LOGIN
|
||||
config_flag = /datum/config_entry/flag/log_suspicious_login
|
||||
|
||||
/datum/log_category/config
|
||||
category = LOG_CATEGORY_CONFIG
|
||||
|
||||
// Logs seperately, printed into on server shutdown to store hard deletes and such
|
||||
/datum/log_category/qdel
|
||||
category = LOG_CATEGORY_QDEL
|
||||
// We want this human readable so it's easy to see at a glance
|
||||
entry_flags = ENTRY_USE_DATA_W_READABLE
|
||||
|
||||
/datum/log_category/vore
|
||||
category = LOG_CATEGORY_VORE
|
||||
config_flag = /datum/config_entry/flag/log_vore
|
||||
@@ -0,0 +1,3 @@
|
||||
/datum/log_category/pda
|
||||
category = LOG_CATEGORY_PDA
|
||||
config_flag = /datum/config_entry/flag/log_pda
|
||||
@@ -0,0 +1,70 @@
|
||||
/// The main datum that contains all log entries for a category
|
||||
/datum/log_category
|
||||
/// The category name
|
||||
var/category
|
||||
|
||||
/// The schema version of this log category.
|
||||
/// Expected format of "Major.Minor.Patch"
|
||||
var/schema_version = LOG_CATEGORY_SCHEMA_VERSION_NOT_SET
|
||||
|
||||
/// The master category that contains this category
|
||||
var/datum/log_category/master_category
|
||||
|
||||
/// Flags to apply to our /datum/log_entry's
|
||||
/// See code/__DEFINES/logging/dm
|
||||
var/entry_flags = NONE
|
||||
|
||||
/// If set this config flag is checked to enable this log category
|
||||
var/config_flag
|
||||
|
||||
/// Whether or not this log should not be publically visible
|
||||
var/secret = FALSE
|
||||
|
||||
/// The list of header information for this category. Used for log file re-initialization
|
||||
var/list/category_header
|
||||
|
||||
/// Whether the readable version of the log message is formatted internally instead of by rustg
|
||||
/// IF YOU CHANGE THIS VERIFY LOGS ARE STILL PARSED CORRECTLY
|
||||
var/internal_formatting = FALSE
|
||||
|
||||
/// List of log entries for this category
|
||||
var/list/entries = list()
|
||||
|
||||
/// Total number of entries this round so far
|
||||
var/entry_count = 0
|
||||
|
||||
GENERAL_PROTECT_DATUM(/datum/log_category)
|
||||
|
||||
/// Add an entry to this category. It is very important that any data you provide doesn't hold references to anything!
|
||||
/datum/log_category/proc/create_entry(message, list/data, list/semver_store)
|
||||
var/datum/log_entry/entry = new(
|
||||
// world state contains raw timestamp
|
||||
timestamp = logger.human_readable_timestamp(),
|
||||
category = category,
|
||||
message = message,
|
||||
flags = entry_flags,
|
||||
data = data,
|
||||
semver_store = semver_store,
|
||||
)
|
||||
|
||||
write_entry(entry)
|
||||
entry_count += 1
|
||||
if(entry_count <= CONFIG_MAX_CACHED_LOG_ENTRIES)
|
||||
entries += entry
|
||||
|
||||
/// Allows for category specific file splitting. Needs to accept a null entry for the default file.
|
||||
/// If master_category it will always return the output of master_category.get_output_file(entry)
|
||||
/datum/log_category/proc/get_output_file(list/entry, extension = "log.json")
|
||||
if(master_category)
|
||||
return master_category.get_output_file(entry, extension)
|
||||
if(secret)
|
||||
return "[GLOB.log_directory]/secret/[category].[extension]"
|
||||
return "[GLOB.log_directory]/[category].[extension]"
|
||||
|
||||
/// Writes an entry to the output file(s) for the category
|
||||
/datum/log_category/proc/write_entry(datum/log_entry/entry)
|
||||
// config isn't loaded? assume we want human readable logs
|
||||
if(isnull(config) || CONFIG_GET(flag/log_as_human_readable))
|
||||
entry.write_readable_entry_to_file(get_output_file(entry, "log"), format_internally = internal_formatting)
|
||||
|
||||
entry.write_entry_to_file(get_output_file(entry))
|
||||
@@ -0,0 +1,127 @@
|
||||
|
||||
// Schema version must always be the very last element in the array.
|
||||
|
||||
// Current Schema: 1.0.0
|
||||
// [timestamp, category, message, data, world_state, semver_store, id, schema_version]
|
||||
|
||||
/// A datum which contains log information.
|
||||
/datum/log_entry
|
||||
/// Next id to assign to a log entry.
|
||||
var/static/next_id = 0
|
||||
|
||||
/// Unique id of the log entry.
|
||||
var/id
|
||||
|
||||
/// Schema version of the log entry.
|
||||
var/schema_version = "1.0.0"
|
||||
|
||||
/// Unix timestamp of the log entry.
|
||||
var/timestamp
|
||||
|
||||
/// Category of the log entry.
|
||||
var/category
|
||||
|
||||
/// Message of the log entry.
|
||||
var/message
|
||||
|
||||
/// Bitfield that describes how exactly to log stuff exactly
|
||||
/// See code/__DEFINES/logging/dm
|
||||
var/flags = NONE
|
||||
|
||||
/// Data of the log entry; optional.
|
||||
var/list/data
|
||||
|
||||
/// Semver store of the log entry, used to store the schema of data entries
|
||||
var/list/semver_store
|
||||
|
||||
GENERAL_PROTECT_DATUM(/datum/log_entry)
|
||||
|
||||
/datum/log_entry/New(timestamp, category, message, flags, list/data, list/semver_store)
|
||||
..()
|
||||
|
||||
src.id = next_id++
|
||||
src.timestamp = timestamp
|
||||
src.category = category
|
||||
src.flags = flags
|
||||
src.message = message
|
||||
with_data(data)
|
||||
with_semver_store(semver_store)
|
||||
|
||||
/datum/log_entry/proc/with_data(list/data)
|
||||
if(!isnull(data))
|
||||
if(!islist(data))
|
||||
src.data = list("data" = data)
|
||||
stack_trace("Log entry data was not a list, it was [data.type].")
|
||||
else
|
||||
src.data = data
|
||||
return src
|
||||
|
||||
/datum/log_entry/proc/with_semver_store(list/semver_store)
|
||||
if(isnull(semver_store))
|
||||
return
|
||||
if(!islist(semver_store))
|
||||
stack_trace("Log entry semver store was not a list, it was [semver_store.type]. We cannot reliably convert it to a list.")
|
||||
else
|
||||
src.semver_store = semver_store
|
||||
return src
|
||||
|
||||
/// Converts the log entry to a human-readable string.
|
||||
/datum/log_entry/proc/to_readable_text(format = TRUE)
|
||||
var/output = ""
|
||||
if(format)
|
||||
output += "\[[timestamp]\] [uppertext(category)]: [message]"
|
||||
else
|
||||
output += "[uppertext(category)]: [message]"
|
||||
|
||||
if(flags & ENTRY_USE_DATA_W_READABLE)
|
||||
output += json_encode(data, JSON_PRETTY_PRINT)
|
||||
return output
|
||||
|
||||
#define MANUAL_JSON_ENTRY(list, key, value) list.Add("\"[key]\":[(!isnull(value)) ? json_encode(value) : "null"]")
|
||||
|
||||
/// Converts the log entry to a JSON string.
|
||||
/datum/log_entry/proc/to_json_text()
|
||||
// I do not trust byond's json encoder, and need to ensure the order doesn't change.
|
||||
var/list/json_entries = list()
|
||||
MANUAL_JSON_ENTRY(json_entries, LOG_ENTRY_KEY_TIMESTAMP, timestamp)
|
||||
MANUAL_JSON_ENTRY(json_entries, LOG_ENTRY_KEY_CATEGORY, category)
|
||||
MANUAL_JSON_ENTRY(json_entries, LOG_ENTRY_KEY_MESSAGE, message)
|
||||
MANUAL_JSON_ENTRY(json_entries, LOG_ENTRY_KEY_DATA, data)
|
||||
MANUAL_JSON_ENTRY(json_entries, LOG_ENTRY_KEY_WORLD_STATE, world.get_world_state_for_logging())
|
||||
MANUAL_JSON_ENTRY(json_entries, LOG_ENTRY_KEY_SEMVER_STORE, semver_store)
|
||||
MANUAL_JSON_ENTRY(json_entries, LOG_ENTRY_KEY_ID, id)
|
||||
MANUAL_JSON_ENTRY(json_entries, LOG_ENTRY_KEY_SCHEMA_VERSION, schema_version)
|
||||
return "{[json_entries.Join(",")]}"
|
||||
|
||||
#undef MANUAL_JSON_ENTRY
|
||||
|
||||
#define CHECK_AND_TRY_FILE_ERROR_RECOVERY(file) \
|
||||
var/static/in_error_recovery = FALSE; \
|
||||
if(!fexists(##file)) { \
|
||||
if(in_error_recovery) { \
|
||||
in_error_recovery = FALSE; \
|
||||
CRASH("Failed to error recover log file: [file]"); \
|
||||
}; \
|
||||
in_error_recovery = TRUE; \
|
||||
logger.Log(LOG_CATEGORY_INTERNAL_ERROR, "attempting to perform file error recovery: [file]"); \
|
||||
logger.init_category_file(logger.log_categories[category]); \
|
||||
call(src, __PROC__)(arglist(args)); \
|
||||
return; \
|
||||
}; \
|
||||
in_error_recovery = FALSE;
|
||||
|
||||
/// Writes the log entry to a file.
|
||||
/datum/log_entry/proc/write_entry_to_file(file)
|
||||
CHECK_AND_TRY_FILE_ERROR_RECOVERY(file)
|
||||
WRITE_LOG_NO_FORMAT(file, "[to_json_text()]\n")
|
||||
|
||||
/// Writes the log entry to a file as a human-readable string.
|
||||
/datum/log_entry/proc/write_readable_entry_to_file(file, format_internally = TRUE)
|
||||
CHECK_AND_TRY_FILE_ERROR_RECOVERY(file)
|
||||
// If it's being formatted internally we need to manually add a newline
|
||||
if(format_internally)
|
||||
WRITE_LOG_NO_FORMAT(file, "[to_readable_text(format = TRUE)]\n")
|
||||
else
|
||||
WRITE_LOG(file, "[to_readable_text(format = FALSE)]")
|
||||
|
||||
#undef CHECK_AND_TRY_FILE_ERROR_RECOVERY
|
||||
@@ -0,0 +1,356 @@
|
||||
GLOBAL_REAL(logger, /datum/log_holder)
|
||||
/**
|
||||
* Main datum to manage logging actions
|
||||
*/
|
||||
/datum/log_holder
|
||||
/// Round ID, if set, that logging is initialized for
|
||||
var/round_id
|
||||
/// When the log_holder first initialized
|
||||
var/logging_start_timestamp
|
||||
|
||||
/// Associative: category -> datum
|
||||
var/list/datum/log_category/log_categories
|
||||
/// typecache list for categories that exist but are disabled
|
||||
var/list/disabled_categories
|
||||
/// category nesting tree for ui purposes
|
||||
var/list/category_group_tree
|
||||
|
||||
/// list of Log args waiting for processing pending log initialization
|
||||
var/list/waiting_log_calls
|
||||
|
||||
/// Whether or not logging as human readable text is enabled
|
||||
var/human_readable_enabled = FALSE
|
||||
|
||||
/// Cached ui_data
|
||||
var/list/data_cache = list()
|
||||
|
||||
/// Last time the ui_data was updated
|
||||
var/last_data_update = 0
|
||||
|
||||
var/initialized = FALSE
|
||||
var/shutdown = FALSE
|
||||
|
||||
GENERAL_PROTECT_DATUM(/datum/log_holder)
|
||||
|
||||
ADMIN_VERB(log_viewer_new, R_ADMIN|R_DEBUG, "View Round Logs", "View the rounds logs.", ADMIN_CATEGORY_LOGS)
|
||||
logger.tgui_interact(user.mob)
|
||||
|
||||
/datum/log_holder/tgui_interact(mob/user, datum/tgui/ui)
|
||||
if(!check_rights_for(user.client, R_ADMIN))
|
||||
return
|
||||
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(isnull(ui))
|
||||
ui = new(user, src, "LogViewer", "Log Viewer")
|
||||
ui.set_autoupdate(FALSE)
|
||||
ui.open()
|
||||
|
||||
/datum/log_holder/tgui_state(mob/user)
|
||||
return ADMIN_STATE(R_ADMIN | R_DEBUG)
|
||||
|
||||
/datum/log_holder/tgui_static_data(mob/user)
|
||||
var/list/data = list(
|
||||
"round_id" = GLOB.round_id,
|
||||
"logging_start_timestamp" = logging_start_timestamp,
|
||||
)
|
||||
|
||||
var/list/tree = list()
|
||||
data["tree"] = tree
|
||||
var/list/enabled_categories = list()
|
||||
for(var/enabled in log_categories)
|
||||
enabled_categories += enabled
|
||||
tree["enabled"] = enabled_categories
|
||||
|
||||
var/list/disabled_categories = list()
|
||||
for(var/disabled in src.disabled_categories)
|
||||
disabled_categories += disabled
|
||||
tree["disabled"] = disabled_categories
|
||||
|
||||
return data
|
||||
|
||||
/datum/log_holder/tgui_data(mob/user)
|
||||
if(!last_data_update || (world.time - last_data_update) > LOG_UPDATE_TIMEOUT)
|
||||
cache_ui_data()
|
||||
return data_cache
|
||||
|
||||
/datum/log_holder/proc/cache_ui_data()
|
||||
var/list/category_map = list()
|
||||
for(var/datum/log_category/category as anything in log_categories)
|
||||
category = log_categories[category]
|
||||
var/list/category_data = list()
|
||||
|
||||
var/list/entries = list()
|
||||
for(var/datum/log_entry/entry as anything in category.entries)
|
||||
entries += list(list(
|
||||
"id" = entry.id,
|
||||
"message" = entry.message,
|
||||
"timestamp" = entry.timestamp,
|
||||
"data" = entry.data,
|
||||
"semver" = entry.semver_store,
|
||||
))
|
||||
category_data["entries"] = entries
|
||||
category_data["entry_count"] = category.entry_count
|
||||
|
||||
category_map[category.category] = category_data
|
||||
|
||||
data_cache.Cut()
|
||||
last_data_update = world.time
|
||||
|
||||
data_cache["categories"] = category_map
|
||||
data_cache["last_data_update"] = last_data_update
|
||||
|
||||
/datum/log_holder/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
|
||||
switch(action)
|
||||
if("refresh")
|
||||
cache_ui_data()
|
||||
SStgui.update_uis(src)
|
||||
return TRUE
|
||||
else
|
||||
stack_trace("unknown ui_act action [action] for [type]")
|
||||
|
||||
/// Assembles basic information for logging, creating the log category datums and checking for config flags as required
|
||||
/datum/log_holder/proc/init_logging()
|
||||
if(initialized)
|
||||
CRASH("Attempted to call init_logging twice!")
|
||||
|
||||
round_id = GLOB.round_id
|
||||
logging_start_timestamp = rustg_unix_timestamp()
|
||||
log_categories = list()
|
||||
disabled_categories = list()
|
||||
|
||||
human_readable_enabled = CONFIG_GET(flag/log_as_human_readable)
|
||||
|
||||
category_group_tree = assemble_log_category_tree()
|
||||
var/config_flag
|
||||
for(var/datum/log_category/master_category as anything in category_group_tree)
|
||||
var/list/sub_categories = category_group_tree[master_category]
|
||||
sub_categories = sub_categories.Copy()
|
||||
for(var/datum/log_category/sub_category as anything in sub_categories)
|
||||
config_flag = initial(sub_category.config_flag)
|
||||
if(config_flag && !config.Get(config_flag))
|
||||
disabled_categories[initial(sub_category.category)] = TRUE
|
||||
sub_categories -= sub_category
|
||||
continue
|
||||
|
||||
config_flag = initial(master_category.config_flag)
|
||||
if(config_flag && !config.Get(config_flag))
|
||||
disabled_categories[initial(master_category.category)] = TRUE
|
||||
if(!length(sub_categories))
|
||||
continue
|
||||
// enabled, or any of the sub categories are enabled
|
||||
init_log_category(master_category, sub_categories)
|
||||
|
||||
initialized = TRUE
|
||||
|
||||
// process any waiting log calls and then cut the list
|
||||
for(var/list/arg_list as anything in waiting_log_calls)
|
||||
Log(arglist(arg_list))
|
||||
waiting_log_calls?.Cut()
|
||||
|
||||
if(fexists(GLOB.config_error_log))
|
||||
fcopy(GLOB.config_error_log, "[GLOB.log_directory]/config_error.log")
|
||||
fdel(GLOB.config_error_log)
|
||||
|
||||
world._initialize_log_files()
|
||||
|
||||
/// Tells the log_holder to not allow any more logging to be done, and dumps all categories to their json file
|
||||
/datum/log_holder/proc/shutdown_logging()
|
||||
if(shutdown)
|
||||
CRASH("Attempted to call shutdown_logging twice!")
|
||||
shutdown = TRUE
|
||||
|
||||
/// Iterates over all log category types to assemble them into a tree of main category -> (sub category)[] while also checking for loops and sanity errors
|
||||
/datum/log_holder/proc/assemble_log_category_tree()
|
||||
var/static/list/category_tree
|
||||
if(category_tree)
|
||||
return category_tree
|
||||
|
||||
category_tree = list()
|
||||
var/list/all_types = subtypesof(/datum/log_category)
|
||||
var/list/known_categories = list()
|
||||
var/list/sub_categories = list()
|
||||
|
||||
// Assemble the master categories
|
||||
for(var/datum/log_category/category_type as anything in all_types)
|
||||
var/category = initial(category_type.category)
|
||||
if(category in known_categories)
|
||||
stack_trace("log category type '[category_type]' has duplicate category '[category]', skipping")
|
||||
continue
|
||||
|
||||
if(!initial(category_type.schema_version))
|
||||
stack_trace("log category type '[category_type]' does not have a valid schema version, skipping")
|
||||
continue
|
||||
|
||||
var/master_category = initial(category_type.master_category)
|
||||
if(master_category)
|
||||
sub_categories[master_category] += list(category_type)
|
||||
continue
|
||||
category_tree[category_type] = list()
|
||||
|
||||
// Sort the sub categories
|
||||
for(var/datum/log_category/master as anything in sub_categories)
|
||||
if(!(master in category_tree))
|
||||
stack_trace("log category [master] is an invalid master category as it's a sub category")
|
||||
continue
|
||||
for(var/datum/log_category/sub_category as anything in sub_categories[master])
|
||||
if(initial(sub_category.secret) != initial(master.secret))
|
||||
stack_trace("log category [sub_category] has a secret status that differs from its master category [master]")
|
||||
category_tree[master] += list(sub_category)
|
||||
|
||||
return category_tree
|
||||
|
||||
/// Log entry header used to mark a file is being reset
|
||||
#define LOG_CATEGORY_RESET_FILE_MARKER "{\"LOG FILE RESET -- THIS IS AN ERROR\"}"
|
||||
#define LOG_CATEGORY_RESET_FILE_MARKER_READABLE "LOG FILE RESET -- THIS IS AN ERROR"
|
||||
/// Gets a recovery file for the given path. Caches the last known recovery path for each path.
|
||||
/datum/log_holder/proc/get_recovery_file_for(path)
|
||||
var/static/cache
|
||||
if(isnull(cache))
|
||||
cache = list()
|
||||
|
||||
var/count = cache[path] || 0
|
||||
while(fexists("[path].rec[count]"))
|
||||
count++
|
||||
cache[path] = count
|
||||
|
||||
return "[path].rec[count]"
|
||||
|
||||
/// Sets up the given category's file and header.
|
||||
/datum/log_holder/proc/init_category_file(datum/log_category/category)
|
||||
var/file_path = category.get_output_file(null)
|
||||
if(fexists(file_path)) // already exists? implant a reset marker
|
||||
rustg_file_append(LOG_CATEGORY_RESET_FILE_MARKER, file_path)
|
||||
fcopy(file_path, get_recovery_file_for(file_path))
|
||||
rustg_file_write("[json_encode(category.category_header)]\n", file_path)
|
||||
|
||||
if(!human_readable_enabled)
|
||||
return
|
||||
|
||||
file_path = category.get_output_file(null, "log")
|
||||
if(fexists(file_path))
|
||||
rustg_file_append(LOG_CATEGORY_RESET_FILE_MARKER_READABLE, file_path)
|
||||
fcopy(file_path, get_recovery_file_for(file_path))
|
||||
rustg_file_write("\[[human_readable_timestamp()]\] Starting up round ID [round_id].\n - -------------------------\n", file_path)
|
||||
|
||||
#undef LOG_CATEGORY_RESET_FILE_MARKER
|
||||
#undef LOG_CATEGORY_RESET_FILE_MARKER_READABLE
|
||||
|
||||
/// Initializes the given log category and populates the list of contained categories based on the sub category list
|
||||
/datum/log_holder/proc/init_log_category(datum/log_category/category_type, list/datum/log_category/sub_categories)
|
||||
var/datum/log_category/category_instance = new category_type
|
||||
|
||||
var/list/contained_categories = list()
|
||||
for(var/datum/log_category/sub_category as anything in sub_categories)
|
||||
sub_category = new sub_category
|
||||
var/sub_category_actual = sub_category.category
|
||||
sub_category.master_category = category_instance
|
||||
log_categories[sub_category_actual] = sub_category
|
||||
|
||||
if(!semver_to_list(sub_category.schema_version))
|
||||
stack_trace("log category [sub_category_actual] has an invalid schema version '[sub_category.schema_version]'")
|
||||
sub_category.schema_version = LOG_CATEGORY_SCHEMA_VERSION_NOT_SET
|
||||
|
||||
contained_categories += sub_category_actual
|
||||
|
||||
log_categories[category_instance.category] = category_instance
|
||||
|
||||
if(!semver_to_list(category_instance.schema_version))
|
||||
stack_trace("log category [category_instance.category] has an invalid schema version '[category_instance.schema_version]'")
|
||||
category_instance.schema_version = LOG_CATEGORY_SCHEMA_VERSION_NOT_SET
|
||||
|
||||
contained_categories += category_instance.category
|
||||
|
||||
var/list/category_header = list(
|
||||
LOG_HEADER_INIT_TIMESTAMP = logging_start_timestamp,
|
||||
LOG_HEADER_ROUND_ID = GLOB.round_id,
|
||||
LOG_HEADER_SECRET = category_instance.secret,
|
||||
LOG_HEADER_CATEGORY_LIST = contained_categories,
|
||||
LOG_HEADER_CATEGORY = category_instance.category,
|
||||
)
|
||||
|
||||
category_instance.category_header = category_header
|
||||
init_category_file(category_instance, category_header)
|
||||
|
||||
/datum/log_holder/proc/human_readable_timestamp()
|
||||
return rustg_formatted_timestamp("%Y-%m-%d %H:%M:%S%.3f")
|
||||
|
||||
/// Adds an entry to the given category, if the category is disabled it will not be logged.
|
||||
/// If the category does not exist, we will CRASH and log to the error category.
|
||||
/// the data list is optional and will be recursively json serialized.
|
||||
/datum/log_holder/proc/Log(category, message, list/data)
|
||||
// This is Log because log is a byond internal proc
|
||||
|
||||
// do not include the message because these go into the runtime log and we might be secret!
|
||||
if(!istext(message))
|
||||
message = "[message]"
|
||||
stack_trace("Logging with a non-text message")
|
||||
|
||||
if(!category)
|
||||
category = LOG_CATEGORY_INTERNAL_CATEGORY_NOT_FOUND
|
||||
stack_trace("Logging with a null or empty category")
|
||||
|
||||
if(data && !islist(data))
|
||||
data = list("data" = data)
|
||||
stack_trace("Logging with data this is not a list, it will be converted to a list with a single key 'data'")
|
||||
|
||||
if(!initialized) // we are initialized during /world/proc/SetupLogging which is called in /world/New
|
||||
waiting_log_calls += list(list(category, message, data))
|
||||
return
|
||||
|
||||
if(disabled_categories[category])
|
||||
return
|
||||
|
||||
var/datum/log_category/log_category = log_categories[category]
|
||||
if(!log_category)
|
||||
Log(LOG_CATEGORY_INTERNAL_CATEGORY_NOT_FOUND, message, data)
|
||||
CRASH("Attempted to log to a category that doesn't exist! [category]")
|
||||
|
||||
var/list/semver_store = null
|
||||
if(length(data))
|
||||
semver_store = list()
|
||||
data = recursive_jsonify(data, semver_store)
|
||||
log_category.create_entry(message, data, semver_store)
|
||||
|
||||
/// Recursively converts an associative list of datums into their jsonified(list) form
|
||||
/datum/log_holder/proc/recursive_jsonify(list/data_list, list/semvers)
|
||||
if(isnull(data_list))
|
||||
return null
|
||||
|
||||
var/list/jsonified_list = list()
|
||||
for(var/key in data_list)
|
||||
var/datum/data = data_list[key]
|
||||
|
||||
if(isnull(data))
|
||||
pass() // nulls are allowed
|
||||
|
||||
else if(islist(data))
|
||||
data = recursive_jsonify(data, semvers)
|
||||
|
||||
else if(isdatum(data))
|
||||
var/list/options_list = list(
|
||||
SCHEMA_VERSION = LOG_CATEGORY_SCHEMA_VERSION_NOT_SET,
|
||||
)
|
||||
|
||||
var/list/serialization_data = data.serialize_list(options_list, semvers)
|
||||
var/current_semver = semvers[data.type]
|
||||
if(!semver_to_list(current_semver))
|
||||
stack_trace("serialization of data had an invalid semver")
|
||||
semvers[data.type] = LOG_CATEGORY_SCHEMA_VERSION_NOT_SET
|
||||
|
||||
if(!length(serialization_data)) // serialize_list wasn't implemented, and errored
|
||||
stack_trace("serialization data was empty")
|
||||
continue
|
||||
|
||||
data = recursive_jsonify(serialization_data, semvers)
|
||||
|
||||
if(islist(data) && !length(data))
|
||||
stack_trace("recursive_jsonify got an empty list after serialization")
|
||||
continue
|
||||
|
||||
jsonified_list[key] = data
|
||||
|
||||
return jsonified_list
|
||||
@@ -13,7 +13,7 @@
|
||||
. = ..()
|
||||
our_landmark = locate() in src
|
||||
if(!our_landmark)
|
||||
testing("Looking glass area [name] couldn't find a landmark")
|
||||
log_mapping("Looking glass area [name] couldn't find a landmark")
|
||||
for(var/turf/simulated/floor/looking_glass/lgt in src)
|
||||
our_turfs += lgt
|
||||
if(lgt.optional)
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
my_area = lga
|
||||
break
|
||||
if(!istype(my_area))
|
||||
testing("Looking glass console [x],[y],[x] not in a looking glass area.")
|
||||
log_mapping("Looking glass console [x],[y],[x] not in a looking glass area.")
|
||||
if(!supported_programs.len)
|
||||
supported_programs["Off"] = null
|
||||
supported_programs["Diagnostics"] = image(icon = 'icons/skybox/skybox.dmi', icon_state = "diagnostic")
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
|
||||
/obj/effect/landmark/looking_glass/proc/gain_viewer(var/client/C)
|
||||
if(C in viewers)
|
||||
testing("Looking Glass [x],[y],[z] tried to add a duplicate viewer.")
|
||||
log_mapping("Looking Glass [x],[y],[z] tried to add a duplicate viewer.")
|
||||
viewers |= C
|
||||
if(holding)
|
||||
show_to(C)
|
||||
|
||||
/obj/effect/landmark/looking_glass/proc/lose_viewer(var/client/C)
|
||||
if(!(C in viewers))
|
||||
testing("Looking Glass [x],[y],[z] tried to remove a viewer it didn't have")
|
||||
log_mapping("Looking Glass [x],[y],[z] tried to remove a viewer it didn't have")
|
||||
viewers -= C
|
||||
if(holding)
|
||||
unshow_to(C)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user