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:
Selis
2025-09-11 17:28:20 +02:00
committed by GitHub
co-authored by Kashargul
parent 67fa43bd5a
commit b0f0f4685f
403 changed files with 4013 additions and 2309 deletions
+14 -7
View File
@@ -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)")
+4 -4
View File
@@ -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
View File
@@ -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,
+4
View File
@@ -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)
+1 -2
View File
@@ -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
+1 -1
View File
@@ -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> - "
+3 -1
View File
@@ -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))
+1 -1
View File
@@ -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 -3
View File
@@ -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
+1 -1
View File
@@ -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 -1
View File
@@ -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.",
+4 -4
View File
@@ -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,
+24 -22
View File
@@ -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"
+2 -2
View File
@@ -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)
+2 -2
View File
@@ -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."))
+4 -4
View File
@@ -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!
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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]")
+3 -3
View File
@@ -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>.")
+1 -1
View File
@@ -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.")
+1 -1
View File
@@ -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.")
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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.
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
. = ..()
+3 -3
View File
@@ -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")
+1 -1
View File
@@ -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
+3
View File
@@ -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//
////////////////////////////////////
+63 -20
View File
@@ -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)
+107
View File
@@ -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])
+2 -2
View File
@@ -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")
+14 -14
View File
@@ -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
+1 -1
View File
@@ -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"])
+2 -2
View File
@@ -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)
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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.
+1 -1
View File
@@ -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)
-9
View File
@@ -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
+119 -81
View File
@@ -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
+129 -141
View File
@@ -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>&lt;&lt;&lt;</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 += "&nbsp;&nbsp;" + 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 : ..()
-6
View File
@@ -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
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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()
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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)
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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()
+4 -4
View File
@@ -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
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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()
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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 -1
View File
@@ -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
+70
View File
@@ -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))
+127
View File
@@ -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
+356
View File
@@ -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
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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")
+2 -2
View File
@@ -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