Adds a new logging system and a logging view (#13115)

* Super early initial commit

* Why do I keep comitting this

* in between

* In between

* Sort fix. Transfer fix. HTML and more

* Scrolling + define values change

* Search fixes and time input fixes

* Minor tweaks. Fuel tank inclusion. Fixes

* derp

* Extra logging to fuel tank

* minor stuff

* add the message to admins for fueltanks

* Don't keep mob/atom references + fixes

* line fixes?

* Review improvements

* pois comment
This commit is contained in:
farie82
2020-03-21 17:28:20 -06:00
committed by GitHub
parent e691ea2b36
commit 604daa05e1
44 changed files with 419 additions and 42 deletions
+6
View File
@@ -0,0 +1,6 @@
#define ATTACK_LOG "Attack"
#define DEFENSE_LOG "Defense"
#define CONVERSION_LOG "Conversion"
#define SAY_LOG "Say"
#define EMOTE_LOG "Emote"
#define MISC_LOG "Misc"
+22 -13
View File
@@ -274,7 +274,7 @@ Proc for attack log creation, because really why not
This is always put in the attack log.
*/
/proc/add_attack_logs(mob/user, mob/target, what_done, custom_level)
/proc/add_attack_logs(atom/user, target, what_done, custom_level)
if(islist(target)) // Multi-victim adding
var/list/targets = target
for(var/mob/M in targets)
@@ -282,24 +282,33 @@ This is always put in the attack log.
return
var/user_str = key_name_log(user) + COORD(user)
var/target_str = key_name_log(target) + COORD(target)
if(istype(user))
user.create_attack_log("<font color='red'>Attacked [target_str]: [what_done]</font>")
if(istype(target))
target.create_attack_log("<font color='orange'>Attacked by [user_str]: [what_done]</font>")
var/target_str
if(isatom(target))
var/atom/AT = target
target_str = key_name_log(AT) + COORD(AT)
else
target_str = target
var/mob/MU = user
var/mob/MT = target
if(istype(MU))
MU.create_log(ATTACK_LOG, what_done, target, get_turf(user))
MU.create_attack_log("<font color='red'>Attacked [target_str]: [what_done]</font>")
if(istype(MT))
MT.create_log(DEFENSE_LOG, what_done, user, get_turf(MT))
MT.create_attack_log("<font color='orange'>Attacked by [user_str]: [what_done]</font>")
log_attack(user_str, target_str, what_done)
var/loglevel = ATKLOG_MOST
if(!isnull(custom_level))
loglevel = custom_level
else if(istype(target))
if(istype(user) && !user.ckey && !target.ckey) // Attacks between NPCs are only shown to admins with ATKLOG_ALL
loglevel = ATKLOG_ALL
else if(!user.ckey || !target.ckey || (user.ckey == target.ckey)) // Player v NPC combat is de-prioritized. Also no self-harm, nobody cares
loglevel = ATKLOG_ALMOSTALL
else if(istype(MT))
if(istype(MU))
if(!MU.ckey && !MT.ckey) // Attacks between NPCs are only shown to admins with ATKLOG_ALL
loglevel = ATKLOG_ALL
else if(!MU.ckey || !MT.ckey || (MU.ckey == MT.ckey)) // Player v NPC combat is de-prioritized. Also no self-harm, nobody cares
loglevel = ATKLOG_ALMOSTALL
else
var/area/A = get_area(target)
var/area/A = get_area(MT)
if(A && A.hide_attacklogs)
loglevel = ATKLOG_ALMOSTALL
if(isLivingSSD(target)) // Attacks on SSDs are shown to admins with any log level except ATKLOG_NONE. Overrides custom level
+15 -1
View File
@@ -50,10 +50,24 @@
return "[date_portion]T[time_portion]"
/proc/gameTimestamp(format = "hh:mm:ss", wtime=null)
if(!wtime)
if(wtime == null)
wtime = world.time
return time2text(wtime - GLOB.timezoneOffset, format)
// max hh:mm:ss supported
/proc/timeStampToNum(timestamp)
var/list/splits = text2numlist(timestamp, ":")
. = 0
var/split_len = length(splits)
for(var/i = 1 to length(splits))
switch(split_len - i)
if(2)
. += splits[i] HOURS
if(1)
. += splits[i] MINUTES
if(0)
. += splits[i] SECONDS
/* This is used for displaying the "station time" equivelent of a world.time value
Calling it with no args will give you the current time, but you can specify a world.time-based value as an argument
- You can use this, for example, to do "This will expire at [station_time_at(world.time + 500)]" to display a "station time" expiration date
+10 -3
View File
@@ -1895,8 +1895,15 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
#undef DELTA_CALC
// This proc gets a list of all "points of interest" (poi's) that can be used by admins to track valuable mobs or atoms (such as the nuke disk).
/proc/getpois(mobs_only=0,skip_mindless=0)
/*
* This proc gets a list of all "points of interest" (poi's) that can be used by admins to track valuable mobs or atoms (such as the nuke disk).
* @param mobs_only if set to TRUE it won't include locations to the returned list
* @param skip_mindless if set to TRUE it will skip mindless mobs
* @param force_include_bots if set to TRUE it will include bots even if skip_mindless is set to TRUE
* @param force_include_cameras if set to TRUE it will include camera eyes even if skip_mindless is set to TRUE
* @return returns a list with the found points of interest
*/
/proc/getpois(mobs_only = FALSE, skip_mindless = FALSE, force_include_bots = FALSE, force_include_cameras = FALSE)
var/list/mobs = sortmobs()
var/list/names = list()
var/list/pois = list()
@@ -1904,7 +1911,7 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
for(var/mob/M in mobs)
if(skip_mindless && (!M.mind && !M.ckey))
if(!isbot(M) && !istype(M, /mob/camera/))
if(!(force_include_bots && isbot(M)) && !(force_include_cameras && istype(M, /mob/camera)))
continue
if(M.client && M.client.holder && M.client.holder.fakekey) //stealthmins
continue
+1 -1
View File
@@ -31,7 +31,7 @@
/datum/proc/vv_get_var(var_name)
switch(var_name)
if("attack_log", "debug_log")
if("attack_log_old", "debug_log")
return debug_variable(var_name, vars[var_name], 0, src, sanitize = FALSE)
if("vars")
return debug_variable(var_name, list(), 0, src)
+37
View File
@@ -0,0 +1,37 @@
/datum/log_record
var/log_type // Type of log
var/raw_time // When did this happen?
var/what // What happened
var/who // Who did it
var/target // Who/what was targeted (can be a string)
var/turf/where // Where did it happen
/datum/log_record/New(_log_type, _who, _what, _target, _where, _raw_time)
log_type = _log_type
who = get_subject_text(_who)
what = _what
target = get_subject_text(_target)
if(!_where)
_where = get_turf(_who)
where = _where
if(!_raw_time)
_raw_time = world.time
raw_time = _raw_time
/datum/log_record/proc/get_subject_text(subject)
if(ismob(subject) || isclient(subject) || istype(subject, /datum/mind))
return key_name_admin(subject)
if(isatom(subject))
var/atom/A = subject
return A.name
if(istype(subject, /datum))
var/datum/D = subject
return D.type
return subject
/proc/compare_log_record(datum/log_record/A, datum/log_record/B)
var/time_diff = A.raw_time - B.raw_time
if(!time_diff) // Same time
return cmp_text_asc(A.log_type, B.log_type)
return time_diff
+232
View File
@@ -0,0 +1,232 @@
#define ALL_LOGS list(ATTACK_LOG, DEFENSE_LOG, CONVERSION_LOG, SAY_LOG, EMOTE_LOG, MISC_LOG)
/datum/log_viewer
var/time_from = 0
var/time_to = 4 HOURS // 4 Hours should be enough. INFINITY would screw the UI up
var/list/selected_mobs = list() // The mobs in question
var/list/selected_log_types = list() // The log types being searched for
var/list/log_records = list() // Found and sorted records
/datum/log_viewer/proc/clear_all()
selected_mobs.Cut()
selected_log_types.Cut()
time_from = initial(time_from)
time_to = initial(time_to)
log_records.Cut()
return
/datum/log_viewer/proc/search()
log_records.Cut() // Empty the old results
var/list/invalid_mobs = list()
for(var/i in selected_mobs)
var/mob/M = i
if(!M || QDELETED(M))
invalid_mobs |= M
continue
for(var/log_type in selected_log_types)
var/list/logs = M.logs[log_type]
var/len_logs = length(logs)
if(len_logs)
var/start_index = get_earliest_log_index(logs)
if(!start_index) // No log found that matches the starting time criteria
continue
var/end_index = get_latest_log_index(logs)
if(!end_index) // No log found that matches the end time criteria
continue
log_records.Add(logs.Copy(start_index, end_index + 1))
if(invalid_mobs.len)
to_chat(usr, "<span class='warning'>The search criteria contained invalid mobs. They have been removed from the criteria.</span>")
for(var/i in invalid_mobs)
selected_mobs -= i // Cleanup
log_records = sortTim(log_records, /proc/compare_log_record)
/** Binary search like implementation to find the earliest log
* Returns the index of the earliest log using the time_from value for the given list of logs.
* It will return 0 if no log after time_from is found
*/
/datum/log_viewer/proc/get_earliest_log_index(list/logs)
if(!time_from)
return 1
var/start = 1
var/end = length(logs)
var/mid
do
mid = round_down((end + start) / 2)
var/datum/log_record/L = logs[mid]
if(L.raw_time >= time_from)
end = mid
else
start = mid
while(end - start > 1)
var/datum/log_record/L = logs[end]
if(L.raw_time >= time_from) // Check if there is atleast one valid log
return end
return 0
/** Binary search like implementation to find the latest log
* Returns the index of the latest log using the time_to value (1 second is added to prevent rounding weirdness) for the given list of logs.
* It will return 0 if no log before time_to + 10 is found
*/
/datum/log_viewer/proc/get_latest_log_index(list/logs)
if(world.time < time_to)
return length(logs)
var/end = length(logs)
var/start = 1
var/mid
var/max_time = time_to + 10
do
mid = round((end + start) / 2 + 0.5)
var/datum/log_record/L = logs[mid]
if(L.raw_time >= max_time)
end = mid
else
start = mid
while(end - start > 1)
var/datum/log_record/L = logs[start]
if(L.raw_time < max_time) // Check if there is atleast one valid log
return start
return 0
/datum/log_viewer/proc/add_mob(mob/user, mob/M)
if(!M || !user)
return
selected_mobs |= M
show_ui(user)
/datum/log_viewer/proc/show_ui(mob/user)
var/all_log_types = ALL_LOGS
var/trStyleTop = "border-top:2px solid; border-bottom:2px solid; padding-top: 5px; padding-bottom: 5px;"
var/trStyle = "border-top:1px solid; border-bottom:1px solid; padding-top: 5px; padding-bottom: 5px;"
var/dat
dat += "<head><style>.adminticket{border:2px solid} td{border:1px solid grey;} th{border:1px solid grey;} span{float:left;width:150px;}</style></head>"
dat += "<div style='height:15vh'>"
dat += "<span>Time Search Range:</span> <a href='?src=[UID()];start_time=1'>[gameTimestamp(wtime = time_from)]</a>"
dat += " To: <a href='?src=[UID()];end_time=1'>[gameTimestamp(wtime = time_to)]</a>"
dat += "<BR>"
dat += "<span>Mobs being used:</span>"
for(var/i in selected_mobs)
var/mob/M = i
dat += "<a href='?src=[UID()];remove_mob=\ref[M]'>[M.name]</a>"
dat += "<a href='?src=[UID()];add_mob=1'>Add Mob</a>"
dat += "<a href='?src=[UID()];clear_mobs=1'>Clear All Mobs</a>"
dat += "<BR>"
dat += "<span>Log Types:</span>"
for(var/i in all_log_types)
var/log_type = i
var/enabled = (log_type in selected_log_types)
var/text
var/style
if(enabled)
text = "<b>[log_type]</b>"
style = "background: [get_logtype_color(i)]"
else
text = log_type
dat += "<a href='?src=[UID()];toggle_log_type=[log_type]' style='[style]'>[text]</a>"
dat += "<BR>"
dat += "<a href='?src=[UID()];clear_all=1'>Clear All Settings</a>"
dat += "<a href='?src=[UID()];search=1'>Search</a>"
dat += "</div>"
// Search results
var/tdStyleTime = "width:80px; text-align:center;"
var/tdStyleType = "width:80px; text-align:center;"
var/tdStyleWho = "width:300px; text-align:center;"
var/tdStyleWhere = "width:150px; text-align:center;"
dat += "<div style='overflow-y: auto; max-height:76vh;'>"
dat += "<table style='width:100%; border: 1px solid;'>"
dat += "<tr style='[trStyleTop]'><th style='[tdStyleTime]'>When</th><th style='[tdStyleType]'>Type</th><th style='[tdStyleWho]'>Who</th><th>What</th><th>Target</th><th style='[tdStyleWhere]'>Where</th></tr>"
for(var/i in log_records)
var/datum/log_record/L = i
var/time = gameTimestamp(wtime = L.raw_time - 9.99) // The time rounds up for some reason. Will result in weird filtering results
dat +="<tr style='[trStyle]'><td style='[tdStyleTime]'>[time]</td><td style='[tdStyleType]background: [get_logtype_color(L.log_type)]'>[L.log_type]</td>\
<td style='[tdStyleWho]'>[L.who]</td><td style='background: [get_logtype_color(L.log_type)];'>[L.what]</td>\
<td style='[tdStyleWho]'>[L.target]</td><td style='[tdStyleWhere]'>[ADMIN_COORDJMP(L.where)]</td></tr>"
dat += "</table>"
dat += "</div>"
var/datum/browser/popup = new(user, "Log viewer", "Log viewer", 1400, 600)
popup.set_content(dat)
popup.open()
/datum/log_viewer/Topic(href, href_list)
if(href_list["start_time"])
var/input = input(usr, "hh:mm:ss", "Start time", "00:00:00") as text|null
if(!input)
return
var/res = timeStampToNum(input)
if(res < 0)
to_chat(usr, "<span class='warning'>'[input]' is an invalid input value.</span>")
return
time_from = res
show_ui(usr)
return
if(href_list["end_time"])
var/input = input(usr, "hh:mm:ss", "End time", "04:00:00") as text|null
if(!input)
return
var/res = timeStampToNum(input)
if(res < 0)
to_chat(usr, "<span class='warning'>'[input]' is an invalid input value.</span>")
return
time_to = res
show_ui(usr)
return
if(href_list["search"])
search(usr)
show_ui(usr)
return
if(href_list["clear_all"])
clear_all(usr)
show_ui(usr)
return
if(href_list["clear_mobs"])
selected_mobs.Cut()
show_ui(usr)
return
if(href_list["add_mob"])
var/list/mobs = getpois(TRUE, TRUE)
var/datum/async_input/A = input_autocomplete_async(usr, "Please, select a mob: ", mobs)
A.on_close(CALLBACK(src, .proc/add_mob, usr))
return
if(href_list["remove_mob"])
var/mob/M = locate(href_list["remove_mob"])
if(M)
selected_mobs -= M
show_ui(usr)
return
if(href_list["toggle_log_type"])
var/log_type = href_list["toggle_log_type"]
if(log_type in selected_log_types)
selected_log_types -= log_type
else
selected_log_types += log_type
show_ui(usr)
return
/datum/log_viewer/proc/get_logtype_color(log_type)
switch(log_type)
if(ATTACK_LOG)
return "darkred"
if(DEFENSE_LOG)
return "chocolate"
if(CONVERSION_LOG)
return "indigo"
if(SAY_LOG)
return "teal"
if(EMOTE_LOG)
return "deepskyblue"
if(MISC_LOG)
return "gray"
return "slategray"
+8
View File
@@ -105,6 +105,14 @@
current.mind = null
leave_all_huds() //leave all the huds in the old body, so it won't get huds if somebody else enters it
for(var/log_type in current.logs) // Copy the old logs
var/list/logs = current.logs[log_type]
if(new_character.logs[log_type])
new_character.logs[log_type] += logs.Copy() // Append the old ones
new_character.logs[log_type] = sortTim(new_character.logs[log_type], /proc/compare_log_record) // Sort them on time
else
new_character.logs[log_type] = logs.Copy() // Just copy them
SSnanoui.user_transferred(current, new_character)
if(new_character.mind) //remove any mind currently in our new body's mind variable
+1 -1
View File
@@ -241,7 +241,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
before_cast(targets)
invocation()
if(user && user.ckey)
user.create_attack_log("<font color='red'>[key_name(user)] cast the spell [name].</font>")
add_attack_logs(user, null, "cast the spell [name]")
spawn(0)
if(charge_type == "recharge" && recharge)
start_recharge()
+2 -1
View File
@@ -141,6 +141,7 @@
else
user.mind.AddSpell(S)
to_chat(user, "<span class='notice'>You flip through the pages. Your understanding of the boundaries of reality increases. You can cast [spellname]!</span>")
user.create_log(MISC_LOG, "learned the spell [spellname] ([S])")
user.create_attack_log("<font color='orange'>[key_name(user)] learned the spell [spellname] ([S]).</font>")
onlearned(user)
@@ -164,4 +165,4 @@
/obj/item/spellbook/oneuse/mime/greaterwall
spell = /obj/effect/proc_holder/spell/targeted/forcewall/mime
spellname = "Invisible Greater Wall"
desc = "It contains illustrations of the great walls of human history."
desc = "It contains illustrations of the great walls of human history."
+1
View File
@@ -189,6 +189,7 @@ GLOBAL_LIST_EMPTY(all_cults)
C.Grant(cult_mind.current)
SEND_SOUND(cult_mind.current, 'sound/ambience/antag/bloodcult.ogg')
cult_mind.current.create_attack_log("<span class='danger'>Has been converted to the cult!</span>")
cult_mind.current.create_log(CONVERSION_LOG, "converted to the cult")
if(jobban_isbanned(cult_mind.current, ROLE_CULTIST) || jobban_isbanned(cult_mind.current, ROLE_SYNDICATE))
replace_jobbanned_player(cult_mind.current, ROLE_CULTIST)
update_cult_icons_added(cult_mind)
@@ -267,6 +267,7 @@
SSticker.mode.abductors -= abductor_mind
abductor_mind.special_role = null
abductor_mind.current.create_attack_log("<span class='danger'>No longer abductor</span>")
abductor_mind.current.create_log(CONVERSION_LOG, "No longer abductor")
if(issilicon(abductor_mind.current))
to_chat(abductor_mind.current, "<span class='userdanger'>You have been turned into a robot! You are no longer an abductor.</span>")
else
@@ -281,4 +282,4 @@
/datum/game_mode/proc/update_abductor_icons_removed(datum/mind/alien_mind)
var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_ABDUCTOR]
hud.leave_hud(alien_mind.current)
set_antag_hud(alien_mind.current, null)
set_antag_hud(alien_mind.current, null)
+1
View File
@@ -68,6 +68,7 @@ proc/issyndicate(mob/living/M as mob)
for(var/datum/objective/nuclear/O in operative_mind.objectives)
operative_mind.objectives -= O
operative_mind.current.create_attack_log("<span class='danger'>No longer nuclear operative</span>")
operative_mind.current.create_log(CONVERSION_LOG, "No longer nuclear operative")
if(issilicon(operative_mind.current))
to_chat(operative_mind.current, "<span class='userdanger'>You have been turned into a robot! You are no longer a Syndicate operative.</span>")
else
+2 -1
View File
@@ -244,6 +244,7 @@
rev_mind.current.Stun(5)
to_chat(rev_mind.current, "<span class='danger'><FONT size = 3> You are now a revolutionary! Help your cause. Do not harm your fellow freedom fighters. You can identify your comrades by the red \"R\" icons, and your leaders by the blue \"R\" icons. Help them kill the heads to win the revolution!</FONT></span>")
rev_mind.current.create_attack_log("<font color='red'>Has been converted to the revolution!</font>")
rev_mind.current.create_log(CONVERSION_LOG, "converted to the revolution")
rev_mind.special_role = SPECIAL_ROLE_REV
update_rev_icons_added(rev_mind)
if(jobban_isbanned(rev_mind.current, ROLE_REV) || jobban_isbanned(rev_mind.current, ROLE_SYNDICATE))
@@ -262,7 +263,7 @@
revolutionaries -= rev_mind
rev_mind.special_role = null
rev_mind.current.create_attack_log("<font color='red'>Has renounced the revolution!</font>")
rev_mind.current.create_log(CONVERSION_LOG, "renounced the revolution")
if(beingborged)
to_chat(rev_mind.current, "<span class='danger'><FONT size = 3>The frame's firmware detects and deletes your neural reprogramming! You remember nothing[remove_head ? "." : " but the name of the one who flashed you."]</FONT></span>")
message_admins("[key_name_admin(rev_mind.current)] [ADMIN_QUE(rev_mind.current,"?")] ([ADMIN_FLW(rev_mind.current,"FLW")]) has been borged while being a [remove_head ? "leader" : " member"] of the revolution.")
@@ -158,6 +158,7 @@ Made by Xhuis
new_thrall_mind.special_role = SPECIAL_ROLE_SHADOWLING_THRALL
update_shadow_icons_added(new_thrall_mind)
new_thrall_mind.current.create_attack_log("<span class='danger'>Became a thrall</span>")
new_thrall_mind.current.create_log(CONVERSION_LOG, "Became a thrall")
new_thrall_mind.current.add_language("Shadowling Hivemind")
new_thrall_mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/lesser_shadow_walk(null))
new_thrall_mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadow_vision/thrall(null))
@@ -179,6 +180,7 @@ Made by Xhuis
return 0 //If there is no mind, the mind isn't a thrall, or the mind's mob isn't alive, return
shadowling_thralls.Remove(thrall_mind)
thrall_mind.current.create_attack_log("<span class='danger'>Dethralled</span>")
thrall_mind.current.create_log(CONVERSION_LOG, "Dethralled")
thrall_mind.special_role = null
update_shadow_icons_removed(thrall_mind)
for(var/obj/effect/proc_holder/spell/S in thrall_mind.spell_list)
@@ -236,6 +238,7 @@ Made by Xhuis
update_shadow_icons_removed(ling_mind)
shadows.Remove(ling_mind)
ling_mind.current.create_attack_log("<span class='danger'>Deshadowlinged</span>")
ling_mind.current.create_log(CONVERSION_LOG, "Deshadowlinged")
ling_mind.special_role = null
for(var/obj/effect/proc_holder/spell/S in ling_mind.spell_list)
ling_mind.RemoveSpell(S)
+1
View File
@@ -348,6 +348,7 @@ You are weak to holy things and starlight. Don't go into space and avoid the Cha
SSticker.mode.vampires -= vampire_mind
vampire_mind.special_role = null
vampire_mind.current.create_attack_log("<span class='danger'>De-vampired</span>")
vampire_mind.current.create_log(CONVERSION_LOG, "De-vampired")
if(vampire_mind.vampire)
vampire_mind.vampire.remove_vampire_powers()
QDEL_NULL(vampire_mind.vampire)
+3 -1
View File
@@ -109,6 +109,7 @@ GLOBAL_VAR_INIT(summon_magic_triggered, FALSE)
H.mind.add_antag_datum(/datum/antagonist/survivalist/guns)
H.create_attack_log("<font color='red'>was made into a survivalist, and trusts no one!</font>")
H.create_log(CONVERSION_LOG, "was made into a survivalist")
var/gun_type = pick(GLOB.summoned_guns)
var/obj/item/gun/G = new gun_type(get_turf(H))
@@ -130,6 +131,7 @@ GLOBAL_VAR_INIT(summon_magic_triggered, FALSE)
H.mind.add_antag_datum(/datum/antagonist/survivalist/magic)
H.create_attack_log("<font color='red'>was made into a survivalist, and trusts no one!</font>")
H.create_log(CONVERSION_LOG, "was made into a survivalist")
var/magic_type = pick(GLOB.summoned_magic)
var/lucky = FALSE
@@ -166,4 +168,4 @@ GLOBAL_VAR_INIT(summon_magic_triggered, FALSE)
if(summon_type == SUMMON_MAGIC)
give_magic(H)
else
give_guns(H)
give_guns(H)
+1 -4
View File
@@ -69,9 +69,6 @@
to_chat(user, "<span class='cultlarge'>\"Come now, do not capture your fellow's soul.\"</span>")
return ..()
M.create_attack_log("<font color='orange'>Has had their soul captured with [src.name] by [key_name(user)]</font>")
user.create_attack_log("<font color='red'>Used the [src.name] to capture the soul of [key_name(M)]</font>")
if(optional)
if(!M.ckey)
to_chat(user, "<span class='warning'>They have no soul!</span>")
@@ -353,7 +350,7 @@
/obj/item/soulstone/proc/get_shade_type()
return /mob/living/simple_animal/shade/cult
/obj/item/soulstone/anybody/get_shade_type()
return /mob/living/simple_animal/shade
+1
View File
@@ -837,6 +837,7 @@
else
user.mind.AddSpell(S)
to_chat(user, "<span class='notice'>you rapidly read through the arcane book. Suddenly you realize you understand [spellname]!</span>")
user.create_log(MISC_LOG, "learned the spell [spellname] ([S])")
user.create_attack_log("<font color='orange'>[key_name(user)] learned the spell [spellname] ([S]).</font>")
onlearned(user)
+1
View File
@@ -59,6 +59,7 @@
SSticker.mode.wizards -= wizard_mind
wizard_mind.special_role = null
wizard_mind.current.create_attack_log("<span class='danger'>De-wizarded</span>")
wizard_mind.current.create_log(CONVERSION_LOG, "De-wizarded")
wizard_mind.current.spellremove(wizard_mind.current)
wizard_mind.current.faction = list("Station")
if(issilicon(wizard_mind.current))
+3
View File
@@ -276,6 +276,7 @@ About the new airlock wires panel:
if(usr)
shockedby += text("\[[time_stamp()]\] - [usr](ckey:[usr.ckey])")
usr.create_attack_log("<font color='red'>Electrified the [name] at [x] [y] [z]</font>")
add_attack_logs(usr, src, "Electrified")
else
shockedby += text("\[[time_stamp()]\] - EMP)")
message = "The door is now electrified [duration == -1 ? "permanently" : "for [duration] second\s"]."
@@ -763,6 +764,7 @@ About the new airlock wires panel:
else if(activate) //electrify door for 30 seconds
shockedby += text("\[[time_stamp()]\][usr](ckey:[usr.ckey])")
usr.create_attack_log("<font color='red'>Electrified the [name] at [x] [y] [z]</font>")
add_attack_logs(usr, src, "Electrified")
to_chat(usr, "The door is now electrified for thirty seconds.")
electrify(30)
if("electrify_permanently")
@@ -774,6 +776,7 @@ About the new airlock wires panel:
else if(activate)
shockedby += text("\[[time_stamp()]\][usr](ckey:[usr.ckey])")
usr.create_attack_log("<font color='red'>Electrified the [name] at [x] [y] [z]</font>")
add_attack_logs(usr, src, "Electrified")
to_chat(usr, "The door is now electrified.")
electrify(-1)
if("open")
+1
View File
@@ -559,6 +559,7 @@
animal_damage = user.obj_damage
animal_damage = min(animal_damage, 20*user.environment_smash)
user.create_attack_log("<font color='red'>attacked [name]</font>")
add_attack_logs(user, src, "Attacked")
attack_generic(user, animal_damage, user.melee_damage_type, "melee", play_soundeffect)
return TRUE
@@ -138,6 +138,7 @@
log_admin("[key_name(user)] EMPd a camera with a laser pointer")
user.create_attack_log("[key_name(user)] EMPd a camera with a laser pointer")
add_attack_logs(user, C, "EMPd with [src]")
else
outmsg = "<span class='info'>You missed the lens of [C] with [src].</span>"
+1
View File
@@ -26,6 +26,7 @@ GLOBAL_LIST_INIT(admin_verbs_admin, list(
/client/proc/cmd_admin_subtle_message, /*send an message to somebody as a 'voice in their head'*/
/client/proc/cmd_admin_delete, /*delete an instance/object/mob/etc*/
/client/proc/cmd_admin_check_contents, /*displays the contents of an instance*/
/client/proc/cmd_admin_open_logging_view,
/client/proc/getserverlogs, /*allows us to fetch server logs (diary) for other days*/
/client/proc/jumptocoord, /*we ghost and jump to a coordinate*/
/client/proc/Getmob, /*teleports a mob to our location*/
+10
View File
@@ -0,0 +1,10 @@
GLOBAL_LIST_INIT(open_logging_views, list())
/client/proc/cmd_admin_open_logging_view()
set category = "Admin"
set name = "Open Logging View"
set desc = "Opens the detailed logging viewer"
if(!GLOB.open_logging_views[usr.client.ckey])
GLOB.open_logging_views[usr.client.ckey] = new /datum/log_viewer()
GLOB.open_logging_views[usr.client.ckey].show_ui(usr)
+1 -1
View File
@@ -885,7 +885,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
return
to_chat(usr, text("<span class='danger'>Attack Log for []</span>", mob))
for(var/t in M.attack_log)
for(var/t in M.attack_log_old)
to_chat(usr, t)
feedback_add_details("admin_verb","ATTL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -285,6 +285,7 @@
else //this looks ugly but it's better than a copy-pasted startgibbing proc override
occupant.create_attack_log("Was gibbed by <b>an autogibber (\the [src])</b>")
add_attack_logs(src, occupant, "gibbed")
occupant.emote("scream")
playsound(get_turf(src), 'sound/goonstation/effects/gib.ogg', 50, 1)
+4 -3
View File
@@ -53,7 +53,8 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
var/turf/T
if(ismob(body))
T = get_turf(body) //Where is the body located?
attack_log = body.attack_log //preserve our attack logs by copying them to our ghost
attack_log_old = body.attack_log_old //preserve our attack logs by copying them to our ghost
logs = body.logs.Copy()
var/mutable_appearance/MA = copy_appearance(body)
if(body.mind && body.mind.name)
@@ -431,7 +432,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
set name = "Orbit" // "Haunt"
set desc = "Follow and orbit a mob."
var/list/mobs = getpois(skip_mindless=1)
var/list/mobs = getpois(FALSE, TRUE, TRUE, TRUE)
var/datum/async_input/A = input_autocomplete_async(usr, "Please, select a mob: ", mobs)
A.on_close(CALLBACK(src, .proc/ManualFollow))
@@ -506,7 +507,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
set desc = "Teleport to a mob"
if(isobserver(usr)) //Make sure they're an observer!
var/list/dest = getpois(mobs_only=1) //Fill list, prompt user with list
var/list/dest = getpois(mobs_only=TRUE) //Fill list, prompt user with list
var/datum/async_input/A = input_autocomplete_async(usr, "Enter a mob name: ", dest)
A.on_close(CALLBACK(src, .proc/jump_to_mob))
+1
View File
@@ -59,6 +59,7 @@
var/mob/living/L = src
L.say_log += "EMOTE: [input]" //say log too so it is easier on admins instead of having to merge the two with timestamps etc
L.emote_log += input //emote only log if an admin wants to search just for emotes they don't have to sift through the say
create_log(EMOTE_LOG, input) // TODO after #13047: Include the channel
// Hearing gasp and such every five seconds is not good emotes were not global for a reason.
// Maybe some people are okay with that.
for(var/mob/M in GLOB.player_list)
+1
View File
@@ -247,6 +247,7 @@
set hidden = 1
if(InCritical())
create_attack_log("[src] has ["succumbed to death"] with [round(health, 0.1)] points of health!")
create_log(MISC_LOG, "has succumbed to death with [round(health, 0.1)] points of health")
adjustOxyLoss(health - HEALTH_THRESHOLD_DEAD)
// super check for weird mobs, including ones that adjust hp
// we don't want to go overboard and gib them, though
+2
View File
@@ -276,6 +276,7 @@ proc/get_radio_key_from_channel(var/channel)
//Log of what we've said, plain message, no spans or junk
say_log += message
create_log(SAY_LOG, message) // TODO after #13047: Include the channel
log_say(message, src)
return 1
@@ -361,6 +362,7 @@ proc/get_radio_key_from_channel(var/channel)
say_log += "whisper: [message]"
log_whisper(message, src)
create_log(SAY_LOG, "WHISPER: [message]")
var/message_range = 1
var/eavesdropping_range = 2
var/watching_range = 5
@@ -30,6 +30,7 @@
to_chat(ghost, "<span class='ghostalert'>Your cyborg shell has been repaired, re-enter if you want to continue!</span> (Verbs -> Ghost -> Re-enter corpse)")
ghost << sound('sound/effects/genetics.ogg')
create_attack_log("revived, trigger reason: [reason]")
create_log(MISC_LOG, "revived, trigger reason: [reason]")
// diag_hud_set_status()
// diag_hud_set_health()
// update_health_hud()
+4 -1
View File
@@ -7,6 +7,7 @@
else if(stat == UNCONSCIOUS)
return 0
create_attack_log("<font color='red'>Fallen unconscious at [atom_loc_line(get_turf(src))]</font>")
add_attack_logs(src, null, "Fallen unconscious")
log_game("[key_name(src)] fell unconscious at [atom_loc_line(get_turf(src))]")
stat = UNCONSCIOUS
if(updating)
@@ -22,6 +23,7 @@
else if(stat == CONSCIOUS)
return 0
create_attack_log("<font color='red'>Woken up at [atom_loc_line(get_turf(src))]</font>")
add_attack_logs(src, null, "Woken up")
log_game("[key_name(src)] woke up at [atom_loc_line(get_turf(src))]")
stat = CONSCIOUS
if(updating)
@@ -45,6 +47,7 @@
if(!can_be_revived())
return 0
create_attack_log("<font color='red'>Came back to life at [atom_loc_line(get_turf(src))]</font>")
add_attack_logs(src, null, "Came back to life")
log_game("[key_name(src)] came back to life at [atom_loc_line(get_turf(src))]")
stat = CONSCIOUS
GLOB.dead_mob_list -= src
@@ -73,4 +76,4 @@
return 1
/mob/living/proc/check_death_method()
return TRUE
return TRUE
+1
View File
@@ -5,6 +5,7 @@
computer_id = client.computer_id
log_access_in(client)
create_attack_log("<font color='red'>Logged in at [atom_loc_line(get_turf(src))]</font>")
create_log(MISC_LOG, "Logged in")
if(config.log_access)
for(var/mob/M in GLOB.player_list)
if(M == src) continue
+1
View File
@@ -4,6 +4,7 @@
GLOB.player_list -= src
log_access_out(src)
create_attack_log("<font color='red'>Logged out at [atom_loc_line(get_turf(src))]</font>")
create_log(MISC_LOG, "Logged out")
// `holder` is nil'd out by now, so we check the `admin_datums` array directly
//Only report this stuff if we are currently playing.
if(GLOB.admin_datums[ckey] && SSticker && SSticker.current_state == GAME_STATE_PLAYING)
+10 -3
View File
@@ -22,6 +22,7 @@
for(var/datum/alternate_appearance/AA in viewing_alternate_appearances)
AA.viewers -= src
viewing_alternate_appearances = null
logs.Cut()
..()
return QDEL_HINT_HARDDEL
@@ -1257,14 +1258,20 @@ GLOBAL_LIST_INIT(slot_equipment_priority, list( \
return list() //must return list or IGNORE_ACCESS
/mob/proc/create_attack_log(text, collapse = TRUE)
LAZYINITLIST(attack_log)
create_log_in_list(attack_log, text, collapse, last_log)
LAZYINITLIST(attack_log_old)
create_log_in_list(attack_log_old, text, collapse, last_log)
last_log = world.timeofday
/mob/proc/create_debug_log(text, collapse = TRUE)
LAZYINITLIST(debug_log)
create_log_in_list(debug_log, text, collapse, world.timeofday)
/mob/proc/create_log(log_type, what, target = null, turf/where = get_turf(src))
LAZYINITLIST(logs[log_type])
var/list/log_list = logs[log_type]
var/datum/log_record/record = new(log_type, src, what, target, where, world.time)
log_list.Add(record)
/proc/create_log_in_list(list/target, text, collapse = TRUE, last_log)//forgive me code gods for this shitcode proc
//this proc enables lovely stuff like an attack log that looks like this: "[18:20:29-18:20:45]21x John Smith attacked Andrew Jackson with a crowbar."
//That makes the logs easier to read, but because all of this is stored in strings, weird things have to be used to get it all out.
@@ -1406,4 +1413,4 @@ GLOBAL_LIST_INIT(slot_equipment_priority, list( \
///Force set the mob nutrition
/mob/proc/set_nutrition(change)
nutrition = max(0, change)
nutrition = max(0, change)
+5 -2
View File
@@ -33,8 +33,11 @@
var/computer_id = null
var/lastattacker = null
var/lastattacked = null
var/list/attack_log = list( )
var/list/attack_log_old = list( )
var/list/debug_log = null
var/list/logs = list() // Logs for each log type defined in __DEFINES/logs.dm
var/last_log = 0
var/obj/machinery/machine = null
var/other_mobs = null
@@ -208,4 +211,4 @@
var/list/tkgrabbed_objects = list() // Assoc list of items to TK grabs
var/forced_look = null // This can either be a numerical direction or a soft object reference (UID). It makes the mob always face towards the selected thing.
var/registered_z
var/registered_z
@@ -51,6 +51,7 @@
user.visible_message("<span class='danger'>[user] zaps [user.p_them()]self with [src].</span>")
playsound(user, fire_sound, 50, 1)
user.create_attack_log("<b>[key_name(user)]</b> zapped [user.p_them()]self with a <b>[src]</b>")
add_attack_logs(null, user, "zapped [user.p_them()]self with a [src]")
/////////////////////////////////////
//WAND OF DEATH
+4 -2
View File
@@ -263,12 +263,14 @@
return
M.create_attack_log("<font color='orange'>[key_name(M)] became [new_mob.real_name].</font>")
new_mob.attack_log = M.attack_log
add_attack_logs(null, M, "became [new_mob.real_name]")
new_mob.a_intent = INTENT_HARM
if(M.mind)
M.mind.transfer_to(new_mob)
else
new_mob.attack_log_old = M.attack_log_old.Copy()
new_mob.logs = M.logs.Copy()
new_mob.key = M.key
to_chat(new_mob, "<B>Your form morphs into that of a [randomize].</B>")
@@ -344,4 +346,4 @@
to_chat(target, "<span class='notice'>You get splatted by [src].</span>")
M.Weaken(slip_weaken)
M.Stun(slip_stun)
. = ..()
. = ..()
+9 -3
View File
@@ -16,7 +16,7 @@
. = ..()
if(. && obj_integrity > 0)
if(tank_volume && (damage_flag == "bullet" || damage_flag == "laser"))
boom()
boom(FALSE, TRUE)
/obj/structure/reagent_dispensers/attackby(obj/item/I, mob/user, params)
if(I.is_refillable())
@@ -43,7 +43,7 @@
/obj/structure/reagent_dispensers/deconstruct(disassembled = TRUE)
if(!(flags & NODECONSTRUCT))
if(!disassembled)
boom()
boom(FALSE, TRUE)
else
qdel(src)
@@ -85,15 +85,19 @@
if(!QDELETED(src)) //wasn't deleted by the projectile's effects.
if(!P.nodamage && ((P.damage_type == BURN) || (P.damage_type == BRUTE)))
message_admins("[key_name_admin(P.firer)] triggered a fueltank explosion with [P.name] at [COORD(loc)] ")
add_attack_logs(P.firer, src, "shot with [P.name]")
log_game("[key_name(P.firer)] triggered a fueltank explosion with [P.name] at [COORD(loc)]")
investigate_log("[key_name(P.firer)] triggered a fueltank explosion with [P.name] at [COORD(loc)]", INVESTIGATE_BOMB)
boom()
/obj/structure/reagent_dispensers/fueltank/boom(rigtrigger = FALSE) // Prevent case where someone who rigged the tank is blamed for the explosion when the rig isn't what triggered the explosion
/obj/structure/reagent_dispensers/fueltank/boom(rigtrigger = FALSE, log_attack = FALSE) // Prevent case where someone who rigged the tank is blamed for the explosion when the rig isn't what triggered the explosion
if(rigtrigger) // If the explosion is triggered by an assembly holder
message_admins("A fueltank, last rigged by [lastrigger], was triggered at [COORD(loc)]") // Then admin is informed of the last person who rigged the fuel tank
log_game("A fueltank, last rigged by [lastrigger], triggered at [COORD(loc)]")
add_attack_logs(lastrigger, src, "rigged fuel tank exploded")
investigate_log("A fueltank, last rigged by [lastrigger], triggered at [COORD(loc)]", INVESTIGATE_BOMB)
if(log_attack)
add_attack_logs(usr, src, "blew up", ATKLOG_FEW)
if(reagents)
reagents.set_reagent_temp(1000) //uh-oh
qdel(src)
@@ -140,6 +144,7 @@
if(istype(H.a_left, /obj/item/assembly/igniter) || istype(H.a_right, /obj/item/assembly/igniter))
msg_admin_attack("[key_name_admin(user)] rigged [src.name] with [I.name] for explosion (<A HREF='?_src_=holder;adminplayerobservecoodjump=1;X=[x];Y=[y];Z=[z]'>JMP</a>)", ATKLOG_FEW)
log_game("[key_name(user)] rigged [src.name] with [I.name] for explosion at [COORD(loc)]")
add_attack_logs(user, src, "rigged fuel tank")
investigate_log("[key_name(user)] rigged [src.name] with [I.name] for explosion at [COORD(loc)]", INVESTIGATE_BOMB)
lastrigger = "[key_name(user)]"
@@ -163,6 +168,7 @@ obj/structure/reagent_dispensers/fueltank/welder_act(mob/user, obj/item/I)
user.visible_message("<span class='danger'>[user] catastrophically fails at refilling [user.p_their()] [I]!</span>", "<span class='userdanger'>That was stupid of you.</span>")
message_admins("[key_name_admin(user)] triggered a fueltank explosion at [COORD(loc)]")
log_game("[key_name(user)] triggered a fueltank explosion at [COORD(loc)]")
add_attack_logs(user, src, "hit with lit welder")
investigate_log("[key_name(user)] triggered a fueltank explosion at [COORD(loc)]", INVESTIGATE_BOMB)
boom()
else
@@ -221,6 +221,7 @@
user.visible_message("<span class='notice'>[user] wraps [target].</span>")
user.create_attack_log("<font color='blue'>Has used [name] on [target]</font>")
add_attack_logs(user, target, "used [name]")
if(amount <= 0 && !src.loc) //if we used our last wrapping paper, drop a cardboard tube
new /obj/item/c_tube( get_turf(user) )
+1
View File
@@ -216,6 +216,7 @@
deal_damage(damage)
visible_message("<span class='danger'>[user]</span> [user.attacktext] [src]!")
user.create_attack_log("<font color='red'>attacked [src.name]</font>")
add_attack_logs(user, src, "attacked")
return TRUE
/obj/spacepod/attack_alien(mob/user)
+1
View File
@@ -243,6 +243,7 @@
qdel(src)
src.visible_message("<span class='danger'>[M] has [M.attacktext] [src]!</span>")
M.create_attack_log("<font color='red'>attacked [src.name]</font>")
add_attack_logs(M, src, "attacked")
/obj/tram/bullet_act(var/obj/item/projectile/proj)
if(prob(proj.damage))
+4
View File
@@ -46,6 +46,7 @@
#include "code\__DEFINES\language.dm"
#include "code\__DEFINES\layers.dm"
#include "code\__DEFINES\lighting.dm"
#include "code\__DEFINES\logs.dm"
#include "code\__DEFINES\machines.dm"
#include "code\__DEFINES\math.dm"
#include "code\__DEFINES\MC.dm"
@@ -263,6 +264,8 @@
#include "code\datums\gas_mixture.dm"
#include "code\datums\holocall.dm"
#include "code\datums\hud.dm"
#include "code\datums\log_record.dm"
#include "code\datums\log_viewer.dm"
#include "code\datums\mind.dm"
#include "code\datums\mixed.dm"
#include "code\datums\mutable_appearance.dm"
@@ -1213,6 +1216,7 @@
#include "code\modules\admin\verbs\gimmick_team.dm"
#include "code\modules\admin\verbs\honksquad.dm"
#include "code\modules\admin\verbs\infiltratorteam_syndicate.dm"
#include "code\modules\admin\verbs\logging_view.dm"
#include "code\modules\admin\verbs\map_template_loadverb.dm"
#include "code\modules\admin\verbs\mapping.dm"
#include "code\modules\admin\verbs\massmodvar.dm"