From a6871ee9e613d4f6f83c95d548cdd65de030c8ef Mon Sep 17 00:00:00 2001 From: CIB Date: Sat, 2 Jun 2012 19:59:57 +0300 Subject: [PATCH 1/5] Added a guy to the whitelist with Spaceman's blessing. --- config/alienwhitelist.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config/alienwhitelist.txt b/config/alienwhitelist.txt index 7bd575035fe..39be52e1117 100644 --- a/config/alienwhitelist.txt +++ b/config/alienwhitelist.txt @@ -3,4 +3,6 @@ searif - Soghun searif - Tajaran galenus - Soghun botanistpower - Tajaran -fenrisian - Tajaran \ No newline at end of file +fenrisian - Tajaran +forsamori - Tajaran +forsamori - Soghun \ No newline at end of file From ae316d58f8184253bd1086144f16527692305850 Mon Sep 17 00:00:00 2001 From: Erthilo Date: Sat, 2 Jun 2012 20:14:23 +0100 Subject: [PATCH 2/5] Tweaked Moderators: * They have their own channel msay, which all mods and admins can use. * They cannot see or use asay, but they can see and respond to adminhelps and admin PM's. * Their PM's will come up in maroon and show MOD PM. * They have a moderator panel that shows name, ckey, IP and CID. * They can use Set-Play, Set-Observe, Show-Skills, and can read and write Player Info. * They can Deadmin themselves for the round. Other Changes: * Player info now shows the rank of who typed what. * Admins are messages when player info is modified. * Tweaks to prevent moderators using some commands. --- code/game/verbs/ooc.dm | 2 + code/modules/admin/admin.dm | 117 +++++++++++++--------- code/modules/admin/admin_verbs.dm | 80 +++++++++------ code/modules/admin/player_panel.dm | 107 ++++++++++++++++++++ code/modules/admin/verbs/adminpm.dm | 20 ++-- code/modules/admin/verbs/adminsay.dm | 32 +++++- code/modules/admin/verbs/pray.dm | 4 +- code/modules/mob/new_player/new_player.dm | 2 +- code/stylesheet.dm | 4 + 9 files changed, 279 insertions(+), 89 deletions(-) diff --git a/code/game/verbs/ooc.dm b/code/game/verbs/ooc.dm index 4b4d39d32b3..9c37e9ca1b0 100644 --- a/code/game/verbs/ooc.dm +++ b/code/game/verbs/ooc.dm @@ -42,6 +42,8 @@ C << "OOC: [src.key][src.client.stealth ? "/([src.client.fakekey])" : ""]: [msg]" else if (src.client.holder.rank == "Retired Admin") C << "OOC: [src.key][src.client.stealth ? "/([src.client.fakekey])" : ""]: [msg]" + else if (src.client.holder.rank == "Moderator") + C << "OOC: [src.key][src.client.stealth ? "/([src.client.fakekey])" : ""]: [msg]" else if (src.client.holder.level >= 5) C << "OOC: [src.key][src.client.stealth ? "/([src.client.fakekey])" : ""]: [msg]" else diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index d0f0b72f56a..919b81a4b6b 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -6,7 +6,7 @@ var/global/BSACooldown = 0 /proc/message_admins(var/text, var/admin_ref = 0) var/rendered = "ADMIN LOG: [text]" for (var/mob/M in world) - if (M && M.client && M.client.holder && M.client.holder.level > -3) //Lets not spam our retirees. + if (M && M.client && M.client.holder && M.client.holder.level > -3 && M.client.holder.level != 0) //Lets not spam our retirees. Or moderators! if (admin_ref) M << dd_replaceText(rendered, "%admin_ref%", "\ref[M]") else @@ -176,6 +176,7 @@ var/global/BSACooldown = 0 var/datum/player_info/P = new P.author = usr.key + P.rank = usr.client.holder.rank P.content = add var/modifyer = "th" switch(time2text(world.timeofday, "DD")) @@ -196,6 +197,9 @@ var/global/BSACooldown = 0 info << infos + message_admins("\blue [key_name_admin(usr)] has edited [key]'s notes.") + log_admin("[key_name(usr)] has edited [key]'s notes.") + del info var/savefile/note_list = new("data/player_notes.sav") @@ -221,6 +225,9 @@ var/global/BSACooldown = 0 infos.Remove(item) info << infos + message_admins("\blue [key_name_admin(usr)] deleted one of [key]'s notes.") + log_admin("[key_name(usr)] deleted one of [key]'s notes.") + del info show_player_info(key) @@ -612,13 +619,6 @@ var/global/BSACooldown = 0 var/datum/job/temp = job_master.GetJob(jobPos) if(!temp) continue joblist += temp.title - if("miscdept") - joblist += "pAI" - for(var/jobPos in nonhuman_positions) - if(!jobPos) continue - var/datum/job/temp = job_master.GetJob(jobPos) - if(!temp) continue - joblist += temp.title else joblist += href_list["jobban3"] @@ -800,7 +800,7 @@ var/global/BSACooldown = 0 usr << browse(dat, "window=jobban2;size=600x250") return if(href_list["newjobban2"]) - if ((src.rank in list("Moderator", "Administrator", "Badmin", "Tyrant" ))) + if ((src.rank in list(/*"Moderator", */"Administrator", "Badmin", "Tyrant" ))) var/mob/M = locate(href_list["jobban4"]) var/job = href_list["newjobban2"] if(!ismob(M)) return @@ -877,7 +877,7 @@ var/global/BSACooldown = 0 remove_goon(t) */ if (href_list["mute2"]) - if ((src.rank in list( "Moderator", "Temporary Admin", "Admin Candidate", "Trial Admin", "Badmin", "Game Admin", "Game Master" ))) + if ((src.rank in list(/* "Moderator", */"Temporary Admin", "Admin Candidate", "Trial Admin", "Badmin", "Game Admin", "Game Master" ))) var/mob/M = locate(href_list["mute2"]) if (ismob(M)) if ((M.client && M.client.holder && (M.client.holder.level >= src.level))) @@ -891,7 +891,7 @@ var/global/BSACooldown = 0 message_admins("\blue [key_name_admin(usr)] has [(M.client.muted ? "muted" : "voiced")] [key_name_admin(M)].", 1) M << "You have been [(M.client.muted ? "muted" : "voiced")]. Please resolve this in adminhelp." if (href_list["mute_complete"]) - if ((src.rank in list( "Moderator", "Temporary Admin", "Admin Candidate", "Trial Admin", "Badmin", "Game Admin", "Game Master" ))) + if ((src.rank in list(/* "Moderator", */"Temporary Admin", "Admin Candidate", "Trial Admin", "Badmin", "Game Admin", "Game Master" ))) var/mob/M = locate(href_list["mute_complete"]) if (ismob(M)) if ((M.client && M.client.holder && (M.client.holder.level >= src.level))) @@ -997,7 +997,7 @@ var/global/BSACooldown = 0 return if (href_list["sendtoprison"]) - if ((src.rank in list( "Moderator", "Admin Candidate", "Temporary Admin", "Trial Admin", "Badmin", "Game Admin", "Game Master" ))) + if ((src.rank in list(/* "Moderator", */"Admin Candidate", "Temporary Admin", "Trial Admin", "Badmin", "Game Admin", "Game Master" ))) var/confirm = alert(usr, "Send to admin prison for the round?", "Message", "Yes", "No") if(confirm != "Yes") @@ -1350,24 +1350,30 @@ var/global/BSACooldown = 0 // Now isn't that much better? IT IS NOW A PROC, i.e. kinda like a big panel like unstable if (href_list["adminplayeropts"]) - var/mob/M = locate(href_list["adminplayeropts"]) - show_player_panel(M) + if(rank in list("Admin Observer", "Temporary Admin", "Admin Candidate", "Trial Admin", "Badmin", "Game Admin", "Game Master")) + var/mob/M = locate(href_list["adminplayeropts"]) + show_player_panel(M) + else + alert("You cannot perform this action. You must be of a higher administrative rank!") if (href_list["player_info"]) var/key = href_list["player_info"] show_player_info(key) if (href_list["adminplayervars"]) - var/mob/M = locate(href_list["adminplayervars"]) - if(src && src.owner) - if(istype(src.owner,/client)) - var/client/cl = src.owner - cl.debug_variables(M) - else if(ismob(src.owner)) - var/mob/MO = src.owner - if(MO.client) - var/client/cl = MO.client + if(rank in list("Admin Observer", "Temporary Admin", "Admin Candidate", "Trial Admin", "Badmin", "Game Admin", "Game Master")) + var/mob/M = locate(href_list["adminplayervars"]) + if(src && src.owner) + if(istype(src.owner,/client)) + var/client/cl = src.owner cl.debug_variables(M) + else if(ismob(src.owner)) + var/mob/MO = src.owner + if(MO.client) + var/client/cl = MO.client + cl.debug_variables(M) + else + alert("You cannot perform this action. You must be of a higher administrative rank!") if (href_list["adminplayersubtlemessage"]) var/mob/M = locate(href_list["adminplayersubtlemessage"]) @@ -1382,30 +1388,38 @@ var/global/BSACooldown = 0 cl.cmd_admin_subtle_message(M) if (href_list["adminplayerobservejump"]) - var/mob/M = locate(href_list["adminplayerobservejump"]) - if(src && src.owner) - if(istype(src.owner,/client)) - var/client/cl = src.owner - cl.admin_observe() - sleep(2) - cl.jumptomob(M) - else if(ismob(src.owner)) - var/mob/MO = src.owner - if(MO.client) - var/client/cl = MO.client + if(rank in list("Admin Observer", "Temporary Admin", "Admin Candidate", "Trial Admin", "Badmin", "Game Admin", "Game Master")) + var/mob/M = locate(href_list["adminplayerobservejump"]) + if(src && src.owner) + if(istype(src.owner,/client)) + var/client/cl = src.owner cl.admin_observe() sleep(2) cl.jumptomob(M) + else if(ismob(src.owner)) + var/mob/MO = src.owner + if(MO.client) + var/client/cl = MO.client + cl.admin_observe() + sleep(2) + cl.jumptomob(M) + else + alert("You cannot perform this action. You must be of a higher administrative rank!") if (href_list["traitor_panel_pp"]) - var/mob/M = locate(href_list["traitor_panel_pp"]) - if(isnull(M)) - usr << "Mob doesn't seem to exist." - return - if(!ismob(M)) - usr << "This doen't seem to be a mob." - return - show_traitor_panel(M) + if(rank in list("Admin Observer", "Temporary Admin", "Admin Candidate", "Trial Admin", "Badmin", "Game Admin", "Game Master")) + var/mob/M = locate(href_list["traitor_panel_pp"]) + if(isnull(M)) + usr << "Mob doesn't seem to exist." + return + if(!ismob(M)) + usr << "This doen't seem to be a mob." + return + show_traitor_panel(M) + else + alert("You cannot perform this action. You must be of a higher administrative rank!") + + if (href_list["BlueSpaceArtillery"]) var/mob/M = locate(href_list["BlueSpaceArtillery"]) @@ -2239,7 +2253,7 @@ var/global/BSACooldown = 0 return if (href_list["secretsadmin"]) - if ((src.rank in list( "Moderator", "Temporary Admin", "Admin Candidate", "Trial Admin", "Badmin", "Game Admin", "Game Master" ))) + if ((src.rank in list(/* "Moderator", */"Temporary Admin", "Admin Candidate", "Trial Admin", "Badmin", "Game Admin", "Game Master" ))) var/ok = 0 switch(href_list["secretsadmin"]) if("clear_bombs") @@ -2380,7 +2394,7 @@ var/global/BSACooldown = 0 message_admins("\blue [user.ckey] warned [M.ckey].") else AddBan(M.ckey, M.computer_id, "Autobanning due to previous warn", user.ckey, 1, AUTOBANTIME) - M << "\redYou have been autobanned by [user.ckey]. This is what we in the biz like to call a \"second warning\"." + M << "\redYou have been autobanned by [user.ckey]." M << "\red This is a temporary ban; it will automatically be removed in [AUTOBANTIME] minutes." log_admin("[user.ckey] warned [M.ckey], resulting in a [AUTOBANTIME] minute autoban.") ban_unban_log_save("[user.ckey] warned [M.ckey], resulting in a [AUTOBANTIME] minute autoban.") @@ -2428,6 +2442,7 @@ var/global/BSACooldown = 0 body += "Mob type = [M.type]

" + body += "Warn | " body += "Kick | " body += "Ban | " body += "Jobban " @@ -2527,10 +2542,11 @@ var/global/BSACooldown = 0 //feedback_add_details("admin_verb","SPP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/player_info/var - author // admin who authored the information - content // text content of the information - timestamp // Because this is bloody annoying + +/datum/player_info/var/author // admin who authored the information +/datum/player_info/var/rank //rank of admin who made the notes +/datum/player_info/var/content // text content of the information +/datum/player_info/var/timestamp // Because this is bloody annoying /obj/admins/proc/player_has_info(var/key as text) var/savefile/info = new("data/player_saves/[copytext(key, 1, 2)]/[key]/info.sav") @@ -2563,7 +2579,10 @@ var/global/BSACooldown = 0 if(!I.timestamp) I.timestamp = "Pre-4/3/2012" update_file = 1 - dat += "[I.content] by [I.author] on [I.timestamp] " + if(!I.rank) + I.rank = "N/A" + update_file = 1 + dat += "[I.content] by [I.author] ([I.rank]) on [I.timestamp] " if(I.author == usr.key) dat += "Remove" dat += "

" diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index f7eec938d0e..270a9bfe08c 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -107,52 +107,32 @@ if (holder.level >= -1) verbs += /client/proc/investigate_show verbs += /client/proc/cmd_admin_say + verbs += /client/proc/cmd_mod_say verbs += /client/proc/cmd_admin_gib_self verbs += /client/proc/deadmin_self else if (holder.level == -3) // Retired Admin verbs += /client/proc/cmd_admin_say + verbs += /client/proc/cmd_mod_say return else return //Moderator if (holder.level >= 0) - verbs += /obj/admins/proc/announce - verbs += /obj/admins/proc/startnow - verbs += /obj/admins/proc/toggleAI //Toggle the AI - verbs += /obj/admins/proc/toggleenter //Toggle enterting -// verbs += /obj/admins/proc/toggleguests //Toggle guests entering - verbs += /obj/admins/proc/toggleooc //toggle ooc - verbs += /obj/admins/proc/toggleoocdead //toggle ooc for dead/unc - verbs += /obj/admins/proc/voteres //toggle votes - verbs += /obj/admins/proc/vmode - verbs += /obj/admins/proc/votekill - verbs += /obj/admins/proc/show_player_panel - verbs += /client/proc/deadchat //toggles deadchat - //verbs += /client/proc/cmd_admin_mute --was never used (according to stats trackind) - use show player panel --erro verbs += /client/proc/cmd_admin_pm_context verbs += /client/proc/cmd_admin_pm_panel - verbs += /client/proc/cmd_admin_subtle_message - //verbs += /client/proc/warn - was never used - verbs += /client/proc/dsay verbs += /client/proc/admin_play verbs += /client/proc/admin_observe - verbs += /client/proc/game_panel -// verbs += /client/proc/player_panel - verbs += /client/proc/player_panel_new - verbs += /client/proc/unban_panel - verbs += /client/proc/jobbans - - verbs += /client/proc/voting verbs += /client/proc/hide_verbs - verbs += /client/proc/general_report - //verbs += /client/proc/air_report verbs += /client/proc/deadmin_self - //verbs += /client/proc/cmd_admin_prison --Merged with player panel - //verbs += /obj/admins/proc/unprison --Merged with player panel - verbs += /client/proc/playernotes verbs += /obj/admins/proc/show_skills else return + if(holder.level == 0) //Moderators don't get asay, only msay + verbs -= /client/proc/cmd_admin_say + verbs -= /client/proc/investigate_show + verbs -= /client/proc/cmd_admin_gib_self + verbs += /client/proc/mod_panel + //Temporary Admin if (holder.level >= 1) verbs += /obj/admins/proc/delay //game start delay @@ -165,6 +145,35 @@ verbs += /client/proc/deadmin_self //verbs += /client/proc/cmd_admin_attack_log --Merged with view variables verbs += /client/proc/cmd_admin_change_custom_event + // + //MOVED FROM MODERATOR + // + verbs += /client/proc/cmd_admin_subtle_message + verbs += /obj/admins/proc/announce + verbs += /obj/admins/proc/startnow + verbs += /obj/admins/proc/toggleAI //Toggle the AI + verbs += /obj/admins/proc/toggleenter //Toggle enterting +// verbs += /obj/admins/proc/toggleguests //Toggle guests entering + verbs += /obj/admins/proc/toggleooc //toggle ooc + verbs += /obj/admins/proc/toggleoocdead //toggle ooc for dead/unc + verbs += /obj/admins/proc/voteres //toggle votes + verbs += /obj/admins/proc/vmode + verbs += /obj/admins/proc/votekill + verbs += /client/proc/deadchat //toggles deadchat + //verbs += /client/proc/cmd_admin_mute --was never used (according to stats trackind) - use show player panel --erro + verbs += /client/proc/dsay + verbs += /client/proc/game_panel + verbs += /obj/admins/proc/show_player_panel +// verbs += /client/proc/player_panel + verbs += /client/proc/player_panel_new + verbs += /client/proc/unban_panel + verbs += /client/proc/jobbans + verbs += /client/proc/voting + verbs += /client/proc/general_report + //verbs += /client/proc/air_report + //verbs += /client/proc/cmd_admin_prison --Merged with player panel + //verbs += /obj/admins/proc/unprison --Merged with player panel + verbs += /client/proc/playernotes else return //Admin Candidate @@ -393,8 +402,8 @@ verbs -= /client/proc/cmd_admin_pm_context verbs -= /client/proc/cmd_admin_pm_panel verbs -= /client/proc/cmd_admin_say + verbs -= /client/proc/cmd_mod_say verbs -= /client/proc/cmd_admin_subtle_message - //verbs -= /client/proc/warn verbs -= /client/proc/dsay verbs -= /client/proc/admin_play verbs -= /client/proc/admin_observe @@ -418,6 +427,7 @@ verbs -= /client/proc/toggle_hear_radio verbs -= /client/proc/tension_report verbs -= /client/proc/player_panel_new + verbs -= /client/proc/mod_panel verbs -= /client/proc/toggle_gravity_on verbs -= /client/proc/toggle_gravity_off verbs -= /client/proc/toggle_random_events @@ -428,7 +438,7 @@ verbs -= /client/proc/getserverlog verbs -= /client/proc/cinematic //show a cinematic sequence verbs -= /client/proc/admin_memo - verbs -= /client/proc/investigate_show + verbs -= /client/proc/investigate_show verbs -= /client/proc/cmd_admin_change_custom_event verbs -= /client/proc/admin_invis verbs -= /client/proc/callprocgen @@ -538,6 +548,14 @@ // feedback_add_details("admin_verb","PPN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! return +/client/proc/mod_panel() + set name = "Moderator Panel" + set category = "Admin" + if(holder) + holder.mod_panel() +// feedback_add_details("admin_verb","MP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + /client/proc/check_antagonists() set name = "Check Antagonists" set category = "Admin" @@ -836,6 +854,7 @@ verbs += /client/proc/deadchat //toggles deadchat verbs += /obj/admins/proc/toggleooc //toggle ooc verbs += /client/proc/cmd_admin_say//asay + verbs += /client/proc/cmd_mod_say verbs += /client/proc/toggleadminhelpsound // feedback_add_details("admin_verb","HMV") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! return @@ -856,6 +875,7 @@ verbs += /client/proc/deadchat //toggles deadchat verbs += /obj/admins/proc/toggleooc //toggle ooc verbs += /client/proc/cmd_admin_say//asay + verbs += /client/proc/cmd_mod_say//asay // feedback_add_details("admin_verb","TAVVH") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! return diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm index 2c87cf9410a..3cfff463db4 100644 --- a/code/modules/admin/player_panel.dm +++ b/code/modules/admin/player_panel.dm @@ -113,6 +113,113 @@ usr << browse(dat, "window=players;size=905x600") + +/obj/admins/proc/mod_panel()//For moderators! + if (!usr.client.holder) + return + var/dat = "Player Menu" + dat += "" + //add to this if wanting to add back in IP checking + //add if you want to know their ip to the lists below + var/list/mobs = get_sorted_mobs() + var/i = 1 + + for(var/mob/M in mobs) + if(M.ckey) + var/color = "#e6e6e6" + i++ + if(i%2 == 0) + color = "#f2f2f2" + var/real = (M.real_name == M.original_name ? "[M.name]/[M.real_name]" : "[M.original_name] (as [M.name]/[M.real_name])") + var/turf/T = get_turf(M) + var/client_key = (M.key? M.key : "No key") + dat += "" // Adds current name +/* if(isobserver(M)) + dat += "" + else if(isalien(M)) + dat += "" + else if(islarva(M)) + dat += "" + else if(istajaran(M)) + dat += "" + else if(ishuman(M)) + dat += "" + else if(ismetroid(M)) + dat += "" + else if(ismonkey(M)) + dat += "" + else if(isAI(M)) + dat += "" + else if(ispAI(M)) + dat += "" + else if(isrobot(M)) + dat += "" + else if(isanimal(M)) + dat += "" + else if(iscorgi(M)) + dat += "" + else if(istype(M,/mob/new_player)) + dat += "" + else + dat += "\black" + + if(M.mind && M.mind.assigned_role && istype(M, /mob/living/carbon/human)) // Adds a column to Player Panel that shows their current job. + var/mob/living/carbon/human/H = M + + if (H.wear_id) + var/obj/item/weapon/card/id/id + + if(istype(H.wear_id, /obj/item/device/pda)) + var/obj/item/device/pda/PDA = H.wear_id + if(!isnull(PDA.id)) // The PDA may contain no ID + id = PDA.id // The ID is contained inside the PDA + + else + id = H.wear_id // The ID was on the ID slot + + if(!id) // Happens when there's no ID in the PDA located on the wear_id slot + dat += "" + + else if(isnull(id.assignment)) // Preventing runtime errors blocking the player panel + if(istype(id, /obj/item/weapon/card/id/syndicate)) + dat += "" + else + usr << "ERROR: Inform the coders that an [id.name] was checked for its assignment variable, and it was null." + dat += "" + + else + if(M.mind.assigned_role == id.assignment) // Polymorph + dat += "" + + else + dat += "" + + else + dat += "" +*/ + dat += {" + "} + + dat += {" + "} + if(1) + dat += {""} + if(2) + dat += {""} +*/ + dat += "
Name/Real NameIP/CIDInfoOptions
IP:Type Assigned Job Traitor?(IP: [M.lastKnownIP])
[real]
[M.client ? M.client : "No client ([client_key])"] at ([T.x], [T.y], [T.z])
GhostAlienAlien larvaTajaran[M.job]MetroidMonkeyAIpAICyborgAnimalCorgiNew Player\red ERROR[M.mind.assigned_role] (No ID)AntagonistERROR[M.mind.assigned_role][M.mind.assigned_role] ([id.assignment])" + + else + dat += "[M.mind.assigned_role] (No ID)No Assigned RoleIP: [M.client.address]
+ CID: [M.client.computer_id]
[player_has_info(M.ckey) ? "Info" : "N/A"] PM + "} + +/* switch(is_special_character(M)) + if(0) + dat += {"Traitor?Traitor?Traitor?
" + + usr << browse(dat, "window=players;size=600x600") + //The old one /obj/admins/proc/player_panel_old() if (!usr.client.holder) diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm index 94bd6098f51..2c0f9eba325 100644 --- a/code/modules/admin/verbs/adminpm.dm +++ b/code/modules/admin/verbs/adminpm.dm @@ -61,8 +61,12 @@ if(C.holder) if(holder) //both are admins - C << "Admin PM from-[key_name(src, C, 1)]: [msg]" - src << "Admin PM to-[key_name(C, src, 1)]: [msg]" + if(holder.rank == "Moderator") //If moderator + C << "Mod PM from-[key_name(src, C, 1)]: [msg]" + src << "Mod PM to-[key_name(C, src, 1)]: [msg]" + else + C << "Admin PM from-[key_name(src, C, 1)]: [msg]" + src << "Admin PM to-[key_name(C, src, 1)]: [msg]" else //recipient is an admin but sender is not C << "Reply PM from-[key_name(src, C, 1)]: [msg]" @@ -75,10 +79,14 @@ else if(holder) //sender is an admin but recipient is not. Do BIG RED TEXT // C << "-- Administrator private message --" - C << "Admin PM from-[key_name(src, C, 0)]: [msg]" - C << "Click on the administrator's name to reply." - src << "Admin PM to-[key_name(C, src, 1)]: [msg]" - + if(holder.rank == "Moderator") + C << "Mod PM from-[key_name(src, C, 0)]: [msg]" + C << "Click on the moderators's name to reply." + src << "Mod PM to-[key_name(C, src, 1)]: [msg]" + else + C << "Admin PM from-[key_name(src, C, 0)]: [msg]" + C << "Click on the administrator's name to reply." + src << "Admin PM to-[key_name(C, src, 1)]: [msg]" //always play non-admin recipients the adminhelp sound C << 'adminhelp.ogg' diff --git a/code/modules/admin/verbs/adminsay.dm b/code/modules/admin/verbs/adminsay.dm index 92165d06a2f..07ccbbbddd3 100644 --- a/code/modules/admin/verbs/adminsay.dm +++ b/code/modules/admin/verbs/adminsay.dm @@ -20,9 +20,39 @@ //feedback_add_details("admin_verb","M") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! for (var/mob/M in world) - if (M.client && M.client.holder) + if (M.client && M.client.holder && M.client.holder.level != 0) //Moderators cannot see it. if (src.holder.rank == "Admin Observer") M << "ADMIN: [key_name(usr, M)]: [msg]" else M << "ADMIN: [key_name(usr, M)] (JMP): [msg]" +/client/proc/cmd_mod_say(msg as text) + set category = "Special Verbs" + set name = "Msay" + set hidden = 1 + + if (!src.holder) + src << "Only administrators may use this command." + return + + if (src.muted || src.muted_complete) + src << "You are muted." + return + + msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN) + log_admin("MOD: [key_name(src)] : [msg]") + + + if (!msg) + return + //feedback_add_details("admin_verb","M") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + for (var/mob/M in world) + if (M.client && M.client.holder) + if (src.holder.rank == "Admin Observer") + M << "MOD: [key_name(usr, M)]: [msg]" + else if (src.holder.rank == "Moderator") + M << "MOD: [key_name(usr, M)] (JMP): [msg]" + else + M << "MOD: [key_name(usr, M)] (JMP): [msg]" + diff --git a/code/modules/admin/verbs/pray.dm b/code/modules/admin/verbs/pray.dm index 47e61313879..a4eb0ed0582 100644 --- a/code/modules/admin/verbs/pray.dm +++ b/code/modules/admin/verbs/pray.dm @@ -28,7 +28,7 @@ // log_admin("[key_name(Sender)] sent a message to Centcomm! The message was [msg]") // Handled somewhere else for (var/mob/M in world) - if (M.client && M.client.holder) + if (M.client && M.client.holder && M.client.holder.level != 0) M << "\blue CENTCOMM:[key_name(Sender, M)] (PP) (VV) (SM) (JMP) (CA) (BSA) (RPLY): [msg]" // /proc/Syndicate_announce(var/text , var/mob/Sender) @@ -38,6 +38,6 @@ // log_admin("[key_name(Sender)] sent a message to the Syndicate! The message was [msg]") // Handled somewhere else for (var/mob/M in world) - if (M.client && M.client.holder) + if (M.client && M.client.holder && M.client.holder.level != 0) M << "\blue SYNDICATE:[key_name(Sender, M)] (PP) (VV) (SM) (JMP) (CA) (BSA) (RPLY): [msg]" // diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index ec9484a289e..270a8448c10 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -301,7 +301,7 @@ proc/ManifestLateSpawn(var/mob/living/carbon/human/H, icon/H_icon) // Attempted fix to add late joiners to various databases -- TLE - // This is basically ripped wholesale from the normal code for adding people to the databases during a fresh round //Erth Mark + // This is basically ripped wholesale from the normal code for adding people to the databases during a fresh round if (!isnull(H.mind) && (H.mind.assigned_role != "MODE")) var/datum/data/record/G = new() var/datum/data/record/M = new() diff --git a/code/stylesheet.dm b/code/stylesheet.dm index a3401ade9fd..b5467c59337 100644 --- a/code/stylesheet.dm +++ b/code/stylesheet.dm @@ -20,6 +20,10 @@ em {font-style: normal; font-weight: bold;} .adminobserver {color: #996600; font-weight: bold;} .admin {color: #386aff; font-weight: bold;} +.mod {color: #799AFC; font-weight: bold;} +.modooc {color: #184880; font-weight: bold;} +.adminmod {color: #3E5CB5; font-weight: bold;} + .name { font-weight: bold;} .say {} From aa7da6f36ca229267eaf1c18b0cfc1ee2a4c4227 Mon Sep 17 00:00:00 2001 From: Erthilo Date: Sat, 2 Jun 2012 21:17:37 +0100 Subject: [PATCH 3/5] Fixes Mods being able to use Subtle messages. Fixes mod panel. Prevents mods from seeing stealthed admins in various places. Fixes mods seeing gibbed messages. Changed colours to be more readable, mod say is now brown. --- code/game/machinery/kitchen/gibber.dm | 2 +- code/game/verbs/ooc.dm | 2 +- code/game/verbs/who.dm | 2 +- code/modules/admin/admin.dm | 21 ++++++++++++--------- code/modules/admin/player_panel.dm | 4 ++-- code/stylesheet.dm | 4 ++-- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/code/game/machinery/kitchen/gibber.dm b/code/game/machinery/kitchen/gibber.dm index c00d181d9f7..146867ffda8 100644 --- a/code/game/machinery/kitchen/gibber.dm +++ b/code/game/machinery/kitchen/gibber.dm @@ -181,7 +181,7 @@ allmeat += newmeat for (var/mob/M in world) - if (M.client && M.client.holder && (M.client.holder.level != -3)) + if (M.client && M.client.holder && (M.client.holder.level != -3) && (M.client.holder.level != 0)) M << "\red [user.name]/[user.ckey] has gibbed [src.occupant.name]/[src.occupant.ckey]" src.occupant.death(1) src.occupant.ghostize() diff --git a/code/game/verbs/ooc.dm b/code/game/verbs/ooc.dm index 9c37e9ca1b0..d138c6ece49 100644 --- a/code/game/verbs/ooc.dm +++ b/code/game/verbs/ooc.dm @@ -37,7 +37,7 @@ log_ooc("[src.name]/[src.key] : [msg]") for (var/client/C) - if (src.client.holder && (!src.client.stealth || C.holder)) + if (src.client.holder && (!src.client.stealth || ( C.holder && C.holder.level != 0))) if (src.client.holder.rank == "Admin Observer") C << "OOC: [src.key][src.client.stealth ? "/([src.client.fakekey])" : ""]: [msg]" else if (src.client.holder.rank == "Retired Admin") diff --git a/code/game/verbs/who.dm b/code/game/verbs/who.dm index eb45379de53..26ac9307b61 100644 --- a/code/game/verbs/who.dm +++ b/code/game/verbs/who.dm @@ -30,7 +30,7 @@ for (var/mob/M in world) if(M && M.client && M.client.holder) - if(usr.client.holder) + if(usr.client.holder && (usr.client.holder.level != 0)) usr << "[M.key] is a [M.client.holder.rank][M.client.stealth ? " (as [M.client.fakekey])" : ""]" else if(!M.client.stealth && (M.client.holder.level != -3)) usr << "\t[pick(nobles)] [M.client] is a [M.client.holder.rank]" diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 919b81a4b6b..f0d7113b7d2 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -1376,16 +1376,19 @@ var/global/BSACooldown = 0 alert("You cannot perform this action. You must be of a higher administrative rank!") if (href_list["adminplayersubtlemessage"]) - var/mob/M = locate(href_list["adminplayersubtlemessage"]) - if(src && src.owner) - if(istype(src.owner,/client)) - var/client/cl = src.owner - cl.cmd_admin_subtle_message(M) - else if(ismob(src.owner)) - var/mob/MO = src.owner - if(MO.client) - var/client/cl = MO.client + if(rank in list("Admin Observer", "Temporary Admin", "Admin Candidate", "Trial Admin", "Badmin", "Game Admin", "Game Master")) + var/mob/M = locate(href_list["adminplayersubtlemessage"]) + if(src && src.owner) + if(istype(src.owner,/client)) + var/client/cl = src.owner cl.cmd_admin_subtle_message(M) + else if(ismob(src.owner)) + var/mob/MO = src.owner + if(MO.client) + var/client/cl = MO.client + cl.cmd_admin_subtle_message(M) + else + alert("You cannot perform this action. You must be of a higher administrative rank!") if (href_list["adminplayerobservejump"]) if(rank in list("Admin Observer", "Temporary Admin", "Admin Candidate", "Trial Admin", "Badmin", "Game Admin", "Game Master")) diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm index 3cfff463db4..408815774e9 100644 --- a/code/modules/admin/player_panel.dm +++ b/code/modules/admin/player_panel.dm @@ -114,7 +114,7 @@ usr << browse(dat, "window=players;size=905x600") -/obj/admins/proc/mod_panel()//For moderators! +/obj/admins/proc/mod_panel()//The new one if (!usr.client.holder) return var/dat = "Player Menu" @@ -205,7 +205,7 @@ "} dat += {"[player_has_info(M.ckey) ? "Info" : "N/A"] - PM + PM "} /* switch(is_special_character(M)) diff --git a/code/stylesheet.dm b/code/stylesheet.dm index b5467c59337..bf2272ba291 100644 --- a/code/stylesheet.dm +++ b/code/stylesheet.dm @@ -20,9 +20,9 @@ em {font-style: normal; font-weight: bold;} .adminobserver {color: #996600; font-weight: bold;} .admin {color: #386aff; font-weight: bold;} -.mod {color: #799AFC; font-weight: bold;} +.mod {color: #735638; font-weight: bold;} .modooc {color: #184880; font-weight: bold;} -.adminmod {color: #3E5CB5; font-weight: bold;} +.adminmod {color: #402A14; font-weight: bold;} .name { font-weight: bold;} From 863d39090172934977dc6a504c321784c2d7eebe Mon Sep 17 00:00:00 2001 From: Erthilo Date: Sat, 2 Jun 2012 21:25:17 +0100 Subject: [PATCH 4/5] Readds chinksy's detective hat sprites. Was a mistake with TG merging. --- icons/mob/head.dmi | Bin 109027 -> 109039 bytes icons/obj/clothing/hats.dmi | Bin 37389 -> 37532 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi index ceb1d3bd0211feca6800ac2a783e32a6a031f368..ce4098bcd85d1073dc79c2cbad7a5ccd8f2077ed 100644 GIT binary patch delta 1559 zcmV+y2I%?Y(+2O;29P5H1`ClTF@KGvi6~Mfsdczxjrw$l=V; z`#$^g^Iu=SWzS!~tX|l8u_oJQAAj4d9`;g&CfjBoo>mVHOWlO=4<9ISw$0u(tUC_x zRu7dppKY^`tB0MVj(xVx@>}fb!sYk5iYYYy-+`*AQ@I5JxO8K4?$Hf%&nBEBl#&0I67xw5Hc;Z)hwvUH|wYT@K~Ftr67?+J)?dqRTRp2C_mCbuS?5`QQ0MTm5y%+{P8M-R@BaZ>Upcs45ngN*)G1i6E;4Fy}Xi( zpV1Km_5hhc$Ok)tamju!byTk3ON|s~fmM(fF04lCvk49C1~x(24w62yPuK8#Vj4)| zx8somR+M^<*ET)%41ZKLa21AEU@y{&sX+|*Fb(hGi`VcjF4d5$8VZ}@z*^gVylZgH z!w&L92YIT4O#!9^H+;ej(j4Te4mQQU5#X-5#uLV$B%deBZwe$cn!*VqPms=&q&Gzc zWz5%j!pM{4b5!0*y+#7K0ayd74<-Hw|`m`H>|sOgg>Kk*`20P zT#YjV0&&^yOM3t@VB+GC+;hZ$4j7q=48?n2BL|F6Q$28H71CIo!oU_{s=O8;H$0QT z0wcn<$Z-W`@MjYE z@`EuzSA8%BN`L08<^T?I3df~e33GYpr<+RZx|(VwAbDsjAsoaQAX&bk!0uvQiK*we z?1Q$ppRt*hpEpW(6Q4szPJBE#ju=oxtc{rOalIo4>^dvJZnJ{ab<`vG^N1nU2=$u8 z6pgh&p*W?H_+sNB+ld7twi%pfq>?{@$ZOSJ17~T(pnuBK+a1tq7`eD$?U)UE?@wXy zGA&A~fQo3s#)W^dvGGjr2OA3{c^wCb9GoKugwSu5|3;zYREnNG;~W;GiYBpirWsEU*)f9Ma2k z-TZGDZhx?+_+09-Fn~(23ybX7dho!0tp|_XMXt6`&^wa}qQy>2-viBqpDcjbs;Lq} zN}n?cjl3$YtBVF1@)1qwcJxEnw?OCh)*zwdk?11FU6f)A)x|?Y{5~3m;vU)5z9+@S z_+zO;X{Px1zH5cF+ZXt{X9QJkn>E%Byr7V5j(^MM39_3tn_3LBdn5gyH2_DR=ps*c zu~~Ow;0cT@ICfk(PZ8d%;n$EW8XR?sc%C3$GQF@XkVNf*BI!N_RQ8ObRRkz|LV*S+ zSJuVT{t-juC^YPf;6#`4FLsS%^|!^SxR@TK7L0;EkDnHKZ({JPDiGPn7uBfXKYu@` zoqyC@gOq3H5=l^xFisXFP#T)YF;}E#ZDY!Z%|VlmWLk}@6vB;!_Tf0 z8Xh?IBey9GOQVmK7Z{dCe+zw$18q=p6&s}9I+IA58G5S`(KCttRtk|ka&bY@qPXak za!f^@)Cj&cO?9@|74TCnatBkivST;z=YKvO7e5TG6deUWRH;jLZMxVHS?g;Hq++tZ zph}YfD>j9Zd-(tJ-v&1&U~0*jS}Z1(iNN?>D3t9wG8NF@zM?U4I9Zv(m~_XLD4>xx zVKL!Txd$nq;92SS+2?Q1+4I*gtN#Ftgy|~|rK1~3{l)+Q delta 1529 zcmV(+1B8mrIn5O6{_lZwRPutxWlM2Uy;2+`RDV=#*^`+Q ze5hUMI~THTmVbJD${+8WW4>O*JL9SYGWs)&N%yPzR7rI@xpeM_PPFs#2e{+TlT`t?1Q`?1( z{nEMm#dBp_$-=3!tz_v;*;c~I=fTt#aJ(nrtlJZEsO>4N$zXD8GJh%OB)$l7(croo znDmGtmwdw;;D&E_gNe<2@eiv9&4v!yHp?HcGH6B3?5Cc$3iV($d+plz>Q31B9QN`` zKE6js42S`;fshY&0^^e1UaF{Ex0ebj&H}3-FYz@zlRy-(4a&9gpc*+S5e+2q+wjN% zD@Yy3YnhHZ2C5ji>VLv3t`}*A? zAWwC$DZmuqhEJG5nu9#m!KT>P0^BXvc*6LTVU)D8BtubiGSZf7c?%rGZc!eaYjJk zTz31?9Y742xOgPj95JBdMOKrgc&}^Zfbkis2G&j?jl?Mogbm4=m6`imPPNKPPCwdZj)?mhdCV?+MSPOK~ z2Wz2Z&KVBiAb+QDT)L4kmv?@;shqB>r$z#jhc*(zL97Lm%-uoCApwbqM{rS2}cg;-T8L@Hw!n| zQ+zJfSbrEmrPzf9C@OPJlVx&-HL@L zFtXs-ap62gc(aCIL#}9W)G6Y5f_TaF!oENfwF`=*`xH>wGm2IbpzH|+8l0@1kEi`3 zhR9K<*%!f!DdS)48prByhf#4cGe{*E1$~}=TI9Wn{Rgk9fBpHKwo`8n5}uWd5=l^# z-+!Qs5-1H4UaWWg3IymYlGVJn6pEdA#@?W&I4lo8EZ(Kx)`p*5Cp0{8>_=@=7?wsK zD=#oCjsEue8VA~-mGVqQp414wHBEK4 z*cI^8Eb<3iG_qqq@8>=p7e5S*6fK2*&|#@dc5S-Y5LxSM3#4LleL>og04p|yk$d?6 z^WOzGC17gFm|83*mWjaleJGS`Te2GHZ(q@vIGn6ZVNAMZEedF)O;}9$RPI5_CwNx+ fefIg=bN2l8%jzE?lHr~_l3h-NI0m;k1_8IDP{Zx^ diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi index 4f9661433873bfd7aac20ed6321a51117a3caf76..91032dbec0a72b66e593a9a44b709e484a884d00 100644 GIT binary patch delta 19286 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 Date: Sat, 2 Jun 2012 13:28:47 -0700 Subject: [PATCH 5/5] Detective improvements, take two. --- baystation12.dme | 2 + code/defines/obj/weapon.dm | 2 +- code/game/atom_procs.dm | 23 +- code/game/objects/devices/scanners.dm | 160 -------- code/modules/DetectiveWork/detective_work.dm | 359 +++++------------- code/modules/DetectiveWork/evidence.dm | 5 +- .../DetectiveWork/footprints_and_rag.dm | 207 ++++++++++ code/modules/DetectiveWork/scanner.dm | 172 +++++++++ 8 files changed, 499 insertions(+), 431 deletions(-) create mode 100644 code/modules/DetectiveWork/footprints_and_rag.dm create mode 100644 code/modules/DetectiveWork/scanner.dm diff --git a/baystation12.dme b/baystation12.dme index 5828ec80391..98ba31217bb 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -879,6 +879,8 @@ #include "code\modules\customitems\item_spawning.dm" #include "code\modules\DetectiveWork\detective_work.dm" #include "code\modules\DetectiveWork\evidence.dm" +#include "code\modules\DetectiveWork\footprints_and_rag.dm" +#include "code\modules\DetectiveWork\scanner.dm" #include "code\modules\flufftext\Dreaming.dm" #include "code\modules\flufftext\Hallucination.dm" #include "code\modules\flufftext\TextFilters.dm" diff --git a/code/defines/obj/weapon.dm b/code/defines/obj/weapon.dm index a36f4bed2ac..e8eba76c3d4 100644 --- a/code/defines/obj/weapon.dm +++ b/code/defines/obj/weapon.dm @@ -1017,7 +1017,7 @@ icon_state = "cone" /obj/item/weapon/paper - name = "Paper" + name = "\improper Paper" icon = 'paper.dmi' icon_state = "paper" var/info = null diff --git a/code/game/atom_procs.dm b/code/game/atom_procs.dm index 15b56014d13..55bbb859d07 100644 --- a/code/game/atom_procs.dm +++ b/code/game/atom_procs.dm @@ -104,13 +104,6 @@ if(isnull(M.key)) return if (!( src.flags ) & FPRINT) return - //Smudge up dem prints some - for(var/P in fingerprints) - var/test_print = stars(fingerprints[P], rand(85,95)) - if(stringpercent(test_print) == 32) //She's full of stars! (No actual print left) - fingerprints.Remove(P) - else - fingerprints[P] = test_print if (ishuman(M)) //Add the list if it does not exist. if(!fingerprintshidden) @@ -142,6 +135,15 @@ return 0 else if(H.gloves && !istype(H.gloves, /obj/item/clothing/gloves/latex) && !istype(H.gloves, /obj/item/clothing/gloves/fingerless)) return 0 + //Smudge up dem prints some + for(var/P in fingerprints) + if(P == md5(H.dna.uni_identity)) + continue + var/test_print = stars(fingerprints[P], rand(85,95)) + if(stringpercent(test_print) == 32) //She's full of stars! (No actual print left) + fingerprints.Remove(P) + else + fingerprints[P] = test_print //More adminstuffz if(fingerprintslast != H.key) fingerprintshidden += text("Real name: [], Key: []",H.real_name, H.key) @@ -160,6 +162,13 @@ fingerprints[full_print] = stringmerge(print, stars(full_print, (H.gloves ? rand(10,20) : rand(25,40)))) return 1 else + //Smudge up dem prints some + for(var/P in fingerprints) + var/test_print = stars(fingerprints[P], rand(85,95)) + if(stringpercent(test_print) == 32) //She's full of stars! (No actual print left) + fingerprints.Remove(P) + else + fingerprints[P] = test_print if(fingerprintslast != M.key) fingerprintshidden += text("Real name: [], Key: []",M.real_name, M.key) fingerprintslast = M.key diff --git a/code/game/objects/devices/scanners.dm b/code/game/objects/devices/scanners.dm index f1e42988ef0..13b69e2db33 100644 --- a/code/game/objects/devices/scanners.dm +++ b/code/game/objects/devices/scanners.dm @@ -60,166 +60,6 @@ MASS SPECTROMETER if(M) M.invisibility = 2 -/obj/item/device/detective_scanner - name = "Scanner" - desc = "Used to scan objects for DNA and fingerprints." - icon_state = "forensic1" - var/amount = 20.0 - var/list/stored = list() - w_class = 3.0 - item_state = "electronic" - flags = FPRINT | TABLEPASS | CONDUCT | USEDELAY - slot_flags = SLOT_BELT - - attackby(obj/item/weapon/f_card/W as obj, mob/user as mob) - ..() - if (istype(W, /obj/item/weapon/f_card)) - if (W.fingerprints) - return - if (src.amount == 20) - return - if (W.amount + src.amount > 20) - src.amount = 20 - W.amount = W.amount + src.amount - 20 - else - src.amount += W.amount - //W = null - del(W) - add_fingerprint(user) - if (W) - W.add_fingerprint(user) - return - - attack(mob/living/carbon/human/M as mob, mob/user as mob) - if (!ishuman(M)) - user << "\red [M] is not human and cannot have the fingerprints." - return 0 - if (( !( istype(M.dna, /datum/dna) ) || M.gloves) ) - user << "\blue No fingerprints found on [M]" - return 0 - else - if (src.amount < 1) - user << text("\blue Fingerprints scanned on [M]. Need more cards to print.") - else - src.amount-- - var/obj/item/weapon/f_card/F = new /obj/item/weapon/f_card( user.loc ) - F.amount = 1 - F.add_fingerprint(M) - F.icon_state = "fingerprint1" - F.name = text("FPrintC- '[M.name]'") - - user << "\blue Done printing." - user << "\blue [M]'s Fingerprints: [md5(M.dna.uni_identity)]" - if ( !M.blood_DNA || !M.blood_DNA.len ) - user << "\blue No blood found on [M]" - if(M.blood_DNA) - del(M.blood_DNA) - else - user << "\blue Blood found on [M]. Analysing..." - spawn(15) - for(var/blood in M.blood_DNA) - user << "\blue Blood type: [M.blood_DNA[blood]]\nDNA: [blood]" - return - - afterattack(atom/A as obj|turf|area, mob/user as mob) - if(!in_range(A,user)) - return - if(loc != user) - return - if(istype(A,/obj/machinery/computer/forensic_scanning)) //breaks shit. - return - if(istype(A,/obj/item/weapon/f_card)) - user << "The scanner displays on the screen: \"ERROR 43: Object on Excluded Object List.\"" - return - - add_fingerprint(user) - - - //Special case for blood splaters. - if (istype(A, /obj/effect/decal/cleanable/blood) || istype(A, /obj/effect/rune)) - if(!isnull(A.blood_DNA)) - for(var/blood in A.blood_DNA) - user << "\blue Blood type: [A.blood_DNA[blood]]\nDNA: [blood]" - return - - //General - if ((!A.fingerprints || !A.fingerprints.len) && !A.suit_fibers && !A.blood_DNA) - user.visible_message("\The [user] scans \the [A] with \a [src], the air around [user.get_gender_form("it")] humming[prob(70) ? " gently." : "."]" ,\ - "\blue Unable to locate any fingerprints, materials, fibers, or blood on [A]!",\ - "You hear a faint hum of electrical equipment.") - return 0 - - if(add_data(A)) - user << "\blue Object already in internal memory. Consolidating data..." - return - - - //PRINTS - if(!A.fingerprints || !A.fingerprints.len) - if(A.fingerprints) - del(A.fingerprints) - else - user << "\blue Isolated [A.fingerprints.len] fingerprints: Data Stored: Scan with Hi-Res Forensic Scanner to retrieve." - - //FIBERS - if(A.suit_fibers) - user << "\blue Fibers/Materials Data Stored: Scan with Hi-Res Forensic Scanner to retrieve." - - //Blood - if (A.blood_DNA) - user << "\blue Blood found on [A]. Analysing..." - spawn(15) - for(var/blood in A.blood_DNA) - user << "Blood type: \red [A.blood_DNA[blood]] \t \black DNA: \red [blood]" - if(prob(5)) - user.visible_message("\The [user] scans \the [A] with \a [src], the air around [user.get_gender_form("it")] humming[prob(70) ? " gently." : "."]" ,\ - "You finish scanning \the [A].",\ - "You hear a faint hum of electrical equipment.") - return 0 - else - user.visible_message("\The [user] scans \the [A] with \a [src], the air around [user.get_gender_form("it")] humming[prob(70) ? " gently." : "."]\n[user.get_gender_form("It")] seems to perk up slightly at the readout." ,\ - "The results of the scan pique your interest.",\ - "You hear a faint hum of electrical equipment, and someone making a thoughtful noise.") - return 0 - return - - proc/add_data(atom/A as mob|obj|turf|area) - //I love hashtables. - var/list/data_entry = stored["\ref [A]"] - if(islist(data_entry)) //Yay, it was already stored! - //Merge the fingerprints. - var/list/data_prints = data_entry[1] - for(var/print in A.fingerprints) - var/merged_print = data_prints[print] - if(!merged_print) - data_prints[print] = A.fingerprints[print] - else - data_prints[print] = stringmerge(data_prints[print],A.fingerprints[print]) - - //Now the fibers - var/list/fibers = data_entry[2] - if(!fibers) - fibers = list() - if(A.suit_fibers && A.suit_fibers.len) - for(var/j = 1, j <= A.suit_fibers.len, j++) //Fibers~~~ - if(!fibers.Find(A.suit_fibers[j])) //It isn't! Add! - fibers += A.suit_fibers[j] - var/list/blood = data_entry[3] - if(!blood) - blood = list() - if(A.blood_DNA && A.blood_DNA.len) - for(var/main_blood in A.blood_DNA) - if(!blood[main_blood]) - blood[main_blood] = A.blood_DNA[blood] - return 1 - var/list/sum_list[4] //Pack it back up! - sum_list[1] = A.fingerprints - sum_list[2] = A.suit_fibers - sum_list[3] = A.blood_DNA - sum_list[4] = "\The [A] in \the [get_area(A)]" - stored["\ref [A]"] = sum_list - return 0 - /obj/item/device/healthanalyzer name = "Health Analyzer" diff --git a/code/modules/DetectiveWork/detective_work.dm b/code/modules/DetectiveWork/detective_work.dm index 4c9b20b7281..37524d9c691 100644 --- a/code/modules/DetectiveWork/detective_work.dm +++ b/code/modules/DetectiveWork/detective_work.dm @@ -1,4 +1,4 @@ -//gloves w_uniform wear_suit shoes +//CONTAINS: Suit fibers and Detective's Scanning Computer atom/var/list/suit_fibers @@ -52,7 +52,7 @@ var/const/FINGERPRINT_COMPLETE = 6 //This is the output of the stringpercent(pri //the print must be there for it to be complete. (Prints are 32 digits) obj/machinery/computer/forensic_scanning - name = "High-Res Forensic Scanning Computer" + name = "\improper High-Res Forensic Scanning Computer" icon_state = "forensic" var obj/item/scanning @@ -136,9 +136,6 @@ obj/machinery/computer/forensic_scanning return if (allowed(M)) authenticated = 1 - //usr << "\green Access Granted" - //if(!authenticated) - //usr << "\red Access Denied" if("logout") authenticated = 0 if("clear") @@ -197,10 +194,9 @@ obj/machinery/computer/forensic_scanning if(files && files.len) temp = "Criminal Evidence Database

" temp += "Consolidated data points:
" - var/i = 1 for(var/print in files) - temp += "{Dossier [i]}
" - i++ + var/list/file = files[print] + temp += "{[file[2]]}
" temp += "
{Insert Finger Print Card (To complete a Dossier)}


" else temp = "" @@ -213,22 +209,37 @@ obj/machinery/computer/forensic_scanning if("record") //Viewing a record from the "files" database. canclear = 0 if(files) - temp = "Criminal Evidence Database

" - temp += "Consolidated data points: Dossier [files.Find(href_list["identifier"])]
" var/list/dossier = files[href_list["identifier"]] + if(href_list["ren"]) + var/new_title = copytext(sanitize(input("Rename to what?", "Dossier Editing", "Dossier [files.Find(href_list["identifier"])]") as null|text),1,MAX_MESSAGE_LEN) + if(new_title) + dossier[2] = new_title + else + usr << "Illegal or blank name." + temp = "Criminal Evidence Database

" + temp += "Consolidated data points: [dossier[2]]
" var/print_string = "Fingerprints: Print not complete!
" if(stringpercent(dossier[1]) <= FINGERPRINT_COMPLETE) print_string = "Fingerprints: (80% or higher completion reached)
[dossier[1]]
" temp += print_string for(var/object in dossier) - if(object == dossier[1]) + if(object == dossier[1] || object == dossier[2]) continue temp += "
" var/list/outputs = dossier[object] - var/list/prints_len = outputs[1] + var/list/prints = outputs[1] temp += "Object: [outputs[4]]
" temp += " Fingerprints:
" - temp += "    [prints_len.len] Unique fingerprints found.
" + temp += "    [prints.len] Unique fingerprints found.
" + var/complete_prints = 0 + for(var/print in prints) + if(stringpercent(prints[print]) <= FINGERPRINT_COMPLETE) + complete_prints++ + temp += "      [prints[print]]
" + if(complete_prints) + temp += "      And [prints.len - complete_prints] unknown unique prints.
" + else + temp += "      No prints of sufficient completeness.
" var/list/fibers = outputs[2] if(fibers && fibers.len) temp += " Fibers:
" @@ -239,6 +250,7 @@ obj/machinery/computer/forensic_scanning temp += " Blood:
" for(var/named in blood) temp += "      Type: [blood[named]], DNA: [named]
" + temp += "
{Rename this Dossier}" temp += "
{Delete this Dossier}" temp += "
{Print}" else @@ -247,24 +259,33 @@ obj/machinery/computer/forensic_scanning if("databaseprint") //Printing from the "files" database. if(files) var/obj/item/weapon/paper/P = new(loc) - P.name = "Database File (Dossier [files.Find(href_list["identifier"])])" + P.name = "\improper Database File (Dossier [files.Find(href_list["identifier"])])" P.overlays += "paper_words" P.info = "Criminal Evidence Database

" - P.info += "Consolidated data points: Dossier [href_list["identifier"]]
" var/list/dossier = files[href_list["identifier"]] + P.info += "Consolidated data points: [dossier[2]]
" var/print_string = "Fingerprints: Print not complete!
" if(stringpercent(dossier[1]) <= FINGERPRINT_COMPLETE) print_string = "Fingerprints: (80% or higher completion reached)
[dossier[1]]
" P.info += print_string for(var/object in dossier) - if(object == dossier[1]) + if(object == dossier[1] || object == dossier[2]) continue P.info += "
" var/list/outputs = dossier[object] - var/list/prints_len = outputs[1] + var/list/prints = outputs[1] P.info += "Object: [outputs[4]]
" P.info += " Fingerprints:
" - P.info += "    [prints_len.len] Unique fingerprints found.
" + P.info += "    [prints.len] Unique fingerprints found.
" + var/complete_prints = 0 + for(var/print in prints) + if(stringpercent(prints[print]) <= FINGERPRINT_COMPLETE) + complete_prints++ + P.info += "      [prints[print]]
" + if(complete_prints) + P.info += "      And [prints.len - complete_prints] unknown unique prints.
" + else + P.info += "      No prints of sufficient completeness.
" var/list/fibers = outputs[2] if(fibers && fibers.len) P.info += " Fibers:
" @@ -283,11 +304,24 @@ obj/machinery/computer/forensic_scanning temp = "Auxiliary Evidence Database

" var/list/outputs = misc[href_list["identifier"]] temp += "Consolidated data points: [outputs[3]]
" + var/list/prints = outputs[4] + if(prints) + temp += " Fingerprints:
" + temp += "    [prints.len] Unique fingerprints found.
" + var/complete_prints = 0 + for(var/print in prints) + if(stringpercent(prints[print]) <= FINGERPRINT_COMPLETE) + complete_prints++ + temp += "      [prints[print]]
" + if(complete_prints) + temp += "      And [prints.len - complete_prints] unknown unique prints.
" + else + temp += "      No prints of sufficient completeness.
" var/list/fibers = outputs[1] if(fibers && fibers.len) temp += " Fibers:
" - for(var/j = 1, j <= fibers.len, j++) - temp += "      [fibers[j]]
" + for(var/fiber in fibers) + temp += "      [fiber]
" var/list/blood = outputs[2] if(blood && blood.len) temp += " Blood:
" @@ -302,15 +336,28 @@ obj/machinery/computer/forensic_scanning if(misc) var/obj/item/weapon/paper/P = new(loc) var/list/outputs = misc[href_list["identifier"]] - P.name = "Auxiliary Database File ([outputs[3]])" + P.name = "\improper Auxiliary Database File ([outputs[3]])" P.overlays += "paper_words" P.info = "Auxiliary Evidence Database

" P.info += "Consolidated data points: [outputs[3]]
" + var/list/prints = outputs[4] + if(prints) + P.info += " Fingerprints:
" + P.info += "    [prints.len] Unique fingerprints found.
" + var/complete_prints = 0 + for(var/print in prints) + if(stringpercent(prints[print]) <= FINGERPRINT_COMPLETE) + complete_prints++ + P.info += "      [prints[print]]
" + if(complete_prints) + P.info += "      And [prints.len - complete_prints] unknown unique prints.
" + else + P.info += "      No prints of sufficient completeness.
" var/list/fibers = outputs[1] if(fibers && fibers.len) P.info += " Fibers:
" - for(var/j = 1, j <= fibers.len, j++) - P.info += "      [fibers[j]]
" + for(var/fiber in fibers) + P.info += "      [fiber]
" var/list/blood = outputs[2] if(blood && blood.len) P.info += " Blood:
" @@ -361,36 +408,17 @@ obj/machinery/computer/forensic_scanning if(!scanning.fingerprints) scan_data += "No Fingerprints Found

" else - var/list/L = scanning.fingerprints - scan_data += "Isolated [L.len] Fingerprints. Loaded into database.
" + scan_data += "Isolated [scanning.fingerprints.len] Fingerprints. Loaded into database.
" add_data(scanning) if(!scanning.suit_fibers) - /*if(istype(scanning,/obj/item/device/detective_scanner)) - var/obj/item/device/detective_scanner/scanner = scanning - if(scanner.stored_name) - scan_data += "Fibers/Materials Data - [scanner.stored_name]:
" - for(var/data in scanner.stored_fibers) - scan_data += "- [data]
" - else - scan_data += "No Fibers/Materials Data
" - else*/ scan_data += "No Fibers/Materials Located
" else - /*if(istype(scanning,/obj/item/device/detective_scanner)) - var/obj/item/device/detective_scanner/scanner = scanning - if(scanner.stored_name) - scan_data += "Fibers/Materials Data - [scanner.stored_name]:
" - for(var/data in scanner.stored_fibers) - scan_data += "- [data]
" - else - scan_data += "No Fibers/Materials Data
"*/ - scan_data += "Fibers/Materials Found:
" for(var/data in scanning.suit_fibers) scan_data += "- [data]
" if(istype(scanning,/obj/item/device/detective_scanner) || (istype(scanning, /obj/item/device/pda) && scanning:cartridge && scanning:cartridge.access_security)) - scan_data += "
Data transfered from Scanner to Database.
" + scan_data += "
Data transfered from \the [scanning] to Database.
" add_data_scanner(scanning) else if(!scanning.fingerprints) scan_data += "
Add to Database?
" @@ -402,7 +430,7 @@ obj/machinery/computer/forensic_scanning if(scan_data) temp = "Scan Data Printed." var/obj/item/weapon/paper/P = new(loc) - P.name = "Scan Data ([scan_name])" + P.name = "\improper Scan Data ([scan_name])" P.info = "[scan_data]" P.overlays += "paper_words" else @@ -416,6 +444,14 @@ obj/machinery/computer/forensic_scanning add_data(scanning) else temp = "Data Transfer Failed: No Object." + if("rename") + if(!files || !files[href_list["identifier"]]) + temp = "ERROR: Record/Database not found!" + else + var/new_title = copytext(sanitize(input("Rename to what?", "Dossier Editing", "Dossier [files.Find(href_list["identifier"])]") as null|text),1,MAX_MESSAGE_LEN) + if(new_title) + var/list/file = files[href_list["identifier"]] + file[2] = new_title updateUsrDialog() ex_act() @@ -439,7 +475,7 @@ obj/machinery/computer/forensic_scanning proc/add_data(var/atom/scanned_atom) return add_data_master("\ref [scanned_atom]", scanned_atom.fingerprints,\ - scanned_atom.suit_fibers, scanned_atom.blood_DNA, scanned_atom.name) + scanned_atom.suit_fibers, scanned_atom.blood_DNA, "[scanned_atom.name] (Direct Scan)") @@ -458,9 +494,9 @@ obj/machinery/computer/forensic_scanning if(!fibers) fibers = list() if(atom_suit_fibers) - for(var/j = 1, j <= atom_suit_fibers.len, j++) //Fibers~~~ - if(!fibers.Find(atom_suit_fibers[j])) //It isn't! Add! - fibers += atom_suit_fibers[j] + for(var/fiber in atom_suit_fibers) //Fibers~~~ + if(!fibers.Find(fiber)) //It isn't! Add! + fibers += fiber var/list/blood = data_entry[2] if(!blood) blood = list() @@ -468,11 +504,19 @@ obj/machinery/computer/forensic_scanning for(var/main_blood in atom_blood_DNA) if(!blood[main_blood]) blood[main_blood] = atom_blood_DNA[blood] + var/list/prints = data_entry[4] + if(!prints && atom_fingerprints) + prints = list() + if(atom_fingerprints) + for(var/print in atom_fingerprints) + if(!prints[print]) + prints[print] = atom_fingerprints[print] else - var/list/templist[3] + var/list/templist[4] templist[1] = atom_suit_fibers templist[2] = atom_blood_DNA templist[3] = atom_name + templist[4] = atom_fingerprints misc[atom_reference] = templist //Store it! //Has prints. if(atom_fingerprints) @@ -500,9 +544,9 @@ obj/machinery/computer/forensic_scanning if(!fibers) fibers = list() if(atom_suit_fibers) - for(var/j = 1, j < atom_suit_fibers.len, j++) //Fibers~~~ - if(!fibers.Find(atom_suit_fibers[j])) //It isn't! Add! - fibers += atom_suit_fibers[j] + for(var/fiber in atom_suit_fibers) //Fibers~~~ + if(!fibers.Find(fiber)) //It isn't! Add! + fibers += fiber //Blood. var/list/blood = internal_atom[3] if(!blood) @@ -528,8 +572,9 @@ obj/machinery/computer/forensic_scanning data_point[2] = atom_suit_fibers data_point[3] = atom_blood_DNA data_point[4] = atom_name - var/list/new_file[1] + var/list/new_file[2] new_file[1] = atom_fingerprints[main_print] + new_file[2] = "Dossier [files.len + 1]" new_file[atom_reference] = data_point files[main_print] = new_file return 1 @@ -580,209 +625,3 @@ obj/machinery/computer/forensic_scanning detective icon_state = "old" name = "PowerScan Mk.I" - -obj/item/clothing/shoes/var - track_blood = 0 - mob/living/carbon/human/track_blood_mob - track_blood_type -mob/var - bloody_hands = 0 - mob/living/carbon/human/bloody_hands_mob - track_blood - mob/living/carbon/human/track_blood_mob - track_blood_type -obj/item/clothing/gloves/var - transfer_blood = 0 - mob/living/carbon/human/bloody_hands_mob - - -obj/effect/decal/cleanable/var - track_amt = 3 - mob/blood_owner - -turf/Exited(mob/living/carbon/human/M) - if(istype(M,/mob/living) && !istype(M,/mob/living/carbon/metroid)) - if(!istype(src, /turf/space)) // Bloody tracks code starts here - var/dofoot = 1 - if(istype(M,/mob/living/simple_animal)) - if(!(istype(M,/mob/living/simple_animal/cat) || istype(M,/mob/living/simple_animal/corgi) || istype(M,/mob/living/simple_animal/constructwraith))) - dofoot = 0 - - if(dofoot) - - if(!istype(src, /turf/space)) // Bloody tracks code starts here - if(M.track_blood > 0) - M.track_blood-- - src.add_bloody_footprints(M.track_blood_mob,1,M.dir,get_tracks(M),M.track_blood_type) - else if(istype(M,/mob/living/carbon/human)) - if(M.shoes && istype(M.shoes,/obj/item/clothing/shoes)) - var/obj/item/clothing/shoes/S = M.shoes - if(S.track_blood > 0) - S.track_blood-- - src.add_bloody_footprints(S.track_blood_mob,1,M.dir,S.name,S.track_blood_type) // And bloody tracks end here - . = ..() -turf/Entered(mob/living/carbon/human/M) - if(istype(M,/mob/living) && !istype(M,/mob/living/carbon/metroid)) - var/dofoot = 1 - if(istype(M,/mob/living/simple_animal)) - if(!(istype(M,/mob/living/simple_animal/cat) || istype(M,/mob/living/simple_animal/corgi) || istype(M,/mob/living/simple_animal/constructwraith))) - dofoot = 0 - - if(dofoot) - - if(M.track_blood > 0) - M.track_blood-- - src.add_bloody_footprints(M.track_blood_mob,0,M.dir,get_tracks(M),M.track_blood_type) - else if(istype(M,/mob/living/carbon/human)) - if(M.shoes && istype(M.shoes,/obj/item/clothing/shoes) && !istype(src,/turf/space)) - var/obj/item/clothing/shoes/S = M.shoes - if(S.track_blood > 0) - S.track_blood-- - src.add_bloody_footprints(S.track_blood_mob,0,M.dir,S.name,S.track_blood_type) - - - for(var/obj/effect/decal/cleanable/B in src) - if(B:track_amt <= 0) continue - if(B.type != /obj/effect/decal/cleanable/blood/tracks) - if(istype(B, /obj/effect/decal/cleanable/xenoblood) || istype(B, /obj/effect/decal/cleanable/blood) || istype(B, /obj/effect/decal/cleanable/oil) || istype(B, /obj/effect/decal/cleanable/robot_debris)) - - var/track_type = "blood" - if(istype(B, /obj/effect/decal/cleanable/xenoblood)) - track_type = "xeno" - else if(istype(B, /obj/effect/decal/cleanable/oil) || istype(B, /obj/effect/decal/cleanable/robot_debris)) - track_type = "oil" - - if(istype(M,/mob/living/carbon/human)) - if(M.shoes && istype(M.shoes,/obj/item/clothing/shoes)) - var/obj/item/clothing/shoes/S = M.shoes - S.add_blood(B.blood_owner) - S.track_blood_mob = B.blood_owner - S.track_blood = max(S.track_blood,8) - S.track_blood_type = track_type - else - M.add_blood(B.blood_owner) - M.track_blood_mob = B.blood_owner - M.track_blood = max(M.track_blood,rand(4,8)) - M.track_blood_type = track_type - B.track_amt-- - break - . = ..() - -turf/proc/add_bloody_footprints(mob/living/carbon/human/M,leaving,d,info,bloodcolor) - for(var/obj/effect/decal/cleanable/blood/tracks/T in src) - if(T.dir == d && findtext(T.icon_state, bloodcolor)) - if((leaving && T.icon_state == "steps2") || (!leaving && T.icon_state == "steps1")) - T.desc = "These bloody footprints appear to have been made by [info]." - if(!T.blood_DNA) - T.blood_DNA = list() - if(istype(M,/mob/living/carbon/human)) - T.blood_DNA[M.dna.unique_enzymes] = M.dna.b_type - else if(istype(M,/mob/living/carbon/alien)) - T.blood_DNA["UNKNOWN DNA"] = "X*" - else if(istype(M,/mob/living/carbon/monkey)) - T.blood_DNA["Non-human DNA"] = "A+" - return - var/obj/effect/decal/cleanable/blood/tracks/this = new(src) - this.icon = 'footprints.dmi' - - var/preiconstate = "" - - if(info == "animal paws") - preiconstate = "paw" - else if(info == "alien claws") - preiconstate = "claw" - else if(info == "small alien feet") - preiconstate = "paw" - - if(leaving) - this.icon_state = "[bloodcolor][preiconstate]2" - else - this.icon_state = "[bloodcolor][preiconstate]1" - this.dir = d - - if(bloodcolor == "blood") - this.desc = "These bloody footprints appear to have been made by [info]." - else if(bloodcolor == "xeno") - this.desc = "These acidic bloody footprints appear to have been made by [info]." - else if(bloodcolor == "oil") - this.name = "oil" - this.desc = "These oil footprints appear to have been made by [info]." - - if(istype(M,/mob/living/carbon/human)) - if(!this.blood_DNA) - this.blood_DNA = list() - this.blood_DNA[M.dna.unique_enzymes] = M.dna.b_type - -proc/get_tracks(mob/M) - if(istype(M,/mob/living)) - if(istype(M,/mob/living/carbon/human)) - . = "human feet" - else if(istype(M,/mob/living/carbon/monkey) || istype(M,/mob/living/simple_animal/cat) || istype(M,/mob/living/simple_animal/corgi) || istype(M,/mob/living/simple_animal/crab)) - . = "animal paws" - else if(istype(M,/mob/living/silicon/robot)) - . = "robot feet" - else if(istype(M,/mob/living/carbon/alien/humanoid)) - . = "alien claws" - else if(istype(M,/mob/living/carbon/alien/larva)) - . = "small alien feet" - else - . = "an unknown creature" - - -proc/blood_incompatible(donor,receiver) - var - donor_antigen = copytext(donor,1,lentext(donor)) - receiver_antigen = copytext(receiver,1,lentext(receiver)) - donor_rh = findtext("+",donor) - receiver_rh = findtext("+",receiver) - if(donor_rh && !receiver_rh) return 1 - switch(receiver_antigen) - if("A") - if(donor_antigen != "A" && donor_antigen != "O") return 1 - if("B") - if(donor_antigen != "B" && donor_antigen != "O") return 1 - if("O") - if(donor_antigen != "O") return 1 - //AB is a universal receiver. - return 0 - -/obj/item/weapon/rag - New() // So I don't have to grab maplock - spawn(1) - new/obj/item/weapon/reagent_containers/glass/rag(loc) - del src - -/obj/item/weapon/reagent_containers/glass/rag - name = "damp rag" - desc = "For cleaning up messes, you suppose." - w_class = 1 - icon = 'toy.dmi' - icon_state = "rag" - amount_per_transfer_from_this = 5 - possible_transfer_amounts = list(5) - volume = 5 - can_be_placed_into = null - - attack(atom/target as obj|turf|area, mob/user as mob , flag) - if(ismob(target) && target.reagents && reagents.total_volume) - user.visible_message("\red \The [target] has been smothered with \the [src] by \the [user]!", "\red You smother \the [target] with \the [src]!", "You hear some struggling and muffled cries of surprise") - src.reagents.reaction(target, TOUCH) - spawn(5) src.reagents.clear_reagents() - return - else - ..() - - afterattack(atom/A as obj|turf|area, mob/user as mob) - if(istype(A) && src in user) - user.visible_message("[user] starts to wipe down [A] with [src]!") - if(do_after(user,30)) - user.visible_message("[user] finishes wiping off the [A]!") - A.clean_blood() - return - - examine() - if (!usr) - return - usr << "That's \a [src]." - usr << desc - return diff --git a/code/modules/DetectiveWork/evidence.dm b/code/modules/DetectiveWork/evidence.dm index a35a4982a2d..8853efb2be6 100644 --- a/code/modules/DetectiveWork/evidence.dm +++ b/code/modules/DetectiveWork/evidence.dm @@ -1,6 +1,5 @@ -//CONTAINS: -//Evidence bags and stuff -/////////// +//CONTAINS: Evidence bags + /obj/item/weapon/evidencebag name = "evidence bag" desc = "An empty evidence bag." diff --git a/code/modules/DetectiveWork/footprints_and_rag.dm b/code/modules/DetectiveWork/footprints_and_rag.dm new file mode 100644 index 00000000000..83e49347897 --- /dev/null +++ b/code/modules/DetectiveWork/footprints_and_rag.dm @@ -0,0 +1,207 @@ +//CONTAINS: Bloody footprints code, code for managing blood incompatability, and a rag to wipe away evidence. + +obj/item/clothing/shoes/var + track_blood = 0 + mob/living/carbon/human/track_blood_mob + track_blood_type +mob/var + bloody_hands = 0 + mob/living/carbon/human/bloody_hands_mob + track_blood + mob/living/carbon/human/track_blood_mob + track_blood_type +obj/item/clothing/gloves/var + transfer_blood = 0 + mob/living/carbon/human/bloody_hands_mob + + +obj/effect/decal/cleanable/var + track_amt = 3 + mob/blood_owner + +turf/Exited(mob/living/carbon/human/M) + if(istype(M,/mob/living) && !istype(M,/mob/living/carbon/metroid)) + if(!istype(src, /turf/space)) // Bloody tracks code starts here + var/dofoot = 1 + if(istype(M,/mob/living/simple_animal)) + if(!(istype(M,/mob/living/simple_animal/cat) || istype(M,/mob/living/simple_animal/corgi) || istype(M,/mob/living/simple_animal/constructwraith))) + dofoot = 0 + + if(dofoot) + + if(!istype(src, /turf/space)) // Bloody tracks code starts here + if(M.track_blood > 0) + M.track_blood-- + src.add_bloody_footprints(M.track_blood_mob,1,M.dir,get_tracks(M),M.track_blood_type) + else if(istype(M,/mob/living/carbon/human)) + if(M.shoes && istype(M.shoes,/obj/item/clothing/shoes)) + var/obj/item/clothing/shoes/S = M.shoes + if(S.track_blood > 0) + S.track_blood-- + src.add_bloody_footprints(S.track_blood_mob,1,M.dir,S.name,S.track_blood_type) // And bloody tracks end here + . = ..() +turf/Entered(mob/living/carbon/human/M) + if(istype(M,/mob/living) && !istype(M,/mob/living/carbon/metroid)) + var/dofoot = 1 + if(istype(M,/mob/living/simple_animal)) + if(!(istype(M,/mob/living/simple_animal/cat) || istype(M,/mob/living/simple_animal/corgi) || istype(M,/mob/living/simple_animal/constructwraith))) + dofoot = 0 + + if(dofoot) + + if(M.track_blood > 0) + M.track_blood-- + src.add_bloody_footprints(M.track_blood_mob,0,M.dir,get_tracks(M),M.track_blood_type) + else if(istype(M,/mob/living/carbon/human)) + if(M.shoes && istype(M.shoes,/obj/item/clothing/shoes) && !istype(src,/turf/space)) + var/obj/item/clothing/shoes/S = M.shoes + if(S.track_blood > 0) + S.track_blood-- + src.add_bloody_footprints(S.track_blood_mob,0,M.dir,S.name,S.track_blood_type) + + + for(var/obj/effect/decal/cleanable/B in src) + if(B:track_amt <= 0) continue + if(B.type != /obj/effect/decal/cleanable/blood/tracks) + if(istype(B, /obj/effect/decal/cleanable/xenoblood) || istype(B, /obj/effect/decal/cleanable/blood) || istype(B, /obj/effect/decal/cleanable/oil) || istype(B, /obj/effect/decal/cleanable/robot_debris)) + + var/track_type = "blood" + if(istype(B, /obj/effect/decal/cleanable/xenoblood)) + track_type = "xeno" + else if(istype(B, /obj/effect/decal/cleanable/oil) || istype(B, /obj/effect/decal/cleanable/robot_debris)) + track_type = "oil" + + if(istype(M,/mob/living/carbon/human)) + if(M.shoes && istype(M.shoes,/obj/item/clothing/shoes)) + var/obj/item/clothing/shoes/S = M.shoes + S.add_blood(B.blood_owner) + S.track_blood_mob = B.blood_owner + S.track_blood = max(S.track_blood,8) + S.track_blood_type = track_type + else + M.add_blood(B.blood_owner) + M.track_blood_mob = B.blood_owner + M.track_blood = max(M.track_blood,rand(4,8)) + M.track_blood_type = track_type + B.track_amt-- + break + . = ..() + +turf/proc/add_bloody_footprints(mob/living/carbon/human/M,leaving,d,info,bloodcolor) + for(var/obj/effect/decal/cleanable/blood/tracks/T in src) + if(T.dir == d && findtext(T.icon_state, bloodcolor)) + if((leaving && T.icon_state == "steps2") || (!leaving && T.icon_state == "steps1")) + T.desc = "These bloody footprints appear to have been made by [info]." + if(!T.blood_DNA) + T.blood_DNA = list() + if(istype(M,/mob/living/carbon/human)) + T.blood_DNA[M.dna.unique_enzymes] = M.dna.b_type + else if(istype(M,/mob/living/carbon/alien)) + T.blood_DNA["UNKNOWN DNA"] = "X*" + else if(istype(M,/mob/living/carbon/monkey)) + T.blood_DNA["Non-human DNA"] = "A+" + return + var/obj/effect/decal/cleanable/blood/tracks/this = new(src) + this.icon = 'footprints.dmi' + + var/preiconstate = "" + + if(info == "animal paws") + preiconstate = "paw" + else if(info == "alien claws") + preiconstate = "claw" + else if(info == "small alien feet") + preiconstate = "paw" + + if(leaving) + this.icon_state = "[bloodcolor][preiconstate]2" + else + this.icon_state = "[bloodcolor][preiconstate]1" + this.dir = d + + if(bloodcolor == "blood") + this.desc = "These bloody footprints appear to have been made by [info]." + else if(bloodcolor == "xeno") + this.desc = "These acidic bloody footprints appear to have been made by [info]." + else if(bloodcolor == "oil") + this.name = "oil" + this.desc = "These oil footprints appear to have been made by [info]." + + if(istype(M,/mob/living/carbon/human)) + if(!this.blood_DNA) + this.blood_DNA = list() + this.blood_DNA[M.dna.unique_enzymes] = M.dna.b_type + +proc/get_tracks(mob/M) + if(istype(M,/mob/living)) + if(istype(M,/mob/living/carbon/human)) + . = "human feet" + else if(istype(M,/mob/living/carbon/monkey) || istype(M,/mob/living/simple_animal/cat) || istype(M,/mob/living/simple_animal/corgi) || istype(M,/mob/living/simple_animal/crab)) + . = "animal paws" + else if(istype(M,/mob/living/silicon/robot)) + . = "robot feet" + else if(istype(M,/mob/living/carbon/alien/humanoid)) + . = "alien claws" + else if(istype(M,/mob/living/carbon/alien/larva)) + . = "small alien feet" + else + . = "an unknown creature" + + +proc/blood_incompatible(donor,receiver) + var + donor_antigen = copytext(donor,1,lentext(donor)) + receiver_antigen = copytext(receiver,1,lentext(receiver)) + donor_rh = findtext("+",donor) + receiver_rh = findtext("+",receiver) + if(donor_rh && !receiver_rh) return 1 + switch(receiver_antigen) + if("A") + if(donor_antigen != "A" && donor_antigen != "O") return 1 + if("B") + if(donor_antigen != "B" && donor_antigen != "O") return 1 + if("O") + if(donor_antigen != "O") return 1 + //AB is a universal receiver. + return 0 + +/obj/item/weapon/rag + New() // So I don't have to grab maplock + spawn(1) + new/obj/item/weapon/reagent_containers/glass/rag(loc) + del src + +/obj/item/weapon/reagent_containers/glass/rag + name = "damp rag" + desc = "For cleaning up messes, you suppose." + w_class = 1 + icon = 'toy.dmi' + icon_state = "rag" + amount_per_transfer_from_this = 5 + possible_transfer_amounts = list(5) + volume = 5 + can_be_placed_into = null + + attack(atom/target as obj|turf|area, mob/user as mob , flag) + if(ismob(target) && target.reagents && reagents.total_volume) + user.visible_message("\red \The [target] has been smothered with \the [src] by \the [user]!", "\red You smother \the [target] with \the [src]!", "You hear some struggling and muffled cries of surprise") + src.reagents.reaction(target, TOUCH) + spawn(5) src.reagents.clear_reagents() + return + else + ..() + + afterattack(atom/A as obj|turf|area, mob/user as mob) + if(istype(A) && src in user) + user.visible_message("\The [user] starts to wipe down \a [A] with \a [src]!", "You start to wipe down \the [A].", "You hear a damp rag being rubbed against something.") + if(do_after(user,30)) + user.visible_message("\The [user] finishes wiping off \a [A]!", "You finish wiping down \the [A].") + A.clean_blood() + return + + examine() + if (!usr) + return + usr << "That's \a [src]." + usr << desc + return \ No newline at end of file diff --git a/code/modules/DetectiveWork/scanner.dm b/code/modules/DetectiveWork/scanner.dm new file mode 100644 index 00000000000..65285684049 --- /dev/null +++ b/code/modules/DetectiveWork/scanner.dm @@ -0,0 +1,172 @@ +//CONTAINS: Detective's Scanner + +/obj/item/device/detective_scanner + name = "Scanner" + desc = "Used to scan objects for DNA and fingerprints." + icon_state = "forensic1" + var/amount = 20.0 + var/list/stored = list() + w_class = 3.0 + item_state = "electronic" + flags = FPRINT | TABLEPASS | CONDUCT | USEDELAY + slot_flags = SLOT_BELT + + attackby(obj/item/weapon/f_card/W as obj, mob/user as mob) + ..() + if (istype(W, /obj/item/weapon/f_card)) + if (W.fingerprints) + return + if (src.amount == 20) + return + if (W.amount + src.amount > 20) + src.amount = 20 + W.amount = W.amount + src.amount - 20 + else + src.amount += W.amount + //W = null + del(W) + add_fingerprint(user) + if (W) + W.add_fingerprint(user) + return + + attack(mob/living/carbon/human/M as mob, mob/user as mob) + if (!ishuman(M)) + user << "\red [M] is not human and cannot have the fingerprints." + return 0 + if (( !( istype(M.dna, /datum/dna) ) || M.gloves) ) + user << "\blue No fingerprints found on [M]" + return 0 + else + if (src.amount < 1) + user << text("\blue Fingerprints scanned on [M]. Need more cards to print.") + else + src.amount-- + var/obj/item/weapon/f_card/F = new /obj/item/weapon/f_card( user.loc ) + F.amount = 1 + F.add_fingerprint(M) + F.icon_state = "fingerprint1" + F.name = text("FPrintC- '[M.name]'") + + user << "\blue Done printing." + user << "\blue [M]'s Fingerprints: [md5(M.dna.uni_identity)]" + if ( !M.blood_DNA || !M.blood_DNA.len ) + user << "\blue No blood found on [M]" + if(M.blood_DNA) + del(M.blood_DNA) + else + user << "\blue Blood found on [M]. Analysing..." + spawn(15) + for(var/blood in M.blood_DNA) + user << "\blue Blood type: [M.blood_DNA[blood]]\nDNA: [blood]" + return + + afterattack(atom/A as obj|turf|area, mob/user as mob) + if(!in_range(A,user)) + return + if(loc != user) + return + if(istype(A,/obj/machinery/computer/forensic_scanning)) //breaks shit. + return + if(istype(A,/obj/item/weapon/f_card)) + user << "The scanner displays on the screen: \"ERROR 43: Object on Excluded Object List.\"" + return + + add_fingerprint(user) + + + //Special case for blood splaters. + if (istype(A, /obj/effect/decal/cleanable/blood) || istype(A, /obj/effect/rune)) + if(!isnull(A.blood_DNA)) + for(var/blood in A.blood_DNA) + user << "\blue Blood type: [A.blood_DNA[blood]]\nDNA: [blood]" + return + + //General + if ((!A.fingerprints || !A.fingerprints.len) && !A.suit_fibers && !A.blood_DNA) + user.visible_message("\The [user] scans \the [A] with \a [src], the air around [user.get_gender_form("it")] humming[prob(70) ? " gently." : "."]" ,\ + "\blue Unable to locate any fingerprints, materials, fibers, or blood on [A]!",\ + "You hear a faint hum of electrical equipment.") + return 0 + + if(add_data(A)) + user << "\blue Object already in internal memory. Consolidating data..." + return + + + //PRINTS + if(!A.fingerprints || !A.fingerprints.len) + if(A.fingerprints) + del(A.fingerprints) + else + user << "\blue Isolated [A.fingerprints.len] fingerprints: Data Stored: Scan with Hi-Res Forensic Scanner to retrieve." + var/list/complete_prints = list() + for(var/i in A.fingerprints) + var/print = A.fingerprints[i] + if(stringpercent(print) <= FINGERPRINT_COMPLETE) + complete_prints += print + if(complete_prints.len < 1) + user << "\blue   No intact prints found" + else + user << "\blue   Found [complete_prints.len] intact prints" + for(var/i in complete_prints) + user << "\blue     [i]" + + //FIBERS + if(A.suit_fibers) + user << "\blue Fibers/Materials Data Stored: Scan with Hi-Res Forensic Scanner to retrieve." + + //Blood + if (A.blood_DNA) + user << "\blue Blood found on [A]. Analysing..." + spawn(15) + for(var/blood in A.blood_DNA) + user << "Blood type: \red [A.blood_DNA[blood]] \t \black DNA: \red [blood]" + if(prob(80) || !A.fingerprints) + user.visible_message("\The [user] scans \the [A] with \a [src], the air around [user.get_gender_form("it")] humming[prob(70) ? " gently." : "."]" ,\ + "You finish scanning \the [A].",\ + "You hear a faint hum of electrical equipment.") + return 0 + else + user.visible_message("\The [user] scans \the [A] with \a [src], the air around [user.get_gender_form("it")] humming[prob(70) ? " gently." : "."]\n[user.get_gender_form("It")] seems to perk up slightly at the readout." ,\ + "The results of the scan pique your interest.",\ + "You hear a faint hum of electrical equipment, and someone making a thoughtful noise.") + return 0 + return + + proc/add_data(atom/A as mob|obj|turf|area) + //I love hashtables. + var/list/data_entry = stored["\ref [A]"] + if(islist(data_entry)) //Yay, it was already stored! + //Merge the fingerprints. + var/list/data_prints = data_entry[1] + for(var/print in A.fingerprints) + var/merged_print = data_prints[print] + if(!merged_print) + data_prints[print] = A.fingerprints[print] + else + data_prints[print] = stringmerge(data_prints[print],A.fingerprints[print]) + + //Now the fibers + var/list/fibers = data_entry[2] + if(!fibers) + fibers = list() + if(A.suit_fibers && A.suit_fibers.len) + for(var/j = 1, j <= A.suit_fibers.len, j++) //Fibers~~~ + if(!fibers.Find(A.suit_fibers[j])) //It isn't! Add! + fibers += A.suit_fibers[j] + var/list/blood = data_entry[3] + if(!blood) + blood = list() + if(A.blood_DNA && A.blood_DNA.len) + for(var/main_blood in A.blood_DNA) + if(!blood[main_blood]) + blood[main_blood] = A.blood_DNA[blood] + return 1 + var/list/sum_list[4] //Pack it back up! + sum_list[1] = A.fingerprints + sum_list[2] = A.suit_fibers + sum_list[3] = A.blood_DNA + sum_list[4] = "\The [A] in \the [get_area(A)]" + stored["\ref [A]"] = sum_list + return 0 \ No newline at end of file