[MIRROR] reimplements attack and player logs (#12553)

Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2026-03-19 15:03:53 -07:00
committed by GitHub
parent bb90e792ca
commit 99afe463dc
50 changed files with 411 additions and 270 deletions
+25 -12
View File
@@ -76,7 +76,7 @@ CREATE TABLE IF NOT EXISTS `erro_feedback` (
`var_value` int(16) DEFAULT NULL,
`details` text,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Table structure for table `erro_player`
CREATE TABLE IF NOT EXISTS `erro_player` (
@@ -93,7 +93,7 @@ CREATE TABLE IF NOT EXISTS `erro_player` (
KEY `discord_id` (`discord_id`),
KEY `computerid` (`computerid`),
KEY `ip` (`ip`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Table structure for table `erro_poll_option`
CREATE TABLE IF NOT EXISTS `erro_poll_option` (
@@ -107,7 +107,7 @@ CREATE TABLE IF NOT EXISTS `erro_poll_option` (
`descmid` varchar(32) DEFAULT NULL,
`descmax` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Table structure for table `erro_poll_question`
CREATE TABLE IF NOT EXISTS `erro_poll_question` (
@@ -118,7 +118,7 @@ CREATE TABLE IF NOT EXISTS `erro_poll_question` (
`question` varchar(255) NOT NULL,
`adminonly` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Table structure for table `erro_poll_textreply`
CREATE TABLE IF NOT EXISTS `erro_poll_textreply` (
@@ -130,7 +130,7 @@ CREATE TABLE IF NOT EXISTS `erro_poll_textreply` (
`replytext` text NOT NULL,
`adminrank` varchar(32) NOT NULL DEFAULT 'Player',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Table structure for table `erro_poll_vote`
CREATE TABLE IF NOT EXISTS `erro_poll_vote` (
@@ -143,7 +143,7 @@ CREATE TABLE IF NOT EXISTS `erro_poll_vote` (
`adminrank` varchar(32) NOT NULL,
`rating` int(2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Table structure for table `erro_privacy`
CREATE TABLE IF NOT EXISTS `erro_privacy` (
@@ -152,7 +152,20 @@ CREATE TABLE IF NOT EXISTS `erro_privacy` (
`ckey` varchar(32) NOT NULL,
`option` varchar(128) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Dumping structure for table ss13.erro_dialog
CREATE TABLE IF NOT EXISTS `erro_dialog` (
`mid` int(11) NOT NULL AUTO_INCREMENT,
`time` datetime NOT NULL,
`ckey` varchar(32) NOT NULL,
`color` varchar(16) DEFAULT NULL,
`mob` varchar(128) DEFAULT NULL,
`area` varchar(128) DEFAULT NULL,
`type` varchar(32) NOT NULL,
`message` text NOT NULL,
PRIMARY KEY (`mid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Table structure for table `death`
CREATE TABLE IF NOT EXISTS `death` (
@@ -172,7 +185,7 @@ CREATE TABLE IF NOT EXISTS `death` (
`fireloss` INT(11) NOT NULL,
`oxyloss` INT(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Table structure for table `karma`
@@ -188,7 +201,7 @@ CREATE TABLE IF NOT EXISTS `karma` (
`spenderip` TEXT NOT NULL,
`time` DATETIME NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Table structure for table `karmatotals`
CREATE TABLE IF NOT EXISTS `karmatotals` (
@@ -196,7 +209,7 @@ CREATE TABLE IF NOT EXISTS `karmatotals` (
`byondkey` TEXT NOT NULL,
`karma` INT(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Table structure for table `library`
CREATE TABLE IF NOT EXISTS `library` (
@@ -206,7 +219,7 @@ CREATE TABLE IF NOT EXISTS `library` (
`content` TEXT NOT NULL,
`category` TEXT NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ;
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ;
-- Table structure for table `population`
CREATE TABLE IF NOT EXISTS `population` (
@@ -215,7 +228,7 @@ CREATE TABLE IF NOT EXISTS `population` (
`admincount` INT(11) NULL DEFAULT NULL,
`time` DATETIME NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Table structure for table `vr_player_hours`
CREATE TABLE IF NOT EXISTS `vr_player_hours` (
+27
View File
@@ -38,6 +38,33 @@
#define LOG_SPEECH_INDICATORS (1 << 22)
#define LOG_TRANSPORT (1 << 23)
GLOBAL_ALIST_INIT(logtype_to_string, alist(
LOG_ATTACK = "ATTACK",
LOG_SAY = "SAY",
LOG_WHISPER = "WHISPER",
LOG_EMOTE = "EMOTE",
LOG_DSAY = "DEADSAY",
LOG_PDA = "PDA",
LOG_CHAT = "CHAT",
LOG_COMMENT = "COMMENT",
LOG_TELECOMMS = "RADIO",
LOG_OOC = "OOC",
LOG_LOOC = "LOOC",
LOG_ADMIN = "ADMIN",
LOG_OWNERSHIP = "OWNERSHIP",
LOG_GAME = "GAME",
LOG_ADMIN_PRIVATE = "ADMIN_PRIV",
LOG_ASAY = "ASAY",
LOG_MECHA = "MECH",
LOG_VIRUS = "VIRUS",
LOG_SHUTTLE = "SHUTTLE",
LOG_ECON = "ECON",
LOG_VICTIM = "VICTIM",
LOG_RADIO_EMOTE = "RADIO_EMOTE",
LOG_SPEECH_INDICATORS = "SPEECH_INDICATOR",
LOG_TRANSPORT = "TRANSPORT"
))
//Individual logging panel pages
#define INDIVIDUAL_GAME_LOG (LOG_GAME)
#define INDIVIDUAL_ATTACK_LOG (LOG_ATTACK | LOG_VICTIM)
+2 -2
View File
@@ -29,11 +29,11 @@
var/postfix = "[sobject][saddition][hp]"
var/message = "[what_done] [starget][postfix]"
user.log_message(message, LOG_ATTACK, color="red")
user.log_message(message, LOG_ATTACK, color="#ff0000")
if(user != target)
var/reverse_message = "was [what_done] by [ssource][postfix]"
target.log_message(reverse_message, LOG_VICTIM, color="orange", log_globally=FALSE)
target.log_message(reverse_message, LOG_VICTIM, color="#ffb300", log_globally=FALSE)
/// Log for vore interactions
/proc/log_vore(text, list/data)
+13
View File
@@ -0,0 +1,13 @@
/proc/db_log_insert(mob/log_target, message, type, color)
set waitfor = FALSE
if(!ismob(log_target))
return
if(!SSdbcore.IsConnected())
return
var/datum/db_query/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, area, type, color, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_area, :message_type, :message_color, :message_content)",
list("sender_ckey" = log_target.ckey, "sender_mob" = log_target.real_name, "message_area" = "[loc_name(log_target)]", "message_type" = "[type]", "message_color" = color, "message_content" = message))
if(!query_insert.Execute())
log_sql("Error during logging: "+query_insert.ErrorMsg())
qdel(query_insert)
return
qdel(query_insert)
+20 -32
View File
@@ -13,42 +13,30 @@
return
// Cannot use the list as a map if the key is a number, so we stringify it (thank you BYOND)
var/smessage_type = num2text(message_type, MAX_BITFLAG_DIGITS)
if(HAS_CONNECTED_PLAYER(src))
if(!islist(persistent_client.logging[smessage_type]))
persistent_client.logging[smessage_type] = list()
if(!islist(logging[smessage_type]))
logging[smessage_type] = list()
var/colored_message = message
if(color)
if(color[1] == "#")
colored_message = "<font color=[color]>[message]</font>"
else
colored_message = "<font color='[color]'>[message]</font>"
var/smessage_type = GLOB.logtype_to_string[message_type]
//This makes readability a bit better for admins.
switch(message_type)
if(LOG_WHISPER)
colored_message = "(WHISPER) [colored_message]"
if(LOG_OOC)
colored_message = "(OOC) [colored_message]"
if(LOG_LOOC)
colored_message = "(LOOC) [colored_message]"
if(LOG_ASAY)
colored_message = "(ASAY) [colored_message]"
if(LOG_EMOTE)
colored_message = "(EMOTE) [colored_message]"
if(LOG_RADIO_EMOTE)
colored_message = "(RADIOEMOTE) [colored_message]"
var/list/timestamped_message = list(
"time" = world.timeofday,
"name" = name,
"ckey" = ckey,
"loc" = loc_name(src),
"event_id" = LAZYLEN(logging[smessage_type]),
"message" = message,
"color" = color
)
var/list/timestamped_message = list("\[[time_stamp(format = "YYYY-MM-DD hh:mm:ss")]\] [key_name_and_tag(src)] [loc_name(src)] (Event #[LAZYLEN(logging[smessage_type])])" = colored_message)
logging[smessage_type] += timestamped_message
if(!CONFIG_GET(flag/database_logging))
if(!islist(logging[smessage_type]))
logging[smessage_type] = list()
UNTYPED_LIST_ADD(logging[smessage_type], timestamped_message)
if(HAS_CONNECTED_PLAYER(src))
persistent_client.logging[smessage_type] += timestamped_message
if(CONFIG_GET(flag/database_logging))
db_log_insert(src, message, smessage_type, color)
else
if(!islist(persistent_client.logging[smessage_type]))
persistent_client.logging[smessage_type] = list()
UNTYPED_LIST_ADD(persistent_client.logging[smessage_type], timestamped_message)
..()
+2 -2
View File
@@ -11,10 +11,10 @@
* * log_globally - boolean checking whether or not we write this log to the log file
* * forced_by - source that forced the dialogue if any
*/
/atom/proc/log_talk(message, message_type, tag = null, log_globally = TRUE, forced_by = null, custom_say_emote = null)
/atom/proc/log_talk(message, message_type, tag = null, log_globally = TRUE, forced_by = null, custom_say_emote = null, color)
var/prefix = tag ? "([tag]) " : ""
var/suffix = forced_by ? " FORCED by [forced_by]" : ""
log_message("[prefix][custom_say_emote ? "*[custom_say_emote]*, " : ""]\"[message]\"[suffix]", message_type, log_globally = log_globally)
log_message("[prefix][custom_say_emote ? "*[custom_say_emote]*, " : ""]\"[message]\"[suffix]", message_type, color, log_globally)
/// Logging for generic spoken messages
/proc/log_say(text, list/data)
-5
View File
@@ -151,13 +151,8 @@ Proc for attack log creation, because really why not
add_attack_logs(user,M,what_done,admin_notify)
return
var/user_str = key_name(user)
var/target_str = key_name(target)
if(ismob(user))
user.attack_log += text("\[[time_stamp()]\] [span_red("Attacked [target_str]: [what_done]")]")
if(ismob(target))
target.attack_log += text("\[[time_stamp()]\] [span_orange("Attacked by [user_str]: [what_done]")]")
log_combat(user, target, what_done)
if(admin_notify)
msg_admin_attack("[key_name_admin(user)] vs [target_str]: [what_done]")
@@ -729,7 +729,7 @@
/datum/config_entry/flag/allow_simple_mob_recolor
/// Chatlogs are now saved by calling the chatlogging library instead of letting the clients handle it
/// REQUIRES an database
/// REQUIRES a database
/datum/config_entry/flag/chatlog_database_backend
default = FALSE
@@ -737,6 +737,11 @@
/// REQUIRES chatlog_database_backend to be enabled
/datum/config_entry/string/chatlog_database_api_endpoint
/// Enables logging dialogue and attack logs to the database
/// REQUIRES a database
/datum/config_entry/flag/database_logging
default = FALSE
/datum/config_entry/flag/forbid_admin_profiling
/datum/config_entry/flag/pixel_size_limit
+5
View File
@@ -44,6 +44,11 @@ SUBSYSTEM_DEF(dbcore)
/datum/controller/subsystem/dbcore/Initialize()
Connect()
if(IsConnected() && CONFIG_GET(flag/database_logging))
var/datum/db_query/query_truncate = NewQuery("TRUNCATE erro_dialog")
if(!query_truncate.Execute())
log_sql("ERROR TRYING TO CLEAR erro_dialog: "+query_truncate.ErrorMsg())
qdel(query_truncate)
return SS_INIT_SUCCESS
/datum/controller/subsystem/dbcore/stat_entry(msg)
-1
View File
@@ -41,7 +41,6 @@ SUBSYSTEM_DEF(mobs)
// Lets handle all of these while we have time, should always remain extremely small...
if(length(death_list)) // Don't contact DB if this list is empty
if(CONFIG_GET(flag/sql_enabled))
establish_db_connection()
if(!SSdbcore.IsConnected())
log_game("SQL ERROR during death reporting. Failed to connect.")
else
@@ -20,7 +20,6 @@ SUBSYSTEM_DEF(persist)
if(!CONFIG_GET(flag/time_off))
return
establish_db_connection()
if(!SSdbcore.IsConnected())
src.currentrun.Cut()
return
-2
View File
@@ -6,7 +6,6 @@
for(var/mob/M in GLOB.player_list)
if(M.client)
playercount += 1
establish_db_connection()
if(!SSdbcore.IsConnected())
log_game("SQL ERROR during population polling. Failed to connect.")
else
@@ -47,7 +46,6 @@
log_game("Round ended without any feedback being generated. No feedback was sent to the database.")
return
establish_db_connection()
if(!SSdbcore.IsConnected())
log_game("SQL ERROR during feedback reporting. Failed to connect.")
else
@@ -20,7 +20,7 @@
/obj/item/mecha_parts/mecha_equipment/anticcw_armor_booster/handle_melee_contact(var/obj/item/W, var/mob/living/user, var/inc_damage = null)
if(!action_checks(user))
return inc_damage
chassis.log_message("Attacked by [W]. Attacker - [user]")
chassis.log_message("Attacked by [W]. Attacker - [user]", LOG_GAME)
if(prob(chassis.deflect_chance*deflect_coeff))
to_chat(user, span_danger("\The [W] bounces off \the [chassis]'s armor."))
chassis.log_append_to_last("Armor saved.")
+2 -2
View File
@@ -2803,7 +2803,7 @@
src.log_append_to_last("Armor saved.")
src.occupant_message(span_notice("\The [user]'s attack is stopped by the armor."))
visible_message(span_infoplain(span_bold("\The [user]") + " rebounds off [src.name]'s armor!"))
user.attack_log += text("\[[time_stamp()]\] [span_red("attacked [src.name]")]")
add_attack_logs(user, src, "attacked")
playsound(src, 'sound/weapons/slash.ogg', 50, 1, -1)
else if(damage < damage_minimum)//Pathetic damage levels just don't harm MECH. //CHOMPedit temp_damage_minimum -> damage_minimum
@@ -2818,7 +2818,7 @@
if(damage > internal_damage_minimum) //Only decently painful attacks trigger a chance of mech damage.
src.check_for_internal_damage(list(MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST))
visible_message(span_danger("[user] [attack_message] [src]!"))
user.attack_log += text("\[[time_stamp()]\] [span_red("attacked [src.name]")]")
add_attack_logs(user, src, "attacked")
return 1
+3 -3
View File
@@ -285,7 +285,7 @@
else
deflect_chance = initial(deflect_chance)
src.occupant_message(span_red("You disable [src] defence mode."))
src.log_message("Toggled defence mode.")
src.log_message("Toggled defence mode.", LOG_GAME)
return
@@ -363,7 +363,7 @@
return
if(src.occupant.client)
src.zoom = !src.zoom
src.log_message("Toggled zoom mode.")
src.log_message("Toggled zoom mode.", LOG_GAME)
if(src.zoom)
src.occupant_message(span_blue("Zoom mode enabled."))
else
@@ -390,7 +390,7 @@
if(src.occupant)
if(get_charge() > 0)
thrusters = !thrusters
src.log_message("Toggled thrusters.")
src.log_message("Toggled thrusters.", LOG_GAME)
if(src.thrusters)
src.occupant_message(span_blue("Thrusters enabled."))
else
-71
View File
@@ -645,75 +645,6 @@ GLOBAL_VAR_INIT(world_topic_spam_protect_time, world.timeofday)
if (src.status != s)
src.status = s
#define FAILED_DB_CONNECTION_CUTOFF 5
GLOBAL_VAR_INIT(failed_db_connections, 0)
/hook/startup/proc/connectDB()
if(!CONFIG_GET(flag/sql_enabled))
log_sql("SQL connection disabled in config.")
else if(!setup_database_connection())
log_sql("Your server failed to establish a connection with the feedback database.")
else
log_sql("Feedback database connection established.")
return 1
/proc/setup_database_connection()
if(!CONFIG_GET(flag/sql_enabled))
return 0
if(GLOB.failed_db_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to conenct anymore.
return 0
if(!SSdbcore)
SSdbcore = new()
var/user = CONFIG_GET(string/feedback_login)
var/pass = CONFIG_GET(string/feedback_password)
var/db = CONFIG_GET(string/feedback_database)
var/address = CONFIG_GET(string/address)
var/port = CONFIG_GET(number/port)
SSdbcore.Connect("dbi:mysql:[db]:[address]:[port]","[user]","[pass]")
. = SSdbcore.IsConnected()
if ( . )
GLOB.failed_db_connections = 0 //If this connection succeeded, reset the failed connections counter.
return
GLOB.failed_db_connections++ //If it failed, increase the failed connections counter.
log_sql(SSdbcore.ErrorMsg())
return
//This proc ensures that the connection to the feedback database (global variable dbcon) is established
/proc/establish_db_connection()
if(GLOB.failed_db_connections > FAILED_DB_CONNECTION_CUTOFF)
return 0
if(!SSdbcore || !SSdbcore.IsConnected())
return setup_database_connection()
else
return 1
// Cleans up DB connections and recreates them
/proc/reset_database_connections()
var/list/results = list("-- Resetting DB connections --")
GLOB.failed_db_connections = 0
if(SSdbcore?.IsConnected())
SSdbcore.Disconnect()
results += "SSdbcore was connected and asked to disconnect"
else
results += "SSdbcore was not connected"
if(!CONFIG_GET(flag/sql_enabled))
results += "stopping because config.sql_enabled = false"
else
. = setup_database_connection()
if(.)
results += "SUCCESS: set up a connection successfully with setup_database_connection()"
else
results += "FAIL: failed to connect to the database with setup_database_connection()"
results += "-- DB Reset End --"
log_sql(results.Join("\n"))
// Things to do when a new z-level was just made.
/world/proc/max_z_changed()
if(!istype(GLOB.players_by_zlevel, /list))
@@ -784,8 +715,6 @@ GLOBAL_VAR_INIT(failed_db_connections, 0)
/world/proc/on_tickrate_change()
SStimer?.reset_buckets()
#undef FAILED_DB_CONNECTION_CUTOFF
/proc/auxtools_stack_trace(msg)
CRASH(msg)
-4
View File
@@ -4,7 +4,6 @@
if(!check_rights(R_MOD,0) && !check_rights(R_BAN)) return
establish_db_connection()
if(!SSdbcore.IsConnected())
return
@@ -126,7 +125,6 @@
if(job)
sql += " AND job = '[job]'"
establish_db_connection()
if(!SSdbcore.IsConnected())
return
@@ -224,7 +222,6 @@
var/sql = "SELECT ckey FROM erro_ban WHERE id = [id]"
establish_db_connection()
if(!SSdbcore.IsConnected())
return
@@ -276,7 +273,6 @@
if(!check_rights_for(user, R_BAN))
return
establish_db_connection()
if(!SSdbcore.IsConnected())
to_chat(usr, span_filter_adminlog("[span_red("Failed to establish database connection")]"))
return
+1 -1
View File
@@ -40,7 +40,7 @@
var/ckeytext = ckey(key)
if(!establish_db_connection())
if(!SSdbcore.IsConnected())
var/msg = "Ban database connection failure. Key [ckeytext] not checked"
log_world(msg)
message_admins(msg)
-29
View File
@@ -1,7 +1,5 @@
/atom/var/lastattacker = null
/mob/var/lastattacked = null
/mob/var/attack_log = list( )
/mob/var/dialogue_log = list( )
/proc/log_and_message_admins(var/message as text, var/mob/user = usr)
log_admin(user ? "[key_name(user)] [message]" : "EVENT [message]")
@@ -17,30 +15,3 @@
log_admin("[english_list(user_keys)] [message]")
message_admins("[english_list(user_keys)] [message]")
/* Old procs
/proc/admin_attack_log(var/mob/attacker, var/mob/victim, var/attacker_message, var/victim_message, var/admin_message)
if(victim)
victim.attack_log += text("\[[time_stamp()]\]" + span_orange(" [key_name(attacker)] - [victim_message]"))
if(attacker)
attacker.attack_log += text("\[[time_stamp()]\]" + span_red(" [key_name(victim)] - [attacker_message]"))
msg_admin_attack("[key_name(attacker)] [admin_message] [key_name(victim)] (INTENT: [attacker? uppertext(attacker.a_intent) : "N/A"]) (<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[attacker.x];Y=[attacker.y];Z=[attacker.z]'>JMP</a>)")
/proc/admin_attacker_log_many_victims(var/mob/attacker, var/list/mob/victims, var/attacker_message, var/victim_message, var/admin_message)
if(!victims || !victims.len)
return
for(var/mob/victim in victims)
admin_attack_log(attacker, victim, attacker_message, victim_message, admin_message)
/proc/admin_inject_log(mob/attacker, mob/victim, obj/item, reagents, amount_transferred, violent=0)
if(violent)
violent = "violently "
else
violent = ""
admin_attack_log(attacker,
victim,
"used \the [weapon] to [violent]inject - [reagents] - [amount_transferred]u transferred",
"was [violent]injected with \the [weapon] - [reagents] - [amount_transferred]u transferred",
"used \the [weapon] to [violent]inject [reagents] ([amount_transferred]u transferred) into")
*/
+71 -52
View File
@@ -1,65 +1,84 @@
/client/proc/cmd_admin_check_player_logs(mob/living/M as mob in GLOB.mob_list)
set category = "Admin.Logs"
set name = "Check Player Attack Logs"
set desc = "Check a player's attack logs."
show_cmd_admin_check_player_logs(M)
ADMIN_VERB_ONLY_CONTEXT_MENU(cmd_admin_check_player_logs, R_ADMIN|R_MOD, "Check Mob Logs", mob/living/player in GLOB.mob_list)
user.show_cmd_admin_check_player_logs(player.logging, player.name, player.ckey, player.mind?.special_role)
//Views specific attack logs belonging to one player.
/client/proc/show_cmd_admin_check_player_logs(mob/living/M)
var/dat = span_bold("[M]'s Attack Log:<HR>")
dat += span_bold("Viewing attack logs of [M]") + " - (Played by ([key_name(M)]).<br>"
if(M.mind)
dat += span_bold("Current Antag?:") + " [(M.mind.special_role)?"Yes":"No"]<br>"
dat += "<br>" + span_bold("Note:") + " This is arranged from earliest to latest. <br><br>"
/client/proc/show_cmd_admin_check_player_logs(list/mob_data, target_name, target_ckey, target_special)
if(CONFIG_GET(flag/database_logging))
var/datum/db_query/query = SSdbcore.NewQuery("SELECT mid,time,ckey,mob,area,color,type,message from erro_dialog WHERE ckey = :t_ckey", list("t_ckey" = ckey))
if(!query.Execute())
to_chat(src, span_admin("Database query error"))
qdel(query)
return
var/list/data = list()
while(query.NextRow())
var/list/timestamped_message = list(
"event_id" = query.item[1],
"time" = query.item[2],
"ckey" = query.item[3],
"name" = query.item[4],
"loc" = query.item[5],
"color" = query.item[6],
"message" = query.item[8]
)
var/entry_type = query.item[7]
if(!islist(data[entry_type]))
data[entry_type] = list()
UNTYPED_LIST_ADD(data[entry_type], timestamped_message)
qdel(query)
if(!length(data))
to_chat(src, span_admin("No data found."))
return
admin_log_view_target(data, target_name, target_ckey, target_special)
return
if(!length(mob_data))
to_chat(src, span_admin("No data found."))
return
admin_log_view_target(mob_data, target_name, target_ckey, target_special)
if(!isemptylist(M.attack_log))
dat += "<fieldset style='border: 2px solid white; display: inline'>"
for(var/l in M.attack_log)
dat += "[l]<br>"
ADMIN_VERB(persistent_client_logs, R_ADMIN|R_MOD, "Check Player Logs", "Displays the client logs of the selected ckey.", ADMIN_CATEGORY_LOGS)
var/mob/living/selected_key = tgui_input_list(user, "Select a ckey to check their logs", "Ckey", GLOB.persistent_clients_by_ckey)
if(!selected_key)
return
dat += "</fieldset>"
var/datum/persistent_client/selected = GLOB.persistent_clients_by_ckey[selected_key]
else
dat += span_italics("No attack logs found for [M].")
user.show_cmd_admin_check_player_logs(selected.logging, selected.mob?.name, selected_key, selected.mob?.mind?.special_role)
var/datum/browser/popup = new(usr, "admin_attack_log", "[src]", 650, 650, src)
popup.set_content(jointext(dat,null))
popup.open()
/client/proc/admin_log_view_target(list/log_data, name, ckey, special, use_db)
var/datum/player_log_viwer/log_view = new(log_data, name, ckey, special)
log_view.tgui_interact(mob)
onclose(usr, "admin_attack_log")
/datum/player_log_viwer
var/list/log_data
var/target_name
var/target_ckey
var/special_role
feedback_add_details("admin_verb","PL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/player_log_viwer/tgui_close(mob/user)
. = ..()
log_data = null
/client/proc/cmd_admin_check_dialogue_logs(mob/living/M as mob in GLOB.mob_list)
set category = "Admin.Logs"
set name = "Check Player Dialogue Logs"
set desc = "Check a player's dialogue logs."
show_cmd_admin_check_dialogue_logs(M)
/datum/player_log_viwer/New(list/data, name, ckey, special)
. = ..()
log_data = data
target_name = name
target_ckey = ckey
special_role = special
//Views specific dialogue logs belonging to one player.
/client/proc/show_cmd_admin_check_dialogue_logs(mob/living/M)
var/dat = span_bold("[M]'s Dialogue Log:<HR>")
dat += span_bold("Viewing say and emote logs of [M]") + " - (Played by ([key_name(M)]).<br>"
if(M.mind)
dat += span_bold("Current Antag?:") + " [(M.mind.special_role)?"Yes":"No"]<br>"
dat += "<br>" + span_bold("Note:") + " This is arranged from earliest to latest. <br><br>"
/datum/player_log_viwer/tgui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "PlayerLogViewer")
ui.open()
if(!isemptylist(M.dialogue_log))
dat += "<fieldset style='border: 2px solid white; display: inline'>"
/datum/player_log_viwer/tgui_state(mob/user)
return ADMIN_STATE(R_ADMIN|R_MOD)
for(var/d in M.dialogue_log)
dat += "[d]<br>"
dat += "</fieldset>"
else
dat += span_italics("No dialogue logs found for [M].")
var/datum/browser/popup = new(usr, "admin_dialogue_log", "[src]", 650, 650, src)
popup.set_content(jointext(dat,null))
popup.open()
onclose(usr, "admin_dialogue_log")
feedback_add_details("admin_verb","PDL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/player_log_viwer/tgui_static_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
return list(
"entries" = log_data,
"name" = target_name,
"ckey" = target_ckey,
"special" = special_role
)
@@ -20,8 +20,6 @@ GLOBAL_LIST_INIT(admin_verbs_admin, list(
/client/proc/cmd_admin_pm_context, //right-click adminPM interface,
/client/proc/cmd_admin_pm_panel, //admin-pm list,
/client/proc/cmd_admin_subtle_message, //send an message to somebody as a 'voice in their head',
/client/proc/cmd_admin_check_player_logs, //checks a player's attack logs,
/client/proc/cmd_admin_check_dialogue_logs, //checks a player's dialogue logs,
/datum/admins/proc/access_news_network, //allows access of newscasters,
/client/proc/jumptocoord, //we ghost and jump to a coordinate,
/client/proc/Getmob, //teleports a mob to our location,
@@ -174,8 +172,6 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, list(
/datum/admins/proc/toggleguests,
/datum/admins/proc/announce,
/client/proc/cmd_admin_subtle_message,
/client/proc/cmd_admin_check_player_logs,
/client/proc/cmd_admin_check_dialogue_logs,
/datum/admins/proc/access_news_network,
/client/proc/cmd_admin_direct_narrate,
/client/proc/cmd_admin_world_narrate,
@@ -277,8 +273,6 @@ GLOBAL_LIST_INIT(admin_verbs_event_manager, list(
/client/proc/cmd_admin_pm_context, //right-click adminPM interface,
/client/proc/cmd_admin_pm_panel, //admin-pm list,
/client/proc/cmd_admin_subtle_message, //send an message to somebody as a 'voice in their head',
/client/proc/cmd_admin_check_player_logs, //checks a player's attack logs,
/client/proc/cmd_admin_check_dialogue_logs, //checks a player's dialogue logs,
/datum/admins/proc/access_news_network, //allows access of newscasters,
/client/proc/jumptocoord, //we ghost and jump to a coordinate,
/client/proc/Getmob, //teleports a mob to our location,
-1
View File
@@ -683,7 +683,6 @@ ADMIN_VERB(removetickets, R_ADMIN, "Security Tickets", "Allows one to remove tic
var/dat = "<HEAD><TITLE>Book Inventory Management</TITLE></HEAD><BODY>\n"
dat += "<h3>ADMINISTRATIVE MANAGEMENT</h3>"
establish_db_connection()
if(!SSdbcore.IsConnected())
dat += span_red(span_bold("ERROR") + ": Unable to contact External Archive. Please contact your system administrator for assistance.")
+1 -1
View File
@@ -69,7 +69,7 @@ DEBUG
GLOB.jobban_keylist=list()
log_admin("jobban_keylist was empty")
else
if(!establish_db_connection())
if(!SSdbcore.IsConnected())
log_sql("Database connection failed. Reverting to the legacy ban system.")
CONFIG_SET(flag/ban_legacy_system, TRUE)
jobban_loadbanfile()
+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]"))))
src.mob.log_talk("(AOOC) [msg]", LOG_OOC)
src.mob.log_talk("(AOOC) [msg]", LOG_OOC, color="#ff0000")
@@ -31,7 +31,6 @@ GLOBAL_VAR_INIT(inactive_keys, "None<br>")
if(GLOB.checked_for_inactives)
return
establish_db_connection()
if(!SSdbcore.IsConnected())
return
-1
View File
@@ -15,7 +15,6 @@
else
log_admin("Database already disconnected")
establish_db_connection()
var/errno = SSdbcore.ErrorMsg()
if(errno)
log_admin("Database connection returned error message `[errno]`. Aborting.")
-2
View File
@@ -437,7 +437,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
// Returns null if no DB connection can be established, or -1 if the requested key was not found in the database
/proc/get_player_age(key)
establish_db_connection()
if(!SSdbcore.IsConnected())
return null
@@ -458,7 +457,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if ( IsGuestKey(src.key) )
return
establish_db_connection()
if(!SSdbcore.IsConnected())
return
+2 -2
View File
@@ -42,7 +42,7 @@
message_admins("[key_name_admin(src)] has attempted to post a link in OOC: [msg]")
return
src.mob.log_talk(msg, LOG_OOC)
src.mob.log_talk(msg, LOG_OOC, color="#0051ff")
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
src.mob.log_message(msg, LOG_LOOC)
src.mob.log_message(msg, LOG_LOOC, color="#53cbff")
if(msg)
handle_spam_prevention(MUTE_LOOC)
@@ -235,8 +235,7 @@
else
to_chat(victim, span_danger("Searing hot oil scorches your [E ? E.name : "flesh"]!"))
user.attack_log += text("\[[time_stamp()]\] [span_red("Has [cook_type] \the [victim] ([victim.ckey]) in \a [src]")]")
victim.attack_log += text("\[[time_stamp()]\] [span_orange("Has been [cook_type] in \a [src] by [user.name] ([user.ckey])")]")
add_attack_logs(user, victim, "has been [cook_type] with [src]")
msg_admin_attack("[key_name_admin(user)] [cook_type] \the [victim] ([victim.ckey]) in \a [src]. (<A href='byond://?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[user.x];Y=[user.y];Z=[user.z]'>JMP</a>)")
//Coat the victim in some oil
-6
View File
@@ -43,7 +43,6 @@
<A href='byond://?src=\ref[src];setauthor=1'>Filter by Author: [author]</A><BR>
<A href='byond://?src=\ref[src];search=1'>\[Start Search\]</A><BR>"}
if(1)
establish_db_connection()
if(!SSdbcore.IsConnected())
dat += span_red(span_bold("ERROR") + ": Unable to contact External Archive. Please contact your system administrator for assistance.") + "<BR>"
else if(!SQLquery)
@@ -263,7 +262,6 @@
<A href='byond://?src=\ref[src];switchscreen=0'>No.</A><BR>"}
if(8)
dat += "<h3>External Archive</h3>" //VOREStation Edit
establish_db_connection()
//dat += "<h3>" + span_red("arning: System Administrator has slated this archive for removal. Personal uploads should be taken to the NT board of internal literature.") + "</h3>" //VOREStation Removal
@@ -302,7 +300,6 @@
var/dat = "<HEAD><TITLE>Book Inventory Management</TITLE></HEAD><BODY>\n" // <META HTTP-EQUIV='Refresh' CONTENT='10'>
dat += "<h3>ADMINISTRATIVE MANAGEMENT</h3>"
establish_db_connection()
if(!SSdbcore.IsConnected())
dat += span_red(span_bold("ERROR") + ": Unable to contact External Archive. Please contact your system administrator for assistance.")
@@ -423,7 +420,6 @@
if(scanner.cache.unique)
tgui_alert_async(usr, "This book has been rejected from the database. Aborting!")
else
establish_db_connection()
if(!SSdbcore.IsConnected())
tgui_alert_async(usr, "Connection to Archive has been severed. Aborting.")
else
@@ -448,7 +444,6 @@
if(href_list["targetid"])
var/sqlid = sanitizeSQL(href_list["targetid"])
establish_db_connection()
if(!SSdbcore.IsConnected())
tgui_alert_async(usr, "Connection to Archive has been severed. Aborting.")
if(bibledelay)
@@ -480,7 +475,6 @@
if(!check_rights(R_ADMIN))
return
var/sqlid = sanitizeSQL(href_list["delid"])
establish_db_connection()
if(!SSdbcore.IsConnected())
tgui_alert_async(usr, "Connection to Archive has been severed. Aborting.")
else
@@ -65,7 +65,6 @@
if(ismob(loc))
var/mob/M = loc
T = get_turf(M) //Where is the body located?
attack_log = M.attack_log //preserve our attack logs by copying them to our ghost
gender = M.gender
if(M.mind && M.mind.name)
name = M.mind.name
+2 -2
View File
@@ -4,7 +4,7 @@
if(!message)
return
log_talk("(GHOST SAY) [message]", LOG_SAY)
log_talk("(GHOST SAY) [message]", LOG_SAY, color="#9c00bc")
if (client)
if(message)
@@ -20,7 +20,7 @@
if(!message)
return
log_message("(GHOST EMOTE) [message]", LOG_EMOTE)
log_message("(GHOST EMOTE) [message]", LOG_EMOTE, color="#9c00bc")
if(client)
if(message)
+2 -2
View File
@@ -423,12 +423,12 @@ GLOBAL_LIST_EMPTY(channel_to_radio_key)
blooploop(message, extrarange = -6, volume = 25, sound_preference = /datum/preference/toggle/whisper_sounds)
// playsound(T, pick(voice_sounds_list), 25, TRUE, extrarange = -6, falloff = 1 , is_global = TRUE, frequency = voice_freq, ignore_walls = TRUE, preference = /datum/preference/toggle/whisper_sounds) //CHOMPEdit - ignore_walls = TRUE
log_talk(message, LOG_WHISPER)
log_talk(message, LOG_WHISPER, color="#aeaeae")
else
if(do_sound && message)
blooploop(message, volume = 75)
// playsound(T, pick(voice_sounds_list), 75, TRUE, falloff = 1 , is_global = TRUE, frequency = voice_freq, ignore_walls = TRUE, preference = /datum/preference/toggle/say_sounds) //CHOMPEdit - ignore_walls = TRUE
log_talk(message, LOG_SAY)
log_talk(message, LOG_SAY, color="#c0c0c0")
return 1
/*************************************************************************/
@@ -67,7 +67,6 @@
if(!client) return 0
if(href_list["privacy_poll"])
establish_db_connection()
if(!SSdbcore.IsConnected())
return
var/voted = 0
-6
View File
@@ -1,6 +1,5 @@
/mob/new_player/proc/handle_privacy_poll()
establish_db_connection()
if(!SSdbcore.IsConnected())
return
var/voted = 0
@@ -47,7 +46,6 @@
var/optiontext
/mob/new_player/proc/handle_player_polling()
establish_db_connection()
if(SSdbcore.IsConnected())
var/isadmin = 0
if(src.client && check_rights_for(src.client, R_HOLDER))
@@ -81,7 +79,6 @@
/mob/new_player/proc/poll_player(var/pollid = -1)
if(pollid == -1) return
establish_db_connection()
if(SSdbcore.IsConnected())
var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT starttime, endtime, question, polltype, multiplechoiceoptions FROM erro_poll_question WHERE id = [pollid]")
@@ -343,7 +340,6 @@
if(!isnum(pollid) || !isnum(optionid))
return
establish_db_connection()
if(SSdbcore.IsConnected())
var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT starttime, endtime, question, polltype, multiplechoiceoptions FROM erro_poll_question WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime")
@@ -415,7 +411,6 @@
if(!isnum(pollid) || !istext(replytext))
return
establish_db_connection()
if(SSdbcore.IsConnected())
var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT starttime, endtime, question, polltype FROM erro_poll_question WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime")
@@ -474,7 +469,6 @@
if(!isnum(pollid) || !isnum(optionid))
return
establish_db_connection()
if(SSdbcore.IsConnected())
var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT starttime, endtime, question, polltype FROM erro_poll_question WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime")
+2 -2
View File
@@ -366,7 +366,7 @@
G.client?.prefs?.read_preference(/datum/preference/toggle/ghost_see_whisubtle))
if(client?.prefs?.read_preference(/datum/preference/toggle/whisubtle_vis) || check_rights_for(G.client, R_HOLDER))
to_chat(G, span_psay("[formatted_name] thinks, \"[message]\""))
M.log_talk("(PSAY) [message]", LOG_SAY)
M.log_talk("(PSAY) [message]", LOG_SAY, color="#d900ff")
else //There wasn't anyone to send the message to, pred or prey, so let's just say it instead and correct our psay just in case.
M.forced_psay = FALSE
M.say(message)
@@ -480,7 +480,7 @@
G.client?.prefs?.read_preference(/datum/preference/toggle/ghost_see_whisubtle))
if(client?.prefs?.read_preference(/datum/preference/toggle/whisubtle_vis) || check_rights_for(G.client, R_HOLDER))
to_chat(G, span_pemote("[formatted_name] [message]"))
M.log_talk(message, LOG_SAY)
M.log_talk(message, LOG_SAY, color="#d900ff")
else //There wasn't anyone to send the message to, pred or prey, so let's just emote it instead and correct our psay just in case.
M.forced_psay = FALSE
M.me_verb(message)
@@ -107,7 +107,7 @@
type = MESSAGE_TYPE_NIF,
html = span_nif(span_bold("\[[icon2html(nif.big_icon, CS.client)]NIF\]") + message))
sender.log_talk("NSAY (NIF:[nif.human.real_name]): [message]", LOG_SAY)
sender.log_talk("NSAY (NIF:[nif.human.real_name]): [message]", LOG_SAY, color="#ff00c8")
/datum/nifsoft/soulcatcher/proc/emote_into(var/message, var/mob/living/sender, var/mob/eyeobj, var/whisper)
var/sender_name = eyeobj ? eyeobj.name : sender.name
@@ -135,7 +135,7 @@
type = MESSAGE_TYPE_NIF,
html = span_nif(span_bold("\[[icon2html(nif.big_icon,CS.client)]NIF\]") + message))
sender.log_message("NME (NIF:[nif.human.real_name]): [message]", LOG_EMOTE)
sender.log_message("NME (NIF:[nif.human.real_name]): [message]", LOG_EMOTE, color="#ff00c8")
/datum/nifsoft/soulcatcher/proc/show_settings(var/mob/living/carbon/human/H)
set waitfor = FALSE
+1 -1
View File
@@ -179,7 +179,7 @@
PM.receive_message(list("sent" = 0, "owner" = "[pda.owner]", "job" = "[pda.ownjob]", "message" = "[t]", "target" = "\ref[pda]"), "\ref[pda]")
SStgui.update_user_uis(U, P) // Update the sending user's PDA UI so that they can see the new message
U.log_message("(PDA: [src.name] | [U.real_name]) sent \"[t]\" to [P.name]", LOG_PDA)
U.log_message("(PDA: [src.name] | [U.real_name]) sent \"[t]\" to [P.name]", LOG_PDA, color="#00ff00")
to_chat(U, "[icon2html(pda,U.client)] <b>Sent message to [P.owner] ([P.ownjob]), </b>\"[t]\"")
else
to_chat(U, span_notice("ERROR: Messaging server is not responding.\n\n\
+1 -1
View File
@@ -21,7 +21,7 @@
if(user in target) //no wrapping closets that you are inside - it's not physically possible
return
user.attack_log += text("\[[time_stamp()]\] [span_blue("Has used [src.name] on \ref[target]")]")
add_attack_logs(user, target, "has been wrapped with [src]")
if (istype(target, /obj/item) && !(istype(target, /obj/item/storage) && !istype(target,/obj/item/storage/box)))
var/obj/item/O = target
-1
View File
@@ -363,7 +363,6 @@ GLOBAL_DATUM(blackbox, /obj/machinery/blackbox_recorder)
if(!feedback) return
round_end_data_gathering() //round_end time logging and some other data processing
establish_db_connection()
if(!SSdbcore.IsConnected()) return
var/round_id
+2 -2
View File
@@ -383,7 +383,7 @@
else
destroy_shields()
my_tool.set_ready_state(TRUE)
my_tool.log_message("Power lost.")
my_tool.log_message("Power lost.", LOG_GAME)
else
my_tool.set_ready_state(TRUE)
@@ -409,5 +409,5 @@
..()
my_mecha.use_power(my_tool.energy_drain)
if(!active && shield_health < shield_regen_amount)
my_tool.log_message("Shield overloaded.")
my_tool.log_message("Shield overloaded.", LOG_GAME)
my_mecha.use_power(my_tool.energy_drain * 4)
+1 -1
View File
@@ -88,7 +88,7 @@
take_charge(user, skipcharge)
before_cast(targets) //applies any overlays and effects
user.attack_log += text("\[[time_stamp()]\] [span_red("[user.real_name] ([user.ckey]) cast the spell [name].")]")
add_attack_logs(user, targets, "casted the spell [name]")
if(prob(critfailchance))
critfail(targets, user)
else
+1 -1
View File
@@ -283,7 +283,7 @@
else
user.add_spell(S)
to_chat(user, span_notice("you rapidly read through the arcane book. Suddenly you realize you understand [spellname]!"))
user.attack_log += text("\[[time_stamp()]\] [span_orange("[user.real_name] ([user.ckey]) learned the spell [spellname] ([S]).")]")
add_attack_logs(user, src, "learned the spell [spellname] ([S]).")
onlearned(user)
/obj/item/spellbook/oneuse/proc/recoil(mob/user as mob)
+1 -1
View File
@@ -418,7 +418,7 @@
if(!damage)
return
visible_message(span_danger("[user] [attack_message] the [src]!"))
user.attack_log += text("\[[time_stamp()]\] [span_red("attacked [src.name]")]")
add_attack_logs(user, src, "attacked")
user.do_attack_animation(src)
src.health -= damage
if(mechanical && prob(10))
+2 -2
View File
@@ -104,7 +104,7 @@
for(var/mob/living/carbon/brain/caught_soul/CS as anything in brainmobs)
to_chat(CS, message)
sender.log_talk("NSAY (NIF:[owner.real_name]): [message]", LOG_SAY)
sender.log_talk("NSAY (SC:[owner.real_name]): [message]", LOG_SAY, color="#ff006f")
// Forwards the emotes of captured souls
/obj/soulgem/proc/use_emote(var/message, var/mob/living/sender, var/mob/eyeobj, var/whisper)
@@ -127,7 +127,7 @@
for(var/mob/living/carbon/brain/caught_soul/CS as anything in brainmobs)
to_chat(CS, message)
sender.log_message("NME (NIF:[owner.real_name]): [message]", LOG_EMOTE)
sender.log_message("NME (SC:[owner.real_name]): [message]", LOG_EMOTE, color="#ff006f")
// The capture function which transfers the given mob's mind into the soulcatcher
/obj/soulgem/proc/catch_mob(var/mob/M, var/custom_name)
+5
View File
@@ -590,6 +590,11 @@ JUKEBOX_TRACK_FILES config/jukebox.json
# REQUIRES chatlog_database_backend to be enabled
#CHATLOG_DATABASE_API_ENDPOINT https://example.com
# Logs player logs inside the Database. Reduces memory usage, but increases cpu load
# REQUIRES an database
# Uncomment to enable
#DATABASE_LOGGING
## Uncomment to block granting profiling privileges to users with R_DEBUG, for performance purposes
#FORBID_ADMIN_PROFILING
@@ -0,0 +1,11 @@
import { formatTime } from 'tgui-core/format';
export function stripHtml(str: string) {
return str.replace(/<[^>]+>/g, '');
}
export function displayTime(time: string | number): string {
if (typeof time === 'number') return formatTime(time);
const d = new Date(time);
return d.toLocaleTimeString();
}
@@ -0,0 +1,176 @@
import { useState } from 'react';
import { useBackend } from 'tgui/backend';
import { Window } from 'tgui/layouts';
import { Box, Button, Input, Section, Stack, Tabs } from 'tgui-core/components';
import { displayTime, stripHtml } from './function';
import type { Data, ExtendedLogEntry } from './types';
export const PlayerLogViewer = (props) => {
const { data } = useBackend<Data>();
const [activeTab, setActiveTab] = useState('');
const [search, setSearch] = useState('');
const [searchRegex, setSearchRegex] = useState(false);
const [caseSensitive, setCaseSensitive] = useState(false);
if (!search && searchRegex) {
setSearchRegex(false);
}
const { entries, name, ckey, special } = data;
const allEntries = Object.entries(entries)
.flatMap(([category, logs]) =>
logs.map((entry) => ({ ...entry, category: category })),
)
.sort((a, b) => {
if (a.time !== b.time)
return displayTime(a.time).localeCompare(displayTime(b.time));
return a.event_id - b.event_id;
});
const entriesWithAll: Record<string, ExtendedLogEntry[]> = {
ALL: allEntries,
...entries,
};
if (!activeTab && Object.keys(entriesWithAll).length > 0) {
setActiveTab('ALL');
}
return (
<Window width={800} height={600}>
<Window.Content>
<Stack vertical fill>
<Stack.Item>
<Section title="General Information">
<Stack vertical>
<Stack.Item>
{`Viewing logs of `}
<Box inline bold>
{name}
</Box>
{`, played by `}
<Box inline bold>
{ckey ?? '<No Player>'}
</Box>
</Stack.Item>
<Stack.Item>
{`Characrer special role: `}
<Box inline color={special ? 'green' : 'red'}>
{special ? special : '<None>'}
</Box>
</Stack.Item>
</Stack>
</Section>
</Stack.Item>
<Stack.Item>
<Tabs scrollable>
{Object.keys(entriesWithAll).map((entry) => (
<Tabs.Tab
selected={entry === activeTab}
key={entry}
onClick={() => setActiveTab(entry)}
>
{entry}
</Tabs.Tab>
))}
</Tabs>
</Stack.Item>
<Stack.Item grow>
<Section
fill
scrollable
title={`Active Log: ${activeTab}`}
buttons={
<Stack>
<Stack.Item>
<Input
width="200px"
placeholder="Search"
value={search}
onChange={setSearch}
/>
</Stack.Item>
<Stack.Item>
<Button
icon="code"
tooltip="RegEx Search"
selected={searchRegex}
onClick={() => setSearchRegex(!searchRegex)}
/>
</Stack.Item>
<Stack.Item>
<Button
icon="font"
selected={caseSensitive}
tooltip="Case Sensitive"
onClick={() => setCaseSensitive(!caseSensitive)}
/>
</Stack.Item>
<Stack.Item>
<Button
icon="trash"
tooltip="Clear Search"
color="bad"
onClick={() => {
setSearch('');
setSearchRegex(false);
}}
/>
</Stack.Item>
</Stack>
}
>
<Stack fill vertical>
{entriesWithAll[activeTab] ? (
entriesWithAll[activeTab]
.filter((entry) => {
if (!search) return true;
const cleanMessage = stripHtml(entry.message);
if (searchRegex) {
try {
const regex = new RegExp(
search,
caseSensitive ? 'g' : 'gi',
);
return regex.test(cleanMessage);
} catch {
return true;
}
} else {
if (caseSensitive) return cleanMessage.includes(search);
return cleanMessage
.toLowerCase()
.includes(search.toLowerCase());
}
})
.map((log_entry) => (
<Stack.Item
key={`${log_entry.category}-${log_entry.event_id}`}
>
<Box inline>{displayTime(log_entry.time)}</Box>
<Box inline preserveWhitespace bold>
{` ${log_entry.name}/${log_entry.ckey}`}
</Box>
<Box inline preserveWhitespace>
{` at (${log_entry.loc}): `}
</Box>
<Box inline preserveWhitespace color={log_entry.color}>
{stripHtml(log_entry.message)}
</Box>
</Stack.Item>
))
) : (
<Box color="red" textAlign="center">
No Entry Selected.
</Box>
)}
</Stack>
</Section>
</Stack.Item>
</Stack>
</Window.Content>
</Window>
);
};
@@ -0,0 +1,18 @@
export type Data = {
entries: Record<string, LogEntry[]>;
name: string | null;
ckey: string;
special: string | null;
};
type LogEntry = {
event_id: number;
time: number | string;
ckey: string | null;
name: string;
loc: string | null;
color: string | null;
message: string;
};
export type ExtendedLogEntry = { category: string } & Required<LogEntry>;
+1
View File
@@ -440,6 +440,7 @@
#include "code\_helpers\logging\attack.dm"
#include "code\_helpers\logging\debug.dm"
#include "code\_helpers\logging\game.dm"
#include "code\_helpers\logging\log_to_db.dm"
#include "code\_helpers\logging\mob.dm"
#include "code\_helpers\logging\pda.dm"
#include "code\_helpers\logging\research.dm"