diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index a9e6d1f68aa..2d847f39b57 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -101,3 +101,10 @@
#define SNPC_STEALTH 2
#define SNPC_MARTYR 3
#define SNPC_PSYCHO 4
+
+//Individual logging defines
+#define INDIVIDUAL_ATTACK_LOG "Attack log"
+#define INDIVIDUAL_SAY_LOG "Say log"
+#define INDIVIDUAL_EMOTE_LOG "Emote log"
+#define INDIVIDUAL_OOC_LOG "OOC log"
+#define INDIVIDUAL_SHOW_ALL_LOG "All logs"
\ No newline at end of file
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index 9fed8a9f2f4..e932e9dfa84 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -181,16 +181,12 @@ Proc for attack log creation, because really why not
living_target = target
if(is_mob_user)
- var/message = "\[[time_stamp()]\] [user ? "[user.name][(user.ckey) ? "([user.ckey])" : ""]" : "NON-EXISTANT SUBJECT"] has [what_done] [target ? "[target.name][(is_mob_target && target.ckey) ? "([target.ckey])" : ""]" : "NON-EXISTANT SUBJECT"][object ? " with [object]" : " "][addition][(living_target) ? " (NEWHP: [living_target.health])" : ""][(attack_location) ? "([attack_location.x],[attack_location.y],[attack_location.z])" : ""]"
- user.attack_log += message
- if(user.mind)
- user.mind.attack_log += message
+ var/message = "has [what_done] [target ? "[target.name][(is_mob_target && target.ckey) ? "([target.ckey])" : ""]" : "NON-EXISTANT SUBJECT"][object ? " with [object]" : " "][addition][(living_target) ? " (NEWHP: [living_target.health])" : ""][(attack_location) ? "([attack_location.x],[attack_location.y],[attack_location.z])" : ""]"
+ user.log_message(message, INDIVIDUAL_ATTACK_LOG)
if(is_mob_target)
- var/message = "\[[time_stamp()]\] [target ? "[target.name][(target.ckey) ? "([target.ckey])" : ""]" : "NON-EXISTANT SUBJECT"] has been [what_done] by [user ? "[user.name][(is_mob_user && user.ckey) ? "([user.ckey])" : ""]" : "NON-EXISTANT SUBJECT"][object ? " with [object]" : " "][addition][(living_target) ? " (NEWHP: [living_target.health])" : ""][(attack_location) ? "([attack_location.x],[attack_location.y],[attack_location.z])" : ""]"
- target.attack_log += message
- if(target.mind)
- target.mind.attack_log += message
+ var/message = "has been [what_done] by [user ? "[user.name][(is_mob_user && user.ckey) ? "([user.ckey])" : ""]" : "NON-EXISTANT SUBJECT"][object ? " with [object]" : " "][addition][(living_target) ? " (NEWHP: [living_target.health])" : ""][(attack_location) ? "([attack_location.x],[attack_location.y],[attack_location.z])" : ""]"
+ target.log_message(message, INDIVIDUAL_ATTACK_LOG)
log_attack("[user ? "[user.name][(is_mob_user && user.ckey) ? "([user.ckey])" : ""]" : "NON-EXISTANT SUBJECT"] [what_done] [target ? "[target.name][(is_mob_target && target.ckey)? "([target.ckey])" : ""]" : "NON-EXISTANT SUBJECT"][object ? " with [object]" : " "][addition][(living_target) ? " (NEWHP: [living_target.health])" : ""][(attack_location) ? "([attack_location.x],[attack_location.y],[attack_location.z])" : ""]")
diff --git a/code/datums/antagonists/datum_clockcult.dm b/code/datums/antagonists/datum_clockcult.dm
index b66a8cbc11c..f679b36daec 100644
--- a/code/datums/antagonists/datum_clockcult.dm
+++ b/code/datums/antagonists/datum_clockcult.dm
@@ -43,7 +43,7 @@
INVOKE_ASYNC(ticker.mode, /datum/game_mode.proc/replace_jobbaned_player, owner, ROLE_SERVANT_OF_RATVAR, ROLE_SERVANT_OF_RATVAR)
if(owner.mind)
owner.mind.special_role = "Servant of Ratvar"
- owner.attack_log += "\[[time_stamp()]\] Has been converted to the cult of Ratvar!"
+ owner.log_message("Has been converted to the cult of Ratvar!", INDIVIDUAL_ATTACK_LOG)
if(issilicon(owner))
var/mob/living/silicon/S = owner
if(iscyborg(S) && !silent_update)
@@ -154,7 +154,7 @@
if(owner.mind)
owner.mind.wipe_memory()
owner.mind.special_role = null
- owner.attack_log += "\[[time_stamp()]\] Has renounced the cult of Ratvar!"
+ owner.log_message("Has renounced the cult of Ratvar!", INDIVIDUAL_ATTACK_LOG)
if(iscyborg(owner))
owner << "Despite your freedom from Ratvar's influence, you are still irreparably damaged and no longer possess certain functions such as AI linking."
..()
diff --git a/code/datums/antagonists/datum_cult.dm b/code/datums/antagonists/datum_cult.dm
index b49cb41c78b..06ecdce7709 100644
--- a/code/datums/antagonists/datum_cult.dm
+++ b/code/datums/antagonists/datum_cult.dm
@@ -23,7 +23,7 @@
INVOKE_ASYNC(ticker.mode, /datum/game_mode.proc/replace_jobbaned_player, owner, ROLE_CULTIST, ROLE_CULTIST)
if(owner.mind)
owner.mind.special_role = "Cultist"
- owner.attack_log += "\[[time_stamp()]\] Has been converted to the cult of Nar'Sie!"
+ owner.log_message("Has been converted to the cult of Nar'Sie!", INDIVIDUAL_ATTACK_LOG)
..()
/datum/antagonist/cultist/apply_innate_effects()
@@ -44,7 +44,7 @@
ticker.mode.cult -= owner.mind
ticker.mode.update_cult_icons_removed(owner.mind)
owner << "An unfamiliar white light flashes through your mind, cleansing the taint of the Dark One and all your memories as its servant."
- owner.attack_log += "\[[time_stamp()]\] Has renounced the cult of Nar'Sie!"
+ owner.log_message("Has renounced the cult of Nar'Sie!", INDIVIDUAL_ATTACK_LOG)
if(!silent_update)
owner.visible_message("[owner] looks like [owner.p_they()] just reverted to their old faith!")
..()
diff --git a/code/datums/emotes.dm b/code/datums/emotes.dm
index 1f17abbae1c..7039bf2c488 100644
--- a/code/datums/emotes.dm
+++ b/code/datums/emotes.dm
@@ -50,6 +50,7 @@ var/global/list/emote_list = list()
if(!msg)
return FALSE
+ user.log_message(msg, INDIVIDUAL_EMOTE_LOG)
msg = "[user] " + msg
for(var/mob/M in dead_mob_list)
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 369260467d2..7bb88022fcd 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -36,7 +36,6 @@
var/active = 0
var/memory
- var/attack_log
var/assigned_role
var/special_role
diff --git a/code/game/gamemodes/changeling/powers/absorb.dm b/code/game/gamemodes/changeling/powers/absorb.dm
index c0feab272cb..5ed20c46c38 100644
--- a/code/game/gamemodes/changeling/powers/absorb.dm
+++ b/code/game/gamemodes/changeling/powers/absorb.dm
@@ -65,13 +65,15 @@
//Recent as opposed to all because rounds tend to have a LOT of text.
var/list/recent_speech = list()
- if(target.say_log.len > LING_ABSORB_RECENT_SPEECH)
- recent_speech = target.say_log.Copy(target.say_log.len-LING_ABSORB_RECENT_SPEECH+1,0) //0 so len-LING_ARS+1 to end of list
+ var/list/say_log = target.logging[INDIVIDUAL_SAY_LOG]
+
+ if(say_log.len > LING_ABSORB_RECENT_SPEECH)
+ recent_speech = say_log.Copy(say_log.len-LING_ABSORB_RECENT_SPEECH+1,0) //0 so len-LING_ARS+1 to end of list
else
- for(var/spoken_memory in target.say_log)
+ for(var/spoken_memory in say_log)
if(recent_speech.len >= LING_ABSORB_RECENT_SPEECH)
break
- recent_speech[spoken_memory] = target.say_log[spoken_memory]
+ recent_speech[spoken_memory] = say_log[spoken_memory]
if(recent_speech.len)
user.mind.store_memory("Some of [target]'s speech patterns, we should study these to better impersonate them!")
diff --git a/code/game/gamemodes/gang/gang.dm b/code/game/gamemodes/gang/gang.dm
index 8d6709b1e2a..916ba1ba34e 100644
--- a/code/game/gamemodes/gang/gang.dm
+++ b/code/game/gamemodes/gang/gang.dm
@@ -174,7 +174,7 @@ var/list/gang_colors_pool = list("red","orange","yellow","green","blue","purple"
gangster_mind.current << "Their ultimate objective is to take over the station with a Dominator machine."
gangster_mind.current << "You can identify your bosses by their large, bright [G.color] \[G\] icon."
gangster_mind.store_memory("You are a member of the [G.name] Gang!")
- gangster_mind.current.attack_log += "\[[time_stamp()]\] Has been converted to the [G.name] Gang!"
+ gangster_mind.current.log_message("Has been converted to the [G.name] Gang!", INDIVIDUAL_ATTACK_LOG)
gangster_mind.special_role = "[G.name] Gangster"
G.add_gang_hud(gangster_mind)
@@ -208,7 +208,7 @@ var/list/gang_colors_pool = list("red","orange","yellow","green","blue","purple"
gangster_mind.gang_datum = null
if(silent < 2)
- gangster_mind.current.attack_log += "\[[time_stamp()]\] Has reformed and defected from the [gang.name] Gang!"
+ gangster_mind.current.log_message("Has reformed and defected from the [gang.name] Gang!", INDIVIDUAL_ATTACK_LOG)
if(beingborged)
if(!silent)
diff --git a/code/game/gamemodes/revolution/revolution.dm b/code/game/gamemodes/revolution/revolution.dm
index be32c62fab1..72af1264dd9 100644
--- a/code/game/gamemodes/revolution/revolution.dm
+++ b/code/game/gamemodes/revolution/revolution.dm
@@ -271,7 +271,7 @@
carbon_mob.flash_act(1, 1)
rev_mind.current.Stun(5)
rev_mind.current << " 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!"
- rev_mind.current.attack_log += "\[[time_stamp()]\] Has been converted to the revolution!"
+ rev_mind.current.log_message("Has been converted to the revolution!", INDIVIDUAL_ATTACK_LOG)
rev_mind.special_role = "Revolutionary"
update_rev_icons_added(rev_mind)
if(jobban_isbanned(rev_mind.current, ROLE_REV))
@@ -289,7 +289,7 @@
if((rev_mind in revolutionaries) || remove_head)
revolutionaries -= rev_mind
rev_mind.special_role = null
- rev_mind.current.attack_log += "\[[time_stamp()]\] Has renounced the revolution!"
+ rev_mind.current.log_message("Has renounced the revolution!", INDIVIDUAL_ATTACK_LOG)
if(beingborged)
rev_mind.current << "The frame's firmware detects and deletes your neural reprogramming! You remember nothing[remove_head ? "." : " but the name of the one who flashed you."]"
diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm
index 5a81e67fa24..4acf1fc0032 100644
--- a/code/game/gamemodes/wizard/spellbook.dm
+++ b/code/game/gamemodes/wizard/spellbook.dm
@@ -774,7 +774,7 @@
else
user.mind.AddSpell(S)
user <<"you rapidly read through the arcane book. Suddenly you realize you understand [spellname]!"
- user.attack_log += text("\[[time_stamp()]\] [user.real_name] ([user.ckey]) learned the spell [spellname] ([S]).")
+ user.log_message("learned the spell [spellname] ([S]).", INDIVIDUAL_ATTACK_LOG)
onlearned(user)
/obj/item/weapon/spellbook/oneuse/proc/recoil(mob/user)
diff --git a/code/game/objects/items/stacks/wrap.dm b/code/game/objects/items/stacks/wrap.dm
index d743f96ea56..44691f65c29 100644
--- a/code/game/objects/items/stacks/wrap.dm
+++ b/code/game/objects/items/stacks/wrap.dm
@@ -97,7 +97,7 @@
return
user.visible_message("[user] wraps [target].")
- user.attack_log += text("\[[time_stamp()]\] Has used [name] on [target]")
+ user.log_message("Has used [name] on [target]", INDIVIDUAL_ATTACK_LOG)
/obj/item/stack/packageWrap/Destroy()
if(!amount)
diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm
index a47e980a9c5..d8758d39349 100644
--- a/code/game/objects/structures/morgue.dm
+++ b/code/game/objects/structures/morgue.dm
@@ -203,7 +203,7 @@ var/global/list/crematoriums = new/list()
if (M.stat != DEAD)
M.emote("scream")
if(user)
- user.attack_log +="\[[time_stamp()]\] Cremated [M]/[M.ckey]"
+ user.log_message("Cremated [M]/[M.ckey]", INDIVIDUAL_ATTACK_LOG)
log_attack("\[[time_stamp()]\] [user]/[user.ckey] cremated [M]/[M.ckey]")
else
log_attack("\[[time_stamp()]\] UNKNOWN cremated [M]/[M.ckey]")
diff --git a/code/game/turfs/open.dm b/code/game/turfs/open.dm
index 795acbc7c16..7672ae2d0a8 100644
--- a/code/game/turfs/open.dm
+++ b/code/game/turfs/open.dm
@@ -154,7 +154,7 @@
return 0
if(!(lube&SLIDE_ICE))
C << "You slipped[ O ? " on the [O.name]" : ""]!"
- C.attack_log += "\[[time_stamp()]\] Slipped[O ? " on the [O.name]" : ""][(lube&SLIDE)? " (LUBE)" : ""]!"
+ C.log_message("Slipped[O ? " on the [O.name]" : ""][(lube&SLIDE)? " (LUBE)" : ""]!", INDIVIDUAL_ATTACK_LOG)
if(!(lube&SLIDE_ICE))
playsound(C.loc, 'sound/misc/slip.ogg', 50, 1, -3)
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 24ee7c1ae33..fb926931192 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -82,7 +82,8 @@ var/global/BSACooldown = 0
body += "
"
body += "Traitor panel | "
body += "Narrate to | "
- body += "Subtle message"
+ body += "Subtle message | "
+ body += "Individual Round Logs"
if (M.client)
if(!isnewplayer(M))
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 389ed32aff9..7c4270b4eba 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -1820,6 +1820,17 @@
var/mob/M = locate(href_list["subtlemessage"])
usr.client.cmd_admin_subtle_message(M)
+ else if(href_list["individuallog"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ var/mob/M = locate(href_list["individuallog"]) in mob_list
+ if(!ismob(M))
+ usr << "This can only be used on instances of type /mob."
+ return
+
+ show_individual_logging_panel(M, href_list["log_type"])
+
else if(href_list["traitor"])
if(!check_rights(R_ADMIN))
return
diff --git a/code/modules/admin/verbs/individual_logging.dm b/code/modules/admin/verbs/individual_logging.dm
new file mode 100644
index 00000000000..8130f84ff07
--- /dev/null
+++ b/code/modules/admin/verbs/individual_logging.dm
@@ -0,0 +1,27 @@
+/proc/show_individual_logging_panel(mob/M, type = INDIVIDUAL_ATTACK_LOG)
+ if(!M || !ismob(M))
+ return
+ var/dat = "