diff --git a/.github/workflows/label_merge_conflicts.yml b/.github/workflows/label_merge_conflicts.yml
new file mode 100644
index 00000000000..d2a56e2ef3d
--- /dev/null
+++ b/.github/workflows/label_merge_conflicts.yml
@@ -0,0 +1,13 @@
+name: 'Merge Conflict Detection'
+on:
+ push:
+ branches:
+ - master
+jobs:
+ triage:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: mschilde/auto-label-merge-conflicts@master
+ with:
+ CONFLICT_LABEL_NAME: 'Merge Conflict'
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_syndicate_base1.dmm b/_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_syndicate_base1.dmm
index 279afa9713d..e00aedc8100 100644
--- a/_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_syndicate_base1.dmm
+++ b/_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_syndicate_base1.dmm
@@ -1789,7 +1789,6 @@
/area/ruin/unpowered/syndicate_lava_base/cargo)
"ed" = (
/obj/structure/table,
-/obj/item/paper_bin,
/obj/item/pen,
/obj/effect/decal/cleanable/dirt,
/obj/effect/decal/cleanable/dirt,
@@ -1803,6 +1802,7 @@
/obj/effect/turf_decal/tile/neutral{
dir = 8
},
+/obj/item/paper_bin/syndicate,
/turf/simulated/floor/plasteel/dark,
/area/ruin/unpowered/syndicate_lava_base/cargo)
"ef" = (
@@ -1898,7 +1898,6 @@
"ep" = (
/obj/structure/table/reinforced,
/obj/effect/decal/cleanable/dirt,
-/obj/item/paper_bin,
/obj/item/pen,
/obj/machinery/atmospherics/pipe/simple/hidden/supply,
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,
@@ -1912,6 +1911,7 @@
/obj/effect/turf_decal/tile/neutral{
dir = 8
},
+/obj/item/paper_bin/syndicate,
/turf/simulated/floor/plasteel/dark,
/area/ruin/unpowered/syndicate_lava_base/testlab)
"eq" = (
@@ -6790,8 +6790,8 @@
/area/ruin/unpowered/syndicate_lava_base/telecomms)
"ov" = (
/obj/structure/table/reinforced,
-/obj/item/paper_bin,
/obj/item/pen,
+/obj/item/paper_bin/syndicate,
/turf/simulated/floor/plasteel/dark,
/area/ruin/unpowered/syndicate_lava_base/telecomms)
"ox" = (
diff --git a/_maps/map_files/cyberiad/cyberiad.dmm b/_maps/map_files/cyberiad/cyberiad.dmm
index 74e179255ce..7cf27593f6f 100644
--- a/_maps/map_files/cyberiad/cyberiad.dmm
+++ b/_maps/map_files/cyberiad/cyberiad.dmm
@@ -1619,15 +1619,12 @@
/area/security/range)
"adH" = (
/obj/structure/table/wood,
-/obj/item/paper_bin{
- pixel_x = -3;
- pixel_y = 7
- },
/obj/item/pen/multi,
/obj/machinery/light{
dir = 1;
on = 1
},
+/obj/item/paper_bin/nanotrasen,
/turf/simulated/floor/plasteel{
icon_state = "dark"
},
@@ -16077,16 +16074,13 @@
/area/lawoffice)
"aAI" = (
/obj/structure/table/reinforced,
-/obj/item/paper_bin{
- pixel_x = -3;
- pixel_y = 7
- },
/obj/item/pen,
/obj/machinery/alarm{
dir = 8;
pixel_x = 25;
pixel_y = 0
},
+/obj/item/paper_bin/nanotrasen,
/turf/simulated/floor/plasteel{
tag = "icon-cult";
icon_state = "cult";
@@ -16863,11 +16857,8 @@
/area/magistrateoffice)
"aCj" = (
/obj/structure/table/reinforced,
-/obj/item/paper_bin{
- pixel_x = -3;
- pixel_y = 7
- },
/obj/item/stamp/magistrate,
+/obj/item/paper_bin/nanotrasen,
/turf/simulated/floor/carpet,
/area/magistrateoffice)
"aCk" = (
@@ -21875,11 +21866,8 @@
/area/maintenance/fpmaint2)
"aMO" = (
/obj/structure/table/reinforced,
-/obj/item/paper_bin{
- pixel_x = -3;
- pixel_y = 7
- },
/obj/item/pen,
+/obj/item/paper_bin/nanotrasen,
/turf/simulated/floor/plasteel{
tag = "icon-cult";
icon_state = "cult";
@@ -27795,16 +27783,13 @@
/obj/item/folder/yellow,
/obj/item/stamp/ce,
/obj/item/book/manual/sop_engineering,
-/obj/item/paper_bin{
- pixel_x = -3;
- pixel_y = 7
- },
/obj/item/storage/fancy/cigarettes,
/obj/item/lighter/zippo,
/obj/item/pen/multi,
/obj/item/enginepicker{
layer = 3.1
},
+/obj/item/paper_bin/nanotrasen,
/turf/simulated/floor/plasteel{
dir = 8;
icon_state = "neutralfull"
@@ -43355,12 +43340,9 @@
},
/area/hallway/primary/central/west)
"bCG" = (
-/obj/item/paper_bin{
- pixel_x = -3;
- pixel_y = 7
- },
/obj/item/pen,
/obj/structure/table/wood,
+/obj/item/paper_bin/nanotrasen,
/turf/simulated/floor/carpet,
/area/bridge/meeting_room)
"bCH" = (
@@ -47113,10 +47095,6 @@
/area/shuttle/constructionsite)
"bJN" = (
/obj/structure/table/wood,
-/obj/item/paper_bin{
- pixel_x = -3;
- pixel_y = 10
- },
/obj/item/pen/multi/fountain,
/obj/machinery/door_control{
id = "captainofficedoor";
@@ -47129,6 +47107,7 @@
/obj/item/radio/intercom{
pixel_x = -28
},
+/obj/item/paper_bin/nanotrasen,
/turf/simulated/floor/wood,
/area/crew_quarters/captain)
"bJO" = (
@@ -50576,15 +50555,12 @@
},
/area/toxins/lab)
"bPE" = (
-/obj/item/paper_bin{
- pixel_x = 1;
- pixel_y = 9
- },
/obj/structure/table/glass,
/obj/item/pen/multi,
/obj/item/book/manual/sop_science{
pixel_y = -14
},
+/obj/item/paper_bin/nanotrasen,
/turf/simulated/floor/plasteel{
dir = 2;
icon_state = "cafeteria";
@@ -54151,10 +54127,6 @@
/area/hallway/primary/central/se)
"bVy" = (
/obj/structure/table,
-/obj/item/paper_bin{
- pixel_x = -3;
- pixel_y = 7
- },
/obj/item/pen/multi,
/obj/item/pen/multi,
/obj/item/megaphone,
@@ -55112,6 +55084,7 @@
/obj/item/book/manual/sop_service,
/obj/item/book/manual/sop_supply,
/obj/item/book/manual/sop_command,
+/obj/item/paper_bin/nanotrasen,
/turf/simulated/floor/plasteel,
/area/crew_quarters/heads)
"bXa" = (
@@ -60880,7 +60853,6 @@
/area/medical/cmo)
"cfW" = (
/obj/structure/table/glass,
-/obj/item/paper_bin,
/obj/item/pen/multi,
/obj/item/folder/white{
pixel_y = 7
@@ -60890,6 +60862,7 @@
pixel_x = 32;
pixel_y = 0
},
+/obj/item/paper_bin/nanotrasen,
/turf/simulated/floor/plasteel{
dir = 4;
icon_state = "darkblue"
@@ -67468,12 +67441,12 @@
/area/blueshield)
"cqS" = (
/obj/structure/table/wood,
-/obj/item/paper_bin,
/obj/item/flashlight/lamp/green{
pixel_x = -5;
pixel_y = 12
},
/obj/item/paper/ntrep,
+/obj/item/paper_bin/nanotrasen,
/turf/simulated/floor/carpet,
/area/ntrep)
"cqT" = (
diff --git a/code/ATMOSPHERICS/datum_icon_manager.dm b/code/ATMOSPHERICS/datum_icon_manager.dm
index 23a4dab33da..2286b3523a0 100644
--- a/code/ATMOSPHERICS/datum_icon_manager.dm
+++ b/code/ATMOSPHERICS/datum_icon_manager.dm
@@ -6,16 +6,6 @@
// atmospherics devices.
//--------------------------------------------
-#define PIPE_COLOR_GREY "#ffffff" //yes white is grey
-#define PIPE_COLOR_RED "#ff0000"
-#define PIPE_COLOR_BLUE "#0000ff"
-#define PIPE_COLOR_CYAN "#00ffff"
-#define PIPE_COLOR_GREEN "#00ff00"
-#define PIPE_COLOR_YELLOW "#ffcc00"
-#define PIPE_COLOR_PURPLE "#5c1ec0"
-
-GLOBAL_LIST_INIT(pipe_colors, list("grey" = PIPE_COLOR_GREY, "red" = PIPE_COLOR_RED, "blue" = PIPE_COLOR_BLUE, "cyan" = PIPE_COLOR_CYAN, "green" = PIPE_COLOR_GREEN, "yellow" = PIPE_COLOR_YELLOW, "purple" = PIPE_COLOR_PURPLE))
-
/proc/pipe_color_lookup(var/color)
for(var/C in GLOB.pipe_colors)
if(color == GLOB.pipe_colors[C])
diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm
index 13f7777d386..2bb53c91ce4 100644
--- a/code/__DEFINES/colors.dm
+++ b/code/__DEFINES/colors.dm
@@ -99,3 +99,12 @@
#define COLOR_ASSEMBLY_LBLUE "#5D99BE"
#define COLOR_ASSEMBLY_BLUE "#38559E"
#define COLOR_ASSEMBLY_PURPLE "#6F6192"
+
+// Pipe colours
+#define PIPE_COLOR_GREY "#ffffff" //yes white is grey
+#define PIPE_COLOR_RED "#ff0000"
+#define PIPE_COLOR_BLUE "#0000ff"
+#define PIPE_COLOR_CYAN "#00ffff"
+#define PIPE_COLOR_GREEN "#00ff00"
+#define PIPE_COLOR_YELLOW "#ffcc00"
+#define PIPE_COLOR_PURPLE "#5c1ec0"
diff --git a/code/__DEFINES/logs.dm b/code/__DEFINES/logs.dm
new file mode 100644
index 00000000000..a8a4a457c7c
--- /dev/null
+++ b/code/__DEFINES/logs.dm
@@ -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"
\ No newline at end of file
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index cdbb969f817..ff127fd3a48 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -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("Attacked [target_str]: [what_done]")
- if(istype(target))
- target.create_attack_log("Attacked by [user_str]: [what_done]")
+ 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("Attacked [target_str]: [what_done]")
+ if(istype(MT))
+ MT.create_log(DEFENSE_LOG, what_done, user, get_turf(MT))
+ MT.create_attack_log("Attacked by [user_str]: [what_done]")
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
@@ -346,24 +355,35 @@ This is always put in the attack log.
if(progress)
qdel(progbar)
-/proc/do_after(mob/user, delay, needhand = 1, atom/target = null, progress = 1, datum/callback/extra_checks = null)
+/* Use this proc when you want to have code under it execute after a delay, and ensure certain conditions are met during that delay...
+ * Such as the user not being interrupted via getting stunned or by moving off the tile they're currently on.
+ *
+ * Example usage:
+ *
+ * if(do_after(user, 50, target = sometarget, extra_checks = list(callback_check1, callback_check2)))
+ * do_stuff()
+ *
+ * This will create progress bar that lasts for 5 seconds. If the user doesn't move or otherwise do something that would cause the checks to fail in those 5 seconds, do_stuff() would execute.
+ * The Proc returns TRUE upon success (the progress bar reached the end), or FALSE upon failure (the user moved or some other check failed)
+ */
+/proc/do_after(mob/user, delay, needhand = 1, atom/target = null, progress = 1, list/extra_checks = list(), use_default_checks = TRUE)
if(!user)
- return 0
+ return FALSE
var/atom/Tloc = null
if(target)
Tloc = target.loc
var/atom/Uloc = user.loc
- var/drifting = 0
+ var/drifting = FALSE
if(!user.Process_Spacemove(0) && user.inertia_dir)
- drifting = 1
+ drifting = TRUE
var/holding = user.get_active_hand()
- var/holdingnull = 1 //User's hand started out empty, check for an empty hand
+ var/holdingnull = TRUE //User's hand started out empty, check for an empty hand
if(holding)
- holdingnull = 0 //Users hand started holding something, check to see if it's still holding that
+ holdingnull = FALSE //Users hand started holding something, check to see if it's still holding that
var/datum/progressbar/progbar
if(progress)
@@ -371,22 +391,29 @@ This is always put in the attack log.
var/endtime = world.time + delay
var/starttime = world.time
- . = 1
+ . = TRUE
+
+ // By default, checks for weakness and stunned get added to the extra_checks list.
+ // Setting `use_default_checks` to FALSE means that you don't want the do_after to check for these statuses, or that you will be supplying your own checks.
+ if(use_default_checks)
+ extra_checks += CALLBACK(user, /mob.proc/IsWeakened)
+ extra_checks += CALLBACK(user, /mob.proc/IsStunned)
+
while(world.time < endtime)
sleep(1)
if(progress)
progbar.update(world.time - starttime)
if(drifting && !user.inertia_dir)
- drifting = 0
+ drifting = FALSE
Uloc = user.loc
- if(!user || user.stat || user.IsWeakened() || user.stunned || (!drifting && user.loc != Uloc)|| (extra_checks && !extra_checks.Invoke()))
- . = 0
+ if(!user || user.stat || (!drifting && user.loc != Uloc) || check_for_true_callbacks(extra_checks))
+ . = FALSE
break
if(Tloc && (!target || Tloc != target.loc))
- . = 0
+ . = FALSE
break
if(needhand)
@@ -394,14 +421,21 @@ This is always put in the attack log.
//i.e the hand is used to pull some item/tool out of the construction
if(!holdingnull)
if(!holding)
- . = 0
+ . = FALSE
break
if(user.get_active_hand() != holding)
- . = 0
+ . = FALSE
break
if(progress)
qdel(progbar)
+// Upon any of the callbacks in the list returning TRUE, the proc will return TRUE.
+/proc/check_for_true_callbacks(list/extra_checks)
+ for(var/datum/callback/CB in extra_checks)
+ if(CB.Invoke())
+ return TRUE
+ return FALSE
+
#define DOAFTERONCE_MAGIC "Magic~~"
GLOBAL_LIST_INIT(do_after_once_tracker, list())
/proc/do_after_once(mob/user, delay, needhand = 1, atom/target = null, progress = 1, attempt_cancel_message = "Attempt cancelled.")
@@ -414,7 +448,7 @@ GLOBAL_LIST_INIT(do_after_once_tracker, list())
to_chat(user, "[attempt_cancel_message]")
return FALSE
GLOB.do_after_once_tracker[cache_key] = TRUE
- . = do_after(user, delay, needhand, target, progress, extra_checks = CALLBACK(GLOBAL_PROC, .proc/do_after_once_checks, cache_key))
+ . = do_after(user, delay, needhand, target, progress, extra_checks = list(CALLBACK(GLOBAL_PROC, .proc/do_after_once_checks, cache_key)))
GLOB.do_after_once_tracker[cache_key] = FALSE
/proc/do_after_once_checks(cache_key)
diff --git a/code/__HELPERS/time.dm b/code/__HELPERS/time.dm
index d13bf08c58b..340d7a3b134 100644
--- a/code/__HELPERS/time.dm
+++ b/code/__HELPERS/time.dm
@@ -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
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index c5a77880cf0..aedc16c4162 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -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
diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm
index 5fd40bf86d9..32d6370f3d0 100644
--- a/code/_globalvars/misc.dm
+++ b/code/_globalvars/misc.dm
@@ -100,3 +100,4 @@ GLOBAL_DATUM_INIT(dbcon, /DBConnection, new) //Feedback database (New database)
GLOBAL_PROTECT(dbcon)
GLOBAL_LIST_EMPTY(ability_verbs) // Create-level abilities
+GLOBAL_LIST_INIT(pipe_colors, list("grey" = PIPE_COLOR_GREY, "red" = PIPE_COLOR_RED, "blue" = PIPE_COLOR_BLUE, "cyan" = PIPE_COLOR_CYAN, "green" = PIPE_COLOR_GREEN, "yellow" = PIPE_COLOR_YELLOW, "purple" = PIPE_COLOR_PURPLE))
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index 1defb9a6afa..b2b09003934 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -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)
diff --git a/code/datums/log_record.dm b/code/datums/log_record.dm
new file mode 100644
index 00000000000..9b7d52b5390
--- /dev/null
+++ b/code/datums/log_record.dm
@@ -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
diff --git a/code/datums/log_viewer.dm b/code/datums/log_viewer.dm
new file mode 100644
index 00000000000..70f7d832924
--- /dev/null
+++ b/code/datums/log_viewer.dm
@@ -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, "The search criteria contained invalid mobs. They have been removed from the criteria.")
+ 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 += "
"
+ dat += ""
+ dat += "
Time Search Range: [gameTimestamp(wtime = time_from)]"
+ dat += " To:
[gameTimestamp(wtime = time_to)]"
+ dat += "
"
+
+ dat += "
Mobs being used:"
+ for(var/i in selected_mobs)
+ var/mob/M = i
+ dat += "
[M.name]"
+ dat += "
Add Mob"
+ dat += "
Clear All Mobs"
+ dat += "
"
+
+ dat += "
Log Types:"
+ 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 = "
[log_type]"
+ style = "background: [get_logtype_color(i)]"
+ else
+ text = log_type
+
+ dat += "
[text]"
+
+ dat += "
"
+ dat += "
Clear All Settings"
+ dat += "
Search"
+ dat += "
"
+
+ // 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 += ""
+ dat += "
"
+ dat += "| When | Type | Who | What | Target | Where |
"
+ 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 +="| [time] | [L.log_type] | \
+ [L.who] | [L.what] | \
+ [L.target] | [ADMIN_COORDJMP(L.where)] |
"
+
+ dat += "
"
+ dat += "
"
+
+ 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, "'[input]' is an invalid input value.")
+ 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, "'[input]' is an invalid input value.")
+ 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"
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 4f176970a92..135114c11f9 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -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
diff --git a/code/datums/spell.dm b/code/datums/spell.dm
index 6b1865cf73d..30347d1e796 100644
--- a/code/datums/spell.dm
+++ b/code/datums/spell.dm
@@ -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("[key_name(user)] cast the spell [name].")
+ add_attack_logs(user, null, "cast the spell [name]")
spawn(0)
if(charge_type == "recharge" && recharge)
start_recharge()
diff --git a/code/datums/spells/mime.dm b/code/datums/spells/mime.dm
index 31066001270..d0c052649d9 100644
--- a/code/datums/spells/mime.dm
+++ b/code/datums/spells/mime.dm
@@ -141,6 +141,7 @@
else
user.mind.AddSpell(S)
to_chat(user, "You flip through the pages. Your understanding of the boundaries of reality increases. You can cast [spellname]!")
+ user.create_log(MISC_LOG, "learned the spell [spellname] ([S])")
user.create_attack_log("[key_name(user)] learned the spell [spellname] ([S]).")
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."
\ No newline at end of file
diff --git a/code/game/gamemodes/changeling/evolution_menu.dm b/code/game/gamemodes/changeling/evolution_menu.dm
index d2b1cdae519..9c33ffaaca5 100644
--- a/code/game/gamemodes/changeling/evolution_menu.dm
+++ b/code/game/gamemodes/changeling/evolution_menu.dm
@@ -12,7 +12,7 @@ GLOBAL_LIST_EMPTY(sting_paths)
return
var/datum/changeling/changeling = usr.mind.changeling
- if(!GLOB.sting_paths)
+ if(!GLOB.sting_paths || !GLOB.sting_paths.len)
GLOB.sting_paths = init_subtypes(/datum/action/changeling)
var/dat = create_menu(changeling)
diff --git a/code/game/gamemodes/cult/cult.dm b/code/game/gamemodes/cult/cult.dm
index ffe67595f11..1a9dd15c65e 100644
--- a/code/game/gamemodes/cult/cult.dm
+++ b/code/game/gamemodes/cult/cult.dm
@@ -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("Has been converted to the cult!")
+ 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)
diff --git a/code/game/gamemodes/miniantags/abduction/abduction.dm b/code/game/gamemodes/miniantags/abduction/abduction.dm
index 6914ae55363..1888d0e4a1d 100644
--- a/code/game/gamemodes/miniantags/abduction/abduction.dm
+++ b/code/game/gamemodes/miniantags/abduction/abduction.dm
@@ -267,6 +267,7 @@
SSticker.mode.abductors -= abductor_mind
abductor_mind.special_role = null
abductor_mind.current.create_attack_log("No longer abductor")
+ abductor_mind.current.create_log(CONVERSION_LOG, "No longer abductor")
if(issilicon(abductor_mind.current))
to_chat(abductor_mind.current, "You have been turned into a robot! You are no longer an abductor.")
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)
\ No newline at end of file
diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm
index 8fd5da01a2c..ad1146c0bc8 100644
--- a/code/game/gamemodes/nuclear/nuclear.dm
+++ b/code/game/gamemodes/nuclear/nuclear.dm
@@ -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("No longer nuclear operative")
+ operative_mind.current.create_log(CONVERSION_LOG, "No longer nuclear operative")
if(issilicon(operative_mind.current))
to_chat(operative_mind.current, "You have been turned into a robot! You are no longer a Syndicate operative.")
else
diff --git a/code/game/gamemodes/revolution/revolution.dm b/code/game/gamemodes/revolution/revolution.dm
index f7da210b4a9..00ce24b6c46 100644
--- a/code/game/gamemodes/revolution/revolution.dm
+++ b/code/game/gamemodes/revolution/revolution.dm
@@ -244,6 +244,7 @@
rev_mind.current.Stun(5)
to_chat(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.create_attack_log("Has been converted to the revolution!")
+ 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("Has renounced the revolution!")
-
+ rev_mind.current.create_log(CONVERSION_LOG, "renounced the revolution")
if(beingborged)
to_chat(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."]")
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.")
diff --git a/code/game/gamemodes/shadowling/shadowling.dm b/code/game/gamemodes/shadowling/shadowling.dm
index d190009dbb2..9cd91817af4 100644
--- a/code/game/gamemodes/shadowling/shadowling.dm
+++ b/code/game/gamemodes/shadowling/shadowling.dm
@@ -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("Became a thrall")
+ 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("Dethralled")
+ 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("Deshadowlinged")
+ 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)
diff --git a/code/game/gamemodes/vampire/vampire.dm b/code/game/gamemodes/vampire/vampire.dm
index e0ea19b2f0a..6c22313ef6e 100644
--- a/code/game/gamemodes/vampire/vampire.dm
+++ b/code/game/gamemodes/vampire/vampire.dm
@@ -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("De-vampired")
+ vampire_mind.current.create_log(CONVERSION_LOG, "De-vampired")
if(vampire_mind.vampire)
vampire_mind.vampire.remove_vampire_powers()
QDEL_NULL(vampire_mind.vampire)
diff --git a/code/game/gamemodes/wizard/rightandwrong.dm b/code/game/gamemodes/wizard/rightandwrong.dm
index 4f2d20b373a..3dbd9211399 100644
--- a/code/game/gamemodes/wizard/rightandwrong.dm
+++ b/code/game/gamemodes/wizard/rightandwrong.dm
@@ -109,6 +109,7 @@ GLOBAL_VAR_INIT(summon_magic_triggered, FALSE)
H.mind.add_antag_datum(/datum/antagonist/survivalist/guns)
H.create_attack_log("was made into a survivalist, and trusts no one!")
+ 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("was made into a survivalist, and trusts no one!")
+ 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)
\ No newline at end of file
diff --git a/code/game/gamemodes/wizard/soulstone.dm b/code/game/gamemodes/wizard/soulstone.dm
index cf0ce4843f0..634fc8f5a38 100644
--- a/code/game/gamemodes/wizard/soulstone.dm
+++ b/code/game/gamemodes/wizard/soulstone.dm
@@ -69,9 +69,6 @@
to_chat(user, "\"Come now, do not capture your fellow's soul.\"")
return ..()
- M.create_attack_log("Has had their soul captured with [src.name] by [key_name(user)]")
- user.create_attack_log("Used the [src.name] to capture the soul of [key_name(M)]")
-
if(optional)
if(!M.ckey)
to_chat(user, "They have no soul!")
@@ -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
diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm
index 360a1ddcacd..d6e36a805a4 100644
--- a/code/game/gamemodes/wizard/spellbook.dm
+++ b/code/game/gamemodes/wizard/spellbook.dm
@@ -891,6 +891,7 @@
else
user.mind.AddSpell(S)
to_chat(user, "you rapidly read through the arcane book. Suddenly you realize you understand [spellname]!")
+ user.create_log(MISC_LOG, "learned the spell [spellname] ([S])")
user.create_attack_log("[key_name(user)] learned the spell [spellname] ([S]).")
onlearned(user)
diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm
index 84f5b665e4f..3d07ae173db 100644
--- a/code/game/gamemodes/wizard/wizard.dm
+++ b/code/game/gamemodes/wizard/wizard.dm
@@ -59,6 +59,7 @@
SSticker.mode.wizards -= wizard_mind
wizard_mind.special_role = null
wizard_mind.current.create_attack_log("De-wizarded")
+ 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))
diff --git a/code/game/machinery/adv_med.dm b/code/game/machinery/adv_med.dm
index 6b6f3087ad2..9d96ec94b99 100644
--- a/code/game/machinery/adv_med.dm
+++ b/code/game/machinery/adv_med.dm
@@ -425,6 +425,7 @@
var/AN = ""
var/open = ""
var/infected = ""
+ var/dead = ""
var/robot = ""
var/imp = ""
var/bled = ""
@@ -439,6 +440,8 @@
splint = "Splinted:"
if(e.status & ORGAN_BROKEN)
AN = "[e.broken_description]:"
+ if(e.status & ORGAN_DEAD)
+ dead = "DEAD:"
if(e.is_robotic())
robot = "Robotic:"
if(e.open)
@@ -454,9 +457,9 @@
infected = "Acute Infection:"
if(INFECTION_LEVEL_TWO + 200 to INFECTION_LEVEL_TWO + 300)
infected = "Acute Infection+:"
- if(INFECTION_LEVEL_TWO + 300 to INFECTION_LEVEL_TWO + 400)
+ if(INFECTION_LEVEL_TWO + 300 to INFECTION_LEVEL_TWO + 399)
infected = "Acute Infection++:"
- if(INFECTION_LEVEL_THREE to INFINITY)
+ if(INFECTION_LEVEL_TWO + 400 to INFINITY)
infected = "Septic:"
var/unknown_body = 0
@@ -467,11 +470,14 @@
imp += "Unknown body present:"
if(!AN && !open && !infected & !imp)
AN = "None:"
- dat += "[e.name] | [e.burn_dam] | [e.brute_dam] | [robot][bled][AN][splint][open][infected][imp][internal_bleeding][lung_ruptured] | "
+ dat += "[e.name] | [e.burn_dam] | [e.brute_dam] | [robot][bled][AN][splint][open][infected][imp][internal_bleeding][lung_ruptured][dead] | "
dat += ""
for(var/obj/item/organ/internal/i in occupant.internal_organs)
var/mech = i.desc
var/infection = "None"
+ var/dead = ""
+ if(i.status & ORGAN_DEAD)
+ dead = "DEAD:"
switch(i.germ_level)
if(1 to INFECTION_LEVEL_ONE + 200)
infection = "Mild Infection:"
@@ -483,11 +489,13 @@
infection = "Acute Infection:"
if(INFECTION_LEVEL_TWO + 200 to INFECTION_LEVEL_TWO + 300)
infection = "Acute Infection+:"
- if(INFECTION_LEVEL_TWO + 300 to INFINITY)
+ if(INFECTION_LEVEL_TWO + 300 to INFECTION_LEVEL_TWO + 399)
infection = "Acute Infection++:"
+ if(INFECTION_LEVEL_TWO + 400 to INFINITY)
+ infection = "Septic:"
dat += ""
- dat += "| [i.name] | N/A | [i.damage] | [infection]:[mech] | | "
+ dat += "[i.name] | N/A | [i.damage] | [infection]:[mech][dead] | | "
dat += "
"
dat += ""
if(occupant.disabilities & BLIND)
diff --git a/code/game/machinery/computer/cloning.dm b/code/game/machinery/computer/cloning.dm
index 6a8aafb364d..14a8e2b6ddc 100644
--- a/code/game/machinery/computer/cloning.dm
+++ b/code/game/machinery/computer/cloning.dm
@@ -358,35 +358,37 @@
return
if(scan_brain && !can_brainscan())
return
- if((isnull(subject)) || (!(ishuman(subject))) || (!subject.dna) || (NO_SCAN in subject.dna.species.species_traits))
- scantemp = "Error: Unable to locate valid genetic data."
- SSnanoui.update_uis(src)
- return
+ if(isnull(subject) || (!(ishuman(subject))) || (!subject.dna))
+ if(isalien(subject))
+ scantemp = "Error: Xenomorphs are not scannable."
+ SSnanoui.update_uis(src)
+ return
+ // can add more conditions for specific non-human messages here
+ else
+ scantemp = "Error: Subject species is not scannable."
+ SSnanoui.update_uis(src)
+ return
if(subject.get_int_organ(/obj/item/organ/internal/brain))
var/obj/item/organ/internal/brain/Brn = subject.get_int_organ(/obj/item/organ/internal/brain)
if(istype(Brn))
if(NO_SCAN in Brn.dna.species.species_traits)
- scantemp = "Error: Subject's brain is incompatible."
+ scantemp = "Error: [subject.dna.species.name_plural] are not scannable."
SSnanoui.update_uis(src)
return
if(!subject.get_int_organ(/obj/item/organ/internal/brain))
- scantemp = "Error: No signs of intelligence detected."
+ scantemp = "Error: No brain detected in subject."
SSnanoui.update_uis(src)
return
if(subject.suiciding)
- scantemp = "Error: Subject's brain is not responding to scanning stimuli."
+ scantemp = "Error: Subject has committed suicide and is not scannable."
SSnanoui.update_uis(src)
return
if((!subject.ckey) || (!subject.client))
- scantemp = "Error: Mental interface failure."
+ scantemp = "Error: Subject's brain is not responding. Further attempts after a short delay may succeed."
SSnanoui.update_uis(src)
return
if((NOCLONE in subject.mutations) && src.scanner.scan_level < 2)
- scantemp = "Error: Mental interface failure."
- SSnanoui.update_uis(src)
- return
- if(scan_brain && !subject.get_int_organ(/obj/item/organ/internal/brain))
- scantemp = "Error: No brain found."
+ scantemp = "Error: Subject has incompatible genetic mutations."
SSnanoui.update_uis(src)
return
if(!isnull(find_record(subject.ckey)))
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index cc76c2ab86f..692024899a2 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -276,6 +276,7 @@ About the new airlock wires panel:
if(usr)
shockedby += text("\[[time_stamp()]\] - [usr](ckey:[usr.ckey])")
usr.create_attack_log("Electrified the [name] at [x] [y] [z]")
+ 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("Electrified the [name] at [x] [y] [z]")
+ 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("Electrified the [name] at [x] [y] [z]")
+ add_attack_logs(usr, src, "Electrified")
to_chat(usr, "The door is now electrified.")
electrify(-1)
if("open")
@@ -1000,7 +1003,7 @@ About the new airlock wires panel:
"You begin [welded ? "unwelding":"welding"] the airlock...", \
"You hear welding.")
- if(I.use_tool(src, user, 40, volume = I.tool_volume, extra_checks = CALLBACK(src, .proc/weld_checks, I, user)))
+ if(I.use_tool(src, user, 40, volume = I.tool_volume, extra_checks = list(CALLBACK(src, .proc/weld_checks, I, user))))
if(!density && !welded)
return
welded = !welded
@@ -1011,7 +1014,7 @@ About the new airlock wires panel:
user.visible_message("[user] is welding the airlock.", \
"You begin repairing the airlock...", \
"You hear welding.")
- if(I.use_tool(src, user, 40, volume = I.tool_volume, extra_checks = CALLBACK(src, .proc/weld_checks, I, user)))
+ if(I.use_tool(src, user, 40, volume = I.tool_volume, extra_checks = list(CALLBACK(src, .proc/weld_checks, I, user))))
obj_integrity = max_integrity
stat &= ~BROKEN
user.visible_message("[user.name] has repaired [src].", \
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index cc58d7efe7e..ec6f2c6456e 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -559,6 +559,7 @@
animal_damage = user.obj_damage
animal_damage = min(animal_damage, 20*user.environment_smash)
user.create_attack_log("attacked [name]")
+ add_attack_logs(user, src, "Attacked")
attack_generic(user, animal_damage, user.melee_damage_type, "melee", play_soundeffect)
return TRUE
diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm
index 21b71e7668c..095b1ea9456 100644
--- a/code/game/objects/items/devices/laserpointer.dm
+++ b/code/game/objects/items/devices/laserpointer.dm
@@ -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 = "You missed the lens of [C] with [src]."
diff --git a/code/game/objects/items/tools/tool_behaviour.dm b/code/game/objects/items/tools/tool_behaviour.dm
index c937f80c30d..80b00dcd1ed 100644
--- a/code/game/objects/items/tools/tool_behaviour.dm
+++ b/code/game/objects/items/tools/tool_behaviour.dm
@@ -16,11 +16,11 @@
var/datum/callback/tool_check = CALLBACK(src, .proc/tool_check_callback, user, target, amount, extra_checks)
if(ismob(target))
- if(!do_mob(user, target, delay, extra_checks=tool_check))
+ if(!do_mob(user, target, delay, extra_checks = list(tool_check)))
return
else
- if(!do_after(user, delay, target=target, extra_checks=tool_check))
+ if(!do_after(user, delay, target=target, extra_checks = list(tool_check)))
return
else
// Invoke the extra checks once, just in case.
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index 9faab45cf97..8fe46ca052d 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -254,7 +254,7 @@ GLOBAL_LIST_INIT(wcCommon, pick(list("#379963", "#0d8395", "#58b5c3", "#49e46e",
if(!can_be_reached(user))
return
to_chat(user, "You begin to lever the window [state == WINDOW_OUT_OF_FRAME ? "into":"out of"] the frame...")
- if(!I.use_tool(src, user, decon_speed, volume = I.tool_volume, extra_checks = CALLBACK(src, .proc/check_state_and_anchored, state, anchored)))
+ if(!I.use_tool(src, user, decon_speed, volume = I.tool_volume, extra_checks = list(CALLBACK(src, .proc/check_state_and_anchored, state, anchored))))
return
state = (state == WINDOW_OUT_OF_FRAME ? WINDOW_IN_FRAME : WINDOW_OUT_OF_FRAME)
to_chat(user, "You pry the window [state == WINDOW_IN_FRAME ? "into":"out of"] the frame.")
@@ -268,20 +268,20 @@ GLOBAL_LIST_INIT(wcCommon, pick(list("#379963", "#0d8395", "#58b5c3", "#49e46e",
if(reinf)
if(state == WINDOW_SCREWED_TO_FRAME || state == WINDOW_IN_FRAME)
to_chat(user, "You begin to [state == WINDOW_SCREWED_TO_FRAME ? "unscrew the window from":"screw the window to"] the frame...")
- if(!I.use_tool(src, user, decon_speed, volume = I.tool_volume, extra_checks = CALLBACK(src, .proc/check_state_and_anchored, state, anchored)))
+ if(!I.use_tool(src, user, decon_speed, volume = I.tool_volume, extra_checks = list(CALLBACK(src, .proc/check_state_and_anchored, state, anchored))))
return
state = (state == WINDOW_IN_FRAME ? WINDOW_SCREWED_TO_FRAME : WINDOW_IN_FRAME)
to_chat(user, "You [state == WINDOW_IN_FRAME ? "unfasten the window from":"fasten the window to"] the frame.")
else if(state == WINDOW_OUT_OF_FRAME)
to_chat(user, "You begin to [anchored ? "unscrew the frame from":"screw the frame to"] the floor...")
- if(!I.use_tool(src, user, decon_speed, volume = I.tool_volume, extra_checks = CALLBACK(src, .proc/check_state_and_anchored, state, anchored)))
+ if(!I.use_tool(src, user, decon_speed, volume = I.tool_volume, extra_checks = list(CALLBACK(src, .proc/check_state_and_anchored, state, anchored))))
return
anchored = !anchored
update_nearby_icons()
to_chat(user, "You [anchored ? "fasten the frame to":"unfasten the frame from"] the floor.")
else //if we're not reinforced, we don't need to check or update state
to_chat(user, "You begin to [anchored ? "unscrew the window from":"screw the window to"] the floor...")
- if(!I.use_tool(src, user, decon_speed, volume = I.tool_volume, extra_checks = CALLBACK(src, .proc/check_anchored, anchored)))
+ if(!I.use_tool(src, user, decon_speed, volume = I.tool_volume, extra_checks = list(CALLBACK(src, .proc/check_anchored, anchored))))
return
anchored = !anchored
air_update_turf(TRUE)
@@ -297,7 +297,7 @@ GLOBAL_LIST_INIT(wcCommon, pick(list("#379963", "#0d8395", "#58b5c3", "#49e46e",
if(!can_be_reached(user))
return
TOOL_ATTEMPT_DISMANTLE_MESSAGE
- if(!I.use_tool(src, user, decon_speed, volume = I.tool_volume, extra_checks = CALLBACK(src, .proc/check_state_and_anchored, state, anchored)))
+ if(!I.use_tool(src, user, decon_speed, volume = I.tool_volume, extra_checks = list(CALLBACK(src, .proc/check_state_and_anchored, state, anchored))))
return
var/obj/item/stack/sheet/G = new glass_type(user.loc, glass_amount)
G.add_fingerprint(user)
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index ec197c61651..34929b52f53 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -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*/
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 340acc52ef7..03b5cb09720 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -2196,9 +2196,15 @@
var/reply_to = locate(href_list["replyto"])
var/destination
var/notify
-
- var/obj/item/paper/P = new /obj/item/paper(null) //hopefully the null loc won't cause trouble for us
-
+ var/obj/item/paper/P
+ var/use_letterheard = alert("Use letterhead? If so, do not add your own header or a footer. Type and format only your actual message.",,"Nanotrasen","Syndicate", "No")
+ switch(use_letterheard)
+ if("Nanotrasen")
+ P = new /obj/item/paper/central_command(null)
+ if("Syndicate")
+ P = new /obj/item/paper/syndicate(null)
+ if("No")
+ P = new /obj/item/paper(null)
if(!fax)
var/list/departmentoptions = GLOB.alldepartments + GLOB.hidden_departments + "All Departments"
destination = input(usr, "To which department?", "Choose a department", "") as null|anything in departmentoptions
diff --git a/code/modules/admin/verbs/logging_view.dm b/code/modules/admin/verbs/logging_view.dm
new file mode 100644
index 00000000000..2a222e21dbd
--- /dev/null
+++ b/code/modules/admin/verbs/logging_view.dm
@@ -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)
\ No newline at end of file
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index 8c68d73a390..a8136655d8f 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -885,7 +885,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
return
to_chat(usr, text("Attack Log for []", 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!
diff --git a/code/modules/client/asset_cache.dm b/code/modules/client/asset_cache.dm
index b29482a73cc..e1e68bd0617 100644
--- a/code/modules/client/asset_cache.dm
+++ b/code/modules/client/asset_cache.dm
@@ -195,7 +195,8 @@ GLOBAL_LIST_EMPTY(asset_datums)
"large_stamp-rep.png" = 'icons/paper_icons/large_stamp-rep.png',
"large_stamp-magistrate.png"= 'icons/paper_icons/large_stamp-magistrate.png',
"talisman.png" = 'icons/paper_icons/talisman.png',
- "ntlogo.png" = 'icons/paper_icons/ntlogo.png'
+ "ntlogo.png" = 'icons/paper_icons/ntlogo.png',
+ "syndielogo.png" ='icons/paper_icons/syndielogo.png'
)
/datum/asset/simple/chess
diff --git a/code/modules/food_and_drinks/kitchen_machinery/gibber.dm b/code/modules/food_and_drinks/kitchen_machinery/gibber.dm
index e0f456facec..7d17ac07720 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/gibber.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/gibber.dm
@@ -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 an autogibber (\the [src])")
+ add_attack_logs(src, occupant, "gibbed")
occupant.emote("scream")
playsound(get_turf(src), 'sound/goonstation/effects/gib.ogg', 50, 1)
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 0c283c60a7f..548d22a3fa3 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -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))
diff --git a/code/modules/mob/emote.dm b/code/modules/mob/emote.dm
index 57232d74f44..5f18a27adc9 100644
--- a/code/modules/mob/emote.dm
+++ b/code/modules/mob/emote.dm
@@ -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)
diff --git a/code/modules/mob/living/carbon/human/species/slime.dm b/code/modules/mob/living/carbon/human/species/slime.dm
index d9fa1ccbf3c..622e0919b45 100644
--- a/code/modules/mob/living/carbon/human/species/slime.dm
+++ b/code/modules/mob/living/carbon/human/species/slime.dm
@@ -146,11 +146,13 @@
return
var/limb_select = input(H, "Choose a limb to regrow", "Limb Regrowth") as null|anything in missing_limbs
+ if(!limb_select) // If the user hit cancel on the popup, return
+ return
var/chosen_limb = missing_limbs[limb_select]
H.visible_message("[H] begins to hold still and concentrate on [H.p_their()] missing [limb_select]...", "You begin to focus on regrowing your missing [limb_select]... (This will take [round(SLIMEPERSON_REGROWTHDELAY/10)] seconds, and you must hold still.)")
- if(do_after(H, SLIMEPERSON_REGROWTHDELAY, needhand = 0, target = H))
- if(H.incapacitated())
+ if(do_after(H, SLIMEPERSON_REGROWTHDELAY, FALSE, H, extra_checks = list(CALLBACK(H, /mob.proc/IsStunned)), use_default_checks = FALSE)) // Override the check for weakness, only check for stunned
+ if(H.incapacitated(ignore_lying = TRUE, extra_checks = list(CALLBACK(H, /mob.proc/IsStunned)), use_default_checks = FALSE)) // Override the check for weakness, only check for stunned
to_chat(H, "You cannot regenerate missing limbs in your current state.")
return
@@ -196,4 +198,4 @@
#undef SLIMEPERSON_HUNGERCOST
#undef SLIMEPERSON_MINHUNGER
-#undef SLIMEPERSON_REGROWTHDELAY
+#undef SLIMEPERSON_REGROWTHDELAY
\ No newline at end of file
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 0f6ba5df181..30e10553d3e 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -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
diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm
index 782808f2190..eeaed10b9c5 100644
--- a/code/modules/mob/living/say.dm
+++ b/code/modules/mob/living/say.dm
@@ -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
diff --git a/code/modules/mob/living/silicon/robot/update_status.dm b/code/modules/mob/living/silicon/robot/update_status.dm
index 63c13f97d24..5f1519395c7 100644
--- a/code/modules/mob/living/silicon/robot/update_status.dm
+++ b/code/modules/mob/living/silicon/robot/update_status.dm
@@ -30,6 +30,7 @@
to_chat(ghost, "Your cyborg shell has been repaired, re-enter if you want to continue! (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()
diff --git a/code/modules/mob/living/stat_states.dm b/code/modules/mob/living/stat_states.dm
index 57fd6e17cbc..9ff67107abd 100644
--- a/code/modules/mob/living/stat_states.dm
+++ b/code/modules/mob/living/stat_states.dm
@@ -7,6 +7,7 @@
else if(stat == UNCONSCIOUS)
return 0
create_attack_log("Fallen unconscious at [atom_loc_line(get_turf(src))]")
+ 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("Woken up at [atom_loc_line(get_turf(src))]")
+ 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("Came back to life at [atom_loc_line(get_turf(src))]")
+ 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
\ No newline at end of file
diff --git a/code/modules/mob/living/update_status.dm b/code/modules/mob/living/update_status.dm
index 20cc969119f..d24301b0dd0 100644
--- a/code/modules/mob/living/update_status.dm
+++ b/code/modules/mob/living/update_status.dm
@@ -65,8 +65,14 @@
return !(IsWeakened() || paralysis || stat || (status_flags & FAKEDEATH))
// Whether the mob is capable of actions or not
-/mob/living/incapacitated(ignore_restraints = FALSE, ignore_grab = FALSE, ignore_lying = FALSE)
- if(stat || paralysis || stunned || IsWeakened() || (!ignore_restraints && restrained()) || (!ignore_lying && lying))
+/mob/living/incapacitated(ignore_restraints = FALSE, ignore_grab = FALSE, ignore_lying = FALSE, list/extra_checks = list(), use_default_checks = TRUE)
+ // By default, checks for weakness and stunned get added to the extra_checks list.
+ // Setting `use_default_checks` to FALSE means that you don't want it checking for these statuses or you are supplying your own checks.
+ if(use_default_checks)
+ extra_checks += CALLBACK(src, /mob.proc/IsWeakened)
+ extra_checks += CALLBACK(src, /mob.proc/IsStunned)
+
+ if(stat || paralysis || (!ignore_restraints && restrained()) || (!ignore_lying && lying) || check_for_true_callbacks(extra_checks))
return TRUE
// wonderful proc names, I know - used to check whether the blur overlay
diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm
index 04dfa081be1..5345bda5b94 100644
--- a/code/modules/mob/login.dm
+++ b/code/modules/mob/login.dm
@@ -5,6 +5,7 @@
computer_id = client.computer_id
log_access_in(client)
create_attack_log("Logged in at [atom_loc_line(get_turf(src))]")
+ create_log(MISC_LOG, "Logged in")
if(config.log_access)
for(var/mob/M in GLOB.player_list)
if(M == src) continue
diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm
index bbf477fa1a0..8bdb0d89332 100644
--- a/code/modules/mob/logout.dm
+++ b/code/modules/mob/logout.dm
@@ -4,6 +4,7 @@
GLOB.player_list -= src
log_access_out(src)
create_attack_log("Logged out at [atom_loc_line(get_turf(src))]")
+ 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)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 7d1edef93cf..d93b2723024 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -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)
\ No newline at end of file
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 5d63b7b4ad9..2f074aeaa9f 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -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
\ No newline at end of file
diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm
index 3b3f34fdb13..81d22e40a0b 100644
--- a/code/modules/mob/say.dm
+++ b/code/modules/mob/say.dm
@@ -1,5 +1,5 @@
-#define ILLEGAL_CHARACTERS_LIST list("<" = "", ">" = "", "^" = "", \
+#define ILLEGAL_CHARACTERS_LIST list("<" = "", ">" = "", \
"\[" = "", "]" = "", "{" = "", "}" = "")
/mob/proc/say()
diff --git a/code/modules/mob/status_procs.dm b/code/modules/mob/status_procs.dm
index 0aeb9beef84..b61ce171194 100644
--- a/code/modules/mob/status_procs.dm
+++ b/code/modules/mob/status_procs.dm
@@ -173,6 +173,9 @@
/mob/proc/Stun()
return
+/mob/proc/IsStunned()
+ return stunned
+
/mob/proc/SetStunned()
return
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index 57bdc94b08c..34acdcf0983 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -21,7 +21,9 @@
max_integrity = 50
attack_verb = list("bapped")
dog_fashion = /datum/dog_fashion/head
+ var/header //Above the main body, displayed at the top
var/info //What's actually written on the paper.
+ var/footer //The bottom stuff before the stamp but after the body
var/info_links //A different version of the paper which includes html links at fields and EOF
var/stamps //The (text for the) stamps on the paper.
var/fields //Amount of user created fields
@@ -76,9 +78,9 @@
var/data
var/stars = (!user.say_understands(null, GLOB.all_languages["Galactic Common"]) && !forceshow) || forcestars
if(stars) //assuming all paper is written in common is better than hardcoded type checks
- data = "[stars(info)][stamps]"
+ data = "[header][stars(info)][footer][stamps]"
else
- data = "[infolinks ? info_links : info]
[stamps]"
+ data = "[header][infolinks ? info_links : info]
[footer][stamps]"
if(view)
var/datum/browser/popup = new(user, "Paper[UID()]", , paper_width, paper_height)
popup.stylesheets = list()
@@ -587,6 +589,23 @@
/obj/item/paper/crumpled
name = "paper scrap"
icon_state = "scrap"
+
+/obj/item/paper/syndicate
+ name = "paper"
+ header = "
"
+ info = ""
+
+/obj/item/paper/nanotrasen
+ name = "paper"
+ header = "
"
+ info = ""
+
+/obj/item/paper/central_command
+ name = "paper"
+ header ="
Nanotrasen Central Command
Official Expedited Memorandum
"
+ info = ""
+ footer = "
Failure to adhere appropriately to orders that may be contained herein is in violation of Space Law, and punishments may be administered appropriately upon return to Central Command.
The recipient(s) of this memorandum acknowledge by reading it that they are liable for any and all damages to crew or station that may arise from ignoring suggestions or advice given herein.
"
+
/obj/item/paper/crumpled/update_icon()
return
diff --git a/code/modules/paperwork/paperbin.dm b/code/modules/paperwork/paperbin.dm
index 68afe9fbd12..d76765b61a7 100644
--- a/code/modules/paperwork/paperbin.dm
+++ b/code/modules/paperwork/paperbin.dm
@@ -10,7 +10,8 @@
pressure_resistance = 8
var/amount = 30 //How much paper is in the bin.
var/list/papers = list() //List of papers put in the bin for reference.
-
+ var/letterhead_type
+
/obj/item/paper_bin/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE)
if(amount)
amount = 0
@@ -71,7 +72,10 @@
P = papers[papers.len]
papers.Remove(P)
else
- P = new /obj/item/paper
+ if(letterhead_type && alert("Choose a style",,"Letterhead","Blank")=="Letterhead")
+ P = new letterhead_type
+ else
+ P = new /obj/item/paper
if(SSholiday.holidays && SSholiday.holidays[APRIL_FOOLS])
if(prob(30))
P.info = "HONK HONK HONK HONK HONK HONK HONK
HOOOOOOOOOOOOOOOOOOOOOONK
APRIL FOOLS"
@@ -139,3 +143,13 @@
add_fingerprint(user)
return
+
+
+/obj/item/paper_bin/nanotrasen
+ name = "nanotrasen paper bin"
+ letterhead_type = /obj/item/paper/nanotrasen
+
+/obj/item/paper_bin/syndicate
+ name = "syndicate paper bin"
+ letterhead_type = /obj/item/paper/syndicate
+
diff --git a/code/modules/paperwork/photocopier.dm b/code/modules/paperwork/photocopier.dm
index aa1fe064626..5ca53396253 100644
--- a/code/modules/paperwork/photocopier.dm
+++ b/code/modules/paperwork/photocopier.dm
@@ -187,7 +187,9 @@
/obj/machinery/photocopier/proc/copy(var/obj/item/paper/copy)
var/obj/item/paper/c = new /obj/item/paper (loc)
+ c.header = copy.header
c.info = copy.info
+ c.footer = copy.footer
c.name = copy.name // -- Doohl
c.fields = copy.fields
c.stamps = copy.stamps
diff --git a/code/modules/projectiles/guns/magic/wand.dm b/code/modules/projectiles/guns/magic/wand.dm
index 544ca67a361..68a7e7275a7 100644
--- a/code/modules/projectiles/guns/magic/wand.dm
+++ b/code/modules/projectiles/guns/magic/wand.dm
@@ -51,6 +51,7 @@
user.visible_message("[user] zaps [user.p_them()]self with [src].")
playsound(user, fire_sound, 50, 1)
user.create_attack_log("[key_name(user)] zapped [user.p_them()]self with a [src]")
+ add_attack_logs(null, user, "zapped [user.p_them()]self with a [src]")
/////////////////////////////////////
//WAND OF DEATH
diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm
index d15e6c1375d..6c464a9dca1 100644
--- a/code/modules/projectiles/projectile/magic.dm
+++ b/code/modules/projectiles/projectile/magic.dm
@@ -263,12 +263,14 @@
return
M.create_attack_log("[key_name(M)] became [new_mob.real_name].")
- 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, "Your form morphs into that of a [randomize].")
@@ -344,4 +346,4 @@
to_chat(target, "You get splatted by [src].")
M.Weaken(slip_weaken)
M.Stun(slip_stun)
- . = ..()
+ . = ..()
\ No newline at end of file
diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm
index 194675a21d8..48c35415387 100644
--- a/code/modules/reagents/reagent_dispenser.dm
+++ b/code/modules/reagents/reagent_dispenser.dm
@@ -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 (JMP)", 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("[user] catastrophically fails at refilling [user.p_their()] [I]!", "That was stupid of you.")
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
diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm
index 7d2be6953c6..92245d83acb 100755
--- a/code/modules/recycling/sortingmachinery.dm
+++ b/code/modules/recycling/sortingmachinery.dm
@@ -221,6 +221,7 @@
user.visible_message("[user] wraps [target].")
user.create_attack_log("Has used [name] on [target]")
+ 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) )
diff --git a/code/modules/spacepods/spacepod.dm b/code/modules/spacepods/spacepod.dm
index 5b0be539578..7b154ef2a25 100644
--- a/code/modules/spacepods/spacepod.dm
+++ b/code/modules/spacepods/spacepod.dm
@@ -216,6 +216,7 @@
deal_damage(damage)
visible_message("[user] [user.attacktext] [src]!")
user.create_attack_log("attacked [src.name]")
+ add_attack_logs(user, src, "attacked")
return TRUE
/obj/spacepod/attack_alien(mob/user)
diff --git a/code/modules/tram/tram.dm b/code/modules/tram/tram.dm
index 109e820345b..ea43f3fd659 100644
--- a/code/modules/tram/tram.dm
+++ b/code/modules/tram/tram.dm
@@ -243,6 +243,7 @@
qdel(src)
src.visible_message("[M] has [M.attacktext] [src]!")
M.create_attack_log("attacked [src.name]")
+ add_attack_logs(M, src, "attacked")
/obj/tram/bullet_act(var/obj/item/projectile/proj)
if(prob(proj.damage))
diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi
index 3d02cfd7a58..26cd5a1337b 100644
Binary files a/icons/mob/inhands/items_lefthand.dmi and b/icons/mob/inhands/items_lefthand.dmi differ
diff --git a/icons/mob/inhands/items_righthand.dmi b/icons/mob/inhands/items_righthand.dmi
index 265230ad369..78aa94f2907 100644
Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ
diff --git a/icons/paper_icons/ntlogo.png b/icons/paper_icons/ntlogo.png
index d5614a964ee..3aeae313238 100644
Binary files a/icons/paper_icons/ntlogo.png and b/icons/paper_icons/ntlogo.png differ
diff --git a/icons/paper_icons/syndielogo.png b/icons/paper_icons/syndielogo.png
new file mode 100644
index 00000000000..6b0c4966541
Binary files /dev/null and b/icons/paper_icons/syndielogo.png differ
diff --git a/paradise.dme b/paradise.dme
index c5ba389fa06..782ca737428 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -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"
@@ -1214,6 +1217,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"