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 += "" + 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 +="\ + \ + " + + dat += "
WhenTypeWhoWhatTargetWhere
[time][L.log_type][L.who][L.what][L.target][ADMIN_COORDJMP(L.where)]
" + 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"